I'm using Parsec to parse some expressions (see this question for more context), and most relevant part of my code is:
statement :: Parser Stmt
statement = assignStmt <|> simpleStmt
assignStmt :: Parser Stmt
assignStmt =
do var <- identifier
reservedOp "="
expr <- expression
return $ Assign var expr
simpleStmt :: Parser Stmt
simpleStmt =
do expr <- expression
return $ Simple expr
In action:
boobla> foo = 100 + ~100
167
boobla> foo
parser error: (line 1, column 4):
unexpected end of input
expecting letter or digit or "="
Second expression should have evaluated to 167
, value of foo
.
I thought that when Parsec would try to extract token reservedOp "="
, it should have failed because there is no such token in the string, then it was to try second function simpleStmt
and succeed with it. But it works differently: it expects more input and just throws this exception.
What should I use to make assignStmt
fail if there is no more characters in the string (or in current line). foo = 10
should be parsed with assignStmt
and foo
should be parsed with simpleStmt
.
You're missing the try
function.
By default, the <|>
operator will try the left parser, and if it fails without consuming any characters, it will try the right parser.
However, if — as in your case — the parser fails after having consumed some characters, and the right parser is never tried. Note that this is often the behavior you want; if you had something like
parseForLoop <|> parseWhileLoop
then if the input is something like "for break
", then that's not a valid for-loop, and there's no point trying to parse it as a while-loop, since that will surely fail also.
The try
combinator changes this behaviour. Specifically, it makes a failed parser appear to have consumed no input. (This has a space penalty; the input could have been thrown away, but try
makes it hang around.)