1use convert_case::{Case, Casing};
8use proc_macro::TokenStream;
9use proc_macro2::{Ident, Span};
10use quote::{quote, ToTokens};
11use syn::{
12 parse_macro_input, DeriveInput, FnArg, ImplItem, ImplItemFn, ItemImpl, ItemTrait, MetaNameValue,
13};
14
15#[proc_macro_attribute]
36pub fn plugin(_: TokenStream, input: TokenStream) -> TokenStream {
37 let original_trait = parse_macro_input!(input as ItemTrait);
38 let async_trait = generate_async_trait(&original_trait);
39
40 let output = quote! {
41 #original_trait
42 #async_trait
43 };
44
45 output.into()
46}
47
48fn generate_async_trait(trait_item: &ItemTrait) -> proc_macro2::TokenStream {
49 let trait_name = &trait_item.ident;
50 let trait_methods = &trait_item.items;
51
52 let mut generic_types = vec![];
53
54 let async_methods = trait_methods.iter().map(|item| match item {
55 syn::TraitItem::Fn(method) => {
56 let method_name = &method.sig.ident;
57 let method_inputs: Vec<_> = method
58 .sig
59 .inputs
60 .iter()
61 .map(|input| match &input {
62 FnArg::Receiver(_) => input.to_token_stream(),
63 FnArg::Typed(typed) => match *typed.ty.clone() {
64 syn::Type::Path(path) => {
65 if path.path.segments.iter().any(|seg| {
66 seg.ident == "Self"
67 || seg.arguments.to_token_stream().to_string().contains("Self")
68 }) {
69 let arg_name = &typed.pat;
70 quote! {
71 #arg_name: &Vec<u8>
72 }
73 } else {
74 input.to_token_stream()
75 }
76 }
77 _ => input.to_token_stream(),
78 },
79 })
80 .collect();
81 let method_output = &method.sig.output;
82 let method_name_str = method_name.to_string();
83 let values: Vec<_> = method
84 .sig
85 .inputs
86 .iter()
87 .filter_map(|arg| match arg {
88 syn::FnArg::Receiver(_) => None,
89 syn::FnArg::Typed(t) => Some(t.pat.to_token_stream()),
90 })
91 .collect();
92 quote! {
93 pub async fn #method_name(#(#method_inputs), *) #method_output {
94 let func = self.handle.get_func(#method_name_str).await.unwrap();
95 func.call_unchecked(&(#(#values),*)).await
96 }
97 }
98 }
99 syn::TraitItem::Type(ty) => {
100 let ident_type = &ty.ident;
101
102 generic_types.push(quote! {
103 #ident_type = Vec<u8>
104 });
105 quote! {}
106 }
107
108 _ => {
109 quote! {}
110 }
111 });
112
113 let callable_trait_name = format!("{}Wrapper", trait_name);
114 let callable_trait_ident = syn::Ident::new(&callable_trait_name, trait_name.span());
115
116 quote! {
117 #[cfg(not(target_arch = "wasm32"))]
118 #[derive(Debug, Clone)]
119 pub struct #callable_trait_ident<P, D> {
120 pub handle: plugy::runtime::PluginHandle<plugy::runtime::Plugin<D>>,
121 inner: std::marker::PhantomData<P>
122 }
123 #[cfg(not(target_arch = "wasm32"))]
124 impl<P, D: Clone + Send> #callable_trait_ident<P, D> {
125 #(#async_methods)*
126 }
127 #[cfg(not(target_arch = "wasm32"))]
128 impl<P, D> plugy::runtime::IntoCallable<P, D> for Box<dyn #trait_name<#(#generic_types),*>> {
129 type Output = #callable_trait_ident<P, D>;
130 fn into_callable(handle: plugy::runtime::PluginHandle<plugy::runtime::Plugin<D>>) -> Self::Output {
131 #callable_trait_ident { handle, inner: std::marker::PhantomData }
132 }
133 }
134 }
135}
136
137fn impl_methods(imp: &ItemImpl) -> impl Iterator<Item = &ImplItemFn> {
138 imp.items
139 .iter()
140 .filter_map(|i| match i {
141 ImplItem::Fn(m) => Some(m),
142 _ => None,
143 })
144 .filter(|_m| imp.trait_.is_some())
145}
146
147#[proc_macro_attribute]
181pub fn plugin_impl(_metadata: TokenStream, input: TokenStream) -> TokenStream {
182 let cur_impl: proc_macro2::TokenStream = input.clone().into();
183 let imp = parse_macro_input!(input as ItemImpl);
184 let ty = &imp.self_ty;
185 let methods: Vec<&ImplItemFn> = impl_methods(&imp).collect();
186 let derived: proc_macro2::TokenStream = methods
187 .iter()
188 .map(|m| {
189 let method_name = &m.sig.ident;
190 let args = &m.sig.inputs;
191 let types: Vec<_> = args
192 .iter()
193 .filter_map(|arg| match arg {
194 syn::FnArg::Receiver(_) => None,
195 syn::FnArg::Typed(t) => Some(t.ty.to_token_stream()),
196 })
197 .collect();
198 let values: Vec<_> = args
199 .iter()
200 .filter_map(|arg| match arg {
201 syn::FnArg::Receiver(_) => None,
202 syn::FnArg::Typed(t) => Some(t.pat.to_token_stream()),
203 })
204 .collect();
205 let expose_name = format!("_plugy_guest_{}", method_name);
206 let expose_name_ident = syn::Ident::new(&expose_name, Span::call_site());
207 quote! {
208 #[no_mangle]
209 pub unsafe extern "C" fn #expose_name_ident(value: u64) -> u64 {
210 let (value, #(#values),*): (#ty, #(#types),*) = plugy::core::guest::read_msg(value);
211 plugy::core::guest::write_msg(&value.#method_name(#(#values),*))
212 }
213 }
214 })
215 .collect();
216
217 quote! {
218 #cur_impl
219 #derived
220 }
221 .into()
222}
223
224#[proc_macro_attribute]
225pub fn plugin_import(args: TokenStream, input: TokenStream) -> TokenStream {
226 let input = parse_macro_input!(input as DeriveInput);
227 let struct_name = &input.ident;
228 let parsed = syn::parse2::<MetaNameValue>(args.into()).unwrap();
229 assert_eq!(parsed.path.to_token_stream().to_string(), "file");
230 let file_path = parsed.value;
231
232 quote! {
233 #input
234
235 impl PluginLoader for #struct_name {
236 fn bytes(&self) -> std::pin::Pin<std::boxed::Box<dyn std::future::Future<Output = Result<Vec<u8>, anyhow::Error>>>> {
237 std::boxed::Box::pin(async {
238 let res = std::fs::read(#file_path)?;
239 Ok(res)
240 })
241 }
242 fn name(&self) -> &'static str {
243 std::any::type_name::<Self>()
244 }
245 }
246 }.into()
247}
248
249#[proc_macro_attribute]
250pub fn context(args: TokenStream, input: TokenStream) -> TokenStream {
251 let input = parse_macro_input!(input as ItemImpl);
253
254 let data_ident = &args
255 .into_iter()
256 .nth(2)
257 .map(|d| Ident::new(&d.to_string(), d.span().into()))
258 .unwrap_or(Ident::new("_", Span::call_site()));
259
260 let struct_name = &input.self_ty.to_token_stream();
262
263 let mod_name = Ident::new(
264 &struct_name.to_string().to_case(Case::Snake),
265 Span::call_site(),
266 );
267
268 let mut externs = Vec::new();
269
270 let mut links = Vec::new();
271
272 let generated_methods = input
274 .items
275 .iter()
276 .filter_map(|item| {
277 if let syn::ImplItem::Fn(method) = item {
278 let generics = &method.sig.generics;
279 let method_name = &method.sig.ident;
280 let method_args: Vec<_> = method
281 .sig
282 .inputs
283 .iter()
284 .skip(1) .map(|arg| {
286 if let FnArg::Typed(pat_type) = arg {
287 pat_type.to_token_stream()
288 } else {
289 panic!("Unsupported function argument type");
290 }
291 })
292 .collect();
293 let method_pats: Vec<_> = method
294 .sig
295 .inputs
296 .iter()
297 .skip(1) .map(|arg| {
299 if let FnArg::Typed(pat_type) = arg {
300 pat_type.pat.to_token_stream()
301 } else {
302 panic!("Unsupported function argument type");
303 }
304 })
305 .collect();
306 let return_type = &method.sig.output;
307 let extern_method_name = Ident::new(
308 &format!("_plugy_context_{}", method_name),
309 Span::call_site(),
310 );
311
312 externs.push(quote::quote! {
313 extern "C" {
314 fn #extern_method_name(ptr: u64) -> u64;
315 }
316 });
317
318 let extern_method_name_str = extern_method_name.to_string();
319
320 links.push(quote! {
321 linker
322 .func_wrap1_async(
323 "env",
324 #extern_method_name_str,
325 move |mut caller: plugy::runtime::Caller<_>,
326 ptr: u64|
327 -> Box<dyn std::future::Future<Output = u64> + Send> {
328 use plugy::core::bitwise::{from_bitwise, into_bitwise};
329 Box::new(async move {
330 let store = caller.data().clone().unwrap();
331 let plugy::runtime::RuntimeCaller {
332 memory,
333 alloc_fn,
334 dealloc_fn,
335 plugin
336 } = store;
337
338 let (ptr, len) = from_bitwise(ptr);
339 let mut buffer = vec![0u8; len as _];
340 memory.read(&mut caller, ptr as _, &mut buffer).unwrap();
341 dealloc_fn
342 .call_async(&mut caller, into_bitwise(ptr, len))
343 .await
344 .unwrap();
345 let (#(#method_pats),*) = bincode::deserialize(&buffer).unwrap();
346 let buffer =
347 bincode::serialize(&#struct_name::#method_name(&mut caller, #(#method_pats),*).await)
348 .unwrap();
349 let ptr = alloc_fn
350 .call_async(&mut caller, buffer.len() as _)
351 .await
352 .unwrap();
353 memory.write(&mut caller, ptr as _, &buffer).unwrap();
354 into_bitwise(ptr, buffer.len() as _)
355 })
356 },
357 )
358 .unwrap();
359 });
360
361 Some(quote! {
362 #[allow(unused_variables)]
363 pub fn #method_name #generics (#(#method_args),*) #return_type {
364 #[cfg(target_arch = "wasm32")]
365 {
366 let args = (#(#method_pats),*);
367 let ptr = plugy::core::guest::write_msg(&args);
368 unsafe { plugy::core::guest::read_msg(#extern_method_name(ptr)) }
369 }
370 #[cfg(not(target_arch = "wasm32"))]
371 panic!("You are trying to call wasm methods outside of wasm32")
372 }
373 })
374 } else {
375 None
376 }
377 })
378 .collect::<Vec<_>>();
379 let generated = quote::quote! {
381 #[cfg(not(target_arch = "wasm32"))]
382 #input
383 #[cfg(not(target_arch = "wasm32"))]
384 impl plugy::runtime::Context<#data_ident> for #struct_name {
385 fn link(&self, linker: &mut plugy::runtime::Linker<plugy::runtime::Plugin<#data_ident>>) {
386 #(#links)*
387 }
388 }
389
390 impl #struct_name {
391 pub fn get(&self) -> #mod_name::sync::#struct_name {
392 #mod_name::sync::#struct_name
393 }
394 }
395 pub mod #mod_name {
396 pub mod sync {
397 #[cfg(target_arch = "wasm32")]
398 #(#externs)*
399
400 pub struct #struct_name;
401 impl #struct_name {
402 #(#generated_methods)*
403 }
404 }
405 }
406 };
407
408 generated.into()
410}