module Data.Binary.Bits.Put
( BitPut
, runBitPut
, joinPut
, putBool
, putWord8
, putWord16be
, putWord32be
, putWord64be
, putByteString
)
where
import qualified Data.Binary.Builder as B
import Data.Binary.Builder ( Builder )
import qualified Data.Binary.Put as Put
import Data.Binary.Put ( Put )
import Data.ByteString
import Control.Applicative
import Data.Bits
import Data.Monoid
import Data.Word
data BitPut a = BitPut { run :: (S -> PairS a) }
data PairS a = PairS a !S
data S = S !Builder !Word8 !Int
putBool :: Bool -> BitPut ()
putBool b = putWord8 1 (if b then 0xff else 0x00)
make_mask :: (Bits a, Num a) => Int -> a
make_mask n = (1 `shiftL` fromIntegral n) 1
putWord8 :: Int -> Word8 -> BitPut ()
putWord8 n w = BitPut $ \s -> PairS () $
let w' = make_mask n .&. w in
case s of
(S b t o) | n == 8 && o == 0 -> flush $ S b w n
| n <= 8 o -> flush $ S b (t .|. (w' `shiftL` (8 n o))) (o+n)
| otherwise -> flush $
let o' = o + n 8
b' = t .|. (w' `shiftR` o')
t' = w `shiftL` (8 o')
in S (b `mappend` B.singleton b') t' o'
putWord16be :: Int -> Word16 -> BitPut ()
putWord16be n w
| n <= 8 = putWord8 n (fromIntegral w)
| otherwise =
BitPut $ \s -> PairS () $
let w' = make_mask n .&. w in
case s of
(S b t o) | o + n <= 16 -> flush $
let o' = o + n 8
b' = t .|. fromIntegral (w' `shiftR` o')
t' = fromIntegral (w `shiftL` (8o'))
in (S (b `mappend` B.singleton b') t' o')
| otherwise -> flush $
let o' = o + n 16
b' = t .|. fromIntegral (w' `shiftR` (o' + 8))
b'' = fromIntegral ((w `shiftR` o') .&. 0xff)
t' = fromIntegral (w `shiftL` (8o'))
in (S (b `mappend` B.singleton b' `mappend` B.singleton b'') t' o')
putWord32be :: Int -> Word32 -> BitPut ()
putWord32be n w
| n <= 16 = putWord16be n (fromIntegral w)
| otherwise = do
putWord32be (n16) (w`shiftR`16)
putWord32be 16 (w .&. 0x0000ffff)
putWord64be :: Int -> Word64 -> BitPut ()
putWord64be n w
| n <= 32 = putWord32be n (fromIntegral w)
| otherwise = do
putWord64be (n32) (w`shiftR`32)
putWord64be 32 (w .&. 0xffffffff)
putByteString :: ByteString -> BitPut ()
putByteString bs = do
offset <- hasOffset
if offset
then mapM_ (putWord8 8) (unpack bs)
else joinPut (Put.putByteString bs)
where
hasOffset = BitPut $ \ s@(S _ _ o) -> PairS (o /= 0) s
joinPut :: 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)
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 (\s -> PairS a s)
(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''
return x = BitPut $ \s -> PairS x s