Search code examples
haskelltrifecta

How to signal failure in trifecta parser


As an experiment with trifecta I've written the following simple function:

filterParser :: (a -> Bool) -> Parser a -> Parser a
filterParser cond p = do
  a <- p
  if cond a 
    then return a 
    else unexpected "condition failed!"

The idea was to be able to add a condition to a parser. For example (assuming predicate prime already exists), you would write: filterParser prime integer to create a parser that only accepts prime numbers.


With single parses it seems OK:

> parseString (filterParser (> 'm') letter) mempty "z"
> Success 'z

> parseString (filterParser (> 'm') letter) mempty "a"
> Failure (interactive):1:2: error: unexpected
> condition failed!

But with 'many' it doesn't work - compare:

> parseString (many $ filterParser (> 'm') letter) mempty "zzz2"
> Success "zzz"

> parseString (many $ filterParser (> 'm') letter) mempty "zzza"
> Failure (interactive):1:5: error: unexpected
> condition failed!

I was hoping the last example would also return Success "zzz". The call to unexpected seems to de-rail the entire parse, which isn't what I wanted.


Solution

  • You need to make filterParser recoverable by using try:

    import Text.Trifecta
    import Control.Applicative
    
    filterParser :: (a -> Bool) -> Parser a -> Parser a
    filterParser cond p = try $ do
      x <- p
      if cond x then return x else empty
    

    However, this would get rid of the custom parse error. Restoring that by using unexpected "Condition failed" in the else branch doesn't help either, because of the try.

    Instead, we can reinstate the custom error message after the try:

    filterParser :: (a -> Bool) -> Parser a -> Parser a
    filterParser cond p = (<|> unexpected "Condition failed") $ try $ do
      x <- p
      if cond x then return x else empty
    

    This works as expected:

    *Main> parseString (many $ filterParser (> 'm') letter) mempty "zzza"
    Success "zzz"
    

    and

    *Main> parseString (filterParser (> 'm') letter) mempty "a"
    Failure (interactive):1:1: error: unexpected
        Condition failed
    a<EOF>