Ошибка компиляции подробного регулярного выражения 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, или я что-то упустил? Мне нравится использовать [ ] в подробном регулярном выражении вместо обратной косой черты-обратной косой черты-пространства, потому что это избавляет от некоторого визуального шума. Но видимо они не одинаковые!

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

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


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


person Tobia    schedule 13.03.2018    source источник
comment
Не то чтобы это решило вопрос, но чем класс символов, содержащий только пробел, отличается от буквального пробела?   -  person user unknown    schedule 13.03.2018
comment
@userunknown: флаг x (включенный (?x) OP) приводит к игнорированию пробелов и комментариев; поэтому (?x)a b эквивалентно ab, тогда как (?x)a\ b эквивалентно a b. Как объясняет Сокови в своем ответе, проблема в том, что ОП ожидал, что (?x)a[ ]b будет эквивалентен a[ ]b (то есть a b), хотя на самом деле он эквивалентен a[]b (что неверно).   -  person ruakh    schedule 14.03.2018
comment
@ruakh Вот именно. Во всех других механизмах PCRE [ ] является допустимым способом избежать пробелов в подробном регулярном выражении, см., например, Perl: echo 'a b' | perl -lne 'print if /a[ ]b/x' или libpcre: echo 'a b' | pcregrep '(?x)a[ ]b'   -  person Tobia    schedule 14.03.2018


Ответы (5)


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

  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. \\ As the OP mentioned, simply use double backslash and space
  2. [\\ ] Избегайте пробела в классе символов, чтобы он интерпретировался буквально
  3. [ ](?x)\\b Поместите встроенный модификатор после класса персонажа
person ctwheels    schedule 13.03.2018
comment
Похоже, что PHP и Python анализируют его по-разному, при этом [ ] считается буквальным пробелом, несмотря на расширенный режим, согласно regex101.com. Я полагаю, будет справедливо назвать это ошибкой, основываясь на этом. Есть ли какие-либо другие ссылки, которые мы могли бы использовать, чтобы окончательно сказать, что это ошибка? - person Corrodias; 14.03.2018
comment
Perl также интерпретирует [ ] как литеральный пробел даже в режиме (?x) (и это специально упоминается в perlre(1p): класс символов в квадратных скобках не затрагивается /x), а Perl изобрел (?x) режим, поэтому я думаю, что это должно быть диспозитивным: это ошибка. - person zwol; 14.03.2018
comment
ОП здесь. Я много лет пишу расширенные/подробные регулярные выражения на Perl, Python, PHP, libpcre и других разновидностях PCRE. Это первый раз, когда я вижу пропуск пробелов в классе символов. Если регулярное выражение Java должно быть совместимым с Perl и PCRE, то да, это ошибка в коде. В противном случае это ошибка в документации, потому что она не указывает на это отклонение от стандарта де-факто. - person Tobia; 14.03.2018
comment
Как сопоставить #? - person Nils Lindemann; 03.09.2018
comment
@Nils сбежать не получается? Не похоже, что вы можете использовать его иначе, вам придется использовать встроенный модификатор - person ctwheels; 03.09.2018
comment
@ctwheels Да, ты прав. Я тестировал это с помощью онлайн-компилятора Scala (Scala использует Java под капотом), но это не сработало. Теперь тестируем его локально, оба работают, (?x)\# и (?x)(?-x:#) — нет ничего более стабильного и удобного, чем командная строка! - person Nils Lindemann; 03.09.2018

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

"[ ]" совпадает с "\\ " или даже с " ".

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

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

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

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

person Socowi    schedule 13.03.2018
comment
Почему тогда пробел не игнорируется в "(?x)\\ \\b"? - person SergiyKolesnikov; 13.03.2018
comment
@SergiyKolesnikov Потому что обратная косая черта убегает от пробела и не позволяет его удалить. - person Socowi; 13.03.2018
comment
@Socowi Вы должны отредактировать свой ответ, чтобы включить свой комментарий. - person CJ Dennis; 14.03.2018
comment
Ошибка не в том, что класс символов пуст, как объясняет ответ Pshemo. - person Bernhard Barker; 14.03.2018

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

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

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

person Pshemo    schedule 13.03.2018

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

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

(?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.

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

Режим свободного интервала в разных вкусах на [ ]:

  • PCRE действует
  • .NET действует
  • Perl действует
  • Ruby действует
  • TCL действует
  • Java 7 Неверный
  • Java 8 Неверный
person revo    schedule 13.03.2018

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

Взгляните на исходный код 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);
person YCF_L    schedule 13.03.2018