webassemblyで自作言語のplaygroundを作ってみた
TL;DR
- 「The Go Playground」みたいなweb上言語実行環境の自作言語版を作成: Pangaea Playground
-
syscall/js
を使い、Go製インタープリターをwasmへコンパイルして実行
- GitHub Actionsでビルド、GitHub Pagesへデプロイ
はじめに
syscall/js
を使い、Go製インタープリターをwasmへコンパイルして実行昨年(2020年)から、プログラミング言語「Pangaea」を自作しています。
言語仕様については別記事で紹介しておりますのでよろしければご覧ください。
ホスト言語はGo言語で、インタープリターのバイナリも公開しています。
バイナリをダウンロードするだけで使えるのですが、「ちょっと試してみるか」というときにはやはりweb上の方がとっつきやすいですよね...
というわけで、布教しやすいように Web上のPangaea実行環境を作成しました。
「The Go Playground」にあやかって、「Pangaea Playground」という名前にしています。
構成
インタープリターをwasmにコンパイルし、jsから呼び出しています。
- index.html
- style.css
- pangaea.js (wasmのfetch、セットアップ)
- main.wasm (インタープリターのバイナリ)
- wasm_exec.js (golangをコンパイルしたwasmの実行に必要)
wasmのビルド
特別なツールは不要で、go buildにフラグを指定するだけでwasmが生成されます。
GOOS=js GOARCH=wasm go build -o main.wasm
参考:
普通のgo build
と違い、mainパッケージ以外をwasmにビルドしようとすると失敗します!(後述「wasmの謎エラー」)
インタープリター関数をjsから呼び出せるようにする
インタープリターにはソースコード、標準入力の引数を渡したいので、syscall/js
パッケージを使いjsの関数として登録します。
js.Global().Set()
でオブジェクトを登録することで、pangaeaインタープリターの関数をjs上pangaea.execute()
で呼び出せるようになります。
js.Global().Set("pangaea", js.ValueOf(
map[string]interface{}{
"execute": js.FuncOf(ex.Execute),
},
))
あとは、func (this js.Value, args []js.Value) interface{}
のシグネチャに合うようにインタープリター関数をラップしてあげれば実装終了です。
https://github.com/Syuparn/Pangaea/blob/master/web/wasm/executor.go#L30
// シグネチャは (src, stdin) => ({res: res, stdout; stdout, errmsg: errmsg})の形式
func (e *Executor) Execute(this js.Value, args []js.Value) interface{} {
src := e.setupSrc(args)
stdin := e.setupStdin(args)
stdout := &bytes.Buffer{}
// ソースコード実行
res, errmsg := e.execute(src, stdin, stdout)
if errmsg != "" {
return map[string]interface{}{
"res": "",
"stdout": stdout.String(),
"errmsg": errmsg,
}
}
return map[string]interface{}{
"res": res.Repr(),
"stdout": stdout.String(),
"errmsg": errmsg,
}
}
wasmが読み込まれた後は、普通のjsの関数と変わりなく使用することができます。
https://github.com/Syuparn/Pangaea/blob/master/web/playground/index.html#L26
function runScript() {
const src = document.getElementById('source').value;
const stdin = document.getElementById('input').value;
const result = pangaea.execute(src, stdin);
if (result.errmsg !== '') {
document.getElementById("output").textContent = result.errmsg;
return;
}
document.getElementById("output").textContent = result.stdout;
}
参考:
wasmの読み込み、実行
Go製のwasmを動かすにはwasm_exec.js
が必要なので、公式リポジトリからダウンロードします。念のためバイナリと同じバージョンを利用しています。
<!-- https://raw.githubusercontent.com/golang/go/go1.16.5/misc/wasm/wasm_exec.js をコピー -->
<script src="wasm_exec.js"></script>
後は、wasmのfetch処理の後でgo.run()
することで実行されます。
// wasm_exec.js 読み込み
const go = new Go();
// wasmを実行
fetch("./main.wasm").then(response =>
response.arrayBuffer()
).then(bytes =>
// 初期化
WebAssembly.instantiate(bytes, go.importObject)
).then(obj => {
// 実行(完了すると、`pangaea.execute()`でインタープリターが呼び出せるようになる)
go.run(obj.instance);
});
参考:
ビルド
リポジトリのGitHub Pages上で公開しています。
wasmのビルドとGitHub PagesへのデプロイはGitHub Actionsで行っています。マージするたびに勝手に更新されるので便利です
https://github.com/Syuparn/Pangaea/blob/master/.github/workflows/deploy_playground.yml
(yamlが汚い...)
デプロイにはこちらのActionを使用させていただきました。
参考:
詰まったところ
ローカル環境でwasm読み込みができない
初歩的なミスですが、index.htmlをダブルクリックしてもwasmへアクセスできません。ファイルサーバーを立てて確認しましょう。
Fetch API cannot load file:///C:/xxx/Pangaea/web/playground/main.wasm. URL scheme must be "http" or "https" for CORS request.
個人的には python -m http.server 8080
が使いやすくておすすめです。
wasmの謎エラー
Uncaught (in promise) CompileError: WebAssembly.instantiate(): expected magic word 00 61 73 6d, found 21 3c 61 72 @+0
main以外のパッケージからバイナリを生成しようとしたため、wasmではなくオブジェクトファイルが生成されていたのが原因でした。
(21 3c 61 72
をasciiで読むと!<ar
なのですが、何が表示されているのでしょうか...?ご存知の方はコメント欄でご教示いただけるとありがたいです )
mainパッケージはネイティブバイナリのREPL用で既に使っているので、しかたなくwasmパッケージにもgo.modを作り別のmoduleとしました。
初期化が10秒以上かかる
(2021/8/17追記:ボトルネック解消でロードを2~3秒まで縮めることができました)
Pangaeaビルトインオブジェクトのソースコード評価に10秒以上かかるため、その間一切UIが操作を受け付けない状態になってしまいます。
速度を一切無視した弊害が出てきました...ブラウザバックされそう
せめてフリーズはしていないことを伝えられるよう、暫定措置としてロード中にNow loading... (it may take 10 ~ 20s to setup)
と表示することにしました。
メッセージ読んでもブラウザバックしますね
おわりに
以上、Go + WebAssembly + GitHub Pagesで自作言語Playgroundを作る方法の紹介でした。皆さんもPlaygroundで自慢の自作言語を布教しましょう!
Author And Source
この問題について(webassemblyで自作言語のplaygroundを作ってみた), 我々は、より多くの情報をここで見つけました https://qiita.com/Syuparn/items/7463fd798dc0ab94f468著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .