-
Notifications
You must be signed in to change notification settings - Fork 283
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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")'
- Loading branch information
Showing
3 changed files
with
159 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
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) | ||
|
||
@source = 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) | ||
source = "#{@source};\n#{source}" unless @source.empty? | ||
|
||
translate do | ||
eval_in_context(source) | ||
end | ||
end | ||
|
||
def eval(source, options = {}) | ||
source = encode(source) | ||
source = "(#{source})" if /\S/.match?(source) | ||
source = "#{@source};\n#{source}" unless @source.empty? | ||
|
||
translate do | ||
eval_in_context(source) | ||
end | ||
end | ||
|
||
def call(source, *args) | ||
source = encode(source) | ||
source = "(#{source})" if /\S/.match?(source) | ||
source = "#{@source};\n#{source}" unless @source.empty? | ||
|
||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters