问题描述
以下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 ~"$/" }> }