Apakah ada ekspresi reguler untuk mendeteksi ekspresi reguler yang valid?

1007

Apakah mungkin untuk mendeteksi ekspresi reguler yang valid dengan ekspresi reguler lain? Jika ya tolong beri contoh kode di bawah ini.

psytek
sumber
58
Jadi masalah Anda adalah memvalidasi regex, Anda memilih regex untuk menyelesaikannya. Saya bertanya-tanya apakah properti regex yang bertambah banyak adalah additive atau multiplicative. Rasanya seperti 4 masalah, bukan 2 :)
abesto
15
Ada banyak notasi untuk ekspresi reguler - beberapa fitur dan ejaannya umum bagi sebagian besar, beberapa dieja secara berbeda atau hanya tersedia dalam satu notasi tertentu. Sebagian besar dari notasi tersebut tidak "biasa" dalam arti tata bahasa biasa - Anda akan memerlukan parser bebas konteks untuk menangani penyatuan subekspresi yang tidak terbatas - meskipun banyak notasi "ekspresi reguler" modern memiliki ekstensi yang melampaui definisi formal asli dan mungkin memungkinkan notasi mereka sendiri diakui. Bagaimanapun, mengapa tidak bertanya pada perpustakaan regex Anda apakah setiap regex valid?
Steve314
1
@ bevacqua saya perlu memvalidasi regexp dalam skema XML. Bagaimana saya bisa melakukannya tanpa regexp lain?
zenden2k
3
Sebenarnya kompilasi / jalankan regex (pola) untuk diperiksa, di bawah mekanisme penanganan pengecualian yang dimiliki bahasa Anda. Jadi mesin regex bahasa / kompiler itu sendiri akan memeriksanya. (Ini mengasumsikan sintaksis dasar yang benar sehingga program berjalan, tetapi itu dapat dimasukkan dalam cek dengan menggunakan fasilitas bahasa Anda untuk mengevaluasi string untuk regex sebagai (mungkin secara sintaksis salah) kode, atau semacamnya.)
zdim
Ini adalah jawaban sempurna untuk pengguna python: stackoverflow.com/questions/19630994/…
gianni

Jawaban:

979
/
^                                             # start of string
(                                             # first group start
  (?:
    (?:[^?+*{}()[\]\\|]+                      # literals and ^, $
     | \\.                                    # escaped characters
     | \[ (?: \^?\\. | \^[^\\] | [^\\^] )     # character classes
          (?: [^\]\\]+ | \\. )* \]
     | \( (?:\?[:=!]|\?<[=!]|\?>)? (?1)?? \)  # parenthesis, with recursive content
     | \(\? (?:R|[+-]?\d+) \)                 # recursive matching
     )
    (?: (?:[?+*]|\{\d+(?:,\d*)?\}) [?+]? )?   # quantifiers
  | \|                                        # alternative
  )*                                          # repeat content
)                                             # end first group
$                                             # end of string
/

Ini adalah regex rekursif, dan tidak didukung oleh banyak mesin regex. Yang berbasis PCRE harus mendukungnya.

Tanpa spasi dan komentar:

/^((?:(?:[^?+*{}()[\]\\|]+|\\.|\[(?:\^?\\.|\^[^\\]|[^\\^])(?:[^\]\\]+|\\.)*\]|\((?:\?[:=!]|\?<[=!]|\?>)?(?1)??\)|\(\?(?:R|[+-]?\d+)\))(?:(?:[?+*]|\{\d+(?:,\d*)?\})[?+]?)?|\|)*)$/

.NET tidak mendukung rekursi secara langsung. ( Konstruksi (?1)dan (?R)konstruksi.) Rekursi harus dikonversi menjadi penghitungan kelompok berimbang:

^                                         # start of string
(?:
  (?: [^?+*{}()[\]\\|]+                   # literals and ^, $
   | \\.                                  # escaped characters
   | \[ (?: \^?\\. | \^[^\\] | [^\\^] )   # character classes
        (?: [^\]\\]+ | \\. )* \]
   | \( (?:\?[:=!]
         | \?<[=!]
         | \?>
         | \?<[^\W\d]\w*>
         | \?'[^\W\d]\w*'
         )?                               # opening of group
     (?<N>)                               #   increment counter
   | \)                                   # closing of group
     (?<-N>)                              #   decrement counter
   )
  (?: (?:[?+*]|\{\d+(?:,\d*)?\}) [?+]? )? # quantifiers
| \|                                      # alternative
)*                                        # repeat content
$                                         # end of string
(?(N)(?!))                                # fail if counter is non-zero.

Kompak:

^(?:(?:[^?+*{}()[\]\\|]+|\\.|\[(?:\^?\\.|\^[^\\]|[^\\^])(?:[^\]\\]+|\\.)*\]|\((?:\?[:=!]|\?<[=!]|\?>|\?<[^\W\d]\w*>|\?'[^\W\d]\w*')?(?<N>)|\)(?<-N>))(?:(?:[?+*]|\{\d+(?:,\d*)?\})[?+]?)?|\|)*$(?(N)(?!))

Dari komentar:

Apakah ini akan memvalidasi substitusi dan terjemahan?

Ini akan memvalidasi hanya bagian regex dari substitusi dan terjemahan. s/<this part>/.../

Secara teori tidak mungkin untuk mencocokkan semua tata bahasa regex yang valid dengan suatu regex.

Mungkin saja jika mesin regex mendukung rekursi, seperti PCRE, tetapi itu tidak bisa benar-benar disebut ekspresi reguler lagi.

Memang, "ekspresi reguler rekursif" bukanlah ekspresi reguler. Tapi ini ekstensi yang sering diterima untuk mesin regex ... Ironisnya, regex yang diperluas ini tidak cocok dengan regex yang diperluas.

"Secara teori, teori dan praktik adalah sama. Dalam praktik, mereka tidak sama." Hampir setiap orang yang mengetahui ekspresi reguler tahu bahwa ekspresi reguler tidak mendukung rekursi. Tetapi PCRE dan kebanyakan implementasi lainnya mendukung lebih dari sekadar ekspresi reguler dasar.

menggunakan ini dengan skrip shell dalam perintah grep, ini menunjukkan kepada saya beberapa kesalahan .. grep: Konten {} yang tidak valid. Saya membuat skrip yang dapat menangkap basis kode untuk menemukan semua file yang berisi ekspresi reguler

Pola ini mengeksploitasi ekstensi yang disebut ekspresi reguler rekursif. Ini tidak didukung oleh rasa POSIX dari regex. Anda bisa mencoba dengan sakelar -P, untuk mengaktifkan rasa regre PCRE.

Regex sendiri "bukan bahasa biasa dan karenanya tidak dapat diuraikan dengan ekspresi reguler ..."

Ini berlaku untuk ekspresi reguler klasik. Beberapa implementasi modern memungkinkan rekursi, yang membuatnya menjadi bahasa Bebas Konteks, meskipun agak bertele-tele untuk tugas ini.

Saya melihat di mana Anda cocok []()/\. dan karakter regex khusus lainnya. Di mana Anda mengizinkan karakter non-khusus? Sepertinya ini akan cocok ^(?:[\.]+)$, tetapi tidak ^abcdefg$. Itu regex yang valid.

[^?+*{}()[\]\\|]akan cocok dengan karakter tunggal mana pun, bukan bagian dari konstruksi lainnya. Ini termasuk kedua literal ( a- z), dan karakter khusus tertentu ( ^, $, .).

Markus Jarderot
sumber
10
Jawaban ini mengirim orang ke arah yang sepenuhnya salah. Mereka seharusnya tidak pernah menggunakan regEx untuk menemukan ekspresi reguler, karena itu tidak dapat bekerja dengan benar dalam semua kasus. Lihat jawaban saya ditambahkan.
vitaly-t
1
.{,1}tidak tertandingi. Ubah ke ^((?:(?:[^?+*{}()[\]\\|]+|\\.|\[(?:\^?\\.|\^[^\\]|[^\\^])(?:[^\]\\]+|\\.)*\]|\((?:\?[:=!]|\?<[=!]|\?>)?(?1)??\)|\(\?(?:R|[+-]?\d+)\))(?:(?:[?+*]|\{\d*(?:,\d*)?\})[?+]?)?|\|)*)$pertandingan. Ubah \d+ke\d*
yunzen
4
regex oleh def seharusnya tidak memiliki rekursi, setidaknya mengatakan sesuatu seperti itu dalam jawaban Anda, mesin regex Anda mungkin "terlalu kuat" dan bukan benar-benar mesin regex.
Charlie Parker
Hanya sebuah catatan Anda lupa bendera x
RedClover
Validator ini tampaknya dibuat untuk ekspresi PCRE, tetapi akan melewati banyak ERE POSIX yang tidak valid. Khususnya, mereka adalah pilih-pilih sedikit dalam rentang kelas karakter, misalnya ini berlaku di PCRE tapi tidak di ERE: [a-b-c].
Pedro Gimeno
321

Tidak sepertinya.

Evaluasilah dalam try..catchatau apa pun bahasa Anda sediakan.

Dan
sumber
228

Tidak, jika Anda benar-benar berbicara tentang ekspresi reguler dan tidak termasuk beberapa implementasi ekspresi reguler yang sebenarnya adalah tata bahasa bebas konteks.

Ada satu batasan ekspresi reguler yang membuatnya mustahil untuk menulis sebuah regex yang cocok dengan semua dan hanya regex. Anda tidak dapat mencocokkan implementasi seperti kawat gigi yang dipasangkan. Regex menggunakan banyak konstruksi seperti itu, mari kita ambil []sebagai contoh. Setiap kali ada [harus ada yang cocok ], yang cukup sederhana untuk sebuah regex "\[.*\]".

Apa yang membuatnya tidak mungkin bagi regex adalah mereka bisa disarangkan. Bagaimana Anda bisa menulis regex yang cocok dengan tanda kurung bersarang? Jawabannya adalah Anda tidak bisa tanpa regex yang sangat panjang. Anda dapat mencocokkan sejumlah kurung bersarang melalui brute force tetapi Anda tidak dapat mencocokkan set kurung bersarang panjang yang sewenang-wenang.

Kemampuan ini sering disebut sebagai penghitungan, karena Anda menghitung kedalaman sarang. Regex menurut definisi tidak memiliki kemampuan untuk menghitung.


Saya akhirnya menulis " Batasan Ekspresi Reguler " tentang ini.

JaredPar
sumber
53

Pertanyaan bagus.

Bahasa reguler yang sebenarnya tidak dapat memutuskan tanda kurung yang terbentuk dengan kuat. Jika alfabet Anda berisi '('dan ')'tujuannya adalah untuk memutuskan apakah untaian ini memiliki tanda kurung yang cocok. Karena ini adalah persyaratan yang diperlukan untuk ekspresi reguler, jawabannya adalah tidak.

Namun, jika Anda melonggarkan persyaratan dan menambahkan rekursi Anda mungkin dapat melakukannya. Alasannya adalah bahwa rekursi dapat bertindak sebagai tumpukan yang memungkinkan Anda "menghitung" kedalaman sarang saat ini dengan mendorong ke tumpukan ini.

Russ Cox menulis " Pencocokan Ekspresi Reguler Dapat Sederhana dan Cepat " yang merupakan risalah yang luar biasa pada implementasi mesin regex.

SAYA MEMBERIKAN JAWABAN KAMU
sumber
16

Tidak, jika Anda menggunakan ekspresi reguler standar.

Alasannya adalah bahwa Anda tidak dapat memenuhi lemma pemompaan untuk bahasa reguler. Negara-negara memompa lemma bahwa string milik bahasa "L" adalah biasa jika ada sejumlah "N" seperti itu, setelah membagi string menjadi tiga substring x, y, z, sehingga |x|>=1 && |xy|<=N, Anda dapat mengulangi ysebanyak yang Anda inginkan dan seluruh string masih akan menjadi milik L.

Konsekuensi dari lemma pemompaan adalah bahwa Anda tidak dapat memiliki string reguler dalam bentuk a^Nb^Mc^N, yaitu, dua substring memiliki panjang yang sama dipisahkan oleh string lain. Dengan cara apa pun Anda memisahkan string seperti itu ke dalam x, ydan z, Anda tidak dapat "memompa" ytanpa mendapatkan string dengan jumlah berbeda dari "a" dan "c", sehingga meninggalkan bahasa aslinya. Itu yang terjadi, misalnya, dengan tanda kurung dalam ekspresi reguler.

Davide Visentin
sumber
5
Itu bukan deskripsi yang sangat tepat tentang lemma pemompaan. Pertama, itu adalah seluruh bahasa yang bisa teratur atau tidak, bukan string tunggal. Kedua, ini adalah kondisi yang diperlukan, bukan cukup, untuk keteraturan. Akhirnya, hanya string yang cukup panjang yang dapat dipompa.
darij grinberg
13

Meskipun sangat mungkin untuk menggunakan regex rekursif seperti yang diposting MizardX, untuk hal-hal semacam ini jauh lebih berguna sebagai parser. Regex pada awalnya dimaksudkan untuk digunakan dengan bahasa biasa, menjadi rekursif atau memiliki kelompok penyeimbang hanyalah sebuah tambalan.

Bahasa yang mendefinisikan regex yang valid sebenarnya adalah tata bahasa bebas konteks, dan Anda harus menggunakan parser yang sesuai untuk menanganinya. Berikut ini adalah contoh untuk proyek universitas untuk penguraian regex sederhana (tanpa sebagian konstruksi). Ini menggunakan JavaCC. Dan ya, komentar dalam bahasa Spanyol, meskipun nama metode cukup jelas.

SKIP :
{
    " "
|   "\r"
|   "\t"
|   "\n"
}
TOKEN : 
{
    < DIGITO: ["0" - "9"] >
|   < MAYUSCULA: ["A" - "Z"] >
|   < MINUSCULA: ["a" - "z"] >
|   < LAMBDA: "LAMBDA" >
|   < VACIO: "VACIO" >
}

IRegularExpression Expression() :
{
    IRegularExpression r; 
}
{
    r=Alternation() { return r; }
}

// Matchea disyunciones: ER | ER
IRegularExpression Alternation() :
{
    IRegularExpression r1 = null, r2 = null; 
}
{
    r1=Concatenation() ( "|" r2=Alternation() )?
    { 
        if (r2 == null) {
            return r1;
        } else {
            return createAlternation(r1,r2);
        } 
    }
}

// Matchea concatenaciones: ER.ER
IRegularExpression Concatenation() :
{
    IRegularExpression r1 = null, r2 = null; 
}
{
    r1=Repetition() ( "." r2=Repetition() { r1 = createConcatenation(r1,r2); } )*
    { return r1; }
}

// Matchea repeticiones: ER*
IRegularExpression Repetition() :
{
    IRegularExpression r; 
}
{
    r=Atom() ( "*" { r = createRepetition(r); } )*
    { return r; }
}

// Matchea regex atomicas: (ER), Terminal, Vacio, Lambda
IRegularExpression Atom() :
{
    String t;
    IRegularExpression r;
}
{
    ( "(" r=Expression() ")" {return r;}) 
    | t=Terminal() { return createTerminal(t); }
    | <LAMBDA> { return createLambda(); }
    | <VACIO> { return createEmpty(); }
}

// Matchea un terminal (digito o minuscula) y devuelve su valor
String Terminal() :
{
    Token t;
}
{
    ( t=<DIGITO> | t=<MINUSCULA> ) { return t.image; }
}
Santiago Palladino
sumber
11

Anda dapat mengirimkan regex preg_matchyang akan mengembalikan false jika regex tidak valid. Jangan lupa gunakan @untuk menekan pesan kesalahan:

@preg_match($regexToTest, '');
  • Akan mengembalikan 1 jika regex //.
  • Akan mengembalikan 0 jika regex tidak apa-apa.
  • Akan mengembalikan false jika tidak.
Richard - Rogue Wave Limited
sumber
6

Contoh berikut oleh Paul McGuire, berasal dari wiki pyparsing, tetapi sekarang hanya tersedia melalui Wayback Machine , memberikan tata bahasa untuk parsing beberapa regex, untuk tujuan mengembalikan rangkaian string yang cocok. Dengan demikian, ia menolak mereka yang menyertakan istilah pengulangan tanpa batas, seperti '+' dan '*'. Tapi itu harus memberi Anda ide tentang bagaimana menyusun parser yang akan memproses ulang.

# 
# invRegex.py
#
# Copyright 2008, Paul McGuire
#
# pyparsing script to expand a regular expression into all possible matching strings
# Supports:
# - {n} and {m,n} repetition, but not unbounded + or * repetition
# - ? optional elements
# - [] character ranges
# - () grouping
# - | alternation
#
__all__ = ["count","invert"]

from pyparsing import (Literal, oneOf, printables, ParserElement, Combine, 
    SkipTo, operatorPrecedence, ParseFatalException, Word, nums, opAssoc,
    Suppress, ParseResults, srange)

class CharacterRangeEmitter(object):
    def __init__(self,chars):
        # remove duplicate chars in character range, but preserve original order
        seen = set()
        self.charset = "".join( seen.add(c) or c for c in chars if c not in seen )
    def __str__(self):
        return '['+self.charset+']'
    def __repr__(self):
        return '['+self.charset+']'
    def makeGenerator(self):
        def genChars():
            for s in self.charset:
                yield s
        return genChars

class OptionalEmitter(object):
    def __init__(self,expr):
        self.expr = expr
    def makeGenerator(self):
        def optionalGen():
            yield ""
            for s in self.expr.makeGenerator()():
                yield s
        return optionalGen

class DotEmitter(object):
    def makeGenerator(self):
        def dotGen():
            for c in printables:
                yield c
        return dotGen

class GroupEmitter(object):
    def __init__(self,exprs):
        self.exprs = ParseResults(exprs)
    def makeGenerator(self):
        def groupGen():
            def recurseList(elist):
                if len(elist)==1:
                    for s in elist[0].makeGenerator()():
                        yield s
                else:
                    for s in elist[0].makeGenerator()():
                        for s2 in recurseList(elist[1:]):
                            yield s + s2
            if self.exprs:
                for s in recurseList(self.exprs):
                    yield s
        return groupGen

class AlternativeEmitter(object):
    def __init__(self,exprs):
        self.exprs = exprs
    def makeGenerator(self):
        def altGen():
            for e in self.exprs:
                for s in e.makeGenerator()():
                    yield s
        return altGen

class LiteralEmitter(object):
    def __init__(self,lit):
        self.lit = lit
    def __str__(self):
        return "Lit:"+self.lit
    def __repr__(self):
        return "Lit:"+self.lit
    def makeGenerator(self):
        def litGen():
            yield self.lit
        return litGen

def handleRange(toks):
    return CharacterRangeEmitter(srange(toks[0]))

def handleRepetition(toks):
    toks=toks[0]
    if toks[1] in "*+":
        raise ParseFatalException("",0,"unbounded repetition operators not supported")
    if toks[1] == "?":
        return OptionalEmitter(toks[0])
    if "count" in toks:
        return GroupEmitter([toks[0]] * int(toks.count))
    if "minCount" in toks:
        mincount = int(toks.minCount)
        maxcount = int(toks.maxCount)
        optcount = maxcount - mincount
        if optcount:
            opt = OptionalEmitter(toks[0])
            for i in range(1,optcount):
                opt = OptionalEmitter(GroupEmitter([toks[0],opt]))
            return GroupEmitter([toks[0]] * mincount + [opt])
        else:
            return [toks[0]] * mincount

def handleLiteral(toks):
    lit = ""
    for t in toks:
        if t[0] == "\\":
            if t[1] == "t":
                lit += '\t'
            else:
                lit += t[1]
        else:
            lit += t
    return LiteralEmitter(lit)    

def handleMacro(toks):
    macroChar = toks[0][1]
    if macroChar == "d":
        return CharacterRangeEmitter("0123456789")
    elif macroChar == "w":
        return CharacterRangeEmitter(srange("[A-Za-z0-9_]"))
    elif macroChar == "s":
        return LiteralEmitter(" ")
    else:
        raise ParseFatalException("",0,"unsupported macro character (" + macroChar + ")")

def handleSequence(toks):
    return GroupEmitter(toks[0])

def handleDot():
    return CharacterRangeEmitter(printables)

def handleAlternative(toks):
    return AlternativeEmitter(toks[0])


_parser = None
def parser():
    global _parser
    if _parser is None:
        ParserElement.setDefaultWhitespaceChars("")
        lbrack,rbrack,lbrace,rbrace,lparen,rparen = map(Literal,"[]{}()")

        reMacro = Combine("\\" + oneOf(list("dws")))
        escapedChar = ~reMacro + Combine("\\" + oneOf(list(printables)))
        reLiteralChar = "".join(c for c in printables if c not in r"\[]{}().*?+|") + " \t"

        reRange = Combine(lbrack + SkipTo(rbrack,ignore=escapedChar) + rbrack)
        reLiteral = ( escapedChar | oneOf(list(reLiteralChar)) )
        reDot = Literal(".")
        repetition = (
            ( lbrace + Word(nums).setResultsName("count") + rbrace ) |
            ( lbrace + Word(nums).setResultsName("minCount")+","+ Word(nums).setResultsName("maxCount") + rbrace ) |
            oneOf(list("*+?")) 
            )

        reRange.setParseAction(handleRange)
        reLiteral.setParseAction(handleLiteral)
        reMacro.setParseAction(handleMacro)
        reDot.setParseAction(handleDot)

        reTerm = ( reLiteral | reRange | reMacro | reDot )
        reExpr = operatorPrecedence( reTerm,
            [
            (repetition, 1, opAssoc.LEFT, handleRepetition),
            (None, 2, opAssoc.LEFT, handleSequence),
            (Suppress('|'), 2, opAssoc.LEFT, handleAlternative),
            ]
            )
        _parser = reExpr

    return _parser

def count(gen):
    """Simple function to count the number of elements returned by a generator."""
    i = 0
    for s in gen:
        i += 1
    return i

def invert(regex):
    """Call this routine as a generator to return all the strings that
       match the input regular expression.
           for s in invert("[A-Z]{3}\d{3}"):
               print s
    """
    invReGenerator = GroupEmitter(parser().parseString(regex)).makeGenerator()
    return invReGenerator()

def main():
    tests = r"""
    [A-EA]
    [A-D]*
    [A-D]{3}
    X[A-C]{3}Y
    X[A-C]{3}\(
    X\d
    foobar\d\d
    foobar{2}
    foobar{2,9}
    fooba[rz]{2}
    (foobar){2}
    ([01]\d)|(2[0-5])
    ([01]\d\d)|(2[0-4]\d)|(25[0-5])
    [A-C]{1,2}
    [A-C]{0,3}
    [A-C]\s[A-C]\s[A-C]
    [A-C]\s?[A-C][A-C]
    [A-C]\s([A-C][A-C])
    [A-C]\s([A-C][A-C])?
    [A-C]{2}\d{2}
    @|TH[12]
    @(@|TH[12])?
    @(@|TH[12]|AL[12]|SP[123]|TB(1[0-9]?|20?|[3-9]))?
    @(@|TH[12]|AL[12]|SP[123]|TB(1[0-9]?|20?|[3-9])|OH(1[0-9]?|2[0-9]?|30?|[4-9]))?
    (([ECMP]|HA|AK)[SD]|HS)T
    [A-CV]{2}
    A[cglmrstu]|B[aehikr]?|C[adeflmorsu]?|D[bsy]|E[rsu]|F[emr]?|G[ade]|H[efgos]?|I[nr]?|Kr?|L[airu]|M[dgnot]|N[abdeiop]?|Os?|P[abdmortu]?|R[abefghnu]|S[bcegimnr]?|T[abcehilm]|Uu[bhopqst]|U|V|W|Xe|Yb?|Z[nr]
    (a|b)|(x|y)
    (a|b) (x|y)
    """.split('\n')

    for t in tests:
        t = t.strip()
        if not t: continue
        print '-'*50
        print t
        try:
            print count(invert(t))
            for s in invert(t):
                print s
        except ParseFatalException,pfe:
            print pfe.msg
            print
            continue
        print

if __name__ == "__main__":
    main()
PaulMcG
sumber