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フォルダに格納させた。

後は、オレオレ言語をきちんと定義し、このパーサを使って処理するだけだ。これが大変なわけだが。。。。