kotlin内でkotlin scriptを実行する

kotlin 1.1 でjavax.script API(JSR-223)がサポートされています。
https://kotlinlang.org/docs/reference/whatsnew11.html#javaxscript-support
これにより、kotlin上でkotlin(script)ソースをコンパイルして実行することができます。

公式にサンプルソースがありますので参照ください。
https://github.com/JetBrains/kotlin/tree/master/libraries/examples/kotlin-jsr223-local-example

以下、おおまかに説明します。
下記内容は kotlin 1.2.10　で確認しています。

## 目次
1. 準備
2. 実装  
  2.1 単純な実行  
  2.2 変数を渡す  
  2.3 コンパイル  
  2.4 eval
3. 応用例：簡易テンプレートエンジン

## 1. 準備

Gradleプロジェクトを前提とします。
通常のkotlinプロジェクトにkotlin-script-utilパッケージを依存に加えます。

```groovy
buildscript {
    repositories {
        mavenCentral()
    }

    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.10"
    }
}

apply plugin: 'kotlin'

repositories {
    mavenCentral()
}

dependencies {
    compile 'org.jetbrains.kotlin:kotlin-stdlib-jre8'
    compile 'org.jetbrains.kotlin:kotlin-script-util' // <--- 追加
}

compileKotlin {
    kotlinOptions.jvmTarget = '1.8'
}
```

SPI(Service Provider Interface)という仕組みを採用しています。`src/main/resources/META-INF/services/javax.script.ScriptEngineFactory` というファイルを作成し、以下の１行を書きます。
これでFactoryを見つけることができます。

```
org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory
```

## 2. 実装

### 2.1 単純な実行 

まずは簡単な例を実行します。

```kotlin
import javax.script.ScriptEngineManager

fun main(args: Array<String>) {
    val engine = ScriptEngineManager().getEngineByExtension("kts")
    engine.eval("println(\"Hello Kotlin\")") // Hello Kotlin
}
```

`ScriptEngineManager().getEngineByExtension("kts")`で`ScriptEngine`を取得します。
（`ScriptEngineManager().getEngineByName("kotlin")`でも取得できます。）
実態は`org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngine`になります。
`ScriptEngine#eval()`でkotlinコードを評価します。

さらに、`eval()`は評価した結果を受け取ることができます。

```kotlin
fun main(args: Array<String>) {
    val engine = ScriptEngineManager().getEngineByExtension("kts")
    val route2 = engine.eval("kotlin.math.sqrt(2.0)") // kotlin.mathは1.2以上
    println(route2) // 1.4142135623730951
}
```

### 2.2 変数を渡す

スクリプトに変数を渡すことができます。
（ただし、あまり使い勝手が良くありません。）

```kotlin
fun main(args: Array<String>) {
    val engine = ScriptEngineManager().getEngineByExtension("kts")
    val bindings = engine.createBindings()
    bindings["x"] = "Hello!"
    engine.eval("""
        val xxx = bindings["x"]
        println(xxx)
        """, bindings) // Hello!
}
```

`engine.createBindings()`で`Bindings`インスタンスを取得し、`MutableMap<String, Any?>`のように値を入れます。
`eval()`の第２引数に`Bindings`インスタンスを指定し、スクリプトのなかで`bindings`という変数で受け取ります。
スクリプトの中の`bindings`という変数名は固定で、`Map<String, Any?>`型になっており、`bindings["変数名"]`で取得できる値は`Any?`型になるため、適切な型にキャストする必要があります。

```kotlin
fun main(args: Array<String>) {
    val engine = ScriptEngineManager().getEngineByExtension("kts")
    val bindings = engine.createBindings()
    bindings["x"] = "Hello!"
    engine.eval("""
        val xxx = bindings["x"] as String
        println(xxx.substring(1)) // as Stringがないとエラーになる
        """, bindings) // Hello!
}
```


### 2.3 コンパイル

上記の例では`eval()`でコンパイルと実行を同時に行なっていましたが、事前にコンパイルだけ行い、コンパイルした結果を再利用することができます。

```kotlin
import javax.script.Compilable

fun main(args: Array<String>) {
    val engine = ScriptEngineManager().getEngineByExtension("kts")
    val compiled = (engine as Compilable).compile("""
        val xxx = bindings["x"]
        println(xxx)
        """)

    val bindings = engine.createBindings()
    bindings["x"] = "Hello!"
    compiled.eval(bindings)
}
```

`engine`を`Compilable`にキャストして`compile()`でスクリプトをコンパイルして`CompiledScript`インスタンスを生成します。
その後、`eval()`で評価します。
スクリプトを再利用する場合はこの`CompiledScript`インスタンスをキャッシュしておくと良いでしょう。

### 2.4 eval

スクリプトの中でスクリプトを組み立てて評価する`eval()`というメソッドが使えます。
あまりいい例が思いつきませんでしたが、[公式も同じような例](https://github.com/JetBrains/kotlin/blob/c034a73d7253918ac2c24e11dd8fb42a31c12f60/libraries/examples/kotlin-jsr223-local-example/src/test/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223ScriptEngineIT.kt#L156)でした。

```kotlin
fun main(args: Array<String>) {
    val engine = ScriptEngineManager().getEngineByExtension("kts")
    engine.eval("""
        val x = 1
        val y = eval("${'$'}x + 1")
        println(y) // 2
    """)
}
```

### 3. 応用例：簡易テンプレートエンジン

以下のように、この技術を使って簡易テンプレートエンジンを作ることができます。
https://github.com/sdeleuze/kotlin-script-templating

要は最終的に文字列を返すスクリプトを用意し、それを評価すれば完成です。
ロジックは`${}`で記述します。式を返す必要があるので注意が必要です。
変数は`bindings`で受け取るため、変数宣言が必要になります。

```kotlin:template.kts
import jsr223example.*

val name = bindings["name"] as String
@Suppress("UNCHECKED_CAST")
val users = bindings["users"] as List<User>
val condition = true

// ここからテンプレート
"""
Hello! ${name}

${if (condition) {
"""
Hello!
"""
} else ""}

${users.map {
"""
Hello! ${it.name}
"""}.joinToString("\n")}
""" // ここまで
```

- if文はelse句がないと値を返さないためエラーになるのでelseが必須です。
- for文は値を返さないので使用できません。その代わり、`map`や`joinToSting`などを使います。
- `bindings`はスクリプト上では未定義の変数なので、Intellijのエディタ上ではエラーになります。それを回避するため、別ファイルで変数をlateinit宣言してimportすることで見た目上はエラーになりません。

```kotlin:ScriptUtil.kt
package jsr223example

// Hack for Intellij
lateinit var bindings: Map<String, Any?>
```

実用で使用するのは難しいかもしれませんが、ちょっとしたツールなどには使えるかもしれません。

以上になります。

上記のソースは
https://github.com/tiibun/jsr223example
に置いています。