byteslice 0.2.3.0 → 0.2.4.0
raw patch · 8 files changed
+279/−6 lines, 8 filesdep ~bytestringPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: bytestring
API changes (from Hackage documentation)
+ Data.Bytes: emptyPinnedU :: ByteArray
+ Data.Bytes: equalsLatin10 :: Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Bytes -> Bool
+ Data.Bytes: equalsLatin11 :: Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Bytes -> Bool
+ Data.Bytes: equalsLatin12 :: Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Bytes -> Bool
+ Data.Bytes: equalsLatin9 :: Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Bytes -> Bool
+ Data.Bytes: fromByteString :: ByteString -> Bytes
+ Data.Bytes: fromShortByteString :: ShortByteString -> Bytes
+ Data.Bytes: intercalateByte2 :: Word8 -> Bytes -> Bytes -> Bytes
+ Data.Bytes: splitEnd1 :: Word8 -> Bytes -> Maybe (Bytes, Bytes)
+ Data.Bytes: toByteString :: Bytes -> ByteString
+ Data.Bytes: toLowerAsciiByteArrayClone :: Bytes -> ByteArray
+ Data.Bytes: toPinnedByteArray :: Bytes -> ByteArray
+ Data.Bytes: toPinnedByteArrayClone :: Bytes -> ByteArray
+ Data.Bytes: toShortByteString :: Bytes -> ShortByteString
+ Data.Bytes: toShortByteStringClone :: Bytes -> ShortByteString
+ Data.Bytes.Chunks: concatPinnedU :: Chunks -> ByteArray
Files
- CHANGELOG.md +10/−0
- byteslice.cabal +9/−2
- cbits/bs_custom.c +1/−1
- src/Data/Bytes.hs +184/−1
- src/Data/Bytes/Byte.hs +21/−0
- src/Data/Bytes/Chunks.hs +10/−0
- src/Data/Bytes/Pure.hs +28/−1
- test/Main.hs +16/−1
CHANGELOG.md view
@@ -1,5 +1,15 @@ # Revision history for byteslice +## 0.2.4.0 -- 2020-10-15++* Add `toByteString` and `fromByteString`.+* Add `fromShortByteString`.+* Add `equalsLatin(9|10|11|12)`.+* Add `toPinnedByteArray`, `toPinnedByteArrayClone`, and `concatPinnedU`.+* Add `toLowerAsciiByteArrayClone`.+* Add `intercalateByte2`.+* Add `splitEnd1`.+ ## 0.2.3.0 -- 2020-04-30 * Add `fnv1a32` and `fnv1a64`, implementations of the 32-bit and
byteslice.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: byteslice-version: 0.2.3.0+version: 0.2.4.0 synopsis: Slicing managed and unmanaged memory description: This library provides types that allow the user to talk about a slice of@@ -13,10 +13,14 @@ license-file: LICENSE author: Andrew Martin maintainer: andrew.thaddeus@gmail.com-copyright: 2019 Andrew Martin+copyright: 2020 Andrew Martin category: Data extra-source-files: CHANGELOG.md +flag avoid-rawmemchr+ default: True+ description: Avoid using rawmemchr which is non-portable GNU libc only+ library exposed-modules: Data.Bytes@@ -31,6 +35,7 @@ UnliftedBytes build-depends: , base >=4.11.1 && <5+ , bytestring >=0.10.8 && <0.11 , primitive >=0.7 && <0.8 , primitive-unlifted >=0.1.2 && <0.2 , primitive-addr >=0.1 && <0.2@@ -49,6 +54,8 @@ includes: bs_custom.h install-includes: bs_custom.h c-sources: cbits/bs_custom.c+ if flag(avoid-rawmemchr)+ cc-options: -DAVOID_RAWMEMCHR=1 test-suite test default-language: Haskell2010
cbits/bs_custom.c view
@@ -19,7 +19,7 @@ p = p + off; total = 0; for (szIx = 0; szIx < sizesLen; ++szIx) {-#ifdef __linux__+#if defined(__linux__) && !AVOID_RAWMEMCHR pos = (unsigned char*)(rawmemchr((void*)p,(int)w)); delta = (HsInt)(pos - p); #else
src/Data/Bytes.hs view
@@ -1,7 +1,9 @@ {-# language BangPatterns #-} {-# language BlockArguments #-}+{-# language DuplicateRecordFields #-} {-# language MagicHash #-} {-# language NamedFieldPuns #-}+{-# language RankNTypes #-} {-# language TypeApplications #-} {-# language UnboxedTuples #-} @@ -11,6 +13,7 @@ -- * Constants , Pure.empty , Pure.emptyPinned+ , Pure.emptyPinnedU -- * Properties , null , Pure.length@@ -46,18 +49,23 @@ -- * Common Folds , elem -- * Splitting+ -- ** Unlimited , Byte.split , Byte.splitU , Byte.splitInit , Byte.splitInitU , Byte.splitNonEmpty , Byte.splitStream+ -- ** Fixed from Beginning , Byte.split1 , Byte.split2 , Byte.split3 , Byte.split4+ -- ** Fixed from End+ , Byte.splitEnd1 -- * Combining , intercalate+ , intercalateByte2 -- * Counting , Byte.count -- * Prefix and Suffix@@ -84,6 +92,10 @@ , equalsLatin6 , equalsLatin7 , equalsLatin8+ , equalsLatin9+ , equalsLatin10+ , equalsLatin11+ , equalsLatin12 -- ** C Strings , equalsCString -- * Hashing@@ -102,11 +114,19 @@ -- * Conversion , Pure.toByteArray , Pure.toByteArrayClone+ , Pure.toPinnedByteArray+ , Pure.toPinnedByteArrayClone , fromAsciiString , fromLatinString , Pure.fromByteArray , toLatinString , fromCString#+ , toByteString+ , fromByteString+ , fromShortByteString+ , toShortByteString+ , toShortByteStringClone+ , toLowerAsciiByteArrayClone -- * I\/O with Handles , BIO.hGet , readFile@@ -116,7 +136,10 @@ import Prelude hiding (length,takeWhile,dropWhile,null,foldl,foldr,elem,replicate,any,all,readFile) import Control.Monad.Primitive (PrimMonad,primitive_,unsafeIOToPrim)+import Control.Monad.ST (ST) import Control.Monad.ST.Run (runByteArrayST)+import Data.ByteString (ByteString)+import Data.ByteString.Short.Internal (ShortByteString(SBS)) import Data.Bytes.Compat (cstringLength#) import Data.Bytes.Pure (length,fromByteArray) import Data.Bytes.Types (Bytes(Bytes,array,offset))@@ -126,17 +149,23 @@ import Foreign.Ptr (Ptr,plusPtr,castPtr) import GHC.Exts (Int(I#),Char(C#),Ptr(Ptr),word2Int#,chr#) import GHC.Exts (Addr#,Word#,Int#)+import GHC.IO (unsafeIOToST) import GHC.Word (Word8(W8#)) +import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Internal as ByteString+import qualified Data.ByteString.Unsafe as ByteString import qualified Data.Bytes.Byte as Byte import qualified Data.Bytes.Chunks as Chunks import qualified Data.Bytes.IO as BIO import qualified Data.Bytes.Pure as Pure+import qualified Data.Bytes.Types as Types import qualified Data.Foldable as F import qualified Data.List as List import qualified Data.Primitive as PM import qualified Data.Primitive.Ptr as PM import qualified GHC.Exts as Exts+import qualified GHC.ForeignPtr as ForeignPtr -- | Is the byte sequence empty? null :: Bytes -> Bool@@ -408,7 +437,7 @@ -- | Convert a 'String' consisting of only characters representable -- by ISO-8859-1. These are encoded with ISO-8859-1. Any character--- with a codepoint above @U+00FF@ is replace an unspecified byte.+-- with a codepoint above @U+00FF@ is replaced by an unspecified byte. fromLatinString :: String -> Bytes fromLatinString = fromByteArray . Exts.fromList . map (fromIntegral @Int @Word8 . ord)@@ -518,6 +547,72 @@ c7 == indexCharArray arr (off + 7) _ -> False +-- | Is the byte sequence, when interpreted as ISO-8859-1-encoded text,+-- a 9-tuple whose elements match the characters?+equalsLatin9 :: Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Bytes -> Bool+equalsLatin9 !c0 !c1 !c2 !c3 !c4 !c5 !c6 !c7 !c8 (Bytes arr off len) = case len of+ 9 -> c0 == indexCharArray arr off &&+ c1 == indexCharArray arr (off + 1) &&+ c2 == indexCharArray arr (off + 2) &&+ c3 == indexCharArray arr (off + 3) &&+ c4 == indexCharArray arr (off + 4) &&+ c5 == indexCharArray arr (off + 5) &&+ c6 == indexCharArray arr (off + 6) &&+ c7 == indexCharArray arr (off + 7) &&+ c8 == indexCharArray arr (off + 8)+ _ -> False++-- | Is the byte sequence, when interpreted as ISO-8859-1-encoded text,+-- a 10-tuple whose elements match the characters?+equalsLatin10 :: Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Bytes -> Bool+equalsLatin10 !c0 !c1 !c2 !c3 !c4 !c5 !c6 !c7 !c8 !c9 (Bytes arr off len) = case len of+ 10 -> c0 == indexCharArray arr off &&+ c1 == indexCharArray arr (off + 1) &&+ c2 == indexCharArray arr (off + 2) &&+ c3 == indexCharArray arr (off + 3) &&+ c4 == indexCharArray arr (off + 4) &&+ c5 == indexCharArray arr (off + 5) &&+ c6 == indexCharArray arr (off + 6) &&+ c7 == indexCharArray arr (off + 7) &&+ c8 == indexCharArray arr (off + 8) &&+ c9 == indexCharArray arr (off + 9)+ _ -> False++-- | Is the byte sequence, when interpreted as ISO-8859-1-encoded text,+-- a 11-tuple whose elements match the characters?+equalsLatin11 :: Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Bytes -> Bool+equalsLatin11 !c0 !c1 !c2 !c3 !c4 !c5 !c6 !c7 !c8 !c9 !c10 (Bytes arr off len) = case len of+ 11 -> c0 == indexCharArray arr off &&+ c1 == indexCharArray arr (off + 1) &&+ c2 == indexCharArray arr (off + 2) &&+ c3 == indexCharArray arr (off + 3) &&+ c4 == indexCharArray arr (off + 4) &&+ c5 == indexCharArray arr (off + 5) &&+ c6 == indexCharArray arr (off + 6) &&+ c7 == indexCharArray arr (off + 7) &&+ c8 == indexCharArray arr (off + 8) &&+ c9 == indexCharArray arr (off + 9) &&+ c10 == indexCharArray arr (off + 10)+ _ -> False++-- | Is the byte sequence, when interpreted as ISO-8859-1-encoded text,+-- a 12-tuple whose elements match the characters?+equalsLatin12 :: Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Bytes -> Bool+equalsLatin12 !c0 !c1 !c2 !c3 !c4 !c5 !c6 !c7 !c8 !c9 !c10 !c11 (Bytes arr off len) = case len of+ 12 -> c0 == indexCharArray arr off &&+ c1 == indexCharArray arr (off + 1) &&+ c2 == indexCharArray arr (off + 2) &&+ c3 == indexCharArray arr (off + 3) &&+ c4 == indexCharArray arr (off + 4) &&+ c5 == indexCharArray arr (off + 5) &&+ c6 == indexCharArray arr (off + 6) &&+ c7 == indexCharArray arr (off + 7) &&+ c8 == indexCharArray arr (off + 8) &&+ c9 == indexCharArray arr (off + 9) &&+ c10 == indexCharArray arr (off + 10) &&+ c11 == indexCharArray arr (off + 11)+ _ -> False+ -- | Is the byte sequence equal to the @NUL@-terminated C String? -- The C string must be a constant. equalsCString :: CString -> Bytes -> Bool@@ -579,6 +674,25 @@ ) len0 bs PM.unsafeFreezeByteArray marr +-- | Specialization of 'intercalate' where the separator is a single byte and+-- there are exactly two byte sequences that are being concatenated.+intercalateByte2 ::+ Word8 -- ^ Separator+ -> Bytes -- ^ First byte sequence+ -> Bytes -- ^ Second byte sequence+ -> Bytes+intercalateByte2 !sep !a !b = Bytes+ { Types.array = runByteArrayST $ do+ dst <- PM.newByteArray len+ Pure.unsafeCopy dst 0 a+ PM.writeByteArray dst (length a) sep+ Pure.unsafeCopy dst (length a + 1) b+ PM.unsafeFreezeByteArray dst+ , Types.length = len+ , Types.offset = 0+ }+ where len = length a + length b + 1+ -- | /O(n)/ Returns true if any byte in the sequence satisfies the predicate. any :: (Word8 -> Bool) -> Bytes -> Bool {-# inline any #-}@@ -588,3 +702,72 @@ all :: (Word8 -> Bool) -> Bytes -> Bool {-# inline all #-} all f = foldr (\b r -> f b && r) True++-- | /O(n)/ when unpinned, /O(1)/ when pinned. Create a 'ByteString' from+-- a byte sequence. This only copies the byte sequence if it is not pinned.+toByteString :: Bytes -> ByteString+toByteString !b = pinnedToByteString (Pure.pin b)++-- | Convert the sliced 'Bytes' to an unsliced 'ShortByteString'. This+-- reuses the array backing the sliced 'Bytes' if the slicing metadata+-- implies that all of the bytes are used. Otherwise, it makes a copy.+toShortByteString :: Bytes -> ShortByteString+toShortByteString !b = case Pure.toByteArray b of+ PM.ByteArray x -> SBS x++-- | Variant of 'toShortByteString' that unconditionally makes a copy of+-- the array backing the sliced 'Bytes' even if the original array+-- could be reused. Prefer 'toShortByteString'.+toShortByteStringClone :: Bytes -> ShortByteString+toShortByteStringClone !b = case Pure.toByteArrayClone b of+ PM.ByteArray x -> SBS x++-- | /O(1)/ Create 'Bytes' from a 'ShortByteString'.+fromShortByteString :: ShortByteString -> Bytes+fromShortByteString (SBS x) = fromByteArray (ByteArray x)++-- | /O(n)/ Interpreting the bytes an ASCII-encoded characters, convert+-- the string to lowercase. This adds @0x20@ to bytes in the range+-- @[0x41,0x5A]@ and leaves all other bytes alone. Unconditionally+-- copies the bytes.+toLowerAsciiByteArrayClone :: Bytes -> ByteArray+toLowerAsciiByteArrayClone (Bytes src off0 len0) =+ runByteArrayST action+ where+ action :: forall s. ST s ByteArray+ action = do+ dst <- PM.newByteArray len0+ let go !off !ix !len = if len == 0+ then pure ()+ else do+ let w = PM.indexByteArray src off :: Word8+ w' = if w >= 0x41 && w <= 0x5A+ then w + 32+ else w+ PM.writeByteArray dst ix w'+ go (off + 1) (ix + 1) (len - 1)+ go off0 0 len0+ PM.unsafeFreezeByteArray dst+++-- | /O(n)/ Copy a 'ByteString' to a byte sequence.+fromByteString :: ByteString -> Bytes+fromByteString !b = Bytes+ ( runByteArrayST $ unsafeIOToST $ do + dst@(PM.MutableByteArray dst# ) <- PM.newByteArray len+ ByteString.unsafeUseAsCString b $ \src -> do+ PM.copyPtrToMutablePrimArray (PM.MutablePrimArray dst# ) 0 src len+ PM.unsafeFreezeByteArray dst+ ) 0 len+ where+ !len = ByteString.length b++-- Precondition: bytes are pinned+pinnedToByteString :: Bytes -> ByteString+pinnedToByteString (Bytes y@(PM.ByteArray x) off len) =+ ByteString.PS+ (ForeignPtr.ForeignPtr+ (case plusPtr (PM.byteArrayContents y) off of {Exts.Ptr p -> p})+ (ForeignPtr.PlainPtr (Exts.unsafeCoerce# x))+ )+ 0 len
src/Data/Bytes/Byte.hs view
@@ -22,6 +22,7 @@ , split2 , split3 , split4+ , splitEnd1 ) where import Prelude hiding (length)@@ -279,3 +280,23 @@ then off# else elemIndexLoop# w (Bytes arr (off + 1) (len - 1)) +-- Variant of elemIndexLoop# that starts at the end. Similarly, returns+-- negative one if the element is not found.+elemIndexLoopBackwards# :: Word8 -> ByteArray -> Int -> Int -> Int#+elemIndexLoopBackwards# !w !arr !start !pos@(I# pos#) = if pos < start+ then (-1#)+ else if PM.indexByteArray arr pos == w+ then pos#+ else elemIndexLoopBackwards# w arr start (pos - 1)++-- | Split a byte sequence on the last occurrence of the target+-- byte. The target is removed from the result. For example:+--+-- >>> split1 0xA [0x1,0x2,0xA,0xB,0xA,0xC]+-- Just ([0x1,0x2,0xA,0xB],[0xC])+splitEnd1 :: Word8 -> Bytes -> Maybe (Bytes,Bytes)+{-# inline splitEnd1 #-}+splitEnd1 !w (Bytes arr off len) = case elemIndexLoopBackwards# w arr off (off + len - 1) of+ (-1#) -> Nothing+ i# -> let i = I# i# in+ Just (Bytes arr off (i - off), Bytes arr (i + 1) (len - (1 + i - off)))
src/Data/Bytes/Chunks.hs view
@@ -24,6 +24,7 @@ , concat , concatPinned , concatU+ , concatPinnedU , reverse , reverseOnto -- * Folds@@ -120,6 +121,15 @@ ChunksCons b y -> case y of ChunksNil -> Bytes.toByteArray b ChunksCons c z -> case concatFollowing2 b c z of+ (# _, r #) -> ByteArray r++-- | Variant of 'concatPinned' that returns an unsliced pinned byte sequence.+concatPinnedU :: Chunks -> ByteArray+concatPinnedU x = case x of+ ChunksNil -> Bytes.emptyPinnedU+ ChunksCons b y -> case y of+ ChunksNil -> Bytes.toPinnedByteArray b+ ChunksCons c z -> case concatPinnedFollowing2 b c z of (# _, r #) -> ByteArray r concatFollowing2 :: Bytes -> Bytes -> Chunks -> (# Int#, ByteArray# #)
src/Data/Bytes/Pure.hs view
@@ -8,11 +8,14 @@ module Data.Bytes.Pure ( empty , emptyPinned+ , emptyPinnedU , pin , contents , unsafeCopy , toByteArray , toByteArrayClone+ , toPinnedByteArray+ , toPinnedByteArrayClone , fromByteArray , length , foldl'@@ -36,13 +39,18 @@ empty :: Bytes empty = Bytes mempty 0 0 --- | The empty byte sequence.+-- | The empty pinned byte sequence. emptyPinned :: Bytes emptyPinned = Bytes ( runByteArrayST (PM.newPinnedByteArray 0 >>= PM.unsafeFreezeByteArray) ) 0 0 +-- | The empty pinned byte sequence.+emptyPinnedU :: ByteArray+emptyPinnedU = runByteArrayST+ (PM.newPinnedByteArray 0 >>= PM.unsafeFreezeByteArray)+ -- | Yields a pinned byte sequence whose contents are identical to those -- of the original byte sequence. If the @ByteArray@ backing the argument -- was already pinned, this simply aliases the argument and does not perform@@ -118,3 +126,22 @@ -- to call this on a 'Bytes' backed by a pinned @ByteArray@. contents :: Bytes -> Ptr Word8 contents (Bytes arr off _) = plusPtr (PM.byteArrayContents arr) off++-- | Convert the sliced 'Bytes' to an unsliced 'ByteArray'. This+-- reuses the array backing the sliced 'Bytes' if the slicing metadata+-- implies that all of the bytes are used and they are already pinned.+-- Otherwise, it makes a copy.+toPinnedByteArray :: Bytes -> ByteArray+toPinnedByteArray b@(Bytes arr off len)+ | off == 0, PM.sizeofByteArray arr == len, PM.isByteArrayPinned arr = arr+ | otherwise = toPinnedByteArrayClone b++-- | Variant of 'toPinnedByteArray' that unconditionally makes a copy of+-- the array backing the sliced 'Bytes' even if the original array+-- could be reused. Prefer 'toPinnedByteArray'.+toPinnedByteArrayClone :: Bytes -> ByteArray+toPinnedByteArrayClone (Bytes arr off len) = runByteArrayST $ do+ m <- PM.newPinnedByteArray len+ PM.copyByteArray m 0 arr off len+ PM.unsafeFreezeByteArray m+
test/Main.hs view
@@ -38,9 +38,15 @@ , lawsToTest (QCC.ordLaws (Proxy :: Proxy Bytes)) , lawsToTest (QCC.semigroupLaws (Proxy :: Proxy Bytes)) , lawsToTest (QCC.monoidLaws (Proxy :: Proxy Bytes))- , testGroup "isPrefixOf"+ , testGroup "toLowerAsciiByteArrayClone" [ testCase "A" $ THU.assertBool "" $ Bytes.isPrefixOf (bytes "hey") (bytes "hey man")+ ]+ , testGroup "isPrefixOf"+ [ testCase "A" $+ Bytes.toLowerAsciiByteArrayClone (bytes "FooBar")+ @=?+ pack "foobar" , testCase "B" $ THU.assertBool "" $ not (Bytes.isPrefixOf (bytes "an") (bytes "hey man")) ]@@ -148,6 +154,14 @@ [Bytes.fromAsciiString "hello", Bytes.fromAsciiString "world"] @=? (Bytes.splitInit 0x0A (Bytes.fromAsciiString "hello\nworld\nthere"))+ , testProperty "splitEnd1" $ \(x :: Word8) (xs :: [Word8]) ->+ case ByteString.split x (ByteString.pack xs) of+ [] -> Bytes.splitEnd1 x (slicedPack xs) === Nothing+ [_] -> Bytes.splitEnd1 x (slicedPack xs) === Nothing+ [y1,z1] -> case Bytes.splitEnd1 x (slicedPack xs) of+ Nothing -> property False+ Just (y2,z2) -> (y1,z1) === (bytesToByteString y2, bytesToByteString z2)+ _ -> property Discard , testProperty "split1" $ \(x :: Word8) (xs :: [Word8]) -> case ByteString.split x (ByteString.pack xs) of [] -> Bytes.split1 x (slicedPack xs) === Nothing@@ -184,6 +198,7 @@ [ testCase "empty" (Bytes.fnv1a64 Bytes.empty @=? 0xcbf29ce484222325) , testCase "a" (Bytes.fnv1a64 (bytes "a") @=? 0xaf63dc4c8601ec8c) , testCase "foobar" (Bytes.fnv1a64 (bytes "foobar") @=? 0x85944171f73967e8)+ , testCase "google.com" (Bytes.fnv1a64 (bytes "google.com") @=? 0xe1a2c1ae38dcdf45) ] ] , testGroup "Chunks"