如何在 Raku 中使用匹配的定界符
How to use matching delimiters in Raku
我正在尝试编写一个允许嵌套内容与匹配分隔符的标记。如果不是“(AB)”,(AB) 应该至少匹配 "AB"。而 (A(c)B) 会 return 两个匹配 "(A(c)B)" 等等。
从源代码中提取的代码:
#!/home/hsmyers/rakudo741/bin/perl6
use v6d;
my @tie;
class add-in {
method tie($/) { @tie.push: $/; }
}
grammar tied {
rule TOP { <line>* }
token line {
<.ws>?
[
| <tie>
| <simpleNotes>
]+
<.ws>?
}
token tie {
[
|| <.ws>? <simpleNotes>+ <tie>* <simpleNotes>* <.ws>?
|| <openParen> ~ <closeParen> <tie>
]+
}
token openParen { '(' }
token closeParen { ')' }
token simpleNotes {
[
| <[A..Ga..g,'>0..9]>
| <[|\]]>
| <blank>
]
}
}
my $text = "(c2D) | (aA) (A2 | B)>G A>F G>E (A,2 |\nD)>F A>c d>f |]";
tied.parse($text, actions => add-in.new).say;
$text.say;
for (@tie) {
s:g/\v/\n/;
say "«$_»";
}
这给出了部分正确的结果:
«c2D»
«aA»
«(aA)»
«A2 | B»
«\nD»
«A,2 |\nD»
«(A,2 |\nD)>F A>c d>f |]»
«(c2D) | (aA) (A2 | B)>G A>F G>E (A,2 |\nD)>F A>c d>f |]»
顺便说一句,我不关心换行符,它只是用来检查该方法是否可以将文本跨越两行。所以搅拌骨灰我看到有括号和没有括号的捕获,以及一两个非常贪婪的捕获。
很明显我的代码有问题。我对 perl6 的了解最好描述为 "beginner" 所以我请求你的帮助。我正在寻找一个通用的解决方案或至少一个可以概括的示例,并且一如既往地欢迎提出建议和更正。
你有一些额外的复杂性。例如,您将 tie
定义为 (...)
或只是 ...
。但是里面的内容和那行是一样的。
这是一个重写的语法,可以大大简化您想要的内容。写语法的时候,从小处着手,往上爬很有帮助。
grammar Tied {
rule TOP { <notes>+ %% \v+ }
token notes {
[
| <tie>
| <simple-note>
] +
%%
<.ws>?
}
token open-tie { '(' }
token close-tie { ')' }
token tie { <.open-tie> ~ <.close-tie> <notes> }
token simple-note { <[A..Ga..g,'>0..9|\]]> }
}
这里有一些文体注释。语法是类,习惯上大写。令牌是方法,并且倾向于使用 kebap 大小写(当然你可以使用任何你想要的类型)。在 tie
标记中,您会注意到我使用了 <.open-tie>
。 .
意味着我们不需要捕获它(也就是说,我们只是将它用于匹配而不是其他)。在 notes
令牌中,我可以通过使用 %%
并使 TOP
成为自动添加一些空格的规则来简化很多事情。
现在,我创建令牌的顺序是这样的:
<simple-note>
因为它是最基本的项目。其中一组将是
<notes>
,所以我接下来做。这样做的时候,我意识到 运行 的笔记也可以包括…
<tie>
,那就是下一个。在一条领带里面,虽然我只是要有另一个 运行 的音符,所以我可以在里面使用 <notes>
。
最后是<TOP>
,因为如果一行只有运行个音符,我们可以省略一行,使用%% \v+
Actions(通常与你的语法同名,加上-Actions
,所以这里我使用class Tied-Actions { … }
)通常用于创建抽象语法树。但实际上,思考这个问题的最好方法是询问语法的每一层我们想从中得到什么。我发现虽然编写语法最容易从最小的元素向上构建,但对于操作,从 TOP 向下构建是最容易的。这也将帮助您构建更复杂的动作:
- 我们想从
TOP
那里得到什么?
在我们的例子中,我们只想要在每个 <note>
标记中找到的所有关系。这可以通过一个简单的循环来完成(因为我们在 <notes>
上做了一个量词,它将是 Positional
:
method TOP ($/) {
my @ties;
@ties.append: .made for $<notes>;
make @ties;
}
上面的代码创建了我们的临时变量,循环遍历每个 <note>
并附加到 <note>
为我们所做的一切——目前这没什么,但没关系。然后,因为我们想要来自TOP的关系,所以我们make
它们,这允许我们在解析后访问它。
- 你想从
<notes>
那里得到什么?
同样,我们只想要领带(但也许其他时候,您想要领带和滑音,或其他一些信息)。所以我们可以抓住领带,基本上做同样的事情:
method notes ($/) {
my @ties;
@ties.append: .made for $<tie>.grep(*.defined);
make @ties;
}
唯一的区别不是只做 for $<tie>
,我们必须只抓取定义的那些——这是做 [<foo>|<bar>]+
的结果:$<foo>
将为每个量化匹配有一个槽,是否 note <foo>
进行了匹配(这是当你经常想用领带和简单的音符变体将东西弹出到 proto token note
时,但这有点先进)。同样,我们抓住 $<tie>
为我们制作的任何东西——我们稍后会定义它,我们 "make" 它。无论我们 make
是什么,其他操作将通过 <notes>
找到 made
(如 TOP
)。
- 您想从
<tie>
那里得到什么?
在这里,我将只关注领带的内容——如果你愿意,也很容易抓住括号。你会认为我们 只是 使用 make ~$<notes>
,但这遗漏了一些重要的东西:$<notes>
也 有一些关系。但这些很容易抓住:
method tie ($/) {
my @ties = ~$<notes>;
@ties.append: $<notes>.made;
make @ties;
}
这确保我们不仅传递当前的外部关系,而且传递每个单独的内部关系(反过来可能有另一个内部关系,依此类推)。
当你解析时,你需要做的就是获取 Match
:
的 .made
say Tied.parse("a(b(c))d");
# 「a(b(c))d」
# notes => 「a(b(c))d」
# simple-note => 「a」
# tie => 「(b(c))」 <-- there's a tie!
# notes => 「b(c)」
# simple-note => 「b」
# tie => 「(c)」 <-- there's another!
# notes => 「c」
# simple-note => 「c」
# simple-note => 「d」
say Tied.parse("a(b(c))d", actions => TiedActions).made;
# [b(c) c]
现在,如果您真的只需要领带——而不需要其他东西——(我认为情况并非如此),您可以做的事情简单得多。使用相同的语法,改为使用以下操作:
class Tied-Actions {
has @!ties;
method TOP ($/) { make @!ties }
method tie ($/) { @!ties.push: ~$<notes> }
}
与前一个相比,它有几个缺点:虽然它可以工作,但它的可扩展性不是很好。虽然你会得到每一条领带,但你不会知道它的背景。此外,您必须实例化 Tied-Actions(即 actions => TiedActions.new
),而如果您可以避免使用任何属性,则可以传递类型对象。
我正在尝试编写一个允许嵌套内容与匹配分隔符的标记。如果不是“(AB)”,(AB) 应该至少匹配 "AB"。而 (A(c)B) 会 return 两个匹配 "(A(c)B)" 等等。
从源代码中提取的代码:
#!/home/hsmyers/rakudo741/bin/perl6
use v6d;
my @tie;
class add-in {
method tie($/) { @tie.push: $/; }
}
grammar tied {
rule TOP { <line>* }
token line {
<.ws>?
[
| <tie>
| <simpleNotes>
]+
<.ws>?
}
token tie {
[
|| <.ws>? <simpleNotes>+ <tie>* <simpleNotes>* <.ws>?
|| <openParen> ~ <closeParen> <tie>
]+
}
token openParen { '(' }
token closeParen { ')' }
token simpleNotes {
[
| <[A..Ga..g,'>0..9]>
| <[|\]]>
| <blank>
]
}
}
my $text = "(c2D) | (aA) (A2 | B)>G A>F G>E (A,2 |\nD)>F A>c d>f |]";
tied.parse($text, actions => add-in.new).say;
$text.say;
for (@tie) {
s:g/\v/\n/;
say "«$_»";
}
这给出了部分正确的结果:
«c2D»
«aA»
«(aA)»
«A2 | B»
«\nD»
«A,2 |\nD»
«(A,2 |\nD)>F A>c d>f |]»
«(c2D) | (aA) (A2 | B)>G A>F G>E (A,2 |\nD)>F A>c d>f |]»
顺便说一句,我不关心换行符,它只是用来检查该方法是否可以将文本跨越两行。所以搅拌骨灰我看到有括号和没有括号的捕获,以及一两个非常贪婪的捕获。
很明显我的代码有问题。我对 perl6 的了解最好描述为 "beginner" 所以我请求你的帮助。我正在寻找一个通用的解决方案或至少一个可以概括的示例,并且一如既往地欢迎提出建议和更正。
你有一些额外的复杂性。例如,您将 tie
定义为 (...)
或只是 ...
。但是里面的内容和那行是一样的。
这是一个重写的语法,可以大大简化您想要的内容。写语法的时候,从小处着手,往上爬很有帮助。
grammar Tied {
rule TOP { <notes>+ %% \v+ }
token notes {
[
| <tie>
| <simple-note>
] +
%%
<.ws>?
}
token open-tie { '(' }
token close-tie { ')' }
token tie { <.open-tie> ~ <.close-tie> <notes> }
token simple-note { <[A..Ga..g,'>0..9|\]]> }
}
这里有一些文体注释。语法是类,习惯上大写。令牌是方法,并且倾向于使用 kebap 大小写(当然你可以使用任何你想要的类型)。在 tie
标记中,您会注意到我使用了 <.open-tie>
。 .
意味着我们不需要捕获它(也就是说,我们只是将它用于匹配而不是其他)。在 notes
令牌中,我可以通过使用 %%
并使 TOP
成为自动添加一些空格的规则来简化很多事情。
现在,我创建令牌的顺序是这样的:
<simple-note>
因为它是最基本的项目。其中一组将是<notes>
,所以我接下来做。这样做的时候,我意识到 运行 的笔记也可以包括…<tie>
,那就是下一个。在一条领带里面,虽然我只是要有另一个 运行 的音符,所以我可以在里面使用<notes>
。
最后是<TOP>
,因为如果一行只有运行个音符,我们可以省略一行,使用%% \v+
Actions(通常与你的语法同名,加上-Actions
,所以这里我使用class Tied-Actions { … }
)通常用于创建抽象语法树。但实际上,思考这个问题的最好方法是询问语法的每一层我们想从中得到什么。我发现虽然编写语法最容易从最小的元素向上构建,但对于操作,从 TOP 向下构建是最容易的。这也将帮助您构建更复杂的动作:
- 我们想从
TOP
那里得到什么?
在我们的例子中,我们只想要在每个<note>
标记中找到的所有关系。这可以通过一个简单的循环来完成(因为我们在<notes>
上做了一个量词,它将是Positional
:
method TOP ($/) { my @ties; @ties.append: .made for $<notes>; make @ties; }
上面的代码创建了我们的临时变量,循环遍历每个<note>
并附加到<note>
为我们所做的一切——目前这没什么,但没关系。然后,因为我们想要来自TOP的关系,所以我们make
它们,这允许我们在解析后访问它。 - 你想从
<notes>
那里得到什么?
同样,我们只想要领带(但也许其他时候,您想要领带和滑音,或其他一些信息)。所以我们可以抓住领带,基本上做同样的事情:
method notes ($/) { my @ties; @ties.append: .made for $<tie>.grep(*.defined); make @ties; }
唯一的区别不是只做for $<tie>
,我们必须只抓取定义的那些——这是做[<foo>|<bar>]+
的结果:$<foo>
将为每个量化匹配有一个槽,是否 note<foo>
进行了匹配(这是当你经常想用领带和简单的音符变体将东西弹出到proto token note
时,但这有点先进)。同样,我们抓住$<tie>
为我们制作的任何东西——我们稍后会定义它,我们 "make" 它。无论我们make
是什么,其他操作将通过<notes>
找到made
(如TOP
)。 - 您想从
<tie>
那里得到什么? 在这里,我将只关注领带的内容——如果你愿意,也很容易抓住括号。你会认为我们 只是 使用make ~$<notes>
,但这遗漏了一些重要的东西:$<notes>
也 有一些关系。但这些很容易抓住:
method tie ($/) { my @ties = ~$<notes>; @ties.append: $<notes>.made; make @ties; }
这确保我们不仅传递当前的外部关系,而且传递每个单独的内部关系(反过来可能有另一个内部关系,依此类推)。
当你解析时,你需要做的就是获取 Match
:
.made
say Tied.parse("a(b(c))d");
# 「a(b(c))d」
# notes => 「a(b(c))d」
# simple-note => 「a」
# tie => 「(b(c))」 <-- there's a tie!
# notes => 「b(c)」
# simple-note => 「b」
# tie => 「(c)」 <-- there's another!
# notes => 「c」
# simple-note => 「c」
# simple-note => 「d」
say Tied.parse("a(b(c))d", actions => TiedActions).made;
# [b(c) c]
现在,如果您真的只需要领带——而不需要其他东西——(我认为情况并非如此),您可以做的事情简单得多。使用相同的语法,改为使用以下操作:
class Tied-Actions {
has @!ties;
method TOP ($/) { make @!ties }
method tie ($/) { @!ties.push: ~$<notes> }
}
与前一个相比,它有几个缺点:虽然它可以工作,但它的可扩展性不是很好。虽然你会得到每一条领带,但你不会知道它的背景。此外,您必须实例化 Tied-Actions(即 actions => TiedActions.new
),而如果您可以避免使用任何属性,则可以传递类型对象。