wasmy_macros/
lib.rs

1use std::ops::Deref;
2
3use proc_macro::TokenStream;
4use proc_macro2::{Ident, Span};
5use quote::quote;
6use syn::{parse::Parser, FnArg, ItemFn, Lit, Pat, Signature};
7
8// syn::AttributeArgs does not implement syn::Parse
9type AttributeArgs = syn::punctuated::Punctuated<syn::NestedMeta, syn::Token![,]>;
10
11/// Register vm's ABI for handling wasm callbacks.
12/// format description: `#[vm_handle(wasmy_abi::Method)]`
13/// example:
14/// ```
15/// #[vm_handle(123)]
16/// fn xxx<A: wasmy_abi::Message, R: wasmy_abi::Message>(args: A) -> wasmy_abi::Result<R> {todo!()}
17/// ```
18/// or with context
19/// ```
20/// #[vm_handle(123)]
21/// fn yyy<C: wasmy_abi::Message, A: wasmy_abi::Message, R: wasmy_abi::Message>(ctx: Option<&C>, args: A) -> wasmy_abi::Result<R> {todo!()}
22/// ```
23/// command to check expanded code: `cargo +nightly rustc -- -Zunstable-options
24/// --pretty=expanded`
25#[proc_macro_attribute]
26#[cfg(not(test))] // Work around for rust-lang/rust#62127
27pub fn vm_handle(args: TokenStream, item: TokenStream) -> TokenStream {
28    let method = parse_method("vm_handle", args).unwrap();
29    let raw_item = proc_macro2::TokenStream::from(item.clone());
30    let raw_sig = syn::parse_macro_input!(item as ItemFn).sig;
31    let has_ctx = raw_sig.inputs.len() == 2;
32    let raw_ident = raw_sig.ident;
33    let new_ident = Ident::new(&format!("_wasmy_vm_handle_{}", method), Span::call_site());
34
35    let new_item = if has_ctx {
36        quote! {
37            #raw_item
38
39
40            #[allow(redundant_semicolons)]
41            fn #new_ident(ctx_ptr: usize, args: &::wasmy_vm::Any) -> ::wasmy_vm::Result<::wasmy_vm::Any> {
42                #raw_ident(unsafe{::wasmy_vm::VmHandlerApi::try_as(ctx_ptr)}, ::wasmy_vm::VmHandlerApi::unpack_any(args)?).and_then(|res|::wasmy_vm::VmHandlerApi::pack_any(res))
43            }
44            ::wasmy_vm::submit_handler!{
45               ::wasmy_vm::VmHandlerApi::new(#method, #new_ident)
46            }
47        }
48    } else {
49        quote! {
50            #raw_item
51
52
53            #[allow(redundant_semicolons)]
54            fn #new_ident(_ctx_ptr: usize, args: &::wasmy_vm::Any) -> ::wasmy_vm::Result<::wasmy_vm::Any> {
55                #raw_ident(::wasmy_vm::VmHandlerApi::unpack_any(args)?).and_then(|res|::wasmy_vm::VmHandlerApi::pack_any(res))
56            }
57            ::wasmy_vm::submit_handler!{
58               ::wasmy_vm::VmHandlerApi::new(#method, #new_ident)
59            }
60        }
61    };
62
63    #[cfg(debug_assertions)]
64    println!("{}", new_item);
65    TokenStream::from(new_item)
66}
67
68/// Register wasm's ABI for handling requests.
69/// format description: `#[wasm_handle(i32)] or #[wasm_handle(method=i32)]`
70/// example:
71/// ```
72/// #[wasm_handle(method=123)]
73/// fn xxx<W: wasmy_abi::WasmContext<Value>, Value: wasmy_abi::Message, A: wasmy_abi::Message, R: wasmy_abi::Message>(ctx: W, args: A) -> wasmy_abi::Result<R> {todo!()}
74/// ```
75/// command to check expanded code: `cargo +nightly rustc -- -Zunstable-options
76/// --pretty=expanded`
77#[proc_macro_attribute]
78#[cfg(not(test))] // Work around for rust-lang/rust#62127
79pub fn wasm_handle(args: TokenStream, item: TokenStream) -> TokenStream {
80    // println!("{:?}", args);
81    let method = parse_method("wasm_handle", args).unwrap();
82    let mut new_item = item.clone();
83    let raw_sig = syn::parse_macro_input!(item as ItemFn).sig;
84    let (inner_ident, inner_item) = wasm_gen_inner(raw_sig);
85    let outer_ident = Ident::new(&format!("_wasmy_wasm_handle_{}", method), Span::call_site());
86    let outer_item = quote! {
87        #[allow(redundant_semicolons)]
88        #[inline]
89        #[no_mangle]
90        pub extern "C" fn #outer_ident(ctx_size: i32, args_size: i32) {
91            #inner_item;
92            ::wasmy_abi::wasm_handle(ctx_size, args_size, #inner_ident)
93        }
94    };
95    new_item.extend(TokenStream::from(outer_item));
96
97    #[cfg(debug_assertions)]
98    println!("{}", new_item);
99
100    new_item
101}
102
103fn wasm_gen_inner(raw_sig: Signature) -> (Ident, proc_macro2::TokenStream) {
104    let inner_ident = Ident::new("_inner", Span::call_site());
105    let raw_ident = raw_sig.ident.clone();
106    let raw_first_input = raw_sig.inputs.first().unwrap();
107    let fn_args = fn_arg_ident(raw_first_input);
108    (
109        inner_ident.clone(),
110        quote! {
111            #[allow(unused_mut)]
112            #[inline]
113            fn #inner_ident(#raw_first_input, args: ::wasmy_abi::InArgs) -> ::wasmy_abi::Result<::wasmy_abi::Any> {
114               ::wasmy_abi::pack_any(#raw_ident(#fn_args, args.get_args()?)?)
115            }
116        },
117    )
118}
119
120/// Register the ABI for wasm load-time initialization state.
121/// Register wasm's ABI for handling requests.
122/// format description: `#[wasm_onload]`
123/// example:
124/// ```
125/// #[wasm_onload]
126/// fn xxx() {}
127/// ```
128/// command to check expanded code: `cargo +nightly rustc -- -Zunstable-options
129/// --pretty=expanded`
130#[proc_macro_attribute]
131#[cfg(not(test))] // Work around for rust-lang/rust#62127
132pub fn wasm_onload(_args: TokenStream, item: TokenStream) -> TokenStream {
133    let raw_item = proc_macro2::TokenStream::from(item.clone());
134    let raw_ident = syn::parse_macro_input!(item as syn::ItemFn).sig.ident;
135    let new_ident = Ident::new("_wasmy_wasm_onload", Span::call_site());
136    let new_item = quote! {
137        #[allow(redundant_semicolons)]
138        #[inline]
139        #[no_mangle]
140        pub extern "C" fn #new_ident() {
141            #raw_item;
142            #raw_ident();
143        }
144    };
145    #[cfg(debug_assertions)]
146    println!("{}", new_item);
147    TokenStream::from(new_item)
148}
149
150fn parse_method(marco_name: &str, input: TokenStream) -> Result<i32, syn::Error> {
151    let method = input.to_string().parse::<i32>().unwrap_or(-1);
152    if method >= 0 {
153        return Ok(method);
154    }
155    let err = syn::Error::new_spanned(
156        proc_macro2::TokenStream::from(input.clone()),
157        format!("#[{0}(i32)] or #[{0}(method=i32)]", marco_name),
158    );
159    let attr = AttributeArgs::parse_terminated.parse(input).or(Err(err.clone()))?;
160    for arg in attr {
161        match arg {
162            syn::NestedMeta::Meta(syn::Meta::NameValue(namevalue)) => {
163                let ident = namevalue
164                    .path
165                    .get_ident()
166                    .ok_or_else(|| {
167                        syn::Error::new_spanned(&namevalue, "Must have specified ident")
168                    })?
169                    .to_string()
170                    .to_lowercase();
171                match ident.as_str() {
172                    "method" => {
173                        if let Lit::Int(i) = &namevalue.lit {
174                            if let Ok(method) = i.base10_digits().parse::<i32>() {
175                                if method >= 0 {
176                                    return Ok(method);
177                                }
178                            }
179                        }
180                        return Err(syn::Error::new_spanned(
181                            namevalue,
182                            "attribute method is not i32 greater than or equal to 0",
183                        ));
184                    }
185                    name => {
186                        let msg =
187                            format!("Unknown attribute {} is specified; expected `method`", name,);
188                        return Err(syn::Error::new_spanned(namevalue, msg));
189                    }
190                }
191            }
192            other => {
193                return Err(syn::Error::new_spanned(other, "Unknown attribute inside the macro"));
194            }
195        }
196    }
197    Err(err)
198}
199
200fn fn_arg_ident(arg: &FnArg) -> Ident {
201    let fn_args;
202    if let FnArg::Typed(a) = arg {
203        if let Pat::Ident(ident) = a.pat.deref() {
204            fn_args = ident.ident.clone();
205        } else {
206            unreachable!()
207        }
208    } else {
209        unreachable!()
210    }
211    fn_args
212}