我正在尝试编写一个令牌,允许使用匹配的分隔符嵌套内容。如果不是“(AB)”,则 (AB) 应至少匹配“AB”。并且 (A(c)B) 将返回两个匹配项“(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 "«$_»";
}
Run Code Online (Sandbox Code Playgroud)
这给出了部分正确的结果:
«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 |]»
Run Code Online (Sandbox Code Playgroud)
顺便说一句,我不关心换行符,它只是检查该方法是否可以跨越两行文本。所以搅拌灰烬我看到有括号和没有括号的捕获,以及一两个非常贪婪的捕获。
显然我的代码有问题。我对 perl6 的了解可以用“初学者”来形容,所以我请求你的帮助。我正在寻找一个通用的解决方案或至少一个可以概括的例子,并且一如既往地欢迎建议和更正。
您还有一些额外的复杂性。例如,你定义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|\]]> }
}
Run Code Online (Sandbox Code Playgroud)
这里有一些文体注释。语法是类,习惯上将它们大写。令牌是方法,并且往往是带有 kebap 外壳的小写字母(当然,您可以使用任何您想要的类型)。在tie令牌中,您会注意到我使用了<.open-tie>. 这.意味着我们不需要捕获它(也就是说,我们只是将它用于匹配而没有其他用途)。在notes令牌中,我能够通过使用%%并制定TOP自动添加一些空格的规则来简化很多事情。
现在,我创建令牌的顺序是这样的:
<simple-note>因为它是最基础的项目。他们中的一群人将是<notes>,所以我接下来做。在这样做的同时,我意识到一系列笔记还可以包括……<tie>,所以这是下一个。在领带里面,虽然我只是要再写一些笔记,所以我可以<notes>在里面使用。<TOP> 最后,因为如果一行只有一串音符,我们可以省略一行并使用 %% \v+操作(通常与您的语法具有相同的名称,加上-Actions,因此我在这里使用class Tied-Actions { … })通常用于创建抽象语法树。但实际上,考虑这个问题的最好方法是询问语法的每个级别我们想要什么。我发现在编写语法时,从最小的元素向上构建最容易,而对于动作,从顶部向下构建最容易。这也将帮助您构建更复杂的操作:
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是什么,其他动作都会made通过<notes>(例如TOP)。<tie>什么?在这里,我将只查看领带的内容 - 如果您愿意,也很容易抓住括号。您可能会认为我们只是使用make ~$<notes>,但这遗漏了一些重要的东西:$<notes> 也有一些联系。但是这些很容易抓住:method tie ($/) {
my @ties = ~$<notes>;
@ties.append: $<notes>.made;
make @ties;
}当你分析,你需要做的就是抢.made的Match:
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]
Run Code Online (Sandbox Code Playgroud)
现在,如果你真的只需要领带——而别无其他——(我认为不是这种情况),你可以更简单地做事情。使用相同的语法,改为使用以下操作:
class Tied-Actions {
has @!ties;
method TOP ($/) { make @!ties }
method tie ($/) { @!ties.push: ~$<notes> }
}
Run Code Online (Sandbox Code Playgroud)
与以前的相比,这有几个缺点:虽然它有效,但它的可扩展性不是很强。虽然你会得到每一条领带,但你对它的背景一无所知。此外,您必须实例化绑定操作(即actions => TiedActions.new),而如果您可以避免使用任何属性,则可以传递类型对象。