2021-09-23 02:29:09 +09:00
|
|
|
#[cfg(feature = "compat-mode")]
|
|
|
|
mod compat_macro;
|
|
|
|
mod parser;
|
|
|
|
|
|
|
|
#[macro_use]
|
|
|
|
extern crate syn;
|
|
|
|
#[macro_use]
|
|
|
|
extern crate napi_derive_backend;
|
|
|
|
#[macro_use]
|
|
|
|
extern crate quote;
|
|
|
|
use napi_derive_backend::{BindgenResult, TryToTokens};
|
|
|
|
|
|
|
|
#[cfg(feature = "type-def")]
|
|
|
|
use napi_derive_backend::{ToTypeDef, TypeDef};
|
2021-11-23 20:00:31 +09:00
|
|
|
use parser::{attrs::BindgenAttrs, ParseNapi};
|
2021-09-23 02:29:09 +09:00
|
|
|
use proc_macro::TokenStream as RawStream;
|
2021-11-23 20:00:31 +09:00
|
|
|
use proc_macro2::{TokenStream, TokenTree};
|
|
|
|
use quote::ToTokens;
|
2021-09-23 02:29:09 +09:00
|
|
|
use std::env;
|
|
|
|
#[cfg(feature = "type-def")]
|
|
|
|
use std::{
|
|
|
|
fs,
|
|
|
|
io::{BufWriter, Result as IOResult, Write},
|
|
|
|
};
|
|
|
|
#[cfg(feature = "compat-mode")]
|
|
|
|
use syn::{fold::Fold, parse_macro_input, ItemFn};
|
2021-11-23 20:00:31 +09:00
|
|
|
use syn::{Attribute, Item};
|
2021-09-23 02:29:09 +09:00
|
|
|
|
|
|
|
/// ```ignore
|
|
|
|
/// #[napi]
|
|
|
|
/// fn test(ctx: CallContext, name: String) {
|
|
|
|
/// "hello" + name
|
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
#[proc_macro_attribute]
|
|
|
|
pub fn napi(attr: RawStream, input: RawStream) -> RawStream {
|
|
|
|
match expand(attr.into(), input.into()) {
|
|
|
|
Ok(tokens) => {
|
|
|
|
if env::var("DEBUG_GENERATED_CODE").is_ok() {
|
|
|
|
println!("{}", tokens.to_string());
|
|
|
|
}
|
|
|
|
tokens.into()
|
|
|
|
}
|
|
|
|
Err(diagnostic) => {
|
|
|
|
println!("`napi` macro expand failed.");
|
|
|
|
|
|
|
|
(quote! { #diagnostic }).into()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn expand(attr: TokenStream, input: TokenStream) -> BindgenResult<TokenStream> {
|
|
|
|
let mut item = syn::parse2::<syn::Item>(input)?;
|
2021-11-23 20:00:31 +09:00
|
|
|
let opts: BindgenAttrs = syn::parse2(attr)?;
|
2021-09-23 02:29:09 +09:00
|
|
|
let mut tokens = proc_macro2::TokenStream::new();
|
2021-11-23 20:00:31 +09:00
|
|
|
if let Item::Mod(mut js_mod) = item {
|
|
|
|
let js_name = opts.js_name().map_or_else(
|
|
|
|
|| js_mod.ident.to_string(),
|
|
|
|
|(js_name, _)| js_name.to_owned(),
|
|
|
|
);
|
|
|
|
if let Some((_, mut items)) = js_mod.content.clone() {
|
|
|
|
for item in items.iter_mut() {
|
|
|
|
let mut empty_attrs = vec![];
|
|
|
|
if let Some(item_opts) = replace_napi_attr_in_mod(
|
|
|
|
js_name.clone(),
|
|
|
|
match item {
|
|
|
|
syn::Item::Fn(ref mut function) => &mut function.attrs,
|
|
|
|
syn::Item::Struct(ref mut struct_) => &mut struct_.attrs,
|
|
|
|
syn::Item::Enum(ref mut enum_) => &mut enum_.attrs,
|
|
|
|
syn::Item::Const(ref mut const_) => &mut const_.attrs,
|
|
|
|
syn::Item::Impl(ref mut impl_) => &mut impl_.attrs,
|
|
|
|
syn::Item::Mod(mod_) => {
|
|
|
|
let mod_in_mod = mod_
|
|
|
|
.attrs
|
|
|
|
.iter()
|
|
|
|
.enumerate()
|
|
|
|
.find(|(_, m)| m.path.segments[0].ident == "napi");
|
|
|
|
if mod_in_mod.is_some() {
|
|
|
|
bail_span!(
|
|
|
|
mod_,
|
|
|
|
"napi module cannot be nested under another napi module"
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
&mut empty_attrs
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => &mut empty_attrs,
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
let napi = item.parse_napi(&mut tokens, item_opts)?;
|
|
|
|
napi.try_to_tokens(&mut tokens)?;
|
|
|
|
#[cfg(feature = "type-def")]
|
|
|
|
if let Ok(type_def_file) = env::var("TYPE_DEF_TMP_PATH") {
|
|
|
|
if let Err(e) = output_type_def(type_def_file, napi.to_type_def()) {
|
|
|
|
println!("Failed to write type def file: {:?}", e);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
item.to_tokens(&mut tokens);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
js_mod.content = None;
|
2021-09-23 02:29:09 +09:00
|
|
|
};
|
2021-11-23 20:00:31 +09:00
|
|
|
let js_mod_attrs: Vec<Attribute> = js_mod
|
|
|
|
.attrs
|
|
|
|
.clone()
|
|
|
|
.into_iter()
|
|
|
|
.filter(|attr| attr.path.segments[0].ident != "napi")
|
|
|
|
.collect();
|
|
|
|
let mod_name = js_mod.ident;
|
|
|
|
let visible = js_mod.vis;
|
|
|
|
let mod_tokens = quote! { #(#js_mod_attrs)* #visible mod #mod_name { #tokens } };
|
|
|
|
Ok(mod_tokens)
|
|
|
|
} else {
|
|
|
|
let napi = item.parse_napi(&mut tokens, opts)?;
|
|
|
|
napi.try_to_tokens(&mut tokens)?;
|
2021-09-23 02:29:09 +09:00
|
|
|
|
2021-11-23 20:00:31 +09:00
|
|
|
#[cfg(feature = "type-def")]
|
|
|
|
if let Ok(type_def_file) = env::var("TYPE_DEF_TMP_PATH") {
|
|
|
|
if let Err(e) = output_type_def(type_def_file, napi.to_type_def()) {
|
|
|
|
println!("Failed to write type def file: {:?}", e);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
Ok(tokens)
|
|
|
|
}
|
2021-09-23 02:29:09 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(feature = "type-def")]
|
|
|
|
fn output_type_def(type_def_file: String, type_def: TypeDef) -> IOResult<()> {
|
|
|
|
let file = fs::OpenOptions::new()
|
|
|
|
.append(true)
|
|
|
|
.create(true)
|
|
|
|
.open(type_def_file)?;
|
|
|
|
|
|
|
|
let mut writer = BufWriter::<fs::File>::new(file);
|
|
|
|
writer.write_all(type_def.to_string().as_bytes())?;
|
|
|
|
writer.write_all("\n".as_bytes())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(feature = "compat-mode")]
|
|
|
|
#[proc_macro_attribute]
|
|
|
|
pub fn contextless_function(_attr: RawStream, input: RawStream) -> RawStream {
|
|
|
|
let input = parse_macro_input!(input as ItemFn);
|
|
|
|
let mut js_fn = compat_macro::JsFunction::new();
|
|
|
|
js_fn.fold_item_fn(input);
|
|
|
|
let fn_name = js_fn.name.unwrap();
|
|
|
|
let fn_block = js_fn.block;
|
|
|
|
let signature = js_fn.signature.unwrap();
|
|
|
|
let visibility = js_fn.visibility;
|
|
|
|
let new_fn_name = signature.ident.clone();
|
|
|
|
let execute_js_function =
|
|
|
|
compat_macro::get_execute_js_code(new_fn_name, compat_macro::FunctionKind::Contextless);
|
|
|
|
|
|
|
|
let expanded = quote! {
|
|
|
|
#[inline(always)]
|
|
|
|
#signature #(#fn_block)*
|
|
|
|
|
|
|
|
#visibility extern "C" fn #fn_name(
|
|
|
|
raw_env: napi::sys::napi_env,
|
|
|
|
cb_info: napi::sys::napi_callback_info,
|
|
|
|
) -> napi::sys::napi_value {
|
|
|
|
use std::ptr;
|
|
|
|
use std::panic::{self, AssertUnwindSafe};
|
|
|
|
use std::ffi::CString;
|
|
|
|
use napi::{Env, NapiValue, NapiRaw, Error, Status};
|
|
|
|
|
|
|
|
let ctx = unsafe { Env::from_raw(raw_env) };
|
|
|
|
#execute_js_function
|
|
|
|
}
|
|
|
|
};
|
|
|
|
// Hand the output tokens back to the compiler
|
|
|
|
RawStream::from(expanded)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(feature = "compat-mode")]
|
|
|
|
#[proc_macro_attribute]
|
|
|
|
pub fn js_function(attr: RawStream, input: RawStream) -> RawStream {
|
|
|
|
let arg_len = parse_macro_input!(attr as compat_macro::ArgLength);
|
|
|
|
let arg_len_span = arg_len.length;
|
|
|
|
let input = parse_macro_input!(input as ItemFn);
|
|
|
|
let mut js_fn = compat_macro::JsFunction::new();
|
|
|
|
js_fn.fold_item_fn(input);
|
|
|
|
let fn_name = js_fn.name.unwrap();
|
|
|
|
let fn_block = js_fn.block;
|
|
|
|
let signature = js_fn.signature.unwrap();
|
|
|
|
let visibility = js_fn.visibility;
|
|
|
|
let new_fn_name = signature.ident.clone();
|
|
|
|
let execute_js_function =
|
|
|
|
compat_macro::get_execute_js_code(new_fn_name, compat_macro::FunctionKind::JsFunction);
|
|
|
|
let expanded = quote! {
|
|
|
|
#[inline(always)]
|
|
|
|
#signature #(#fn_block)*
|
|
|
|
|
|
|
|
#visibility extern "C" fn #fn_name(
|
|
|
|
raw_env: napi::sys::napi_env,
|
|
|
|
cb_info: napi::sys::napi_callback_info,
|
|
|
|
) -> napi::sys::napi_value {
|
|
|
|
use std::ptr;
|
|
|
|
use std::panic::{self, AssertUnwindSafe};
|
|
|
|
use std::ffi::CString;
|
|
|
|
use napi::{Env, Error, Status, NapiValue, NapiRaw, CallContext};
|
|
|
|
let mut argc = #arg_len_span as usize;
|
2021-10-25 00:13:08 +09:00
|
|
|
#[cfg(all(target_os = "windows", target_arch = "x86"))]
|
|
|
|
let mut raw_args = vec![ptr::null_mut(); #arg_len_span];
|
|
|
|
#[cfg(not(all(target_os = "windows", target_arch = "x86")))]
|
|
|
|
let mut raw_args = [ptr::null_mut(); #arg_len_span];
|
2021-09-23 02:29:09 +09:00
|
|
|
let mut raw_this = ptr::null_mut();
|
|
|
|
|
|
|
|
unsafe {
|
|
|
|
let status = napi::sys::napi_get_cb_info(
|
|
|
|
raw_env,
|
|
|
|
cb_info,
|
|
|
|
&mut argc,
|
|
|
|
raw_args.as_mut_ptr(),
|
|
|
|
&mut raw_this,
|
|
|
|
ptr::null_mut(),
|
|
|
|
);
|
|
|
|
debug_assert!(Status::from(status) == Status::Ok, "napi_get_cb_info failed");
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut env = unsafe { Env::from_raw(raw_env) };
|
2021-10-25 00:13:08 +09:00
|
|
|
#[cfg(all(target_os = "windows", target_arch = "x86"))]
|
|
|
|
let ctx = CallContext::new(&mut env, cb_info, raw_this, raw_args.as_slice(), argc);
|
|
|
|
#[cfg(not(all(target_os = "windows", target_arch = "x86")))]
|
2021-09-23 02:29:09 +09:00
|
|
|
let ctx = CallContext::new(&mut env, cb_info, raw_this, &raw_args, argc);
|
|
|
|
#execute_js_function
|
|
|
|
}
|
|
|
|
};
|
|
|
|
// Hand the output tokens back to the compiler
|
|
|
|
RawStream::from(expanded)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(feature = "compat-mode")]
|
|
|
|
#[proc_macro_attribute]
|
|
|
|
pub fn module_exports(_attr: RawStream, input: RawStream) -> RawStream {
|
|
|
|
let input = parse_macro_input!(input as ItemFn);
|
|
|
|
let mut js_fn = compat_macro::JsFunction::new();
|
|
|
|
js_fn.fold_item_fn(input);
|
|
|
|
let fn_block = js_fn.block;
|
|
|
|
let fn_name = js_fn.name.unwrap();
|
|
|
|
let signature = js_fn.signature_raw.unwrap();
|
|
|
|
let args_len = js_fn.args.len();
|
|
|
|
let call_expr = if args_len == 1 {
|
|
|
|
quote! { #fn_name(exports) }
|
|
|
|
} else if args_len == 2 {
|
|
|
|
quote! { #fn_name(exports, env) }
|
|
|
|
} else {
|
|
|
|
panic!("Arguments length of #[module_exports] function must be 1 or 2");
|
|
|
|
};
|
|
|
|
|
|
|
|
let register = quote! {
|
|
|
|
#[napi::bindgen_prelude::ctor]
|
|
|
|
fn __napi__explicit_module_register() {
|
|
|
|
unsafe fn register(raw_env: napi::sys::napi_env, raw_exports: napi::sys::napi_value) -> napi::Result<()> {
|
|
|
|
use napi::{Env, JsObject, NapiValue};
|
|
|
|
|
|
|
|
let env = Env::from_raw(raw_env);
|
|
|
|
let exports = JsObject::from_raw_unchecked(raw_env, raw_exports);
|
|
|
|
|
|
|
|
#call_expr
|
|
|
|
}
|
|
|
|
|
|
|
|
napi::bindgen_prelude::register_module_exports(register)
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
(quote! {
|
|
|
|
#[inline]
|
|
|
|
#signature #(#fn_block)*
|
|
|
|
|
|
|
|
#register
|
|
|
|
})
|
|
|
|
.into()
|
|
|
|
}
|
2021-11-23 20:00:31 +09:00
|
|
|
|
|
|
|
fn replace_napi_attr_in_mod(
|
|
|
|
js_namespace: String,
|
|
|
|
attrs: &mut Vec<syn::Attribute>,
|
|
|
|
) -> Option<BindgenAttrs> {
|
|
|
|
let napi_attr = attrs.clone();
|
|
|
|
let napi_attr = napi_attr
|
|
|
|
.iter()
|
|
|
|
.enumerate()
|
|
|
|
.find(|(_, m)| m.path.segments[0].ident == "napi");
|
|
|
|
if let Some((index, napi_attr)) = napi_attr {
|
|
|
|
let attr_token_stream = napi_attr.tokens.clone();
|
|
|
|
let raw_attr_stream = attr_token_stream.to_string();
|
|
|
|
let raw_attr_stream = if !raw_attr_stream.is_empty() {
|
|
|
|
raw_attr_stream
|
|
|
|
.strip_prefix('(')
|
|
|
|
.unwrap()
|
|
|
|
.strip_suffix(')')
|
|
|
|
.unwrap()
|
|
|
|
.to_string()
|
|
|
|
} else {
|
|
|
|
raw_attr_stream
|
|
|
|
};
|
|
|
|
let raw_attr_token_stream = syn::parse_str::<TokenStream>(raw_attr_stream.as_str()).unwrap();
|
|
|
|
|
|
|
|
let new_attr: syn::Attribute = if !raw_attr_stream.is_empty() {
|
|
|
|
syn::parse_quote!(
|
|
|
|
#[napi(#raw_attr_token_stream, namespace = #js_namespace)]
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
syn::parse_quote!(
|
|
|
|
#[napi(namespace = #js_namespace)]
|
|
|
|
)
|
|
|
|
};
|
|
|
|
let struct_opts: BindgenAttrs;
|
|
|
|
if let Some(TokenTree::Group(g)) = new_attr.tokens.into_iter().next() {
|
|
|
|
struct_opts = syn::parse2(g.stream()).ok()?;
|
|
|
|
} else {
|
|
|
|
struct_opts = syn::parse2(quote! {}).ok()?;
|
|
|
|
}
|
|
|
|
attrs.remove(index);
|
|
|
|
Some(struct_opts)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|