wasm_wrapper_gen_impl/
lib.rs

1extern crate arrayvec;
2#[macro_use]
3extern crate failure;
4#[macro_use]
5extern crate proc_macro_hack;
6#[macro_use]
7extern crate quote;
8extern crate syn;
9
10extern crate wasm_wrapper_gen_shared;
11
12use failure::{Error, ResultExt};
13
14use wasm_wrapper_gen_shared::{extract_func_info, get_argument_types, get_ret_type,
15                              transform_macro_input_to_items, SupportedArgumentType,
16                              SupportedRetType, TransformedRustIdent};
17
18
19#[derive(Debug, Clone)]
20/// Small constructed ident struct supporting up to four suffixes.
21struct ConstructedArgIdent {
22    base: &'static str,
23    number_suffix: u32,
24    suffixes: arrayvec::ArrayVec<[&'static str; 4]>,
25}
26
27impl ConstructedArgIdent {
28    fn new(base: &'static str, number_suffix: u32) -> Self {
29        ConstructedArgIdent {
30            base,
31            number_suffix,
32            suffixes: arrayvec::ArrayVec::new(),
33        }
34    }
35
36    fn with_suffix(&self, suffix: &'static str) -> Self {
37        let mut cloned = self.clone(); // this is a cheap copy
38        cloned.suffixes.push(suffix);
39        cloned
40    }
41}
42
43impl quote::ToTokens for ConstructedArgIdent {
44    fn to_tokens(&self, tokens: &mut quote::Tokens) {
45        let mut ident = format!("{}{}", self.base, self.number_suffix);
46        for suffix in &self.suffixes {
47            ident.push_str(suffix);
48        }
49        tokens.append(ident);
50    }
51}
52
53proc_macro_item_impl! {
54    pub fn __js_fn_impl(input: &str) -> String {
55        match process_all_functions(input) {
56            Ok(v) => v,
57            Err(e) => {
58                panic!("js_fn macro failed: {}", e);
59            }
60        }
61    }
62}
63
64fn process_all_functions(input: &str) -> Result<String, Error> {
65    let token_trees = syn::parse_token_trees(input)
66        .map_err(|e| format_err!("failed to parse macro input as an item: {}", e))?;
67
68    let ast = transform_macro_input_to_items(token_trees)?;
69
70    let mut full_out = quote::Tokens::new();
71    for item in &ast {
72        let output = process_item(item)
73            .with_context(|e| format!("failed to process function '{:?}': {}", item, e))?;
74
75        full_out.append(output);
76    }
77    Ok(full_out.to_string())
78}
79
80fn process_item(item: &syn::Item) -> Result<quote::Tokens, Error> {
81    let (item, decl, block) = extract_func_info(item)?;
82
83    let out = generate_function_wrapper(item, decl, block)?;
84
85    Ok(out)
86}
87
88fn generate_function_wrapper(
89    item: &syn::Item,
90    decl: &syn::FnDecl,
91    code: &syn::Block,
92) -> Result<quote::Tokens, Error> {
93    let callable_body = generate_callable_body(item, decl, code)?;
94
95    let argument_types = get_argument_types(decl)?;
96    let ret_ty = get_ret_type(decl)?;
97
98    let argument_names = (0..argument_types.len() as u32)
99        .map(|index| ConstructedArgIdent::new("__arg", index))
100        .collect::<Vec<_>>();
101
102    let mut function_body = quote::Tokens::new();
103
104    for (ty, arg_name) in argument_types.iter().zip(&argument_names) {
105        function_body.append(setup_for_argument(&arg_name, ty)?);
106    }
107
108    let mut arg_names_as_argument_list = quote::Tokens::new();
109    for arg_name in &argument_names {
110        arg_names_as_argument_list.append(quote! { #arg_name, });
111    }
112
113    function_body.append(quote! {
114        // TODO: handle results as well...
115        let result: #ret_ty = (#callable_body)(#arg_names_as_argument_list);
116    });
117
118    function_body.append(return_handling(&ret_ty)?);
119
120    let func_ident = TransformedRustIdent::new(&item.ident);
121
122    let mut real_arguments_list = quote::Tokens::new();
123    for (ty, arg_name) in argument_types.iter().zip(&argument_names) {
124        expand_argument_into(arg_name, ty, &mut real_arguments_list)?;
125    }
126
127    let ret_def = WrittenReturnType(ret_ty);
128
129    let full_definition = quote! {
130        #[no_mangle]
131        #[doc(hidden)]
132        pub extern "C" fn #func_ident (#real_arguments_list) #ret_def {
133            #function_body
134        }
135    };
136
137    Ok(full_definition)
138    // let temp_ident = &item.ident;
139    // let temp_str = full_definition.to_string();
140    // Ok(quote! {
141    //     fn #temp_ident() -> &'static str {
142    //         #temp_str
143    //     }
144    // })
145}
146
147fn expand_argument_into(
148    arg_name: &ConstructedArgIdent,
149    type_type: &SupportedArgumentType,
150    tokens: &mut quote::Tokens,
151) -> Result<(), Error> {
152    match *type_type {
153        SupportedArgumentType::IntegerSliceRef(int_ty) => {
154            let ptr_arg_name = arg_name.with_suffix("_ptr");
155            let length_arg_name = arg_name.with_suffix("_len");
156            tokens.append(quote! {
157                #ptr_arg_name: *const #int_ty,
158                #length_arg_name: usize,
159            });
160        }
161        SupportedArgumentType::IntegerSliceMutRef(int_ty)
162        | SupportedArgumentType::IntegerVec(int_ty) => {
163            let ptr_arg_name = arg_name.with_suffix("_ptr");
164            let length_arg_name = arg_name.with_suffix("_len");
165            tokens.append(quote! {
166                #ptr_arg_name: *mut #int_ty,
167                #length_arg_name: usize,
168            });
169        }
170        SupportedArgumentType::Integer(int_ty) => tokens.append(quote! {
171            #arg_name: #int_ty,
172        }),
173    }
174
175    Ok(())
176}
177
178struct WrittenReturnType(SupportedRetType);
179
180impl quote::ToTokens for WrittenReturnType {
181    fn to_tokens(&self, tokens: &mut quote::Tokens) {
182        match self.0 {
183            SupportedRetType::Unit => (),
184            SupportedRetType::Integer(int_ty) => {
185                tokens.append(quote! { -> #int_ty });
186            }
187            SupportedRetType::IntegerVec(_) => {
188                tokens.append(quote! { -> *const usize });
189            }
190        }
191    }
192}
193
194fn setup_for_argument(
195    arg_name: &ConstructedArgIdent,
196    ty: &SupportedArgumentType,
197) -> Result<quote::Tokens, Error> {
198    let tokens = match *ty {
199        SupportedArgumentType::IntegerSliceRef(int_ty) => {
200            // TODO: coordinate _ptr / _len suffixes
201            let ptr_arg_name = arg_name.with_suffix("_ptr");
202            let length_arg_name = arg_name.with_suffix("_len");
203            quote! {
204                let #arg_name: &[#int_ty] = unsafe {
205                    ::std::slice::from_raw_parts(#ptr_arg_name, #length_arg_name)
206                };
207            }
208        }
209        SupportedArgumentType::IntegerSliceMutRef(int_ty) => {
210            let ptr_arg_name = arg_name.with_suffix("_ptr");
211            let length_arg_name = arg_name.with_suffix("_len");
212            quote! {
213                let #arg_name: &mut [#int_ty] = unsafe {
214                    ::std::slice::from_raw_parts_mut(#ptr_arg_name, #length_arg_name)
215                };
216            }
217        }
218        SupportedArgumentType::IntegerVec(int_ty) => {
219            let ptr_arg_name = arg_name.with_suffix("_ptr");
220            let length_arg_name = arg_name.with_suffix("_len");
221            quote! {
222                let #arg_name: Vec<#int_ty> = unsafe {
223                    ::std::vec::Vec::from_raw_parts(#ptr_arg_name,
224                        #length_arg_name, #length_arg_name)
225                };
226            }
227        }
228        SupportedArgumentType::Integer(_) => quote::Tokens::new(), // no setup for simple integers
229    };
230
231    Ok(tokens)
232}
233
234fn return_handling(ty: &SupportedRetType) -> Result<quote::Tokens, Error> {
235    let tokens = match *ty {
236        SupportedRetType::Unit | SupportedRetType::Integer(_) => quote! { result },
237        SupportedRetType::IntegerVec(int_ty) => {
238            quote! {
239                {
240                    let result_ptr = result.as_slice().as_ptr() as *mut #int_ty;
241                    let result_len = result.len();
242                    let result_cap = result.capacity();
243                    let to_return = Box::new([result_ptr as usize, result_len, result_cap]);
244                    ::std::mem::forget(result);
245                    ::std::boxed::Box::into_raw(to_return) as *const usize
246                }
247            }
248        }
249    };
250
251    Ok(tokens)
252}
253
254fn generate_callable_body(
255    _item: &syn::Item,
256    decl: &syn::FnDecl,
257    code: &syn::Block,
258) -> Result<quote::Tokens, Error> {
259    // we'll see what works best here.
260    // This set of if statements is for if we've been given a path to the implementing function.
261    //
262    // In this case, we want to just call the function at that path with the same arguments the
263    // function declaration takes.
264    if let Some(statement) = code.stmts.first() {
265        if let syn::Stmt::Expr(ref inner_expr) = *statement {
266            if let syn::ExprKind::Path(_, _) = inner_expr.node {
267                return Ok(quote! {
268                    // output the path alone so that it can be called like (path::to::func)(args)
269                    (#inner_expr)
270                });
271            }
272        }
273    }
274
275    // if it isn't our special case of a path, we can assume the full code
276    // to call the inner function has been written out. We'll give the code
277    // then a copy of the inputs and call it
278    let mut arguments = quote::Tokens::new();
279    for input in &decl.inputs {
280        arguments.append(quote! {
281            #input,
282        });
283    }
284    Ok(quote! {
285        // syn::Block ToTokens includes '{}' always already.
286        (|#arguments| #code )
287    })
288}