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.
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