とりあえず、まずはANTLRとはどんなものか使ってみましょう。
ANTLR のページからダウンロードします。
今回は、現在(2006/07/18)の安定最新版である 2.7.6 を使うことにしました。
ANTLRのようなツールを使うときに、最低限知っておく必要のある事があります。
それは正規表現です。
これを知っていないと、正直ANTLRを使うのは厳しいです。
基本的なところだけ知っていれば充分ですので、
まずは正規表現を学んでおきましょう。
ANTLRでは、一つのパーサを作成するのに
Parser / Lexer を対で使います。
今回はAntlrのサイトに習って(パクって?)、数値演算式を解析するパーサを作成します。
まず、以下の内容をファイルに保存します。
expr.g
class ExprParser extends Parser; expr: mexpr ((PLUS|MINUS) mexpr)* ; mexpr: atom (STAR atom)* ; atom: INT | LPAREN expr RPAREN ;
これがParserクラスとなります。
続いて、Lexerクラスを以下の内容で作成します。
先程保存したファイルと同一のファイルに追加する形で以下を保存して下さい。
expr.g
class ExprLexer extends Lexer;
options {
k=2; // needed for newline junk
charVocabulary='\u0000'..'\u007F'; // allow ascii
}
LPAREN: '(' ;
RPAREN: ')' ;
PLUS : '+' ;
MINUS : '-' ;
STAR : '*' ;
INT : ('0'..'9')+ ;
WS : ( ' '
| '\r' '\n'
| '\n'
| '\t'
)
{$setType(Token.SKIP);}
;
これで準備完了です。
詳しい説明の前に、とりあえずまずコンパイルすることにします。
ダウンロードしたファイルを解凍し、
antlr-2.7.6.jar stringtemplate-2.3b8.jar
の2つにクラスパスを通したところで、コマンドラインから以下を実行します。
% java antlr.Tool expr.g
これで、expr.g ファイルを解析していくつかのJavaファイルが自動生成されます。
ただしまだmainが無いので、実行して確認することが出来ません。
以下のような簡単なMainクラスを作成しましょう。
import antlr.*;
public class Main {
public static void main(String[] args) throws Exception {
ExprLexer lexer = new ExprLexer(System.in);
ExprParser parser = new ExprParser(lexer);
parser.expr();
}
}
見ての通り、標準入力から受け取った文字列をパーサに渡すだけの単純なクラスです。
では、早速実行してみます。
% java Main
入力待ち状態になるので、適当な数式を入力して下さい。
3+(4*5)
数式に間違いが無ければ、何も表示せずにそのまま終了します。
もし、
3++
のように間違った式を入力すると、エラー文字列が表示されるはずです。
さて、ここからは少し説明を。
まずはParserから。
先頭の
class ExprParser extends Parser;
という部分で、Parserクラスを継承したExprParserクラスを宣言しています。
次の
expr: mexpr ((PLUS|MINUS) mexpr)* ;
という部分で、expr という「ルール」を定義しています。
この「ルール」はパーサを理解する上で重要な要素となるので、覚えておいて下さい。expr は「入力文字列が数式である事」というルールになります。
例えば…
mexpr mexpr PLUS mexpr mexpr PLUS mexpr MINUS mexpr
などがルールに適合します。
逆に、
PLUS mexpr PLUS mexpr anothor_rule
などはルールに適合しません。
ここで出てきた mexpr / PLUS / MINUS は、
「ルール」もしくは「リテラル」を表しています。
mexpr: atom (STAR atom)* ; atom: INT | LPAREN expr RPAREN ;
mexpr / atom は、ルールです。
PLUS : '+' ;
PLUS は、リテラルです。
先程のソースを見てわかるように、ルールはParser、リテラルはLexerで定義されています。
JavaCCなどでは両者を混合して扱いますが、Antlrではそうでは無いようです。
ルールは一つ以上のルールまたはリテラルで構成されています。
mexpr に適合するルールは、以下のようなものになります。
atom atom STAR atom
atom に適合するルールは、以下のようなものになります。
INT LPAREN expr RPAREN
atom を展開すると、mexpr に適合するルールは以下のようなものになります。
INT INT STAR INT INT STAR LPAREN expr RPAREN
expr もルールですから、展開することが可能です。
リテラルはこれ以上展開することが出来ません。
このように、最終的に全てのルールはリテラルの集合に展開できるのです。
先程の例で挙げた
3+(4*5)
は、以下のリテラルとして解釈されます。
INT PLUS LPAREN INT STAR INT RPAREN
今度は逆展開をして、ルールに適合するかどうか見てみましょう。INT STAR INT は atom に適合するので…という感じで進めてみます。
INT PLUS LPAREN atom RPAREN INT PLUS LPAREN expr RPAREN INT PLUS atom mexpr PLUS mexpr expr
という訳で、expr ルールに適合していることが判ります。
こちらはリテラルを定義したものですので、
Parserよりはずっと理解しやすいと思います。
今回の例で特殊なリテラルが一つあるので、それを説明します。
WS : ( ' '
| '\r' '\n'
| '\n'
| '\t'
)
{$setType(Token.SKIP);}
;
これです。
半角スペース、改行文字、タブ文字を定義している事は判ると思いますが
このリテラルはどのルールからも使用されていません。
$setType(Token.SKIP);
この一文により、このリテラルを「スキップ文字」として定義しています。
スキップ文字とは、各リテラル間に存在しても無視される文字のことを言います。
例えば、
INT STAR INT
というリテラルがあったとします。
これに適合する文字列は
2*4 3*6
などがありますが、このスキップ文字を定義しておくことで
以下のような文字列も適合するようになります。
2 * 4 3 * 6
スキップ文字を定義しておかないと、
INT (WS)* STAR (WS)* INT
のようにルールを定義する必要があり、コードが読みにくくなってしまいます。
是非、スキップ文字を使いましょう。
ルールを一つ定義すると、生成されるParserクラスにルールと同名のメソッドが定義されます。
メソッドの引数は Lexer クラスのインスタンスです。
まず、解析したい文字列を元にLexerクラスのインスタンスを生成します。
次に、そのLexerをParserクラスのコンストラクタに渡しましょう。
最後に、Parserクラスのメソッドを呼び出します。
XXXLexer lexer = new XXXLexer(in); XXXParser parser = new XXXParser(lexer); parser.ruleName();
デフォルトでは、ルールメソッドは引数、戻り値共に持ちません。
この場合、ルールに適合すれば何もせず
ルールに適合しなければ AntlrException 例外が発生します。
実際に使うときには引数や戻り値を定義して使うことになると思います。
次章では、そのやり方について説明します。