Perl 6 语法与我认为的不匹配

Perl 6 Grammar doesn't match like I think it should

我正在做 Advent of Code day 9:

You sit for a while and record part of the stream (your puzzle input). The characters represent groups - sequences that begin with { and end with }. Within a group, there are zero or more other things, separated by commas: either another group or garbage. Since groups can contain other groups, a } only closes the most-recently-opened unclosed group - that is, they are nestable. Your puzzle input represents a single, large group which itself contains many smaller ones.

Sometimes, instead of a group, you will find garbage. Garbage begins with < and ends with >. Between those angle brackets, almost any character can appear, including { and }. Within garbage, < has no special meaning.

In a futile attempt to clean up the garbage, some program has canceled some of the characters within it using !: inside garbage, any character that comes after ! should be ignored, including <, >, and even another !.

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

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

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

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

这似乎在简单的例子上工作得很好,但是连续两个 garbchar 就出错了:

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

给出 Nil.

Grammar::Tracer 没有帮助:

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

多个garbignore没问题:

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

给出:

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

有什么想法吗?

对我自己的问题的部分回答:将所有 rule 更改为 token 并且有效。 这是有道理的,因为差异是 :sigspace,我们在这里不需要也不想要。不过,我不明白的是为什么它确实适用于 some 输入,就像我的第二个例子。

如果您有兴趣,结果代码是 here

UPD 鉴于代码问题的出现没有提到 whitespace 你根本不应该使用 rule 结构。只需将所有 rule 切换为 token 即可设置。一般来说,遵循 Brad 的建议——使用 token 除非你 知道 你需要 rule(下面讨论)或 regex(如果你需要回溯)。


我在下面的原始回答探讨了为什么 rule 不起作用。我暂时保留它。


TL;DR <garbchar> | 包含一个 space。 rule 中任何 atom 之后的白色 space 表示分词中断。你可以简单地删除这个不合适的space,即写<garbchar>|(或者更好,<.garbchar>|,如果你不需要捕获垃圾)以获得你寻求的结果。


正如你原来的问题所允许的那样,这不是一个错误,只是你的心智模型不对。

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

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

why ... my second example ... goes wrong with two garbchars in a row:

'{<aa>}'

简化,问题是如何标记这个:

aa

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

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

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

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

Typically, tokenization occurs at the word level. However, it is sometimes difficult to define what is meant by a "word". Often a tokenizer relies on simple heuristics, for example:

  • Punctuation and whitespace may or may not be included in the resulting list of tokens.

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

  • All contiguous strings of alphabetic characters are part of one token; likewise with numbers.

  • Tokens are separated by whitespace characters, such as a space or line break, or by punctuation characters.

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

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

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