diff --git a/README.md b/README.md index 95cd663..87cb5b9 100644 --- a/README.md +++ b/README.md @@ -283,6 +283,22 @@ context.eval("a = 2") # nothing works on the context from now on, its a shell waiting to be disposed ``` +### Function call + +This calls the function passed as first argument: + +```ruby +context = MiniRacer::Context.new +context.eval('function hello(name) { return "Hello, " + name + "!" }') +context.function_call('hello', 'George') +# "Hello, George!" +``` + +Performance is slightly better than running `eval('hello("George")')` since: + + - compilation of eval'd string is avoided + - function arguments don't need to be converted to JSON + ## Performance The `bench` folder contains benchmark. diff --git a/ext/mini_racer_extension/mini_racer_extension.cc b/ext/mini_racer_extension/mini_racer_extension.cc index 58dddb9..b9963a4 100644 --- a/ext/mini_racer_extension/mini_racer_extension.cc +++ b/ext/mini_racer_extension/mini_racer_extension.cc @@ -1079,6 +1079,93 @@ rb_context_dispose(VALUE self) { return Qnil; } +#if 0 +static VALUE rb_function_get(VALUE self, VALUE function_name) { + ContextInfo* context_info; + Data_Get_Struct(self, ContextInfo, context_info); + + if (TYPE(function_name) != T_STRING) { + rb_raise(rb_eTypeError, "function_name should be a String"); + } + + char *fname = RSTRING_PTR(function_name); + if (!fname) { + return Qnil; + } + + Isolate* isolate = context_info->isolate_info->isolate; + + Locker lock(isolate); + Isolate::Scope isolate_scope(isolate); + HandleScope handle_scope(isolate); + TryCatch trycatch(isolate); + Local context = context_info->context->Get(isolate); + Context::Scope context_scope(context); + + Local fun = v8::Local::Cast(context->Global()->Get( + String::NewFromUtf8(isolate, fname))); + + return convert_v8_to_ruby(isolate, res); +} +#endif + +static VALUE +// rb_function_call(VALUE self, VALUE function_name, VALUE *args) { +rb_function_call(int argc, VALUE *argv, VALUE self) { + + ContextInfo* context_info; + Data_Get_Struct(self, ContextInfo, context_info); + + if (argc < 1) { + rb_raise(rb_eArgError, "need at least one argument %d", argc); + } + + VALUE function_name = argv[0]; + if (TYPE(function_name) != T_STRING) { + rb_raise(rb_eTypeError, "first arg should be a String"); + } + + char *fname = RSTRING_PTR(function_name); + if (!fname) { + return Qnil; + } + + Isolate* isolate = context_info->isolate_info->isolate; + + Locker lock(isolate); + Isolate::Scope isolate_scope(isolate); + HandleScope handle_scope(isolate); + TryCatch trycatch(isolate); + Local context = context_info->context->Get(isolate); + Context::Scope context_scope(context); + + // examples of such usage can be found in + // https://github.com/v8/v8/blob/36b32aa28db5e993312f4588d60aad5c8330c8a5/test/cctest/test-api.cc#L15711 + + Local fun = v8::Local::Cast(context->Global()->Get( + String::NewFromUtf8(isolate, fname))); + + int fun_argc = argc - 1; + + v8::Local *fun_args = NULL; + + if (fun_argc > 0) { + fun_args = (v8::Local *) malloc(sizeof(void *) * fun_argc); + if (!fun_args) { + return Qnil; + } + for(int i=0; i < fun_argc; i++) { + fun_args[i] = convert_ruby_to_v8(isolate, argv[i+1]); + } + } + + Local res = fun->Call(context, context->Global(), argc - 1, fun_args).ToLocalChecked(); + + free(fun_args); + + return convert_v8_to_ruby(isolate, res); +} + extern "C" { void Init_mini_racer_extension ( void ) @@ -1108,6 +1195,7 @@ extern "C" { rb_define_method(rb_cContext, "stop", (VALUE(*)(...))&rb_context_stop, 0); rb_define_method(rb_cContext, "dispose_unsafe", (VALUE(*)(...))&rb_context_dispose, 0); rb_define_method(rb_cContext, "heap_stats", (VALUE(*)(...))&rb_heap_stats, 0); + rb_define_method(rb_cContext, "function_call", (VALUE(*)(...))&rb_function_call, -1); rb_define_alloc_func(rb_cContext, allocate); rb_define_alloc_func(rb_cSnapshot, allocate_snapshot); diff --git a/test/test_function.rb b/test/test_function.rb new file mode 100644 index 0000000..394e51c --- /dev/null +++ b/test/test_function.rb @@ -0,0 +1,27 @@ +require 'test_helper' + +class MiniRacerFunctionTest < Minitest::Test + + def test_fun + context = MiniRacer::Context.new + context.eval("function f(x) { return 'I need ' + x + ' galettes' }") + assert_equal context.eval('f(10)'), "I need 10 galettes" + + assert_raises(ArgumentError) do + context.function_call + end + + count = 4 + res = context.function_call('f', count) + assert_equal res, "I need #{count} galettes" + end + + def test_fun2 + context = MiniRacer::Context.new + context.eval("function f(x, y) { return 'I need ' + x + ' ' + y }") + + res = context.function_call('f', 3, "dogs") + assert_equal res, "I need 3 dogs" + end +end +