Open7

ANTRL メモ

horie-thorie-t

使用例

  • 検索オプション
  • Markdown拡張
  • Terraformのグラフ化(Docコメントに図形表示の指示を書く)
  • CI/CDコードの要約?
  • 自作アプリ用のPlugin?
  • GUIアプリ内のシェル?
horie-thorie-t

最初の例

  • 四則演算(優先順位: *, / -> + -)
  • 検索演算(優先順位: AND -> OR)
horie-thorie-t

ANTLRのインストール

ANTLRのダウンロードページで最新版のバージョンを確認して、以下のようにインストール・セットアップする

​​cd /usr/local/lib/
sudo curl -O https://www.antlr.org/download/antlr-4.13.2-complete.jar

~/.bashrcファイルに以下を追加する。

~/.bashrc
# for ANTLR
export CLASSPATH=".:/usr/local/lib/antlr-4.13.2-complete.jar:$CLASSPATH"

ファイル追加後に、以下を実行する

. .bashrc

/usr/local/bin/antlr4ファイルを作成して以下の内容を記述する

/usr/local/bin/antlr4
#!/bin/sh
java -cp "/usr/local/lib/antlr4-complete.jar:$CLASSPATH" org.antlr.v4.Tool $*

以下のコマンドを実行して実行権を与える。

sudo chmod +x /usr/local/bin/antlr4 

以下のようにANTLRを実行できたらインストール成功です。

$ antlr4
ANTLR Parser Generator  Version 4.13.2
 -o ___              specify output directory where all output is generated
 -lib ___            specify location of grammars, tokens files
 -atn                generate rule augmented transition network diagrams
 -encoding ___       specify grammar file encoding; e.g., euc-jp
 -message-format ___ specify output style for messages in antlr, gnu, vs2005
 -long-messages      show exception details when available for errors and warnings
 -listener           generate parse tree listener (default)
 -no-listener        don't generate parse tree listener
 -visitor            generate parse tree visitor
 -no-visitor         don't generate parse tree visitor (default)
 -package ___        specify a package/namespace for the generated code
 -depend             generate file dependencies
 -D<option>=value    set/override a grammar-level option
 -Werror             treat warnings as errors
 -XdbgST             launch StringTemplate visualizer on generated code
 -XdbgSTWait         wait for STViz to close before continuing
 -Xforce-atn         use the ATN simulator for all predictions
 -Xlog               dump lots of logging info to antlr-timestamp.log
 -Xexact-output-dir  all output goes into -o dir regardless of paths/package

テストツールのエイリアスの設定。~/.bashrcに以下を設定する。

alias grun='java org.antlr.v4.runtime.misc.TestRig'
horie-thorie-t

Mavenのプロジェクトでの利用方法

pom.xml に以下を追加する。

pom.xml
	<properties>
    <!-- 中略 -->
		<antlr.version>4.13.2</antlr.version>
	</properties>

  <!-- 中略 -->
		<dependency>
			<groupId>org.antlr</groupId>
			<artifactId>antlr4-runtime</artifactId>
			<version>${antlr.version}</version>
		</dependency>
    <!-- 中略 -->
			<plugin>
				<groupId>org.antlr</groupId>
				<artifactId>antlr4-maven-plugin</artifactId>
				<version>${antlr.version}</version>
				<configuration>
					<listener>true</listener>
					<visitor>true</visitor>
				</configuration>
				<executions>
					<execution>
						<id>antlr-generate</id>
						<phase>generate-sources</phase>
						<goals>
							<goal>antlr4</goal>
						</goals>
					</execution>
				</executions>
			</plugin>

Java以外のプログラミング言語の場合は、ダウンロードページを参照する。

horie-thorie-t

Javaパーサを生成・利用する手順

適当なディレクトリを作成して、移動する。

mkdir antlr_arithmetic
cd antlr_arithmetic/

文法ファイルを、Arithmetic.g4 というファイル名で作成する。

arithmetic.g4
// 最初はgrammerから始まる。この名前はファイル名(拡張子部分を除く)と一致している事。この名前を使ってクラス等が作成される
grammar Arithmetic;

// パーサールール
expr   : expr ('*' | '/') expr    # MulDiv
       | expr ('+' | '-') expr    # AddSub
       | '(' expr ')'             # Parens
       | NUMBER                   # Number
       ;

// レキサールール(整数または小数点数)
NUMBER : [1-9] [0-9]* ('.' [0-9]+)?
       | '0' ('.' [0-9]+)?
       ;  

// 余計な空白を無視
WS     : [ \t\r\n]+ -> skip ;

文法ファイルが出来たら、パーサを生成する。

antlr4 Arithmetic.g4

ファイルが生成される。

 ls
Arithmetic.g4      Arithmetic.tokens            ArithmeticLexer.interp  ArithmeticLexer.tokens   ArithmeticParser.java
Arithmetic.interp  ArithmeticBaseListener.java  ArithmeticLexer.java    ArithmeticListener.java 

生成されたソースコードをコンパイル

javac *.java

テストの実行(-guiではGUIで表示されるが、-treeではコンソール画面に表示される)

 grun Arithmetic expr -gui
Warning: TestRig moved to org.antlr.v4.gui.TestRig; calling automatically
1 + 2 * 3    # 改行を入力した後で、Ctrl + Dで解析が実行される

horie-thorie-t

ANTLRのイディオム

MyGrammer.g4 ファイルで文法ファイルを定義してantlrのツールでMyGrammerXxxクラスを生成させる。
生成されてたクラスは以下のように使う。

MyGrammerListenerを使う場合

xxxはパースするファイルの拡張子。yyyMyGrammer.g4で定義されたトップレベルの文法規則。

    var path = "/path/to/file_for_parse.xxx";
    try (InputStream is = new FileInputStream(path)) {
        var lexer = new MyGrammerLexer(CharStreams.fromStream(is));
        var parser = new MyGrammerParser(new CommonTokenStream(lexer));
        var listener = new MyGrammerBaseListener();   // 実際はこのクラスの継承クラスを使う
        ParseTreeWalker.DEFAULT.walk(listener, parser.yyy());
        // 後続の処理...
    } catch (IOException e) {
        e.printStackTrace();
    }

MyGrammerVisitorを使う場合

    var path = "/path/to/file_for_parse.xxx";
    try (InputStream is = new FileInputStream(path)) {
        var lexer = new MyGrammerLexer(CharStreams.fromStream(is));
        var parser = new MyGrammerParser(new CommonTokenStream(lexer));
        parser.setBuildParseTree(true);
        var tree = parser.yyy();
        var visitor = new MyGrammerBaseVisitor();   // 実際はこのクラスの継承クラスを使う
        visitor.visit(tree);

        // 後続の処理...
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
horie-thorie-t

解析木を走査して結果をまとめる方法

四則演算の式の解析木を巡回して値を計算する。

ビジターの戻り値を使う。

上記の木の葉の、 1, 2, 3はそのまま、1, 2, 3とみなし、 2 * 3の節点は6とみなせる。 12 * 3の和は、 7とみなせる。葉や節点をvisitした戻り値をこれらの値にすればよい。

    @Override
    public Double visitExpr(ArithmeticParser.ExprContext ctx) {
        // 節点で計算する
        return 123.456;  // 計算結果を返す
    }

リスナークラスでスタックマシンを構築する

葉や節点をExitする時にその葉や節点の値を、リスナーのフィールドやグローバル変数のスタックにpushする。節点の値を計算するときは、スタックから2つpopして計算する。

    public Stack<Double> resultStack = new Stack();

    @Override
    public void exitExpr(ArithmeticParser.ExprContext ctx) {
        val e0 = resultStack.pop();
        val e1 = resultStack.pop();
        // 節点で計算する
        resultStack.push(e0 + e1);    // 足し算の節点の場合
        return
    }

木を走査し終えたら、resultStackから最終結果をpopすればよい。

葉や節点と、値のマップを使用する

葉や節点をキーに、葉や節点の値をバリューにしたマップをリスナーのフィールド等で保持しておいて、その値を適宜使う。

    public ParseTreeProperty<Double> prop = ParseTreeProperty();

    @Override
    public void exitExpr(ArithmeticParser.ExprContext ctx) {
        val e0 = prop.get(ctx.expr(0));
        val e1 = prop.get(ctx.expr(0));
        prop.push(ctx, e0 + e1);   // 足し算の節点の場合
        return
    }

木を走査し終えたら、propから最終結果をgetすればよい。