{-# LANGUAGE RankNTypes #-} -- | This module exports facilities that allow you to encode and decode Pipes -- streams of binary values. It builds on top of the @binary@, @pipes@ and -- @pipes-parse@ libraries, and assumes you know how to use those libraries. module Pipes.Binary ( -- * @Binary@ instances encode , decode , decodeMany -- * @Get@ monad , decodeGet , decodeGetMany -- * @Put@ monad , encodePut -- * Types , I.DecodingError(..) -- * Exports -- $exports , isEndOfBytes , module Data.Binary.Get ) where ------------------------------------------------------------------------------- import qualified Data.ByteString as B import qualified Data.ByteString.Lazy.Internal as BLI import Pipes import Pipes.Core import qualified Pipes.Binary.Internal as I import qualified Pipes.Lift as P import qualified Pipes.Parse as Pp import qualified Data.Binary as Bin (get, put) import qualified Data.Binary.Put as Put (runPut) -------------------------------------------------------------------------------- import Data.Binary (Binary) import Data.Binary.Get (ByteOffset, Get) import Data.Binary.Put (Put) -- $exports -- -- The following types are re-exported on this module for your convenience: -- -- [From "Data.Binary"] 'Binary'. -- -- [From "Data.Binary.Get"] 'Get', 'ByteOffset'. -- -- [From "Data.Binary.Put"] 'Put'. -------------------------------------------------------------------------------- -- | Try to decode leading output from the underlying 'Producer' into a -- 'Bin.Binary' instance, returning either a 'I.DecodingError' on failure, or a -- pair with the decoded entity together with the number of bytes consumed in -- order to produce it. -- -- /Do not/ use this function if 'isEndOfBytes' returns 'True', otherwise you -- may get unexpected decoding errors. decode :: (Monad m, Binary b) => Pp.StateT (Producer B.ByteString m r) m (Either I.DecodingError (ByteOffset, b)) -- ^ decode = decodeGet Bin.get {-# INLINABLE decode #-} -- | Like 'decode', except it takes an explicit 'Bin.Get' monad. decodeGet :: Monad m => Get b -- ^ -> Pp.StateT (Producer B.ByteString m r) m (Either I.DecodingError (ByteOffset, b)) decodeGet get = do (er, mlo) <- I.parseWithDraw get case mlo of Just lo -> Pp.unDraw lo Nothing -> return () return er {-# INLINABLE decodeGet #-} -- | Continuously decode output from the given 'Producer' into a 'Bin.Binary' -- instance, sending downstream pairs of each successfully decoded entity -- together with the number of bytes consumed in order to produce it. -- -- This 'Producer' runs until it either runs out of input or a decoding -- failure occurs, in which case it returns 'Left' with a 'I.DecodingError' 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. decodeMany :: (Monad m, Binary b) => Producer B.ByteString m r -- ^Producer from which to draw input. -> Producer' (ByteOffset, b) m (Either (I.DecodingError, Producer B.ByteString m r) r) decodeMany src = decodeGetMany Bin.get src {-# INLINABLE decodeMany #-} -- | Like 'decodeMany', except it takes an explicit 'Bin.Get' monad. decodeGetMany :: Monad m => Get b -> Producer B.ByteString m r -- ^Producer from which to draw input. -> Producer' (ByteOffset, b) m (Either (I.DecodingError, Producer B.ByteString m r) r) decodeGetMany get 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 isEndOfBytes if eof then do ra <- lift Pp.draw case ra of Left r -> return (Right r) Right _ -> error "Pipes.Binary.decodeGetMany: impossible!" else do eb <- lift (decodeGet get) case eb of Left e -> return (Left e) Right b -> yield b >> go {-# INLINABLE decodeGetMany #-} -------------------------------------------------------------------------------- -- | Encodes the given 'Bin.Binary' instance and sends it downstream in -- 'BS.ByteString' chunks. encode :: (Monad m, Binary x) => x -> Producer' B.ByteString m () encode = \x -> encodePut (Bin.put x) {-# INLINABLE encode #-} -- | Like 'encode', except it takes an explicit 'Bin.Put' monad. encodePut :: Monad m => Put -> Producer' B.ByteString m () encodePut = \put -> do BLI.foldrChunks (\e a -> respond e >> a) (return ()) (Put.runPut put) {-# INLINABLE encodePut #-} -------------------------------------------------------------------------------- -- XXX: this function is here until pipes-bytestring exports it -- | Checks if the underlying 'Producer' has any bytes left. -- Leading 'BS.empty' chunks are discarded. isEndOfBytes :: Monad m => Pp.StateT (Producer B.ByteString m r) m Bool isEndOfBytes = do ma <- Pp.draw case ma of Left _ -> return True Right a | B.null a -> isEndOfBytes | otherwise -> Pp.unDraw a >> return False {-# INLINABLE isEndOfBytes #-} {-# DEPRECATED isEndOfBytes "Will be removed as soon as the `pipes-bytestring` library exports it" #-}