Gradle, Kotlin and ANTLR
This is one of those things I always do and somehow I forget about it next day, how to properly have a Gradle build definition file with Kotlin and ANTLR? Yes, ANTLR generates your parser and lexer skeleton and you want to write your specialized classes using them in Kotlin, all of that with a Gradle build file.
You will assume is easy, after all, there is a Gradle plugin for ANTLR, but for some reason is not that transparent. Let’s start with a simple grammar file (taken from the amazing The Definitive ANTLR4 Reference book):
grammar ArrayInit;
init: '{' value (',' value)* '}';
value: init | INT;
INT: [0-9]+;
WS: [ \t\r\n]+ -> skip;
Remember to save this file in the proper place, including potential package
directory, in my case it will be src/main/antlr/com/cprieto/samples/ArrayInit.g4
. This is important because if not your generated classes will be placed in the default package!
Now let’s write our simple consumer for our parser in Kotlin:
package com.cprieto.sample
import org.antlr.v4.runtime.CharStreams
import org.antlr.v4.runtime.CommonTokenStream
fun main() {
val input = CharStreams.fromStream(System.`in`)
val lexer = ArrayInitLexer(input)
val tokens = CommonTokenStream(lexer)
val parser = ArrayInitParser(tokens)
val tree = parser.init()
println(tree.toStringTree(parser))
}
Nice, you know where to put this (src/main/kotlin/com/cprieto/samples/App.kt
). Our build.gradle
file should be pretty simple using the right plugins:
plugins {
id 'antlr'
id 'org.jetbrains.kotlin.jvm' version '1.6.21'
}
repositories() {
mavenCentral()
}
dependencies {
antlr 'org.antlr:antlr4:4.10.1'
implementation 'org.antlr:antlr4-runtime:4.10.1'
}
But if we try to build this application will find two nasty surprises!
There is no package
declaration
Our Lexers would be generated in the correct directory (in default case, generated-src/antlr/main/com/cprieto/samples
) but if you check the generated .java
files, no package
headers is included! There are a few ways to solve this issue, including the @header
option in .g4
files, but I like my grammars to be a little independent, so I prefer to include that as part of the build definition. The ANTLR tool can do this for you with the parameter -package
, it will be matter of adding this as part of the grammar generation process (controlled by the generateGrammarSource
task)
generateGrammarSource {
arguments += ["-package", "com.cprieto.sample"]
}
When building, Kotlin compiler cannot find generated Java sources
This is a little more tricky to do, the sources are .java
files and we need to tell Gradle about this dependency. An easy way to do this is telling the Kotlin compilation task (named compileKotlin
in Gradle) to wait for the ANTLR generation task to finish before kicking on:
compileKotlin {
dependsOn generateGrammarSource
}
With this two things a simple build file can be done and expanded!
plugins {
id 'antlr'
id 'application'
id 'org.jetbrains.kotlin.jvm' version '1.6.21'
}
repositories() {
mavenCentral()
}
dependencies {
antlr 'org.antlr:antlr4:4.10.1'
implementation 'org.antlr:antlr4-runtime:4.10.1'
}
generateGrammarSource {
arguments += ["-package", "com.cprieto.sample"]
}
compileKotlin {
dependsOn generateGrammarSource
}