module Codec.Scale.Compact (Compact(..)) where
import Control.Monad (replicateM)
import Data.Bits (shiftL, shiftR, (.&.), (.|.))
import Data.List (unfoldr)
import Data.Serialize.Get (getWord16le, getWord32le, getWord8,
lookAhead)
import Data.Serialize.Put (putWord16le, putWord32le, putWord8)
import Codec.Scale.Class (Decode (..), Encode (..))
newtype Compact a = Compact { unCompact :: a }
deriving (Eq, Ord)
instance Show a => Show (Compact a) where
show = ("Compact " ++) . show . unCompact
instance Integral a => Encode (Compact a) where
put (Compact x)
| n < 0 = error "negatives not supported by compact codec"
| n < 64 = singleByteMode
| n < 2^14 = twoByteMode
| n < 2^30 = fourByteMode
| n < 2^536 = bigIntegerMode
| otherwise = error $ "unable to encode " ++ show n ++ " as compact"
where
n = toInteger x
singleByteMode = putWord8 (fromIntegral x `shiftL` 2)
twoByteMode = putWord16le (fromIntegral x `shiftL` 2 .|. 1)
fourByteMode = putWord32le (fromIntegral x `shiftL` 2 .|. 2)
bigIntegerMode = do
let step 0 = Nothing
step i = Just (fromIntegral i, i `shiftR` 8)
unroll = unfoldr step n
putWord8 (fromIntegral (length unroll) `shiftL` 2 .|. 3)
mapM_ putWord8 unroll
instance Integral a => Decode (Compact a) where
get = do
mode <- lookAhead ((3 .&.) <$> getWord8)
Compact <$> case mode of
0 -> fromIntegral <$> singleByteMode
1 -> fromIntegral <$> twoByteMode
2 -> fromIntegral <$> fourByteMode
3 -> bigIntegerMode
_ -> fail "unexpected prefix decoding compact number"
where
singleByteMode = flip shiftR 2 <$> getWord8
twoByteMode = flip shiftR 2 <$> getWord16le
fourByteMode = flip shiftR 2 <$> getWord32le
bigIntegerMode = do
let unstep b a = a `shiftL` 8 .|. fromIntegral b
roll = fromInteger . foldr unstep 0
len <- flip shiftR 2 <$> getWord8
roll <$> replicateM (fromIntegral len) getWord8