Msgstr "Переменная длина lookbehind не реализована", но это не переменная длина
У меня очень сумасшедшее регулярное выражение, которое я пытаюсь диагностировать. Это также очень долго, но я сократил его до следующего сценария. Запустите с помощью 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".
Я надеюсь, вы можете помочь с несколькими вопросами:
- Я не понимаю, почему эта ошибка возникла, потому что все возможные значения lookbehind имеют 7 символов: "понедельник", "пятница", "воскресенье", "август".
- Я сам не писал это регулярное выражение, и я не уверен, как интерпретировать синтаксис
(?i)
и (?-i)
. Когда я избавляюсь от (?i)
ошибка действительно уходит. Как perl интерпретировать эту часть регулярного выражения? Я бы подумал, что первые два символа оцениваются как "необязательные литеральные круглые скобки", за исключением того, что скобки не экранированы, и в этом случае я получаю другую синтаксическую ошибку, потому что закрывающие круглые скобки не будут сопоставляться. - Такое поведение начинается где-то между Perl 5.16.3_64 и 5.26.1_64, по крайней мере, в Strawberry Perl. Первая версия в порядке с кодом, а вторая - нет. Почему это началось?
Ответы
Ответ 1
Я уменьшил вашу проблему до этого:
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.
См. Тесно связанную дискуссию здесь
Ответ 2
Это потому, что st
может быть лигатурой. То же самое происходит с fi
и ff
:
#!/usr/bin/perl
use warnings;
use strict;
use utf8;
my $fi = 'fi';
print $fi =~ /fi/i;
Итак, представьте себе что-то вроде fi|fi
где, действительно, длины альтернатив не совпадают.
Ответ 3
st
может быть представлена в 1-символьной стилистической лигатуре как st
или ſt
, поэтому ее длина может быть 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
Они соответственно представляют собой ff
, fi
, fl
, ß
и st
/ſt
лигатуры.
(ſt
представляет ſt
, используя устаревший длинный символ s, он соответствует st
и не соответствует ft
.)
Perl также поддерживает оставшиеся стилистические лигатуры, ffi
и ffl
для ffi
и ffl
, хотя в этом контексте это не примечательно, поскольку у lookbehind уже есть проблемы с ff
и fi
/fl
отдельно.
Будущие выпуски 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. Указание дважды дает дополнительную защиту.
Ответ 4
Положите (?i)
после lookbehind:
(?<!(Mon|Fri|Sun)day |August )(?i)abcd(?-i)
или же
(?<!(Mon|Fri|Sun)day |August )(?i:abcd)
Для меня это кажется ошибкой.