packages feed

leb128 (empty) → 0.1.0.0

raw patch · 13 files changed

+862/−0 lines, 13 filesdep +QuickCheckdep +basedep +bytestringsetup-changed

Dependencies added: QuickCheck, base, bytestring, criterion, deepseq, ghc-prim, leb128, test-framework, test-framework-quickcheck2, transformers

Files

+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history for leb128
+
+## 0.1.0.0 -- 2020-04-23
+
+* First version. Released on an unsuspecting world.
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2019, Andreas Klebinger
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Andreas Klebinger nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,16 @@+# General purpose library for encoding values to LEB128 encoded byte sequences.
+
+This library provides a generic interface to the core (S)LEB128 encoding
+algorithm.
+
+## Provided specific interfaces:
+
+* A specializations based on `bytestring` in "Codec.LEB128".
+* A specialization over lists in "Codec.LEB128.List".
+* Other implementations should be easy to derive from the interface provided
+in "Codec.LEB128.Generic"
+
+## Alternative implementations
+
+The package `leb128-cereal` provides a way to decode using a
+cereal parser using the same algorithm.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple
+main = defaultMain
+ bench/Bench.hs view
@@ -0,0 +1,98 @@+{-# LANGUAGE BangPatterns #-}++module Main (main) where++import Criterion.Main+import GHC.Exts++-- import Data.LEB+import Codec.LEB128.List+import qualified Codec.LEB128 as BS+import Data.Int+import Data.Word+import Data.ByteString (pack, ByteString)+import Control.DeepSeq+import Data.ByteString.Builder+import Data.ByteString.Builder.Internal (toLazyByteStringWith, safeStrategy, untrimmedStrategy)+import Data.ByteString.Lazy (toStrict)++bytes_maxBoundInt = [255,255,255,255,255,255,255,255,127] :: [Word8]+bytes_minBoundInt = [128,128,128,128,128,128,128,128,128,127] :: [Word8]+bytes_1 = [1] :: [Word8]++-- Going through the BS.Builder interface comes with a good deal+-- of overhead. Decoding a large integer can take as much as 4x+-- as long as decoding to a list directly for example.+-- However in exchange we get a fair bit of flexibility.+{-# INLINE build_bs #-}+build_bs :: Builder -> ByteString+build_bs = toStrict .+    (toLazyByteStringWith+        (untrimmedStrategy 11 123)+        mempty+    )++benchPut :: NFData a => (Word64 -> a) -> Benchmark+benchPut encoder = bgroup "putOutlined"+    [   bench "w64_0" $ nf encoder (0 :: Word64)+    ,   bench "w64_1" $ nf encoder (1 :: Word64)+    ,   bench "w64_255" $ nf encoder (255 :: Word64)+    ,   bench "w64_max" $ nf encoder (maxBound :: Word64)+    ]++benchGetBS :: NFData b => String -> (ByteString -> b) -> Benchmark+benchGetBS name decoder =+    bgroup name+        [   bench "w64_1  " $ nf (decoder) bs_1+        ,   bench "w64_max" $ nf (decoder) bs_maxBoundInt+        ]++bs_1 :: ByteString+bs_1 = pack [1]+bs_maxBoundInt :: ByteString+bs_maxBoundInt = pack $ toSLEB128 (maxBound :: Int) :: ByteString++main = do+  return $! force bs_1+  return $! force bs_maxBoundInt++  defaultMain+    [   bench "benchmark_overhead" $ nf (:[]) (0 :: Word64)+    ,   bgroup "list" [+            bgroup "unsigned"+            [   bgroup "put"+                    [   bench "w64_0" $ nf (toULEB128) (0 :: Word64)+                    ,   bench "w64_1" $ nf (toULEB128) (1 :: Word64)+                    ,   bench "w64_255" $ nf (toULEB128) (255 :: Word64)+                    ,   bench "w64_max" $ nf (toULEB128) (maxBound :: Word64)+                    ]+            ,   bgroup "get"+                    [   bench "w64_1  " $ whnf (fst . fromULEB128 :: [Word8] -> Word) bytes_1+                    ,   bench "w64_max" $ whnf (fst . fromULEB128 :: [Word8] -> Word) bytes_maxBoundInt+                    ]+            ]+        ,   bgroup "signed"+            [   bgroup "put"+                    [   bench "i64_0" $ whnf (sum . toSLEB128) (0 :: Int64)+                    ,   bench "i64_1" $ whnf (sum . toSLEB128) (1 :: Int64)+                    ,   bench "i64_255" $ whnf (sum . toSLEB128) (255 :: Int64)+                    ,   bench "i64_max" $ whnf (sum . toSLEB128) (maxBound :: Int64)+                    ]+            ,   bgroup "get"+                    [   bench "w64_1  " $ whnf (fst . fromSLEB128 :: [Word8] -> Int) bytes_1+                    ,   bench "w64_max" $ whnf (fst . fromSLEB128 :: [Word8] -> Int) bytes_maxBoundInt+                    ,   bench "w64_max" $ whnf (fst . fromSLEB128 :: [Word8] -> Int) bytes_minBoundInt+                    ]+            ]+        ]+    ,   bgroup "bs"+            [   benchPut BS.toULEB128ByteString+            ,   benchPut BS.toULEB128ByteString+            ,   benchGetBS "getInt" (BS.fromSLEB128ByteString :: ByteString -> (Maybe Int,ByteString))+            ,   benchGetBS "getInteger" (BS.fromSLEB128ByteString :: ByteString -> (Maybe Integer,ByteString))+            ,   benchGetBS "getIntUnsafe" (BS.fromSLEB128ByteStringUnsafe :: ByteString -> (Int,ByteString))+            ,   benchGetBS "getIntegerUnsafe" (BS.fromSLEB128ByteStringUnsafe :: ByteString -> (Integer,ByteString))+            ]++    ]+
+ leb128.cabal view
@@ -0,0 +1,69 @@+cabal-version:       1.22++name:                leb128+version:             0.1.0.0+synopsis:            LEB128 encoding logic for and in Haskell+description:         Encode and decode integral numbers to and from sequences of bytes using LEB128.+homepage:            https://github.com/AndreasPK/leb128/issues++-- bug-reports:+license:             BSD3+license-file:        LICENSE+author:              Andreas Klebinger+maintainer:          klebinger.andreas@gmx.at+-- copyright:+category:            Codec+build-type:          Simple+extra-source-files:  CHANGELOG.md, README.md++source-repository head+  type:     git+  location: https://github.com/AndreasPK/leb128++library+  exposed-modules:+    Codec.LEB128.List+    Codec.LEB128.Generic+    Codec.LEB128.Internal.BS+    Codec.LEB128+    Codec.LEB128.Constraints+  build-depends:+      base >= 4.8 && < 4.15+    , bytestring >= 0.10.6 && < 0.10.11+    , ghc-prim+    , transformers+  hs-source-dirs:      src+  default-language:    Haskell2010+  ghc-options: -Wall+  -- ghc-options: -ddump-stg-final -ddump-simpl -ddump-to-file+              --  -dno-typeable-binds+++test-suite leb128-test+  default-language:    Haskell2010+  type:                exitcode-stdio-1.0+  hs-source-dirs:      test+  main-is:+      TestLEB.hs+  other-modules:+      LEB128.Props++  build-depends:+      base+    , leb128 -any+    , QuickCheck >= 2.13 && < 2.15+    , test-framework >= 0.8 && < 0.9+    , test-framework-quickcheck2 >= 0.3 && < 0.4+    , bytestring++benchmark leb128-bench+  default-language:    Haskell2010+  type:                exitcode-stdio-1.0+  hs-source-dirs:      bench+  main-is:             Bench.hs+  build-depends:+      base+    , leb128 -any+    , criterion >= 1.5 && < 1.6+    , bytestring+    , deepseq
+ src/Codec/LEB128.hs view
@@ -0,0 +1,133 @@+{- | Encode numbers to and from bytestrings using the (S)LEB128 encoding.
+
+     The implementation is backed by the generic algorithms defined
+     in "Codec.LEB128.Generic".
+
+-}
+
+module Codec.LEB128
+    (
+    -- * Encode values as bytestring builder.
+      toULEB128Builder
+    , toSLEB128Builder
+    -- * Encode value as bytestring.
+    , toULEB128ByteString
+    , toSLEB128ByteString
+
+    -- * Encode value from bytestring.
+    , fromULEB128ByteString
+    , fromSLEB128ByteString
+    , fromULEB128ByteStringN
+    , fromSLEB128ByteStringI
+
+    -- * Encode value from bytestring. Throws an error if not enough bytes are provided.
+    , fromULEB128ByteStringUnsafe
+    , fromSLEB128ByteStringUnsafe
+    , fromULEB128ByteStringNUnsafe
+    , fromSLEB128ByteStringIUnsafe
+    )
+where
+
+import Numeric.Natural
+import Data.Word
+import Data.Int
+
+import Data.ByteString.Builder.Extra (toLazyByteStringWith, safeStrategy, AllocationStrategy)
+import Data.ByteString.Builder as B(Builder)
+import Data.ByteString.Lazy (toStrict)
+import Data.ByteString (ByteString)
+
+import Codec.LEB128.Constraints
+import Codec.LEB128.Internal.BS as B
+
+-- Builders
+
+{-# SPECIALIZE toULEB128Builder :: Natural -> Builder #-}
+{-# SPECIALIZE toULEB128Builder :: Word -> Builder #-}
+{-# SPECIALIZE toULEB128Builder :: Word64 -> Builder #-}
+{-# SPECIALIZE toULEB128Builder :: Word32 -> Builder #-}
+-- | Encode values as bytestring builder.
+{-# INLINEABLE toULEB128Builder #-}
+toULEB128Builder :: LEB128 a => a -> B.Builder
+toULEB128Builder = B.toULEB128
+
+{-# SPECIALIZE toSLEB128Builder :: Integer -> Builder #-}
+{-# SPECIALIZE toSLEB128Builder :: Int -> Builder #-}
+{-# SPECIALIZE toSLEB128Builder :: Int64 -> Builder #-}
+{-# SPECIALIZE toSLEB128Builder :: Int32 -> Builder #-}
+{-# INLINEABLE toSLEB128Builder #-}
+toSLEB128Builder :: SLEB128 a => a -> B.Builder
+toSLEB128Builder = B.toSLEB128
+
+smallChunks :: AllocationStrategy
+smallChunks = safeStrategy 12 64
+
+-- Conversion to encodede ByteString
+
+{-# SPECIALIZE toULEB128ByteString :: Natural -> ByteString #-}
+{-# SPECIALIZE toULEB128ByteString :: Word -> ByteString #-}
+{-# SPECIALIZE toULEB128ByteString :: Word64 -> ByteString #-}
+{-# SPECIALIZE toULEB128ByteString :: Word32 -> ByteString #-}
+{-# INLINEABLE toULEB128ByteString #-}
+toULEB128ByteString :: LEB128 a => a -> ByteString
+toULEB128ByteString = toStrict . toLazyByteStringWith smallChunks mempty . toULEB128Builder
+
+{-# SPECIALIZE toSLEB128ByteString :: Integer -> ByteString #-}
+{-# SPECIALIZE toSLEB128ByteString :: Int -> ByteString #-}
+{-# SPECIALIZE toSLEB128ByteString :: Int64 -> ByteString #-}
+{-# SPECIALIZE toSLEB128ByteString :: Int32 -> ByteString #-}
+{-# INLINEABLE toSLEB128ByteString #-}
+toSLEB128ByteString :: SLEB128 a => a -> ByteString
+toSLEB128ByteString = toStrict . toLazyByteStringWith smallChunks mempty . toSLEB128Builder
+
+-- Conversion from encoded ByteString
+
+{-# SPECIALIZE fromULEB128ByteString :: ByteString-> (Maybe Natural,ByteString) #-}
+{-# SPECIALIZE fromULEB128ByteString :: ByteString-> (Maybe Word,ByteString) #-}
+{-# SPECIALIZE fromULEB128ByteString :: ByteString-> (Maybe Word64,ByteString) #-}
+{-# SPECIALIZE fromULEB128ByteString :: ByteString-> (Maybe Word32,ByteString) #-}
+{-# INLINEABLE fromULEB128ByteString #-}
+fromULEB128ByteString :: LEB128 a => ByteString -> (Maybe a,ByteString)
+fromULEB128ByteString = B.fromULEB128
+
+{-# SPECIALIZE fromSLEB128ByteString :: ByteString-> (Maybe Integer,ByteString) #-}
+{-# SPECIALIZE fromSLEB128ByteString :: ByteString-> (Maybe Int,ByteString) #-}
+{-# SPECIALIZE fromSLEB128ByteString :: ByteString-> (Maybe Int64,ByteString) #-}
+{-# SPECIALIZE fromSLEB128ByteString :: ByteString-> (Maybe Int32,ByteString) #-}
+{-# INLINEABLE fromSLEB128ByteString #-}
+fromSLEB128ByteString :: SLEB128 a => ByteString -> (Maybe a,ByteString)
+fromSLEB128ByteString = B.fromSLEB128
+
+{-# NOINLINE fromULEB128ByteStringN #-}
+fromULEB128ByteStringN :: ByteString -> (Maybe Natural,ByteString)
+fromULEB128ByteStringN = B.fromULEB128
+
+{-# NOINLINE fromSLEB128ByteStringI #-}
+fromSLEB128ByteStringI :: ByteString -> (Maybe Integer,ByteString)
+fromSLEB128ByteStringI = B.fromSLEB128
+
+-- Be partial if not enough bytes are provided.
+
+{-# SPECIALIZE fromULEB128ByteStringUnsafe :: ByteString-> (Natural,ByteString) #-}
+{-# SPECIALIZE fromULEB128ByteStringUnsafe :: ByteString-> (Word,ByteString) #-}
+{-# SPECIALIZE fromULEB128ByteStringUnsafe :: ByteString-> (Word64,ByteString) #-}
+{-# SPECIALIZE fromULEB128ByteStringUnsafe :: ByteString-> (Word32,ByteString) #-}
+{-# INLINEABLE fromULEB128ByteStringUnsafe #-}
+fromULEB128ByteStringUnsafe :: LEB128 a => ByteString -> (a,ByteString)
+fromULEB128ByteStringUnsafe = B.fromULEB128Unsafe
+
+{-# SPECIALIZE fromSLEB128ByteStringUnsafe :: ByteString-> (Integer,ByteString) #-}
+{-# SPECIALIZE fromSLEB128ByteStringUnsafe :: ByteString-> (Int,ByteString) #-}
+{-# SPECIALIZE fromSLEB128ByteStringUnsafe :: ByteString-> (Int64,ByteString) #-}
+{-# SPECIALIZE fromSLEB128ByteStringUnsafe :: ByteString-> (Int32,ByteString) #-}
+{-# INLINEABLE fromSLEB128ByteStringUnsafe #-}
+fromSLEB128ByteStringUnsafe :: SLEB128 a => ByteString -> (a,ByteString)
+fromSLEB128ByteStringUnsafe = B.fromSLEB128Unsafe
+
+{-# NOINLINE fromULEB128ByteStringNUnsafe #-}
+fromULEB128ByteStringNUnsafe :: ByteString -> (Natural,ByteString)
+fromULEB128ByteStringNUnsafe = B.fromULEB128Unsafe
+
+{-# NOINLINE fromSLEB128ByteStringIUnsafe #-}
+fromSLEB128ByteStringIUnsafe :: ByteString -> (Integer,ByteString)
+fromSLEB128ByteStringIUnsafe = B.fromSLEB128Unsafe
+ src/Codec/LEB128/Constraints.hs view
@@ -0,0 +1,62 @@+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+
+{- |
+    Module      : Codec.LEB128.Constraints
+    Description : Constrain encoding to compatible types.
+    Copyright   : (c) Andreas Klebinger 2020
+    License     : BSD3
+    Maintainer  : Andreas Klebinger
+    Portability : GHC >= 7.10
+
+    SLEB128 is a synonym for the required constraints to encode values
+    in the signed version of LEB128.
+
+    LEB128 is a typeclass which by default limits encoding in unsigned LEB128
+    format to GHC-Provided untyped valued.
+
+    UnsafeAnyLEB128 is a newtype providing an explicit way to avoid this
+    restriction for cases where the type is signed, but values are not.
+-}
+
+module Codec.LEB128.Constraints
+    ( LEB128
+    , SLEB128
+    , UnsafeAnyLEB128(..)
+    )
+where
+
+import Numeric.Natural
+import Data.Word
+import Data.Int
+import Data.Bits
+
+newtype UnsafeAnyLEB128 a = UnsafeAnyLEB128 a deriving (Eq,Bits,Num,Ord,Real,Enum,Integral)
+
+-- | Indicates that a type can safely be encoded as (unsigned) LEB128.
+class (Bits a, Integral a) => LEB128 a where
+-- | Indicates that a type can safely be encoded as (signed) SLEB128.
+class (Bits a, Integral a) => SLEB128 a where
+
+-- | Unsafe escape hatch to force a particular encoding.
+instance (Bits a, Integral a) => LEB128  (UnsafeAnyLEB128 a)
+-- | Unsafe escape hatch to force a particular encoding.
+instance (Bits a, Integral a) => SLEB128 (UnsafeAnyLEB128 a)
+
+-- Some instances
+instance LEB128 Natural
+instance LEB128 Word
+instance LEB128 Word8
+instance LEB128 Word16
+instance LEB128 Word32
+instance LEB128 Word64
+
+-- Some instances
+instance SLEB128 Integer
+instance SLEB128 Int
+instance SLEB128 Int8
+instance SLEB128 Int16
+instance SLEB128 Int32
+instance SLEB128 Int64
+
+ src/Codec/LEB128/Generic.hs view
@@ -0,0 +1,141 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE BangPatterns #-}+++{- |+    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+  (+    -- * Generic encoding functions+    encodeLEB128+  , encodeSLEB128+    -- * Generic decoding functions+  , decodeLEB128+  , decodeSLEB128)+where++-- import Control.Applicative+import Data.Bits ((.|.), unsafeShiftR, unsafeShiftL,+                  testBit, clearBit, setBit, bit)+import Data.Word+import Data.Monoid ((<>))+import Prelude hiding ((<>))+import GHC.Magic++import Codec.LEB128.Constraints++-- | 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.+{-# INLINE encodeLEB128 #-}+encodeLEB128 :: forall a m. (Monoid m, LEB128 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)++-- | SLEB128-encodes an singed value into a sequence of bytes.+--+-- Works the same as @encodeLEB128@ but supports negative values.+{-# INLINE encodeSLEB128 #-}+encodeSLEB128 :: forall a m. (Monoid m, SLEB128 a) => (Word8 -> m) -> a -> m+encodeSLEB128 putWord8 = go+  where+    go val = do+        let !byte = fromIntegral (clearBit val 7) :: Word8+        let !val' = val `unsafeShiftR` 7+        let !signBit = testBit byte 6+        let !done =+                -- Unsigned value, val' == 0 and last value can+                -- be discriminated from a negative number.+                (val' == 0 && not signBit) ||+                -- Signed value,+                (val' == -1 && signBit)+        let !byte' = if done then byte else setBit byte 7+        putWord8 byte' <> if done then mempty else go val'++-- | 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.+{-# INLINE decodeLEB128 #-}+decodeLEB128 :: forall a m. (Monad m, LEB128 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++-- | SLEB128-decodes a unsigned number given a monadic way to request bytes.+--+-- Same as decodeLEB128 but for the signed encoding.+{-# INLINE decodeSLEB128 #-}+decodeSLEB128 :: forall a m. (Monad m, SLEB128 a) => m Word8 -> m a+decodeSLEB128 getWord8 = go 0 0+  where+    go :: Int -> a -> m a+    go !shift !w = do+        byte <- getWord8 :: m Word8+        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 do+                let !signed = testBit byte 6+                if signed+                then pure $! val - bit shift'+                else pure $! val
+ src/Codec/LEB128/Internal/BS.hs view
@@ -0,0 +1,108 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE FlexibleInstances #-}++{- |+    Module      : Codec.LEB128.Internal.BS+    Description : Encode values via (S)LEB128 using bytestring.+    Copyright   : (c) Andreas Klebinger 2020+    License     : BSD3+    Maintainer  : Andreas Klebinger+    Portability : GHC >= 7.10++     This module specializes the generic algorithms defined+     in "Codec.LEB128.Generic" to use ByteString as byte+     sequence representation.+-}++module Codec.LEB128.Internal.BS+  ( fromULEB128+  , fromSLEB128+  , fromULEB128Unsafe+  , fromSLEB128Unsafe+  , toULEB128+  , toSLEB128+  )+where++import Data.Word (Word8)+import Data.Maybe+import GHC.Magic+import qualified Data.ByteString.Builder as B+import qualified Data.ByteString as BS++import Control.Monad.Trans.State.Strict+import Control.Monad.Trans.Maybe+import Control.Monad.Trans.Class+import Control.Monad++import Codec.LEB128.Constraints+import Codec.LEB128.Generic as G++-- | Encode a unsigned value as bytestring builder in LEB128 encoding.+{-# INLINEABLE toULEB128 #-}+toULEB128 :: LEB128 a => a -> B.Builder+toULEB128 = (inline G.encodeLEB128) (B.word8)++-- | Encode a signed value as bytestring builder in SLEB128 encoding.+{-# INLINEABLE toSLEB128 #-}+toSLEB128 :: SLEB128 a => a -> B.Builder+toSLEB128 = (inline G.encodeSLEB128) (B.word8)++type UnsafeByteProvider = State (BS.ByteString)++{-# INLINABLE getByteUnsafe #-}+getByteUnsafe :: UnsafeByteProvider Word8+getByteUnsafe = do+    (bs) <- get+    let (!byte,!bs') = fromMaybe (error "Not enough bytes") $ BS.uncons bs+    put $! bs'+    return byte++{-# INLINABLE fromULEB128Unsafe #-}+-- | Decode a value in unsigned LEB128 encoding and return remaining bytes.+fromULEB128Unsafe :: LEB128 a => BS.ByteString -> (a,BS.ByteString)+fromULEB128Unsafe bytes = runState+                    ((inline G.decodeLEB128) getByteUnsafe)+                    bytes++{-# INLINABLE fromSLEB128Unsafe #-}+-- | Decode a value in (signed) SLEB128 encoding and return remaining bytes.+fromSLEB128Unsafe :: SLEB128 a => BS.ByteString -> (a,BS.ByteString)+fromSLEB128Unsafe bytes = runState+                    ((inline G.decodeSLEB128) getByteUnsafe)+                    bytes++type ByteProvider a = MaybeT (State BS.ByteString) a++{-# INLINE runByteProvider #-}+runByteProvider :: ByteProvider a -> BS.ByteString -> (Maybe a, BS.ByteString)+runByteProvider action = runState (runMaybeT action)++{-# INLINE liftMaybe #-}+liftMaybe :: (MonadPlus m) => Maybe a -> m a+liftMaybe = maybe mzero return++{-# INLINABLE getByte #-}+getByte :: (ByteProvider Word8)+getByte = do+    (bs) <- lift get+    (!byte,!bs') <- liftMaybe (BS.uncons bs)+    lift $! put $! bs'+    return byte++{-# INLINABLE fromULEB128 #-}+-- | Decode a value in unsigned LEB128 encoding and return remaining bytes.+fromULEB128 :: forall a. LEB128 a => BS.ByteString -> (Maybe a,BS.ByteString)+fromULEB128 =+    let decode = (inline G.decodeLEB128) getByte :: MaybeT (State BS.ByteString) a+    in runByteProvider decode++{-# INLINABLE fromSLEB128 #-}+-- | Decode a value in (signed) SLEB128 encoding and return remaining bytes.+fromSLEB128 :: forall a. SLEB128 a => BS.ByteString -> (Maybe a,BS.ByteString)+fromSLEB128 =+    let decode = (inline G.decodeSLEB128) getByte :: MaybeT (State BS.ByteString) a+    in runByteProvider decode++
+ src/Codec/LEB128/List.hs view
@@ -0,0 +1,65 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE InstanceSigs #-}++{- |+    Module      : Codec.LEB128.Generic+    Description : Encode values via (S)LEB128 to/from lists.+    Copyright   : (c) Andreas Klebinger 2020+    License     : BSD3+    Maintainer  : Andreas Klebinger+    Portability : GHC >= 7.10++   The implementation is backed by the generic algorithms defined+   in "Codec.LEB128.Generic".++   The code is quite fast but does not fuse.+-}++module Codec.LEB128.List+  ( fromULEB128+  , fromSLEB128+  , toULEB128+  , toSLEB128+  )+where++import Data.Word+import GHC.Magic++import Control.Monad.Trans.State.Strict++import Codec.LEB128.Constraints+import Codec.LEB128.Generic as G++-- | Encode a __unsigned__ value in LEB128.+{-# INLINEABLE toULEB128 #-}+toULEB128 :: LEB128 a => a -> [Word8]+toULEB128 = (inline G.encodeLEB128) pure++-- | Encode a __signed__ value in LEB128.+{-# INLINEABLE toSLEB128 #-}+toSLEB128 :: SLEB128 a => a -> [Word8]+toSLEB128 = (inline G.encodeSLEB128) pure++type ByteProvider a = State [Word8] a++{-# INLINE getByte #-}+getByte :: ByteProvider Word8+getByte = do+  wds <- get+  case wds of+    [] -> error "decode LEB128: Not enough bytes"+    (x:xs) -> put xs >> return x++{-# INLINEABLE fromULEB128 #-}+-- | Decode a __unsigned__ value from LEB128 encoding.+fromULEB128 :: LEB128 a => [Word8] -> (a,[Word8])+fromULEB128 = runState ((inline G.decodeLEB128) getByte)++{-# INLINEABLE fromSLEB128 #-}+-- | Decode a __signed__ value from SLEB128 encoding.+fromSLEB128 :: SLEB128 a => [Word8] -> (a,[Word8])+fromSLEB128 = runState ((inline G.decodeSLEB128) getByte)
+ test/LEB128/Props.hs view
@@ -0,0 +1,30 @@+{-# LANGUAGE TemplateHaskell #-}
+
+module LEB128.Props
+
+where
+
+import Data.Monoid (mempty)
+import Test.Framework (defaultMain, defaultMainWithOpts, testGroup)
+import Test.Framework.Options (TestOptions, TestOptions'(..))
+import Test.Framework.Runners.Options (RunnerOptions, RunnerOptions'(..))
+import Test.Framework.Providers.QuickCheck2 (testProperty)
+
+import Test.QuickCheck
+import Test.QuickCheck.All
+
+import Codec.LEB128
+
+import Data.Int
+import Data.Word
+import Data.ByteString (ByteString)
+
+
+prop_roundtrip_bs :: Large Word -> Bool
+prop_roundtrip_bs (Large w) =
+    w == (fst (fromULEB128ByteStringUnsafe $ toULEB128ByteString w))
+
+prop_roundtrip_bs_2 :: Large Word -> Bool
+prop_roundtrip_bs_2 (Large w) =
+    toULEB128ByteString w == ((toULEB128ByteString :: Word -> ByteString) . fst . fromULEB128ByteStringUnsafe $ toULEB128ByteString w)
+
+ test/TestLEB.hs view
@@ -0,0 +1,103 @@+{-# LANGUAGE TemplateHaskell #-}
+
+module Main
+where
+
+import Data.Monoid (mempty)
+import Test.Framework (defaultMain, defaultMainWithOpts, testGroup)
+import Test.Framework.Options (TestOptions, TestOptions'(..))
+import Test.Framework.Runners.Options (RunnerOptions, RunnerOptions'(..))
+import Test.Framework.Providers.QuickCheck2 (testProperty)
+
+import Test.QuickCheck
+import Test.QuickCheck.All
+
+import Codec.LEB128.Constraints
+import Codec.LEB128.List as L
+import Codec.LEB128.Generic as G
+
+import Data.Int
+import Data.Word
+
+import LEB128.Props
+
+main = defaultMain tests
+
+mainWithOpts = do
+  -- Test options can also be specified in the code. The TestOptions
+  -- type is an instance of the Monoid type class, so the easiest way
+  -- to get an empty set of options is with `mempty`.
+  let empty_test_opts = mempty :: TestOptions
+
+  -- We update the empty TestOptions with our desired values.
+  let my_test_opts = empty_test_opts {
+    topt_maximum_generated_tests = Just 5000
+  }
+
+  -- Now we create an empty RunnerOptions in the same way, and add
+  -- our TestOptions to it.
+  let empty_runner_opts = mempty :: RunnerOptions
+  let my_runner_opts = empty_runner_opts {
+    ropt_test_options = Just my_test_opts
+  }
+
+  defaultMainWithOpts tests my_runner_opts
+
+tests = [
+        testGroup "Roundtrips" [
+                testProperty "unsigned_w32" prop_roundTripUW32,
+                testProperty "unsigned_i16" prop_roundTripUW16,
+                testProperty "signed_i32" prop_roundTripSI32,
+                testProperty "signed_i16" prop_roundTripSI16,
+                testProperty "uleb128/=leb128" (expectFailure prop_uleb_eq_leb)
+            ],
+        testGroup "ByteString"
+            [ testProperty "rountrip_1" prop_roundtrip_bs
+            , testProperty "rountrip_2" prop_roundtrip_bs_2
+            ]
+        -- testGroup "Sorting Group 2" [
+        --         testGroup "Nested Group 1" [
+        --               testProperty "sort4" prop_sort4,
+        --               testProperty "sort5" prop_sort5,
+        --               testProperty "sort6" prop_sort6
+        --             ],
+        --         testProperty "sort7" prop_sort7,
+        --         testCase "sort8" test_sort8,
+        --         testCase "sort9" test_sort9
+        --     ]
+    ]
+
+prop_roundTripUW32 :: Word32 -> Bool
+prop_roundTripUW32 w =
+  w == fst (L.fromULEB128 (L.toULEB128 w))
+
+prop_roundTripUW16 :: Word16 -> Bool
+prop_roundTripUW16 w =
+  w == fst ( L.fromULEB128 (L.toULEB128 w))
+
+prop_roundTripSI32 :: Int32 -> Bool
+prop_roundTripSI32 w =
+  w  == fst ( L.fromSLEB128 (L.toSLEB128 w))
+
+prop_roundTripSI16 :: Int16 -> Bool
+prop_roundTripSI16 w =
+  w  == fst (fromSLEB128 (toSLEB128 w))
+
+prop_roundTripBytes :: Bool
+prop_roundTripBytes =
+    let bytes = [201,202,203,1]
+    in bytes == toSLEB128 (fst . fromSLEB128 $ bytes :: Int)
+
+-- This is supposed to fail!
+prop_uleb_eq_leb :: Int16 -> Bool
+prop_uleb_eq_leb x = ((toSLEB128 $ UnsafeAnyLEB128 x) == toULEB128 (UnsafeAnyLEB128 x))
+
+
+
+
+-- --------------------------
+-- return []
+-- runTests :: IO Bool
+-- runTests = $verboseCheckAll
+
+-- main = runTests