therubyracer-xcode 0.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/.travis.yml +14 -0
- data/Changelog.md +263 -0
- data/Gemfile +12 -0
- data/README.md +227 -0
- data/Rakefile +42 -0
- data/benchmarks.rb +218 -0
- data/ext/v8/accessor.cc +181 -0
- data/ext/v8/array.cc +26 -0
- data/ext/v8/backref.cc +45 -0
- data/ext/v8/constants.cc +34 -0
- data/ext/v8/constraints.cc +52 -0
- data/ext/v8/context.cc +130 -0
- data/ext/v8/date.cc +18 -0
- data/ext/v8/exception.cc +38 -0
- data/ext/v8/extconf.rb +34 -0
- data/ext/v8/external.cc +43 -0
- data/ext/v8/function.cc +58 -0
- data/ext/v8/gc.cc +43 -0
- data/ext/v8/handles.cc +34 -0
- data/ext/v8/heap.cc +35 -0
- data/ext/v8/init.cc +39 -0
- data/ext/v8/invocation.cc +86 -0
- data/ext/v8/locker.cc +77 -0
- data/ext/v8/message.cc +51 -0
- data/ext/v8/object.cc +335 -0
- data/ext/v8/primitive.cc +8 -0
- data/ext/v8/rr.cc +83 -0
- data/ext/v8/rr.h +934 -0
- data/ext/v8/script.cc +115 -0
- data/ext/v8/signature.cc +18 -0
- data/ext/v8/stack.cc +76 -0
- data/ext/v8/string.cc +47 -0
- data/ext/v8/template.cc +175 -0
- data/ext/v8/trycatch.cc +87 -0
- data/ext/v8/v8.cc +87 -0
- data/ext/v8/value.cc +239 -0
- data/lib/therubyracer.rb +1 -0
- data/lib/v8/access/indices.rb +40 -0
- data/lib/v8/access/invocation.rb +47 -0
- data/lib/v8/access/names.rb +65 -0
- data/lib/v8/access.rb +5 -0
- data/lib/v8/array.rb +26 -0
- data/lib/v8/context.rb +258 -0
- data/lib/v8/conversion/array.rb +11 -0
- data/lib/v8/conversion/class.rb +119 -0
- data/lib/v8/conversion/code.rb +38 -0
- data/lib/v8/conversion/fixnum.rb +11 -0
- data/lib/v8/conversion/fundamental.rb +11 -0
- data/lib/v8/conversion/hash.rb +11 -0
- data/lib/v8/conversion/indentity.rb +31 -0
- data/lib/v8/conversion/method.rb +26 -0
- data/lib/v8/conversion/object.rb +28 -0
- data/lib/v8/conversion/primitive.rb +7 -0
- data/lib/v8/conversion/proc.rb +5 -0
- data/lib/v8/conversion/reference.rb +16 -0
- data/lib/v8/conversion/string.rb +12 -0
- data/lib/v8/conversion/symbol.rb +7 -0
- data/lib/v8/conversion/time.rb +13 -0
- data/lib/v8/conversion.rb +36 -0
- data/lib/v8/error.rb +169 -0
- data/lib/v8/function.rb +28 -0
- data/lib/v8/object.rb +79 -0
- data/lib/v8/stack.rb +85 -0
- data/lib/v8/version.rb +3 -0
- data/lib/v8/weak.rb +82 -0
- data/lib/v8.rb +30 -0
- data/spec/c/array_spec.rb +19 -0
- data/spec/c/constants_spec.rb +22 -0
- data/spec/c/exception_spec.rb +28 -0
- data/spec/c/external_spec.rb +11 -0
- data/spec/c/function_spec.rb +48 -0
- data/spec/c/handles_spec.rb +31 -0
- data/spec/c/locker_spec.rb +36 -0
- data/spec/c/object_spec.rb +47 -0
- data/spec/c/script_spec.rb +30 -0
- data/spec/c/string_spec.rb +18 -0
- data/spec/c/template_spec.rb +31 -0
- data/spec/c/trycatch_spec.rb +52 -0
- data/spec/mem/blunt_spec.rb +42 -0
- data/spec/redjs_spec.rb +10 -0
- data/spec/spec_helper.rb +41 -0
- data/spec/threading_spec.rb +64 -0
- data/spec/v8/context_spec.rb +19 -0
- data/spec/v8/conversion_spec.rb +52 -0
- data/spec/v8/error_spec.rb +167 -0
- data/spec/v8/function_spec.rb +9 -0
- data/spec/v8/object_spec.rb +15 -0
- data/thefrontside.png +0 -0
- data/therubyracer.gemspec +22 -0
- metadata +186 -0
data/ext/v8/value.cc
ADDED
@@ -0,0 +1,239 @@
|
|
1
|
+
#include "rr.h"
|
2
|
+
|
3
|
+
namespace rr {
|
4
|
+
|
5
|
+
VALUE Value::Empty;
|
6
|
+
|
7
|
+
void Value::Init() {
|
8
|
+
Empty = rb_eval_string("Object.new");
|
9
|
+
ClassBuilder("Value").
|
10
|
+
defineConst("Empty", Empty).
|
11
|
+
defineMethod("IsUndefined", &IsUndefined).
|
12
|
+
defineMethod("IsNull", &IsNull).
|
13
|
+
defineMethod("IsTrue", &IsTrue).
|
14
|
+
defineMethod("IsFalse", &IsFalse).
|
15
|
+
defineMethod("IsString", &IsString).
|
16
|
+
defineMethod("IsFunction", &IsFunction).
|
17
|
+
defineMethod("IsArray", &IsArray).
|
18
|
+
defineMethod("IsObject", &IsObject).
|
19
|
+
defineMethod("IsBoolean", &IsBoolean).
|
20
|
+
defineMethod("IsNumber", &IsNumber).
|
21
|
+
defineMethod("IsExternal", &IsExternal).
|
22
|
+
defineMethod("IsInt32", &IsInt32).
|
23
|
+
defineMethod("IsUint32", &IsUint32).
|
24
|
+
defineMethod("IsDate", &IsDate).
|
25
|
+
defineMethod("IsBooleanObject", &IsBooleanObject).
|
26
|
+
defineMethod("IsNumberObject", &IsNumberObject).
|
27
|
+
defineMethod("IsStringObject", &IsStringObject).
|
28
|
+
defineMethod("IsNativeError", &IsNativeError).
|
29
|
+
defineMethod("IsRegExp", &IsRegExp).
|
30
|
+
defineMethod("ToString", &ToString).
|
31
|
+
defineMethod("ToDetailString", &ToDetailString).
|
32
|
+
defineMethod("ToObject", &ToObject).
|
33
|
+
defineMethod("BooleanValue", &BooleanValue).
|
34
|
+
defineMethod("NumberValue", &NumberValue).
|
35
|
+
defineMethod("IntegerValue", &IntegerValue).
|
36
|
+
defineMethod("Uint32Value", &Uint32Value).
|
37
|
+
defineMethod("IntegerValue", &IntegerValue).
|
38
|
+
defineMethod("Equals", &Equals).
|
39
|
+
defineMethod("StrictEquals", &StrictEquals)
|
40
|
+
.store(&Class);
|
41
|
+
rb_gc_register_address(&Empty);
|
42
|
+
}
|
43
|
+
|
44
|
+
VALUE Value::IsUndefined(VALUE self) {
|
45
|
+
return Bool(Value(self)->IsUndefined());
|
46
|
+
}
|
47
|
+
VALUE Value::IsNull(VALUE self) {
|
48
|
+
return Bool(Value(self)->IsNull());
|
49
|
+
}
|
50
|
+
VALUE Value::IsTrue(VALUE self) {
|
51
|
+
return Bool(Value(self)->IsTrue());
|
52
|
+
}
|
53
|
+
VALUE Value::IsFalse(VALUE self) {
|
54
|
+
return Bool(Value(self)->IsFalse());
|
55
|
+
}
|
56
|
+
VALUE Value::IsString(VALUE self) {
|
57
|
+
return Bool(Value(self)->IsString());
|
58
|
+
}
|
59
|
+
VALUE Value::IsFunction(VALUE self) {
|
60
|
+
return Bool(Value(self)->IsFunction());
|
61
|
+
}
|
62
|
+
VALUE Value::IsArray(VALUE self) {
|
63
|
+
return Bool(Value(self)->IsArray());
|
64
|
+
}
|
65
|
+
VALUE Value::IsObject(VALUE self) {
|
66
|
+
return Bool(Value(self)->IsObject());
|
67
|
+
}
|
68
|
+
VALUE Value::IsBoolean(VALUE self) {
|
69
|
+
return Bool(Value(self)->IsBoolean());
|
70
|
+
}
|
71
|
+
VALUE Value::IsNumber(VALUE self) {
|
72
|
+
return Bool(Value(self)->IsNumber());
|
73
|
+
}
|
74
|
+
VALUE Value::IsExternal(VALUE self) {
|
75
|
+
return Bool(Value(self)->IsExternal());
|
76
|
+
}
|
77
|
+
VALUE Value::IsInt32(VALUE self) {
|
78
|
+
return Bool(Value(self)->IsInt32());
|
79
|
+
}
|
80
|
+
VALUE Value::IsUint32(VALUE self) {
|
81
|
+
return Bool(Value(self)->IsUint32());
|
82
|
+
}
|
83
|
+
VALUE Value::IsDate(VALUE self) {
|
84
|
+
return Bool(Value(self)->IsDate());
|
85
|
+
}
|
86
|
+
VALUE Value::IsBooleanObject(VALUE self) {
|
87
|
+
return Bool(Value(self)->IsBooleanObject());
|
88
|
+
}
|
89
|
+
VALUE Value::IsNumberObject(VALUE self) {
|
90
|
+
return Bool(Value(self)->IsNumberObject());
|
91
|
+
}
|
92
|
+
VALUE Value::IsStringObject(VALUE self) {
|
93
|
+
return Bool(Value(self)->IsStringObject());
|
94
|
+
}
|
95
|
+
VALUE Value::IsNativeError(VALUE self) {
|
96
|
+
return Bool(Value(self)->IsNativeError());
|
97
|
+
}
|
98
|
+
VALUE Value::IsRegExp(VALUE self) {
|
99
|
+
return Bool(Value(self)->IsRegExp());
|
100
|
+
}
|
101
|
+
|
102
|
+
// VALUE Value::ToBoolean(VALUE self) {
|
103
|
+
// return Boolean(Value(self)->ToBoolean());
|
104
|
+
// }
|
105
|
+
|
106
|
+
// VALUE Value::ToNumber(VALUE self) {
|
107
|
+
// return Number(Value(self)->ToNumber());
|
108
|
+
// }
|
109
|
+
VALUE Value::ToString(VALUE self) {
|
110
|
+
return String(Value(self)->ToString());
|
111
|
+
}
|
112
|
+
|
113
|
+
VALUE Value::ToDetailString(VALUE self) {
|
114
|
+
return String(Value(self)->ToDetailString());
|
115
|
+
}
|
116
|
+
|
117
|
+
VALUE Value::ToObject(VALUE self) {
|
118
|
+
return Object(Value(self)->ToObject());
|
119
|
+
}
|
120
|
+
|
121
|
+
// VALUE Value::ToInteger(VALUE self) {
|
122
|
+
// return Integer(Value(self)->ToInteger());
|
123
|
+
// }
|
124
|
+
|
125
|
+
// VALUE Value::ToUint32(VALUE self) {
|
126
|
+
// return Uint32(Value(self)->ToUint32());
|
127
|
+
// }
|
128
|
+
|
129
|
+
// VALUE Value::ToInt32(VALUE self) {
|
130
|
+
// return Int32(Value(self)->ToInt32());
|
131
|
+
// }
|
132
|
+
|
133
|
+
|
134
|
+
// VALUE Value::ToArrayIndex(VALUE self) {
|
135
|
+
// return Uint32(Value(self)->ToArrayIndex());
|
136
|
+
// }
|
137
|
+
|
138
|
+
VALUE Value::BooleanValue(VALUE self) {
|
139
|
+
return Bool(Value(self)->BooleanValue());
|
140
|
+
}
|
141
|
+
VALUE Value::NumberValue(VALUE self) {
|
142
|
+
return rb_float_new(Value(self)->NumberValue());
|
143
|
+
}
|
144
|
+
VALUE Value::IntegerValue(VALUE self) {
|
145
|
+
return INT2NUM(Value(self)->IntegerValue());
|
146
|
+
}
|
147
|
+
VALUE Value::Uint32Value(VALUE self) {
|
148
|
+
return UINT2NUM(Value(self)->Uint32Value());
|
149
|
+
}
|
150
|
+
VALUE Value::Int32Value(VALUE self) {
|
151
|
+
return INT2FIX(Value(self)->Int32Value());
|
152
|
+
}
|
153
|
+
|
154
|
+
VALUE Value::Equals(VALUE self, VALUE other) {
|
155
|
+
return Bool(Value(self)->Equals(Value(other)));
|
156
|
+
}
|
157
|
+
|
158
|
+
VALUE Value::StrictEquals(VALUE self, VALUE other) {
|
159
|
+
return Bool(Value(self)->StrictEquals(Value(other)));
|
160
|
+
}
|
161
|
+
|
162
|
+
Value::operator VALUE() {
|
163
|
+
if (handle.IsEmpty() || handle->IsUndefined() || handle->IsNull()) {
|
164
|
+
return Qnil;
|
165
|
+
}
|
166
|
+
if (handle->IsTrue()) {
|
167
|
+
return Qtrue;
|
168
|
+
}
|
169
|
+
if (handle->IsFalse()) {
|
170
|
+
return Qfalse;
|
171
|
+
}
|
172
|
+
if (handle->IsExternal()) {
|
173
|
+
return External((v8::Handle<v8::External>)v8::External::Cast(*handle));
|
174
|
+
}
|
175
|
+
if (handle->IsUint32()) {
|
176
|
+
return UInt32(handle->Uint32Value());
|
177
|
+
}
|
178
|
+
if (handle->IsInt32()) {
|
179
|
+
return INT2FIX(handle->Int32Value());
|
180
|
+
}
|
181
|
+
if (handle->IsBoolean()) {
|
182
|
+
return handle->BooleanValue() ? Qtrue : Qfalse;
|
183
|
+
}
|
184
|
+
if (handle->IsNumber()) {
|
185
|
+
return rb_float_new(handle->NumberValue());
|
186
|
+
}
|
187
|
+
if (handle->IsString()) {
|
188
|
+
return String(handle->ToString());
|
189
|
+
}
|
190
|
+
if (handle->IsDate()) {
|
191
|
+
return Date((v8::Handle<v8::Date>)v8::Date::Cast(*handle));
|
192
|
+
}
|
193
|
+
if (handle->IsObject()) {
|
194
|
+
return Object(handle->ToObject());
|
195
|
+
}
|
196
|
+
return Ref<v8::Value>::operator VALUE();
|
197
|
+
}
|
198
|
+
|
199
|
+
Value::operator v8::Handle<v8::Value>() const {
|
200
|
+
if (rb_equal(value,Empty)) {
|
201
|
+
return v8::Handle<v8::Value>();
|
202
|
+
}
|
203
|
+
switch (TYPE(value)) {
|
204
|
+
case T_FIXNUM:
|
205
|
+
return v8::Integer::New(NUM2INT(value));
|
206
|
+
case T_FLOAT:
|
207
|
+
return v8::Number::New(NUM2DBL(value));
|
208
|
+
case T_STRING:
|
209
|
+
return v8::String::New(RSTRING_PTR(value), (int)RSTRING_LEN(value));
|
210
|
+
case T_NIL:
|
211
|
+
return v8::Null();
|
212
|
+
case T_TRUE:
|
213
|
+
return v8::True();
|
214
|
+
case T_FALSE:
|
215
|
+
return v8::False();
|
216
|
+
case T_DATA:
|
217
|
+
return Ref<v8::Value>::operator v8::Handle<v8::Value>();
|
218
|
+
case T_OBJECT:
|
219
|
+
case T_CLASS:
|
220
|
+
case T_ICLASS:
|
221
|
+
case T_MODULE:
|
222
|
+
case T_REGEXP:
|
223
|
+
case T_MATCH:
|
224
|
+
case T_ARRAY:
|
225
|
+
case T_HASH:
|
226
|
+
case T_STRUCT:
|
227
|
+
case T_BIGNUM:
|
228
|
+
case T_FILE:
|
229
|
+
case T_SYMBOL:
|
230
|
+
case T_UNDEF:
|
231
|
+
case T_NODE:
|
232
|
+
default:
|
233
|
+
rb_warn("unknown conversion to V8 for: %s", RSTRING_PTR(rb_inspect(value)));
|
234
|
+
return v8::String::New("Undefined Conversion");
|
235
|
+
}
|
236
|
+
|
237
|
+
return v8::Undefined();
|
238
|
+
}
|
239
|
+
}
|
data/lib/therubyracer.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "v8"
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class V8::Access
|
2
|
+
module Indices
|
3
|
+
|
4
|
+
def indices(obj)
|
5
|
+
obj.respond_to?(:length) ? (0..obj.length).to_a : []
|
6
|
+
end
|
7
|
+
|
8
|
+
def iget(obj, index, &dontintercept)
|
9
|
+
if obj.respond_to?(:[])
|
10
|
+
obj.send(:[], index, &dontintercept)
|
11
|
+
else
|
12
|
+
yield
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def iset(obj, index, value, &dontintercept)
|
17
|
+
if obj.respond_to?(:[]=)
|
18
|
+
obj.send(:[]=, index, value, &dontintercept)
|
19
|
+
else
|
20
|
+
yield
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def iquery(obj, index, attributes, &dontintercept)
|
25
|
+
if obj.respond_to?(:[])
|
26
|
+
attributes.dont_delete
|
27
|
+
unless obj.respond_to?(:[]=)
|
28
|
+
attributes.read_only
|
29
|
+
end
|
30
|
+
else
|
31
|
+
yield
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def idelete(obj, index, &dontintercept)
|
36
|
+
yield
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class V8::Access
|
2
|
+
module Invocation
|
3
|
+
def methodcall(code, this, args)
|
4
|
+
code.methodcall this, args
|
5
|
+
end
|
6
|
+
|
7
|
+
module Aritize
|
8
|
+
def aritize(args)
|
9
|
+
arity < 0 ? args : Array.new(arity).to_enum(:each_with_index).map {|item, i| args[i]}
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module Proc
|
14
|
+
include Aritize
|
15
|
+
def methodcall(this, args)
|
16
|
+
call *aritize([this].concat(args))
|
17
|
+
end
|
18
|
+
::Proc.send :include, self
|
19
|
+
end
|
20
|
+
|
21
|
+
module Method
|
22
|
+
include Aritize
|
23
|
+
def methodcall(this, args)
|
24
|
+
context = V8::Context.current
|
25
|
+
access = context.access
|
26
|
+
if this.equal? self.receiver
|
27
|
+
call *aritize(args)
|
28
|
+
elsif this.class <= self.receiver.class
|
29
|
+
access.methodcall(unbind, this, args)
|
30
|
+
elsif this.equal? context.scope
|
31
|
+
call *aritize(args)
|
32
|
+
else
|
33
|
+
fail TypeError, "cannot invoke #{self} on #{this}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
::Method.send :include, self
|
37
|
+
end
|
38
|
+
|
39
|
+
module UnboundMethod
|
40
|
+
def methodcall(this, args)
|
41
|
+
access = V8::Context.current.access
|
42
|
+
access.methodcall bind(this), this, args
|
43
|
+
end
|
44
|
+
::UnboundMethod.send :include, self
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'set'
|
2
|
+
class V8::Access
|
3
|
+
module Names
|
4
|
+
def names(obj)
|
5
|
+
accessible_names(obj)
|
6
|
+
end
|
7
|
+
|
8
|
+
def get(obj, name, &dontintercept)
|
9
|
+
methods = accessible_names(obj)
|
10
|
+
if methods.include?(name)
|
11
|
+
method = obj.method(name)
|
12
|
+
method.arity == 0 ? method.call : method.unbind
|
13
|
+
elsif obj.respond_to?(:[]) && !special?(name)
|
14
|
+
obj.send(:[], name, &dontintercept)
|
15
|
+
else
|
16
|
+
yield
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def set(obj, name, value, &dontintercept)
|
21
|
+
setter = name + "="
|
22
|
+
methods = accessible_names(obj, true)
|
23
|
+
if methods.include?(setter)
|
24
|
+
obj.send(setter, value)
|
25
|
+
elsif obj.respond_to?(:[]=) && !special?(name)
|
26
|
+
obj.send(:[]=, name, value, &dontintercept)
|
27
|
+
else
|
28
|
+
yield
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def query(obj, name, attributes, &dontintercept)
|
33
|
+
if obj.respond_to?(name)
|
34
|
+
attributes.dont_delete
|
35
|
+
unless obj.respond_to?(name + "=")
|
36
|
+
attributes.read_only
|
37
|
+
end
|
38
|
+
else
|
39
|
+
yield
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def delete(obj, name, &dontintercept)
|
44
|
+
yield
|
45
|
+
end
|
46
|
+
|
47
|
+
def accessible_names(obj, special_methods = false)
|
48
|
+
obj.public_methods(false).map {|m| m.to_s}.to_set.tap do |methods|
|
49
|
+
ancestors = obj.class.ancestors.dup
|
50
|
+
while ancestor = ancestors.shift
|
51
|
+
break if ancestor == ::Object
|
52
|
+
methods.merge(ancestor.public_instance_methods(false).map {|m| m.to_s})
|
53
|
+
end
|
54
|
+
methods.reject!(&special?) unless special_methods
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def special?(name = nil)
|
61
|
+
@special ||= lambda {|m| m == "[]" || m == "[]=" || m =~ /=$/}
|
62
|
+
name.nil? ? @special : @special[name]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/v8/access.rb
ADDED
data/lib/v8/array.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
class V8::Array < V8::Object
|
2
|
+
|
3
|
+
def initialize(native_or_length = nil)
|
4
|
+
super do
|
5
|
+
if native_or_length.is_a?(Numeric)
|
6
|
+
V8::C::Array::New(native_or_length)
|
7
|
+
elsif native_or_length.is_a?(V8::C::Array)
|
8
|
+
native_or_length
|
9
|
+
else
|
10
|
+
V8::C::Array::New()
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def each
|
16
|
+
@context.enter do
|
17
|
+
0.upto(@native.Length() - 1) do |i|
|
18
|
+
yield @context.to_ruby(@native.Get(i))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def length
|
24
|
+
@native.Length()
|
25
|
+
end
|
26
|
+
end
|
data/lib/v8/context.rb
ADDED
@@ -0,0 +1,258 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'stringio'
|
3
|
+
module V8
|
4
|
+
# All JavaScript must be executed in a context. This context consists of a global scope containing the
|
5
|
+
# standard JavaScript objects¨and functions like Object, String, Array, as well as any objects or
|
6
|
+
# functions from Ruby which have been embedded into it from the containing enviroment. E.g.
|
7
|
+
#
|
8
|
+
# V8::Context.new do |cxt|
|
9
|
+
# cxt['num'] = 5
|
10
|
+
# cxt.eval('num + 5') #=> 10
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# The same object may appear in any number of contexts, but only one context may be executing JavaScript code
|
14
|
+
# in any given thread. If a new context is opened in a thread in which a context is already opened, the second
|
15
|
+
# context will "mask" the old context e.g.
|
16
|
+
#
|
17
|
+
# six = 6
|
18
|
+
# Context.new do |cxt|
|
19
|
+
# cxt['num'] = 5
|
20
|
+
# cxt.eval('num') # => 5
|
21
|
+
# Context.new do |cxt|
|
22
|
+
# cxt['num'] = 10
|
23
|
+
# cxt.eval('num') # => 10
|
24
|
+
# cxt.eval('++num') # => 11
|
25
|
+
# end
|
26
|
+
# cxt.eval('num') # => 5
|
27
|
+
# end
|
28
|
+
class Context
|
29
|
+
include V8::Error::Try
|
30
|
+
|
31
|
+
# @!attribute [r] conversion
|
32
|
+
# @return [V8::Conversion] conversion behavior for this context
|
33
|
+
attr_reader :conversion
|
34
|
+
|
35
|
+
# @!attrribute [r] access
|
36
|
+
# @return [V8::Access] Ruby access behavior for this context
|
37
|
+
attr_reader :access
|
38
|
+
|
39
|
+
# @!attribute [r] native
|
40
|
+
# @return [V8::C::Context] the underlying C++ object
|
41
|
+
attr_reader :native
|
42
|
+
|
43
|
+
# @!attribute [r] timeout
|
44
|
+
# @return [Number] maximum execution time in milliseconds for scripts executed in this context
|
45
|
+
attr_reader :timeout
|
46
|
+
|
47
|
+
# Creates a new context.
|
48
|
+
#
|
49
|
+
# If passed the `:with` option, that object will be used as
|
50
|
+
# the global scope of the newly creating context. e.g.
|
51
|
+
#
|
52
|
+
# scope = Object.new
|
53
|
+
# def scope.hello; "Hi"; end
|
54
|
+
# V8::Context.new(:with => scope) do |cxt|
|
55
|
+
# cxt['hello'] #=> 'Hi'
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# If passed the `:timeout` option, every eval will timeout once
|
59
|
+
# N milliseconds elapse
|
60
|
+
#
|
61
|
+
# @param [Hash<Symbol, Object>] options initial context configuration
|
62
|
+
# * :with scope serves as the global scope of the new context
|
63
|
+
# @yield [V8::Context] the newly created context
|
64
|
+
def initialize(options = {})
|
65
|
+
@conversion = Conversion.new
|
66
|
+
@access = Access.new
|
67
|
+
@timeout = options[:timeout]
|
68
|
+
if global = options[:with]
|
69
|
+
Context.new.enter do
|
70
|
+
global_template = global.class.to_template.InstanceTemplate()
|
71
|
+
@native = V8::C::Context::New(nil, global_template)
|
72
|
+
end
|
73
|
+
enter {link global, @native.Global()}
|
74
|
+
else
|
75
|
+
V8::C::Locker() do
|
76
|
+
@native = V8::C::Context::New()
|
77
|
+
end
|
78
|
+
end
|
79
|
+
yield self if block_given?
|
80
|
+
end
|
81
|
+
|
82
|
+
# Compile and execute a string of JavaScript source.
|
83
|
+
#
|
84
|
+
# If `source` is an IO object it will be read fully before being evaluated
|
85
|
+
#
|
86
|
+
# @param [String,IO] source the source code to compile and execute
|
87
|
+
# @param [String] filename the name to use for this code when generating stack traces
|
88
|
+
# @param [Integer] line the line number to start with
|
89
|
+
# @return [Object] the result of the evaluation
|
90
|
+
def eval(source, filename = '<eval>', line = 1)
|
91
|
+
if IO === source || StringIO === source
|
92
|
+
source = source.read
|
93
|
+
end
|
94
|
+
enter do
|
95
|
+
script = try { V8::C::Script::New(source.to_s, filename.to_s) }
|
96
|
+
if @timeout
|
97
|
+
to_ruby try {script.RunWithTimeout(@timeout)}
|
98
|
+
else
|
99
|
+
to_ruby try {script.Run()}
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Read a value from the global scope of this context
|
105
|
+
#
|
106
|
+
# @param [Object] key the name of the value to read
|
107
|
+
# @return [Object] value the value at `key`
|
108
|
+
def [](key)
|
109
|
+
enter do
|
110
|
+
to_ruby(@native.Global().Get(to_v8(key)))
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Binds `value` to the name `key` in the global scope of this context.
|
115
|
+
#
|
116
|
+
# @param [Object] key the name to bind to
|
117
|
+
# @param [Object] value the value to bind
|
118
|
+
def []=(key, value)
|
119
|
+
enter do
|
120
|
+
@native.Global().Set(to_v8(key), to_v8(value))
|
121
|
+
end
|
122
|
+
return value
|
123
|
+
end
|
124
|
+
|
125
|
+
# Destroy this context and release any internal references it may
|
126
|
+
# contain to embedded Ruby objects.
|
127
|
+
#
|
128
|
+
# A disposed context may never again be used for anything, and all
|
129
|
+
# objects created with it will become unusable.
|
130
|
+
def dispose
|
131
|
+
return unless @native
|
132
|
+
@native.Dispose()
|
133
|
+
@native = nil
|
134
|
+
V8::C::V8::ContextDisposedNotification()
|
135
|
+
def self.enter
|
136
|
+
fail "cannot enter a context which has already been disposed"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Returns this context's global object. This will be a `V8::Object`
|
141
|
+
# if no scope was provided or just an `Object` if a Ruby object
|
142
|
+
# is serving as the global scope.
|
143
|
+
#
|
144
|
+
# @return [Object] scope the context's global scope.
|
145
|
+
def scope
|
146
|
+
enter { to_ruby @native.Global() }
|
147
|
+
end
|
148
|
+
|
149
|
+
# Converts a v8 C++ object into its ruby counterpart. This is method
|
150
|
+
# is used to translate all values passed to Ruby from JavaScript, either
|
151
|
+
# as return values or as callback parameters.
|
152
|
+
#
|
153
|
+
# @param [V8::C::Object] v8_object the native c++ object to convert.
|
154
|
+
# @return [Object] to pass to Ruby
|
155
|
+
# @see V8::Conversion for how to customize and extend this mechanism
|
156
|
+
def to_ruby(v8_object)
|
157
|
+
@conversion.to_ruby(v8_object)
|
158
|
+
end
|
159
|
+
|
160
|
+
# Converts a Ruby object into a native v8 C++ object. This method is
|
161
|
+
# used to translate all values passed to JavaScript from Ruby, either
|
162
|
+
# as return value or as callback parameters.
|
163
|
+
#
|
164
|
+
# @param [Object] ruby_object the Ruby object to convert
|
165
|
+
# @return [V8::C::Object] to pass to V8
|
166
|
+
# @see V8::Conversion for customizing and extending this mechanism
|
167
|
+
def to_v8(ruby_object)
|
168
|
+
@conversion.to_v8(ruby_object)
|
169
|
+
end
|
170
|
+
|
171
|
+
# Marks a Ruby object and a v8 C++ Object as being the same. In other
|
172
|
+
# words whenever `ruby_object` is passed to v8, the result of the
|
173
|
+
# conversion should be `v8_object`. Conversely, whenever `v8_object`
|
174
|
+
# is passed to Ruby, the result of the conversion should be `ruby_object`.
|
175
|
+
# The Ruby Racer uses this mechanism to maintain referential integrity
|
176
|
+
# between Ruby and JavaScript peers
|
177
|
+
#
|
178
|
+
# @param [Object] ruby_object the Ruby half of the object identity
|
179
|
+
# @param [V8::C::Object] v8_object the V8 half of the object identity.
|
180
|
+
# @see V8::Conversion::Identity
|
181
|
+
def link(ruby_object, v8_object)
|
182
|
+
@conversion.equate ruby_object, v8_object
|
183
|
+
end
|
184
|
+
|
185
|
+
# Links `ruby_object` and `v8_object` inside the currently entered
|
186
|
+
# context. This is an error if no context has been entered.
|
187
|
+
#
|
188
|
+
# @param [Object] ruby_object the Ruby half of the object identity
|
189
|
+
# @param [V8::C::Object] v8_object the V8 half of the object identity.
|
190
|
+
def self.link(ruby_object, v8_object)
|
191
|
+
current.link ruby_object, v8_object
|
192
|
+
end
|
193
|
+
|
194
|
+
# Run some Ruby code in the context of this context.
|
195
|
+
#
|
196
|
+
# This will acquire the V8 interpreter lock (possibly blocking
|
197
|
+
# until it is available), and prepare V8 for JavaScript execution.
|
198
|
+
#
|
199
|
+
# Only one context may be running at a time per thread.
|
200
|
+
#
|
201
|
+
# @return [Object] the result of executing `block`
|
202
|
+
def enter(&block)
|
203
|
+
if !entered?
|
204
|
+
lock_scope_and_enter(&block)
|
205
|
+
else
|
206
|
+
yield
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# Indicates if this context is the currently entered context
|
211
|
+
#
|
212
|
+
# @return true if this context is currently entered
|
213
|
+
def entered?
|
214
|
+
Context.current == self
|
215
|
+
end
|
216
|
+
|
217
|
+
# Get the currently entered context.
|
218
|
+
#
|
219
|
+
# @return [V8::Context] currently entered context, nil if none entered.
|
220
|
+
def self.current
|
221
|
+
Thread.current[:v8_context]
|
222
|
+
end
|
223
|
+
|
224
|
+
# Compile and execute the contents of the file with path `filename`
|
225
|
+
# as JavaScript code.
|
226
|
+
#
|
227
|
+
# @param [String] filename path to the file to execute.
|
228
|
+
# @return [Object] the result of the evaluation.
|
229
|
+
def load(filename)
|
230
|
+
File.open(filename) do |file|
|
231
|
+
self.eval file, filename
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
private
|
236
|
+
|
237
|
+
def self.current=(context)
|
238
|
+
Thread.current[:v8_context] = context
|
239
|
+
end
|
240
|
+
|
241
|
+
def lock_scope_and_enter
|
242
|
+
current = Context.current
|
243
|
+
Context.current = self
|
244
|
+
V8::C::Locker() do
|
245
|
+
V8::C::HandleScope() do
|
246
|
+
begin
|
247
|
+
@native.Enter()
|
248
|
+
yield if block_given?
|
249
|
+
ensure
|
250
|
+
@native.Exit()
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
ensure
|
255
|
+
Context.current = current
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|