1#[cfg(feature = "compat-mode")]
2mod compat_macro;
3mod expand;
4mod parser;
5
6#[macro_use]
7extern crate syn;
8#[macro_use]
9extern crate napi_derive_backend;
10#[macro_use]
11extern crate quote;
12
13use std::env;
14
15use proc_macro::TokenStream;
16#[cfg(feature = "compat-mode")]
17use syn::{fold::Fold, parse_macro_input, ItemFn};
18
19/// ```ignore
20/// #[napi]
21/// fn test(ctx: CallContext, name: String) {
22/// "hello" + name
23/// }
24/// ```
25#[proc_macro_attribute]
26pub fn napi(attr: TokenStream, input: TokenStream) -> TokenStream {
27 match expand::expand(attr:attr.into(), input:input.into()) {
28 Ok(tokens: TokenStream) => {
29 if env::var(key:"DEBUG_GENERATED_CODE").is_ok() {
30 println!("{}", tokens);
31 }
32 tokens.into()
33 }
34 Err(diagnostic: Diagnostic) => {
35 println!("`napi` macro expand failed.");
36
37 (quote! { #diagnostic }).into()
38 }
39 }
40}
41
42#[cfg(feature = "compat-mode")]
43#[proc_macro_attribute]
44pub fn contextless_function(_attr: TokenStream, input: TokenStream) -> TokenStream {
45 let input = parse_macro_input!(input as ItemFn);
46 let mut js_fn = compat_macro::JsFunction::new();
47 js_fn.fold_item_fn(input);
48 let fn_name = js_fn.name.unwrap();
49 let fn_block = js_fn.block;
50 let signature = js_fn.signature.unwrap();
51 let visibility = js_fn.visibility;
52 let new_fn_name = signature.ident.clone();
53 let execute_js_function =
54 compat_macro::get_execute_js_code(new_fn_name, compat_macro::FunctionKind::Contextless);
55
56 let expanded = quote! {
57 #[inline(always)]
58 #signature #(#fn_block)*
59
60 #visibility extern "C" fn #fn_name(
61 raw_env: napi::sys::napi_env,
62 cb_info: napi::sys::napi_callback_info,
63 ) -> napi::sys::napi_value {
64 use std::ptr;
65 use std::panic::{self, AssertUnwindSafe};
66 use std::ffi::CString;
67 use napi::{Env, NapiValue, NapiRaw, Error, Status};
68
69 let ctx = unsafe { Env::from_raw(raw_env) };
70 #execute_js_function
71 }
72 };
73 // Hand the output tokens back to the compiler
74 TokenStream::from(expanded)
75}
76
77#[cfg(feature = "compat-mode")]
78#[proc_macro_attribute]
79pub fn js_function(attr: TokenStream, input: TokenStream) -> TokenStream {
80 let arg_len = parse_macro_input!(attr as compat_macro::ArgLength);
81 let arg_len_span = arg_len.length;
82 let input = parse_macro_input!(input as ItemFn);
83 let mut js_fn = compat_macro::JsFunction::new();
84 js_fn.fold_item_fn(input);
85 let fn_name = js_fn.name.unwrap();
86 let fn_block = js_fn.block;
87 let signature = js_fn.signature.unwrap();
88 let visibility = js_fn.visibility;
89 let new_fn_name = signature.ident.clone();
90 let execute_js_function =
91 compat_macro::get_execute_js_code(new_fn_name, compat_macro::FunctionKind::JsFunction);
92 let expanded = quote! {
93 #[inline(always)]
94 #signature #(#fn_block)*
95
96 #visibility extern "C" fn #fn_name(
97 raw_env: napi::sys::napi_env,
98 cb_info: napi::sys::napi_callback_info,
99 ) -> napi::sys::napi_value {
100 use std::ptr;
101 use std::panic::{self, AssertUnwindSafe};
102 use std::ffi::CString;
103 use napi::{Env, Error, Status, NapiValue, NapiRaw, CallContext};
104 let mut argc = #arg_len_span as usize;
105 #[cfg(all(target_os = "windows", target_arch = "x86"))]
106 let mut raw_args = vec![ptr::null_mut(); #arg_len_span];
107 #[cfg(not(all(target_os = "windows", target_arch = "x86")))]
108 let mut raw_args = [ptr::null_mut(); #arg_len_span];
109 let mut raw_this = ptr::null_mut();
110
111 unsafe {
112 let status = napi::sys::napi_get_cb_info(
113 raw_env,
114 cb_info,
115 &mut argc,
116 raw_args.as_mut_ptr(),
117 &mut raw_this,
118 ptr::null_mut(),
119 );
120 debug_assert!(Status::from(status) == Status::Ok, "napi_get_cb_info failed");
121 }
122
123 let mut env = unsafe { Env::from_raw(raw_env) };
124 #[cfg(all(target_os = "windows", target_arch = "x86"))]
125 let ctx = CallContext::new(&mut env, cb_info, raw_this, raw_args.as_slice(), argc);
126 #[cfg(not(all(target_os = "windows", target_arch = "x86")))]
127 let ctx = CallContext::new(&mut env, cb_info, raw_this, &raw_args, argc);
128 #execute_js_function
129 }
130 };
131 // Hand the output tokens back to the compiler
132 TokenStream::from(expanded)
133}
134
135#[cfg(feature = "compat-mode")]
136#[proc_macro_attribute]
137pub fn module_exports(_attr: TokenStream, input: TokenStream) -> TokenStream {
138 let input = parse_macro_input!(input as ItemFn);
139 let mut js_fn = compat_macro::JsFunction::new();
140 js_fn.fold_item_fn(input);
141 let fn_block = js_fn.block;
142 let fn_name = js_fn.name.unwrap();
143 let signature = js_fn.signature_raw.unwrap();
144 let args_len = js_fn.args.len();
145 let call_expr = if args_len == 1 {
146 quote! { #fn_name(exports) }
147 } else if args_len == 2 {
148 quote! { #fn_name(exports, env) }
149 } else {
150 panic!("Arguments length of #[module_exports] function must be 1 or 2");
151 };
152
153 let register = quote! {
154 #[cfg_attr(not(target_family = "wasm"), napi::bindgen_prelude::ctor)]
155 fn __napi__explicit_module_register() {
156 unsafe fn register(raw_env: napi::sys::napi_env, raw_exports: napi::sys::napi_value) -> napi::Result<()> {
157 use napi::{Env, JsObject, NapiValue};
158
159 let env = Env::from_raw(raw_env);
160 let exports = JsObject::from_raw_unchecked(raw_env, raw_exports);
161
162 #call_expr
163 }
164
165 napi::bindgen_prelude::register_module_exports(register)
166 }
167 };
168
169 (quote! {
170 #[inline]
171 #signature #(#fn_block)*
172
173 #register
174 })
175 .into()
176}
177