为什么在使用 Parsec 的 buildExpressionParser时,只有第一个定义的中缀运算符解析?



我正在尝试使用Parsec为命题演算编写一个解析器。解析器使用Text.Parsec.Expr中的buildExpressionParser函数。下面是我定义逻辑运算符的代码。

operators = [ [Prefix (string "~" >> return Negation)]
, [binary "&" Conjunction]
, [binary "|" Disjunction]
, [binary "->" Conditional]
, [binary "<->" Biconditional]
]
binary n c = Infix (spaces >> string n >> spaces >> return c) AssocRight
expr = buildExpressionParser operators term
<?> "compound expression"

我省略了变量、术语和带括号表达式的解析器,但如果您认为它们可能与问题有关,则可以阅读解析器的完整源代码。

对于只使用否定和连接的表达式,即唯一的前缀运算符和第一个中缀运算符,解析器会成功。

*Data.Logic.Propositional.Parser2> runPT expr () "" "p & ~q"
Right (p ∧ ¬q)

使用任何其他运算符的表达式在运算符的第一个字符上失败,错误如下:

*Data.Logic.Propositional.Parser2> runPT expr () "" "p | q"
Left (line 1, column 3):
unexpected "|"
expecting space or "&"

如果我注释掉定义连接语法分析器的行,那么析取语法分析器将工作(但其余部分仍然会失败)。将它们全部放在一个列表中(即具有相同优先级)也不起作用:同样的问题仍然会显现出来。

有人能指出我做错了什么吗?非常感谢。


感谢Daniel Fischer给出的及时而有用的回答。

为了使这个解析器正确工作,我还需要处理否定符号的重复应用,以便例如~~p能够正确解析。这个SO回答向我展示了如何做到这一点,我对解析器所做的更改可以在这里找到。

您的问题是

binary n c = Infix (spaces >> string n >> spaces >> return c) AssocRight

第一个尝试的中缀运算符在失败之前会消耗一个空间,因此不会尝试后面的可能性。(Parsec倾向于使用解析器,<|>只有在第一个解析器失败时才尝试运行第二个解析器,而没有消耗任何输入。)

如果第一个操作失败,要尝试其他中缀运算符,可以将binary解析器封装在try

binary n c = Infix (try $ ...) AssocRight

这样,当这样的解析器失败时,它就不会消耗任何输入,或者,更好的是,这个问题的传统解决方案是从中删除初始spaces

binary n c = Infix (string n >> spaces >> return c) AssocRight

并让您的所有解析器在解析的令牌后消耗空间

variable = do c <- letter
spaces
return $ Variable (Var c)
<?> "variable"
parens p = do char '('
spaces
x <- p
char ')'
spaces
return x
<?> "parens"

当然,如果您有可以解析带有公共前缀的运算符的解析器,那么您仍然需要将这些运算符封装在try中,这样,如果例如解析>=失败,仍然可以尝试>>=

模拟命题的数据类型并改变如上所述的空间消耗行为,

*PropositionalParser Text.Parsec> head $ runPT expr () "" "p | q -> r & s"
Right (Conditional (Disjunction (Variable (Var 'p')) (Variable (Var 'q'))) (Conjunction (Variable (Var 'r')) (Variable (Var 's'))))

甚至可以解析更复杂的表达式。

相关内容

最新更新