* The MIT License (MIT)
* Copyright (C) 2024 Huawei Device Co., Ltd.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
#[cfg(feature = "compat-mode")]
mod compat_macro;
mod expand;
mod parser;
#[macro_use]
extern crate syn;
#[macro_use]
extern crate napi_derive_backend;
#[macro_use]
extern crate quote;
use std::env;
use proc_macro::TokenStream;
#[cfg(feature = "compat-mode")]
use syn::{fold::Fold, parse_macro_input, ItemFn};
#[cfg(not(feature = "ohos-explicit-register"))]
mod ohos_register {
use convert_case::{Case, Casing};
use std::env;
use std::sync::atomic::{AtomicBool, Ordering};
static IS_FIRST_NAPI_MACRO: AtomicBool = AtomicBool::new(true);
pub fn gen_napi_module_register() -> proc_macro2::TokenStream {
if IS_FIRST_NAPI_MACRO
.compare_exchange(true, false, Ordering::Acquire, Ordering::Relaxed)
.is_err()
{
return quote!();
}
let name = env::var("CARGO_PKG_NAME")
.map_or(String::from("entry"), |v| v)
.to_case(Case::Snake);
let prepare = quote!(
#[napi::bindgen_prelude::ctor]
fn __napi__ohos_implicit_module_register() {
napi::oh_log_info!("ohos implicit register, module name = {}", #name);
let name = std::ffi::CString::new(#name).expect("Get module name,but failed");
let mut modules = napi::sys::napi_module {
nm_version: 1,
nm_filename: std::ptr::null_mut(),
nm_flags: 0,
nm_modname: name.as_ptr().cast(),
nm_priv: std::ptr::null_mut() as *mut _,
nm_register_func: Some(napi::bindgen_prelude::napi_register_module_v1),
reserved: [std::ptr::null_mut() as *mut _; 4],
};
unsafe {
napi::sys::napi_module_register(&mut modules);
}
}
);
prepare
}
}
#[cfg(feature = "ohos-explicit-register")]
mod ohos_register {
pub fn gen_napi_module_register() -> proc_macro2::TokenStream {
quote!()
}
}
#[proc_macro_attribute]
pub fn napi(attr: TokenStream, input: TokenStream) -> TokenStream {
match expand::expand(attr.into(), input.into()) {
Ok(tokens) => {
if env::var("DEBUG_GENERATED_CODE").is_ok() {
println!("{}", tokens);
}
let active_register = ohos_register::gen_napi_module_register();
let final_token = quote! {
#active_register
#tokens
};
final_token.into()
}
Err(diagnostic) => {
println!("`napi` macro expand failed.");
(quote! { #diagnostic }).into()
}
}
}
#[cfg(feature = "compat-mode")]
#[proc_macro_attribute]
pub fn contextless_function(_attr: TokenStream, input: TokenStream) -> TokenStream {
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
}
};
TokenStream::from(expanded)
}
#[cfg(feature = "compat-mode")]
#[proc_macro_attribute]
pub fn js_function(attr: TokenStream, input: TokenStream) -> TokenStream {
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;
#[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];
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) };
#[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")))]
let ctx = CallContext::new(&mut env, cb_info, raw_this, &raw_args, argc);
#execute_js_function
}
};
TokenStream::from(expanded)
}
#[cfg(feature = "compat-mode")]
#[proc_macro_attribute]
pub fn module_exports(_attr: TokenStream, input: TokenStream) -> TokenStream {
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! {
#[cfg_attr(not(target_family = "wasm"), 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()
}