johnson 1.1.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/CHANGELOG.rdoc +12 -0
  2. data/Manifest.txt +6 -4
  3. data/README.rdoc +2 -8
  4. data/Rakefile +11 -11
  5. data/bin/johnson +1 -3
  6. data/ext/spidermonkey/context.c +3 -2
  7. data/ext/spidermonkey/conversions.c +61 -27
  8. data/ext/spidermonkey/conversions.h +13 -0
  9. data/ext/spidermonkey/debugger.c +13 -5
  10. data/ext/spidermonkey/debugger.h +1 -0
  11. data/ext/spidermonkey/extconf.rb +2 -3
  12. data/ext/spidermonkey/jroot.h +11 -1
  13. data/ext/spidermonkey/js_land_proxy.c +21 -11
  14. data/ext/spidermonkey/ruby_land_proxy.c +116 -41
  15. data/ext/spidermonkey/ruby_land_proxy.h +21 -0
  16. data/ext/spidermonkey/runtime.c +85 -19
  17. data/ext/spidermonkey/runtime.h +2 -0
  18. data/ext/spidermonkey/spidermonkey.c +3 -1
  19. data/lib/johnson.rb +19 -27
  20. data/lib/johnson/cli.rb +2 -1
  21. data/{js/johnson → lib/johnson/js}/cli.js +0 -0
  22. data/lib/johnson/js/core.js +34 -0
  23. data/lib/johnson/js/prelude.js +149 -0
  24. data/lib/johnson/ruby_land_proxy.rb +113 -0
  25. data/lib/johnson/runtime.rb +92 -33
  26. data/lib/johnson/spidermonkey.rb +12 -0
  27. data/lib/johnson/spidermonkey/js_land_proxy.rb +10 -8
  28. data/lib/johnson/spidermonkey/ruby_land_proxy.rb +10 -47
  29. data/lib/johnson/spidermonkey/runtime.rb +11 -31
  30. data/test/johnson/conversions/array_test.rb +41 -3
  31. data/test/johnson/conversions/string_test.rb +12 -0
  32. data/test/johnson/custom_conversions_test.rb +50 -0
  33. data/test/johnson/prelude_test.rb +23 -0
  34. data/test/johnson/runtime_test.rb +82 -2
  35. data/test/johnson/spidermonkey/ruby_land_proxy_test.rb +17 -1
  36. data/test/johnson/spidermonkey/runtime_test.rb +24 -0
  37. data/vendor/spidermonkey/jsprf.c +2 -0
  38. metadata +22 -9
  39. data/js/johnson/prelude.js +0 -80
  40. data/lib/johnson/version.rb +0 -3
  41. 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
- include Enumerable
5
-
6
- def initialize
7
- raise Johnson::Error, "#{self.class.name} is an internal support class."
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
- private :initialize
11
-
12
- # FIXME: need to revisit array vs non-array proxy, to_a/to_ary semantics, etc.
13
- alias_method :size, :length
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
- self["Ruby"] = Object
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
- def [](key)
32
- global[key]
33
- end
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
- @compiled_scripts[filename] = native_compile(script, filename, linenum)
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
- for(x in list) {
25
- new_list.push(x + 1);
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(list.map { |x| x + 1}, @runtime['new_list'].to_a)
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
- ", 'awesome_script')
43
- @runtime.break('awesome_script', 4) do
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