Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 8e1f797

Browse files
committed
Initial commit
0 parents  commit 8e1f797

16 files changed

+817
-0
lines changed

.github/workflows/main.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Github Pages
2+
on: [push]
3+
jobs:
4+
build-and-deploy:
5+
runs-on: ubuntu-latest
6+
steps:
7+
- name: Checkout
8+
uses: actions/checkout@master
9+
10+
- name: Setup Ruby
11+
uses: ruby/setup-ruby@v1
12+
with:
13+
bundler-cache: true
14+
ruby-version: head
15+
16+
- name: Setup node
17+
uses: actions/setup-node@v2
18+
with:
19+
node-version: 14.x
20+
cache: yarn
21+
22+
- name: Package application
23+
run: |
24+
bundle exec rake
25+
yarn install --frozen-lockfile
26+
yarn build
27+
28+
- name: Deploy
29+
uses: peaceiris/actions-gh-pages@v3
30+
with:
31+
github_token: ${{ secrets.GITHUB_TOKEN }}
32+
publish_dir: ./docs

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/docs/index.js*
2+
/head-wasm32-unknown-wasi-full-js/
3+
/lib/lib/
4+
/node_modules/
5+
/ruby.wasm
6+
/src/app.wasm
7+
/vendor/
8+
/wasi-vfs
9+
/wasmtime
10+
/yarn-error.log

Gemfile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# frozen_string_literal: true
2+
3+
source "https://rubygems.org"
4+
5+
gem "bundler"
6+
gem "rake"
7+
gem "syntax_tree"

Gemfile.lock

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
GEM
2+
remote: https://rubygems.org/
3+
specs:
4+
rake (13.0.6)
5+
syntax_tree (2.3.1)
6+
7+
PLATFORMS
8+
x86_64-darwin-21
9+
x86_64-linux
10+
11+
DEPENDENCIES
12+
bundler
13+
rake
14+
syntax_tree
15+
16+
BUNDLED WITH
17+
2.3.6

Rakefile

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# frozen_string_literal: true
2+
3+
file "wasi-vfs" do
4+
version = "0.1.1"
5+
filename =
6+
if ENV["CI"]
7+
"wasi-vfs-cli-x86_64-unknown-linux-gnu.zip"
8+
else
9+
"wasi-vfs-cli-x86_64-apple-darwin.zip"
10+
end
11+
12+
`curl -LO "https://github.com/kateinoigakukun/wasi-vfs/releases/download/v#{version}/#{filename}"`
13+
`unzip #{filename}`
14+
rm filename
15+
end
16+
17+
file "wasmtime" do
18+
`curl https://wasmtime.dev/install.sh -sSf | bash`
19+
cp "#{ENV["HOME"]}/.wasmtime/bin/wasmtime", "wasmtime"
20+
end
21+
22+
file "head-wasm32-unknown-wasi-full-js" do
23+
require "json"
24+
version = JSON.parse(File.read("package.json"))["dependencies"]["ruby-head-wasm-wasi"][1..]
25+
filename = "ruby-head-wasm32-unknown-wasi-full-js.tar.gz"
26+
27+
`curl -LO https://github.com/ruby/ruby.wasm/releases/download/ruby-head-wasm-wasi-#{version}/#{filename}`
28+
`tar xfz #{filename}`
29+
rm filename
30+
end
31+
32+
file "ruby.wasm" => ["head-wasm32-unknown-wasi-full-js"] do
33+
mv "head-wasm32-unknown-wasi-full-js/usr/local/bin/ruby", "ruby.wasm"
34+
end
35+
36+
file "src/app.wasm" => ["wasi-vfs", "wasmtime", "ruby.wasm", "lib/app.rb"] do
37+
require "bundler/setup"
38+
cp_r $:.find { _1.include?("syntax_tree") }, "lib/lib"
39+
40+
`./wasi-vfs pack ruby.wasm --mapdir /lib::./lib --mapdir /usr::./head-wasm32-unknown-wasi-full-js/usr -o src/app.wasm`
41+
rm_rf "lib/lib"
42+
end
43+
44+
task default: ["src/app.wasm"]

bin/build

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/usr/bin/env node
2+
3+
const esbuild = require("esbuild");
4+
const path = require("path");
5+
6+
const wasmPlugin = require("./wasmPlugin");
7+
8+
esbuild.build({
9+
bundle: true,
10+
entryPoints: [path.join(__dirname, "../src/index.tsx")],
11+
format: "esm",
12+
minify: true,
13+
outdir: path.join(__dirname, "../docs"),
14+
plugins: [wasmPlugin],
15+
sourcemap: true,
16+
splitting: true,
17+
target: "es6"
18+
});

bin/serve

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/usr/bin/env node
2+
3+
const esbuild = require("esbuild");
4+
const path = require("path");
5+
6+
const wasmPlugin = require("./wasmPlugin");
7+
8+
const serveOptions = {
9+
servedir: path.join(__dirname, "../docs")
10+
};
11+
12+
const buildOptions = {
13+
bundle: true,
14+
entryPoints: [path.join(__dirname, "../src/index.tsx")],
15+
format: "esm",
16+
outdir: path.join(__dirname, "../docs"),
17+
plugins: [wasmPlugin],
18+
sourcemap: true,
19+
splitting: true,
20+
target: "esnext"
21+
};
22+
23+
esbuild.serve(serveOptions, buildOptions).then((result) => {
24+
console.log(`Listening at ${result.host}:${result.port}`);
25+
});

bin/wasmPlugin.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
module.exports = {
2+
name: 'wasm',
3+
setup(build) {
4+
let path = require('path')
5+
let fs = require('fs')
6+
7+
// Resolve ".wasm" files to a path with a namespace
8+
build.onResolve({ filter: /\.wasm$/ }, args => {
9+
// If this is the import inside the stub module, import the
10+
// binary itself. Put the path in the "wasm-binary" namespace
11+
// to tell our binary load callback to load the binary file.
12+
if (args.namespace === 'wasm-stub') {
13+
return {
14+
path: args.path,
15+
namespace: 'wasm-binary',
16+
}
17+
}
18+
19+
// Otherwise, generate the JavaScript stub module for this
20+
// ".wasm" file. Put it in the "wasm-stub" namespace to tell
21+
// our stub load callback to fill it with JavaScript.
22+
//
23+
// Resolve relative paths to absolute paths here since this
24+
// resolve callback is given "resolveDir", the directory to
25+
// resolve imports against.
26+
if (args.resolveDir === '') {
27+
return // Ignore unresolvable paths
28+
}
29+
return {
30+
path: path.isAbsolute(args.path) ? args.path : path.join(args.resolveDir, args.path),
31+
namespace: 'wasm-stub',
32+
}
33+
})
34+
35+
// Virtual modules in the "wasm-stub" namespace are filled with
36+
// the JavaScript code for compiling the WebAssembly binary. The
37+
// binary itself is imported from a second virtual module.
38+
build.onLoad({ filter: /.*/, namespace: 'wasm-stub' }, async (args) => ({
39+
contents: `import wasm from ${JSON.stringify(args.path)}
40+
export default (imports) => WebAssembly.instantiate(wasm, imports).then((result) => result.instance)`,
41+
}))
42+
43+
// Virtual modules in the "wasm-binary" namespace contain the
44+
// actual bytes of the WebAssembly file. This uses esbuild's
45+
// built-in "binary" loader instead of manually embedding the
46+
// binary data inside JavaScript code ourselves.
47+
build.onLoad({ filter: /.*/, namespace: 'wasm-binary' }, async (args) => ({
48+
contents: await fs.promises.readFile(args.path),
49+
loader: 'binary',
50+
}))
51+
},
52+
}

docs/index.css

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
html, body {
2+
height: 100vh;
3+
margin: 0;
4+
}
5+
6+
#root {
7+
box-sizing: border-box;
8+
display: flex;
9+
gap: 0.5em;
10+
height: 100vh;
11+
padding: 0.5em;
12+
}
13+
14+
textarea {
15+
box-sizing: border-box;
16+
height: 100%;
17+
width: 100%;
18+
}

docs/index.html

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="initial-scale=1, maximum-scale=5">
6+
<title>Syntax Tree</title>
7+
<link rel="stylesheet" type="text/css" href="index.css">
8+
</head>
9+
<body>
10+
<noscript>JavaScript must be enabled in order to run this application.</noscript>
11+
<div id="root"></div>
12+
<script type="module" src="index.js"></script>
13+
</body>
14+
</html>

lib/app.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# frozen_string_literal: true
2+
3+
$:.unshift(File.expand_path("lib", __dir__))
4+
require "syntax_tree"

package.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"license": "MIT",
3+
"scripts": {
4+
"build": "bin/build",
5+
"serve": "bin/serve"
6+
},
7+
"dependencies": {
8+
"@wasmer/wasi": "^0.12.0",
9+
"@wasmer/wasmfs": "^0.12.0",
10+
"path-browserify": "^1.0.1",
11+
"react": "^18.0.0",
12+
"react-dom": "^18.0.0",
13+
"ruby-head-wasm-wasi": "^0.3.0"
14+
},
15+
"devDependencies": {
16+
"@types/node": "^17.0.27",
17+
"@types/path-browserify": "^1.0.0",
18+
"@types/react": "^18.0.6",
19+
"@types/react-dom": "^18.0.2",
20+
"esbuild": "^0.14.38"
21+
}
22+
}

src/createRuby.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { WASI } from "@wasmer/wasi";
2+
import { WasmFs } from "@wasmer/wasmfs";
3+
import path from "path-browserify";
4+
import { RubyVM } from "ruby-head-wasm-wasi/dist/index";
5+
6+
import load from "./app.wasm";
7+
8+
// This overwrites the default writeSync function used by the WasmFs to instead
9+
// pipe it out to the console.
10+
function createWriter(originalWriter: Function) {
11+
return function () {
12+
let text: string;
13+
14+
if (arguments.length === 4) {
15+
text = arguments[1];
16+
} else {
17+
text = new TextDecoder("utf-8").decode(arguments[1]);
18+
}
19+
20+
switch (arguments[0]) {
21+
case 1:
22+
console.log(text);
23+
break;
24+
case 2:
25+
console.warn(text);
26+
break;
27+
}
28+
29+
return originalWriter.call(arguments);
30+
}
31+
}
32+
33+
export default async function createRuby() {
34+
// First, create a new file system that we can use internally within the Ruby
35+
// WASM VM.
36+
const wasmFs = new WasmFs();
37+
wasmFs.fs.mkdirSync("/tmp", 0o777);
38+
wasmFs.fs.writeSync = createWriter(wasmFs.fs.writeSync.bind(wasmFs.fs));
39+
40+
// Next, create a new WASI instance with the correct options overridden from
41+
// the defaults.
42+
const wasi = new WASI({
43+
bindings: { ...WASI.defaultBindings, fs: wasmFs.fs, path: path },
44+
preopens: { "/": "/tmp" }
45+
});
46+
47+
// Then, create a new Ruby VM instance that we can use to store the memory for
48+
// our application.
49+
const ruby = new RubyVM();
50+
const imports = { wasi_snapshot_preview1: wasi.wasiImport };
51+
ruby.addToImports(imports);
52+
53+
// Set the WASI memory to use the memory for our application.
54+
const instance = await load(imports);
55+
wasi.setMemory(instance.exports.memory);
56+
57+
// Load our application into the virtual machine.
58+
instance.exports._initialize();
59+
await ruby.setInstance(instance);
60+
61+
// Initial our virtual machine and return it. It should now be able to
62+
// evaluate and execute Ruby code.
63+
ruby.initialize();
64+
65+
// Once our virtual machine is booted, we're going to require the necessary
66+
// files to make it work. I'm not sure why I need to explicitly require
67+
// did_you_mean here, but it doesn't work without it.
68+
ruby.eval(`require "did_you_mean"; require "json"; require_relative "/lib/app"`);
69+
70+
return {
71+
// A function that calls through to PP to get the pretty-printed version of
72+
// the syntax tree.
73+
prettyPrint(source: string) {
74+
const rubySource = `
75+
PP.format([], 80) do |q|
76+
source = JSON.parse(${JSON.stringify(JSON.stringify(source))})
77+
SyntaxTree.parse(source).pretty_print(q)
78+
end.join
79+
`;
80+
81+
return ruby.eval(rubySource).toString();
82+
}
83+
};
84+
};
85+
86+
export type Ruby = Awaited<ReturnType<typeof createRuby>>;

0 commit comments

Comments
 (0)