{-# LANGUAGE Rank2Types, FlexibleContexts #-} -- |Monadic and General Iteratees: -- incremental input parsers, processors and transformers module Bio.Iteratee.Iteratee ( -- * Types EnumerateeHandler -- ** Error handling ,throwErr ,throwRecoverableErr ,checkErr -- ** Basic Iteratees ,skipToEof ,isStreamFinished -- ** Iteratee composition ,mBind ,mBind_ ,ioBind ,ioBind_ -- ** Chunkwise Iteratees ,mapChunksM_ ,foldChunksM ,getChunk ,getChunks -- ** Nested iteratee combinators ,mapChunks ,mapChunksM ,convStream ,unfoldConvStream ,unfoldConvStreamCheck ,joinI ,joinIM -- * Enumerators ,Enumerator ,Enumeratee -- ** Basic enumerators ,enumChunk ,enumEof ,enumErr ,enumPure1Chunk ,enumList ,enumCheckIfDone ,enumFromCallback ,enumFromCallbackCatch -- ** Enumerator Combinators ,eneeCheckIfDone ,eneeCheckIfDoneHandle ,eneeCheckIfDoneIgnore ,eneeCheckIfDonePass ,mergeEnums -- ** Enumeratee Combinators ,($=) ,(=$) ,(><>) ,(<><) -- * Misc. ,seek -- * Classes ,module Bio.Iteratee.Base ) where import Bio.Iteratee.Base import Bio.Prelude hiding (loop) import Control.Monad.IO.Class (MonadIO(..)) import Control.Monad.Trans.Class (MonadTrans(..)) -- exception helpers excDivergent :: SomeException excDivergent = toException DivergentException -- ------------------------------------------------------------------------ -- Primitive iteratees -- |Report and propagate an unrecoverable error. -- Disregard the input first and then propagate the error. This error -- cannot be handled by 'enumFromCallbackCatch', although it can be cleared -- by 'checkErr'. throwErr :: SomeException -> Iteratee s m a throwErr e = icont (const (throwErr e)) (Just e) -- |Report and propagate a recoverable error. This error can be handled by -- both 'enumFromCallbackCatch' and 'checkErr'. throwRecoverableErr :: SomeException -> (Stream s -> Iteratee s m a) -> Iteratee s m a throwRecoverableErr e i = icont i (Just e) -- |Check if an iteratee produces an error. -- Returns @Right a@ if it completes without errors, otherwise -- @Left SomeException@. 'checkErr' is useful for iteratees that may not -- terminate, such as @Data.Iteratee.head@ with an empty stream. checkErr :: (NullPoint s) => Iteratee s m a -> Iteratee s m (Either SomeException a) checkErr iter = Iteratee $ \onDone onCont -> let od = onDone . Right oc k Nothing = onCont (checkErr . k) Nothing oc _ (Just e) = onDone (Left e) (Chunk emptyP) in runIter iter od oc -- ------------------------------------------------------------------------ -- Parser combinators -- |Get the stream status of an iteratee. isStreamFinished :: (Nullable s) => Iteratee s m (Maybe SomeException) isStreamFinished = liftI check where check s@(Chunk xs) | nullC xs = isStreamFinished | otherwise = idone Nothing s check s@(EOF e) = idone (Just $ fromMaybe (toException EofException) e) s {-# INLINE isStreamFinished #-} -- |Skip the rest of the stream skipToEof :: Iteratee s m () skipToEof = icont check Nothing where check (Chunk _) = skipToEof check s = idone () s -- |Seek to a position in the stream seek :: Nullable s => FileOffset -> Iteratee s m () seek o = throwRecoverableErr (toException $ SeekException o) (idone ()) -- | Map a monadic function over the chunks of the stream and ignore the -- result. Useful for creating efficient monadic iteratee consumers, e.g. -- -- > logger = mapChunksM_ (liftIO . putStrLn) -- -- these can be efficiently run in parallel with other iteratees via -- @Data.Iteratee.ListLike.zip@. mapChunksM_ :: (Monad m, Nullable s) => (s -> m b) -> Iteratee s m () mapChunksM_ f = liftI step where step (Chunk xs) | nullC xs = liftI step | otherwise = lift (f xs) >> liftI step step s@(EOF _) = idone () s {-# INLINE mapChunksM_ #-} -- | A fold over chunks foldChunksM :: (Monad m, Nullable s) => (a -> s -> m a) -> a -> Iteratee s m a foldChunksM f = liftI . go where go a (Chunk c) = lift (f a c) >>= liftI . go go a e = idone a e {-# INLINE foldChunksM #-} -- | Get the current chunk from the stream. getChunk :: Nullable s => Iteratee s m s getChunk = liftI step where step (Chunk xs) | nullC xs = liftI step | otherwise = idone xs $ Chunk emptyP step (EOF Nothing) = throwErr $ toException EofException step (EOF (Just e)) = throwErr e {-# INLINE getChunk #-} -- | Get a list of all chunks from the stream. getChunks :: (Nullable s) => Iteratee s m [s] getChunks = liftI (step id) where step acc (Chunk xs) | nullC xs = liftI (step acc) | otherwise = liftI (step $ acc . (xs:)) step acc stream = idone (acc []) stream {-# INLINE getChunks #-} -- --------------------------------------------------- -- The converters show a different way of composing two iteratees: -- `vertical' rather than `horizontal' type Enumeratee sFrom sTo m a = Iteratee sTo m a -> Iteratee sFrom m (Iteratee sTo m a) -- The following pattern appears often in Enumeratee code {-# INLINE eneeCheckIfDone #-} -- | Utility function for creating enumeratees. Typical usage is demonstrated -- by the @breakE@ definition. -- -- > breakE -- > :: (Monad m, LL.ListLike s el, NullPoint s) -- > => (el -> Bool) -- > -> Enumeratee s s m a -- > breakE cpred = eneeCheckIfDone (liftI . step) -- > where -- > step k (Chunk s) -- > | LL.null s = liftI (step k) -- > | otherwise = case LL.break cpred s of -- > (str', tail') -- > | LL.null tail' -> eneeCheckIfDone (liftI . step) . k $ Chunk str' -- > | otherwise -> idone (k $ Chunk str') (Chunk tail') -- > step k stream = idone (k stream) stream -- eneeCheckIfDone :: (Monad m, NullPoint elo) => ((Stream eli -> Iteratee eli m a) -> Iteratee elo m (Iteratee eli m a)) -> Enumeratee elo eli m a eneeCheckIfDone f = eneeCheckIfDonePass f' where f' k Nothing = f k f' k (Just e) = throwRecoverableErr e (\s -> joinIM $ enumChunk s $ eneeCheckIfDone f (liftI k)) type EnumerateeHandler eli elo m a = (Stream eli -> Iteratee eli m a) -> SomeException -> Iteratee elo m (Iteratee eli m a) -- | The same as eneeCheckIfDonePass, with one extra argument: -- a handler which is used -- to process any exceptions in a separate method. eneeCheckIfDoneHandle :: (NullPoint elo) => EnumerateeHandler eli elo m a -> ((Stream eli -> Iteratee eli m a) -> Maybe SomeException -> Iteratee elo m (Iteratee eli m a) ) -> Enumeratee elo eli m a eneeCheckIfDoneHandle h f inner = Iteratee $ \od oc -> let onDone x s = od (idone x s) (Chunk emptyP) onCont k Nothing = runIter (f k Nothing) od oc onCont k (Just e) = runIter (h k e) od oc in runIter inner onDone onCont {-# INLINABLE eneeCheckIfDoneHandle #-} eneeCheckIfDonePass :: (NullPoint elo) => ((Stream eli -> Iteratee eli m a) -> Maybe SomeException -> Iteratee elo m (Iteratee eli m a) ) -> Enumeratee elo eli m a eneeCheckIfDonePass f = eneeCheckIfDoneHandle (\k e -> f k (Just e)) f {-# INLINABLE eneeCheckIfDonePass #-} eneeCheckIfDoneIgnore :: (NullPoint elo) => ((Stream eli -> Iteratee eli m a) -> Maybe SomeException -> Iteratee elo m (Iteratee eli m a) ) -> Enumeratee elo eli m a eneeCheckIfDoneIgnore f = eneeCheckIfDoneHandle (\k _ -> f k Nothing) f {-# INLINE mBind #-} -- | Lifts a monadic action and combines it with a continuation. -- @mBind m f@ is the same as @lift m >>= f@, but does not require a -- 'Nullable' constraint on the stream type. infixl 1 `mBind` mBind :: Monad m => m a -> (a -> Iteratee s m b) -> Iteratee s m b mBind m f = Iteratee $ \onDone onCont -> m >>= \a -> runIter (f a) onDone onCont {-# INLINE mBind_ #-} -- | Lifts a monadic action, ignored the result and combines it with a -- continuation. @mBind_ m f@ is the same as @lift m >>= f@, but does -- not require a 'Nullable' constraint on the stream type. infixl 1 `mBind_` mBind_ :: Monad m => m a -> Iteratee s m b -> Iteratee s m b mBind_ m b = Iteratee $ \onDone onCont -> m >> runIter b onDone onCont {-# INLINE ioBind #-} -- | Lifts an IO action and combines it with a continuation. -- @ioBind m f@ is the same as @liftIO m >>= f@, but does not require a -- 'Nullable' constraint on the stream type. infixl 1 `ioBind` ioBind :: MonadIO m => IO a -> (a -> Iteratee s m b) -> Iteratee s m b ioBind m f = Iteratee $ \onDone onCont -> liftIO m >>= \a -> runIter (f a) onDone onCont {-# INLINE ioBind_ #-} -- | Lifts an IO action, ignores its result, and combines it with a -- continuation. @ioBind_ m f@ is the same as @liftIO m >> f@, but does -- not require a 'Nullable' constraint on the stream type. infixl 1 `ioBind_` ioBind_ :: MonadIO m => IO a -> Iteratee s m b -> Iteratee s m b ioBind_ m b = Iteratee $ \onDone onCont -> liftIO m >> runIter b onDone onCont -- | Convert one stream into another with the supplied mapping function. -- This function operates on whole chunks at a time, contrasting to -- @mapStream@ which operates on single elements. -- -- > unpacker :: Enumeratee B.ByteString [Word8] m a -- > unpacker = mapChunks B.unpack -- mapChunks :: (NullPoint s) => (s -> s') -> Enumeratee s s' m a mapChunks f = eneeCheckIfDonePass (icont . step) where step k (Chunk xs) = eneeCheckIfDonePass (icont . step) . k . Chunk $ f xs step k str@(EOF mErr) = idone (k $ EOF mErr) str {-# INLINE mapChunks #-} -- | Convert a stream of @s@ to a stream of @s'@ using the supplied function. mapChunksM :: (Monad m, NullPoint s) => (s -> m s') -> Enumeratee s s' m a mapChunksM f = eneeCheckIfDonePass (icont . step) where step k (Chunk xs) = f xs `mBind` eneeCheckIfDonePass (icont . step) . k . Chunk step k str = idone (liftI k) str {-# INLINE mapChunksM #-} -- |Convert one stream into another, not necessarily in lockstep. -- -- The transformer mapStream maps one element of the outer stream -- to one element of the nested stream. The transformer below is more -- general: it may take several elements of the outer stream to produce -- one element of the inner stream, or the other way around. -- The transformation from one stream to the other is specified as -- Iteratee s m s'. convStream :: (Monad m, Nullable s) => Iteratee s m s' -> Enumeratee s s' m a convStream fi = eneeCheckIfDonePass check where check k (Just e) = throwRecoverableErr e (idone ()) >> check k Nothing check k _ = isStreamFinished >>= maybe (step k) (idone (liftI k) . EOF . Just) step k = fi >>= eneeCheckIfDonePass check . k . Chunk {-# INLINABLE convStream #-} -- |The most general stream converter. Given a function to produce iteratee -- transformers and an initial state, convert the stream using iteratees -- generated by the function while continually updating the internal state. unfoldConvStream :: (Monad m, Nullable s) => (acc -> Iteratee s m (acc, s')) -> acc -> Enumeratee s s' m a unfoldConvStream f acc0 = eneeCheckIfDonePass (check acc0) where check acc k (Just e) = throwRecoverableErr e (idone ()) >> check acc k Nothing check acc k _ = isStreamFinished >>= maybe (step acc k) (idone (liftI k) . EOF . Just) step acc k = f acc >>= \(acc', s') -> eneeCheckIfDonePass (check acc') . k . Chunk $ s' {-# INLINABLE unfoldConvStream #-} unfoldConvStreamCheck :: (Monad m, Nullable elo) => (((Stream eli -> Iteratee eli m a) -> Maybe SomeException -> Iteratee elo m (Iteratee eli m a) ) -> Enumeratee elo eli m a ) -> (acc -> Iteratee elo m (acc, eli)) -> acc -> Enumeratee elo eli m a unfoldConvStreamCheck checkDone f acc0 = checkDone (check acc0) where check acc k mX = isStreamFinished >>= maybe (step acc k mX) (idone (icont k mX) . EOF . Just) step acc k Nothing = f acc >>= \(acc', s') -> (checkDone (check acc') . k $ Chunk s') step acc k (Just ex) = throwRecoverableErr ex $ \str' -> let i = f acc >>= \(acc', s') -> (checkDone (check acc') . k $ Chunk s') in joinIM $ enumChunk str' i {-# INLINABLE unfoldConvStreamCheck #-} -- | Collapse a nested iteratee. The inner iteratee is terminated by @EOF@. -- Errors are propagated through the result. -- -- The stream resumes from the point of the outer iteratee; any remaining -- input in the inner iteratee will be lost. -- Differs from 'Control.Monad.join' in that the inner iteratee is terminated, -- and may have a different stream type than the result. joinI :: (Monad m, Nullable s) => Iteratee s m (Iteratee s' m a) -> Iteratee s m a joinI = (>>= \inner -> Iteratee $ \od oc -> let onDone x _ = od x (Chunk emptyP) onCont k Nothing = runIter (k (EOF Nothing)) onDone onCont' onCont _ (Just e) = runIter (throwErr e) od oc onCont' _ e = runIter (throwErr (fromMaybe excDivergent e)) od oc in runIter inner onDone onCont) {-# INLINE joinI #-} -- | Lift an iteratee inside a monad to an iteratee. joinIM :: (Monad m) => m (Iteratee s m a) -> Iteratee s m a joinIM mIter = Iteratee $ \od oc -> mIter >>= \iter -> runIter iter od oc -- ------------------------------------------------------------------------ -- Enumerators -- | Each enumerator takes an iteratee and returns an iteratee -- -- an Enumerator is an iteratee transformer. -- The enumerator normally stops when the stream is terminated -- or when the iteratee moves to the done state, whichever comes first. -- When to stop is of course up to the enumerator... type Enumerator s m a = Iteratee s m a -> m (Iteratee s m a) -- |Applies the iteratee to the given stream. This wraps 'enumEof', -- 'enumErr', and 'enumPure1Chunk', calling the appropriate enumerator -- based upon 'Stream'. enumChunk :: (Monad m) => Stream s -> Enumerator s m a enumChunk (Chunk xs) = enumPure1Chunk xs enumChunk (EOF Nothing) = enumEof enumChunk (EOF (Just e)) = enumErr e -- |The most primitive enumerator: applies the iteratee to the terminated -- stream. The result is the iteratee in the Done state. It is an error -- if the iteratee does not terminate on EOF. enumEof :: (Monad m) => Enumerator s m a enumEof iter = runIter iter onDone onCont where onDone x _str = return $ idone x (EOF Nothing) onCont k Nothing = runIter (k (EOF Nothing)) onDone onCont' onCont k e = return $ icont k e onCont' _ Nothing = return $ throwErr excDivergent onCont' k e = return $ icont k e -- |Another primitive enumerator: tell the Iteratee the stream terminated -- with an error. enumErr :: (Exception e, Monad m) => e -> Enumerator s m a enumErr e iter = runIter iter onDone onCont where onDone x _ = return $ idone x (EOF . Just $ toException e) onCont k Nothing = runIter (k (EOF (Just (toException e)))) onDone onCont' onCont k e' = return $ icont k e' onCont' _ Nothing = return $ throwErr excDivergent onCont' k e' = return $ icont k e' infixr 0 =$ -- | Combines an Enumeratee from @s@ to @s'@ and an Iteratee that -- consumes @s'@ into an Iteratee which consumes @s@ (=$) :: (Nullable s, Monad m) => Enumeratee s s' m a -> Iteratee s' m a -> Iteratee s m a (=$) = (.) joinI infixl 1 $= -- | Combines Enumerator which produces stream of @s@ and @Enumeratee@ -- which transforms stream of @s@ to stream -- of @s'@ to into Enumerator which produces stream of @s'@ ($=) :: Monad m => (forall a. Enumerator s m a) -> Enumeratee s s' m b -> Enumerator s' m b ($=) enum enee iter = enum (enee iter) >>= run -- | Enumeratee composition -- Run the second enumeratee within the first. In this example, stream2list -- is run within the 'takeStream 10', which is itself run within 'takeStream 15', resulting -- in 15 elements being consumed -- -- >>> run =<< enumPure1Chunk [1..1000 :: Int] (joinI $ (I.takeStream 15 ><> I.takeStream 10) I.stream2list) -- [1,2,3,4,5,6,7,8,9,10] -- (><>) :: (Nullable s1, Monad m) => (forall x . Enumeratee s1 s2 m x) -> Enumeratee s2 s3 m a -> Enumeratee s1 s3 m a f ><> g = joinI . f . g -- | enumeratee composition with the arguments flipped, see '><>' (<><) :: (Nullable s1, Monad m) => Enumeratee s2 s3 m a -> (forall x. Enumeratee s1 s2 m x) -> Enumeratee s1 s3 m a f <>< g = joinI . g . f -- | Combine enumeration over two streams. The merging enumeratee would -- typically be the result of 'Data.Iteratee.ListLike.merge' or -- 'Data.Iteratee.ListLike.mergeByChunks' (see @merge@ for example). mergeEnums :: (Nullable s2, Nullable s1, Monad m) => Enumerator s1 m a -- ^ inner enumerator -> Enumerator s2 (Iteratee s1 m) a -- ^ outer enumerator -> Enumeratee s2 s1 (Iteratee s1 m) a -- ^ merging enumeratee -> Enumerator s1 m a mergeEnums e1 e2 etee i = e1 $ e2 (joinI . etee $ ilift lift i) >>= run {-# INLINE mergeEnums #-} -- | The pure 1-chunk enumerator -- -- It passes a given list of elements to the iteratee in one chunk -- This enumerator does no IO and is useful for testing of base parsing enumPure1Chunk :: (Monad m) => s -> Enumerator s m a enumPure1Chunk str iter = runIter iter idoneM onCont where onCont k Nothing = return $ k $ Chunk str onCont k e = return $ icont k e -- | Enumerate chunks from a list -- enumList :: (Monad m) => [s] -> Enumerator s m a enumList = go where go [] i = return i go xs' i = runIter i idoneM (onCont xs') where onCont (x:xs) k Nothing = go xs . k $ Chunk x onCont _ _ (Just e) = return $ throwErr e onCont _ k Nothing = return $ icont k Nothing {-# INLINABLE enumList #-} -- | Checks if an iteratee has finished. -- -- This enumerator runs the iteratee, performing any monadic actions. -- If the result is True, the returned iteratee is done. enumCheckIfDone :: (Monad m) => Iteratee s m a -> m (Bool, Iteratee s m a) enumCheckIfDone iter = runIter iter onDone onCont where onDone x str = return (True, idone x str) onCont k e = return (False, icont k e) {-# INLINE enumCheckIfDone #-} -- |Create an enumerator from a callback function enumFromCallback :: (Monad m, NullPoint s) => (st -> m (Either SomeException ((Bool, st), s))) -> st -> Enumerator s m a enumFromCallback c = loop where loop st iter = runIter iter idoneM (onCont st) check k (True, st') = loop st' . k . Chunk check k (False,_st') = return . k . Chunk onCont st k Nothing = c st >>= either (return . k . EOF . Just) (uncurry (check k)) onCont _st k j = return (icont k j) -- |Create an enumerator from a callback function with an exception handler. -- The exception handler is called if an iteratee reports an exception. enumFromCallbackCatch :: (IException e, Monad m, NullPoint s) => (st -> m (Either SomeException ((Bool, st), s))) -> (e -> m (Maybe EnumException)) -> st -> Enumerator s m a enumFromCallbackCatch c handler = loop where loop st iter = runIter iter idoneM (onCont st) check k (True, st') = loop st' . k . Chunk check k (False,_st') = return . k . Chunk onCont st k Nothing = c st >>= either (return . k . EOF . Just) (uncurry (check k)) onCont st k j@(Just e) = case fromException e of Just e' -> handler e' >>= maybe (loop st . k $ Chunk emptyP) (return . icont k . Just) . fmap toException Nothing -> return (icont k j) {-# INLINE enumFromCallbackCatch #-}