Оператор пересечения класса символов &&
, по определению его функции, должен быть коммутативным. [a&&b]
должно совпадать с теми же символами, что и [b&&a]
для любых a и b. Я обнаружил, что все следующие шаблоны удовлетворяют этому критерию.
[a-z&&abcd]
так же, как [abcd&&a-z]
[a-z&&ab[cd]]
так же, как [ab[cd]&&a-z]
[a-z&&[ab][cd]]
так же, как [[ab][cd]&&a-z]
Все они эквивалентны [abcd]
. Однако, если выражено [a-z&&[ab]cd]
, это больше не так. Это выражение соответствует только c
и d
, но не a
и b
. Однако перевернутая версия [[ab]cd&&a-z]
соответствует всем четырем символам, как и другие шаблоны. Другими словами
[[ab]cd&&a-z]
не совпадает с [a-z&&[ab]cd]
Я пошел в источники Pattern
, чтобы выяснить, почему это так, и обнаружил, что именно так реализовано пересечение (Java 1.8.0_60 JDK)
case '&':
// ...
ch = next();
if (ch == '&') {
ch = next();
CharProperty rightNode = null;
while (ch != ']' && ch != '&') {
if (ch == '[') {
if (rightNode == null)
rightNode = clazz(true);
else
rightNode = union(rightNode, clazz(true));
} else { // abc&&def
unread();
rightNode = clazz(false); // here is what happens
}
ch = peek();
}
Обратите внимание, что отмеченная строка
rightNode = clazz(false);
а не
rightNode = union(rightNode, clazz(true));
Другими словами, в правой части &&
всякий раз, когда встречается первый символ, не входящий в класс вложенных символов, анализатор шаблонов предполагает, что перед ним ничего нет. Таким образом, после &&
синтаксический анализатор читает [ab]
в rightNode
, затем читает cd
, но вместо слияния с [ab]
он просто перезаписывает его.
Я знаю, что практически никто не пишет регулярные выражения, подобные [a-z&&[ab]cd]
, но, тем не менее, документация подразумевает, что он должен работать. Это ошибка в реализации, или она действительно должна работать таким образом?