Why is using Parsec buildExpressionParser only the first specified infix operator processed?

I am trying to write a parser for calculating statements using Parsec. The parser uses the buildExpressionParser function from Text.Parsec.Expr . Here is the code where I define the logical operators.

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

I skipped parsers for variables, terms, and expressions in parentheses, but if you think they might be related to the problem, you can read the full source for the parser .

The parser succeeds for expressions that use only negation and compound, i.e. the only prefix operator and the first infix operator.

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

Expressions using any other operators fail with the first character of the operator with an error similar to the following:

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

If I comment on the line defining the parser for the conjunctions, then the parser for the disjunction will work (but the rest will still fail). Inclusion of all of them in one list (i.e., of the same priority) does not work either: the same problem still appears.

Can someone point out what I'm doing wrong? Many thanks.


Thanks to Daniel Fisher for such a quick and helpful answer.

To finish this analyzer, I also need to handle repetitive negation symbol applications, so that, for example, ~~p will parse correctly. This "SO" answer showed me how to do this, and the change I made for the analyzer can be found.

+6
source share
1 answer

Your problem is that

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

the first tested infix operator consumes space before it crashes, so later features will not be tested. (Parsec prefers to use parsers, and <|> tries to start the second parser if the first one was not executed without using any input.)

To let other infix statements try if the first glitch, you can either wrap the binary analyzers in try

 binary nc = Infix (try $ ...) AssocRight 

so when such a parser fails, it does not consume any input, or, better, a conventional solution to this problem, remove the initial spaces from it,

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

and all your parsers consume spaces after the marker they parsed

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

Of course, if you have parsers that can parse operators with a common prefix, you still have to wrap them in try so that if, for example, parsing >= failed, >>= you can still try.

Examining the data type for sentences and changing the spatially labor-intensive behavior, as indicated above,

 *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')))) 

analyzes an even more complex expression.

+8
source

All Articles