small-bytearray-builder 0.2.0.0 → 0.2.1.0
raw patch · 7 files changed
+225/−10 lines, 7 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
- Data.ByteArray.Builder: Builder :: (forall s. MutableByteArray# s -> Int# -> Int# -> State# s -> (# State# s, Int# #)) -> Builder
- Data.ByteArray.Builder: newtype Builder
+ Data.ByteArray.Builder: data Builder
+ Data.ByteArray.Builder: word16LowerHex :: Word16 -> Builder
+ Data.ByteArray.Builder: word16PaddedLowerHex :: Word16 -> Builder
+ Data.ByteArray.Builder: word16UpperHex :: Word16 -> Builder
+ Data.ByteArray.Builder: word8LowerHex :: Word8 -> Builder
+ Data.ByteArray.Builder.Bounded: word16LowerHex :: Word16 -> Builder 4
+ Data.ByteArray.Builder.Bounded: word16PaddedLowerHex :: Word16 -> Builder 4
+ Data.ByteArray.Builder.Bounded: word16UpperHex :: Word16 -> Builder 4
+ Data.ByteArray.Builder.Bounded: word8LowerHex :: Word8 -> Builder 2
Files
- CHANGELOG.md +10/−0
- small-bytearray-builder.cabal +1/−1
- src/Data/ByteArray/Builder.hs +38/−1
- src/Data/ByteArray/Builder/Bounded.hs +163/−3
- src/Data/ByteArray/Builder/Bounded/Unsafe.hs +4/−1
- src/Data/ByteArray/Builder/Unsafe.hs +5/−4
- test/Main.hs +4/−0
CHANGELOG.md view
@@ -1,5 +1,15 @@ # Revision history for small-bytearray-builder +## 0.2.1.0 -- 2019-09-05++* Stop exporting data constructor in `Data.ByteArray.Builder`.+ This is technically a breaking change, but it was only+ exported by accident. So, with this release, we will technically+ violate PVP, and the previous release will be deprecated on hackage.+* Add more functions for encoding unsigned words: `word16PaddedLowerHex`,+ `word16LowerHex`, `word16UpperHex`, `word8LowerHex`.+* Unroll loop for `word8Dec`.+ ## 0.2.0.0 -- 2019-09-04 * Use `natural-arithmetic` to make manipulation of bounds possible.
small-bytearray-builder.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: small-bytearray-builder-version: 0.2.0.0+version: 0.2.1.0 synopsis: Serialize to a small byte arrays description: This is similar to the builder facilities provided by
src/Data/ByteArray/Builder.hs view
@@ -8,7 +8,7 @@ module Data.ByteArray.Builder ( -- * Bounded Primitives- Builder(..)+ Builder , construct , fromBounded -- * Evaluation@@ -38,10 +38,19 @@ , int16Dec , int8Dec , intDec+ -- * Unsigned Words+ -- ** 64-bit , word64PaddedUpperHex+ -- ** 32-bit , word32PaddedUpperHex+ -- ** 16-bit , word16PaddedUpperHex+ , word16PaddedLowerHex+ , word16LowerHex+ , word16UpperHex+ -- ** 8-bit , word8PaddedUpperHex+ , word8LowerHex , ascii , char -- ** Machine-Readable@@ -351,6 +360,34 @@ word16PaddedUpperHex :: Word16 -> Builder word16PaddedUpperHex w = fromBounded Nat.constant (Bounded.word16PaddedUpperHex w)++-- | Encode a 16-bit unsigned integer as hexadecimal, zero-padding+-- the encoding to 4 digits. This uses lowercase for the alphabetical+-- digits. For example, this encodes the number 1022 as @03fe@.+word16PaddedLowerHex :: Word16 -> Builder+word16PaddedLowerHex w =+ fromBounded Nat.constant (Bounded.word16PaddedLowerHex w)++-- | Encode a 16-bit unsigned integer as hexadecimal without leading+-- zeroes. This uses lowercase for the alphabetical digits. For+-- example, this encodes the number 1022 as @3fe@.+word16LowerHex :: Word16 -> Builder+word16LowerHex w =+ fromBounded Nat.constant (Bounded.word16LowerHex w)++-- | Encode a 16-bit unsigned integer as hexadecimal without leading+-- zeroes. This uses uppercase for the alphabetical digits. For+-- example, this encodes the number 1022 as @3FE@.+word16UpperHex :: Word16 -> Builder+word16UpperHex w =+ fromBounded Nat.constant (Bounded.word16UpperHex w)++-- | Encode a 16-bit unsigned integer as hexadecimal without leading+-- zeroes. This uses lowercase for the alphabetical digits. For+-- example, this encodes the number 1022 as @3FE@.+word8LowerHex :: Word8 -> Builder+word8LowerHex w =+ fromBounded Nat.constant (Bounded.word8LowerHex w) -- | Encode a 8-bit unsigned integer as hexadecimal, zero-padding -- the encoding to 2 digits. This uses uppercase for the alphabetical
src/Data/ByteArray/Builder/Bounded.hs view
@@ -10,7 +10,8 @@ {-# language TypeOperators #-} {-# language UnboxedTuples #-} --- | The functions in this module are explict in the amount of bytes they require.+-- | The functions in this module are explict about the maximum number+-- of bytes they require. module Data.ByteArray.Builder.Bounded ( -- * Builder Builder@@ -35,10 +36,19 @@ , int16Dec , int8Dec , intDec+ -- * Unsigned Words+ -- ** 64-bit , word64PaddedUpperHex+ -- ** 32-bit , word32PaddedUpperHex+ -- ** 16-bit+ , word16PaddedLowerHex , word16PaddedUpperHex+ , word16LowerHex+ , word16UpperHex+ -- ** 8-bit , word8PaddedUpperHex+ , word8LowerHex , ascii , char -- ** Machine-Readable@@ -67,6 +77,7 @@ import qualified Arithmetic.Types as Arithmetic import qualified Arithmetic.Nat as Nat+import qualified Arithmetic.Lte as Lte import qualified Data.ByteArray.Builder.Bounded.Unsafe as Unsafe import qualified Data.Primitive as PM @@ -161,7 +172,14 @@ -- | 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+word8Dec (W8# w) =+ -- We unroll the loop when encoding Word8s. This speeds things+ -- up IPv4 encoding by about 10% in the @ip@ library. We can+ -- encode Word8s at twice this speed by using a lookup table.+ -- However, I (Andrew Martin) am concerned that although lookup+ -- table perform very well in microbenchmarks, they can thrash+ -- L1 cache in real applications.+ word8Dec# w -- | Requires up to 19 bytes. Encodes an unsigned machine-sized integer -- as decimal. This encoding never starts with a zero unless the argument@@ -204,6 +222,22 @@ intDec :: Int -> Builder 20 intDec (I# w) = intCommonDec# w +word8Dec# :: Word# -> Builder 3+{-# noinline word8Dec# #-}+word8Dec# w# = Unsafe.construct $ \arr off0 -> do+ let !(I# off0# ) = off0+ !(!x,!ones) = quotRem w 10+ !(hundreds@(W# hundreds# ),tens@(W# tens# )) = quotRem x 10+ writeByteArray arr off0 (fromIntegral (hundreds + 0x30) :: Word8)+ let !hasHundreds = gtWord# hundreds# 0##+ !off1@(I# off1# ) = I# (off0# +# hasHundreds)+ writeByteArray arr off1 (fromIntegral (tens + 0x30) :: Word8)+ let !off2 = I# (off1# +# (orI# hasHundreds (gtWord# tens# 0## )))+ writeByteArray arr off2 (fromIntegral (ones + 0x30) :: Word8)+ pure (off2 + 1)+ where+ w = W# w#+ -- Requires a number of bytes that is bounded by the size of -- the word. This is only used internally. wordCommonDec# :: Word# -> Builder n@@ -259,6 +293,17 @@ loSolved = w + 48 hiSolved = w + 55 +toHexLower :: Word -> Word8+toHexLower 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 + 87+ -- | 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@@ -275,9 +320,45 @@ -- | 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 0xab0+-- 0AB0 word16PaddedUpperHex :: Word16 -> Builder 4 word16PaddedUpperHex (W16# w) = word16PaddedUpperHex# w +-- | Requires exactly 4 bytes. Encodes a 16-bit unsigned integer as+-- hexadecimal, zero-padding the encoding to 4 digits. This uses+-- lowercase for the alphabetical digits.+--+-- >>> word16PaddedLowerHex 0xab0+-- 0ab0+word16PaddedLowerHex :: Word16 -> Builder 4+word16PaddedLowerHex (W16# w) = word16PaddedLowerHex# w++-- | Requires at most 4 bytes. Encodes a 16-bit unsigned integer as+-- hexadecimal. No leading zeroes are displayed. Letters are presented+-- in lowercase. If the number is zero, a single zero digit is used.+--+-- >>> word16LowerHex 0xab0+-- ab0+word16LowerHex :: Word16 -> Builder 4+word16LowerHex (W16# w) = word16LowerHex# w++-- | Requires at most 4 bytes. Encodes a 16-bit unsigned integer as+-- hexadecimal. No leading zeroes are displayed. Letters are presented+-- in uppercase. If the number is zero, a single zero digit is used.+--+-- >>> word16UpperHex 0xab0+-- AB0+word16UpperHex :: Word16 -> Builder 4+word16UpperHex (W16# w) = word16UpperHex# w++-- | Requires at most 2 bytes. Encodes a 8-bit unsigned integer as+-- hexadecimal. No leading zeroes are displayed. If the number is zero,+-- a single zero digit is used.+word8LowerHex :: Word8 -> Builder 2+word8LowerHex (W8# w) = word8LowerHex# 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.@@ -337,13 +418,92 @@ where w = W# w# +word16PaddedLowerHex# :: Word# -> Builder 4+word16PaddedLowerHex# w# = Unsafe.construct $ \arr off -> do+ writeByteArray arr off (toHexLower (unsafeShiftR w 12))+ writeByteArray arr (off + 1) (toHexLower (unsafeShiftR w 8))+ writeByteArray arr (off + 2) (toHexLower (unsafeShiftR w 4))+ writeByteArray arr (off + 3) (toHexLower (unsafeShiftR w 0))+ pure (off + 4)+ where+ w = W# w#++word12PaddedLowerHex# :: Word# -> Builder 3+word12PaddedLowerHex# w# = Unsafe.construct $ \arr off -> do+ writeByteArray arr off (toHexLower (unsafeShiftR w 8))+ writeByteArray arr (off + 1) (toHexLower (unsafeShiftR w 4))+ writeByteArray arr (off + 2) (toHexLower (unsafeShiftR w 0))+ pure (off + 3)+ where+ w = W# w#++word12PaddedUpperHex# :: Word# -> Builder 3+word12PaddedUpperHex# w# = Unsafe.construct $ \arr off -> do+ writeByteArray arr off (toHexUpper (unsafeShiftR w 8))+ writeByteArray arr (off + 1) (toHexUpper (unsafeShiftR w 4))+ writeByteArray arr (off + 2) (toHexUpper (unsafeShiftR w 0))+ pure (off + 3)+ where+ w = W# w#+ -- Definitely want this to inline. It's maybe a dozen instructions total. word8PaddedUpperHex# :: Word# -> Builder 2-{-# inline word8PaddedUpperHex #-}+{-# 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#++word8PaddedLowerHex# :: Word# -> Builder 2+{-# inline word8PaddedLowerHex# #-}+word8PaddedLowerHex# w# = Unsafe.construct $ \arr off -> do+ writeByteArray arr off (toHexLower (unsafeShiftR w 4))+ writeByteArray arr (off + 1) (toHexLower (unsafeShiftR w 0))+ pure (off + 2)+ where+ w = W# w#++word4PaddedLowerHex# :: Word# -> Builder 1+{-# inline word4PaddedLowerHex# #-}+word4PaddedLowerHex# w# = Unsafe.construct $ \arr off -> do+ writeByteArray arr off (toHexLower w)+ pure (off + 1)+ where+ w = W# w#++word4PaddedUpperHex# :: Word# -> Builder 1+{-# inline word4PaddedUpperHex# #-}+word4PaddedUpperHex# w# = Unsafe.construct $ \arr off -> do+ writeByteArray arr off (toHexUpper w)+ pure (off + 1)+ where+ w = W# w#++word16UpperHex# :: Word# -> Builder 4+word16UpperHex# w#+ | w <= 0xF = weaken Lte.constant (word4PaddedUpperHex# w#)+ | w <= 0xFF = weaken Lte.constant (word8PaddedUpperHex# w#)+ | w <= 0xFFF = weaken Lte.constant (word12PaddedUpperHex# w#)+ | otherwise = word16PaddedUpperHex# w#+ where+ w = W# w#++word16LowerHex# :: Word# -> Builder 4+word16LowerHex# w#+ | w <= 0xF = weaken Lte.constant (word4PaddedLowerHex# w#)+ | w <= 0xFF = weaken Lte.constant (word8PaddedLowerHex# w#)+ | w <= 0xFFF = weaken Lte.constant (word12PaddedLowerHex# w#)+ | otherwise = word16PaddedLowerHex# w#+ where+ w = W# w#++-- Precondition: argument less than 256+word8LowerHex# :: Word# -> Builder 2+word8LowerHex# w#+ | w <= 0xF = weaken Lte.constant (word4PaddedLowerHex# w#)+ | otherwise = weaken Lte.constant (word8PaddedLowerHex# w#) where w = W# w#
src/Data/ByteArray/Builder/Bounded/Unsafe.hs view
@@ -27,8 +27,11 @@ -- when executed. newtype Builder :: Nat -> Type where Builder ::- (forall s. MutableByteArray# s -> Int# -> State# s -> (# State# s, Int# #))+ (forall s. MutableByteArray# s -> Int# -> State# s -> (# State# s, Int# #)) + -- ^ This function takes a buffer, an offset, and a number of remaining bytes.+ -- It returns the new offset. -> Builder n+ -- | Constructor for 'Builder' that works on a function with lifted -- arguments instead of unlifted ones. This is just as unsafe as the
src/Data/ByteArray/Builder/Unsafe.hs view
@@ -31,10 +31,11 @@ -- | 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# #))+newtype Builder+ = Builder (forall s. MutableByteArray# s -> Int# -> Int# -> State# s -> (# State# s, Int# #))+ -- ^ This function takes a buffer, an offset, and a number of remaining bytes.+ -- It returns the new offset (should be greater than the old offset), or if+ -- there was not enough space left in buffer, it returns -1. instance IsString Builder where {-# inline fromString #-}
test/Main.hs view
@@ -53,6 +53,10 @@ run 1 (word64PaddedUpperHex w) === pack (showWord64PaddedUpperHex w)+ , TQC.testProperty "word8Dec" $ \w ->+ run 1 (word8Dec w)+ ===+ pack (show w) , TQC.testProperty "pasteArrayST" $ \(xs :: [Word64]) -> (runArray word64Dec (V.fromList xs)) ===