packages feed

bytezap-1.4.0: src/Tmp/BSExt.hs

{-# LANGUAGE UnboxedTuples #-}

-- | raehik's bytestring extras (reimplementations of unexported internals).

module Tmp.BSExt
  ( module Tmp.BSExt
  , B.mkDeferredByteString
  ) where

import GHC.ForeignPtr ( ForeignPtr, unsafeWithForeignPtr, withForeignPtr )
import Foreign.Ptr ( Ptr )
import Foreign.Marshal.Utils ( copyBytes )
import Data.ByteString.Internal qualified as B
import Data.ByteString ( ByteString )
import Data.Word ( Word8 )
import Control.Exception ( assert )
import GHC.IO ( IO(IO) )
import GHC.Exts ( runRW# )

-- | Copy the given number of bytes from the second area (source) into the first
--   (destination); the copied areas may not overlap.
--
-- Reimplemented from the unexported function
-- 'Data.ByteString.Internal.Type.memcpyFp'.
memcpyFp :: ForeignPtr Word8 -> ForeignPtr Word8 -> Int -> IO ()
memcpyFp fp fq s = unsafeWithForeignPtr fp $ \p ->
                     unsafeWithForeignPtr fq $ \q -> copyBytes p q s

-- | Create a 'ByteString' of size @l@ and use action @f@ to fill its contents.
--
-- Reimplemented from the unexported function
-- 'Data.ByteString.Internal.Type.createFp.
createFp :: Int -> (ForeignPtr Word8 -> IO ()) -> IO ByteString
createFp len action = assert (len >= 0) $ do
    fp <- B.mallocByteString len
    action fp
    B.mkDeferredByteString fp len
{-# INLINE createFp #-}

createUptoNCPS
    :: Int
    -> (Ptr Word8 -> (Int -> IO ByteString) -> IO r)
    -> IO r
createUptoNCPS maxLen action = assert (maxLen >= 0) $ do
    fp <- B.mallocByteString maxLen
    withForeignPtr fp $ \p -> action p $ \len ->
        B.mkDeferredByteString fp len
{-# INLINE createUptoNCPS #-}

createCPS
    :: Int
    -> (ForeignPtr Word8 -> Int -> IO r)
    -> ((Int -> IO r) -> Ptr Word8 -> IO r)
    -> IO r
createCPS maxLen finalize f = assert (maxLen >= 0) $ do
    fp <- B.mallocByteString maxLen
    withForeignPtr fp $ \buf -> f (finalize fp) buf
{-# INLINE createCPS #-}

{-
withBuffer
    :: Int
    -> (Ptr Word8 -> Int -> IO r)
    -> (r ->
withBuffer bufLen
{-# INLINE withBuffer #-}
-}

createAndTrimCPS
    :: Int
    -> (Ptr Word8 -> (Int -> IO ByteString) -> IO r)
    -> IO r
createAndTrimCPS maxLen action = assert (maxLen >= 0) $ do
    fp <- B.mallocByteString maxLen
    withForeignPtr fp $ \p -> action p $ \len ->
        if   len < maxLen
        then createFp len (\fp' -> memcpyFp fp' fp len)
             -- ^ apparently @fp@ will get GCed automatically, up to GHC
        else B.mkDeferredByteString fp maxLen
{-# INLINE createAndTrimCPS #-}

unsafeCreateAndTrimCPS
    :: Int
    -> (Ptr Word8 -> (Int -> IO ByteString) -> IO r)
    -> r
unsafeCreateAndTrimCPS l f =
    unsafeDupablePerformIOByteString (createAndTrimCPS l f)
{-# INLINE unsafeCreateAndTrimCPS #-}

createAndTrimFailable
    :: Int
    -> (Ptr Word8 -> IO (Either e Int))
    -> IO (Either e ByteString)
createAndTrimFailable l action = createFpAndTrimFailable l (wrapAction action)
{-# INLINE createAndTrimFailable #-}

-- TODO how do I omit the Either allocation?
createFpAndTrimFailable
    :: Int
    -> (ForeignPtr Word8 -> IO (Either e Int))
    -> IO (Either e ByteString)
createFpAndTrimFailable maxLen action = assert (maxLen >= 0) $ do
    fp <- B.mallocByteString maxLen
    action fp >>= \case
      Right len ->
        if   len < maxLen
        then Right <$> createFp len (\fp' -> memcpyFp fp' fp len)
             -- ^ apparently @fp@ will get GCed automatically, up to GHC
        else Right <$> B.mkDeferredByteString fp maxLen
      Left  err -> pure $ Left err
{-# INLINE createFpAndTrimFailable #-}

createUptoNFailable
    :: Int
    -> (Ptr Word8 -> IO (Either e Int))
    -> IO (Either e ByteString)
createUptoNFailable l action = createFpUptoNFailable l (wrapAction action)
{-# INLINE createUptoNFailable #-}

createFpUptoNFailable
    :: Int
    -> (ForeignPtr Word8 -> IO (Either e Int))
    -> IO (Either e ByteString)
createFpUptoNFailable maxLen action = assert (maxLen >= 0) $ do
    fp <- B.mallocByteString maxLen
    action fp >>= \case
      Right len -> Right <$> B.mkDeferredByteString fp len
      Left  err -> pure $ Left err
{-# INLINE createFpUptoNFailable #-}

createFailable
    :: Int
    -> (Ptr Word8 -> IO (Either e Int))
    -> IO (Either e ByteString)
createFailable l action = createFpFailable l (wrapAction action)
{-# INLINE createFailable #-}

-- TODO how do I omit the Either allocation?
createFpFailable
    :: Int
    -> (ForeignPtr Word8 -> IO (Either e Int))
    -> IO (Either e ByteString)
createFpFailable maxLen action = assert (maxLen >= 0) $ do
    fp <- B.mallocByteString maxLen
    action fp >>= \case
      Right len ->
        -- TODO does not check for correctness (len <= maxLen)!! don't lie!!!!
        Right <$> B.mkDeferredByteString fp len
      Left  err -> pure $ Left err
{-# INLINE createFpFailable #-}

-- TODO probably don't export
wrapAction :: (Ptr Word8 -> IO res) -> ForeignPtr Word8 -> IO res
wrapAction = flip withForeignPtr
  -- Cannot use unsafeWithForeignPtr, because action can diverge

unsafeDupablePerformIOByteString :: IO a -> a
-- Why does this exist? In base-4.15.1.0 until at least base-4.18.0.0,
-- the version of unsafeDupablePerformIO in base prevents unboxing of
-- its results with an opaque call to GHC.Exts.lazy, for reasons described
-- in Note [unsafePerformIO and strictness] in GHC.IO.Unsafe. (See
-- https://hackage.haskell.org/package/base-4.18.0.0/docs/src/GHC.IO.Unsafe.html#line-30 .)
-- Even if we accept the (very questionable) premise that the sort of
-- function described in that note should work, we expect no such
-- calls to be made in the context of bytestring.  (And we really want
-- unboxing!)
unsafeDupablePerformIOByteString (IO act) =
    case runRW# act of (# _, res #) -> res