使用正则表达式中的代码块时,可能会出现“不一致”的匹配结果[Raku]。

7

在检查和测试正则表达式的各个方面时,我偶然发现了一种奇怪而“不一致”的行为。我试图在正则表达式中使用一些代码,但是即使是使用一个空的代码块也会出现相同的行为。

特别让我感到困惑的是,在交换:g和:x修饰符时,匹配结果的区别。

以下代码片段描述了这种“不一致”的行为。

首先,没有代码块:

use v6.d;

if "test1 test2 test3 test4" ~~ m:g/ (\w+) / {
    say ~$_ for $/.list;
}

结果:

test1
test2
test3
test4

然后使用:g修改符和代码块:

use v6.d;

if "test1 test2 test3 test4" ~~ m:g/ (\w+) {} / {
    say ~$_ for $/.list;
}

结果:

test4

最后,使用:x修饰符和代码块。
use v6.d;

if "test1 test2 test3 test4" ~~ m:x(4)/ (\w+) {} / {
    say ~$_ for $/.list;
}

结果:

test1
test2
test3
test4

我原本期望这三个结果是相同的,但事实却令我感到惊讶和失望。

这种行为有何解释吗?


问题已由Jonathan Worthington修复:https://github.com/rakudo/rakudo/commit/1e0474d4fc0d0ab2e0e36121149d706e27af73ae - jakar
1个回答

5

TL;DR 由@jakar提交的问题已被jnthn修复


(在更多测试和代码调查后重写。)

在使用:g和嵌入式块时,对我(和可能也对您)来说,这似乎是一个错误。 $/ 在某种程度上被破坏了。

本答案涵盖了以下内容:

  • 聚焦问题

  • 查看编译器源代码

  • 搜索问题队列并/或提交新问题

聚焦问题

my &debug = {;} # start off doing no debugging
$_ = 'aa';

say       m      / {debug 1} 'a' {debug 2} /; debug 3; # 「a」
say $/ if m      / {debug 1} 'a' {debug 2} /; debug 3; # 「a」

say       m:x(2) / {debug 1} 'a' {debug 2} /; debug 3; # (「a」 「a」)
say $/ if m:x(2) / {debug 1} 'a' {debug 2} /; debug 3; # (「a」 「a」)

say       m:g    / {debug 1} 'a' {debug 2} /; debug 3; # (「a」 「a」)
say $/ if m:g    / {debug 1} 'a' {debug 2} /; debug 3; # 「a」 <--- Uhoh

现在让debug输出有用的信息并运行第一对(无需使用正则表达式副词):
&debug = { say $_, $/.WHICH } # Say location of object bound to `$/`

say       m      / {debug 1} 'a' {debug 2} /; debug 3; # 「a」
# 1Match|66118928
# 2Match|66118928
# 「a」
# 3Match|66118928

say $/ if m      / {debug 1} 'a' {debug 2} /; debug 3; # 「a」
# 1Match|66119072
# 2Match|66119072
# 「a」
# 3Match|66119072

在这两种情况下都会得到相同的简单结果。匹配过程会创建一个Match对象,并一直使用同一个对象。
现在考虑使用“:x(2)”副词的两种变体:
say       m:x(2) / {debug 1} 'a' {debug 2} /; debug 3; # (「a」 「a」)
# 1Match|66119936
# 2Match|66119936
# 1Match|66120080
# 2Match|66120080
# 1Match|66120224
# (「a」 「a」)
# 3List|67612624

say $/ if m:x(2) / {debug 1} 'a' {debug 2} /; debug 3; # (「a」 「a」)
# 1Match|66120368
# 2Match|66120368
# 1Match|66120512
# 2Match|66120512
# 1Match|66120656
# (「a」 「a」)
# 3List|67612672

这次匹配过程创建了一个Match对象,并在第一遍使用它,然后在第二遍使用第二个匹配对象,在第三遍使用第三个匹配对象,直到无法匹配第三个'a'(因此相应的debug 2没有被调用)。在m..../.../调用结束时,它创建了一个List对象并将该对象绑定到$/
接下来我们运行两个:g中的第一个案例:
say       m:g    / {debug 1} 'a' {debug 2} /; debug 3; # (「a」 「a」)
# 1Match|66119216
# 2Match|66119216
# 1Match|66119360
# 2Match|66119360
# 1Match|66119504
# (「a」 「a」)
# 3Match|66119504

x:(2) 情况一样,我们尝试第三次匹配仍然失败。但是匹配过程并没有返回一个 List 而是返回一个 Match 对象,而且它是在第三次尝试中创建的。(这让我感到惊讶。)

最后是 "Uhoh" 情况:

say $/ if m:g    / {debug 1} 'a' {debug 2} /; debug 3; # 「a」 <--- Uhoh
# 1Match|66119648
# 2Match|66119648
# 1Match|66119792
# 2Match|66119792
# 「a」
# 3Match|66119792

值得注意的是,预期的第三次通行似乎没有开始。

查看编译器源代码

探索相关的源代码可能是有价值的。如果您或其他读者感兴趣,以及这是否是一个错误,我将在此处写下相关内容,以便对正在修复问题的人有用。

据我所知,在正则表达式中的代码块会导致生成 AST 节点,在这里 插入一个子节点到该块中的语句之前,执行绑定操作:

                    :op('bind'),

                    QAST::Var.new( :name('$/'), :scope('lexical') ),

                    QAST::Op.new(
                        QAST::Var.new( :name('$¢'), :scope('lexical') ),
                        :name('MATCH'),
                        :op('callmethod')
                    )

我的理解是,它插入的代码绑定了词法符号$ /到在运行块之前紧接着绑定到词法符号$ ¢的对象上调用.MATCH方法的结果。

文档中有一个关于章节,我引用一句话:

$/$ ¢之间的主要区别是作用域:后者仅在正则表达式内部具有值

我想知道为什么存在$ ¢以及其他区别是什么。

继续阅读...

我看到了raku级别的.MATCH。但它几乎什么都没做。所以我认为相关的代码是这里

此时我会暂停。我可能会在以后的编辑中继续。

搜索问题队列和/或提交新问题

如果有人在接下来的几天内提供了答案,证明您展示的不是错误,或者已被视为错误,那么就可以了。

否则,请考虑自己搜索问题队列和/或在您认为最合适的问题队列(默认为/rakudo/rakudo/issues)中开始一个新问题。

我已经在编写本回答的过程中搜索了四个github.com问题队列:

我搜索了两个关键词,希望能发现一个已存在的问题(“global”和“publish”)。没有相关的匹配问题。也许你可以寻找其他你认为筛选器可能使用的关键词。

如果您要提交问题,请考虑添加您的测试用例、我的测试用例或其他变体,并将其转换为标准烤肉测试用例(如果您知道如何进行操作)。


1
感谢您的时间和努力。我会等待几天以获取回复,如果没有结果,我将按照您的指示继续进行!在问题最终解决之前,我不会将您的答案标记为“已接受”。 - jakar
1
在 Github(rakudo)上提交了一个错误报告:https://github.com/rakudo/rakudo/issues/3554 - jakar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接