packages feed

extism 1.2.0.3 → 1.2.1.0

raw patch · 7 files changed

+217/−166 lines, 7 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Extism: class (ToBytes a, FromBytes a) => Encoding a
+ Extism: class PluginInput a
+ Extism: pluginInput :: PluginInput a => a -> ByteString
+ Extism: reset :: Plugin -> IO ()
+ Extism.HostFunction: data Function
+ Extism.HostFunction: newFunction :: String -> [ValType] -> [ValType] -> a -> (CurrentPlugin -> a -> IO ()) -> IO Function
+ Extism.HostFunction: newFunction' :: String -> String -> [ValType] -> [ValType] -> a -> (CurrentPlugin -> a -> IO ()) -> IO Function

Files

CHANGELOG.md view
@@ -1,5 +1,9 @@ # Revision history for extism +## 1.2.1.0 -- 2024-04-22++* Added `newFunction`+ ## 1.0.0.0 -- 2024-01-08  * Extism 1.0 compatible release
Example.hs view
@@ -13,12 +13,12 @@   putStrLn . unwrap <$> input currPlugin 0   putStrLn "Hello from Haskell!"   putStrLn msg-  output currPlugin 0 (JSON $ Count 999)+  output currPlugin 0 $ JSON $ Count 999  main = do   setLogFile "stdout" LogError   let m = manifest [wasmFile "wasm/code-functions.wasm"]-  f <- hostFunction "hello_world" [ptr] [ptr] hello "Hello, again"+  f <- newFunction "hello_world" [ptr] [ptr] "Hello, again" hello   plugin <- unwrap <$> newPlugin m [f] True   id <- pluginID plugin   print id
README.md view
@@ -38,10 +38,12 @@ import Extism  main = do-  let wasm = wasmURL "GET" "https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm"   plugin <- unwrap <$> newPlugin (manifest [wasm]) [] True   res <- unwrap <$> call plugin "count_vowels" "Hello, world!"   putStrLn res+  where+    wasm = wasmURL "GET" "https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm"+ -- Prints: {"count":3,"total":3,"vowels":"aeiouAEIOU"}" ``` @@ -116,13 +118,14 @@  main = do   setLogFile "stdout" LogError-  let m = manifest [wasmFile "wasm/code-functions.wasm"]-  f <- hostFunction "hello_world" [ptr] [ptr] hello "Hello, again"+  f <- newFunction "hello_world" [ptr] [ptr] "Hello, again" hello   plugin <- unwrap <$> newPlugin m [f] True   id <- pluginID plugin   print id   JSON res <- (unwrap <$> call plugin "count_vowels" "this is a test" :: IO (JSON Count))   print res+  where+    m = manifest [wasmFile "wasm/code-functions.wasm"] -- Prints: Count {count = 999} ``` 
extism.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.0 name:               extism-version:            1.2.0.3+version:            1.2.1.0 license:            BSD-3-Clause maintainer:         oss@extism.org author:             Extism authors
src/Extism.hs view
@@ -4,6 +4,7 @@ -- Requires a libextism installation, see [https://extism.org/docs/install](https://extism.org/docs/install) module Extism   ( module Extism.Manifest,+    module Extism.Encoding,     Function (..),     Plugin (..),     CancelHandle (..),@@ -22,9 +23,10 @@     pluginID,     unwrap,     ToBytes (..),-    Encoding,     FromBytes (..),     JSON (..),+    PluginInput (..),+    reset,   ) where @@ -80,37 +82,34 @@ -- | Create a 'Plugin' from a WASM module, `useWasi` determines if WASI should -- | be linked newPlugin :: (PluginInput a) => a -> [Function] -> Bool -> IO (Result Plugin)-newPlugin input functions useWasi =-  let wasm = pluginInput input-   in let nfunctions = fromIntegral (length functions)-       in let length' = fromIntegral (B.length wasm)-           in let wasi = fromInteger (if useWasi then 1 else 0)-               in 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_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_plugin_free p)-                              return $ Right (Plugin ptr)-                      )+newPlugin 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_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_plugin_free p)+        return $ Right (Plugin ptr)+  where+    wasm = pluginInput input+    nfunctions = fromIntegral (length functions)+    length' = fromIntegral (B.length wasm)+    wasi = fromInteger (if useWasi then 1 else 0)++-- | 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+ -- | Check if a 'Plugin' is valid isValid :: Plugin -> IO Bool isValid (Plugin p) = withForeignPtr p (\x -> return (x /= nullPtr))@@ -118,19 +117,14 @@ -- | Set configuration values for a plugin setConfig :: Plugin -> [(String, Maybe String)] -> IO Bool setConfig (Plugin plugin) x =-  let obj = Text.JSON.toJSObject [(k, Text.JSON.showJSON v) | (k, v) <- x]-   in let bs = toByteString (Text.JSON.encode obj)-       in let length' = fromIntegral (B.length bs)-           in unsafeUseAsCString-                bs-                ( \s ->-                    withForeignPtr-                      plugin-                      ( \plugin' -> do-                          b <- extism_plugin_config plugin' (castPtr s) length'-                          return $ b /= 0-                      )-                )+  unsafeUseAsCString bs $ \s ->+    withForeignPtr plugin $ \plugin' -> do+      b <- extism_plugin_config plugin' (castPtr s) length'+      return $ b /= 0+  where+    obj = Text.JSON.toJSObject [(k, Text.JSON.showJSON v) | (k, v) <- x]+    bs = toByteString (Text.JSON.encode obj)+    length' = fromIntegral (B.length bs)  levelStr LogError = "error" levelStr LogDebug = "debug"@@ -141,61 +135,48 @@ -- | Set the log file and level, this is a global configuration setLogFile :: String -> LogLevel -> IO Bool setLogFile filename level =-  let s = levelStr level-   in withCString-        filename-        ( \f ->-            withCString-              s-              ( \l -> do-                  b <- extism_log_file f l-                  return $ b /= 0-              )-        )+  withCString filename $ \f ->+    withCString s $ \l -> do+      b <- extism_log_file f l+      return $ b /= 0+  where+    s = levelStr level  -- | Check if a function exists in the given plugin functionExists :: Plugin -> String -> IO Bool functionExists (Plugin plugin) name =-  withForeignPtr-    plugin-    ( \plugin' -> do-        b <- withCString name (extism_plugin_function_exists plugin')-        if b == 1 then return True else return False-    )+  withForeignPtr plugin $ \plugin' -> do+    b <- withCString name (extism_plugin_function_exists plugin')+    if b == 1 then return True else return False  --- | Call a function provided by the given plugin call :: (ToBytes a, FromBytes b) => Plugin -> String -> a -> IO (Result b) call (Plugin plugin) name inp =-  let input = toBytes inp-   in let length' = fromIntegral (B.length input)-       in withForeignPtr-            plugin-            ( \plugin' -> do-                rc <--                  withCString-                    name-                    ( \name' ->-                        unsafeUseAsCString-                          input-                          ( \input' ->-                              extism_plugin_call plugin' name' (castPtr input') length'-                          )-                    )-                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")-            )+  withForeignPtr plugin $ \plugin' -> do+    rc <- withCString name $ \name' ->+      unsafeUseAsCString input $ \input' ->+        extism_plugin_call plugin' name' (castPtr input') length'+    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) +call' :: (ToBytes a, FromBytes b) => Plugin -> String -> a -> IO b+call' plugin name inp = do+  unwrap <$> call plugin name inp+ -- | Create a new 'CancelHandle' that can be used to cancel a running plugin -- | from another thread. cancelHandle :: Plugin -> IO CancelHandle@@ -210,15 +191,16 @@  pluginID :: Plugin -> IO Data.UUID.UUID pluginID (Plugin plugin) =-  withForeignPtr-    plugin-    ( \plugin' -> do-        ptr <- extism_plugin_id plugin'-        buf <- B.packCStringLen (castPtr ptr, 16)-        case Data.UUID.fromByteString (BL.fromStrict buf) of-          Nothing -> error "Invalid Plugin ID"-          Just x -> return x-    )+  withForeignPtr plugin $ \plugin' -> do+    ptr <- extism_plugin_id plugin'+    buf <- B.packCStringLen (castPtr ptr, 16)+    case Data.UUID.fromByteString (BL.fromStrict buf) of+      Nothing -> error "Invalid Plugin ID"+      Just x -> return x++reset :: Plugin -> IO ()+reset (Plugin plugin) =+  withForeignPtr plugin extism_plugin_reset  unwrap (Right x) = x unwrap (Left (ExtismError msg)) =
src/Extism/Bindings.hs view
@@ -90,47 +90,93 @@   poke ptr x =     pokeByteOff ptr 0 (intOfValType x) -foreign import ccall safe "extism.h extism_plugin_new" 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"+  extism_plugin_new ::+    Ptr Word8 -> Word64 -> Ptr (Ptr ExtismFunction) -> Word64 -> CBool -> Ptr CString -> IO (Ptr ExtismPlugin) -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"+  extism_plugin_call ::+    Ptr ExtismPlugin -> CString -> Ptr Word8 -> Word64 -> IO Int32 -foreign import ccall safe "extism.h extism_plugin_function_exists" extism_plugin_function_exists :: Ptr ExtismPlugin -> CString -> IO CBool+foreign import ccall safe "extism.h extism_plugin_function_exists"+  extism_plugin_function_exists ::+    Ptr ExtismPlugin -> CString -> IO CBool -foreign import ccall safe "extism.h extism_plugin_error" extism_error :: Ptr ExtismPlugin -> IO CString+foreign import ccall safe "extism.h extism_plugin_error"+  extism_error ::+    Ptr ExtismPlugin -> IO CString -foreign import ccall safe "extism.h extism_plugin_output_length" extism_plugin_output_length :: Ptr ExtismPlugin -> IO Word64+foreign import ccall safe "extism.h extism_plugin_output_length"+  extism_plugin_output_length ::+    Ptr ExtismPlugin -> IO Word64 -foreign import ccall safe "extism.h extism_plugin_output_data" extism_plugin_output_data :: Ptr ExtismPlugin -> IO (Ptr Word8)+foreign import ccall safe "extism.h extism_plugin_output_data"+  extism_plugin_output_data ::+    Ptr ExtismPlugin -> IO (Ptr Word8) -foreign import ccall safe "extism.h extism_log_file" extism_log_file :: CString -> CString -> IO CBool+foreign import ccall safe "extism.h extism_log_file"+  extism_log_file ::+    CString -> CString -> IO CBool -foreign import ccall safe "extism.h extism_plugin_config" extism_plugin_config :: Ptr ExtismPlugin -> Ptr Word8 -> Int64 -> IO CBool+foreign import ccall safe "extism.h extism_plugin_config"+  extism_plugin_config ::+    Ptr ExtismPlugin -> Ptr Word8 -> Int64 -> IO CBool -foreign import ccall safe "extism.h extism_plugin_free" extism_plugin_free :: Ptr ExtismPlugin -> IO ()+foreign import ccall safe "extism.h extism_plugin_free"+  extism_plugin_free ::+    Ptr ExtismPlugin -> IO () -foreign import ccall safe "extism.h extism_plugin_new_error_free" extism_plugin_new_error_free :: CString -> IO ()+foreign import ccall safe "extism.h extism_plugin_reset"+  extism_plugin_reset ::+    Ptr ExtismPlugin -> IO () -foreign import ccall safe "extism.h extism_version" extism_version :: IO CString+foreign import ccall safe "extism.h extism_plugin_new_error_free"+  extism_plugin_new_error_free ::+    CString -> IO () -foreign import ccall safe "extism.h extism_plugin_id" extism_plugin_id :: Ptr ExtismPlugin -> IO (Ptr Word8)+foreign import ccall safe "extism.h extism_version"+  extism_version ::+    IO CString -foreign import ccall safe "extism.h extism_plugin_cancel_handle" extism_plugin_cancel_handle :: Ptr ExtismPlugin -> IO (Ptr ExtismCancelHandle)+foreign import ccall safe "extism.h extism_plugin_id"+  extism_plugin_id ::+    Ptr ExtismPlugin -> IO (Ptr Word8) -foreign import ccall safe "extism.h extism_plugin_cancel" extism_plugin_cancel :: Ptr ExtismCancelHandle -> IO Bool+foreign import ccall safe "extism.h extism_plugin_cancel_handle"+  extism_plugin_cancel_handle ::+    Ptr ExtismPlugin -> IO (Ptr ExtismCancelHandle) -foreign import ccall safe "extism.h extism_function_new" extism_function_new :: CString -> Ptr ValType -> Word64 -> Ptr ValType -> Word64 -> FunPtr CCallback -> Ptr () -> FunPtr FreeCallback -> IO (Ptr ExtismFunction)+foreign import ccall safe "extism.h extism_plugin_cancel"+  extism_plugin_cancel ::+    Ptr ExtismCancelHandle -> IO Bool -foreign import ccall safe "extism.h extism_function_free" extism_function_free :: Ptr ExtismFunction -> IO ()+foreign import ccall safe "extism.h extism_function_new"+  extism_function_new ::+    CString -> Ptr ValType -> Word64 -> Ptr ValType -> Word64 -> FunPtr CCallback -> Ptr () -> FunPtr FreeCallback -> IO (Ptr ExtismFunction) -foreign import ccall safe "extism.h extism_function_set_namespace" extism_function_set_namespace :: Ptr ExtismFunction -> CString -> IO ()+foreign import ccall safe "extism.h extism_function_free"+  extism_function_free ::+    Ptr ExtismFunction -> IO () -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_function_set_namespace"+  extism_function_set_namespace ::+    Ptr ExtismFunction -> CString -> IO () -foreign import ccall safe "extism.h extism_current_plugin_memory_alloc" extism_current_plugin_memory_alloc :: Ptr ExtismCurrentPlugin -> Word64 -> IO Word64+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_memory_length" extism_current_plugin_memory_length :: Ptr ExtismCurrentPlugin -> Word64 -> IO Word64+foreign import ccall safe "extism.h extism_current_plugin_memory_alloc"+  extism_current_plugin_memory_alloc ::+    Ptr ExtismCurrentPlugin -> Word64 -> IO Word64 -foreign import ccall safe "extism.h extism_current_plugin_memory_free" extism_current_plugin_memory_free :: Ptr ExtismCurrentPlugin -> Word64 -> IO ()+foreign import ccall safe "extism.h extism_current_plugin_memory_length"+  extism_current_plugin_memory_length ::+    Ptr ExtismCurrentPlugin -> Word64 -> IO Word64++foreign import ccall safe "extism.h extism_current_plugin_memory_free"+  extism_current_plugin_memory_free ::+    Ptr ExtismCurrentPlugin -> Word64 -> IO ()  freePtr ptr = do   let s = castPtrToStablePtr ptr
src/Extism/HostFunction.hs view
@@ -7,6 +7,7 @@     ValType (..),     Val (..),     MemoryHandle,+    Function,     memoryAlloc,     memoryLength,     memoryFree,@@ -28,6 +29,8 @@     fromF64,     hostFunction,     hostFunction',+    newFunction,+    newFunction',     input,     output,     getParams,@@ -101,6 +104,12 @@   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@@ -168,22 +177,28 @@  output :: (ToBytes a) => CurrentPlugin -> Int -> a -> IO () output !p !index !x =-  let CurrentPlugin _ _ !res !len = p-   in do-        mem <- alloc p x-        if index >= len-          then return ()-          else pokeElemOff res index (toI64 mem)+  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 =-  let (CurrentPlugin _ params _ _) = plugin-   in let x = fromI64 (params !! index) :: Maybe Word64-       in case x of-            Nothing -> return $ Left (ExtismError "invalid parameter")-            Just offs -> do-              memoryGet plugin (MemoryHandle offs)+  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+ 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@@ -191,34 +206,25 @@   f (CurrentPlugin plugin p results (fromIntegral nresults)) userData  hostFunctionWithNamespace' ns name params results f v =-  let nparams = fromIntegral $ length params-   in let nresults = fromIntegral $ length results-       in 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)+  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 -- | 'Function' in the default namespace that can be called from a 'Plugin'@@ -229,3 +235,13 @@ -- | 'Function' in the provided namespace that can be called from a '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 :: 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' :: 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