【问题标题】:Stopping Raku grammar at EOS (End of String)在 EOS(字符串结束)处停止 Raku 语法
【发布时间】:2019-12-26 01:43:04
【问题描述】:

在编写将一种音乐语言翻译成另一种音乐语言(ABC 到 Alda)作为学习 Raku DSL 能力的借口的过程中,我注意到似乎没有办法终止 .parse!这是我缩短的演示代码:

#!/home/hsmyers/rakudo741/bin/perl6
use v6d;

# use Grammar::Debugger;
use Grammar::Tracer;

my $test-n01 = q:to/EOS/;
a b c d e f g
A B C D E F G
EOS

grammar test {
  token TOP { <score>+ }
  token score {
      <.ws>?
      [
          | <uc>
          | <lc>
      ]+
      <.ws>?
  }
  token uc { <[A..G]> }
  token lc { <[a..g]> }
}

test.parse($test-n01).say;

Grammer::Tracer 显示的最后一部分展示了我的问题。

|  score
|  |  uc
|  |  * MATCH "G"
|  * MATCH "G\n"
|  score
|  * FAIL
* MATCH "a b c d e f g\nA B C D E F G\n"
「a b c d e f g
A B C D E F G
」

在倒数第二行,FAIL 这个词告诉我 .parse 运行无法退出。我想知道这是否正确? .say 显示所有内容,所以我不清楚 FAIL 有多真实?问题仍然存在,“如何正确编写解析多行而不会出错的语法?”

【问题讨论】:

  • 我不想干涉你的学习过程,但万一你不知道,有一个ABC module
  • 好吧,至少我们没有选择相同的曲调进行测试!

标签: parsing grammar raku


【解决方案1】:

当您使用语法调试器时,它可以让您准确地看到引擎是如何解析字符串的——失败是正常的,也是意料之中的。例如,考虑将a+b* 与字符串aab 匹配。您应该得到两个匹配 'a',然后是失败(因为 b 不是 a),但随后它将使用 b 重试并成功匹配。

如果您与||(强制执行顺序)进行交替,这可能会更容易看到。如果你有

token TOP   { I have a <fruit> }
token fruit { apple || orange || kiwi }

你解析句子“I have a kiwi”,你会看到它首先匹配“I have a”,然后是两个失败的“apple”和“orange”,最后一个匹配“kiwi”。

现在让我们看看你的情况:

TOP                  # Trying to match top (need >1 match of score)
|  score             #   Trying to match score (need >1 match of lc/uc)
|  |  lc             #     Trying to match lc
|  |  * MATCH "a"    #     lc had a successful match! ("a")
|  * MATCH "a "      #   and as a result so did score! ("a ")
|  score             #   Trying to match score again (because <score>+)
|  |  lc             #     Trying to match lc 
|  |  * MATCH "b"    #     lc had a successful match! ("b")
|  * MATCH "b "      #   and as a result so did score! ("b ")
……………                #     …so forth and so on until…
|  score             #   Trying to match score again (because <score>+)
|  |  uc             #     Trying to match uc
|  |  * MATCH "G"    #     uc had a successful match! ("G")
|  * MATCH "G\n"     #   and as a result, so did score! ("G\n")
|  score             #   Trying to match *score* again (because <score>+)
|  * FAIL            #   failed to match score, because no lc/uc.
|
|  # <--------------   At this point, the question is, did TOP match?
|  #                     Remember, TOP is <score>+, so we match TOP if there 
|  #                     was at least one <score> token that matched, there was so...
|
* MATCH "a b c d e f g\nA B C D E F G\n" # this is the TOP match

这里的失败是正常的:在某些时候我们会用完&lt;score&gt; 令牌,所以失败是不可避免的。发生这种情况时,语法引擎可以继续处理语法中 &lt;score&gt;+ 之后的任何内容。由于没有任何内容,因此失败实际上会导致整个字符串匹配(因为 TOP 与隐式 /^…$/ 匹配)。

另外,您可以考虑使用自动插入 <.ws>* 的规则重写您的语法(除非重要的是它只能是一个空格):

grammar test {
  rule TOP { <score>+ }
  token score {
      [
          | <uc>
          | <lc>
      ]+
  }
  token uc { <[A..G]> }
  token lc { <[a..g]> }
}

此外,IME,您可能还想为 uc/lc 添加一个 proto 令牌,因为当您拥有 [ &lt;foo&gt; | &lt;bar&gt; ] 时,您将始终有其中一个未定义,这可以在动作类中处理它们有点烦人。你可以试试:

grammar test {
  rule  TOP   { <score>  + }
  token score { <letter> + }

  proto token letter    {     *    }
        token letter:uc { <[A..G]> }
        token letter:lc { <[a..g]> }
}

$&lt;letter&gt; 将始终以这种方式定义。

【讨论】:

  • 这解释了匹配对象返回 'so's out as true 即使使用 'FAIL' 的事实。我认为可能是这样。我将回到为实际项目添加必要的令牌;)
  • 真正的语法似乎不喜欢自动插入<.ws>*;可能是由于除了 之外还涉及其他层。只要我能够理解这项技术,您对使用 proto 的建议就看起来不错……
  • 我讨厌有我不需要的代码——更多的调试,然后就是它的美感!实际的问题是 ABC 根本不在乎空格。有一些例外,但总的来说,它们几乎可以在任何地方发生。 “用例”是一个易读性问题,有点像大数字字符串中的逗号。我将根据需要重新审视问题,直到我了解问题并将其减少到最低限度。
  • hsmyers:谢天谢地,理解proto 并不难,一旦你掌握了窍门,你的生活就会轻松很多。
猜你喜欢
  • 2015-07-27
  • 2016-05-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-08
  • 1970-01-01
相关资源
最近更新 更多