2
0

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-06-21

はじめに

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

minirubyをmininodeで実行できるか?

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

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

minirubyのsimplify()結果を保存

fizzbuzzのコード

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

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を使えば簡単ですね。

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 を少し整形して掲載します。

{"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を読み込み、それを実行します。


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() を追加

変数宣言無しを認める

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];
  }

  // ... 省略 ...
}

比較演算子 == を追加

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

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

  // ... 省略 ...
}

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

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で実行することができました。

ここまでのソース

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0