forked from ruby/TryRuby
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcruby_wasi.rb
More file actions
121 lines (104 loc) · 3.66 KB
/
cruby_wasi.rb
File metadata and controls
121 lines (104 loc) · 3.66 KB
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
# await: *await*, loading
# backtick_javascript: true
require 'await'
class RubyEngine
class CRubyWASI < RubyEngine
REQUIRED_SCRIPTS = [
{
# https://www.jsdelivr.com/package/npm/@ruby/wasm-wasi?version=2.8.1&tab=files&path=dist
src: "https://cdn.jsdelivr.net/npm/@ruby/wasm-wasi@2.8.1/dist/index.umd.js",
integrity: "sha256-sxHtVJLn+brnck+saGLUKbu/JEP7pilIBgChJvVdjko=",
crossorigin: "anonymous"
},
{
src: "https://cdn.jsdelivr.net/npm/@wasmer/wasmfs@0.12.0/lib/index.iife.js",
integrity: "sha256-sOd4ekxVsN4PXhR+cn/4uNAxeQOJRcsaW5qalYfvkTw=",
crossorigin: "anonymous"
},
{
src: "https://cdn.jsdelivr.net/npm/@wasmer/wasi@0.12.0/lib/index.iife.js",
integrity: "sha256-FslFp/Vq4bDf2GXu+9QyBEDLtEWO3fkMjpyOaJMHJT8=",
crossorigin: "anonymous"
}
]
def initialize(ruby_wasm_url, version)
@ruby_wasm_url = ruby_wasm_url
@version = version
end
def name
"CRuby #{@version}"
end
def engine_id
"cruby-#{@version}"
end
# Below functions will be compiled as async functions
def self.inject_scripts
@injected ||= begin
REQUIRED_SCRIPTS.map do |script|
promise = PromiseV2.new
script = $document.create_element("script", attrs: script)
script.on("load") { promise.resolve }
script.on("error") { promise.reject(StandardError.new("failed to load #{script[:src]}")) }
$document.head << script
promise
end.each_await(&:itself)
true
end
end
def wasm_module
@module ||= begin
response = `fetch(#{@ruby_wasm_url})`.await
buffer = `response.arrayBuffer()`.await
`WebAssembly.compile(buffer)`.await
end
end
def run(source)
`var $WASI, $WasmFs, $RubyVM`
wasmInstance, wasmModule, vm, wasi, imports = nil
loading("downloading scripts") { CRubyWASI.inject_scripts.await }
loading("early load") do
`$WASI = window["WASI"].WASI`
`$WasmFs = window["WasmFs"].WasmFs`
`$RubyVM = window["ruby-wasm-wasi"].RubyVM`
wasmFs = `new $WasmFs()`
originalWriteSync = `wasmFs.fs.writeSync.bind(wasmFs.fs)`
textDecoder = `new TextDecoder("utf-8")`
%x{
wasmFs.fs.writeSync = (fd, buffer, offset, length, position) => {
if (fd == 1 || fd == 2) {
const text = textDecoder.decode(buffer);
#{@writer.print_to_output(`text`, "")};
}
return originalWriteSync(fd, buffer, offset, length, position);
};
}
vm = `new $RubyVM()`
wasi = `new $WASI({
bindings: { ...$WASI.defaultBindings, fs: wasmFs.fs },
})`
imports = `{ wasi_snapshot_preview1: wasi.wasiImport }`
`vm.addToImports(imports)`
end
loading("downloading ruby") do
wasmModule = wasm_module.await
end
loading("instantiating") do
wasmInstance = `WebAssembly.instantiate(wasmModule, imports)`.await
end
loading("initializing") do await
`vm.setInstance(wasmInstance)`.await
`wasi.setMemory(wasmInstance.exports.memory)`
`vm.initialize()`
set_external_encoding = "Encoding.default_external = Encoding::UTF_8"
`vm.eval(set_external_encoding)`
end
yield `vm.eval(source).toString()`
rescue JS::Error => err
raise err
end
def exception_to_string(err)
# "...: undefined method `reverse' for 40:Integer (NoMethodError)\n (Exception)\n"
super(err).sub(/\s+\(Exception\)\s*\z/, '')
end
end
end