2022/07/19 追記
概要
パーサを実装しようとANTLRを使い始めた。
JavaのプロジェクトなのでGradleでいい感じにできないかと調べたらプラグインで対応していたので使い方を色々調べながらビルドスクリプトを書いたけど、package名のサブディレクトリはいかに置いたらgenerateGrammarSource
タスクに失敗するようになった。
情報が少なかったけど、issueとか見ながらどうにか対応したので備忘録兼ねて書いておく。
構成
project-name
-- src/
-- antlr
-- package/
-- PackageLexer.g4
-- PackageParser.g4
-- java
-- package/
-- Main.java
-- build.gradle
build.gradleは下記のように記述
plugins {
id 'java'
id 'antlr'
id 'application'
}
group 'group'
version '1.0'
mainClassName = 'package.Main'
repositories {
mavenCentral()
}
dependencies {
antlr 'org.antlr:antlr4:4.9.2'
implementation 'org.antlr:antlr4-runtime:4.9.2'
}
Lexer、Parserは次のように
lexer grammar PackageLexer;
HELLO: 'hello' ;
parser grammar PackageParser;
options { tokenVocab=PackageLexer; }
hello : HELLO ;
問題
ここでParserファイルにoptionsでLexerを指定するとビルドがコケる。
ググるとStackOverFlowで下記の記事がヒット
https://stackoverflow.com/questions/40995727/gradle-cant-find-antlr-token-file/49388412
どうも同様の問題っぽい。
対処法は文法ファイルをpackage下ではなく直に置けと
しかしJavaでルートに置くのはなんか嫌なのでもう少しまともな方法がないかと見てみたらGradleのGitHub Issueで次の報告が
https://github.com/gradle/gradle/issues/2565
package名がparrot!
これは見覚えあるぞ、Groovy3.0で実装された新しいパーサの名前だ!
ということはGroovyのビルドファイルで何かしら対応しているのではと思いApacheGroovyのリポジトリを確認
見事に解決法を見つけたので下記に記す。
対応方法
ApacheGroovyのリポジトリの中でANTLRが使われているのはsrc/antlr以下にLexerとParserがあった。
基本通りの配置で前述の通りルートに置かれている。
GroovyはGradle使って管理されているので、antlrのプラグインや依存関係の設定しているところを探すとbuildSrc/src/main/groovy/groovy-core.gradleがヒット
最下部を見るとやはりパーサを生成するgenerateGrammarSource
タスクに対して設定がされていた。
tasks.named("generateGrammarSource") {
maxHeapSize = '1g'
outputs.cacheIf { true }
outputDirectory = file("${buildDir}/generated/sources/antlr4")
sourceSets.main.java.srcDirs += outputDirectory
final PARSER_PACKAGE_NAME = 'org.apache.groovy.parser.antlr4'
arguments += ["-visitor", "-no-listener", "-package", PARSER_PACKAGE_NAME]
doLast {
def parserFilePattern = 'Groovy*'
def outputPath = generateGrammarSource.outputDirectory.canonicalPath
def parserPackagePath = "${outputPath}/${PARSER_PACKAGE_NAME.replace('.', '/')}"
file(parserPackagePath).mkdirs()
copy {
from outputPath
into parserPackagePath
include parserFilePattern
}
delete fileTree(outputPath) {
include parserFilePattern
}
}
}
Gradleではこのように記述すると、そのタスクの実行後にdoLast
の処理が呼ばれて独自の処理を行うことができる。
上記のコードはbuild/(antlrの出力ディレクトリ)/
以下にあるGroovy
から始まるファイルをpackage名以下にコピーしている。
かなり無理矢理な対応だけど、ANTLRやGradleの機能を使いこなしているであろうGroovyの設定でさえこうなのだから、これしか対応方法がないのだろう。
ということで必要な部分のみ流用して下記のように変更
まずはsrc/antlr
直下に定義ファイルを移動
次にビルドスクリプトに下記を追加
generateGrammarSource {
final PARSER_PACKAGE_NAME = 'package'
arguments += [ "-package", PARSER_PACKAGE_NAME ]
doLast {
def parserFilePattern = 'Package*'
def outputPath = generateGrammarSource.outputDirectory.canonicalPath
def parserPackagePath = "${outputPath}/${PARSER_PACKAGE_NAME.replace('.', '/')}"
file(parserPackagePath).mkdirs()
copy {
from outputPath
into parserPackagePath
include parserFilePattern
}
delete fileTree(outputPath) {
include parserFilePattern
}
}
}
参考元と微妙に記述が違うけど気にしないでいいよ。
これでビルドすると問題なく動作した。
忘れないうちに記事にしようと、ちょっとまとまってない部分も多いけどとりあえずこんな感じで動かすことができたよってことで。
まだANTLR自体の使い方全然わからないのでゆっくり調べていこう。
より簡単な方法
単純にgenerateGrammarSourceの中でoutputDirectoryに指定すればいいことがわかった。
なんで記事書いた当初この方法見つけきらなかったのだろうか……
generateGrammarSource {
final PARSER_PACKAGE_NAME = 'package'
arguments += ["-visitor", "-package", PARSER_PACKAGE_NAME]
outputDirectory = file("${generateGrammarSource.outputDirectory}/${PARSER_PACKAGE_NAME.replace('.', '/')}")
}