Node.js でつくる Node.js - Extra 1: ミニRubyの単純化Treeを実行する

# はじめに

「RubyでつくるRuby ゼロから学びなおすプログラミング言語入門」([ラムダノート](https://www.lambdanote.com/products/ruby-ruby), [Amazon](https://www.amazon.co.jp/dp/4908686017)) に感銘をうけて、ミニNode.js（以下mininode）を作ってきました。中間で生成している単純化構文木 (Tree)は、基本的にミニRuby（以下miniruby）と同じ形をしています。ということは、それを中間言語と扱ってminiruby用のコードを実行できるのではないでしょうか？

* [もくじ](https://qiita.com/massie_g/items/3ee11c105b4458686bc1)


# minirubyをmininodeで実行できるか？

一種の中間言語となるTreeが共通なら、minirubyで生成したものをmininodeで実行できるはずです。となると、minirubyはブートストラップ可能なので、そのままmininodeで実行できるのでは？　と一瞬期待してしまいました。
が、冷静に考えると組み込み関数として用意された部分はRubyの機能やモジュールに依存していて、mininodeからは実行できません。
そこで、minirubyそのものを実行するのではなく、途中で作られるsimplify()済みのTreeをmininodeで読み込んで実行することに切り替えました。

* rubyコード → minirubyでsimplify()  → JSONで保存 → mininodeで読み込み実行（evaluate)

# minirubyのsimplify()結果を保存

## fizzbuzzのコード

もとになるRubyのFizzBuzzのコードです。ユーザ定義関数を使うようにしました。

```rb:fizzbuzz_func.rb
def fizzbuzz(n)
  if n % 15 == 0
    "FizzBuzz"
  elsif n % 3 == 0
    "Fizz"
  elsif n % 5 == 0
    "Buzz"
  else
    n
  end
end

i = 1
while i < 100
  p(fizzbuzz(i))
  i = i + 1
end
```

## Rubyで単純化Treeを保存

先ほどのRubyのコードをパースして単純化します。用意されているgemを使えば簡単ですね。

```rb:savetree.rb
require "./minruby"
require "json"

# read file 
str = minruby_load()

# parse line
tree = minruby_parse(str)

# write to json
json_file_path = 'fizzbuzz_tree.json';
j = { tree: tree };
open(json_file_path, 'w') do |io|
  JSON.dump(j, io)
end
```

実行すると、決め打ちのファイル名 fizzbuzz_tree.json に単純化したTreeがJSON形式で書き込まれます。

```
$ ruby savetree.rb fizzbuzz_func.rb
```

## 保存されたTree

保存された fizzbuzz_tree.json を少し整形して掲載します。

```json
{"tree":
  ["stmts",
    ["func_def","fizzbuzz",["n"],
      ["if",["==",["%",["var_ref","n"],["lit",15]],["lit",0]],
        ["lit","FizzBuzz"],
        ["if",["==",["%",["var_ref","n"],["lit",3]],["lit",0]],
          ["lit","Fizz"],
          ["if",["==",
            ["%",["var_ref","n"],["lit",5]],["lit",0]],
            ["lit","Buzz"],["var_ref","n"]
          ]
        ]
      ]
    ],
    ["var_assign","i",["lit",1]],
    ["while",["<",["var_ref","i"],["lit",100]],
      ["stmts",
        ["func_call","p",
          ["func_call","fizzbuzz",["var_ref","i"]]
        ],
        ["var_assign","i",["+",["var_ref","i"],["lit",1]]]
      ]
    ]
  ]
}
```

# mininodeで実行

##単純化Tree 読み込む

保存済みTreeを読み込み、それを実行します。

```js

const fs = require('fs');

// ---- Extra1 for miniruby ----
function loadJson(filename) {
  const src = fs.readFileSync(filename, 'utf-8');
  const json = JSON.parse(src);
  return json;
}

const json = loadJson(process.argv[2]);
const tree = json.tree;
const answer = evaluate(tree, genv, lenv);
```

実際にはこれだけで動かず、いくつか対策が必要でした。

* 変数宣言(var_decl)無しで変数を使えるようにする
* 比較演算子 == を追加
* 組み込み関数 p() を追加

## 変数宣言無しを認める

```js
function evaluate(tree, genv, lenv) {
  // ... 省略 ...

 if (tree[0] === 'var_assign') {
    // -- check EXIST --
    const name = tree[1];

    // ---- Extra1 for miniruby ----
    /* -- without check --
    if (name in lenv) {
      lenv[name] = evaluate(tree[2], genv, lenv);
      return lenv[name];
    }
    */

    lenv[name] = evaluate(tree[2], genv, lenv);
    return lenv[name];
  }

  // ... 省略 ...
}
```


## 比較演算子 == を追加

```js
function evaluate(tree, genv, lenv) {
  // ... 省略 ...

  // ---- Extra1 for miniruby ----
  if (tree[0] === '==') {
    return evaluate(tree[1], genv, lenv) == evaluate(tree[2], genv, lenv);
  }

  // ... 省略 ...
}
```

## 組み込み関数 p(), pp() を追加

```js
let genv = {
  // ... 省略 ...

  // ---- Extra1 for miniruby ----
  'p' : ['builtin', 'println'],
  'pp' : ['builtin','printObj'],
};
```

## 実行してみる

それでは実行してみましょう。

- minirubyで作った単純化Tree ... miniruby/fizzbuzz_tree.json
- 今回拡張したmininode ... mininode_extra_r.js

とします。

```
$ node mininode_extra_r.js miniruby/fizzbuzz_tree.json
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
... 省略 ...
94
Buzz
Fizz
97
98
Fizz
```

実行できました！

#  まとめ

中間表現となる単純化Treeが共通なminirubyとmininodeは、いわば兄弟言語と言えます。目論見通りminiruby向けのコードを、中間表現を経由して間接的にmininodeで実行することができました。

# ここまでのソース

* [mininode_extra_r.js](https://github.com/mganeko/mininode/blob/master/mininode_extra_r.js)
* [miniruby/savetree.rb](https://github.com/mganeko/mininode/blob/master/miniruby/savetree.rb)
* [miniruby/fizzbuzz_func.rb](https://github.com/mganeko/mininode/blob/master/miniruby/fizzbuzz_func.rb)
* [miniruby/fizzbuzz_tree.json](https://github.com/mganeko/mininode/blob/master/miniruby/fizzbuzz_tree.json)