У меня очень сумасшедшее регулярное выражение, которое я пытаюсь диагностировать. Это также очень долго, но я сократил его до следующего сценария. Запустите с помощью 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".
Я надеюсь, вы можете помочь с несколькими вопросами:
(?i)
и (?-i)
. Когда я избавляюсь от (?i)
ошибка действительно уходит. Как perl интерпретировать эту часть регулярного выражения? Я бы подумал, что первые два символа оцениваются как "необязательные литеральные круглые скобки", за исключением того, что скобки не экранированы, и в этом случае я получаю другую синтаксическую ошибку, потому что закрывающие круглые скобки не будут сопоставляться.Я уменьшил вашу проблему до этого:
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.
perldiag
также упоминается: «В /i
есть неочевидные правила Unicode, которые могут различаться по разному, но которые вы, возможно, не думаете». Другим решением, кроме ключа /aa
, может быть обращение строки и сделать это с помощью lookaheads, если вам нужна поддержка Unicode и обходные пути переменной длины.
Это потому, что st
может быть лигатурой. То же самое происходит с fi
и ff
:
#!/usr/bin/perl
use warnings;
use strict;
use utf8;
my $fi = 'fi';
print $fi =~ /fi/i;
Итак, представьте себе что-то вроде fi|fi
где, действительно, длины альтернатив не совпадают.
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
& 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!
/aa
(perl 5. 14+)Чтобы запретить совпадения ASCII/не-ASCII (например,
k
с\N{KELVIN SIGN}
), укажитеa
дважды, например/aai
или/aia
. (Первое вхождениеa
ограничивает\d
и т.д., А второе вхождение добавляет ограничения/i
.) Но обратите внимание, что в кодовых точках вне диапазона ASCII будут использоваться правила Unicode для/i
, поэтому модификатор не действительно ограничивают вещи только ASCII; он просто запрещает смешивание ASCII и не-ASCII.Подводя итог, этот модификатор обеспечивает защиту для приложений, которые не хотят быть доступными ко всем Unicode. Указание дважды дает дополнительную защиту.
Положите (?i)
после lookbehind:
(?<!(Mon|Fri|Sun)day |August )(?i)abcd(?-i)
или же
(?<!(Mon|Fri|Sun)day |August )(?i:abcd)
Для меня это кажется ошибкой.
/(?<!August )a/i
уже говорит «Lookbhind переменной длины ...», но удалите одну букву изAugust
и она работает нормально. Удалите/i
и он отлично работает. И если вы хотите смеяться:August
не работает, как показано выше.Abcdst
не работает. НоAbcdet
работает.