Chapter 4. A Quick Tour^3

4.3 Building a Translator with a Listener

ボスがあなたにJavaクラス定義のメソッドからJavaインタフェースを生成するツールを作成するよう命じたとしよう。もしあなたがジュニアプログラマならパニックに陥るだろう。熟練のJava開発者なら、メソッドシグニチャの抽出にJavaリフレクションAPIやjavapの使用を提案するかもしれない。もしあなたがJavaツールの達人なら、ASM(http://asm.ow2.org)のようなバイトコードのライブラリを使うかもしれない。そのときボスが言う「ああそうだ、メソッドシグネチャのホワイトスペースとコメントは残してね」。これで方法がなくなった。Javaソースコードを解析する必要がある。たとえば、このようなJavaコードを入力して、

import java.util.List;
import java.util.Map;
public class Demo {
	void f(int x, String y) { }
	int[ ] g(/*no args*/) { return null; }
	List<Map<String, Integer>>[] h() { return null; }
}

次のようなメソッドシグネチャでインタフェースを生成する。ホワイトスペースとコメント付きで。

interface IDemo {
	void f(int x, String y);
	int[ ] g(/*no args*/);
	List<Map<String, Integer>>[] h();
}

信じるかどうかにかかわらず、この問題の核心はJava構文解析木走査器から発火される「イベント」を処理する15行ほどのコードで解決できる。Java構文解析木は、既存のJava文法から生成された構文解析器をベースにしている。生成するインタフェースの名前はクラス名から、メソッドのシグニチャ(戻り値の型、メソッド名、引数リスト)はメソッド定義から導出する。

文法とリスナーオブジェクトの間でキーとなる「インタフェース」はJavaListenerと呼ばれ、ANTLRが自動的に生成する。これはクラスParseTreeWalkerが構文解析木を走査するとき起動できる全てのメソッドを定義する。この場合、走査器がクラス定義に入るとき/出るとき、およびメソッド定義に出会ったとき、の3つのイベントに反応する3つのメソッドをオーバーライドする必要がある。生成されたリスナーインタフェースの関連する部分は以下のとおり。

public interface JavaListener extends ParseTreeListener {
	void enterClassDeclaration(JavaParser.ClassDeclarationContext ctx);
	void exitClassDeclaration(JavaParser.ClassDeclarationContext ctx);
	void enterMethodDeclaration(JavaParser.MethodDeclarationContext ctx);
	...
}

リスナーとビジターの最大の違いは、リスナーメソッドはANTLRの提供する走査器オブジェクトによって呼ばれるのに対し、ビジターメソッドは明示的なvisit()呼び出しで子供を探索しなければならないこと。ノード上の子供でvisit()の呼び出しを忘れると、そのサブツリーは訪問されない。

リスナーを実装するために、ルールclassDeclarationとmethodDeclarationがどのようなものか知る必要がある。なぜならリスナーメソッドはルールによってマッチする句要素を捕まえる必要があるため。ファイルJava.g4はJavaの完全な文法だが、ここで見る必要があるのは次の2つのメソッドである。

<snip>classDeclaration in tour/Java.g4</snip>
<snip>methodDeclaration in tour/Java.g4</snip>

ANTLRはJavaBaseListenerというデフォルト実装を生成するので、200またはそれ以上のインタフェースメソッドを実装する必要はない。インタフェース抽出器はそのJavaBaseListenerサブクラスなので、必要なメソッドだけをオーバーライドすれば良い。

基本戦略はクラス定義が始まった時、インタフェースのヘッダーがプリントアウトされること。そしてクラス定義の終わりで終了の } をプリントする。各メソッド定義では、そのシグニチャを抽出する。完全な実装は以下のとおり。

<snip>tour/ExtractInterfaceListener.java</snip>

処理を実行するには、この章の他のと同じようなメインプログラムが必要。構文解析器を立ち上げた後、このアプリケーション・コードを開始する。

<snip>tour/ExtractInterfaceTool.java</snip>

以下は既存のJava.g4文法とExtractInterfaceToolのコンパイルとテスト手順。

$ antlr4 Java.g4
$ javac Java*.java ExtractInterface*.java
$ java ExtractInterfaceTool Demo.java
interface IDemo {
        void f(int x, String y);
        int[ ] g(/*no args*/);
        List<Map<String, Integer>>[] h();
}

この実装はListのようなインタフェースメソッドで参照される型のimport文のインタフェースファイルを取り込まないため完全ではない。練習としてimportを扱ってみよう。それによって、リスナーを使うことでこれらの種類の抽出器または変換器の作成が容易だと納得するだろう。importDeclaration規則がどうなっているかさえ知る必要がない。なぜならenterImportDeclaration()は規則にマッチしたテキストを単にプリントする:parser.getTokenStream.getText(ctx)。

ビジターとリスナーメカニズムはよく働く、そして構文解析構文解析アプリの関心の分離を促進する。それでも時々、さらなる制御と柔軟性が必要になる。

from The Definitive ANTLR4 Reference by Terence Parr