From 9409ff0f4995bf85b00185857eddc275e74c84cb Mon Sep 17 00:00:00 2001 From: Dave Herman Date: Mon, 9 May 2016 23:46:29 -0700 Subject: [PATCH] closes #60: call JS functions from Rust --- crates/neon-sys/src/fun.rs | 3 +++ crates/neon-sys/src/neon.cc | 5 +++++ crates/neon-sys/src/neon.h | 1 + src/internal/js/mod.rs | 23 +++++++++++++++++++++++ tests/lib/functions.js | 16 ++++++++++++++++ tests/native/src/js/functions.rs | 20 ++++++++++++++++++++ tests/native/src/lib.rs | 13 +++++++++---- 7 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 tests/lib/functions.js create mode 100644 tests/native/src/js/functions.rs diff --git a/crates/neon-sys/src/fun.rs b/crates/neon-sys/src/fun.rs index d69fb0649..b6bc58cc0 100644 --- a/crates/neon-sys/src/fun.rs +++ b/crates/neon-sys/src/fun.rs @@ -12,4 +12,7 @@ extern "system" { #[link_name = "NeonSys_Fun_GetKernel"] pub fn get_kernel(obj: Local) -> *mut c_void; + #[link_name = "NeonSys_Fun_Call"] + pub fn call(out: &mut Local, isolate: *mut c_void, fun: Local, this: Local, argc: i32, argv: *mut c_void) -> bool; + } diff --git a/crates/neon-sys/src/neon.cc b/crates/neon-sys/src/neon.cc index 5e39f11fd..450f0de1a 100644 --- a/crates/neon-sys/src/neon.cc +++ b/crates/neon-sys/src/neon.cc @@ -363,6 +363,11 @@ extern "C" void *NeonSys_Fun_GetKernel(v8::Local data) { return data->Value(); } +extern "C" bool NeonSys_Fun_Call(v8::Local *out, v8::Isolate *isolate, v8::Local fun, v8::Local self, int32_t argc, v8::Local argv[]) { + v8::MaybeLocal maybe_result = fun->Call(isolate->GetCurrentContext(), self, argc, argv); + return maybe_result.ToLocal(out); +} + extern "C" tag_t NeonSys_Tag_Of(v8::Local val) { return val->IsNull() ? tag_null : val->IsUndefined() ? tag_undefined diff --git a/crates/neon-sys/src/neon.h b/crates/neon-sys/src/neon.h index eee186988..ffc6313cd 100644 --- a/crates/neon-sys/src/neon.h +++ b/crates/neon-sys/src/neon.h @@ -80,6 +80,7 @@ extern "C" { bool NeonSys_Fun_New(v8::Local *out, v8::Isolate *isolate, v8::FunctionCallback callback, void *kernel); void NeonSys_Fun_ExecKernel(void *kernel, NeonSys_RootScopeCallback callback, v8::FunctionCallbackInfo *info, void *scope); void *NeonSys_Fun_GetKernel(v8::Local obj); + bool NeonSys_Fun_Call(v8::Local *out, v8::Isolate *isolate, v8::Local fun, v8::Local self, int32_t argc, v8::Local argv[]); typedef void *(*NeonSys_AllocateCallback)(const v8::FunctionCallbackInfo *info); typedef bool (*NeonSys_ConstructCallback)(const v8::FunctionCallbackInfo *info); diff --git a/src/internal/js/mod.rs b/src/internal/js/mod.rs index c5d319f8f..b0eec1567 100644 --- a/src/internal/js/mod.rs +++ b/src/internal/js/mod.rs @@ -687,6 +687,8 @@ impl Kernel<()> for FunctionKernel { } } +// Maximum number of function arguments in V8. +const V8_ARGC_LIMIT: usize = 65535; impl JsFunction { pub fn new<'a, T: Scope<'a>, U: Value>(scope: &mut T, f: fn(Call) -> JsResult) -> JsResult<'a, JsFunction> { @@ -698,6 +700,27 @@ impl JsFunction { } }) } + + pub fn call<'a, 'b, S: Scope<'a>, T, A, AS, R>(self, scope: &mut S, this: Handle<'b, T>, args: AS) -> JsResult<'a, R> + where T: Value, + A: Value + 'b, + AS: IntoIterator>, + R: Value + { + let mut v: Vec<_> = args.into_iter().collect(); + let mut slice = &mut v[..]; + let argv = slice.as_mut_ptr(); + let argc = slice.len(); + if argc > V8_ARGC_LIMIT { + return JsError::throw(Kind::RangeError, "too many arguments"); + } + build(|out| { + unsafe { + let isolate: *mut c_void = mem::transmute(scope.isolate().to_raw()); + neon_sys::fun::call(out, isolate, self.to_raw(), this.to_raw(), argc as i32, argv as *mut c_void) + } + }) + } } impl Value for JsFunction { } diff --git a/tests/lib/functions.js b/tests/lib/functions.js new file mode 100644 index 000000000..9ac00a947 --- /dev/null +++ b/tests/lib/functions.js @@ -0,0 +1,16 @@ +var addon = require('../native'); +var assert = require('chai').assert; + +describe('JsFunction', function() { + it('should return a JsFunction built in Rust', function () { + assert.isFunction(addon.return_js_function()); + }); + + it('should return a JsFunction built in Rust that implements x => x + 1', function () { + assert.equal(addon.return_js_function()(41), 42); + }); + + it('should call a JsFunction built in JS that implements x => x + 1', function () { + assert.equal(addon.call_js_function(function(x) { return x + 1 }), 17); + }); +}); diff --git a/tests/native/src/js/functions.rs b/tests/native/src/js/functions.rs new file mode 100644 index 000000000..f44cea0be --- /dev/null +++ b/tests/native/src/js/functions.rs @@ -0,0 +1,20 @@ +use neon::vm::{Call, JsResult}; +use neon::mem::Handle; +use neon::js::{JsNumber, JsNull, JsFunction}; + +fn add1(call: Call) -> JsResult { + let scope = call.scope; + let x = try!(try!(call.arguments.require(scope, 0)).check::()).value(); + Ok(JsNumber::new(scope, x + 1.0)) +} + +pub fn return_js_function(call: Call) -> JsResult { + JsFunction::new(call.scope, add1) +} + +pub fn call_js_function(call: Call) -> JsResult { + let scope = call.scope; + let f = try!(try!(call.arguments.require(scope, 0)).check::()); + let args: Vec> = vec![JsNumber::new(scope, 16.0)]; + f.call(scope, JsNull::new(), args) +} diff --git a/tests/native/src/lib.rs b/tests/native/src/lib.rs index faca430bf..caa82be2d 100644 --- a/tests/native/src/lib.rs +++ b/tests/native/src/lib.rs @@ -2,16 +2,18 @@ extern crate neon; mod js { - pub mod strings; - pub mod integers; - pub mod arrays; - pub mod objects; + pub mod strings; + pub mod integers; + pub mod arrays; + pub mod objects; + pub mod functions; } use js::strings::return_js_string; use js::integers::return_js_integer; use js::arrays::*; use js::objects::*; +use js::functions::*; register_module!(m, { try!(m.export("return_js_string", return_js_string)); @@ -25,6 +27,9 @@ register_module!(m, { try!(m.export("return_js_object", return_js_object)); try!(m.export("return_js_object_with_integer", return_js_object_with_integer)); try!(m.export("return_js_object_with_string", return_js_object_with_string)); + + try!(m.export("return_js_function", return_js_function)); + try!(m.export("call_js_function", call_js_function)); Ok(()) });