All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.simiacryptus.skyenet.kotlin.KotlinInterpreter.kt Maven / Gradle / Ivy

The newest version!
package com.simiacryptus.skyenet.kotlin

import com.simiacryptus.skyenet.core.actors.CodingActor
import com.simiacryptus.skyenet.interpreter.Interpreter
import org.jetbrains.kotlin.cli.common.repl.KotlinJsr223JvmScriptEngineBase
import org.jetbrains.kotlin.cli.common.repl.KotlinJsr223JvmScriptEngineFactoryBase
import org.jetbrains.kotlin.cli.common.repl.ScriptArgsWithTypes
import org.slf4j.LoggerFactory
import javax.script.Bindings
import javax.script.CompiledScript
import javax.script.ScriptContext
import javax.script.ScriptException
import kotlin.script.experimental.api.enableScriptsInstancesSharing
import kotlin.script.experimental.api.with
import kotlin.script.experimental.jsr223.KotlinJsr223DefaultScriptCompilationConfiguration
import kotlin.script.experimental.jsr223.KotlinJsr223DefaultScriptEvaluationConfiguration
import kotlin.script.experimental.jvm.jvm
import kotlin.script.experimental.jvm.updateClasspath
import kotlin.script.experimental.jvm.util.scriptCompilationClasspathFromContext
import kotlin.script.experimental.jvmhost.jsr223.KotlinJsr223ScriptEngineImpl

open class KotlinInterpreter(
    val defs: Map = mapOf(),
) : Interpreter {

    final override fun getLanguage(): String = "Kotlin"
    override fun getSymbols() = defs

    open val scriptEngine: KotlinJsr223JvmScriptEngineBase
        get() = object : KotlinJsr223JvmScriptEngineFactoryBase() {
            override fun getScriptEngine() = KotlinJsr223ScriptEngineImpl(
                this,
                KotlinJsr223DefaultScriptCompilationConfiguration.with {
                    classLoader?.also { classLoader ->
                        jvm {
                            updateClasspath(
                                scriptCompilationClasspathFromContext(
                                    classLoader = classLoader,
                                    wholeClasspath = true,
                                    unpackJarCollections = false
                                )
                            )
                        }
                    }
                },
                KotlinJsr223DefaultScriptEvaluationConfiguration.with {
                    this.enableScriptsInstancesSharing()
                }
            ) {
                ScriptArgsWithTypes(
                    arrayOf(),
                    arrayOf()
                )
            }.apply {
                getBindings(ScriptContext.ENGINE_SCOPE).putAll(getSymbols())
            }
        }.scriptEngine

    override fun validate(code: String): Throwable? {
        val wrappedCode = wrapCode(code)
        return try {
            scriptEngine.compile(wrappedCode)
            null
        } catch (ex: ScriptException) {
            wrapException(ex, wrappedCode, code)
        } catch (ex: Throwable) {
            CodingActor.FailedToImplementException(
                cause = ex,
                language = "Kotlin",
                code = code,
            )
        }
    }

    override fun run(code: String): Any? {
        val wrappedCode = wrapCode(code)
        log.debug(
            """
      |Running:
      |   ${wrappedCode.trimIndent().replace("\n", "\n\t")}
      |""".trimMargin().trim()
        )
        val bindings: Bindings?
        val compile: CompiledScript
        val scriptEngine: KotlinJsr223JvmScriptEngineBase
        try {
            scriptEngine = this.scriptEngine
            compile = scriptEngine.compile(wrappedCode)
            bindings = scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE)
            return kotlinx.coroutines.runBlocking { compile.eval(bindings) }
        } catch (ex: ScriptException) {
            throw wrapException(ex, wrappedCode, code)
        } catch (ex: Throwable) {
            throw CodingActor.FailedToImplementException(
                cause = ex,
                language = "Kotlin",
                code = code,
            )
        }
    }

    protected open fun wrapException(
        cause: ScriptException,
        wrappedCode: String,
        code: String
    ): CodingActor.FailedToImplementException {
        var lineNumber = cause.lineNumber
        var column = cause.columnNumber
        if (lineNumber == -1 && column == -1) {
            val match = Regex("\\(.*:(\\d+):(\\d+)\\)").find(cause.message ?: "")
            if (match != null) {
                lineNumber = match.groupValues[1].toInt()
                column = match.groupValues[2].toInt()
            }
        }
        return CodingActor.FailedToImplementException(
            cause = cause,
            message = errorMessage(
                code = wrappedCode,
                line = lineNumber,
                column = column,
                message = cause.message ?: ""
            ),
            language = "Kotlin",
            code = code,
        )
    }

    override fun wrapCode(code: String): String {
        val out = ArrayList()
        val (imports, otherCode) = code.split("\n").partition { it.trim().startsWith("import ") }
        out.addAll(imports)
        out.addAll(otherCode)
        return out.joinToString("\n")
    }


    companion object {
        private val log = LoggerFactory.getLogger(KotlinInterpreter::class.java)

        fun errorMessage(
            code: String,
            line: Int,
            column: Int,
            message: String
        ) = """
        |```text
        |$message at line ${line} column ${column}
        |  ${if (line < 0) "" else code.split("\n")[line - 1]}
        |  ${if (column < 0) "" else " ".repeat(column - 1) + "^"}
        |```
        """.trimMargin().trim()

        // TODO: Make this threadlocal with wrapper methods
        var classLoader: ClassLoader? = KotlinInterpreter::class.java.classLoader

    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy