{-# LANGUAGE FlexibleContexts #-} -- | Variable length encodings -- -- * Unsigned Little Endian Base 128 (ULEB128) -- -- The word is splitted in chunks of 7 bits, starting from least significant -- bits. Each chunk is put in a Word8. The highest bit indicates if there is a -- following byte (0 false, 1 true) module Haskus.Number.VariableLength ( fromULEB128 , toULEB128 , getULEB128 , putULEB128 , getSLEB128 , putSLEB128 , getLEB128Buffer ) where import Haskus.Number.Word import Haskus.Number.Int import Haskus.Binary.Get import Haskus.Binary.Put import Haskus.Binary.Bits import Haskus.Binary.Bits.Put import Haskus.Binary.Bits.Order import Haskus.Binary.Buffer -- | Convert a stream of ULEB 128 bytes into an Integral -- -- >>> :set -XBinaryLiterals -- >>> import Control.Monad.Trans.State -- >>> getNext = do { ~(x:xs) <- get; put xs; pure x } -- >>> let x = evalState (fromULEB128 getNext) [0b10000001, 0b01111111] :: Word64 -- >>> x == 0b11111110000001 -- True fromULEB128 :: (Bits a, Monad m, Integral a) => m Word8 -> m a fromULEB128 getW8 = go 0 0 where go acc n = do a <- getW8 let w = fromIntegral (a .&. 0x7f) acc' = w `shiftL` n .|. acc if not (testBit a 7) then return acc' else go acc' (n+7) -- | Convert an Integral into a stream of ULEB128 bytes -- -- >>> :set -XBinaryLiterals -- >>> :set -XFlexibleContexts -- >>> let f = toULEB128 (putStr . (++ " ") . bitsToString) -- >>> f (0b1001001010101010 :: Word64) -- 10101010 10100101 00000010 toULEB128 :: (Bits a, Monad m, Integral a) => (Word8 -> m ()) -> a -> m () toULEB128 putW8 = goFirst where goFirst 0 = putW8 0 goFirst n = go n go 0 = pure () go x = do let r = x `shiftR` 7 w = fromIntegral (x .&. 0x7f) w' = if r == 0 then w else setBit w 7 putW8 w' go r -- | Get an unsigned word in Little Endian Base 128 getULEB128 :: (Integral a, Bits a) => Get a getULEB128 = fromULEB128 getWord8 -- | Put an unsigned word in Little Endian Base 128 putULEB128 :: (Integral a, Bits a) => a -> Put putULEB128 = toULEB128 putWord8 -- | Get a signed int in Little Endian Base 128 getSLEB128 :: (Integral a, Bits a) => Get a getSLEB128 = do let toInt8 :: Word8 -> Int8 toInt8 = fromIntegral a <- getWord8 if not (testBit a 7) then return . fromIntegral . toInt8 $ (a .&. 0x7f) .|. ((a .&. 0x40) `shiftL` 1) else do b <- getSLEB128 return $ (b `shiftL` 7) .|. (fromIntegral (a .&. 0x7f)) -- | Put a signed int in Little Endian Base 128 putSLEB128 :: (Integral a, Bits a) => a -> Put putSLEB128 a = rec a where ext = if a >= 0 then 0 else complement 0 rec x = do let r = x `shiftR` 7 w = x .&. 0x7f if r /= ext then do putWord8 (fromIntegral w .|. 0x80) rec r else if (testBit w 6 && a < 0) || (not (testBit w 6) && a >= 0) then putWord8 (fromIntegral w) -- no need for sign byte else do putWord8 (fromIntegral w .|. 0x80) putWord8 (fromIntegral ext .&. 0x7f) -- sign byte -- | Get a bytestring containing a decoded LEB128 string getLEB128Buffer :: BitOrder -> Get Buffer getLEB128Buffer bo = rec (newBitPutState bo) where rec state = do w <- getWord8 let state2 = putBits 7 w state case testBit w 7 of True -> rec state2 False -> return (getBitPutBuffer state2)