johnson 1.1.2 → 1.2.0
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.
- 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
|