JavaScriptでおもちゃのLispを作ろう(3) 〜リーダ/パーサテスト編〜

これまでに実装したのは、文字列からS式を読み込むリーダとその逆のプリンタです。今回はそれらの機能が期待した通りの動作をするかテストします。

JavaScript用の自動単体テストフレームワークJsUnitがありますが、ここは趣味のプログラミングなので、オレ流超簡単テスト関数を作ります。

var Tester = function () {
  var passed = 0;
  var failed = 0;
  return {
    equal: function (act, exp) {
      if (act === exp) {
        passed++;
      } else {
        failed++;
        print("!!! Failed: act=" + act + ", exp=" + exp);
      }
    },
    summary: function () {
      print("passed: " + passed + ", failed: " + failed);
    }
  };
};

まず、リーダ関数のテストをします。S式を正しく読み込めたかどうかをテストします。コンスセルはJavaScriptのオブジェクトで { car: cell1, cdr: cell2 }のように表現していますが、それと配列表現[ cell1, cell2 ]と比較して同じ構造であるかを調べて、S式が正しく読み込めたかどうかを判定することにします。つまり、配列のインデックス0をcar、インデックス1をcdrとします。すると、読み込んだS式とその配列表現が同じであるかどうかの関数list_eqlは以下のように定義できます。

function list_eql(exp, arr) {
  if (exp instanceof Cell) {
    return list_eql(car(exp), arr[0]) && list_eql(cdr(exp), arr[1])
  } else {
    return exp === arr;
  }
}

これを使って、文字列から読み込んだS式が正しく読み込めたかどうかをテストする関数は以下のようになります。

function test_read() {
  var tst = new Tester;
  var Q = QUOTE;
  function S(x) { return intern(x); }
  function eq(act, exp) {
    var s = read(act);
    if (list_eql(s, exp)) {
      tst.equal(1, 1);
    }
  }

  eq("()",          NIL);
  eq("nil",         NIL);
  eq("123",         123);
  eq("abc",         S("abc"));
  eq("'abc",        [Q, S("abc")]);
  eq("1.23",        1.23);
  eq('"abc"',       "abc");;
  eq('"987"',       "987");;
  eq('"98.7.6"',    "98.7.6");;
  eq('"abc\ndef"',  "abc\ndef");
  eq("(a)",         [S("a"), NIL]);
  eq("(a . b)",     [S("a"), S("b")]);
  eq("(a b)",       [S("a"), [S("b"), NIL]]);
  eq("(a b c)",     [S("a"), [S("b"), [S("c"), NIL]]]);
  eq("(a . (b))",   [S("a"), [S("b"), NIL]]);
  eq("(abc '(x y))",[S("abc"), [[Q, [S("x"), [S("y"), NIL]]], NIL]]);
  eq('("a")',       ["a", NIL]);
  eq('("a" "b")',   ["a", ["b", NIL]]);
  tst.summary();
}

実行すると、

passed: 18, failed: 0

と出力されました。期待通りに動いているようです。テスト項目は、これでは不十分ですが、後で、単純にどんどん追加していけばよいでしょう。

同様に、プリンタ関数のテストは以下のようになります。

function test_print() {
  var tst = new Tester;
  function R(x) {
    var s = read(x);
    var pr = new Printer;
    return pr.toStr(s);
  }
  function eq(act, exp) {
    tst.equal(R(act), exp);
  }

  eq("()",              "nil");
  eq("nil",             "nil");
  eq("123",             "123");
  eq("12.34",           "12.34");
  eq("12e34",           "1.2e+35");
  eq('"abc"',           '"abc"');
  eq("(a b c)",         "(a b c)");
  eq("(a . b)",         "(a . b)");
  eq("(a b . c)",       "(a b . c)");
  eq("(a . (b))",       "(a b)");
  eq("(())",            "(nil)");
  eq("((()))",          "((nil))");
  eq("((a) . b)",       "((a) . b)");
  eq("'abc",            "'abc");
  eq("'()",             "'nil");  
  eq("'(a b)",          "'(a b)");
  eq("'(a b '(c d))",   "'(a b '(c d))");
  
  tst.summary();
}

実行すると、

passed: 17, failed: 0

と出力されました。期待通りに動いているようです。

必要最低限のS式の読み込みと文字列への出力ができました。次回はいよいよ、評価器(evaluator)の実装をします。