Java/GradleでのANTLRの使い方、その1
Antlrとは、いわゆるパーサージェネレータであり、プログラミング言語の文法を定義してやると、そのパーサーを自動生成してくれるものだ。つまり、
- オレオレ言語の文法を定義する
- ANTLRに上の定義を入れてやると、その「オレオレ言語のソースをパースするJavaプログラム」を生成する
もちろん、ANTLRが作り出すJavaプログラムは、「言語ソースをパースする」というだけのものだ。つまり、入力されたソースがオレオレ言語文法に沿っているのかをチェックし、そうであればその要素に分解してくれるというだけである。
要素に分解された後で、それに対して何をするのかは、自前で書かなくてはいけない。
プロジェクトの構成
例によってEclipse上にプロジェクトを作成し、JavaソースはEclipseの流儀に合わせる。Gradleを使う。
通常のJavaソースはsrcフォルダに、antlrのソースはprsに入れる。
build.gradle
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'antlr'
repositories {
jcenter()
}
sourceSets {
main {
java.srcDirs = ['src']
antlr.srcDirs = ['prs']
}
}
dependencies {
antlr "org.antlr:antlr4:4.7"
}
ここがわかりにくいのだが、gradleにはもともとantlr用のプラグインが備わっており、「apply plugin: ‘antlr’」でそれを呼び出すことができるのだが、このプラグインはantlr2, 3, 4をサポートしているのだそうだ。
したがって、dependenciesで今回はantlr4を指定してやる。その場合もcompileやらimplementationではなく、「antlr “org.antlr:antlr4:4.7″」などと指定する。
オレオレ言語の定義としては、prsフォルダの中にtest.g4とでもして以下を入れる(どこからか持ってきたもので、何をするものかわかっていない)。
grammar test;
r : 'hello' ID;
ID : [a-z]+ ;
WS : [ \t\r\n]+ -> skip ;
Eclipseプロジェクトは以下のようになる。Javaソースは適当だ。
プロジェクトの依存関係とコンパイル
最終的には、antlrでtest.g4を処理させてパーサのJavaソースコードを生成させ、それをコンパイルする。その上で、それを利用する本体Javaプログラム(今は何も無い)をコンパイルし、最終出力(jarなり何なり)を得るというわけなのだが、gradleのantlrプラグインにはこの依存が仕込まれている。
つまり、compileJavaとすれば、自動でantlr側の処理をやってくれるようだ。そこでcompileJavaを行う。するとbuild以下にパーサのソースが生成される。
パーサーソース生成先を変更し、パッケージを追加させる
上記では、デフォルトのフォルダにパーサーのソースが生成され、そのパッケージ名も記述されていない。
つまり、「build/generated-src/antlr/main/myparser/testParser.java」などというファイルになり、しかも、以下のようにpackageが記述されていない。「myparser」というパッケージのフォルダに格納されているにも関わらずだ。
// Generated from myparser\test.g4 by ANTLR 4.7
import org.antlr.v4.runtime.atn.*;
import org.antlr.v4.runtime.dfa.DFA;
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.misc.*;
import org.antlr.v4.runtime.tree.*;
import java.util.List;
import java.util.Iterator;
import java.util.ArrayList;
@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"})
public class testParser extends Parser {
static { RuntimeMetaData.checkVersion("4.7", RuntimeMetaData.VERSION); }
以下としたい。
- 自前のJavaソースから生成コードを呼び出すために、「ソースフォルダ」に出力させたい
- 当然だが、packageを記述してもらわないと面倒。
build.gradleを以下に変更
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'antlr'
repositories {
jcenter()
}
sourceSets {
main {
java.srcDirs = ['src', 'src2'] // 'src2'を追加
antlr.srcDirs = ['prs']
}
}
// これを追加
generateGrammarSource {
outputDirectory = file('src2')
}
dependencies {
antlr "org.antlr:antlr4:4.7"
}
test.g4ではなくTest.g4という名称にし、以下に変更。grammer名称もTestにする。@headerをつける。
grammar Test;
@header {
package myparser;
}
r : 'hello' ID;
ID : [a-z]+ ;
WS : [ \t\r\n]+ -> skip ;
gradleで「generateGrammarSource」を実行する。これはコンパイルせずにJavaソースを出力させるだけになる。すると以下になった。この場合はbuildフォルダには何も出力されない。
いい感じである。Test.g4というオレオレ言語定義から、それをパースするJavaソースコード(パーサ)を生成させ、それをsrc2フォルダに格納させた。
後は、オレオレ言語をきちんと定義し、このパーサを使って処理するだけだ。これが大変なわけだが。。。。