如何在PetitParser

时间:2019-01-15 22:43:18

标签: smalltalk petitparser

这是我要在PetitParser中实现的(简化)EBNF部分:

variable :: component / identifier
component :: indexed / field
indexed :: variable , $[ , blah , $]
field :: variable , $. , identifier

我要做的是将所有这些产品(identifier除外)添加为我的PPCompositeParser子类的ivars,并定义相应的方法,如下所示:

variable
  ^component / self identifier

component
  ^indexed / field

identifier
  ^(#letter asParser, (#word asParser) star) flatten

indexed
  ^variable , $[ asParser, #digit asParser, $] asParser

field
  ^variable , $. asParser, self identifier

start
  ^variable

最后,我创建了解析器的新实例,并向其发送了消息parse: 'a.b[0]'

问题:我堆栈溢出。

2 个答案:

答案 0 :(得分:7)

语法左递归:variable -> component -> indexed -> variable。 PetitParser使用Parsing Expression Grammars (PEGs)不能处理左递归。 PEG解析器始终采用left选项,直到找到匹配项。在这种情况下,由于左递归,它将找不到匹配项。为了使它起作用,您需要首先消除左递归。消除所有左递归可能会比较棘手,因为在消除第一个递归之后,您还将通过field获得一个递归。例如,您可以按如下方式编写语法,以使左递归更加明显:

variable = (variable , $[ , blah , $]) | (variable , $. , identifier) | identifier

如果您的左递归类似:

A  -> A a |  b

您可以像消除它一样(e是一个空的解析器)

A  -> b A'
A' -> a A' | e

您需要两次应用此操作才能摆脱递归。 另外,如果您不想解析所有可能的标识符组合,则可以选择简化语法。

答案 1 :(得分:5)

问题在于您的语法是左递归。 PetitParser使用自上而下的贪婪算法来解析输入字符串。如果您按照这些步骤操作,将会看到它从startvariable -> component -> indexed -> variable。这成为一个循环,无需消耗任何输入就可以无限执行,这也是堆栈溢出的原因(实际上是左递归)。

解决该问题的技巧是通过添加中间步骤来避免左递归来重写解析器。基本思想是,重写的版本在每个周期中至少消耗一个字符。让我们首先简化一下解析器,重构“索引”和“字段”的非递归部分,然后将它们移到底部。

variable
  ^component, self identifier

component
  ^indexed / field

indexed
  ^variable, subscript

field
  ^variable, fieldName

start
  ^variable


subscript
    ^$[ asParser, #digit asParser, $] asParser

fieldName
    ^$. asParser, self identifier

identifier
  ^(#letter asParser, (#word asParser) star) flatten

现在,您可以更容易地看到(通过循环)如果variable中的递归将要结束,则必须在开头找到一个标识符。这是唯一的开始,然后会有更多的输入(或结束)。我们称第二部分为variable'

variable
    ^self identifier, variable'

现在variable'实际上是指已消耗标识符的事物,我们可以安全地将响应从indexed的左侧和field移至variable'的右侧:

variable'
    component', variable' / nil asParser

component'
    ^indexed' / field'

indexed'
    ^subscript

field'
    ^fieldName

我编写此答案时并未实际测试代码,但应该没问题。解析器可以进一步简化,我将其保留为练习;)。

有关消除左递归的更多信息,请查看left recursion elimination