Perl正则表达式引擎错误?

时间:2017-09-21 21:27:19

标签: regex perl validation debugging pcre

我一直在尝试编写一个正则表达式来验证文件,以确保它遵循特定的格式。该文件应该有version();行,然后是一个或多个element();块。

以下是有效文件的示例:

version(1.0);

element
(
);

element
(
);

element
(
);

作为测试,我创建了以下Perl示例:

use strict;
use warnings;

my $text = <<'END_TEXT';
version(1.0);

element
(
);

garbage <--- THIS SHOULD NOT MATCH!

element
(
);

element
(
);

END_TEXT

my $rx_defs = qr{(?(DEFINE)
    (?<valid_text>
        \A\s*(?&version)\s*
        (?: (?&element) \s* )+
        \s*\Z
    )
    (?<version>
        version\(.+?\);
    )
    (?<element>
        element\s*
        (?&element_body);
    )
    (?<element_body>
        \( (?: [^()]++ | (?&element_body) )* \)
    )
)}xms;

if ($text =~ m/(?&valid_text)$rx_defs/) {
    print "match";
}

正如你所看到的,文本中有一行“垃圾”应该使它无效,但出于某种原因,Perl似乎仍然认为这个文本是有效的!当我运行此代码时,它会生成输出:

match

我花了好几个小时试图找出我的正则表达式出了什么问题,但我只是没有看到它。我甚至使用online regular expression tester测试了正确的正则表达式,根据测试我的正则表达式应该正常工作! (如果要在格式有效时看到它确实匹配,请尝试删除“垃圾”行。)

这让我整天彻底难过,让我想知道Perl正则表达式引擎本身是否存在错误。有人可以告诉我为什么这不匹配,不应该吗?

我使用的是perl v5.20.1

2 个答案:

答案 0 :(得分:4)

来自http://www.pcre.org/current/doc/html/pcre2compat.html的PCRE文档:

  
      
  1. 子程序调用(无论是否递归)被视为直到PCRE2版本10.23的原子组,但是从版本10.30开始,这已经改变,现在支持回溯到子程序调用,如Perl。
  2.   

regex101使用PHP运行PCRE。根据{{​​3}},PHP仅支持PCRE1(8.x分支)。因此,regex101不支持回溯到子程序调用。

......这正是这里发生的事情:

  • 我们进入(?&valid_text>)并尝试匹配\A\s*(?&version)\s*
  • \A(字符串开头)和\s*(可选空格)很简单
  • (?&version)执行version\(.+?\);
  • 这匹配输入的以下部分:

    version();
    
    element
    (
    );
    

    version(字面匹配。下一个字符).+?使用(需要至少一个字符匹配)。然后.+?慢慢消耗越来越多的字符(它不贪婪),直到它达到);。第一次发生这种情况是在消费; element (之后,这就是我们现在停止的地方。

  • (?&version)调用返回
  • 我们使用以下任何空格
  • 下一部分是(?: (?&element) \s* )+,即一个或多个元素,每个元素后跟可选的空格
  • (?&element)执行element\s*,即必须以element
  • 开头
  • 我们在输入中的当前位置是garbage ...,因此无法

此时正则表达式引擎试图回溯。在PCRE中&lt; 10.30,唯一可以回溯的部分是\s*(即&#34;可选的空格&#34;位),但匹配较少的空白字符也不会导致成功匹配,所以整个事情失败很快。

然而,在Perl中我们可以回溯到子例程调用:我们重新输入(?&version)并让.+?匹配更多字符(直到找到);的下一个出现),然后重试(?&element)。这最终会让(?&version)消耗garbage和以下element,从而使整个正则表达式成功。

  

有人可以告诉我为什么这不匹配的时候不应该?

我不明白为什么你认为它不应该匹配。 : - )

它在PHP中不匹配的唯一原因是它使用的旧PCRE版本的限制。

答案 1 :(得分:3)

非贪婪的比赛一旦满足就不会停止。它试图尽快继续。如果正则表达式的其余部分无法匹配,则仍会发生回溯 - 但对于非贪婪的量词,回溯意味着匹配更多。

避免这种情况的一种可能性在于回溯控制。例如,您可能希望在version最初匹配后禁止回溯。我们可以通过(?> ...)构造做到这一点。这与外部模式独​​立地匹配包含的模式。如果模式的其余部分失败,则回溯将不会继续进入包含的模式,但会跳过整个包含的模式。描述这个有点困难,请参阅perldoc perlre了解详细信息。

+添加到量词(例如++?+*+)与(?> ...)具有类似的效果。在高效的正则表达式中,首选这些无回溯量词和(?>...)组是非常可取的。

具体来说,替换

(?<valid_text>
    \A\s*(?&version)\s*
    (?: (?&element) \s* )+
    \s*\Z
)

(?<valid_text>
    \A\s*(?>(?&version))\s*
    (?: (?&element) \s* )++
    \s*\Z
)

作为另一种选择,您可以使用(*PRUNE)回溯控制动词。遇到PRUNE命令后,不会发生超过该点的回溯。这使得匹配到目前为止选择的替代方案。

(?<valid_text>
    \A\s*(?&version)\s* (*PRUNE)
    (?: (?&element) \s* )+
    \s*\Z
)