LoginSignup
0
0

More than 1 year has passed since last update.

GradleでANTLRファイルのpackageが認識されない問題に対処した

Last updated at Posted at 2021-07-05

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('.', '/')}")
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0