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
@@ -3,6 +3,8 @@
3
3
 
4
4
  #include "spidermonkey.h"
5
5
 
6
+ //#define LEAK_ROOT_NAMES
7
+
6
8
  #define RAISE_JS_ERROR(rb_runtime, ex) \
7
9
  do {\
8
10
  JohnsonRuntime * _rt = NULL;\
@@ -6,7 +6,9 @@
6
6
 
7
7
  void Init_spidermonkey()
8
8
  {
9
- VALUE johnson = rb_define_module("Johnson"); // FIXME: this belongs outside the extension
9
+ JS_SetCStringsAreUTF8();
10
+
11
+ VALUE johnson = rb_const_get(rb_mKernel, rb_intern("Johnson"));
10
12
  VALUE spidermonkey = rb_define_module_under(johnson, "SpiderMonkey");
11
13
 
12
14
  init_Johnson_SpiderMonkey_Context(spidermonkey);
@@ -1,11 +1,10 @@
1
1
  require "generator"
2
- require "johnson/version"
3
2
 
4
- # the command-line option parser and support libs
5
- require "johnson/cli"
6
-
7
- # the native SpiderMonkey extension
8
- require "johnson/spidermonkey"
3
+ # the 'public' interface
4
+ require "johnson/error"
5
+ require "johnson/runtime"
6
+ require "johnson/ruby_land_proxy"
7
+ require "johnson/parser"
9
8
 
10
9
  # visitable module and visitors
11
10
  require "johnson/visitable"
@@ -14,39 +13,32 @@ require "johnson/visitors"
14
13
  # parse tree nodes
15
14
  require "johnson/nodes"
16
15
 
17
- # the SpiderMonkey bits written in Ruby
18
- require "johnson/spidermonkey/runtime"
19
- require "johnson/spidermonkey/context"
20
- require "johnson/spidermonkey/js_land_proxy"
21
- require "johnson/spidermonkey/ruby_land_proxy"
22
- require "johnson/spidermonkey/mutable_tree_visitor"
23
- require "johnson/spidermonkey/debugger"
24
- require "johnson/spidermonkey/immutable_node"
25
-
26
- # the 'public' interface
27
- require "johnson/error"
28
- require "johnson/runtime"
29
- require "johnson/parser"
30
-
31
- # make sure all the Johnson JavaScript libs are in the load path
32
- $LOAD_PATH.push(File.expand_path("#{File.dirname(__FILE__)}/../js"))
16
+ # SpiderMonkey, the default JS engine
17
+ require "johnson/spidermonkey"
33
18
 
34
19
  module Johnson
35
- PRELUDE = IO.read(File.dirname(__FILE__) + "/../js/johnson/prelude.js")
36
-
20
+ VERSION = "1.2.0"
21
+
22
+ ###
23
+ # Evaluate the given JavaScript +expression+ in a new runtime, after
24
+ # setting the given +vars+ into the global object.
25
+ #
26
+ # Returns the result of evaluating the given expression.
37
27
  def self.evaluate(expression, vars={})
38
28
  runtime = Johnson::Runtime.new
39
29
  vars.each { |key, value| runtime[key] = value }
40
-
30
+
41
31
  runtime.evaluate(expression)
42
32
  end
43
-
33
+
44
34
  def self.parse(js, *args)
45
35
  Johnson::Parser.parse(js, *args)
46
36
  end
47
37
 
48
38
  ###
49
- # Create a new runtime and load all +files+. Returns a new Johnson::Runtime.
39
+ # Create a new runtime and load all +files+.
40
+ #
41
+ # Returns the new Johnson::Runtime.
50
42
  def self.load(*files)
51
43
  rt = Johnson::Runtime.new
52
44
  rt.load(*files)
@@ -1,7 +1,8 @@
1
+ require "johnson"
1
2
  require "johnson/cli/options"
2
3
 
3
4
  module Johnson #:nodoc:
4
5
  module CLI #:nodoc:
5
- JS = IO.read(File.dirname(__FILE__) + "/../../js/johnson/cli.js")
6
+ JS = IO.read File.dirname(__FILE__) + "/js/cli.js"
6
7
  end
7
8
  end
File without changes
@@ -0,0 +1,34 @@
1
+
2
+ // Ruby ProxyHelper => JS original
3
+ Johnson.addConversion(function(v) {
4
+ return v.javascript_proxy;
5
+ }, (function(Helper_send) {
6
+ return function(v) {
7
+ return Helper_send('===', v);
8
+ };
9
+ })(Ruby.Johnson.RubyLandProxy.ProxyHelper.method('send')));
10
+
11
+
12
+ // Ruby Time => JS Date
13
+ Johnson.addWrapper(function(v) {
14
+ var d = new Date(v.to_f() * 1000);
15
+ d.wrappedRuby = v;
16
+ return d;
17
+ }, (function(Helper_send, RubyTime_send) {
18
+ return function(v) {
19
+ return !Helper_send('===', v) && RubyTime_send('===', v);
20
+ };
21
+ })(Ruby.Johnson.RubyLandProxy.ProxyHelper.method('send'), Ruby.Time.method('send')));
22
+
23
+
24
+ // Ruby Date/DateTime => JS Date
25
+ Johnson.addWrapper(function(v) {
26
+ var d = new Date(parseFloat(v.strftime('%Q')));
27
+ d.wrappedRuby = v;
28
+ return d;
29
+ }, (function(Helper_send, RubyDate_send) {
30
+ return function(v) {
31
+ return !Helper_send('===', v) && RubyDate_send('===', v);
32
+ };
33
+ })(Ruby.Johnson.RubyLandProxy.ProxyHelper.method('send'), Ruby.Date.method('send')));
34
+
@@ -0,0 +1,149 @@
1
+ var Johnson = {};
2
+
3
+ Johnson.Symbol = function(string) {
4
+ this.string = string;
5
+ };
6
+
7
+ Johnson.Symbol.prototype = {
8
+ toString: function() {
9
+ return this.string;
10
+ },
11
+
12
+ inspect: function() {
13
+ return ":" + this.toString();
14
+ }
15
+ };
16
+
17
+ Johnson.symbolCache = {};
18
+
19
+ Johnson.symbolize = function(string) {
20
+ if (!Johnson.symbolCache[string])
21
+ Johnson.symbolCache[string] = new Johnson.Symbol(string);
22
+
23
+ return Johnson.symbolCache[string];
24
+ };
25
+
26
+ Object.defineProperty(String.prototype, "toSymbol", function() {
27
+ return Johnson.symbolize(this.toString());
28
+ }, Object.READ_ONLY | Object.NON_DELETABLE);
29
+
30
+ (function(origApply) {
31
+ Object.defineProperty(Function.prototype, "apply", function(thisObj, arrayLike) {
32
+ var realArray = arrayLike;
33
+ if (arrayLike != null &&
34
+ !(arrayLike instanceof Array) &&
35
+ typeof arrayLike == 'object' &&
36
+ arrayLike.length != null) {
37
+ realArray = [];
38
+ for (var i = arrayLike.length - 1; i >= 0; i--) {
39
+ realArray[i] = arrayLike[i];
40
+ }
41
+ }
42
+ return origApply.call(this, thisObj, realArray);
43
+ }, Object.READ_ONLY | Object.NON_DELETABLE);
44
+ })(Function.prototype.apply);
45
+
46
+ (function() {
47
+ var wrappers = [];
48
+ var conversions = [];
49
+ Johnson.addConversion = function(conversion, test) {
50
+ conversions.push([conversion, test]);
51
+ };
52
+ Johnson.applyConversions = function(proxy) {
53
+ var converted = false;
54
+ conversions.forEach(function(pair) {
55
+ if (converted) return;
56
+
57
+ var [conversion, test] = pair;
58
+
59
+ if (test(proxy)) {
60
+ proxy = conversion(proxy);
61
+ converted = true;
62
+ }
63
+ });
64
+ return proxy;
65
+ };
66
+ Johnson.addWrapper = function(wrapper, test) {
67
+ wrappers.push([wrapper, test]);
68
+ };
69
+ Johnson.applyWrappers = function(proxy) {
70
+ wrappers.forEach(function(pair) {
71
+ var [wrapper, test] = pair;
72
+
73
+ if (test && !test(proxy)) return;
74
+ if (wrapper.test && !wrapper.test(proxy)) return;
75
+
76
+ if (typeof wrapper == 'function')
77
+ proxy = wrapper(proxy);
78
+ else
79
+ for (var [m, v] in wrapper)
80
+ proxy[m] = v;
81
+ });
82
+ return proxy;
83
+ };
84
+ })();
85
+
86
+ Johnson.Generator = function(enumerableProxy, namesOnly) {
87
+ if (enumerableProxy.js_properties) {
88
+ this.items = enumerableProxy.js_properties();
89
+ } else {
90
+ this.items = (enumerableProxy.keys ? enumerableProxy.keys() : []).concat(enumerableProxy.methods());
91
+ }
92
+ this.index = 0;
93
+ if (!namesOnly)
94
+ this.obj = enumerableProxy;
95
+ };
96
+
97
+ Johnson.Generator.prototype.__iterator__ = function() {
98
+ return this;
99
+ };
100
+
101
+ Johnson.Generator.prototype.hasNext = function() {
102
+ return this.index < this.items.length;
103
+ }
104
+
105
+ Johnson.Generator.prototype.next = function() {
106
+ if (this.hasNext()) {
107
+ var name = this.items[this.index++];
108
+ if (this.obj) {
109
+ return [name, this.obj[name]];
110
+ } else {
111
+ return name;
112
+ }
113
+ }
114
+ throw StopIteration;
115
+ }
116
+
117
+ Johnson.Generator.create = function(namesOnly) {
118
+ return new Johnson.Generator(this, namesOnly);
119
+ }
120
+
121
+ Johnson.required = {};
122
+
123
+ Johnson.require = function(file) {
124
+ file = Ruby.File.join(Ruby.File.dirname(file),
125
+ Ruby.File.basename(file, ".js") + ".js");
126
+
127
+ if(Johnson.required[file]) return false;
128
+
129
+ for(var directory in Ruby["$LOAD_PATH"]) {
130
+ var path = Ruby.File.join(directory, file);
131
+
132
+ if(Ruby.File.send("file?", path)) {
133
+ Johnson.required[file] = true;
134
+ Johnson.runtime.load(path);
135
+
136
+ return true;
137
+ }
138
+ }
139
+
140
+ throw Ruby.LoadError;
141
+ }
142
+
143
+ this.__defineGetter__("__FILE__", function() {
144
+ try { throw new Error; } catch(e) {
145
+ return e.stack.split("\n")[2].split("@")[1].split(":").slice(0,-1).join(":");
146
+ }
147
+ })
148
+
149
+ null; // no need to marshal a result
@@ -0,0 +1,113 @@
1
+ module Johnson #:nodoc:
2
+ class RubyLandProxy
3
+ class << self
4
+ def apply_wrappers(proxy)
5
+ wrappers.each do |(wrapper, test)|
6
+ next if test && !test.call(proxy)
7
+ next if wrapper.respond_to?(:test?) && !wrapper.test?(proxy)
8
+
9
+ if wrapper.respond_to?(:call)
10
+ proxy = wrapper.call(proxy)
11
+ break unless Johnson::RubyLandProxy === proxy
12
+ else
13
+ proxy.send :extend, wrapper
14
+ end
15
+ end
16
+ proxy
17
+ end
18
+ def add_wrapper(wrapper, &test)
19
+ wrappers.push [wrapper, test]
20
+ end
21
+ def insert_wrapper(wrapper, &test)
22
+ wrappers.unshift [wrapper, test]
23
+ end
24
+ def wrappers
25
+ @wrappers ||= []
26
+ end
27
+
28
+ def apply_conversions(proxy)
29
+ conversions.each do |(conversion, test)|
30
+ if test.call(proxy)
31
+ converted = conversion.call(proxy)
32
+ return converted unless converted.eql? proxy
33
+ end
34
+ end
35
+ proxy
36
+ end
37
+ def add_conversion(conversion, &test)
38
+ conversions << [conversion, test]
39
+ end
40
+ def conversions
41
+ @conversions ||= []
42
+ end
43
+ end
44
+
45
+ module Callable
46
+ def self.test?(proxy)
47
+ proxy.respond_to?(:call_using)
48
+ end
49
+
50
+ def to_proc
51
+ @proc ||= Proc.new { |*args| call(*args) }
52
+ end
53
+
54
+ def call(*args)
55
+ call_using(runtime.global, *args)
56
+ end
57
+ end
58
+
59
+ module ProxyHelper
60
+ def self.wrap(proxy, result)
61
+ result.extend self
62
+ result.javascript_proxy = proxy
63
+ result
64
+ end
65
+
66
+ attr_accessor :javascript_proxy
67
+ end
68
+
69
+ add_wrapper Callable
70
+
71
+ # JS wrapper => Ruby original
72
+ add_conversion lambda {|o| o.wrappedRuby } do |o| o.respond_to? :wrappedRuby end
73
+
74
+ # JS Date => Ruby DateTime
75
+ add_conversion lambda {|o| ProxyHelper.wrap(o, DateTime.parse(o.toUTCString)) } do |o| o.respond_to? :setUTCMilliseconds end
76
+
77
+ include Enumerable
78
+
79
+ # FIXME: need to revisit array vs non-array proxy, to_a/to_ary semantics, etc.
80
+ def size; length; end
81
+ def to_ary; to_a; end
82
+
83
+ def initialize
84
+ raise Johnson::Error, "#{self.class.name} is an internal support class."
85
+ end
86
+ private :initialize
87
+
88
+ def inspect
89
+ toString
90
+ end
91
+
92
+ def method_missing(sym, *args, &block)
93
+ args << block if block_given?
94
+
95
+ name = sym.to_s
96
+ assignment = "=" == name[-1, 1]
97
+
98
+ # default behavior if the slot's not there
99
+ return super unless assignment || respond_to?(sym)
100
+
101
+ unless function_property?(name)
102
+ # for arity 0, treat it as a get
103
+ return self[name] if args.empty?
104
+
105
+ # arity 1 and quacking like an assignment, treat it as a set
106
+ return self[name[0..-2]] = args[0] if assignment && 1 == args.size
107
+ end
108
+
109
+ # okay, must really be a function
110
+ call_function_property(name, *args)
111
+ end
112
+ end
113
+ end
@@ -1,36 +1,104 @@
1
1
  module Johnson
2
+ ###
3
+ # An interface to a JavaScript engine.
2
4
  class Runtime
3
- attr_reader :delegate
4
-
5
- def initialize(delegate=Johnson::SpiderMonkey::Runtime)
6
- @delegate = delegate.is_a?(Class) ? delegate.new : delegate
7
- evaluate(Johnson::PRELUDE, "Johnson::PRELUDE", 1)
5
+
6
+ PRELUDE_PATH = File.expand_path File.dirname(__FILE__) +
7
+ "/js/prelude.js" # :nodoc:
8
+ CORE_PATH = File.expand_path File.dirname(__FILE__) +
9
+ "/js/core.js" # :nodoc:
10
+
11
+ PRELUDE = IO.read PRELUDE_PATH # :nodoc:
12
+ CORE = IO.read CORE_PATH # :nodoc:
13
+
14
+ ###
15
+ # Deprecated: Previously, returned the underlying JavaScript engine
16
+ # instance. Now returns self.
17
+ def delegate
18
+ self
19
+ end
20
+
21
+ ###
22
+ # Create a new Runtime instance, using the default JavaScript
23
+ # engine.
24
+ #
25
+ # Optionally takes a parameter specifying which engine to use, but
26
+ # this is deprecated; instead, just create an instance of that
27
+ # engine's runtime directly.
28
+ #
29
+ # :call-seq:
30
+ # new(runtime_class=nil)
31
+ #
32
+ def self.new(*args)
33
+ return super if self < Johnson::Runtime
34
+
35
+ delegate = args.first
36
+ if delegate.is_a? Class
37
+ delegate.new
38
+ elsif delegate
39
+ delegate
40
+ else
41
+ Johnson::SpiderMonkey::Runtime.new
42
+ end
43
+ end
44
+
45
+ ###
46
+ # Install the Johnson prelude into this runtime environment.
47
+ def initialize # :notnew:
48
+ evaluate PRELUDE, PRELUDE_PATH, 1
8
49
  global.Johnson.runtime = self
50
+ global['Ruby'] = Object
51
+ evaluate CORE, CORE_PATH, 1
9
52
  end
10
-
53
+
54
+ ###
55
+ # Access the +key+ property of the JavaScript +global+ object.
11
56
  def [](key)
12
- delegate[key.to_s]
57
+ global[key]
13
58
  end
14
-
59
+
60
+ ###
61
+ # Set the +key+ property of the JavaScript +global+ object to
62
+ # +value+.
15
63
  def []=(key, value)
16
- delegate[key.to_s] = value
64
+ global[key] = value
17
65
  end
18
-
19
- def evaluate(expression, filename=nil, linenum=nil)
20
- return nil if expression.nil?
21
- delegate.evaluate(expression, filename, linenum)
66
+
67
+
68
+ ###
69
+ # Execute the JavaScript source in +script+. If supplied, the script
70
+ # is marked as starting on line +linenum+ of +filename+.
71
+ #
72
+ # Equivalent to calling RubyLandScript#execute on the result of
73
+ # Runtime#compile.
74
+ def evaluate(script, filename = nil, linenum = nil)
75
+ return nil if script.nil?
76
+ compiled_script = compile(script, filename, linenum)
77
+ evaluate_compiled_script(compiled_script)
22
78
  end
23
-
79
+
80
+
81
+ ###
82
+ # The JavaScript unique Global Object.
24
83
  def global
25
- delegate.global
84
+ raise NotImplementedError
26
85
  end
27
-
86
+
87
+ ###
88
+ # Load and execute the named JavaScript +files+.
89
+ #
90
+ # Checks for (and skips) a shebang line at the top of any of them.
28
91
  def load(*files)
29
- files.map { |f| delegate.evaluate(File.read(f).gsub(/\A#!.*$/, ''), f, 1) }.last
92
+ files.map { |f|
93
+ evaluate(File.read(f).gsub(/\A#!.*$/, ''), f, 1)
94
+ }.last
30
95
  end
31
96
 
32
97
  ###
33
- # Johnson.require on each file in +files+
98
+ # Search the Ruby load path for each of the named +files+, and
99
+ # evaluate them *if they have not yet been loaded*.
100
+ #
101
+ # Calls Johnson.require() in JavaScript on each filename in turn.
34
102
  def require(*files)
35
103
  files.each do |file|
36
104
  evaluate("Johnson.require('#{file}');")
@@ -38,26 +106,17 @@ module Johnson
38
106
  end
39
107
 
40
108
  ###
41
- # Compile +script+ with +filename+ and +linenum+
109
+ # Compile the JavaScript source in +script+. If supplied, the script
110
+ # is marked as starting on line +linenum+ of +filename+.
42
111
  def compile(script, filename=nil, linenum=nil)
43
- delegate.compile(script, filename, linenum)
112
+ raise NotImplementedError
44
113
  end
45
114
 
46
115
  ###
47
- # Yield to +block+ in +filename+ at +linenum+
48
- def break(filename, linenum, &block)
49
- delegate.break(filename, linenum, &block)
50
- end
51
-
116
+ # Evaluates the given JS script, that should have been returned by a
117
+ # previous call to #compile().
52
118
  def evaluate_compiled_script(script)
53
- delegate.evaluate_compiled(script)
54
- end
55
-
56
- private
57
- # Called by SpiderMonkey's garbage collector to determine whether or
58
- # not it should GC
59
- def should_sm_gc?
60
- false
119
+ raise NotImplementedError
61
120
  end
62
121
  end
63
122
  end