はじめに
Node.jsで小さなプログラミング言語を作ってみるシリーズを、「ミニインタープリター」「ミニコンパイラー」とやってきました。そして三部作(?)の最後として、 ミニNode.jsからWASMを生成する小さなコンパイラーに取り組んでいます。
※以前の記事「Node.js でつくる WASM トランスパイラー - 05:条件分岐とループを実装する」のアップデート版になります。
これまでの取り組み
今回実現したいこと(1) if
今回の目標の1つ目は条件分岐(if 〜 else)のサポートです。
ifの例
対象となるミニNode.jsのコードはこちら。
if (1) {
putn(111);
}
生成したいWATはこちらです。
i32.const 1
if
i32.const 111
call $putn
end
if 〜 else の例
もう1つ、elseがある場合はこちら。
if (0) {
putn(111);
}
else {
putn(0);
}
同様に、このようにWATを生成していきます。
i32.const 0
if
i32.const 111
call $putn
else
i32.const 0
call $putn
end
if 〜 else の生成
いつものようにコンパイラーのgenerate()関数を拡張します。実際にif〜elseに対応する部分は、genereateIf()関数で生成しています。
function generate(tree, indent, lctx) {
// ... 省略 ...
// --- if ---
if (tree[0] === 'if') {
const block = genereateIf(tree, indent, lctx);
return block;
}
// ... 省略 ...
}
// --- if ---
// tree = ['if', condition, then, else]
function genereateIf(tree, indent, lctx) {
const conditionBlock = generate(tree[1], indent, lctx);
const positiveBlock = generate(tree[2], indent + 1, lctx);
// -- condition --
let block = conditionBlock + LF();
// -- if-then --
block = block + TABs(indent) + 'if' + LF();
block = block + positiveBlock;
// -- else ---
if (tree[3]) {
const negativeBlock = generate(tree[3], indent + 1, lctx);
block = block + TABs(indent) + 'else' + LF();
block = block + negativeBlock;
}
// -- end --
block = block + TABs(indent) + 'end' + LF();
return block;
}
if 〜 else の実行
複数の条件分岐を使ったサンプルを用意します。
let a = 1;
if (a === 1) {
putn(111);
}
if (a !== 1) {
putn(222);
}
else {
putn(333);
}
putn(999);
33;
これをWASMに変換、実行した結果がこちらです。
$ node mininode_wasm_05.js sample/if.js
$ wat2wasm generated.wat
$ node run_wasm_putn.js generated.wasm
Loading wasm file: generated.wasm
111
333
999
ret code=33
想定通りの出力になりました。
今回実現したいこと(2) while ループ
今回の目標の2つ目は while ループのサポートです。
whileの例
let a = 0;
while (a < 10) {
putn(a);
a = a + 1;
}
生成したいWATはこちら。
(local $a i32)
i32.const 0
set_local $a
loop ;; --begin of while loop--
get_local $a
i32.const 10
i32.lt_s
if
get_local $a
call $putn
get_local $a
i32.const 1
i32.add
set_local $a
br 1 ;; --jump to head of while loop--
end ;; end of if-then
end ;; --end of while loop--
色々な実現の仕方があると思いますが、今回は while ループの中身を if で記述し、br 命令でループの先頭に戻るようにしました。
while の生成
再度generate()関数を拡張します。実際にwhileに対応する部分は、genereateWhile()関数で生成しています。
function generate(tree, indent, lctx) {
// ... 省略 ...
// --- while ---
if (tree[0] === 'while') {
const block = genereateWhile(tree, indent, lctx);
return block;
}
// ... 省略 ...
}
// --- while ---
// tree = ['while', condition, then, else]
function genereateWhile(tree, indent, lctx) {
const conditionBlock = generate(tree[1], indent + 1, lctx);
const innerBlock = generate(tree[2], indent + 2, lctx);
let block = TABs(indent) + 'loop ;; --begin of while loop--' + LF();
block = block + conditionBlock;
block = block + TABs(indent + 1) + 'if' + LF();
block = block + innerBlock;
block = block + TABs(indent + 2) + 'br 1 ;; --jump to head of while loop--' + LF();
block = block + TABs(indent + 1) + 'end ;; end of if-then' + LF();
block = block + TABs(indent) + 'end ;; --end of while loop--';
return block;
}
while の実行
whileのサンプルも用意しました。
let a = 0;
while (a < 10) {
putn(a);
a = a + 1;
}
0;
これをWASMに変換、実行した結果がこちらです。
$ node mininode_wasm_05.js sample/while.js
$ wat2wasm generated.wat
$ node run_wasm_putn.js generated.wasm
Loading wasm file: generated.wasm
0
1
2
3
4
5
6
7
8
9
ret code=0
無事ループが実行されました。
次回は
いよいよ目標の1つであるFizzBuzzにチャレンジする予定です。
ここまでのソース
GitHubにソースを上げておきます。
- GitHubのレポジトリ ... https://github.com/mganeko/mini_node_wasm
- mininode_wasm_05.js ... 今回のWASMコンパイラー
- sample/if.js ... if〜elseのサンプル
- sample/while.js ... whileのサンプル