LoginSignup
2
0

More than 5 years have passed since last update.

Node.jsでつくるNode.js - Step 9: 配列とハッシュ(連想配列)を使う

Last updated at Posted at 2018-06-13

はじめに

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

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0