packages feed

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 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))