johnson 1.1.2 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +12 -0
- data/Manifest.txt +6 -4
- data/README.rdoc +2 -8
- data/Rakefile +11 -11
- data/bin/johnson +1 -3
- data/ext/spidermonkey/context.c +3 -2
- data/ext/spidermonkey/conversions.c +61 -27
- data/ext/spidermonkey/conversions.h +13 -0
- data/ext/spidermonkey/debugger.c +13 -5
- data/ext/spidermonkey/debugger.h +1 -0
- data/ext/spidermonkey/extconf.rb +2 -3
- data/ext/spidermonkey/jroot.h +11 -1
- data/ext/spidermonkey/js_land_proxy.c +21 -11
- data/ext/spidermonkey/ruby_land_proxy.c +116 -41
- data/ext/spidermonkey/ruby_land_proxy.h +21 -0
- data/ext/spidermonkey/runtime.c +85 -19
- data/ext/spidermonkey/runtime.h +2 -0
- data/ext/spidermonkey/spidermonkey.c +3 -1
- data/lib/johnson.rb +19 -27
- data/lib/johnson/cli.rb +2 -1
- data/{js/johnson → lib/johnson/js}/cli.js +0 -0
- data/lib/johnson/js/core.js +34 -0
- data/lib/johnson/js/prelude.js +149 -0
- data/lib/johnson/ruby_land_proxy.rb +113 -0
- data/lib/johnson/runtime.rb +92 -33
- data/lib/johnson/spidermonkey.rb +12 -0
- data/lib/johnson/spidermonkey/js_land_proxy.rb +10 -8
- data/lib/johnson/spidermonkey/ruby_land_proxy.rb +10 -47
- data/lib/johnson/spidermonkey/runtime.rb +11 -31
- data/test/johnson/conversions/array_test.rb +41 -3
- data/test/johnson/conversions/string_test.rb +12 -0
- data/test/johnson/custom_conversions_test.rb +50 -0
- data/test/johnson/prelude_test.rb +23 -0
- data/test/johnson/runtime_test.rb +82 -2
- data/test/johnson/spidermonkey/ruby_land_proxy_test.rb +17 -1
- data/test/johnson/spidermonkey/runtime_test.rb +24 -0
- data/vendor/spidermonkey/jsprf.c +2 -0
- metadata +22 -9
- data/js/johnson/prelude.js +0 -80
- data/lib/johnson/version.rb +0 -3
- data/lib/rails/init.rb +0 -37
@@ -0,0 +1,12 @@
|
|
1
|
+
|
2
|
+
# the native SpiderMonkey extension
|
3
|
+
require "johnson/spidermonkey/spidermonkey"
|
4
|
+
|
5
|
+
# the SpiderMonkey bits written in Ruby
|
6
|
+
require "johnson/spidermonkey/runtime"
|
7
|
+
require "johnson/spidermonkey/context"
|
8
|
+
require "johnson/spidermonkey/js_land_proxy"
|
9
|
+
require "johnson/spidermonkey/ruby_land_proxy"
|
10
|
+
require "johnson/spidermonkey/mutable_tree_visitor"
|
11
|
+
require "johnson/spidermonkey/debugger"
|
12
|
+
require "johnson/spidermonkey/immutable_node"
|
@@ -1,3 +1,13 @@
|
|
1
|
+
# FIXME: This definition should probably be somewhere else
|
2
|
+
class Array
|
3
|
+
def js_property?(name)
|
4
|
+
name == :length
|
5
|
+
end
|
6
|
+
def js_properties
|
7
|
+
(0...size).to_a
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
1
11
|
module Johnson
|
2
12
|
module SpiderMonkey
|
3
13
|
module JSLandProxy #:nodoc:
|
@@ -16,14 +26,6 @@ module Johnson
|
|
16
26
|
(target.respond_to?(:js_property?) && target.__send__(:js_property?, name))
|
17
27
|
end
|
18
28
|
|
19
|
-
def self.call_proc_by_oid(oid, *args)
|
20
|
-
id2ref(oid).call(*args)
|
21
|
-
end
|
22
|
-
|
23
|
-
def self.id2ref(oid)
|
24
|
-
ObjectSpace._id2ref(oid)
|
25
|
-
end
|
26
|
-
|
27
29
|
def self.autovivified(target, attribute)
|
28
30
|
target.send(:__johnson_js_properties)[attribute]
|
29
31
|
end
|
@@ -1,53 +1,16 @@
|
|
1
1
|
module Johnson #:nodoc:
|
2
2
|
module SpiderMonkey #:nodoc:
|
3
|
-
class RubyLandProxy # native
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
class RubyLandProxy < Johnson::RubyLandProxy # native
|
4
|
+
module Callable
|
5
|
+
def call_using(this, *args)
|
6
|
+
native_call(this, *args)
|
7
|
+
end
|
8
8
|
end
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
alias_method :to_ary, :to_a
|
15
|
-
|
16
|
-
def to_proc
|
17
|
-
@proc ||= Proc.new { |*args| call(*args) }
|
18
|
-
end
|
19
|
-
|
20
|
-
def call(*args)
|
21
|
-
call_using(runtime.global, *args)
|
22
|
-
end
|
23
|
-
|
24
|
-
def call_using(this, *args)
|
25
|
-
native_call(this, *args)
|
26
|
-
end
|
27
|
-
|
28
|
-
def inspect
|
29
|
-
toString
|
30
|
-
end
|
31
|
-
|
32
|
-
def method_missing(sym, *args, &block)
|
33
|
-
args << block if block_given?
|
34
|
-
|
35
|
-
name = sym.to_s
|
36
|
-
assignment = "=" == name[-1, 1]
|
37
|
-
|
38
|
-
# default behavior if the slot's not there
|
39
|
-
return super unless assignment || respond_to?(sym)
|
40
|
-
|
41
|
-
unless function_property?(name)
|
42
|
-
# for arity 0, treat it as a get
|
43
|
-
return self[name] if args.empty?
|
44
|
-
|
45
|
-
# arity 1 and quacking like an assignment, treat it as a set
|
46
|
-
return self[name[0..-2]] = args[0] if assignment && 1 == args.size
|
47
|
-
end
|
48
|
-
|
49
|
-
# okay, must really be a function
|
50
|
-
call_function_property(name, *args)
|
9
|
+
end
|
10
|
+
class RubyLandScript < Johnson::SpiderMonkey::RubyLandProxy # native
|
11
|
+
def break(linenum, &block)
|
12
|
+
runtime.set_trap(self, linenum, block)
|
13
|
+
runtime.traps << [self, linenum]
|
51
14
|
end
|
52
15
|
end
|
53
16
|
end
|
@@ -1,15 +1,15 @@
|
|
1
1
|
module Johnson #:nodoc:
|
2
2
|
module SpiderMonkey #:nodoc:
|
3
|
-
class Runtime # native
|
3
|
+
class Runtime < Johnson::Runtime # native
|
4
4
|
CONTEXT_MAP_KEY = :johnson_context_map
|
5
5
|
|
6
|
+
attr_reader :traps
|
6
7
|
def initialize(options={})
|
7
8
|
@debugger = nil
|
8
|
-
@compiled_scripts = {}
|
9
9
|
@gcthings = {}
|
10
10
|
@traps = []
|
11
11
|
initialize_native(options)
|
12
|
-
|
12
|
+
super()
|
13
13
|
end
|
14
14
|
|
15
15
|
# called from js_land_proxy.c:make_js_land_proxy
|
@@ -22,29 +22,19 @@ module Johnson #:nodoc:
|
|
22
22
|
@gcthings.delete(object_id) if defined? @gcthings
|
23
23
|
end
|
24
24
|
|
25
|
+
def debugger?
|
26
|
+
not @debugger.nil?
|
27
|
+
end
|
25
28
|
|
26
29
|
def current_context
|
27
30
|
contexts = (Thread.current[CONTEXT_MAP_KEY] ||= {})
|
28
31
|
contexts[self.object_id] ||= Context.new(self)
|
29
32
|
end
|
30
33
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
def []=(key, value)
|
36
|
-
global[key] = value
|
37
|
-
end
|
38
|
-
|
39
|
-
###
|
40
|
-
# Evaluate +script+ with +filename+ and +linenum+
|
41
|
-
def evaluate(script, filename = nil, linenum = nil)
|
42
|
-
compiled_script = compile(script, filename, linenum)
|
43
|
-
evaluate_compiled_script(compiled_script)
|
44
|
-
end
|
45
|
-
|
46
|
-
def evaluate_compiled script
|
47
|
-
evaluate_compiled_script(script)
|
34
|
+
alias :evaluate_compiled_script_without_clearing_traps :evaluate_compiled_script
|
35
|
+
def evaluate_compiled_script script
|
36
|
+
evaluate_compiled_script_without_clearing_traps(script)
|
37
|
+
ensure
|
48
38
|
@traps.each do |trap_tuple|
|
49
39
|
clear_trap(*trap_tuple)
|
50
40
|
end
|
@@ -55,17 +45,7 @@ module Johnson #:nodoc:
|
|
55
45
|
def compile(script, filename=nil, linenum=nil)
|
56
46
|
filename ||= 'none'
|
57
47
|
linenum ||= 1
|
58
|
-
|
59
|
-
end
|
60
|
-
|
61
|
-
###
|
62
|
-
# Yield to +block+ in +filename+ at +linenum+
|
63
|
-
def break(filename, linenum, &block)
|
64
|
-
raise "#{filename} has not been compiled" unless @compiled_scripts.key?(filename)
|
65
|
-
|
66
|
-
compiled_script = @compiled_scripts[filename]
|
67
|
-
set_trap(compiled_script, linenum, block)
|
68
|
-
@traps << [compiled_script, linenum]
|
48
|
+
native_compile(script, filename, linenum)
|
69
49
|
end
|
70
50
|
|
71
51
|
class << self
|
@@ -14,18 +14,56 @@ module Johnson
|
|
14
14
|
assert_equal(42, @runtime[:list][0])
|
15
15
|
end
|
16
16
|
|
17
|
+
def test_array_length
|
18
|
+
@runtime[:list] = [1, 2, 3, 4]
|
19
|
+
assert_equal(4, @runtime.evaluate("list.length"))
|
20
|
+
end
|
21
|
+
|
17
22
|
def test_array_works_with_for_in
|
23
|
+
list = [9, 3, 12, 8]
|
24
|
+
|
25
|
+
@runtime['alert'] = lambda { |x| p x }
|
26
|
+
@runtime['list'] = list
|
27
|
+
@runtime.evaluate("
|
28
|
+
var new_list = [];
|
29
|
+
for(var x in list) {
|
30
|
+
new_list.push(x);
|
31
|
+
}
|
32
|
+
")
|
33
|
+
assert_equal([0, 1, 2, 3], @runtime['new_list'].to_a)
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_array_works_with_iterator
|
37
|
+
list = [9, 3, 12, 8]
|
38
|
+
|
39
|
+
@runtime['alert'] = lambda { |x| p x }
|
40
|
+
@runtime['list'] = list
|
41
|
+
@runtime.evaluate("
|
42
|
+
var new_list = [];
|
43
|
+
var it = Iterator(list);
|
44
|
+
for(var x in it) {
|
45
|
+
new_list.push(x);
|
46
|
+
}
|
47
|
+
")
|
48
|
+
assert_equal([[0, 9], [1, 3], [2, 12], [3, 8]], @runtime['new_list'].to_a.map {|a| a.to_a })
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_array_works_with_function_apply
|
18
52
|
list = [1, 2, 3, 4]
|
19
53
|
|
20
54
|
@runtime['alert'] = lambda { |x| p x }
|
21
55
|
@runtime['list'] = list
|
22
56
|
@runtime.evaluate("
|
23
57
|
var new_list = [];
|
24
|
-
|
25
|
-
new_list.push(
|
58
|
+
function process_list(a, b, c, d) {
|
59
|
+
new_list.push(a * 2);
|
60
|
+
new_list.push(b * 2);
|
61
|
+
// skip c
|
62
|
+
new_list.push(d * 2);
|
26
63
|
}
|
64
|
+
process_list.apply(process_list, list);
|
27
65
|
")
|
28
|
-
assert_equal(
|
66
|
+
assert_equal([2, 4, 8], @runtime['new_list'].to_a)
|
29
67
|
end
|
30
68
|
end
|
31
69
|
end
|
@@ -21,6 +21,18 @@ module Johnson
|
|
21
21
|
@runtime[:v] = v = "hola"
|
22
22
|
assert_not_same(v, @runtime.evaluate("v"))
|
23
23
|
end
|
24
|
+
|
25
|
+
def test_multibyte_character_roundtrip_js
|
26
|
+
assert_equal(1, @runtime.evaluate("'\\u20AC'.length"))
|
27
|
+
assert_equal(0x20ac, @runtime.evaluate("'\\u20AC'.charCodeAt(0)"))
|
28
|
+
assert_equal(0x0000, @runtime.evaluate("'\\u0000'.charCodeAt(0)"))
|
29
|
+
@runtime[:s] = "\xe2\x82\xac"
|
30
|
+
assert_equal(0x20ac, @runtime.evaluate("s.charCodeAt(0)"))
|
31
|
+
assert_equal('', @runtime.evaluate("s.substr(1)"))
|
32
|
+
assert_equal(1, @runtime.evaluate("s.length"))
|
33
|
+
assert_equal("\xe2\x82\xac", @runtime.evaluate("s"))
|
34
|
+
assert_js("'\\u20ac' == s")
|
35
|
+
end
|
24
36
|
end
|
25
37
|
end
|
26
38
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "/../helper"))
|
2
|
+
|
3
|
+
module Johnson
|
4
|
+
class CustomConversionsTest < Johnson::TestCase
|
5
|
+
def test_ruby_time_round_trips
|
6
|
+
@runtime['t'] = t = Time.now
|
7
|
+
assert_js_equal(t, 't')
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_ruby_time_wrappers_are_equal
|
11
|
+
@runtime['t'] = t = Time.now
|
12
|
+
@runtime['t2'] = t
|
13
|
+
assert_js('t == t2')
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_ruby_time_has_js_methods
|
17
|
+
@runtime['t'] = t = Time.now
|
18
|
+
assert_js_equal(t.year, 't.getFullYear()')
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_ruby_date_round_trips
|
22
|
+
@runtime['t'] = t = Date.today
|
23
|
+
assert_js_equal(t, 't')
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_ruby_date_has_js_methods
|
27
|
+
@runtime['t'] = t = Date.today
|
28
|
+
assert_js_equal(t.year, 't.getFullYear()')
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_js_date_is_ruby_date
|
32
|
+
@runtime.evaluate %{this.t = new Date('Jul 22, 2009 12:34:56');}
|
33
|
+
|
34
|
+
assert_kind_of(Date, @runtime['t'])
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_js_date_round_trips
|
38
|
+
@runtime.evaluate %{this.t = new Date('Jul 22, 2009 12:34:56');}
|
39
|
+
@runtime['t2'] = @runtime['t']
|
40
|
+
|
41
|
+
assert_js('t == t2')
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_js_date_has_ruby_methods
|
45
|
+
@runtime.evaluate %{this.t = new Date('Jul 22, 2009 12:34:56');}
|
46
|
+
|
47
|
+
assert_equal('Wed, 22 July, 2009', @runtime['t'].strftime('%a, %d %B, %Y'))
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -52,5 +52,28 @@ module Johnson
|
|
52
52
|
flag;
|
53
53
|
END
|
54
54
|
end
|
55
|
+
|
56
|
+
def test_apply_without_second_param_works
|
57
|
+
assert_js(<<-END)
|
58
|
+
var func = function() { return arguments.length; };
|
59
|
+
func.apply(null, null) == 0 && func.apply(null) == 0;
|
60
|
+
END
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_apply_with_non_array_throws
|
64
|
+
assert_raise(Johnson::Error) {
|
65
|
+
@runtime.evaluate(<<-END)
|
66
|
+
var func = function() { return arguments.length; };
|
67
|
+
func.apply(null, "foo");
|
68
|
+
END
|
69
|
+
}
|
70
|
+
|
71
|
+
assert_raise(Johnson::Error) {
|
72
|
+
@runtime.evaluate(<<-END)
|
73
|
+
var func = function() { return arguments.length; };
|
74
|
+
func.apply(null, { foo: 'bar' });
|
75
|
+
END
|
76
|
+
}
|
77
|
+
end
|
55
78
|
end
|
56
79
|
end
|
@@ -11,6 +11,23 @@ module Johnson
|
|
11
11
|
assert_nil(@runtime.evaluate(nil))
|
12
12
|
end
|
13
13
|
|
14
|
+
def test_global_treats_symbols_as_strings
|
15
|
+
@runtime[:foo] = 17
|
16
|
+
@runtime['bar'] = 4
|
17
|
+
assert_equal 17, @runtime['foo']
|
18
|
+
assert_equal 4, @runtime[:bar]
|
19
|
+
assert_js_equal 21, 'foo + bar'
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_disallows_invalid_key_types
|
23
|
+
assert_raises(TypeError) {
|
24
|
+
@runtime[4.1] = 3
|
25
|
+
}
|
26
|
+
assert_raises(TypeError) {
|
27
|
+
@runtime[%w(hello world)]
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
14
31
|
def test_js_eval
|
15
32
|
assert_equal(1, @runtime.evaluate('eval("1");'))
|
16
33
|
end
|
@@ -39,8 +56,8 @@ module Johnson
|
|
39
56
|
x++;
|
40
57
|
}
|
41
58
|
some_number++;
|
42
|
-
"
|
43
|
-
|
59
|
+
")
|
60
|
+
script.break(4) do
|
44
61
|
break_times += 1
|
45
62
|
assert_equal(@runtime['i'], @runtime['x'])
|
46
63
|
assert_equal(1, @runtime['some_number'])
|
@@ -50,6 +67,57 @@ module Johnson
|
|
50
67
|
assert_equal(2, @runtime['some_number'])
|
51
68
|
end
|
52
69
|
|
70
|
+
def test_breakpoint_can_raise
|
71
|
+
break_times = 0
|
72
|
+
@runtime['some_number'] = 0
|
73
|
+
@runtime['alert'] = lambda {|x,y| p [x, y] }
|
74
|
+
script = @runtime.compile("some_number++;
|
75
|
+
var x = 0;
|
76
|
+
try {
|
77
|
+
for(var i = 0; i < 10; i++) {
|
78
|
+
x++;
|
79
|
+
}
|
80
|
+
} catch(ex) {
|
81
|
+
note_error(ex);
|
82
|
+
}
|
83
|
+
some_number++;
|
84
|
+
")
|
85
|
+
script.break(5) do
|
86
|
+
break_times += 1
|
87
|
+
assert_equal(@runtime['i'], @runtime['x'])
|
88
|
+
assert_equal(1, @runtime['some_number'])
|
89
|
+
raise ArgumentError, "Test" if @runtime['i'] > 4
|
90
|
+
end
|
91
|
+
break_ex = nil
|
92
|
+
@runtime['note_error'] = lambda {|ex| break_ex = ex }
|
93
|
+
@runtime.evaluate_compiled_script(script)
|
94
|
+
assert_match(/ArgumentError: Test/, break_ex.message)
|
95
|
+
assert_equal(6, break_times)
|
96
|
+
assert_equal(2, @runtime['some_number'])
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_breakpoints_are_cleared
|
100
|
+
break_times = 0
|
101
|
+
@runtime['some_number'] = 0
|
102
|
+
script = @runtime.compile("some_number++;
|
103
|
+
var x = 0;
|
104
|
+
for(var i = 0; i < 10; i++) {
|
105
|
+
x++;
|
106
|
+
}
|
107
|
+
some_number++;
|
108
|
+
")
|
109
|
+
script.break(4) do
|
110
|
+
break_times += 1
|
111
|
+
assert_equal(@runtime['i'], @runtime['x'])
|
112
|
+
assert_equal(1, @runtime['some_number'] % 2)
|
113
|
+
end
|
114
|
+
3.times do
|
115
|
+
@runtime.evaluate_compiled_script(script)
|
116
|
+
end
|
117
|
+
assert_equal(10, break_times)
|
118
|
+
assert_equal(6, @runtime['some_number'])
|
119
|
+
end
|
120
|
+
|
53
121
|
def test_try_to_gc
|
54
122
|
10.times {
|
55
123
|
thread = Thread.new do
|
@@ -60,5 +128,17 @@ module Johnson
|
|
60
128
|
GC.start
|
61
129
|
}
|
62
130
|
end
|
131
|
+
|
132
|
+
def test_evaluated_compiled_script_checks_argument_type
|
133
|
+
assert_raises(ArgumentError) {
|
134
|
+
@runtime.evaluate_compiled_script(nil)
|
135
|
+
}
|
136
|
+
assert_raises(ArgumentError) {
|
137
|
+
@runtime.evaluate_compiled_script(17)
|
138
|
+
}
|
139
|
+
assert_raises(ArgumentError) {
|
140
|
+
@runtime.evaluate_compiled_script("3+9")
|
141
|
+
}
|
142
|
+
end
|
63
143
|
end
|
64
144
|
end
|