{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, UndecidableInstances #-} ----------------------------------------------------------------------------- -- | -- Module : Data.Binary.BitPut -- Copyright : Dominic Steinitz -- License : BSD3-style (see LICENSE) -- -- Maintainer : Dominic Steinitz -- Stability : experimental -- -- This is the writer dual to BitGet. It allows one to append bits in a monad -- and get a strict ByteString as a result. Bits are appended from the MSB of -- the first byte towards the LSB of the last byte. -- -- This is best suited to small bit-fields because it accumulates bytes using -- snoc, so large results will cause a lot of copying. It would be possible -- to switch to using something similar to the Builder monad if need arises. -- However, since most protocols only have small bit fields, this should -- suffice for many cases. ----------------------------------------------------------------------------- module Data.Binary.BitPut ( BitPut , BitPutM , BitPutT , runBitPut , runBitPutM , runBitPutT , putBit , putBitT , putNBits , putNBitsT , putBits , putByteString , putLeftByteString ) where import Data.Bits (bitSize, Bits) import Control.Monad import Control.Monad.Trans import Control.Monad.Error import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as BL import qualified Data.Binary.BitBuilder as BB newtype BitPutM a = BitPutM { unPut :: (a, BB.BitBuilder) } type BitPut = BitPutM () instance Functor BitPutM where fmap f m = BitPutM (let (a, w) = unPut m in (f a, w)) instance Applicative BitPutM where pure = return (<*>) = ap instance Monad BitPutM where return a = BitPutM (a,BB.empty) m >>= k = BitPutM (let (a, w) = unPut m (b, w') = unPut (k a) in (b, w `BB.append` w')) m >> k = BitPutM (let (_, w) = unPut m (b, w') = unPut k in (b, w `BB.append` w')) {-# INLINE (>>) #-} newtype BitPutT m a = BitPutT { unPutT :: m (a, BB.BitBuilder) } -- | Append a single bit putBit :: Bool -> BitPut putBit bit = BitPutM ((), BB.singleton bit) -- | Append the bottom n bits of the given bits value. In the case that more -- bits are requested than the value provides, this acts as if the value -- has as unlimited number of leading 0 bits. putNBits :: (Integral a, Bits a) => Int -> a -> BitPut putNBits n v = BitPutM ((), BB.fromBits n v) -- | Append a value. Note that this function is undefined for instances of Bits -- which have no fixed bitsize (like Integer) putBits :: (Integral a, Bits a) => a -> BitPut putBits v = putNBits (bitSize v) v -- | Append a ByteString putByteString :: B.ByteString -> BitPut putByteString bs = BitPutM ((), BB.fromByteString (bs, 0)) -- | Append a left aligned ByteString where ByteString has a partial byte -- with the given number of valid bits, from the MSB downwards. The number -- of such bits must be 0..7. (A normal ByteString, which all bytes full -- would use 0) putLeftByteString :: (B.ByteString, Int) -> BitPut putLeftByteString bs = BitPutM ((), BB.fromByteString bs) runBitPut :: BitPut -> BL.ByteString runBitPut m = let (_, w) = unPut m in BB.toLazyByteString w runBitPutM :: BitPutM a -> (a, BL.ByteString) runBitPutM m = let (x, w) = unPut m in (x, BB.toLazyByteString w) instance Monad m => Functor (BitPutT m) where fmap f m = BitPutT $ do ~(x, w) <- unPutT m return (f x, w) instance Monad m => Applicative (BitPutT m) where pure = return (<*>) = ap instance Monad m => Monad (BitPutT m) where return a = BitPutT $ return (a, BB.empty) m >>= k = BitPutT $ do ~(a, w) <- unPutT m ~(b, w') <- unPutT (k a) return (b, w `BB.append` w') runBitPutT :: Monad m => BitPutT m a -> m (a, BL.ByteString) runBitPutT m = do ~(x, w) <- unPutT m return (x, BB.toLazyByteString w) putBitT :: (Monad m) => Bool -> BitPutT m () putBitT bit = BitPutT $ return ((), BB.singleton bit) putNBitsT :: (Monad m, Integral a, Bits a) => Int -> a -> BitPutT m () putNBitsT n v = BitPutT $ return ((), BB.fromBits n v) instance MonadTrans BitPutT where lift m = BitPutT $ do a <- m return (a, BB.empty) instance MonadError e m => MonadError e (BitPutT m) where throwError = lift . throwError m `catchError` h = BitPutT $ do unPutT m `catchError` \e -> unPutT (h e)