“可变长度lookbehind未实现”,但它不是可变长度

时间:2018-05-15 17:35:45

标签: regex perl

我有一个非常疯狂的正则表达式,我正在尝试诊断。它也很长,但我把它简化为以下脚本。使用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 :(得分:75)

我已将问题减少到:

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 /非ASCII匹配(例如“k”与“\ N {KELVIN SIGN}”),请指定“a”两次,例如/aai/aia。 (第一次出现“a”限制\d等,第二次出现会增加“/ i”限制。)但是,请注意,ASCII范围之外的代码点将使用{{1的Unicode规则匹配,所以修饰符并不真正将事物限制为ASCII; 它只是禁止ASCII和非ASCII的混合

See a closely related discussion here

答案 1 :(得分:21)

那是因为st可以是一个结扎线。 fiff

也是如此
#!/usr/bin/perl
use warnings;
use strict;

use utf8;

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

想象一下像fi|fi之类的东西,确实,替代品的长度并不相同。

答案 2 :(得分:2)

st可以用表示为1个字符stylistic ligature,因此其长度可以是2或1。

使用bash命令快速查找perl的2→1字符连字的完整列表:

$ 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 character;它与st匹配,而匹配{{ 1}}。)

Perl还支持ft的剩余文体连字,ffi,尽管在这种情况下这并不值得注意,因为看起来已经存在问题<{1}}和ffl / 分开。

perl的未来版本可能包含更多样式的连字,但所有剩余的都是特定于字体的(例如Linux Libertine具有ch的风格连字)或具有风格的格式(例如的荷兰语ijct的过时西班牙语。对于不完全可互换的连线进行此处理似乎不合适(没有人会接受ij ll),尽管还有其他情况,例如包括dœs感谢它的uppercase form being SS

Perl 5.16.3(以及类似的旧版本)仅在does(对于ß)偶然发现并且无法扩展外观中的其他连字(它们具有固定宽度且不匹配)。我没有找出错误修正来详细列出哪些版本受到影响。

Perl 5.14引入了连字支持,因此早期版本没有这个问题。

变通方法

ss的解决方法(只有第一个会正确避免ß):

  • /(?<!August)x/i(绝对全面)
  • August(只是后视中的/(?<!Augus[t])(?<!Augu(?=st).)x/i是“ASCII-safe”²)
  • /(?<!Augu(?aa:st))x/i(整个后方是“ASCII-safe”²)
  • st(整个正则表达式是“ASCII-safe”²)
  • /(?<!(?aa)August)x/i(打破结扎寻求¹)
  • /(?<!August)x/iaa(略有不同,匹配更多)
  • /(?<!Augus[t])x/i(区分大小写的/(?<!Augus.)x/i,与/(?<!Augu(?-i:st))x/i不匹配)

这些玩具删除不区分大小写的修饰符¹或在各个地方添加 ASCII安全修饰符²,通常要求正则表达式编写者专门知道变量-width ligature。

第一个变体(它是唯一的综合变体)将变量宽度与两个lookbehinds匹配:首先是六个字符版本(没有连字,如下面的第一个引用中所述),第二个是任何连字,使用 st的前向预测(宽度为零!)(包括连字),然后用AugusTx

计算单个字符的宽度

perlre man page的两个部分:

¹不区分大小写的修饰符st&amp;连字

  

有许多Unicode字符与序列匹配   .下的多个字符。例如,“LATIN SMALL LIGATURE   FI“应该与序列/i匹配.Perl目前无法使用   当多个字符在模式中时,执行此操作   分组之间,或者当一个或多个被量化时。因此

     
/i

²ASCII安全修饰符fi(perl 5.14 +)

  

禁止ASCII /非ASCII匹配(例如"\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),   指定k两次,例如\N{KELVIN SIGN}a。 (首先   /aai的出现限制了/aia等,第二次出现   添加a限制。)但是,请注意代码指向外部   ASCII范围将使用Unicode规则进行\d匹配,因此修饰符   并没有真正将事情限制在ASCII;它只是禁止了   ASCII和非ASCII的混合。

     

总而言之,此修饰符为应用程序提供保护   不希望暴露于所有Unicode。指定它两次给出   增加了保护。

答案 3 :(得分:0)

在lookbehind之后放置(?i)

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

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

对我而言,这似乎是一个错误。