leb128-binary-0.1.2: lib/Data/Binary/ZLEB128.hs
{-# LANGUAGE CPP #-}
#include <MachDeps.h>
-- | __ZigZag LEB128 codec__. This codec encodes the [ZigZag]
-- (https://en.wikipedia.org/wiki/Variable-length_quantity#Zigzag_encoding)
-- representation of a signed number through
-- [ULEB128](https://en.wikipedia.org/wiki/LEB128#Unsigned_LEB128).
--
-- Any /getXXX/ decoder can decode bytes generated using any of the /putXXX/
-- encoders, provided the encoded number fits in the target type.
--
-- __WARNING__: This is not compatible with the /Unsigned LEB128/ codec at
-- "Data.Binary.ULEB128" nor with the /Signed LEB128/ codec at
-- "Data.Binary.SLEB128".
module Data.Binary.ZLEB128 {--}
( ZLEB128(..)
-- * Put
, putInteger
, putInt64
, putInt32
, putInt16
, putInt8
, putInt
, putNatural
, putWord64
, putWord32
, putWord16
, putWord8
, putWord
-- * Get
, getInteger
, getInt64
, getInt32
, getInt16
, getInt8
, getInt
, getNatural
, getWord64
, getWord32
, getWord16
, getWord8
, getWord
) --}
where
import Data.Binary qualified as Bin
import Data.Binary.Get qualified as Bin
import Data.Bits
import GHC.Num.BigNat
import GHC.Num.Integer
import GHC.Num.Natural
import GHC.Int
import GHC.Word
import GHC.Exts
import Data.Binary.ULEB128 qualified as U
--------------------------------------------------------------------------------
-- | Newtype wrapper for 'Bin.Binary' encoding and decoding @x@ using the
-- /ZigZag LEB128/ codec. Useful in conjunction with @DerivingVia@.
newtype ZLEB128 x = ZLEB128 x
-- | Note: Maximum allowed number of input bytes is restricted to 1024.
-- Use 'putNatural' if you want a greater limit.
instance Bin.Binary (ZLEB128 Integer) where
put = coerce putInteger
{-# INLINE put #-}
get = coerce (getInteger 1024)
{-# INLINE get #-}
-- | Note: Maximum allowed number of input bytes is restricted to 1024.
-- Use 'putNatural' if you want a greater limit.
instance Bin.Binary (ZLEB128 Natural) where
put = coerce putNatural
{-# INLINE put #-}
get = coerce (getNatural 1024)
{-# INLINE get #-}
instance Bin.Binary (ZLEB128 Int) where
put = coerce putInt
{-# INLINE put #-}
get = coerce getInt
{-# INLINE get #-}
instance Bin.Binary (ZLEB128 Word) where
put = coerce putWord
{-# INLINE put #-}
get = coerce getWord
{-# INLINE get #-}
instance Bin.Binary (ZLEB128 Int8) where
put = coerce putInt8
{-# INLINE put #-}
get = coerce getInt8
{-# INLINE get #-}
instance Bin.Binary (ZLEB128 Word8) where
put = coerce putWord8
{-# INLINE put #-}
get = coerce getWord8
{-# INLINE get #-}
instance Bin.Binary (ZLEB128 Int16) where
put = coerce putInt16
{-# INLINE put #-}
get = coerce getInt16
{-# INLINE get #-}
instance Bin.Binary (ZLEB128 Word16) where
put = coerce putWord16
{-# INLINE put #-}
get = coerce getWord16
{-# INLINE get #-}
instance Bin.Binary (ZLEB128 Int32) where
put = coerce putInt32
{-# INLINE put #-}
get = coerce getInt32
{-# INLINE get #-}
instance Bin.Binary (ZLEB128 Word32) where
put = coerce putWord32
{-# INLINE put #-}
get = coerce getWord32
{-# INLINE get #-}
instance Bin.Binary (ZLEB128 Int64) where
put = coerce putInt64
{-# INLINE put #-}
get = coerce getInt64
{-# INLINE get #-}
instance Bin.Binary (ZLEB128 Word64) where
put = coerce putWord64
{-# INLINE put #-}
get = coerce getWord64
{-# INLINE get #-}
--------------------------------------------------------------------------------
putInteger :: Integer -> Bin.Put
-- putInteger = U.putNatural . _zigZagInteger
putInteger = \case
IS x | y <- zigZagInt (I# x) -> U.putWord y
IP x -> U.putNatural (NB (bigNatShiftL# x 1##))
IN x -> U.putNatural (NB (bigNatShiftL# x 1## `bigNatSubWordUnsafe#` 1##))
{-# INLINE putInteger #-}
putNatural :: Natural -> Bin.Put
putNatural = \n -> U.putNatural (unsafeShiftL n 1)
{-# INLINE putNatural #-}
putWord8 :: Word8 -> Bin.Put
putWord8 = putInt16 . fromIntegral
{-# INLINE putWord8 #-}
putWord16 :: Word16 -> Bin.Put
putWord16 = putInt32 . fromIntegral
{-# INLINE putWord16 #-}
putWord32 :: Word32 -> Bin.Put
putWord32 = putInt64 . fromIntegral
{-# INLINE putWord32 #-}
putWord64 :: Word64 -> Bin.Put
putWord64 = putInteger . fromIntegral
{-# INLINE putWord64 #-}
putWord :: Word -> Bin.Put
putWord = putInteger . fromIntegral
{-# INLINE putWord #-}
putInt8 :: Int8 -> Bin.Put
putInt8 = U.putWord8 . zigZagInt8
{-# INLINE putInt8 #-}
putInt16 :: Int16 -> Bin.Put
putInt16 = U.putWord16 . zigZagInt16
{-# INLINE putInt16 #-}
putInt32 :: Int32 -> Bin.Put
putInt32 = U.putWord32 . zigZagInt32
{-# INLINE putInt32 #-}
putInt64 :: Int64 -> Bin.Put
putInt64 = U.putWord64 . zigZagInt64
{-# INLINE putInt64 #-}
putInt :: Int -> Bin.Put
putInt = U.putWord . zigZagInt
{-# INLINE putInt #-}
--------------------------------------------------------------------------------
getInteger
:: Int
-- ^ /Maximum/ number of bytes to consume. If the 'Integer' number can be
-- determined before consuming this number of bytes, it will be. If @0@,
-- parsing fails.
--
-- Each ULEB128 byte encodes at most 7 bits of data. That is,
-- \(length(encoded) == \lceil\frac{length(data)}{7}\rceil\).
-> Bin.Get Integer
getInteger = fmap zagZigInteger . U.getNatural
{-# INLINE getInteger #-}
getNatural
:: Int
-- ^ /Maximum/ number of bytes to consume. If the 'Integer' number can be
-- determined before consuming this number of bytes, it will be. If @0@,
-- parsing fails.
--
-- Each ULEB128 byte encodes at most 7 bits of data. That is,
-- \(length(encoded) == \lceil\frac{length(data)}{7}\rceil\).
-> Bin.Get Natural
getNatural = \m -> do
i <- getInteger m
Bin.label "ZLEB128" $ naturalFromInteger i
{-# INLINE getNatural #-}
getBoundedIntegral
:: forall s u
. (Bits s, Integral s, Bits u, Integral u)
=> Bin.Get s
-> Bin.Get u
getBoundedIntegral = \gs -> do
s <- gs
Bin.label "ZLEB128" $ case toIntegralSized s of
Just u -> pure u
Nothing -> fail "underflow or overflow"
{-# INLINE getBoundedIntegral #-}
getInt8 :: Bin.Get Int8
getInt8 = zagZigInt8 <$> U.getWord8
{-# INLINE getInt8 #-}
getInt16 :: Bin.Get Int16
getInt16 = zagZigInt16 <$> U.getWord16
{-# INLINE getInt16 #-}
getInt32 :: Bin.Get Int32
getInt32 = zagZigInt32 <$> U.getWord32
{-# INLINE getInt32 #-}
getInt64 :: Bin.Get Int64
getInt64 = zagZigInt64 <$> U.getWord64
{-# INLINE getInt64 #-}
getInt :: Bin.Get Int
getInt = zagZigInt <$> U.getWord
{-# INLINE getInt #-}
getWord8 :: Bin.Get Word8
getWord8 = getBoundedIntegral getInt16
{-# INLINE getWord8 #-}
getWord16 :: Bin.Get Word16
getWord16 = getBoundedIntegral getInt32
{-# INLINE getWord16 #-}
getWord32 :: Bin.Get Word32
getWord32 = getBoundedIntegral getInt64
{-# INLINE getWord32 #-}
getWord64 :: Bin.Get Word64
getWord64 = getBoundedIntegral (getInteger 10)
{-# INLINE getWord64 #-}
getWord :: Bin.Get Word
getWord = getBoundedIntegral (getInteger 10)
{-# INLINE getWord #-}
--------------------------------------------------------------------------------
-- | OK, but not used. See putInteger.
{-# INLINE _zigZagInteger #-}
_zigZagInteger :: Integer -> Natural
_zigZagInteger = \case
IS x | W# y <- zigZagInt (I# x) -> NS y
IP x -> NB (bigNatShiftL# x 1##)
IN x -> NB (bigNatShiftL# x 1## `bigNatSubWordUnsafe#` 1##)
{-# INLINE zagZigInteger #-}
zagZigInteger :: Natural -> Integer
zagZigInteger = \case
NS x | I# y <- zagZigInt (W# x) -> IS y
NB x -- Unnecessary check because of Natural invariant:
-- | bigNatIsZero x -> IS 0#
| 0## <- and# 1## (indexWordArray# x 0#) -> IP (bigNatShiftR# x 1##)
| otherwise -> IN (bigNatShiftR# (bigNatAddWord# x 1##) 1##)
-- | @s@ is expected to be the signed version of @u@. This is not checked.
{-# INLINE unsafeZigZagFixed #-}
unsafeZigZagFixed
:: forall s u. (FiniteBits s, FiniteBits u, Integral s, Integral u) => s -> u
unsafeZigZagFixed =
let !n = finiteBitSize (undefined :: s) - 1
in \s -> fromIntegral $! xor (unsafeShiftL s 1) (unsafeShiftR s n)
-- | @u@ is expected to be the unsigned version of @s@. This is not checked.
{-# INLINE unsafeZagZigFixed #-}
unsafeZagZigFixed
:: forall u s. (FiniteBits u, FiniteBits s, Integral u, Integral s) => u -> s
unsafeZagZigFixed = \u ->
fromIntegral $! xor (unsafeShiftR u 1) (negate (u .&. 1))
{-# INLINE zigZagInt8 #-}
zigZagInt8 :: Int8 -> Word8
zigZagInt8 = unsafeZigZagFixed
{-# INLINE zagZigInt8 #-}
zagZigInt8 :: Word8 -> Int8
zagZigInt8 = unsafeZagZigFixed
{-# INLINE zigZagInt16 #-}
zigZagInt16 :: Int16 -> Word16
zigZagInt16 = unsafeZigZagFixed
{-# INLINE zagZigInt16 #-}
zagZigInt16 :: Word16 -> Int16
zagZigInt16 = unsafeZagZigFixed
{-# INLINE zigZagInt32 #-}
zigZagInt32 :: Int32 -> Word32
zigZagInt32 = unsafeZigZagFixed
{-# INLINE zagZigInt32 #-}
zagZigInt32 :: Word32 -> Int32
zagZigInt32 = unsafeZagZigFixed
{-# INLINE zigZagInt64 #-}
zigZagInt64 :: Int64 -> Word64
zigZagInt64 = unsafeZigZagFixed
{-# INLINE zagZigInt64 #-}
zagZigInt64 :: Word64 -> Int64
zagZigInt64 = unsafeZagZigFixed
{-# INLINE zigZagInt #-}
zigZagInt :: Int -> Word
zigZagInt = unsafeZigZagFixed
{-# INLINE zagZigInt #-}
zagZigInt :: Word -> Int
zagZigInt = unsafeZagZigFixed
{-# INLINE naturalFromInteger #-}
naturalFromInteger :: MonadFail m => Integer -> m Natural
naturalFromInteger = \case
IS x | isTrue# (0# <=# x) -> pure $ naturalFromWord# (int2Word# x)
IP x -> pure $ naturalFromBigNat# x
_ -> fail "underflow"