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