の続きです。四則演算の対応するオペレーター(演算子)を増やします。
オペレーターを増やす
前回+
に対応しました。
次に、-
, *
,/
,%
に対応します。
実装
switch文に演算子ごとの分岐を追加するだけです。
const esprima = require('esprima') const util = require('util') console.assert(test('1 + 1') === 2) console.assert(test('1 + 2') === 3) console.assert(test('1 - 2') === -1) console.assert(test('2 * 2') === 4) console.assert(test('10 / 2') === 5) console.assert(test('100 % 49') === 2) function test(expresssion) { const parsed = esprima.parse(expresssion) console.log(util.inspect(parsed, false, null)) const body = parsed.body for (const statement of body) { return evaluate(statement) } } function evaluate(statement) { switch (statement.type) { case 'ExpressionStatement': switch (statement.expression.type) { case 'BinaryExpression': let left, right switch (statement.expression.operator) { case '+': [left, right] = getOperandFromBinaryExpression(statement.expression) return left + right break; case '-': [left, right] = getOperandFromBinaryExpression(statement.expression) return left - right break; case '*': [left, right] = getOperandFromBinaryExpression(statement.expression) return left * right break; case '/': [left, right] = getOperandFromBinaryExpression(statement.expression) return left / right break; case '%': [left, right] = getOperandFromBinaryExpression(statement.expression) return left % right break; default: console.log(`unknown operator ${statement.expression.operator}`); } break; default: console.log(`unknown expression ${statement.expression}`); } break; default: console.log(`unknown type ${statement.type}`); } } function getOperandFromBinaryExpression(expression) { let left; if (expression.left.type === 'Literal') { left = expression.left.value } else { console.log(`unknown type ${expression.left.type}`); } let right; if (expression.right.type === 'Literal') { right = expression.right.value } else { console.log(`unknown type ${expression.right.type}`); } return [left, right] }
leftとrightの値をとる処理をgetOperandFromBinaryExpression関数にしました。
項数を増やす
1 + 1 + 1
のように式の項数を増やします。
この時、ASTは
Script { type: 'Program', body: [ ExpressionStatement { type: 'ExpressionStatement', expression: BinaryExpression { type: 'BinaryExpression', operator: '+', left: BinaryExpression { type: 'BinaryExpression', operator: '+', left: Literal { type: 'Literal', value: 1, raw: '1' }, right: Literal { type: 'Literal', value: 1, raw: '1' } }, right: Literal { type: 'Literal', value: 1, raw: '1' } } } ], sourceType: 'script' }
leftの中にBinaryExpression
が入れ子になっています。
これに対応すると、小町算を計算できるようになります。
(1 + 2) / 3 * 4 * (56 / 7 + 8 + 9) = 100
実装
BinaryExpression
の評価を再帰的にしたいので、evaluateBinaryExpression関数を作って再起呼び出しします。
const esprima = require('esprima') const util = require('util') console.assert(test('1 + 1') === 2) console.assert(test('1 + 2') === 3) console.assert(test('1 - 2') === -1) console.assert(test('2 * 2') === 4) console.assert(test('10 / 2') === 5) console.assert(test('100 % 49') === 2) console.assert(test('1 + 1 + 1') === 3) console.assert(test('(1 + 2) / 3 * 4 * (56 / 7 + 8 + 9)') === 100) function test(expresssion) { const parsed = esprima.parse(expresssion) console.log(util.inspect(parsed, false, null)) const body = parsed.body for (const statement of body) { return evaluateStatement(statement) } } function evaluateStatement(statement) { switch (statement.type) { case 'ExpressionStatement': switch (statement.expression.type) { case 'BinaryExpression': return evaluateBinaryExpression(statement.expression) break; default: console.log(`unknown expression ${statement.expression}`); } break; default: console.log(`unknown type ${statement.type}`); } } function evaluateBinaryExpression(expression) { let left, right switch (expression.operator) { case '+': [left, right] = getOperandFromBinaryExpression(expression) return left + right break; case '-': [left, right] = getOperandFromBinaryExpression(expression) return left - right break; case '*': [left, right] = getOperandFromBinaryExpression(expression) return left * right break; case '/': [left, right] = getOperandFromBinaryExpression(expression) return left / right break; case '%': [left, right] = getOperandFromBinaryExpression(expression) return left % right break; default: console.log(`unknown operator ${expression.operator}`); } } function getOperandFromBinaryExpression(expression) { return [getOperandValue(expression.left), getOperandValue(expression.right)] } function getOperandValue(operand) { switch (operand.type) { case 'Literal': return operand.value case 'BinaryExpression': return evaluateBinaryExpression(operand) default: console.log(`unknown type ${operand.type}`); } }
getOperandValue関数はleftとrightにコピペするのが面倒だったので、関数にしました。
RubyでつくるRubyとの違い
EsprimaのASTはstatementとexpressionの二階層になっています。 一方minirubyのASTたexpressionだけの一階層です。
Rubyで作るRubyのソースコードは本を買って確認してください。
式と文の取り扱い
これは言語仕様の違いによるものです。
Rubyの
プログラムは式を並べたものです
式と文に区別はありません。
JavaScriptでは式と文は区別されます。 例えば
1 + 1
は式です。
var i = 1
は文です。JavaScriptの文は値を返しません。