I am completely new to haskell and seen examples online of how to add error handling but I'm not sure how to incorporate it in my context. Below is an example of the code which works before trying to handle errors.
expr'::Parser Double
expr' = term' `chainl1'` addop
term'::Parser Double
term' = factor' `chainl1` mulop
chainl :: Parser a -> Parser (a -> a -> a) -> a -> Parser a
chainl p op a = (p `chainl1` op) <|> pure a
chainl1 ::Parser a -> Parser (a -> a -> a) -> Parser a
chainl1 p op = p >>= rest
where
rest a = (do
f <- op
b <- p
rest (f a b)) <|> pure a
addop, mulop :: Parser (Double -> Double -> Double)
I've since expanded this to let addop
and mulop
return error messages if something irregular is found. This causes the function definition to change to:
addop, mulop :: Parser (Either String (Double -> Double -> Double))
In other programming languages I would check if f <- op
is a String and return the string. However I'm not sure how to go about this in Haskell. The idea is that this error message returns all the way back to term'
. Hence its function definition also needs to change eventually. This is all in the attempt to build a Monadic Parser.
CodePudding user response:
If you're using parsec
then you can make your code more general to work with the ParsecT
monad transformer:
import Text.Parsec hiding (chainl1)
import Control.Monad.Trans.Class (lift)
expr' :: ParsecT String () (Either String) Double
expr' = term' `chainl1` addop
term' :: ParsecT String () (Either String) Double
term' = factor' `chainl1` mulop
factor' :: ParsecT String () (Either String) Double
factor' = read <$> many1 digit
chainl1 :: Monad m => ParsecT s u m a -> ParsecT s u m (a -> a -> a) -> ParsecT s u m a
chainl1 p op = p >>= rest
where
rest a = (do
f <- op
b <- p
rest (f a b))
<|> pure a
addop, mulop :: ParsecT String () (Either String) (Double -> Double -> Double)
addop = ( ) <$ char ' ' <|> (-) <$ char '-'
mulop = ((*) <$ char '*' <* lift (Left "error")) <|> (/) <$ char '/' <|> (**) <$ char '^'
I don't know what kind of errors you would want to return, so I've just made an error if an '*' is encountered in the input.
You can run the parser like this:
ghci> runParserT (expr' <* eof) () "buffer" "1 2 3"
Right (Right 6.0)
ghci> runParserT (expr' <* eof) () "buffer" "1 2*3"
Left "error"
CodePudding user response:
The answer based on parsec implementation.
Actually the operator <|>
is what you need. It handles any parsing errors. In expression a <|> b
if the parser a
fails then the parser b
will be run (expect if the parser a
consume some input before fails; for handle this case you can use combinator try
like this: try a <|> b
).
But if you want to handle error depending to the kind of error then you should do like @Noughtmare answered. But then I recomend you to do that:
Define your type for errors. It will be bugless to handle errors.
data MyError = ME_DivByZero | ...
You can simplify type signature if you define type alias for your parser.
type MyParser = ParsecT String () (Either MyError)
Then signatires will look like this:
expr' :: MyParser Double addop, mulop :: MyParser (Double -> Double -> Double)
Use
throwError
to throw your errors andcatchError
to handle your errors, that will be more idiomatic. So it's look like this:f <- catchError op $ \case ME_DivByZero -> ... ME_... -> ... err -> throwError err -- rethrow error