packages feed

hermes-json-0.1.0.0: src/Data/Hermes/SIMDJSON/Wrapper.hs

-- | Contains functions for constructing and working with foreign simdjson instances.

module Data.Hermes.SIMDJSON.Wrapper
  ( getDocumentInfo
  , mkSIMDParser
  , mkSIMDDocument
  , mkSIMDPaddedStr
  , withInputBuffer
  )
  where

import           Control.Monad.IO.Class (liftIO)
import           Data.ByteString (ByteString)
import qualified Data.ByteString.Unsafe as Unsafe
import           Data.Maybe (fromMaybe)
import           Data.Text (Text)
import qualified Data.Text as T
import           UnliftIO.Exception (bracket, mask_)
import           UnliftIO.Foreign
  ( ForeignPtr
  , alloca
  , allocaBytes
  , finalizeForeignPtr
  , newForeignPtr
  , peek
  , peekCString
  , peekCStringLen
  , withForeignPtr
  )

import           Data.Hermes.SIMDJSON.Bindings
  ( currentLocationImpl
  , deleteDocumentImpl
  , deleteInputImpl
  , makeDocumentImpl
  , makeInputImpl
  , parserDestroy
  , parserInit
  , toDebugStringImpl
  )
import           Data.Hermes.SIMDJSON.Types
  ( Document
  , InputBuffer(..)
  , PaddedString
  , SIMDDocument
  , SIMDErrorCode(..)
  , SIMDParser
  )

mkSIMDParser :: Maybe Int -> IO (ForeignPtr SIMDParser)
mkSIMDParser mCap = mask_ $ do
  let maxCap = 4000000000; -- 4GB
  ptr <- parserInit . toEnum $ fromMaybe maxCap mCap
  newForeignPtr parserDestroy ptr

mkSIMDDocument :: IO (ForeignPtr SIMDDocument)
mkSIMDDocument = mask_ $ do
  ptr <- makeDocumentImpl
  newForeignPtr deleteDocumentImpl ptr

mkSIMDPaddedStr :: ByteString -> IO (ForeignPtr PaddedString)
mkSIMDPaddedStr input = mask_ $
  Unsafe.unsafeUseAsCStringLen input $ \(cstr, len) -> do
    ptr <- makeInputImpl cstr (fromIntegral len)
    newForeignPtr deleteInputImpl ptr

-- | Construct a simdjson:padded_string from a Haskell `ByteString`, and pass
-- it to a monadic action. The instance lifetime is managed by the `bracket` function.
withInputBuffer :: ByteString -> (InputBuffer -> IO a) -> IO a
withInputBuffer bs f =
  bracket acquire release $ \fPtr -> withForeignPtr fPtr $ f . InputBuffer
  where
    acquire = liftIO $ mkSIMDPaddedStr bs
    release = liftIO . finalizeForeignPtr

-- | Read the document location and debug string. If the iterator is out of bounds
-- then we abort reading from the iterator buffers to prevent reading garbage.
getDocumentInfo :: Document -> IO (Text, Text)
getDocumentInfo docPtr = alloca $ \locStrPtr -> alloca $ \lenPtr -> do
  err <- liftIO $ currentLocationImpl docPtr locStrPtr
  let errCode = toEnum $ fromIntegral err
  if errCode == OUT_OF_BOUNDS
    then pure ("out of bounds", "")
    else allocaBytes 128 $ \dbStrPtr -> do
      locStr <- fmap T.pack $ peekCString =<< peek locStrPtr
      toDebugStringImpl docPtr dbStrPtr lenPtr
      len <- fmap fromIntegral $ peek lenPtr
      debugStr <- fmap T.pack $ peekCStringLen (dbStrPtr, len)
      pure (locStr, debugStr)