{-# LANGUAGE OverloadedStrings, MagicHash, BangPatterns, RecordWildCards, DeriveDataTypeable #-}
module Data.BufferBuilder (
BufferBuilder
, runBufferBuilder
, runBufferBuilder'
, Options(..)
, runBufferBuilderWithOptions
, runBufferBuilderWithOptions'
, calculateLength
, currentLength
, appendByte
, appendChar8
, appendBS
, appendLBS
, appendLiteral
, unsafeAppendLiteralN
, appendByte7
, appendChar7
, appendBS7
, appendLiteral7
, unsafeAppendLiteralN7
, appendCharUtf8
, appendStringUtf8
, appendDecimalSignedInt
, appendDecimalDouble
, appendEscapedJson
, appendEscapedJsonLiteral
, appendEscapedJsonText
, appendUrlEncoded
) where
import GHC.Base
import GHC.Word
import GHC.Ptr
import GHC.IO
import GHC.ForeignPtr
import Foreign.ForeignPtr
import Foreign.Marshal.Alloc
import qualified Data.ByteString as BS
import qualified Data.ByteString.Internal as BS
import qualified Data.ByteString.Lazy as BSL
import Control.Applicative (Applicative(..), pure)
import Control.Exception (Exception, throw)
import Control.Monad (when)
import Data.Typeable (Typeable)
import Data.Text ()
import Data.Text.Internal (Text (..))
import Data.Text.Array (Array (..))
data Handle'
type Handle = Ptr Handle'
foreign import ccall unsafe "strlen" c_strlen :: Ptr Word8 -> IO Int
foreign import ccall unsafe "bw_new" bw_new :: Int -> IO Handle
foreign import ccall unsafe "bw_new_length_calculator" bw_new_length_calculator :: IO Handle
foreign import ccall unsafe "&bw_free" bw_free :: FunPtr (Handle -> IO ())
foreign import ccall unsafe "bw_trim" bw_trim :: Handle -> IO ()
foreign import ccall unsafe "bw_get_size" bw_get_size :: Handle -> IO Int
foreign import ccall unsafe "bw_release_address" bw_release_address :: Handle -> IO (Ptr Word8)
foreign import ccall unsafe "bw_append_byte" bw_append_byte :: Handle -> Word8 -> IO ()
foreign import ccall unsafe "bw_append_char_utf8" bw_append_char_utf8 :: Handle -> Char -> IO ()
foreign import ccall unsafe "bw_append_bs" bw_append_bs :: Handle -> Int -> Ptr Word8 -> IO ()
foreign import ccall unsafe "bw_append_bsz" bw_append_bsz :: Handle -> Ptr Word8 -> IO ()
foreign import ccall unsafe "bw_append_byte7" bw_append_byte7 :: Handle -> Word8 -> IO ()
foreign import ccall unsafe "bw_append_bs7" bw_append_bs7 :: Handle -> Int -> Ptr Word8 -> IO ()
foreign import ccall unsafe "bw_append_bsz7" bw_append_bsz7 :: Handle -> Ptr Word8 -> IO ()
foreign import ccall unsafe "bw_append_decimal_signed_int" bw_append_decimal_signed_int :: Handle -> Int -> IO ()
foreign import ccall unsafe "bw_append_decimal_double" bw_append_decimal_double :: Handle -> Double -> IO ()
foreign import ccall unsafe "bw_append_json_escaped" bw_append_json_escaped :: Handle -> Int -> Ptr Word8 -> IO ()
foreign import ccall unsafe "bw_append_json_escaped_utf16" bw_append_json_escaped_utf16 :: Handle -> Int -> Ptr Word16 -> IO ()
foreign import ccall unsafe "bw_append_url_encoded" bw_append_url_encoded :: Handle -> Int -> Ptr Word8 -> IO ()
newtype BufferBuilder a = BB (Handle -> IO a)
unBB :: BufferBuilder a -> (Handle -> IO a)
unBB (BB a) = a
instance Functor BufferBuilder where
{-# INLINE fmap #-}
fmap f (BB a) = BB $ \h -> fmap f (a h)
instance Applicative BufferBuilder where
{-# INLINE pure #-}
pure = BB . const . pure
{-# INLINE (<*>) #-}
(BB f) <*> (BB a) = BB $ \h -> (f h) <*> (a h)
instance Monad BufferBuilder where
{-# INLINE return #-}
return = BB . const . return
{-# INLINE (>>=) #-}
(BB lhs) >>= next = BB $ \h -> do
a <- lhs h
unBB (next a) h
withHandle :: (Handle -> IO a) -> BufferBuilder a
withHandle = BB
{-# INLINE withHandle #-}
data BufferOutOfMemoryError = BufferOutOfMemoryError
deriving (Show, Typeable)
instance Exception BufferOutOfMemoryError
data Options = Options
{ initialCapacity :: !Int
, trimFinalBuffer :: !Bool
}
defaultOptions :: Options
defaultOptions = Options
{ initialCapacity = 128
, trimFinalBuffer = False
}
runBufferBuilder :: BufferBuilder a -> BS.ByteString
runBufferBuilder = snd . runBufferBuilder'
runBufferBuilder' :: BufferBuilder a -> (a, BS.ByteString)
runBufferBuilder' = runBufferBuilderWithOptions' defaultOptions
runBufferBuilderWithOptions :: Options -> BufferBuilder a -> BS.ByteString
runBufferBuilderWithOptions options = snd . runBufferBuilderWithOptions' options
runBufferBuilderWithOptions' :: Options -> BufferBuilder a -> (a, BS.ByteString)
runBufferBuilderWithOptions' options = unsafeDupablePerformIO . runBufferBuilderIO options
runBufferBuilderIO :: Options -> BufferBuilder a -> IO (a, BS.ByteString)
runBufferBuilderIO !Options{..} !(BB bw) = do
handle <- bw_new initialCapacity
when (handle == nullPtr) $ do
throw BufferOutOfMemoryError
handleFP <- newForeignPtr bw_free handle
rv <- bw handle
when trimFinalBuffer $ do
bw_trim handle
size <- bw_get_size handle
src <- bw_release_address handle
when (src == nullPtr) $ do
throw BufferOutOfMemoryError
borrowed <- newForeignPtr finalizerFree src
let bs = BS.fromForeignPtr borrowed 0 size
touchForeignPtr handleFP
return (rv, bs)
calculateLength :: BufferBuilder a -> Int
calculateLength !(BB bw) = unsafeDupablePerformIO $ do
handle <- bw_new_length_calculator
when (handle == nullPtr) $ do
throw BufferOutOfMemoryError
handleFP <- newForeignPtr bw_free handle
_ <- bw handle
size <- bw_get_size handle
touchForeignPtr handleFP
return size
currentLength :: BufferBuilder Int
currentLength = withHandle bw_get_size
appendByte :: Word8 -> BufferBuilder ()
appendByte b = withHandle $ \h -> bw_append_byte h b
{-# INLINE appendByte #-}
c2w :: Char -> Word8
c2w = fromIntegral . ord
{-# INLINE c2w #-}
appendChar8 :: Char -> BufferBuilder ()
appendChar8 = appendByte . c2w
{-# INLINE appendChar8 #-}
appendBS :: BS.ByteString -> BufferBuilder ()
appendBS !(BS.PS fp offset len) =
withHandle $ \h ->
withForeignPtr fp $ \addr ->
bw_append_bs h len (plusPtr addr offset)
{-# INLINE appendBS #-}
appendLBS :: BSL.ByteString -> BufferBuilder ()
appendLBS lbs = mapM_ appendBS $ BSL.toChunks lbs
{-# INLINABLE appendLBS #-}
appendLiteral :: Addr# -> BufferBuilder ()
appendLiteral addr =
withHandle $ \h ->
bw_append_bsz h (Ptr addr)
{-# INLINE appendLiteral #-}
unsafeAppendLiteralN :: Int -> Addr# -> BufferBuilder ()
unsafeAppendLiteralN len addr =
withHandle $ \h ->
bw_append_bs h len (Ptr addr)
{-# INLINE unsafeAppendLiteralN #-}
appendByte7 :: Word8 -> BufferBuilder ()
appendByte7 b = withHandle $ \h -> bw_append_byte7 h b
{-# INLINE appendByte7 #-}
appendChar7 :: Char -> BufferBuilder ()
appendChar7 = appendByte7 . c2w
{-# INLINE appendChar7 #-}
appendBS7 :: BS.ByteString -> BufferBuilder ()
appendBS7 !(BS.PS fp offset len) =
withHandle $ \h ->
withForeignPtr fp $ \addr ->
bw_append_bs7 h len (plusPtr addr offset)
{-# INLINE appendBS7 #-}
appendLiteral7 :: Addr# -> BufferBuilder ()
appendLiteral7 addr =
withHandle $ \h ->
bw_append_bsz7 h (Ptr addr)
{-# INLINE appendLiteral7 #-}
unsafeAppendLiteralN7 :: Int -> Addr# -> BufferBuilder ()
unsafeAppendLiteralN7 len addr =
withHandle $ \h ->
bw_append_bs7 h len (Ptr addr)
{-# INLINE unsafeAppendLiteralN7 #-}
appendCharUtf8 :: Char -> BufferBuilder ()
appendCharUtf8 c = withHandle $ \h -> bw_append_char_utf8 h c
{-# INLINE appendCharUtf8 #-}
appendStringUtf8 :: String -> BufferBuilder ()
appendStringUtf8 = mapM_ appendCharUtf8
{-# INLINABLE appendStringUtf8 #-}
appendDecimalSignedInt :: Int -> BufferBuilder ()
appendDecimalSignedInt i =
withHandle $ \h ->
bw_append_decimal_signed_int h i
{-# INLINE appendDecimalSignedInt #-}
appendDecimalDouble :: Double -> BufferBuilder ()
appendDecimalDouble d =
withHandle $ \h ->
bw_append_decimal_double h d
{-# INLINE appendDecimalDouble #-}
appendEscapedJson :: BS.ByteString -> BufferBuilder ()
appendEscapedJson !(BS.PS (ForeignPtr addr _) offset len) =
withHandle $ \h ->
bw_append_json_escaped h len (plusPtr (Ptr addr) offset)
{-# INLINE appendEscapedJson #-}
appendEscapedJsonLiteral :: Addr# -> BufferBuilder ()
appendEscapedJsonLiteral addr =
withHandle $ \h -> do
len <- c_strlen (Ptr addr)
bw_append_json_escaped h len (Ptr addr)
{-# INLINE appendEscapedJsonLiteral #-}
appendEscapedJsonText :: Text -> BufferBuilder ()
appendEscapedJsonText !(Text arr ofs len) =
let byteArray = aBA arr
in withHandle $ \h ->
bw_append_json_escaped_utf16 h len (Ptr (byteArrayContents# byteArray) `plusPtr` (2 * ofs))
{-# INLINE appendEscapedJsonText #-}
appendUrlEncoded :: BS.ByteString -> BufferBuilder ()
appendUrlEncoded !(BS.PS (ForeignPtr addr _) offset len) =
withHandle $ \h ->
bw_append_url_encoded h len (plusPtr (Ptr addr) offset)
{-# INLINE appendUrlEncoded #-}