pipes-binary-0.2.0: src/Pipes/Binary.hs
{-# 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" #-}