diff --git a/ext/v8/callbacks.cpp b/ext/v8/callbacks.cpp index 3a4fd74..c0a86d6 100644 --- a/ext/v8/callbacks.cpp +++ b/ext/v8/callbacks.cpp @@ -5,6 +5,39 @@ using namespace v8; +namespace { + VALUE unwrap(const AccessorInfo& info) { + return (VALUE)External::Unwrap(info.Data()); + } + + Local TO_ARRAY(Arguments& args) { + Local array = Array::New(args.Length()); + for (int i = 0; i < args.Length(); i++) { + array->Set(Integer::New(i), args[i]); + } + } + + Local Racer_Call_Ruby_Method(VALUE object, VALUE method, Local args) { + VALUE * arguments = new VALUE[args->Length()]; + for (unsigned int i = 0; i < args->Length(); i++) { + Handle val = args->Get(Integer::New(i)); + arguments[i] = V82RB(val); + } + VALUE result = rb_funcall2(object, rb_to_id(method), args->Length(), arguments); + Local converted = RB2V8(result); + return converted; + } + + Local Racer_Access_Ruby_Property(VALUE object, VALUE name) { + VALUE method = rb_obj_method(object, name); + if (FIX2INT(rb_funcall(method, rb_intern("arity"), 0)) == 0) { + return Racer_Call_Ruby_Method(object, name, Array::New(0)); + } else { + return RB2V8(method); + } + } +} + Handle RacerRubyInvocationCallback(const Arguments& args) { VALUE code = (VALUE)External::Unwrap(args.Data()); if (NIL_P(code)) { @@ -22,4 +55,98 @@ Handle RacerRubyInvocationCallback(const Arguments& args) { Handle convertedResult = RB2V8(result); return convertedResult ; } -} \ No newline at end of file +} + + + + +/** + * NamedProperty[Getter|Setter] are used as interceptors on object. + * See ObjectTemplate::SetNamedPropertyHandler. + */ + +Handle RacerRubyNamedPropertyGetter(Local property, const AccessorInfo& info) { + // printf("Getter '%s'
", *String::AsciiValue(property)); + if (property->Length() == 0) { + return Handle(); + } + VALUE object = unwrap(info); + VALUE camel_name = V82RB((Local&)property); + VALUE perl_name = rb_funcall(V8_To, rb_intern("perl_case"), 1, camel_name); + VALUE methods = rb_funcall(object, rb_intern("public_methods"), 1, Qfalse); + + if (RTEST(rb_ary_includes(methods, perl_name))) { + return Racer_Access_Ruby_Property(object, perl_name); + } + if (RTEST(rb_ary_includes(methods, camel_name))) { + return Racer_Access_Ruby_Property(object, camel_name); + } + return Handle(); +} + +/** + * Returns the value if the setter intercepts the request. + * Otherwise, returns an empty handle. + */ +Handle RacerRubyNamedPropertySetter(Local property, Local value, const AccessorInfo& info) { + if (property->Length() == 0) { + return Handle(); + } + // printf("Setter: '%s'
", *String::AsciiValue(property)); + std::string setter = V82String((Handle&)property); + setter += "="; + Local setter_name = String::New(setter.c_str()); + VALUE object = unwrap(info); + VALUE camel_name = V82RB((Local&)setter_name); + VALUE perl_name = rb_funcall(V8_To, rb_intern("perl_case"), 1, camel_name); + VALUE methods = rb_funcall(object, rb_intern("public_methods"), 1, Qfalse); + Local args = Array::New(1); + args->Set(Integer::New(0), value); + if (RTEST(rb_ary_includes(methods, perl_name))) { + Racer_Call_Ruby_Method(object, perl_name, args); + return value; + } + if (RTEST(rb_ary_includes(methods, camel_name))) { + Racer_Call_Ruby_Method(object, camel_name, args); + return value; + } + return Handle(); +} + +/** + * Returns a non-empty handle if the interceptor intercepts the request. + * The result is true if the property exists and false otherwise. + */ +Handle RacerRubyNamedPropertyQuery(Local property, const AccessorInfo& info) { + printf("Query: '%s'
", *String::AsciiValue(property)); + if (property->Length() == 0) { + return False(); + } + VALUE object = unwrap(info); + VALUE methods = rb_funcall(object, rb_intern("public_methods"), 1, Qfalse); + VALUE attr_name = V82RB((Local&)property); + VALUE perl_name = rb_funcall(V8_To, rb_intern("perl_case"), 1, attr_name); + + if (RTEST(rb_ary_includes(methods, attr_name)) || RTEST(rb_ary_includes(methods, perl_name))) { + return True(); + } else { + return Handle(); + } +} + +/** + * Returns a non-empty handle if the deleter intercepts the request. + * The return value is true if the property could be deleted and false + * otherwise. + */ +Handle RacerRubyNamedPropertyDeleter(Local property, const AccessorInfo& info) { + return False(); +} + +/** + * Returns an array containing the names of the properties the named + * property getter intercepts. + */ +Handle RacerRubyNamedPropertyEnumerator(const AccessorInfo& info) { + return Local(); +} diff --git a/ext/v8/callbacks.h b/ext/v8/callbacks.h index 574b0a1..ca3b55e 100644 --- a/ext/v8/callbacks.h +++ b/ext/v8/callbacks.h @@ -5,4 +5,10 @@ v8::Handle RacerRubyInvocationCallback(const v8::Arguments& args); +v8::Handle RacerRubyNamedPropertyGetter(v8::Local property, const v8::AccessorInfo& info); +v8::Handle RacerRubyNamedPropertySetter(v8::Local property, v8::Local value, const v8::AccessorInfo& info); +v8::Handle RacerRubyNamedPropertyQuery(v8::Local property, const v8::AccessorInfo& info); +v8::Handle RacerRubyNamedPropertyDeleter(v8::Local property, const v8::AccessorInfo& info); +v8::Handle RacerRubyNamedPropertyEnumerator(const v8::AccessorInfo& info); + #endif /* end of include guard: CALLBACKS_H_8VK3LWBG */ diff --git a/ext/v8/converters.cpp b/ext/v8/converters.cpp index d1f23c4..eab0f08 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_template.h" using namespace v8; @@ -32,8 +33,10 @@ VALUE V82RB(Handle& value) { if (value->IsObject()) { Local object(Object::Cast(*value)); Local peer = object->GetHiddenValue(String::New("TheRubyRacer::RubyObject")); + // Local peer = object->GetInternalField(0); if (peer.IsEmpty()) { VALUE context_ref = V8_Ref_Create(V8_C_Context, Context::GetCurrent()); + // object->SetPointerInInternalField(1, (void *)context_ref); object->SetHiddenValue(String::New("TheRubyRacer::Context"), External::Wrap((void *)context_ref)); return V8_Ref_Create(V8_C_Object, value, context_ref); } else { @@ -55,10 +58,10 @@ Local RB2V8(VALUE value) { if (convert(value, result)) { return result; } - Local tmpl = RB_VALUE_2_V8_ObjectTemplate(value); - Local object = tmpl->NewInstance(); - object->SetHiddenValue(String::New("TheRubyRacer::RubyObject"), External::Wrap((void *)value)); - return object; + Local o = Racer_Create_V8_ObjectTemplate(value)->NewInstance(); + o->SetHiddenValue(String::New("TheRubyRacer::RubyObject"), External::Wrap((void *) value)); + // o->SetPointerInInternalField(0, (void*)value); + return o; } std::string V82String(Handle& value) { @@ -77,21 +80,4 @@ std::string V82String(Handle& value) { } return UNDEFINED_STR; -} - -Local RB_VALUE_2_V8_ObjectTemplate(VALUE value) { - Local tmpl = ObjectTemplate::New(); - VALUE methods = rb_funcall(value, rb_intern("public_methods"), 1, Qfalse); - int len = RARRAY_LEN(methods); - for (int i = 0; i < len; i++) { - VALUE method_name = RARRAY_PTR(methods)[i]; - VALUE camel_method_name = rb_funcall(V8_To, rb_intern("camelcase"), 1, method_name); - VALUE method = rb_funcall(value, rb_intern("method"), 1, method_name); - Local keystr = (String *)*RB2V8(method_name); - Local camelstr = (String *)*RB2V8(camel_method_name); - Local fun = FunctionTemplate::New(RacerRubyInvocationCallback, External::Wrap((void *)method)); - tmpl->Set(keystr, fun); - tmpl->Set(camelstr, fun); - } - return tmpl; -} +} \ No newline at end of file diff --git a/ext/v8/converters.h b/ext/v8/converters.h index 3485e44..f43df98 100644 --- a/ext/v8/converters.h +++ b/ext/v8/converters.h @@ -20,6 +20,4 @@ v8::Local RB2V8(VALUE value); std::string RB2String(VALUE value); std::string V82String(v8::Handle& value); -v8::Local RB_VALUE_2_V8_ObjectTemplate(VALUE value); - #endif \ No newline at end of file diff --git a/ext/v8/v8_cxt.cpp b/ext/v8/v8_cxt.cpp index 88aaa98..869dd2e 100644 --- a/ext/v8/v8_cxt.cpp +++ b/ext/v8/v8_cxt.cpp @@ -1,5 +1,6 @@ #include "v8_cxt.h" #include "v8_msg.h" +#include "v8_template.h" #include "converters.h" using namespace v8; @@ -16,9 +17,10 @@ VALUE v8_Context_New(int argc, VALUE *argv, VALUE self) { if (NIL_P(scope)) { return V8_Ref_Create(self, Context::New()); } else { - Persistent context = Context::New(0, RB_VALUE_2_V8_ObjectTemplate(scope)); + Persistent context = Context::New(0, Racer_Create_V8_ObjectTemplate(scope)); Context::Scope enter(context); context->Global()->SetHiddenValue(String::New("TheRubyRacer::RubyObject"), External::Wrap((void *)scope)); + // context->Global()->SetPointerInInternalField(0, (void*)scope); VALUE ref = V8_Ref_Create(self, context, scope); context.Dispose(); return ref; diff --git a/ext/v8/v8_obj.cpp b/ext/v8/v8_obj.cpp index 8dd0bbf..49b878a 100644 --- a/ext/v8/v8_obj.cpp +++ b/ext/v8/v8_obj.cpp @@ -47,5 +47,6 @@ VALUE v8_Object_context(VALUE self) { HandleScope handles; Local object = unwrap(self); Local cxt = object->GetHiddenValue(String::New("TheRubyRacer::Context")); + // Local cxt = object->GetInternalField(1); return cxt.IsEmpty() ? Qnil : (VALUE)External::Unwrap(cxt); } diff --git a/ext/v8/v8_template.cpp b/ext/v8/v8_template.cpp index 39aa538..31b8e8e 100644 --- a/ext/v8/v8_template.cpp +++ b/ext/v8/v8_template.cpp @@ -7,6 +7,22 @@ #include "callbacks.h" using namespace v8; + +Local Racer_Create_V8_ObjectTemplate(VALUE value) { + Local tmpl = ObjectTemplate::New(); + // tmpl->SetInternalFieldCount(2); + tmpl->SetNamedPropertyHandler( + RacerRubyNamedPropertyGetter, + RacerRubyNamedPropertySetter, + 0, // RacerRubyNamedPropertyQuery, + 0, // RacerRubyNamedPropertyDeleter, + 0, // RacerRubyNamedPropertyEnumerator, + External::Wrap((void *)value) + ); + return tmpl; +} + + VALUE v8_Template_Set(VALUE self, VALUE name, VALUE value) { HandleScope handles; diff --git a/ext/v8/v8_template.h b/ext/v8/v8_template.h index d05408e..68060b3 100644 --- a/ext/v8/v8_template.h +++ b/ext/v8/v8_template.h @@ -1,6 +1,8 @@ #ifndef _RUBY_V8_TEMPLATE_ #define _RUBY_V8_TEMPLATE_ +v8::Local Racer_Create_V8_ObjectTemplate(VALUE object); + VALUE v8_Template_Set(VALUE self, VALUE name, VALUE value); VALUE v8_ObjectTemplate_New(VALUE clazz); diff --git a/lib/v8/context.rb b/lib/v8/context.rb index 6a98ccc..972c736 100644 --- a/lib/v8/context.rb +++ b/lib/v8/context.rb @@ -22,7 +22,7 @@ module V8 if IO === javascript || StringIO === javascript javascript = javascript.read() end - @native.open do + @native.open do @native.eval(javascript).tap do |result| raise JavascriptError.new(result) if result.kind_of?(C::Message) return To.ruby(result) diff --git a/lib/v8/to.rb b/lib/v8/to.rb index bd44012..9e2e167 100644 --- a/lib/v8/to.rb +++ b/lib/v8/to.rb @@ -23,7 +23,11 @@ module V8 def camelcase(str) str.to_s.gsub(/_(\w)/) {$1.upcase} - end + end + + def perl_case(str) + str.gsub(/([A-Z])([a-z])/) {"_#{$1.downcase}#{$2}"}.downcase + end end end end \ No newline at end of file diff --git a/spec/redjs b/spec/redjs index 0acbbe9..26e3054 160000 --- a/spec/redjs +++ b/spec/redjs @@ -1 +1 @@ -Subproject commit 0acbbe951d4cf2b8be2fc3ce2b941c132f83aa26 +Subproject commit 26e3054452beb85bdb5ab6f42f1d9531f654a7d8 diff --git a/spec/v8/to_spec.rb b/spec/v8/to_spec.rb new file mode 100644 index 0000000..13ef334 --- /dev/null +++ b/spec/v8/to_spec.rb @@ -0,0 +1,15 @@ +require File.dirname(__FILE__) + '/../spec_helper' + +include V8 + +describe V8::To do + + it "can convert into perl case" do + To.perl_case("foo").should == "foo" + To.perl_case("fooBar").should == "foo_bar" + To.perl_case("fooBarBaz").should == "foo_bar_baz" + To.perl_case("XMLDocument").should == "xml_document" + end + + +end \ No newline at end of file