{-# Language RankNTypes #-}
-- | The utilities in this module allow you to run Attoparsec parsers on input
-- flowing downstream through pipes, possibly interleaving other stream effects
-- while doing so.
--
-- This module builds on top of the @attoparsec@, @pipes@ and @pipes-parse@
-- libraries and assumes you understand how to use those.
module Pipes.Attoparsec
( -- * Parsing
parse
, parseMany
, isEndOfParserInput
-- * Types
, I.ParserInput
, I.ParsingError(..)
) where
--------------------------------------------------------------------------------
import Pipes
import qualified Pipes.Parse as Pp
import qualified Pipes.Lift as P
import qualified Pipes.Attoparsec.Internal as I
import qualified Control.Monad.Trans.State.Strict as S
import Data.Attoparsec.Types (Parser)
import Data.Monoid (Monoid(mempty))
--------------------------------------------------------------------------------
-- | Run an Attoparsec 'Parser' on input from the underlying 'Producer',
-- returning either a 'I.ParsingError' on failure, or a pair with the parsed
-- entity together with the length of input consumed in order to produce it.
--
-- Use this function only if 'isEndOfParserInput' returns 'False', otherwise
-- you'll get unexpected parsing errors.
parse
:: (Monad m, I.ParserInput a)
=> Parser a b -- ^Attoparsec parser.
-> S.StateT (Producer a m r) m (Either I.ParsingError (Int, b))
parse attoparser = do
(eb, mlo) <- I.parseWithDraw attoparser
case mlo of
Just lo -> Pp.unDraw lo
Nothing -> return ()
return eb
{-# INLINABLE parse #-}
-- | Continuously run an Attoparsec 'Parser' on input from the given 'Producer',
-- sending downstream pairs of each successfully parsed entity together with the
-- length of input consumed in order to produce it.
--
-- This 'Producer' runs until it either runs out of input or a parsing
-- failure occurs, in which case it returns 'Left' with a 'I.ParsingError' and a
-- 'Producer' with any leftovers. You can use 'P.errorP' to turn the 'Either'
-- return value into an 'Control.Monad.Trans.Error.ErrorT' monad transformer.
parseMany
:: (Monad m, I.ParserInput a)
=> Parser a b -- ^Attoparsec parser.
-> Producer a m r -- ^Producer from which to draw input.
-> Producer' (Int, b) m (Either (I.ParsingError, Producer a m r) r)
parseMany attoparser src = do
(me, src') <- P.runStateP src go
return $ case me of
Left e -> Left (e, src')
Right r -> Right r
where
go = do
eof <- lift isEndOfParserInput
if eof
then do
ra <- lift Pp.draw
case ra of
Left r -> return (Right r)
Right _ -> error "Pipes.Attoparsec.parseMany: impossible!!"
else do
eb <- lift (parse attoparser)
case eb of
Left e -> return (Left e)
Right b -> yield b >> go
--------------------------------------------------------------------------------
-- | Like 'P.isEndOfInput', except it also consumes and discards leading
-- empty 'I.ParserInput' chunks.
isEndOfParserInput
:: (I.ParserInput a, Monad m)
=> S.StateT (Producer a m r) m Bool
isEndOfParserInput = do
ma <- Pp.draw
case ma of
Left _ -> return True
Right a
| a == mempty -> isEndOfParserInput
| otherwise -> Pp.unDraw a >> return False
{-# INLINABLE isEndOfParserInput #-}