diff --git a/Cargo.lock b/Cargo.lock index c0923994..350029c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -800,7 +800,7 @@ dependencies = [ [[package]] name = "nj-core" -version = "1.2.1" +version = "2.0.0" dependencies = [ "async-trait", "ctor", @@ -816,7 +816,7 @@ dependencies = [ [[package]] name = "nj-derive" -version = "1.0.0" +version = "2.0.0" dependencies = [ "Inflector", "proc-macro2", @@ -830,7 +830,7 @@ version = "1.0.0" [[package]] name = "node-bindgen" -version = "1.3.0" +version = "2.0.0" dependencies = [ "nj-build", "nj-core", @@ -999,9 +999,9 @@ checksum = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e" [[package]] name = "proc-macro2" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acb317c6ff86a4e579dfa00fc5e6cca91ecbb4e7eb2df0468805b674eb88548" +checksum = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3" dependencies = [ "unicode-xid", ] @@ -1014,9 +1014,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" +checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" dependencies = [ "proc-macro2", ] @@ -1141,9 +1141,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535622e6be132bccd223f4bb2b8ac8d53cda3c7a6394944d3b2b33fb974f9d76" +checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" [[package]] name = "schannel" @@ -1175,9 +1175,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ddb15a5fec93b7021b8a9e96009c5d8d51c15673569f7c0f6b7204e5b7b404f" +checksum = "17bf11d99252f512695eb468de5516e5cf75455521e69dfe343f3b74e4748405" dependencies = [ "core-foundation-sys", "libc", @@ -1206,9 +1206,9 @@ checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399" [[package]] name = "serde_json" -version = "1.0.51" +version = "1.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da07b57ee2623368351e9a0488bb0b261322a15a6e0ae53e243cbdc0f4208da9" +checksum = "a7894c8ed05b7a3a279aeb79025fdec1d3158080b75b98a08faf2806bb799edd" dependencies = [ "itoa", "ryu", @@ -1241,9 +1241,9 @@ checksum = "5c2fb2ec9bcd216a5b0d0ccf31ab17b5ed1d627960edff65bbe95d3ce221cefc" [[package]] name = "syn" -version = "1.0.14" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f5" +checksum = "410a7488c0a728c7ceb4ad59b9567eb4053d02e8cc7f5c0e0eeeb39518369213" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 7d425830..aecefaaa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "node-bindgen" -version = "1.3.0" +version = "2.0.0" authors = ["Fluvio Contributors "] edition = "2018" description = "easy way to write nodejs module using rust" @@ -8,9 +8,6 @@ repository = "https://github.com/infinyon/node-bindgen" readme = "README.md" license = "Apache-2.0" -[lib] -test = false - #[patch.crates-io] #flv-future-aio = { path = "../flv-future/src/future-aio"} @@ -21,6 +18,6 @@ build = ["nj-build"] [dependencies] nj-sys = { path = "nj-sys", version = "1.0.0", optional = true } -nj-core = { path = "nj-core", version = "1.2.1", optional = true } +nj-core = { path = "nj-core", version = "2.0.0", optional = true } nj-build = { path = "nj-build", version = "0.2.0", optional = true } -nj-derive = { path = "nj-derive", version = "1.0.0", optional = true} \ No newline at end of file +nj-derive = { path = "nj-derive", version = "2.0.0", optional = true} diff --git a/Makefile b/Makefile index 03567786..49b69c2d 100644 --- a/Makefile +++ b/Makefile @@ -7,5 +7,12 @@ build-windows: cargo build --target=x86_64-pc-windows-gnu -test: - make -C examples test \ No newline at end of file +test: test-derive + make -C examples test + + +test-derive: + cd nj-derive; RUST_LOG=debug cargo test derive_ui -- --nocapture + +test-try: + cd nj-derive; RUST_LOG=debug cargo test derive_try -- --nocapture \ No newline at end of file diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 36f3abc7..a0457f52 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -800,7 +800,7 @@ dependencies = [ [[package]] name = "nj-core" -version = "1.2.1" +version = "2.0.0" dependencies = [ "async-trait", "ctor", @@ -816,7 +816,7 @@ dependencies = [ [[package]] name = "nj-derive" -version = "1.0.0" +version = "2.0.0" dependencies = [ "Inflector", "proc-macro2", @@ -840,13 +840,27 @@ dependencies = [ ] [[package]] -name = "nj-example-class" +name = "nj-example-class-async" version = "0.1.0" dependencies = [ "flv-future-aio", "node-bindgen", ] +[[package]] +name = "nj-example-class-simple" +version = "0.1.0" +dependencies = [ + "node-bindgen", +] + +[[package]] +name = "nj-example-class-wrapper" +version = "0.1.0" +dependencies = [ + "node-bindgen", +] + [[package]] name = "nj-example-function" version = "0.1.0" @@ -854,6 +868,20 @@ dependencies = [ "node-bindgen", ] +[[package]] +name = "nj-example-jsenv" +version = "0.1.0" +dependencies = [ + "node-bindgen", +] + +[[package]] +name = "nj-example-json" +version = "0.1.0" +dependencies = [ + "node-bindgen", +] + [[package]] name = "nj-example-promise" version = "0.1.0" @@ -877,7 +905,7 @@ version = "1.0.0" [[package]] name = "node-bindgen" -version = "1.3.0" +version = "2.0.0" dependencies = [ "nj-build", "nj-core", diff --git a/examples/Cargo.toml b/examples/Cargo.toml index ec02009f..0007f551 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -1,11 +1,15 @@ [workspace] members = [ - "class", "function", "cb", "promise", "async-cb", - "stream" + "json", + "js-env", + "class-simple", + "class-wrapper", + "class-async", + "stream", ] #[patch.crates-io] diff --git a/examples/Makefile b/examples/Makefile index 312dbd4f..d995ccd7 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -1,12 +1,17 @@ clean: + cargo clean make -C function clean make -C async-cb clean make -C cb clean make -C promise clean - make -C class clean make -C stream clean + make -C json clean + make -C class-simple clean + make -C class-wrapper clean + make -C class-async clean -test: test-function test-cb test-class test-promise test-stream + +test: test-function test-cb test-async-cb test-promise test-json test-class-simple test-class-wrapper test-class-async test-stream test-function: make -C function test @@ -20,8 +25,17 @@ test-cb: test-promise: make -C promise test -test-class: - make -C class test +test-json: + make -C json test test-stream: - make -C stream test \ No newline at end of file + make -C stream test + +test-class-simple: + make -C class-simple test + +test-class-wrapper: + make -C class-wrapper test + +test-class-async: + make -C class-async test \ No newline at end of file diff --git a/examples/async-cb/Makefile b/examples/async-cb/Makefile index bb6c7236..708da103 100644 --- a/examples/async-cb/Makefile +++ b/examples/async-cb/Makefile @@ -1,9 +1,5 @@ - -DYLIB = ../target/debug/libnj_example_async_cb.dylib - all: build - build: nj-cli build diff --git a/examples/async-cb/README.md b/examples/async-cb/README.md index 5e0bec32..6f2a53d1 100644 --- a/examples/async-cb/README.md +++ b/examples/async-cb/README.md @@ -1 +1 @@ -simple hello world callback \ No newline at end of file +callback inside async fn \ No newline at end of file diff --git a/examples/async-cb/build.rs b/examples/async-cb/build.rs index f9599528..9b831a3a 100644 --- a/examples/async-cb/build.rs +++ b/examples/async-cb/build.rs @@ -1,4 +1,3 @@ fn main() { - node_bindgen::build::configure(); -} \ No newline at end of file +} diff --git a/examples/async-cb/src/lib.rs b/examples/async-cb/src/lib.rs index 6af5a36f..43e02d96 100644 --- a/examples/async-cb/src/lib.rs +++ b/examples/async-cb/src/lib.rs @@ -1,13 +1,21 @@ - use std::time::Duration; use flv_future_aio::timer::sleep; use node_bindgen::derive::node_bindgen; +#[node_bindgen] +async fn basic( seconds: i32, cb: F) { + + sleep(Duration::from_secs(1)).await; + cb(seconds as f64,(seconds*2) as f64); + +} + + #[node_bindgen] async fn hello( seconds: i32, cb: F) { - + // println!("sleeping"); sleep(Duration::from_secs(seconds as u64)).await; // println!("woke from time"); @@ -15,6 +23,3 @@ async fn hello( seconds: i32, cb: F) { cb(10.0,"hello world".to_string()); } - - - diff --git a/examples/async-cb/test_cb.js b/examples/async-cb/test_cb.js index f53119ef..3820d218 100644 --- a/examples/async-cb/test_cb.js +++ b/examples/async-cb/test_cb.js @@ -1,9 +1,17 @@ let addon = require('./dist'); const assert = require('assert'); + addon.hello(1,function(val,msg){ assert.equal(val,10); assert.equal(msg,"hello world"); console.log("callback test succeed"); }); + +addon.basic(10,function(val,val2){ + assert.equal(val,10); + assert.equal(val2,20); + console.log("callback test succeed"); +}); + diff --git a/examples/cb/Makefile b/examples/cb/Makefile index 2682d3ee..d34ad2ab 100644 --- a/examples/cb/Makefile +++ b/examples/cb/Makefile @@ -1,6 +1,3 @@ - -DYLIB = ../target/debug/libnj_example_cb.dylib - all: build build: diff --git a/examples/cb/src/lib.rs b/examples/cb/src/lib.rs index 31025847..25726b40 100644 --- a/examples/cb/src/lib.rs +++ b/examples/cb/src/lib.rs @@ -5,4 +5,12 @@ fn hello(first: f64, second: F) { let msg = format!("argument is: {}", first); second(msg); -} \ No newline at end of file +} + + +#[node_bindgen] +fn example(cb: F,second: i32) { + cb(second*2) +} + + diff --git a/examples/cb/test.js b/examples/cb/test.js index 4f74b98e..1eb1eb39 100644 --- a/examples/cb/test.js +++ b/examples/cb/test.js @@ -7,5 +7,9 @@ addon.hello(2,function(msg){ }); assert.throws( () => addon.hello(2),{ - message: '2 args expected but 1 is present' -}); \ No newline at end of file + message: 'expected argument of type: callback' +}); + +addon.example(function(val){ + assert.equal(val,20); +},10); \ No newline at end of file diff --git a/examples/class/.gitignore b/examples/class-async/.gitignore similarity index 100% rename from examples/class/.gitignore rename to examples/class-async/.gitignore diff --git a/examples/class/Cargo.toml b/examples/class-async/Cargo.toml similarity index 89% rename from examples/class/Cargo.toml rename to examples/class-async/Cargo.toml index ac343956..2a60e63b 100644 --- a/examples/class/Cargo.toml +++ b/examples/class-async/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "nj-example-class" +name = "nj-example-class-async" version = "0.1.0" authors = ["fluvio.io"] edition = "2018" diff --git a/examples/class/Makefile b/examples/class-async/Makefile similarity index 60% rename from examples/class/Makefile rename to examples/class-async/Makefile index 0b576550..a61313ae 100644 --- a/examples/class/Makefile +++ b/examples/class-async/Makefile @@ -1,6 +1,3 @@ - -DYLIB = ../target/debug/libnj_example_wrapper.dylib - all: build build: diff --git a/examples/class/README.md b/examples/class-async/README.md similarity index 100% rename from examples/class/README.md rename to examples/class-async/README.md diff --git a/examples/class/build.rs b/examples/class-async/build.rs similarity index 100% rename from examples/class/build.rs rename to examples/class-async/build.rs diff --git a/examples/class-async/src/lib.rs b/examples/class-async/src/lib.rs new file mode 100644 index 00000000..907510ce --- /dev/null +++ b/examples/class-async/src/lib.rs @@ -0,0 +1,89 @@ + +use std::time::Duration; + + +use flv_future_aio::timer::sleep; + +use node_bindgen::sys::napi_value; +use node_bindgen::core::NjError; +use node_bindgen::core::val::JsObject; +use node_bindgen::core::val::JsEnv; +use node_bindgen::core::TryIntoJs; +use node_bindgen::derive::node_bindgen; + + +struct MyJson { + val: f64 +} + + +impl TryIntoJs for MyJson { + + fn try_to_js(self, js_env: &JsEnv) -> Result { + + // create JSON + let mut json = JsObject::new(js_env.clone(), js_env.create_object()?); + + let js_val = js_env.create_double(self.val)?; + json.set_property("val",js_val)?; + + json.try_to_js(js_env) + } +} + + + +struct MyObject { + val: f64, +} + + +#[node_bindgen] +impl MyObject { + + #[node_bindgen(constructor)] + fn new(val: f64) -> Self { + Self { val } + } + + + /// promise which result in primitive type + #[node_bindgen] + async fn plus_two(&self, arg: f64) -> f64 { + + println!("sleeping"); + sleep(Duration::from_secs(1)).await; + println!("woke and adding {}",arg); + + self.val + arg + } + + + /// promise where result is arbitrary struct. + /// returning struct must implement TryIntoJs + /// which can create new JS instance + #[node_bindgen] + async fn multiply2(&self,arg: f64) -> MyObjectConstructor { + + println!("sleeping"); + sleep(Duration::from_secs(1)).await; + println!("woke and adding {}",arg); + + MyObjectConstructor::new(self.val * arg) + } + + + + /// loop and emit event + #[node_bindgen] + async fn sleep(&self,cb: F) { + + println!("sleeping"); + sleep(Duration::from_secs(1)).await; + let msg = format!("hello world"); + cb(msg); + + } + + +} diff --git a/examples/class-async/test.js b/examples/class-async/test.js new file mode 100644 index 00000000..a50dad17 --- /dev/null +++ b/examples/class-async/test.js @@ -0,0 +1,18 @@ +const assert = require('assert'); + +let addon = require('./dist'); + +let obj = new addon.MyObject(10); + + +obj.plusTwo(10).then( (val) => { + console.log("plus two is ",val); +}); + +obj.multiply2(-1).then( (obj3) => { + console.log("multiply two ",obj3.value); +}); + +obj.sleep((msg) => { + assert.equal(msg,"hello world");; +}); diff --git a/examples/class-simple/.gitignore b/examples/class-simple/.gitignore new file mode 100644 index 00000000..53c37a16 --- /dev/null +++ b/examples/class-simple/.gitignore @@ -0,0 +1 @@ +dist \ No newline at end of file diff --git a/examples/class-simple/Cargo.toml b/examples/class-simple/Cargo.toml new file mode 100644 index 00000000..9a3f3dfd --- /dev/null +++ b/examples/class-simple/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "nj-example-class-simple" +version = "0.1.0" +authors = ["fluvio.io"] +edition = "2018" + + +[lib] +crate-type = ["cdylib"] + + +[dependencies] +node-bindgen = { path = "../.."} + + +[build-dependencies] +node-bindgen = { path = "../../", features = ["build"] } diff --git a/examples/class-simple/Makefile b/examples/class-simple/Makefile new file mode 100644 index 00000000..a61313ae --- /dev/null +++ b/examples/class-simple/Makefile @@ -0,0 +1,12 @@ +all: build + +build: + nj-cli build + +test: build + node test.js + + +clean: + rm -rf dist + diff --git a/examples/class-simple/README.md b/examples/class-simple/README.md new file mode 100644 index 00000000..61600274 --- /dev/null +++ b/examples/class-simple/README.md @@ -0,0 +1 @@ +simple hello world \ No newline at end of file diff --git a/examples/class-simple/build.rs b/examples/class-simple/build.rs new file mode 100644 index 00000000..9b831a3a --- /dev/null +++ b/examples/class-simple/build.rs @@ -0,0 +1,3 @@ +fn main() { + node_bindgen::build::configure(); +} diff --git a/examples/class-simple/src/lib.rs b/examples/class-simple/src/lib.rs new file mode 100644 index 00000000..195ba4fa --- /dev/null +++ b/examples/class-simple/src/lib.rs @@ -0,0 +1,65 @@ +use node_bindgen::derive::node_bindgen; + +struct MyObject { + val: f64, +} + +#[node_bindgen] +impl MyObject { + #[node_bindgen(constructor)] + fn new(val: f64) -> Self { + Self { val } + } + + /// simple method which return f64 + /// rust values are automatically converted into equivalent JS value + /// method name are generated from rust method name + /// Js: let y = obj.plusOne(); + #[node_bindgen] + fn plus_one(&self) -> f64 { + self.val + 1.0 + } + + /// JS getter + /// Js: let y = obj.value; + #[node_bindgen(getter)] + fn value(&self) -> f64 { + self.val + } + + /// JS Setter + /// Js: obj.value3 = 10; + #[node_bindgen(setter)] + fn value3(&mut self, val: f64) { + self.val = val; + } + + /// method with custom name instead of generated name + /// Js: obj.set_value(10); + #[node_bindgen(name = "value2")] + fn set_value(&mut self, val: f64) { + self.val = val; + } + + #[node_bindgen(setter, name = "value4")] + fn set_value4(&mut self, val: f64) { + self.val = val; + } + + #[node_bindgen] + fn change_value(&mut self, val: f64) { + self.val = val; + } + + #[node_bindgen(getter)] + fn is_positive(&self) -> bool { + self.val > 0.0 + } + + #[node_bindgen(setter)] + fn clear(&mut self, val: bool) { + if val { + self.val = 0.0; + } + } +} diff --git a/examples/class-simple/test.js b/examples/class-simple/test.js new file mode 100644 index 00000000..8f2ee05f --- /dev/null +++ b/examples/class-simple/test.js @@ -0,0 +1,35 @@ +const assert = require('assert'); + +let addon = require('./dist'); + + +let obj = new addon.MyObject(10); +assert.equal(obj.value,10,"verify value works"); +assert.equal(obj.plusOne(),11); + + +obj.changeValue(100); +assert.equal(obj.value,100); + +obj.value2(50); +assert.equal(obj.value,50); + +obj.value3 = 10; +assert.equal(obj.value,10,"test setter"); + +obj.value4 = 60; +assert.equal(obj.value,60,"test test setter with custom property"); + +obj.value4 = -10; +assert.equal(obj.isPositive,false); + +obj.value4 = 10; +assert.equal(obj.isPositive,true); + +obj.clear = false; +assert.equal(obj.value,10); + +obj.clear = true; +assert.equal(obj.value,0); + +console.log("class simple test succeed"); \ No newline at end of file diff --git a/examples/class-wrapper/.gitignore b/examples/class-wrapper/.gitignore new file mode 100644 index 00000000..53c37a16 --- /dev/null +++ b/examples/class-wrapper/.gitignore @@ -0,0 +1 @@ +dist \ No newline at end of file diff --git a/examples/class-wrapper/Cargo.toml b/examples/class-wrapper/Cargo.toml new file mode 100644 index 00000000..dda72c16 --- /dev/null +++ b/examples/class-wrapper/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "nj-example-class-wrapper" +version = "0.1.0" +authors = ["fluvio.io"] +edition = "2018" + + +[lib] +crate-type = ["cdylib"] + + +[dependencies] +node-bindgen = { path = "../.."} + + +[build-dependencies] +node-bindgen = { path = "../../", features = ["build"] } diff --git a/examples/class-wrapper/Makefile b/examples/class-wrapper/Makefile new file mode 100644 index 00000000..a61313ae --- /dev/null +++ b/examples/class-wrapper/Makefile @@ -0,0 +1,12 @@ +all: build + +build: + nj-cli build + +test: build + node test.js + + +clean: + rm -rf dist + diff --git a/examples/class-wrapper/README.md b/examples/class-wrapper/README.md new file mode 100644 index 00000000..61600274 --- /dev/null +++ b/examples/class-wrapper/README.md @@ -0,0 +1 @@ +simple hello world \ No newline at end of file diff --git a/examples/class-wrapper/build.rs b/examples/class-wrapper/build.rs new file mode 100644 index 00000000..f9599528 --- /dev/null +++ b/examples/class-wrapper/build.rs @@ -0,0 +1,4 @@ +fn main() { + + node_bindgen::build::configure(); +} \ No newline at end of file diff --git a/examples/class-wrapper/src/lib.rs b/examples/class-wrapper/src/lib.rs new file mode 100644 index 00000000..d393af24 --- /dev/null +++ b/examples/class-wrapper/src/lib.rs @@ -0,0 +1,56 @@ +use std::io::Error as IoError; + +use node_bindgen::sys::napi_value; +use node_bindgen::core::JSClass; +use node_bindgen::core::NjError; +use node_bindgen::core::val::JsEnv; +use node_bindgen::core::TryIntoJs; +use node_bindgen::derive::node_bindgen; + +#[node_bindgen] +async fn create(val: f64) -> Result { + Ok(MyObjectWrapper{ val }) +} + +struct MyObjectWrapper { + val: f64 +} + +impl TryIntoJs for MyObjectWrapper { + + fn try_to_js(self, js_env: &JsEnv) -> Result { + let instance = TestObject::new_instance(js_env,vec![])?; + let test_object = TestObject::unwrap_mut(js_env,instance)?; + test_object.set_value(self.val); + Ok(instance) + } +} + +struct TestObject { + val: Option +} + +#[node_bindgen] +impl TestObject { + + #[node_bindgen(constructor)] + fn new() -> Self { + Self { val: None } + } + + #[node_bindgen(setter,name="value")] + fn set_value(&mut self,val: f64) { + self.val.replace(val); + } + + #[node_bindgen(getter)] + fn value2(&self) -> f64 { + self.val.unwrap_or(0.0) + } + + + #[node_bindgen] + fn test(&self) -> f64 { + 0.0 + } +} diff --git a/examples/class-wrapper/test.js b/examples/class-wrapper/test.js new file mode 100644 index 00000000..f171106a --- /dev/null +++ b/examples/class-wrapper/test.js @@ -0,0 +1,12 @@ +const assert = require('assert'); + +let addon = require('./dist'); + +let t = new addon.TestObject(); +t.value = 20; +assert.equal(t.value2,20); + + +addon.create(10).then( (test_object) => { + console.log("test value is %s",test_object.value2); +}); \ No newline at end of file diff --git a/examples/class/src/lib.rs b/examples/class/src/lib.rs deleted file mode 100644 index b9777a99..00000000 --- a/examples/class/src/lib.rs +++ /dev/null @@ -1,258 +0,0 @@ - -use std::time::Duration; -use std::io::Error as IoError; - -use flv_future_aio::timer::sleep; - -use node_bindgen::sys::napi_value; -use node_bindgen::core::val::JsCallback; -use node_bindgen::core::JSClass; -use node_bindgen::core::NjError; -use node_bindgen::core::val::JsObject; -use node_bindgen::core::val::JsEnv; -use node_bindgen::core::TryIntoJs; -use node_bindgen::derive::node_bindgen; - -#[node_bindgen] -async fn create(val: f64) -> Result { - Ok(MyObjectWrapper{ val }) -} - -struct MyObjectWrapper { - val: f64 -} - -impl TryIntoJs for MyObjectWrapper { - - fn try_to_js(self, js_env: &JsEnv) -> Result { - let instance = TestObject::new_instance(js_env,vec![])?; - let test_object = TestObject::unwrap_mut(js_env,instance)?; - test_object.set_value(self.val); - Ok(instance) - } -} - -struct TestObject { - val: Option -} - -#[node_bindgen] -impl TestObject { - - #[node_bindgen(constructor)] - fn new() -> Self { - Self { val: None } - } - - #[node_bindgen(setter,name="value")] - fn set_value(&mut self,val: f64) { - self.val.replace(val); - } - - #[node_bindgen(getter)] - fn value2(&self) -> f64 { - self.val.unwrap_or(0.0) - } - - - #[node_bindgen] - fn test(&self) -> f64 { - 0.0 - } -} - -/* -impl JSValue for TestObject { - - const JS_TYPE: u32 = node_bindgen::sys::napi_valuetype_napi_object; - - fn convert_to_rust(env: &JsEnv,js_value: napi_value) -> Result { - - env.unwrap::(js_value) - } - -} -*/ - -struct MyJson { - val: f64 -} - - -impl TryIntoJs for MyJson { - - fn try_to_js(self, js_env: &JsEnv) -> Result { - - // create JSON - let mut json = JsObject::new(js_env.clone(), js_env.create_object()?); - - let js_val = js_env.create_double(self.val)?; - json.set_property("val",js_val)?; - - json.try_to_js(js_env) - } -} - - - -struct MyObject { - val: f64, -} - - -#[node_bindgen] -impl MyObject { - - #[node_bindgen(constructor)] - fn new(val: f64) -> Self { - Self { val } - } - - /// simple method which return f64 - /// rust values are automatically converted into equivalent JS value - /// method name are generated from rust method name - /// Js: let y = obj.plusOne(); - #[node_bindgen] - fn plus_one(&self) -> f64 { - self.val + 1.0 - } - - /// JS getter - /// Js: let y = obj.value; - #[node_bindgen(getter)] - fn value(&self) -> f64 { - self.val - } - - /// Getter with custom name that return JSON - /// JS: let y = obj.json - /// { - /// value: f64, - /// } - #[node_bindgen(getter)] - fn json(&self) -> MyJson { - MyJson { - val: self.val - } - } - - /// JS Setter - /// Js: obj.value3 = 10; - #[node_bindgen(setter)] - fn value3(&mut self,val: f64) { - self.val = val; - } - - /// method with custom name instead of generated name - /// Js: obj.set_value(10); - #[node_bindgen(name="value2")] - fn set_value(&mut self,val: f64) { - self.val = val; - } - - - #[node_bindgen(setter,name="value4")] - fn set_value4(&mut self,val: f64) { - self.val = val; - } - - - #[node_bindgen] - fn change_value(&mut self,val: f64) { - self.val = val; - } - - - #[node_bindgen(getter)] - fn is_positive(&self) -> bool { - self.val > 0.0 - } - - #[node_bindgen(setter)] - fn clear(&mut self,val: bool) { - if val { - self.val = 0.0; - } - } - - /// accept arbitrary js object, here we are looking integer property with value - #[node_bindgen] - fn plus_score(&mut self,config: JsObject) -> Result { - - let score_property = config.get_property("score")?; - println!("score founded"); - let score = score_property.as_value::()?; - Ok(self.val + score) - } - - - - //// accept Rust object - /// Js: obj.plus_test(test) - #[node_bindgen] - fn plus_test(&mut self,config: &TestObject) -> Result { - - Ok(self.val + config.value2()) - } - - - - /// example where we receive callback cb explicitly. - /// in this case, we can manually create new instance. - /// callback must be 2nd argument and be of type JsCallback. - /// JS example: let obj2 = obj.multiply(-1); - #[node_bindgen] - fn multiply(&self, cb: &JsCallback, arg: f64) -> Result { - - let new_val = cb.env().create_double(arg * self.val)?; - Self::new_instance(cb.env(), vec![new_val]) - } - - - - - /// promise which result in primitive type - #[node_bindgen] - async fn plus_two(&self, arg: f64) -> f64 { - - println!("sleeping"); - sleep(Duration::from_secs(1)).await; - println!("woke and adding {}",arg); - - self.val + arg - } - - - /// promise where result is arbitrary struct. - /// returning struct must implement TryIntoJs - /// which can create new JS instance - #[node_bindgen] - async fn multiply2(&self,arg: f64) -> MyObjectConstructor { - - println!("sleeping"); - sleep(Duration::from_secs(1)).await; - println!("woke and adding {}",arg); - - MyObjectConstructor::new(self.val * arg) - } - - - - /// loop and emit event - #[node_bindgen] - async fn sleep(&self,cb: F) { - - println!("sleeping"); - sleep(Duration::from_secs(1)).await; - let msg = format!("hello world"); - cb(msg); - - } - - #[node_bindgen] - fn test(&self) -> f64 { - 0.0 - } - - -} diff --git a/examples/class/test.js b/examples/class/test.js deleted file mode 100644 index 62427014..00000000 --- a/examples/class/test.js +++ /dev/null @@ -1,64 +0,0 @@ -const assert = require('assert'); - -let addon = require('./dist'); - -let obj = new addon.MyObject(10); -assert.equal(obj.value,10,"verify value works"); -assert.deepEqual(obj.json, { val: 10},"verify json"); -assert.equal(obj.plusOne(),11); - -let obj2 = obj.multiply(-1); -assert.equal(obj2.value,-10); - -obj.changeValue(100); -assert.equal(obj.value,100); - -obj.value2(50); -assert.equal(obj.value,50); - -obj.value3 = 10; -assert.equal(obj.value,10,"test setter"); - -obj.value4 = 60; -assert.equal(obj.value,60,"test test setter with custom property"); - -obj.value4 = -10; -assert.equal(obj.isPositive,false); - -obj.value4 = 10; -assert.equal(obj.isPositive,true); - -obj.clear = false; -assert.equal(obj.value,10); - -obj.clear = true; -assert.equal(obj.value,0); - -assert.equal(obj.plusScore( { score: 10 }),10); - - - - -let t = new addon.TestObject(); -t.value = 20; -assert.equal(t.value2,20); - -// test passing of test object -assert.equal(obj.plusTest(t),20); - -obj.plusTwo(10).then( (val) => { - console.log("plus two is ",val); -}); - -obj.multiply2(-1).then( (obj3) => { - console.log("multiply two ",obj3.value); -}); - -obj.sleep((msg) => { - assert.equal(msg,"hello world");; -}); - - -addon.create(10).then( (test_object) => { - console.log("test value is %s",test_object.value2); -}); \ No newline at end of file diff --git a/examples/function/Makefile b/examples/function/Makefile index 0d7a282b..f9016df6 100644 --- a/examples/function/Makefile +++ b/examples/function/Makefile @@ -1,15 +1,10 @@ - -DYLIB = ../target/debug/libnj_example_function.dylib - all: build - build: nj-cli build test: build node test.js - clean: rm -rf dist \ No newline at end of file diff --git a/examples/function/src/lib.rs b/examples/function/src/lib.rs index d5ce60c1..1a6cd3c6 100644 --- a/examples/function/src/lib.rs +++ b/examples/function/src/lib.rs @@ -3,7 +3,7 @@ use node_bindgen::derive::node_bindgen; use node_bindgen::core::NjError; -#[node_bindgen] +#[node_bindgen()] fn hello(count: i32) -> String { format!("hello world {}", count) } @@ -14,14 +14,30 @@ fn sum(first: i32, second: i32) -> i32 { first + second } + // throw error if first > second, otherwise return sum #[node_bindgen] fn min_max(first: i32, second: i32) -> Result { if first > second { - println!("throwing error"); Err(NjError::Other("first arg is greater".to_owned())) } else { Ok(first + second ) } } +#[node_bindgen(name="multiply")] +fn mul(first: i32,second: i32) -> i32 { + first * second +} + + +/// add second if supplied +#[node_bindgen()] +fn sum2(first: i32, second_arg: Option) -> i32 { + if let Some(second) = second_arg { + first + second + } else { + first + } +} + diff --git a/examples/function/test.js b/examples/function/test.js index e843d94c..3c033f02 100644 --- a/examples/function/test.js +++ b/examples/function/test.js @@ -1,4 +1,4 @@ -let addon = require('./dist'); +let addon =require('./dist'); const assert = require('assert'); assert.equal(addon.hello(2),"hello world 2"); @@ -11,9 +11,8 @@ assert.throws( () => addon.hello("hello"),{ assert.throws(() => addon.hello(),{ - message: '1 args expected but 0 is present' + message: 'expected argument of type: i32' }); -console.log("function tests succeed"); assert.equal(addon.sum(1,2),3); @@ -22,3 +21,10 @@ assert.throws( () => addon.minMax(10,0),{ }); assert.equal(addon.minMax(1,2),3); + +assert.equal(addon.multiply(2,5),10); + +assert.equal(addon.sum2(10),10); +assert.equal(addon.sum2(5,100),105); + +console.log("function tests succeed"); \ No newline at end of file diff --git a/examples/js-env/.gitignore b/examples/js-env/.gitignore new file mode 100644 index 00000000..53c37a16 --- /dev/null +++ b/examples/js-env/.gitignore @@ -0,0 +1 @@ +dist \ No newline at end of file diff --git a/examples/js-env/Cargo.toml b/examples/js-env/Cargo.toml new file mode 100644 index 00000000..6a513baa --- /dev/null +++ b/examples/js-env/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "nj-example-jsenv" +version = "0.1.0" +authors = ["fluvio.io"] +edition = "2018" + + +[lib] +crate-type = ["cdylib"] + + +[dependencies] +node-bindgen = { path = "../.."} + + +[build-dependencies] +node-bindgen = { path = "../../", features = ["build"] } diff --git a/examples/js-env/Makefile b/examples/js-env/Makefile new file mode 100644 index 00000000..a61313ae --- /dev/null +++ b/examples/js-env/Makefile @@ -0,0 +1,12 @@ +all: build + +build: + nj-cli build + +test: build + node test.js + + +clean: + rm -rf dist + diff --git a/examples/js-env/README.md b/examples/js-env/README.md new file mode 100644 index 00000000..92cc22f7 --- /dev/null +++ b/examples/js-env/README.md @@ -0,0 +1 @@ +manual JS callback \ No newline at end of file diff --git a/examples/js-env/build.rs b/examples/js-env/build.rs new file mode 100644 index 00000000..9b831a3a --- /dev/null +++ b/examples/js-env/build.rs @@ -0,0 +1,3 @@ +fn main() { + node_bindgen::build::configure(); +} diff --git a/examples/js-env/src/lib.rs b/examples/js-env/src/lib.rs new file mode 100644 index 00000000..1371d891 --- /dev/null +++ b/examples/js-env/src/lib.rs @@ -0,0 +1,16 @@ +use node_bindgen::derive::node_bindgen; +use node_bindgen::sys::napi_value; +use node_bindgen::core::NjError; +use node_bindgen::core::val::JsEnv; + + +/// example where we receive napi callback manually +/// with napi callback, have full control over JS object lifecycle +/// JsEnv argument does not manipulate JsCb arguments +#[node_bindgen] +fn multiply(env: JsEnv, arg: f64) -> Result { + + println!("arg: {}",arg); + env.create_double(arg * 2.0) +} + diff --git a/examples/js-env/test.js b/examples/js-env/test.js new file mode 100644 index 00000000..783f750a --- /dev/null +++ b/examples/js-env/test.js @@ -0,0 +1,5 @@ +const assert = require('assert'); + +let addon = require('./dist'); + +assert.equal(addon.multiply(5),10); \ No newline at end of file diff --git a/examples/json/.gitignore b/examples/json/.gitignore new file mode 100644 index 00000000..53c37a16 --- /dev/null +++ b/examples/json/.gitignore @@ -0,0 +1 @@ +dist \ No newline at end of file diff --git a/examples/json/Cargo.toml b/examples/json/Cargo.toml new file mode 100644 index 00000000..3b6a44aa --- /dev/null +++ b/examples/json/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "nj-example-json" +version = "0.1.0" +authors = ["fluvio.io"] +edition = "2018" + + +[lib] +crate-type = ["cdylib"] + + +[dependencies] +node-bindgen = { path = "../.."} + + +[build-dependencies] +node-bindgen = { path = "../../", features = ["build"] } diff --git a/examples/json/Makefile b/examples/json/Makefile new file mode 100644 index 00000000..a61313ae --- /dev/null +++ b/examples/json/Makefile @@ -0,0 +1,12 @@ +all: build + +build: + nj-cli build + +test: build + node test.js + + +clean: + rm -rf dist + diff --git a/examples/json/README.md b/examples/json/README.md new file mode 100644 index 00000000..3fe33237 --- /dev/null +++ b/examples/json/README.md @@ -0,0 +1 @@ +return json object \ No newline at end of file diff --git a/examples/json/build.rs b/examples/json/build.rs new file mode 100644 index 00000000..9b831a3a --- /dev/null +++ b/examples/json/build.rs @@ -0,0 +1,3 @@ +fn main() { + node_bindgen::build::configure(); +} diff --git a/examples/json/src/lib.rs b/examples/json/src/lib.rs new file mode 100644 index 00000000..1b9d646c --- /dev/null +++ b/examples/json/src/lib.rs @@ -0,0 +1,35 @@ +use node_bindgen::derive::node_bindgen; +use node_bindgen::sys::napi_value; +use node_bindgen::core::NjError; +use node_bindgen::core::val::JsEnv; +use node_bindgen::core::TryIntoJs; +use node_bindgen::core::val::JsObject; + +struct MyJson { + val: f64 +} + + +impl TryIntoJs for MyJson { + + /// serialize into json object + fn try_to_js(self, js_env: &JsEnv) -> Result { + + // create JSON + let mut json = JsObject::new(js_env.clone(), js_env.create_object()?); + + let js_val = js_env.create_double(self.val)?; + json.set_property("val",js_val)?; + + json.try_to_js(js_env) + } +} + + +/// return json object +#[node_bindgen] +fn json() -> MyJson { + MyJson { + val: 10.0 + } +} \ No newline at end of file diff --git a/examples/json/test.js b/examples/json/test.js new file mode 100644 index 00000000..9ff6db13 --- /dev/null +++ b/examples/json/test.js @@ -0,0 +1,5 @@ +const assert = require('assert'); + +let addon = require('./dist'); + +assert.deepEqual(addon.json(), { val: 10},"verify json"); \ No newline at end of file diff --git a/examples/promise/Makefile b/examples/promise/Makefile index f70a6cd6..95f142a4 100644 --- a/examples/promise/Makefile +++ b/examples/promise/Makefile @@ -1,6 +1,3 @@ - -DYLIB = ../target/debug/libnj_example_promise.dylib - all: build diff --git a/examples/promise/README.md b/examples/promise/README.md index 5e0bec32..30652f4b 100644 --- a/examples/promise/README.md +++ b/examples/promise/README.md @@ -1 +1 @@ -simple hello world callback \ No newline at end of file +regular async fn return as promise \ No newline at end of file diff --git a/examples/stream/Makefile b/examples/stream/Makefile index 9e3119ff..95f142a4 100644 --- a/examples/stream/Makefile +++ b/examples/stream/Makefile @@ -1,6 +1,3 @@ - -DYLIB = ../target/debug/libnj_example_stream.dylib - all: build diff --git a/nj-core/Cargo.toml b/nj-core/Cargo.toml index 6eb8cf8c..730d391a 100644 --- a/nj-core/Cargo.toml +++ b/nj-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nj-core" -version = "1.2.1" +version = "2.0.0" authors = ["fluvio.io"] edition = "2018" description = "high level wrapper for Node N-API" diff --git a/nj-core/src/basic.rs b/nj-core/src/basic.rs index e479c1c9..58b0c2dd 100644 --- a/nj-core/src/basic.rs +++ b/nj-core/src/basic.rs @@ -1,5 +1,6 @@ use std::ptr; use std::ffi::CString; +use std::collections::VecDeque; use libc::size_t; use log::error; @@ -213,52 +214,34 @@ impl JsEnv { Ok(result) } - /// get callback with argument size - pub fn get_cb_info(&self,info: napi_callback_info,arg_count: usize) -> Result { + /// get callback information + pub fn get_cb_info(&self,info: napi_callback_info,max_count: usize) -> Result { use nj_sys::napi_get_cb_info; let mut this = ptr::null_mut(); - let args = if arg_count == 0 { - napi_call_result!( - napi_get_cb_info( - self.0, - info, - ptr::null_mut(), - ptr::null_mut(), - &mut this, - ptr::null_mut() - ))?; - vec![] + + let mut argc: size_t = max_count as size_t; + let mut args = vec![ptr::null_mut();max_count]; + napi_call_result!( + napi_get_cb_info( + self.0, + info, + &mut argc, + args.as_mut_ptr(), + &mut this, + ptr::null_mut() + ))?; - } else { - let mut argc: size_t = arg_count as size_t; - let mut args = vec![ptr::null_mut();arg_count]; - napi_call_result!( - napi_get_cb_info( - self.0, - info, - &mut argc, - args.as_mut_ptr(), - &mut this, - ptr::null_mut() - ))?; - - if argc != arg_count { - debug!("expected {}, actual {}",arg_count,argc); - return Err(NjError::InvalidArgCount(argc as usize,arg_count)); - } - args - }; - + // truncate arg to actual received count + args.resize(argc,ptr::null_mut()); - Ok(JsCallback { - env: JsEnv::new(self.0), - args, - this - - }) + Ok(JsCallback::new( + JsEnv::new(self.0), + this, + args + )) } /// define classes @@ -573,7 +556,7 @@ impl JsEnv { pub struct JsCallback { env: JsEnv, this: napi_value, - args: Vec + args: VecDeque, } unsafe impl Send for JsCallback{} @@ -581,6 +564,14 @@ unsafe impl Sync for JsCallback{} impl JsCallback { + pub fn new(env: JsEnv,this: napi_value,args: Vec) -> Self { + Self { + env, + this, + args: args.into() + } + } + pub fn env(&self) -> &JsEnv { &self.env } @@ -597,80 +588,100 @@ impl JsCallback { self.this } + pub fn remove_napi(&mut self) -> Option { + self.args.remove(0) + } + /// get value of callback info and verify type - pub fn get_value(&self, index: usize) -> Result - where T: JSValue + pub fn get_value(&mut self) -> Result + where T: ExtractFromJs { - - trace!("trying get cb value: {},len: {}",index,self.args.len()); + trace!("trying extract value out of {} args", self.args.len()); + + T::extract(self) + } - if index >= self.args.len() { - error!("attempt to access callback: {} len: {}",index,self.args.len()); - return Err(NjError::InvalidArgIndex(index,self.args.len())) + /// create thread safe function + pub fn create_thread_safe_function( + &mut self, + name: &str, + call_js_cb: napi_threadsafe_function_call_js) -> Result { + + if let Some(n_value) = self.remove_napi() { + self.env.create_thread_safe_function( + name, + Some(n_value), + call_js_cb + ) + } else { + return Err(NjError::Other("expected js callback".to_owned())) } - self.env.convert_to_rust::(self.args[index]) - } + + } - /// get reference to wrapper object - pub fn get_ref(&self, index: usize) -> Result<&'static T,NjError> - { - use crate::sys::napi_typeof; - let mut valuetype: napi_valuetype = 0; - - trace!("get value: {},len: {}",index,self.args.len()); + pub fn unwrap_mut(&self) -> Result<&'static mut T,NjError> { + Ok(self.env.unwrap_mut::>(self.this())?.mut_inner()) + } - if index >= self.args.len() { - debug!("attempt to access callback: {} len: {}",index,self.args.len()); - return Err(NjError::InvalidArgIndex(index,self.args.len())) - } + pub fn unwrap(&self) -> Result<&'static T,NjError> { + Ok(self.env.unwrap::>(self.this())?.inner()) + } + +} - napi_call_result!( - napi_typeof( - self.env.inner(), - self.args[index], - &mut valuetype - ))?; - /* - if valuetype != T::JS_TYPE { - debug!("value type is: {} but should be: {}",valuetype,T::JS_TYPE); - return Err(NjError::InvalidType) - } - */ +pub trait ExtractFromJs: Sized { - Ok(self.env.unwrap::>(self.args[index])?.inner()) - + fn label() -> &'static str { + std::any::type_name::() } + /// extract from js callback + fn extract(js_cb: &mut JsCallback) -> Result; +} +impl ExtractFromJs for T where T: JSValue { - pub fn create_thread_safe_function( - &self, - name: &str, - index: usize, - call_js_cb: napi_threadsafe_function_call_js) -> Result { + fn label() -> &'static str { + T::label() + } - self.env.create_thread_safe_function( - name, - Some(self.args[index]), - call_js_cb - ) + fn extract(js_cb: &mut JsCallback) -> Result { + + if let Some(n_value) = js_cb.remove_napi() { + T::convert_to_rust(js_cb.env(), n_value) + } else { + return Err(NjError::Other(format!("expected argument of type: {}",Self::label()))) + } } +} +/// for optional argument +impl ExtractFromJs for Option where T: JSValue { - pub fn unwrap_mut(&self) -> Result<&'static mut T,NjError> { - Ok(self.env.unwrap_mut::>(self.this())?.mut_inner()) + fn label() -> &'static str { + T::label() } - pub fn unwrap(&self) -> Result<&'static T,NjError> { - Ok(self.env.unwrap::>(self.this())?.inner()) + fn extract(js_cb: &mut JsCallback) -> Result { + + if let Some(n_value) = js_cb.remove_napi() { + Ok(Some(T::convert_to_rust(js_cb.env(), n_value)?)) + } else { + Ok(None) + } } +} - +impl ExtractFromJs for JsEnv { + + fn extract(js_cb: &mut JsCallback) -> Result { + Ok(js_cb.env().clone()) + } } @@ -744,6 +755,10 @@ unsafe impl Sync for JsCallbackFunction{} impl JSValue for JsCallbackFunction { + fn label() -> &'static str { + "callback" + } + fn convert_to_rust(env: &JsEnv,js_value: napi_value) -> Result { env.assert_type(js_value, crate::sys::napi_valuetype_napi_function)?; diff --git a/nj-core/src/convert.rs b/nj-core/src/convert.rs index a5f6a8a9..cdc7aad2 100644 --- a/nj-core/src/convert.rs +++ b/nj-core/src/convert.rs @@ -82,10 +82,13 @@ pub trait IntoJs { - - +/// Try to convert napi value to Rust value pub trait JSValue: Sized { + fn label() -> &'static str { + std::any::type_name::() + } + fn convert_to_rust(env: &JsEnv,js_value: napi_value) -> Result; } diff --git a/nj-derive/Cargo.lock b/nj-derive/Cargo.lock new file mode 100644 index 00000000..83499d4b --- /dev/null +++ b/nj-derive/Cargo.lock @@ -0,0 +1,965 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + +[[package]] +name = "aho-corasick" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" +dependencies = [ + "memchr", +] + +[[package]] +name = "async-std" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "538ecb01eb64eecd772087e5b6f7540cbc917f047727339a472dafed2185b267" +dependencies = [ + "async-task", + "broadcaster", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "futures-core", + "futures-io", + "futures-timer", + "kv-log-macro", + "log", + "memchr", + "mio", + "mio-uds", + "num_cpus", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "async-task" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ac2c016b079e771204030951c366db398864f5026f84a44dafb0ff20f02085d" +dependencies = [ + "libc", + "winapi 0.3.8", +] + +[[package]] +name = "async-trait" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da71fef07bc806586090247e971229289f64c210a278ee5ae419314eb386b31d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi 0.3.8", +] + +[[package]] +name = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "broadcaster" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c972e21e0d055a36cf73e4daae870941fe7a8abcd5ac3396aab9e4c126bd87" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "futures-util", + "parking_lot", + "slab", +] + +[[package]] +name = "bytes" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" + +[[package]] +name = "cc" +version = "1.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d87b23d6a92cd03af510a5ade527033f6aa6fa92161e2d5863a907d4c5e31d" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "chrono" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" +dependencies = [ + "num-integer", + "num-traits", + "time", +] + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags", +] + +[[package]] +name = "crossbeam-channel" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061" +dependencies = [ + "crossbeam-utils", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-deque" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "lazy_static", + "maybe-uninit", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +dependencies = [ + "autocfg", + "cfg-if", + "lazy_static", +] + +[[package]] +name = "ctor" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf6b25ee9ac1995c54d7adb2eff8cfffb7260bc774fb63c601ec65467f43cd9d" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "env_logger" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "flv-future-aio" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6694301bbdf04f606a41ced7f34ef065ef5255fa08d85a60cd31c566ebadfa06" +dependencies = [ + "async-std", + "async-trait", + "bytes", + "flv-util", + "futures", + "futures-timer", + "log", + "memmap", + "nix", + "pin-project", + "pin-utils", + "tokio", +] + +[[package]] +name = "flv-util" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63dfd9a699e5b73d0163b7abd4019c0e37c7293c68b822dfb12cae2ca753703d" +dependencies = [ + "chrono", + "env_logger", + "log", +] + +[[package]] +name = "fnv" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + +[[package]] +name = "futures" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c329ae8753502fb44ae4fc2b622fa2a94652c41e795143765ba0927f92ab780" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c77d04ce8edd9cb903932b608268b3fffec4163dc053b3b402bf47eac1f1a8" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f25592f769825e89b92358db00d26f965761e094951ac44d3663ef25b7ac464a" + +[[package]] +name = "futures-executor" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f674f3e1bcb15b37284a90cedf55afdba482ab061c407a9c0ebbd0f3109741ba" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a638959aa96152c7a4cddf50fcb1e3fede0583b27157c26e67d6f99904090dc6" + +[[package]] +name = "futures-macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a5081aa3de1f7542a794a397cde100ed903b0630152d0973479018fd85423a7" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3466821b4bc114d95b087b850a724c6f83115e929bc88f1fa98a3304a944c8a6" + +[[package]] +name = "futures-task" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0a34e53cf6cdcd0178aa573aed466b646eb3db769570841fda0c7ede375a27" + +[[package]] +name = "futures-timer" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1de7508b218029b0f01662ed8f61b1c964b3ae99d6f25462d0f55a595109df6" + +[[package]] +name = "futures-util" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22766cf25d64306bedf0384da004d05c9974ab104fcc4528f1236181c18004c5" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "slab", +] + +[[package]] +name = "ghost" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a36606a68532b5640dc86bb1f33c64b45c4682aad4c50f3937b317ea387f3d6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "hermit-abi" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61565ff7aaace3525556587bd2dc31d4a07071957be715e63ce7b1eccf51a8f4" +dependencies = [ + "libc", +] + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + +[[package]] +name = "inventory" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d3f4b90287725c97b17478c60dda0c6324e7c84ee1ed72fb9179d0fdf13956" +dependencies = [ + "ctor", + "ghost", + "inventory-impl", +] + +[[package]] +name = "inventory-impl" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9092a4fefc9d503e9287ef137f03180a6e7d1b04c419563171ee14947c5e80ec" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + +[[package]] +name = "itoa" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c54d9f465d530a752e6ebdc217e081a7a614b48cb200f6f0aee21ba6bc9aabb" +dependencies = [ + "log", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005" + +[[package]] +name = "lock_api" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + +[[package]] +name = "memchr" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" + +[[package]] +name = "memmap" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" +dependencies = [ + "libc", + "winapi 0.3.8", +] + +[[package]] +name = "memoffset" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4fc2c02a7e374099d4ee95a193111f72d2110197fe200272371758f6c3643d8" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mio" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f" +dependencies = [ + "cfg-if", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio-uds" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" +dependencies = [ + "iovec", + "libc", + "mio", +] + +[[package]] +name = "miow" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + +[[package]] +name = "net2" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" +dependencies = [ + "cfg-if", + "libc", + "winapi 0.3.8", +] + +[[package]] +name = "nix" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0eaf8df8bab402257e0a5c17a254e4cc1f72a93588a1ddfb5d356c801aa7cb" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", + "void", +] + +[[package]] +name = "nj-core" +version = "2.0.0" +dependencies = [ + "async-trait", + "ctor", + "flv-future-aio", + "flv-util", + "futures", + "inventory", + "libc", + "log", + "nj-sys", + "pin-utils", +] + +[[package]] +name = "nj-derive" +version = "2.0.0" +dependencies = [ + "Inflector", + "flv-future-aio", + "node-bindgen", + "proc-macro2", + "quote", + "syn", + "trybuild", +] + +[[package]] +name = "nj-sys" +version = "1.0.0" + +[[package]] +name = "node-bindgen" +version = "2.0.0" +dependencies = [ + "nj-core", + "nj-derive", + "nj-sys", +] + +[[package]] +name = "num-integer" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b" + +[[package]] +name = "parking_lot" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" +dependencies = [ + "cfg-if", + "cloudabi", + "libc", + "redox_syscall", + "smallvec", + "winapi 0.3.8", +] + +[[package]] +name = "pin-project" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f6a7f5eee6292c559c793430c55c00aea9d3b3d1905e855806ca4d7253426a2" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8988430ce790d8682672117bc06dda364c0be32d3abd738234f19f3240bad99a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro-hack" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63" + +[[package]] +name = "proc-macro-nested" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694" + +[[package]] +name = "proc-macro2" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" + +[[package]] +name = "regex" +version = "1.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-syntax" +version = "0.6.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" + +[[package]] +name = "ryu" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da07b57ee2623368351e9a0488bb0b261322a15a6e0ae53e243cbdc0f4208da9" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" + +[[package]] +name = "smallvec" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" + +[[package]] +name = "syn" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "410a7488c0a728c7ceb4ad59b9567eb4053d02e8cc7f5c0e0eeeb39518369213" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "termcolor" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi 0.3.8", +] + +[[package]] +name = "tokio" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d9c43f1bb96970e153bcbae39a65e249ccb942bd9d36dbdf086024920417c9c" +dependencies = [ + "bytes", + "fnv", + "pin-project-lite", +] + +[[package]] +name = "toml" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" +dependencies = [ + "serde", +] + +[[package]] +name = "trybuild" +version = "1.0.26" +source = "git+https://github.com/sehz/trybuild?branch=check_option#af10c619356e14a649d7ff36454365389b2ec868" +dependencies = [ + "glob", + "lazy_static", + "serde", + "serde_json", + "termcolor", + "toml", +] + +[[package]] +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[package]] +name = "winapi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi 0.3.8", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] diff --git a/nj-derive/Cargo.toml b/nj-derive/Cargo.toml index 8a983bec..ce10e3ec 100644 --- a/nj-derive/Cargo.toml +++ b/nj-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nj-derive" -version = "1.0.0" +version = "2.0.0" authors = ["fluvio.io"] edition = "2018" description = "procedure macro for node-bindgen" @@ -14,6 +14,11 @@ proc-macro = true [dependencies] proc-macro2 = "1.0" -quote = "1.0.2" -syn = { version = "1.0", features = ["full", "visit-mut","derive","extra-traits"] } -Inflector = "0.11.4" \ No newline at end of file +quote = "1.0" +syn = { version = "1.0", features = ["full", "parsing", "visit-mut","derive","extra-traits"] } +Inflector = "0.11.4" + +[dev-dependencies] +trybuild = { git = "https://github.com/sehz/trybuild", branch = "check_option" } +node-bindgen = { path = ".." } +flv-future-aio = { version = "2.0.0" } \ No newline at end of file diff --git a/nj-derive/src/ast/arg.rs b/nj-derive/src/ast/arg.rs new file mode 100644 index 00000000..1eb9f831 --- /dev/null +++ b/nj-derive/src/ast/arg.rs @@ -0,0 +1,248 @@ +use syn::FnArg; +use syn::Ident; +use syn::Type; +use syn::Pat; +use syn::Error; +use syn::Generics; +use syn::TypeParam; +use syn::Signature; +use syn::TypeParamBound; +use syn::PathArguments; +use syn::Result; +use syn::spanned::Spanned; +use syn::ParenthesizedGenericArguments; +use syn::Receiver; + +use super::MyTypePath; +use super::MyReferenceType; + + +/// Information about Function Arguments +#[derive(Debug,Default)] +pub struct FunctionArgs<'a> { + pub args: Vec>, + pub is_method: bool, + receiver: Option<&'a Receiver>, +} + +impl <'a>FunctionArgs<'a> { + + pub fn from_ast(sig: &'a Signature) -> Result { + + //println!("fn: {:#?}",input_fn); + let generics = &sig.generics; + + let mut args: Vec = vec![]; + + let is_method = has_receiver(sig); + + // extract arguments, + let i = 0; + for ref arg in &sig.inputs { + + // println!("arg: {:#?}",arg); + match arg { + FnArg::Receiver(_) => {} + FnArg::Typed(arg_type) => { + + match &*arg_type.pat { + Pat::Ident(identity) => { + + let arg = FunctionArg::new( + i, + &identity.ident, + &*arg_type.ty, + generics + )?; + args.push(arg); + }, + _ => return Err(Error::new(arg_type.span(), "not supported type")), + } + } + } + } + + Ok(Self { + args, + is_method, + ..Default::default() + }) + + } + + + pub fn inner(&self) -> &Vec { + &self.args + } + + pub fn len(&self) -> usize { + self.args.len() + } + + + +} + + +/// find receiver if any, this will be used to indicate if this is method +fn has_receiver(sig: &Signature) -> bool { + + sig.inputs.iter().find(|input| { + match input { + FnArg::Receiver(_rec) => true, + _ => false + } + }).is_some() +} + + +#[derive(Debug)] +pub struct FunctionArg<'a> { + pub arg_index: u32, + pub typ: FunctionArgType<'a>, +} + +impl <'a> FunctionArg<'a> { + + /// given this, convert into normalized type signature + fn new(arg_index: u32, ident: &'a Ident, ty: &'a Type, generics: &'a Generics) -> Result { + + match ty { + Type::Path(path_type) => { + let my_type = MyTypePath::from(path_type)?; + + // check whether type references in the generic indicates this is closure + if let Some(param) = find_generic(generics,my_type.ident()) { + let closure = ClosureType::from(ident,param)?; + Ok(Self { + arg_index, + typ: FunctionArgType::Closure(closure) + }) + } else { + Ok(Self { + arg_index, + typ: FunctionArgType::Path(my_type) + }) + } + } + Type::Reference(ref_type) => { + let my_type = MyReferenceType::from(ref_type)?; + /* + if my_type.is_callback() { + Ok(Self { + arg_index, + typ: FunctionArgType::JSCallback(my_type) + }) + } else { + Ok(Self { + arg_index, + typ: FunctionArgType::Ref(my_type) + }) + } + */ + Ok(Self { + arg_index, + typ: FunctionArgType::Ref(my_type) + }) + } + _ => Err(Error::new(ty.span(), "not supported type")) + } + } + + /* + pub fn is_js_env(&self) -> bool { + match self.typ { + FunctionArgType::JsEnv(_) => true, + _ => false, + } + } + */ +} + + +/// Categorize function argument +#[derive(Debug)] +pub enum FunctionArgType<'a> { + Path(MyTypePath<'a>), // normal type + Ref(MyReferenceType<'a>), // reference type + Closure(ClosureType<'a>), // closure callback + // JsEnv(MyReferenceType<'a>), // indicating that we want to receive JsEnv +} + + +/// find generic with match ident +fn find_generic<'a,'b>(generics: &'a Generics, ident: Option<&'b Ident>) -> Option<&'a TypeParam> { + + if let Some(ident) = ident { + generics.type_params().find(|ty| ty.ident.to_string() == ident.to_string()) + } else { + None + } + +} + + +#[derive(Debug)] +pub struct ClosureType<'a> { + //pub ty: &'a ParenthesizedGenericArguments, + pub inputs: Vec>, + pub ident: &'a Ident +} + +impl <'a>ClosureType<'a> { + // try to see if we can find closure, otherwise return none + pub fn from(ident: &'a Ident,param: &'a TypeParam) -> Result { + for ref bound in ¶m.bounds { + match bound { + TypeParamBound::Trait(tt) => { + for ref segment in &tt.path.segments { + match segment.arguments { + + PathArguments::Parenthesized(ref path) => return Ok(Self { + ident, + inputs: find_inputs(path)? + }), + _ => return Err(Error::new(param.span(), "not supported closure type")), + } + } + return Err(Error::new(param.span(), "not supported closure type")) + } + TypeParamBound::Lifetime(_) => return Err(Error::new(param.span(), "not supported closure type")), + } + } + return Err(Error::new(param.span(), "not supported closure type")) + } + + + // name of function is used by thread safe function to complete closure + pub fn async_js_callback_identifier(&self) -> Ident { + + use proc_macro2::Span; + + Ident::new(&format!("thread_safe_{}_complete",self.ident),Span::call_site()) + } + + +} + + +fn find_inputs(ty: &ParenthesizedGenericArguments) -> Result> { + + let mut types: Vec = vec![]; + + for path in &ty.inputs { + let my_type = match path { + Type::Path(ref path_type) => { + match MyTypePath::from(path_type) { + Ok(m_type) => m_type, + Err(err) => return Err(err) + } + }, + _ => return Err(Error::new(ty.span(), "not supported closure type")) + }; + types.push(my_type); + } + + Ok(types) + +} \ No newline at end of file diff --git a/nj-derive/src/ast/attribute.rs b/nj-derive/src/ast/attribute.rs new file mode 100644 index 00000000..c9ea1030 --- /dev/null +++ b/nj-derive/src/ast/attribute.rs @@ -0,0 +1,289 @@ + +use proc_macro2::Span; +use syn::AttributeArgs; +use syn::Attribute; +use syn::Result; +use syn::spanned::Spanned; +use syn::Error; +use syn::NestedMeta; +use syn::Meta; +use syn::MetaNameValue; +use syn::Lit; +use syn::LitStr; +use syn::Ident; +use syn::Path; + + + +/// Represents bindgen attributes +/// Attribute can be attached to free standing function or impl block +/// name="my_function" +/// constructor +/// setter +/// mt +#[derive(Debug)] +pub enum FunctionAttribute { + Getter(Ident), + Setter(Ident), + Constructor(Ident), + Name(LitStr), + MT(Ident) +} + +impl FunctionAttribute { + + fn from_ast(meta: Meta) -> Result { + + match meta { + Meta::NameValue(name_value) => { + if has_attribute(&name_value,"name") { + // check make sure name is str literal + match name_value.lit { + Lit::Str(str) => Ok(Self::Name(str)), + _ => Err(Error::new(name_value.span(), "name value is not string literal")) + } + } else { + Err(Error::new(name_value.span(), "unsupported attribute:")) + } + }, + Meta::Path(p) => + Self::from_ident(find_any_identifier(p)?), + + Meta::List(lit) => Err(Error::new(lit.span(),"nested attributes are not supported")) + } + + } + + + + + + fn from_ident(ident: Ident) -> Result { + + + if ident == "constructor" { + Ok(Self::Constructor(ident)) + } else if ident == "getter" { + Ok(Self::Getter(ident)) + } else if ident == "setter" { + Ok(Self::Setter(ident)) + } else if ident == "mt" { + Ok(Self::MT(ident)) + } else { + Err(Error::new(ident.span(), "unrecognized attribute name")) + } + } + + + + + + fn is_constructor(&self) -> bool { + match self { + Self::Constructor(_) => true, + _ => false + } + } + + fn is_multi_threaded(&self) -> bool { + match self { + Self::MT(_) => true, + _ => false + } + } + + /// get function name, if this is not name, return none + fn fn_name(&self) -> Option<&LitStr> { + match self { + Self::Name(ref name) => Some(name), + _ => None + } + } + + fn is_getter(&self) -> bool { + match self { + Self::Getter(_) => true, + _ => false + } + } + + fn is_setter(&self) -> bool { + match self { + Self::Setter(_) => true, + _ => false + } + } +} + +fn has_attribute(name_value: &MetaNameValue,attr_name: &str) -> bool { + + name_value.path + .segments + .iter() + .find(|seg| seg.ident == attr_name) + .is_some() +} + +fn find_any_identifier(path: Path) -> Result { + + if path.segments.len() == 0 { + Err(Error::new(path.span(),"invalid attribute")) + } else { + Ok(path + .segments + .into_iter() + .find(|_| true) + .map(|segment| segment.ident ) + .unwrap()) + } + + +} + +/// ast information related to attributes +#[derive(Debug,Default)] +pub struct FunctionAttributes { + pub constructor: Option, + pub multi_threaded: Option, + pub getter: Option, + pub setter: Option, + name: Option +} + +impl FunctionAttributes { + + + + pub fn from_ast(args: AttributeArgs) -> Result { + + //println!("attrs: {:#?}",args); + let mut attrs: Vec = vec![]; + + for attr in args { + match attr { + NestedMeta::Meta(meta) => { + attrs.push(FunctionAttribute::from_ast(meta)?); + }, + _ => return Err(Error::new(attr.span(), "invalid syntax")) + } + + } + + Self::from(attrs) + } + + + /// validate and parse attributes for individual features + /// if check_method is true, check class specific attributes + /// note that there are attribute parsing phases for class + /// first phase is as part of Impl structure, this is where class level attribute validation can be done + /// second phase is individual functions where we don't know if function is method or not + fn from(attrs: Vec) -> Result { + + let mut constructor = None; + let mut multi_threaded = None; + let mut getter = None; + let mut setter = None; + let mut name = None; + + for attr in attrs { + if attr.is_constructor() { + constructor = Some(attr); + } else if attr.is_multi_threaded() { + multi_threaded = Some(attr); + } else if attr.is_getter() { + getter = Some(attr); + } else if attr.is_setter() { + setter = Some(attr); + } else if let Some(name_lit) = attr.fn_name() { + name = Some(name_lit.value()); + } + + } + + + + Ok(Self { + constructor, + multi_threaded, + getter, + setter, + name + }) + } + + pub fn from_method_attribute(attribute: &Attribute) -> Result { + + //println!("token tree: {:#?}",attribute); + + match attribute.parse_meta()? { + + Meta::Path(_) => { + // ignore node_bindgen which already know exists + Ok(FunctionAttributes::default()) + }, + Meta::NameValue(n) => Err(Error::new(n.span(),"invalid attribute syntax")), + Meta::List(list) => { + let mut attrs = vec![]; + for nested_meta in list.nested.into_iter() { + match nested_meta { + NestedMeta::Meta(meta) => { + attrs.push(FunctionAttribute::from_ast(meta)?); + }, + NestedMeta::Lit(lit) => return Err(Error::new(lit.span(),"unrecognized syntax")), + } + + } + + Self::from(attrs) + } + } + + + } + + + + pub fn name(&self) -> Option<&String> { + (&self.name).as_ref() + } + + pub fn is_multi_threaded(&self) -> bool { + self.multi_threaded.is_some() + } + + pub fn is_constructor(&self) -> bool { + self.constructor.is_some() + } + + pub fn is_getter(&self) -> bool { + self.getter.is_some() + } + + pub fn is_setter(&self) -> bool { + self.setter.is_some() + } + + /// check if we method specific attribute if we not method + pub fn valid_as_non_method(&self) -> Result<()> { + + /* + if self.constructor.is_some() { + return Err(Error::new(Span::call_site(), "constructor is only allowed in method")); + } + */ + + if self.setter.is_some() { + return Err(Error::new(Span::call_site(), "setter is only allowed in method")); + } + + if self.getter.is_some() { + return Err(Error::new(Span::call_site(), "getter is only allowed in method")); + } + + Ok(()) + } + + + +} \ No newline at end of file diff --git a/nj-derive/src/ast/class.rs b/nj-derive/src/ast/class.rs new file mode 100644 index 00000000..d861eae4 --- /dev/null +++ b/nj-derive/src/ast/class.rs @@ -0,0 +1,108 @@ +use proc_macro2::Span; +use syn::ItemImpl; +use syn::ImplItem; +use syn::Result; +use syn::Ident; +use syn::LitStr; +use syn::Error; +use syn::ImplItemMethod; +use syn::spanned::Spanned; + +use crate::ast::MethodUtil; +use crate::util::default_function_property_name; +use super::FunctionAttributes; +use super::FunctionArgs; +use super::MyTypePath; + +pub struct Class<'a> { + pub self_ty: MyTypePath<'a>, + pub methods: Vec> +} + +impl <'a> Class<'a> { + + /// convert + pub fn from_ast(item: &'a ItemImpl) -> Result { + + use syn::Type; + + let mut methods = vec![]; + for item in &item.items { + + match item { + ImplItem::Method(method) => { + + if let Some(method) = Method::from_ast(method)? { + methods.push(method); + } + + }, + _ => {} + } + } + + // find type path + let self_ty = match &*item.self_ty { + Type::Path(path_type) => MyTypePath::from(&path_type)?, + _ => return Err(Error::new(item.span(), "not supported receiver type")) + }; + + Ok(Self{ + methods, + self_ty + }) + } + + pub fn constructor(&'a self) -> Option<&'a Method> { + self.methods.iter().find(|method| method.attributes.is_constructor()) + } + + pub fn my_type(&'a self) -> &MyTypePath<'a> { + &self.self_ty + } +} + +pub struct Method<'a> { + pub method: &'a ImplItemMethod, + pub attributes: FunctionAttributes, + pub args: FunctionArgs<'a> +} + + +impl <'a>Method<'a> { + + + /// extract js method, if it can't find marker attribute, return some + pub fn from_ast(method: &'a ImplItemMethod) -> Result> { + + if let Some(attr) = method.find_attr() { + + let args = FunctionArgs::from_ast(&method.sig)?; + Ok(Some(Self { + method, + args, + attributes: FunctionAttributes::from_method_attribute(attr)? + })) + + } else { + Ok(None) + } + } + + pub fn method_name(&self) -> &Ident { + &self.method.sig.ident + } + + /// used for registering in the Napi + pub fn property_name(&self) -> LitStr { + + if let Some(name) = self.attributes.name() { + LitStr::new(name, Span::call_site()) + } else { + LitStr::new(&default_function_property_name(&self.method_name().to_string()),Span::call_site()) + } + + } + + +} \ No newline at end of file diff --git a/nj-derive/src/ast/mod.rs b/nj-derive/src/ast/mod.rs new file mode 100644 index 00000000..1fd35da8 --- /dev/null +++ b/nj-derive/src/ast/mod.rs @@ -0,0 +1,13 @@ +mod attribute; +mod arg; +mod node; +mod util; +mod class; +mod types; + +pub use attribute::*; +pub use arg::*; +pub use node::*; +pub use util::*; +pub use class::*; +pub use types::*; diff --git a/nj-derive/src/parser.rs b/nj-derive/src/ast/node.rs similarity index 96% rename from nj-derive/src/parser.rs rename to nj-derive/src/ast/node.rs index a7351abe..a195cecb 100644 --- a/nj-derive/src/parser.rs +++ b/nj-derive/src/ast/node.rs @@ -4,6 +4,7 @@ use syn::parse::{Parse, ParseStream}; use syn::ItemImpl; use syn::ItemFn; +#[derive(Debug)] pub enum NodeItem { Function(ItemFn), Impl(ItemImpl), diff --git a/nj-derive/src/ast/types.rs b/nj-derive/src/ast/types.rs new file mode 100644 index 00000000..9926714d --- /dev/null +++ b/nj-derive/src/ast/types.rs @@ -0,0 +1,71 @@ +use syn::Ident; +use syn::TypePath; +use syn::Type; +use syn::TypeReference; +use syn::Error; +use syn::Result; +use syn::spanned::Spanned; +use quote::quote; +use proc_macro2::TokenStream; + +use crate::ast::TypePathUtil; + + +#[derive(Debug)] +pub struct MyTypePath<'a>(&'a TypePath); + +impl <'a>MyTypePath<'a> { + + + pub fn from(ty: &'a TypePath) -> Result { + + Ok(Self(ty)) + } + + + pub fn ident(&self) -> Option<&Ident> { + self.0.name_identifier() + } + + pub fn expansion(&self) -> TokenStream { + let ty = self.0; + quote! { + #ty + } + } + +} + + + + +#[derive(Debug)] +pub struct MyReferenceType<'a>(&'a Ident); + +impl <'a> MyReferenceType<'a> { + + + pub fn from(ty: &'a TypeReference) -> Result { + + match ty.elem.as_ref() { + Type::Path(path) => { + + if let Some(name_id) = path.name_identifier() { + Ok(Self(name_id)) + } else { + Err(Error::new(ty.span(), "no type identifier found")) + } + }, + _ => Err(Error::new(ty.span(), "no type identifier found")) + } + } + + + + /// return any first one + pub fn type_name(&self) -> &Ident { + self.0 + } + + +} \ No newline at end of file diff --git a/nj-derive/src/ast/util.rs b/nj-derive/src/ast/util.rs new file mode 100644 index 00000000..4c42c4be --- /dev/null +++ b/nj-derive/src/ast/util.rs @@ -0,0 +1,57 @@ +use syn::ItemFn; +use syn::Ident; +use syn::TypePath; +use syn::ImplItemMethod; +use syn::Attribute; + +/// traits for function item +pub trait FunctionItem { + + fn name(&self) -> &Ident; +} + +impl FunctionItem for ItemFn { + + fn name(&self) -> &Ident { + &self.sig.ident + } + +} + +pub trait TypePathUtil { + fn name_identifier(&self) -> Option<&Ident>; +} + +impl TypePathUtil for TypePath { + + /// find name identifier + fn name_identifier(&self) -> Option<&Ident> { + + self.path.segments.iter().find(|_| true).map(|segment| &segment.ident ) + } + +} + +pub trait MethodUtil { + + fn find_attr(&self) -> Option<&Attribute>; +} + +impl MethodUtil for ImplItemMethod { + + /// find attr that contains node bindgen + fn find_attr(&self) -> Option<&Attribute> { + + self.attrs.iter() + .find(|attr| { + attr.path.segments.iter().find( |seg| seg.ident == "node_bindgen").is_some() + }) + + } +} + + + + + + diff --git a/nj-derive/src/class.rs b/nj-derive/src/class.rs deleted file mode 100644 index 466efaa2..00000000 --- a/nj-derive/src/class.rs +++ /dev/null @@ -1,414 +0,0 @@ -use quote::quote; -use syn::ItemImpl; -use syn::ImplItem; -use syn::ImplItemMethod; -use syn::Ident; -use syn::LitStr; -use proc_macro2::Span; -use proc_macro2::TokenStream; -use proc_macro2::TokenTree; -use proc_macro2::Literal; - - -use crate::util::MyTypePath; -use crate::FunctionArgMetadata; -use crate::FunctionAttribute; -use crate::FunctionContext; -use crate::util::default_function_property_name; - -pub fn generate_class(impl_item: ItemImpl) -> TokenStream { - - //println!("class: {:#?}",impl_item); - let class_metadata = ClassMetadata::new(impl_item); - class_metadata.as_token_stream() -} - -struct ClassMetadata { - item: ItemImpl, -} - -impl ClassMetadata { - - fn new(item: ItemImpl) -> Self { - Self { - item - } - } - - /// extract class type - fn class_type(&self) -> Option { - MyTypePath::from(self.item.self_ty.clone()) - } - - - /// find methods which are defined in node_bindgen annotation - fn generate_properties(&self) -> (Vec,Option) { - - let mut constructor: Option = None; - let properties = self.item.items.iter() - .filter_map(|i_item| { - match i_item { - ImplItem::Method(method) => { - if is_js_method(method) { - let method_ident = &method.sig.ident; - // println!("method: {:#?}",method); - let attribute = FunctionTags::parse_attr(method); - let property_name = attribute.name().unwrap_or_else(|| Literal::string(&default_function_property_name(&method_ident.to_string()))); - let napi_name = Ident::new(&format!("napi_{}",method_ident),Span::call_site()); - if attribute.is_getter() { - Some(quote! { - node_bindgen::core::Property::new(#property_name).getter(Self::#napi_name), - }) - } else if attribute.is_setter() { - Some(quote! { - node_bindgen::core::Property::new(#property_name).setter(Self::#napi_name), - }) - - } else if attribute.is_constructor() { - match constructor { - Some(_) => {}, - None => { - std::mem::replace(&mut constructor,Some(method.clone())); - } - } - None - } else { - Some(quote! { - node_bindgen::core::Property::new(#property_name).method(Self::#napi_name), - }) - } - } else { - None - } - }, - _ => None - } - }) - .collect(); - - (properties,constructor) - } - - /// create constructor method - /// rust method must return Self - fn class_constructor(&self,method_opt: Option) -> (TokenStream,Option) { - - let (arg_conversion, metadata) = match method_opt { - - Some(method) => { - let method_ident = &method.sig.ident; - let arg_metadata = match FunctionArgMetadata::parse(&method.sig,false) { - Ok(arg) => arg, - Err(err) => { - eprintln!("error parsing sig: {}",err); - return (quote! { - compile_error!("unsupported argument types") - }, None) - } - }; - - let mut ctx = FunctionContext::default(); - let arg_tokens = arg_metadata.as_arg_token(&ctx); - let rust_inputs = arg_metadata.rust_args_input(&mut ctx); - - (quote! { - - #arg_tokens - - let rust_value = Self::#method_ident( #(#rust_inputs)* ); - Ok((rust_value,js_cb)) - - }, Some(arg_metadata)) - }, - None => (quote! { - let js_cb = js_env.get_cb_info(cb_info,0)?; - Ok((Self::new(),js_cb)) - }, None) - }; - - (quote! { - fn create_from_js(js_env: &node_bindgen::core::val::JsEnv, cb_info: node_bindgen::sys::napi_callback_info) -> Result<(Self,node_bindgen::core::val::JsCallback),node_bindgen::core::NjError> { - - #arg_conversion - } - },metadata) - } - - /// generate class constructor - fn generate_class_arg(&self,class_name: &Ident,arg_option: Option ) -> TokenStream { - - if let Some(arg_metadata) = arg_option { - - let args = arg_metadata.constructor_args(); - let struct_args = arg_metadata.constructor_new(); - let constr_conversion = arg_metadata.as_constructor_try_to_js(); - let invocation = arg_metadata.as_constructor_invocation(); - let construct_name = Ident::new(&format!("{}Constructor",class_name),Span::call_site()); - quote! { - - pub struct #construct_name { - #args - } - - impl #construct_name { - pub fn new(#args) -> Self { - Self { - #struct_args - } - } - } - - impl node_bindgen::core::TryIntoJs for #construct_name { - - fn try_to_js(self, js_env: &node_bindgen::core::val::JsEnv) -> Result { - - #constr_conversion - let new_instance = #class_name::new_instance(js_env,vec![#invocation])?; - Ok(new_instance) - } - } - } - } else { - quote! {} - } - - } - - // generate internal module that contains Js class helper - fn generate_class_helper(&self,class_type: &MyTypePath) -> TokenStream { - - let type_name = class_type.type_name().unwrap(); - - let helper_module_name = Ident::new( - &format!("{}_helper",type_name).to_lowercase(), - Span::call_site()); - - let class_type_lit = LitStr::new(&format!("{}",type_name),Span::call_site()); - - let ty = class_type.inner(); - - let (properties,constructor ) = self.generate_properties(); - - let ( constructor_token,fn_arg) = self.class_constructor(constructor); - - let constructor_arg = self.generate_class_arg(&type_name,fn_arg); - - let construct_name = Ident::new(&format!("{}Constructor",type_name),Span::call_site()); - - quote!{ - - use #helper_module_name::#construct_name; - - mod #helper_module_name { - - use std::ptr; - use node_bindgen::core::JSClass; - - use super::#ty; - - static mut CLASS_CONSTRUCTOR: node_bindgen::sys::napi_ref = ptr::null_mut(); - - impl node_bindgen::core::JSClass for #ty { - const CLASS_NAME: &'static str = #class_type_lit; - - - fn set_constructor(constructor: node_bindgen::sys::napi_ref) { - unsafe { - CLASS_CONSTRUCTOR = constructor; - } - } - - fn get_constructor() -> node_bindgen::sys::napi_ref { - unsafe { CLASS_CONSTRUCTOR } - } - - fn properties() -> node_bindgen::core::PropertiesBuilder { - - vec![ - #(#properties)* - ].into() - } - - #constructor_token - - } - - #constructor_arg - - use node_bindgen::core::submit_register_callback; - - - #[node_bindgen::core::ctor] - fn register_class() { - submit_register_callback(#ty::js_init); - } - } - - - } - } - - fn as_token_stream(&self) -> TokenStream { - - let class_type = match self.class_type() { - Some(info) => info, - None => return quote! { - compile_error!("can only handle type path for now") - } - }; - - let item = &self.item; - let class_helper = self.generate_class_helper(&class_type); - - quote! { - - #item - - #class_helper - - } - } -} - - -fn is_js_method(method: &ImplItemMethod) -> bool { - - method.attrs.iter() - .find(|attr| { - attr.path.segments.iter().find( |seg| seg.ident == "node_bindgen").is_some() - }).is_some() - -} - -/// contain list of function attributes -struct FunctionTags(Vec); - - -impl FunctionTags { - - /// parse method into function tags - /// TODO: this need to be refactored to handle parsing error - /// currently, if this only handle happy case - fn parse_attr(method: &ImplItemMethod) -> FunctionTags { - - let mut tags = vec![]; - for attr in method.attrs.iter() { - - for group_token in attr.tokens.clone().into_iter() { - - match group_token { - - TokenTree::Group(group) => { - - let mut group_peekable = group.stream().into_iter().peekable(); - - while let Some(iden_token ) = group_peekable.next() { - - match iden_token { - TokenTree::Ident(ident) => { - if ident == "getter" { - tags.push(FunctionAttribute::Getter); - } else if ident == "constructor" { - tags.push(FunctionAttribute::Constructor); - } else if ident == "setter" { - tags.push(FunctionAttribute::Setter); - } else if ident == "register" { - tags.push(FunctionAttribute::Register); - }else if ident == "name" { - // name must have = and literal - if let Some(punch) = group_peekable.peek() { - match punch { - TokenTree::Punct(punc) => { - if punc.as_char() == '=' { - let _ = group_peekable.next(); // consume puctual - if let Some(literal) = group_peekable.next() { - match literal { - TokenTree::Literal(name_literal) => { - tags.push(FunctionAttribute::Name(name_literal)) - }, - _=> {} - } - } - } - }, - _ => {} - } - } - } else { - tags.push(FunctionAttribute::Other); - } - }, - _ => { - tags.push(FunctionAttribute::Other); - } - } - - } - - }, - _ => { - tags.push(FunctionAttribute::Other); - } - - - } - } - } - - Self(tags) - } - - - fn is_getter(&self) -> bool { - self.0.iter().find(|tag| { - match tag { - FunctionAttribute::Getter => true, - _ => false - } - }).is_some() - } - - fn is_setter(&self) -> bool { - self.0.iter().find(|tag| { - match tag { - FunctionAttribute::Setter => true, - _ => false - } - }).is_some() - } - - #[allow(unused)] - fn is_register(&self) -> bool { - self.0.iter().find(|tag| { - match tag { - FunctionAttribute::Register => true, - _ => false - } - }).is_some() - } - - fn is_constructor(&self) -> bool { - self.0.iter().find(|tag| { - match tag { - FunctionAttribute::Constructor => true, - _ => false - } - }).is_some() - } - - /// optional name attribute, there should be only 1 name attribute - fn name(&self) -> Option { - self.0.iter().find_map(|tag| { - match tag { - FunctionAttribute::Name(liter) => Some(liter.clone()), - _ => None - } - }) - } - - - -} - - - diff --git a/nj-derive/src/function.rs b/nj-derive/src/function.rs deleted file mode 100644 index 71f7b4fa..00000000 --- a/nj-derive/src/function.rs +++ /dev/null @@ -1,947 +0,0 @@ -use quote::quote; -use syn::FnArg; -use syn::Ident; -use syn::LitInt; -use syn::Type; -use syn::Pat; -use syn::Error; -use syn::LitStr; -use syn::Generics; -use syn::TypeParam; -use syn::ItemFn; -use syn::Signature; -use syn::TypeParamBound; -use syn::PathArguments; -use syn::Receiver; -use syn::AttributeArgs; -use syn::ParenthesizedGenericArguments; -use syn::NestedMeta; -use syn::Meta; -use syn::ReturnType; -use proc_macro2::Span; -use proc_macro2::TokenStream; -use proc_macro2::Literal; - -use crate::util::MyTypePath; -use crate::util::rust_arg_var; -use crate::util::MyReferenceType; -use crate::util::default_function_property_name; - -pub fn generate_function(input_fn: ItemFn, args: AttributeArgs) -> TokenStream { - - //println!("args: {:#?}, fn: {:#?}",args,input_fn); - let fn_wrapper = FunctionMetadata::new(input_fn, args); - fn_wrapper.as_token_stream() -} - -pub enum FunctionAttribute { - Getter, - Setter, - Constructor, - Name(Literal), - Register, - Other, -} - -struct FunctionMetadata { - fn_item: ItemFn, - args: AttributeArgs, -} - -impl FunctionMetadata { - fn new(fn_item: ItemFn, args: AttributeArgs) -> Self { - Self { fn_item, args } - } - - fn is_async(&self) -> bool { - self.fn_item.sig.asyncness.is_some() - } - - /// check whether this function return () - fn has_default_output(&self) -> bool { - match self.fn_item.sig.output { - ReturnType::Default => true, - _ => false - } - } - - /// check if this method should be constructor - fn is_constructor(&self) -> bool { - self.has_attribute("constructor") - } - - /// check if closure should support multi-threaded - fn support_multi_threaded(&self) -> bool { - self.has_attribute("mt") - } - - /// check if we have attribute, this should be refactored to use attribute parser - fn has_attribute(&self,attribute: &str) -> bool { - self.args - .iter() - .find(|arg| match arg { - NestedMeta::Meta(meta) => match meta { - Meta::Path(path) => path - .segments - .iter() - .find(|seg| seg.ident == attribute) - .is_some(), - _ => false, - }, - _ => false, - }) - .is_some() - } - - /// name of the function - fn name(&self) -> &Ident { - &self.fn_item.sig.ident - } - - /// identifier for napi wrapper function - fn napi_fn_id(&self) -> Ident { - let n_fn_name = format!("napi_{}", self.name()); - Ident::new(&n_fn_name, Span::call_site()) - } - - - fn analyze_args(&self) -> Result { - FunctionArgMetadata::parse(&self.fn_item.sig,self.support_multi_threaded()) - } - - /// start of code generation - pub fn as_token_stream(&self) -> TokenStream { - - if self.is_constructor() { - let item = &self.fn_item; - return quote! { - #item - }; - } - - let args = match self.analyze_args() { - Ok(data) => data, - Err(err) => return err.to_compile_error(), - }; - - let napi_code = self.generate_napi_code(&mut &args); - let property_code = self.generate_property_code(&args); - - quote! { - - #napi_code - - #property_code - - } - } - - - /// generate native code to be invoked by napi - fn generate_napi_code(&self, args: &FunctionArgMetadata) -> TokenStream { - - - let input_fn = &self.fn_item; - let ident_n_api_fn = self.napi_fn_id(); - - let mut ctx = FunctionContext { - is_async: self.is_async(), - is_multi_threaded: args.is_multi_threaded(), - name: self.name().to_string(), - ..Default::default() - }; - - let rust_invocation = self.generate_rust_invocation(args,&mut ctx); - let rust_args_struct= &ctx.cb_args; - - if args.is_method() { - - // if function is method, we can't put rust function inside our napi because we need to preserver self - // in the rust method. - let napi_fn = raw_napi_function_template( - self.napi_fn_id(), - quote! {}, - rust_args_struct, - rust_invocation); - - quote! { - #input_fn - - #napi_fn - } - } else { - - // otherwise we can put rust function inside to make it tidy - raw_napi_function_template( - ident_n_api_fn, - quote! { #input_fn }, - rust_args_struct, - rust_invocation) - } - } - - - - /// this code generation does following: - /// - /// * extract arguments from napi environment based on rust function arguments type (including type checking) - /// * invoke rust function (including async wrapper) - /// * convert rust result back to JS - fn generate_rust_invocation(&self, function_args: &FunctionArgMetadata,ctx: &mut FunctionContext) -> TokenStream { - - - // code to convert extract rust values from Js Env - let js_to_rust_values = function_args.as_arg_token(&ctx); - - // express to invoke rust function - let receiver = function_args.receiver(); - let rust_invoke = function_args.as_token_stream(self.name(),ctx); - - // if this is async, wrap with JsFuture - let rust_invoke_ft_wrapper = if ctx.is_async { - - if self.has_default_output() { - - // since this doesn't have any output, we don't need return promise, we just - // spawn async and return null ptr - quote! { - - node_bindgen::core::future::spawn(async move { - #rust_invoke.await; - }); - - Ok(std::ptr::null_mut()) - } - - } else { - - let async_name = format!("{}_ft", self.name()); - let async_lit = LitStr::new(&async_name, Span::call_site()); - quote! { - (node_bindgen::core::JsPromiseFuture::new( - #rust_invoke,#async_lit - )).try_to_js(&js_env) - } - } - } else { - quote! { - #rust_invoke.try_to_js(&js_env) - } - - }; - - quote! { - - let result: Result = ( move || { - - #js_to_rust_values - - #receiver - - #rust_invoke_ft_wrapper - - })(); - - - result.to_js(&js_env) - } - } - - - /// generate code to register this function property to global property - fn generate_property_code(&self, args: &FunctionArgMetadata) -> TokenStream { - - - if args.is_method() { - return quote! {}; - } - - let ident_n_api_fn = self.napi_fn_id(); - - let register_fn_name = format!("register_{}", ident_n_api_fn); - let property_name = default_function_property_name(&self.name().to_string()); - let ident_register_fn = Ident::new(®ister_fn_name, Span::call_site()); - let property_name_literal = LitStr::new(&property_name, Span::call_site()); - - quote! { - #[node_bindgen::core::ctor] - fn #ident_register_fn() { - - let property = node_bindgen::core::Property::new(#property_name_literal).method(#ident_n_api_fn); - node_bindgen::core::submit_property(property); - } - - } - } - - -} - -#[derive(Default)] -pub struct FunctionContext { - is_async: bool, - is_multi_threaded: bool, - name: String, - cb_args: Vec // accumulated arg structure -} - -/// Represents functional arguments -pub struct FunctionArgMetadata { - receiver: Option, - args: Vec, - multi_threaded: bool -} - -impl FunctionArgMetadata { - pub fn parse(sig: &Signature,multi_threaded: bool) -> Result { - let generics = &sig.generics; - - let mut args: Vec = vec![]; - - let receiver = Self::find_receiver(sig); - - // extract arguments, - for arg in &sig.inputs { - match arg { - FnArg::Receiver(_) => {} - FnArg::Typed(arg_type) => { - - match &*arg_type.pat { - Pat::Ident(identity) => { - - if let Some(arg) = FunctionArg::new( - identity.ident.clone(), - arg_type.ty.clone(), - GenerericInfo::new(generics), - multi_threaded - ) { - args.push(arg); - } else { - return Err(Error::new( - Span::call_site(), - "not supported arg types", - )); - } - } - _ => return Err(Error::new(Span::call_site(), "not supported type")), - } - } - } - } - - Ok(FunctionArgMetadata { receiver, args, multi_threaded }) - } - - /// find receiver if any, this will be used to indicate if this is method - fn find_receiver(sig: &Signature) -> Option { - for arg in &sig.inputs { - match arg { - FnArg::Receiver(rec) => return Some(rec.clone()), - _ => {} - } - } - - None - } - - fn is_method(&self) -> bool { - self.receiver.is_some() - } - - fn is_multi_threaded(&self) -> bool { - self.multi_threaded - } - - /// used as argument - pub fn constructor_args(&self) -> TokenStream { - let args = self - .args - .iter() - .filter(|arg| !arg.is_callback()) - .enumerate() - .map(|(i, arg)| arg.as_rust_fn_arg(i)); - - quote! { - #(#args)* - } - } - - /// suitable for used in constructor create - pub fn constructor_new(&self) -> TokenStream { - let args = self - .args - .iter() - .filter(|arg| !arg.is_callback()) - .enumerate() - .map(|(i, arg)| arg.as_struct_arg(i)); - - quote! { - #(#args)* - } - } - - /// generate tokens for extracting arguments from JS - pub fn rust_args_input(&self,ctx: &mut FunctionContext) -> Vec { - let mut arg_index = 0; - self.args - .iter() - .map(|arg| arg.as_arg_token_stream(&mut arg_index,ctx)) - .collect() - } - - /// size of the rust arguments - fn rust_args_len(&self) -> usize { - self.args.iter().filter(|arg| !arg.is_callback()).count() - } - - /// generate code to extract cb based on method signature - /// first it extract cb info based on argument count - /// then it generates rust variables based on arguments conversion. - pub fn as_arg_token(&self, ctx: &FunctionContext) -> TokenStream { - let arg_len = self.rust_args_len(); - let count_literal = arg_len.to_string(); - let js_count = LitInt::new(&count_literal, Span::call_site()); - - let rust_args: Vec = self - .args - .iter() - .filter(|arg| !arg.is_callback()) - .enumerate() - .map(|(i, arg)| arg.js_to_rust_token_stream(i, ctx)) - .collect(); - - quote! { - - let js_cb = js_env.get_cb_info(cb_info, #js_count)?; - #(#rust_args)* - - } - } - - /// generate expression to convert constructor into new instance - pub fn as_constructor_try_to_js(&self) -> TokenStream { - let arg_len = self.rust_args_len(); - let args: Vec = (0..arg_len) - .map(|index| { - let arg_name = Ident::new(&format!("arg{}", index), Span::call_site()); - quote! { - let #arg_name = self.#arg_name.try_to_js(js_env)?; - } - }) - .collect(); - - quote! { - - #(#args)* - - } - } - - pub fn as_constructor_invocation(&self) -> TokenStream { - let arg_len = self.rust_args_len(); - let args: Vec = (0..arg_len) - .map(|index| { - let arg_name = Ident::new(&format!("arg{}", index), Span::call_site()); - quote! { - #arg_name, - } - }) - .collect(); - - quote! { - - #(#args)* - - } - } - - - fn receiver(&self) -> TokenStream { - - if self.is_method() { - quote! { - let receiver = (js_cb.unwrap_mut::()?); - } - } else { - quote! {} - } - } - - /// convert myself as rust code which can convert JS arguments into rust. - fn as_token_stream(&self, rust_fn_ident: &Ident,ctx: &mut FunctionContext) -> TokenStream { - - let rust_args_input = self.rust_args_input(ctx); - - if self.is_method() { - quote! { - receiver.#rust_fn_ident( #(#rust_args_input)* ) - } - } else { - quote! { - #rust_fn_ident( #(#rust_args_input)* ) - } - } - - } -} - -/// generate napi function invocation whether it is method or just free standing function -fn raw_napi_function_template( - ident_n_api_fn: Ident, - input_fn: TokenStream, - rust_args_struct: &Vec, - rust_invocation: TokenStream, -) -> TokenStream { - - quote! { - - extern "C" fn #ident_n_api_fn(env: node_bindgen::sys::napi_env,cb_info: node_bindgen::sys::napi_callback_info) -> node_bindgen::sys::napi_value - { - use node_bindgen::core::TryIntoJs; - use node_bindgen::core::IntoJs; - use node_bindgen::core::val::JsCallbackFunction; - - #input_fn - - #(#rust_args_struct)* - - let js_env = node_bindgen::core::val::JsEnv::new(env); - - #rust_invocation - } - } -} - -/// Categorize function argument -enum FunctionArg { - Path(MyTypePath), // normal type - Ref(MyReferenceType), // reference type - Closure(ClosureType), // callback - JSCallback(MyReferenceType), // indicating that we want to receive typed JsCallback -} - -impl FunctionArg { - /// given this, convert into normalized type signature - fn new(ident: Ident, ty: Box, generics: GenerericInfo,multi_threaded: bool) -> Option { - match *ty { - Type::Path(path_type) => { - let my_type = MyTypePath::new(path_type); - - // check whether type references in the generic indicates this is closure - if let Some(param) = generics.find_generic(&my_type.type_name().unwrap()) { - if let Some(closure) = ClosureType::from(ident,param,multi_threaded) { - Some(Self::Closure(closure)) - } else { - None - } - } else { - Some(Self::Path(my_type)) - } - } - Type::Reference(ref_type) => { - let my_type = MyReferenceType::new(ref_type); - if my_type.is_callback() { - Some(Self::JSCallback(my_type)) - } else { - Some(Self::Ref(my_type)) - } - } - _ => None, - } - } - - /// generate as argument to rust function - /// ex: arg0: f64, arg1: String, - fn as_rust_fn_arg(&self, index: usize) -> TokenStream { - let name = Ident::new(&format!("arg{}", index), Span::call_site()); - match self { - Self::Path(ty) => { - let inner = ty.inner(); - quote! { - #name: #inner, - } - } - Self::Closure(_) => quote! { compile_error!("closure can't be used in constructor ")}, - Self::JSCallback(_) => quote! { compile_error!("JsCallback can't be used in constructor")}, - Self::Ref(_) => quote! { compile_error!("ref can't be used in constructor")} - } - } - - fn as_struct_arg(&self, index: usize) -> TokenStream { - let name = Ident::new(&format!("arg{}", index), Span::call_site()); - quote! { - #name, - } - } - - /// generate code as part of invoking rust function - /// for normal argument, it is just variable - /// other may like closure may need to convert to rust closure - /// for example, - fn as_arg_token_stream(&self, index: &mut usize,ctx: &mut FunctionContext) -> TokenStream { - match self { - Self::Path(t) => { - let output = t.as_arg_token_stream(*index); - *index = *index + 1; - output - }, - Self::Ref(t) => { - let output = t.as_arg_token_stream(*index); - *index = *index + 1; - output - } - Self::Closure(t) => { - let arg_name = rust_arg_var(*index); - let output = t.as_arg_token_stream(*index, &arg_name,ctx); - *index = *index + 1; - output - } - Self::JSCallback(_) => { - let js_cb = Ident::new("js_cb", Span::call_site()); - quote! { &#js_cb, } - } - } - } - - fn is_callback(&self) -> bool { - match self { - Self::JSCallback(_) => true, - _ => false, - } - } - - /// generate expression to extract rust value from Js env - /// example as below: - /// let r_arg1 = cb.get_value::(1)?; - /// - fn js_to_rust_token_stream(&self, arg_index: usize, ctx: &FunctionContext) -> TokenStream { - - // generate expression to convert napi value to rust value from callback - fn rust_value(possible_type_name: Option, index: usize) -> TokenStream { - if let Some(type_name) = possible_type_name { - let rust_value = rust_arg_var(index); - let index_literal = index.to_string(); - let index_ident = LitInt::new(&index_literal, Span::call_site()); - quote! { - let #rust_value = js_cb.get_value::<#type_name>(#index_ident)?; - } - } else { - quote! { - compile_error!("not supported type ") - } - } - } - - /// generate expression to convert napi_value as rust ref - fn rust_ref(possible_type_name: Option, index: usize) -> TokenStream { - if let Some(type_name) = possible_type_name { - let rust_value = rust_arg_var(index); - let index_literal = index.to_string(); - let index_ident = LitInt::new(&index_literal, Span::call_site()); - quote! { - let #rust_value = js_cb.get_ref::<#type_name>(#index_ident)?; - } - } else { - quote! { - compile_error!("not supported type ") - } - } - } - - match self { - Self::Closure(ty) => { - if ctx.is_async { - ty.generate_as_async_token_stream(arg_index,ctx) - } else if ctx.is_multi_threaded { - ty.generate_as_async_token_stream(arg_index,ctx) - } else { - rust_value(Some(ty.type_name()), arg_index) - } - } - Self::Path(ty) => rust_value(ty.type_name(), arg_index), - Self::Ref(ty) => rust_ref(ty.type_name(), arg_index), - Self::JSCallback(_ty) => quote! { compile_error!("should not be converted from JS")}, - } - } -} - -struct GenerericInfo<'a> { - generics: &'a Generics, -} - -impl<'a> GenerericInfo<'a> { - fn new(generics: &'a Generics) -> Self { - Self { generics } - } - - /// find generic with identifier - fn find_generic(&self, ident: &Ident) -> Option { - for ty in self.generics.type_params() { - if ty.ident.to_string() == ident.to_string() { - return Some(ty.clone()); - } - } - - None - } -} - - -struct ClosureType { - ty: ParenthesizedGenericArguments, - ident: Ident, - multi_threaded: bool -} - -impl ClosureType { - // try to see if we can find closure, otherwise return none - fn from(ident: Ident,param: TypeParam,multi_threaded: bool) -> Option { - for bound in param.bounds { - match bound { - TypeParamBound::Trait(tt) => { - for segment in tt.path.segments { - match segment.arguments { - PathArguments::Parenthesized(path) => return Some(Self { - ident, - ty: path, - multi_threaded - }), - _ => return None, - } - } - return None; - } - TypeParamBound::Lifetime(_) => return None, - } - } - None - } - - fn type_name(&self) -> Ident { - - let callback_type = if self.multi_threaded { - "JsMultiThreadedCallbackFunction" - } else { - "JsCallbackFunction" - }; - - Ident::new(callback_type, Span::call_site()) - } - - - fn as_arg_token_stream(&self, arg_index: usize, closure_var: &Ident,ctx: &mut FunctionContext) -> TokenStream { - - let args: Vec = self - .ty - .inputs - .iter() - .enumerate() - .map(|(index, path)| { - match path { - Type::Path(path_type) => { - let ty = MyTypePath::new(path_type.clone()); - let arg_name = format!("cb_arg{}", index); - let var_name = Ident::new(&arg_name, Span::call_site()); - let type_name = ty.type_name().unwrap(); - quote! { - #var_name: #type_name, - } - } - _ => quote! {}, - } - }) - .collect(); - - let inner_closure = if ctx.is_async || ctx.is_multi_threaded { - self.as_async_arg_token_stream(arg_index,closure_var,ctx) - } else { - self.as_sync_arg_token_stream(arg_index,closure_var) - }; - - quote! { - move | #(#args)* | { - - #inner_closure - } - - } - - } - - /// generate as argument to sync rust function or method - /// since this is closure, we generate closure - fn as_sync_arg_token_stream(&self, _i: usize, closure_var: &Ident) -> TokenStream { - - let js_conversions: Vec = self - .ty - .inputs - .iter() - .enumerate() - .map(|(index, _path)| { - let arg_name = format!("cb_arg{}", index); - let var_name = Ident::new(&arg_name, Span::call_site()); - quote! { - #var_name, - } - }) - .collect(); - - quote! { - - // invoke sync closure - let result = (|| { - let args = vec![ - #(#js_conversions)* - ]; - #closure_var.call(args) - })(); - - result.to_js(&js_env); - - } - } - - // name of function is used by thread safe function to complete closure - fn async_js_callback_identifier(&self) -> Ident { - Ident::new(&format!("thread_safe_{}_complete",self.ident),Span::call_site()) - } - - fn as_async_arg_token_stream(&self, _index: usize, closure_var: &Ident,ctx: &mut FunctionContext) -> TokenStream { - - let arg_struct_name = Ident::new(&format!("Arg{}",self.ident),Span::call_site()); - let arg_cb_complete = self.async_js_callback_identifier(); - let struct_fields: Vec = self - .ty - .inputs - .iter() - .enumerate() - .map(|(index, path)| { - match path { - Type::Path(path_type) => { - let ty = MyTypePath::new(path_type.clone()); - let var_name = Ident::new(&format!("arg{}", index), Span::call_site()); - let type_name = ty.type_name().unwrap(); - quote! { - #var_name: #type_name, - } - } - _ => quote! {}, - } - }) - .collect(); - - - let js_complete_conversions: Vec = self - .ty - .inputs - .iter() - .enumerate() - .map(|(index, _path)| { - let js_var_iden = Ident::new(&format!("js_arg{}",index),Span::call_site()); - let arg_idn = Ident::new(&format!("arg{}",index),Span::call_site()); - quote !{ - let #js_var_iden = my_val.#arg_idn.try_to_js(&js_env)?; - } - }) - .collect(); - - let js_call: Vec = self - .ty - .inputs - .iter() - .enumerate() - .map(|(index, _path)| { - let js_var_iden = Ident::new(&format!("js_arg{}",index),Span::call_site()); - quote !{ - #js_var_iden, - } - }) - .collect(); - - ctx.cb_args.push(quote!{ - - #[derive(Debug)] - struct #arg_struct_name { - #(#struct_fields)* - } - - extern "C" fn #arg_cb_complete( - env: node_bindgen::sys::napi_env, - js_cb: node_bindgen::sys::napi_value, - _context: *mut ::std::os::raw::c_void, - data: *mut ::std::os::raw::c_void) { - - if env != std::ptr::null_mut() { - - node_bindgen::core::log::debug!("async cb invoked"); - let js_env = node_bindgen::core::val::JsEnv::new(env); - - let result: Result<(), node_bindgen::core::NjError> = (move ||{ - - let global = js_env.get_global()?; - let my_val: Box<#arg_struct_name> = unsafe { Box::from_raw(data as *mut #arg_struct_name) }; - node_bindgen::core::log::trace!("arg: {:#?}",my_val); - #(#js_complete_conversions)* - - node_bindgen::core::log::debug!("async cb, invoking js cb"); - js_env.call_function(global,js_cb,vec![#(#js_call)*])?; - node_bindgen::core::log::trace!("async cb, done"); - Ok(()) - - })(); - - node_bindgen::core::assert_napi!(result) - - } - - } - - }); - - - - let args: Vec = self - .ty - .inputs - .iter() - .enumerate() - .map(|(index, _path)| { - let arg_name = Ident::new(&format!("arg{}",index),Span::call_site()); - let cb_name: Ident = Ident::new(&format!("cb_arg{}",index),Span::call_site()); - quote! { - #arg_name: #cb_name, - } - }) - .collect(); - - quote! { - - let arg = #arg_struct_name { - #(#args)* - }; - - node_bindgen::core::log::trace!("converting rust to raw ptr"); - let my_box = Box::new(arg); - let ptr = Box::into_raw(my_box); - - #closure_var.call(Some(ptr as *mut core::ffi::c_void)).expect("callback should work"); - - } - - } - - /// generate thread safe function when callback are used in the async - /// for example: - /// let r_arg1 = cb.create_thread_safe_function("hello_sf",0,Some(hello_callback_js))?; - fn generate_as_async_token_stream(&self,index: usize,ctx: &FunctionContext) -> TokenStream { - - let sf_identifier = LitStr::new(&format!("{}_sf", ctx.name), Span::call_site()); - let rust_var_name = rust_arg_var(index); - let js_cb_completion = self.async_js_callback_identifier(); - - quote! { - let #rust_var_name = js_cb.create_thread_safe_function(#sf_identifier,#index,Some(#js_cb_completion))?; - } - - } -} \ No newline at end of file diff --git a/nj-derive/src/generator/class/arg.rs b/nj-derive/src/generator/class/arg.rs new file mode 100644 index 00000000..e374254a --- /dev/null +++ b/nj-derive/src/generator/class/arg.rs @@ -0,0 +1,142 @@ +use proc_macro2::TokenStream; +use quote::quote; + +use crate::ast::FunctionArgType; +use crate::ast::Method; +use crate::ast::Class; +use crate::ast::FunctionArgs; +use crate::util::arg_ident; +use crate::util::ident; + +pub fn generate_class_arg(method: Option<&Method>,class: &Class) -> TokenStream { + + + if let Some(method) = method { + + let class_name = class.my_type().ident().unwrap(); // class should have identifier + let args = generate_args(&method.args); + let struct_args = generate_structure_args(&method.args); + + let constr_conversion = as_constructor_try_to_js(&method.args); + let invocation = as_constructor_invocation(&method.args); + let construct_name = ident(&format!("{}Constructor",class_name)); + quote! { + + pub struct #construct_name { + #args + } + + impl #construct_name { + pub fn new(#args) -> Self { + Self { + #struct_args + } + } + } + + impl node_bindgen::core::TryIntoJs for #construct_name { + + fn try_to_js(self, js_env: &node_bindgen::core::val::JsEnv) -> Result { + + #constr_conversion + let new_instance = #class_name::new_instance(js_env,vec![#invocation])?; + Ok(new_instance) + } + } + } + } else { + quote! {} + } + +} + + + + + + +/// generate arg with type, for used in defining structures +fn generate_args(args: &FunctionArgs) -> TokenStream { + + let args = args + .args + .iter() + // .filter(|arg| !arg.is_callback()) + .enumerate() + .map(|(i, arg)| { + + let name = arg_ident(i); + match &arg.typ { + FunctionArgType::Path(ty) => { + let inner = ty.expansion(); + quote! { + #name: #inner + } + } + FunctionArgType::Closure(_) => quote! { compile_error!("closure can't be used in constructor ")}, + // FunctionArgType::JSCallback(_) => quote! { compile_error!("JsCallback can't be used in constructor")}, + FunctionArgType::Ref(_) => quote! { compile_error!("ref can't be used in constructor")} + } + }); + + quote! { + #(#args),* + } + +} + + +fn generate_structure_args(args: &FunctionArgs) -> TokenStream { + + + let args = args + .args + .iter() + // .filter(|arg| !arg.is_callback()) + .enumerate() + .map(|(i, _arg)| { + let name = arg_ident(i); + quote! { + #name + } + }); + + quote! { + #(#args),* + } +} + +/// generate expression to convert constructor into new instance +fn as_constructor_try_to_js(args: &FunctionArgs) -> TokenStream { + let arg_len = args.len(); + let args = (0..arg_len) + .map(|i| { + let arg_name = arg_ident(i); + quote! { + let #arg_name = self.#arg_name.try_to_js(js_env)?; + } + }); + + quote! { + + #(#args)* + + } +} + +fn as_constructor_invocation(args: &FunctionArgs) -> TokenStream { + let arg_len = args.len(); + let args = (0..arg_len) + .map(|i| { + let arg_name = arg_ident(i); + quote! { + #arg_name + } + }); + + quote! { + + #(#args),* + + } +} diff --git a/nj-derive/src/generator/class/constructor.rs b/nj-derive/src/generator/class/constructor.rs new file mode 100644 index 00000000..cfb377a8 --- /dev/null +++ b/nj-derive/src/generator/class/constructor.rs @@ -0,0 +1,47 @@ +use proc_macro2::TokenStream; +use quote::quote; + +use crate::generator::FnGeneratorCtx; +use crate::ast::Method; + +/// generate class constructor +pub fn class_constructor(method: Option<&Method>) -> TokenStream { + + use crate::generator::as_arg_token; + use crate::generator::rust_args_input; + + + let expansion = if let Some(method) = method { + + + let method_ident = &method.method_name(); + + + let ctx = FnGeneratorCtx::new(&method.method.sig,&method.args,&method.attributes); + let arg_tokens = as_arg_token(&ctx); + + let mut cb_args = vec![]; + let rust_inputs = rust_args_input(&ctx,&mut cb_args); + + quote! { + + #arg_tokens + + let rust_value = Self::#method_ident( #(#rust_inputs)* ); + Ok((rust_value,js_cb)) + + } + } else { + quote! { + let js_cb = js_env.get_cb_info(cb_info,0)?; + Ok((Self::new(),js_cb)) + } + }; + + quote! { + fn create_from_js(js_env: &node_bindgen::core::val::JsEnv, cb_info: node_bindgen::sys::napi_callback_info) -> Result<(Self,node_bindgen::core::val::JsCallback),node_bindgen::core::NjError> { + + #expansion + } + } +} diff --git a/nj-derive/src/generator/class/mod.rs b/nj-derive/src/generator/class/mod.rs new file mode 100644 index 00000000..677cd904 --- /dev/null +++ b/nj-derive/src/generator/class/mod.rs @@ -0,0 +1,146 @@ +mod constructor; +mod arg; + +use quote::quote; +use proc_macro2::TokenStream; +use syn::ItemImpl; + +use crate::ast::Class; +use crate::util::ident; +use crate::util::lit_str; + + +pub fn generate_class(impl_item: ItemImpl) -> TokenStream { + + match Class::from_ast(&impl_item) { + Err(err) => return err.to_compile_error().into(), + Ok(class) => { + + let class_helper = generate_class_helper(class); + + quote! { + + #impl_item + + #class_helper + + } + + // quote!{} + + + + } + + } +} + + + +// generate internal module that contains Js class helper +fn generate_class_helper(class: Class) -> TokenStream { + + use constructor::class_constructor; + use arg::generate_class_arg; + + let constructor_method = class.constructor(); + let type_name = class.self_ty.expansion(); + + let helper_module_name = ident(&format!("{}_helper",type_name).to_lowercase()); + + let class_type_lit = lit_str(&format!("{}",type_name)); + let properties = generate_properties(&class); + let constructor_exp = class_constructor(constructor_method); + let class_arg_exp = generate_class_arg(constructor_method,&class); + let construct_name = ident(&format!("{}Constructor",type_name)); + + quote!{ + + use #helper_module_name::#construct_name; + + mod #helper_module_name { + + use std::ptr; + use node_bindgen::core::JSClass; + + use super::#type_name; + + static mut CLASS_CONSTRUCTOR: node_bindgen::sys::napi_ref = ptr::null_mut(); + + impl node_bindgen::core::JSClass for #type_name { + const CLASS_NAME: &'static str = #class_type_lit; + + + fn set_constructor(constructor: node_bindgen::sys::napi_ref) { + unsafe { + CLASS_CONSTRUCTOR = constructor; + } + } + + fn get_constructor() -> node_bindgen::sys::napi_ref { + unsafe { CLASS_CONSTRUCTOR } + } + + fn properties() -> node_bindgen::core::PropertiesBuilder { + + vec![ + #(#properties)* + ].into() + } + + #constructor_exp + + } + + #class_arg_exp + + use node_bindgen::core::submit_register_callback; + + + #[node_bindgen::core::ctor] + fn register_class() { + submit_register_callback(#type_name::js_init); + } + } + } + + +} + + + +/// find methods which are defined in node_bindgen annotation +fn generate_properties(class: &Class) -> Vec { + + class.methods.iter() + .filter_map(|method| { + + if method.attributes.is_constructor() { + None + } else { + + let method_ident = &method.method_name(); + + + let property_name = method.property_name(); + let napi_name = ident(&format!("napi_{}",method_ident)); + + Some( + if method.attributes.is_getter() { + quote! { + node_bindgen::core::Property::new(#property_name).getter(Self::#napi_name), + } + } else if method.attributes.is_setter() { + quote! { + node_bindgen::core::Property::new(#property_name).setter(Self::#napi_name), + } + } else { + quote! { + node_bindgen::core::Property::new(#property_name).method(Self::#napi_name), + } + } + ) + } + }) + .collect() +} \ No newline at end of file diff --git a/nj-derive/src/generator/context.rs b/nj-derive/src/generator/context.rs new file mode 100644 index 00000000..f044d0fc --- /dev/null +++ b/nj-derive/src/generator/context.rs @@ -0,0 +1,116 @@ + +use proc_macro2::Span; +use syn::Ident; +use syn::FnArg; +use syn::Signature; +use syn::Receiver; +use syn::LitStr; +use syn::ReturnType; +use quote::quote; +use proc_macro2::TokenStream; + +use crate::ast::FunctionArgs; +use crate::ast::FunctionAttributes; +use crate::util::ident; + +/// Context for code function code generation +pub struct FnGeneratorCtx<'a> { + pub args: &'a FunctionArgs<'a>, + pub attributes: &'a FunctionAttributes, + pub sig: &'a Signature, + receiver: Option<&'a Receiver> +} + +impl <'a>FnGeneratorCtx<'a> { + + + // TODO: is_multi_threaded should be check and return self + pub fn new( + sig: &'a Signature, + args: &'a FunctionArgs<'a>, + attributes: &'a FunctionAttributes, + ) -> Self { + Self { + sig, + args, + attributes, + receiver: find_receiver(sig) + } + } + + + /// function name identifier + pub fn fn_name(&self) -> &Ident { + &self.sig.ident + } + + pub fn is_method(&self) -> bool { + self.receiver.is_some() + } + + + pub fn is_async(&self) -> bool { + self.sig.asyncness.is_some() + } + + /// check whether this function return () + pub fn has_default_output(&self) -> bool { + match self.sig.output { + ReturnType::Default => true, + _ => false + } + } + + + + pub fn napi_fn_id(&self) -> Ident { + ident(&format!("napi_{}", self.fn_name())) + } + + pub fn attributes(&self) -> &FunctionAttributes { + &self.attributes + } + + /// used for registering in the Napi + pub fn property_name(&self) -> LitStr { + + use crate::util::default_function_property_name; + + if let Some(name) = self.attributes().name() { + LitStr::new(name, Span::call_site()) + } else { + LitStr::new(&default_function_property_name(&self.fn_name().to_string()),Span::call_site()) + } + + } + + // callback type name + pub fn callback_type_name(&self) -> TokenStream { + + let callback_type = if self.attributes.is_multi_threaded() { + "JsMultiThreadedCallbackFunction" + } else { + "JsCallbackFunction" + }; + + let ident = Ident::new(callback_type, Span::call_site()); + quote! { #ident } + } + + +} + + + + +fn find_receiver(sig: &Signature) -> Option<&Receiver> { + + sig.inputs.iter().find_map( |arg| + + match arg { + FnArg::Receiver(rec) => Some(rec), + _ => None + } + ) + +} \ No newline at end of file diff --git a/nj-derive/src/generator/function.rs b/nj-derive/src/generator/function.rs new file mode 100644 index 00000000..4c8955c3 --- /dev/null +++ b/nj-derive/src/generator/function.rs @@ -0,0 +1,570 @@ +use quote::quote; +use proc_macro2::TokenStream; +use proc_macro2::Span; +use syn::ItemFn; +use syn::Ident; +use syn::LitInt; +use syn::LitStr; + + + +use crate::ast::FunctionArgs; +use crate::ast::FunctionArg; +use crate::ast::FunctionAttributes; +use crate::ast::FunctionArgType; +use crate::util::ident; + +use super::FnGeneratorCtx; +use super::generate_napi_code; +use super::generate_property_code; + +use closure::generate_closure_invocation; + +pub type CbArgs = Vec; + +pub use arg_extraction::*; +pub use args_input::*; + +/// generate JS wrapper to translate rust function +pub fn generate_function(input_fn: ItemFn,attributes: FunctionAttributes) -> TokenStream { + + + match FunctionArgs::from_ast(&input_fn.sig) { + Err(err) => return err.to_compile_error().into(), + Ok(args) => { + + // validate additional attribute in method context + + if !args.is_method { + + if let Err(err) = attributes.valid_as_non_method() { + return err.to_compile_error().into() + } + + } + + + + let mut ctx = FnGeneratorCtx::new(&input_fn.sig,&args,&attributes); + + + if attributes.is_constructor() { + return quote! { + #input_fn + }; + } + + + let napi_code = generate_napi_code(&mut ctx,&input_fn); + let property_code = generate_property_code(&ctx); + + let expansion = quote! { + + #napi_code + + #property_code + + }; + + + expansion + } + + } + +} + + + + + +/// generate code to extract Rust values from JS environment +/// Given rust code like this: +/// +/// fn sum(first: i32, second: i32) -> i32 { +/// first + second +/// } +/// +/// +/// Generate extract code like as below: +/// let result: Result = +/// (move || { +/// let js_cb = js_env.get_cb_info(cb_info, 2)?; +/// let rust_value_0 = js_cb.get_value::(0)?; +/// let rust_value_1 = js_cb.get_value::(1)?; +/// sum(rust_value_0, rust_value_1).try_to_js(&js_env) +/// })(); +/// result.to_js(&js_env) +/// +/// Code generation does +/// - compute number of parameters from input signatures +/// - for each arg type, generates converting line +/// let rust_value_{N} = js_cb.get_value::<{T}>(N)?; +/// - then invoke original rust code +/// +/// This leverages TryIntoJs trait +/// +pub fn generate_rust_invocation(ctx: &FnGeneratorCtx,cb_args: &mut CbArgs) -> TokenStream { + + // code to convert extract rust values from Js Env + let js_to_rust_values = arg_extraction::as_arg_token(ctx); + + let rust_invoke = invocation::rust_invocation(ctx,cb_args); + + // if this is async, wrap with JsFuture + let rust_invoke_ft_wrapper = if ctx.is_async() { + + + if ctx.has_default_output() { + + // since this doesn't have any output, we don't need return promise, we just + // spawn async and return null ptr + quote! { + + node_bindgen::core::future::spawn(async move { + #rust_invoke.await; + }); + + Ok(std::ptr::null_mut()) + } + + } else { + + let async_name = format!("{}_ft", ctx.fn_name()); + let async_lit = LitStr::new(&async_name, Span::call_site()); + quote! { + (node_bindgen::core::JsPromiseFuture::new( + #rust_invoke,#async_lit + )).try_to_js(&js_env) + } + } + + + } else { + quote! { + #rust_invoke.try_to_js(&js_env) + } + + }; + + let receiver = if ctx.is_method() { + quote! { + let receiver = (js_cb.unwrap_mut::()?); + } + } else { + quote! {} + }; + + + quote! { + + let result: Result = ( move || { + + #js_to_rust_values + + #receiver + + #rust_invoke_ft_wrapper + + })(); + + + result.to_js(&js_env) + } +} + + +/// generate rust value from js cb context +/// +/// let js_cb = js_env.get_cb_info(cb_info, 2)?; +/// let rust_value_0 = js_cb.get_value::(0)?; +/// let rust_value_1 = js_cb.get_value::(1)?; +mod arg_extraction { + + use super::*; + use crate::ast::ClosureType; + + pub fn as_arg_token(ctx: &FnGeneratorCtx) -> TokenStream { + + let count_literal = ctx.args.len().to_string(); + let js_count = LitInt::new(&count_literal, Span::call_site()); + + //println!("fc arguments: {:#?}",ctx.args); + + let rust_args = ctx + .args + .inner() + .iter() + .enumerate() + .map(|(i, arg)| js_to_rust_token_stream(i, arg,ctx)); + + quote! { + + let mut js_cb = js_env.get_cb_info(cb_info, #js_count)?; + + #(#rust_args)* + + } + } + + + + /// generate expression to extract rust value from Js env + /// example as below: + /// let r_arg1 = cb.get_value::(1)?; + /// + fn js_to_rust_token_stream(arg_index: usize, arg: &FunctionArg, ctx: &FnGeneratorCtx) -> TokenStream { + + // println!("js to rust arg: {}, {:#?}",arg_index,arg.typ); + match &arg.typ { + FunctionArgType::Closure(ty) => { + if ctx.is_async() || ctx.attributes.is_multi_threaded() { + generate_as_async_token_stream(ty,arg_index,ctx) + } else { + rust_value(ctx.callback_type_name(), arg_index) + } + } + FunctionArgType::Path(ty) => rust_value(ty.expansion(), arg_index), + FunctionArgType::Ref(ty) => rust_ref(ty.type_name(), arg_index), + /* + FunctionArgType::JSCallback(_ty) => { + let rust_value = rust_arg_var(arg_index); + quote! { + let #rust_value = &js_cb; + } + } + */ + + } + } + + /// generate thread safe function when callback are used in the async + /// for example: + /// let r_arg1 = cb.create_thread_safe_function("hello_sf",0,Some(hello_callback_js))?; + fn generate_as_async_token_stream(ty: &ClosureType,index: usize,ctx: &FnGeneratorCtx) -> TokenStream { + + let sf_identifier = LitStr::new(&format!("{}_sf", ctx.fn_name()), Span::call_site()); + let rust_var_name = rust_arg_var(index); + let js_cb_completion = ty.async_js_callback_identifier(); + + quote! { + let #rust_var_name = js_cb.create_thread_safe_function(#sf_identifier,Some(#js_cb_completion))?; + } + + } +} + +/// generating code to invoke rust function +mod invocation { + + use super::*; + + /// generate code to invoke rust function inside js wrapper + /// for example + /// sum(rust_value_0, rust_value_1).try_to_js(&js_env) + /// + pub fn rust_invocation(ctx: &FnGeneratorCtx,cb_args: &mut CbArgs) -> TokenStream { + + let rust_args_input: Vec = rust_args_input(ctx,cb_args); + let rust_fn_ident = ctx.fn_name(); + + if ctx.is_method() { + quote! { + receiver.#rust_fn_ident( #(#rust_args_input),* ) + } + } else { + quote! { + #rust_fn_ident( #(#rust_args_input),* ) + } + } + + } + +} + + +mod closure { + + use super::*; + + use crate::ast::ClosureType; + + /// for closure, we need create wrapper closure that translates inner closure + /// for example, if we have following rust closure + /// + /// fn rust_fn(cb: F) + /// + /// we need to invoke in the bindgen code as below + /// let js_cb = js_env.get_cb_info(cb_info,1)?; + /// let rust_value_0 = js_cb.get_value::(0)?; + /// + /// rust_fn( move |cb_arg0: i32| { + /// let result = (|| { + /// let args = vec![cb_arg0]; + /// rust_value_0.call(args) + /// })(); + /// result.to_js(&js_env) + /// }).try_to_js(&js_env) + /// + pub fn generate_closure_invocation(closure: &ClosureType, arg_index: usize, closure_var: &Ident,ctx: &FnGeneratorCtx,cb_args: &mut CbArgs) -> TokenStream { + + + let args: Vec = closure + .inputs + .iter() + .enumerate() + .map(|(index, ty)| { + + let arg_name = format!("cb_arg{}", index); + let var_name = ident(&arg_name); + let type_name = ty.expansion(); + quote! { + #var_name: #type_name + } + + }) + .collect(); + + let inner_closure = if ctx.is_async() || ctx.attributes().is_multi_threaded() { + as_async_arg_token_stream(closure,closure_var,ctx,cb_args) + } else { + as_sync_arg_token_stream(closure,arg_index,closure_var) + }; + + quote! { + move | #(#args),* | { + + #inner_closure + } + } + + } + + /// generate as argument to sync rust function or method + /// since this is closure, we generate closure + fn as_sync_arg_token_stream(closure: &ClosureType, _i: usize, closure_var: &Ident) -> TokenStream { + + let js_conversions: Vec = closure + .inputs + .iter() + .enumerate() + .map(|(index, _path)| { + let arg_name = format!("cb_arg{}", index); + let var_name = Ident::new(&arg_name, Span::call_site()); + quote! { + #var_name + } + }) + .collect(); + + quote! { + + // invoke sync closure + let result = (|| { + let args = vec![ + #(#js_conversions),* + ]; + #closure_var.call(args) + })(); + + result.to_js(&js_env); + + } + } + + fn as_async_arg_token_stream( + closure: &ClosureType, + closure_var: &Ident, + _ctx: &FnGeneratorCtx, + cb_args: &mut CbArgs + ) -> TokenStream { + + let arg_struct_name = Ident::new(&format!("Arg{}",closure.ident),Span::call_site()); + let arg_cb_complete = closure.async_js_callback_identifier(); + let struct_fields: Vec = closure + .inputs + .iter() + .enumerate() + .map(|(index, ty)| { + let var_name = Ident::new(&format!("arg{}", index), Span::call_site()); + let type_name = ty.expansion(); + quote! { + #var_name: #type_name + } + }) + .collect(); + + + let js_complete_conversions: Vec = closure + .inputs + .iter() + .enumerate() + .map(|(index, _path)| { + let js_var_iden = Ident::new(&format!("js_arg{}",index),Span::call_site()); + let arg_idn = Ident::new(&format!("arg{}",index),Span::call_site()); + quote !{ + let #js_var_iden = my_val.#arg_idn.try_to_js(&js_env)?; + } + }) + .collect(); + + let js_call: Vec = closure + .inputs + .iter() + .enumerate() + .map(|(index, _path)| { + let js_var_iden = Ident::new(&format!("js_arg{}",index),Span::call_site()); + quote !{ + #js_var_iden + } + }) + .collect(); + + cb_args.push(quote!{ + + #[derive(Debug)] + struct #arg_struct_name { + #(#struct_fields),* + } + + extern "C" fn #arg_cb_complete( + env: node_bindgen::sys::napi_env, + js_cb: node_bindgen::sys::napi_value, + _context: *mut ::std::os::raw::c_void, + data: *mut ::std::os::raw::c_void) { + + if env != std::ptr::null_mut() { + + node_bindgen::core::log::debug!("async cb invoked"); + let js_env = node_bindgen::core::val::JsEnv::new(env); + + let result: Result<(), node_bindgen::core::NjError> = (move ||{ + + let global = js_env.get_global()?; + let my_val: Box<#arg_struct_name> = unsafe { Box::from_raw(data as *mut #arg_struct_name) }; + node_bindgen::core::log::trace!("arg: {:#?}",my_val); + #(#js_complete_conversions)* + + node_bindgen::core::log::debug!("async cb, invoking js cb"); + js_env.call_function(global,js_cb,vec![#(#js_call),*])?; + node_bindgen::core::log::trace!("async cb, done"); + Ok(()) + + })(); + + node_bindgen::core::assert_napi!(result) + + } + + } + + }); + + + let args: Vec = closure + .inputs + .iter() + .enumerate() + .map(|(index, _path)| { + let arg_name = Ident::new(&format!("arg{}",index),Span::call_site()); + let cb_name: Ident = Ident::new(&format!("cb_arg{}",index),Span::call_site()); + quote! { + #arg_name: #cb_name + } + }) + .collect(); + + quote! { + + let arg = #arg_struct_name { + #(#args),* + }; + + node_bindgen::core::log::trace!("converting rust to raw ptr"); + let my_box = Box::new(arg); + let ptr = Box::into_raw(my_box); + + #closure_var.call(Some(ptr as *mut core::ffi::c_void)).expect("callback should work"); + + } + + } + + +} + + + +/// generate expression to convert napi_value as rust ref +fn rust_ref(type_name: &Ident, index: usize) -> TokenStream { + + let rust_value = rust_arg_var(index); + quote! { + let #rust_value = js_cb.get_ref::<#type_name>(&mut arg_index)?; + } + +} + +// generate expression to convert napi value to rust value from callback +fn rust_value(type_name: TokenStream, index: usize) -> TokenStream { + + let rust_value = rust_arg_var(index); + quote! { + let #rust_value = js_cb.get_value::<#type_name>()?; + } + +} + +/// generate rust_value with index +fn generate_rust_arg_var(index: usize) -> TokenStream { + + let var_name = rust_arg_var(index); + + quote! { + #var_name + } +} + +/// given index, generate rust_value var name +fn rust_arg_var(index: usize) -> Ident { + let var_name = format!("rust_value_{}", index); + Ident::new(&var_name, Span::call_site()) +} + +mod args_input { + + use super::*; + + pub fn rust_args_input(ctx: &FnGeneratorCtx,cb_args: &mut CbArgs) -> Vec { + let mut arg_index = 0; + ctx.args + .args + .iter() + .map(|arg| as_arg_token_stream(&mut arg_index,arg,ctx,cb_args)) + .collect() + } + + // generate code as part of invoking rust function + /// so given rust function rust_fn + /// it will pass parameter like rust_fn(rust_value_0,rust_value_1,...) + /// only special case is closure where we pass closure + fn as_arg_token_stream(index: &mut usize,arg: &FunctionArg,ctx: &FnGeneratorCtx,cb_args: &mut CbArgs) -> TokenStream { + + + match &arg.typ { + + FunctionArgType::Closure(t) => { + let arg_name = rust_arg_var(*index); + let output = generate_closure_invocation(t,*index,&arg_name,ctx,cb_args); + //println!("closure: {}",output); + *index = *index + 1; + output + }, + _ => { + let output = generate_rust_arg_var(*index); + *index = *index + 1; + output + } + } + } +} \ No newline at end of file diff --git a/nj-derive/src/generator/mod.rs b/nj-derive/src/generator/mod.rs new file mode 100644 index 00000000..9ac9ea19 --- /dev/null +++ b/nj-derive/src/generator/mod.rs @@ -0,0 +1,16 @@ +mod function; +mod property; +mod context; +mod napi; +mod class; + + +use property::*; +use context::*; +use napi::*; +use function::*; + + +pub use function::generate_function; +pub use class::*; + diff --git a/nj-derive/src/generator/napi.rs b/nj-derive/src/generator/napi.rs new file mode 100644 index 00000000..42511c0d --- /dev/null +++ b/nj-derive/src/generator/napi.rs @@ -0,0 +1,71 @@ +use quote::quote; +use proc_macro2::TokenStream; +use syn::Ident; +use syn::ItemFn; + +use crate::util::ident; +use super::FnGeneratorCtx; +use super::generate_rust_invocation; + +/// generate native code to be invoked by napi +pub fn generate_napi_code(ctx: &FnGeneratorCtx,input_fn: &ItemFn) -> TokenStream { + + + let mut cb_args = vec![]; + let rust_invocation = generate_rust_invocation(ctx,&mut cb_args); + let ident_n_api_fn = ident(& format!("napi_{}", ctx.fn_name())); + + if ctx.is_method() { + + // if function is method, we can't put rust function inside our napi because we need to preserver self + // in the rust method. + let napi_fn = raw_napi_function_template( + ident_n_api_fn, + quote! {}, + cb_args, + rust_invocation); + + quote! { + #input_fn + + #napi_fn + } + + } else { + + // otherwise we can put rust function inside to make it tidy + raw_napi_function_template( + ident_n_api_fn, + quote! { #input_fn }, + cb_args, + rust_invocation) + } +} + +/// generate napi function invocation whether it is method or just free standing function +fn raw_napi_function_template( + ident_n_api_fn: Ident, + input_fn: TokenStream, + rust_args_struct: Vec, + rust_invocation: TokenStream, +) -> TokenStream { + + quote! { + + extern "C" fn #ident_n_api_fn(env: node_bindgen::sys::napi_env,cb_info: node_bindgen::sys::napi_callback_info) -> node_bindgen::sys::napi_value + { + use node_bindgen::core::TryIntoJs; + use node_bindgen::core::IntoJs; + use node_bindgen::core::val::JsCallbackFunction; + + #input_fn + + #(#rust_args_struct)* + + let js_env = node_bindgen::core::val::JsEnv::new(env); + + #rust_invocation + } + } +} + diff --git a/nj-derive/src/generator/property.rs b/nj-derive/src/generator/property.rs new file mode 100644 index 00000000..face1c11 --- /dev/null +++ b/nj-derive/src/generator/property.rs @@ -0,0 +1,29 @@ +use quote::quote; +use proc_macro2::TokenStream; + +use crate::util::ident; +use super::FnGeneratorCtx; + + +/// generate code to register this function property to global property +pub fn generate_property_code(ctx: &FnGeneratorCtx) -> TokenStream { + + + if ctx.is_method() { + return quote! {}; + } + + let ident_n_api_fn = ctx.napi_fn_id(); + let ident_register_fn = ident(&format!("register_{}", ident_n_api_fn)); + let property_name_literal = ctx.property_name(); + + quote! { + #[node_bindgen::core::ctor] + fn #ident_register_fn() { + + let property = node_bindgen::core::Property::new(#property_name_literal).method(#ident_n_api_fn); + node_bindgen::core::submit_property(property); + } + + } +} diff --git a/nj-derive/src/lib.rs b/nj-derive/src/lib.rs index 96b94129..375d0ad3 100644 --- a/nj-derive/src/lib.rs +++ b/nj-derive/src/lib.rs @@ -1,20 +1,13 @@ extern crate proc_macro; -mod function; -mod class; -mod util; -mod parser; -use function::generate_function; -use function::FunctionAttribute; -use class::generate_class; -use parser::NodeItem; -use function::FunctionArgMetadata; -use function::FunctionContext; +mod util; +mod ast; +mod generator; use proc_macro::TokenStream; -use syn::AttributeArgs; + /// This turns regular rust function into N-API compatible native module /// @@ -39,12 +32,36 @@ use syn::AttributeArgs; #[proc_macro_attribute] pub fn node_bindgen(args: TokenStream, item: TokenStream) -> TokenStream { - let parsed_item = syn::parse_macro_input!(item as NodeItem); + use syn::AttributeArgs; + + use ast::FunctionAttributes; + use ast::NodeItem; + use generator::generate_function; + use generator::generate_class; + let attribute_args = syn::parse_macro_input!(args as AttributeArgs); - let expand_expression = match parsed_item { - NodeItem::Function(fn_item) => generate_function(fn_item,attribute_args), - NodeItem::Impl(impl_item) => generate_class(impl_item) + + let attribute: FunctionAttributes = + match FunctionAttributes::from_ast(attribute_args) { + Ok(attr) => attr, + Err(err) => return err.to_compile_error().into() + }; + + let parsed_item = syn::parse_macro_input!(item as NodeItem); + + let out_express = match parsed_item { + NodeItem::Function(fn_item) => { + generate_function(fn_item,attribute) + } + NodeItem::Impl(impl_item) => { + generate_class(impl_item) + } }; - expand_expression.into() + + // used for debugging, if error occurs println do not work so should uncomment express + //println!("{}",out_express); + //let out_express = quote::quote! {}; + + out_express.into() } diff --git a/nj-derive/src/util.rs b/nj-derive/src/util.rs index 8f42b7c7..5ce11425 100644 --- a/nj-derive/src/util.rs +++ b/nj-derive/src/util.rs @@ -1,115 +1,22 @@ -use quote::quote; -use syn::Ident; -use syn::TypePath; -use syn::Type; -use syn::TypeReference; use proc_macro2::Span; -use proc_macro2::TokenStream; +use syn::LitStr; -pub struct MyTypePath(TypePath); - -impl MyTypePath { - - /// given this, convert into normalized type signature - pub fn from(ty: Box) -> Option { - - match *ty { - Type::Path(path_type) => Some(MyTypePath(path_type)), - _ => None - } - } - - pub fn new(path_type: TypePath) -> Self { - - Self(path_type) - } - - pub fn inner(&self) -> &TypePath { - &self.0 - } - - /// return possible type name - pub fn type_name(&self) -> Option { - - for segment in &self.0.path.segments { - return Some(segment.ident.clone()); - } - None - } - - /// generate code as part of invoking rust function - /// for normal argument, it is just variable - /// other may like closure may need to convert to rust closure - pub fn as_arg_token_stream(&self,index: usize) -> TokenStream { - - let var_name = rust_arg_var(index); - - quote! { - #var_name, - } - } +/// generate default property name for function which uses camel case +pub fn default_function_property_name(fn_name: &str) -> String { + use inflector::Inflector; + fn_name.to_camel_case() } - - -/// rust argument -pub fn rust_arg_var(index: usize) -> Ident { - let var_name = format!("rust_value_{}", index); - Ident::new(&var_name, Span::call_site()) +pub fn arg_ident(index: usize) -> syn::Ident { + ident(&format!("arg{}", index)) } - -pub struct MyReferenceType(TypeReference); - -impl MyReferenceType { - pub fn new(ty: TypeReference) -> Self { - Self(ty) - } - - - pub fn is_callback(&self) -> bool { - match &*self.0.elem { - Type::Path(path_type) => { - path_type.path.segments.iter().find(|segment| segment.ident == "JsCallback").is_some() - }, - _ => false - } - } - - /// generate code as part of invoking rust function - pub fn as_arg_token_stream(&self,index: usize) -> TokenStream { - - let var_name = rust_arg_var(index); - - quote! { - #var_name, - } - } - - /// return possible type name - pub fn type_name(&self) -> Option { - - match self.0.elem.as_ref() { - Type::Path(path) => { - - for segment in &path.path.segments { - return Some(segment.ident.clone()); - } - None - - }, - _ => None - } - - - } +pub fn ident(ident: &str) -> syn::Ident { + syn::Ident::new(ident,Span::call_site()) } -/// generate default property name for function which uses camel case -pub fn default_function_property_name(fn_name: &str) -> String { - use inflector::Inflector; - - fn_name.to_camel_case() -} \ No newline at end of file +pub fn lit_str(ident: &str) -> LitStr { + LitStr::new(ident,Span::call_site()) +} diff --git a/nj-derive/tests/parse.rs b/nj-derive/tests/parse.rs new file mode 100644 index 00000000..c3c690dd --- /dev/null +++ b/nj-derive/tests/parse.rs @@ -0,0 +1,7 @@ +#[test] +fn derive_ui() { + let t = trybuild::TestCases::check_only(); + + t.pass("ui-tests/pass_*.rs"); + t.compile_fail("ui-tests/fail_*.rs"); +} \ No newline at end of file diff --git a/nj-derive/ui-tests/fail_attribute_name.rs b/nj-derive/ui-tests/fail_attribute_name.rs new file mode 100644 index 00000000..d57cea7d --- /dev/null +++ b/nj-derive/ui-tests/fail_attribute_name.rs @@ -0,0 +1,11 @@ +use node_bindgen::derive::node_bindgen; + +/// name2 is not valid attribute +#[node_bindgen(name2="hello")] +fn example1(count: i32) -> String { + format!("hello world {}", count) +} + +fn main() { + +} \ No newline at end of file diff --git a/nj-derive/ui-tests/fail_attribute_number.rs b/nj-derive/ui-tests/fail_attribute_number.rs new file mode 100644 index 00000000..d3e5f54a --- /dev/null +++ b/nj-derive/ui-tests/fail_attribute_number.rs @@ -0,0 +1,13 @@ +use node_bindgen::derive::node_bindgen; + +/// name must be string +#[node_bindgen(name=20)] +fn example2(count: i32) -> i32 { + count +} + + + +fn main() { + +} \ No newline at end of file diff --git a/nj-derive/ui-tests/fail_attriubte_gibberish.rs b/nj-derive/ui-tests/fail_attriubte_gibberish.rs new file mode 100644 index 00000000..67880a01 --- /dev/null +++ b/nj-derive/ui-tests/fail_attriubte_gibberish.rs @@ -0,0 +1,10 @@ +use node_bindgen::derive::node_bindgen; + +#[node_bindgen(gibberish)] +fn example3(count: i32) -> i32 { + count +} + +fn main() { + +} \ No newline at end of file diff --git a/nj-derive/ui-tests/fail_class_attr_name.rs b/nj-derive/ui-tests/fail_class_attr_name.rs new file mode 100644 index 00000000..c5a6a412 --- /dev/null +++ b/nj-derive/ui-tests/fail_class_attr_name.rs @@ -0,0 +1,23 @@ +use node_bindgen::derive::node_bindgen; + + + +struct MyObject { + val: f64, +} + + +#[node_bindgen] +impl MyObject { + + #[node_bindgen(name2="hello")] + fn new(val: f64) -> Self { + Self { val } + } + +} + + +fn main() { + +} \ No newline at end of file diff --git a/nj-derive/ui-tests/fail_class_attr_number.rs b/nj-derive/ui-tests/fail_class_attr_number.rs new file mode 100644 index 00000000..79c65bcb --- /dev/null +++ b/nj-derive/ui-tests/fail_class_attr_number.rs @@ -0,0 +1,23 @@ +use node_bindgen::derive::node_bindgen; + + + +struct MyObject { + val: f64, +} + + +#[node_bindgen] +impl MyObject { + + #[node_bindgen(name=20)] + fn new(val: f64) -> Self { + Self { val } + } + +} + + +fn main() { + +} \ No newline at end of file diff --git a/nj-derive/ui-tests/fail_class_gibberish.rs b/nj-derive/ui-tests/fail_class_gibberish.rs new file mode 100644 index 00000000..e903f77b --- /dev/null +++ b/nj-derive/ui-tests/fail_class_gibberish.rs @@ -0,0 +1,23 @@ +use node_bindgen::derive::node_bindgen; + + + +struct MyObject { + val: f64, +} + + +#[node_bindgen] +impl MyObject { + + #[node_bindgen(xyz)] + fn new(val: f64) -> Self { + Self { val } + } + +} + + +fn main() { + +} \ No newline at end of file diff --git a/nj-derive/ui-tests/fail_non_method.rs b/nj-derive/ui-tests/fail_non_method.rs new file mode 100644 index 00000000..88582204 --- /dev/null +++ b/nj-derive/ui-tests/fail_non_method.rs @@ -0,0 +1,21 @@ +use node_bindgen::derive::node_bindgen; + + +#[node_bindgen(constructor)] +fn example2(count: i32) -> i32 { + count +} + +#[node_bindgen(getter)] +fn example3(count: i32) -> i32 { + count +} + +#[node_bindgen(setter)] +fn example4(count: i32) -> i32 { + count +} + +fn main() { + +} \ No newline at end of file diff --git a/nj-derive/ui-tests/fail_structure.rs b/nj-derive/ui-tests/fail_structure.rs new file mode 100644 index 00000000..b3fb7b7b --- /dev/null +++ b/nj-derive/ui-tests/fail_structure.rs @@ -0,0 +1,9 @@ +use node_bindgen::derive::node_bindgen; + + +#[node_bindgen] +struct Example4{} + +fn main() { + +} \ No newline at end of file diff --git a/nj-derive/ui-tests/pass_async.rs b/nj-derive/ui-tests/pass_async.rs new file mode 100644 index 00000000..2d49daf9 --- /dev/null +++ b/nj-derive/ui-tests/pass_async.rs @@ -0,0 +1,18 @@ +use node_bindgen::derive::node_bindgen; + + +/// async callback +#[node_bindgen] +async fn example5( seconds: i32, cb: F) { +} + + +#[node_bindgen] +async fn example6(arg: f64) -> f64 { + 0.0 +} + + + +fn main() { +} \ No newline at end of file diff --git a/nj-derive/ui-tests/pass_callback.rs b/nj-derive/ui-tests/pass_callback.rs new file mode 100644 index 00000000..45a65a92 --- /dev/null +++ b/nj-derive/ui-tests/pass_callback.rs @@ -0,0 +1,16 @@ +use node_bindgen::derive::node_bindgen; + + +#[node_bindgen] +fn example(cb: F,second: i32) { + cb(second); +} + +fn example2(first: i32,cb: F) { + cb(format!("hello world: {}",first),first as i64); +} + + + +fn main() { +} \ No newline at end of file diff --git a/nj-derive/ui-tests/pass_class_async.rs b/nj-derive/ui-tests/pass_class_async.rs new file mode 100644 index 00000000..9017de28 --- /dev/null +++ b/nj-derive/ui-tests/pass_class_async.rs @@ -0,0 +1,39 @@ +use std::time::Duration; +use std::io::Error as IoError; + +use flv_future_aio::timer::sleep; + +use node_bindgen::derive::node_bindgen; + + +struct MyObject { + val: f64, +} + + +#[node_bindgen] +impl MyObject { + + + #[node_bindgen(constructor)] + fn new(val: f64) -> Self { + Self { val } + } + + /// loop and emit event + #[node_bindgen] + async fn sleep(&self,cb: F) { + + println!("sleeping"); + sleep(Duration::from_secs(1)).await; + let msg = format!("hello world"); + cb(msg); + + } + + +} + +fn main() { + +} \ No newline at end of file diff --git a/nj-derive/ui-tests/pass_class_simple.rs b/nj-derive/ui-tests/pass_class_simple.rs new file mode 100644 index 00000000..88ff2f06 --- /dev/null +++ b/nj-derive/ui-tests/pass_class_simple.rs @@ -0,0 +1,39 @@ +use node_bindgen::derive::node_bindgen; + + +struct MyObject { + val: f64, +} + + +#[node_bindgen] +impl MyObject { + + + #[node_bindgen(constructor)] + fn new(val: f64) -> Self { + Self { val } + } + + #[node_bindgen] + fn twice(&self) -> f64 { + self.val * 2.0 + } + + + #[node_bindgen(getter)] + fn value(&self) -> f64 { + self.val + } + + + #[node_bindgen(name = "value2",getter)] + fn set_value(&mut self, val: f64) { + self.val = val; + } + +} + +fn main() { + +} \ No newline at end of file diff --git a/nj-derive/ui-tests/pass_function.rs b/nj-derive/ui-tests/pass_function.rs new file mode 100644 index 00000000..760ed475 --- /dev/null +++ b/nj-derive/ui-tests/pass_function.rs @@ -0,0 +1,32 @@ +use node_bindgen::derive::node_bindgen; + + +/// no argument and no result +#[node_bindgen] +fn example1() { +} + + +/// single argument with result +#[node_bindgen] +fn example2(arg1: i32) -> i32 { + arg1 +} + +/// multiple arguments +#[node_bindgen] +fn example3(_arg1: bool,_arg2: i32,_arg3: String) -> i32 { + 4 +} + + +#[node_bindgen(name="hello2")] +fn example4(count: i32) -> i32 { + count +} + + + +fn main() { + +} \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..ad07abc8 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,2 @@ +reorder_modules = false +reorder_imports = false \ No newline at end of file