Kompilierte Struktur des regulären Ausdrucks
Kobis Antwort ist genau auf das Verhalten von Java Regex (Sun / Oracle-Implementierung) für den Fall "^\\d{1}{2}$"
oder "{1}"
.
Unten ist die interne kompilierte Struktur von "^\\d{1}{2}$"
:
^\d{1}{2}$
Begin. \A or default ^
Curly. Greedy quantifier {1,1}
Ctype. POSIX (US-ASCII): DIGIT
Node. Accept match
Curly. Greedy quantifier {2,2}
Slice. (length=0)
Node. Accept match
Dollar(multiline=false). \Z or default $
java.util.regex.Pattern$LastNode
Node. Accept match
Blick auf den Quellcode
Aus meiner Untersuchung geht hervor, dass der Fehler wahrscheinlich auf die Tatsache zurückzuführen {
ist , dass er in der privaten Methode nicht ordnungsgemäß überprüft wurde sequence()
.
Die Methode sequence()
ruft das atom()
auf, um das Atom zu analysieren, hängt dann durch Aufrufen einen Quantifizierer an das Atom an closure()
und verkettet alle Atome mit Verschluss zu einer Sequenz.
Zum Beispiel bei diesem regulären Ausdruck:
^\d{4}a(bc|gh)+d*$
Dann wird der Top-Level - Aufruf sequence()
erhält die kompilierten Knoten für ^
, \d{4}
, a
, (bc|gh)+
, d*
, $
und Kette sie zusammen.
Schauen wir uns vor diesem Hintergrund den Quellcode sequence()
von OpenJDK 8-b132 an (Oracle verwendet dieselbe Codebasis):
@SuppressWarnings("fallthrough")
private Node sequence(Node end) {
Node head = null;
Node tail = null;
Node node = null;
LOOP:
for (;;) {
int ch = peek();
switch (ch) {
case '(':
node = group0();
if (node == null)
continue;
if (head == null)
head = node;
else
tail.next = node;
tail = root;
continue;
case '[':
node = clazz(true);
break;
case '\\':
ch = nextEscaped();
if (ch == 'p' || ch == 'P') {
boolean oneLetter = true;
boolean comp = (ch == 'P');
ch = next();
if (ch != '{') {
unread();
} else {
oneLetter = false;
}
node = family(oneLetter, comp);
} else {
unread();
node = atom();
}
break;
case '^':
next();
if (has(MULTILINE)) {
if (has(UNIX_LINES))
node = new UnixCaret();
else
node = new Caret();
} else {
node = new Begin();
}
break;
case '$':
next();
if (has(UNIX_LINES))
node = new UnixDollar(has(MULTILINE));
else
node = new Dollar(has(MULTILINE));
break;
case '.':
next();
if (has(DOTALL)) {
node = new All();
} else {
if (has(UNIX_LINES))
node = new UnixDot();
else {
node = new Dot();
}
}
break;
case '|':
case ')':
break LOOP;
case ']':
case '}':
node = atom();
break;
case '?':
case '*':
case '+':
next();
throw error("Dangling meta character '" + ((char)ch) + "'");
case 0:
if (cursor >= patternLength) {
break LOOP;
}
default:
node = atom();
break;
}
node = closure(node);
if (head == null) {
head = tail = node;
} else {
tail.next = node;
tail = node;
}
}
if (head == null) {
return end;
}
tail.next = end;
root = tail;
return head;
}
Beachten Sie die Linie throw error("Dangling meta character '" + ((char)ch) + "'");
. Dies ist , wo der Fehler ausgelöst wird , wenn +
, *
, ?
baumeln und ist nicht Teil eines vorhergehenden Token. Wie Sie sehen können, {
gehört nicht zu den Fällen, Fehler zu werfen. Tatsächlich ist es in der Liste der Fälle in nicht vorhanden sequence()
, und der Kompilierungsprozess wird von default
Fall zu Fall direkt an weitergeleitet atom()
.
@SuppressWarnings("fallthrough")
private Node atom() {
int first = 0;
int prev = -1;
boolean hasSupplementary = false;
int ch = peek();
for (;;) {
switch (ch) {
case '*':
case '+':
case '?':
case '{':
if (first > 1) {
cursor = prev;
first--;
}
break;
}
break;
}
if (first == 1) {
return newSingle(buffer[0]);
} else {
return newSlice(buffer, first, hasSupplementary);
}
}
Wenn der Prozess eintritt atom()
, {
bricht er ab switch
und for
wiederholt sich, und es wird ein neues Slice mit der Länge 0 erstellt (die Länge kommt von first
, was 0 ist).
Wenn dieses Slice zurückgegeben wird, wird der Quantifizierer von analysiert closure()
, was zu dem führt, was wir sehen.
Beim Vergleich des Quellcodes von Java 1.4.0, Java 5 und Java 8 scheint sich der Quellcode von sequence()
und nicht wesentlich zu ändern atom()
. Es scheint, dass dieser Fehler von Anfang an vorhanden war.
Standard für regulären Ausdruck
Die am häufigsten gewählte Antwort unter Berufung auf den IEEE-Standard 1003.1 (oder den POSIX-Standard) ist für die Diskussion irrelevant, da Java BRE und ERE nicht implementiert .
Es gibt viele Syntaxen, die gemäß dem Standard zu undefiniertem Verhalten führen, aber es ist ein genau definiertes Verhalten für viele andere Regex-Varianten (obwohl es eine andere Sache ist, ob sie übereinstimmen oder nicht). Zum Beispiel \d
ist es gemäß dem Standard undefiniert, stimmt jedoch in vielen Regex-Varianten mit Ziffern (ASCII / Unicode) überein.
Leider gibt es keinen anderen Standard für die Syntax regulärer Ausdrücke.
Es gibt jedoch einen Standard für Unicode Regular Expression, der sich auf Funktionen konzentriert, die eine Unicode-Regex-Engine haben sollte. Die Java- Pattern
Klasse implementiert mehr oder weniger die Unterstützung der Stufe 1, wie in UTS # 18: Unicode Regular Expression und RL2.1 beschrieben (wenn auch extrem fehlerhaft).