Skip to content

Commit 6c16c8f

Browse files
committed
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")'
1 parent 73a6717 commit 6c16c8f

File tree

3 files changed

+159
-1
lines changed

3 files changed

+159
-1
lines changed

.github/workflows/ci.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ jobs:
77
strategy:
88
fail-fast: false
99
matrix:
10-
ruby: [ '3.0', '2.7', '2.6', '2.5', 'jruby', 'truffleruby' ]
10+
ruby: [ '3.0', '2.7', '2.6', '2.5', 'jruby', 'truffleruby', 'truffleruby+graalvm-head' ]
1111
runs-on: ubuntu-latest
1212
steps:
1313
- name: Checkout
@@ -18,12 +18,17 @@ jobs:
1818
uses: ruby/setup-ruby@v1
1919
with:
2020
ruby-version: ${{ matrix.ruby }}
21+
2122
- name: Update Rubygems
2223
run: gem update --system
2324
- name: Install bundler
2425
run: gem install bundler -v '2.2.16'
2526
- name: Install dependencies
2627
run: bundle install
28+
29+
- name: Set TRUFFLERUBYOPT
30+
run: echo "TRUFFLERUBYOPT=--jvm --polyglot" >> $GITHUB_ENV
31+
if: matrix.ruby == 'truffleruby+graalvm-head'
2732
- name: Run test
2833
run: rake
2934
- name: Install gem

lib/execjs/graaljs_runtime.rb

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
require "execjs/runtime"
2+
3+
module ExecJS
4+
class GraalJSRuntime < Runtime
5+
class Context < Runtime::Context
6+
def initialize(runtime, source = "", options = {})
7+
@context = Polyglot::InnerContext.new
8+
@context.eval('js', 'delete this.console')
9+
@js_object = @context.eval('js', 'Object')
10+
11+
source = encode(source)
12+
13+
@source = source
14+
unless source.empty?
15+
translate do
16+
eval_in_context(source)
17+
end
18+
end
19+
end
20+
21+
def exec(source, options = {})
22+
source = encode(source)
23+
source = "(function(){#{source}})()" if /\S/.match?(source)
24+
source = "#{@source};\n#{source}" unless @source.empty?
25+
26+
translate do
27+
eval_in_context(source)
28+
end
29+
end
30+
31+
def eval(source, options = {})
32+
source = encode(source)
33+
source = "(#{source})" if /\S/.match?(source)
34+
source = "#{@source};\n#{source}" unless @source.empty?
35+
36+
translate do
37+
eval_in_context(source)
38+
end
39+
end
40+
41+
def call(source, *args)
42+
source = encode(source)
43+
source = "(#{source})" if /\S/.match?(source)
44+
source = "#{@source};\n#{source}" unless @source.empty?
45+
46+
translate do
47+
function = eval_in_context(source)
48+
function.call(*convert_ruby_to_js(args))
49+
end
50+
end
51+
52+
private
53+
54+
def translate
55+
begin
56+
convert_js_to_ruby yield
57+
rescue ::RuntimeError => e
58+
if e.message.start_with?('SyntaxError:')
59+
error_class = ExecJS::RuntimeError
60+
else
61+
error_class = ExecJS::ProgramError
62+
end
63+
64+
backtrace = e.backtrace.map { |line| line.sub('(eval)', '(execjs)') }
65+
raise error_class, e.message, backtrace
66+
end
67+
end
68+
69+
def convert_js_to_ruby(value)
70+
case value
71+
when true, false, Integer, Float
72+
value
73+
else
74+
if value.nil?
75+
nil
76+
elsif value.respond_to?(:call)
77+
nil
78+
elsif value.respond_to?(:to_str)
79+
value.to_str
80+
elsif value.respond_to?(:to_ary)
81+
value.to_ary.map do |e|
82+
if e.respond_to?(:call)
83+
nil
84+
else
85+
convert_js_to_ruby(e)
86+
end
87+
end
88+
else
89+
object = value
90+
h = {}
91+
object.instance_variables.each do |member|
92+
v = object[member]
93+
unless v.respond_to?(:call)
94+
h[member.to_s] = convert_js_to_ruby(v)
95+
end
96+
end
97+
h
98+
end
99+
end
100+
end
101+
102+
def convert_ruby_to_js(value)
103+
case value
104+
when nil, true, false, Integer, Float, String
105+
value
106+
when Array
107+
value.map { |e| convert_ruby_to_js(e) }
108+
when Hash
109+
h = @js_object.new
110+
value.each_pair do |k,v|
111+
h[convert_ruby_to_js(k)] = convert_ruby_to_js(v)
112+
end
113+
h
114+
else
115+
raise TypeError, "Unknown how to convert to JS: #{value.inspect}"
116+
end
117+
end
118+
119+
class_eval <<-'RUBY', "(execjs)", 1
120+
def eval_in_context(code); @context.eval('js', code); end
121+
RUBY
122+
end
123+
124+
def name
125+
"GraalVM (Graal.js)"
126+
end
127+
128+
def available?
129+
return @available if defined?(@available)
130+
131+
unless RUBY_ENGINE == "truffleruby"
132+
return @available = false
133+
end
134+
135+
unless defined?(Polyglot::InnerContext)
136+
warn "TruffleRuby #{RUBY_ENGINE_VERSION} does not have support for inner contexts, use a more recent version", uplevel: 0
137+
return @available = false
138+
end
139+
140+
unless Polyglot.languages.include? "js"
141+
warn "The language 'js' is not available, you likely need to `export TRUFFLERUBYOPT='--jvm --polyglot'`", uplevel: 0
142+
warn "Note that you need TruffleRuby+GraalVM and not just the TruffleRuby standalone to use #{self.class}", uplevel: 0
143+
return @available = false
144+
end
145+
146+
@available = true
147+
end
148+
end
149+
end

lib/execjs/runtimes.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
require "execjs/external_runtime"
55
require "execjs/ruby_rhino_runtime"
66
require "execjs/mini_racer_runtime"
7+
require "execjs/graaljs_runtime"
78

89
module ExecJS
910
module Runtimes
@@ -13,6 +14,8 @@ module Runtimes
1314

1415
RubyRhino = RubyRhinoRuntime.new
1516

17+
GraalJS = GraalJSRuntime.new
18+
1619
MiniRacer = MiniRacerRuntime.new
1720

1821
Node = ExternalRuntime.new(
@@ -82,6 +85,7 @@ def self.names
8285
def self.runtimes
8386
@runtimes ||= [
8487
RubyRhino,
88+
GraalJS,
8589
Duktape,
8690
MiniRacer,
8791
Node,

0 commit comments

Comments
 (0)