packages feed

extism 1.2.1.0 → 1.3.0.0

raw patch · 7 files changed

+170/−23 lines, 7 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Extism: instance GHC.Show.Show Extism.CancelHandle
- Extism: instance GHC.Show.Show Extism.LogLevel
- Extism: instance GHC.Show.Show Extism.Plugin
- Extism.Encoding: instance Data.Data.Data a => Extism.Encoding.FromBytes (Extism.Encoding.JSON a)
- Extism.Encoding: instance Data.Data.Data a => Extism.Encoding.ToBytes (Extism.Encoding.JSON a)
- Extism.Encoding: instance Extism.Encoding.FromBytes GHC.Int.Int32
- Extism.Encoding: instance Extism.Encoding.FromBytes GHC.Int.Int64
- Extism.Encoding: instance Extism.Encoding.FromBytes GHC.Word.Word32
- Extism.Encoding: instance Extism.Encoding.FromBytes GHC.Word.Word64
- Extism.Encoding: instance Extism.Encoding.ToBytes GHC.Int.Int32
- Extism.Encoding: instance Extism.Encoding.ToBytes GHC.Int.Int64
- Extism.Encoding: instance Extism.Encoding.ToBytes GHC.Word.Word32
- Extism.Encoding: instance Extism.Encoding.ToBytes GHC.Word.Word64
- Extism.Encoding: instance GHC.Show.Show Extism.Encoding.Error
- Extism.HostFunction: instance GHC.Enum.Enum Extism.HostFunction.MemoryHandle
- Extism.HostFunction: instance GHC.Num.Num Extism.HostFunction.MemoryHandle
- Extism.HostFunction: instance GHC.Real.Integral Extism.HostFunction.MemoryHandle
- Extism.HostFunction: instance GHC.Real.Real Extism.HostFunction.MemoryHandle
- Extism.HostFunction: instance GHC.Show.Show Extism.HostFunction.MemoryHandle
+ Extism: call' :: (ToBytes a, FromBytes b) => Plugin -> String -> a -> IO b
+ Extism: callWithHostContext :: (ToBytes a, FromBytes b) => Plugin -> String -> a -> c -> IO (Result b)
+ Extism: callWithHostContext' :: (ToBytes a, FromBytes b) => Plugin -> String -> a -> c -> IO b
+ Extism: instance GHC.Classes.Eq Extism.CompiledPlugin
+ Extism: instance GHC.Internal.Show.Show Extism.CancelHandle
+ Extism: instance GHC.Internal.Show.Show Extism.CompiledPlugin
+ Extism: instance GHC.Internal.Show.Show Extism.LogLevel
+ Extism: instance GHC.Internal.Show.Show Extism.Plugin
+ Extism: newCompiledPlugin :: PluginInput a => a -> [Function] -> Bool -> IO (Result CompiledPlugin)
+ Extism: newPluginFromCompiled :: CompiledPlugin -> IO (Result Plugin)
+ Extism.Encoding: instance Extism.Encoding.FromBytes GHC.Internal.Int.Int32
+ Extism.Encoding: instance Extism.Encoding.FromBytes GHC.Internal.Int.Int64
+ Extism.Encoding: instance Extism.Encoding.FromBytes GHC.Internal.Word.Word32
+ Extism.Encoding: instance Extism.Encoding.FromBytes GHC.Internal.Word.Word64
+ Extism.Encoding: instance Extism.Encoding.ToBytes GHC.Internal.Int.Int32
+ Extism.Encoding: instance Extism.Encoding.ToBytes GHC.Internal.Int.Int64
+ Extism.Encoding: instance Extism.Encoding.ToBytes GHC.Internal.Word.Word32
+ Extism.Encoding: instance Extism.Encoding.ToBytes GHC.Internal.Word.Word64
+ Extism.Encoding: instance GHC.Internal.Data.Data.Data a => Extism.Encoding.FromBytes (Extism.Encoding.JSON a)
+ Extism.Encoding: instance GHC.Internal.Data.Data.Data a => Extism.Encoding.ToBytes (Extism.Encoding.JSON a)
+ Extism.Encoding: instance GHC.Internal.Show.Show Extism.Encoding.Error
+ Extism.HostFunction: hostContext :: CurrentPlugin -> IO (Maybe a)
+ Extism.HostFunction: instance GHC.Internal.Enum.Enum Extism.HostFunction.MemoryHandle
+ Extism.HostFunction: instance GHC.Internal.Num.Num Extism.HostFunction.MemoryHandle
+ Extism.HostFunction: instance GHC.Internal.Real.Integral Extism.HostFunction.MemoryHandle
+ Extism.HostFunction: instance GHC.Internal.Real.Real Extism.HostFunction.MemoryHandle
+ Extism.HostFunction: instance GHC.Internal.Show.Show Extism.HostFunction.MemoryHandle

Files

Example.hs view
@@ -7,7 +7,7 @@ import Extism.JSON import Extism.Manifest (manifest, wasmFile) -newtype Count = Count {count :: Int} deriving (Data, Typeable, Show)+newtype Count = Count {count :: Int} deriving (Data, Show)  hello currPlugin msg = do   putStrLn . unwrap <$> input currPlugin 0
README.md view
@@ -108,7 +108,7 @@ import Extism.JSON import Extism.Manifest (manifest, wasmFile) -newtype Count = Count {count :: Int} deriving (Data, Typeable, Show)+newtype Count = Count {count :: Int} deriving (Data, Show)  hello currPlugin msg = do   putStrLn . unwrap <$> input currPlugin 0
extism.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.0 name:               extism-version:            1.2.1.0+version:            1.3.0.0 license:            BSD-3-Clause maintainer:         oss@extism.org author:             Extism authors
src/Extism.hs view
@@ -13,11 +13,16 @@     Result (..),     extismVersion,     newPlugin,+    newCompiledPlugin,+    newPluginFromCompiled,     isValid,     setConfig,     setLogFile,     functionExists,     call,+    call',+    callWithHostContext,+    callWithHostContext',     cancelHandle,     cancel,     pluginID,@@ -39,6 +44,7 @@ import Data.Word import Extism.Bindings import Extism.Encoding+import Extism.JSON import Extism.Manifest (Manifest) import Foreign.C.String import Foreign.Concurrent@@ -49,7 +55,7 @@ import Foreign.StablePtr import Foreign.Storable import GHC.Ptr-import qualified Text.JSON (Result (..), decode, encode, showJSON, toJSObject)+import qualified Text.JSON (JSValue (JSNull, JSString), Result (..), decode, encode, toJSObject, toJSString)  -- | Host function, see 'Extism.HostFunction.hostFunction' data Function = Function (ForeignPtr ExtismFunction) (StablePtr ()) deriving (Eq)@@ -57,6 +63,9 @@ -- | Plugins can be used to call WASM function newtype Plugin = Plugin (ForeignPtr ExtismPlugin) deriving (Eq, Show) +-- | CompiledPlugins can be used to create multiple plugin instances from a single pre-compiled plugin+newtype CompiledPlugin = CompiledPlugin (ForeignPtr ExtismCompiledPlugin) deriving (Eq, Show)+ -- | Cancellation handle for Plugins newtype CancelHandle = CancelHandle (Ptr ExtismCancelHandle) deriving (Eq, Show) @@ -105,11 +114,59 @@     length' = fromIntegral (B.length wasm)     wasi = fromInteger (if useWasi then 1 else 0) +-- | Create a 'Plugin' from a WASM module, `useWasi` determines if WASI should+-- | be linked+newCompiledPlugin :: (PluginInput a) => a -> [Function] -> Bool -> IO (Result CompiledPlugin)+newCompiledPlugin input functions useWasi = do+  funcs <- mapM (\(Function ptr _) -> withForeignPtr ptr return) functions+  alloca $ \e -> do+    let errmsg = (e :: Ptr CString)+    p <- unsafeUseAsCString wasm $ \s ->+      withArray funcs $ \funcs ->+        extism_compiled_plugin_new (castPtr s) length' funcs nfunctions wasi errmsg++    if p == nullPtr+      then do+        err <- peek errmsg+        e <- peekCString err+        extism_plugin_new_error_free err+        return $ Left (ExtismError e)+      else do+        ptr <- Foreign.Concurrent.newForeignPtr p (extism_compiled_plugin_free p)+        return $ Right (CompiledPlugin ptr)+  where+    wasm = pluginInput input+    nfunctions = fromIntegral (length functions)+    length' = fromIntegral (B.length wasm)+    wasi = fromInteger (if useWasi then 1 else 0)++-- | Create a new plugin from a `CompiledPlugin`+newPluginFromCompiled :: CompiledPlugin -> IO (Result Plugin)+newPluginFromCompiled (CompiledPlugin compiled) = do+  alloca $ \e -> do+    let errmsg = (e :: Ptr CString)+    p <- withForeignPtr compiled $ \c ->+      extism_plugin_new_from_compiled c errmsg+    if p == nullPtr+      then do+        err <- peek errmsg+        e <- peekCString err+        extism_plugin_new_error_free err+        return $ Left (ExtismError e)+      else do+        ptr <- Foreign.Concurrent.newForeignPtr p (extism_plugin_free p)+        return $ Right (Plugin ptr)+ -- | Same as `newPlugin` but converts the error case to an exception newPlugin' :: (PluginInput a) => a -> [Function] -> Bool -> IO Plugin newPlugin' input functions useWasi = do   unwrap <$> newPlugin input functions useWasi +-- | Same as `newPluginFromCompiled` but converts the error case to an exception+newPluginFromCompiled' :: CompiledPlugin -> IO Plugin+newPluginFromCompiled' c =+  unwrap <$> newPluginFromCompiled c+ -- | Check if a 'Plugin' is valid isValid :: Plugin -> IO Bool isValid (Plugin p) = withForeignPtr p (\x -> return (x /= nullPtr))@@ -120,9 +177,11 @@   unsafeUseAsCString bs $ \s ->     withForeignPtr plugin $ \plugin' -> do       b <- extism_plugin_config plugin' (castPtr s) length'+      print b       return $ b /= 0   where-    obj = Text.JSON.toJSObject [(k, Text.JSON.showJSON v) | (k, v) <- x]+    mk = maybe Text.JSON.JSNull (Text.JSON.JSString . Text.JSON.toJSString)+    obj = Text.JSON.toJSObject [(k, mk v) | (k, v) <- x]     bs = toByteString (Text.JSON.encode obj)     length' = fromIntegral (B.length bs) @@ -176,6 +235,37 @@ call' :: (ToBytes a, FromBytes b) => Plugin -> String -> a -> IO b call' plugin name inp = do   unwrap <$> call plugin name inp++--- | Call a function provided by the given plugin with a host context value+callWithHostContext :: (ToBytes a, FromBytes b) => Plugin -> String -> a -> c -> IO (Result b)+callWithHostContext (Plugin plugin) name inp ctx =+  withForeignPtr plugin $ \plugin' -> do+    sptr <- newStablePtr ctx+    let ptr = castStablePtrToPtr sptr+    rc <- withCString name $ \name' ->+      unsafeUseAsCString input $ \input' ->+        extism_plugin_call_with_host_context plugin' name' (castPtr input') length' ptr+    freeStablePtr sptr+    err <- extism_error plugin'+    if err /= nullPtr+      then do+        e <- peekCString err+        return $ Left (ExtismError e)+      else+        if rc == 0+          then do+            len <- extism_plugin_output_length plugin'+            Ptr ptr <- extism_plugin_output_data plugin'+            x <- unsafePackLenAddress (fromIntegral len) ptr+            return $ fromBytes x+          else return $ Left (ExtismError "Call failed")+  where+    input = toBytes inp+    length' = fromIntegral (B.length input)++callWithHostContext' :: (ToBytes a, FromBytes b) => Plugin -> String -> a -> c -> IO b+callWithHostContext' plugin name inp ctx = do+  unwrap <$> callWithHostContext plugin name inp ctx  -- | Create a new 'CancelHandle' that can be used to cancel a running plugin -- | from another thread.
src/Extism/Bindings.hs view
@@ -24,6 +24,8 @@  newtype ExtismCurrentPlugin = ExtismCurrentPlugin () deriving (Show) +newtype ExtismCompiledPlugin = ExtismCompiledPlugin () deriving (Show)+ -- | Low-level Wasm types data ValType = I32 | I64 | F32 | F64 | V128 | FuncRef | ExternRef deriving (Show, Eq) @@ -94,10 +96,22 @@   extism_plugin_new ::     Ptr Word8 -> Word64 -> Ptr (Ptr ExtismFunction) -> Word64 -> CBool -> Ptr CString -> IO (Ptr ExtismPlugin) +foreign import ccall safe "extism.h extism_plugin_new_from_compiled"+  extism_plugin_new_from_compiled ::+    Ptr ExtismCompiledPlugin -> Ptr CString -> IO (Ptr ExtismPlugin)++foreign import ccall safe "extism.h extism_compiled_plugin_new"+  extism_compiled_plugin_new ::+    Ptr Word8 -> Word64 -> Ptr (Ptr ExtismFunction) -> Word64 -> CBool -> Ptr CString -> IO (Ptr ExtismCompiledPlugin)+ foreign import ccall safe "extism.h extism_plugin_call"   extism_plugin_call ::     Ptr ExtismPlugin -> CString -> Ptr Word8 -> Word64 -> IO Int32 +foreign import ccall safe "extism.h extism_plugin_call_with_host_context"+  extism_plugin_call_with_host_context ::+    Ptr ExtismPlugin -> CString -> Ptr Word8 -> Word64 -> Ptr () -> IO Int32+ foreign import ccall safe "extism.h extism_plugin_function_exists"   extism_plugin_function_exists ::     Ptr ExtismPlugin -> CString -> IO CBool@@ -126,6 +140,10 @@   extism_plugin_free ::     Ptr ExtismPlugin -> IO () +foreign import ccall safe "extism.h extism_compiled_plugin_free"+  extism_compiled_plugin_free ::+    Ptr ExtismCompiledPlugin -> IO ()+ foreign import ccall safe "extism.h extism_plugin_reset"   extism_plugin_reset ::     Ptr ExtismPlugin -> IO ()@@ -165,6 +183,10 @@ foreign import ccall safe "extism.h extism_current_plugin_memory"   extism_current_plugin_memory ::     Ptr ExtismCurrentPlugin -> IO (Ptr Word8)++foreign import ccall safe "extism.h extism_current_plugin_host_context"+  extism_current_plugin_host_context ::+    Ptr ExtismCurrentPlugin -> IO (Ptr ())  foreign import ccall safe "extism.h extism_current_plugin_memory_alloc"   extism_current_plugin_memory_alloc ::
src/Extism/HostFunction.hs view
@@ -35,6 +35,7 @@     output,     getParams,     setResults,+    hostContext,     ptr,   ) where@@ -199,6 +200,15 @@ 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@@ -226,22 +236,22 @@     nparams = fromIntegral $ length params     nresults = fromIntegral $ length results --- | 'hostFunction "function_name" inputTypes outputTypes callback userData' creates a new--- | 'Function' in the default namespace that can be called from a 'Plugin'+-- | @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--- | 'Function' in the provided namespace that can be called from a 'Plugin'+-- | @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 callback userData' creates a new--- | 'Function' in the default namespace that can be called from a 'Plugin'+-- | @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 callback userData' creates a new--- | 'Function' in the provided namespace that can be called from a 'Plugin'+-- | @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
test/Test.hs view
@@ -1,5 +1,8 @@+{-# LANGUAGE DeriveDataTypeable #-}+ import Extism import Extism.HostFunction+import Extism.JSON import Extism.Manifest import Test.HUnit @@ -7,9 +10,9 @@ assertUnwrap (Left (ExtismError msg)) =   assertFailure msg -defaultManifest = manifest [wasmFile "../wasm/code.wasm"]+defaultManifest = manifest [wasmFile "wasm/code.wasm"] -hostFunctionManifest = manifest [wasmFile "../wasm/code-functions.wasm"]+hostFunctionManifest = manifest [wasmFile "wasm/code-functions.wasm"]  initPlugin :: IO Plugin initPlugin =@@ -24,23 +27,38 @@  checkCallResult p = do   res <- call p "count_vowels" "this is a test" >>= assertUnwrap-  assertEqual "count vowels output" "{\"count\": 4}" res+  assertEqual "count vowels output" "{\"count\":4,\"total\":4,\"vowels\":\"aeiouAEIOU\"}" res  pluginCall = do   p <- initPlugin   checkCallResult p -hello plugin () = do-  s <- unwrap <$> input plugin 0-  assertEqual "host function input" "{\"count\": 4}" s+newtype Count = Count {count :: Int} deriving (Data, Show)++hello currPlugin msg = do+  putStrLn . unwrap <$> input currPlugin 0   putStrLn "Hello from Haskell!"-  output plugin 0 "{\"count\": 999}"+  putStrLn msg+  output currPlugin 0 $ JSON $ Count 999  pluginCallHostFunction = do-  p <- Extism.newPlugin hostFunctionManifest [] False >>= assertUnwrap+  f <- newFunction "hello_world" [ptr] [ptr] "Hello, again" hello+  p <- Extism.newPlugin hostFunctionManifest [f] True >>= assertUnwrap   res <- call p "count_vowels" "this is a test" >>= assertUnwrap-  assertEqual "count vowels output" "{\"count\": 999}" res+  assertEqual "count vowels output" "{\"count\":999}" res +helloContext currPlugin msg = do+  ctx <- hostContext currPlugin+  case ctx of+    Nothing -> assertBool "Expected host context" False+    Just s -> putStrLn s+  output currPlugin 0 msg++pluginCallHostContext = do+  f <- newFunction "hello_world" [ptr] [ptr] "Hello, again" helloContext+  p <- Extism.newPlugin hostFunctionManifest [f] True >>= assertUnwrap+  callWithHostContext p "count_vowels" "this is a test" "host context" >>= assertUnwrap+ pluginMultiple = do   p <- initPlugin   checkCallResult p@@ -58,6 +76,11 @@   b <- setLogFile "stderr" Extism.LogError   assertBool "set log file" b +testCompiledPlugin = do+  c <- Extism.newCompiledPlugin defaultManifest [] False >>= assertUnwrap+  p <- Extism.newPluginFromCompiled c >>= assertUnwrap+  checkCallResult p+ t name f = TestLabel name (TestCase f)  main = do@@ -66,8 +89,10 @@         [ t "Plugin.FunctionExists" pluginFunctionExists,           t "Plugin.Call" pluginCall,           t "Plugin.CallHostFunction" pluginCallHostFunction,+          t "Plugin.CallHostContext" pluginCallHostContext,           t "Plugin.Multiple" pluginMultiple,           t "Plugin.Config" pluginConfig,-          t "SetLogFile" testSetLogFile+          t "SetLogFile" testSetLogFile,+          t "CompiledPlugin" testCompiledPlugin         ]     )