Chapter 4. A Quick Tour^5

4.5 Cool Lexical Features

ANTLRには紹介する価値のある字句関連の機能が3つある。1つはXMLのような、1つのファイルに異なる字句構造(タグの内側/外側)をもつような形式の扱い方。次は入力ストリームを微調整してJavaクラスにフィールドを挿入する方法。最小限の労力で入力に類似した出力を生成する方法を示す。最後は、ANTLR構文解析器がホワイトスペースとコメントを捨てること無く無視する方法を見ていく。

Island Grammars:Dealing with Different Formats in the Same File

ここまで見てきたすべての入力ファイルは単一の言語を格納していたが、複数言語を格納する共通ファイルフォーマットがある。たとえばミニ言語に従う@authorタグなどのJavaドキュメンコメントは、コメントの外がすべてJavaコードである。StringTemplate(http://www.stringtemplate.org)やDjango(https://www.djangoproject.com)のようなテンプレートエンジンも同じような問題を抱えている。それらはテンプレート式に囲まれているすべてのテキストを異なるものとして扱わなければならない。これらは時々「島文法」と呼ばれる。

ANTLRはフォーマットが混在するコンテンツの扱いを容易にする「字句解析モード」と呼ばれる、よく知られた字句解析機能を提供する。この基本的な考えは、特別な標識文字が現れたら、字句解析器のモードを切り替えるスイッチを持つというものである。

XMLはこの良い例である。XML構文解析器は、タグとエンティティ参照以外(£のような)をすべてテキストの塊として扱う。字句解析器は < に出会った時「内部」モードへ切り替え、>, />でデフォルトモードへ戻す。以下のサンプルではこれがどのように機能するか説明する。この詳細はChapter 12 Wielding Lexical Black magicで扱う。

<snip>tour/XMLLexer.g4</snip>

以下のファイルを文法へ与えてみよう。

<tools>
	<tool name="ANTLR">A parser generator</tool>
</tools>

ビルドとテストは以下の通り。

$ antlr4 XMLLexer.g4
$ javac XML*.java
$ java org.antlr.v4.gui.TestRig XML tokens -tokens t.xml
[@0,413:413='\n',<4>,8:3]
[@1,414:414='<',<1>,9:0]
[@2,415:419='tools',<10>,9:1]
[@3,420:420='>',<5>,9:6]
[@4,421:422='\n\t',<4>,9:7]
[@5,423:423='<',<1>,10:1]
[@6,424:427='tool',<10>,10:2]
[@7,429:432='name',<10>,10:7]
[@8,433:433='=',<7>,10:11]
[@9,434:440='"ANTLR"',<8>,10:12]
[@10,441:441='>',<5>,10:19]
[@11,442:459='A parser generator',<4>,10:20]
[@12,460:460='<',<1>,10:38]
[@13,461:465='/tool',<9>,10:39]
[@14,466:466='>',<5>,10:44]
[@15,467:467='\n',<4>,10:45]
[@16,468:468='<',<1>,11:0]
[@17,469:474='/tools',<9>,11:1]
[@18,475:475='>',<5>,11:7]
[@19,476:476='\n',<4>,11:8]
[@20,477:476='<EOF>',<-1>,12:0]

出力の各行はトークンを表し、トークンのインデックス、文字の開始/終了位置、トークンのテキスト、トークンのタイプ、行中でのトークンの位置を含んでいる。これによって字句解析器がどのようにトークン化したかわかる。

テストリグのコマンドラインでは、XML tokensは通常、文法名と開始する規則を表す。この場合は、文法名に続き、構文解析器ではなく字句解析器を実行するための特別な規則名tokensを使った。そしてマッチした字句リストを出力するため-tokensオプションを使っている。

字句解析器から構文解析器へ流れる字句ストリームの知識はとても役立つ。たとえばある種の変換の問題は、入力を調整するだけ。まったく新しい出力を生成するより、むしろオリジナルの字句ストリームを変更することで解決できる。

Rewriting the Input Stream

Javaソースコードに、(Eclipseが自動的に行うような)java.io.Serializableで使うシリアル番号serialVersionUIDを挿入するツールを作ってみる。私たちはANTLRによってJava文法から生成されたJavaListenerインタフェースのリスナーメソッド毎の実装を避け、ただテキストをキャプチャーしてそれを出力したい。オリジナルの字句ストリームに適切な定数フィールドを挿入し、変更された入力ストリームを出力するのがより簡単である。

メインプログラムは、リスナーが終ったとき字句ストリームを出力するのを除いて4.3 Building a Translator with a ListenerのExtractInterfaceTool.javaと全く同じである。

<snip>tour/InsertSerialID.java</snip>

このリスナーを実装するには、クラスの始まりで挿入トリガーが必要。

<snip>tour/InsertSerialIDListener.java</snip>

ポイントは、ストリームを実際に変更すること無く、字句ストリームの見え方を変更できるTokenStreamRewriterオブジェクトにある。これはテキストとして解釈し直すためにトークンストリームを走査するとき、すべての走査メソッドを命令として扱い、遅延実行のためキューに入れる。rewriterはそれらの命令をgetText()が呼ばれる度に行う。

リスナーをビルドして、以前使ったDemo.javaテストファイルでリスナーをテストする。

$ antlr4 Java.g4
$ javac InsertSerialID*.java Java*.java
$ java InsertSerialID Demo.java
/***
 * Excerpted from "The Definitive ANTLR 4 Reference",
 * published by The Pragmatic Bookshelf.
 * Copyrights apply to this code. It may not be used to create training material,
 * courses, books, articles, and the like. Contact us if you are in doubt.
 * We make no guarantees that this code is fit for any purpose.
 * Visit http://www.pragmaticprogrammer.com/titles/tpantlr2 for more book information.
***/
import java.util.List;
import java.util.Map;
public class Demo {
        public static final long serialVersionUID = 1L;
        void f(int x, String y) { }
        int[ ] g(/*no args*/) { return null; }
        List<Map<String, Integer>>[] h() { return null; }
}

挿入箇所の外側を乱すこと無く、たった数行で、Javaクラス定義を微調整できた。この戦略はソースコードの編集・リファクタリングに対する一般的な問題にとても効果的である。TokenStreamRewriterは字句ストリームを操作するのに強力でとても効果的である。

Sending Tokens on Different Chanles

前出のJavaインタフェース抽出器は、メソッドシグネチャのホワイトスペースとコメントを以下の様に保存する。

int[ ] g(/*no args*/) { return null; }

伝統的に、これは満たすのが厄介な要求だった。たいていの文法では、コメントとホワイトスペースは構文解析器が無視できるもの。もし文法中におかれたホワイトスペースとコメントを明示的に認めないなら、字句解析器がそれを捨てる必要がある。あいにくそれは、アプリコードと後続の処理からホワイトスペースとコメントにアクセスできなくなることを意味する。保存するがコメントホワイトスペースを無視する秘密は、それらのトークンを構文解析器の「hidden channel」へ送るところにある。構文解析器は1つのチャネルにだけ同調するので、私たちはなんでも他のチャネルへ渡すことができる。文法的には以下のようになる。

<snip>tour/Java.g4</snip>

"-> channel(HIDDEN)"は、以前見た字句解析器のコマンド"->skip"に似ている。この場合、構文解析器によって無視されるトークンのチャネル番号をセットする。トークンストリームは、オリジナルのトークンの順番を維持するが、構文解析器に送る時、オフチャネルの字句はスキップされる。

===

この章ではANTLRを使いやすく、柔軟にするすべての要素をカバーした。詳細の一部はカバーしていないが、ANTLRがいくつかの小さいが現実の問題を解決する様子を見た。文法表記の概要を学んだ。文法にアクションを埋め込むこと無く計算と変換を行うビジターとリスナーを実装した。また時々アクションを埋め込んだ。埋め込まれたアクションは、我々の風変わりな内部制御を満たすため正確に何かをしている。そして最後に、ANTLR字句解析とトークンストリームでできるクールなことを見た。

ペースを減速し、すべての詳細を学ぶことを目標に、この章で検討する概念のすべてを再検討する時間が来た。次のパートの各章は、言語実装者になる新たな一歩となる。ANTLR表記を学習し、例および言語リファレンスマニュアルから文法を導出する方法を考え出すことから始める。これらの基礎を持つことができれば、現実世界の言語のための文法を構築し、構文解析木のリスナーとビジターを詳細を学べるだろう。

from The Definitive ANTLR4 Reference by Terence Parr