JVM-Math-LanguageをGradleプロジェクトとして手元で動かす

久しぶりに記事書きます。

[TCGの方](https://qiita.com/Kakky/items/19fecdf849026d952742)は気力がないので当分は書かないかもしれません。

ほぼ役に立つ文章は[方法② 設定からプロセッサを指定する。](#方法② 設定からプロセッサを指定する。)だけなので、読み飛ばしてください。

# 要旨

JVM-Math-Languageを手元に落として動かした。

~~コンパイル時に -processor オプションを付けると実行できた。~~

設定から Annotation Processor (アノテーションプロセッサ) を設定すると、ソースコードがプロジェクトに紐づけられた。

動いた。



# 動機

Truffleを使ったGraalVM上で動くプログラミング言語を作りたかったです。

GraalのSLは思っていたよりもシンプルじゃなかったので、もっと簡単なインタプリタを基に自分の言語を実装しようと思いました。

・[graalvm/simplelanguage](https://github.com/graalvm/simplelanguage)

・[Truffle Tutorial Slides](https://lafo.ssw.uni-linz.ac.at/pub/papers/2016_PLDI_Truffle.pdf)

スライドの雑翻訳も前にちょっと書いた。

・[Truffle Tutorial Slides 自分用翻訳メモ①](https://qiita.com/Kakky/items/1a3197c7ee26db5f50c9)



# JVM-Math-Language とは？

[JJUG CCC 2017 Fall オレオレJVM言語を作ってみる（四則演算するだけだけど）](https://www.slideshare.net/jyukutyo/jjug-ccc-2017-fall-jvm)というセッションで紹介された、 ANTLR + Truffle で作成されたプログラミング言語です。

・[jyukutyo/JVM-Math-Language](https://github.com/jyukutyo/JVM-Math-Language)

> An AST interpreter with Truffle. This program can execute four 
> arithmetic operations. You can use numbers, +, -, *, /, "()", and 
> "->" for running in another thread.

四則演算ができるTruffleのAST(抽象構文木)インタプリタだそうです。



# プロジェクトの作成

プロジェクトを作成します。

[Gradle](https://gradle.org/) プロジェクトで、コンパイラは [JDK 1.8](https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) を利用しました。

IDEはIntelliJを利用しています。

手法は省略しますが、一応[似たような記事](https://qiita.com/Kakky/items/27fa8940016428ee71f6)を前に書いたのでそれとか見てもいいと思います。

で、GitHubからプロジェクトのフォルダをzipで落としてきます。(Clone or Download)

解凍したら、srcフォルダをまるまるプロジェクト直下に落としてきます。

JVM-Math-LanguageはMavenプロジェクトっぽいので、build.gradleを書いてやる必要があります。

一例として、JVM-Math-Languageの[pom.xml](https://github.com/jyukutyo/JVM-Math-Language/blob/master/pom.xml)を雑に移植しといた(dependances書いただけ)奴を置いておきます。

```groovy:build.gradle
plugins {
    id 'java'
}

version '1.0-SNAPSHOT'

sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

compileJava.options.encoding = 'UTF-8'
compileTestJava.options.encoding = 'UTF-8'

dependencies {
    testCompile group: 'junit', name: 'junit', version: '4.12'
    //ANTLRを動かすのに必要
    compile group: 'org.antlr', name: 'antlr4-runtime', version: '4.7'
    //Truffleを動かすのに必要
    compile group: 'org.graalvm.truffle', name: 'truffle-api', version: '1.0.0-rc12'
    //アノテーションからコードを生成するのに必要
    compile group: 'org.graalvm.truffle', name: 'truffle-dsl-processor', version: '1.0.0-rc12'
}
```

余談ですが、Gradleをコンソールからいい感じに使うと、pom.xmlからプロジェクトを(もちろんbuild.gradleも)生成できるらしいです。

・[Maven Pomをgradle buildに変換する](https://codeday.me/jp/qa/20190306/334913.html)

具体的には、pom.xmlを置いたディレクトリ(プロジェクトディレクトリになる)に、以下のコマンドを入力します。

```shell
gradle init --type pom
```



# コンパイルエラー

さて、とりあえずビルド.....といったところで、コンパイルエラーが6件ぐらい発生します。

ついでにいえば赤線も生えていますね。

確認してみると、以下のクラスが足りないようです。

・jvmmathlang.truffle.JvmMathLangTypesGen

・nodes.ops.AddNodeGen (以下パッケージ略)

・DivNodeGen

・MulNodeGen

・SubNodeGen



# ～～Genクラスとは？

これらのクラスは、[JSR 269: Pluggable Annotation Processing API](https://docs.oracle.com/javase/1.5.0/docs/guide/apt/index.html)というものを用いて、Truffle DSL Processorが自動で生成してくれるコードらしいです。

ざっくり言うと、特定のアノテーションを付けた抽象クラスを作ると、その実装が自動で生成されるらしいです。

で、今はその生成周りがなにも設定されていないがためにコンパイルエラーになっているわけです。

では、アノテーションプロセッサの設定をしていきましょう。

また、Annotation Processing APIについては、以下の記事が分かりやすかったです。

[Pluggable Annotation Processing API 使い方メモ](https://qiita.com/opengl-8080/items/beda51fe4f23750c33e9)



**方法①はCompileJavaでしか動きませんが、CompileJavaではオプションを指定しなくてもコンパイルに成功するようです。(つまり意味がない)**

**ただ、-processorオプションやgradleでの設定方法が説明されている記事にたどり着くのに苦労したため、文章自体は置いておこうと思います。**

# ~~方法① コンパイルオプションで指定する。~~

~~Javaのコンパイラには、プロセッサクラスを指定してコンパイルするオプションがあります。~~

```shell
javac -processor 完全修飾クラス名,完全修飾クラス名,完全修飾クラス名
```

~~また、Gradleプロジェクトではコンパイルオプションはビルドスクリプトに記述します。~~

~~・[アノテーションプロセッサーで自動生成するときにハマったあたりの話 #ソースコード自動生成](https://mike-neck.hatenadiary.com/entry/2014/08/28/194306)~~

```groovy:build.gradle
compileTestJava.options.compilerArgs += ['-processor', '完全修飾クラス名,完全修飾クラス名,完全修飾クラス名']
```

~~ここで、一つ問題が発生します。~~

~~私はプロセッサクラスの一覧を持っていません。~~

~~しかしそれは幸運なことに、それは私のすぐ手元にありました。~~

~~truffle-dsl-processor-1.0.0-rc12.jar内の META-INF -> service に、javax.annotation.processing.Processorというファイルがあります。~~

~~その中に、完全なクラス名の一覧があります。~~

```:javax.annotation.processing.Processor
com.oracle.truffle.dsl.processor.TruffleProcessor
com.oracle.truffle.dsl.processor.verify.VerifyTruffleProcessor
com.oracle.truffle.dsl.processor.LanguageRegistrationProcessor
com.oracle.truffle.dsl.processor.InstrumentRegistrationProcessor
com.oracle.truffle.dsl.processor.InstrumentableProcessor
com.oracle.truffle.dsl.processor.verify.VerifyCompilationFinalProcessor
com.oracle.truffle.dsl.processor.OptionProcessor
com.oracle.truffle.dsl.processor.interop.InteropDSLProcessor
com.oracle.truffle.object.dsl.processor.LayoutProcessor
```

~~これを先ほどのbuild.gradleに当てはめます。~~

```groovy:build.gradle
//略
compileJava.options.compilerArgs += ['-processor', 'com.oracle.truffle.dsl.processor.TruffleProcessor,' +
        'com.oracle.truffle.dsl.processor.LanguageRegistrationProcessor,' +
        'com.oracle.truffle.dsl.processor.InstrumentRegistrationProcessor,' +
        'com.oracle.truffle.dsl.processor.InstrumentableProcessor,' +
        'com.oracle.truffle.dsl.processor.verify.VerifyCompilationFinalProcessor,' +
        'com.oracle.truffle.dsl.processor.OptionProcessor,' +
        'com.oracle.truffle.dsl.processor.interop.InteropDSLProcessor,' +
        'com.oracle.truffle.object.dsl.processor.LayoutProcessor']
```

~~サイドバーのGradle -> Tasks -> compileJavaからやるとうごきます。~~



~~また、Gradleにすべてのクラス名を記述すると見通しが悪くなるので、~~

~~src -> main -> resource -> META-INF -> services にjavax.annotation.processing.Processorをもってくることで、コンパイルオプションを省略できます。~~



# 方法② 設定からプロセッサを指定する。

はい、ここからが本命です。

ファイル -> 設定 -> ビルド、実行、デプロイ -> コンパイラー -> 注釈プロセッサ を開きます。

Defaultを選択し、注釈処理を使用可能にする にチェックを入れます。

次の場所に生成されたソース関連を保管 で、モジュール・コンテント・ルート を選択します。



これで、ビルド時にsrc/main内にコードが生成されます。

が、まだgenerated及びgenerated_testsフォルダがソースとして認識されていません。

そこで、build.gradleを編集してソースフォルダとして認識されるようにします。

・[逆引きマニュアル:  IntelliJ IDEAでAnnotation Processorsを使用する方法](https://www.ikemo3.com/inverted/intellij-idea/use-annotation-processor/)

```groovy:build.gradle
apply plugin: 'idea'

idea {
    module {
        sourceDirs += file('src/main/generated')
        generatedSourceDirs += file('src/main/generated')
        testSourceDirs += file('src/test/generated_tests')
    }
}
```

これで、エラーがすべてなくなったと思います。



# 最後に

方法②とかいう30秒で終わりそうな設定にたどり着くために1週半ぐらいかかってしまいました。

皆さんもそういうことがあればぜひ記事を書いて教えてください。

同じ苦労をする人を減らしていきましょう！



## P.S.

ネットソースの独学はいつもしんどい