1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4use proc_macro2::{Ident, Literal};
5use quote::{format_ident, quote};
6use syn::fold::{fold_fn_arg, fold_signature, Fold};
7use syn::parse::{Parse, ParseStream, Result};
8use syn::punctuated::Punctuated;
9use syn::{parse_macro_input, Block, FnArg, ItemFn, Signature, Token};
10
11struct ArgLength {
12 length: Option<Literal>,
13}
14
15impl Parse for ArgLength {
16 fn parse(input: ParseStream) -> Result<Self> {
17 let vars = Punctuated::<Literal, Token![,]>::parse_terminated(input)?;
18 Ok(ArgLength {
19 length: vars.first().map(|i| i.clone()),
20 })
21 }
22}
23
24struct JsFunction {
25 args: Vec<FnArg>,
26 name: Option<Ident>,
27 signature: Option<Signature>,
28 block: Vec<Block>,
29}
30
31impl JsFunction {
32 pub fn new() -> Self {
33 JsFunction {
34 args: vec![],
35 name: None,
36 signature: None,
37 block: vec![],
38 }
39 }
40}
41
42impl Fold for JsFunction {
43 fn fold_fn_arg(&mut self, arg: FnArg) -> FnArg {
44 self.args.push(arg.clone());
45 fold_fn_arg(self, arg)
46 }
47
48 fn fold_signature(&mut self, signature: Signature) -> Signature {
49 self.name = Some(format_ident!("{}", signature.ident));
50 let mut new_signature = signature.clone();
51 new_signature.ident = format_ident!("_{}", signature.ident);
52 self.signature = Some(new_signature);
53 fold_signature(self, signature)
54 }
55
56 fn fold_block(&mut self, node: Block) -> Block {
57 self.block.push(node.clone());
58 node
59 }
60}
61
62#[proc_macro_attribute]
63pub fn js_function(attr: TokenStream, input: TokenStream) -> TokenStream {
64 let arg_len = parse_macro_input!(attr as ArgLength);
65 let arg_len_span = arg_len.length.unwrap_or(Literal::usize_unsuffixed(0));
66 let input = parse_macro_input!(input as ItemFn);
67 let mut js_fn = JsFunction::new();
68 js_fn.fold_item_fn(input);
69 let fn_name = js_fn.name.unwrap();
70 let fn_block = js_fn.block;
71 let signature = js_fn.signature.unwrap();
72 let new_fn_name = signature.ident.clone();
73 let expanded = quote! {
74 #signature #(#fn_block)*
75
76 extern "C" fn #fn_name(
77 raw_env: napi_rs::sys::napi_env,
78 cb_info: napi_rs::sys::napi_callback_info,
79 ) -> napi_rs::sys::napi_value {
80 use std::io::Write;
81 use std::mem;
82 use std::os::raw::c_char;
83 use std::ptr;
84 use napi_rs::{Any, Env, Status, Value, CallContext};
85 let mut argc = #arg_len_span as usize;
86 let mut raw_args =
87 unsafe { mem::MaybeUninit::<[napi_rs::sys::napi_value; 8]>::uninit().assume_init() };
88 let mut raw_this = ptr::null_mut();
89
90 let mut has_error = false;
91
92 unsafe {
93 let status = napi_rs::sys::napi_get_cb_info(
94 raw_env,
95 cb_info,
96 &mut argc as *mut usize as *mut u64,
97 &mut raw_args[0],
98 &mut raw_this,
99 ptr::null_mut(),
100 );
101 has_error = has_error && (Status::from(status) == Status::Ok);
102 }
103
104 let mut env = Env::from_raw(raw_env);
105 let call_ctx = CallContext::new(&mut env, raw_this, raw_args, #arg_len_span);
106 let result = call_ctx.and_then(|ctx| #new_fn_name(ctx));
107 has_error = has_error && result.is_err();
108
109 match result {
110 Ok(result) => result.into_raw(),
111 Err(e) => {
112 let message = format!("{:?}", e);
113 unsafe {
114 napi_rs::sys::napi_throw_error(raw_env, ptr::null(), message.as_ptr() as *const c_char);
115 }
116 let mut undefined = ptr::null_mut();
117 unsafe { napi_rs::sys::napi_get_undefined(raw_env, &mut undefined) };
118 undefined
119 }
120 }
121 }
122 };
123 TokenStream::from(expanded)
125}