module Data.BufferBuilder (
BufferBuilder
, runBufferBuilder
, Options(..)
, runBufferBuilderWithOptions
, 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_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
fmap f (BB a) = BB $ \h -> fmap f (a h)
instance Applicative BufferBuilder where
pure = BB . const . pure
(BB f) <*> (BB a) = BB $ \h -> (f h) <*> (a h)
instance Monad BufferBuilder where
return = BB . const . return
(BB lhs) >>= next = BB $ \h -> do
a <- lhs h
unBB (next a) h
withHandle :: (Handle -> IO ()) -> BufferBuilder ()
withHandle = BB
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 () -> BS.ByteString
runBufferBuilder = runBufferBuilderWithOptions defaultOptions
runBufferBuilderWithOptions :: Options -> BufferBuilder () -> BS.ByteString
runBufferBuilderWithOptions options = unsafeDupablePerformIO . runBufferBuilderIO options
runBufferBuilderIO :: Options-> BufferBuilder () -> IO BS.ByteString
runBufferBuilderIO !Options{..} !(BB bw) = do
handle <- bw_new initialCapacity
when (handle == nullPtr) $ do
throw BufferOutOfMemoryError
handleFP <- newForeignPtr bw_free handle
() <- 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 bs
appendByte :: Word8 -> BufferBuilder ()
appendByte b = withHandle $ \h -> bw_append_byte h b
c2w :: Char -> Word8
c2w = fromIntegral . ord
appendChar8 :: Char -> BufferBuilder ()
appendChar8 = appendByte . c2w
appendBS :: BS.ByteString -> BufferBuilder ()
appendBS !(BS.PS fp offset len) =
withHandle $ \h ->
withForeignPtr fp $ \addr ->
bw_append_bs h len (plusPtr addr offset)
appendLBS :: BSL.ByteString -> BufferBuilder ()
appendLBS lbs = mapM_ appendBS $ BSL.toChunks lbs
appendLiteral :: Addr# -> BufferBuilder ()
appendLiteral addr =
withHandle $ \h ->
bw_append_bsz h (Ptr addr)
unsafeAppendLiteralN :: Int -> Addr# -> BufferBuilder ()
unsafeAppendLiteralN len addr =
withHandle $ \h ->
bw_append_bs h len (Ptr addr)
appendByte7 :: Word8 -> BufferBuilder ()
appendByte7 b = withHandle $ \h -> bw_append_byte7 h b
appendChar7 :: Char -> BufferBuilder ()
appendChar7 = appendByte7 . c2w
appendBS7 :: BS.ByteString -> BufferBuilder ()
appendBS7 !(BS.PS fp offset len) =
withHandle $ \h ->
withForeignPtr fp $ \addr ->
bw_append_bs7 h len (plusPtr addr offset)
appendLiteral7 :: Addr# -> BufferBuilder ()
appendLiteral7 addr =
withHandle $ \h ->
bw_append_bsz7 h (Ptr addr)
unsafeAppendLiteralN7 :: Int -> Addr# -> BufferBuilder ()
unsafeAppendLiteralN7 len addr =
withHandle $ \h ->
bw_append_bs7 h len (Ptr addr)
appendCharUtf8 :: Char -> BufferBuilder ()
appendCharUtf8 c = withHandle $ \h -> bw_append_char_utf8 h c
appendStringUtf8 :: String -> BufferBuilder ()
appendStringUtf8 = mapM_ appendCharUtf8
appendDecimalSignedInt :: Int -> BufferBuilder ()
appendDecimalSignedInt i =
withHandle $ \h ->
bw_append_decimal_signed_int h i
appendDecimalDouble :: Double -> BufferBuilder ()
appendDecimalDouble d =
withHandle $ \h ->
bw_append_decimal_double h d
appendEscapedJson :: BS.ByteString -> BufferBuilder ()
appendEscapedJson !(BS.PS (ForeignPtr addr _) offset len) =
withHandle $ \h ->
bw_append_json_escaped h len (plusPtr (Ptr addr) offset)
appendEscapedJsonLiteral :: Addr# -> BufferBuilder ()
appendEscapedJsonLiteral addr =
withHandle $ \h -> do
len <- c_strlen (Ptr addr)
bw_append_json_escaped h len (Ptr addr)
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` ofs)
appendUrlEncoded :: BS.ByteString -> BufferBuilder ()
appendUrlEncoded !(BS.PS (ForeignPtr addr _) offset len) =
withHandle $ \h ->
bw_append_url_encoded h len (plusPtr (Ptr addr) offset)