我想设计一个组合器来解析命题逻辑。这是simple BNF:
<sentence> ::= <atomic-sentence> | <complex-sentence>
<atomic-sentence> ::= True | False | P | Q | R
<complex-sentence> ::= (<sentence>)
| <sentence> <connective> <sentence>
| ¬<sentence>
<connective> ::= ∧ | ∨ | ⇒ | ⇔
问题在于语法是左递归的,这导致无限循环:一个句子可以是一个复杂的句子,可以从一个句子开始,可以是一个复杂的句子,直到永远。这是导致此问题的示例句子:
P∧Q
是否有一种简单的方法来修复语法,使其适合于解析器组合器?谢谢。
FWIW,我在F#中使用FParsec,但我认为任何解析器组合器库都会有相同的问题。
答案 0 :(得分:1)
FParsec可以使用OperatorPrecedenceParser
类来处理中缀运算符,您只需要指定具有哪些关联性和优先级的运算符,而不必为中缀表达式编写语法。该答案的其余部分将说明在该类不适用的情况下,对于没有等效类的解析器组合器或者在您只是不想使用它的情况下,如何解决该类不存在的问题。至少对您如何解决不存在的问题感兴趣。
解析器组合器倾向于不支持左递归,但是它们确实倾向于支持重复。幸运的是,可以使用<a> ::= <a> <b> | <c>
重复运算符将*
的左递归规则重写为<a> ::= <c> <b>*
。如果然后将鼠标左键放在结果列表上,则可以构造一棵看起来像从原始语法中得到的解析树的树。
因此,如果我们首先将<complex-sentence>
内联到<sentence>
中,然后应用上述模式,则会得到<a> = <sentence>
,<b> = <connective> <sentence>
和<c> = <atomic-sentence> | '(' <sentence> ')' | ¬<sentence>
,从而得到以下结果转换后的规则:
<sentence> ::= ( <atomic-sentence>
| '(' <sentence> ')'
| ¬<sentence>
)* <connective> <sentence>
为了提高可读性,我们将带括号的部分放在自己的规则中:
<operand> ::= <atomic-sentence>
| '(' <sentence ')'
| ¬<sentence>
<sentence> ::= <operand> (<connective> <sentence>)*
现在,如果您尝试这种语法,您会注意到一些奇怪的事情:由*
创建的列表将仅包含一个元素(或不包含任何元素)。这是因为如果有两个以上的操作数,则对<sentence>
的右递归调用将耗尽所有操作数,从而创建一个右关联的分析树。
所以上面的语法确实与此等效(或者说语法是模棱两可的,但是解析器组合器会将其视为等同于此):
<sentence> ::= <operand> <connective> <sentence>
发生这种情况是因为原始语法不明确。模棱两可的定义<s> ::= <s> <c> <s> | <o>
可以解释为左递归<s> ::= <s> <c> <o> | <o>
(将创建左关联解析树)或右递归<s> ::= <o> <c> <s> | <o>
(右关联解析树) 。因此,我们应该首先通过选择其中一种形式消除歧义,然后在适用的情况下应用转换。
因此,如果我们选择左递归形式,我们将得到:
<sentence> ::= <operand> (<connective> <operand>)*
实际上将创建包含多个元素的列表。另外,如果我们选择右递归规则,则可以保持原样(无需重复运算符),因为没有左递归需要消除。
正如我所说,我们现在可以通过从左递归版本中获取列表并将其左折叠来获取左联想树,或者通过获取右递归版本来获取一个右联想树。但是,这两个选项都会给我们留下一棵树,该树将所有运算符都视为具有相同的优先级。
要固定优先级,您可以将诸如调车码算法之类的内容应用于列表,也可以首先重新编写语法以考虑优先级,然后应用转换。