How to combine many parsers?

Viewed 81

Why this parser fails and how to fix it?

λ> str1 = string "elif "
λ> str2 = string "else "
λ> strs = (,) <$> many str1 <*> optionMaybe str2
λ> parse strs "" "elif elif elif else "
Left (line 1, column 16):
unexpected "s"
expecting "elif "

How to combine a many parser and an optionalMaybe parser?

2 Answers

The problem is that string "elif " will eat the el in else, and since it has consumed input it will not backtrack and instead complain about an unexpected s

The easiest fix is to allow backtracking with try:

str1 = try $ string "elif "

The other answer shows the easy fix using try. That does introduce a cost, though, namely the introduction of backtracking. In this answer I introduce another solution with no backtracking, and which therefore should be slightly faster and use slightly less memory. The basic idea is to parse the shared prefix, then dispatch once we hit the part where the two things actually differ. So:

strs = (string "el" *> (elseParser <|> elifParser)) <|> pure ([], Nothing) where
    elseParser = ([], Just "else ") <$ string "se "
    elifParser = liftA2
        (\_ (elifs, elses) -> ("elif ":elifs, elses))
        (string "if ")
        strs

For simplicity, I use constant "else " and "elif " strings in the results, but these can be built from the partial results of the string parsers with a bit of extra wiring.

Related