JParsecのチュートリアルの適当な日本語訳

A Tutorial for JParsec Framework
JParsecフレームワークのためのチュートリアル


このチュートリアルはJava1.4のみに対応しています。
java 5のジェネリックを使ったチュートリアルは、チュートリアルforジェネリックを参照してください.
このチュートリアルでは、古典的な計算器の例を使い、JParsec ディレクティブAPIを使ってパーサーが作れることを見ていきます。


ここで作る計算機は以下のようなものです。


・実数の計算をサポート
演算子'+','-','*','/'のサポート
・'('と')'を使ったグループ式
・パーサー作りによく必要になる、Javaスタイルの"//"から始まる一行コメントと"/*"と"*/"で囲まれるブロックコメント
・数学で使う'*'の省略。例えば"3a"は"3 * a"の略。しかし、簡単にできないので、空白を入れる。


典型的なパーサするときのプロセスは2つのステップをとおります。


・入力をスキャンし、トークンリストを作る。このトークンにはキーワード、リテラル、識別子、演算子等が入っている。
・このステップのプログラムをスキャナー又はレキサーとよぶ。
トークンを元に言語の文法から構文木を作る。
 このステップのプログラムのことをパーサーと呼ぶ。


Lexer
レキサー


始めに、レキサーを作りましょう。レキサーは実数の数字と、カッコと演算子を受理します。
入力された文字列をトークン列に変換します。


実数を受理するために正規表現を使うことが出来ます。Jparsecは正規表現をベースにしたスキャナーをサポートしています。
しかし、より直感的な道を提示するのに直接jparsec APIを使用します。


Decimal Number Scanner
実数スキャナー


jfun.parsec.pattern.Pattern と jfun.parsec.pattern.Patterns は入力文字列のパターンを高度に表現する2つの役に立つクラスです。
整数のパターンの表現は以下のコードになります。

final Pattern igits = Patterns.range('0','9').many(1);

・Patterns.range('0','9').many(1)は数字の文字にマッチします。
・many(1)はパターンオブジェクトを1文字以上の数字にマッチするようにする効果があります。


実数は1文字以上の数字の並びにさらに、'.'と1文字以上の数字が並んだ少数を表す部分があるものです。

final Pattern fractional = Patterns.isChar('.').seq(digits);
final Pattern number = digits.seq(fractional.optional());

・digits.seq(...)はパターンオブジェクトに2番目のパターンとして数字を後ろにマッチさせる効果があります。
・Patterns.isChar('.')は'.'文字のみにマッチします。
・fractional.optional() は選択のパターンを示します。


最後に、このパターンオブジェクトを使ってパーサオブジェクトを作ります。

final Parser s_number = Scanners.isPattern(number);

・Scanners.isPattern は関数のパターンオブジェクトをスキャナーパーサオブジェクトに変換します。
・We use "s_" to denote a scanner object.
・スキャナーオブジェクトには名前に"s_"をつけることにします。


事実上、JParsecは組み込みでしばしば整数や、文字列リテラル、10進数などをサポートしています。
それらは[Lexers|http://jparsec.codehaus.org/jparsec1/api/jfun/parsec/]にあります。


"チュートリアル"の目的にあうように、組み込みの関数のレキサーをスクラッチから書くようにしました。


Decimal Number Lexer
10進数レキサー

スキャナーパーサーオブジェクトは入力にマッチするだけです。
それは場所とマッチした長さを知っていますが、
トークンを返すことはありません。


jfun.parsec.Tokenizerオブジェクトを使ってマッチしたトークンの部分文字列に変換する必要があります。

final Parser l_number = Scanners.lexer(s_number, jfun.parsectokens.TokenDecimal.getTokenizer());

TokenDecimal.getTokenizer() は入力文字列をTokenDecimalオブジェクトにマッチさせるように変換したTokenizerオブジェクトを返します。
Scanners.lexer(...) は、Tokenizer オブジェクトを使ってトークンオブジェクトを返す、lexer Parser オブジェクトをつくります。

Lexer for Operators
演算子のためのLexer

演算子のためのレキサーを作るために、ヘルパークラス Termsを使うことが出来ます。

final Terms ops = Terms.getOperators(new String[]{"+","-","*","/","(",")"});
final Parser l_ops = ops.getLexer();

・Terms.getOperators(...) は演算子レキサーとパーサーを含んだTermsオブジェクトを作ります。
ops.getLexer()はこれらの演算子を含んだ、lexerパーサーオブジェクトを返します。

Whitespaces
空白

計算器に必要なトークンは終わりました。後は空白を残すのみです。


この計算機は複数の空白を数字や演算子の前後に入れることを許容します。
一行コメントやブロックコメントも許容し空白として扱います。

final Parser s_line_comment = Scanners.javaLineComment();
final Parser s_block_comment = Scanners.isBlockComment("/*", "*/");
final Parser s_whitespace = Scanners.isWhitespaces();

・Scannars.javaLineComment() は"//"から始まる一行コメントを表すスキャナーパーサーオブジェクトを作ります。
・Scanners.isBlockComment("/*", "*/")は"/*"と"*/"で囲まれたブロックコメントを表すスキャナーパーサーオブジェクトを作ります。
・Scanners.isWhitespaces()は空白を表すスキャナーパーサーオブジェクトを作ります。

計算機は上の3つのものについて一つを無視します。

final Parser s_delim = Parsers.sum(new Parser[]{
  s_line_comment, s_block_comment, s_whitespace
});

・Parsers.sum(...)はすべてのパーサが失敗するまで実行しつづけるParserオブジェクトを作ります。
・このs_delimは実際には空白として無視します。