caerbannog-0.6.1.1: source/library/Data/Binary/Bits/Put.hs
-- | Put bits easily.
module Data.Binary.Bits.Put
( BitPut
, runBitPut
, joinPut
-- * Data types
-- ** Bool
, putBool
-- ** Words
, putWord8
, putWord16be
, putWord32be
, putWord64be
-- ** ByteString
, putByteString
) where
import Data.Bits ((.&.), (.|.))
import qualified Data.Binary.Builder as B
import qualified Data.Binary.Put as Put
import qualified Data.Bits as Bits
import qualified Data.ByteString as ByteString
import qualified Data.Word as Word
newtype BitPut a = BitPut
{ run :: S -> PairS a
}
data PairS a = PairS a {-# UNPACK #-} !S
data S = S !B.Builder !Word.Word8 !Int
-- | Put a 1 bit 'Bool'.
putBool :: Bool -> BitPut ()
putBool b = putWord8 1 (if b then 0xff else 0x00)
-- | makeMask 3 = 00000111
makeMask :: (Bits.Bits a, Num a) => Int -> a
makeMask n = (1 `Bits.shiftL` fromIntegral n) - 1
{-# SPECIALIZE makeMask :: Int -> Int #-}
{-# SPECIALIZE makeMask :: Int -> Word #-}
{-# SPECIALIZE makeMask :: Int -> Word.Word8 #-}
{-# SPECIALIZE makeMask :: Int -> Word.Word16 #-}
{-# SPECIALIZE makeMask :: Int -> Word.Word32 #-}
{-# SPECIALIZE makeMask :: Int -> Word.Word64 #-}
-- | Put the @n@ lower bits of a 'Word8'.
putWord8 :: Int -> Word.Word8 -> BitPut ()
putWord8 n w = BitPut $ \s ->
PairS ()
$ let w' = makeMask n .&. w
in
case s of
-- a whole word8, no offset
(S b t o)
| n == 8 && o == 0
-> flush $ S b w n
|
-- less than a word8, will fit in the current word8
n <= 8 - o
-> flush $ S b (t .|. (w' `Bits.shiftL` (8 - n - o))) (o + n)
|
-- will finish this word8, and spill into the next one
otherwise
-> flush
$ let
o' = o + n - 8
b' = t .|. (w' `Bits.shiftR` o')
t' = w `Bits.shiftL` (8 - o')
in S (b `mappend` B.singleton b') t' o'
-- | Put the @n@ lower bits of a 'Word16'.
putWord16be :: Int -> Word.Word16 -> BitPut ()
putWord16be n w
| n <= 8 = putWord8 n (fromIntegral w)
| otherwise = BitPut $ \s ->
PairS ()
$ let w' = makeMask n .&. w
in
case s of
-- as n>=9, it's too big to fit into one single byte
-- it'll either use 2 or 3 bytes
-- it'll fit in 2 bytes
(S b t o)
| o + n <= 16
-> flush
$ let
o' = o + n - 8
b' = t .|. fromIntegral (w' `Bits.shiftR` o')
t' = fromIntegral (w `Bits.shiftL` (8 - o'))
in S (b `mappend` B.singleton b') t' o'
|
-- 3 bytes required
otherwise
-> flush
$ let
o' = o + n - 16
b' = t .|. fromIntegral (w' `Bits.shiftR` (o' + 8))
b'' = fromIntegral ((w `Bits.shiftR` o') .&. 0xff)
t' = fromIntegral (w `Bits.shiftL` (8 - o'))
in S
(b `mappend` B.singleton b' `mappend` B.singleton b'')
t'
o'
-- | Put the @n@ lower bits of a 'Word32'.
putWord32be :: Int -> Word.Word32 -> BitPut ()
putWord32be n w
| n <= 16 = putWord16be n (fromIntegral w)
| otherwise = do
putWord32be (n - 16) (w `Bits.shiftR` 16)
putWord32be 16 (w .&. 0x0000ffff)
-- | Put the @n@ lower bits of a 'Word64'.
putWord64be :: Int -> Word.Word64 -> BitPut ()
putWord64be n w
| n <= 32 = putWord32be n (fromIntegral w)
| otherwise = do
putWord64be (n - 32) (w `Bits.shiftR` 32)
putWord64be 32 (w .&. 0xffffffff)
-- | Put a 'ByteString'.
putByteString :: ByteString.ByteString -> BitPut ()
putByteString bs = do
offset <- hasOffset
if offset
then mapM_ (putWord8 8) (ByteString.unpack bs) -- naive
else joinPut (Put.putByteString bs)
where hasOffset = BitPut $ \s@(S _ _ o) -> PairS (o /= 0) s
-- | Run a 'Put' inside 'BitPut'. Any partially written bytes will be flushed
-- before 'Put' executes to ensure byte alignment.
joinPut :: Put.Put -> BitPut ()
joinPut m = BitPut $ \s0 ->
PairS ()
$ let
(S b0 _ _) = flushIncomplete s0
b = Put.execPut m
in S (b0 `mappend` b) 0 0
flush :: S -> S
flush s@(S b w o)
| o > 8 = error "flush: offset > 8"
| o == 8 = S (b `mappend` B.singleton w) 0 0
| otherwise = s
flushIncomplete :: S -> S
flushIncomplete s@(S b w o)
| o == 0 = s
| otherwise = S (b `mappend` B.singleton w) 0 0
-- | Run the 'BitPut' monad inside 'Put'.
runBitPut :: BitPut () -> Put.Put
runBitPut m = Put.putBuilder b
where
PairS _ s = run m (S mempty 0 0)
(S b _ _) = flushIncomplete s
instance Functor BitPut where
fmap f (BitPut k) = BitPut $ \s -> let PairS x s' = k s in PairS (f x) s'
instance Applicative BitPut where
pure a = BitPut (PairS a)
(BitPut f) <*> (BitPut g) = BitPut $ \s ->
let
PairS a s' = f s
PairS b s'' = g s'
in PairS (a b) s''
instance Monad BitPut where
m >>= k = BitPut $ \s ->
let
PairS a s' = run m s
PairS b s'' = run (k a) s'
in PairS b s''