Складена структура регулярного виразу
Відповідь Кобі - про поведінку регулярного виразу Java (реалізація Sun / Oracle) для випадку "^\\d{1}{2}$", або "{1}".
Нижче представлена внутрішня скомпільована структура "^\\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
Переглядаючи вихідний код
З мого розслідування, помилка, мабуть, пов’язана з тим фактом, який {не був належним чином перевірений приватним методом sequence().
Метод sequence()викликає до atom()синтаксичного аналізу атома, потім приєднує квантор до атома за допомогою виклику closure()та з'єднує всі атоми із замиканням в одну послідовність.
Наприклад, враховуючи цей регулярний вираз:
^\d{4}a(bc|gh)+d*$
Тоді виклик верхнього рівня , щоб sequence()отримає скомпільовані вузли для ^, \d{4}, a, (bc|gh)+, d*,$ і ланцюгів їх разом.
З огляду на цю ідею, давайте розглянемо вихідний код sequence(), скопійований з OpenJDK 8-b132 (Oracle використовує ту саму основу коду):
@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;
}
Візьміть на замітку рядок throw error("Dangling meta character '" + ((char)ch) + "'");. Це де помилка виникає , якщо +, *, ?звисає і не є частиною попередніх маркерів. Як бачите, {не серед випадків викидання помилок. Насправді він відсутній у списку справ sequence(), а процес компіляції буде переходити defaultбезпосередньо до кожного випадку 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);
}
}
Коли процес вступає atom(), оскільки він стикається {відразу, він обривається switchі forцикл, і створюється новий зріз довжиною 0 (довжина походить відfirst , що дорівнює 0).
Коли цей зріз повертається, квантор аналізується closure(), в результаті чого ми бачимо.
Порівнюючи вихідний код Java 1.4.0, Java 5 та Java 8, здається, що у вихідному коді sequence()та atom(). Здається, ця помилка існує з самого початку.
Стандарт для регулярних виразів
Відповідь з найбільшою оцінкою, посилаючись на стандарт IEEE 1003.1 (або стандарт POSIX), не має значення для обговорення, оскільки Java не застосовує BRE та ERE.
Існує багато синтаксису, що призводить до невизначеної поведінки відповідно до стандарту, але це чітко визначена поведінка у багатьох інших варіантах регулярних виразів (хоча погоджуються вони чи ні - це інша справа). Наприклад,\d не визначено відповідно до стандарту, але воно відповідає цифрам (ASCII / Unicode) у багатьох варіантах регулярних виразів.
На жаль, немає іншого стандарту щодо синтаксису регулярних виразів.
Однак існує стандарт регулярного виразу Unicode, який зосереджується на особливостях, які повинен мати механізм регулярних виразів Unicode. PatternКлас Java більш-менш реалізує підтримку рівня 1, як описано в UTS # 18: Регулярний вираз Unicode та RL2.1 (хоча і надзвичайно глючний).