File: graaljs_runtime.rb

package info (click to toggle)
ruby-execjs 2.10.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,128 kB
  • sloc: javascript: 32,134; ruby: 1,175; makefile: 5
file content (146 lines) | stat: -rw-r--r-- 4,163 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
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 = source.encode(Encoding::UTF_8)
        unless source.empty?
          translate do
            eval_in_context(source)
          end
        end
      end

      def exec(source, options = {})
        source = source.encode(Encoding::UTF_8)
        source = "(function(){#{source}})()" if /\S/.match?(source)

        translate do
          eval_in_context(source)
        end
      end

      def eval(source, options = {})
        source = source.encode(Encoding::UTF_8)
        source = "(#{source})" if /\S/.match?(source)

        translate do
          eval_in_context(source)
        end
      end

      def call(source, *args)
        source = source.encode(Encoding::UTF_8)
        source = "(#{source})" if /\S/.match?(source)

        translate do
          function = eval_in_context(source)
          function.call(*convert_ruby_to_js(args))
        end
      end

      private

      ForeignException = defined?(Polyglot::ForeignException) ? Polyglot::ForeignException : ::RuntimeError

      def translate
        convert_js_to_ruby yield
      rescue ForeignException => e
        if e.message && 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)
        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
          value
        when String, Symbol
          Truffle::Interop.as_truffle_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 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 if $VERBOSE
        warn "You also need to install the 'js' component, see https://github.com/oracle/truffleruby/blob/master/doc/user/polyglot.md#installing-other-languages", uplevel: 0 if $VERBOSE
        return @available = false
      end

      @available = true
    end
  end
end