commit
4f35700816
13 changed files with 285 additions and 76 deletions
9
.npmignore
Normal file
9
.npmignore
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
target
|
||||||
|
build
|
||||||
|
napi
|
||||||
|
napi-derive
|
||||||
|
napi-derive-example
|
||||||
|
test_module
|
||||||
|
.yarnrc
|
||||||
|
Cargo.lock
|
||||||
|
rustfmt.toml
|
|
@ -1,6 +1,8 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"./build",
|
"./build",
|
||||||
"./napi"
|
"./napi",
|
||||||
|
"./napi-derive",
|
||||||
|
"./napi-derive-example"
|
||||||
]
|
]
|
||||||
exclude = ["./test_module"]
|
exclude = ["./test_module"]
|
||||||
|
|
15
napi-derive-example/Cargo.toml
Normal file
15
napi-derive-example/Cargo.toml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
[package]
|
||||||
|
name = "napi-derive-example"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["LongYinan <lynweklm@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
napi-rs = { path = "../napi" }
|
||||||
|
napi-derive = { path = "../napi-derive" }
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
napi-build = { path = "../build" }
|
7
napi-derive-example/build.rs
Normal file
7
napi-derive-example/build.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
extern crate napi_build;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
use napi_build::setup;
|
||||||
|
|
||||||
|
setup();
|
||||||
|
}
|
46
napi-derive-example/src/lib.rs
Normal file
46
napi-derive-example/src/lib.rs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
#[macro_use]
|
||||||
|
extern crate napi_rs as napi;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate napi_derive;
|
||||||
|
|
||||||
|
use napi::{Any, Env, Error, Object, Result, Status, Value, CallContext, Number};
|
||||||
|
use std::convert::TryInto;
|
||||||
|
|
||||||
|
register_module!(test_module, init);
|
||||||
|
|
||||||
|
fn init<'env>(
|
||||||
|
env: &'env Env,
|
||||||
|
exports: &'env mut Value<'env, Object>,
|
||||||
|
) -> Result<Option<Value<'env, Object>>> {
|
||||||
|
exports.set_named_property(
|
||||||
|
"testThrow",
|
||||||
|
env.create_function("testThrow", test_throw)?,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
exports.set_named_property(
|
||||||
|
"fibonacci",
|
||||||
|
env.create_function("fibonacci", fibonacci)?,
|
||||||
|
)?;
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[js_function]
|
||||||
|
fn test_throw<'a>(
|
||||||
|
_ctx: CallContext,
|
||||||
|
) -> Result<Value<'a, Any>> {
|
||||||
|
Err(Error::new(Status::GenericFailure))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[js_function]
|
||||||
|
fn fibonacci<'env>(ctx: CallContext<'env>) -> Result<Value<'env, Number>> {
|
||||||
|
let n = ctx.get::<Number>(0)?.try_into()?;
|
||||||
|
ctx.env.create_int64(fibonacci_native(n))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn fibonacci_native(n: i64) -> i64 {
|
||||||
|
match n {
|
||||||
|
1 | 2 => 1,
|
||||||
|
_ => fibonacci_native(n - 1) + fibonacci_native(n - 2)
|
||||||
|
}
|
||||||
|
}
|
13
napi-derive/Cargo.toml
Normal file
13
napi-derive/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "napi-derive"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["LongYinan <lynweklm@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
syn = { version = "1.0", features = ["fold", "full"] }
|
||||||
|
quote = "1.0"
|
||||||
|
proc-macro2 = "1.0"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
23
napi-derive/README.md
Normal file
23
napi-derive/README.md
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# napi-derive
|
||||||
|
|
||||||
|
## js_function
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use napi_rs::{Result, Value, CallContext, Number};
|
||||||
|
use napi_derive::js_function;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
|
||||||
|
#[js_function]
|
||||||
|
fn fibonacci<'env>(ctx: CallContext<'env>) -> Result<Value<'env, Number>> {
|
||||||
|
let n = ctx.get::<Number>(0)?.try_into()?;
|
||||||
|
ctx.env.create_int64(fibonacci_native(n))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn fibonacci_native(n: i64) -> i64 {
|
||||||
|
match n {
|
||||||
|
1 | 2 => 1,
|
||||||
|
_ => fibonacci_native(n - 1) + fibonacci_native(n - 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
128
napi-derive/src/lib.rs
Normal file
128
napi-derive/src/lib.rs
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
extern crate proc_macro;
|
||||||
|
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use proc_macro2::{Ident, Literal};
|
||||||
|
use quote::{quote, format_ident};
|
||||||
|
use syn::fold::{fold_signature, fold_fn_arg, Fold};
|
||||||
|
use syn::parse::{Parse, ParseStream, Result};
|
||||||
|
use syn::punctuated::Punctuated;
|
||||||
|
use syn::{parse_macro_input, FnArg, ItemFn, Signature, Block, Token};
|
||||||
|
|
||||||
|
struct ArgLength {
|
||||||
|
length: Option<Literal>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for ArgLength {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
let vars = Punctuated::<Literal, Token![,]>::parse_terminated(input)?;
|
||||||
|
Ok(ArgLength {
|
||||||
|
length: vars.first().map(|i| i.clone()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct JsFunction {
|
||||||
|
args: Vec<FnArg>,
|
||||||
|
name: Option<Ident>,
|
||||||
|
signature: Option<Signature>,
|
||||||
|
block: Vec<Block>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JsFunction {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
JsFunction {
|
||||||
|
args: vec![],
|
||||||
|
name: None,
|
||||||
|
signature: None,
|
||||||
|
block: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Fold for JsFunction {
|
||||||
|
fn fold_fn_arg(&mut self, arg: FnArg) -> FnArg {
|
||||||
|
self.args.push(arg.clone());
|
||||||
|
fold_fn_arg(self, arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fold_signature(&mut self, signature: Signature) -> Signature {
|
||||||
|
self.name = Some(format_ident!("{}", signature.ident));
|
||||||
|
let mut new_signature = signature.clone();
|
||||||
|
new_signature.ident = format_ident!("_{}", signature.ident);
|
||||||
|
self.signature = Some(new_signature);
|
||||||
|
fold_signature(self, signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fold_block(&mut self, node: Block) -> Block {
|
||||||
|
self.block.push(node.clone());
|
||||||
|
node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn js_function(attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
|
let arg_len = parse_macro_input!(attr as ArgLength);
|
||||||
|
let arg_len_span = arg_len.length.unwrap_or(Literal::usize_unsuffixed(0));
|
||||||
|
let input = parse_macro_input!(input as ItemFn);
|
||||||
|
let mut js_fn = 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 new_fn_name = signature.ident.clone();
|
||||||
|
let expanded = quote! {
|
||||||
|
#signature #(#fn_block)*
|
||||||
|
|
||||||
|
extern "C" fn #fn_name(
|
||||||
|
raw_env: napi_rs::sys::napi_env,
|
||||||
|
cb_info: napi_rs::sys::napi_callback_info,
|
||||||
|
) -> napi_rs::sys::napi_value {
|
||||||
|
use std::io::Write;
|
||||||
|
use std::mem;
|
||||||
|
use std::os::raw::c_char;
|
||||||
|
use std::ptr;
|
||||||
|
use napi_rs::{Any, Env, Status, Value, CallContext};
|
||||||
|
let mut argc = #arg_len_span as usize;
|
||||||
|
let mut raw_args =
|
||||||
|
unsafe { mem::MaybeUninit::<[napi_rs::sys::napi_value; 8]>::uninit().assume_init() };
|
||||||
|
let mut raw_this = ptr::null_mut();
|
||||||
|
|
||||||
|
let mut has_error = false;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let status = napi_rs::sys::napi_get_cb_info(
|
||||||
|
raw_env,
|
||||||
|
cb_info,
|
||||||
|
&mut argc as *mut usize as *mut u64,
|
||||||
|
&mut raw_args[0],
|
||||||
|
&mut raw_this,
|
||||||
|
ptr::null_mut(),
|
||||||
|
);
|
||||||
|
has_error = has_error && (Status::from(status) == Status::Ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
let env = Env::from_raw(raw_env);
|
||||||
|
let call_ctx = CallContext::new(&env, raw_this, raw_args, #arg_len_span);
|
||||||
|
let result = call_ctx.and_then(|ctx| #new_fn_name(ctx));
|
||||||
|
has_error = has_error && result.is_err();
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(result) => result.into_raw(),
|
||||||
|
Err(e) => {
|
||||||
|
if !cfg!(windows) {
|
||||||
|
let _ = writeln!(::std::io::stderr(), "Error calling function: {:?}", e);
|
||||||
|
}
|
||||||
|
let message = format!("{:?}", e);
|
||||||
|
unsafe {
|
||||||
|
napi_rs::sys::napi_throw_error(raw_env, ptr::null(), message.as_ptr() as *const c_char);
|
||||||
|
}
|
||||||
|
let mut undefined = ptr::null_mut();
|
||||||
|
unsafe { napi_rs::sys::napi_get_undefined(raw_env, &mut undefined) };
|
||||||
|
undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Hand the output tokens back to the compiler
|
||||||
|
TokenStream::from(expanded)
|
||||||
|
}
|
22
napi/src/call_context.rs
Normal file
22
napi/src/call_context.rs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
use crate::{Any, Value, Env, ValueType, Error, Status, Result, sys};
|
||||||
|
|
||||||
|
pub struct CallContext<'env, T: ValueType = Any> {
|
||||||
|
pub env: &'env Env,
|
||||||
|
pub this: Value<'env, T>,
|
||||||
|
args: [sys::napi_value; 8],
|
||||||
|
arg_len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <'env, T: ValueType> CallContext <'env, T> {
|
||||||
|
pub fn new(env: &'env Env, this: sys::napi_value, args: [sys::napi_value; 8], arg_len: usize) -> Result<Self> {
|
||||||
|
Ok(Self { env, this: Value::<'env, T>::from_raw(env, this)?, args, arg_len })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get<ArgType: ValueType>(&'env self, index: usize) -> Result<Value<'env, ArgType>> {
|
||||||
|
if index + 1 > self.arg_len {
|
||||||
|
Err(Error::new(Status::GenericFailure))
|
||||||
|
} else {
|
||||||
|
Value::<'env, ArgType>::from_raw(&self.env, self.args[index])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,7 +13,9 @@ use std::string::String as RustString;
|
||||||
|
|
||||||
mod executor;
|
mod executor;
|
||||||
pub mod sys;
|
pub mod sys;
|
||||||
|
mod call_context;
|
||||||
|
|
||||||
|
pub use call_context::CallContext;
|
||||||
pub use sys::{napi_valuetype, Status};
|
pub use sys::{napi_valuetype, Status};
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
@ -157,68 +159,6 @@ macro_rules! register_module {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! callback {
|
|
||||||
($callback_expr:expr) => {{
|
|
||||||
use std::io::Write;
|
|
||||||
use std::mem;
|
|
||||||
use std::os::raw::c_char;
|
|
||||||
use std::ptr;
|
|
||||||
use $crate::sys;
|
|
||||||
use $crate::{Any, Env, Status, Value};
|
|
||||||
|
|
||||||
extern "C" fn raw_callback(
|
|
||||||
raw_env: sys::napi_env,
|
|
||||||
cb_info: sys::napi_callback_info,
|
|
||||||
) -> sys::napi_value {
|
|
||||||
const MAX_ARGC: usize = 8;
|
|
||||||
let mut argc = MAX_ARGC;
|
|
||||||
let mut raw_args =
|
|
||||||
unsafe { mem::MaybeUninit::<[$crate::sys::napi_value; MAX_ARGC]>::uninit().assume_init() };
|
|
||||||
let mut raw_this = ptr::null_mut();
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let status = sys::napi_get_cb_info(
|
|
||||||
raw_env,
|
|
||||||
cb_info,
|
|
||||||
&mut argc as *mut usize as *mut u64,
|
|
||||||
&mut raw_args[0],
|
|
||||||
&mut raw_this,
|
|
||||||
ptr::null_mut(),
|
|
||||||
);
|
|
||||||
debug_assert!(Status::from(status) == Status::Ok);
|
|
||||||
}
|
|
||||||
|
|
||||||
let env = Env::from_raw(raw_env);
|
|
||||||
let this = Value::from_raw(&env, raw_this).unwrap();
|
|
||||||
let mut args = unsafe { mem::MaybeUninit::<[Value<Any>; 8]>::uninit().assume_init() };
|
|
||||||
for (i, raw_arg) in raw_args.iter().enumerate() {
|
|
||||||
args[i] = Value::from_raw(&env, *raw_arg).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
let callback = $callback_expr;
|
|
||||||
let result = callback(&env, this, &args[0..argc]);
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(Some(result)) => result.into_raw(),
|
|
||||||
Ok(None) => env.get_undefined().unwrap().into_raw(),
|
|
||||||
Err(e) => {
|
|
||||||
if !cfg!(windows) {
|
|
||||||
let _ = writeln!(::std::io::stderr(), "Error calling function: {:?}", e);
|
|
||||||
}
|
|
||||||
let message = format!("{:?}", e);
|
|
||||||
unsafe {
|
|
||||||
$crate::sys::napi_throw_error(raw_env, ptr::null(), message.as_ptr() as *const c_char);
|
|
||||||
}
|
|
||||||
env.get_undefined().unwrap().into_raw()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
raw_callback
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
pub fn new(status: Status) -> Self {
|
pub fn new(status: Status) -> Self {
|
||||||
Error { status: status }
|
Error { status: status }
|
||||||
|
|
|
@ -10,6 +10,7 @@ crate-type = ["cdylib"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
napi-rs = {path = "../napi"}
|
napi-rs = {path = "../napi"}
|
||||||
|
napi-derive = {path = "../napi-derive"}
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
napi-build = { path = "../build" }
|
napi-build = { path = "../build" }
|
||||||
|
|
|
@ -3,7 +3,8 @@ extern crate napi_rs as napi;
|
||||||
|
|
||||||
extern crate futures;
|
extern crate futures;
|
||||||
|
|
||||||
use napi::{Any, Env, Error, Object, Result, Status, Value};
|
use napi::{Any, Env, Error, Object, Result, Status, Value, CallContext};
|
||||||
|
use napi_derive::js_function;
|
||||||
|
|
||||||
register_module!(test_module, init);
|
register_module!(test_module, init);
|
||||||
|
|
||||||
|
@ -13,23 +14,22 @@ fn init<'env>(
|
||||||
) -> Result<Option<Value<'env, Object>>> {
|
) -> Result<Option<Value<'env, Object>>> {
|
||||||
exports.set_named_property(
|
exports.set_named_property(
|
||||||
"testSpawn",
|
"testSpawn",
|
||||||
env.create_function("testSpawn", callback!(test_spawn))?,
|
env.create_function("testSpawn", test_spawn)?,
|
||||||
)?;
|
)?;
|
||||||
exports.set_named_property(
|
exports.set_named_property(
|
||||||
"testThrow",
|
"testThrow",
|
||||||
env.create_function("testThrow", callback!(test_throw))?,
|
env.create_function("testThrow", test_throw)?,
|
||||||
)?;
|
)?;
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[js_function]
|
||||||
fn test_spawn<'a>(
|
fn test_spawn<'a>(
|
||||||
env: &'a Env,
|
ctx: CallContext<'a>
|
||||||
_this: Value<'a, Any>,
|
) -> Result<Value<'a, Object>> {
|
||||||
_args: &[Value<'a, Any>],
|
|
||||||
) -> Result<Option<Value<'a, Any>>> {
|
|
||||||
use futures::executor::ThreadPool;
|
use futures::executor::ThreadPool;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
|
let env = ctx.env;
|
||||||
let async_context = env.async_init(None, "test_spawn")?;
|
let async_context = env.async_init(None, "test_spawn")?;
|
||||||
let pool = ThreadPool::new().expect("Failed to build pool");
|
let pool = ThreadPool::new().expect("Failed to build pool");
|
||||||
let (promise, deferred) = env.create_promise()?;
|
let (promise, deferred) = env.create_promise()?;
|
||||||
|
@ -55,13 +55,12 @@ fn test_spawn<'a>(
|
||||||
|
|
||||||
env.create_executor().execute(fut_values);
|
env.create_executor().execute(fut_values);
|
||||||
|
|
||||||
Ok(Some(promise.into_any()))
|
Ok(promise)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[js_function]
|
||||||
fn test_throw<'a>(
|
fn test_throw<'a>(
|
||||||
_env: &'a Env,
|
_ctx: CallContext<'a>,
|
||||||
_this: Value<'a, Any>,
|
) -> Result<Value<'a, Any>> {
|
||||||
_args: &[Value<'a, Any>],
|
|
||||||
) -> Result<Option<Value<'a, Any>>> {
|
|
||||||
Err(Error::new(Status::GenericFailure))
|
Err(Error::new(Status::GenericFailure))
|
||||||
}
|
}
|
||||||
|
|
4
test_module/yarn.lock
Normal file
4
test_module/yarn.lock
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue