Reputation: 2721
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?
Upvotes: 3
Views: 1106
Reputation: 64740
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
.
Upvotes: 3