Node.jsでつくるNode.js - Step 4:  四則演算で電卓を作る

# はじめに

「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/ad6ae33ec1e46b67ad57)は「+ 演算子」を評価して足し算ができるところまで作りました。今回は「RubyでつくるRuby」の第4章と同じく、四則演算（+, -, *, /)で電卓を作ります。さらに余りや比較が行えるところまでを目指します。

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

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

simplify()を拡張して、+以外の演算子に対応させます。

```js
function simplify(exp) {
  if (exp.type === 'Literal') {
    return ['lit', exp.value];
  }
  if (exp.type === 'BinaryExpression') {
    if (exp.operator === '+') {
      return ['+', simplify(exp.left), simplify(exp.right)]
    }
    if (exp.operator === '-') {
      return ['-', simplify(exp.left), simplify(exp.right)]
    }
    if (exp.operator === '*') {
      return ['*', simplify(exp.left), simplify(exp.right)]
    }
    if (exp.operator === '/') {
      return ['/', simplify(exp.left), simplify(exp.right)]
    }
  }

  println('-- ERROR: unknown type in simplify) ---');
  printObj(exp);
  return null;
}
```

試しに '1+5 - 3*4/2' のASTを渡して見ると、次のツリーが得られます。

```
[ '-',
  [ '+', [ 'lit', 1 ], [ 'lit', 5 ] ],
  [ '/', [ '*', [ 'lit', 3 ], [ 'lit', 4 ] ], [ 'lit', 2 ] ] ]
```

どうやら大丈夫そうです。

# 四則演算の評価：evaluate()の拡張

同様に実行を担当する evaluate()も拡張して四則演算に対応します。

```js
function evaluate(tree) {
  if (tree[0] === 'lit') {
    return tree[1];
  }
  if (tree[0] === '+') {
    return evaluate(tree[1]) + evaluate(tree[2]);
  }
  if (tree[0] === '-') {
    return evaluate(tree[1]) - evaluate(tree[2]);
  }
  if (tree[0] === '*') {
    return evaluate(tree[1]) * evaluate(tree[2]);
  }
  if (tree[0] === '/') {
    return evaluate(tree[1]) / evaluate(tree[2]);
  }

  println('-- ERROR: unknown node in evluate() ---');
  printObj(tree);
  return null;
}
```

先ほどの '1+5 - 3*4/2' を実行してみると、答えは 0 (ゼロ)になりました。正解です。

# ここまでソース

一旦ここまでのソースはこちらです。この次は演算子を追加します。

```js
// -------------------------
// mininode.js - Node.js by Node.js
// Step4:
// - Calculator +,-,*,/
// -------------------------

const esprima = require("esprima");

// --- parser ----
function parseSrc(src) {
  const ast = esprima.parseScript(src);
  return ast;
}

function makeTree(ast) {
  const exp = ast.body[0].expression;
  return simplify(exp);
}

function simplify(exp) {
  if (exp.type === 'Literal') {
    return ['lit', exp.value];
  }
  if (exp.type === 'BinaryExpression') {
    if (exp.operator === '+') {
      return ['+', simplify(exp.left), simplify(exp.right)]
    }
    if (exp.operator === '-') {
      return ['-', simplify(exp.left), simplify(exp.right)]
    }
    if (exp.operator === '*') {
      return ['*', simplify(exp.left), simplify(exp.right)]
    }
    if (exp.operator === '/') {
      return ['/', simplify(exp.left), simplify(exp.right)]
    }
  }

  println('-- ERROR: unknown type in simplify) ---');
  printObj(exp);
  return null;
}

// --- common ----
function printObj(obj) {
  console.dir(obj, {depth: 10});
}

function println(str) {
  console.log(str);
}

// --- evaluator ---
function evaluate(tree) {
  if (tree[0] === 'lit') {
    return tree[1];
  }
  if (tree[0] === '+') {
    return evaluate(tree[1]) + evaluate(tree[2]);
  }
  if (tree[0] === '-') {
    return evaluate(tree[1]) - evaluate(tree[2]);
  }
  if (tree[0] === '*') {
    return evaluate(tree[1]) * evaluate(tree[2]);
  }
  if (tree[0] === '/') {
    return evaluate(tree[1]) / evaluate(tree[2]);
  }

  println('-- ERROR: unknown node in evluate() ---');
  printObj(tree);
  return null;
}

// --------

const ast = parseSrc('1 + 5 - 3 * 4 / 2');
const tree = makeTree(ast);
println('--- tree ---');
printObj(tree);

println('--- answer ---');
const answer = evaluate(tree);
println(answer); // expect: (1+5) - (3*4/2) = 6 - 6 = 0

```

# 余り(%)と比較演算子を追加

FizzBuzzを作る予定なので、ちょっと先回りして余りを求める % 演算子と、比較のための演算子(===, !==, <, >, <=, >=)を追加しておきます。

まずは simplify() に追加します。

```js
function simplify(exp) {
  if (exp.type === 'Literal') {
    return ['lit', exp.value];
  }
  if (exp.type === 'BinaryExpression') {
    // ... 省略 ...

    if (exp.operator === '%') {
      return ['%', simplify(exp.left), simplify(exp.right)]
    }


    if (exp.operator === '===') {
      return ['===', simplify(exp.left), simplify(exp.right)]
    }
    if (exp.operator === '!==') {
      return ['!==', simplify(exp.left), simplify(exp.right)]
    }

    // ... まだまだ追加 ...
  }
}
```

とここまで書いていて、すべての演算子で同じパターンなことに気がつきました（遅い）。simplify()はこうしました。

```js
function simplify(exp) {
  if (exp.type === 'Literal') {
    return ['lit', exp.value];
  }
  if (exp.type === 'BinaryExpression') {
    return [exp.operator, simplify(exp.left), simplify(exp.right)];
  }

  println('-- ERROR: unknown type in simplify) ---');
  printObj(exp);
  return null;
}
```

一方 evaluate() は、一つ一つ処理して行きます。

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

  if (tree[0] === '%') {
    return evaluate(tree[1]) % evaluate(tree[2]);
  }

  if (tree[0] === '===') {
    return evaluate(tree[1]) === evaluate(tree[2]);
  }
  if (tree[0] === '!==') {
    return evaluate(tree[1]) !== evaluate(tree[2]);
  }
  if (tree[0] === '<') {
    return evaluate(tree[1]) < evaluate(tree[2]);
  }
  if (tree[0] === '>') {
    return evaluate(tree[1]) > evaluate(tree[2]);
  }
  if (tree[0] === '<=') {
    return evaluate(tree[1]) <= evaluate(tree[2]);
  }
  if (tree[0] === '>=') {
    return evaluate(tree[1]) >= evaluate(tree[2]);
  }

  println('-- ERROR: unknown node in evluate() ---');
  printObj(tree);
  return null;
}
```

これで今後のFizzBuzzも実現できそうです。

# 次回

* [Step 5: 変数を使う](https://qiita.com/massie_g/items/1e5bf8b3cca0ae08fd1c)

# ここまでのソースコード

Step4までの全体のコードはこちらです。

```js
// -------------------------
// mininode.js - Node.js by Node.js
// Step4:
// - Calculator +,-,*,/, %, ===,!==,<,>,<=,>=
// -------------------------

const esprima = require("esprima");

// --- parser ----
function parseSrc(src) {
  const ast = esprima.parseScript(src);
  return ast;
}

function makeTree(ast) {
  const exp = ast.body[0].expression;
  return simplify(exp);
}

function simplify(exp) {
  if (exp.type === 'Literal') {
    return ['lit', exp.value];
  }
  if (exp.type === 'BinaryExpression') {
    return [exp.operator, simplify(exp.left), simplify(exp.right)];
  }

  println('-- ERROR: unknown type in simplify) ---');
  printObj(exp);
  return null;
}

// --- common ----
function printObj(obj) {
  console.dir(obj, {depth: 10});
}

function println(str) {
  console.log(str);
}

// --- evaluator ---
function evaluate(tree) {
  if (tree[0] === 'lit') {
    return tree[1];
  }
  if (tree[0] === '+') {
    return evaluate(tree[1]) + evaluate(tree[2]);
  }
  if (tree[0] === '-') {
    return evaluate(tree[1]) - evaluate(tree[2]);
  }
  if (tree[0] === '*') {
    return evaluate(tree[1]) * evaluate(tree[2]);
  }
  if (tree[0] === '/') {
    return evaluate(tree[1]) / evaluate(tree[2]);
  }
  if (tree[0] === '%') {
    return evaluate(tree[1]) % evaluate(tree[2]);
  }
  /*
  if (tree[0] === '**') {
    return evaluate(tree[1]) ** evaluate(tree[2]);
  }
  */

  if (tree[0] === '===') {
    return evaluate(tree[1]) === evaluate(tree[2]);
  }
  if (tree[0] === '!==') {
    return evaluate(tree[1]) !== evaluate(tree[2]);
  }
  if (tree[0] === '<') {
    return evaluate(tree[1]) < evaluate(tree[2]);
  }
  if (tree[0] === '>') {
    return evaluate(tree[1]) > evaluate(tree[2]);
  }
  if (tree[0] === '<=') {
    return evaluate(tree[1]) <= evaluate(tree[2]);
  }
  if (tree[0] === '>=') {
    return evaluate(tree[1]) >= evaluate(tree[2]);
  }

  println('-- ERROR: unknown node in evluate() ---');
  printObj(tree);
  return null;
}

// --------

const ast = parseSrc('1 % 2');
const tree = makeTree(ast);
printObj(tree);
const answer = evaluate(tree);
println(answer); // expect: 1

printObj(evaluate(makeTree(parseSrc('1 < 2')))); // expect: true
printObj(evaluate(makeTree(parseSrc('1 > 2')))); // expect: false
printObj(evaluate(makeTree(parseSrc('1 === 2')))); // expect: false
printObj(evaluate(makeTree(parseSrc('1 !== 2')))); // expect: true
printObj(evaluate(makeTree(parseSrc('1 >= 2')))); // expect: false
printObj(evaluate(makeTree(parseSrc('1 <= 2')))); // expect: true

```