Skip to content

Commit 39118e2

Browse files
authored
Merge pull request #107 from eregon/graaljs-runtime-ready
Add runtime using GraalJS on TruffleRuby
2 parents 642fd30 + 8d4412a commit 39118e2

File tree

6 files changed

+154
-4
lines changed

6 files changed

+154
-4
lines changed

.github/workflows/ci.yml

+6-1
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' ]
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: startsWith(matrix.ruby, 'truffleruby+graalvm')
2732
- name: Run test
2833
run: rake
2934
- name: Install gem

lib/execjs/encoding.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def encode(string)
1919
end
2020
else
2121
def encode(string)
22-
string.encode('UTF-8')
22+
string.encode(::Encoding::UTF_8)
2323
end
2424
end
2525
end

lib/execjs/graaljs_runtime.rb

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

lib/execjs/runtimes.rb

+4
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,

lib/execjs/support/jsc_runner.js

-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
}, function(program) {
33
var output;
44
try {
5-
delete this.console;
65
delete this.console;
76
delete this.setTimeout;
87
delete this.setInterval;

test/test_execjs.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ def test_context_call_missing_function
156156
assert_output value, ExecJS.eval("#{json_value}")
157157
end
158158

159-
define_method("test_strinigfy_value_#{index}") do
159+
define_method("test_stringify_value_#{index}") do
160160
context = ExecJS.compile("function json(obj) { return JSON.stringify(obj); }")
161161
assert_output json_value, context.call("json", value)
162162
end

0 commit comments

Comments
 (0)