therubyracer 0.4.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of therubyracer might be problematic. Click here for more details.

@@ -0,0 +1,31 @@
1
+ #ifndef __v8_standalone_h__
2
+ #define __v8_standalone_h__
3
+
4
+ #include "ruby.h"
5
+
6
+ /**
7
+ * interned symbol for "call"
8
+ */
9
+ extern VALUE ruby_call_symbol;
10
+
11
+ /**
12
+ * id for respond_to?
13
+ */
14
+ extern VALUE ruby_respond_to_ID;
15
+
16
+ extern VALUE ruby_proc_class;
17
+ extern VALUE ruby_method_class;
18
+
19
+
20
+ /**
21
+ * Determine whether or not a value can respond to "call".
22
+ * i.e. is this Proc/Method object?
23
+ */
24
+ bool is_callable(VALUE& object);
25
+
26
+ /**
27
+ * Debugging aid. Println debugging goo for VALUES.
28
+ */
29
+ VALUE v8_what_is_this(VALUE self, VALUE object);
30
+
31
+ #endif
data/ext/v8/v8_str.cpp ADDED
@@ -0,0 +1,17 @@
1
+
2
+ #include "v8_str.h"
3
+ #include "v8.h"
4
+ #include "v8_ref.h"
5
+
6
+ using namespace v8;
7
+
8
+ VALUE v8_str_new(VALUE clazz, VALUE str) {
9
+ HandleScope handles;
10
+ return V8_Ref_Create(clazz, String::New(RSTRING(str)->ptr));
11
+ }
12
+
13
+ VALUE v8_str_to_s(VALUE self){
14
+ HandleScope handles;
15
+ Local<String> str = V8_Ref_Get<String>(self);
16
+ return rb_str_new2(*String::AsciiValue(str));
17
+ }
data/ext/v8/v8_str.h ADDED
@@ -0,0 +1,9 @@
1
+ #ifndef _RUBY_V8_STR_
2
+ #define _RUBY_V8_STR_
3
+
4
+ #include "ruby.h"
5
+
6
+ VALUE v8_str_new(VALUE clazz, VALUE str);
7
+ VALUE v8_str_to_s(VALUE self);
8
+
9
+ #endif
@@ -0,0 +1,55 @@
1
+ #include <ruby.h>
2
+ #include <v8.h>
3
+ #include "v8_ref.h"
4
+ #include "v8_func.h"
5
+ #include "v8_template.h"
6
+ #include "converters.h"
7
+
8
+ using namespace v8;
9
+
10
+ Handle<Value> RubyInvocationCallback(const Arguments& args) {
11
+ VALUE code = (VALUE)External::Unwrap(args.Data());
12
+ if (NIL_P(code)) {
13
+ return Null();
14
+ } else {
15
+ VALUE* arguments = new VALUE[args.Length()];
16
+ for(int c=0;c<args.Length(); ++c) {
17
+ Handle<Value> val = args[c];
18
+ arguments[c] = V82RB(val);
19
+ }
20
+
21
+ VALUE result = rb_funcall2(code, rb_intern("call"), args.Length(), arguments);
22
+ delete [] arguments;
23
+
24
+ Handle<Value> convertedResult = RB2V8(result);
25
+ return convertedResult ;
26
+ }
27
+ }
28
+
29
+ VALUE v8_Template_Set(VALUE self, VALUE name, VALUE value) {
30
+ HandleScope handles;
31
+ Local<Template> tmpl = V8_Ref_Get<Template>(self);
32
+ Local<Data> data = V8_Ref_Get<Data>(value);
33
+ tmpl->Set(RSTRING(name)->ptr, data);
34
+ return Qnil;
35
+ }
36
+
37
+ VALUE v8_ObjectTemplate_New(VALUE clazz) {
38
+ HandleScope handles;
39
+ return V8_Ref_Create(clazz, ObjectTemplate::New());
40
+ }
41
+
42
+ VALUE v8_FunctionTemplate_New(int argc, VALUE *argv, VALUE self) {
43
+ VALUE code;
44
+ rb_scan_args(argc, argv, "00&", &code);
45
+ HandleScope handles;
46
+ Local<FunctionTemplate> t = FunctionTemplate::New(RubyInvocationCallback, External::Wrap((void *)code));
47
+ return V8_Ref_Create(self,t,code);
48
+ }
49
+
50
+ VALUE v8_FunctionTemplate_GetFunction(VALUE self) {
51
+ HandleScope handles;
52
+ Local<FunctionTemplate> t = V8_Ref_Get<FunctionTemplate>(self);
53
+ return V8_Wrap_Function(t->GetFunction());
54
+ }
55
+
@@ -0,0 +1,11 @@
1
+ #ifndef _RUBY_V8_TEMPLATE_
2
+ #define _RUBY_V8_TEMPLATE_
3
+
4
+ VALUE v8_Template_Set(VALUE self, VALUE name, VALUE value);
5
+
6
+ VALUE v8_ObjectTemplate_New(VALUE clazz);
7
+
8
+ VALUE v8_FunctionTemplate_New(int argc, VALUE *argv, VALUE self);
9
+ VALUE v8_FunctionTemplate_GetFunction(VALUE self);
10
+
11
+ #endif
data/lib/v8.rb ADDED
@@ -0,0 +1,10 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ module V8
5
+ VERSION = '0.4.0'
6
+ require 'v8/v8' #native glue
7
+ require 'v8/to'
8
+ require 'v8/context'
9
+ require 'v8/object'
10
+ end
data/lib/v8/context.rb ADDED
@@ -0,0 +1,41 @@
1
+ module V8
2
+ class Context
3
+ def initialize
4
+ @native = C::Context.new
5
+ end
6
+
7
+ def open(&block)
8
+ @native.open do
9
+ block.call(self).tap do |result|
10
+ raise JavascriptError.new(result) if result.kind_of?(C::Message)
11
+ end
12
+ end if block_given?
13
+ end
14
+
15
+ def eval(javascript)
16
+ To.ruby @native.eval(javascript)
17
+ end
18
+
19
+ def evaluate(*args)
20
+ self.eval(*args)
21
+ end
22
+
23
+ def []=(key, value)
24
+ value.tap do
25
+ @native.Global().tap do |scope|
26
+ scope.Set(key.to_s, value)
27
+ end
28
+ end
29
+ end
30
+
31
+ def self.open(&block)
32
+ new.open(&block)
33
+ end
34
+ end
35
+
36
+ class ContextError < StandardError
37
+ end
38
+ class JavascriptError < StandardError
39
+
40
+ end
41
+ end
data/lib/v8/object.rb ADDED
@@ -0,0 +1,12 @@
1
+
2
+ module V8
3
+ class Object
4
+ def initialize(native)
5
+ @native = native
6
+ end
7
+
8
+ def [](key)
9
+ To.ruby(@native.Get(key))
10
+ end
11
+ end
12
+ end
data/lib/v8/to.rb ADDED
@@ -0,0 +1,25 @@
1
+
2
+ module V8
3
+ module To
4
+ class << self
5
+ def ruby(value)
6
+ case value
7
+ when V8::C::Object then V8::Object.new(value)
8
+ when V8::C::String then "Wonkers!"
9
+ else
10
+ value
11
+ end
12
+ end
13
+
14
+ def v8(value)
15
+ case value
16
+ when String then C::String.new(value)
17
+ when Proc then C::FunctionTemplate.new(&value).GetFunction()
18
+ when Method then C::FunctionTemplate.new(&value.to_proc).GetFunction()
19
+ else
20
+ value
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
data/script/console ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # File: script/console
3
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
4
+
5
+ libs = " -r irb/completion"
6
+ # Perhaps use a console_lib to store any extra methods I may want available in the cosole
7
+ # libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
8
+ libs << " -I #{File.dirname(__FILE__) + '/../lib'} -r v8"
9
+ puts "Revving Up The Ruby Racer!"
10
+ exec "#{irb} #{libs} --simple-prompt"
data/script/destroy ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/destroy'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Destroy.new.run(ARGV)
data/script/generate ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/generate'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Generate.new.run(ARGV)
@@ -0,0 +1,8 @@
1
+ Rspecs for a Ruby interface to javascript.
2
+
3
+ This is useful for different javascript interpreters wanting to present a unified interface to ruby.
4
+
5
+ Currently used by
6
+ The Ruby Rhino: http://github.com/cowboyd/therubyrhino
7
+ The Ruby Racer: http://github.com/cowboyd/therubyracer
8
+
@@ -0,0 +1,376 @@
1
+ require 'rubygems'
2
+ require "#{File.dirname(__FILE__)}/../redjs_helper.rb"
3
+
4
+ describe "Ruby Javascript API" do
5
+
6
+ describe "Basic Evaluation" do
7
+ it "can evaluate some javascript" do
8
+ Context.open do |cxt|
9
+ cxt.eval("5 + 3").should == 8
10
+ end
11
+ end
12
+
13
+ it "can pass back null to ruby" do
14
+ Context.open do |cxt|
15
+ cxt.eval("null").should be_nil
16
+ end
17
+ end
18
+
19
+ it "can pass back undefined to ruby" do
20
+ Context.open do |cxt|
21
+ cxt.eval("this.undefined").should be_nil
22
+ end
23
+ end
24
+
25
+ it "can pass the empty string back to ruby" do
26
+ eval("''").should == ""
27
+ end
28
+
29
+ it "can pass doubles back to ruby" do
30
+ eval("2.5").should == 2.5
31
+ end
32
+
33
+ it "can pass fixed numbers back to ruby" do
34
+ eval("1").should == 1
35
+ end
36
+
37
+ it "can pass boolean values back to ruby" do
38
+ eval("true").should be(true)
39
+ eval("false").should be(false)
40
+ end
41
+
42
+ it "treats nil and the empty string as the same thing when it comes to eval" do
43
+ Context.open do |cxt|
44
+ cxt.eval(nil).should == cxt.eval('')
45
+ end
46
+ end
47
+
48
+ it "can pass back strings to ruby" do
49
+ Context.open do |cxt|
50
+ cxt['foo'] = "Hello World"
51
+ cxt.eval("foo").should == "Hello World"
52
+ end
53
+ end
54
+
55
+ it "can pass back very long strings to ruby" do
56
+ lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis faucibus, diam vel pellentesque aliquet, nisl sapien molestie eros, vitae vehicula libero massa vel neque. Phasellus tempor pharetra ipsum vel venenatis. Quisque vitae nisl vitae quam mattis pellentesque et in sapien. Sed at lectus quis eros pharetra feugiat non ac neque. Vivamus lacus eros, feugiat at volutpat at, viverra id nisl. Vivamus ac dolor eleifend libero venenatis pharetra ut iaculis arcu. Donec neque nibh, vehicula non porta a, consectetur eu erat. Sed eleifend, metus vel euismod placerat, lectus lectus sollicitudin nisl, ac elementum sem quam nec dolor. In hac habitasse platea dictumst. Proin vitae suscipit orci. Suspendisse a ipsum vel lorem tempus scelerisque et vitae neque. Proin sodales, tellus sit amet consequat cursus, odio massa ultricies enim, eu fermentum velit lectus in lacus. Quisque eu porttitor diam. Nunc felis purus, facilisis non tristique ac, pulvinar nec nulla. Duis dolor risus, egestas nec tristique ac, ullamcorper cras amet."
57
+ Context.open do |cxt|
58
+ cxt.eval("'#{lorem}'").should == lorem
59
+ end
60
+ end
61
+
62
+ it "can pass objects back to ruby" do
63
+ Context.open do |cxt|
64
+ cxt.eval("({foo: 'bar', baz: 'bang', '5': 5, embedded: {badda: 'bing'}})").tap do |object|
65
+ object.should_not be_nil
66
+ object['foo'].should == 'bar'
67
+ object['baz'].should == 'bang'
68
+ object['5'].should == 5
69
+ object['embedded'].tap do |embedded|
70
+ embedded.should_not be_nil
71
+ embedded['badda'].should == 'bing'
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ it "unwraps ruby objects returned by embedded ruby code to maintain referential integrity" do
78
+ pending
79
+ mock(:object).tap do |o|
80
+ Context.open do |cxt|
81
+ cxt['get'] = lambda {o}
82
+ cxt.eval('get()').should be(o)
83
+ end
84
+ end
85
+ end
86
+
87
+ it "won't let you do some operations unless the context is open" do
88
+ pending "I'm not sure about this requirement"
89
+ Context.new.tap do |closed|
90
+ lambda {closed.eval('1')}.should raise_error(ContextError)
91
+ end
92
+ end
93
+ end
94
+
95
+ describe "Calling Ruby Code From Within Javascript" do
96
+
97
+ before(:each) do
98
+ @class = Class.new
99
+ @instance = @class.new
100
+ end
101
+
102
+ it "can embed a closure into a context and call it" do
103
+ Context.open do |cxt|
104
+ cxt["say"] = C::FunctionTemplate.new {|word, times| word * times}
105
+ cxt.eval("say('Hello',2)").should == "HelloHello"
106
+ end
107
+ end
108
+
109
+ it "can embed a ruby object into a context and call its methods" do
110
+ class_eval do
111
+ def say_hello(to)
112
+ "Hello #{to}!"
113
+ end
114
+ end
115
+ evaljs('o.say_hello("Gracie")').should == "Hello Gracie!"
116
+ end
117
+
118
+ it "can call a bound ruby method" do
119
+ five = class_eval do
120
+ def initialize(lhs)
121
+ @lhs = lhs
122
+ end
123
+ def times(rhs)
124
+ @lhs * rhs
125
+ end
126
+ new(5)
127
+ end
128
+ Context.open do |cxt|
129
+ cxt['timesfive'] = five.method(:times)
130
+ cxt.eval('timesfive(3)').should == 15
131
+ end
132
+ end
133
+
134
+ it "can call public locally defined ruby methods" do
135
+ class_eval do
136
+ def voo
137
+ "doo"
138
+ end
139
+ end
140
+ evaljs("o.voo").should_not be_nil
141
+ evaljs("o.voo()").should == "doo"
142
+ end
143
+
144
+ it "translates ruby naming conventions into javascript naming conventions, but you can still access them by their original names" do
145
+ class_eval do
146
+ def my_special_method
147
+ "hello"
148
+ end
149
+ end
150
+ evaljs("o.mySpecialMethod").should_not be_nil
151
+ evaljs("o.mySpecialMethod()").should == "hello"
152
+ evaljs("o.my_special_method").should_not be_nil
153
+ evaljs("o.my_special_method()").should == "hello"
154
+ end
155
+
156
+ it "hides methods not defined directly on this instance's class" do
157
+ class_eval do
158
+ def bar
159
+ end
160
+ end
161
+ evaljs("o.to_s").should be_nil
162
+ end
163
+
164
+ it "translated camel case properties are enumerated by default, but perl case are not" do
165
+ class_eval do
166
+ def foo_bar
167
+ end
168
+
169
+ def baz_bang
170
+ end
171
+ end
172
+ pending "why the hell isn't the return value of getIds() being respected?!?"
173
+ evaljs(<<-EOJS).should == ["fooBar,bazBang"]
174
+ var names = [];
175
+ for (var p in o) {
176
+ names.push(p);
177
+ }
178
+ names;
179
+ EOJS
180
+ end
181
+
182
+ it "will see a method that appears after the wrapper was first created" do
183
+ Context.open do |cxt|
184
+ cxt['o'] = @instance
185
+ class_eval do
186
+ def bar
187
+ "baz!"
188
+ end
189
+ end
190
+ cxt.eval("o.bar").should_not be_nil
191
+ cxt.eval("o.bar()").should == "baz!"
192
+ end
193
+ end
194
+
195
+ it "treats ruby methods that have an arity of 0 as javascript properties by default"
196
+
197
+ it "will call ruby accesssor function when setting a property from javascript"
198
+
199
+ def evaljs(str)
200
+ Context.open do |cxt|
201
+ cxt['puts'] = lambda {|o| puts o.inspect}
202
+ cxt['o'] = @instance
203
+ cxt.eval(str)
204
+ end
205
+ end
206
+
207
+ def class_eval(&body)
208
+ @class.class_eval &body
209
+ end
210
+
211
+ end
212
+
213
+ describe "Setting up the Host Environment" do
214
+ it "can eval javascript with a given ruby object as the scope." do
215
+ scope = Class.new.class_eval do
216
+ def plus(lhs, rhs)
217
+ lhs + rhs
218
+ end
219
+
220
+ def minus(lhs, rhs)
221
+ lhs - rhs
222
+ end
223
+
224
+ new
225
+ end
226
+
227
+ Context.open(:with => scope) do |cxt|
228
+ cxt.eval("plus(1,2)").should == 3
229
+ cxt.eval("minus(10, 20)").should == -10
230
+ cxt.eval("this").should be(scope)
231
+ end
232
+ end
233
+
234
+ it "can directly embed ruby values into javascript" do
235
+ Context.open do |cxt|
236
+ cxt["bar"] = 9
237
+ cxt['foo'] = "bar"
238
+ cxt['num'] = 3.14
239
+ cxt['trU'] = true
240
+ cxt['falls'] = false
241
+ cxt.eval("bar + 10").should be(19)
242
+ cxt.eval('foo').should == "bar"
243
+ cxt.eval('num').should == 3.14
244
+ cxt.eval('trU').should be(true)
245
+ cxt.eval('falls').should be(false)
246
+ end
247
+ end
248
+
249
+
250
+ it "extends object to allow for the arbitrary execution of javascript with any object as the scope" do
251
+ Class.new.class_eval do
252
+
253
+ def initialize
254
+ @lhs = 5
255
+ end
256
+
257
+ def timesfive(rhs)
258
+ @lhs * rhs
259
+ end
260
+
261
+ new.eval_js("timesfive(6)").should == 30
262
+ end
263
+ end
264
+
265
+ it "can limit the number of instructions that are executed in the context" do
266
+ lambda {
267
+ Context.open do |cxt|
268
+ cxt.instruction_limit = 100 * 1000
269
+ timeout(1) do
270
+ cxt.eval('while (true);')
271
+ end
272
+ end
273
+ }.should raise_error(Rhino::RunawayScriptError)
274
+ end
275
+
276
+ it "has a private constructor" do
277
+ lambda {
278
+ Context.new(nil)
279
+ }.should raise_error
280
+ end
281
+ end
282
+
283
+ describe "loading javascript source into the interpreter" do
284
+
285
+ it "can take an IO object in the eval method instead of a string" do
286
+ source = StringIO.new(<<-EOJS)
287
+ /*
288
+ * we want to have a fairly verbose function so that we can be assured tha
289
+ * we overflow the buffer size so that we see that the reader is chunking
290
+ * it's payload in at least several fragments.
291
+ *
292
+ * That's why we're wasting space here
293
+ */
294
+ function five() {
295
+ return 5
296
+ }
297
+ foo = 'bar'
298
+ five();
299
+ EOJS
300
+ Context.open do |cxt|
301
+ cxt.eval(source, "StringIO").should == 5
302
+ cxt['foo'].should == "bar"
303
+ end
304
+ end
305
+
306
+ it "can load a file into the runtime" do
307
+ mock(:JavascriptSourceFile).tap do |file|
308
+ File.should_receive(:open).with("path/to/mysource.js").and_yield(file)
309
+ Context.open do |cxt|
310
+ cxt.should_receive(:evaluate).with(file, "path/to/mysource.js", 1)
311
+ cxt.load("path/to/mysource.js")
312
+ end
313
+ end
314
+
315
+ end
316
+ end
317
+
318
+ describe "A Javascript Object Reflected Into Ruby" do
319
+
320
+ before(:each) do
321
+ @o = Context.open do |cxt|
322
+ @cxt = cxt
323
+ cxt.eval("o = new Object(); o")
324
+ end
325
+ end
326
+
327
+ def evaljs(js)
328
+ @cxt.open do
329
+ @cxt.eval(js)
330
+ end
331
+ end
332
+
333
+ it "can have its properties manipulated via ruby style [] hash access" do
334
+ @o["foo"] = 'bar'
335
+ evaljs('o.foo').should == "bar"
336
+ evaljs('o.blue = "blam"')
337
+ @o["blue"].should == "blam"
338
+ end
339
+
340
+ it "doesn't matter if you use a symbol or a string to set a value" do
341
+ @o[:foo] = "bar"
342
+ @o['foo'].should == "bar"
343
+ @o['baz'] = "bang"
344
+ @o[:baz].should == "bang"
345
+ end
346
+
347
+ it "returns nil when the value is null, null, or not defined" do
348
+ @o[:foo].should be_nil
349
+ end
350
+
351
+ it "traverses the prototype chain when hash accessing properties from the ruby object" do
352
+ Context.open do |cxt|
353
+ cxt.eval(<<EOJS)['bar'].should == "baz"
354
+ function Foo() {}
355
+ Foo.prototype.bar = 'baz'
356
+ new Foo()
357
+ EOJS
358
+ end
359
+ end
360
+
361
+ it "is enumenable" do
362
+ evaljs("o.foo = 'bar'; o.bang = 'baz'; o[5] = 'flip'")
363
+ @o.inject({}) {|i,p| k,v = p; i.tap {i[k] = v}}.should == {"foo" => 'bar', "bang" => 'baz', 5 => 'flip'}
364
+ end
365
+ end
366
+
367
+ describe "Exception Handling" do
368
+ it "raises javascript exceptions as ruby exceptions" do
369
+ lambda {
370
+ Context.open do |cxt|
371
+ cxt.eval('foo')
372
+ end
373
+ }.should raise_error(JavascriptError)
374
+ end
375
+ end
376
+ end