attoparsec-run-0.0.2.0: library/Data/Attoparsec/Run.hs
module Data.Attoparsec.Run where
import Data.Attoparsec.Types
import Data.IORef (newIORef, readIORef, writeIORef)
import Data.List (intercalate)
import Prelude (Either (..), Eq, Ord, Show, String, IO, Monoid, mempty,
pure, error, otherwise, null, ($), ($!), (++))
import Control.Monad.State (MonadState)
import qualified Control.Monad.State as State
data FinalResult i a = FinalResult
i -- ^ Remaining unparsed input
(Either ParseError a) -- ^ Either an error or a successfully parsed value
deriving (Eq, Ord, Show)
finalizeResult ::
IResult i a -- ^ Must be either 'Done' or 'Fail', not 'Partial'
-> FinalResult i a
finalizeResult r = case r of
Done remainder v ->
FinalResult remainder (Right v)
Fail remainder context message ->
FinalResult remainder (Left (ParseError context message))
Partial{} ->
error "parseWith should not return Partial"
data ParseError = ParseError
[String] -- ^ A list of contexts in which the error occurred
String -- ^ The message describing the error, if any
deriving (Eq, Ord, Show)
-- | Format a parse error in a matter suitable for displaying in log output
showParseError :: ParseError -> String
showParseError (ParseError context message)
| null context = message
| otherwise = intercalate " > " context ++ ": " ++ message
data BufferedInput m i = BufferedInput
i -- ^ Initial input
(m i) -- ^ Get the next chunk of input, or an empty string if the
-- end of input has been reached
{-| An effectful source of parser input which supports a "restore" operation
that can be used to push unused portions of input back to the source
For an example, see 'newRestorableIO'. -}
data RestorableInput m i = RestorableInput
(m i) -- ^ Get the next chunk of input, or an empty string if the
-- end of input has been reached
(i -> m ()) -- ^ Restore a non-empty chunk of input to the input stream
{-| Turn any 'IO' input source into a 'RestorableInput'
Internally, this is backed by an 'IORef' that holds any unparsed remainder. -}
newRestorableIO :: IO i -> IO (RestorableInput IO i)
newRestorableIO unbufferedGet = do
buffer <- newIORef [] -- The buffer stores the unparsed inputs
-- that have pushed back by "restore".
pure $ RestorableInput (get buffer) (restore buffer)
where
-- Restoring writes an input chunk to the top of the stack.
restore buffer x = do
xs <- readIORef buffer
writeIORef buffer $! (x : xs)
get buffer = do
bufferContent <- readIORef buffer
case bufferContent of
-- If the buffer is empty, then "get" just runs the action.
[] -> unbufferedGet
-- If there is content that has been pushed back onto the
-- buffer, then "get" pops a chunk off of the stack instead
-- of running the action.
(x : xs) -> do
writeIORef buffer $! xs
pure x
{-| A 'RestorableInput' in which getting and restoring are both backed
by 'MonadState' operations. -}
inputState :: (Monoid i, MonadState [i] m) => RestorableInput m i
inputState = RestorableInput get restore
where
get = do
xs <- State.get
case xs of
[] -> pure mempty
(x : xs') -> do
State.put xs'
pure x
restore x = State.modify' (x :)