packages feed

ipedb-0.2.0.0: vendor/leb128-0.1.0.0/src/Codec/LEB128/Generic.hs

{- HLINT ignore -}
{- FOURMOLU_DISABLE -}
{- |
    Module      : Codec.LEB128.Generic
    Description : Encode values via (S)LEB128
    Copyright   : (c) Andreas Klebinger 2020
    License     : BSD3
    Maintainer  : Andreas Klebinger
    Portability : GHC >= 7.10

     This module provides a generic interface over the encoding
     and decoding algorithm. It can be instantiated to a wide
     variate of types.

     Instantiations based on bytestring and lists are provided in the
     "Codec.LEB128.List" and "Codec.LEB128.Internal.BS" modules.

     Size checks for inputs or output types are not performed by default.
     However they can be included in the put/get functions if desired.
-}
module Codec.LEB128.Generic (
  encodeLEB128,
  decodeLEB128,
) where

import Data.Bits (Bits (clearBit, setBit, testBit, unsafeShiftL, unsafeShiftR, (.|.)))
import Data.Word (Word8)
import GHC.Exts (inline)

-- 2026-06-16:
-- The LEB128 constraint (present in the actual leb128 package) has been inlined.

{- |
LEB128-encode a unsigned value into a sequence of bytes.

For example to encode a integer into a list of words you might use.

> encodeLEB128 pure :: Integer -> [Word8]

To do the same using a serialization library like bytestrings builder:

> encodeLEB128 (B.word8)

For performance reasons it can be important to make sure @encodeLEB128@
is sufficiently specialized. One way to achieve this is to force inlining
using the @inline@ function from GHC.Magic (defined in the ghc-prim package).
For an efficient example generic over the value type this gives us for lists:

@
   toULEB128 :: (Integral a, Bits a) => a -> [Word8]
   toULEB128 = (inline G.encodeLEB128) pure
@

Results are undefined for negative numbers.
-}
encodeLEB128 :: forall a m. (Monoid m, Integral a, Bits a) => (Word8 -> m) -> a -> m
encodeLEB128 !putWord8 = go
 where
  go !i
    | i <= 127 =
        (inline putWord8) $! (fromIntegral i :: Word8)
    | otherwise =
        -- bit 7 (8th bit) indicates more to come.
        let !byte = (setBit (fromIntegral i) 7)
         in (inline putWord8) byte <> go (i `unsafeShiftR` 7)
{-# INLINE encodeLEB128 #-}

{- |
LEB128-decodes a unsigned value given a monadic way to request bytes.

For example a implementation over a state monad might look like:

> execState . decodeLEB128 getByte

This pattern is used by the bytestring based decoder in this package.
See there for a complete example.
-}
decodeLEB128 :: forall a m. (Monad m, Integral a, Bits a) => m Word8 -> m a
decodeLEB128 getWord8 = go 0 0
 where
  go :: Int -> a -> m a
  go !shift !w = do
    byte <- getWord8
    let !byteVal = fromIntegral (clearBit byte 7)
    let !hasMore = testBit byte 7
    let !val = w .|. (byteVal `unsafeShiftL` shift)
    let !shift' = shift + 7
    if hasMore
      then go shift' val
      else return $! val
{-# INLINE decodeLEB128 #-}