Ist es möglich, einen gültigen regulären Ausdruck mit einem anderen regulären Ausdruck zu erkennen? Wenn ja, geben Sie bitte den folgenden Beispielcode an.
Ist es möglich, einen gültigen regulären Ausdruck mit einem anderen regulären Ausdruck zu erkennen? Wenn ja, geben Sie bitte den folgenden Beispielcode an.
Antworten:
/
^ # 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
/
Dies ist eine rekursive Regex und wird von vielen Regex-Engines nicht unterstützt. PCRE-basierte sollten dies unterstützen.
Ohne Leerzeichen und Kommentare:
/^((?:(?:[^?+*{}()[\]\\|]+|\\.|\[(?:\^?\\.|\^[^\\]|[^\\^])(?:[^\]\\]+|\\.)*\]|\((?:\?[:=!]|\?<[=!]|\?>)?(?1)??\)|\(\?(?:R|[+-]?\d+)\))(?:(?:[?+*]|\{\d+(?:,\d*)?\})[?+]?)?|\|)*)$/
.NET unterstützt die Rekursion nicht direkt. (Die (?1)
und (?R)
Konstrukte.) Die Rekursion müsste in das Zählen ausgeglichener Gruppen umgewandelt werden:
^ # 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.
Verdichtet:
^(?:(?:[^?+*{}()[\]\\|]+|\\.|\[(?:\^?\\.|\^[^\\]|[^\\^])(?:[^\]\\]+|\\.)*\]|\((?:\?[:=!]|\?<[=!]|\?>|\?<[^\W\d]\w*>|\?'[^\W\d]\w*')?(?<N>)|\)(?<-N>))(?:(?:[?+*]|\{\d+(?:,\d*)?\})[?+]?)?|\|)*$(?(N)(?!))
Aus den Kommentaren:
Wird dies Substitutionen und Übersetzungen validieren?
Es wird nur der Regex-Teil von Substitutionen und Übersetzungen validiert. s/<this part>/.../
Es ist theoretisch nicht möglich, alle gültigen Regex-Grammatiken mit einem Regex abzugleichen.
Es ist möglich, dass die Regex-Engine eine Rekursion wie PCRE unterstützt, dies kann jedoch nicht mehr als reguläre Ausdrücke bezeichnet werden.
In der Tat ist ein "rekursiver regulärer Ausdruck" kein regulärer Ausdruck. Aber dies ist eine häufig akzeptierte Erweiterung für Regex-Engines ... Ironischerweise stimmt diese erweiterte Regex nicht mit erweiterten Regex überein.
"In Theorie, Theorie und Praxis sind sie gleich. In der Praxis sind sie es nicht." Fast jeder, der reguläre Ausdrücke kennt, weiß, dass reguläre Ausdrücke keine Rekursion unterstützen. PCRE und die meisten anderen Implementierungen unterstützen jedoch weit mehr als grundlegende reguläre Ausdrücke.
Wenn ich dies mit dem Shell-Skript im Befehl grep verwende, wird mir ein Fehler angezeigt. grep: Ungültiger Inhalt von {}. Ich mache ein Skript, das eine Codebasis durchsuchen könnte, um alle Dateien zu finden, die reguläre Ausdrücke enthalten
Dieses Muster nutzt eine Erweiterung, die als rekursive reguläre Ausdrücke bezeichnet wird. Dies wird von der POSIX-Variante von Regex nicht unterstützt. Sie können versuchen, mit dem -P-Schalter die PCRE-Regex-Variante zu aktivieren.
Regex selbst "ist keine reguläre Sprache und kann daher nicht durch reguläre Ausdrücke analysiert werden ..."
Dies gilt für klassische reguläre Ausdrücke. Einige moderne Implementierungen erlauben eine Rekursion, wodurch sie zu einer kontextfreien Sprache wird, obwohl sie für diese Aufgabe etwas ausführlich ist.
Ich sehe, wo du zusammenpasst
[]()/\
. und andere Regex-Sonderzeichen. Wo erlauben Sie nicht spezielle Zeichen? Es scheint, als würde dies übereinstimmen^(?:[\.]+)$
, aber nicht^abcdefg$
. Das ist eine gültige Regex.
[^?+*{}()[\]\\|]
stimmt mit jedem einzelnen Zeichen überein, das nicht Teil eines der anderen Konstrukte ist. Dazu gehören sowohl die wörtliche ( a
- z
) und bestimmte Sonderzeichen ( ^
, $
, .
).
.{,1}
ist unübertroffen. Wechseln Sie zu ^((?:(?:[^?+*{}()[\]\\|]+|\\.|\[(?:\^?\\.|\^[^\\]|[^\\^])(?:[^\]\\]+|\\.)*\]|\((?:\?[:=!]|\?<[=!]|\?>)?(?1)??\)|\(\?(?:R|[+-]?\d+)\))(?:(?:[?+*]|\{\d*(?:,\d*)?\})[?+]?)?|\|)*)$
Übereinstimmungen. Ändern Sie \d+
zu\d*
[a-b-c]
.
Nein, wenn Sie streng genommen von regulären Ausdrücken sprechen und einige Implementierungen regulärer Ausdrücke nicht einschließen, die tatsächlich kontextfreie Grammatiken sind.
Es gibt eine Einschränkung für reguläre Ausdrücke, die es unmöglich macht, einen regulären Ausdruck zu schreiben, der allen und nur regulären Ausdrücken entspricht. Sie können keine Implementierungen wie gepaarte Klammern abgleichen. Regexes verwenden viele solcher Konstrukte. Nehmen wir []
als Beispiel. Wann immer es eine [
gibt, muss es eine Übereinstimmung geben ]
, die für einen regulären Ausdruck einfach genug ist "\[.*\]"
.
Was es für Regexes unmöglich macht, ist, dass sie verschachtelt werden können. Wie kann man einen regulären Ausdruck schreiben, der mit verschachtelten Klammern übereinstimmt? Die Antwort ist, dass Sie nicht ohne einen unendlich langen regulären Ausdruck auskommen können. Sie können eine beliebige Anzahl verschachtelter Klammern durch Brute Force abgleichen, aber Sie können niemals einen beliebig langen Satz verschachtelter Klammern abgleichen.
Diese Funktion wird häufig als Zählen bezeichnet, da Sie die Tiefe der Verschachtelung zählen. Ein Regex kann per Definition nicht zählen.
Am Ende schrieb ich " Einschränkungen für reguläre Ausdrücke " darüber.
Gute Frage.
Echte reguläre Sprachen können nicht willkürlich tief verschachtelte, wohlgeformte Klammern bestimmen. Wenn Ihr Alphabet enthält '('
und ')'
das Ziel darin besteht, zu entscheiden, ob eine Zeichenfolge davon gut geformte übereinstimmende Klammern enthält. Da dies eine notwendige Voraussetzung für reguläre Ausdrücke ist, lautet die Antwort nein.
Wenn Sie jedoch die Anforderung lösen und eine Rekursion hinzufügen, können Sie dies wahrscheinlich tun. Der Grund dafür ist, dass die Rekursion als Stapel fungieren kann, mit dem Sie die aktuelle Verschachtelungstiefe "zählen" können, indem Sie auf diesen Stapel drücken.
Russ Cox schrieb " Regular Expression Matching kann einfach und schnell sein ", eine wunderbare Abhandlung über die Implementierung von Regex-Engines.
Nein, wenn Sie reguläre Standardausdrücke verwenden.
Der Grund ist, dass Sie das Pump-Lemma für reguläre Sprachen nicht erfüllen können . Die Pump Lemma besagt , dass ein String Sprache „L“ gehört , ist regulär , wenn es eine Reihe existiert „N“ , so dass nach der Zeichenfolge in drei Teil Dividieren x
, y
, z
, so dass |x|>=1 && |xy|<=N
Sie wiederholen können , y
so oft wie Sie wollen , und die Die gesamte Zeichenfolge gehört weiterhin dazu L
.
Eine Folge des Pump-Lemmas ist, dass Sie keine regulären Zeichenfolgen in der Form haben a^Nb^Mc^N
können, dh zwei Teilzeichenfolgen mit derselben Länge, die durch eine andere Zeichenfolge getrennt sind. In keiner Weise geteilt Sie solche Strings x
, y
und z
können Sie nicht „Pumpe“ y
ohne einen String mit einer unterschiedlichen Anzahl von „a“ und „c“ zu erhalten, damit die ursprünglichen Sprache zu verlassen. Dies ist beispielsweise bei Klammern in regulären Ausdrücken der Fall.
Obwohl es durchaus möglich ist, einen rekursiven regulären Ausdruck zu verwenden, wie MizardX es gepostet hat, ist es für diese Art von Dingen ein Parser viel nützlicher. Regexes sollten ursprünglich mit regulären Sprachen verwendet werden. Rekursiv zu sein oder Ausgleichsgruppen zu haben, ist nur ein Patch.
Die Sprache, die gültige reguläre Ausdrücke definiert, ist eigentlich eine kontextfreie Grammatik, und Sie sollten einen geeigneten Parser verwenden, um damit umzugehen. Hier ist ein Beispiel für ein Universitätsprojekt zum Parsen einfacher Regexe (ohne die meisten Konstrukte). Es verwendet JavaCC. Und ja, Kommentare sind auf Spanisch, obwohl Methodennamen ziemlich selbsterklärend sind.
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; }
}
Sie können den regulären Ausdruck senden, an den preg_match
false zurückgegeben wird, wenn der reguläre Ausdruck ungültig ist. Vergessen Sie nicht, die zu verwenden @
, um Fehlermeldungen zu unterdrücken:
@preg_match($regexToTest, '');
//
.Das folgende Beispiel von Paul McGuire, das ursprünglich aus dem Pyparsing-Wiki stammt, jetzt aber nur über die Wayback-Maschine verfügbar ist , enthält eine Grammatik zum Parsen einiger regulärer Ausdrücke, um den Satz übereinstimmender Zeichenfolgen zurückzugeben. Als solches lehnt es diejenigen Re ab, die unbegrenzte Wiederholungsbegriffe wie '+' und '*' enthalten. Aber es sollte Ihnen eine Vorstellung davon geben, wie Sie einen Parser strukturieren, der Re's verarbeitet.
#
# 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()