{-# 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 #-}