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

Ben*_*ugh 6 haskell parsec

我正在尝试使用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"
Run Code Online (Sandbox Code Playgroud)

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

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

*Data.Logic.Propositional.Parser2> runPT expr () "" "p & ~q"
Right (p ? ¬q)
Run Code Online (Sandbox Code Playgroud)

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

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

如果我注释掉为连词定义解析器的行,那么析取的解析器将起作用(但其余的仍将失败).将它们全部放入一个列表(即具有相同的优先级)也不起作用:同样的问题仍然表现出来.

谁能指出我做错了什么?非常感谢.


感谢Daniel Fischer提供了一个迅速而有用的答案.

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

Dan*_*her 8

你的问题是

binary n c = Infix (spaces >> string n >> spaces >> return c) AssocRight
Run Code Online (Sandbox Code Playgroud)

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

要让其他中缀运算符在第一次失败时尝试,您可以将binary解析器包装在一个try

binary n c = Infix (try $ ...) AssocRight
Run Code Online (Sandbox Code Playgroud)

所以,当这样的解析器失败时,它不会消耗任何输入,或者更好,并且解决该问题的传统方法,从中删除初始化spaces,

binary n c = Infix (string n >> spaces >> return c) AssocRight
Run Code Online (Sandbox Code Playgroud)

并且解析令牌之后让所有解析器占用空格

variable = do c <- letter
              spaces
              return $ Variable (Var c)
        <?> "variable"

parens p = do char '('
              spaces
              x <- p
              char ')'
              spaces
              return x
        <?> "parens"
Run Code Online (Sandbox Code Playgroud)

当然,如果你有解析器可以使用公共前缀解析运算符,你仍然需要将它们包装在一起,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'))))
Run Code Online (Sandbox Code Playgroud)

即使是更复杂的表达式也会被解析.