{-# LANGUAGE CPP, BangPatterns, Rank2Types #-}
#ifdef USE_MONO_PAT_BINDS
{-# LANGUAGE MonoPatBinds #-}
#endif
module Blaze.ByteString.Builder.Internal (
BufRange(..)
, BuildSignal
, BuildStep
, done
, bufferFull
, insertByteString
, Builder
, fromBuildStepCont
, fromPut
, flush
, Put
, putBuilder
, putBuildStepCont
, putLiftIO
, module Blaze.ByteString.Builder.Internal.Write
, writeToByteString
, toLazyByteString
, toLazyByteStringWith
, toByteString
, toByteStringIO
, toByteStringIOWith
, defaultFirstBufferSize
, defaultMinimalBufferSize
, defaultBufferSize
, defaultMaximalCopySize
) where
#ifdef HAS_FOREIGN_UNSAFE_MODULE
import Foreign (withForeignPtr, sizeOf, copyBytes, plusPtr, minusPtr)
import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
#else
import Foreign (unsafeForeignPtrToPtr, withForeignPtr, sizeOf, copyBytes, plusPtr, minusPtr)
#endif
import Control.Monad (unless)
import System.IO.Unsafe (unsafeDupablePerformIO)
import qualified Data.ByteString 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 Blaze.ByteString.Builder.Internal.Types
import Blaze.ByteString.Builder.Internal.Write
defaultBufferSize :: Int
defaultBufferSize = 32 * 1024 - overhead
where overhead = 2 * sizeOf (undefined :: Int)
defaultMinimalBufferSize :: Int
defaultMinimalBufferSize = 4 * 1024 - overhead
where overhead = 2 * sizeOf (undefined :: Int)
defaultFirstBufferSize :: Int
defaultFirstBufferSize = 64
defaultMaximalCopySize :: Int
defaultMaximalCopySize = 2 * defaultMinimalBufferSize
{-# INLINE nonEmptyChunk #-}
nonEmptyChunk :: S.ByteString -> L.ByteString -> L.ByteString
nonEmptyChunk bs lbs | S.null bs = lbs
| otherwise = L.Chunk bs lbs
{-# INLINE flush #-}
flush :: Builder
flush = fromBuildStepCont step
where
step k !(BufRange op _) = return $ insertByteString op S.empty k
toLazyByteStringWith
:: Int
-> Int
-> Int
-> Builder
-> L.ByteString
-> L.ByteString
toLazyByteStringWith bufSize minBufSize firstBufSize (Builder b) k =
S.inlinePerformIO $ fillFirstBuffer (b (buildStep finalStep))
where
finalStep (BufRange pf _) = return $ Done pf ()
fillFirstBuffer !step0
| minBufSize <= firstBufSize = fillNewBuffer firstBufSize step0
| otherwise = do
fpbuf <- S.mallocByteString firstBufSize
withForeignPtr fpbuf $ \pf -> do
let !pe = pf `plusPtr` firstBufSize
mkbs pf' = S.PS fpbuf 0 (pf' `minusPtr` pf)
{-# INLINE mkbs #-}
next <- runBuildStep step0 (BufRange pf pe)
case next of
Done pf' _
| pf' == pf -> return k
| otherwise -> return $ L.Chunk (mkbs pf') k
BufferFull newSize pf' nextStep -> do
let !l = pf' `minusPtr` pf
fillNewBuffer (max (l + newSize) minBufSize) $ buildStep $
\(BufRange pfNew peNew) -> do
copyBytes pfNew pf l
let !br' = BufRange (pfNew `plusPtr` l) peNew
runBuildStep nextStep br'
InsertByteString pf' bs nextStep
| pf' == pf ->
return $ nonEmptyChunk bs (S.inlinePerformIO $ fillNewBuffer bufSize nextStep)
| otherwise ->
return $ L.Chunk (mkbs pf')
(nonEmptyChunk bs (S.inlinePerformIO $ fillNewBuffer bufSize nextStep))
fillNewBuffer !size !step0 = do
fpbuf <- S.mallocByteString size
withForeignPtr fpbuf $ fillBuffer fpbuf
where
fillBuffer fpbuf !pbuf = fill pbuf step0
where
!pe = pbuf `plusPtr` size
fill !pf !step = do
next <- runBuildStep step (BufRange pf pe)
let mkbs pf' = S.PS fpbuf (pf `minusPtr` pbuf) (pf' `minusPtr` pf)
{-# INLINE mkbs #-}
case next of
Done pf' _
| pf' == pf -> return k
| otherwise -> return $ L.Chunk (mkbs pf') k
BufferFull newSize pf' nextStep
| pf' == pf ->
fillNewBuffer (max newSize bufSize) nextStep
| otherwise ->
return $ L.Chunk (mkbs pf')
(S.inlinePerformIO $
fillNewBuffer (max newSize bufSize) nextStep)
InsertByteString pf' bs nextStep
| pf' == pf ->
return $ nonEmptyChunk bs (S.inlinePerformIO $ fill pf' nextStep)
| minBufSize < pe `minusPtr` pf' ->
return $ L.Chunk (mkbs pf')
(nonEmptyChunk bs (S.inlinePerformIO $ fill pf' nextStep))
| otherwise ->
return $ L.Chunk (mkbs pf')
(nonEmptyChunk bs (S.inlinePerformIO $ fillNewBuffer bufSize nextStep))
toLazyByteString :: Builder -> L.ByteString
toLazyByteString b = toLazyByteStringWith
defaultBufferSize defaultMinimalBufferSize defaultFirstBufferSize b L.empty
{-# INLINE toLazyByteString #-}
packChunks :: L.ByteString -> S.ByteString
packChunks lbs = do
S.unsafeCreate (fromIntegral $ L.length lbs) (copyChunks lbs)
where
copyChunks !L.Empty !_pf = return ()
copyChunks !(L.Chunk (S.PS fpbuf o l) lbs') !pf = do
withForeignPtr fpbuf $ \pbuf ->
copyBytes pf (pbuf `plusPtr` o) l
copyChunks lbs' (pf `plusPtr` l)
toByteString :: Builder -> S.ByteString
toByteString = packChunks . toLazyByteString
toByteStringIOWith :: Int
-> (S.ByteString -> IO ())
-> Builder
-> IO ()
toByteStringIOWith bufSize io (Builder b) =
fillBuffer bufSize (b (buildStep finalStep))
where
finalStep !(BufRange pf _) = return $ Done pf ()
fillBuffer !size step = do
S.mallocByteString size >>= fill
where
fill fpbuf = do
let !pf = unsafeForeignPtrToPtr fpbuf
!br = BufRange pf (pf `plusPtr` size)
signal <- runBuildStep step br
case signal of
Done pf' _ -> io $ S.PS fpbuf 0 (pf' `minusPtr` pf)
BufferFull minSize pf' nextStep -> do
io $ S.PS fpbuf 0 (pf' `minusPtr` pf)
fillBuffer (max bufSize minSize) nextStep
InsertByteString pf' bs nextStep -> do
io $ S.PS fpbuf 0 (pf' `minusPtr` pf)
unless (S.null bs) (io bs)
fillBuffer bufSize nextStep
toByteStringIO :: (S.ByteString -> IO ()) -> Builder -> IO ()
toByteStringIO = toByteStringIOWith defaultBufferSize
{-# INLINE toByteStringIO #-}
writeToByteString :: Write -> S.ByteString
writeToByteString !w = unsafeDupablePerformIO $ do
fptr <- S.mallocByteString (getBound w)
len <- withForeignPtr fptr $ \ptr -> do
end <- runWrite w ptr
return $! end `minusPtr` ptr
return $! S.fromForeignPtr fptr 0 len
{-# INLINE writeToByteString #-}