Reputation: 580
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
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
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