@ruby/wasm-wasi 2.4.1 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,8 +8,8 @@ See [Cheat Sheet](https://github.com/ruby/ruby.wasm/blob/main/docs/cheat_sheet.m
8
8
 
9
9
  ## Ruby Version Support
10
10
 
11
- | Version | Package |
12
- | ------- | -------------------------------------------------- |
11
+ | Version | Package |
12
+ | ------- | --------------------------------------------------------------------------------------------------------------- |
13
13
  | `head` | [`@ruby/head-wasm-wasi`](https://github.com/ruby/ruby.wasm/tree/main/packages/npm-packages/ruby-head-wasm-wasi) |
14
14
  | `3.3` | [`@ruby/3.3-wasm-wasi`](https://github.com/ruby/ruby.wasm/tree/main/packages/npm-packages/ruby-3.3-wasm-wasi) |
15
15
  | `3.2` | [`@ruby/3.2-wasm-wasi`](https://github.com/ruby/ruby.wasm/tree/main/packages/npm-packages/ruby-3.2-wasm-wasi) |
@@ -18,6 +18,41 @@ See [Cheat Sheet](https://github.com/ruby/ruby.wasm/blob/main/docs/cheat_sheet.m
18
18
 
19
19
  <!-- Generated by documentation.js. Update this documentation by updating the source code. -->
20
20
 
21
+ ### consolePrinter
22
+
23
+ Create a console printer that can be used as an overlay of WASI imports.
24
+ See the example below for how to use it.
25
+
26
+ ```javascript
27
+ const imports = {
28
+ wasi_snapshot_preview1: wasi.wasiImport,
29
+ };
30
+ const printer = consolePrinter();
31
+ printer.addToImports(imports);
32
+
33
+ const instance = await WebAssembly.instantiate(module, imports);
34
+ printer.setMemory(instance.exports.memory);
35
+ ```
36
+
37
+ Note that the `stdout` and `stderr` functions are called with text, not
38
+ bytes. This means that bytes written to stdout/stderr will be decoded as
39
+ UTF-8 and then passed to the `stdout`/`stderr` functions every time a write
40
+ occurs without buffering.
41
+
42
+ #### Parameters
43
+
44
+ - `$0` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** (optional, default `{stdout:console.log,stderr:console.warn}`)
45
+
46
+ - `$0.stdout` &#x20;
47
+ - `$0.stderr` &#x20;
48
+
49
+ - `stdout` A function that will be called when stdout is written to.
50
+ Defaults to `console.log`.
51
+ - `stderr` A function that will be called when stderr is written to.
52
+ Defaults to `console.warn`.
53
+
54
+ Returns **any** An object that can be used as an overlay of WASI imports.
55
+
21
56
  ### RubyVM
22
57
 
23
58
  A Ruby VM instance
@@ -46,7 +81,7 @@ Initialize the Ruby VM with the given command line arguments
46
81
  ##### Parameters
47
82
 
48
83
  - `args` The command line arguments to pass to Ruby. Must be
49
- an array of strings starting with the Ruby program name. (optional, default `["ruby.wasm","--disable-gems","-EUTF-8","-e_=0"]`)
84
+ an array of strings starting with the Ruby program name. (optional, default `["ruby.wasm","-EUTF-8","-e_=0"]`)
50
85
 
51
86
  #### setInstance
52
87
 
@@ -151,6 +186,37 @@ ary.call("push", 4);
151
186
  console.log(ary.call("sample").toString());
152
187
  ```
153
188
 
189
+ Returns **any** The result of the method call as a new RbValue.
190
+
191
+ #### callAsync
192
+
193
+ Call a given method that may call `JS::Object#await` with given arguments
194
+
195
+ ##### Parameters
196
+
197
+ - `callee` name of the Ruby method to call
198
+ - `args` **...any** arguments to pass to the method. Must be an array of RbValue
199
+
200
+ ##### Examples
201
+
202
+ ```javascript
203
+ const client = vm.eval(`
204
+ require 'js'
205
+ class HttpClient
206
+ def get(url)
207
+ JS.global.fetch(url).await
208
+ end
209
+ end
210
+ HttpClient.new
211
+ `);
212
+ const response = await client.callAsync(
213
+ "get",
214
+ vm.eval(`"https://example.com"`),
215
+ );
216
+ ```
217
+
218
+ Returns **any** A Promise that resolves to the result of the method call as a new RbValue.
219
+
154
220
  #### toPrimitive
155
221
 
156
222
  - **See**: <https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toPrimitive>
@@ -52,6 +52,107 @@
52
52
 
53
53
  class WASIProcExit extends Error{constructor(code){super("exit with exit code "+code);this.code=code;}}let WASI=class WASI{start(instance){this.inst=instance;try{instance.exports._start();}catch(e){if(e instanceof WASIProcExit){return e.code}else {throw e}}}initialize(instance){this.inst=instance;instance.exports._initialize();}constructor(args,env,fds,options={}){this.args=[];this.env=[];this.fds=[];debug.enable(options.debug);this.args=args;this.env=env;this.fds=fds;const self=this;this.wasiImport={args_sizes_get(argc,argv_buf_size){const buffer=new DataView(self.inst.exports.memory.buffer);buffer.setUint32(argc,self.args.length,true);let buf_size=0;for(const arg of self.args){buf_size+=arg.length+1;}buffer.setUint32(argv_buf_size,buf_size,true);debug.log(buffer.getUint32(argc,true),buffer.getUint32(argv_buf_size,true));return 0},args_get(argv,argv_buf){const buffer=new DataView(self.inst.exports.memory.buffer);const buffer8=new Uint8Array(self.inst.exports.memory.buffer);const orig_argv_buf=argv_buf;for(let i=0;i<self.args.length;i++){buffer.setUint32(argv,argv_buf,true);argv+=4;const arg=new TextEncoder().encode(self.args[i]);buffer8.set(arg,argv_buf);buffer.setUint8(argv_buf+arg.length,0);argv_buf+=arg.length+1;}if(debug.enabled){debug.log(new TextDecoder("utf-8").decode(buffer8.slice(orig_argv_buf,argv_buf)));}return 0},environ_sizes_get(environ_count,environ_size){const buffer=new DataView(self.inst.exports.memory.buffer);buffer.setUint32(environ_count,self.env.length,true);let buf_size=0;for(const environ of self.env){buf_size+=environ.length+1;}buffer.setUint32(environ_size,buf_size,true);debug.log(buffer.getUint32(environ_count,true),buffer.getUint32(environ_size,true));return 0},environ_get(environ,environ_buf){const buffer=new DataView(self.inst.exports.memory.buffer);const buffer8=new Uint8Array(self.inst.exports.memory.buffer);const orig_environ_buf=environ_buf;for(let i=0;i<self.env.length;i++){buffer.setUint32(environ,environ_buf,true);environ+=4;const e=new TextEncoder().encode(self.env[i]);buffer8.set(e,environ_buf);buffer.setUint8(environ_buf+e.length,0);environ_buf+=e.length+1;}if(debug.enabled){debug.log(new TextDecoder("utf-8").decode(buffer8.slice(orig_environ_buf,environ_buf)));}return 0},clock_res_get(id,res_ptr){let resolutionValue;switch(id){case CLOCKID_MONOTONIC:{resolutionValue=5000n;break}case CLOCKID_REALTIME:{resolutionValue=1000000n;break}default:return ERRNO_NOSYS}const view=new DataView(self.inst.exports.memory.buffer);view.setBigUint64(res_ptr,resolutionValue,true);return ERRNO_SUCCESS},clock_time_get(id,precision,time){const buffer=new DataView(self.inst.exports.memory.buffer);if(id===CLOCKID_REALTIME){buffer.setBigUint64(time,BigInt(new Date().getTime())*1000000n,true);}else if(id==CLOCKID_MONOTONIC){let monotonic_time;try{monotonic_time=BigInt(Math.round(performance.now()*1e6));}catch(e){monotonic_time=0n;}buffer.setBigUint64(time,monotonic_time,true);}else {buffer.setBigUint64(time,0n,true);}return 0},fd_advise(fd,offset,len,advice){if(self.fds[fd]!=undefined){return self.fds[fd].fd_advise(offset,len,advice)}else {return ERRNO_BADF}},fd_allocate(fd,offset,len){if(self.fds[fd]!=undefined){return self.fds[fd].fd_allocate(offset,len)}else {return ERRNO_BADF}},fd_close(fd){if(self.fds[fd]!=undefined){const ret=self.fds[fd].fd_close();self.fds[fd]=undefined;return ret}else {return ERRNO_BADF}},fd_datasync(fd){if(self.fds[fd]!=undefined){return self.fds[fd].fd_datasync()}else {return ERRNO_BADF}},fd_fdstat_get(fd,fdstat_ptr){if(self.fds[fd]!=undefined){const{ret,fdstat}=self.fds[fd].fd_fdstat_get();if(fdstat!=null){fdstat.write_bytes(new DataView(self.inst.exports.memory.buffer),fdstat_ptr);}return ret}else {return ERRNO_BADF}},fd_fdstat_set_flags(fd,flags){if(self.fds[fd]!=undefined){return self.fds[fd].fd_fdstat_set_flags(flags)}else {return ERRNO_BADF}},fd_fdstat_set_rights(fd,fs_rights_base,fs_rights_inheriting){if(self.fds[fd]!=undefined){return self.fds[fd].fd_fdstat_set_rights(fs_rights_base,fs_rights_inheriting)}else {return ERRNO_BADF}},fd_filestat_get(fd,filestat_ptr){if(self.fds[fd]!=undefined){const{ret,filestat}=self.fds[fd].fd_filestat_get();if(filestat!=null){filestat.write_bytes(new DataView(self.inst.exports.memory.buffer),filestat_ptr);}return ret}else {return ERRNO_BADF}},fd_filestat_set_size(fd,size){if(self.fds[fd]!=undefined){return self.fds[fd].fd_filestat_set_size(size)}else {return ERRNO_BADF}},fd_filestat_set_times(fd,atim,mtim,fst_flags){if(self.fds[fd]!=undefined){return self.fds[fd].fd_filestat_set_times(atim,mtim,fst_flags)}else {return ERRNO_BADF}},fd_pread(fd,iovs_ptr,iovs_len,offset,nread_ptr){const buffer=new DataView(self.inst.exports.memory.buffer);const buffer8=new Uint8Array(self.inst.exports.memory.buffer);if(self.fds[fd]!=undefined){const iovecs=Iovec.read_bytes_array(buffer,iovs_ptr,iovs_len);const{ret,nread}=self.fds[fd].fd_pread(buffer8,iovecs,offset);buffer.setUint32(nread_ptr,nread,true);return ret}else {return ERRNO_BADF}},fd_prestat_get(fd,buf_ptr){const buffer=new DataView(self.inst.exports.memory.buffer);if(self.fds[fd]!=undefined){const{ret,prestat}=self.fds[fd].fd_prestat_get();if(prestat!=null){prestat.write_bytes(buffer,buf_ptr);}return ret}else {return ERRNO_BADF}},fd_prestat_dir_name(fd,path_ptr,path_len){if(self.fds[fd]!=undefined){const{ret,prestat_dir_name}=self.fds[fd].fd_prestat_dir_name();if(prestat_dir_name!=null){const buffer8=new Uint8Array(self.inst.exports.memory.buffer);buffer8.set(prestat_dir_name,path_ptr);}return ret}else {return ERRNO_BADF}},fd_pwrite(fd,iovs_ptr,iovs_len,offset,nwritten_ptr){const buffer=new DataView(self.inst.exports.memory.buffer);const buffer8=new Uint8Array(self.inst.exports.memory.buffer);if(self.fds[fd]!=undefined){const iovecs=Ciovec.read_bytes_array(buffer,iovs_ptr,iovs_len);const{ret,nwritten}=self.fds[fd].fd_pwrite(buffer8,iovecs,offset);buffer.setUint32(nwritten_ptr,nwritten,true);return ret}else {return ERRNO_BADF}},fd_read(fd,iovs_ptr,iovs_len,nread_ptr){const buffer=new DataView(self.inst.exports.memory.buffer);const buffer8=new Uint8Array(self.inst.exports.memory.buffer);if(self.fds[fd]!=undefined){const iovecs=Iovec.read_bytes_array(buffer,iovs_ptr,iovs_len);const{ret,nread}=self.fds[fd].fd_read(buffer8,iovecs);buffer.setUint32(nread_ptr,nread,true);return ret}else {return ERRNO_BADF}},fd_readdir(fd,buf,buf_len,cookie,bufused_ptr){const buffer=new DataView(self.inst.exports.memory.buffer);const buffer8=new Uint8Array(self.inst.exports.memory.buffer);if(self.fds[fd]!=undefined){let bufused=0;while(true){const{ret,dirent}=self.fds[fd].fd_readdir_single(cookie);if(ret!=0){buffer.setUint32(bufused_ptr,bufused,true);return ret}if(dirent==null){break}if(buf_len-bufused<dirent.head_length()){bufused=buf_len;break}const head_bytes=new ArrayBuffer(dirent.head_length());dirent.write_head_bytes(new DataView(head_bytes),0);buffer8.set(new Uint8Array(head_bytes).slice(0,Math.min(head_bytes.byteLength,buf_len-bufused)),buf);buf+=dirent.head_length();bufused+=dirent.head_length();if(buf_len-bufused<dirent.name_length()){bufused=buf_len;break}dirent.write_name_bytes(buffer8,buf,buf_len-bufused);buf+=dirent.name_length();bufused+=dirent.name_length();cookie=dirent.d_next;}buffer.setUint32(bufused_ptr,bufused,true);return 0}else {return ERRNO_BADF}},fd_renumber(fd,to){if(self.fds[fd]!=undefined&&self.fds[to]!=undefined){const ret=self.fds[to].fd_close();if(ret!=0){return ret}self.fds[to]=self.fds[fd];self.fds[fd]=undefined;return 0}else {return ERRNO_BADF}},fd_seek(fd,offset,whence,offset_out_ptr){const buffer=new DataView(self.inst.exports.memory.buffer);if(self.fds[fd]!=undefined){const{ret,offset:offset_out}=self.fds[fd].fd_seek(offset,whence);buffer.setBigInt64(offset_out_ptr,offset_out,true);return ret}else {return ERRNO_BADF}},fd_sync(fd){if(self.fds[fd]!=undefined){return self.fds[fd].fd_sync()}else {return ERRNO_BADF}},fd_tell(fd,offset_ptr){const buffer=new DataView(self.inst.exports.memory.buffer);if(self.fds[fd]!=undefined){const{ret,offset}=self.fds[fd].fd_tell();buffer.setBigUint64(offset_ptr,offset,true);return ret}else {return ERRNO_BADF}},fd_write(fd,iovs_ptr,iovs_len,nwritten_ptr){const buffer=new DataView(self.inst.exports.memory.buffer);const buffer8=new Uint8Array(self.inst.exports.memory.buffer);if(self.fds[fd]!=undefined){const iovecs=Ciovec.read_bytes_array(buffer,iovs_ptr,iovs_len);const{ret,nwritten}=self.fds[fd].fd_write(buffer8,iovecs);buffer.setUint32(nwritten_ptr,nwritten,true);return ret}else {return ERRNO_BADF}},path_create_directory(fd,path_ptr,path_len){const buffer8=new Uint8Array(self.inst.exports.memory.buffer);if(self.fds[fd]!=undefined){const path=new TextDecoder("utf-8").decode(buffer8.slice(path_ptr,path_ptr+path_len));return self.fds[fd].path_create_directory(path)}},path_filestat_get(fd,flags,path_ptr,path_len,filestat_ptr){const buffer=new DataView(self.inst.exports.memory.buffer);const buffer8=new Uint8Array(self.inst.exports.memory.buffer);if(self.fds[fd]!=undefined){const path=new TextDecoder("utf-8").decode(buffer8.slice(path_ptr,path_ptr+path_len));const{ret,filestat}=self.fds[fd].path_filestat_get(flags,path);if(filestat!=null){filestat.write_bytes(buffer,filestat_ptr);}return ret}else {return ERRNO_BADF}},path_filestat_set_times(fd,flags,path_ptr,path_len,atim,mtim,fst_flags){const buffer8=new Uint8Array(self.inst.exports.memory.buffer);if(self.fds[fd]!=undefined){const path=new TextDecoder("utf-8").decode(buffer8.slice(path_ptr,path_ptr+path_len));return self.fds[fd].path_filestat_set_times(flags,path,atim,mtim,fst_flags)}else {return ERRNO_BADF}},path_link(old_fd,old_flags,old_path_ptr,old_path_len,new_fd,new_path_ptr,new_path_len){const buffer8=new Uint8Array(self.inst.exports.memory.buffer);if(self.fds[old_fd]!=undefined&&self.fds[new_fd]!=undefined){const old_path=new TextDecoder("utf-8").decode(buffer8.slice(old_path_ptr,old_path_ptr+old_path_len));const new_path=new TextDecoder("utf-8").decode(buffer8.slice(new_path_ptr,new_path_ptr+new_path_len));return self.fds[new_fd].path_link(old_fd,old_flags,old_path,new_path)}else {return ERRNO_BADF}},path_open(fd,dirflags,path_ptr,path_len,oflags,fs_rights_base,fs_rights_inheriting,fd_flags,opened_fd_ptr){const buffer=new DataView(self.inst.exports.memory.buffer);const buffer8=new Uint8Array(self.inst.exports.memory.buffer);if(self.fds[fd]!=undefined){const path=new TextDecoder("utf-8").decode(buffer8.slice(path_ptr,path_ptr+path_len));debug.log(path);const{ret,fd_obj}=self.fds[fd].path_open(dirflags,path,oflags,fs_rights_base,fs_rights_inheriting,fd_flags);if(ret!=0){return ret}self.fds.push(fd_obj);const opened_fd=self.fds.length-1;buffer.setUint32(opened_fd_ptr,opened_fd,true);return 0}else {return ERRNO_BADF}},path_readlink(fd,path_ptr,path_len,buf_ptr,buf_len,nread_ptr){const buffer=new DataView(self.inst.exports.memory.buffer);const buffer8=new Uint8Array(self.inst.exports.memory.buffer);if(self.fds[fd]!=undefined){const path=new TextDecoder("utf-8").decode(buffer8.slice(path_ptr,path_ptr+path_len));debug.log(path);const{ret,data}=self.fds[fd].path_readlink(path);if(data!=null){if(data.length>buf_len){buffer.setUint32(nread_ptr,0,true);return ERRNO_BADF}buffer8.set(data,buf_ptr);buffer.setUint32(nread_ptr,data.length,true);}return ret}else {return ERRNO_BADF}},path_remove_directory(fd,path_ptr,path_len){const buffer8=new Uint8Array(self.inst.exports.memory.buffer);if(self.fds[fd]!=undefined){const path=new TextDecoder("utf-8").decode(buffer8.slice(path_ptr,path_ptr+path_len));return self.fds[fd].path_remove_directory(path)}else {return ERRNO_BADF}},path_rename(fd,old_path_ptr,old_path_len,new_fd,new_path_ptr,new_path_len){throw "FIXME what is the best abstraction for this?"},path_symlink(old_path_ptr,old_path_len,fd,new_path_ptr,new_path_len){const buffer8=new Uint8Array(self.inst.exports.memory.buffer);if(self.fds[fd]!=undefined){const old_path=new TextDecoder("utf-8").decode(buffer8.slice(old_path_ptr,old_path_ptr+old_path_len));const new_path=new TextDecoder("utf-8").decode(buffer8.slice(new_path_ptr,new_path_ptr+new_path_len));return self.fds[fd].path_symlink(old_path,new_path)}else {return ERRNO_BADF}},path_unlink_file(fd,path_ptr,path_len){const buffer8=new Uint8Array(self.inst.exports.memory.buffer);if(self.fds[fd]!=undefined){const path=new TextDecoder("utf-8").decode(buffer8.slice(path_ptr,path_ptr+path_len));return self.fds[fd].path_unlink_file(path)}else {return ERRNO_BADF}},poll_oneoff(in_,out,nsubscriptions){throw "async io not supported"},proc_exit(exit_code){throw new WASIProcExit(exit_code)},proc_raise(sig){throw "raised signal "+sig},sched_yield(){},random_get(buf,buf_len){const buffer8=new Uint8Array(self.inst.exports.memory.buffer);for(let i=0;i<buf_len;i++){buffer8[buf+i]=Math.random()*256|0;}},sock_recv(fd,ri_data,ri_flags){throw "sockets not supported"},sock_send(fd,si_data,si_flags){throw "sockets not supported"},sock_shutdown(fd,how){throw "sockets not supported"},sock_accept(fd,flags){throw "sockets not supported"}};}};
54
54
 
55
+ /**
56
+ * Create a console printer that can be used as an overlay of WASI imports.
57
+ * See the example below for how to use it.
58
+ *
59
+ * ```javascript
60
+ * const imports = {
61
+ * "wasi_snapshot_preview1": wasi.wasiImport,
62
+ * }
63
+ * const printer = consolePrinter();
64
+ * printer.addToImports(imports);
65
+ *
66
+ * const instance = await WebAssembly.instantiate(module, imports);
67
+ * printer.setMemory(instance.exports.memory);
68
+ * ```
69
+ *
70
+ * Note that the `stdout` and `stderr` functions are called with text, not
71
+ * bytes. This means that bytes written to stdout/stderr will be decoded as
72
+ * UTF-8 and then passed to the `stdout`/`stderr` functions every time a write
73
+ * occurs without buffering.
74
+ *
75
+ * @param stdout A function that will be called when stdout is written to.
76
+ * Defaults to `console.log`.
77
+ * @param stderr A function that will be called when stderr is written to.
78
+ * Defaults to `console.warn`.
79
+ * @returns An object that can be used as an overlay of WASI imports.
80
+ */
81
+ function consolePrinter({ stdout, stderr, } = {
82
+ stdout: console.log,
83
+ stderr: console.warn,
84
+ }) {
85
+ let memory = undefined;
86
+ let _view = undefined;
87
+ function getMemoryView() {
88
+ if (typeof memory === "undefined") {
89
+ throw new Error("Memory is not set");
90
+ }
91
+ if (_view === undefined || _view.buffer.byteLength === 0) {
92
+ _view = new DataView(memory.buffer);
93
+ }
94
+ return _view;
95
+ }
96
+ const decoder = new TextDecoder();
97
+ return {
98
+ addToImports(imports) {
99
+ const wasiImport = imports.wasi_snapshot_preview1;
100
+ const original_fd_write = wasiImport.fd_write;
101
+ wasiImport.fd_write = (fd, iovs, iovsLen, nwritten) => {
102
+ if (fd !== 1 && fd !== 2) {
103
+ return original_fd_write(fd, iovs, iovsLen, nwritten);
104
+ }
105
+ const view = getMemoryView();
106
+ const buffers = Array.from({ length: iovsLen }, (_, i) => {
107
+ const ptr = iovs + i * 8;
108
+ const buf = view.getUint32(ptr, true);
109
+ const bufLen = view.getUint32(ptr + 4, true);
110
+ return new Uint8Array(memory.buffer, buf, bufLen);
111
+ });
112
+ let written = 0;
113
+ let str = "";
114
+ for (const buffer of buffers) {
115
+ str += decoder.decode(buffer);
116
+ written += buffer.byteLength;
117
+ }
118
+ view.setUint32(nwritten, written, true);
119
+ const log = fd === 1 ? stdout : stderr;
120
+ log(str);
121
+ return 0;
122
+ };
123
+ const original_fd_filestat_get = wasiImport.fd_filestat_get;
124
+ wasiImport.fd_filestat_get = (fd, filestat) => {
125
+ if (fd !== 1 && fd !== 2) {
126
+ return original_fd_filestat_get(fd, filestat);
127
+ }
128
+ const view = getMemoryView();
129
+ const result = original_fd_filestat_get(fd, filestat);
130
+ if (result !== 0) {
131
+ return result;
132
+ }
133
+ const filetypePtr = filestat + 0;
134
+ view.setUint8(filetypePtr, 2); // FILETYPE_CHARACTER_DEVICE
135
+ return 0;
136
+ };
137
+ const original_fd_fdstat_get = wasiImport.fd_fdstat_get;
138
+ wasiImport.fd_fdstat_get = (fd, fdstat) => {
139
+ if (fd !== 1 && fd !== 2) {
140
+ return original_fd_fdstat_get(fd, fdstat);
141
+ }
142
+ const view = getMemoryView();
143
+ const fs_filetypePtr = fdstat + 0;
144
+ view.setUint8(fs_filetypePtr, 2); // FILETYPE_CHARACTER_DEVICE
145
+ const fs_rights_basePtr = fdstat + 8;
146
+ view.setBigUint64(fs_rights_basePtr, BigInt(1)); // RIGHTS_FD_WRITE
147
+ return 0;
148
+ };
149
+ },
150
+ setMemory(m) {
151
+ memory = m;
152
+ },
153
+ };
154
+ }
155
+
55
156
  let DATA_VIEW = new DataView(new ArrayBuffer());
56
157
 
57
158
  function data_view(mem) {
@@ -649,107 +750,6 @@
649
750
  };
650
751
  }
651
752
 
652
- /**
653
- * Create a console printer that can be used as an overlay of WASI imports.
654
- * See the example below for how to use it.
655
- *
656
- * ```javascript
657
- * const imports = {
658
- * "wasi_snapshot_preview1": wasi.wasiImport,
659
- * }
660
- * const printer = consolePrinter();
661
- * printer.addToImports(imports);
662
- *
663
- * const instance = await WebAssembly.instantiate(module, imports);
664
- * printer.setMemory(instance.exports.memory);
665
- * ```
666
- *
667
- * Note that the `stdout` and `stderr` functions are called with text, not
668
- * bytes. This means that bytes written to stdout/stderr will be decoded as
669
- * UTF-8 and then passed to the `stdout`/`stderr` functions every time a write
670
- * occurs without buffering.
671
- *
672
- * @param stdout A function that will be called when stdout is written to.
673
- * Defaults to `console.log`.
674
- * @param stderr A function that will be called when stderr is written to.
675
- * Defaults to `console.warn`.
676
- * @returns An object that can be used as an overlay of WASI imports.
677
- */
678
- function consolePrinter({ stdout, stderr, } = {
679
- stdout: console.log,
680
- stderr: console.warn,
681
- }) {
682
- let memory = undefined;
683
- let _view = undefined;
684
- function getMemoryView() {
685
- if (typeof memory === "undefined") {
686
- throw new Error("Memory is not set");
687
- }
688
- if (_view === undefined || _view.buffer.byteLength === 0) {
689
- _view = new DataView(memory.buffer);
690
- }
691
- return _view;
692
- }
693
- const decoder = new TextDecoder();
694
- return {
695
- addToImports(imports) {
696
- const wasiImport = imports.wasi_snapshot_preview1;
697
- const original_fd_write = wasiImport.fd_write;
698
- wasiImport.fd_write = (fd, iovs, iovsLen, nwritten) => {
699
- if (fd !== 1 && fd !== 2) {
700
- return original_fd_write(fd, iovs, iovsLen, nwritten);
701
- }
702
- const view = getMemoryView();
703
- const buffers = Array.from({ length: iovsLen }, (_, i) => {
704
- const ptr = iovs + i * 8;
705
- const buf = view.getUint32(ptr, true);
706
- const bufLen = view.getUint32(ptr + 4, true);
707
- return new Uint8Array(memory.buffer, buf, bufLen);
708
- });
709
- let written = 0;
710
- let str = "";
711
- for (const buffer of buffers) {
712
- str += decoder.decode(buffer);
713
- written += buffer.byteLength;
714
- }
715
- view.setUint32(nwritten, written, true);
716
- const log = fd === 1 ? stdout : stderr;
717
- log(str);
718
- return 0;
719
- };
720
- const original_fd_filestat_get = wasiImport.fd_filestat_get;
721
- wasiImport.fd_filestat_get = (fd, filestat) => {
722
- if (fd !== 1 && fd !== 2) {
723
- return original_fd_filestat_get(fd, filestat);
724
- }
725
- const view = getMemoryView();
726
- const result = original_fd_filestat_get(fd, filestat);
727
- if (result !== 0) {
728
- return result;
729
- }
730
- const filetypePtr = filestat + 0;
731
- view.setUint8(filetypePtr, 2); // FILETYPE_CHARACTER_DEVICE
732
- return 0;
733
- };
734
- const original_fd_fdstat_get = wasiImport.fd_fdstat_get;
735
- wasiImport.fd_fdstat_get = (fd, fdstat) => {
736
- if (fd !== 1 && fd !== 2) {
737
- return original_fd_fdstat_get(fd, fdstat);
738
- }
739
- const view = getMemoryView();
740
- const fs_filetypePtr = fdstat + 0;
741
- view.setUint8(fs_filetypePtr, 2); // FILETYPE_CHARACTER_DEVICE
742
- const fs_rights_basePtr = fdstat + 8;
743
- view.setBigUint64(fs_rights_basePtr, BigInt(1)); // RIGHTS_FD_WRITE
744
- return 0;
745
- };
746
- },
747
- setMemory(m) {
748
- memory = m;
749
- },
750
- };
751
- }
752
-
753
753
  /**
754
754
  * A Ruby VM instance
755
755
  *
@@ -822,11 +822,12 @@
822
822
  * @param args The command line arguments to pass to Ruby. Must be
823
823
  * an array of strings starting with the Ruby program name.
824
824
  */
825
- initialize(args = ["ruby.wasm", "--disable-gems", "-EUTF-8", "-e_=0"]) {
825
+ initialize(args = ["ruby.wasm", "-EUTF-8", "-e_=0"]) {
826
826
  const c_args = args.map((arg) => arg + "\0");
827
827
  this.guest.rubyInit();
828
828
  this.guest.rubySysinit(c_args);
829
829
  this.guest.rubyOptions(c_args);
830
+ this.eval(`require "/bundle/setup"`);
830
831
  }
831
832
  /**
832
833
  * Set a given instance to interact JavaScript and Ruby's
@@ -1061,13 +1062,8 @@
1061
1062
  */
1062
1063
  evalAsync(code) {
1063
1064
  const JS = this.eval("require 'js'; JS");
1064
- return new Promise((resolve, reject) => {
1065
- JS.call("__eval_async_rb", this.wrap(code), this.wrap({
1066
- resolve,
1067
- reject: (error) => {
1068
- reject(new RbError(this.exceptionFormatter.format(error, this, this.privateObject())));
1069
- },
1070
- }));
1065
+ return newRbPromise(this, this.privateObject(), (future) => {
1066
+ JS.call("__eval_async_rb", this.wrap(code), future);
1071
1067
  });
1072
1068
  }
1073
1069
  /**
@@ -1152,17 +1148,42 @@
1152
1148
  *
1153
1149
  * @param callee name of the Ruby method to call
1154
1150
  * @param args arguments to pass to the method. Must be an array of RbValue
1151
+ * @returns The result of the method call as a new RbValue.
1155
1152
  *
1156
1153
  * @example
1157
1154
  * const ary = vm.eval("[1, 2, 3]");
1158
1155
  * ary.call("push", 4);
1159
1156
  * console.log(ary.call("sample").toString());
1160
- *
1161
1157
  */
1162
1158
  call(callee, ...args) {
1163
1159
  const innerArgs = args.map((arg) => arg.inner);
1164
1160
  return new RbValue(callRbMethod(this.vm, this.privateObject, this.inner, callee, innerArgs), this.vm, this.privateObject);
1165
1161
  }
1162
+ /**
1163
+ * Call a given method that may call `JS::Object#await` with given arguments
1164
+ *
1165
+ * @param callee name of the Ruby method to call
1166
+ * @param args arguments to pass to the method. Must be an array of RbValue
1167
+ * @returns A Promise that resolves to the result of the method call as a new RbValue.
1168
+ *
1169
+ * @example
1170
+ * const client = vm.eval(`
1171
+ * require 'js'
1172
+ * class HttpClient
1173
+ * def get(url)
1174
+ * JS.global.fetch(url).await
1175
+ * end
1176
+ * end
1177
+ * HttpClient.new
1178
+ * `);
1179
+ * const response = await client.callAsync("get", vm.eval(`"https://example.com"`));
1180
+ */
1181
+ callAsync(callee, ...args) {
1182
+ const JS = this.vm.eval("require 'js'; JS");
1183
+ return newRbPromise(this.vm, this.privateObject, (future) => {
1184
+ JS.call("__call_async_method", this, this.vm.wrap(callee), future, ...args);
1185
+ });
1186
+ }
1166
1187
  /**
1167
1188
  * @see {@link https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toPrimitive}
1168
1189
  * @param hint Preferred type of the result primitive value. `"number"`, `"string"`, or `"default"`.
@@ -1361,6 +1382,18 @@
1361
1382
  return new RbValue(value, vm, privateObject);
1362
1383
  });
1363
1384
  };
1385
+ function newRbPromise(vm, privateObject, body) {
1386
+ return new Promise((resolve, reject) => {
1387
+ const future = vm.wrap({
1388
+ resolve,
1389
+ reject: (error) => {
1390
+ const rbError = new RbError(privateObject.exceptionFormatter.format(error, vm, privateObject));
1391
+ reject(rbError);
1392
+ },
1393
+ });
1394
+ body(future);
1395
+ });
1396
+ }
1364
1397
  /**
1365
1398
  * Error class thrown by Ruby execution
1366
1399
  */