はじめに
「RubyでつくるRuby ゼロから学びなおすプログラミング言語入門」(ラムダノート, Amazon) と PythonでつくるPythonに影響を受けて、Node.jsでミニNode.js作りにチャンレンジ中です。
前回はユーザ定義関数を利用できるようにしました。。次の目標であるブートストラップまではまだ遠いので、今回は配列とRubyで言うところのハッシュ(JavaScriptの連想配列)を実装します。(今回は「RubyでつくるRuby」の第9章の前半をベースにしています)
配列を使う
配列を利用したケースのAST
配列の宣言と初期代入、参照、再代入処理を含む、次のソースを用意します。
let ar = [1, 2];
ar[0];
ar[1] = 5;
esprimaを通すと、ASTの配列を新規に作る処理の該当箇所はこうなってます。
ArrayExpression {
type: 'ArrayExpression',
elements:
[ Literal { type: 'Literal', value: 1, raw: '1' },
Literal { type: 'Literal', value: 2, raw: '2' } ] }
ArrayExpressionを解釈すれば良さそうです。
そして配列の要素の参照に該当する箇所はこちら。
ComputedMemberExpression {
type: 'MemberExpression',
computed: true,
object: Identifier { type: 'Identifier', name: 'ar' },
property: Literal { type: 'Literal', value: 0, raw: '0' } }
要素の参照はMemberExpressionになっています。
そして要素への代入はこちら。
AssignmentExpression {
type: 'AssignmentExpression',
operator: '=',
left:
ComputedMemberExpression {
type: 'MemberExpression',
computed: true,
object: Identifier { type: 'Identifier', name: 'ar' },
property: Literal { type: 'Literal', value: 1, raw: '1' } },
right: Literal { type: 'Literal', value: 5, raw: '5' } }
AssignmentExpressionの左辺にMemberExpressionが来ています。通常の変数への代入と区別する必要がありそうです。
simplify()を配列に対応させる
次のtreeを目標にします。
[ 'stmts',
[ 'var_decl', 'ar', [ 'ary_new', [ 'lit', 1 ], [ 'lit', 2 ] ] ],
[ 'ary_ref', [ 'var_ref', 'ar' ], [ 'lit', 0 ] ],
[ 'ary_assign', [ 'var_ref', 'ar' ], [ 'lit', 1 ], [ 'lit', 5 ] ] ]
配列の宣言と、要素の参照に関する拡張はこうしました。
function simplify(exp) {
// ... 省略 ...
if (exp.type === 'ArrayExpression') {
const astElements = exp.elements;
let treeElements = [];
let i = 0;
while (astElements[i]) {
treeElements[i] = simplify(astElements[i]);
i = i + 1;
}
const tree = ['ary_new'].concat(treeElements);
return tree;
}
if (exp.type === 'MemberExpression') {
const name = simplify(exp.object);
const prop = simplify(exp.property);
const tree = ['ary_ref', name, prop];
return tree;
}
// ... 省略 ...
}
要素への代入は、すでにある変数への代入の部分を拡張します。
function simplify(exp) {
// ... 省略 ...
if (exp.type === 'AssignmentExpression') {
// --- 変数への代入 ---
if (exp.left.type === 'Identifier') {
const name = exp.left.name;
const val = simplify(exp.right);
return ['var_assign', name, val];
}
// --- 配列の要素の代入 ---
if (exp.left.type === 'MemberExpression') {
const name = simplify(exp.left.object);
const prop = simplify(exp.left.property)
const val = simplify(exp.right);
return ['ary_assign', name, prop, val];
}
// --- 不明な代入 ---
println('-- ERROR: unknown type of AssignmentExpression in simplify()) ---');
printObj(exp);
abort();
}
// ... 省略 ...
}
実行 evaluate()を配列向けに拡張
配列の宣言、参照、代入の処理はこう拡張しました。
function evaluate(tree, genv, lenv) {
// ... 省略 ...
if (tree[0] === 'ary_new') {
let ary = [];
i = 0;
while (tree[1 + i]) {
ary[i] = evaluate(tree[1 + i], genv, lenv);
i = i + 1;
}
return ary;
}
if (tree[0] === 'ary_ref') {
const ary = evaluate(tree[1], genv, lenv);
const idx = evaluate(tree[2], genv, lenv);
return ary[idx];
}
if (tree[0] === 'ary_assign') {
const ary = evaluate(tree[1], genv, lenv);
const idx = evaluate(tree[2], genv, lenv);
const val = evaluate(tree[3], genv, lenv);
ary[idx] = val;
return val;
}
// ... 省略 ...
}
ハッシュを使う
ハッシュのASTを見てみる
配列の宣言、参照、要素の代入を行う次のソースを用意します。
let h = { 'a' :1, 'b':2 };
h['a'];
h['b'] = 22;
ハッシュを新規に作る部分のASTはこちら。ObjectExpressionが新登場です。
ObjectExpression {
type: 'ObjectExpression',
properties:
[ Property {
type: 'Property',
key: Literal { type: 'Literal', value: 'a', raw: '\'a\'' },
computed: false,
value: Literal { type: 'Literal', value: 1, raw: '1' },
kind: 'init',
method: false,
shorthand: false },
Property {
type: 'Property',
key: Literal { type: 'Literal', value: 'b', raw: '\'b\'' },
computed: false,
value: Literal { type: 'Literal', value: 2, raw: '2' },
kind: 'init',
method: false,
shorthand: false } ] }
ハッシュの要素の参照のASTは、配列と同じ形。
ComputedMemberExpression {
type: 'MemberExpression',
computed: true,
object: Identifier { type: 'Identifier', name: 'h' },
property: Literal { type: 'Literal', value: 'a', raw: '\'a\'' } }
そして代入のASTも、配列と同じ形です。
ExpressionStatement {
type: 'ExpressionStatement',
expression:
AssignmentExpression {
type: 'AssignmentExpression',
operator: '=',
left:
ComputedMemberExpression {
type: 'MemberExpression',
computed: true,
object: Identifier { type: 'Identifier', name: 'h' },
property: Literal { type: 'Literal', value: 'b', raw: '\'b\'' } },
right: Literal { type: 'Literal', value: 22, raw: '22' } } }
simplify()のハッシュ対応
ハッシュを新しく作る部分だけ追加すればOKです。
function simplify(exp) {
// ... 省略 ...
if (exp.type === 'ObjectExpression') {
const astProps = exp.properties;
let treeElements = [];
let i = 0;
while (astProps[i]) {
const key = simplify(astProps[i].key);
const val = simplify(astProps[i].value)
treeElements[i * 2] = key;
treeElements[i * 2 + 1] = val;
i = i + 1;
}
const tree = ['hash_new'].concat(treeElements);
return tree;
}
// ... 省略 ...
}
実行 evaluate() のハッシュ向け拡張
こちらもハッシュを新規に作る処理だけ追加対応します。参照や代入は配列と同じコードで動いてしまいます。ラッキーですね。(そうなるように、単純化したtreeが表現されているのですね)
function evaluate(tree, genv, lenv) {
// ... 省略 ...
if (tree[0] === 'hash_new') {
let hsh = {};
let i = 1;
while (tree[i]) {
const key = evaluate(tree[i], genv, lenv);
const val = evaluate(tree[i + 1], genv, lenv);
hsh[key] = val;
i = i + 2;
}
return hsh;
}
// ... 省略 ...
}
次回は
配列、ハッシュ(連想配列)ときて、「RubyでつくるRuby」ではブートストラップが実現しています。残念ながら今回作っているミニNode.jsでは、まだブートストラップはできません。次回はその課題を潰していく予定です。
ここまでのソース
GitHubにあげておきます。