diff --git a/ext/v8/context.cc b/ext/v8/context.cc index 4789c12..e1594b4 100644 --- a/ext/v8/context.cc +++ b/ext/v8/context.cc @@ -10,6 +10,8 @@ namespace rr { defineMethod("Enter", &Enter). defineMethod("Exit", &Exit). + defineMethod("Global", &Global). + store(&Class); // TODO @@ -58,6 +60,13 @@ namespace rr { return Qnil; } + VALUE Context::Global(VALUE self) { + Context context(self); + Locker lock(context.getIsolate()); + + return Object(context->GetIsolate(), context->Global()); + } + // TODO // template <> void Pointer::unwrap(VALUE value) { // Data_Get_Struct(value, class v8::ExtensionConfiguration, pointer); diff --git a/ext/v8/context.h b/ext/v8/context.h index 449465a..967000c 100644 --- a/ext/v8/context.h +++ b/ext/v8/context.h @@ -13,8 +13,9 @@ namespace rr { static VALUE Enter(VALUE self); static VALUE Exit(VALUE self); + static VALUE Global(VALUE self); + // TODO - // static VALUE Global(VALUE self); // static VALUE DetachGlobal(VALUE self); // static VALUE ReattachGlobal(VALUE self, VALUE global); // static VALUE GetEntered(VALUE self); diff --git a/ext/v8/function.cc b/ext/v8/function.cc new file mode 100644 index 0000000..83cbe13 --- /dev/null +++ b/ext/v8/function.cc @@ -0,0 +1,95 @@ +#include "rr.h" + +namespace rr { + + void Function::Init() { + ClassBuilder("Function", Object::Class). + + defineMethod("NewInstance", &NewInstance). + defineMethod("Call", &Call). + defineMethod("SetName", &SetName). + defineMethod("GetName", &GetName). + defineMethod("GetInferredName", &GetInferredName). + defineMethod("GetScriptLineNumber", &GetScriptLineNumber). + defineMethod("GetScriptColumnNumber", &GetScriptColumnNumber). + defineMethod("GetScriptId", &GetScriptId). + defineMethod("GetScriptOrigin", &GetScriptOrigin). + + store(&Class); + } + + VALUE Function::NewInstance(int argc, VALUE argv[], VALUE self) { + VALUE args; + rb_scan_args(argc, argv, "01", &args); + + Function function(self); + v8::Isolate* isolate = function.getIsolate(); + Locker lock(isolate); + + if (RTEST(args)) { + std::vector< v8::Handle > vector(Value::convertRubyArray(isolate, args)); + return Object(isolate, function->NewInstance(RARRAY_LENINT(args), &vector[0])); + } else { + return Object(isolate, function->NewInstance()); + } + } + + VALUE Function::Call(VALUE self, VALUE receiver, VALUE argv) { + Function function(self); + v8::Isolate* isolate = function.getIsolate(); + Locker lock(isolate); + + std::vector< v8::Handle > vector(Value::convertRubyArray(isolate, argv)); + + return Value::handleToRubyObject(isolate, function->Call(Value(receiver), RARRAY_LENINT(argv), &vector[0])); + } + + VALUE Function::SetName(VALUE self, VALUE name) { + Function function(self); + Locker lock(function.getIsolate()); + + function->SetName(String(name)); + + return Qnil; + } + + VALUE Function::GetName(VALUE self) { + Function function(self); + Locker lock(function.getIsolate()); + + return Value::handleToRubyObject(function.getIsolate(), function->GetName()); + } + + VALUE Function::GetInferredName(VALUE self) { + Function function(self); + Locker lock(function.getIsolate()); + + return Value::handleToRubyObject(function.getIsolate(), function->GetInferredName()); + } + + VALUE Function::GetScriptLineNumber(VALUE self) { + Function function(self); + Locker lock(function.getIsolate()); + + return INT2FIX(function->GetScriptLineNumber()); + } + + VALUE Function::GetScriptColumnNumber(VALUE self) { + Function function(self); + Locker lock(function.getIsolate()); + + return INT2FIX(function->GetScriptColumnNumber()); + } + + VALUE Function::GetScriptId(VALUE self) { + Function function(self); + Locker lock(function.getIsolate()); + + return INT2FIX(function->ScriptId()); + } + + VALUE Function::GetScriptOrigin(VALUE self) { + return not_implemented("GetScriptOrigin"); + } + +} diff --git a/ext/v8/function.h b/ext/v8/function.h new file mode 100644 index 0000000..60c489b --- /dev/null +++ b/ext/v8/function.h @@ -0,0 +1,26 @@ +#ifndef RR_FUNCTION +#define RR_FUNCTION + +namespace rr { + + class Function : public Ref { + public: + static void Init(); + + static VALUE NewInstance(int argc, VALUE argv[], VALUE self); + static VALUE Call(VALUE self, VALUE receiver, VALUE argv); + static VALUE SetName(VALUE self, VALUE name); + static VALUE GetName(VALUE self); + static VALUE GetInferredName(VALUE self); + static VALUE GetScriptLineNumber(VALUE self); + static VALUE GetScriptColumnNumber(VALUE self); + static VALUE GetScriptId(VALUE self); + static VALUE GetScriptOrigin(VALUE self); + + inline Function(VALUE value) : Ref(value) {} + inline Function(v8::Isolate* isolate, v8::Handle function) : Ref(isolate, function) {} + }; + +} + +#endif diff --git a/ext/v8/init.cc b/ext/v8/init.cc index 795329d..af60e34 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -17,16 +17,16 @@ extern "C" { Object::Init(); Primitive::Init(); String::Init(); + Function::Init(); + Script::Init(); // Accessor::Init(); // Invocation::Init(); // Signature::Init(); // Array::Init(); - // Function::Init(); // Date::Init(); // Constants::Init(); // External::Init(); - // Script::Init(); // Template::Init(); // Stack::Init(); // Message::Init(); diff --git a/ext/v8/ref.h b/ext/v8/ref.h index bdaae29..d708b17 100644 --- a/ext/v8/ref.h +++ b/ext/v8/ref.h @@ -138,6 +138,26 @@ namespace rr { v8::Isolate* isolate; v8::Handle handle; + + public: + template + class array { + public: + inline array(VALUE ary) : argv(ary), vector(RARRAY_LENINT(argv)) { } + + inline operator v8::Handle*() { + for (uint32_t i = 0; i < vector.size(); i++) { + vector[i] = C(rb_ary_entry(argv, i)); + } + + return &vector[0]; + } + + private: + VALUE argv; + std::vector< v8::Handle > vector; + }; + }; template diff --git a/ext/v8/rr.h b/ext/v8/rr.h index c85dfbd..edc478e 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -10,6 +10,12 @@ #include "ruby/encoding.h" #endif +inline VALUE not_implemented(const char* message) { + rb_raise(rb_eStandardError, "not yet implemented %s", message); + + return Qnil; +} + #include "class_builder.h" #include "equiv.h" @@ -33,4 +39,7 @@ // This one is named v8_string to avoid name collisions with C's string.h #include "rr_string.h" +#include "function.h" +#include "script.h" + #endif diff --git a/ext/v8/script.cc b/ext/v8/script.cc new file mode 100644 index 0000000..75f2620 --- /dev/null +++ b/ext/v8/script.cc @@ -0,0 +1,48 @@ +#include "rr.h" + +namespace rr { + + void Script::Init() { + ClassBuilder("Script"). + defineSingletonMethod("Compile", &Compile). + + defineMethod("Run", &Run). + // TODO + // defineMethod("RunWithTimeout", &RunWithTimeout). + + store(&Class); + + // TODO + // ClassBuilder("ScriptOrigin"). + // defineSingletonMethod("new", &ScriptOrigin::initialize). + // store(&ScriptOrigin::Class); + + // TODO + // ClassBuilder("ScriptData"). + // defineSingletonMethod("PreCompile", &ScriptData::PreCompile). + // defineSingletonMethod("New", &ScriptData::New). + // defineMethod("Length", &ScriptData::Length). + // defineMethod("Data", &ScriptData::Data). + // defineMethod("HasError", &ScriptData::HasError). + // store(&ScriptData::Class); + } + + VALUE Script::Compile(int argc, VALUE argv[], VALUE self) { + VALUE source, rb_context, origin; + rb_scan_args(argc, argv, "21", &source, &rb_context, &origin); + + Context context(rb_context); + Locker lock(context.getIsolate()); + + // TODO: ScriptOrigin + return Script(context.getIsolate(), v8::Script::Compile(String(source))); + } + + VALUE Script::Run(VALUE self, VALUE rb_context) { + Context context(rb_context); + Locker lock(context->GetIsolate()); + + return Value::handleToRubyObject(context->GetIsolate(), Script(self)->Run()); + } + +} diff --git a/ext/v8/script.h b/ext/v8/script.h new file mode 100644 index 0000000..9ed9d3c --- /dev/null +++ b/ext/v8/script.h @@ -0,0 +1,19 @@ +#ifndef RR_SCRIPT +#define RR_SCRIPT + +namespace rr { + + class Script : public Ref { + public: + static void Init(); + static VALUE Compile(int argc, VALUE argv[], VALUE self); + static VALUE Run(VALUE self, VALUE context); + // static VALUE RunWithTimeout(VALUE self, VALUE timeout); + + inline Script(VALUE value) : Ref(value) {} + inline Script(v8::Isolate* isolate, v8::Handle script) : Ref(isolate, script) {} + }; + +} + +#endif diff --git a/ext/v8/value.cc b/ext/v8/value.cc index 01bdb93..b4e4f3f 100644 --- a/ext/v8/value.cc +++ b/ext/v8/value.cc @@ -15,7 +15,7 @@ namespace rr { defineMethod("IsTrue", &IsTrue). defineMethod("IsFalse", &IsFalse). defineMethod("IsString", &IsString). - // defineMethod("IsFunction", &IsFunction). + defineMethod("IsFunction", &IsFunction). // defineMethod("IsArray", &IsArray). defineMethod("IsObject", &IsObject). // defineMethod("IsBoolean", &IsBoolean). @@ -83,6 +83,13 @@ namespace rr { return Bool(value->IsString()); } + VALUE Value::IsFunction(VALUE self) { + Value value(self); + Locker lock(value.getIsolate()); + + return Bool(value->IsFunction()); + } + VALUE Value::IsObject(VALUE self) { Value value(self); Locker lock(value.getIsolate()); @@ -190,6 +197,10 @@ namespace rr { // return Date((v8::Handle)v8::Date::Cast(*handle)); // } + if (handle->IsFunction()) { + return Function(isolate, v8::Handle::Cast(handle)); + } + if (handle->IsObject()) { return Object(isolate, handle->ToObject()); } @@ -239,4 +250,14 @@ namespace rr { return v8::Undefined(isolate); } + std::vector< v8::Handle > Value::convertRubyArray(v8::Isolate* isolate, VALUE value) { + std::vector< v8::Handle > vector(RARRAY_LENINT(value)); + + for (uint32_t i = 0; i < vector.size(); i++) { + vector[i] = Value::rubyObjectToHandle(isolate, rb_ary_entry(value, i)); + } + + return vector; + } + } diff --git a/ext/v8/value.h b/ext/v8/value.h index 9d676e8..c7a9be6 100644 --- a/ext/v8/value.h +++ b/ext/v8/value.h @@ -12,7 +12,7 @@ namespace rr { static VALUE IsTrue(VALUE self); static VALUE IsFalse(VALUE self); static VALUE IsString(VALUE self); - // static VALUE IsFunction(VALUE self); + static VALUE IsFunction(VALUE self); // static VALUE IsArray(VALUE self); static VALUE IsObject(VALUE self); static VALUE IsBoolean(VALUE self); @@ -47,6 +47,8 @@ namespace rr { static VALUE handleToRubyObject(v8::Isolate* isolate, v8::Handle value); static v8::Handle rubyObjectToHandle(v8::Isolate* isolate, VALUE value); + static std::vector< v8::Handle > convertRubyArray(v8::Isolate* isolate, VALUE value); + static VALUE Empty; }; diff --git a/spec/c/function_spec.rb b/spec/c/function_spec.rb new file mode 100644 index 0000000..76ed15c --- /dev/null +++ b/spec/c/function_spec.rb @@ -0,0 +1,53 @@ +require 'c_spec_helper' + +describe V8::C::Function do + requires_v8_context + + it 'can be called' do + fn = run '(function() { return "foo" })' + expect(fn.Call(@ctx.Global, []).Utf8Value).to eq 'foo' + end + + it 'can be called with arguments and context' do + fn = run '(function(one, two, three) {this.one = one; this.two = two; this.three = three})' + + one = V8::C::Object.New(@isolate) + two = V8::C::Object.New(@isolate) + + fn.Call(@ctx.Global, [one, two, 3]) + + expect(@ctx.Global.Get(V8::C::String.NewFromUtf8(@isolate, 'one'))).to eq one + expect(@ctx.Global.Get(V8::C::String.NewFromUtf8(@isolate, 'two'))).to eq two + expect(@ctx.Global.Get(V8::C::String.NewFromUtf8(@isolate, 'three'))).to eq 3 + end + + it 'can be called as a constructor' do + fn = run '(function() {this.foo = "foo"})' + expect(fn.NewInstance.Get(V8::C::String.NewFromUtf8(@isolate, 'foo')).Utf8Value).to eq 'foo' + end + + # it 'can be called as a constructor with arguments' do + # fn = run '(function(foo) {this.foo = foo})' + # object = fn.NewInstance([V8::C::String.NewFromUtf8(@isolate, 'bar')]) + + # expect(object.Get(V8::C::String.NewFromUtf8(@isolate, 'foo')).Utf8Value).to eq 'bar' + # end + + # TODO + # it 'doesn\'t kill the world if invoking it throws a javascript exception' do + # V8::C::TryCatch do + # fn = run '(function() { throw new Error("boom!")})' + # fn.Call(@ctx.Global(), []) + # fn.NewInstance([]) + # end + # end + + def run(source) + source = V8::C::String.NewFromUtf8(@isolate, source.to_s) + filename = V8::C::String.NewFromUtf8(@isolate, "") + script = V8::C::Script.Compile(source, filename) + result = script.Run(@ctx) + + result.kind_of?(V8::C::String) ? result.Utf8Value : result + end +end diff --git a/spec/c/script_spec.rb b/spec/c/script_spec.rb new file mode 100644 index 0000000..d7cc61a --- /dev/null +++ b/spec/c/script_spec.rb @@ -0,0 +1,32 @@ +require 'c_spec_helper' + +describe V8::C::Script do + requires_v8_context + + # TODO + # it 'can run a script and return a polymorphic result' do + # source = V8::C::String::New("(new Array())") + # script = V8::C::Script::New(source) + # + # result = script.Run() + # expect(result).to be_an V8::C::Array + # end + + # TODO + # it 'can accept precompiled script data' do + # source = "7 * 6" + # name = V8::C::String::New("") + # origin = V8::C::ScriptOrigin.new(name) + # data = V8::C::ScriptData::PreCompile(source, source.length) + # data.HasError().should be_false + # script = V8::C::Script::New(V8::C::String::New(source), origin, data) + # script.Run().should eql 42 + # end + + # TODO + # it 'can detect errors in the script data' do + # source = "^ = ;" + # data = V8::C::ScriptData::PreCompile(source, source.length) + # data.HasError().should be_true + # end +end diff --git a/spec/c_spec_helper.rb b/spec/c_spec_helper.rb index 62397cb..c5e52d5 100644 --- a/spec/c_spec_helper.rb +++ b/spec/c_spec_helper.rb @@ -14,12 +14,12 @@ module V8ContextHelpers @isolate = V8::C::Isolate.New V8::C::HandleScope(@isolate) do - @cxt = V8::C::Context::New(@isolate) + @ctx = V8::C::Context::New(@isolate) begin - @cxt.Enter + @ctx.Enter yield ensure - @cxt.Exit + @ctx.Exit end end end