kotlinから使えるテンプレートエンジンを作る手順調べたところ、ANTLRに行き着いたので開発環境を作る過程を軽くまとめました。
概要
ホスト言語から大きく外れた文法をサポートしたい場合は、(おそらく)独自のパーサーを作る必要があります。
JVM上で動くパーサジェネレータをいくつか調べたところ、候補はいくつかありましたがANTLRが最も信頼できそうでした。ANTLRの特徴は以下の通りです。
- LL(*)による構文解析(詳しくは理解してない)
- Java以外にもc#,python,jsなどのコード生成が可能
- gradleプラグインとIntelliJプラグインが用意されている
- 比較的実行速度が遅い
- コミュニティが大きく、参考にできるソースコードがGithub上に十分ある
動作速度は気になるものの、テンプレートエンジンの開発はよくわかってなく部分が多いのでとりあえず動くものを作るにはANTLRが最適なように思います。
プロジェクトの作成
IntelliJで新規のGradleプロジェクトを作成しましょう。

build.gradleの編集
build.gradleに以下の差分を追加します。mainClassName
はapplicationプラグインのエントリポイントを指定するプロパティです。kotlinを対象にする場合はファイル名の末尾にKt
をつけます。
group 'com.kazy'
version '1.0-SNAPSHOT'
buildscript {
ext.kotlin_version = '1.0.3'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'java'
apply plugin: 'kotlin'
+ apply plugin: 'application'
+ apply plugin: 'antlr'
repositories {
mavenCentral()
}
+ mainClassName = "com.kazy.MainKt"
dependencies {
+ antlr 'org.antlr:antlr4:4.5.3'
+ compile "org.antlr:antlr4-runtime:4.5.3"
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
testCompile group: 'junit', name: 'junit', version: '4.11'
}
文法定義ファイルからパーサーを生成する
環境を作ることが目的なので文法定義はgetting-startedのものを利用します。
紹介されている文法定義をそのまま使ってしまうと、Javaのコードを生成した際にpackageが指定されていなく不便なので@header
を使ってpackage名を指定します。作成した文法定義ファイルはsrc>antlr
以下に指定したパッケージと同名のディレクトリ階層を作成し配置します。
// Define a grammar called Hello
grammar Hello;
@header {
package your.package.name.generated;
}
r : 'hello' ID ; // match keyword hello followed by an identifier
ID : [a-z]+ ; // match lower-case identifiers
WS : [ \t\r\n]+ -> skip ; // skip spaces, tabs, newlines

ここまで準備ができたらコードの自動生成をしましょう。
プロジェクトのディレクトリで./gradlew generateGrammarSouce
するか メニューバーのView>Windows>Gradle
でメニューを出してotherタスク内のgenerateGrammarSouce
を実行すると、
build>generated-src>antlr
以下にパーサーのコードが自動生成されています。

生成されたパーサーを実行する
エントリポイントを作って、生成されたコードを実行してみましょう。mainClassName = "com.kazy.MainKt"
とした場合はcom.kazy.Main.kt
というファイルをkotlinディレクトリ以下に作成します。mainの実装は以下のようにします。
fun main(args: Array<String>) {
val text = "hello world"
val source = ANTLRInputStream(text)
val lexer = HelloLexer(source)
val tokenStream = CommonTokenStream(lexer)
val parser = HelloParser(tokenStream)
parser.r()
}
この状態で./gradlew run
もしくはGUI上でMain.ktを右クリックしてRunさせて正常に終了した場合は正しく動作しています。成功しても何も表示されないので面白みに欠けますが、試しにtextを"Hello world"にするとTokenizerがコケることが確認できると思います。
終わり
ここまで環境を整えるに半日かかってしまいました。。
ANTLRはコミュニティが大きそうに見えますが日本語の記事はあまり多くないので、もし開発が継続できたら少しずつ知見を投稿します。