diff --git a/lib/execjs.rb b/lib/execjs.rb index 88242a8..7cd0288 100644 --- a/lib/execjs.rb +++ b/lib/execjs.rb @@ -18,6 +18,10 @@ module ExecJS runtime.eval(source) end + def self.compile(source) + runtime.compile(source) + end + def self.runtimes Runtimes.runtimes end diff --git a/lib/execjs/external_runtime.rb b/lib/execjs/external_runtime.rb index 7583807..f19ed63 100644 --- a/lib/execjs/external_runtime.rb +++ b/lib/execjs/external_runtime.rb @@ -3,6 +3,56 @@ require "tempfile" module ExecJS class ExternalRuntime + class Context + def initialize(runtime) + @runtime = runtime + @script = "" + end + + def eval(source) + if /\S/ =~ source + exec("return eval(#{"(#{source})".to_json})") + end + end + + def exec(source) + @script << source + @script << "\n" + + compile_to_tempfile(@script) do |file| + extract_result(@runtime.exec_runtime(file.path)) + end + end + + protected + def compile_to_tempfile(source) + tempfile = Tempfile.open("execjs") + tempfile.write compile(source) + tempfile.close + yield tempfile + ensure + tempfile.close! + end + + def compile(source) + @runtime.runner_source.dup.tap do |output| + output.sub!('#{source}', source) + output.sub!('#{json2_source}') do + IO.read(ExecJS.root + "/support/json2.js") + end + end + end + + def extract_result(output) + status, value = output.empty? ? [] : JSON.parse(output) + if status == "ok" + value + else + raise ProgramError, value + end + end + end + attr_reader :name def initialize(options) @@ -15,22 +65,39 @@ module ExecJS @binary = locate_binary end - def eval(source) - if /\S/ =~ source - exec("return eval(#{"(#{source})".to_json})") - end + def exec(source) + context = Context.new(self) + context.exec(source) end - def exec(source) - compile_to_tempfile(source) do |file| - extract_result(exec_runtime(file.path)) - end + def eval(source) + context = Context.new(self) + context.eval(source) + end + + def compile(source) + context = Context.new(self) + context.exec(source) + context end def available? @binary ? true : false end + def runner_source + @runner_source ||= IO.read(@runner_path) + end + + def exec_runtime(filename) + output = sh("#{@binary} #{filename} 2>&1") + if $?.success? + output + else + raise RuntimeError, output + end + end + protected def locate_binary if binary = which(@command) @@ -55,37 +122,6 @@ module ExecJS end end - def compile(source) - runner_source.dup.tap do |output| - output.sub!('#{source}', source) - output.sub!('#{json2_source}') do - IO.read(ExecJS.root + "/support/json2.js") - end - end - end - - def runner_source - @runner_source ||= IO.read(@runner_path) - end - - def compile_to_tempfile(source) - tempfile = Tempfile.open("execjs") - tempfile.write compile(source) - tempfile.close - yield tempfile - ensure - tempfile.close! - end - - def exec_runtime(filename) - output = sh("#{@binary} #{filename} 2>&1") - if $?.success? - output - else - raise RuntimeError, output - end - end - if "".respond_to?(:force_encoding) def sh(command) output, options = nil, {} @@ -108,14 +144,5 @@ module ExecJS end end end - - def extract_result(output) - status, value = output.empty? ? [] : JSON.parse(output) - if status == "ok" - value - else - raise ProgramError, value - end - end end end diff --git a/lib/execjs/ruby_racer_runtime.rb b/lib/execjs/ruby_racer_runtime.rb index fac00c8..3c9bc0b 100644 --- a/lib/execjs/ruby_racer_runtime.rb +++ b/lib/execjs/ruby_racer_runtime.rb @@ -1,26 +1,63 @@ module ExecJS class RubyRacerRuntime + class Context + def initialize + @v8_context = ::V8::Context.new + end + + def exec(source) + if /\S/ =~ source + eval "(function(){#{source}})()" + end + end + + def eval(source) + if /\S/ =~ source + unbox @v8_context.eval("(#{source})") + end + rescue ::V8::JSError => e + if e.value["name"] == "SyntaxError" + raise RuntimeError, e + else + raise ProgramError, e + end + end + + def unbox(value) + case value + when ::V8::Function + nil + when ::V8::Array + value.map { |v| unbox(v) } + when ::V8::Object + value.inject({}) do |vs, (k, v)| + vs[k] = unbox(v) unless v.is_a?(::V8::Function) + vs + end + else + value + end + end + end + def name "therubyracer (V8)" end def exec(source) - if /\S/ =~ source - eval "(function(){#{source}})()" - end + context = Context.new + context.exec(source) end def eval(source) - if /\S/ =~ source - context = ::V8::Context.new - unbox context.eval("(#{source})") - end - rescue ::V8::JSError => e - if e.value["name"] == "SyntaxError" - raise RuntimeError, e - else - raise ProgramError, e - end + context = Context.new + context.eval(source) + end + + def compile(source) + context = Context.new + context.exec(source) + context end def available? @@ -29,21 +66,5 @@ module ExecJS rescue LoadError false end - - def unbox(value) - case value - when ::V8::Function - nil - when ::V8::Array - value.map { |v| unbox(v) } - when ::V8::Object - value.inject({}) do |vs, (k, v)| - vs[k] = unbox(v) unless v.is_a?(::V8::Function) - vs - end - else - value - end - end end end diff --git a/lib/execjs/ruby_rhino_runtime.rb b/lib/execjs/ruby_rhino_runtime.rb index 1803273..48923a3 100644 --- a/lib/execjs/ruby_rhino_runtime.rb +++ b/lib/execjs/ruby_rhino_runtime.rb @@ -1,26 +1,61 @@ module ExecJS class RubyRhinoRuntime + class Context + def initialize + @rhino_context = ::Rhino::Context.new + end + + def exec(source) + if /\S/ =~ source + eval "(function(){#{source}})()" + end + end + + def eval(source) + if /\S/ =~ source + unbox @rhino_context.eval("(#{source})") + end + rescue ::Rhino::JavascriptError => e + if e.message == "syntax error" + raise RuntimeError, e + else + raise ProgramError, e + end + end + + def unbox(value) + case value + when ::Rhino::NativeFunction + nil + when ::Rhino::NativeObject + value.inject({}) do |vs, (k, v)| + vs[k] = unbox(v) unless v.is_a?(::Rhino::NativeFunction) + vs + end + else + value + end + end + end + def name "therubyrhino (Rhino)" end def exec(source) - if /\S/ =~ source - eval "(function(){#{source}})()" - end + context = Context.new + context.exec(source) end def eval(source) - if /\S/ =~ source - context = ::Rhino::Context.new - unbox context.eval("(#{source})") - end - rescue ::Rhino::JavascriptError => e - if e.message == "syntax error" - raise RuntimeError, e - else - raise ProgramError, e - end + context = Context.new + context.eval(source) + end + + def compile(source) + context = Context.new + context.exec(source) + context end def available? @@ -29,19 +64,5 @@ module ExecJS rescue LoadError false end - - def unbox(value) - case value - when ::Rhino::NativeFunction - nil - when ::Rhino::NativeObject - value.inject({}) do |vs, (k, v)| - vs[k] = unbox(v) unless v.is_a?(::Rhino::NativeFunction) - vs - end - else - value - end - end end end diff --git a/test/test_execjs.rb b/test/test_execjs.rb index eaec324..751a544 100644 --- a/test/test_execjs.rb +++ b/test/test_execjs.rb @@ -17,4 +17,10 @@ class TestExecJS < Test::Unit::TestCase runtime = ExecJS::ExternalRuntime.new(:command => "ruby") assert runtime.available? end + + def test_compile + context = ExecJS.compile("foo = function() { return \"bar\"; }") + assert_equal "bar", context.exec("return foo()") + assert_equal "bar", context.eval("foo()") + end end diff --git a/test/test_runtimes.rb b/test/test_runtimes.rb index 673d758..d27a196 100644 --- a/test/test_runtimes.rb +++ b/test/test_runtimes.rb @@ -29,6 +29,12 @@ module TestRuntime assert_equal "café", @runtime.eval("'café'") end + def test_compile + context = @runtime.compile("foo = function() { return \"bar\"; }") + assert_equal "bar", context.exec("return foo()") + assert_equal "bar", context.eval("foo()") + end + def test_this_is_global_scope assert_equal true, @runtime.eval("this === (function() {return this})()") assert_equal true, @runtime.exec("return this === (function() {return this})()")