@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/dist/cjs/vm.js ADDED
@@ -0,0 +1,677 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RbFatalError = exports.RbError = exports.RbValue = exports.RubyVM = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const RbAbi = tslib_1.__importStar(require("./bindgen/rb-abi-guest.js"));
6
+ const rb_js_abi_host_js_1 = require("./bindgen/rb-js-abi-host.js");
7
+ /**
8
+ * A Ruby VM instance
9
+ *
10
+ * @example
11
+ *
12
+ * const wasi = new WASI();
13
+ * const vm = new RubyVM();
14
+ * const imports = {
15
+ * wasi_snapshot_preview1: wasi.wasiImport,
16
+ * };
17
+ *
18
+ * vm.addToImports(imports);
19
+ *
20
+ * const instance = await WebAssembly.instantiate(rubyModule, imports);
21
+ * await vm.setInstance(instance);
22
+ * wasi.initialize(instance);
23
+ * vm.initialize();
24
+ *
25
+ */
26
+ class RubyVM {
27
+ constructor() {
28
+ this.instance = null;
29
+ this.interfaceState = {
30
+ hasJSFrameAfterRbFrame: false,
31
+ };
32
+ // Wrap exported functions from Ruby VM to prohibit nested VM operation
33
+ // if the call stack has sandwitched JS frames like JS -> Ruby -> JS -> Ruby.
34
+ const proxyExports = (exports) => {
35
+ const excludedMethods = [
36
+ "addToImports",
37
+ "instantiate",
38
+ "rbSetShouldProhibitRewind",
39
+ "rbGcDisable",
40
+ "rbGcEnable",
41
+ ];
42
+ const excluded = ["constructor"].concat(excludedMethods);
43
+ // wrap all methods in RbAbi.RbAbiGuest class
44
+ for (const key of Object.getOwnPropertyNames(RbAbi.RbAbiGuest.prototype)) {
45
+ if (excluded.includes(key)) {
46
+ continue;
47
+ }
48
+ const value = exports[key];
49
+ if (typeof value === "function") {
50
+ exports[key] = (...args) => {
51
+ const isNestedVMCall = this.interfaceState.hasJSFrameAfterRbFrame;
52
+ if (isNestedVMCall) {
53
+ const oldShouldProhibitRewind = this.guest.rbSetShouldProhibitRewind(true);
54
+ const oldIsDisabledGc = this.guest.rbGcDisable();
55
+ const result = Reflect.apply(value, exports, args);
56
+ this.guest.rbSetShouldProhibitRewind(oldShouldProhibitRewind);
57
+ if (!oldIsDisabledGc) {
58
+ this.guest.rbGcEnable();
59
+ }
60
+ return result;
61
+ }
62
+ else {
63
+ return Reflect.apply(value, exports, args);
64
+ }
65
+ };
66
+ }
67
+ }
68
+ return exports;
69
+ };
70
+ this.guest = proxyExports(new RbAbi.RbAbiGuest());
71
+ this.transport = new JsValueTransport();
72
+ this.exceptionFormatter = new RbExceptionFormatter();
73
+ }
74
+ /**
75
+ * Initialize the Ruby VM with the given command line arguments
76
+ * @param args The command line arguments to pass to Ruby. Must be
77
+ * an array of strings starting with the Ruby program name.
78
+ */
79
+ initialize(args = ["ruby.wasm", "-EUTF-8", "-e_=0"]) {
80
+ const c_args = args.map((arg) => arg + "\0");
81
+ this.guest.rubyInit();
82
+ this.guest.rubySysinit(c_args);
83
+ this.guest.rubyOptions(c_args);
84
+ this.eval(`require "/bundle/setup"`);
85
+ }
86
+ /**
87
+ * Set a given instance to interact JavaScript and Ruby's
88
+ * WebAssembly instance. This method must be called before calling
89
+ * Ruby API.
90
+ *
91
+ * @param instance The WebAssembly instance to interact with. Must
92
+ * be instantiated from a Ruby built with JS extension, and built
93
+ * with Reactor ABI instead of command line.
94
+ */
95
+ async setInstance(instance) {
96
+ this.instance = instance;
97
+ await this.guest.instantiate(instance);
98
+ }
99
+ /**
100
+ * Add intrinsic import entries, which is necessary to interact JavaScript
101
+ * and Ruby's WebAssembly instance.
102
+ * @param imports The import object to add to the WebAssembly instance
103
+ */
104
+ addToImports(imports) {
105
+ this.guest.addToImports(imports);
106
+ function wrapTry(f) {
107
+ return (...args) => {
108
+ try {
109
+ return { tag: "success", val: f(...args) };
110
+ }
111
+ catch (e) {
112
+ if (e instanceof RbFatalError) {
113
+ // RbFatalError should not be caught by Ruby because it Ruby VM
114
+ // can be already in an inconsistent state.
115
+ throw e;
116
+ }
117
+ return { tag: "failure", val: e };
118
+ }
119
+ };
120
+ }
121
+ imports["rb-js-abi-host"] = {
122
+ rb_wasm_throw_prohibit_rewind_exception: (messagePtr, messageLen) => {
123
+ const memory = this.instance.exports.memory;
124
+ const str = new TextDecoder().decode(new Uint8Array(memory.buffer, messagePtr, messageLen));
125
+ throw new RbFatalError("Ruby APIs that may rewind the VM stack are prohibited under nested VM operation " +
126
+ `(${str})\n` +
127
+ "Nested VM operation means that the call stack has sandwitched JS frames like JS -> Ruby -> JS -> Ruby " +
128
+ "caused by something like `window.rubyVM.eval(\"JS.global[:rubyVM].eval('Fiber.yield')\")`\n" +
129
+ "\n" +
130
+ "Please check your call stack and make sure that you are **not** doing any of the following inside the nested Ruby frame:\n" +
131
+ " 1. Switching fibers (e.g. Fiber#resume, Fiber.yield, and Fiber#transfer)\n" +
132
+ " Note that `evalAsync` JS API switches fibers internally\n" +
133
+ " 2. Raising uncaught exceptions\n" +
134
+ " Please catch all exceptions inside the nested operation\n" +
135
+ " 3. Calling Continuation APIs\n");
136
+ },
137
+ };
138
+ // NOTE: The GC may collect objects that are still referenced by Wasm
139
+ // locals because Asyncify cannot scan the Wasm stack above the JS frame.
140
+ // So we need to keep track whether the JS frame is sandwitched by Ruby
141
+ // frames or not, and prohibit nested VM operation if it is.
142
+ const proxyImports = (imports) => {
143
+ for (const [key, value] of Object.entries(imports)) {
144
+ if (typeof value === "function") {
145
+ imports[key] = (...args) => {
146
+ const oldValue = this.interfaceState.hasJSFrameAfterRbFrame;
147
+ this.interfaceState.hasJSFrameAfterRbFrame = true;
148
+ const result = Reflect.apply(value, imports, args);
149
+ this.interfaceState.hasJSFrameAfterRbFrame = oldValue;
150
+ return result;
151
+ };
152
+ }
153
+ }
154
+ return imports;
155
+ };
156
+ (0, rb_js_abi_host_js_1.addRbJsAbiHostToImports)(imports, proxyImports({
157
+ evalJs: wrapTry((code) => {
158
+ return Function(code)();
159
+ }),
160
+ isJs: (value) => {
161
+ // Just for compatibility with the old JS API
162
+ return true;
163
+ },
164
+ globalThis: () => {
165
+ if (typeof globalThis !== "undefined") {
166
+ return globalThis;
167
+ }
168
+ else if (typeof global !== "undefined") {
169
+ return global;
170
+ }
171
+ else if (typeof window !== "undefined") {
172
+ return window;
173
+ }
174
+ throw new Error("unable to locate global object");
175
+ },
176
+ intToJsNumber: (value) => {
177
+ return value;
178
+ },
179
+ floatToJsNumber: (value) => {
180
+ return value;
181
+ },
182
+ stringToJsString: (value) => {
183
+ return value;
184
+ },
185
+ boolToJsBool: (value) => {
186
+ return value;
187
+ },
188
+ procToJsFunction: (rawRbAbiValue) => {
189
+ const rbValue = this.rbValueOfPointer(rawRbAbiValue);
190
+ return (...args) => {
191
+ rbValue.call("call", ...args.map((arg) => this.wrap(arg)));
192
+ };
193
+ },
194
+ rbObjectToJsRbValue: (rawRbAbiValue) => {
195
+ return this.rbValueOfPointer(rawRbAbiValue);
196
+ },
197
+ jsValueToString: (value) => {
198
+ // According to the [spec](https://tc39.es/ecma262/multipage/text-processing.html#sec-string-constructor-string-value)
199
+ // `String(value)` always returns a string.
200
+ return String(value);
201
+ },
202
+ jsValueToInteger(value) {
203
+ if (typeof value === "number") {
204
+ return { tag: "f64", val: value };
205
+ }
206
+ else if (typeof value === "bigint") {
207
+ return { tag: "bignum", val: BigInt(value).toString(10) + "\0" };
208
+ }
209
+ else if (typeof value === "string") {
210
+ return { tag: "bignum", val: value + "\0" };
211
+ }
212
+ else if (typeof value === "undefined") {
213
+ return { tag: "f64", val: 0 };
214
+ }
215
+ else {
216
+ return { tag: "f64", val: Number(value) };
217
+ }
218
+ },
219
+ exportJsValueToHost: (value) => {
220
+ // See `JsValueExporter` for the reason why we need to do this
221
+ this.transport.takeJsValue(value);
222
+ },
223
+ importJsValueFromHost: () => {
224
+ return this.transport.consumeJsValue();
225
+ },
226
+ instanceOf: (value, klass) => {
227
+ if (typeof klass === "function") {
228
+ return value instanceof klass;
229
+ }
230
+ else {
231
+ return false;
232
+ }
233
+ },
234
+ jsValueTypeof(value) {
235
+ return typeof value;
236
+ },
237
+ jsValueEqual(lhs, rhs) {
238
+ return lhs == rhs;
239
+ },
240
+ jsValueStrictlyEqual(lhs, rhs) {
241
+ return lhs === rhs;
242
+ },
243
+ reflectApply: wrapTry((target, thisArgument, args) => {
244
+ return Reflect.apply(target, thisArgument, args);
245
+ }),
246
+ reflectConstruct: function (target, args) {
247
+ throw new Error("Function not implemented.");
248
+ },
249
+ reflectDeleteProperty: function (target, propertyKey) {
250
+ throw new Error("Function not implemented.");
251
+ },
252
+ reflectGet: wrapTry((target, propertyKey) => {
253
+ return target[propertyKey];
254
+ }),
255
+ reflectGetOwnPropertyDescriptor: function (target, propertyKey) {
256
+ throw new Error("Function not implemented.");
257
+ },
258
+ reflectGetPrototypeOf: function (target) {
259
+ throw new Error("Function not implemented.");
260
+ },
261
+ reflectHas: function (target, propertyKey) {
262
+ throw new Error("Function not implemented.");
263
+ },
264
+ reflectIsExtensible: function (target) {
265
+ throw new Error("Function not implemented.");
266
+ },
267
+ reflectOwnKeys: function (target) {
268
+ throw new Error("Function not implemented.");
269
+ },
270
+ reflectPreventExtensions: function (target) {
271
+ throw new Error("Function not implemented.");
272
+ },
273
+ reflectSet: wrapTry((target, propertyKey, value) => {
274
+ return Reflect.set(target, propertyKey, value);
275
+ }),
276
+ reflectSetPrototypeOf: function (target, prototype) {
277
+ throw new Error("Function not implemented.");
278
+ },
279
+ }), (name) => {
280
+ return this.instance.exports[name];
281
+ });
282
+ }
283
+ /**
284
+ * Print the Ruby version to stdout
285
+ */
286
+ printVersion() {
287
+ this.guest.rubyShowVersion();
288
+ }
289
+ /**
290
+ * Runs a string of Ruby code from JavaScript
291
+ * @param code The Ruby code to run
292
+ * @returns the result of the last expression
293
+ *
294
+ * @example
295
+ * vm.eval("puts 'hello world'");
296
+ * const result = vm.eval("1 + 2");
297
+ * console.log(result.toString()); // 3
298
+ *
299
+ */
300
+ eval(code) {
301
+ return evalRbCode(this, this.privateObject(), code);
302
+ }
303
+ /**
304
+ * Runs a string of Ruby code with top-level `JS::Object#await`
305
+ * Returns a promise that resolves when execution completes.
306
+ * @param code The Ruby code to run
307
+ * @returns a promise that resolves to the result of the last expression
308
+ *
309
+ * @example
310
+ * const text = await vm.evalAsync(`
311
+ * require 'js'
312
+ * response = JS.global.fetch('https://example.com').await
313
+ * response.text.await
314
+ * `);
315
+ * console.log(text.toString()); // <html>...</html>
316
+ */
317
+ evalAsync(code) {
318
+ const JS = this.eval("require 'js'; JS");
319
+ return newRbPromise(this, this.privateObject(), (future) => {
320
+ JS.call("__eval_async_rb", this.wrap(code), future);
321
+ });
322
+ }
323
+ /**
324
+ * Wrap a JavaScript value into a Ruby JS::Object
325
+ * @param value The value to convert to RbValue
326
+ * @returns the RbValue object representing the given JS value
327
+ *
328
+ * @example
329
+ * const hash = vm.eval(`Hash.new`)
330
+ * hash.call("store", vm.eval(`"key1"`), vm.wrap(new Object()));
331
+ */
332
+ wrap(value) {
333
+ return this.transport.importJsValue(value, this);
334
+ }
335
+ /** @private */
336
+ privateObject() {
337
+ return {
338
+ transport: this.transport,
339
+ exceptionFormatter: this.exceptionFormatter,
340
+ };
341
+ }
342
+ /** @private */
343
+ rbValueOfPointer(pointer) {
344
+ const abiValue = new RbAbi.RbAbiValue(pointer, this.guest);
345
+ return new RbValue(abiValue, this, this.privateObject());
346
+ }
347
+ }
348
+ exports.RubyVM = RubyVM;
349
+ /**
350
+ * Export a JS value held by the Ruby VM to the JS environment.
351
+ * This is implemented in a dirty way since wit cannot reference resources
352
+ * defined in other interfaces.
353
+ * In our case, we can't express `function(v: rb-abi-value) -> js-abi-value`
354
+ * because `rb-js-abi-host.wit`, that defines `js-abi-value`, is implemented
355
+ * by embedder side (JS) but `rb-abi-guest.wit`, that defines `rb-abi-value`
356
+ * is implemented by guest side (Wasm).
357
+ *
358
+ * This class is a helper to export by:
359
+ * 1. Call `function __export_to_js(v: rb-abi-value)` defined in guest from embedder side.
360
+ * 2. Call `function takeJsValue(v: js-abi-value)` defined in embedder from guest side with
361
+ * underlying JS value of given `rb-abi-value`.
362
+ * 3. Then `takeJsValue` implementation escapes the given JS value to the `_takenJsValues`
363
+ * stored in embedder side.
364
+ * 4. Finally, embedder side can take `_takenJsValues`.
365
+ *
366
+ * Note that `exportJsValue` is not reentrant.
367
+ *
368
+ * @private
369
+ */
370
+ class JsValueTransport {
371
+ constructor() {
372
+ this._takenJsValue = null;
373
+ }
374
+ takeJsValue(value) {
375
+ this._takenJsValue = value;
376
+ }
377
+ consumeJsValue() {
378
+ return this._takenJsValue;
379
+ }
380
+ exportJsValue(value) {
381
+ value.call("__export_to_js");
382
+ return this._takenJsValue;
383
+ }
384
+ importJsValue(value, vm) {
385
+ this._takenJsValue = value;
386
+ return vm.eval('require "js"; JS::Object').call("__import_from_js");
387
+ }
388
+ }
389
+ /**
390
+ * A RbValue is an object that represents a value in Ruby
391
+ */
392
+ class RbValue {
393
+ /**
394
+ * @hideconstructor
395
+ */
396
+ constructor(inner, vm, privateObject) {
397
+ this.inner = inner;
398
+ this.vm = vm;
399
+ this.privateObject = privateObject;
400
+ }
401
+ /**
402
+ * Call a given method with given arguments
403
+ *
404
+ * @param callee name of the Ruby method to call
405
+ * @param args arguments to pass to the method. Must be an array of RbValue
406
+ * @returns The result of the method call as a new RbValue.
407
+ *
408
+ * @example
409
+ * const ary = vm.eval("[1, 2, 3]");
410
+ * ary.call("push", 4);
411
+ * console.log(ary.call("sample").toString());
412
+ */
413
+ call(callee, ...args) {
414
+ const innerArgs = args.map((arg) => arg.inner);
415
+ return new RbValue(callRbMethod(this.vm, this.privateObject, this.inner, callee, innerArgs), this.vm, this.privateObject);
416
+ }
417
+ /**
418
+ * Call a given method that may call `JS::Object#await` with given arguments
419
+ *
420
+ * @param callee name of the Ruby method to call
421
+ * @param args arguments to pass to the method. Must be an array of RbValue
422
+ * @returns A Promise that resolves to the result of the method call as a new RbValue.
423
+ *
424
+ * @example
425
+ * const client = vm.eval(`
426
+ * require 'js'
427
+ * class HttpClient
428
+ * def get(url)
429
+ * JS.global.fetch(url).await
430
+ * end
431
+ * end
432
+ * HttpClient.new
433
+ * `);
434
+ * const response = await client.callAsync("get", vm.eval(`"https://example.com"`));
435
+ */
436
+ callAsync(callee, ...args) {
437
+ const JS = this.vm.eval("require 'js'; JS");
438
+ return newRbPromise(this.vm, this.privateObject, (future) => {
439
+ JS.call("__call_async_method", this, this.vm.wrap(callee), future, ...args);
440
+ });
441
+ }
442
+ /**
443
+ * @see {@link https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toPrimitive}
444
+ * @param hint Preferred type of the result primitive value. `"number"`, `"string"`, or `"default"`.
445
+ */
446
+ [Symbol.toPrimitive](hint) {
447
+ if (hint === "string" || hint === "default") {
448
+ return this.toString();
449
+ }
450
+ else if (hint === "number") {
451
+ return null;
452
+ }
453
+ return null;
454
+ }
455
+ /**
456
+ * Returns a string representation of the value by calling `to_s`
457
+ */
458
+ toString() {
459
+ const rbString = callRbMethod(this.vm, this.privateObject, this.inner, "to_s", []);
460
+ return this.vm.guest.rstringPtr(rbString);
461
+ }
462
+ /**
463
+ * Returns a JavaScript object representation of the value
464
+ * by calling `to_js`.
465
+ *
466
+ * Returns null if the value is not convertible to a JavaScript object.
467
+ */
468
+ toJS() {
469
+ const JS = this.vm.eval("JS");
470
+ const jsValue = JS.call("try_convert", this);
471
+ if (jsValue.call("nil?").toString() === "true") {
472
+ return null;
473
+ }
474
+ return this.privateObject.transport.exportJsValue(jsValue);
475
+ }
476
+ }
477
+ exports.RbValue = RbValue;
478
+ var ruby_tag_type;
479
+ (function (ruby_tag_type) {
480
+ ruby_tag_type[ruby_tag_type["None"] = 0] = "None";
481
+ ruby_tag_type[ruby_tag_type["Return"] = 1] = "Return";
482
+ ruby_tag_type[ruby_tag_type["Break"] = 2] = "Break";
483
+ ruby_tag_type[ruby_tag_type["Next"] = 3] = "Next";
484
+ ruby_tag_type[ruby_tag_type["Retry"] = 4] = "Retry";
485
+ ruby_tag_type[ruby_tag_type["Redo"] = 5] = "Redo";
486
+ ruby_tag_type[ruby_tag_type["Raise"] = 6] = "Raise";
487
+ ruby_tag_type[ruby_tag_type["Throw"] = 7] = "Throw";
488
+ ruby_tag_type[ruby_tag_type["Fatal"] = 8] = "Fatal";
489
+ ruby_tag_type[ruby_tag_type["Mask"] = 15] = "Mask";
490
+ })(ruby_tag_type || (ruby_tag_type = {}));
491
+ class RbExceptionFormatter {
492
+ constructor() {
493
+ this.literalsCache = null;
494
+ this.isFormmatting = false;
495
+ }
496
+ format(error, vm, privateObject) {
497
+ // All Ruby exceptions raised during formatting exception message should
498
+ // be caught and return a fallback message.
499
+ // Therefore, we don't need to worry about infinite recursion here ideally
500
+ // but checking re-entrancy just in case.
501
+ class RbExceptionFormatterError extends Error {
502
+ }
503
+ if (this.isFormmatting) {
504
+ throw new RbExceptionFormatterError("Unexpected exception occurred during formatting exception message");
505
+ }
506
+ this.isFormmatting = true;
507
+ try {
508
+ return this._format(error, vm, privateObject);
509
+ }
510
+ finally {
511
+ this.isFormmatting = false;
512
+ }
513
+ }
514
+ _format(error, vm, privateObject) {
515
+ const [zeroLiteral, oneLiteral, newLineLiteral] = (() => {
516
+ if (this.literalsCache == null) {
517
+ const zeroOneNewLine = [
518
+ evalRbCode(vm, privateObject, "0"),
519
+ evalRbCode(vm, privateObject, "1"),
520
+ evalRbCode(vm, privateObject, `"\n"`),
521
+ ];
522
+ this.literalsCache = zeroOneNewLine;
523
+ return zeroOneNewLine;
524
+ }
525
+ else {
526
+ return this.literalsCache;
527
+ }
528
+ })();
529
+ let className;
530
+ let backtrace;
531
+ let message;
532
+ try {
533
+ className = error.call("class").toString();
534
+ }
535
+ catch (e) {
536
+ className = "unknown";
537
+ }
538
+ try {
539
+ message = error.toString();
540
+ }
541
+ catch (e) {
542
+ message = "unknown";
543
+ }
544
+ try {
545
+ backtrace = error.call("backtrace");
546
+ }
547
+ catch (e) {
548
+ return this.formatString(className, message);
549
+ }
550
+ if (backtrace.call("nil?").toString() === "true") {
551
+ return this.formatString(className, message);
552
+ }
553
+ try {
554
+ const firstLine = backtrace.call("at", zeroLiteral);
555
+ const restLines = backtrace
556
+ .call("drop", oneLiteral)
557
+ .call("join", newLineLiteral);
558
+ return this.formatString(className, message, [
559
+ firstLine.toString(),
560
+ restLines.toString(),
561
+ ]);
562
+ }
563
+ catch (e) {
564
+ return this.formatString(className, message);
565
+ }
566
+ }
567
+ formatString(klass, message, backtrace) {
568
+ if (backtrace) {
569
+ return `${backtrace[0]}: ${message} (${klass})\n${backtrace[1]}`;
570
+ }
571
+ else {
572
+ return `${klass}: ${message}`;
573
+ }
574
+ }
575
+ }
576
+ const checkStatusTag = (rawTag, vm, privateObject) => {
577
+ switch (rawTag & ruby_tag_type.Mask) {
578
+ case ruby_tag_type.None:
579
+ break;
580
+ case ruby_tag_type.Return:
581
+ throw new RbError("unexpected return");
582
+ case ruby_tag_type.Next:
583
+ throw new RbError("unexpected next");
584
+ case ruby_tag_type.Break:
585
+ throw new RbError("unexpected break");
586
+ case ruby_tag_type.Redo:
587
+ throw new RbError("unexpected redo");
588
+ case ruby_tag_type.Retry:
589
+ throw new RbError("retry outside of rescue clause");
590
+ case ruby_tag_type.Throw:
591
+ throw new RbError("unexpected throw");
592
+ case ruby_tag_type.Raise:
593
+ case ruby_tag_type.Fatal:
594
+ const error = new RbValue(vm.guest.rbErrinfo(), vm, privateObject);
595
+ if (error.call("nil?").toString() === "true") {
596
+ throw new RbError("no exception object");
597
+ }
598
+ // clear errinfo if got exception due to no rb_jump_tag
599
+ vm.guest.rbClearErrinfo();
600
+ throw new RbError(privateObject.exceptionFormatter.format(error, vm, privateObject));
601
+ default:
602
+ throw new RbError(`unknown error tag: ${rawTag}`);
603
+ }
604
+ };
605
+ function wrapRbOperation(vm, body) {
606
+ try {
607
+ return body();
608
+ }
609
+ catch (e) {
610
+ if (e instanceof RbError) {
611
+ throw e;
612
+ }
613
+ // All JS exceptions triggered by Ruby code are translated to Ruby exceptions,
614
+ // so non-RbError exceptions are unexpected.
615
+ vm.guest.rbVmBugreport();
616
+ if (e instanceof WebAssembly.RuntimeError && e.message === "unreachable") {
617
+ const error = new RbError(`Something went wrong in Ruby VM: ${e}`);
618
+ error.stack = e.stack;
619
+ throw error;
620
+ }
621
+ else {
622
+ throw e;
623
+ }
624
+ }
625
+ }
626
+ const callRbMethod = (vm, privateObject, recv, callee, args) => {
627
+ const mid = vm.guest.rbIntern(callee + "\0");
628
+ return wrapRbOperation(vm, () => {
629
+ const [value, status] = vm.guest.rbFuncallvProtect(recv, mid, args);
630
+ checkStatusTag(status, vm, privateObject);
631
+ return value;
632
+ });
633
+ };
634
+ const evalRbCode = (vm, privateObject, code) => {
635
+ return wrapRbOperation(vm, () => {
636
+ const [value, status] = vm.guest.rbEvalStringProtect(code + "\0");
637
+ checkStatusTag(status, vm, privateObject);
638
+ return new RbValue(value, vm, privateObject);
639
+ });
640
+ };
641
+ function newRbPromise(vm, privateObject, body) {
642
+ return new Promise((resolve, reject) => {
643
+ const future = vm.wrap({
644
+ resolve,
645
+ reject: (error) => {
646
+ const rbError = new RbError(privateObject.exceptionFormatter.format(error, vm, privateObject));
647
+ reject(rbError);
648
+ },
649
+ });
650
+ body(future);
651
+ });
652
+ }
653
+ /**
654
+ * Error class thrown by Ruby execution
655
+ */
656
+ class RbError extends Error {
657
+ /**
658
+ * @hideconstructor
659
+ */
660
+ constructor(message) {
661
+ super(message);
662
+ }
663
+ }
664
+ exports.RbError = RbError;
665
+ /**
666
+ * Error class thrown by Ruby execution when it is not possible to recover.
667
+ * This is usually caused when Ruby VM is in an inconsistent state.
668
+ */
669
+ class RbFatalError extends RbError {
670
+ /**
671
+ * @hideconstructor
672
+ */
673
+ constructor(message) {
674
+ super("Ruby Fatal Error: " + message);
675
+ }
676
+ }
677
+ exports.RbFatalError = RbFatalError;
@@ -1,5 +1,5 @@
1
1
  import { WASI } from "@bjorn3/browser_wasi_shim";
2
- import { RubyVM } from "./index.js";
2
+ import { RubyVM } from "./vm.js";
3
3
  export declare const DefaultRubyVM: (rubyModule: WebAssembly.Module, options?: {
4
4
  consolePrint?: boolean;
5
5
  env?: Record<string, string> | undefined;
@@ -1,5 +1,6 @@
1
1
  import { WASI } from "@bjorn3/browser_wasi_shim";
2
- import { RubyVM, consolePrinter } from "./index.js";
2
+ import { consolePrinter } from "./console.js";
3
+ import { RubyVM } from "./vm.js";
3
4
  export const DefaultRubyVM = async (rubyModule, options = {}) => {
4
5
  var _a, _b;
5
6
  const args = [];