{-# 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 , module Data.Binary , module Data.Binary.Get , module Data.Binary.Put ) 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 Pipes.ByteString.Parse (isEndOfBytes) 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. -- -- Hint: You can easily turn this 'Producer'' into a 'Pipe' that encodes -- 'Binary' instances as they flow downstream using: -- -- @ -- 'for' 'cat' 'encode' :: ('Monad' m, 'Binary' a) => 'Pipe' a 'B.ByteString' m r -- @ 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 #-}