Node.jsでつくるNode.js - Step 7: 組み込み関数を呼び出す

# はじめに

「RubyでつくるRuby ゼロから学びなおすプログラミング言語入門」([ラムダノート](https://www.lambdanote.com/products/ruby-ruby), [Amazon](https://www.amazon.co.jp/dp/4908686017)) と [PythonでつくるPython](http://akimacho.hatenablog.com/entry/2018/05/22/220109)に影響を受けて、Node.jsでミニNode.js作りにチャンレンジ中です。

[前回](https://qiita.com/massie_g/items/f801692fb9b07d007285)はifによる条件分岐と、whileによる繰り返しを作り、最初の目標のFizzBuzzを動かすところまで実装しました。今回は次の目標であるブートストラップを目指して、組み込み関数の呼び出しを作ります。（今回も「RubyでつくるRuby」の第7章をベースにしています）

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

# 関数呼び出しのAST

実はStep 5で手抜きの関数呼び出しを作っていますが、今回は真面目に実装します。まずはASTを見てみましょう。今回読み込ませるソースはこちら。

```js
println('abc');
println(1, 2);
```

println()という関数は、私が勝手に決めた画面出力用の組み込み関数です。「RubyでつくるRuby」に登場するp()に相当します。

ASTはこちら。

```
Script {
  type: 'Program',
  body:
   [ ExpressionStatement {
       type: 'ExpressionStatement',
       expression:
        CallExpression {
          type: 'CallExpression',
          callee: Identifier { type: 'Identifier', name: 'println' },
          arguments: [ Literal { type: 'Literal', value: 'abc', raw: '\'abc\'' } ] } },
     ExpressionStatement {
       type: 'ExpressionStatement',
       expression:
        CallExpression {
          type: 'CallExpression',
          callee: Identifier { type: 'Identifier', name: 'println' },
          arguments:
           [ Literal { type: 'Literal', value: 1, raw: '1' },
             Literal { type: 'Literal', value: 2, raw: '2' } ] } } ],
  sourceType: 'script' }
```

どうやら CallExpression を解釈すれば良さそうです。関数名は callee.nameに、引数はargumentsに複数含まれます。このargumentsを複数扱うのがポイントになりそうです。

## AST単純化処理 simplify() の拡張

CallExpression の場合には、種別(func_call)、関数名の後に、引数を並べるようにします。 println(1, 2) の場合に欲しい結果はこうなります。

```
  [ 'func_call', 'println', [ 'lit', 1 ], [ 'lit', 2 ] ] 
```

ソースの拡張部分はこうしました。

```js
function simplify(exp) {
  // ... 省略 ...

  if (exp.type === 'CallExpression') {
    const name = exp.callee.name;
    const astArgs = exp.arguments;

    // -- for multi args ---
    let i = 0;
    let treeArgs = [];
    while (astArgs[i]) {
      treeArgs[i] = simplify(astArgs[i]);
      i = i + 1;
    }

    const tree = ['func_call', name].concat(treeArgs);
    return tree;
  }

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

# 実行 evaluate() の拡張

## 組み込み関数の一覧

準備している組み込み関数を、あらかじめハッシュで持っておきます。「RubyでつくるRuby」のまねですが、文字列でなく関数の参照で持つようにしてみました。

```js
let genv = {
  'println' : ['builtin', console.log],
  'printObj' : ['builtin', printObj],
  'abort' : ['builtin', abort],
};
```

## 組み込み関数の実体

組み込み関数の実体は、次のように用意しました。

```js
// オブジェクトをダンプ
function printObj(obj) {
  console.dir(obj, {depth: 10});
  return null;
}

// 処理を中断
function abort() {
  process.exit(1);
}

// mininode本体で使うための暫定実装。組み込み関数は、console.logを直接利用
function println(str) {
  console.log(str);
  return null;
}
```


## 組み込み関数の呼び出し

実際に関数を呼び出す部分で悩みました。Rubyではsendを使ってオブジェクトにメッセージを送っているようですが、JavaScriptには同じ仕組みはなさそうです（私の知る限り）。今回は apply() を使うことにしました。

```js
function callBuiltin(func, args) {
  return func.apply({}, args); // 1st:this, 2nd:args
}
```

## 実行部分の拡張

それでは準備したものを使って、evaluate()を拡張します。まずは evaluate()の引数に用意した genv も渡すように、すべての呼び出し箇所を書き換えます。

```js
// before
evaluate(tree, env)

// after
evaluate(tree, genv, lenv)
```

そして evaluate() の内部の処理も追加します。

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

  if (tree[0] === 'func_call') {
    const mhd = genv[tree[1]];
    let args = [];
    let i = 0;
    while (tree[2 + i]) {
      args[i] = evaluate(tree[2 + i], genv, lenv);
      i = i + 1;
    }

    if (mhd[0] === 'builtin') {
      // -- call builtin function --
      return callBuiltin(mhd[1], args);
    }

    // ... ユーザー定義関数の呼び出しはまだ ...
    println('--- ERROR, user func NOT supported YET ---');
    abort();
  }

  // ... 省略 ...
}

```

genv から組み込み関数への参照を取得、引数を新しいハッシュに詰め替えて、callBuiltin()を経由して組み込み関数を呼び出しています。

# 次回

* [Step 8: ユーザー定義関数を使えるようにする](https://qiita.com/massie_g/items/e537f14300a6113a6237)

# ここまでのソース

長くなってきたので、[GitHubのmininode](https://github.com/mganeko/mininode)に移しました

* [mininode_step7.js](https://github.com/mganeko/mininode/blob/master/mininode_step7.js)
* [sample/test7.js](https://github.com/mganeko/mininode/blob/master/sample/test7.js)