如何在 Raku 中使用匹配的分隔符

hsm*_*ers 5 grammar raku

我正在尝试编写一个令牌,允许使用匹配的分隔符嵌套内容。如果不是“(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 的了解可以用“初学者”来形容,所以我请求你的帮助。我正在寻找一个通用的解决方案或至少一个可以概括的例子,并且一如既往地欢迎建议和更正。

use*_*601 7

您还有一些额外的复杂性。例如,你定义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自动添加一些空格的规则来简化很多事情。

现在,我创建令牌的顺序是这样的:

  1. <simple-note>因为它是最基础的项目。他们中的一群人将是
  2. <notes>,所以我接下来做。在这样做的同时,我意识到一系列笔记还可以包括……
  3. <tie>,所以这是下一个。在领带里面,虽然我只是要再写一些笔记,所以我可以<notes>在里面使用。
  4. <TOP> 最后,因为如果一行只有一串音符,我们可以省略一行并使用 %% \v+

操作(通常与您的语法具有相同的名称,加上-Actions,因此我在这里使用class Tied-Actions { … })通常用于创建抽象语法树。但实际上,考虑这个问题的最好方法是询问语法的每个级别我们想要什么。我发现在编写语法时,从最小的元素向上构建最容易,而对于动作,从顶部向下构建最容易。这也将帮助您构建更复杂的操作:

  1. 我们要TOP什么?
    在我们的例子中,我们只想要我们在每个<note>令牌中找到的所有联系。这可以通过一个简单的循环来完成(因为我们<notes>对它做了一个量词将是Positional
    method TOP ($/) {  my @ties; @ties.append: .made for $<notes>; make @ties; }
    上面的代码创建了我们的临时变量,循环遍历每个变量<note>并附加<note>为我们所做的一切——目前还没有,但这没关系。然后,因为我们想要来自 TOP 的关系,所以我们make他们,这允许我们在解析后访问它。
  2. 你想要<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)。
  3. 你想要<tie>什么?在这里,我将只查看领带的内容 - 如果您愿意,也很容易抓住括号。您可能会认为我们只是使用make ~$<notes>,但这遗漏了一些重要的东西:$<notes> 有一些联系。但是这些很容易抓住:
    method tie ($/) { my @ties = ~$<notes>; @ties.append: $<notes>.made; make @ties; }
    这确保我们不仅传递当前的外部领带,而且还传递每个单独的内部领带(这反过来可能有另一个内部领带,依此类推)。

当你分析,你需要做的就是抢.madeMatch

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),而如果您可以避免使用任何属性,则可以传递类型对象。