byteslice 0.2.1.0 → 0.2.2.0
raw patch · 7 files changed
+222/−7 lines, 7 filesdep +gaugedep ~basedep ~primitivePVP ok
version bump matches the API change (PVP)
Dependencies added: gauge
Dependency ranges changed: base, primitive
API changes (from Hackage documentation)
+ Data.Bytes: all :: (Word8 -> Bool) -> Bytes -> Bool
+ Data.Bytes: any :: (Word8 -> Bool) -> Bytes -> Bool
+ Data.Bytes: emptyPinned :: Bytes
+ Data.Bytes: equalsCString :: CString -> Bytes -> Bool
+ Data.Bytes: equalsLatin8 :: Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Bytes -> Bool
+ Data.Bytes: intercalate :: Bytes -> [Bytes] -> Bytes
+ Data.Bytes: split4 :: Word8 -> Bytes -> Maybe (Bytes, Bytes, Bytes, Bytes, Bytes)
+ Data.Bytes: stripCStringPrefix :: CString -> Bytes -> Maybe Bytes
+ Data.Bytes.Chunks: concatPinned :: Chunks -> Bytes
Files
- CHANGELOG.md +10/−0
- bench/Main.hs +17/−0
- byteslice.cabal +13/−1
- src/Data/Bytes.hs +129/−2
- src/Data/Bytes/Chunks.hs +26/−2
- src/Data/Bytes/Types.hs +17/−2
- test/Main.hs +10/−0
CHANGELOG.md view
@@ -1,5 +1,15 @@ # Revision history for byteslice +## 0.2.2.0 -- 2020-02-27++* Add `split4`.+* Add `equalsCString`.+* Add `stripCStringPrefix`.+* Add `equalsLatin8`.+* Add `emptyPinned`.+* Add `concatPinned` to `Data.Bytes.Chunks`.+* Add `any` and `all`.+ ## 0.2.1.0 -- 2020-01-22 * Add `longestCommonPrefix`.
+ bench/Main.hs view
@@ -0,0 +1,17 @@+import Gauge.Main (defaultMain, bench, whnf)++import Data.Bytes.Types+import Data.List (permutations)+import qualified Data.Bytes as Bytes++naiveMconcat :: [Bytes] -> Bytes+naiveMconcat = foldr mappend mempty++main :: IO ()+main = defaultMain+ [ bench "mconcat" $ whnf mconcat mconcatBytes+ , bench "naiveMconcat" $ whnf naiveMconcat mconcatBytes+ ]++mconcatBytes :: [Bytes]+mconcatBytes = fmap Bytes.fromAsciiString $ permutations ['a'..'g']
byteslice.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: byteslice-version: 0.2.1.0+version: 0.2.2.0 synopsis: Slicing managed and unmanaged memory description: This library provides types that allow the user to talk about a slice of@@ -54,3 +54,15 @@ , tasty , tasty-hunit , tasty-quickcheck++benchmark bench+ type: exitcode-stdio-1.0+ build-depends:+ , base+ , byteslice+ , gauge+ , primitive+ ghc-options: -Wall -O2+ default-language: Haskell2010+ hs-source-dirs: bench+ main-is: Main.hs
src/Data/Bytes.hs view
@@ -10,12 +10,16 @@ Bytes -- * Constants , empty+ , emptyPinned -- * Properties , null , length -- * Decompose , uncons , unsnoc+ -- * Predicates+ , any+ , all -- * Create -- ** Sliced , singleton@@ -50,6 +54,9 @@ , split1 , split2 , split3+ , split4+ -- * Combining+ , intercalate -- * Counting , Byte.count -- * Prefix and Suffix@@ -61,10 +68,13 @@ , stripSuffix , stripOptionalSuffix , longestCommonPrefix+ -- ** C Strings+ , stripCStringPrefix -- ** Single Byte , isBytePrefixOf , isByteSuffixOf -- * Equality+ -- ** Fixed Characters , equalsLatin1 , equalsLatin2 , equalsLatin3@@ -72,6 +82,9 @@ , equalsLatin5 , equalsLatin6 , equalsLatin7+ , equalsLatin8+ -- ** C Strings+ , equalsCString -- * Unsafe Slicing , unsafeTake , unsafeDrop@@ -94,14 +107,15 @@ , hPut ) where -import Prelude hiding (length,takeWhile,dropWhile,null,foldl,foldr,elem,replicate)+import Prelude hiding (length,takeWhile,dropWhile,null,foldl,foldr,elem,replicate,any,all) import Control.Monad.Primitive (PrimMonad,PrimState,primitive_,unsafeIOToPrim) import Control.Monad.ST.Run (runByteArrayST) import Data.Bytes.Types (Bytes(Bytes,array,offset)) import Data.Char (ord) import Data.Primitive (ByteArray(ByteArray),MutableByteArray)-import Foreign.Ptr (Ptr,plusPtr)+import Foreign.C.String (CString)+import Foreign.Ptr (Ptr,plusPtr,castPtr) import GHC.Exts (Int(I#),Char(C#),word2Int#,chr#) import GHC.Exts (Word#,Int#) import GHC.IO (IO(IO))@@ -109,7 +123,10 @@ import System.IO (Handle) import qualified Data.Bytes.Byte as Byte+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 System.IO as IO @@ -317,6 +334,33 @@ , Bytes arr (k + 1) (len - (1 + k - off)) ) +-- | Split a byte sequence on the first, second, third, and fourth+-- occurrences of the target byte. The target is removed from the result.+-- For example:+--+-- >>> split4 0xA [0x1,0x2,0xA,0xB,0xA,0xA,0xA]+-- Just ([0x1,0x2],[0xB],[],[],[])+split4 :: Word8 -> Bytes -> Maybe (Bytes,Bytes,Bytes,Bytes,Bytes)+{-# inline split4 #-}+split4 w b@(Bytes arr off len) = case elemIndexLoop# w b of+ (-1#) -> Nothing+ i# -> let i = I# i# in+ case elemIndexLoop# w (Bytes arr (i + 1) (len - (1 + i - off))) of+ (-1#) -> Nothing+ j# -> let j = I# j# in+ case elemIndexLoop# w (Bytes arr (j + 1) (len - (1 + j - off))) of+ (-1#) -> Nothing+ k# -> let k = I# k# in+ case elemIndexLoop# w (Bytes arr (k + 1) (len - (1 + k - off))) of+ (-1#) -> Nothing+ m# -> let m = I# m# in Just+ ( Bytes arr off (i - off)+ , Bytes arr (i + 1) (j - (i + 1))+ , Bytes arr (j + 1) (k - (j + 1))+ , Bytes arr (k + 1) (m - (k + 1))+ , Bytes arr (m + 1) (len - (1 + m - off))+ )+ -- This returns the offset into the byte array. This is not an index -- that will mean anything to the end user, so it cannot be returned -- to them.@@ -565,6 +609,44 @@ c6 == indexCharArray arr (off + 6) _ -> False +-- | Is the byte sequence, when interpreted as ISO-8859-1-encoded text,+-- an octupleton whose elements match the characters?+equalsLatin8 :: Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Bytes -> Bool+equalsLatin8 !c0 !c1 !c2 !c3 !c4 !c5 !c6 !c7 (Bytes arr off len) = case len of+ 8 -> 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)+ _ -> False++-- | Is the byte sequence equal to the @NUL@-terminated C String?+-- The C string must be a constant.+equalsCString :: CString -> Bytes -> Bool+{-# inline equalsCString #-}+equalsCString !ptr0 (Bytes arr off0 len0) = go (castPtr ptr0 :: Ptr Word8) off0 len0 where+ go !ptr !off !len = case len of+ 0 -> PM.indexOffPtr ptr 0 == (0 :: Word8)+ _ -> case PM.indexOffPtr ptr 0 of+ 0 -> False+ c -> c == PM.indexByteArray arr off && go (plusPtr ptr 1) (off + 1) (len - 1)++-- | /O(n)/ Variant of 'stripPrefix' that takes a @NUL@-terminated C String+-- as the prefix to test for.+stripCStringPrefix :: CString -> Bytes -> Maybe Bytes+{-# inline stripCStringPrefix #-}+stripCStringPrefix !ptr0 (Bytes arr off0 len0) = go (castPtr ptr0 :: Ptr Word8) off0 len0 where+ go !ptr !off !len = case PM.indexOffPtr ptr 0 of+ 0 -> Just (Bytes arr off len)+ c -> case len of+ 0 -> Nothing+ _ -> case c == PM.indexByteArray arr off of+ True -> go (plusPtr ptr 1) (off + 1) (len - 1)+ False -> Nothing+ -- | Copy the byte sequence into a mutable buffer. The buffer must have -- enough space to accomodate the byte sequence, but this this is not -- checked.@@ -610,6 +692,13 @@ empty :: Bytes empty = Bytes mempty 0 0 +-- | The empty byte sequence.+emptyPinned :: Bytes+emptyPinned = Bytes+ ( runByteArrayST+ (PM.newPinnedByteArray 0 >>= PM.unsafeFreezeByteArray)+ ) 0 0+ -- | Read 'Bytes' directly from the specified 'Handle'. The resulting -- 'Bytes' are pinned. This is implemented with 'hGetBuf'. hGet :: Handle -> Int -> IO Bytes@@ -629,6 +718,7 @@ createPinnedAndTrim maxSz f = do arr@(PM.MutableByteArray arr#) <- PM.newPinnedByteArray maxSz sz <- f (PM.mutableByteArrayContents arr)+ touchMutableByteArrayIO arr PM.shrinkMutablePrimArray (PM.MutablePrimArray @Exts.RealWorld @Word8 arr#) sz r <- PM.unsafeFreezeByteArray arr pure (Bytes r 0 sz)@@ -636,3 +726,40 @@ touchByteArrayIO :: ByteArray -> IO () touchByteArrayIO (ByteArray x) = IO (\s -> (# Exts.touch# x s, () #))++touchMutableByteArrayIO :: MutableByteArray s -> IO ()+touchMutableByteArrayIO (PM.MutableByteArray x) =+ IO (\s -> (# Exts.touch# x s, () #))++-- | /O(n)/ The intercalate function takes a separator 'Bytes' and a list of+-- 'Bytes' and concatenates the list elements by interspersing the separator+-- between each element.+intercalate ::+ Bytes -- ^ Separator (interspersed into the list)+ -> [Bytes] -- ^ List+ -> Bytes+intercalate !_ [] = mempty+intercalate !_ [x] = x+intercalate (Bytes sarr soff slen) (Bytes arr0 off0 len0 : bs) = Bytes r 0 fullLen+ where+ !fullLen = List.foldl' (\acc (Bytes _ _ len) -> acc + len + slen) 0 bs + len0+ r = runByteArrayST $ do+ marr <- PM.newByteArray fullLen+ PM.copyByteArray marr 0 arr0 off0 len0+ !_ <- F.foldlM+ (\ !currLen (Bytes arr off len) -> do+ PM.copyByteArray marr currLen sarr soff slen+ PM.copyByteArray marr (currLen + slen) arr off len+ pure (currLen + len + slen)+ ) len0 bs+ PM.unsafeFreezeByteArray marr++-- | /O(n)/ Returns true if any byte in the sequence satisfies the predicate.+any :: (Word8 -> Bool) -> Bytes -> Bool+{-# inline any #-}+any f = foldr (\b r -> f b || r) False++-- | /O(n)/ Returns true if all bytes in the sequence satisfy the predicate.+all :: (Word8 -> Bool) -> Bytes -> Bool+{-# inline all #-}+all f = foldr (\b r -> f b && r) True
src/Data/Bytes/Chunks.hs view
@@ -5,6 +5,7 @@ {-# language MagicHash #-} {-# language UnboxedTuples #-} {-# language NamedFieldPuns #-}+{-# language RankNTypes #-} -- | Chunks of bytes. This is useful as a target for a builder -- or as a way to read a large amount of whose size is unknown@@ -19,6 +20,7 @@ , length -- * Manipulate , concat+ , concatPinned , concatU , reverse , reverseOnto@@ -67,6 +69,16 @@ -- it is tedious. a == b = concat a == concat b +-- | Variant of 'concat' that ensure that the resulting byte+-- sequence is pinned memory.+concatPinned :: Chunks -> Bytes+concatPinned x = case x of+ ChunksNil -> Bytes.emptyPinned+ ChunksCons b y -> case y of+ ChunksNil -> Bytes.pin b+ ChunksCons c z -> case concatPinnedFollowing2 b c z of+ (# len, r #) -> Bytes (ByteArray r) 0 (I# len)+ -- | Concatenate chunks into a single contiguous byte sequence. concat :: Chunks -> Bytes concat x = case x of@@ -86,13 +98,25 @@ (# _, r #) -> ByteArray r concatFollowing2 :: Bytes -> Bytes -> Chunks -> (# Int#, ByteArray# #)-concatFollowing2+concatFollowing2 = internalConcatFollowing2 PM.newByteArray++concatPinnedFollowing2 :: Bytes -> Bytes -> Chunks -> (# Int#, ByteArray# #)+concatPinnedFollowing2 = internalConcatFollowing2 PM.newPinnedByteArray++internalConcatFollowing2 ::+ (forall s. Int -> ST s (MutableByteArray s))+ -> Bytes+ -> Bytes+ -> Chunks+ -> (# Int#, ByteArray# #)+{-# inline internalConcatFollowing2 #-}+internalConcatFollowing2 allocate (Bytes{array=c,offset=coff,length=szc}) (Bytes{array=d,offset=doff,length=szd}) ds = let !(I# x, ByteArray y) = runIntByteArrayST $ do let !szboth = szc + szd !len = chunksLengthGo szboth ds- dst <- PM.newByteArray len+ dst <- allocate len PM.copyByteArray dst 0 c coff szc PM.copyByteArray dst szc d doff szd -- Note: len2 will always be the same as len.
src/Data/Bytes/Types.hs view
@@ -10,16 +10,18 @@ ) where import Control.Monad.ST (runST)-import Data.Primitive (ByteArray(..),MutableByteArray(..))-import Data.Primitive.Addr (Addr)+import Control.Monad.ST.Run (runByteArrayST) import Data.Bits ((.&.),unsafeShiftR) import Data.Char (ord)+import Data.Primitive (ByteArray(..),MutableByteArray(..))+import Data.Primitive.Addr (Addr) import Data.Word (Word8) import GHC.Base (unsafeChr) import GHC.Exts (Int(I#),unsafeCoerce#,sameMutableByteArray#) import GHC.Exts (isTrue#,compareByteArrays#,IsList(..)) import qualified Data.List as L+import qualified Data.Foldable as F import qualified Data.Primitive as PM -- | A slice of a 'ByteArray'.@@ -98,6 +100,19 @@ instance Monoid Bytes where mempty = Bytes mempty 0 0+ mconcat [] = mempty+ mconcat [x] = x+ mconcat bs = Bytes r 0 fullLen+ where+ !fullLen = L.foldl' (\acc (Bytes _ _ len) -> acc + len) 0 bs+ r = runByteArrayST $ do+ marr <- PM.newByteArray fullLen+ !_ <- F.foldlM+ (\ !currLen (Bytes arr off len) -> do+ PM.copyByteArray marr currLen arr off len+ pure (currLen + len)+ ) 0 bs+ PM.unsafeFreezeByteArray marr compareByteArrays :: ByteArray -> Int -> ByteArray -> Int -> Int -> Ordering {-# INLINE compareByteArrays #-}
test/Main.hs view
@@ -120,6 +120,10 @@ ByteString.split x (ByteString.pack xs) === map bytesToByteString (Bytes.split x (slicedPack xs))+ , testProperty "intercalate" $ \(x :: Bytes) (xs :: [Bytes]) ->+ mconcat (List.intersperse x xs)+ ===+ Bytes.intercalate x xs , testProperty "splitNonEmpty" $ \(x :: Word8) (xs :: [Word8]) -> Bytes.split x (slicedPack xs) ===@@ -163,6 +167,12 @@ ==> case Bytes.split3 0xEF (slicedPack (ws ++ [0xEF] ++ xs ++ [0xEF] ++ ys ++ [0xEF] ++ zs)) of Just r -> r === (slicedPack ws, slicedPack xs, slicedPack ys, slicedPack zs)+ Nothing -> property False+ , testProperty "split4" $ \(ws :: [Word8]) (xs :: [Word8]) (ys :: [Word8]) (zs :: [Word8]) (ps :: [Word8]) ->+ (all (/=0xEF) ws && all (/=0xEF) xs && all (/=0xEF) ys && all (/=0xEF) zs && all (/=0xEF) ps)+ ==>+ case Bytes.split4 0xEF (slicedPack (ws ++ [0xEF] ++ xs ++ [0xEF] ++ ys ++ [0xEF] ++ zs ++ [0xEF] ++ ps)) of+ Just r -> r === (slicedPack ws, slicedPack xs, slicedPack ys, slicedPack zs, slicedPack ps) Nothing -> property False , testGroup "Chunks" [ lawsToTest (QCC.eqLaws (Proxy :: Proxy Chunks))