Clinton
Clinton

Reputation: 23135

How to conditionally transform a character using `attoparsec`?

Using the attoparsec library, I've written this function here:

satisfyMaybe :: (Char -> Maybe a) -> Parser a
satisfyMaybe f =
  (fromJust . f) <$> satisfy (isJust . f)

Which basically applies f to the next character, and if it's Just x, then returns x, else fails the parse.

The thing is, I can't work out how to write this without using potentially failing functions like fromJust. I believe my code above is correct anyway and will never fail, but I feel like I'm using the library wrong if I need an unsafe function just to conditionally apply a function to a character from the input text.

Is there a way to use the library to write this function without fromJust or similar incomplete functions?

Upvotes: 3

Views: 52

Answers (2)

James Brock
James Brock

Reputation: 3426

I haven't run this code but I would try something like this:

satisfyMaybe :: (Char -> Maybe a) -> Parser a
satisfyMaybe f =
  try $ do
    c <- anyChar
    case (f c) of
      Just x -> pure x
      Nothing -> fail $ cons c " does not satisfy"

Upvotes: 0

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476493

Essentially we can replicate what satisfyWith :: (Word8 -> a) -> (a -> Bool) -> Parser a [Hackage] does [src]:

satisfyWith :: (Word8 -> a) -> (a -> Bool) -> Parser a
satisfyWith f p = do
  h <- peekWord8'
  let c = f h
  if p c
    then advance 1 >> return c
    else fail "satisfyWith"
{-# INLINE satisfyWith #-}

It thus first peeks the next word8' in case it satisfies the condition p c, it advances and returns c, otherwise it fails.

We thus can use this as:

import Data.ByteString.Internal(w2c)
import Data.Attoparsec.Text as DAT

satisfyMaybe :: (Char -> Maybe a) -> Parser a
satisfyMaybe f p = do
  h <- peekWord8'
  case f (c2w h) of
    Just r -> DAT.take 1 >> pure r
    _ -> fail "satisfyMaybe"

Upvotes: 3

Related Questions