今日はちょっと変わったところで、SGFファイルを扱う方法です。
SGFというのは、様々なプラットフォーム上で販売されているSmartGoというアプリケーションのファイルフォーマットです。Smart Game Formatの略で、囲碁以外のボードゲームにも対応可能なフォーマットです。
囲碁のファイルフォーマットはアプリケーション毎に他にも色々ありますが、仕様がドキュメント化されているこのSGFが事実上標準フォーマットという感じです。
テキストフォーマットなので中身を見たことのある人も多いかと思います。こんな感じ。
(;FF[4]GM[1]CA[UTF-8]AP[囲碁の師匠:1.1.2]SZ[19]HA[2]AB[pd][dp];W[dd])
見ただけでなんとなく意味もわかる、わかりやすいフォーマットですね。
こういうフォーマットを扱うには、扱うプログラムを直接書くのではなく、パーサジェネレータというものを使うと楽ができます。
パーサジェネレータというのは、フォーマットの規則を与えると、その規則に則ってフォーマットに従った文字列からデータ構造を抽出するコード(パーサ)を生成してくれるものです。
パーサジェネレータを使って作った自作のパーサをいくつかご紹介します。
- jssgf: JavaScript用SGFパーサ。Jisonというパーサジェネレータを使いました。
- rust-sgf: Rust用SGFパーサ。rust-pegというパーサジェネレータを使いました。
- swift-SGF: Swift用SGFパーサ。citronというパーサジェネレータを使いました。
なんでいくつも作っているのかというと、それだけ作るのが簡単だからです^^
jssgfを作った時のSGFフォーマットを記述を見てみましょう。
/lex %% /* language grammar */ output : collection EOF { return $1; } ; collection : gametree { $$ = [$1]; } | collection gametree { $1.push($2); $$ = $1; } ; gametree : '(' sequence ')' { $$ = $2; } | '(' sequence gametrees ')' { $$ = addGameTrees($2, $3); } ; gametrees : gametree { $$ = [$1]; } | gametrees gametree { $1.push($2); $$ = $1; } ; sequence : node { $$ = $1; } | node sequence { $1._children.push($2); $$ = $1; } ; node : ';' { $$ = {_children: []}; } | node property { if (typeof $1[$2[0]] !== 'undefined') { if (strict) { throw new Error('double properties'); } } else { $1[$2[0]] = $2[1]; $$ = $1; } } ; property : PROPIDENT propvalues { $$ = [$1, $2]; } ; propvalues : propvalue { $$ = $1; } | propvalues propvalue { var a; if ($1 instanceof Array) a = $1; else a = [$1]; $$ = a.concat($2); } ; propvalue : /* empty */ { $$ = ''; } | CVALUETYPE { $$ = decodeValue($1); } ; %%
こんな風に、コレクションはどういうもの?ゲームツリーはどういうもの?シークエンスはどういうもの?ノードはどういうもの?という規則を書くと、JavaScriptで書かれたパーサを生成してくれます。
実はこの規則、SGFの仕様書に書かれたものをほぼ書き写したものです。以下はSGFの仕様書より。
Collection = GameTree { GameTree } GameTree = "(" Sequence { GameTree } ")" Sequence = Node { Node } Node = ";" { Property } Property = PropIdent PropValue { PropValue } PropIdent = UcLetter { UcLetter } PropValue = "[" CValueType "]" CValueType = (ValueType | Compose) ValueType = (None | Number | Real | Double | Color | SimpleText | Text | Point | Move | Stone)
似てるでしょ?
なので、新しいプログラミング言語に取り組んだときも、その言語のパーサジェネレータがあれば、その言語用にSGFパーサを用意することは簡単なのです。
今日はこの辺で。