Add new REPL API JVM implementation

This commit is contained in:
Ilya Muradyan
2020-02-28 19:45:20 +03:00
committed by Ilya Chernikov
parent 4c2c44b106
commit d2fec96f38
51 changed files with 2557 additions and 503 deletions
@@ -0,0 +1,69 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package kotlin.script.experimental.jvm
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
import kotlin.reflect.KClass
import kotlin.script.experimental.api.*
import kotlin.script.experimental.jvm.util.SnippetsHistory
import kotlin.script.experimental.util.LinkedSnippet
import kotlin.script.experimental.util.LinkedSnippetImpl
import kotlin.script.experimental.util.add
class BasicJvmReplEvaluator(val scriptEvaluator: ScriptEvaluator = BasicJvmScriptEvaluator()) :
ReplEvaluator<CompiledSnippet, KJvmEvaluatedSnippet> {
override var lastEvaluatedSnippet: LinkedSnippetImpl<KJvmEvaluatedSnippet>? = null
private set
private val history = SnippetsHistory<KClass<*>?, Any?>()
override suspend fun eval(
snippet: LinkedSnippet<out CompiledSnippet>,
configuration: ScriptEvaluationConfiguration
): ResultWithDiagnostics<LinkedSnippet<KJvmEvaluatedSnippet>> {
val lastSnippetClass = history.lastItem()?.first
val historyBeforeSnippet = history.items.map { it.second }
val currentConfiguration = ScriptEvaluationConfiguration(configuration) {
if (historyBeforeSnippet.isNotEmpty()) {
previousSnippets.put(historyBeforeSnippet)
}
if (lastSnippetClass != null) {
jvm {
baseClassLoader(lastSnippetClass.java.classLoader)
}
}
}
val snippetVal = snippet.get()
val newEvalRes = when (val res = scriptEvaluator(snippetVal, currentConfiguration)) {
is ResultWithDiagnostics.Success -> {
val retVal = res.value.returnValue
when (retVal) {
is ResultValue.Error -> history.add(retVal.scriptClass, null)
is ResultValue.Value, is ResultValue.Unit -> history.add(retVal.scriptClass, retVal.scriptInstance)
}
KJvmEvaluatedSnippet(snippetVal, currentConfiguration, retVal)
}
else ->
KJvmEvaluatedSnippet(snippetVal, currentConfiguration, ResultValue.NotEvaluated)
}
val newNode = lastEvaluatedSnippet.add(newEvalRes)
lastEvaluatedSnippet = newNode
return newNode.asSuccess()
}
}
class KJvmEvaluatedSnippet(
override val compiledSnippet: CompiledSnippet,
override val configuration: ScriptEvaluationConfiguration,
override val result: ResultValue
) : EvaluatedSnippet
@@ -5,6 +5,7 @@
package kotlin.script.experimental.jvm.impl
import org.jetbrains.annotations.TestOnly
import java.io.*
import java.net.URL
import java.net.URLClassLoader
@@ -39,13 +40,13 @@ internal class KJvmCompiledScriptData(
companion object {
@JvmStatic
private val serialVersionUID = 4L
private val serialVersionUID = 5L
}
}
class KJvmCompiledScript internal constructor(
open class KJvmCompiledScript internal constructor(
internal var data: KJvmCompiledScriptData,
var compiledModule: KJvmCompiledModule? // module should be null for imported (other) scripts, so only one reference to the module is kept
internal var compiledModule: KJvmCompiledModule? // module should be null for imported (other) scripts, so only one reference to the module is kept
) : CompiledScript, Serializable {
constructor(
@@ -93,6 +94,9 @@ class KJvmCompiledScript internal constructor(
)
}
@TestOnly
fun getCompiledModule() = compiledModule
private fun writeObject(outputStream: ObjectOutputStream) {
outputStream.writeObject(data)
outputStream.writeObject(compiledModule)
@@ -177,7 +181,7 @@ fun KJvmCompiledScript.toBytes(): ByteArray {
oos = ObjectOutputStream(bos)
oos.writeObject(this)
oos.flush()
return bos.toByteArray()!!
return bos.toByteArray()
} finally {
try {
oos?.close()
@@ -0,0 +1,37 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package kotlin.script.experimental.jvm.util
import java.io.Serializable
import java.util.*
typealias CompiledHistoryItem<CompiledT, ResultT> = Pair<CompiledT, ResultT>
typealias CompiledHistoryStorage<CompiledT, ResultT> = ArrayList<CompiledHistoryItem<CompiledT, ResultT>>
typealias CompiledHistoryList<CompiledT, ResultT> = List<CompiledHistoryItem<CompiledT, ResultT>>
/*
WARNING: Not thread safe, the caller is assumed to lock access.
*/
open class SnippetsHistory<CompiledT, ResultT>(startingHistory: CompiledHistoryList<CompiledT, ResultT> = emptyList()) : Serializable {
protected val history: CompiledHistoryStorage<CompiledT, ResultT> = ArrayList(startingHistory)
fun add(line: CompiledT, value: ResultT) {
history.add(line to value)
}
fun lastItem(): CompiledHistoryItem<CompiledT, ResultT>? = history.lastOrNull()
fun lastValue(): ResultT? = lastItem()?.second
val items: List<CompiledHistoryItem<CompiledT, ResultT>> = history
fun isEmpty(): Boolean = history.isEmpty()
fun isNotEmpty(): Boolean = history.isNotEmpty()
companion object {
private const val serialVersionUID: Long = 8328353001L
}
}
@@ -0,0 +1,14 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package kotlin.script.experimental.jvm.util
import kotlin.script.experimental.api.ResultWithDiagnostics
import kotlin.script.experimental.api.ScriptDiagnostic
fun <T> ResultWithDiagnostics<T>.isIncomplete() = this.reports.any { it.code == ScriptDiagnostic.incompleteCode }
fun <T> ResultWithDiagnostics<T>.isError() = this is ResultWithDiagnostics.Failure
@@ -0,0 +1,46 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package kotlin.script.experimental.jvm.util
import java.io.Serializable
import kotlin.script.experimental.api.SourceCode
data class AbsSourceCodePosition(val line: Int, val col: Int, val absolutePos: Int) : Serializable
internal fun String.findNth(s: String, n: Int, start: Int = 0): Int {
if (n < 1) return -1
var i = start
for (k in 1..n) {
i = indexOf(s, i)
if (i == -1) return -1
i += s.length
}
return i - s.length
}
fun Int.toSourceCodePosition(code: SourceCode): SourceCode.Position {
val substr = code.text.substring(0, this)
val line = 1 + substr.count { it == '\n' }
val sep = code.text.determineSep()
val col = 1 + substr.length - substr.lastIndexOf(sep) - sep.length
return SourceCode.Position(line, col, this)
}
fun String.determineSep() = if (indexOf("\r\n") == -1) "\n" else "\r\n"
fun SourceCode.Position.calcAbsolute(code: SourceCode): Int {
if (absolutePos != null)
return absolutePos!!
if (line == 1)
return col - 1
val sep = code.text.determineSep()
return code.text.findNth(sep, line - 1) + sep.length + col - 1
}
@@ -0,0 +1,40 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package kotlin.script.experimental.jvm.test
import org.junit.Assert
import org.junit.Test
import kotlin.script.experimental.api.SourceCode
import kotlin.script.experimental.jvm.util.calcAbsolute
import kotlin.script.experimental.jvm.util.toSourceCodePosition
class CalcAbsoluteTest {
@Test
fun testMultiline() {
val source = """
abcdefg
hij
klmnopqrst
covid19
uv
""".trimIndent().toSource()
val pos = SourceCode.Position(4, 6)
val absPos = pos.calcAbsolute(source)
Assert.assertEquals('1', source.text[absPos])
Assert.assertEquals(17, 17.toSourceCodePosition(source).calcAbsolute(source))
}
fun String.toSource() = SourceCodeTestImpl(this)
class SourceCodeTestImpl(override val text: String) : SourceCode {
override val name: String? = null
override val locationId: String? = null
}
}