A Starter ANTLR Project^1

最初のプロジェクトは、とても小さなC言語、あるいはJava派生言語のサブセットの文法構築。特に整数、もしかすると{1,2,3}, {1,{2,3},4}のようにネストしているかもしれない、を認識する。これらの構成物はintの配列、またはstructの初期化子になれるかもしれない。この構文の文法は様々な状況で役立つだろう。たとえば、もし初期値のすべてがbyteに収まる場合、intの配列をbyteの配列に変換するC言語ソースのリファクタリングツールを作るのに使えるかもしれない。またこの文法は、Javaの初期化されたshort配列をStringへ変換するのにも使えるかもしれない。たとえば以下のようなshort配列を等価なUnicode定数(=short)の文字列へ変換する。

static short[] data = {1,2,3};
↓
static String data = "\u0001\u0002\u0003"; // Java char are unsigned short

このように変換するのはJavaの.classファイルフォーマットの制限を乗り越えるため必要とされるかもしれないから。Javaのclassフィルは、配列の初期化子を(byteにパックされたコンパクトなブロックではなく、data[0]=1;data[1]=2;data[2]=3;のような)明示的な配列要素の初期化子の連続として格納する。Javaは初期化メソッドの長さを制限しているので、初期化できる配列のサイズは制限される。一方、Java classファイルは、文字列をshortの連続として格納する。よって、配列初期化子をStringへ変換することにより、よりコンパクトなclassファイルとJavaの初期化メソッドサイズの制限を避けるというメリットが得られる。

この最初の例題を通して、ANTLR文法、ANTLRが文法から何を生成するか、生成された構文解析器をJavaアプリへ組み込む方法、構文解析器リスナーによる変換器の作成方法、などの概要を見ていく。

3.1 The ANTLR Tool, Runtime, and Generated Code

ANTLRのjarファイルには、2つの主要なANTLRコンポーネントがある。

  1. ANTLR(自身)
  2. ANTLRの実行時API

「文法に対してANTLRを実行する」は、クラスorg.antlr.v4.Toolを実行するということ。ANTLRの生成したコード(構文解析器、字句解析器)の実行は、文法で記述された言語の文を認識すること。字句解析器は字の入力ストリームをトークン/句に分け、それを構文チェックする構文解析器へ渡す。実行時APIはParser, Lexer, Tokenといった生成されたコードで必要とするクラス、メソッドのライブラリ。最初に文法に対してANTLRを実行し、そしてjarの実行時クラスを前提とする生成されたコードをコンパイルする。最終的に、コンパイルされたアプリは実行時クラスとともに実行する。

言語アプリを作る最初のステップは、言語の構文規則(有効な文の集合)を記述した文法を作ること。文法の書き方はChapter 5Designing Grammarsで学ぶが、ここで必要としているのは以下のような文法である。

<snip>starter/ArrayInt.g4</snip>
  • 文法は常にgrammarで始まる。この文法はArrayIntと呼ばれ、ファイル名はArrayInt.g4であること
  • initと呼ばれる規則は{ ... }で囲まれたカンマ区切りの値にマッチする
  • valueはネストした配列/構造体、またはただのinteger(INT)
  • 構文解析器の規則は英小文字で始まる、字句解析器の規則は英大文字で始まる
  • トークンINTを1桁以上の数字として定義
  • トークンWSはwhitespaceで、読み捨てる

ANTLRはArrayInt.g4から多くのファイルを生成する。以下は、主要なフィルの内容。

ArrayInitParser.java

このファイルには配列言語構文を認識するArrayInt文法の構文解析器クラス定義が入っている。これは文法の各規則に対応するメソッド、サポートコードなどで構成されている。

public class ArrayInitParser extends Parser { ... }

ArrayInitLexer.java

ANTLRは文法から自動的に構文解析器と字句解析器の仕様を抽出する。このファイルには、ANTLRがINT/WSの字句規則やリテラル'{', '.', '}'の文法を解析して生成した字句解析器クラスの定義が入っている。(字句解析器は入力を語彙記号へ分解する)

public class ArrayInitLexer extends Lexer { ... }

ArrayInitLexer.tokens

ANTLRは定義されたトークンに対し、そのトークンを表す数値を割り当て、それらの値をこのファイルへ格納する。これは大きな文法を複数の小さな文法へ分割するとき、ANTLRがすべてのトークン番号を同期するのに必要とされる。

ArrayInitListener.java, ArrayInitBaseListener.java

デフォルトでは、ANTLR構文解析器は入力から木を構築する。木走査器はその木を走査することで、私達が記述したリスナーオブジェクトへイベント(コールバック)を発射する。ArrayInitListenerは私達が実装できるコールバックのインタフェース。ArrayInitBaseListenerは、(ArrayInitListenerに対する)空のデフォルト実装クラス。このクラスに必要なコールバックのみオーバーライドすることで、実装が簡単に行える。また、ANTLRは-visitorコマンドラインプションでvisitorも生成できる。

public interface ArrayInitListener extends ParseTreeListener { ... }
public class ArrayInitBaseListener implements ArrayInitListener { ... }

from The Definitive ANTLR4 Reference by Terence Parr