Wait the light to fall

带退出码的增强

焉知非鱼

categories: [“Raku”] #

在我的上一篇文章中,我找到了一个很好的方法来匹配 exitcode。我想把它扩展到与 STDERR 匹配,如果 exitcode 是非零。我已经有了一种方法来捕获一个管道的所有错误流。

px<find /tmp»  px<your-script-here>  @a :stderr(Capture);

我使用 Capture(类型对象)的方式和我们使用 * 或 Whatever 一样。它只是表示,神奇的东西应该发生。这个神奇的东西归结为把所有 STDERR 流粘到一个二维数组中。如果我想处理错误,我可能想对 exitcode、shell 命令的名称和它输出到 STDERR 的部分进行匹配。像下面这样的语法会很好。

my $ex = Exitcode.new: :STDERR(<abc def ghi>), :exitint(42), :command<find>;
given $ex {
    whenfind& 42 & /def\s(\S+)/ {
        notefind terminated with 42 and $0;
    }
}

事实证明,在 Junction 中得到对 StrNumericRegex 的匹配是很容易做到的。我们需要做的就是增强 Regex

augment class Regex {
    multi method ACCEPTS(Regex:D: Shell::Piping::Exitcode:D $ex) {
        ?$ex.STDERR.join(„\n).match(self)
    }
}

augment class Int {
    multi method ACCEPTS(Int:D: Shell::Piping::Exitcode:D $ex) {
        self.ACCEPTS($ex.exitint)
    }
}

augment class Str {
    multi method ACCEPTS(Str:D: Shell::Piping::Exitcode:D $ex) {
        self.ACCEPTS($ex.command)
    }
}

这只对匹配有效。我无法用这种方式将 (\S+) 捕获到 $0。我们知道并喜欢 Str.match 能做到这一点-看起来很容易。让我们从它那里学代码吧!

proto method match(|) { $/ := nqp::getlexcaller('$/'); {*} }

所以 .match 要做的第一件事就是将本地的 $/ 与调用者的 $/ 绑定。因此,任何对本地版本的改变都会实际改变调用者的版本。我试着模仿了一下,但没有成功。无论是 nqp 方式还是稍微干净的 Raku 方式都不行。

augment class Regex {
    multi method ACCEPTS(Regex:D: Shell::Piping::Exitcode:D $ex) {
         CALLER::<$/> := $/;

        ?$ex.STDERR.join(„\n).match(self)
    }
}

至少在 given/when 块中不行。简单的说 $ex ~~ defs(\S+); 在全局作用域内就能正常工作。我甚至得到了一个错误信息:OUTER 中不存在 \$。考虑到根据定义,它应该存在于每一个块中,这很奇怪。

我们可以用下面的构造来检查调用者的词法作用域。

say CALLER::.keys;
say Backtrace.new.gist;

这将输出词法和有多少堆栈帧。而事实上,given/when 确实引入了一个额外的栈帧,它包含 $_ 但不包含 $/(以及其他一些零碎的东西)。由于 caller 是一个 Stash,什么是转半的 Hash,我们可以像往常一样使用 :existence,再增加一个 caller。

augment class Regex {
    multi method ACCEPTS(Regex:D: Shell::Piping::Exitcode:D $ex) {
        CALLER::<$/>:exists ?? (CALLER::<$/> := $/) !! (CALLER::CALLER::<$/> := $/);

        ?$ex.STDERR.join(„\n).match(self)
    }
}

现在,它的工作原理与设想的一样。

然而-实际上 HOWEVER-我正在增强一个内置类。这是有风险的。我不是一个人。我们得到了一个 ircbot,可以在生态系统的源代码中 grep。

20:01 < gfldex> greppable6: augment class
20:01 < greppable6> gfldex, 49 lines, 26 modules: https://gist.github.com/4088b5b8e7b51d94276b15500c240a5f

当我们编写模块时,我们会引入自定义名称所在的作用域。一个模块的用户可以决定将这些名字导入到用户控制的作用域中。当我们增强模块时,我们将一个新的名字引入到一个全局的作用域中。如果两个模块有相同的想法会怎样?如果注入的方法实际上是一个多,那么它很可能会工作。但它不一定要这样。当两个或更多的 multi 候选者具有相同的优先权时,第一个被发现的将获胜。通过在一个非 multi 上使用 augment,我们有机会得到一个错误信息。如果我们通过 MOP 添加一个方法,我们就不会。如果我想在 when 语句或代码块中允许智能匹配,我需要提供在 ~~ 的 LHS 上有我的自定义类的能力。所以没有办法绕过 augment。我甚至可以说,这就是在语言设计中加入 augment 的原因。当我们考虑到 Raku 越来越不年轻,语言版本不断增加时,情况就会变得更糟。一个测试马虎的模块可能会与语言版本增加的新方法发生碰撞。我们是否应该在 compunit 中强制执行一个带有语言版本的使用语句,并带有一个增强语句?

这真的困扰着我。我们可以使用 META6 并添加字段 augments: "Cool,Int,Regex"。这样 zef 就有机会发现碰撞并提供一个警告。遗憾的是,我们没有办法强制执行这个功能(因为 EVAL)。我会花更多的时间来思考这个问题,可能会开始一个解决问题的问题。