JavaScript
Node.js

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

はじめに

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

追加する演算子

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

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

比較演算子はこれまでの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() を次のように拡張しました。

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()も拡張します。

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

  // ... 省略 ...
}

シンプルです。

使ってみる

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

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にあげておきます。