Perl 正则表达式何时需要 (?(DEFINE)...)?
结论
题目中的两个正则表达式分别对给定字符串执行时,会得到相同结果。但这两种结构不能普遍互换:(?(DEFINE)...) 跳过定义区后继续匹配,(*ACCEPT) 则立即使当前匹配成功结束。当后面还有约束,或模式被嵌入其他正则表达式时,这个区别就会影响结果。
(?(DEFINE)...) 用于集中存放可调用的子模式,并非所有子模式调用都必须使用这种语法。
为什么原例结果相同?
原例的主体是:
^ (\w*?) (?&SUB) (\w*)
(?&SUB) 调用 SUB,其中的 (?1) 再调用第一组的模式 \w*?。这里的 (?1) 是子模式调用,不是匹配第一组已有内容的反向引用。
主体匹配完成后,两种写法的执行路径不同:
- 遇到
(*ACCEPT),直接成功结束,后面的SUB定义不会顺序执行。 - 遇到
(?(DEFINE)...),跳过其内部定义,然后正常到达模式末尾。
原例后面没有其他匹配要求,因此根据这两种结构的语义,两条路径会产生相同结果。有关定义见 Perl 正则表达式文档。
哪种情况会出现差异?
将模式编译为 qr//,再添加后缀,就能看出区别:
my $early = qr{
(?&SUB)
(*ACCEPT)
(?<SUB>a)
}x;
my $defined = qr{
(?&SUB)
(?(DEFINE)(?<SUB>a))
}x;
my $s = 'ac';
my $first = $s =~ /\A $early b \z/x;
my $second = $s =~ /\A $defined b \z/x;
按照这两个模式的语义,$first 为真,$second 为假:
- 第一种匹配到
a后执行(*ACCEPT),外围的b和\z都被跳过。 - 第二种跳过定义区后继续检查
b,但字符串接下来是c,因此失败。
普通的 qr// 插值和分组不会将 (*ACCEPT) 的作用限制在该片段内。在递归调用或 (??{}) 动态生成的嵌套模式中,它只结束最内层匹配,因此分析其作用范围时需要结合调用方式。参见 (*ACCEPT) 文档。
如果模式之后还要添加边界、后缀或其他检查,使用 (?(DEFINE)...) 可以保留正常的顺序匹配流程。
捕获组和列表上下文需要注意什么?
(?(DEFINE)...) 本身不创建捕获组,但内部的 (?<SUB>...) 和 (D) 都会占用捕获编号。它们在子模式调用期间匹配到的捕获内容,在调用返回后不会作为这些组的最终捕获值保留。perlre 对此有明确说明。
因此,原例有四个捕获组。在不带 /g 的列表上下文匹配中,后两个位置仍会存在,结果为:
("before", "after", undef, undef)
文档建议将定义区放在末尾,这样可以让业务捕获组排在前面,但末尾的 undef 仍会存在。列表上下文并无此语法要求。定义区也会影响后续捕获组的编号,拼接模式时尤其要留意数字子模式调用。
原问题中的 @{^CAPTURE) 有括号笔误,正确写法是 @{^CAPTURE}。它与列表上下文的匹配返回值应分开讨论。
备注:内容仅供参考。
