«Переменная длина не реализована», но это не переменная длина - PullRequest
0 голосов
/ 15 мая 2018

У меня есть очень сумасшедшее регулярное выражение, которое я пытаюсь диагностировать.Это также очень долго, но я сократил его до следующего сценария.Запускать с использованием 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";
}

Это выдает ошибку «Просмотр переменной длины не реализован в регулярном выражении.»

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

  1. Я не понимаю, почему возникла эта ошибка, потому что все возможные значения просмотра за спиной - 7 символов: «Понедельник», «Пятница», «Воскресенье», «Август».
  2. Я сам не писал это регулярное выражение, и я не уверен, как интерпретировать синтаксис (?i) и (?-i).Когда я избавляюсь от (?i), ошибка на самом деле исчезает.Как Perl интерпретирует эту часть регулярного выражения?Я думаю, что первые два символа оцениваются как «необязательные литеральные скобки», за исключением того, что круглые скобки не экранируются, а также в этом случае я получаю другую синтаксическую ошибку, потому что закрывающие скобки не будут соответствовать.
  3. Это поведение начинается где-то между Perl 5.16.3_64 и 5.26.1_64, по крайней мере, в Strawberry Perl.Первая версия подходит для кода, а вторая - нет.Почему это началось?

Ответы [ 4 ]

0 голосов
/ 08 июня 2018

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

Быстрый поиск полного perlсписок из 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 с использованием устаревшего символа long s ; соответствует st и , а не соответствует ft.)

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

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

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

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

Временные решения

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

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

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

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

Два сегмента perlre справочной страницы :

¹ Модификатор без учета регистра /i & лигатуры

Существует несколько символов 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.

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

0 голосов
/ 15 мая 2018

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

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

use utf8;

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

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

0 голосов
/ 15 мая 2018

Я свел вашу проблему к этому:

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");

С perlre :

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

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

0 голосов
/ 15 мая 2018

Положите (?i) после взгляда:

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

или

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

Мне кажется, это ошибка.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...