3
votes

I'm making a parser with Parsec and I try to return a specific error during the parsing.

This is a minimal parser example to expose my problem :

parseA = try seq1
      <|>  seq2

seq1 = do
          manyTill anyChar (try $ string "\n* ")
          many1 anyChar
          fail "My error message" 

seq2 = do
          manyTill anyChar (try $ string "\n- ")
          many1 anyChar

I would like to perform some tests in the first try $ do sequence and stop the parsing and return a specific error message. When I don't use fail I get :

ghci>  parse parseA  "" "aaaaaa\nbbbb\n* ccccc\n- ddd"
Right "ccccc\n- ddd"

When I use fail or unexpected, my parser doesn't stop (due to the try function) and execute the next do sequence:

ghci>  parse parseA  "" "aaaaaa\nbbbb\n* ccccc\n- ddd"
Right "ddd"

And it's not what I want!

I considered using the basic error function to stop the execution of my parser but I would like to have a "clean" error returned by the parsing function like this:

ghci>  parse parseA  "" "aaaaaa\nbbbb\n* ccccc\n- ddd"
Left "My error message"

Do you know how to properly stop a parser and return a custom error?

1
If the seq1 fail, it print out the error message and try seq2? or just stop parsing when seq1 fail?assembly.jc

1 Answers

2
votes

If you want the monad to behave differently then perhaps you should build a different monad. (N.B. I'm not entirely clear what you want, but moving forward anyway).

Solution: Use a Monad Transformer Stack

For example, to get a fail-like function that isn't caught and ignored by Parsec's try you could use an Except monad. Except allows you to throw errors much like exceptions but they are plumbed monadically instead of using the actual exception mechanism which demands IO to catch it.

First, lets define our monad:

import Text.Parsec
import Text.Parsec.Combinator
import Text.Parsec.Char
import Control.Monad.Trans.Except
import Control.Monad.Trans

type EscParse a = ParsecT String () (Except String) a

So the monad is EscParse and combines features of Parsec (via the transformer ParsecT) and Except.

Second, let's define some helpers:

run :: EscParse a -> SourceName -> String -> Either String (Either ParseError a)
run op sn input = runExcept (runPT op () sn input)

escFail :: String -> EscParse a
escFail = lift. throwE

Our run is like runParse but also runs the except monad. You might want to do something to avoid the nested Either, but that's an easy cosmetic change. escFail is what you'd use if you don't want the error to be ignored.

Third, we need to implement your parser using this new monad:

parseA :: EscParse String
parseA = try seq1 <|>  seq2

seq1 :: EscParse String
seq1 = do manyTill anyChar (try $ string "\n* ")
          many1 anyChar
          escFail "My error message"

seq2 :: EscParse String
seq2 = do manyTill anyChar (try $ string "\n- ")
          many1 anyChar

Other than spacing and type signature, the above matches what you had but using escFail instead of fail.