|
| 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 |
0 commit comments