ANTRL メモ
使用例
- 検索オプション
- Markdown拡張
- Terraformのグラフ化(Docコメントに図形表示の指示を書く)
- CI/CDコードの要約?
- 自作アプリ用のPlugin?
- GUIアプリ内のシェル?
最初の例
- 四則演算(優先順位: *, / -> + -)
- 検索演算(優先順位: AND -> OR)
ANTLRのインストール
ANTLRのダウンロードページで最新版のバージョンを確認して、以下のようにインストール・セットアップする
cd /usr/local/lib/
sudo curl -O https://www.antlr.org/download/antlr-4.13.2-complete.jar
~/.bashrc
ファイルに以下を追加する。
# for ANTLR
export CLASSPATH=".:/usr/local/lib/antlr-4.13.2-complete.jar:$CLASSPATH"
ファイル追加後に、以下を実行する
. .bashrc
/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'
Mavenのプロジェクトでの利用方法
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以外のプログラミング言語の場合は、ダウンロードページを参照する。
Javaパーサを生成・利用する手順
適当なディレクトリを作成して、移動する。
mkdir antlr_arithmetic
cd antlr_arithmetic/
文法ファイルを、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で解析が実行される
ANTLRのイディオム
MyGrammer.g4
ファイルで文法ファイルを定義してantlrのツールでMyGrammerXxxクラスを生成させる。
生成されてたクラスは以下のように使う。
MyGrammerListenerを使う場合
xxx
はパースするファイルの拡張子。yyy
はMyGrammer.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);
}
解析木を走査して結果をまとめる方法
四則演算の式の解析木を巡回して値を計算する。
ビジターの戻り値を使う。
上記の木の葉の、 1
, 2
, 3
はそのまま、1
, 2
, 3
とみなし、 2 * 3
の節点は6
とみなせる。 1
と2 * 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すればよい。