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 ~"$/" }> }
以下 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 (~) 匹配 - 如我所解释的
token number { \S+ <?{ defined +"$/" }> }
token string { \S+ <?{ defined ~"$/" }> }