«Задняя часть переменной длины не реализована», но это не переменная длина

54

У меня очень сумасшедшее регулярное выражение, которое я пытаюсь диагностировать. Это также очень долго, но я сократил его до следующего сценария. Запустите с помощью Strawberry Perl v5.26.2.

use strict;
use warnings;

my $text = "M Y H A P P Y T E X T";
my $regex = '(?i)(?<!(Mon|Fri|Sun)day |August )abcd(?-i)';

if ($text =~ m/$regex/){
    print "true\n";
}
else {
    print "false\n";
}

Это дает ошибку "Переменная длина lookbehind не реализована в regex".

Я надеюсь, вы можете помочь с несколькими вопросами:

  1. Я не понимаю, почему эта ошибка возникла, потому что все возможные значения lookbehind имеют 7 символов: "понедельник", "пятница", "воскресенье", "август".
  2. Я сам не писал это регулярное выражение, и я не уверен, как интерпретировать синтаксис (?i) и (?-i). Когда я избавляюсь от (?i) ошибка действительно уходит. Как perl интерпретировать эту часть регулярного выражения? Я бы подумал, что первые два символа оцениваются как "необязательные литеральные круглые скобки", за исключением того, что скобки не экранированы, и в этом случае я получаю другую синтаксическую ошибку, потому что закрывающие круглые скобки не будут сопоставляться.
  3. Такое поведение начинается где-то между Perl 5.16.3_64 и 5.26.1_64, по крайней мере, в Strawberry Perl. Первая версия в порядке с кодом, а вторая - нет. Почему это началось?
  • 1
    Интересно, что он работает на regex101.
  • 5
    Проблема может быть упрощена еще больше: /(?<!August )a/i уже говорит «Lookbhind переменной длины ...», но удалите одну букву из August и она работает нормально. Удалите /i и он отлично работает. И если вы хотите смеяться: August не работает, как показано выше. Abcdst не работает. Но Abcdet работает.
Показать ещё 5 комментариев
Теги:

4 ответа

76
Лучший ответ

Я уменьшил вашу проблему до этого:

my $text = 'M Y H A P P Y T E X T';
my $regex = '(?<!st)A';
print ($text =~ m/$regex/i ? "true\n" : "false\n");

Из-за наличия модификатора /i (без учета регистра) и наличия определенных комбинаций символов, таких как "ss" или "st" которые могут быть заменены на Typographic_ligature, что приводит к переменной длине (/August/i соответствует, например, на обоих AUGUST (6 символов) и august (5 символов, последний - U + FB06)).

Однако если мы удалим модификатор /i (нечувствительный к регистру), то он работает, потому что типографские лигатуры не совпадают.

Решение. Используйте модификаторы aa, т.е.:

/(?<!st)A/iaa

Или в вашем регулярном выражении:

my $text = 'M Y H A P P Y T E X T';
my $regex = '(?<!(Mon|Fri|Sun)day |August )abcd';
print ($text =~ m/$regex/iaa ? "true\n" : "false\n");

От перла:

Чтобы запретить совпадения ASCII/не-ASCII (например, "k" с "\n {KELVIN SIGN}"), укажите "a" дважды, например /aai или /aia. (Первое вхождение "a" ограничивает \d и т.д., А второе вхождение добавляет ограничения "/i".) Но обратите внимание, что в пунктах кода вне диапазона ASCII будут использоваться правила Юникода для соответствия /i, поэтому модификатор действительно не ограничивает вещи только ASCII; он просто запрещает смешивание ASCII и не-ASCII.

См. Тесно связанную дискуссию здесь

  • 10
    Чтобы внести свой вклад в это: в perldiag также упоминается: «В /i есть неочевидные правила Unicode, которые могут различаться по разному, но которые вы, возможно, не думаете». Другим решением, кроме ключа /aa , может быть обращение строки и сделать это с помощью lookaheads, если вам нужна поддержка Unicode и обходные пути переменной длины.
  • 0
    Знаете ли вы, почему такого поведения не было в более ранних версиях Perl? 5.16.3_64 не выдает ошибку, но 5.26.1_64, по крайней мере, в Strawberry Perl.
Показать ещё 4 комментария
21

Это потому, что st может быть лигатурой. То же самое происходит с fi и ff:

#!/usr/bin/perl
use warnings;
use strict;

use utf8;

my $fi = 'fi';
print $fi =~ /fi/i;

Итак, представьте себе что-то вроде fi|fi где, действительно, длины альтернатив не совпадают.

1

st может быть представлена в 1-символьной стилистической лигатуре как или , поэтому ее длина может быть 2 или 1.

Быстро найти полный список 2 → 1-символьных лигатур с помощью команды bash:

$ perl -e 'print $^V'
v5.26.2
$ for lig in {a..z}{a..z}; do \
    perl -e 'print if /(?<!'$lig')x/i' 2>/dev/null || echo $lig; done

ff fi fl ss st

Они соответственно представляют собой , , , ß и / лигатуры.
( представляет ſt, используя устаревший длинный символ s, он соответствует st и не соответствует ft.)

Perl также поддерживает оставшиеся стилистические лигатуры, и для ffi и ffl, хотя в этом контексте это не примечательно, поскольку у lookbehind уже есть проблемы с и / отдельно.

Будущие выпуски perl могут включать в себя более стилистические лигатуры, хотя все, что осталось, зависит от шрифтов (например, Linux Libertine имеет стилистические лигатуры для ct и ch) или debatably stylistic (например, голландский ij для ij или устаревший испанский для ll). Это не представляется целесообразным, чтобы это лечение лигатур, которые не являются полностью взаимозаменяемыми (никто не будет принимать dœs для does), хотя существуют и другие сценарии, такие как включение ß благодаря своей заглавной форме будучи SS.

Perl 5.16.3 (и аналогично старые версии) только спотыкаются на ss (для ß) и не могут расширять другие лигатуры в lookbehinds (они имеют фиксированную ширину и не совпадают). Я не искал исправления, чтобы точно определить, какие версии затронуты.

Perl 5.14 представила поддержку лигатуры, поэтому более ранние версии этой проблемы не имеют.

обходные

Методы обхода для /(?<!August)x/i (только первый будет правильно избегать August):

  • /(?<!Augus[t])(?<!Augu(?=st).)x/i (абсолютно полный)
  • /(?<!Augu(?aa:st))x/i (только st в lookbehind является "ASCII-безопасным" ²)
  • /(?<!(?aa)August)x/i (весь lookbehind является "ASCII-безопасным" ²)
  • /(?<!August)x/iaa (все регулярное выражение "ASCII-safe" ²)
  • /(?<!Augus[t])x/i (прерывает поиск лигатуры ¹)
  • /(?<!Augus.)x/i (немного отличается, соответствует больше)
  • /(?<!Augu(?-i:st))x/i (чувствительный к регистру st в lookbehind, не будет соответствовать AugusTx)

Эта игрушка с удалением модифицирующего модификатора корпуса [CN00 ]¹ или добавлением ASCII-безопасного модификатора² в разных местах, часто требуя, чтобы писатель-реджикс специально знал о лигатуре переменной ширины.

Первая вариация (которая является единственной всеобъемлющей) соответствует ширине переменной с двумя lookbehinds: сначала для версии с шестью символами (без лигатур, как указано в первой цитате ниже), а вторая для любых лигатур, с использованием прямого вида (который имеет ноль width!) для st (включая лигатуры), а затем учет его ширины одного символа с a .

Два сегмента страницы perlre man:

¹ Случай -i нечувствительный модификатор /i & ligatures

Существует несколько символов Unicode, которые соответствуют последовательности из нескольких символов под /i. Например, "LATIN SMALL LIGATURE FI" должно соответствовать последовательности fi. Perl в настоящее время не в состоянии сделать это, когда несколько символов находятся в шаблоне и разделены между группировками, или когда один или несколько количественно определены. таким образом

"\N{LATIN SMALL LIGATURE FI}" =~ /fi/i;          # Matches [in perl 5.14+]
"\N{LATIN SMALL LIGATURE FI}" =~ /[fi][fi]/i;    # Doesn't match!
"\N{LATIN SMALL LIGATURE FI}" =~ /fi*/i;         # Doesn't match!
"\N{LATIN SMALL LIGATURE FI}" =~ /(f)(i)/i;      # Doesn't match!

² ASCII-безопасный модификатор /aa (perl 5. 14+)

Чтобы запретить совпадения ASCII/не-ASCII (например, k с \N{KELVIN SIGN}), укажите a дважды, например /aai или /aia. (Первое вхождение a ограничивает \d и т.д., А второе вхождение добавляет ограничения /i.) Но обратите внимание, что в кодовых точках вне диапазона ASCII будут использоваться правила Unicode для /i, поэтому модификатор не действительно ограничивают вещи только ASCII; он просто запрещает смешивание ASCII и не-ASCII.

Подводя итог, этот модификатор обеспечивает защиту для приложений, которые не хотят быть доступными ко всем Unicode. Указание дважды дает дополнительную защиту.

-1

Положите (?i) после lookbehind:

(?<!(Mon|Fri|Sun)day |August )(?i)abcd(?-i)

или же

(?<!(Mon|Fri|Sun)day |August )(?i:abcd)

Для меня это кажется ошибкой.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню