Node.js でつくる Node.js - Extra 2: 演算子を追加する

# はじめに

「RubyでつくるRuby ゼロから学びなおすプログラミング言語入門」(ラムダノート, Amazon) に感銘をうけて、ミニNode.js（以下mininode）を作ってきました。今回はおまけとして、論理演算子などを追加したいと思います。

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

# 追加する演算子

次の演算子を追加します。

* 比較演算子: !==, ==
* 否定演算子: !
* 論理演算子: &&, ||

比較演算子はこれまでの2項演算子と同じですが、否定演算子と論理演算子は新しい種類になります。

# 新しい演算子のAST

## 否定演算子

こちらのソースをesprimaでパースしてみます。

```
! 1;
```

ASTはこちら。UnaryExpression というのが新しく登場しました。

```
Script {
  type: 'Program',
  body:
   [ ExpressionStatement {
       type: 'ExpressionStatement',
       expression:
        UnaryExpression {
          type: 'UnaryExpression',
          operator: '!',
          argument: Literal { type: 'Literal', value: 1, raw: '1' },
          prefix: true } } ],
  sourceType: 'script' }
```

単純化した構文木Treeは、こちらの形を目指します。

```
[ '!', [ 'lit', 1 ] ]
```

## 論理演算子

同様にこちらのソースをesprimaでパースしてみます。

```
1 || 0;
1 && 0;
```

ASTの該当箇所はこちら。今度は LogicalExpression が新登場です。

```
   [ ExpressionStatement {
       type: 'ExpressionStatement',
       expression:
        BinaryExpression {
          type: 'LogicalExpression',
          operator: '||',
          left: Literal { type: 'Literal', value: 1, raw: '1' },
          right: Literal { type: 'Literal', value: 0, raw: '0' } } },
     ExpressionStatement {
       type: 'ExpressionStatement',
       expression:
        BinaryExpression {
          type: 'LogicalExpression',
          operator: '&&',
          left: Literal { type: 'Literal', value: 1, raw: '1' },
          right: Literal { type: 'Literal', value: 0, raw: '0' } } } ],
```

単純化した構文木Treeは、2項演算子と同じ形にしました。

```
[ 'stmts',
  [ '||', [ 'lit', 1 ], [ 'lit', 0 ] ],
  [ '&&', [ 'lit', 1 ], [ 'lit', 0 ] ] ]
```

## 単純化処理の拡張

新しい演算子に対応するため、simplify() を次のように拡張しました。

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

  // --- Extra 2 ---
  if (exp.type === 'UnaryExpression') {
    return [exp.operator, simplify(exp.argument)];
  }
  if (exp.type === 'LogicalExpression') {
    return [exp.operator, simplify(exp.left), simplify(exp.right)];
  }
  // ... 省略 ...
}
```

# 演算子の実行

追加した演算子を実行するため、evaluate()も拡張します。

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

  // ---- Extra2 ----
  if (tree[0] === '!==') {
    return evaluate(tree[1], genv, lenv) !== evaluate(tree[2], genv, lenv);
  }
  if (tree[0] === '==') {
    return evaluate(tree[1], genv, lenv) == evaluate(tree[2], genv, lenv);
  }
  if (tree[0] === '!') {
    let val = ! evaluate(tree[1], genv, lenv);
    return val;
  }
  if (tree[0] === '||' ) {
    return evaluate(tree[1], genv, lenv) || evaluate(tree[2], genv, lenv);
  }
  if (tree[0] === '&&' ) {
    return evaluate(tree[1], genv, lenv) && evaluate(tree[2], genv, lenv);
  }

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

シンプルです。

# 使ってみる

こちらのテストコードを用意しました。

```js:test_extra2.js
let a = true;
println(a); // true
println(! a); // false
println(a || false); // true
println(a && false); //false 

println(a !== (1 > 0)) //false
println(a == 1) //true
println(a === 1) //false
```

実行結果はこちら。うまく行ったようです。

```
$ node mininode_extra_2.js sample/test_extra2.js
true
false
true
false
false
true
false
```

さらに、mininodeでmininodeを実行したブートストラップ状態でも実行できます。

```
$ node mininode_extra_2.js mininode_extra_2.js sample/test_extra2.js
```

なんて、本当は初めてやった時にはバグで動きませんでした。正しいファイルをrequre()していなかったのが原因でした。一見意味のないブートストラップも、バグの発見に役立ちます。


# ここまでのソース

今回のソースもGitHubにあげておきます。

* [mininode_extra_2.js](https://github.com/mganeko/mininode/blob/master/mininode_extra_2.js)
* [module_parser_extra2.js](https://github.com/mganeko/mininode/blob/master/module_parser_extra2.js)
* [module_builtin_extra2.js](https://github.com/mganeko/mininode/blob/master/module_builtin_extra2.js)
* [sample/test_extra2.js](https://github.com/mganeko/mininode/blob/master/sample/test_extra2.js)