Ошибка компиляции подробного Java-регулярного выражения с символьным классом и границей слов

Почему этот шаблон не скомпилирован:

Pattern.compile("(?x)[ ]\\b");

ошибка

ERROR java.util.regex.PatternSyntaxException:
Illegal/unsupported escape sequence near index 8
(?x)[ ]\b
        ^
at java_util_regex_Pattern$compile.call (Unknown Source)

В то время как работают следующие эквивалентные?

Pattern.compile("(?x)\\ \\b");
Pattern.compile("[ ]\\b");
Pattern.compile(" \\b");

Это ошибка в компиляторе Java regex, или я чего-то не хватает? Мне нравится использовать [ ] в расширенном регулярном выражении вместо обратного слэша-обратного слэша, потому что он сохраняет некоторый визуальный шум. Но, видимо, они не то же самое!

PS: этот вопрос не касается обратных косых черт. Это об экранировании пробелов в многословном регулярном выражении с использованием символьного класса, содержащего единственное пространство [ ] вместо использования обратного слэша.

Так или иначе комбинация verbose regex (?x) и класса символов, содержащего одно пространство [ ] выдает компилятор и не распознает escape-последовательность слова \b


Протестировано с Java до 1.8.0_151

Ответы

Ответ 1

Это ошибка в peekPastWhitespace() Java peekPastWhitespace() в классе Pattern. Отслеживание всей этой проблемы... Я решил взглянуть на реализацию OpenJDK 8-b132 Pattern. Позвольте начать забивать это сверху:

  1. compile() вызывает expr() в строке 1696
  2. expr() вызывает sequence() в строке 1996
  3. sequence() вызывает clazz() в строке 2063, так как случай [ был встречен
  4. clazz() вызывает peek() в строке 2509
  5. peek() вызывает peekPastWhitespace() в строке 1830, так как if(has(COMMENTS)) оценивает значение true (из-за того, что добавлен флаг x (?x) в начале шаблона)
  6. peekPastWhitespace() (опубликовано ниже) пропускает все пробелы в шаблоне.

peekPastWhitespace()

private int peekPastWhitespace(int ch) {
    while (ASCII.isSpace(ch) || ch == '#') {
        while (ASCII.isSpace(ch))
            ch = temp[++cursor]
        if (ch == '#') {
            ch = peekPastLine();
        }
    }
    return ch;
}

Такая же ошибка существует в parsePastWhitespace().

Ваше регулярное выражение интерпретируется как []\\b, что является причиной вашей ошибки, потому что \b не поддерживается в классе символов в Java. Более того, как только вы исправляете проблему \b, ваш класс символов также не имеет закрытия ].

Что вы можете сделать, чтобы исправить эту проблему:

  1. \\ Как упоминалось в ОП, просто используйте двойную обратную косую черту и пространство
  2. [\\ ] Побег в пространстве символов, чтобы он интерпретировался буквально
  3. [ ](?x)\\b Поместите встроенный модификатор после класса символов

Ответ 2

Мне нравится использовать [ ] в расширенном регулярном выражении вместо обратного слэша-обратного слэша, потому что он сохраняет некоторый визуальный шум. Но, видимо, они не то же самое!

"[ ]" - это то же самое, что "\\ " или даже " ".

Проблема заключается в том, что (?x) в начале включает режим комментариев. Как указано в документации

Разрешает пробелы и комментарии в шаблоне.
В этом режиме пробелы игнорируются, а встроенные комментарии, начинающиеся с #, игнорируются до конца строки.
Режим комментариев также можно включить с помощью встроенного флага (?x).

В режиме комментариев регулярное выражение "(?x)[ ]\\b" совпадает с "[]\\b" и не будет компилироваться, потому что пустой класс символа [] не анализируется как пустой, а анализируется как "[\\]" (незакрытый класс символов, содержащий литерал ]).

Вместо этого используйте " \\b". Альтернативно, сохраните пространство в режиме комментариев, экранировав его обратным слэшем: "(?x)[\\ ]\\b" или "(?x)\\ \\b".

Ответ 3

Похоже, из-за свободного пробега (verbose) mode (?x) в [ ] игнорируется, поэтому механизм regex видит ваше регулярное выражение как []\\b.
Если мы удалим \\b это будет выглядеть как [] и мы получим ошибку об Unclosed character class - character class не может быть пустым, поэтому ] помещены непосредственно после того, как [ рассматривается как первый символ, который принадлежит этому классу вместо метасимвола который закрывает класс символов.

Так как [ открыто, движок регулярных выражений видит \b как помещенный внутри этого символьного класса. Но \b не может быть размещен там (это не символ, а "место"), поэтому мы видим ошибку в "неподдерживаемой escape-последовательности" (внутри класса символов, но эта часть была пропущена).

Другими словами, вы не можете использовать [ ] для выхода из пространства в подробном режиме (по крайней мере, на Java). Вам нужно будет либо использовать "\\ " либо "[\\ ]".

Ответ 4

Обходной путь

Помимо ускорения пробелов отдельно, которые в буквальном смысле такие же, как [ ], вы можете использовать режим x для всего регулярного выражения, но отключите его во время работы с шаблонами, которым требуются пробелы, inline:

(?x)match-this-(?-x: with spaces )\\b
    ^^^^^^^^^^^     ^^^^^^^^^^^^^ ^^^
    'x' is on            off       on

или альтернативой будет использование метасимволов qouting \Q...\E:

(?x)match-this-\Q with s p a c e s \E\\b
    ^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^^  ^^^
    'x' is on            off          on

Почему Exception?

В расширенном или в режиме комментариев (x) пробелы игнорируются, но обработка пробелов внутри классов символов в разных вариантах выполняется по-разному.

Например, в PCRE все пробельные символы игнорируются, за исключением тех, что указаны в символьном классе. Это означает, что [ ] является допустимым регулярным выражением, но у Java нет исключения:

В этом режиме пробелы игнорируются...

Период. Таким образом, этот [ ] равен этому [] который недопустим и PatternSyntaxException исключение PatternSyntaxException.

Почти все ароматы регулярных выражений, кроме JavaScript, нуждаются в классе символов, чтобы иметь хотя бы один блок данных. Они рассматривают пустой класс символов как незамкнутый набор, которому нужна закрывающая скобка. Сказать, что []] действует в большинстве вкусов.

Режим свободного интервала в дефференциальных ароматах на [ ]:

  • PCRE действителен
  • .NET valid
  • Perl действует
  • Ruby valid
  • TCL действует
  • Ошибка Java 7
  • Ошибка Java 8

Ответ 5

Давайте проанализируем, что произойдет точно.

Взгляните на исходный код java.util.regex.Pattern

Разрешает пробелы и комментарии в шаблоне. В этом режиме пробелы игнорируются, а встроенные комментарии, начинающиеся С#, игнорируются до конца строки.

Режим комментариев также можно включить с помощью встроенного флага (? X).

Ваше регулярное выражение направит вас к этой строке

private void accept(int ch, String s) {
    int testChar = temp[cursor++];
    if (has(COMMENTS))
        testChar = parsePastWhitespace(testChar);
    if (ch != testChar) {
        throw error(s);
    }
}

Если вы заметили свой кодовый вызов parsePastWhitespace (testChar);

private int parsePastWhitespace(int ch) {
    while (ASCII.isSpace(ch) || ch == '#') {
        while (ASCII.isSpace(ch))//<----------------Here is the key of your error
            ch = temp[cursor++];
        if (ch == '#')
            ch = parsePastLine();
    }
    return ch;
}

В вашем случае у вас есть пробел в вашем регулярном выражении (?x)[ ]\\b это вернет что-то (я не могу проанализировать его правильно):

    if (ch != testChar) {
        throw error(s);
    }

который не равен ch и здесь исключение составляет броски

throw error(s);