Search code examples
haskellparsec

Make Parsec function fail instead of expecting more input


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.


Solution

  • 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.)