use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::{quote, ToTokens};
use syn::{parse_macro_input, DeriveInput, ImplItem, ImplItemFn, ItemImpl, ItemTrait, MetaNameValue};
#[proc_macro_attribute]
pub fn plugin(_: TokenStream, input: TokenStream) -> TokenStream {
let original_trait = parse_macro_input!(input as ItemTrait);
let async_trait = generate_async_trait(&original_trait);
let output = quote! {
#original_trait
#async_trait
};
output.into()
}
fn generate_async_trait(trait_item: &ItemTrait) -> proc_macro2::TokenStream {
let trait_name = &trait_item.ident;
let trait_methods = &trait_item.items;
let async_methods = trait_methods.iter().map(|item| {
if let syn::TraitItem::Fn(method) = item {
let method_name = &method.sig.ident;
let method_inputs = &method.sig.inputs;
let method_output = &method.sig.output;
let method_name_str = method_name.to_string();
let values: Vec<_> = method_inputs
.iter()
.filter_map(|arg| match arg {
syn::FnArg::Receiver(_) => None,
syn::FnArg::Typed(t) => Some(t.pat.to_token_stream()),
})
.collect();
quote! {
pub async fn #method_name(#method_inputs) #method_output {
let func = self.handle.get_func(#method_name_str).unwrap();
func.call_unchecked(&(#(#values),*)).await
}
}
} else {
item.to_token_stream()
}
});
let callable_trait_name = format!("{}Wrapper", trait_name);
let callable_trait_ident = syn::Ident::new(&callable_trait_name, trait_name.span());
quote! {
#[cfg(not(target_arch = "wasm32"))]
#[derive(Debug, Clone)]
pub struct #callable_trait_ident<P> {
pub handle: plugy::runtime::PluginHandle<P>
}
#[cfg(not(target_arch = "wasm32"))]
impl<P> #callable_trait_ident<P> {
#(#async_methods)*
}
#[cfg(not(target_arch = "wasm32"))]
impl<P> plugy::runtime::IntoCallable<P> for Box<dyn #trait_name> {
type Output = #callable_trait_ident<P>;
fn into_callable(handle: plugy::runtime::PluginHandle<P>) -> Self::Output {
#callable_trait_ident { handle }
}
}
}
}
fn impl_methods(imp: &ItemImpl) -> impl Iterator<Item = &ImplItemFn> {
imp.items
.iter()
.filter_map(|i| match i {
ImplItem::Fn(m) => Some(m),
_ => None,
})
.filter(|_m| imp.trait_.is_some())
}
#[proc_macro_attribute]
pub fn plugin_impl(_metadata: TokenStream, input: TokenStream) -> TokenStream {
let cur_impl: proc_macro2::TokenStream = input.clone().into();
let imp = parse_macro_input!(input as ItemImpl);
let ty = &imp.self_ty;
let methods: Vec<&ImplItemFn> = impl_methods(&imp).collect();
let derived: proc_macro2::TokenStream = methods
.iter()
.map(|m| {
let method_name = &m.sig.ident;
let args = &m.sig.inputs;
let values: Vec<_> = args
.iter()
.filter_map(|arg| match arg {
syn::FnArg::Receiver(_) => None,
syn::FnArg::Typed(t) => Some(t.to_token_stream()),
})
.collect();
let expose_name = format!("_plugy_guest_{}", method_name);
let expose_name_ident = syn::Ident::new(&expose_name, Span::call_site());
quote! {
#[no_mangle]
pub unsafe extern "C" fn #expose_name_ident(value: u64) -> u64 {
let value: #ty = plugy_core::guest::read_msg(value);
plugy_core::guest::write_msg(&#ty.#method_name(#(#values)*))
}
}
})
.collect();
quote! {
#cur_impl
#derived
}
.into()
}
#[proc_macro_attribute]
pub fn plugin_import(args: TokenStream, input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let struct_name = &input.ident;
let parsed = syn::parse2::<MetaNameValue>(args.into()).unwrap();
assert_eq!(parsed.path.to_token_stream().to_string(), "file");
let file_path = parsed.value;
quote! {
#input
impl PluginLoader for #struct_name {
fn load(&self) -> std::pin::Pin<std::boxed::Box<dyn std::future::Future<Output = Result<Vec<u8>, anyhow::Error>>>> {
std::boxed::Box::pin(async {
let res = std::fs::read(#file_path)?;
Ok(res)
})
}
}
}.into()
}