はじめに
「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);
}
// ... 省略 ...
}
シンプルです。
使ってみる
こちらのテストコードを用意しました。
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にあげておきます。