binary-strict-0.3.1: src/Data/Binary/Strict/BitPut.hs
-----------------------------------------------------------------------------
-- |
-- Module : Data.Binary.Strict.BitPut
-- Copyright : Dominic Steinitz
-- License : BSD3-style (see LICENSE)
--
-- Maintainer : Dominic Steinitz <dominic.steinitz@blueyonder.co.uk>
-- 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.Strict.BitPut
( BitPut
, runBitPut
, putBit
, putNBits
, putBits
, putLeftByteString
) where
import Control.Monad
import Data.Word (Word8)
import Data.Bits (shiftL, shiftR, (.&.), (.|.), bitSize, Bits)
import qualified Data.ByteString as B
import Data.Binary.Strict.BitUtil
-- | The state of the BitPut.
-- The current offset is in [0..7]. The current byte is packed from MSB, downwards
data S = S {-# UNPACK #-} !B.ByteString -- ^ output
{-# UNPACK #-} !Word8 -- ^ bit offset in current byte
{-# UNPACK #-} !Word8 -- ^ current byte
deriving (Show)
newtype BitPut' a = BitPut' { unPut :: S -> (a,S) }
type BitPut = BitPut' ()
instance Functor BitPut' where
fmap f m = BitPut' (\s -> let (a,s') = unPut m s in (f a,s'))
instance Monad BitPut' where
return a = BitPut' (\s -> (a,s))
m >>= k = BitPut' (\s -> let (a,s') = unPut m s in unPut (k a) s')
get :: BitPut' S
get = BitPut' (\s -> (s,s))
put :: S -> BitPut
put s = BitPut' (const ((), s))
-- | Append a single bit
putBit :: Bool -> BitPut
putBit bit = do
S bytes boff curr <- get
let v = if bit then 1 else 0
newCurr = curr .|. (shiftL (fromIntegral v) (fromIntegral (7 - boff)))
newBoff = boff + 1
if newBoff == 8
then put (S (bytes `B.snoc` (curr .|. v)) 0 0)
else put (S bytes newBoff newCurr)
-- | 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
| n == 0 = return ()
| otherwise = do
S bytes boff curr <- get
let space = 8 - boff
if n < fromIntegral space
then do
let boff' = boff + fromIntegral n
shifted = (fromIntegral v .&. bottomNBits n) `shiftL` (8 - (fromIntegral boff + n))
curr' = curr .|. shifted
put $ S bytes boff' curr'
else do
let bits = v `shiftR` remainingBits
remainingBits = n - fromIntegral space
mask = bottomNBits $ fromIntegral space
bytes' = bytes `B.snoc` (curr .|. (mask .&. fromIntegral bits))
put $ S bytes' 0 0
putNBits remainingBits 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 the first n bits of a left aligned ByteString.
putLeftByteString :: Int -> B.ByteString -> BitPut
putLeftByteString bits bs
| bits < 8 = putNBits bits $ B.head bs
| otherwise = putBits (B.head bs) >> putLeftByteString (bits - 8) (B.tail bs)
runBitPut :: BitPut -> B.ByteString
runBitPut m = r where
(_, (S bytes boff curr)) = unPut m (S B.empty 0 0)
r = if boff > 0
then bytes `B.snoc` (topNBits (fromIntegral boff) .&. curr)
else bytes