Raku:捕获标记的效果丢失 "higher up"

Raku: effect of capture markers is lost "higher up"

以下 Raku 脚本:

#!/usr/bin/env raku
use v6.d;

grammar MyGrammar
{
    rule TOP { <keyword> '=' <value> }
    token keyword { \w+ }
    token value { <strvalue> | <numvalue> }
    token strvalue { '"' <( <-["]>* )> '"' }
    token numvalue { '-'? \d+ [ '.' \d* ]? }
}

say MyGrammar.parse('foo = 42');
say MyGrammar.parse('bar = "Hello, World!"');

具有以下输出:

「foo = 42」
 keyword => 「foo」
 value => 「42」
  numvalue => 「42」
「bar = "Hello, World!"」
 keyword => 「bar」
 value => 「"Hello, World!"」
  strvalue => 「Hello, World!」

对于第二项,请注意 strvalue 包含不带引号的字符串值,正如捕获市场 <( ... )> 所预期的那样。 然而,令我惊讶的是,引号 包含在 value.

有办法解决这个问题吗?

TL;DR 使用“多重分派”。[1,2] 见@user0721090601 对事物为何如此的详尽解释的回答。如果您希望您的数字语法与 Raku 的语法相匹配,请参阅 @p6steve 对您的语法进行真正明智的更改。

多重分派解决方案

Is there a way around this?

一种方法是切换到显式多重分派。

您目前有一个 value 令牌,它调用专门命名的值变体:

    token value { <strvalue> | <numvalue> }

将其替换为:

    proto token value {*}

然后根据语法multiple dispatch targeting规则重命名调用的token,所以语法变成:

grammar MyGrammar
{
    rule TOP { <keyword> '=' <value> }
    token keyword { \w+ }
    proto token value {*}
    token value:str { '"' <( <-["]>* )> '"' }
    token value:num { '-'? \d+ [ '.' \d* ]? }
}

say MyGrammar.parse('foo = 42');
say MyGrammar.parse('bar = "Hello, World!"');

这显示:

「foo = 42」
 keyword => 「foo」
 value => 「42」
「bar = "Hello, World!"」
 keyword => 「bar」
 value => 「Hello, World!」

默认情况下,这不会捕获单个交替。我们可以坚持“多重分派”,但重新引入 sub-captures:

的命名
grammar MyGrammar
{
    rule TOP { <keyword> '=' <value> }
    token keyword { \w+ }
    proto token value { * }
    token value:str { '"' <( $<strvalue>=(<-["]>*) )> '"' }
    token value:num { $<numvalue>=('-'? \d+ [ '.' \d* ]?) }
}

say MyGrammar.parse('foo = 42');
say MyGrammar.parse('bar = "Hello, World!"');

显示:

「foo = 42」
 keyword => 「foo」
 value => 「42」
  numvalue => 「42」
「bar = "Hello, World!"」
 keyword => 「bar」
 value => 「Hello, World!」
  strvalue => 「Hello, World!」

惊喜

to my surprise, the quotes are included in value.

一开始我也很惊讶[3]

但至少在以下方面,当前的行为对我来说也是有意义的:

  • 现有行为在某些情况下是有价值的;

  • 如果我预料到这一点也就不足为奇了,我认为在其他情况下我可能会做到这一点;

  • 如果 想要的,但实际上却像您(和我)最初预期的那样工作,那么很难看出如何获得当前行为;

  • 有一个解决方案,如上所述。

脚注

[1] 多重调度的使用[2] a 解决方案,但考虑到原始问题,imo 似乎过于复杂。也许有一个更简单的解决方案。也许有人会在您问题的另一个答案中提供它。如果没有,我希望有一天我们至少有一个更简单的解决方案。但是,如果我们多年没有得到一个,我也不会感到惊讶。我们有上述解决方案,还有很多其他事情要做。

[2] 而你 可以 声明,比如说,method value:foo { ... }并编写一个方法(假设每个这样的方法 returns 一个匹配对象),我不认为 Rakudo 使用通常的多方法调度机制来调度 non-method 规则交替,而是使用 NFA.

[3] 有些人可能会争辩说它“应该”、“可能”或“会”“是最好的” “如果乐如我们所料。我发现如果我通常避免 [sh|c|w] 关于 bugs/features 除非我愿意考虑其他人提出的任何和所有缺点 愿意帮助完成完成任务所需的工作。所以我只想说,我目前将其视为 10% 的错误,90% 的功能,但“可能”转变为 100% 的错误或 100% 的功能,具体取决于我在给定场景中是否想要这种行为, 并取决于其他人的想法。

<()> 捕获标记仅在给定的给定标记内有效。基本上,每个标记 returns 一个 Match 对象,表示“我将原始字符串从索引 X (.from) 匹配到索引 Y (.to)”,它被纳入字符串化 Match 个对象时的帐户。这就是您的 strvalue 令牌发生的情况:

my $text = 'bar = "Hello, World!"';
my $m = MyGrammar.parse: $text;

my $start = $m<value><strvalue>.from;     # 7
my $end   = $m<value><strvalue>.to;       # 20
say $text.substr: $start, $end - $start;  # Hello, World!

您会注意到只有两个数字:开始值和结束值。这意味着当您查看您拥有的 value 标记时,它无法创建不连续的匹配项。所以它的 .from 设置为 6,它的 .to 设置为 21.

有两种解决方法:使用 (a) 动作对象或 (b) 多令牌。两者都有各自的优势,并且取决于您希望如何在更大的项目中使用它,您可能想要选择其中之一。

虽然从技术上讲,您可以直接在语法中定义动作,但通过单独的 class 来实现它们要容易得多。所以我们可能会为您准备:

class MyActions { 
  method TOP      ($/) { make $<keyword>.made => $<value>.made }
  method keyword  ($/) { make ~$/ }
  method value    ($/) { make ($<numvalue> // $<strvalue>).made }
  method numvalue ($/) { make +$/ }
  method strvalue ($/) { make ~$/ }
}

每个级别 make 将值传递给包含它的任何标记。封闭的令牌可以通过 .made 方法访问它们的值。当您不想使用纯字符串值,而是想先以某种方式处理它们并创建一个对象或类似对象时,这真的很好。

要解析,你只需要做:

my $m = MyGrammar.parse: $text, :actions(MyActions);
say $m.made; # bar => Hello, World!

这实际上是一个 Pair 对象。您可以通过修改 TOP 方法来更改确切的结果。

解决问题的第二种方法是使用 multi token。在开发语法时使用类似于

的东西是相当普遍的
token foo { <option-A> | <option-B> }

但是从动作class中可以看出,它需要我们检查并查看实际匹配的是哪一个。相反,如果使用 | 可以接受交替,则可以使用多令牌:

proto token foo { * }
multi token:sym<A> { ... }
multi token:sym<B> { ... }

当您在语法中使用 <foo> 时,它将匹配两个多版本中的任何一个,就好像它已经在基线 <foo> 中一样。更好的是,如果您使用的是操作 class,您同样可以只使用 $<foo> 并且知道它存在而无需任何条件或其他检查。

在你的情况下,它看起来像这样:

grammar MyGrammar
{
    rule TOP { <keyword> '=' <value> }
    token keyword { \w+ }
    proto token value { * }
    multi token value:sym<str> { '"' <( <-["]>* )> '"' }
    multi token value:sym<num> { '-'? \d+ [ '.' \d* ]? }
}

现在我们可以访问您最初期望的内容,而无需使用操作对象:

my $text = 'bar = "Hello, World!"';
my $m = MyGrammar.parse: $text;

say $m;        # 「bar = "Hello, World!"」
               #  keyword => 「bar」
               #  value => 「Hello, World!」

say $m<value>; # 「Hello, World!」

作为参考,您可以结合使用这两种技术。下面是我现在如何编写给定多令牌的操作对象:

class MyActions { 
  method TOP            ($/) { make $<keyword>.made => $<value>.made }
  method keyword        ($/) { make ~$/ }
  method value:sym<str> ($/) { make ~$/ }
  method value:sym<num> ($/) { make +$/ }
}

乍一看更容易理解。

与其滚动自己的令牌 value:str 和令牌 value:num,不如使用正则表达式布尔检查 Num (+) 和 Str (~) 匹配 - 如我所解释的 and documented here

token number { \S+ <?{ defined +"$/" }> }
token string { \S+ <?{ defined ~"$/" }> }