Raku:捕获标记的效果在“更高”处丢失

msc*_*cha 6 regex grammar raku

以下 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!"');
Run Code Online (Sandbox Code Playgroud)

有以下输出:

?foo = 42?
 keyword => ?foo?
 value => ?42?
  numvalue => ?42?
?bar = "Hello, World!"?
 keyword => ?bar?
 value => ?"Hello, World!"?
  strvalue => ?Hello, World!?
Run Code Online (Sandbox Code Playgroud)

对于第二个项目,需要注意的是strvalue含有不带引号的字符串值,如预期与捕捉市场<(... )>。然而,出乎我的意料,引号列入value

有没有解决的办法?

rai*_*iph 6

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

多分派解决方案

有没有解决的办法?

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

您当前有一个value令牌,它调用特定命名的值变体:

    token value { <strvalue> | <numvalue> }
Run Code Online (Sandbox Code Playgroud)

替换为:

    proto token value {*}
Run Code Online (Sandbox Code Playgroud)

然后根据语法多分派目标规则重命名被调用的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!"');
Run Code Online (Sandbox Code Playgroud)

这显示:

?foo = 42?
 keyword => ?foo?
 value => ?42?
?bar = "Hello, World!"?
 keyword => ?bar?
 value => ?Hello, World!?
Run Code Online (Sandbox Code Playgroud)

默认情况下,这不会捕获单个更改。我们可以坚持使用“多重调度”,但重新引入子捕获的命名:

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!"');
Run Code Online (Sandbox Code Playgroud)

显示:

?foo = 42?
 keyword => ?foo?
 value => ?42?
  numvalue => ?42?
?bar = "Hello, World!"?
 keyword => ?bar?
 value => ?Hello, World!?
  strvalue => ?Hello, World!?
Run Code Online (Sandbox Code Playgroud)

惊喜

令我惊讶的是,引号包含在value.

一开始我也很惊讶。[3]

但目前的行为对我来说至少在以下意义上也有意义:

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

  • 如果我预料到它就不足为奇了,我想在其他一些情况下我可能会这样做;

  • 这是不容易看到一个如何获得当前的行为,如果它通缉,而是担任你(和我)最初的预期;

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

脚注

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

[2]虽然您可以声明、说method value:foo { ... }和编写一个方法(前提是每个这样的方法都返回一个匹配对象),但我不认为 Rakudo 使用通常的多方法分派机制来分派非方法规则交替,而是使用NFA

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


use*_*601 6

<()>捕捉标记只在给定令牌的工作。基本上,每个标记都会返回一个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!
Run Code Online (Sandbox Code Playgroud)

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

有两种方法可以解决这个问题:使用 (a) 一个动作对象或 (b) 一个多令牌。两者都有其优点,根据您希望如何在更大的项目中使用它,您可能希望选择其中之一。

虽然您可以在技术上直接在语法中定义动作,但通过单独的类来完成它们要容易得多。因此,我们可能为您准备了:

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

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

要解析,您只需执行以下操作:

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

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

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

token foo { <option-A> | <option-B> }
Run Code Online (Sandbox Code Playgroud)

但是正如您从动作类中看到的那样,它要求我们检查并查看实际匹配的是哪个。相反,如果|使用done 可以接受交替,则可以使用多令牌:

proto token foo { * }
multi token:sym<A> { ... }
multi token:sym<B> { ... }
Run Code Online (Sandbox Code Playgroud)

当您<foo>在语法中使用时,它将匹配两个多版本中的任何一个,就像在基线中一样<foo>。更好的是,如果您使用的是操作类,您可以类似地使用$<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* ]? }
}
Run Code Online (Sandbox Code Playgroud)

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

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!?
Run Code Online (Sandbox Code Playgroud)

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

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

乍一看,这有点令人困惑。