【问题标题】:Perl 6 Grammar doesn't match like I think it shouldPerl 6 语法不像我认为的那样匹配
【发布时间】:2017-12-09 12:11:20
【问题描述】:

我在做Advent of Code day 9:

您坐了一会儿并记录流的一部分(您的拼图输入)。字符代表组 - 以{ 开头并以} 结尾的序列。在一个组中,有零个或多个其他东西,用逗号分隔:另一个组或垃圾。由于组可以包含其他组,} 仅关闭最近打开的未关闭组 - 也就是说,它们是可嵌套的。您的拼图输入代表一个单独的大组,该组本身包含许多较小的组。

有时,您会发现垃圾而不是一组。垃圾以< 开头,以> 结尾。在这些尖括号之间,几乎可以出现任何字符,包括{}。在垃圾中,< 没有特殊含义。

在清理垃圾的徒劳尝试中,某些程序使用!取消了其中的一些字符:在垃圾中,应忽略!之后的任何字符,包括<、@987654335 @,甚至是另一个!

当然,这需要 Perl 6 语法...

grammar Stream
{
    rule TOP { ^ <group> $ }

    rule group { '{' [ <group> || <garbage> ]* % ',' '}' }
    rule garbage { '<' [ <garbchar> | <garbignore> ]* '>' }

    token garbignore { '!' . }
    token garbchar { <-[ !> ]> }
}

这在简单的示例上似乎可以正常工作,但是连续两个garbchars 会出错:

say Stream.parse('{<aa>}');

Nil

Grammar::Tracer 无济于事:

TOP
|  group
|  |  group
|  |  * FAIL
|  |  garbage
|  |  |  garbchar
|  |  |  * MATCH "a"
|  |  * FAIL
|  * FAIL
* FAIL
Nil

多个garbignores 没问题:

say Stream.parse('{<!!a!a>}');

给予:

「{<!!a!a>}」
 group => 「{<!!a!a>}」
  garbage => 「<!!a!a>」
   garbignore => 「!!」
   garbchar => 「a」
   garbignore => 「!a」

有什么想法吗?

【问题讨论】:

  • 我会使用token group { '{' ~ '}' [ &lt;group&gt; || &lt;garbage&gt; ]* % ',' },因为它将{} 放在一起。
  • @Brad:可以做到这一点,好吧,没有最后的},还有一些benefits,但我个人认为这不太可读,因为你不能再简单地从从左到右。
  • @mscha twiddles 的基本原理是,如果解析失败,从左到右编写往往会产生奇怪的错误消息,而旋转往往会产生很好的错误消息。此外,虽然我第一次看到 twiddle 结构时认为它的可读性较差,但熟悉度很快让我发现它更具可读性。

标签: regex grammar raku


【解决方案1】:

UPD 鉴于代码问题的出现没有提到空格,您根本不应该使用rule 构造。只需将所有 rules 切换为 tokens 即可。一般来说,请遵循 Brad 的建议 - 使用 token,除非您知道您需要 rule(下文讨论)或 regex(如果您需要回溯)。


我在下面的原始答案探讨了为什么 rules 不起作用。我先把它留在里面。


TL;DR &lt;garbchar&gt; | 包含一个空格。 rule 中任何 atom 之后的空格表示一个标记中断。您可以简单地删除这个不合适的空间,即改为写&lt;garbchar&gt;|(或者更好的是,如果您不需要捕获垃圾,则写&lt;.garbchar&gt;|)以获得您寻求的结果。


正如您最初的问题所允许的那样,这不是错误,只是您的心理模型已关闭。

您的回答正确地指出了问题:tokenization

所以我们剩下的就是你的后续问题,这是关于你的标记化心理模型,或者至少是 Perl 6 默认如何标记化:

为什么......我的第二个例子......连续两个 garbchars 出错:

'{<aa>}'

简单来说,问题是如何标记这个:

aa

简单的高级答案是,在解析白话时,aa 通常会被视为一个标记,而不是两个标记,并且默认情况下,Perl 6 假定这个普通定义。这是您遇到的问题。

您可以否决这个普通定义,以获得您想要实现的任何标记化结果。但很少需要这样做,而且在这种简单的情况下肯定不会这样做。

我将提供两条冗余路径,希望它们可以引导人们找到正确的心智模型:

摘自the "Obstacles" section of the wikipedia page on tokenization,并将摘录与P6的具体讨论交错:

通常,标记化发生在单词级别。然而,有时很难定义“词”的含义。通常,分词器依赖于简单的启发式方法,例如:

  • 标点符号和空格可能会或可能不会包含在生成的标记列表中。

在 Perl 6 中,您可以使用与标记化正交的捕获功能来控制解析树中包含或不包含的内容。

  • 所有连续的字母字符串都是一个标记的一部分;数字也是如此。

  • 标记由空格字符(例如空格或换行符)或标点字符分隔。

默认情况下,Perl 6 设计体现了这两种启发式的等效方法。

获得的关键是rule 构造处理一串标记,复数。 token 构造用于定义每次调用单个令牌

我想我会在这里结束我的回答,因为它已经变得很长了。请使用 cmets 帮助我们改进此答案。我希望到目前为止我所写的内容有所帮助。

【讨论】:

  • 感谢您的广泛回答。我现在对令牌和规则有了更好的理解。
  • 不客气。届时我将保持原样。感谢您的反馈。 :)
【解决方案2】:

我自己的问题的部分答案:将所有 rules 更改为 tokens 并且它可以工作。 这是有道理的,因为区别是:sigspace,我们在这里不需要或不想要它。不过,我不明白为什么它确实适用于 some 输入,就像我的第二个示例一样。

如果您有兴趣,生成的代码是here

【讨论】:

  • 我认为你应该默认使用标记,并且只使用你经常处理空格的规则。
  • 默认的ws 实现是token ws { &lt;!ww&gt; \s* }&lt;!ww&gt; 的意思是“不在一个单词中”,因此它无法在两个 a 之间匹配,但成功匹配单词和非单词字符之间的零个字符。
  • 谢谢,@moritz,这就是我一直在寻找的为什么我的代码有时有效有时无效的简单解释。
猜你喜欢
  • 1970-01-01
  • 2011-09-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-19
  • 2013-02-24
  • 1970-01-01
相关资源
最近更新 更多