BillyBadBoy
BillyBadBoy

Reputation: 580

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.

Upvotes: 2

Views: 347

Answers (2)

BillyBadBoy
BillyBadBoy

Reputation: 580

In addition to the solution suggested by Cactus, there is the following:


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

This seems to give me what I want:

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

> parseString (filterParser (> 'm') letter) mempty "4"
> Failure (interactive):1:1: error: expected: letter

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

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

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

Upvotes: 1

Cactus
Cactus

Reputation: 27626

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> 

Upvotes: 1

Related Questions