extism-1.3.0.0: src/Extism/HostFunction.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Extism.HostFunction
( CurrentPlugin (..),
ValType (..),
Val (..),
MemoryHandle,
Function,
memoryAlloc,
memoryLength,
memoryFree,
memory,
memoryOffset,
memoryBytes,
memoryString,
memoryGet,
allocBytes,
allocString,
alloc,
toI32,
toI64,
toF32,
toF64,
fromI32,
fromI64,
fromF32,
fromF64,
hostFunction,
hostFunction',
newFunction,
newFunction',
input,
output,
getParams,
setResults,
hostContext,
ptr,
)
where
import qualified Data.ByteString as B
import qualified Data.ByteString.Internal as BS (c2w, unsafePackLenAddress)
import Data.IORef
import Data.Word
import Extism
import Extism.Bindings
import Extism.Encoding
import Foreign.C.String
import Foreign.Concurrent
import Foreign.ForeignPtr
import Foreign.Marshal.Array
import Foreign.Ptr
import Foreign.StablePtr
import Foreign.Storable
import GHC.Ptr
ptr :: ValType
ptr = I64
-- | Access the plugin that is currently executing from inside a host function
data CurrentPlugin = CurrentPlugin (Ptr ExtismCurrentPlugin) [Val] (Ptr Val) Int
-- | A memory handle represents an allocated block of Extism memory
newtype MemoryHandle = MemoryHandle Word64 deriving (Num, Enum, Eq, Ord, Real, Integral, Show)
-- | Allocate a new handle of the given size
memoryAlloc :: CurrentPlugin -> Word64 -> IO MemoryHandle
memoryAlloc (CurrentPlugin p _ _ _) n = MemoryHandle <$> extism_current_plugin_memory_alloc p n
-- | Get the length of a handle, returns 0 if the handle is invalid
memoryLength :: CurrentPlugin -> MemoryHandle -> IO Word64
memoryLength (CurrentPlugin p _ _ _) (MemoryHandle offs) = extism_current_plugin_memory_length p offs
-- | Free allocated memory
memoryFree :: CurrentPlugin -> MemoryHandle -> IO ()
memoryFree (CurrentPlugin p _ _ _) (MemoryHandle offs) = extism_current_plugin_memory_free p offs
-- | Access a pointer to the entire memory region
memory :: CurrentPlugin -> IO (Ptr Word8)
memory (CurrentPlugin p _ _ _) = extism_current_plugin_memory p
-- | Access the pointer for the given 'MemoryHandle'
memoryOffset :: CurrentPlugin -> MemoryHandle -> IO (Ptr Word8)
memoryOffset (CurrentPlugin plugin _ _ _) (MemoryHandle offs) = do
x <- extism_current_plugin_memory plugin
return $ plusPtr x (fromIntegral offs)
-- | Access the data associated with a handle as a 'ByteString'
memoryBytes :: CurrentPlugin -> MemoryHandle -> IO B.ByteString
memoryBytes plugin offs = do
Ptr ptr <- memoryOffset plugin offs
len <- memoryLength plugin offs
BS.unsafePackLenAddress (fromIntegral len) ptr
-- | Access the data associated with a handle as a 'String'
memoryString :: CurrentPlugin -> MemoryHandle -> IO String
memoryString plugin offs = do
fromByteString <$> memoryBytes plugin offs
-- | Access the data associated with a handle and convert it into a Haskell type
memoryGet :: (FromBytes a) => CurrentPlugin -> MemoryHandle -> IO (Result a)
memoryGet plugin offs = do
x <- memoryBytes plugin offs
return $ fromBytes x
-- | Access the data associated with a handle and convert it into a Haskell type
memoryGet' :: (FromBytes a) => CurrentPlugin -> MemoryHandle -> IO a
memoryGet' plugin offs = do
x <- memoryBytes plugin offs
return $ unwrap $ fromBytes x
-- | Allocate memory and copy an existing 'ByteString' into it
allocBytes :: CurrentPlugin -> B.ByteString -> IO MemoryHandle
allocBytes plugin s = do
let length = B.length s
offs <- memoryAlloc plugin (fromIntegral length)
ptr <- memoryOffset plugin offs
pokeArray ptr (B.unpack s)
return offs
-- | Allocate memory and copy an existing 'String' into it
allocString :: CurrentPlugin -> String -> IO MemoryHandle
allocString plugin s = do
let length = Prelude.length s
offs <- memoryAlloc plugin (fromIntegral length)
ptr <- memoryOffset plugin offs
pokeArray ptr (Prelude.map BS.c2w s)
return offs
alloc :: (ToBytes a) => CurrentPlugin -> a -> IO MemoryHandle
alloc plugin x =
let a = toBytes x
in allocBytes plugin a
-- | Create a new I32 'Val'
toI32 :: (Integral a) => a -> Val
toI32 x = ValI32 (fromIntegral x)
-- | Create a new I64 'Val'
toI64 :: (Integral a) => a -> Val
toI64 x = ValI64 (fromIntegral x)
-- | Create a new F32 'Val'
toF32 :: Float -> Val
toF32 = ValF32
-- | Create a new F64 'Val'
toF64 :: Double -> Val
toF64 = ValF64
-- | Get I32 'Val'
fromI32 :: (Integral a) => Val -> Maybe a
fromI32 (ValI32 x) = Just (fromIntegral x)
fromI32 _ = Nothing
-- | Get I64 'Val'
fromI64 :: (Integral a) => Val -> Maybe a
fromI64 (ValI64 x) = Just (fromIntegral x)
fromI64 _ = Nothing
-- | Get F32 'Val'
fromF32 :: Val -> Maybe Float
fromF32 (ValF32 x) = Just x
fromF32 _ = Nothing
-- | Get F64 'Val'
fromF64 :: Val -> Maybe Double
fromF64 (ValF64 x) = Just x
fromF64 _ = Nothing
setResults :: CurrentPlugin -> [Val] -> IO ()
setResults (CurrentPlugin _ _ res _) = pokeArray res
getParams :: CurrentPlugin -> [Val]
getParams (CurrentPlugin _ params _ _) = params
output :: (ToBytes a) => CurrentPlugin -> Int -> a -> IO ()
output !p !index !x =
do
mem <- alloc p x
if index >= len
then return ()
else pokeElemOff res index (toI64 mem)
where
CurrentPlugin _ _ !res !len = p
input :: (FromBytes a) => CurrentPlugin -> Int -> IO (Result a)
input plugin index =
case x of
Nothing -> return $ Left (ExtismError "invalid parameter")
Just offs -> do
memoryGet plugin (MemoryHandle offs)
where
(CurrentPlugin _ params _ _) = plugin
x = fromI64 (params !! index) :: Maybe Word64
input' :: (FromBytes a) => CurrentPlugin -> Int -> IO a
input' plugin index =
unwrap <$> input plugin index
hostContext :: CurrentPlugin -> IO (Maybe a)
hostContext (CurrentPlugin cp _ _ _) = do
ptr <- extism_current_plugin_host_context cp
if ptr == nullPtr
then return Nothing
else do
x <- deRefStablePtr (castPtrToStablePtr ptr)
return $ Just x
callback :: (CurrentPlugin -> a -> IO ()) -> (Ptr ExtismCurrentPlugin -> Ptr Val -> Word64 -> Ptr Val -> Word64 -> Ptr () -> IO ())
callback f plugin params nparams results nresults ptr = do
p <- peekArray (fromIntegral nparams) params
(userData, _, _, _) <- deRefStablePtr (castPtrToStablePtr ptr)
f (CurrentPlugin plugin p results (fromIntegral nresults)) userData
hostFunctionWithNamespace' ns name params results f v =
do
let g = callback f
cb <- callbackWrap g
free <- freePtrWrap freePtr
userData <- newStablePtr (v, free, cb, g)
let userDataPtr = castStablePtrToPtr userData
x <- withCString name $ \name' ->
withArray params $ \params' ->
withArray results $ \results' ->
extism_function_new name' params' nparams results' nresults cb userDataPtr free
let freeFn = extism_function_free x
case ns of
Nothing -> return ()
Just ns -> withCString ns (extism_function_set_namespace x)
fptr <- Foreign.Concurrent.newForeignPtr x freeFn
return $ Function fptr (castPtrToStablePtr userDataPtr)
where
nparams = fromIntegral $ length params
nresults = fromIntegral $ length results
-- | @hostFunction "function_name" inputTypes outputTypes callback userData@ creates a new
-- | 'Extism.Function' in the default namespace that can be called from a 'Extism.Plugin'
hostFunction :: String -> [ValType] -> [ValType] -> (CurrentPlugin -> a -> IO ()) -> a -> IO Function
hostFunction = hostFunctionWithNamespace' Nothing
-- | @hostFunction' "namespace" "function_name" inputTypes outputTypes callback userData@ creates a new
-- | 'Extism.Function' in the provided namespace that can be called from a 'Extism.Plugin'
hostFunction' :: String -> String -> [ValType] -> [ValType] -> (CurrentPlugin -> a -> IO ()) -> a -> IO Function
hostFunction' ns = hostFunctionWithNamespace' (Just ns)
-- | @newFunction' "function_name" inputTypes outputTypes userData callback@ creates a new
-- | 'Extism.Function' in the default namespace that can be called from a 'Extism.Plugin'
newFunction :: String -> [ValType] -> [ValType] -> a -> (CurrentPlugin -> a -> IO ()) -> IO Function
newFunction name params results x f = hostFunctionWithNamespace' Nothing name params results f x
-- | @newFunction' "namespace" "function_name" inputTypes outputTypes userData callback@ creates a new
-- | 'Extism.Function' in the provided namespace that can be called from a 'Extism.Plugin'
newFunction' :: String -> String -> [ValType] -> [ValType] -> a -> (CurrentPlugin -> a -> IO ()) -> IO Function
newFunction' ns name params results x f = hostFunctionWithNamespace' (Just ns) name params results f x