1use core::panic;
2
3use proc_macro::TokenStream;
4use quote::{format_ident, quote};
5use syn::{parse_macro_input, Block, FnArg, ForeignItemFn, Ident, ItemFn, Pat, Type, Visibility};
6
7/// Attribute macro to be used guest-side within a plugin.
8/// ```ignore
9/// #[export]
10/// pub fn say_hello() -> String {
11/// "Hello from Wasm".into()
12/// }
13/// ```
14/// This macro makes a function defined guest-side available host-side.
15/// Note that all arguments and return types must be `serde`.
16#[proc_macro_attribute]
17pub fn export(args: TokenStream, function: TokenStream) -> TokenStream {
18 if !args.is_empty() {
19 panic!("The export attribute does not take any arguments");
20 }
21
22 let inner_fn = parse_macro_input!(function as ItemFn);
23
24 if !inner_fn.sig.generics.params.is_empty() {
25 panic!("Exported functions can not take generic parameters");
26 }
27
28 if let Visibility::Public(_) = inner_fn.vis {
29 } else {
30 panic!("The export attribute only works for public functions");
31 }
32
33 let inner_fn_name = format_ident!("{}", inner_fn.sig.ident);
34 let outer_fn_name = format_ident!("__{}", inner_fn_name);
35
36 let variadic = inner_fn.sig.inputs.len();
37 let i = (0..variadic).map(syn::Index::from);
38 let t: Vec<Type> = inner_fn
39 .sig
40 .inputs
41 .iter()
42 .map(|x| match x {
43 FnArg::Receiver(_) => {
44 panic!("All arguments must have specified types, no `self` allowed")
45 }
46 FnArg::Typed(item) => *item.ty.clone(),
47 })
48 .collect();
49
50 // this is cursed...
51 let (args, ty) = if variadic != 1 {
52 (
53 quote! {
54 #( data.#i ),*
55 },
56 quote! {
57 ( #( #t ),* )
58 },
59 )
60 } else {
61 let ty = &t[0];
62 (quote! { data }, quote! { #ty })
63 };
64
65 TokenStream::from(quote! {
66 #[no_mangle]
67 #inner_fn
68
69 #[no_mangle]
70 pub extern "C" fn #outer_fn_name(packed_buffer: u64) -> u64 {
71 // setup
72 let data = unsafe { ::plugin::__Buffer::from_u64(packed_buffer).to_vec() };
73
74 // operation
75 let data: #ty = match ::plugin::bincode::deserialize(&data) {
76 Ok(d) => d,
77 Err(e) => panic!("Data passed to function not deserializable."),
78 };
79 let result = #inner_fn_name(#args);
80 let new_data: Result<Vec<u8>, _> = ::plugin::bincode::serialize(&result);
81 let new_data = new_data.unwrap();
82
83 // teardown
84 let new_buffer = unsafe { ::plugin::__Buffer::from_vec(new_data) }.into_u64();
85 return new_buffer;
86 }
87 })
88}
89
90/// Attribute macro to be used guest-side within a plugin.
91/// ```ignore
92/// #[import]
93/// pub fn operating_system_name() -> String;
94/// ```
95/// This macro makes a function defined host-side available guest-side.
96/// Note that all arguments and return types must be `serde`.
97/// All that's provided is a signature, as the function is implemented host-side.
98#[proc_macro_attribute]
99pub fn import(args: TokenStream, function: TokenStream) -> TokenStream {
100 if !args.is_empty() {
101 panic!("The import attribute does not take any arguments");
102 }
103
104 let fn_declare = parse_macro_input!(function as ForeignItemFn);
105
106 if !fn_declare.sig.generics.params.is_empty() {
107 panic!("Exported functions can not take generic parameters");
108 }
109
110 // let inner_fn_name = format_ident!("{}", fn_declare.sig.ident);
111 let extern_fn_name = format_ident!("__{}", fn_declare.sig.ident);
112
113 let (args, tys): (Vec<Ident>, Vec<Type>) = fn_declare
114 .sig
115 .inputs
116 .clone()
117 .into_iter()
118 .map(|x| match x {
119 FnArg::Receiver(_) => {
120 panic!("All arguments must have specified types, no `self` allowed")
121 }
122 FnArg::Typed(t) => {
123 if let Pat::Ident(i) = *t.pat {
124 (i.ident, *t.ty)
125 } else {
126 panic!("All function arguments must be identifiers");
127 }
128 }
129 })
130 .unzip();
131
132 let body = TokenStream::from(quote! {
133 {
134 // setup
135 let data: (#( #tys ),*) = (#( #args ),*);
136 let data = ::plugin::bincode::serialize(&data).unwrap();
137 let buffer = unsafe { ::plugin::__Buffer::from_vec(data) };
138
139 // operation
140 let new_buffer = unsafe { #extern_fn_name(buffer.into_u64()) };
141 let new_data = unsafe { ::plugin::__Buffer::from_u64(new_buffer).to_vec() };
142
143 // teardown
144 match ::plugin::bincode::deserialize(&new_data) {
145 Ok(d) => d,
146 Err(e) => panic!("Data returned from function not deserializable."),
147 }
148 }
149 });
150
151 let block = parse_macro_input!(body as Block);
152
153 let inner_fn = ItemFn {
154 attrs: fn_declare.attrs,
155 vis: fn_declare.vis,
156 sig: fn_declare.sig,
157 block: Box::new(block),
158 };
159
160 TokenStream::from(quote! {
161 extern "C" {
162 fn #extern_fn_name(buffer: u64) -> u64;
163 }
164
165 #[no_mangle]
166 #inner_fn
167 })
168}