SGF Parser 0.1

囲碁の棋譜フォーマット SGF の構文解析処理 (parsing / syntactic analysis) を作る前段階として、 前回作成したパーサージェネレーターを利用して、SGF (Smart Game Format) の構文解析のクラス SGFParser を生成しました。

実行結果

以下は実際に使用している SGF の構文です。 上記の SGF の構文の文字列を EBNFParser に渡し、SGFParser のコードを以下の通り生成しました。 字下げは Aptana Studio 等のツールで簡単にできるので行っていません。

ドキュメント

JsDoc Toolkit で作成したドキュメントはこちら

ソース


sgf01.html

このソースコードは Jasmine のサンプル SpecRunner.html から大部分を持ってきました。前半はスタイルシートとスクリプトを読み込んでいる部分です。
<head>
<meta charset="UTF-8">
<title>JavaScriptでプログラミング - SGF Parser 0.1</title>

<link type="text/css" rel="stylesheet" href="lib/jasmine-1.3.1/jasmine.css">
<link type="text/css" rel="stylesheet" href="css/spec.css">
<link type="text/css" rel="stylesheet" href="css/shCoreEclipse.css">
<link type="text/css" rel="stylesheet" href="css/shThemeEclipse.css">
<script type="text/javascript" src="js/XRegExp.js"></script>
<script type="text/javascript" src="js/shCore.js"></script>
<script type="text/javascript" src="js/shBrushJScript.js"></script>
<script type="text/javascript" src="js/shBrushXml.js"></script>
<script type="text/javascript" src="js/shBrushCss.js"></script>
<script type="text/javascript" src="lib/jasmine-1.3.1/jasmine.js"></script>
<script type="text/javascript" src="lib/jasmine-1.3.1/jasmine-html.js"></script>

<!-- include source files here... -->
<script type="text/javascript" src="js/generator03/source01.js"></script>
<script type="text/javascript" src="js/generator03/lex03.js"></script>
<script type="text/javascript" src="js/generator03/generator03.js"></script>

<!-- include spec files here... -->
<script type="text/javascript" src="js/generator03/sourcespec01.js"></script>
<script type="text/javascript" src="js/generator03/lexspec03.js"></script>
<script type="text/javascript" src="js/generator03/generatorspec03.js"></script>

generator02.js にあった構文の定義を EBNFParser に渡す処理を、 HTML ファイルの <head> タグの後半に持ってきました。 これで色々な構文を試すときも js ファイルのほうを変更しなくて済みます。

最後の部分は Jasmine によるテスト結果を HTML 上で展開するレポーターと呼ばれる部分です。前回から変更ありません。

<script type="text/javascript">
  // define SGF syntax
  var gapFree = ["PropIdent", "Number", "Real", "Text", "SimpleText"];
  var sgfSyntax = "Collection = GameTree, {GameTree}.\n";
  sgfSyntax += "GameTree = '(', Sequence, {GameTree}, ')'.\n";
  sgfSyntax += "Sequence = Node, {Node}.\n";
  sgfSyntax += "Property = PropIdent, PropValue, {PropValue}.\n";
  sgfSyntax += "PropIdent = upper, {upper}.\n";
  sgfSyntax += "PropValue = '[', CValueType, ']'.\n";
  sgfSyntax += "CValueType = ValueType | Compose.\n";
  sgfSyntax += "ValueType = None | Number | Real | Double | Color | SimpleText | Text | Move.\n";
  sgfSyntax += "None = [sp].\n";
  sgfSyntax += "Number = ['+' | '-'], digit, {digit}.\n";
  sgfSyntax += "Real = Number, ['.', digit, {digit}].\n";
  sgfSyntax += "Double = '1' | '2'.\n";
  sgfSyntax += "Color = 'B' | 'W'.\n";
  sgfSyntax += "Move = (lower | upper), (lower | upper).\n";
  sgfSyntax += "Compose = ValueType, ':', ValueType.\n";
  var ebnf = new EBNFParser(sgfSyntax, gapFree, "SGF", "0.1");
  SyntaxHighlighter.all();
  (function() {
    var jasmineEnv = jasmine.getEnv();
    jasmineEnv.updateInterval = 1000;

    var htmlReporter = new jasmine.HtmlReporter();
    jasmineEnv.addReporter(htmlReporter);
    jasmineEnv.specFilter = function(spec) {
      return htmlReporter.specFilter(spec);
    };

    var currentWindowOnload = window.onload;
    window.onload = function() {
      if (currentWindowOnload) {
        currentWindowOnload();
      }
      execJasmine();
    };

    function execJasmine() {
      jasmineEnv.execute();
    }
  })();
</script>
また、今回は <body> タグの実行結果のところに直接スクリプトを書きました。 これは結果に対して SyntaxHighlighter を使用するためです。

実行結果

以下は実際に使用している SGF の構文です。 <script type="text/javascript"> // get element from HTML and set text (code) var html = '<pre class="code">\n'; html += sgfSyntax; html += '</pre>\n'; document.writeln(html); </script> 上記の SGF の構文の文字列を EBNFParser に渡し、SGFParser のコードを以下の通り生成しました。 字下げは Aptana Studio 等のツールで簡単にできるので行っていません。 <script type="text/javascript"> // get element from HTML and set text (code) var html = "<pre class=\"brush: js; class-name: 'list'\">\n"; html += ebnf.syntax(); html += "</pre>\n"; document.writeln(html); </script>

generator03.js

クラス EBNFParser のソースコードです。パーサーを生成するためのツールです。前回作成したクラス EBNFParser を一つのファイルにまとめ直しました。 リンク先をご覧下さい。

source01.js

クラス Source のソースコードです。Lex, Parser の対象となるテキストを格納するクラスです。前回から変更ありません。 リンク先をご覧下さい。

lex03.js

クラス Lex のソースコードです。字句解析のためのツールです。前回から変更ありません。 リンク先をご覧下さい。

sourcespec01.js

このソースでは Source のテスト仕様を定義しています。前回から変更ありません。
describe("Source 仕様 0.1", function() {
  var exp = "1+2=";
  var src;

  beforeEach(function() {
    src = new Source(exp);	// Lexer and Parser Source
  });

  it("eod() は false を返す", function() {
    expect(src.eod()).toBeFalsy();
  });

  describe("先頭でpush()しptr++したとき、", function() {
    beforeEach(function() {
      src.rewind();
      src.push();
      src.ptr++;
    });

    it("pop(true)後、ptrは 0 を返す", function() {
      var p = src.ptr;
      src.pop(true);
      expect(src.ptr).toEqual(0);
    });

    it("pop(false)後、ptrは 1 を返す", function() {
      var p = src.ptr;
      src.pop(false);
      expect(src.ptr).toEqual(1);
    });

  });

});

lexspec03.js

このソースでは Lex のテスト仕様を定義しています。前回から変更ありません。
describe("Lex 仕様 0.3", function() {
  var exp = "1+2=";
  var la;

  beforeEach(function() {
    la = new Lex(exp);	// Lexical Analiser
  });

  it("eod() は false を返す", function() {
    expect(la.eod()).toBeFalsy();
  });

  it("digit() は '1' を返す", function() {
    expect(la.digit()).toEqual('1');
  });

  it("upper() は null を返す", function() {
    expect(la.upper()).toBe(null);
  });

  it("lower() は null を返す", function() {
    expect(la.lower()).toBe(null);
  });

  it("sp() は null を返す", function() {
    expect(la.sp()).toBeFalsy();
  });

  it("sq() は null を返す", function() {
    expect(la.sq()).toBeFalsy();
  });

  it("ch('1') は '1' を返す", function() {
    expect(la.ch('1')).toEqual('1');
  });

  it("text('1') は '1' を返す", function() {
    expect(la.text('1')).toBe('1');
  });

  it("text('1+') は '1+' を返す", function() {
    expect(la.text('1+')).toBe('1+');
  });

  it("text('2') は null を返す", function() {
    expect(la.text('2')).toBe(null);
  });

  describe("3文字読んだ時点で、", function() {
    beforeEach(function() {
      la.rewind();
      la.digit();
      la.ch('+');
      la.digit();
    });

    it("eod() は false を返す", function() {
      expect(la.eod()).toBe(false);
    });

    it("digit() は null を返す", function() {
      expect(la.digit()).toBe(null);
    });

    it("upper() は null を返す", function() {
      expect(la.upper()).toBe(null);
    });

    it("lower() は null を返す", function() {
      expect(la.lower()).toEqual(null);
    });

    it("ch('=') は '=' を返す", function() {
      expect(la.ch('=')).toBe('=');
    });

  });

  describe("4文字読んだ時点で、", function() {
    beforeEach(function() {
      la.rewind();
      la.digit();
      la.ch('+');
      la.digit();
      la.ch('=');
    });

    it("eod() は true を返す", function() {
      expect(la.eod()).toBeTruthy();
    });

  });

});

generatorspec03.js

このソースでは Generator のテスト仕様を定義しています。以下の仕様変更を行いました。
  1. バージョンを 0.3 に変更。
  2. コメントの 〜 Parser Generator Object を 〜 Parser Object に変更。
  3. ドキュメント用のコメント @class を追加。
  4. 引数の名称 syntax を src に変更。
describe("Generator 仕様 0.3", function() {
  var g = new EBNFParser("syntax = {line, nl}.", [], "Test", "0.3");
  var code = "/**\n";
  code += " * @fileOverview TestParser - Test Parser Object\n";
  code += " * @version 0.3\n";
  code += " * @author EBNFParser written by Nonki Takahashi\n";
  code += " */\n";
  code += "\n";
  code += "/**\n";
  code += " * Test Parser Object\n";
  code += " * @class Represents Test Parser Object\n";
  code += " * @this {TestParser}\n";
  code += " * @param {String} src source to parser\n";
  code += " * @param {Object} gapFree array of gap free id\n";
  code += " * @param {String} name name of the parser\n";
  code += " * @param {String} ver version of the parser\n";
  code += " * @param {Boolean} ignoreCase true if ignore case (optional)\n";
  code += " * @property {String} buf source buffer\n";
  code += " * @property {Integer} ptr source buffer pointer\n";
  code += " * @property {Object} gapFree array of gap free id\n";
  code += " * @property {String} name name of the parser\n";
  code += " * @property {String} ver version of the parser\n";
  code += " * @property {Boolean} ignoreCase true if ignore case\n";
  code += " * @since 0.3\n";
  code += " */\n";
  code += "TestParser = function(src, gapFree, name, ver, ignoreCase) {\n";
  code += "this.gapFree = gapFree;\n";
  code += "this.name = name;\n";
  code += "this.ver = ver;\n";
  code += "if (ignoreCase)\n";
  code += "this.ignoreCase = true;\n";
  code += "else\n";
  code += "this.ignoreCase = false;\n";
  code += "// inherit the methods of class Lex\n";
  code += "Lex.call(this, src);\n";
  code += "};\n";
  code += "TestParser.prototype = new Lex();\n";
  code += "\n";
  code += "/**\n";
  code += " * syntax = {line, nl}.\n";
  code += " * @return {String} code generated if matched, null if not matched\n";
  code += " * @since 0.3\n";
  code += " */\n";
  code += "TestParser.prototype.syntax = function() {\n";
  code += "var match = [];\n";
  code += "var save = this.ptr;\n";
  code += "var n = -1;\n";
  code += "this.sp();\n";
  code += "while(match[n]) {\n";
  code += "this.sp();\n";
  code += "match[++n] = this.line();\n";
  code += "if (match[n]) {\n";
  code += "this.sp();\n";
  code += "match[++n] = this.nl();\n";
  code += "}\n";
  code += "}\n";
  code += "match[++n] = true;\n";
  code += "if (!match[n]) {\n";
  code += "this.ptr = save;\n";
  code += "return null;\n";
  code += "}\n";
  code += "return this.run(n, match, \"syntax\");\n";
  code += "};\n\n";

  it("syntax() は " + code + " を返す", function() {
    expect(g.syntax()).toEqual(code);
  });

});

テスト結果