Work on plugin builder

Isaac Clayton created

Change summary

crates/plugin_macros/Cargo.toml   |  3 
crates/plugin_macros/src/lib.rs   | 84 +++++++++++++++++++++-----------
crates/plugin_runtime/src/lib.rs  | 12 ++++
crates/plugin_runtime/src/wasi.rs | 33 +++++++-----
plugins/json_language/src/lib.rs  | 23 ++++----
5 files changed, 98 insertions(+), 57 deletions(-)

Detailed changes

crates/plugin_macros/Cargo.toml 🔗

@@ -7,7 +7,8 @@ edition = "2021"
 proc-macro = true
 
 [dependencies]
-syn = { version = "1.0", features = ["full"] }
+# TODO: remove "extra-traits"
+syn = { version = "1.0", features = ["full",  "extra-traits"] }
 quote = "1.0"
 proc-macro2 = "1.0"
 serde = "1.0"

crates/plugin_macros/src/lib.rs 🔗

@@ -2,7 +2,7 @@ use core::panic;
 
 use proc_macro::TokenStream;
 use quote::{format_ident, quote};
-use syn::{parse_macro_input, FnArg, ItemFn, Type, Visibility};
+use syn::{parse_macro_input, FnArg, ForeignItemFn, ItemFn, Type, Visibility};
 
 #[proc_macro_attribute]
 pub fn export(args: TokenStream, function: TokenStream) -> TokenStream {
@@ -11,6 +11,11 @@ pub fn export(args: TokenStream, function: TokenStream) -> TokenStream {
     }
 
     let inner_fn = parse_macro_input!(function as ItemFn);
+
+    if !inner_fn.sig.generics.params.is_empty() {
+        panic!("Exported functions can not take generic parameters");
+    }
+
     if let Visibility::Public(_) = inner_fn.vis {
     } else {
         panic!("The export attribute only works for public functions");
@@ -77,15 +82,27 @@ pub fn export(args: TokenStream, function: TokenStream) -> TokenStream {
 
 #[proc_macro_attribute]
 pub fn import(args: TokenStream, function: TokenStream) -> TokenStream {
-    todo!()
-    //     if !args.is_empty() {
-    //         panic!("The import attribute does not take any arguments");
-    //     }
+    if !args.is_empty() {
+        panic!("The import attribute does not take any arguments");
+    }
 
-    //     let inner_fn = parse_macro_input!(function as ItemFn);
+    let fn_declare = parse_macro_input!(function as ForeignItemFn);
 
-    //     let inner_fn_name = format_ident!("{}", inner_fn.sig.ident);
-    //     // let outer_fn_name = format_ident!("__{}", inner_fn_name);
+    if !fn_declare.sig.generics.params.is_empty() {
+        panic!("Exported functions can not take generic parameters");
+    }
+
+    dbg!(&fn_declare.sig);
+
+    // let inner_fn = ItemFn {
+    //     attrs: fn_declare.attrs,
+    //     vis: fn_declare.vis,
+    //     sig: fn_declare.sig,
+    //     block: todo!(),
+    // };
+
+    // let inner_fn_name = format_ident!("{}", inner_fn.sig.ident);
+    // let outer_fn_name = format_ident!("__{}", inner_fn_name);
 
     //     let variadic = inner_fn.sig.inputs.len();
     //     let i = (0..variadic).map(syn::Index::from);
@@ -116,28 +133,35 @@ pub fn import(args: TokenStream, function: TokenStream) -> TokenStream {
     //         (quote! { data }, quote! { #ty })
     //     };
 
-    //     TokenStream::from(quote! {
+    // TokenStream::from(quote! {
+    //     extern "C" {
     //         #[no_mangle]
-    //         #inner_fn
+    //         fn #outer_fn_name(ptr: *const u8, len: usize) -> *const ::plugin::__Buffer;
+    //     }
 
-    //         #[no_mangle]
-    //         pub extern "C" fn #outer_fn_name(ptr: *const u8, len: usize) -> *const ::plugin::__Buffer {
-    //             // setup
-    //             let buffer = ::plugin::__Buffer { ptr, len };
-    //             let data = unsafe { buffer.to_vec() };
-
-    //             // operation
-    //             let data: #ty = match ::plugin::bincode::deserialize(&data) {
-    //                 Ok(d) => d,
-    //                 Err(e) => panic!("Data passed to function not deserializable."),
-    //             };
-    //             let result = #inner_fn_name(#args);
-    //             let new_data: Result<Vec<u8>, _> = ::plugin::bincode::serialize(&result);
-    //             let new_data = new_data.unwrap();
-
-    //             // teardown
-    //             let new_buffer = unsafe { ::plugin::__Buffer::from_vec(new_data) };
-    //             return new_buffer.leak_to_heap();
-    //         }
-    //     })
+    //     #[no_mangle]
+    //     fn #inner_fn_name #params -> #output {
+    //         println!("executing command: {}", string);
+    //         // serialize data
+    //         let data = #collect_params;
+    //         let data = ::plugin::bincode::serialize(&data).unwrap();
+    //         let buffer = unsafe { ::plugin::__Buffer::from_vec(data) };
+    //         let ptr = buffer.ptr;
+    //         let len = buffer.len;
+    //         // leak data to heap
+    //         buffer.leak_to_heap();
+    //         // call extern function
+    //         let result = unsafe { __command(ptr, len) };
+    //         // get result
+    //         let result = todo!(); // convert into box
+
+    //         // deserialize data
+    //         let data: Option<String> = match ::plugin::bincode::deserialize(&data) {
+    //             Ok(d) => d,
+    //             Err(e) => panic!("Data passed to function not deserializable."),
+    //         };
+    //         return data;
+    //     }
+    // })
+    todo!()
 }

crates/plugin_runtime/src/lib.rs 🔗

@@ -1,2 +1,14 @@
 pub mod wasi;
 pub use wasi::*;
+
+// #[cfg(test)]
+// mod tests {
+//     use super::*;
+
+//     pub fn init_wasi() {
+//         let plugin = WasiPluginBuilder::new().init(todo!()).unwrap();
+//         let handle: WasiFn<u32, String> = plugin.function("hello").unwrap();
+//         let result = plugin.call(handle, 27).unwrap();
+//         assert_eq!(result, "world 27");
+//     }
+// }

crates/plugin_runtime/src/wasi.rs 🔗

@@ -72,7 +72,7 @@ pub struct Wasi {
 pub type HostFunction = Box<dyn IntoFunc<WasiCtx, (u32, u32), u32>>;
 
 pub struct WasiPluginBuilder {
-    host_functions: HashMap<String, HostFunction>,
+    host_functions: HashMap<String, Box<dyn Fn(&str, &mut Linker<WasiCtx>) -> Result<(), Error>>>,
     wasi_ctx_builder: WasiCtxBuilder,
 }
 
@@ -90,22 +90,22 @@ impl WasiPluginBuilder {
         this
     }
 
-    fn wrap_host_function<A: Serialize, R: DeserializeOwned>(
-        function: impl Fn(A) -> R + Send + Sync + 'static,
-    ) -> HostFunction {
-        Box::new(move |ptr, len| {
-            function(todo!());
-            todo!()
-        })
-    }
-
     pub fn host_function<A: Serialize, R: DeserializeOwned>(
         mut self,
         name: &str,
-        function: impl Fn(A) -> R + Send + Sync + 'static,
+        function: &dyn Fn(A) -> R + Send + Sync + 'static,
     ) -> Self {
-        self.host_functions
-            .insert(name.to_string(), Self::wrap_host_function(function));
+        let name = name.to_string();
+        self.host_functions.insert(
+            name,
+            Box::new(move |name: &str, linker: &mut Linker<WasiCtx>| {
+                linker.func_wrap("env", name, |ptr: u32, len: u32| {
+                    function(todo!());
+                    7u32
+                })?;
+                Ok(())
+            }),
+        );
         self
     }
 
@@ -130,7 +130,8 @@ impl WasiPluginBuilder {
 pub struct WasiPlugin {
     pub module: Vec<u8>,
     pub wasi_ctx: WasiCtx,
-    pub host_functions: HashMap<String, HostFunction>,
+    pub host_functions:
+        HashMap<String, Box<dyn Fn(&str, &mut Linker<WasiCtx>) -> Result<(), Error>>>,
 }
 
 impl Wasi {
@@ -159,6 +160,10 @@ impl Wasi {
         let engine = Engine::new(&config)?;
         let mut linker = Linker::new(&engine);
 
+        for (name, add_to_linker) in plugin.host_functions.into_iter() {
+            add_to_linker(&name, &mut linker)?;
+        }
+
         linker
             .func_wrap("env", "__command", |x: u32, y: u32| x + y)
             .unwrap();

plugins/json_language/src/lib.rs 🔗

@@ -5,12 +5,7 @@ use std::fs;
 use std::path::PathBuf;
 
 // #[import]
-// fn command(string: &str) -> Option<String>;
-
-extern "C" {
-    #[no_mangle]
-    fn __command(ptr: *const u8, len: usize) -> *const ::plugin::__Buffer;
-}
+// fn my_command(string: &str) -> Option<String>;
 
 // #[no_mangle]
 // // TODO: switch len from usize to u32?
@@ -33,9 +28,13 @@ extern "C" {
 //     return new_buffer.leak_to_heap();
 // }
 
+extern "C" {
+    fn __command(ptr: *const u8, len: usize) -> *mut ::plugin::__Buffer;
+}
+
 #[no_mangle]
 fn command(string: &str) -> Option<String> {
-    println!("executing command: {}", string);
+    dbg!("executing command: {}", string);
     // serialize data
     let data = string;
     let data = ::plugin::bincode::serialize(&data).unwrap();
@@ -47,14 +46,15 @@ fn command(string: &str) -> Option<String> {
     // call extern function
     let result = unsafe { __command(ptr, len) };
     // get result
-    let result = todo!(); // convert into box
+    let new_buffer = unsafe { Box::from_raw(result) }; // convert into box
+    let new_data = unsafe { new_buffer.to_vec() };
 
     // deserialize data
-    let data: Option<String> = match ::plugin::bincode::deserialize(&data) {
+    let new_data: Option<String> = match ::plugin::bincode::deserialize(&new_data) {
         Ok(d) => d,
-        Err(e) => panic!("Data passed to function not deserializable."),
+        Err(e) => panic!("Data returned from function not deserializable."),
     };
-    return data;
+    return new_data;
 }
 
 // TODO: some sort of macro to generate ABI bindings
@@ -81,7 +81,6 @@ const BIN_PATH: &'static str =
 #[export]
 pub fn name() -> &'static str {
     // let number = unsafe { hello(27) };
-    // println!("got: {}", number);
     // let number = unsafe { bye(28) };
     // println!("got: {}", number);
     "vscode-json-languageserver"