minimize

事業拡大のため、新しい仲間を募集しています。
→詳しくはこちら

始める前に

とりあえず、まずは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の説明

さて、ここからは少し説明を。
まずは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 ルールに適合していることが判ります。

Lexerの説明

こちらはリテラルを定義したものですので、
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 例外が発生します。

実際に使うときには引数や戻り値を定義して使うことになると思います。
次章では、そのやり方について説明します。