{-# LANGUAGE BangPatterns, OverloadedStrings #-} -- | -- Module : LazyByteString -- Copyright : (c) 2010 Simon Meier -- License : BSD3-style (see LICENSE) -- -- Maintainer : Leon P Smith -- Stability : experimental -- Portability : tested on GHC only -- -- Benchmarking of alternative implementations of functions in -- Data.ByteString.Lazy that construct lazy bytestrings and cannot be -- implemented with slicing only. module LazyByteString where -- (main) where import Data.Char import Data.Word import Data.Monoid import Data.List import Control.Monad import Control.Arrow (second) import Criterion.Main import Foreign import qualified Data.ByteString as S import qualified Data.ByteString.Unsafe as S import qualified Data.ByteString.Internal as S import qualified Data.ByteString.Lazy as L import qualified Data.ByteString.Lazy.Internal as L import Data.ByteString.Base64 import Blaze.ByteString.Builder.Internal import Blaze.ByteString.Builder.Word import Blaze.ByteString.Builder.ByteString ------------------------------------------------------------------------------ -- Benchmarks ------------------------------------------------------------------------------ main :: IO () main = do let (chunkInfos, benchmarks) = unzip {- [ lazyVsBlaze ( "partitionLazy" , (uncurry mappend) . L.partition ((0 <) . sin . fromIntegral) , (uncurry mappend) . partitionLazy ((0 <) . sin . fromIntegral) , (\i -> L.drop 13 $ L.pack $ take i $ cycle [0..]) , n) -} {- [ lazyVsBlaze ( "base64mime" , L.fromChunks . return . joinWith "\r\n" 76 . encode , toLazyByteString . encodeBase64MIME , (\i -> S.drop 13 $ S.pack $ take i $ cycle [0..]) , n) -} {- [ lazyVsBlaze ( "joinWith" , L.fromChunks . return . joinWith "\r\n" 76 , toLazyByteString . intersperseBlocks 76 "\r\n" , (\i -> S.drop 13 $ S.pack $ take i $ cycle [0..]) , n) -} [ lazyVsBlaze ( "base64" , L.fromChunks . return . encode , toLazyByteString . encodeBase64 , (\i -> S.drop 13 $ S.pack $ take i $ cycle [0..]) , n) {- , lazyVsBlaze ( "copy" , L.copy , copyBlaze , (\i -> L.drop 13 $ L.take (fromIntegral i) $ L.fromChunks $ repeat $ S.pack [0..]) , n) , lazyVsBlaze ( "filter ((==0) . (`mod` 3))" , L.filter ((==0) . (`mod` 3)) , filterBlaze ((==0) . (`mod` 3)) , (\i -> L.drop 13 $ L.pack $ take i $ cycle [0..]) , n) , lazyVsBlaze ( "map (+1)" , L.map (+1) , mapBlaze (+1) , (\i -> L.drop 13 $ L.pack $ take i $ cycle [0..]) , n) , lazyVsBlaze ( "concatMap (replicate 10)" , L.concatMap (L.replicate 10) , toLazyByteString . concatMapBuilder (fromReplicateWord8 10) , (\i -> L.pack $ take i $ cycle [0..]) , n `div` 10 ) , lazyVsBlaze ( "unfoldr countToZero" , L.unfoldr countToZero , unfoldrBlaze countToZero , id , n ) -} ] sequence_ (intersperse (putStrLn "") chunkInfos) putStrLn "" defaultMain benchmarks where n :: Int n = 100000 lazyVsBlaze :: (String, a -> L.ByteString, a -> L.ByteString, Int -> a, Int) -> (IO (), Benchmark) lazyVsBlaze (cmpName, lazy, blaze, prep, n) = ( do putStrLn $ cmpName ++ ": " ++ checkResults showChunksize implLazy lazy showChunksize implBlaze blaze , bgroup cmpName [ mkBench implBlaze blaze , mkBench implLazy lazy ] ) where implLazy = "bytestring" implBlaze = "blaze-builder" x = prep n nInfo = "for n = " ++ show n checkResults | lazy x == blaze x = "implementations agree " ++ nInfo | otherwise = unlines [ "ERROR: IMPLEMENTATIONS DISAGREE " ++ nInfo , implLazy ++ ": " ++ show (lazy x) , implBlaze ++ ": " ++ show (blaze x) ] showChunksize implName impl = do let bs = impl x cs = map S.length $ L.toChunks bs putStrLn $ " " ++ implName ++ ": " putStrLn $ " chunks sizes: " ++ show cs putStrLn $ " avg. chunk size: " ++ show ((fromIntegral (sum cs) :: Double) / fromIntegral (length cs)) mkBench implName impl = bench implName $ whnf (L.length . impl) x ------------------------------------------------------------------------------ -- Alternative implementations ------------------------------------------------------------------------------ -- Unfolding ------------ {- -- | /O(n)/ The 'unfoldr' function is analogous to the List \'unfoldr\'. -- 'unfoldr' builds a ByteString from a seed value. The function takes -- the element and returns 'Nothing' if it is done producing the -- ByteString or returns 'Just' @(a,b)@, in which case, @a@ is a -- prepending to the ByteString and @b@ is used as the next element in a -- recursive call. unfoldr :: (a -> Maybe (Word8, a)) -> a -> ByteString unfoldr f s0 = unfoldChunk 32 s0 where unfoldChunk n s = case S.unfoldrN n f s of (c, Nothing) | S.null c -> Empty | otherwise -> Chunk c Empty (c, Just s') -> Chunk c (unfoldChunk (n*2) s') -} countToZero :: Int -> Maybe (Word8, Int) countToZero 0 = Nothing countToZero i = Just (fromIntegral i, i - 1) unfoldrBlaze :: (a -> Maybe (Word8, a)) -> a -> L.ByteString unfoldrBlaze f x = toLazyByteString $ fromWriteUnfoldr writeWord8 f x fromWriteUnfoldr :: (b -> Write) -> (a -> Maybe (b, a)) -> a -> Builder fromWriteUnfoldr write = makeBuilder where makeBuilder f x0 = fromBuildStepCont $ step x0 where step x1 !k = fill x1 where fill x !(BufRange pf0 pe0) = go (f x) pf0 where go !Nothing !pf = do let !br' = BufRange pf pe0 k br' go !(Just (y, x')) !pf | pf `plusPtr` bound <= pe0 = do !pf' <- runWrite (write y) pf go (f x') pf' | otherwise = return $ bufferFull bound pf $ \(BufRange pfNew peNew) -> do !pfNew' <- runWrite (write y) pfNew fill x' (BufRange pfNew' peNew) where bound = getBound $ write y {-# INLINE fromWriteUnfoldr #-} -- Filtering and mapping ------------------------ test :: Int -> (L.ByteString, L.ByteString) test i = ((L.filter ((==0) . (`mod` 3)) $ x) , (filterBlaze ((==0) . (`mod` 3)) $ x)) where x = L.pack $ take i $ cycle [0..] filterBlaze :: (Word8 -> Bool) -> L.ByteString -> L.ByteString filterBlaze f = toLazyByteString . filterLazyByteString f {-# INLINE filterBlaze #-} mapBlaze :: (Word8 -> Word8) -> L.ByteString -> L.ByteString mapBlaze f = toLazyByteString . mapLazyByteString f {-# INLINE mapBlaze #-} filterByteString :: (Word8 -> Bool) -> S.ByteString -> Builder filterByteString p = mapFilterMapByteString id p id {-# INLINE filterByteString #-} filterLazyByteString :: (Word8 -> Bool) -> L.ByteString -> Builder filterLazyByteString p = mapFilterMapLazyByteString id p id {-# INLINE filterLazyByteString #-} mapByteString :: (Word8 -> Word8) -> S.ByteString -> Builder mapByteString f = mapFilterMapByteString f (const True) id {-# INLINE mapByteString #-} mapLazyByteString :: (Word8 -> Word8) -> L.ByteString -> Builder mapLazyByteString f = mapFilterMapLazyByteString f (const True) id {-# INLINE mapLazyByteString #-} mapFilterMapByteString :: (Word8 -> Word8) -> (Word8 -> Bool) -> (Word8 -> Word8) -> S.ByteString -> Builder mapFilterMapByteString f p g = \bs -> fromBuildStepCont $ step bs where step (S.PS ifp ioff isize) !k = goBS (unsafeForeignPtrToPtr ifp `plusPtr` ioff) where !ipe = unsafeForeignPtrToPtr ifp `plusPtr` (ioff + isize) goBS !ip0 !br@(BufRange op0 ope) | ip0 >= ipe = do touchForeignPtr ifp -- input buffer consumed k br | op0 < ope = goPartial (ip0 `plusPtr` min outRemaining inpRemaining) | otherwise = return $ bufferFull 1 op0 (goBS ip0) where outRemaining = ope `minusPtr` op0 inpRemaining = ipe `minusPtr` ip0 goPartial !ipeTmp = go ip0 op0 where go !ip !op | ip < ipeTmp = do w <- peek ip let w' = g w if p w' then poke op (f w') >> go (ip `plusPtr` 1) (op `plusPtr` 1) else go (ip `plusPtr` 1) op | otherwise = goBS ip (BufRange op ope) {-# INLINE mapFilterMapByteString #-} mapFilterMapLazyByteString :: (Word8 -> Word8) -> (Word8 -> Bool) -> (Word8 -> Word8) -> L.ByteString -> Builder mapFilterMapLazyByteString f p g = L.foldrChunks (\c b -> mapFilterMapByteString f p g c `mappend` b) mempty {-# INLINE mapFilterMapLazyByteString #-} -- Concatenation and replication -------------------------------- {- -- | Map a function over a 'ByteString' and concatenate the results concatMap :: (Word8 -> ByteString) -> ByteString -> ByteString concatMap _ Empty = Empty concatMap f (Chunk c0 cs0) = to c0 cs0 where go :: ByteString -> P.ByteString -> ByteString -> ByteString go Empty c' cs' = to c' cs' go (Chunk c cs) c' cs' = Chunk c (go cs c' cs') to :: P.ByteString -> ByteString -> ByteString to c cs | S.null c = case cs of Empty -> Empty (Chunk c' cs') -> to c' cs' | otherwise = go (f (S.unsafeHead c)) (S.unsafeTail c) cs -} fromWriteReplicated :: (a -> Write) -> Int -> a -> Builder fromWriteReplicated write = makeBuilder where makeBuilder !n0 x = fromBuildStepCont $ step where bound = getBound $ write x step !k = fill n0 where fill !n1 !(BufRange pf0 pe0) = go n1 pf0 where go 0 !pf = do let !br' = BufRange pf pe0 k br' go n !pf | pf `plusPtr` bound <= pe0 = do pf' <- runWrite (write x) pf go (n-1) pf' | otherwise = return $ bufferFull bound pf $ \(BufRange pfNew peNew) -> do pfNew' <- runWrite (write x) pfNew fill (n-1) (BufRange pfNew' peNew) {-# INLINE fromWriteReplicated #-} -- FIXME: Output repeated bytestrings for large replications. fromReplicateWord8 :: Int -> Word8 -> Builder fromReplicateWord8 !n0 x = fromBuildStepCont $ step where step !k = fill n0 where fill !n !br@(BufRange pf pe) | n <= 0 = k br | pf' <= pe = do _ <- S.memset pf x (fromIntegral n) -- FIXME: This conversion looses information for 64 bit systems. let !br' = BufRange pf' pe k br' | otherwise = do let !l = pe `minusPtr` pf _ <- S.memset pf x (fromIntegral l) -- FIXME: This conversion looses information for 64 bit systems. return $ bufferFull 1 pe $ fill (n - l) where pf' = pf `plusPtr` n {-# INLINE fromReplicateWord8 #-} {-# RULES "fromWriteReplicated/writeWord8" fromWriteReplicated writeWord8 = fromReplicateWord8 #-} concatMapBuilder :: (Word8 -> Builder) -> L.ByteString -> Builder concatMapBuilder f = L.foldr (\w b -> f w `mappend` b) mempty {-# INLINE concatMapBuilder #-} concatMapBlaze :: (Word8 -> L.ByteString) -> L.ByteString -> L.ByteString concatMapBlaze f = toLazyByteString . concatMapBuilder (fromLazyByteString . f) -- Interspersing ---------------- -- -- not sure if it Builder version is needed, as chunks get only bigger. We -- would need it however, if we used a Builder to ensure latency guarantees; i.e., -- if we use a builder to ensure a bound on the maximal size of chunks. -- {- -- | The 'intersperse' function takes a 'Word8' and a 'ByteString' and -- \`intersperses\' that byte between the elements of the 'ByteString'. -- It is analogous to the intersperse function on Lists. intersperse :: Word8 -> ByteString -> ByteString intersperse _ Empty = Empty intersperse w (Chunk c cs) = Chunk (S.intersperse w c) (foldrChunks (Chunk . intersperse') Empty cs) where intersperse' :: P.ByteString -> P.ByteString intersperse' (S.PS fp o l) = S.unsafeCreate (2*l) $ \p' -> withForeignPtr fp $ \p -> do poke p' w S.c_intersperse (p' `plusPtr` 1) (p `plusPtr` o) (fromIntegral l) w -} {- intersperseBlaze :: Word8 -- ^ Byte to intersperse. -> L.ByteString -- ^ Lazy 'L.ByteString' to be "spread". -> Builder -- ^ Resulting 'Builder'. intersperseBlaze w lbs0 = Builder $ step lbs0 where step lbs1 k = goChunk lbs1 where goChunk L.Empty pf0 pe0 = k pf0 pe0 goChunk (L.Chunk (S.PS fpi oi li) lbs') pf0 pe0 = do go touch where go where !pf' = pf `plusPtr` goChunk !L.Empty !pf = k pf pe0 goChunk !lbs@(L.Chunk bs' lbs') !pf | pf' <= pe0 = do withForeignPtr fpbuf $ \pbuf -> copyBytes pf (pbuf `plusPtr` offset) size go lbs' pf' | otherwise = return $ BufferFull size pf (step lbs k) where !pf' = pf `plusPtr` !(fpbuf, offset, size) = S.toForeignPtr bs' {-# INLINE intersperseBlaze #-} -} -- Packing ---------- packBlaze :: [Word8] -> L.ByteString packBlaze = toLazyByteString . fromWriteList writeWord8 -- Reverse ---------- -- Transpose ------------ -- scanl, scanl1, scanr, scanr1 ------------------------------- -- mapAccumL, mapAccumR ----------------------- -- partition ------------ -- unzip -------- -- copy ------- copyBlaze :: L.ByteString -> L.ByteString copyBlaze = toLazyByteString . copyLazyByteString -- ?? packCString, packCStringLen --------------------------------- -- joinWith -------------------------------------------- intersperseBlocks :: Int -> S.ByteString -> S.ByteString -> Builder intersperseBlocks blockSize sep (S.PS ifp ioff isize) = fromPut $ do lastBS <- go (ip0 `plusPtr` ioff) unless (S.null lastBS) (putBuilder $ fromByteString lastBS) where ip0 = unsafeForeignPtrToPtr ifp ipe = ip0 `plusPtr` (ioff + isize) go !ip | ip `plusPtr` blockSize >= ipe = return $ S.PS ifp (ip `minusPtr` ip0) (ipe `minusPtr` ip) | otherwise = do putBuilder $ fromByteString (S.PS ifp (ip `minusPtr` ip0) blockSize) `mappend` fromByteString sep go (ip `plusPtr` blockSize) intersperseLazyBlocks :: Int -> Builder -> L.ByteString -> Builder intersperseLazyBlocks blockSize sep bs = go (splitLazyAt blockSize bs) where go (pre, suf) | L.null suf = fromLazyByteString pre | otherwise = fromLazyByteString pre `mappend` sep `mappend` go (splitLazyAt blockSize suf) encodeBase64MIME :: S.ByteString -> Builder encodeBase64MIME = intersperseLazyBlocks 76 (fromByteString "\r\n") . toLazyByteString . encodeBase64 -- test blockwise mapping on base64 encoding -------------------------------------------- -- | Encode a bytestring using Base64 encoding according to the specification -- in RFC 4648, . -- -- Note that you need to insert additional linebreaks every 76 bytes using the -- function @joinWith "\r\n" 76@ in order to achieve the MIME Base64 -- Content-Transfer-Encoding . -- -- TODO implement encoding of lazy bytestrings, implement joinWith -- functionality, and convencience function for MIME base-64 encoding. encodeBase64 :: S.ByteString -> Builder encodeBase64 = encodeLazyBase64 . L.fromChunks . return encodeLazyBase64 :: L.ByteString -> Builder encodeLazyBase64 = mkBuilder where mkBuilder bs = fromPut $ do remainder <- putWriteLazyBlocks 3 writeBase64 bs putBuilder $ complete remainder {-# INLINE writeBase64 #-} writeBase64 ip = exactWrite 4 $ \op -> do b0 <- peekByte 0 b1 <- peekByte 1 b2 <- peekByte 2 let w = (b0 `shiftL` 16) .|. (b1 `shiftL` 8) .|. b2 poke (castPtr $ op ) =<< enc (w `shiftR` 12) poke (castPtr $ op `plusPtr` 2) =<< enc (w .&. 0xfff) where peekByte :: Int -> IO Word32 peekByte off = fmap fromIntegral (peekByteOff ip off :: IO Word8) enc = peekElemOff (unsafeForeignPtrToPtr encodeTable) . fromIntegral {-# INLINE complete #-} complete bs | S.null bs = mempty | otherwise = fromWrite $ exactWrite 4 $ \op -> do let poke6Base64 off sh = pokeByteOff op off (alphabet `S.unsafeIndex` fromIntegral (w `shiftR` sh .&. 63)) pad off = pokeByteOff op off (fromIntegral $ ord '=' :: Word8) poke6Base64 0 18 poke6Base64 1 12 if S.length bs == 1 then pad 2 else poke6Base64 2 8 pad 3 where getByte :: Int -> Int -> Word32 getByte i sh = fromIntegral (bs `S.unsafeIndex` i) `shiftL` sh w = getByte 0 16 .|. (if S.length bs == 1 then 0 else getByte 1 8) -- Lookup table trick from Data.ByteString.Base64 by Bryan O'Sullivan {-# NOINLINE alphabet #-} alphabet :: S.ByteString alphabet = S.pack $ [65..90] ++ [97..122] ++ [48..57] ++ [43,47] -- FIXME: Check that the implementation of the lookup table aslo works on -- big-endian systems. {-# NOINLINE encodeTable #-} encodeTable :: ForeignPtr Word16 encodeTable = unsafePerformIO $ do fp <- mallocForeignPtrArray 4096 let ix = fromIntegral . S.index alphabet withForeignPtr fp $ \p -> sequence_ [ pokeElemOff p (j*64+k) ((ix k `shiftL` 8) .|. ix j) | j <- [0..63], k <- [0..63] ] return fp -- | Process a bytestring block-wise using a 'Write' action to produce the -- output per block. -- -- TODO: Compare speed with 'mapFilterMapByteString'. {-# INLINE putWriteBlocks #-} putWriteBlocks :: Int -- ^ Block size. -> (Ptr Word8 -> Write) -- ^ 'Write' given a pointer to the -- beginning of the block. -> S.ByteString -- ^ 'S.ByteString' to consume blockwise. -> Put S.ByteString -- ^ 'Put' returning the remaining -- bytes, which are guaranteed to be -- fewer than the block size. putWriteBlocks blockSize write = \bs -> putBuildStepCont $ step bs where step (S.PS ifp ioff isize) !k = goBS (unsafeForeignPtrToPtr ifp `plusPtr` ioff) where !ipe = unsafeForeignPtrToPtr ifp `plusPtr` (ioff + isize) goBS !ip0 !br@(BufRange op0 ope) | ip0 `plusPtr` blockSize > ipe = do touchForeignPtr ifp -- input buffer consumed let !bs' = S.PS ifp (ip0 `minusPtr` unsafeForeignPtrToPtr ifp) (ipe `minusPtr` ip0) k bs' br | op0 `plusPtr` writeBound < ope = goPartial (ip0 `plusPtr` (blockSize * min outRemaining inpRemaining)) | otherwise = return $ bufferFull writeBound op0 (goBS ip0) where writeBound = getBound' "putWriteBlocks" write outRemaining = (ope `minusPtr` op0) `div` writeBound inpRemaining = (ipe `minusPtr` ip0) `div` blockSize goPartial !ipeTmp = go ip0 op0 where go !ip !op | ip < ipeTmp = do op' <- runWrite (write ip) op go (ip `plusPtr` blockSize) op' | otherwise = goBS ip (BufRange op ope) {-# INLINE putWriteLazyBlocks #-} putWriteLazyBlocks :: Int -- ^ Block size. -> (Ptr Word8 -> Write) -- ^ 'Write' given a pointer to the -- beginning of the block. -> L.ByteString -- ^ 'L.ByteString' to consume blockwise. -> Put S.ByteString -- ^ 'Put' returning the remaining -- bytes, which are guaranteed to be -- fewer than the block size. putWriteLazyBlocks blockSize write = go where go L.Empty = return S.empty go (L.Chunk bs lbs) = do bsRem <- putWriteBlocks blockSize write bs case S.length bsRem of lRem | lRem <= 0 -> go lbs | otherwise -> do let (lbsPre, lbsSuf) = L.splitAt (fromIntegral $ blockSize - lRem) lbs case S.concat $ bsRem : L.toChunks lbsPre of block@(S.PS bfp boff bsize) | bsize < blockSize -> return block | otherwise -> do putBuilder $ fromWrite $ write (unsafeForeignPtrToPtr bfp `plusPtr` boff) putLiftIO $ touchForeignPtr bfp go lbsSuf ------------------------------------------------------------------------------ -- Testing code ------------------------------------------------------------------------------ chunks3 :: [Word8] -> [Word32] chunks3 (b0 : b1 : b2 : bs) = ((fromIntegral b0 `shiftL` 16) .|. (fromIntegral b1 `shiftL` 8) .|. (fromIntegral b2 ) ) : chunks3 bs chunks3 _ = [] cmpWriteToLib :: [Word8] -> (L.ByteString, L.ByteString) cmpWriteToLib bs = -- ( toLazyByteString $ fromWriteList write24bitsBase64 $ chunks3 bs ( toLazyByteString $ encodeBase64 $ S.pack bs , (`L.Chunk` L.empty) $ encode $ S.pack bs ) test3 :: Bool test3 = uncurry (==) $ cmpWriteToLib $ [0..] test2 :: L.ByteString test2 = toLazyByteString $ encodeBase64 $ S.pack [0..] {- OLD code {-# INLINE poke8 #-} poke8 :: Word8 -> Ptr Word8 -> IO () poke8 = flip poke -- | @writeBase64 w@ writes the lower @24@ bits as four times 6 bit in -- little-endian order encoded using the standard alphabeth of Base 64 encoding -- as defined in . -- {-# INLINE write6bitsBase64 #-} write6bitsBase64 :: Word32 -> Write write6bitsBase64 = exactWrite 1 . poke6bitsBase64 {-# INLINE poke6bitsBase64 #-} poke6bitsBase64 :: Word32 -> Ptr Word8 -> IO () poke6bitsBase64 w = poke8 (alphabet `S.unsafeIndex` fromIntegral (w .&. 63)) {- | i < 26 = withOffsets 0 'A' | i < 52 = withOffsets 26 'a' | i < 62 = withOffsets 52 '0' | i == 62 = poke8 $ fromIntegral $ ord '+' | otherwise = poke8 $ fromIntegral $ ord '/' where i :: Int i = fromIntegral (w .&. 63) {-# INLINE withOffsets #-} withOffsets neg pos = poke8 $ fromIntegral (i + ord pos - neg) -} {-# INLINE writePaddedBitsBase64 #-} writePaddedBitsBase64 :: Bool -- ^ Only 8 bits have to be output. -> Word32 -- ^ Input whose lower 8 or 16 bits need to be output. -> Write writePaddedBitsBase64 only8 w = write6bitsBase64 (w `shiftr_w32` 18) `mappend` write6bitsBase64 (w `shiftr_w32` 12) `mappend` writeIf (const only8) (const $ C8.writeChar '=') (write6bitsBase64 . (`shiftr_w32` 6)) w `mappend` C8.writeChar '=' {-# INLINE write24bitsBase64 #-} write24bitsBase64 :: Word32 -> Write write24bitsBase64 w = write6bitsBase64 (w `shiftr_w32` 18) `mappend` write6bitsBase64 (w `shiftr_w32` 12) `mappend` write6bitsBase64 (w `shiftr_w32` 6) `mappend` write6bitsBase64 (w ) -- ASSUMES bits 25 - 31 are zero. {-# INLINE write24bitsBase64' #-} write24bitsBase64' :: Word32 -> Write write24bitsBase64' w = exactWrite 4 $ \p -> do poke (castPtr p ) =<< enc (w `shiftR` 12) poke (castPtr $ p `plusPtr` 2) =<< enc (w .&. 0xfff) where {-# INLINE enc #-} enc = peekElemOff (unsafeForeignPtrToPtr encodeTable) . fromIntegral -} ------------------------------------------------------------------------------- -- A faster split for lazy bytestrings ------------------------------------------------------------------------------- -- | /O(n\/c)/ 'splitAt' @n xs@ is equivalent to @('take' n xs, 'drop' n xs)@. splitLazyAt :: Int -> L.ByteString -> (L.ByteString, L.ByteString) splitLazyAt n cs0 | n <= 0 = (L.Empty, cs0) | otherwise = split cs0 where split L.Empty = (L.Empty, L.Empty) split (L.Chunk c cs) | n < len = case S.splitAt n c of (pre, suf) -> (L.Chunk pre L.Empty, L.Chunk suf cs) | otherwise = case splitLazyAt (n - len) cs of (pre, suf) -> (L.Chunk c pre , suf ) where len = S.length c ------------------------------------------------------------------------------- -- A faster partition for strict and lazy bytestrings ------------------------------------------------------------------------------- {-# INLINE partitionStrict #-} partitionStrict :: (Word8 -> Bool) -> S.ByteString -> (S.ByteString, S.ByteString) partitionStrict f (S.PS ifp ioff ilen) = second S.reverse $ S.inlinePerformIO $ do ofp <- S.mallocByteString ilen withForeignPtr ifp $ wrapper ofp where wrapper !ofp !ip0 = go (ip0 `plusPtr` ioff) op0 (op0 `plusPtr` ilen) where op0 = unsafeForeignPtrToPtr ofp go !ip !opl !oph | oph == opl = return (S.PS ofp 0 olen, S.PS ofp olen (ilen - olen)) | otherwise = do x <- peek ip if f x then do poke opl x go (ip `plusPtr` 1) (opl `plusPtr` 1) oph else do let oph' = oph `plusPtr` (-1) poke oph' x go (ip `plusPtr` 1) opl oph' where olen = opl `minusPtr` op0 {-# INLINE partitionLazy #-} partitionLazy :: (Word8 -> Bool) -> L.ByteString -> (L.ByteString, L.ByteString) partitionLazy f = L.foldrChunks partitionOne (L.empty, L.empty) where partitionOne bs (ls, rs) = (L.Chunk l ls, L.Chunk r rs) where (l, r) = partitionStrict f bs