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!"');

具有以下输出:

「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的语法的真正明智的更改。

多种调度解决方案

有没有解决的办法?

一种方法是切换到显式多调度。

您当前有一个value令牌,该令牌调用专门命名的值变量:

    token value { <strvalue> | <numvalue> }

替换为:

    proto token value {*}

,然后根据语法多个调度目标规则重命名被调用的令牌,因此语法变为:

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!」

默认情况下,这不会捕获各个替代项。我们可以坚持“多次派遣”,但重新引入子捕捉的命名:

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!」
  strvalue => 「Hello,World!」

惊喜

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

最初我也很惊讶。 [3]

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

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

  • 如果我期望的话也就不足为奇了,我认为在其他情况下我可能会做得到;

  • 要想得到一个 想要的当前行为,要想像您(和我)最初的预期那样工作,并不容易;

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

脚语

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

[2] 当您可以声明例如method value:foo { ... }并编写一个方法时(前提是每个这样的方法都返回一个匹配对象),我不认为Rakudo使用通常的多方法分派机制来分派给非方法规则变更,而是使用NFA

[3] 有人可能会说,如果Raku像我们期望的那样,它“应该”,“可能”或“将是最好的”。我发现我最好的想法是,如果我通常避免[sh | c | w]讨论错误/功能,除非我愿意考虑别人提出的任何和所有缺点,并且愿意 帮助完成工作所需的工作。因此,我只想说我目前将其视为10%错误,90%功能,但“可以”转向100%错误或100%功能,具体取决于在特定情况下我是否想要该行为,并取决于其他人的想法。

,

<()>捕获标记仅在给定令牌中起作用。基本上,每个令牌都会返回一个Match对象,该对象表示“我将原始字符串从索引X(.from匹配到索引Y(.to)”,在对{ {1}}个对象。这就是您的strvalue令牌所发生的事情:

Match

您会注意到只有两个数字:起始值和结束值。可以看出,当您查看拥有的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设置为6,其.from设置为21。

有两种解决方法:使用(a)动作对象或(b)多令牌。两者都有其优势,并且取决于您在大型项目中的使用方式,您可能希望选择其中一个。

尽管您可以在语法上直接在技术上定义动作,但通过单独的类进行操作要容易得多。所以我们可能为您准备:

.to

每个级别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

但是正如从action类中可以看到的,它要求我们检查并查看实际匹配的是哪一个。相反,如果通过token foo { <option-A> | <option-B> } 完成替换可以接受,则可以使用多令牌:

|

在语法中使用proto token foo { * } multi token:sym<A> { ... } multi token:sym<B> { ... } 时,它将匹配两个多重版本中的任何一个,就好像它已经在基线<foo>中一样。更好的是,如果您使用的是动作类,则可以类似地仅使用<foo>并知道它在那里,而无需任何条件或其他检查。

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

$<foo>

现在,我们可以使用您最初期望的东西,而无需使用action对象:

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!」

乍一看,这有点古怪。

,

您可能不想使用自己的令牌值:str和令牌值:num,而是想使用正则表达式布尔检查Num(+)和Str(〜)匹配-如here和{{ 3}}

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

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...