# ruby-head-wasm-wasi

WebAssembly port of CRuby with WASI.

This package distributes the latest `master` branch of CRuby.

## Installation

For instaling ruby-head-wasm-wasi family, just run this command in your shell:

```console
$ npm install --save ruby-head-wasm-wasi@latest
# or if you want the nightly snapshot
$ npm install --save ruby-head-wasm-wasi@next
# or you can specify the exact snapshot version
$ npm install --save ruby-head-wasm-wasi@0.3.0-2022-04-16-a
```

## Quick Start (for Node.js)

See [the example project](https://github.com/ruby/ruby.wasm/tree/main/packages/npm-packages/ruby-wasm-wasi/example) for more details.

```javascript
import fs from "fs/promises";
import { DefaultRubyVM } from "ruby-head-wasm-wasi/dist/node.cjs.js";

const main = async () => {
  const binary = await fs.readFile(
    //  Tips: Replace the binary with debug info if you want symbolicated stack trace.
    //  (only nightly release for now)
    //  "./node_modules/ruby-head-wasm-wasi/dist/ruby.debug.wasm"
    "./node_modules/ruby-head-wasm-wasi/dist/ruby.wasm"
  );
  const module = await WebAssembly.compile(binary);
  const { vm } = await DefaultRubyVM(module);

  vm.eval(`
    luckiness = ["Lucky", "Unlucky"].sample
    puts "You are #{luckiness}"
  `);
};

main();
```

Then you can run the example project in your terminal:

```console
$ node --experimental-wasi-unstable-preview1 index.node.js
```

## Quick Start (for Browser)

In browser, you need a WASI polyfill
See [the example project](https://github.com/ruby/ruby.wasm/tree/main/packages/npm-packages/ruby-wasm-wasi/example) for more details.

```html
<html>
  <script src="https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@latest/dist/browser.umd.js"></script>
  <script>
    const { DefaultRubyVM } = window["ruby-wasm-wasi"];
    const main = async () => {
      // Fetch and instntiate WebAssembly binary
      const response = await fetch(
        //      Tips: Replace the binary with debug info if you want symbolicated stack trace.
        //      (only nightly release for now)
        //      "https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@latest/dist/ruby.debug.wasm"
        "https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@latest/dist/ruby.wasm"
      );
      const buffer = await response.arrayBuffer();
      const module = await WebAssembly.compile(buffer);
      const { vm } = await DefaultRubyVM(module);

      vm.printVersion();
      vm.eval(`
        require "js"
        luckiness = ["Lucky", "Unlucky"].sample
        JS::eval("document.body.innerText = '#{luckiness}'")
      `);
    };

    main();
  </script>
  <body></body>
</html>
```

## GC limitation with JavaScript interoperability

Since JavaScript's GC system and Ruby's GC system are separated and not cooperative, they cannot collect cyclic references between JavaScript and Ruby objects.

The following code will cause a memory leak:

```javascript
class JNode {
  setRNode(rnode) {
    this.rnode = rnode;
  }
}
jnode = new JNode();

rnode = vm.eval(`
class RNode
  def set_jnode(jnode)
    @jnode = jnode
  end
end
RNode.new
`);

rnode.call("set_jnode", vm.wrap(jnode));
jnode.setRNode(rnode);
```

<!-- The APIs section was generated by `npx documentation readme ../ruby-wasm-wasi/dist/index.esm.js --section=APIs` -->

## APIs

<!-- Generated by documentation.js. Update this documentation by updating the source code. -->

#### Table of Contents

- [RubyVM](#rubyvm)
  - [Examples](#examples)
  - [initialize](#initialize)
    - [Parameters](#parameters)
  - [setInstance](#setinstance)
    - [Parameters](#parameters-1)
  - [addToImports](#addtoimports)
    - [Parameters](#parameters-2)
  - [printVersion](#printversion)
  - [eval](#eval)
    - [Parameters](#parameters-3)
    - [Examples](#examples-1)
  - [wrap](#wrap)
    - [Parameters](#parameters-4)
    - [Examples](#examples-2)
- [JsValueTransport](#jsvaluetransport)
- [RbValue](#rbvalue)
  - [Parameters](#parameters-5)
  - [call](#call)
    - [Parameters](#parameters-6)
    - [Examples](#examples-3)
  - [toPrimitive](#toprimitive)
    - [Parameters](#parameters-7)
  - [toString](#tostring)
  - [toJS](#tojs)
- [RbError](#rberror)
  - [Parameters](#parameters-8)

### RubyVM

A Ruby VM instance

#### Examples

```javascript
const wasi = new WASI();
const vm = new RubyVM();
const imports = {
  wasi_snapshot_preview1: wasi.wasiImport,
};

vm.addToImports(imports);

const instance = await WebAssembly.instantiate(rubyModule, imports);
await vm.setInstance(instance);
wasi.initialize(instance);
vm.initialize();
```

#### initialize

Initialize the Ruby VM with the given command line arguments

##### Parameters

- `args` The command line arguments to pass to Ruby. Must be
  an array of strings starting with the Ruby program name. (optional, default `["ruby.wasm","--disable-gems","-e_=0"]`)

#### setInstance

Set a given instance to interact JavaScript and Ruby's
WebAssembly instance. This method must be called before calling
Ruby API.

##### Parameters

- `instance` The WebAssembly instance to interact with. Must
  be instantiated from a Ruby built with JS extension, and built
  with Reactor ABI instead of command line.

#### addToImports

Add intrinsic import entries, which is necessary to interact JavaScript
and Ruby's WebAssembly instance.

##### Parameters

- `imports` The import object to add to the WebAssembly instance

#### printVersion

Print the Ruby version to stdout

#### eval

Runs a string of Ruby code from JavaScript

##### Parameters

- `code` The Ruby code to run

##### Examples

```javascript
vm.eval("puts 'hello world'");
const result = vm.eval("1 + 2");
console.log(result.toString()); // 3
```

Returns **any** the result of the last expression

#### wrap

Wrap a JavaScript value into a Ruby JS::Object

##### Parameters

- `value` The value to convert to RbValue

##### Examples

```javascript
const hash = vm.eval(`Hash.new`);
hash.call("store", vm.eval(`"key1"`), vm.wrap(new Object()));
```

Returns **any** the RbValue object representing the given JS value

### JsValueTransport

Export a JS value held by the Ruby VM to the JS environment.
This is implemented in a dirty way since wit cannot reference resources
defined in other interfaces.
In our case, we can't express `function(v: rb-abi-value) -> js-abi-value`
because `rb-js-abi-host.wit`, that defines `js-abi-value`, is implemented
by embedder side (JS) but `rb-abi-guest.wit`, that defines `rb-abi-value`
is implemented by guest side (Wasm).

This class is a helper to export by:

1.  Call `function __export_to_js(v: rb-abi-value)` defined in guest from embedder side.
2.  Call `function takeJsValue(v: js-abi-value)` defined in embedder from guest side with
    underlying JS value of given `rb-abi-value`.
3.  Then `takeJsValue` implementation escapes the given JS value to the `_takenJsValues`
    stored in embedder side.
4.  Finally, embedder side can take `_takenJsValues`.

Note that `exportJsValue` is not reentrant.

### RbValue

A RbValue is an object that represents a value in Ruby

#### Parameters

- `inner`
- `vm`
- `privateObject`

#### call

Call a given method with given arguments

##### Parameters

- `callee` name of the Ruby method to call
- `args` **...any** arguments to pass to the method. Must be an array of RbValue

##### Examples

```javascript
const ary = vm.eval("[1, 2, 3]");
ary.call("push", 4);
console.log(ary.call("sample").toString());
```

#### toPrimitive

- **See**: <https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toPrimitive>

##### Parameters

- `hint`

#### toString

Returns a string representation of the value by calling `to_s`

#### toJS

Returns a JavaScript object representation of the value
by calling `to_js`.

Returns null if the value is not convertible to a JavaScript object.

### RbError

**Extends Error**

Error class thrown by Ruby execution

#### Parameters

- `message`

## Building the package from source

For building the package from source, you need to prepare a Ruby build produced by WASI SDK, and you need [`wit-bindgen`](https://github.com/bytecodealliance/wit-bindgen) and [`wasm-opt`](https://github.com/webassembly/binaryen#tools) in your PATH.

The instructions for building a Ruby targeting WebAssembly are available [here](https://github.com/ruby/ruby.wasm#building-from-source).

Then, you can run the following command in your shell:

```console
# Check the directory structure of your Ruby build
$ tree -L 3 path/to/wasm32-unknown-wasi-full-js/
path/to/wasm32-unknown-wasi-full-js/
├── usr
│   └── local
│       ├── bin
│       ├── include
│       ├── lib
│       └── share
└── var
    └── lib
        └── gems
$ ./build-package.sh path/to/wasm32-unknown-wasi-full-js/
Generating "/Users/katei/.ghq/github.com/ruby/ruby.wasm/packages/ruby-head-wasm-wasi/src/bindgen/intrinsics.js"
Generating "/Users/katei/.ghq/github.com/ruby/ruby.wasm/packages/ruby-head-wasm-wasi/src/bindgen/rb-abi-guest.d.ts"
Generating "/Users/katei/.ghq/github.com/ruby/ruby.wasm/packages/ruby-head-wasm-wasi/src/bindgen/rb-abi-guest.js"
Generating "/Users/katei/.ghq/github.com/ruby/ruby.wasm/packages/ruby-head-wasm-wasi/src/bindgen/rb-js-abi-host.d.ts"
Generating "/Users/katei/.ghq/github.com/ruby/ruby.wasm/packages/ruby-head-wasm-wasi/src/bindgen/rb-js-abi-host.js"

src/index.ts → dist/index.umd.js, dist/index.esm.js, dist/index.cjs.js...
created dist/index.umd.js, dist/index.esm.js, dist/index.cjs.js in 682ms
```
