diff --git a/History.txt b/History.txt index b155b6d..6825d89 100644 --- a/History.txt +++ b/History.txt @@ -1,3 +1,6 @@ +=== Edge +* 1 major enhancement + * call JavaScript functions from Ruby === 0.6.0 2010-03-31 * 4 major enhancements * ruby 1.9 compatible diff --git a/ext/v8/converters.cpp b/ext/v8/converters.cpp index dd2c3be..cd88eb4 100644 --- a/ext/v8/converters.cpp +++ b/ext/v8/converters.cpp @@ -3,6 +3,7 @@ #include "v8_ref.h" #include "v8_obj.h" #include "v8_cxt.h" +#include "v8_func.h" #include "v8_template.h" using namespace v8; @@ -16,6 +17,7 @@ VALUE V8_To; VALUE V82RB(Handle& value) { convert_v8_to_rb_t convert; VALUE result; + VALUE type = V8_C_Object; if(convert(value, result)) { return result; } @@ -30,18 +32,21 @@ VALUE V82RB(Handle& value) { return rb_array; } + if (value->IsFunction()) { + type = V8_C_Function; + } + if (value->IsObject()) { Local object(Object::Cast(*value)); Local peer = object->GetHiddenValue(String::New("TheRubyRacer::RubyObject")); if (peer.IsEmpty()) { VALUE context_ref = V8_Ref_Create(V8_C_Context, Context::GetCurrent()); object->SetHiddenValue(String::New("TheRubyRacer::Context"), External::Wrap((void *)context_ref)); - return V8_Ref_Create(V8_C_Object, value, context_ref); + return V8_Ref_Create(type, value, context_ref); } else { return (VALUE)External::Unwrap(peer); } } - return Qnil; } diff --git a/ext/v8/v8.cpp b/ext/v8/v8.cpp index 3259b9c..5aa6598 100644 --- a/ext/v8/v8.cpp +++ b/ext/v8/v8.cpp @@ -86,5 +86,6 @@ extern "C" { V8_C_Function = rb_define_class_under(rb_mNative, "Function", V8_C_Object); + rb_define_method(V8_C_Function, "Call", (VALUE(*)(...))v8_C_Function_Call, -1); } } diff --git a/ext/v8/v8_func.cpp b/ext/v8/v8_func.cpp index c88b915..8fb685d 100644 --- a/ext/v8/v8_func.cpp +++ b/ext/v8/v8_func.cpp @@ -1,4 +1,5 @@ +#include "converters.h" #include "v8_func.h" using namespace v8; @@ -7,4 +8,21 @@ VALUE V8_C_Function; VALUE V8_Wrap_Function(Handle f) { return V8_Ref_Create(V8_C_Function, f); +} + + +VALUE v8_C_Function_Call(int argc, VALUE *argv, VALUE self) { + HandleScope handles; + VALUE recv; VALUE f_argv; + rb_scan_args(argc, argv, "1*", &recv, &f_argv); + + Local function = V8_Ref_Get(self); + Local thisObject(Object::Cast(*RB2V8(recv))); + int f_argc = argc - 1; + Local arguments[f_argc]; + for (int i = 0; i < f_argc; i++) { + arguments[i] = RB2V8(rb_ary_entry(f_argv,i)); + } + Local result = function->Call(thisObject, f_argc, arguments); + return V82RB(result); } \ No newline at end of file diff --git a/ext/v8/v8_func.h b/ext/v8/v8_func.h index e7f1379..d7992b6 100644 --- a/ext/v8/v8_func.h +++ b/ext/v8/v8_func.h @@ -8,4 +8,6 @@ extern VALUE V8_C_Function; VALUE V8_Wrap_Function(v8::Handle f); + +VALUE v8_C_Function_Call(int argc, VALUE *argv, VALUE self); #endif \ No newline at end of file diff --git a/lib/v8.rb b/lib/v8.rb index b181483..d354bce 100644 --- a/lib/v8.rb +++ b/lib/v8.rb @@ -7,5 +7,6 @@ module V8 require 'v8/to' require 'v8/context' require 'v8/object' + require 'v8/function' require 'v8/tap' end \ No newline at end of file diff --git a/lib/v8/function.rb b/lib/v8/function.rb new file mode 100644 index 0000000..ee85963 --- /dev/null +++ b/lib/v8/function.rb @@ -0,0 +1,9 @@ +module V8 + class Function < V8::Object + + def call(thisObject, *args) + To.ruby(@native.Call(To.v8(thisObject), *args.map {|a| To.v8(a)})) + end + + end +end \ No newline at end of file diff --git a/lib/v8/to.rb b/lib/v8/to.rb index 9523224..51b9a3d 100644 --- a/lib/v8/to.rb +++ b/lib/v8/to.rb @@ -4,8 +4,9 @@ module V8 class << self def ruby(value) case value - when V8::C::Object then V8::Object.new(value) - when V8::C::String then "Wonkers!" + when V8::C::Function then V8::Function.new(value) + when V8::C::Object then V8::Object.new(value) + when V8::C::String then "Wonkers!" else value end diff --git a/spec/ext/func_spec.rb b/spec/ext/func_spec.rb new file mode 100644 index 0000000..b640fad --- /dev/null +++ b/spec/ext/func_spec.rb @@ -0,0 +1,42 @@ +require "#{File.dirname(__FILE__)}/../spec_helper.rb" + +include V8 + +describe C::Function do + it "is callable" do + C::Context.new.open do |cxt| + f = cxt.eval('(function() {return "Hello World"})', ''); + f.Call(cxt.Global).should == "Hello World" + end + end + + it "receives proper argument length from ruby" do + C::Context.new.open do |cxt| + f = cxt.eval('(function() {return arguments.length})', 'eval') + f.Call(nil,1, 2, 3).should == 3 + end + end + + it "maps all arguments from ruby" do + C::Context.new.open do |cxt| + f = cxt.eval('(function(one, two, three) {return one + two + three})', 'eval') + f.Call(nil, 1,2,3).should == 6 + end + end + + it "properly maps ruby objects back and forth from arguments to return value" do + C::Context.new.open do |cxt| + Object.new.tap do |this| + f = cxt.eval('(function() {return this})', 'eval') + f.Call(this).should be(this) + end + end + end + + it "can be called outside of a context" do + C::Context.new.open do |cxt| + @f = cxt.eval('(function() {return "Call Me"})', 'eval') + end + @f.Call(nil).should == "Call Me" + end +end \ No newline at end of file