正则表达式的编译结构
KOBI的答案是当场就有关Java正则表达式的情况下的行为(太阳/ 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
看源代码
根据我的调查,该错误可能是由于{
未在private方法中正确检查这一事实造成的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的新切片(长度为from first
,它为0)。
返回此切片时,将通过解析量词closure()
,从而得到我们所看到的。
比较Java 1.4.0,Java 5和Java 8的源代码,sequence()
和的源代码似乎没有太大变化atom()
。从一开始似乎就存在此错误。
正则表达式的标准
由于Java没有实现BRE和ERE ,因此引用IEEE-Standard 1003.1(或POSIX标准)的最高投票答案与该讨论无关。
根据标准,有许多语法会导致未定义的行为,但是在许多其他正则表达式中,行为是定义良好的(尽管是否同意是另一回事)。例如,\d
根据标准未定义,但是它以许多正则表达式形式匹配数字(ASCII / Unicode)。
可悲的是,关于正则表达式语法没有其他标准。
但是,存在Unicode正则表达式的标准,该标准着重于Unicode正则表达式引擎应具有的功能。JavaPattern
类或多或少实现了UTS#18:Unicode正则表达式和RL2.1中描述的1级支持(尽管存在很多错误)。