packages feed

small-bytearray-builder 0.1.1.0 → 0.2.0.0

raw patch · 11 files changed

+1221/−578 lines, 11 filesdep +natural-arithmeticdep +tasty-hunitdep +textdep ~bytestringPVP ok

version bump matches the API change (PVP)

Dependencies added: natural-arithmetic, tasty-hunit, text, text-short

Dependency ranges changed: bytestring

API changes (from Hackage documentation)

- Data.ByteArray.Builder.Small: Builder :: (forall s. MutableByteArray# s -> Int# -> Int# -> State# s -> (# State# s, Int# #)) -> Builder
- Data.ByteArray.Builder.Small: bytearray :: ByteArray -> Builder
- Data.ByteArray.Builder.Small: bytes :: Bytes -> Builder
- Data.ByteArray.Builder.Small: construct :: (forall s. MutableBytes s -> ST s (Maybe Int)) -> Builder
- Data.ByteArray.Builder.Small: fromUnsafe :: forall n. KnownNat n => Builder n -> Builder
- Data.ByteArray.Builder.Small: instance GHC.Base.Monoid Data.ByteArray.Builder.Small.Builder
- Data.ByteArray.Builder.Small: instance GHC.Base.Semigroup Data.ByteArray.Builder.Small.Builder
- Data.ByteArray.Builder.Small: int64Dec :: Int64 -> Builder
- Data.ByteArray.Builder.Small: newtype Builder
- Data.ByteArray.Builder.Small: pasteArrayIO :: MutableBytes RealWorld -> (a -> Builder) -> Vector a -> IO (Vector a, MutableBytes RealWorld)
- Data.ByteArray.Builder.Small: pasteArrayST :: MutableBytes s -> (a -> Builder) -> Vector a -> ST s (Vector a, MutableBytes s)
- Data.ByteArray.Builder.Small: pasteGrowIO :: Int -> Builder -> MutableByteArray RealWorld -> IO (MutableByteArrayOffset RealWorld)
- Data.ByteArray.Builder.Small: pasteGrowST :: Int -> Builder -> MutableByteArray s -> ST s (MutableByteArrayOffset s)
- Data.ByteArray.Builder.Small: pasteIO :: Builder -> MutableBytes RealWorld -> IO (Maybe Int)
- Data.ByteArray.Builder.Small: pasteST :: Builder -> MutableBytes s -> ST s (Maybe Int)
- Data.ByteArray.Builder.Small: run :: Int -> Builder -> ByteArray
- Data.ByteArray.Builder.Small: word16BE :: Word16 -> Builder
- Data.ByteArray.Builder.Small: word16PaddedUpperHex :: Word16 -> Builder
- Data.ByteArray.Builder.Small: word32BE :: Word32 -> Builder
- Data.ByteArray.Builder.Small: word32PaddedUpperHex :: Word32 -> Builder
- Data.ByteArray.Builder.Small: word64BE :: Word64 -> Builder
- Data.ByteArray.Builder.Small: word64Dec :: Word64 -> Builder
- Data.ByteArray.Builder.Small: word64PaddedUpperHex :: Word64 -> Builder
- Data.ByteArray.Builder.Small: word8PaddedUpperHex :: Word8 -> Builder
- Data.ByteArray.Builder.Small.Unsafe: [Builder] :: (forall s. MutableByteArray# s -> Int# -> State# s -> (# State# s, Int# #)) -> Builder n
- Data.ByteArray.Builder.Small.Unsafe: append :: Builder n -> Builder m -> Builder (n + m)
- Data.ByteArray.Builder.Small.Unsafe: construct :: (forall s. MutableByteArray s -> Int -> ST s Int) -> Builder n
- Data.ByteArray.Builder.Small.Unsafe: infixr 9 `append`
- Data.ByteArray.Builder.Small.Unsafe: int64Dec :: Int64 -> Builder 20
- Data.ByteArray.Builder.Small.Unsafe: newtype Builder :: Nat -> Type
- Data.ByteArray.Builder.Small.Unsafe: pasteIO :: Builder n -> MutableByteArray RealWorld -> Int -> IO Int
- Data.ByteArray.Builder.Small.Unsafe: pasteST :: Builder n -> MutableByteArray s -> Int -> ST s Int
- Data.ByteArray.Builder.Small.Unsafe: run :: forall n. KnownNat n => Builder n -> ByteArray
- Data.ByteArray.Builder.Small.Unsafe: word16BE :: Word16 -> Builder 2
- Data.ByteArray.Builder.Small.Unsafe: word16PaddedUpperHex :: Word16 -> Builder 4
- Data.ByteArray.Builder.Small.Unsafe: word32BE :: Word32 -> Builder 4
- Data.ByteArray.Builder.Small.Unsafe: word32PaddedUpperHex :: Word32 -> Builder 8
- Data.ByteArray.Builder.Small.Unsafe: word64BE :: Word64 -> Builder 8
- Data.ByteArray.Builder.Small.Unsafe: word64Dec :: Word64 -> Builder 19
- Data.ByteArray.Builder.Small.Unsafe: word64PaddedUpperHex :: Word64 -> Builder 16
- Data.ByteArray.Builder.Small.Unsafe: word8PaddedUpperHex :: Word8 -> Builder 2
+ Data.ByteArray.Builder: Builder :: (forall s. MutableByteArray# s -> Int# -> Int# -> State# s -> (# State# s, Int# #)) -> Builder
+ Data.ByteArray.Builder: ascii :: Char -> Builder
+ Data.ByteArray.Builder: bytearray :: ByteArray -> Builder
+ Data.ByteArray.Builder: bytes :: Bytes -> Builder
+ Data.ByteArray.Builder: char :: Char -> Builder
+ Data.ByteArray.Builder: construct :: (forall s. MutableBytes s -> ST s (Maybe Int)) -> Builder
+ Data.ByteArray.Builder: cstring :: CString -> Builder
+ Data.ByteArray.Builder: doubleDec :: Double -> Builder
+ Data.ByteArray.Builder: fromBounded :: Nat n -> Builder n -> Builder
+ Data.ByteArray.Builder: int16Dec :: Int16 -> Builder
+ Data.ByteArray.Builder: int32Dec :: Int32 -> Builder
+ Data.ByteArray.Builder: int64Dec :: Int64 -> Builder
+ Data.ByteArray.Builder: int8Dec :: Int8 -> Builder
+ Data.ByteArray.Builder: intDec :: Int -> Builder
+ Data.ByteArray.Builder: newtype Builder
+ Data.ByteArray.Builder: pasteArrayIO :: MutableBytes RealWorld -> (a -> Builder) -> Vector a -> IO (Vector a, MutableBytes RealWorld)
+ Data.ByteArray.Builder: pasteArrayST :: MutableBytes s -> (a -> Builder) -> Vector a -> ST s (Vector a, MutableBytes s)
+ Data.ByteArray.Builder: pasteGrowIO :: Int -> Builder -> MutableByteArrayOffset RealWorld -> IO (MutableByteArrayOffset RealWorld)
+ Data.ByteArray.Builder: pasteGrowST :: Int -> Builder -> MutableByteArrayOffset s -> ST s (MutableByteArrayOffset s)
+ Data.ByteArray.Builder: pasteIO :: Builder -> MutableBytes RealWorld -> IO (Maybe Int)
+ Data.ByteArray.Builder: pasteST :: Builder -> MutableBytes s -> ST s (Maybe Int)
+ Data.ByteArray.Builder: run :: Int -> Builder -> ByteArray
+ Data.ByteArray.Builder: shortTextJsonString :: ShortText -> Builder
+ Data.ByteArray.Builder: shortTextUtf8 :: ShortText -> Builder
+ Data.ByteArray.Builder: stringUtf8 :: String -> Builder
+ Data.ByteArray.Builder: word16BE :: Word16 -> Builder
+ Data.ByteArray.Builder: word16Dec :: Word16 -> Builder
+ Data.ByteArray.Builder: word16PaddedUpperHex :: Word16 -> Builder
+ Data.ByteArray.Builder: word32BE :: Word32 -> Builder
+ Data.ByteArray.Builder: word32Dec :: Word32 -> Builder
+ Data.ByteArray.Builder: word32PaddedUpperHex :: Word32 -> Builder
+ Data.ByteArray.Builder: word64BE :: Word64 -> Builder
+ Data.ByteArray.Builder: word64Dec :: Word64 -> Builder
+ Data.ByteArray.Builder: word64PaddedUpperHex :: Word64 -> Builder
+ Data.ByteArray.Builder: word8 :: Word8 -> Builder
+ Data.ByteArray.Builder: word8Dec :: Word8 -> Builder
+ Data.ByteArray.Builder: word8PaddedUpperHex :: Word8 -> Builder
+ Data.ByteArray.Builder: wordDec :: Word -> Builder
+ Data.ByteArray.Builder.Bounded: append :: Builder m -> Builder n -> Builder (m + n)
+ Data.ByteArray.Builder.Bounded: ascii :: Char -> Builder 1
+ Data.ByteArray.Builder.Bounded: char :: Char -> Builder 4
+ Data.ByteArray.Builder.Bounded: data Builder :: Nat -> Type
+ Data.ByteArray.Builder.Bounded: doubleDec :: Double -> Builder 32
+ Data.ByteArray.Builder.Bounded: empty :: Builder 0
+ Data.ByteArray.Builder.Bounded: infixr 9 `append`
+ Data.ByteArray.Builder.Bounded: int16Dec :: Int16 -> Builder 6
+ Data.ByteArray.Builder.Bounded: int32Dec :: Int32 -> Builder 11
+ Data.ByteArray.Builder.Bounded: int64Dec :: Int64 -> Builder 20
+ Data.ByteArray.Builder.Bounded: int8Dec :: Int8 -> Builder 4
+ Data.ByteArray.Builder.Bounded: intDec :: Int -> Builder 20
+ Data.ByteArray.Builder.Bounded: pasteGrowST :: Nat n -> Builder n -> MutableByteArrayOffset s -> ST s (MutableByteArrayOffset s)
+ Data.ByteArray.Builder.Bounded: run :: Nat n -> Builder n -> ByteArray
+ Data.ByteArray.Builder.Bounded: substitute :: forall m n. (m :=: n) -> Builder m -> Builder n
+ Data.ByteArray.Builder.Bounded: weaken :: forall m n. (m <= n) -> Builder m -> Builder n
+ Data.ByteArray.Builder.Bounded: word16BE :: Word16 -> Builder 2
+ Data.ByteArray.Builder.Bounded: word16Dec :: Word16 -> Builder 5
+ Data.ByteArray.Builder.Bounded: word16PaddedUpperHex :: Word16 -> Builder 4
+ Data.ByteArray.Builder.Bounded: word32BE :: Word32 -> Builder 4
+ Data.ByteArray.Builder.Bounded: word32Dec :: Word32 -> Builder 10
+ Data.ByteArray.Builder.Bounded: word32PaddedUpperHex :: Word32 -> Builder 8
+ Data.ByteArray.Builder.Bounded: word64BE :: Word64 -> Builder 8
+ Data.ByteArray.Builder.Bounded: word64Dec :: Word64 -> Builder 19
+ Data.ByteArray.Builder.Bounded: word64PaddedUpperHex :: Word64 -> Builder 16
+ Data.ByteArray.Builder.Bounded: word8 :: Word8 -> Builder 1
+ Data.ByteArray.Builder.Bounded: word8Dec :: Word8 -> Builder 3
+ Data.ByteArray.Builder.Bounded: word8PaddedUpperHex :: Word8 -> Builder 2
+ Data.ByteArray.Builder.Bounded: wordDec :: Word -> Builder 19
+ Data.ByteArray.Builder.Bounded.Unsafe: [Builder] :: (forall s. MutableByteArray# s -> Int# -> State# s -> (# State# s, Int# #)) -> Builder n
+ Data.ByteArray.Builder.Bounded.Unsafe: construct :: (forall s. MutableByteArray s -> Int -> ST s Int) -> Builder n
+ Data.ByteArray.Builder.Bounded.Unsafe: newtype Builder :: Nat -> Type
+ Data.ByteArray.Builder.Bounded.Unsafe: pasteIO :: Builder n -> MutableByteArray RealWorld -> Int -> IO Int
+ Data.ByteArray.Builder.Bounded.Unsafe: pasteST :: Builder n -> MutableByteArray s -> Int -> ST s Int
+ Data.ByteArray.Builder.Unsafe: Builder :: (forall s. MutableByteArray# s -> Int# -> Int# -> State# s -> (# State# s, Int# #)) -> Builder
+ Data.ByteArray.Builder.Unsafe: cstring :: CString -> Builder
+ Data.ByteArray.Builder.Unsafe: instance Data.String.IsString Data.ByteArray.Builder.Unsafe.Builder
+ Data.ByteArray.Builder.Unsafe: instance GHC.Base.Monoid Data.ByteArray.Builder.Unsafe.Builder
+ Data.ByteArray.Builder.Unsafe: instance GHC.Base.Semigroup Data.ByteArray.Builder.Unsafe.Builder
+ Data.ByteArray.Builder.Unsafe: newtype Builder
+ Data.ByteArray.Builder.Unsafe: stringUtf8 :: String -> Builder

Files

CHANGELOG.md view
@@ -1,5 +1,18 @@ # Revision history for small-bytearray-builder +## 0.2.0.0 -- 2019-09-04++* Use `natural-arithmetic` to make manipulation of bounds possible.+* Add more functions for encoding numbers. This includes+  `word8/16/32/64` and `int8/16/32/64`.+* Rename the modules.+* Correct a serious error in the implementation of `bytes`.+* Make `pasteGrowST` accept an initial offset.+* Add a `pasteGrowST` for length-indexed builders.+* Add function for rendering floating-point numbers in a slightly+  inaccurate way.+* Add functions for encoding `ShortText` as UTF-8 and as a JSON string.+ ## 0.1.1.0 -- 2019-07-30  * Add several additional functions for encoding numbers.
bench/Main.hs view
@@ -1,9 +1,11 @@+import Data.Primitive (ByteArray)+import Data.Word (Word64) import Gauge (bgroup,bench,whnf) import Gauge.Main (defaultMain)-import Data.Word (Word64)-import Data.Primitive (ByteArray)-import qualified Data.ByteArray.Builder.Small.Unsafe as U +import qualified Arithmetic.Nat as Nat+import qualified Data.ByteArray.Builder.Bounded as U+ import qualified HexWord64  main :: IO ()@@ -33,7 +35,7 @@  encodeHexWord64s :: Word64s -> ByteArray {-# noinline encodeHexWord64s #-}-encodeHexWord64s (Word64s a b c d e f g h) = U.run $+encodeHexWord64s (Word64s a b c d e f g h) = U.run Nat.constant $   U.word64PaddedUpperHex a `U.append`   U.word64PaddedUpperHex b `U.append`   U.word64PaddedUpperHex c `U.append`@@ -45,7 +47,7 @@  encodeHexWord64sLoop :: Word64s -> ByteArray {-# noinline encodeHexWord64sLoop #-}-encodeHexWord64sLoop (Word64s a b c d e f g h) = U.run $+encodeHexWord64sLoop (Word64s a b c d e f g h) = U.run Nat.constant $   HexWord64.word64PaddedUpperHex a `U.append`   HexWord64.word64PaddedUpperHex b `U.append`   HexWord64.word64PaddedUpperHex c `U.append`
common/HexWord64.hs view
@@ -17,7 +17,7 @@  import GHC.ST (ST(ST)) import Data.Bits-import Data.ByteArray.Builder.Small.Unsafe (Builder,construct)+import Data.ByteArray.Builder.Bounded.Unsafe (Builder,construct) import Data.Primitive import Data.Word import GHC.Exts
small-bytearray-builder.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: small-bytearray-builder-version: 0.1.1.0+version: 0.2.0.0 synopsis: Serialize to a small byte arrays description:   This is similar to the builder facilities provided by@@ -36,14 +36,19 @@  library   exposed-modules:-    Data.ByteArray.Builder.Small-    Data.ByteArray.Builder.Small.Unsafe+    Data.ByteArray.Builder+    Data.ByteArray.Builder.Unsafe+    Data.ByteArray.Builder.Bounded+    Data.ByteArray.Builder.Bounded.Unsafe   build-depends:     , base >=4.12.0.0 && <5     , byteslice >=0.1 && <0.2     , primitive-offset >=0.2 && <0.3     , run-st >=0.1 && <0.2     , vector >=0.12.0.3 && <0.13+    , bytestring >=0.10.8.2 && <0.11+    , text-short >=0.1.3 && <0.2+    , natural-arithmetic >=0.1 && <0.2   if flag(checked)     build-depends: primitive-checked >= 0.7 && <0.8   else@@ -57,17 +62,21 @@   type: exitcode-stdio-1.0   hs-source-dirs: test, common   main-is: Main.hs+  ghc-options: -O2 -Wall   other-modules:     HexWord64   build-depends:+    , QuickCheck >=2.13.1 && <2.14     , base >=4.12.0.0 && <5     , byteslice     , bytestring+    , natural-arithmetic+    , primitive     , small-bytearray-builder-    , QuickCheck >=2.13.1 && <2.14-    , tasty-quickcheck >=0.10.1 && <0.11     , tasty >=1.2.3 && <1.3-    , primitive+    , tasty-hunit >=0.10.0.2 && <0.11+    , tasty-quickcheck >=0.10.1 && <0.11+    , text >=1.2 && <1.3     , vector  benchmark bench@@ -75,6 +84,7 @@   build-depends:     , base     , gauge >= 0.2.4+    , natural-arithmetic     , primitive     , small-bytearray-builder   ghc-options: -Wall -O2
+ src/Data/ByteArray/Builder.hs view
@@ -0,0 +1,405 @@+{-# language BangPatterns #-}+{-# language DuplicateRecordFields #-}+{-# language LambdaCase #-}+{-# language MagicHash #-}+{-# language RankNTypes #-}+{-# language ScopedTypeVariables #-}+{-# language UnboxedTuples #-}++module Data.ByteArray.Builder+  ( -- * Bounded Primitives+    Builder(..)+  , construct+  , fromBounded+    -- * Evaluation+  , run+  , pasteST+  , pasteIO+  , pasteGrowST+  , pasteGrowIO+  , pasteArrayST+  , pasteArrayIO+    -- * Materialized Byte Sequences+  , bytes+  , bytearray+  , shortTextUtf8+  , shortTextJsonString+  , cstring+  , stringUtf8+    -- * Encode Integral Types+    -- ** Human-Readable+  , word64Dec+  , word32Dec+  , word16Dec+  , word8Dec+  , wordDec+  , int64Dec+  , int32Dec+  , int16Dec+  , int8Dec+  , intDec+  , word64PaddedUpperHex+  , word32PaddedUpperHex+  , word16PaddedUpperHex+  , word8PaddedUpperHex+  , ascii+  , char+    -- ** Machine-Readable+  , word64BE+  , word32BE+  , word16BE+  , word8+    -- * Encode Floating-Point Types+    -- ** Human-Readable+  , doubleDec+  ) where++import Control.Monad.Primitive (primitive_)+import Control.Monad.ST (ST,stToIO)+import Control.Monad.ST.Run (runByteArrayST)+import Data.ByteArray.Builder.Unsafe (Builder(Builder))+import Data.ByteArray.Builder.Unsafe (stringUtf8,cstring)+import Data.ByteString.Short.Internal (ShortByteString(SBS))+import Data.Bytes.Types (Bytes(Bytes),MutableBytes(MutableBytes))+import Data.Char (ord)+import Data.Int (Int64,Int32,Int16,Int8)+import Data.Primitive (ByteArray(..),MutableByteArray(..))+import Data.Primitive.ByteArray.Offset (MutableByteArrayOffset(..))+import Data.Text.Short (ShortText)+import Data.Word (Word64,Word32,Word16,Word8)+import GHC.Exts (Int(I#),Char(C#),Int#,State#,ByteArray#,RealWorld,(>=#),(/=#))+import GHC.ST (ST(ST))++import qualified Arithmetic.Nat as Nat+import qualified Arithmetic.Types as Arithmetic+import qualified GHC.Exts as Exts+import qualified Data.Text.Short as TS+import qualified Data.Primitive as PM+import qualified Data.Vector as V+import qualified Data.ByteArray.Builder.Bounded as Bounded+import qualified Data.ByteArray.Builder.Bounded.Unsafe as UnsafeBounded++-- | Run a builder. An accurate size hint is important for good performance.+-- The size hint should be slightly larger than the actual size.+run ::+     Int -- ^ Hint for upper bound on size+  -> Builder -- ^ Builder+  -> ByteArray+run hint b = runByteArrayST $ do+  let go !n = do+        arr <- PM.newByteArray n+        pasteST b (MutableBytes arr 0 n) >>= \case+          Nothing -> go (n + 64)+          Just len -> do+            shrinkMutableByteArray arr len+            PM.unsafeFreezeByteArray arr+  go (max hint 1)++-- | Variant of 'pasteArrayST' that runs in 'IO'.+pasteArrayIO ::+     MutableBytes RealWorld -- ^ Buffer+  -> (a -> Builder) -- ^ Builder+  -> V.Vector a -- ^ Elements to serialize+  -> IO (V.Vector a, MutableBytes RealWorld) -- ^ Shifted vector, shifted buffer+pasteArrayIO !arr f !xs = stToIO (pasteArrayST arr f xs)++-- | Fold over a vector, applying the builder to each element until+-- the buffer cannot accomodate any more.+pasteArrayST ::+     MutableBytes s -- ^ Buffer+  -> (a -> Builder) -- ^ Builder+  -> V.Vector a -- ^ Elements to serialize+  -> ST s (V.Vector a, MutableBytes s) -- ^ Shifted vector, shifted buffer+pasteArrayST (MutableBytes arr off0 len0) f !xs0 = do+  let go !xs !ixBufA !lenBufA = if V.length xs > 0+        then do+          let a = V.unsafeHead xs+          pasteST (f a) (MutableBytes arr ixBufA lenBufA) >>= \case+            Nothing -> pure (xs,MutableBytes arr ixBufA lenBufA)+            Just ixBufB ->+              go (V.unsafeTail xs) ixBufB (lenBufA + (ixBufA - ixBufB))+        else pure (xs,MutableBytes arr ixBufA lenBufA)+  go xs0 off0 len0++-- | Paste the builder into the byte array starting at offset zero.+-- This repeatedly reallocates the byte array if it cannot accomodate+-- the builder, replaying the builder each time.+pasteGrowST ::+     Int -- ^ How many bytes to grow by at a time+  -> Builder+  -> MutableByteArrayOffset s+     -- ^ Initial buffer, used linearly. Do not reuse this argument.+  -> ST s (MutableByteArrayOffset s)+     -- ^ Final buffer that accomodated the builder.+pasteGrowST !n b !(MutableByteArrayOffset arr0 off0) = do+  let go !arr !sz = pasteST b (MutableBytes arr off0 (sz - off0)) >>= \case+        Nothing -> do+          let szNext = sz + n+          arrNext <- PM.resizeMutableByteArray arr szNext+          go arrNext szNext+        Just ix -> pure (MutableByteArrayOffset{array=arr,offset=ix})+  go arr0 =<< PM.getSizeofMutableByteArray arr0++-- | Variant of 'pasteGrowST' that runs in 'IO'.+pasteGrowIO ::+     Int -- ^ How many bytes to grow by at a time+  -> Builder+  -> MutableByteArrayOffset RealWorld+     -- ^ Initial buffer, used linearly. Do not reuse this argument.+  -> IO (MutableByteArrayOffset RealWorld)+     -- ^ Final buffer that accomodated the builder.+pasteGrowIO !n b !arr = stToIO (pasteGrowST n b arr)++-- | Execute the builder, pasting its contents into a buffer.+-- If the buffer is not large enough, this returns 'Nothing'.+-- Otherwise, it returns the index in the buffer that follows+-- the payload just written.+pasteST :: Builder -> MutableBytes s -> ST s (Maybe Int)+{-# inline pasteST #-}+pasteST (Builder f) (MutableBytes (MutableByteArray arr) (I# off) (I# len)) =+  ST $ \s0 -> case f arr off len s0 of+    (# s1, r #) -> if Exts.isTrue# (r /=# (-1#))+      then (# s1, Just (I# r) #)+      else (# s1, Nothing #)++-- | Variant of 'pasteST' that runs in 'IO'.+pasteIO :: Builder -> MutableBytes RealWorld -> IO (Maybe Int)+{-# inline pasteIO #-}+pasteIO b m = stToIO (pasteST b m)++-- | Constructor for 'Builder' that works on a function with lifted+-- arguments instead of unlifted ones. This is just as unsafe as the+-- actual constructor.+construct :: (forall s. MutableBytes s -> ST s (Maybe Int)) -> Builder+construct f = Builder+  $ \arr off len s0 ->+    case unST (f (MutableBytes (MutableByteArray arr) (I# off) (I# len))) s0 of+      (# s1, m #) -> case m of+        Nothing -> (# s1, (-1#) #)+        Just (I# n) -> (# s1, n #)++-- | Convert a bounded builder to an unbounded one. If the size+-- is a constant, use @Arithmetic.Nat.constant@ as the first argument+-- to let GHC conjure up this value for you.+fromBounded ::+     Arithmetic.Nat n+  -> Bounded.Builder n+  -> Builder+{-# inline fromBounded #-}+fromBounded n (UnsafeBounded.Builder f) = Builder $ \arr off len s0 ->+  let !(I# req) = Nat.demote n in+  case len >=# req of+    1# -> f arr off s0+    _ -> (# s0, (-1#) #)++-- | Create a builder from an unsliced byte sequence.+bytearray :: ByteArray -> Builder+bytearray a = bytes (Bytes a 0 (PM.sizeofByteArray a))++-- | Create a builder from a sliced byte sequence.+bytes :: Bytes -> Builder+bytes (Bytes src soff slen) = construct $ \(MutableBytes arr off len) -> if len >= slen+  then do+    PM.copyByteArray arr off src soff slen+    pure (Just (off + slen))+  else pure Nothing++-- Internal function. Precondition, the referenced slice of the+-- byte sequence is UTF-8 encoded text.+slicedUtf8TextJson :: ByteArray# -> Int# -> Int# -> Builder+{-# inline slicedUtf8TextJson #-}+slicedUtf8TextJson !src# !soff0# !slen0# = construct $ \(MutableBytes dst doff0 dlen0) ->+  let slen0 = I# slen0#+   in if dlen0 > (2 * slen0) + 2+        then do+          PM.writeByteArray dst doff0 (c2w '"')+          let go !soff !slen !doff = if slen > 0+                then case indexChar8Array (ByteArray src#) soff of+                  '\\' -> write2 dst doff '\\' '\\' *> go (soff + 1) (slen - 1) (doff + 2)+                  '\"' -> write2 dst doff '\\' '\"' *> go (soff + 1) (slen - 1) (doff + 2)+                  '\n' -> write2 dst doff '\\' 'n' *> go (soff + 1) (slen - 1) (doff + 2)+                  '\r' -> write2 dst doff '\\' 'r' *> go (soff + 1) (slen - 1) (doff + 2)+                  '\t' -> write2 dst doff '\\' 't' *> go (soff + 1) (slen - 1) (doff + 2)+                  c -> if c >= '\x20'+                    then PM.writeByteArray dst doff (c2w c) *> go (soff + 1) (slen - 1) (doff + 1)+                    else do+                      write2 dst doff '\\' 'u'+                      doff' <- UnsafeBounded.pasteST+                        (Bounded.word16PaddedUpperHex (fromIntegral (c2w c)))+                        dst (doff + 2)+                      go (soff + 1) (slen - 1) doff'+                else pure doff+          doffRes <- go (I# soff0#) (I# slen0#) (doff0 + 1)+          PM.writeByteArray dst doffRes (c2w '"')+          pure (Just (doffRes + 1))+        else pure Nothing++-- Internal. Write two characters in the ASCII plane to a byte array.+write2 :: MutableByteArray s -> Int -> Char -> Char -> ST s ()+write2 marr ix a b = do+  PM.writeByteArray marr ix (c2w a)+  PM.writeByteArray marr (ix + 1) (c2w b)++-- | Create a builder from text. The text will be UTF-8 encoded.+shortTextUtf8 :: ShortText -> Builder+shortTextUtf8 a =+  let ba = shortTextToByteArray a+   in bytes (Bytes ba 0 (PM.sizeofByteArray ba))++-- | Create a builder from text. The text will be UTF-8 encoded,+-- and JSON special characters will be escaped. Additionally, the+-- result is surrounded by double quotes. For example:+--+-- * @foo ==> "foo"@+-- * @\_"_/ ==> "\\_\"_/"@+-- * @hello<ESC>world ==> "hello\u001Bworld"@ (where <LF> is code point 0x1B)+shortTextJsonString :: ShortText -> Builder+shortTextJsonString a =+  let !(ByteArray ba) = shortTextToByteArray a+      !(I# len) = PM.sizeofByteArray (ByteArray ba)+   in slicedUtf8TextJson ba 0# len++-- | Encodes an unsigned 64-bit integer as decimal.+-- This encoding never starts with a zero unless the+-- argument was zero.+word64Dec :: Word64 -> Builder+word64Dec w = fromBounded Nat.constant (Bounded.word64Dec w)++-- | Encodes an unsigned 16-bit integer as decimal.+-- This encoding never starts with a zero unless the+-- argument was zero.+word32Dec :: Word32 -> Builder+word32Dec w = fromBounded Nat.constant (Bounded.word32Dec w)++-- | Encodes an unsigned 16-bit integer as decimal.+-- This encoding never starts with a zero unless the+-- argument was zero.+word16Dec :: Word16 -> Builder+word16Dec w = fromBounded Nat.constant (Bounded.word16Dec w)++-- | Encodes an unsigned 8-bit integer as decimal.+-- This encoding never starts with a zero unless the+-- argument was zero.+word8Dec :: Word8 -> Builder+word8Dec w = fromBounded Nat.constant (Bounded.word8Dec w)++-- | Encodes an unsigned machine-sized integer as decimal.+-- This encoding never starts with a zero unless the+-- argument was zero.+wordDec :: Word -> Builder+wordDec w = fromBounded Nat.constant (Bounded.wordDec w)++-- | Encode a double-floating-point number, using decimal notation or+-- scientific notation depending on the magnitude. This has undefined+-- behavior when representing @+inf@, @-inf@, and @NaN@. It will not+-- crash, but the generated numbers will be nonsense.+doubleDec :: Double -> Builder+doubleDec w = fromBounded Nat.constant (Bounded.doubleDec w)++-- | Encodes a signed 64-bit integer as decimal.+-- This encoding never starts with a zero unless the argument was zero.+-- Negative numbers are preceded by a minus sign. Positive numbers+-- are not preceded by anything.+int64Dec :: Int64 -> Builder+int64Dec w = fromBounded Nat.constant (Bounded.int64Dec w)++-- | Encodes a signed 32-bit integer as decimal.+-- This encoding never starts with a zero unless the argument was zero.+-- Negative numbers are preceded by a minus sign. Positive numbers+-- are not preceded by anything.+int32Dec :: Int32 -> Builder+int32Dec w = fromBounded Nat.constant (Bounded.int32Dec w)++-- | Encodes a signed 16-bit integer as decimal.+-- This encoding never starts with a zero unless the argument was zero.+-- Negative numbers are preceded by a minus sign. Positive numbers+-- are not preceded by anything.+int16Dec :: Int16 -> Builder+int16Dec w = fromBounded Nat.constant (Bounded.int16Dec w)++-- | Encodes a signed 8-bit integer as decimal.+-- This encoding never starts with a zero unless the argument was zero.+-- Negative numbers are preceded by a minus sign. Positive numbers+-- are not preceded by anything.+int8Dec :: Int8 -> Builder+int8Dec w = fromBounded Nat.constant (Bounded.int8Dec w)++-- | Encodes a signed machine-sized integer as decimal.+-- This encoding never starts with a zero unless the argument was zero.+-- Negative numbers are preceded by a minus sign. Positive numbers+-- are not preceded by anything.+intDec :: Int -> Builder+intDec w = fromBounded Nat.constant (Bounded.intDec w)++-- | Encode a 64-bit unsigned integer as hexadecimal, zero-padding+-- the encoding to 16 digits. This uses uppercase for the alphabetical+-- digits. For example, this encodes the number 1022 as @00000000000003FE@.+word64PaddedUpperHex :: Word64 -> Builder+word64PaddedUpperHex w =+  fromBounded Nat.constant (Bounded.word64PaddedUpperHex w)++-- | Encode a 32-bit unsigned integer as hexadecimal, zero-padding+-- the encoding to 8 digits. This uses uppercase for the alphabetical+-- digits. For example, this encodes the number 1022 as @000003FE@.+word32PaddedUpperHex :: Word32 -> Builder+word32PaddedUpperHex w =+  fromBounded Nat.constant (Bounded.word32PaddedUpperHex w)++-- | Encode a 16-bit unsigned integer as hexadecimal, zero-padding+-- the encoding to 4 digits. This uses uppercase for the alphabetical+-- digits. For example, this encodes the number 1022 as @03FE@.+word16PaddedUpperHex :: Word16 -> Builder+word16PaddedUpperHex w =+  fromBounded Nat.constant (Bounded.word16PaddedUpperHex w)++-- | Encode a 8-bit unsigned integer as hexadecimal, zero-padding+-- the encoding to 2 digits. This uses uppercase for the alphabetical+-- digits. For example, this encodes the number 11 as @0B@.+word8PaddedUpperHex :: Word8 -> Builder+word8PaddedUpperHex w =+  fromBounded Nat.constant (Bounded.word8PaddedUpperHex w)++-- | Encode an ASCII char.+-- Precondition: Input must be an ASCII character. This is not checked.+ascii :: Char -> Builder+ascii c = fromBounded Nat.constant (Bounded.char c)++-- | Encode an UTF8 char. This only uses as much space as is required.+char :: Char -> Builder+char c = fromBounded Nat.constant (Bounded.char c)++unST :: ST s a -> State# s -> (# State# s, a #)+unST (ST f) = f++shrinkMutableByteArray :: MutableByteArray s -> Int -> ST s ()+shrinkMutableByteArray (MutableByteArray arr) (I# sz) =+  primitive_ (Exts.shrinkMutableByteArray# arr sz)++-- | Requires exactly 8 bytes. Dump the octets of a 64-bit+-- word in a big-endian fashion.+word64BE :: Word64 -> Builder+word64BE w = fromBounded Nat.constant (Bounded.word64BE w)++-- | Requires exactly 4 bytes. Dump the octets of a 32-bit+-- word in a big-endian fashion.+word32BE :: Word32 -> Builder+word32BE w = fromBounded Nat.constant (Bounded.word32BE w)++-- | Requires exactly 2 bytes. Dump the octets of a 16-bit+-- word in a big-endian fashion.+word16BE :: Word16 -> Builder+word16BE w = fromBounded Nat.constant (Bounded.word16BE w)++word8 :: Word8 -> Builder+word8 w = fromBounded Nat.constant (Bounded.word8 w)++-- ShortText is already UTF-8 encoded. This is a no-op.+shortTextToByteArray :: ShortText -> ByteArray+shortTextToByteArray x = case TS.toShortByteString x of+  SBS a -> ByteArray a++indexChar8Array :: ByteArray -> Int -> Char+indexChar8Array (ByteArray b) (I# i) = C# (Exts.indexCharArray# b i)++c2w :: Char -> Word8+c2w = fromIntegral . ord
+ src/Data/ByteArray/Builder/Bounded.hs view
@@ -0,0 +1,576 @@+{-# language BangPatterns #-}+{-# language BinaryLiterals #-}+{-# language DataKinds #-}+{-# language KindSignatures #-}+{-# language LambdaCase #-}+{-# language MagicHash #-}+{-# language RankNTypes #-}+{-# language ScopedTypeVariables #-}+{-# language TypeApplications #-}+{-# language TypeOperators #-}+{-# language UnboxedTuples #-}++-- | The functions in this module are explict in the amount of bytes they require.+module Data.ByteArray.Builder.Bounded+  ( -- * Builder+    Builder+    -- * Execute+  , run+  , pasteGrowST+    -- * Combine+  , empty+  , append+    -- * Bounds Manipulation+  , weaken+  , substitute+    -- * Encode Integral Types+    -- ** Human-Readable+  , word64Dec+  , word32Dec+  , word16Dec+  , word8Dec+  , wordDec+  , int64Dec+  , int32Dec+  , int16Dec+  , int8Dec+  , intDec+  , word64PaddedUpperHex+  , word32PaddedUpperHex+  , word16PaddedUpperHex+  , word8PaddedUpperHex+  , ascii+  , char+    -- ** Machine-Readable+  , word64BE+  , word32BE+  , word16BE+  , word8+    -- * Encode Floating-Point Types+  , doubleDec+  ) where++import Arithmetic.Types (type (<=), type (:=:))+import Control.Monad.Primitive+import Control.Monad.ST (ST)+import Control.Monad.ST.Run (runByteArrayST)+import Data.Bits+import Data.ByteArray.Builder.Bounded.Unsafe (Builder(..))+import Data.Char (ord)+import Data.Primitive+import Data.Primitive.ByteArray.Offset (MutableByteArrayOffset(..))+import GHC.Exts+import GHC.Int (Int64(I64#),Int32(I32#),Int16(I16#),Int8(I8#))+import GHC.ST (ST(ST))+import GHC.TypeLits (type (+))+import GHC.Word (Word8(W8#),Word16(W16#),Word32(W32#),Word64(W64#))++import qualified Arithmetic.Types as Arithmetic+import qualified Arithmetic.Nat as Nat+import qualified Data.ByteArray.Builder.Bounded.Unsafe as Unsafe+import qualified Data.Primitive as PM++-- | Execute the bounded builder. If the size is a constant,+-- use @Arithmetic.Nat.constant@ as the first argument to let+-- GHC conjure up this value for you.+run ::+     Arithmetic.Nat n+  -> Builder n -- ^ Builder+  -> ByteArray+{-# inline run #-}+run n b = runByteArrayST $ do+  arr <- newByteArray (Nat.demote n)+  len <- Unsafe.pasteST b arr 0+  shrinkMutableByteArray arr len+  unsafeFreezeByteArray arr++-- | Paste the builder into the byte array starting at offset zero.+-- This reallocates the byte array if it cannot accomodate the builder,+-- growing it by the minimum amount necessary.+pasteGrowST ::+     Arithmetic.Nat n+  -> Builder n+  -> MutableByteArrayOffset s+     -- ^ Initial buffer, used linearly. Do not reuse this argument.+  -> ST s (MutableByteArrayOffset s)+     -- ^ Final buffer that accomodated the builder.+{-# inline pasteGrowST #-}+pasteGrowST n b !(MutableByteArrayOffset{array=arr0,offset=off0}) = do+  sz0 <- PM.getSizeofMutableByteArray arr0+  let req = Nat.demote n+  let sz1 = off0 + req+  if sz1 <= sz0+    then do+      off1 <- Unsafe.pasteST b arr0 off0+      pure (MutableByteArrayOffset arr0 off1)+    else do+      arr1 <- PM.resizeMutableByteArray arr0 sz1+      off1 <- Unsafe.pasteST b arr1 off0+      pure (MutableByteArrayOffset arr1 off1)++-- | The monoidal unit of `append`+empty :: Builder 0+empty = Builder $ \_ off0 s0 -> (# s0, off0 #)++infixr 9 `append`++-- | Concatenate two builders.+append :: Builder m -> Builder n -> Builder (m + n)+append (Builder f) (Builder g) =+  Builder $ \arr off0 s0 -> case f arr off0 s0 of+    (# s1, r #) -> g arr r s1++-- | Weaken the bound on the maximum number of bytes required. For example,+-- to use two builders with unequal bounds in a disjunctive setting:+--+-- > import qualified Arithmetic.Lte as Lte+-- >+-- > buildNumber :: Either Double Word64 -> Builder 32+-- > buildNumber = \case+-- >   Left d  -> doubleDec d+-- >   Right w -> weaken (Lte.constant @19 @32) (word64Dec w)+weaken :: forall m n. (m <= n) -> Builder m -> Builder n+weaken !_ (Builder f) = Builder f++-- | Replace the upper bound on size with an equal number.+substitute :: forall m n. (m :=: n) -> Builder m -> Builder n+substitute !_ (Builder f) = Builder f++-- | Encode a double-floating-point number, using decimal notation or+-- scientific notation depending on the magnitude. This has undefined+-- behavior when representing @+inf@, @-inf@, and @NaN@. It will not+-- crash, but the generated numbers will be nonsense.+doubleDec :: Double -> Builder 32+doubleDec (D# d) = Builder (\arr off0 s0 -> doubleDec# d arr off0 s0)++-- | Requires up to 19 bytes. Encodes an unsigned 64-bit integer as decimal.+-- This encoding never starts with a zero unless the argument was zero.+word64Dec :: Word64 -> Builder 19+word64Dec (W64# w) = wordCommonDec# w++-- | Requires up to 10 bytes. Encodes an unsigned 32-bit integer as decimal.+-- This encoding never starts with a zero unless the argument was zero.+word32Dec :: Word32 -> Builder 10+word32Dec (W32# w) = wordCommonDec# w++-- | Requires up to 5 bytes. Encodes an unsigned 16-bit integer as decimal.+-- This encoding never starts with a zero unless the argument was zero.+word16Dec :: Word16 -> Builder 5+word16Dec (W16# w) = wordCommonDec# w++-- | Requires up to 3 bytes. Encodes an unsigned 8-bit integer as decimal.+-- This encoding never starts with a zero unless the argument was zero.+word8Dec :: Word8 -> Builder 3+word8Dec (W8# w) = wordCommonDec# w++-- | Requires up to 19 bytes. Encodes an unsigned machine-sized integer+-- as decimal. This encoding never starts with a zero unless the argument+-- was zero.+wordDec :: Word -> Builder 19+wordDec (W# w) = wordCommonDec# w++-- | Requires up to 20 bytes. Encodes a signed 64-bit integer as decimal.+-- This encoding never starts with a zero unless the argument was zero.+-- Negative numbers are preceded by a minus sign. Positive numbers+-- are not preceded by anything.+int64Dec :: Int64 -> Builder 20+int64Dec (I64# w) = intCommonDec# w++-- | Requires up to 11 bytes. Encodes a signed 32-bit integer as decimal.+-- This encoding never starts with a zero unless the argument was zero.+-- Negative numbers are preceded by a minus sign. Positive numbers+-- are not preceded by anything.+int32Dec :: Int32 -> Builder 11+int32Dec (I32# w) = intCommonDec# w++-- | Requires up to 6 bytes. Encodes a signed 16-bit integer as decimal.+-- This encoding never starts with a zero unless the argument was zero.+-- Negative numbers are preceded by a minus sign. Positive numbers+-- are not preceded by anything.+int16Dec :: Int16 -> Builder 6+int16Dec (I16# w) = intCommonDec# w++-- | Requires up to 4 bytes. Encodes a signed 8-bit integer as decimal.+-- This encoding never starts with a zero unless the argument was zero.+-- Negative numbers are preceded by a minus sign. Positive numbers+-- are not preceded by anything.+int8Dec :: Int8 -> Builder 4+int8Dec (I8# w) = intCommonDec# w++-- | Requires up to 20 bytes. Encodes a signed machine-sized integer+-- as decimal. This encoding never starts with a zero unless the+-- argument was zero. Negative numbers are preceded by a minus sign.+-- Positive numbers are not preceded by anything.+intDec :: Int -> Builder 20+intDec (I# w) = intCommonDec# w++-- Requires a number of bytes that is bounded by the size of+-- the word. This is only used internally.+wordCommonDec# :: Word# -> Builder n+{-# noinline wordCommonDec# #-}+wordCommonDec# w# = Unsafe.construct $ \arr off0 -> if w /= 0+  then internalWordLoop arr off0 (W# w#)+  else do+    writeByteArray arr off0 (c2w '0')+    pure (off0 + 1)+  where+  w = W64# w#++internalWordLoop :: MutableByteArray s -> Int -> Word -> ST s Int+{-# inline internalWordLoop #-}+internalWordLoop arr off0 x0 = go off0 x0 where+  go !off !(x :: Word) = if x > 0+    then do+      let (y,z) = quotRem x 10+      writeByteArray arr off (fromIntegral (z + 0x30) :: Word8)+      go (off + 1) y+    else do+      reverseBytes arr off0 (off - 1)+      pure off++-- Requires up to 20 bytes. Can be less depending on what the+-- size of the argument is known to be. Unsafe.+intCommonDec# :: Int# -> Builder n+{-# noinline intCommonDec# #-}+intCommonDec# w# = Unsafe.construct $ \arr off0 -> case compare w 0 of+  GT -> internalWordLoop arr off0 (fromIntegral w)+  EQ -> do+    writeByteArray arr off0 (c2w '0')+    pure (off0 + 1)+  LT -> do+    writeByteArray arr off0 (c2w '-')+    internalWordLoop arr (off0 + 1) (fromIntegral (negate w))+  where+  w = I64# w#++-- Convert a number between 0 and 16 to the ASCII+-- representation of its hexadecimal character.+-- The use of fromIntegral causes us to incur an+-- unneeded bitmask. This actually needs a Word64+-- argument.+toHexUpper :: Word -> Word8+toHexUpper w' = fromIntegral+    $ (complement theMask .&. loSolved)+  .|. (theMask .&. hiSolved)+  where+  w = w' .&. 0xF+  -- This is all ones if the value was >= 10+  theMask = (1 .&. unsafeShiftR (w - 10) 63) - 1+  loSolved = w + 48+  hiSolved = w + 55++-- | Requires exactly 16 bytes. Encodes a 64-bit unsigned integer as+-- hexadecimal, zero-padding the encoding to 16 digits. This uses+-- uppercase for the alphabetical digits. For example, this encodes the+-- number 1022 as @00000000000003FE@.+word64PaddedUpperHex :: Word64 -> Builder 16+word64PaddedUpperHex (W64# w) = word64PaddedUpperHex# w++-- | Requires exactly 8 bytes. Encodes a 32-bit unsigned integer as+-- hexadecimal, zero-padding the encoding to 8 digits. This uses+-- uppercase for the alphabetical digits.+word32PaddedUpperHex :: Word32 -> Builder 8+word32PaddedUpperHex (W32# w) = word32PaddedUpperHex# w++-- | Requires exactly 4 bytes. Encodes a 16-bit unsigned integer as+-- hexadecimal, zero-padding the encoding to 4 digits. This uses+-- uppercase for the alphabetical digits.+word16PaddedUpperHex :: Word16 -> Builder 4+word16PaddedUpperHex (W16# w) = word16PaddedUpperHex# w++-- | Requires exactly 2 bytes. Encodes a 8-bit unsigned integer as+-- hexadecimal, zero-padding the encoding to 2 digits. This uses+-- uppercase for the alphabetical digits.+word8PaddedUpperHex :: Word8 -> Builder 2+word8PaddedUpperHex (W8# w) = word8PaddedUpperHex# w++-- TODO: Is it actually worth unrolling this loop. I suspect that it+-- might not be. Benchmark this.+word64PaddedUpperHex# :: Word# -> Builder 16+{-# noinline word64PaddedUpperHex# #-}+word64PaddedUpperHex# w# = Unsafe.construct $ \arr off -> do+  writeByteArray arr off (toHexUpper (unsafeShiftR w 60))+  writeByteArray arr (off + 1) (toHexUpper (unsafeShiftR w 56))+  writeByteArray arr (off + 2) (toHexUpper (unsafeShiftR w 52))+  writeByteArray arr (off + 3) (toHexUpper (unsafeShiftR w 48))+  writeByteArray arr (off + 4) (toHexUpper (unsafeShiftR w 44))+  writeByteArray arr (off + 5) (toHexUpper (unsafeShiftR w 40))+  writeByteArray arr (off + 6) (toHexUpper (unsafeShiftR w 36))+  writeByteArray arr (off + 7) (toHexUpper (unsafeShiftR w 32))+  writeByteArray arr (off + 8) (toHexUpper (unsafeShiftR w 28))+  writeByteArray arr (off + 9) (toHexUpper (unsafeShiftR w 24))+  writeByteArray arr (off + 10) (toHexUpper (unsafeShiftR w 20))+  writeByteArray arr (off + 11) (toHexUpper (unsafeShiftR w 16))+  writeByteArray arr (off + 12) (toHexUpper (unsafeShiftR w 12))+  writeByteArray arr (off + 13) (toHexUpper (unsafeShiftR w 8))+  writeByteArray arr (off + 14) (toHexUpper (unsafeShiftR w 4))+  writeByteArray arr (off + 15) (toHexUpper (unsafeShiftR w 0))+  pure (off + 16)+  where+  w = W# w#++word32PaddedUpperHex# :: Word# -> Builder 8+{-# noinline word32PaddedUpperHex# #-}+word32PaddedUpperHex# w# = Unsafe.construct $ \arr off -> do+  writeByteArray arr off (toHexUpper (unsafeShiftR w 28))+  writeByteArray arr (off + 1) (toHexUpper (unsafeShiftR w 24))+  writeByteArray arr (off + 2) (toHexUpper (unsafeShiftR w 20))+  writeByteArray arr (off + 3) (toHexUpper (unsafeShiftR w 16))+  writeByteArray arr (off + 4) (toHexUpper (unsafeShiftR w 12))+  writeByteArray arr (off + 5) (toHexUpper (unsafeShiftR w 8))+  writeByteArray arr (off + 6) (toHexUpper (unsafeShiftR w 4))+  writeByteArray arr (off + 7) (toHexUpper (unsafeShiftR w 0))+  pure (off + 8)+  where+  w = W# w#++-- Not sure if it is beneficial to inline this. We just let+-- GHC make the decision. Open an issue on github if this is+-- a problem.+word16PaddedUpperHex# :: Word# -> Builder 4+word16PaddedUpperHex# w# = Unsafe.construct $ \arr off -> do+  writeByteArray arr off (toHexUpper (unsafeShiftR w 12))+  writeByteArray arr (off + 1) (toHexUpper (unsafeShiftR w 8))+  writeByteArray arr (off + 2) (toHexUpper (unsafeShiftR w 4))+  writeByteArray arr (off + 3) (toHexUpper (unsafeShiftR w 0))+  pure (off + 4)+  where+  w = W# w#++-- Definitely want this to inline. It's maybe a dozen instructions total.+word8PaddedUpperHex# :: Word# -> Builder 2+{-# inline word8PaddedUpperHex #-}+word8PaddedUpperHex# w# = Unsafe.construct $ \arr off -> do+  writeByteArray arr off (toHexUpper (unsafeShiftR w 4))+  writeByteArray arr (off + 1) (toHexUpper (unsafeShiftR w 0))+  pure (off + 2)+  where+  w = W# w#++-- | Encode an ASCII char.+-- Precondition: Input must be an ASCII character. This is not checked.+ascii :: Char -> Builder 1+ascii c = word8 (fromIntegral @Int @Word8 (ord c))++-- | Encode a character as UTF-8. This only uses as much space as is required.+char :: Char -> Builder 4+char c+  | codepoint < 0x80 = Unsafe.construct $ \arr off -> do+      writeByteArray arr off (unsafeWordToWord8 codepoint)+      pure (off + 1)+  | codepoint < 0x800 = Unsafe.construct $ \arr off -> do+      writeByteArray arr off       (unsafeWordToWord8 (byteTwoOne codepoint))+      writeByteArray arr (off + 1) (unsafeWordToWord8 (byteTwoTwo codepoint))+      return (off + 2)+  | codepoint >= 0xD800 && codepoint < 0xE000 = Unsafe.construct $ \arr off -> do+      -- Codepoint U+FFFD+      writeByteArray arr off       (0xEF :: Word8)+      writeByteArray arr (off + 1) (0xBF :: Word8)+      writeByteArray arr (off + 2) (0xBD :: Word8)+      return (off + 3)+  | codepoint < 0x10000 = Unsafe.construct $ \arr off -> do+      writeByteArray arr off       (unsafeWordToWord8 (byteThreeOne codepoint))+      writeByteArray arr (off + 1) (unsafeWordToWord8 (byteThreeTwo codepoint))+      writeByteArray arr (off + 2) (unsafeWordToWord8 (byteThreeThree codepoint))+      return (off + 3)+  | otherwise = Unsafe.construct $ \arr off -> do+      writeByteArray arr off       (unsafeWordToWord8 (byteFourOne codepoint))+      writeByteArray arr (off + 1) (unsafeWordToWord8 (byteFourTwo codepoint))+      writeByteArray arr (off + 2) (unsafeWordToWord8 (byteFourThree codepoint))+      writeByteArray arr (off + 3) (unsafeWordToWord8 (byteFourFour codepoint))+      return (off + 4)++  where+    codepoint :: Word+    codepoint = fromIntegral (ord c)++    unsafeWordToWord8 :: Word -> Word8+    unsafeWordToWord8 (W# w) = W8# w++    -- precondition: codepoint is less than 0x800+    byteTwoOne :: Word -> Word+    byteTwoOne w = unsafeShiftR w 6 .|. 0b11000000++    byteTwoTwo :: Word -> Word+    byteTwoTwo w = (w .&. 0b00111111) .|. 0b10000000++    -- precondition: codepoint is less than 0x1000+    byteThreeOne :: Word -> Word+    byteThreeOne w = unsafeShiftR w 12 .|. 0b11100000++    byteThreeTwo :: Word -> Word+    byteThreeTwo w = (0b00111111 .&. unsafeShiftR w 6) .|. 0b10000000++    byteThreeThree :: Word -> Word+    byteThreeThree w = (w .&. 0b00111111) .|. 0b10000000++    -- precondition: codepoint is less than 0x110000+    byteFourOne :: Word -> Word+    byteFourOne w = unsafeShiftR w 18 .|. 0b11110000++    byteFourTwo :: Word -> Word+    byteFourTwo w = (0b00111111 .&. unsafeShiftR w 12) .|. 0b10000000++    byteFourThree :: Word -> Word+    byteFourThree w = (0b00111111 .&. unsafeShiftR w 6) .|. 0b10000000++    byteFourFour :: Word -> Word+    byteFourFour w = (0b00111111 .&. w) .|. 0b10000000++-- | Requires exactly 8 bytes. Dump the octets of a 64-bit+-- word in a big-endian fashion.+word64BE :: Word64 -> Builder 8+word64BE w = Unsafe.construct $ \arr off -> do+  writeByteArray arr (off    ) (fromIntegral @Word64 @Word8 (unsafeShiftR w 56))+  writeByteArray arr (off + 1) (fromIntegral @Word64 @Word8 (unsafeShiftR w 48))+  writeByteArray arr (off + 2) (fromIntegral @Word64 @Word8 (unsafeShiftR w 40))+  writeByteArray arr (off + 3) (fromIntegral @Word64 @Word8 (unsafeShiftR w 32))+  writeByteArray arr (off + 4) (fromIntegral @Word64 @Word8 (unsafeShiftR w 24))+  writeByteArray arr (off + 5) (fromIntegral @Word64 @Word8 (unsafeShiftR w 16))+  writeByteArray arr (off + 6) (fromIntegral @Word64 @Word8 (unsafeShiftR w 8))+  writeByteArray arr (off + 7) (fromIntegral @Word64 @Word8 w)+  pure (off + 8)++-- | Requires exactly 4 bytes. Dump the octets of a 32-bit+-- word in a big-endian fashion.+word32BE :: Word32 -> Builder 4+word32BE w = Unsafe.construct $ \arr off -> do+  writeByteArray arr (off    ) (fromIntegral @Word32 @Word8 (unsafeShiftR w 24))+  writeByteArray arr (off + 1) (fromIntegral @Word32 @Word8 (unsafeShiftR w 16))+  writeByteArray arr (off + 2) (fromIntegral @Word32 @Word8 (unsafeShiftR w 8))+  writeByteArray arr (off + 3) (fromIntegral @Word32 @Word8 w)+  pure (off + 4)++-- | Requires exactly 2 bytes. Dump the octets of a 16-bit+-- word in a big-endian fashion.+word16BE :: Word16 -> Builder 2+word16BE w = Unsafe.construct $ \arr off -> do+  writeByteArray arr (off    ) (fromIntegral @Word16 @Word8 (unsafeShiftR w 8))+  writeByteArray arr (off + 1) (fromIntegral @Word16 @Word8 w)+  pure (off + 2)++word8 :: Word8 -> Builder 1+word8 w = Unsafe.construct $ \arr off -> do+  writeByteArray arr off w+  pure (off + 1)++-- Reverse the bytes in the designated slice. This takes+-- an inclusive start offset and an inclusive end offset.+reverseBytes :: MutableByteArray s -> Int -> Int -> ST s ()+{-# inline reverseBytes #-}+reverseBytes arr begin end = go begin end where+  go ixA ixB = if ixA < ixB+    then do+      a :: Word8 <- readByteArray arr ixA+      b :: Word8 <- readByteArray arr ixB+      writeByteArray arr ixA b+      writeByteArray arr ixB a+      go (ixA + 1) (ixB - 1)+    else pure ()++c2w :: Char -> Word8+c2w = fromIntegral . ord++shrinkMutableByteArray :: MutableByteArray s -> Int -> ST s ()+shrinkMutableByteArray (MutableByteArray arr) (I# sz) =+  primitive_ (shrinkMutableByteArray# arr sz)++-- This is adapted from androider's code in https://stackoverflow.com/a/7097567+-- The checks for infinity and NaN have been removed. Note that this is a little+-- inaccurate. This is very visible when encoding a number like 2.25, which+-- is perfectly represented as an IEEE 754 floating point number but is goofed+-- up by this function.+-- If you modify this function, please take a took at the resulting core.+-- It currently performs no boxing at all, and it would be nice to keep+-- it that way.+doubleDec# :: forall s.+  Double# -> MutableByteArray# s -> Int# -> State# s -> (# State# s, Int# #)+{-# noinline doubleDec# #-}+doubleDec# d# marr# off# s0 = unIntST s0 $ do+  let marr = MutableByteArray marr#+  let d0 = D# d#+  let off0 = I# off#+  if d0 == 0+    then do+      writeByteArray marr off0 (c2w '0')+      pure (off0 + 1)+    else do+      let neg = d0 < 0+      off1 <- if neg+        then do+          writeByteArray marr off0 (c2w '-')+          pure (off0 + 1)+        else pure off0+      let d1 = abs d0+      let mag0 = floor (logBase10 d1) :: Int+      let useExp = (mag0 >= 14 || (neg && mag0 >= 9) || mag0 <= (-9))+      -- This straightforward adaptation of the C code is awkward+      -- in Haskell. Binding the triple where mag1 might not even+      -- get used is strange.+      let !(!d2,!mag1,!mag0A) = if useExp+            then+              let mag0' = if mag0 < 0 then mag0 - 1 else mag0+               in (d1 / (10.0 ** fromIntegral @Int @Double mag0'), mag0', 0)+            else (d1,0,mag0)+      let mag0B = if mag0A < 1 then 0 else mag0A+      let goNum :: Double -> Int -> Int -> ST s Int+          goNum !dA0 !mag !offA0 = if (dA0 > doublePrecision || mag >= 0)+            then do+              let weight = 10.0 ** (fromIntegral @Int @Double mag)+              -- We should actually check weight with isinf here,+              -- but we do not.+              (dA1,offA1) <- if weight > 0+                then do+                  -- TODO: use a better floor function+                  let digit = ((floor :: Double -> Int) (dA0 / weight))+                  let discard = fromIntegral @Int @Double digit * weight+                  writeByteArray marr offA0+                    (fromIntegral @Int @Word8 (digit + ord '0'))+                  pure (dA0 - discard,offA0 + 1)+                else pure (dA0,offA0)+              offA2 <- if mag == 0 && dA1 > 0+                then do+                  writeByteArray marr offA1 (c2w '.')+                  pure (offA1 + 1)+                else pure offA1+              goNum dA1 (mag - 1) offA2+            else pure offA0+      !off2 <- goNum d2 mag0B off1+      off3 <- if useExp+        then do+          writeByteArray marr off2 (c2w 'e')+          !mag2 <- if mag1 > 0+            then do+              writeByteArray marr (off2 + 1) (c2w '+')+              pure mag1+            else do+              writeByteArray marr (off2 + 1) (c2w '-')+              pure (-mag1)+          let goMag !mag !off = if mag > 0+                then do+                  let (q,r) = quotRem mag 10+                  writeByteArray marr off (fromIntegral @Int @Word8 (ord '0' + r))+                  goMag q (off + 1)+                else pure off+          !off3 <- goMag mag2 (off2 + 2)+          reverseBytes marr (off2 + 2) (off3 - 1)+          pure off3+        else pure off2+      pure off3++doublePrecision :: Double+doublePrecision = 0.00000000000001++unIntST :: State# s -> ST s Int -> (# State# s, Int# #)+{-# inline unIntST #-}+unIntST s0 (ST f) = case f s0 of+  (# s1, I# i #) -> (# s1, i #)++-- This is slightly inaccurate. I think this can actually cause+-- problems in some situations. The log10 function from C would+-- be better. The inaccuracy here cause the logarithm to be slightly+-- larger than it should be. There might actually be a simple way to+-- fix this by just using recursion to compute it. We just floor the+-- result anyway. Hmm...+logBase10 :: Double -> Double+logBase10 d = log d / 2.30258509299
+ src/Data/ByteArray/Builder/Bounded/Unsafe.hs view
@@ -0,0 +1,59 @@+{-# language DataKinds #-}+{-# language GADTSyntax #-}+{-# language KindSignatures #-}+{-# language MagicHash #-}+{-# language RankNTypes #-}+{-# language ScopedTypeVariables #-}+{-# language UnboxedTuples #-}++module Data.ByteArray.Builder.Bounded.Unsafe+  ( -- * Types+    Builder(..)+    -- * Construct+  , construct+    -- * Run+  , pasteST+  , pasteIO+  ) where++import GHC.TypeLits (Nat)+import Data.Kind (Type)+import GHC.IO (stToIO)+import GHC.ST (ST(ST))+import GHC.Exts (Int(I#),RealWorld,Int#,State#,MutableByteArray#)+import Data.Primitive (MutableByteArray(..))++-- | A builder parameterized by the maximum number of bytes it uses+-- when executed.+newtype Builder :: Nat -> Type where+   Builder ::+        (forall s. MutableByteArray# s -> Int# -> State# s -> (# State# s, Int# #))+     -> Builder n++-- | Constructor for 'Builder' that works on a function with lifted+-- arguments instead of unlifted ones. This is just as unsafe as the+-- actual constructor.+construct :: (forall s. MutableByteArray s -> Int -> ST s Int) -> Builder n+{-# inline construct #-}+construct f = Builder+  $ \arr off s0 ->+    case unST (f (MutableByteArray arr) (I# off)) s0 of+      (# s1, (I# n) #) -> (# s1, n #)++-- | This function does not enforce the known upper bound on the+-- size. It is up to the user to do this.+pasteST :: Builder n -> MutableByteArray s -> Int -> ST s Int+{-# inline pasteST #-}+pasteST (Builder f) (MutableByteArray arr) (I# off) =+  ST $ \s0 -> case f arr off s0 of+    (# s1, r #) -> (# s1, (I# r) #)++-- | This function does not enforce the known upper bound on the+-- size. It is up to the user to do this.+pasteIO :: Builder n -> MutableByteArray RealWorld -> Int -> IO Int+{-# inline pasteIO #-}+pasteIO b m off = stToIO (pasteST b m off)++unST :: ST s a -> State# s -> (# State# s, a #)+unST (ST f) = f+
− src/Data/ByteArray/Builder/Small.hs
@@ -1,250 +0,0 @@-{-# language BangPatterns #-}-{-# language DuplicateRecordFields #-}-{-# language LambdaCase #-}-{-# language MagicHash #-}-{-# language RankNTypes #-}-{-# language ScopedTypeVariables #-}-{-# language UnboxedTuples #-}--module Data.ByteArray.Builder.Small-  ( -- * Unsafe Primitives-    Builder(..)-  , construct-  , fromUnsafe-    -- * Evaluation-  , run-  , pasteST-  , pasteIO-  , pasteGrowST-  , pasteGrowIO-  , pasteArrayST-  , pasteArrayIO-    -- * Materialized Byte Sequences-  , bytes-  , bytearray-    -- * Encode Integral Types-    -- ** Human-Readable-  , word64Dec-  , int64Dec-  , word64PaddedUpperHex-  , word32PaddedUpperHex-  , word16PaddedUpperHex-  , word8PaddedUpperHex-    -- ** Machine-Readable-  , word64BE-  , word32BE-  , word16BE-  ) where--import Control.Monad.Primitive-import Control.Monad.ST-import Control.Monad.ST.Run (runByteArrayST)-import Data.Bytes.Types-import Data.Primitive-import Data.Int (Int64)-import GHC.Exts-import GHC.ST-import GHC.Word-import GHC.TypeLits (KnownNat,natVal')-import Data.Primitive.ByteArray.Offset (MutableByteArrayOffset(..))--import qualified Data.Primitive as PM-import qualified Data.Vector as V-import qualified Data.ByteArray.Builder.Small.Unsafe as Unsafe---- | An unmaterialized sequence of bytes that may be pasted--- into a mutable byte array.-newtype Builder = Builder-  (forall s. MutableByteArray# s -> Int# -> Int# -> State# s -> (# State# s, Int# #))--instance Semigroup Builder where-  {-# inline (<>) #-}-  Builder f <> Builder g = Builder $ \arr off0 len0 s0 -> case f arr off0 len0 s0 of-    (# s1, r #) -> case r /=# (-1#) of-      1# -> g arr r (len0 +# (off0 -# r)) s1-      _ -> (# s1, (-1#) #)--instance Monoid Builder where-  mempty = Builder $ \_ off0 _ s0 -> (# s0, off0 #)---- | Run a builder. An accurate size hint is important for good performance.--- The size hint should be slightly larger than the actual size.-run ::-     Int -- ^ Hint for upper bound on size-  -> Builder -- ^ Builder-  -> ByteArray-run hint b = runByteArrayST $ do-  let go !n = do-        arr <- newByteArray n-        pasteST b (MutableBytes arr 0 n) >>= \case-          Nothing -> go (n + 64)-          Just len -> do-            shrinkMutableByteArray arr len-            unsafeFreezeByteArray arr-  go hint---- | Variant of 'pasteArrayST' that runs in 'IO'.-pasteArrayIO ::-     MutableBytes RealWorld -- ^ Buffer-  -> (a -> Builder) -- ^ Builder-  -> V.Vector a -- ^ Elements to serialize-  -> IO (V.Vector a, MutableBytes RealWorld) -- ^ Shifted vector, shifted buffer-pasteArrayIO !arr f !xs = stToIO (pasteArrayST arr f xs)---- | Fold over a vector, applying the builder to each element until--- the buffer cannot accomodate any more.-pasteArrayST ::-     MutableBytes s -- ^ Buffer-  -> (a -> Builder) -- ^ Builder-  -> V.Vector a -- ^ Elements to serialize-  -> ST s (V.Vector a, MutableBytes s) -- ^ Shifted vector, shifted buffer-pasteArrayST (MutableBytes arr off0 len0) f !xs0 = do-  let go !xs !ixBufA !lenBufA = if V.length xs > 0-        then do-          let a = V.unsafeHead xs-          pasteST (f a) (MutableBytes arr ixBufA lenBufA) >>= \case-            Nothing -> pure (xs,MutableBytes arr ixBufA lenBufA)-            Just ixBufB ->-              go (V.unsafeTail xs) ixBufB (lenBufA + (ixBufA - ixBufB))-        else pure (xs,MutableBytes arr ixBufA lenBufA)-  go xs0 off0 len0---- | Paste the builder into the byte array starting at offset zero.--- This repeatedly reallocates the byte array if it cannot accomodate--- the builder, replaying the builder each time.-pasteGrowST ::-     Int -- ^ How many bytes to grow by at a time-  -> Builder-  -> MutableByteArray s-     -- ^ Initial buffer, used linearly. Do not reuse this argument.-  -> ST s (MutableByteArrayOffset s)-     -- ^ Final buffer that accomodated the builder.-pasteGrowST !n b !arr0 = do-  let go !arr !sz = pasteST b (MutableBytes arr 0 sz) >>= \case-        Nothing -> do-          let szNext = sz + n-          arrNext <- PM.newByteArray szNext-          go arrNext szNext-        Just ix -> pure (MutableByteArrayOffset{array=arr,offset=ix})-  go arr0 =<< PM.getSizeofMutableByteArray arr0---- | Variant of 'pasteGrowST' that runs in 'IO'.-pasteGrowIO ::-     Int -- ^ How many bytes to grow by at a time-  -> Builder-  -> MutableByteArray RealWorld-     -- ^ Initial buffer, used linearly. Do not reuse this argument.-  -> IO (MutableByteArrayOffset RealWorld)-     -- ^ Final buffer that accomodated the builder.-pasteGrowIO !n b !arr = stToIO (pasteGrowST n b arr)---- | Execute the builder, pasting its contents into a buffer.--- If the buffer is not large enough, this returns 'Nothing'.--- Otherwise, it returns the index in the buffer that follows--- the payload just written.-pasteST :: Builder -> MutableBytes s -> ST s (Maybe Int)-{-# inline pasteST #-}-pasteST (Builder f) (MutableBytes (MutableByteArray arr) (I# off) (I# len)) =-  ST $ \s0 -> case f arr off len s0 of-    (# s1, r #) -> if isTrue# (r /=# (-1#))-      then (# s1, Just (I# r) #)-      else (# s1, Nothing #)---- | Variant of 'pasteST' that runs in 'IO'.-pasteIO :: Builder -> MutableBytes RealWorld -> IO (Maybe Int)-{-# inline pasteIO #-}-pasteIO b m = stToIO (pasteST b m)---- | Constructor for 'Builder' that works on a function with lifted--- arguments instead of unlifted ones. This is just as unsafe as the--- actual constructor.-construct :: (forall s. MutableBytes s -> ST s (Maybe Int)) -> Builder-construct f = Builder-  $ \arr off len s0 ->-    case unST (f (MutableBytes (MutableByteArray arr) (I# off) (I# len))) s0 of-      (# s1, m #) -> case m of-        Nothing -> (# s1, (-1#) #)-        Just (I# n) -> (# s1, n #)--fromUnsafe :: forall n. KnownNat n => Unsafe.Builder n -> Builder-{-# inline fromUnsafe #-}-fromUnsafe (Unsafe.Builder f) = Builder $ \arr off len s0 ->-  case fromIntegral (natVal' (proxy# :: Proxy# n)) of-    I# req -> case len >=# req of-      1# -> f arr off s0-      _ -> (# s0, (-1#) #)---- | Create a builder from an unsliced byte sequence.-bytearray :: ByteArray -> Builder-bytearray a = bytes (Bytes a 0 (sizeofByteArray a))---- | Create a builder from a sliced byte sequence.-bytes :: Bytes -> Builder-bytes (Bytes src soff slen) = construct $ \(MutableBytes arr off len) -> if len >= slen-  then do-    copyByteArray arr off src soff slen-    pure (Just (len - slen))-  else pure Nothing---- | Encodes an unsigned 64-bit integer as decimal.--- This encoding never starts with a zero unless the--- argument was zero.-word64Dec :: Word64 -> Builder-word64Dec w = fromUnsafe (Unsafe.word64Dec w)---- | Encodes a signed 64-bit integer as decimal.--- This encoding never starts with a zero unless the argument was zero.--- Negative numbers are preceded by a minus sign. Positive numbers--- are not preceded by anything.-int64Dec :: Int64 -> Builder-int64Dec w = fromUnsafe (Unsafe.int64Dec w)---- | Encode a 64-bit unsigned integer as hexadecimal, zero-padding--- the encoding to 16 digits. This uses uppercase for the alphabetical--- digits. For example, this encodes the number 1022 as @00000000000003FE@.-word64PaddedUpperHex :: Word64 -> Builder-word64PaddedUpperHex w =-  fromUnsafe (Unsafe.word64PaddedUpperHex w)---- | Encode a 32-bit unsigned integer as hexadecimal, zero-padding--- the encoding to 8 digits. This uses uppercase for the alphabetical--- digits. For example, this encodes the number 1022 as @000003FE@.-word32PaddedUpperHex :: Word32 -> Builder-word32PaddedUpperHex w =-  fromUnsafe (Unsafe.word32PaddedUpperHex w)---- | Encode a 16-bit unsigned integer as hexadecimal, zero-padding--- the encoding to 4 digits. This uses uppercase for the alphabetical--- digits. For example, this encodes the number 1022 as @03FE@.-word16PaddedUpperHex :: Word16 -> Builder-word16PaddedUpperHex w =-  fromUnsafe (Unsafe.word16PaddedUpperHex w)---- | Encode a 8-bit unsigned integer as hexadecimal, zero-padding--- the encoding to 2 digits. This uses uppercase for the alphabetical--- digits. For example, this encodes the number 11 as @0B@.-word8PaddedUpperHex :: Word8 -> Builder-word8PaddedUpperHex w =-  fromUnsafe (Unsafe.word8PaddedUpperHex w)--unST :: ST s a -> State# s -> (# State# s, a #)-unST (ST f) = f--shrinkMutableByteArray :: MutableByteArray s -> Int -> ST s ()-shrinkMutableByteArray (MutableByteArray arr) (I# sz) =-  primitive_ (shrinkMutableByteArray# arr sz)---- | Requires exactly 8 bytes. Dump the octets of a 64-bit--- word in a big-endian fashion.-word64BE :: Word64 -> Builder-word64BE w = fromUnsafe (Unsafe.word64BE w)---- | Requires exactly 4 bytes. Dump the octets of a 32-bit--- word in a big-endian fashion.-word32BE :: Word32 -> Builder-word32BE w = fromUnsafe (Unsafe.word32BE w)---- | Requires exactly 2 bytes. Dump the octets of a 16-bit--- word in a big-endian fashion.-word16BE :: Word16 -> Builder-word16BE w = fromUnsafe (Unsafe.word16BE w)
− src/Data/ByteArray/Builder/Small/Unsafe.hs
@@ -1,308 +0,0 @@-{-# language GADTSyntax #-}-{-# language KindSignatures #-}-{-# language ScopedTypeVariables #-}-{-# language BangPatterns #-}-{-# language MagicHash #-}-{-# language UnboxedTuples #-}-{-# language RankNTypes #-}-{-# language LambdaCase #-}-{-# language TypeOperators #-}-{-# language DataKinds #-}-{-# language TypeApplications #-}---- | The functions in this module do not check to--- see if there is enough space in the buffer.-module Data.ByteArray.Builder.Small.Unsafe-  ( -- * Builder-    Builder(..)-  , construct-    -- * Execute-  , run-  , pasteST-  , pasteIO-    -- * Combine-  , append-    -- * Encode Integral Types-    -- ** Human-Readable-  , word64Dec-  , int64Dec-  , word64PaddedUpperHex-  , word32PaddedUpperHex-  , word16PaddedUpperHex-  , word8PaddedUpperHex-    -- ** Machine-Readable-  , word64BE-  , word32BE-  , word16BE-  ) where--import Control.Monad.Primitive-import Control.Monad.ST-import Data.Bits-import Data.Char (ord)-import Data.Primitive-import GHC.Exts-import GHC.ST-import GHC.Word-import GHC.Int-import Data.Kind-import GHC.TypeLits (KnownNat,Nat,type (+),natVal')---- | A builder parameterized by the maximum number of bytes it uses--- when executed.-newtype Builder :: Nat -> Type where-   Builder ::-        (forall s. MutableByteArray# s -> Int# -> State# s -> (# State# s, Int# #))-     -> Builder n---- | Execute the builder. This function is safe.-run :: forall n. KnownNat n-  => Builder n -- ^ Builder-  -> ByteArray-{-# inline run #-}-run b = runST $ do-  arr <- newByteArray (fromIntegral (natVal' (proxy# :: Proxy# n)))-  len <- pasteST b arr 0-  shrinkMutableByteArray arr len-  unsafeFreezeByteArray arr---- | This function does not enforce the known upper bound on the--- size. It is up to the user to do this.-pasteST :: Builder n -> MutableByteArray s -> Int -> ST s Int-{-# inline pasteST #-}-pasteST (Builder f) (MutableByteArray arr) (I# off) =-  ST $ \s0 -> case f arr off s0 of-    (# s1, r #) -> (# s1, (I# r) #)---- | This function does not enforce the known upper bound on the--- size. It is up to the user to do this.-pasteIO :: Builder n -> MutableByteArray RealWorld -> Int -> IO Int-{-# inline pasteIO #-}-pasteIO b m off = stToIO (pasteST b m off)---- | Constructor for 'Builder' that works on a function with lifted--- arguments instead of unlifted ones. This is just as unsafe as the--- actual constructor.-construct :: (forall s. MutableByteArray s -> Int -> ST s Int) -> Builder n-{-# inline construct #-}-construct f = Builder-  $ \arr off s0 ->-    case unST (f (MutableByteArray arr) (I# off)) s0 of-      (# s1, (I# n) #) -> (# s1, n #)--infixr 9 `append`---- | Concatenate two builders.-append :: Builder n -> Builder m -> Builder (n + m)-append (Builder f) (Builder g) =-  Builder $ \arr off0 s0 -> case f arr off0 s0 of-    (# s1, r #) -> g arr r s1---- | Requires up to 19 bytes. Encodes an unsigned 64-bit integer as decimal.--- This encoding never starts with a zero unless the argument was zero.-word64Dec :: Word64 -> Builder 19-word64Dec (W64# w) = word64Dec# w---- | Requires up to 20 bytes. Encodes a signed 64-bit integer as decimal.--- This encoding never starts with a zero unless the argument was zero.--- Negative numbers are preceded by a minus sign. Positive numbers--- are not preceded by anything.-int64Dec :: Int64 -> Builder 20-int64Dec (I64# w) = int64Dec# w---- | Requires up to 19 bytes.-word64Dec# :: Word# -> Builder 19-{-# noinline word64Dec# #-}-word64Dec# w# = construct $ \arr off0 -> if w /= 0-  then internalWordLoop arr off0 (W# w#)-  else do-    writeByteArray arr off0 (c2w '0')-    pure (off0 + 1)-  where-  w = W64# w#--internalWordLoop :: MutableByteArray s -> Int -> Word -> ST s Int-{-# inline internalWordLoop #-}-internalWordLoop arr off0 x0 = go off0 x0 where-  go !off !(x :: Word) = if x > 0-    then do-      let (y,z) = quotRem x 10-      writeByteArray arr off (fromIntegral (z + 0x30) :: Word8)-      go (off + 1) y-    else do-      reverseBytes arr off0 (off - 1)-      pure off---- | Requires up to 19 bytes.-int64Dec# :: Int# -> Builder 20-{-# noinline int64Dec# #-}-int64Dec# w# = construct $ \arr off0 -> case compare w 0 of-  GT -> internalWordLoop arr off0 (fromIntegral w)-  EQ -> do-    writeByteArray arr off0 (c2w '0')-    pure (off0 + 1)-  LT -> do-    writeByteArray arr off0 (c2w '-')-    internalWordLoop arr (off0 + 1) (fromIntegral (negate w))-  where-  w = I64# w#---- Convert a number between 0 and 16 to the ASCII--- representation of its hexadecimal character.--- The use of fromIntegral causes us to incur an--- unneeded bitmask. This actually needs a Word64--- argument.-toHexUpper :: Word -> Word8-toHexUpper w' = fromIntegral-    $ (complement theMask .&. loSolved)-  .|. (theMask .&. hiSolved)-  where-  w = w' .&. 0xF-  -- This is all ones if the value was >= 10-  theMask = (1 .&. unsafeShiftR (w - 10) 63) - 1-  loSolved = w + 48-  hiSolved = w + 55---- | Requires exactly 16 bytes. Encodes a 64-bit unsigned integer as--- hexadecimal, zero-padding the encoding to 16 digits. This uses--- uppercase for the alphabetical digits. For example, this encodes the--- number 1022 as @00000000000003FE@.-word64PaddedUpperHex :: Word64 -> Builder 16-word64PaddedUpperHex (W64# w) = word64PaddedUpperHex# w---- | Requires exactly 8 bytes. Encodes a 32-bit unsigned integer as--- hexadecimal, zero-padding the encoding to 8 digits. This uses--- uppercase for the alphabetical digits.-word32PaddedUpperHex :: Word32 -> Builder 8-word32PaddedUpperHex (W32# w) = word32PaddedUpperHex# w---- | Requires exactly 4 bytes. Encodes a 16-bit unsigned integer as--- hexadecimal, zero-padding the encoding to 4 digits. This uses--- uppercase for the alphabetical digits.-word16PaddedUpperHex :: Word16 -> Builder 4-word16PaddedUpperHex (W16# w) = word16PaddedUpperHex# w---- | Requires exactly 2 bytes. Encodes a 8-bit unsigned integer as--- hexadecimal, zero-padding the encoding to 2 digits. This uses--- uppercase for the alphabetical digits.-word8PaddedUpperHex :: Word8 -> Builder 2-word8PaddedUpperHex (W8# w) = word8PaddedUpperHex# w---- TODO: Is it actually worth unrolling this loop. I suspect that it--- might not be. Benchmark this.-word64PaddedUpperHex# :: Word# -> Builder 16-{-# noinline word64PaddedUpperHex# #-}-word64PaddedUpperHex# w# = construct $ \arr off -> do-  writeByteArray arr off (toHexUpper (unsafeShiftR w 60))-  writeByteArray arr (off + 1) (toHexUpper (unsafeShiftR w 56))-  writeByteArray arr (off + 2) (toHexUpper (unsafeShiftR w 52))-  writeByteArray arr (off + 3) (toHexUpper (unsafeShiftR w 48))-  writeByteArray arr (off + 4) (toHexUpper (unsafeShiftR w 44))-  writeByteArray arr (off + 5) (toHexUpper (unsafeShiftR w 40))-  writeByteArray arr (off + 6) (toHexUpper (unsafeShiftR w 36))-  writeByteArray arr (off + 7) (toHexUpper (unsafeShiftR w 32))-  writeByteArray arr (off + 8) (toHexUpper (unsafeShiftR w 28))-  writeByteArray arr (off + 9) (toHexUpper (unsafeShiftR w 24))-  writeByteArray arr (off + 10) (toHexUpper (unsafeShiftR w 20))-  writeByteArray arr (off + 11) (toHexUpper (unsafeShiftR w 16))-  writeByteArray arr (off + 12) (toHexUpper (unsafeShiftR w 12))-  writeByteArray arr (off + 13) (toHexUpper (unsafeShiftR w 8))-  writeByteArray arr (off + 14) (toHexUpper (unsafeShiftR w 4))-  writeByteArray arr (off + 15) (toHexUpper (unsafeShiftR w 0))-  pure (off + 16)-  where-  w = W# w#--word32PaddedUpperHex# :: Word# -> Builder 8-{-# noinline word32PaddedUpperHex# #-}-word32PaddedUpperHex# w# = construct $ \arr off -> do-  writeByteArray arr off (toHexUpper (unsafeShiftR w 28))-  writeByteArray arr (off + 1) (toHexUpper (unsafeShiftR w 24))-  writeByteArray arr (off + 2) (toHexUpper (unsafeShiftR w 20))-  writeByteArray arr (off + 3) (toHexUpper (unsafeShiftR w 16))-  writeByteArray arr (off + 4) (toHexUpper (unsafeShiftR w 12))-  writeByteArray arr (off + 5) (toHexUpper (unsafeShiftR w 8))-  writeByteArray arr (off + 6) (toHexUpper (unsafeShiftR w 4))-  writeByteArray arr (off + 7) (toHexUpper (unsafeShiftR w 0))-  pure (off + 8)-  where-  w = W# w#---- Not sure if it is beneficial to inline this. We just let--- GHC make the decision. Open an issue on github if this is--- a problem.-word16PaddedUpperHex# :: Word# -> Builder 4-word16PaddedUpperHex# w# = construct $ \arr off -> do-  writeByteArray arr off (toHexUpper (unsafeShiftR w 12))-  writeByteArray arr (off + 1) (toHexUpper (unsafeShiftR w 8))-  writeByteArray arr (off + 2) (toHexUpper (unsafeShiftR w 4))-  writeByteArray arr (off + 3) (toHexUpper (unsafeShiftR w 0))-  pure (off + 4)-  where-  w = W# w#---- Definitely want this to inline. It's maybe a dozen instructions total.-word8PaddedUpperHex# :: Word# -> Builder 2-{-# inline word8PaddedUpperHex #-}-word8PaddedUpperHex# w# = construct $ \arr off -> do-  writeByteArray arr off (toHexUpper (unsafeShiftR w 4))-  writeByteArray arr (off + 1) (toHexUpper (unsafeShiftR w 0))-  pure (off + 2)-  where-  w = W# w#---- | Requires exactly 8 bytes. Dump the octets of a 64-bit--- word in a big-endian fashion.-word64BE :: Word64 -> Builder 8-word64BE w = construct $ \arr off -> do-  writeByteArray arr (off    ) (fromIntegral @Word64 @Word8 (unsafeShiftR w 56))-  writeByteArray arr (off + 1) (fromIntegral @Word64 @Word8 (unsafeShiftR w 48))-  writeByteArray arr (off + 2) (fromIntegral @Word64 @Word8 (unsafeShiftR w 40))-  writeByteArray arr (off + 3) (fromIntegral @Word64 @Word8 (unsafeShiftR w 32))-  writeByteArray arr (off + 4) (fromIntegral @Word64 @Word8 (unsafeShiftR w 24))-  writeByteArray arr (off + 5) (fromIntegral @Word64 @Word8 (unsafeShiftR w 16))-  writeByteArray arr (off + 6) (fromIntegral @Word64 @Word8 (unsafeShiftR w 8))-  writeByteArray arr (off + 7) (fromIntegral @Word64 @Word8 w)-  pure (off + 8)---- | Requires exactly 4 bytes. Dump the octets of a 32-bit--- word in a big-endian fashion.-word32BE :: Word32 -> Builder 4-word32BE w = construct $ \arr off -> do-  writeByteArray arr (off    ) (fromIntegral @Word32 @Word8 (unsafeShiftR w 24))-  writeByteArray arr (off + 1) (fromIntegral @Word32 @Word8 (unsafeShiftR w 16))-  writeByteArray arr (off + 2) (fromIntegral @Word32 @Word8 (unsafeShiftR w 8))-  writeByteArray arr (off + 3) (fromIntegral @Word32 @Word8 w)-  pure (off + 4)---- | Requires exactly 2 bytes. Dump the octets of a 16-bit--- word in a big-endian fashion.-word16BE :: Word16 -> Builder 2-word16BE w = construct $ \arr off -> do-  writeByteArray arr (off    ) (fromIntegral @Word16 @Word8 (unsafeShiftR w 8))-  writeByteArray arr (off + 1) (fromIntegral @Word16 @Word8 w)-  pure (off + 2)---- Reverse the bytes in the designated slice. This takes--- an inclusive start offset and an inclusive end offset.-reverseBytes :: MutableByteArray s -> Int -> Int -> ST s ()-{-# inline reverseBytes #-}-reverseBytes arr begin end = go begin end where-  go ixA ixB = if ixA < ixB-    then do-      a :: Word8 <- readByteArray arr ixA-      b :: Word8 <- readByteArray arr ixB-      writeByteArray arr ixA b-      writeByteArray arr ixB a-      go (ixA + 1) (ixB - 1)-    else pure ()--c2w :: Char -> Word8-c2w = fromIntegral . ord--unST :: ST s a -> State# s -> (# State# s, a #)-unST (ST f) = f--shrinkMutableByteArray :: MutableByteArray s -> Int -> ST s ()-shrinkMutableByteArray (MutableByteArray arr) (I# sz) =-  primitive_ (shrinkMutableByteArray# arr sz)
+ src/Data/ByteArray/Builder/Unsafe.hs view
@@ -0,0 +1,94 @@+{-# language BangPatterns #-}+{-# language DuplicateRecordFields #-}+{-# language LambdaCase #-}+{-# language MagicHash #-}+{-# language RankNTypes #-}+{-# language ScopedTypeVariables #-}+{-# language UnboxedTuples #-}++module Data.ByteArray.Builder.Unsafe+  ( -- * Types+    Builder(..)+    -- * Safe Functions+    -- | These functions are actually completely safe, but they are defined+    -- here because they are used by typeclass instances. Import them from+    -- @Data.ByteArray.Builder@ instead.+  , stringUtf8+  , cstring+  ) where++import Data.Primitive (MutableByteArray(MutableByteArray))+import Foreign.C.String (CString)+import GHC.Exts ((-#),(+#),(/=#),(>#))+import GHC.Exts (Addr#,Int(I#),Ptr(Ptr))+import GHC.Exts (IsString,Int#,State#,MutableByteArray#)+import GHC.ST (ST(ST))+import GHC.Base (unpackCString#,unpackCStringUtf8#)++import qualified GHC.Exts as Exts+import qualified Data.ByteArray.Builder.Bounded as Bounded+import qualified Data.ByteArray.Builder.Bounded.Unsafe as UnsafeBounded++-- | An unmaterialized sequence of bytes that may be pasted+-- into a mutable byte array.+newtype Builder = Builder+  -- This functions takes an offset and a number of remaining bytes+  -- and returns the new offset.+  (forall s. MutableByteArray# s -> Int# -> Int# -> State# s -> (# State# s, Int# #))++instance IsString Builder where+  {-# inline fromString #-}+  fromString = stringUtf8++instance Semigroup Builder where+  {-# inline (<>) #-}+  Builder f <> Builder g = Builder $ \arr off0 len0 s0 -> case f arr off0 len0 s0 of+    (# s1, r #) -> case r /=# (-1#) of+      1# -> g arr r (len0 +# (off0 -# r)) s1+      _ -> (# s1, (-1#) #)++instance Monoid Builder where+  {-# inline mempty #-}+  mempty = Builder $ \_ off0 _ s0 -> (# s0, off0 #)++-- | Create a builder from a cons-list of 'Char'. These+-- are be UTF-8 encoded.+stringUtf8 :: String -> Builder+{-# inline stringUtf8 #-}+stringUtf8 cs = Builder (\arr off0 len0 s0 -> goString cs arr off0 len0 s0)++-- | Create a builder from a @NUL@-terminated 'CString'. This ignores any+-- textual encoding, copying bytes until @NUL@ is reached.+cstring :: CString -> Builder+{-# inline cstring #-}+cstring (Ptr cs) = Builder (\arr off0 len0 s0 -> goCString cs arr off0 len0 s0)++goString :: String -> MutableByteArray# s -> Int# -> Int# -> State# s -> (# State# s, Int# #)+{-# noinline goString #-}+goString [] _ off0 _ s0 = (# s0, off0 #)+goString (c : cs) buf off0 len0 s0 = case len0 ># 3# of+  1# -> case unST (UnsafeBounded.pasteST (Bounded.char c) (MutableByteArray buf) (I# off0)) s0 of+    (# s1, I# off1 #) -> goString cs buf off1 (len0 -# (off1 -# off0)) s1+  _ -> (# s0, (-1#) #)++-- We have to have a rule for both unpackCString# and unpackCStringUtf8#+-- since GHC uses a different function based on whether or not non-ASCII+-- codepoints are used in the string.+{-# RULES+"Builder stringUtf8/cstring" forall s a b c d.+  goString (unpackCString# s) a b c d = goCString s a b c d+"Builder stringUtf8/cstring-utf8" forall s a b c d.+  goString (unpackCStringUtf8# s) a b c d = goCString s a b c d+#-}++goCString :: Addr# -> MutableByteArray# s -> Int# -> Int# -> State# s -> (# State# s, Int# #)+goCString addr buf off0 len0 s0 = case Exts.indexWord8OffAddr# addr 0# of+  0## -> (# s0, off0 #)+  w -> case len0 of+    0# -> (# s0, (-1#) #)+    _ -> case Exts.writeWord8Array# buf off0 w s0 of+      s1 -> goCString (Exts.plusAddr# addr 1# ) buf (off0 +# 1# ) (len0 -# 1# ) s1++unST :: ST s a -> State# s -> (# State# s, a #)+unST (ST f) = f+
test/Main.hs view
@@ -1,25 +1,31 @@ {-# language BangPatterns #-} {-# language ScopedTypeVariables #-} {-# language TypeApplications #-}+{-# language OverloadedStrings #-}  import Control.Monad.ST (runST) import Data.Bytes.Types (MutableBytes(..))-import Data.ByteArray.Builder.Small+import Data.ByteArray.Builder import Data.Word import Data.Char (ord) import Data.Primitive (ByteArray)-import Debug.Trace import Test.Tasty (defaultMain,testGroup,TestTree) import Test.QuickCheck ((===)) import Text.Printf (printf)+import Test.Tasty.HUnit ((@=?))++import qualified Arithmetic.Nat as Nat+import qualified Data.ByteString as ByteString import qualified Data.ByteString.Builder as BB-import qualified Data.Primitive as PM+import qualified Data.ByteString.Lazy.Char8 as LB import qualified Data.List as L+import qualified Data.Primitive as PM+import qualified Data.Text as T+import qualified Data.Text.Encoding as TE import qualified Data.Vector as V-import qualified Test.Tasty.QuickCheck as TQC-import qualified Test.QuickCheck as QC import qualified GHC.Exts as Exts-import qualified Data.ByteString.Lazy.Char8 as LB+import qualified Test.Tasty.HUnit as THU+import qualified Test.Tasty.QuickCheck as TQC  import qualified HexWord64 @@ -51,12 +57,45 @@         (runArray word64Dec (V.fromList xs))         ===         pack (foldMap show xs)+    , THU.testCase "stringUtf8" $+        packUtf8 "¿Cómo estás? I am doing well." @=?+          run 1 (stringUtf8 "¿Cómo estás? I am doing well.")+    , THU.testCase "doubleDec-A" $+        pack (show (2 :: Int)) @=? run 1 (doubleDec 2.0)+    , THU.testCase "doubleDec-B" $+        pack (show (2.5 :: Double)) @=? run 1 (doubleDec 2.5)+    , THU.testCase "doubleDec-C" $+        pack ("1e+15") @=? run 1 (doubleDec 1e15)+    , THU.testCase "doubleDec-D" $+        pack ("-42") @=? run 1 (doubleDec (-42))+    , THU.testCase "doubleDec-E" $+        pack ("-8.88888888888888e+14") @=? run 1 (doubleDec (-888888888888888.8888888))+    , THU.testCase "doubleDec-F" $+        pack ("42") @=? run 1 (doubleDec 42)+    , THU.testCase "doubleDec-G" $+        pack ("0") @=? run 1 (doubleDec 0)+    , THU.testCase "doubleDec-H" $+        pack ("0.5") @=? run 1 (doubleDec 0.5)+    , THU.testCase "doubleDec-I" $+        pack ("-0.5") @=? run 1 (doubleDec (-0.5))+    , THU.testCase "doubleDec-J" $+        pack ("999999999") @=? run 1 (doubleDec 999999999)+    , THU.testCase "doubleDec-K" $+        pack ("-99999999") @=? run 1 (doubleDec (-99999999))+    , THU.testCase "shortTextJsonString-A" $+        pack ("\"hello\"") @=? run 1 (shortTextJsonString "hello")+    , THU.testCase "shortTextJsonString-B" $+        pack ("\"\\\\_\\\"_/\"") @=? run 1 (shortTextJsonString "\\_\"_/")+    , THU.testCase "shortTextJsonString-C" $+        pack ("\"Hi\\r\\nLo\"") @=? run 1 (shortTextJsonString "Hi\r\nLo")+    , THU.testCase "shortTextJsonString-D" $+        pack ("\"Hi\\u001BLo\"") @=? run 1 (shortTextJsonString "Hi\ESCLo")     ]   , testGroup "alternate"     [ TQC.testProperty "HexWord64" $ \x y ->         run 1-          (  fromUnsafe (HexWord64.word64PaddedUpperHex x)-          <> fromUnsafe (HexWord64.word64PaddedUpperHex y)+          (  fromBounded Nat.constant (HexWord64.word64PaddedUpperHex x)+          <> fromBounded Nat.constant (HexWord64.word64PaddedUpperHex y)           )         ===         pack (showWord64PaddedUpperHex x <> showWord64PaddedUpperHex y)@@ -65,6 +104,9 @@  pack :: String -> ByteArray pack = Exts.fromList . map (fromIntegral @Int @Word8 . ord)++packUtf8 :: String -> ByteArray+packUtf8 = Exts.fromList . ByteString.unpack . TE.encodeUtf8 . T.pack  -- This is used to test pasteArrayST runArray ::