packages feed

bytebuild 0.3.9.0 → 0.3.10.0

raw patch · 5 files changed

+157/−20 lines, 5 filesdep +zigzagdep ~QuickCheckdep ~basedep ~bytestringPVP ok

version bump matches the API change (PVP)

Dependencies added: zigzag

Dependency ranges changed: QuickCheck, base, bytestring

API changes (from Hackage documentation)

+ Data.Bytes.Builder: copyCons :: Word8 -> Bytes -> Builder
+ Data.Bytes.Builder: int32LEB128 :: Int32 -> Builder
+ Data.Bytes.Builder: int64LEB128 :: Int64 -> Builder
+ Data.Bytes.Builder: rebuild :: Builder -> Builder
+ Data.Bytes.Builder: sevenEightRight :: Bytes -> Builder
+ Data.Bytes.Builder: sevenEightSmile :: Bytes -> Builder
+ Data.Bytes.Builder: word32LEB128 :: Word32 -> Builder
+ Data.Bytes.Builder.Bounded: word32LEB128 :: Word32 -> Builder 5

Files

CHANGELOG.md view
@@ -5,6 +5,15 @@ `small-bytearray-builder` is now just a compatibility shim to ease the migration process. +## 0.3.10.0 -- 2022-03-01++* Add upper bound on base since this does not build with GHC 9.2.+* Add `rebuild`+* Add `copyCons` for copying small byte sequences with extra byte in front+* Use `zigzag` library from hackage+* Seven eights encoding+* Finish out LEB for 32-bit and signed 32/64+ ## 0.3.9.0 -- 2021-11-19  * Add `cstring#`
bytebuild.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: bytebuild-version: 0.3.9.0+version: 0.3.10.0 synopsis: Serialize to a small byte arrays description:   This is similar to the builder facilities provided by@@ -46,9 +46,9 @@   reexported-modules:     Data.Bytes.Chunks   build-depends:-    , base >=4.12.0.0 && <5+    , base >=4.12.0.0 && <4.16     , byteslice >=0.2.6 && <0.3-    , bytestring >=0.10.8.2 && <0.11+    , bytestring >=0.10.8.2 && <0.12     , haskell-src-meta >=0.8     , integer-logarithms >=1.0.3 && <1.1     , natural-arithmetic >=0.1 && <0.2@@ -58,6 +58,7 @@     , template-haskell >=2.16     , text-short >=0.1.3 && <0.2     , wide-word >=0.1.0.9 && <0.2+    , zigzag   if flag(checked)     build-depends: primitive-checked >= 0.7 && <0.8   else@@ -77,7 +78,7 @@     HexWord64     Word16Tree   build-depends:-    , QuickCheck >=2.13.1 && <2.14+    , QuickCheck >=2.13.1 && <2.15     , base >=4.12.0.0 && <5     , bytebuild     , byteslice
src/Data/Bytes/Builder.hs view
@@ -22,6 +22,7 @@     -- * Materialized Byte Sequences   , bytes   , copy+  , copyCons   , copy2   , insert   , byteArray@@ -32,6 +33,9 @@   , cstring#   , cstringLen   , stringUtf8+    -- * Byte Sequence Encodings+  , sevenEightRight+  , sevenEightSmile     -- * Encode Integral Types     -- ** Human-Readable   , word64Dec@@ -91,7 +95,10 @@   , int16LE     -- **** LEB128   , intLEB128+  , int32LEB128+  , int64LEB128   , wordLEB128+  , word32LEB128   , word64LEB128     -- *** Many   , word8Array@@ -125,24 +132,26 @@   , replicate     -- * Control   , flush+    -- * Rebuild+  , rebuild   ) where  import Prelude hiding (replicate)  import Control.Exception (SomeException,toException)-import Control.Monad.ST (ST,runST) import Control.Monad.IO.Class (MonadIO,liftIO)-import Data.Bits (unsafeShiftR,unsafeShiftL,xor,finiteBitSize)+import Control.Monad.ST (ST,runST)+import Data.Bits ((.&.),(.|.),unsafeShiftL,unsafeShiftR)+import Data.Bytes.Builder.Unsafe (addCommitsLength,copyReverseCommits) import Data.Bytes.Builder.Unsafe (Builder(Builder),commitDistance1) import Data.Bytes.Builder.Unsafe (BuilderState(BuilderState),pasteIO) import Data.Bytes.Builder.Unsafe (Commits(Initial,Mutable,Immutable))-import Data.Bytes.Builder.Unsafe (reverseCommitsOntoChunks) import Data.Bytes.Builder.Unsafe (commitsOntoChunks)+import Data.Bytes.Builder.Unsafe (reverseCommitsOntoChunks) import Data.Bytes.Builder.Unsafe (stringUtf8,cstring,fromEffect)-import Data.Bytes.Builder.Unsafe (addCommitsLength,copyReverseCommits)-import Data.ByteString.Short.Internal (ShortByteString(SBS)) import Data.Bytes.Chunks (Chunks(ChunksNil)) import Data.Bytes.Types (Bytes(Bytes),MutableBytes(MutableBytes))+import Data.ByteString.Short.Internal (ShortByteString(SBS)) import Data.Char (ord) import Data.Foldable (foldlM) import Data.Int (Int64,Int32,Int16,Int8)@@ -150,11 +159,12 @@ import Data.Text.Short (ShortText) import Data.WideWord (Word128,Word256) import Data.Word (Word64,Word32,Word16,Word8)+import Data.Word.Zigzag (toZigzagNative,toZigzag32,toZigzag64) import Foreign.C.String (CStringLen) import GHC.ByteOrder (ByteOrder(BigEndian,LittleEndian),targetByteOrder)+import GHC.Exts (Addr#,(*#),oneShot) import GHC.Exts (Int(I#),Char(C#),Int#,State#,ByteArray#,(>=#)) import GHC.Exts (RealWorld,(+#),(-#),(<#))-import GHC.Exts (Addr#,(*#)) import GHC.Integer.Logarithms.Compat (integerLog2#) import GHC.IO (IO(IO),stToIO) import GHC.Natural (naturalFromInteger,naturalToInteger)@@ -164,6 +174,7 @@  import qualified Arithmetic.Nat as Nat import qualified Arithmetic.Types as Arithmetic+import qualified Data.Bytes as Bytes import qualified Data.Bytes.Builder.Bounded as Bounded import qualified Data.Bytes.Builder.Bounded.Unsafe as UnsafeBounded import qualified Data.Primitive as PM@@ -192,8 +203,8 @@   reverseCommitsOntoChunks cs0 cs  -- | Variant of 'runOnto' that conses the additional chunks--- in reverse order. -reversedOnto :: +-- in reverse order.+reversedOnto ::      Int -- ^ Size of initial chunk (use 4080 if uncertain)   -> Builder -- ^ Builder   -> Chunks@@ -392,6 +403,22 @@   where   !(I# newSz) = max (I# slen#) 4080 +-- | Variant of 'copy' that additionally pastes an extra byte in+-- front of the bytes.+copyCons :: Word8 -> Bytes -> Builder+copyCons (W8# w0) (Bytes (ByteArray src# ) (I# soff# ) (I# slen# )) = Builder+  (\buf0 off0 len0 cs0 s0 -> case len0 <# (slen# +# 1#) of+    1# -> case Exts.newByteArray# newSz s0 of+        (# s1, buf1 #) -> case Exts.copyByteArray# src# soff# buf1 1# slen# s1 of+          s2 -> case Exts.writeWord8Array# buf1 0# w0 s2 of+            s3 -> (# s3, buf1, slen# +# 1#, newSz -# (slen# +# 1#), Mutable buf0 off0 cs0 #)+    _ -> let !s1 = Exts.copyByteArray# src# soff# buf0 (off0 +# 1#) slen# s0+             !s2 = Exts.writeWord8Array# buf0 off0 w0 s1+          in (# s2, buf0, off0 +# (slen# +# 1#), len0 -# (slen# +# 1#), cs0 #)+  )+  where+  !(I# newSz) = max ((I# slen#) + 1) 4080+ cstring# :: Addr# -> Builder {-# inline cstring# #-} cstring# x = cstring (Exts.Ptr x)@@ -410,6 +437,66 @@   where   !(I# newSz) = max (I# slen#) 4080 +-- | Encode seven bytes into eight so that the encoded form is eight-bit clean.+-- Specifically segment the input bytes inot 7-bit groups (lowest-to-highest+-- index byte, most-to-least significant bit within a byte), pads the last group+-- with trailing zeros, and forms octects by prepending a zero to each group.+--+-- The name was chosen because this pads the input bits with zeros on the right,+-- and also because this was likely the originally-indended behavior of the+-- SMILE standard (see 'sevenEightSmile'). Right padding the input bits to a+-- multiple of seven, as in this variant, is consistent with base64 encodings+-- (which encodes 3 bytes in 4) and base85 (which encodes 4 bytes in 5).+sevenEightRight :: Bytes -> Builder+sevenEightRight bs0 = case toWord 0 0 bs0 of+  (0, _) -> mempty+  (len, w) -> go (len * 8) w <> sevenEightSmile (Bytes.unsafeDrop len bs0)+  where+  go :: Int -> Word64 -> Builder+  go !nBits !_ | nBits <= 0 = mempty+  go !nBits !w =+    let octet = (fromIntegral $ unsafeShiftR w (8*7+1)) .&. 0x7f+     in word8 octet <> go (nBits - 7) (unsafeShiftL w 7)+  toWord :: Int -> Word64 -> Bytes -> (Int, Word64)+  toWord !i !acc !bs+    | Bytes.length bs == 0 = (i, acc)+    | otherwise =+        let b = fromIntegral @Word8 @Word64 $ Bytes.unsafeIndex bs 0+            acc' = acc .|. unsafeShiftL b (fromIntegral $ 8 * (7 - i))+         in if i < 7+            then toWord (i + 1) acc' (Bytes.unsafeDrop 1 bs)+            else (i, acc)++-- | Encode seven bytes into eight so that the encoded form is eight-bit clean.+-- Specifically segment the input bytes inot 7-bit groups (lowest-to-highest+-- index byte, most-to-least significant bit within a byte), then pad each group+-- with zeros on the left until each group is an octet.+--+-- The name was chosen because this is the implementation that is used (probably+-- unintentionally) in the reference SMILE implementation, and so is expected tp+-- be accepted by existing SMILE consumers.+sevenEightSmile :: Bytes -> Builder+sevenEightSmile bs0 = case toWord 0 0 bs0 of+  (0, _) -> mempty+  (len, w) -> go (len * 8) w <> sevenEightSmile (Bytes.unsafeDrop len bs0)+  where+  go :: Int -> Word64 -> Builder+  go !nBits !w+    | nBits == 0 = mempty+    | nBits < 7 = go 7 (unsafeShiftR w (7 - nBits))+  go !nBits !w =+    let octet = (fromIntegral $ unsafeShiftR w (8*7+1)) .&. 0x7f+     in word8 octet <> go (nBits - 7) (unsafeShiftL w 7)+  toWord :: Int -> Word64 -> Bytes -> (Int, Word64)+  toWord !i !acc !bs+    | Bytes.length bs == 0 = (i, acc)+    | otherwise =+        let b = fromIntegral @Word8 @Word64 $ Bytes.unsafeIndex bs 0+            acc' = acc .|. unsafeShiftL b (fromIntegral $ 8 * (7 - i))+         in if i < 7+            then toWord (i + 1) acc' (Bytes.unsafeDrop 1 bs)+            else (i, acc)+ -- | Create a builder from two byte sequences. This always results in two -- calls to @memcpy@. This is beneficial when the byte sequences are -- known to be small (less than 256 bytes).@@ -1058,20 +1145,27 @@ c2w :: Char -> Word8 c2w = fromIntegral . ord --- In C, this is: (n << 1) ^ (n >> (BIT_WIDTH - 1))-zigZagNative :: Int -> Word-zigZagNative s = fromIntegral @Int @Word-  ((unsafeShiftL s 1) `xor` (unsafeShiftR s (finiteBitSize (undefined :: Word) - 1)))- -- | Encode a signed machine-sized integer with LEB-128. This uses -- zig-zag encoding. intLEB128 :: Int -> Builder-intLEB128 = wordLEB128 . zigZagNative+intLEB128 = wordLEB128 . toZigzagNative +-- | Encode a 32-bit signed integer with LEB-128. This uses zig-zag encoding.+int32LEB128 :: Int32 -> Builder+int32LEB128 = word32LEB128 . toZigzag32++-- | Encode a 64-bit signed integer with LEB-128. This uses zig-zag encoding.+int64LEB128 :: Int64 -> Builder+int64LEB128 = word64LEB128 . toZigzag64+ -- | Encode a machine-sized word with LEB-128. wordLEB128 :: Word -> Builder wordLEB128 w = fromBounded Nat.constant (Bounded.wordLEB128 w) +-- | Encode a 32-bit word with LEB-128.+word32LEB128 :: Word32 -> Builder+word32LEB128 w = fromBounded Nat.constant (Bounded.word32LEB128 w)+ -- | Encode a 64-bit word with LEB-128. word64LEB128 :: Word64 -> Builder word64LEB128 w = fromBounded Nat.constant (Bounded.word64LEB128 w)@@ -1169,7 +1263,7 @@ -- For numbers less than 1073741829, this gives a correct answer. approxDiv10 :: Word -> Word approxDiv10 !n = unsafeShiftR (0x1999999A * n) 32-  + -- -- A weird beast useful for rewrite rules. Not yet used. This will -- -- ultimately replace fromEffect and fromBounded. -- require :: Int -> Builder@@ -1184,3 +1278,22 @@  unsafeWordToWord8 :: Word -> Word8 unsafeWordToWord8 (W# w) = W8# w++-- | This function and the documentation for it are copied from+-- Takano Akio's fast-builder library.+--+-- @'rebuild' b@ is equivalent to @b@, but it allows GHC to assume+-- that @b@ will be run at most once. This can enable various+-- optimizations that greately improve performance.+--+-- There are two types of typical situations where a use of 'rebuild'+-- is often a win:+--+-- * When constructing a builder using a recursive function. e.g.+--  @rebuild $ foldr ...@.+-- * When constructing a builder using a conditional expression. e.g.+--  @rebuild $ case x of ... @+rebuild :: Builder -> Builder+{-# inline rebuild #-}+rebuild (Builder f) = Builder $ oneShot $ \a -> oneShot $ \b -> oneShot $ \c -> oneShot $ \d -> oneShot $ \e ->+  f a b c d e
src/Data/Bytes/Builder/Bounded.hs view
@@ -97,6 +97,7 @@   , int16LE     -- **** LEB128   , wordLEB128+  , word32LEB128   , word64LEB128     -- * Encode Floating-Point Types   , doubleDec@@ -850,6 +851,10 @@ wordLEB128 :: Word -> Builder 10 wordLEB128 (W# w) = lebCommon (W# w) +-- | Encode a 32-bit word with LEB-128.+word32LEB128 :: Word32 -> Builder 5+word32LEB128 (W32# w) = lebCommon (W# w)+ -- | Encode a 64-bit word with LEB-128. word64LEB128 :: Word64 -> Builder 10 word64LEB128 (W64# w) = lebCommon (W# w)@@ -1071,4 +1076,3 @@  foreign import ccall unsafe "bytebuild_paste_double" c_paste_double ::   MutableByteArray# s -> Int# -> Double# -> IO Int-
test/Main.hs view
@@ -255,6 +255,16 @@         runConcat 1 (naturalDec y)         ===         pack (show y)+    , testGroup "seven/eight encoding"+      [ THU.testCase "deadbeef" $ do+        let inp = Latin1.fromString "\xDE\xAD\xBE\xEF"+        (Chunks.concat . run 16) (sevenEightRight inp)+          @=? Latin1.fromString "\x6F\x2B\x37\x6E\x78"+      , THU.testCase "deadbeef-smile" $ do+        let inp = Latin1.fromString "\xDE\xAD\xBE\xEF"+        (Chunks.concat . run 16) (sevenEightSmile inp)+          @=?Latin1.fromString "\x6F\x2B\x37\x6E\x0F"+      ]     ]   , testGroup "alternate"     [ TQC.testProperty "HexWord64" $ \x y ->