Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cleanup: usability/README improvements #13

Merged
merged 3 commits into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions Example.hs
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
{-# LANGUAGE DeriveDataTypeable #-}

module Main where

import Extism
import Extism.HostFunction
import Extism.JSON
import Extism.Manifest (manifest, wasmFile)

newtype Count = Count {count :: Int} deriving (Data, Typeable, Show)

hello currPlugin msg = do
putStrLn . unwrap <$> input currPlugin 0
putStrLn "Hello from Haskell!"
putStrLn msg
output currPlugin 0 "{\"count\": 999}"
output currPlugin 0 (JSON $ Count 999)

main = do
setLogFile "stdout" LogError
Expand All @@ -17,5 +22,5 @@ main = do
plugin <- unwrap <$> newPlugin m [f] True
id <- pluginID plugin
print id
res <- unwrap <$> call plugin "count_vowels" "this is a test"
putStrLn res
JSON res <- (unwrap <$> call plugin "count_vowels" "this is a test" :: IO (JSON Count))
print res
24 changes: 15 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,27 +99,33 @@ In this example, we want to expose a single function to our plugin (in Haskell t
Let's load the manifest like usual but load up `wasm/code-functions.wasm` plug-in:

```haskell
{-# LANGUAGE DeriveDataTypeable #-}

module Main where

import Extism
import Extism.HostFunction
import Extism.JSON
import Extism.Manifest (manifest, wasmFile)

hello currPlugin () = do
newtype Count = Count {count :: Int} deriving (Data, Typeable, Show)

hello currPlugin msg = do
putStrLn . unwrap <$> input currPlugin 0
putStrLn "Hello from Haskell!"
output currPlugin 0 "{\"count\": 999}"
putStrLn msg
output currPlugin 0 (JSON $ Count 999)

main = do
setLogFile "stdout" LogError -- Enable logging to stdout
setLogFile "stdout" LogError
let m = manifest [wasmFile "wasm/code-functions.wasm"]
f <- hostFunction "hello_world" [I64] [I64] hello () -- Create host function, the final argument
-- is a userData argument that can store any
-- Haskell value
f <- hostFunction "hello_world" [I64] [I64] hello "Hello, again"
plugin <- unwrap <$> newPlugin m [f] True
res <- unwrap <$> call plugin "count_vowels" "this is a test"
putStrLn res
-- Prints: {"count": 999}
id <- pluginID plugin
print id
JSON res <- (unwrap <$> call plugin "count_vowels" "this is a test" :: IO (JSON Count))
print res
-- Prints: Count {count = 999}
```

> *Note*: In order to write host functions you should get familiar with the methods on the [Extism.HostFunction](https://hackage.haskell.org/package/extism/docs/Extism-HostFunction.html) module.
4 changes: 2 additions & 2 deletions extism.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ category: Plugins, WebAssembly
extra-doc-files: cabal.project README.md CHANGELOG.md

library
exposed-modules: Extism Extism.HostFunction Extism.Encoding
reexported-modules: Extism.Manifest
exposed-modules: Extism, Extism.HostFunction, Extism.Encoding
reexported-modules: Extism.Manifest, Extism.JSON
hs-source-dirs: src
other-modules: Extism.Bindings
default-language: Haskell2010
Expand Down
4 changes: 2 additions & 2 deletions manifest/Extism/JSON.hs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module Extism.JSON
( module Extism.JSON,
module Text.JSON,
module Text.JSON.Generic,
Data,
Typeable,
)
where

Expand Down
21 changes: 18 additions & 3 deletions manifest/Extism/Manifest.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ module Extism.Manifest where
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as BS (unpack)
import Extism.JSON
import Text.JSON
import Text.JSON.Generic

-- | Memory options
newtype Memory = Memory
Expand Down Expand Up @@ -235,6 +237,19 @@ withMaxPages :: Manifest -> Int -> Manifest
withMaxPages m pages =
m {memory = NotNull $ Memory (NotNull pages)}

toString :: (JSON a) => a -> String
toString v =
encode (showJSON v)
fromString :: String -> Either String Manifest
fromString s = do
let x = decode s
resultToEither x

fromFile :: FilePath -> IO (Either String Manifest)
fromFile path = do
s <- readFile path
return $ fromString s

toString :: Manifest -> String
toString = encode

toFile :: FilePath -> Manifest -> IO ()
toFile path m = do
writeFile path (toString m)
7 changes: 3 additions & 4 deletions src/Extism.hs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ module Extism
ToBytes (..),
Encoding,
FromBytes (..),
JSONValue (..),
JSON (..),
)
where

Expand All @@ -37,8 +37,7 @@ import qualified Data.UUID (UUID, fromByteString, toString)
import Data.Word
import Extism.Bindings
import Extism.Encoding
import qualified Extism.JSON (JSON (..))
import Extism.Manifest (Manifest, toString)
import Extism.Manifest (Manifest)
import Foreign.C.String
import Foreign.Concurrent
import Foreign.ForeignPtr
Expand Down Expand Up @@ -76,7 +75,7 @@ instance PluginInput B.ByteString where
pluginInput = id

instance PluginInput Manifest where
pluginInput m = toByteString $ toString m
pluginInput m = toByteString $ Text.JSON.encode m

-- | Create a 'Plugin' from a WASM module, `useWasi` determines if WASI should
-- | be linked
Expand Down
28 changes: 16 additions & 12 deletions src/Extism/Encoding.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ module Extism.Encoding
ToBytes (..),
FromBytes (..),
Encoding (..),
JSONValue (..),
JSON (..),
)
where

Expand All @@ -20,8 +20,8 @@ import qualified Data.ByteString as B
import Data.ByteString.Internal (c2w, unsafePackLenAddress, w2c)
import Data.Int
import Data.Word
import qualified Extism.JSON (JSON (..))
import qualified Text.JSON (Result (..), decode, encode, showJSON, toJSObject)
import qualified Text.JSON (JSValue, Result (..), decode, encode, showJSON, toJSObject)
import qualified Text.JSON.Generic (Data, decodeJSON, encodeJSON, fromJSON, toJSON)

-- | Helper function to convert a 'String' to a 'ByteString'
toByteString :: String -> B.ByteString
Expand Down Expand Up @@ -122,14 +122,18 @@ instance FromBytes Double where
Right (_, _, x) -> Right x

-- Wraps a `JSON` value for input/output
newtype JSONValue x = JSONValue x
newtype JSON x = JSON x

instance (Extism.JSON.JSON a) => ToBytes (JSONValue a) where
toBytes (JSONValue x) =
toByteString $ Text.JSON.encode x
instance (Text.JSON.Generic.Data a) => ToBytes (JSON a) where
toBytes (JSON x) =
toByteString $ Text.JSON.Generic.encodeJSON x

instance (Extism.JSON.JSON a) => FromBytes (JSONValue a) where
fromBytes bs = do
case Text.JSON.decode (fromByteString bs) of
Text.JSON.Error x -> Left (ExtismError x)
Text.JSON.Ok x -> Right (JSONValue x)
instance (Text.JSON.Generic.Data a) => FromBytes (JSON a) where
fromBytes bs =
let x = Text.JSON.decode (fromByteString bs)
in case x of
Text.JSON.Error e -> Left (ExtismError e)
Text.JSON.Ok x ->
case Text.JSON.Generic.fromJSON (x :: Text.JSON.JSValue) of
Text.JSON.Error e -> Left (ExtismError e)
Text.JSON.Ok x -> Right (JSON x)
Loading