规则开始相同的 Parslet 语法

Parslet grammar for rules starting identical

我想提供一个解析器来解析所谓的 Subversion 配置授权文件(参见 patch based authorization in the Subversion red book)。在这里我想为

这样的目录定义规则
[/]
* = r
[/trunk]
@PROJECT = rw

所以我有问题的语法部分是路径定义。我目前在 Parslet 中有以下规则:

rule(:auth_rule_head) { (str('[') >> path >> str(']') >> newline).as(:arh) }
rule(:top)          { (str('/')).as(:top) }
rule(:path)         { (top | ((str('/') >> path_ele).repeat)).as(:path) }
rule(:path_ele)     { ((str('/').absent? >> any).repeat).as(:path_ele) }

所以我想分两种情况:

有问题的规则似乎是定义替代方案的 path,这里 / XOR 类似于 /trunk

我已经为那些定义了测试用例,当 运行 测试用例时出现以下错误:

Failed to match sequence (SPACES '[' PATH ']' NEWLINE) at line 1 char 3.
`- Expected "]", but got "t" at line 1 char 3.

所以问题似乎是,一直选择替代(规则:路径)top

这个问题的解决方案(作为语法)是什么?我认为应该有一个解决方案,这看起来像是从这里到那里应该发生的惯用事情。我根本不是 PEG 解析器或解析器/编译器生成方面的专家,所以如果这是一个无法解决的基本问题,我也想知道。

简而言之:交换 OR 条件。

Parlset 规则消耗输入流,直到它们获得匹配,然后它们停止。 如果您有两个可能的选项(或),将尝试第一个,并且仅当它不匹配时才尝试第二个。

在您的情况下,由于您的所有路径都以“/”开头,它们都匹配路径规则的第一部分,因此永远不会探索后半部分。

需要先尝试匹配全路径,如果匹配失败才匹配'top'。

# changing this
rule(:path)         { (top | ((str('/') >> path_ele).repeat)).as(:path) }

# to this
rule(:path)         { ((str('/') >> path_ele).repeat) | top).as(:path) }

# fixes your first problem :)

另外...请注意在循环中不会消耗任何内容的规则。 重复默认为 repeat(0)。通常需要重复(1).

rule(:path)         { ((str('/') >> path_ele).repeat(1)) | top).as(:path) }

还有...

"top"真的是特例吗?所有路径都以“/”结尾,因此 top 只是零长度路径。

rule(:path)         { (path_ele.repeat(0)  >> str('/')).as(:path) }

rule(:path)         { (str('/') >> path_ele.repeat(0)).as(:path) }
rule(:path_ele)     { ((str('/').absent? >> any).repeat(0)).as(:path_ele) >> str('/') } 
# assuming "//" is valid otherwise repeat(1)

看来是我没答对题。我试图在创建一个包含一些单元测试的小示例语法时重现该问题,但现在一切正常。

如果您对此感兴趣,请查看要点https://gist.github.com/mliebelt/a36ace0641e61f49d78f。您应该可以下载该文件,并且 运行 可以直接从命令行下载它。您必须先安装 parsletminitest 应该已经包含在当前的 Ruby 版本中。

我只添加了 newline 的(缺失的)规则,并添加了 3 个单元测试来测试所有情况:

  • 根:/
  • 只有一个元素的路径:/my
  • 具有多个元素的路径:/my/path

按预期工作,所以我在这里得到两个案例:

  • 仅顶部元素
  • 一个或多个路径元素

也许这可以帮助其他人如何调试类似的情况。