From 3bc074ae1fd2f17e5aaf0f1f1bea513eee84a81c Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Sun, 22 Nov 2020 16:22:12 +0100 Subject: [PATCH 1/7] Fix typo --- test/test_execjs.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_execjs.rb b/test/test_execjs.rb index 99d8f0a..a860049 100644 --- a/test/test_execjs.rb +++ b/test/test_execjs.rb @@ -156,7 +156,7 @@ class TestExecJS < Test assert_output value, ExecJS.eval("#{json_value}") end - define_method("test_strinigfy_value_#{index}") do + define_method("test_stringify_value_#{index}") do context = ExecJS.compile("function json(obj) { return JSON.stringify(obj); }") assert_output json_value, context.call("json", value) end From 181cb0a9d17863e04afcb97630159ce3d3a98d48 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 19 Aug 2021 20:09:17 +0200 Subject: [PATCH 2/7] Pass Encoding::UTF_8 to String#encode instead of 'UTF-8' for efficiency --- lib/execjs/encoding.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/execjs/encoding.rb b/lib/execjs/encoding.rb index 406fb0d..50ef928 100644 --- a/lib/execjs/encoding.rb +++ b/lib/execjs/encoding.rb @@ -19,7 +19,7 @@ module ExecJS end else def encode(string) - string.encode('UTF-8') + string.encode(::Encoding::UTF_8) end end end From 566d611c23ca7e2881299ad3f1eb06eed89e2ee3 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Sun, 22 Nov 2020 16:20:48 +0100 Subject: [PATCH 3/7] Add runtime using GraalJS on TruffleRuby * Use Truffle inner contexts to provide correct isolation between ExecJS::Context * To run the tests: TRUFFLERUBYOPT="--jvm --polyglot" bundle exec rake test:graaljs TESTOPTS="--seed=0 --verbose" * Full command without subprocess: TRUFFLERUBYOPT="--jvm --polyglot" jt -u jvm-js ruby -w -Ilib:test -I $PWD/vendor/bundle/truffleruby/*/gems/rake-13.0.1/lib $PWD/vendor/bundle/truffleruby/*/gems/rake-13.0.1/lib/rake/rake_test_loader.rb test/test_execjs.rb --seed=0 --verbose * Try command: TRUFFLERUBYOPT="--jvm --polyglot" jt -u jvm-js ruby -Ilib -rexecjs -e 'p ExecJS.eval("2 + 3")' --- .github/workflows/ci.yml | 7 +- lib/execjs/graaljs_runtime.rb | 144 ++++++++++++++++++++++++++++++++++ lib/execjs/runtimes.rb | 4 + 3 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 lib/execjs/graaljs_runtime.rb diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a80c190..02fb178 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ jobs: strategy: fail-fast: false matrix: - ruby: [ '3.0', '2.7', '2.6', '2.5', 'jruby', 'truffleruby' ] + ruby: [ '3.0', '2.7', '2.6', '2.5', 'jruby', 'truffleruby', 'truffleruby+graalvm-head' ] runs-on: ubuntu-latest steps: - name: Checkout @@ -18,12 +18,17 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} + - name: Update Rubygems run: gem update --system - name: Install bundler run: gem install bundler -v '2.2.16' - name: Install dependencies run: bundle install + + - name: Set TRUFFLERUBYOPT + run: echo "TRUFFLERUBYOPT=--jvm --polyglot" >> $GITHUB_ENV + if: matrix.ruby == 'truffleruby+graalvm-head' - name: Run test run: rake - name: Install gem diff --git a/lib/execjs/graaljs_runtime.rb b/lib/execjs/graaljs_runtime.rb new file mode 100644 index 0000000..43ecdcb --- /dev/null +++ b/lib/execjs/graaljs_runtime.rb @@ -0,0 +1,144 @@ +require "execjs/runtime" + +module ExecJS + class GraalJSRuntime < Runtime + class Context < Runtime::Context + def initialize(runtime, source = "", options = {}) + @context = Polyglot::InnerContext.new + @context.eval('js', 'delete this.console') + @js_object = @context.eval('js', 'Object') + + source = encode(source) + unless source.empty? + translate do + eval_in_context(source) + end + end + end + + def exec(source, options = {}) + source = encode(source) + source = "(function(){#{source}})()" if /\S/.match?(source) + + translate do + eval_in_context(source) + end + end + + def eval(source, options = {}) + source = encode(source) + source = "(#{source})" if /\S/.match?(source) + + translate do + eval_in_context(source) + end + end + + def call(source, *args) + source = encode(source) + source = "(#{source})" if /\S/.match?(source) + + translate do + function = eval_in_context(source) + function.call(*convert_ruby_to_js(args)) + end + end + + private + + def translate + begin + convert_js_to_ruby yield + rescue ::RuntimeError => e + if e.message.start_with?('SyntaxError:') + error_class = ExecJS::RuntimeError + else + error_class = ExecJS::ProgramError + end + + backtrace = e.backtrace.map { |line| line.sub('(eval)', '(execjs)') } + raise error_class, e.message, backtrace + end + end + + def convert_js_to_ruby(value) + case value + when true, false, Integer, Float + value + else + if value.nil? + nil + elsif value.respond_to?(:call) + nil + elsif value.respond_to?(:to_str) + value.to_str + elsif value.respond_to?(:to_ary) + value.to_ary.map do |e| + if e.respond_to?(:call) + nil + else + convert_js_to_ruby(e) + end + end + else + object = value + h = {} + object.instance_variables.each do |member| + v = object[member] + unless v.respond_to?(:call) + h[member.to_s] = convert_js_to_ruby(v) + end + end + h + end + end + end + + def convert_ruby_to_js(value) + case value + when nil, true, false, Integer, Float, String + value + when Array + value.map { |e| convert_ruby_to_js(e) } + when Hash + h = @js_object.new + value.each_pair do |k,v| + h[convert_ruby_to_js(k)] = convert_ruby_to_js(v) + end + h + else + raise TypeError, "Unknown how to convert to JS: #{value.inspect}" + end + end + + class_eval <<-'RUBY', "(execjs)", 1 + def eval_in_context(code); @context.eval('js', code); end + RUBY + end + + def name + "GraalVM (Graal.js)" + end + + def available? + return @available if defined?(@available) + + unless RUBY_ENGINE == "truffleruby" + return @available = false + end + + unless defined?(Polyglot::InnerContext) + warn "TruffleRuby #{RUBY_ENGINE_VERSION} does not have support for inner contexts, use a more recent version", uplevel: 0 + return @available = false + end + + unless Polyglot.languages.include? "js" + warn "The language 'js' is not available, you likely need to `export TRUFFLERUBYOPT='--jvm --polyglot'`", uplevel: 0 + warn "Note that you need TruffleRuby+GraalVM and not just the TruffleRuby standalone to use #{self.class}", uplevel: 0 + return @available = false + end + + @available = true + end + end +end diff --git a/lib/execjs/runtimes.rb b/lib/execjs/runtimes.rb index 19d9d96..615ecd6 100644 --- a/lib/execjs/runtimes.rb +++ b/lib/execjs/runtimes.rb @@ -4,6 +4,7 @@ require "execjs/duktape_runtime" require "execjs/external_runtime" require "execjs/ruby_rhino_runtime" require "execjs/mini_racer_runtime" +require "execjs/graaljs_runtime" module ExecJS module Runtimes @@ -13,6 +14,8 @@ module ExecJS RubyRhino = RubyRhinoRuntime.new + GraalJS = GraalJSRuntime.new + MiniRacer = MiniRacerRuntime.new Node = ExternalRuntime.new( @@ -82,6 +85,7 @@ module ExecJS def self.runtimes @runtimes ||= [ RubyRhino, + GraalJS, Duktape, MiniRacer, Node, From 05476fab17c9b45727e03b848c34bb961fa7a6d2 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 1 Oct 2021 12:27:13 +0200 Subject: [PATCH 4/7] Remove extra begin/end for #translate --- lib/execjs/graaljs_runtime.rb | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/lib/execjs/graaljs_runtime.rb b/lib/execjs/graaljs_runtime.rb index 43ecdcb..2042fbd 100644 --- a/lib/execjs/graaljs_runtime.rb +++ b/lib/execjs/graaljs_runtime.rb @@ -47,18 +47,16 @@ module ExecJS private def translate - begin - convert_js_to_ruby yield - rescue ::RuntimeError => e - if e.message.start_with?('SyntaxError:') - error_class = ExecJS::RuntimeError - else - error_class = ExecJS::ProgramError - end - - backtrace = e.backtrace.map { |line| line.sub('(eval)', '(execjs)') } - raise error_class, e.message, backtrace + convert_js_to_ruby yield + rescue ::RuntimeError => e + if e.message.start_with?('SyntaxError:') + error_class = ExecJS::RuntimeError + else + error_class = ExecJS::ProgramError end + + backtrace = e.backtrace.map { |line| line.sub('(eval)', '(execjs)') } + raise error_class, e.message, backtrace end def convert_js_to_ruby(value) From f29ad3e66bf148adcebdfcd3e209c7d855f848e6 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 12 Oct 2021 14:41:24 +0200 Subject: [PATCH 5/7] Only warn if $VERBOSE * If the GraalJSRuntime is not used it is not necessarily an issue, as it e.g. fall back to an ExternalRuntime like node. --- lib/execjs/graaljs_runtime.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/execjs/graaljs_runtime.rb b/lib/execjs/graaljs_runtime.rb index 2042fbd..73d22a7 100644 --- a/lib/execjs/graaljs_runtime.rb +++ b/lib/execjs/graaljs_runtime.rb @@ -126,13 +126,13 @@ module ExecJS end unless defined?(Polyglot::InnerContext) - warn "TruffleRuby #{RUBY_ENGINE_VERSION} does not have support for inner contexts, use a more recent version", uplevel: 0 + warn "TruffleRuby #{RUBY_ENGINE_VERSION} does not have support for inner contexts, use a more recent version", uplevel: 0 if $VERBOSE return @available = false end unless Polyglot.languages.include? "js" - warn "The language 'js' is not available, you likely need to `export TRUFFLERUBYOPT='--jvm --polyglot'`", uplevel: 0 - warn "Note that you need TruffleRuby+GraalVM and not just the TruffleRuby standalone to use #{self.class}", uplevel: 0 + warn "The language 'js' is not available, you likely need to `export TRUFFLERUBYOPT='--jvm --polyglot'`", uplevel: 0 if $VERBOSE + warn "Note that you need TruffleRuby+GraalVM and not just the TruffleRuby standalone to use #{self.class}", uplevel: 0 if $VERBOSE return @available = false end From e24e381ffc22a8015645d6f36fe95df43be5381e Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 19 Oct 2021 19:35:31 +0200 Subject: [PATCH 6/7] Remove duplicate `delete this.console;` --- lib/execjs/support/jsc_runner.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/execjs/support/jsc_runner.js b/lib/execjs/support/jsc_runner.js index 902ad4d..631aa46 100644 --- a/lib/execjs/support/jsc_runner.js +++ b/lib/execjs/support/jsc_runner.js @@ -2,7 +2,6 @@ }, function(program) { var output; try { - delete this.console; delete this.console; delete this.setTimeout; delete this.setInterval; From 8d4412a78e8d548966fdb35e7eab390ec2a9e7e5 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 19 Oct 2021 19:34:28 +0200 Subject: [PATCH 7/7] Use released version for truffleruby+graalvm in CI --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 02fb178..26fb819 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ jobs: strategy: fail-fast: false matrix: - ruby: [ '3.0', '2.7', '2.6', '2.5', 'jruby', 'truffleruby', 'truffleruby+graalvm-head' ] + ruby: [ '3.0', '2.7', '2.6', '2.5', 'jruby', 'truffleruby', 'truffleruby+graalvm' ] runs-on: ubuntu-latest steps: - name: Checkout @@ -28,7 +28,7 @@ jobs: - name: Set TRUFFLERUBYOPT run: echo "TRUFFLERUBYOPT=--jvm --polyglot" >> $GITHUB_ENV - if: matrix.ruby == 'truffleruby+graalvm-head' + if: startsWith(matrix.ruby, 'truffleruby+graalvm') - name: Run test run: rake - name: Install gem