optics-extra-0.2: src/Optics/Extra/Internal/ByteString.hs
-- | This module spends a lot of time fiddling around with 'Data.ByteString'
-- internals to work around <http://hackage.haskell.org/trac/ghc/ticket/7556> on
-- older Haskell Platforms and to improve constant and asymptotic factors in our
-- performance.
----------------------------------------------------------------------------
module Optics.Extra.Internal.ByteString
( traversedStrictTree
, traversedStrictTree8
, traversedLazy
, traversedLazy8
) where
import Data.Bits
import Data.Char
import Data.Int
import Data.Word
import Foreign.ForeignPtr
import Foreign.Ptr
import Foreign.Storable
import GHC.Base (unsafeChr)
import GHC.ForeignPtr (mallocPlainForeignPtrBytes)
import GHC.IO (unsafeDupablePerformIO)
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as B8
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString.Lazy.Char8 as BL8
import qualified Data.ByteString.Internal as BI
import qualified Data.ByteString.Unsafe as BU
import Data.Profunctor.Indexed
import Optics.Core
import Optics.Internal.Fold
import Optics.Internal.IxFold
import Optics.Internal.Optic
-- | Traverse a strict 'B.ByteString' in a relatively balanced fashion, as a
-- balanced tree with biased runs of elements at the leaves.
traversedStrictTree :: IxTraversal' Int64 B.ByteString Word8
traversedStrictTree = Optic traversedStrictTree__
{-# INLINE traversedStrictTree #-}
-- | Traverse a strict 'B.ByteString' in a relatively balanced fashion, as a
-- balanced tree with biased runs of elements at the leaves, pretending the
-- bytes are chars.
traversedStrictTree8 :: IxTraversal' Int64 B8.ByteString Char
traversedStrictTree8 = Optic traversedStrictTree8__
{-# INLINE traversedStrictTree8 #-}
-- | An 'IxTraversal' of the individual bytes in a lazy 'BL.ByteString'.
traversedLazy :: IxTraversal' Int64 BL.ByteString Word8
traversedLazy = Optic traversedLazy__
{-# INLINE traversedLazy #-}
-- | An 'IxTraversal' of the individual bytes in a lazy 'BL.ByteString'
-- pretending the bytes are chars.
traversedLazy8 :: IxTraversal' Int64 BL.ByteString Char
traversedLazy8 = Optic traversedLazy8__
{-# INLINE traversedLazy8 #-}
----------------------------------------
-- Internal implementations
grain :: Int64
grain = 32
{-# INLINE grain #-}
-- | Internal version of 'traversedStrictTree'.
traversedStrictTree__
:: Traversing p
=> Optic__ p j (Int64 -> j) B.ByteString B.ByteString Word8 Word8
traversedStrictTree__ = iwander $ \f bs ->
let len = B.length bs
go !i !j
| i + grain < j, k <- i + shiftR (j - i) 1 =
(\l r q -> l q >> r q) <$> go i k <*> go k j
| otherwise = run i j
run !(i::Int64) !(j::Int64)
| i == j = pure (\_ -> return ())
| otherwise =
let !i' = fromIntegral i
!x = BU.unsafeIndex bs i'
in (\y ys q -> pokeByteOff q i' y >> ys q)
<$> f i x
<*> run (i + 1) j
in unsafeCreate len <$> go 0 (fromIntegral len)
{-# INLINE [0] traversedStrictTree__ #-}
{-# RULES
"bytes -> map"
forall (o :: FunArrow j Word8 Word8). traversedStrictTree__ o
= roam B.map (reFunArrow o)
:: FunArrow (Int64 -> j) B.ByteString B.ByteString
"bytes -> imap"
forall (o :: IxFunArrow j Word8 Word8). traversedStrictTree__ o = iroam imapB o
:: IxFunArrow (Int64 -> j) B.ByteString B.ByteString
"bytes -> foldr"
forall (o :: Forget r j Word8 Word8). traversedStrictTree__ o
= foldring__ B.foldr (reForget o)
:: Forget r (Int64 -> j) B.ByteString B.ByteString
"bytes -> ifoldr"
forall (o :: IxForget r j Word8 Word8). traversedStrictTree__ o
= ifoldring__ ifoldrB o
:: IxForget r (Int64 -> j) B.ByteString B.ByteString
#-}
-- | Indexed setter for 'traversedStrictTree__'.
imapB :: (Int64 -> Word8 -> Word8) -> B.ByteString -> B.ByteString
imapB f = snd . B.mapAccumL (\i a -> i `seq` (i + 1, f i a)) 0
{-# INLINE imapB #-}
-- | Indexed fold for 'traversedStrictTree__'.
ifoldrB :: (Int64 -> Word8 -> a -> a) -> a -> B.ByteString -> a
ifoldrB f z xs = B.foldr (\x g i -> i `seq` f i x (g (i + 1))) (const z) xs 0
{-# INLINE ifoldrB #-}
----------------------------------------
-- | Internal version of 'traversedStrictTree8'.
traversedStrictTree8__
:: Traversing p
=> Optic__ p j (Int64 -> j) B8.ByteString B8.ByteString Char Char
traversedStrictTree8__ = iwander $ \f bs ->
let len = B.length bs
go !i !j
| i + grain < j, k <- i + shiftR (j - i) 1 =
(\l r q -> l q >> r q) <$> go i k <*> go k j
| otherwise = run i j
run !(i::Int64) !(j::Int64)
| i == j = pure (\_ -> return ())
| otherwise =
let !i' = fromIntegral i
!x = BU.unsafeIndex bs i'
in (\y ys q -> pokeByteOff q i' (c2w y) >> ys q)
<$> f i (w2c x)
<*> run (i + 1) j
in unsafeCreate len <$> go 0 (fromIntegral len)
{-# INLINE [0] traversedStrictTree8__ #-}
{-# RULES
"chars -> map"
forall (o :: FunArrow j Char Char). traversedStrictTree8__ o
= roam B8.map (reFunArrow o)
:: FunArrow (Int64 -> j) B8.ByteString B8.ByteString
"chars -> imap"
forall (o :: IxFunArrow j Char Char). traversedStrictTree8__ o = iroam imapB8 o
:: IxFunArrow (Int64 -> j) B8.ByteString B8.ByteString
"chars -> foldr"
forall (o :: Forget r j Char Char). traversedStrictTree8__ o
= foldring__ B8.foldr (reForget o)
:: Forget r (Int64 -> j) B8.ByteString B8.ByteString
"chars -> ifoldr"
forall (o :: IxForget r j Char Char). traversedStrictTree8__ o
= ifoldring__ ifoldrB8 o
:: IxForget r (Int64 -> j) B8.ByteString B8.ByteString
#-}
-- | Indexed setter for 'traversedStrictTree8__'.
imapB8 :: (Int64 -> Char -> Char) -> B.ByteString -> B.ByteString
imapB8 f = snd . B8.mapAccumL (\i a -> i `seq` (i + 1, f i a)) 0
{-# INLINE imapB8 #-}
-- | Indexed fold for 'traversedStrictTree8__'.
ifoldrB8 :: (Int64 -> Char -> a -> a) -> a -> B.ByteString -> a
ifoldrB8 f z xs = B8.foldr (\x g i -> i `seq` f i x (g (i + 1))) (const z) xs 0
{-# INLINE ifoldrB8 #-}
----------------------------------------
-- | Internal version of 'traversedLazy'.
traversedLazy__
:: Traversing p
=> Optic__ p j (Int64 -> j) BL.ByteString BL.ByteString Word8 Word8
traversedLazy__ = iwander $ \f lbs ->
let go c fcs acc =
let !acc' = acc + fromIntegral (B.length c)
rest = reindexed (\x -> acc + x) traversedStrictTree
in BL.append . BL.fromStrict <$> itraverseOf rest f c <*> fcs acc'
in BL.foldrChunks go (\_ -> pure BL.empty) lbs 0
{-# INLINE [1] traversedLazy__ #-}
{-# RULES
"sets lazy bytestring"
forall (o :: FunArrow j Word8 Word8). traversedLazy__ o
= roam BL.map (reFunArrow o)
:: FunArrow (Int64 -> j) BL.ByteString BL.ByteString
"isets lazy bytestring"
forall (o :: IxFunArrow j Word8 Word8). traversedLazy__ o = iroam imapBL o
:: IxFunArrow (Int64 -> j) BL.ByteString BL.ByteString
"gets lazy bytestring"
forall (o :: Forget r j Word8 Word8). traversedLazy__ o
= foldring__ BL.foldr (reForget o)
:: Forget r (Int64 -> j) BL.ByteString BL.ByteString
"igets lazy bytestring"
forall (o :: IxForget r j Word8 Word8). traversedLazy__ o = ifoldring__ ifoldrBL o
:: IxForget r (Int64 -> j) BL.ByteString BL.ByteString
#-}
-- | Indexed setter for 'traversedLazy__'.
imapBL :: (Int64 -> Word8 -> Word8) -> BL.ByteString -> BL.ByteString
imapBL f = snd . BL.mapAccumL (\i a -> i `seq` (i + 1, f i a)) 0
{-# INLINE imapBL #-}
-- | Indexed fold for 'traversedLazy__'.
ifoldrBL :: (Int64 -> Word8 -> a -> a) -> a -> BL.ByteString -> a
ifoldrBL f z xs = BL.foldr (\x g i -> i `seq` f i x (g (i + 1))) (const z) xs 0
{-# INLINE ifoldrBL #-}
----------------------------------------
-- | Internal version of 'traversedLazy8'.
traversedLazy8__
:: Traversing p
=> Optic__ p j (Int64 -> j) BL.ByteString BL.ByteString Char Char
traversedLazy8__ = iwander $ \f lbs ->
let go c fcs acc =
let !acc' = acc + fromIntegral (B.length c)
rest = reindexed (\x -> acc + x) traversedStrictTree8
in BL.append . BL.fromStrict <$> itraverseOf rest f c <*> fcs acc'
in BL.foldrChunks go (\_ -> pure BL.empty) lbs 0
{-# INLINE [1] traversedLazy8__ #-}
{-# RULES
"sets lazy char bytestring"
forall (o :: FunArrow j Char Char). traversedLazy8__ o
= roam BL8.map (reFunArrow o)
:: FunArrow (Int64 -> j) BL8.ByteString BL8.ByteString
"isets lazy char bytestring"
forall (o :: IxFunArrow j Char Char). traversedLazy8__ o = iroam imapBL8 o
:: IxFunArrow (Int64 -> j) BL8.ByteString BL8.ByteString
"gets lazy char bytestring"
forall (o :: Forget r j Char Char). traversedLazy8__ o
= foldring__ BL8.foldr (reForget o)
:: Forget r (Int64 -> j) BL8.ByteString BL8.ByteString
"igets lazy char bytestring"
forall (o :: IxForget r j Char Char). traversedLazy8__ o = ifoldring__ ifoldrBL8 o
:: IxForget r (Int64 -> j) BL.ByteString BL.ByteString
#-}
-- | Indexed setter for 'traversedLazy8__'.
imapBL8 :: (Int64 -> Char -> Char) -> BL8.ByteString -> BL8.ByteString
imapBL8 f = snd . BL8.mapAccumL (\i a -> i `seq` (i + 1, f i a)) 0
{-# INLINE imapBL8 #-}
-- | Indexed fold for 'traversedLazy8__'.
ifoldrBL8 :: (Int64 -> Char -> a -> a) -> a -> BL8.ByteString -> a
ifoldrBL8 f z xs = BL8.foldr (\x g i -> i `seq` f i x (g (i + 1))) (const z) xs 0
{-# INLINE ifoldrBL8 #-}
------------------------------------------------------------------------------
-- ByteString guts
------------------------------------------------------------------------------
-- | Conversion between 'Word8' and 'Char'. Should compile to a no-op.
w2c :: Word8 -> Char
w2c = unsafeChr . fromIntegral
{-# INLINE w2c #-}
-- | Unsafe conversion between 'Char' and 'Word8'. This is a no-op and silently
-- truncates to 8 bits Chars > '\255'. It is provided as convenience for
-- ByteString construction.
c2w :: Char -> Word8
c2w = fromIntegral . ord
{-# INLINE c2w #-}
-- | A way of creating ByteStrings outside the IO monad. The @Int@ argument
-- gives the final size of the ByteString. Unlike 'createAndTrim' the ByteString
-- is not reallocated if the final size is less than the estimated size.
unsafeCreate :: Int -> (Ptr Word8 -> IO ()) -> B.ByteString
unsafeCreate l f = unsafeDupablePerformIO (create l f)
{-# INLINE unsafeCreate #-}
-- | Create ByteString of size @l@ and use action @f@ to fill it's contents.
create :: Int -> (Ptr Word8 -> IO ()) -> IO B.ByteString
create l f = do
fp <- mallocPlainForeignPtrBytes l
withForeignPtr fp $ \p -> f p
return $! BI.PS fp 0 l
{-# INLINE create #-}