这是我要在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]'
。
问题:我堆栈溢出。
答案 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使用自上而下的贪婪算法来解析输入字符串。如果您按照这些步骤操作,将会看到它从start
到variable -> 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