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
@@ -10,7 +10,6 @@ import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
import org.jetbrains.kotlin.cli.common.repl.ReplCompileResult
import org.jetbrains.kotlin.cli.common.repl.ReplCompiler
import org.jetbrains.kotlin.cli.common.repl.ReplEvalResult
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
@@ -21,8 +21,8 @@ import kotlin.script.experimental.jvmhost.repl.JvmReplEvaluator
class LegacyReplTest : TestCase() {
fun testReplBasics() {
LegacyTestRepl().use { repl ->
val res1 = repl.replCompiler.check(repl.state, ReplCodeLine(0, 0, "val x ="))
TestCase.assertTrue("Unexpected check results: $res1", res1 is ReplCheckResult.Incomplete)
val res1 = repl.replCompiler.compile(repl.state, ReplCodeLine(0, 0, "val x ="))
TestCase.assertTrue("Unexpected check results: $res1", res1 is ReplCompileResult.Incomplete)
assertEvalResult(repl, "val l1 = listOf(1 + 2)\nl1.first()", 3)
@@ -54,8 +54,8 @@ class LegacyReplTest : TestCase() {
fun testReplCodeFormat() {
LegacyTestRepl().use { repl ->
val codeLine0 = ReplCodeLine(0, 0, "val l1 = 1\r\nl1\r\n")
val res0 = repl.replCompiler.check(repl.state, codeLine0)
val res0c = res0 as? ReplCheckResult.Ok
val res0 = repl.replCompiler.compile(repl.state, codeLine0)
val res0c = res0 as? ReplCompileResult.CompiledClasses
TestCase.assertNotNull("Unexpected compile result: $res0", res0c)
}
}
@@ -125,7 +125,7 @@ internal class LegacyTestRepl : Closeable {
fun nextCodeLine(code: String): ReplCodeLine = ReplCodeLine(currentLineCounter.getAndIncrement(), 0, code)
val replCompiler: JvmReplCompiler by lazy {
JvmReplCompiler(simpleScriptCompilationConfiguration)
JvmReplCompiler(simpleScriptCompilationConfiguration, false)
}
val compiledEvaluator: ReplEvaluator by lazy {
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.
*/
@@ -7,24 +7,16 @@ package kotlin.script.experimental.jvmhost.test
import junit.framework.TestCase
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.cli.common.repl.BasicReplStageHistory
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.scripting.compiler.plugin.impl.KJvmReplCompilerImpl
import org.jetbrains.kotlin.scripting.compiler.plugin.impl.KJvmReplCompilerBase
import org.junit.Assert
import org.junit.Test
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.toScriptSource
import kotlin.script.experimental.jvm.BasicJvmScriptEvaluator
import kotlin.script.experimental.jvm.baseClassLoader
import kotlin.script.experimental.jvm.BasicJvmReplEvaluator
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
import kotlin.script.experimental.jvm.jvm
class ReplTest : TestCase() {
companion object {
const val TEST_DATA_DIR = "libraries/scripting/jvm-host-test/testData"
}
@Test
fun testCompileAndEval() {
val out = captureOut {
@@ -156,103 +148,98 @@ class ReplTest : TestCase() {
limit = 100
)
}
}
fun evaluateInRepl(
snippets: Sequence<String>,
compilationConfiguration: ScriptCompilationConfiguration = simpleScriptCompilationConfiguration,
evaluationConfiguration: ScriptEvaluationConfiguration? = simpleScriptEvaluationConfiguration,
limit: Int = 0
): Sequence<ResultWithDiagnostics<EvaluationResult>> {
val replCompilerProxy =
KJvmReplCompilerImpl(defaultJvmScriptingHostConfiguration)
val compilationState = replCompilerProxy.createReplCompilationState(compilationConfiguration)
val compilationHistory = BasicReplStageHistory<ScriptDescriptor>()
val replEvaluator = BasicJvmScriptEvaluator()
var currentEvalConfig = evaluationConfiguration ?: ScriptEvaluationConfiguration()
val snipetsLimited = if (limit == 0) snippets else snippets.take(limit)
return snipetsLimited.mapIndexed { snippetNo, snippetText ->
val snippetSource =
snippetText.toScriptSource("Line_$snippetNo.${compilationConfiguration[ScriptCompilationConfiguration.fileExtension]}")
val snippetId = ReplSnippetIdImpl(snippetNo, 0, snippetSource)
replCompilerProxy.compileReplSnippet(compilationState, snippetSource, snippetId, compilationHistory)
.onSuccess {
runBlocking {
replEvaluator(it, currentEvalConfig)
}
}
.onSuccess {
val snippetClass = it.returnValue.scriptClass
currentEvalConfig = ScriptEvaluationConfiguration(currentEvalConfig) {
previousSnippets.append(it.returnValue.scriptInstance)
if (snippetClass != null) {
jvm {
baseClassLoader(snippetClass.java.classLoader)
}
companion object {
private fun evaluateInRepl(
snippets: Sequence<String>,
compilationConfiguration: ScriptCompilationConfiguration = simpleScriptCompilationConfiguration,
evaluationConfiguration: ScriptEvaluationConfiguration? = simpleScriptEvaluationConfiguration,
limit: Int = 0
): Sequence<ResultWithDiagnostics<EvaluatedSnippet>> {
val replCompiler = KJvmReplCompilerBase.create(defaultJvmScriptingHostConfiguration)
val replEvaluator = BasicJvmReplEvaluator()
val currentEvalConfig = evaluationConfiguration ?: ScriptEvaluationConfiguration()
val snipetsLimited = if (limit == 0) snippets else snippets.take(limit)
return snipetsLimited.mapIndexed { snippetNo, snippetText ->
val snippetSource =
snippetText.toScriptSource("Line_$snippetNo.${compilationConfiguration[ScriptCompilationConfiguration.fileExtension]}")
runBlocking { replCompiler.compile(snippetSource, compilationConfiguration) }
.onSuccess {
runBlocking { replEvaluator.eval(it, currentEvalConfig) }
}
}
it.asSuccess()
}
}
}
fun checkEvaluateInReplDiags(
snippets: Sequence<String>,
expected: Sequence<ResultWithDiagnostics<Any?>>,
compilationConfiguration: ScriptCompilationConfiguration = simpleScriptCompilationConfiguration,
evaluationConfiguration: ScriptEvaluationConfiguration? = simpleScriptEvaluationConfiguration,
limit: Int = 0
) {
val expectedIter = (if (limit == 0) expected else expected.take(limit)).iterator()
evaluateInRepl(snippets, compilationConfiguration, evaluationConfiguration, limit).forEachIndexed { index, res ->
val expectedRes = expectedIter.next()
when {
res is ResultWithDiagnostics.Failure && expectedRes is ResultWithDiagnostics.Failure -> {
Assert.assertTrue(
"#$index: Expected $expectedRes, got $res. Messages are different",
res.reports.map { it.message } == expectedRes.reports.map { it.message }
)
Assert.assertTrue(
"#$index: Expected $expectedRes, got $res. Locations are different",
res.reports.map { it.location }.zip(expectedRes.reports.map { it.location }).all {
it.second == null || it.second == it.first
.onSuccess {
it.get().asSuccess()
}
)
}
res is ResultWithDiagnostics.Success && expectedRes is ResultWithDiagnostics.Success -> {
val expectedVal = expectedRes.value
when (val resVal = res.value.returnValue) {
is ResultValue.Value -> Assert.assertEquals(
"#$index: Expected $expectedVal, got $resVal",
expectedVal,
resVal.value
)
is ResultValue.Unit -> Assert.assertTrue("#$index: Expected $expectedVal, got Unit", expectedVal == null)
is ResultValue.Error -> Assert.assertTrue(
"#$index: Expected $expectedVal, got Error: ${resVal.error}",
expectedVal is Throwable && expectedVal.message == resVal.error.message
)
else -> Assert.assertTrue("#$index: Expected $expectedVal, got unknown result $resVal", expectedVal == null)
}
}
else -> {
Assert.fail("#$index: Expected $expectedRes, got $res")
}
}
}
if (expectedIter.hasNext()) {
Assert.fail("Expected ${expectedIter.next()} got end of results stream")
fun checkEvaluateInReplDiags(
snippets: Sequence<String>,
expected: Sequence<ResultWithDiagnostics<Any?>>,
compilationConfiguration: ScriptCompilationConfiguration = simpleScriptCompilationConfiguration,
evaluationConfiguration: ScriptEvaluationConfiguration? = simpleScriptEvaluationConfiguration,
limit: Int = 0
) {
val expectedIter = (if (limit == 0) expected else expected.take(limit)).iterator()
evaluateInRepl(snippets, compilationConfiguration, evaluationConfiguration, limit).forEachIndexed { index, res ->
val expectedRes = expectedIter.next()
when {
res is ResultWithDiagnostics.Failure && expectedRes is ResultWithDiagnostics.Failure -> {
val resReports = res.reports.filter {
it.code != ScriptDiagnostic.incompleteCode
}
Assert.assertTrue(
"#$index: Expected $expectedRes, got $res. Messages are different",
resReports.map { it.message } == expectedRes.reports.map { it.message }
)
Assert.assertTrue(
"#$index: Expected $expectedRes, got $res. Locations are different",
resReports.map { it.location }.zip(expectedRes.reports.map { it.location }).all {
it.second == null || it.second == it.first
}
)
}
res is ResultWithDiagnostics.Success && expectedRes is ResultWithDiagnostics.Success -> {
val expectedVal = expectedRes.value
val resVal = res.value.result
when (resVal) {
is ResultValue.Value -> Assert.assertEquals(
"#$index: Expected $expectedVal, got $resVal",
expectedVal,
resVal.value
)
is ResultValue.Unit -> Assert.assertNull("#$index: Expected $expectedVal, got Unit", expectedVal)
is ResultValue.Error -> Assert.assertTrue(
"#$index: Expected $expectedVal, got Error: ${resVal.error}",
expectedVal is Throwable && expectedVal.message == resVal.error?.message
)
else -> Assert.assertTrue("#$index: Expected $expectedVal, got unknown result $resVal", expectedVal == null)
}
}
else -> {
Assert.fail("#$index: Expected $expectedRes, got $res")
}
}
}
if (expectedIter.hasNext()) {
Assert.fail("Expected ${expectedIter.next()} got end of results stream")
}
}
fun checkEvaluateInRepl(
snippets: Sequence<String>,
expected: Sequence<Any?>,
compilationConfiguration: ScriptCompilationConfiguration = simpleScriptCompilationConfiguration,
evaluationConfiguration: ScriptEvaluationConfiguration? = simpleScriptEvaluationConfiguration,
limit: Int = 0
) = checkEvaluateInReplDiags(
snippets, expected.map { ResultWithDiagnostics.Success(it) }, compilationConfiguration, evaluationConfiguration, limit
)
class TestReceiver(
@Suppress("unused")
val prop1: Int = 3
)
}
}
fun checkEvaluateInRepl(
snippets: Sequence<String>,
expected: Sequence<Any?>,
compilationConfiguration: ScriptCompilationConfiguration = simpleScriptCompilationConfiguration,
evaluationConfiguration: ScriptEvaluationConfiguration? = simpleScriptEvaluationConfiguration,
limit: Int = 0
) = checkEvaluateInReplDiags(
snippets, expected.map { ResultWithDiagnostics.Success(it) }, compilationConfiguration, evaluationConfiguration, limit
)
class TestReceiver(val prop1: Int = 3)
@@ -13,6 +13,7 @@ import kotlin.script.experimental.host.toScriptSource
import kotlin.script.experimental.jvm.*
import kotlin.script.experimental.jvm.util.classpathFromClass
import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost
import kotlin.script.experimental.jvmhost.test.ReplTest.Companion.checkEvaluateInRepl
class ResolveDependenciesTest : TestCase() {
@@ -21,7 +21,6 @@ import java.net.URLClassLoader
import java.nio.file.Files
import java.util.concurrent.TimeUnit
import java.util.jar.JarFile
import kotlin.reflect.KClass
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.BasicScriptingHost
import kotlin.script.experimental.host.FileBasedScriptSource
@@ -348,7 +347,7 @@ class ScriptingHostTest : TestCase() {
assertTrue(compiledScript is ResultWithDiagnostics.Success)
val jvmCompiledScript = compiledScript.valueOrNull()!! as KJvmCompiledScript
val jvmCompiledModule = jvmCompiledScript.compiledModule as KJvmCompiledModuleInMemoryImpl
val jvmCompiledModule = jvmCompiledScript.getCompiledModule() as KJvmCompiledModuleInMemoryImpl
val bytes = jvmCompiledModule.compilerOutputFiles["SavedScript.class"]!!
var classFileVersion: Int? = null
@@ -6,6 +6,7 @@
package kotlin.script.experimental.jvmhost.jsr223
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.cli.common.repl.ReplCompilerWithoutCheck
import java.util.concurrent.locks.ReentrantReadWriteLock
import javax.script.ScriptContext
import javax.script.ScriptEngineFactory
@@ -61,8 +62,8 @@ class KotlinJsr223ScriptEngineImpl(
}
}
override val replCompiler: ReplCompiler by lazy {
JvmReplCompiler(compilationConfiguration)
override val replCompiler: ReplCompilerWithoutCheck by lazy {
JvmReplCompiler(compilationConfiguration, true)
}
private val localEvaluator by lazy {
@@ -27,8 +27,8 @@ open class BasicJvmScriptClassFilesGenerator(val outputDir: File) : ScriptEvalua
try {
if (compiledScript !is KJvmCompiledScript)
return failure("Cannot generate classes: unsupported compiled script type $compiledScript")
val module = (compiledScript.compiledModule as? KJvmCompiledModuleInMemory)
?: return failure("Cannot generate classes: unsupported module type ${compiledScript.compiledModule}")
val module = (compiledScript.getCompiledModule() as? KJvmCompiledModuleInMemory)
?: return failure("Cannot generate classes: unsupported module type ${compiledScript.getCompiledModule()}")
for ((path, bytes) in module.compilerOutputFiles) {
File(outputDir, path).apply {
if (!parentFile.isDirectory) {
@@ -47,8 +47,8 @@ open class BasicJvmScriptClassFilesGenerator(val outputDir: File) : ScriptEvalua
}
fun KJvmCompiledScript.saveToJar(outputJar: File) {
val module = (compiledModule as? KJvmCompiledModuleInMemory)
?: throw IllegalArgumentException("Unsupported module type $compiledModule")
val module = (getCompiledModule() as? KJvmCompiledModuleInMemory)
?: throw IllegalArgumentException("Unsupported module type ${getCompiledModule()}")
val dependenciesFromScript = compilationConfiguration[ScriptCompilationConfiguration.dependencies]
?.filterIsInstance<JvmDependency>()
?.flatMap { it.classpath }
@@ -5,66 +5,96 @@
package kotlin.script.experimental.jvmhost.repl
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.backend.common.push
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.scripting.compiler.plugin.impl.KJvmReplCompilerImpl
import org.jetbrains.kotlin.cli.common.repl.ReplCompilerWithoutCheck
import org.jetbrains.kotlin.scripting.compiler.plugin.impl.KJvmReplCompilerBase
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.JvmReplCompilerStageHistory
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.JvmReplCompilerState
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.KJvmReplCompilerProxy
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzerBase
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.write
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.host.withDefaultsFrom
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
import kotlin.script.experimental.jvm.util.isIncomplete
/**
* REPL Compilation wrapper for "legacy" REPL APIs defined in the org.jetbrains.kotlin.cli.common.repl package
*/
class JvmReplCompiler(
val scriptCompilationConfiguration: ScriptCompilationConfiguration,
val allowReInit: Boolean = true,
val hostConfiguration: ScriptingHostConfiguration = defaultJvmScriptingHostConfiguration,
val replCompilerProxy: KJvmReplCompilerProxy = KJvmReplCompilerImpl(
var replCompiler: KJvmReplCompilerBase<ReplCodeAnalyzerBase> = KJvmReplCompilerBase.create(
hostConfiguration.withDefaultsFrom(defaultJvmScriptingHostConfiguration)
)
) : ReplCompiler {
) : ReplCompilerWithoutCheck {
override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> = JvmReplCompilerState(replCompilerProxy, lock)
private val compilers = mutableListOf(replCompiler)
override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult = state.lock.write {
override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> =
if (allowReInit) {
JvmReplCompilerState({ replCompiler.createReplCompilationState(it, replCompiler.initAnalyzer) }, lock)
} else {
replCompiler.state
}
override fun compile(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCompileResult = replCompiler.state.lock.write {
val replCompilerState = state.asState(JvmReplCompilerState::class.java)
val compilation = replCompilerState.getCompilationState(scriptCompilationConfiguration)
val res =
replCompilerProxy.checkSyntax(
codeLine.toSourceCode(scriptCompilationConfiguration),
compilation.baseScriptCompilationConfiguration,
compilation.environment.project
)
when {
// TODO: implement diagnostics rendering
res is ResultWithDiagnostics.Success && res.value -> ReplCheckResult.Ok()
res is ResultWithDiagnostics.Success && !res.value -> ReplCheckResult.Incomplete()
else -> ReplCheckResult.Error(res.reports.joinToString("\n") { it.message })
val snippet = codeLine.toSourceCode(scriptCompilationConfiguration)
if (allowReInit) {
replCompiler = compilers.find { historiesEq(it.history, replCompilerState.history) } ?: {
compilers.push(
KJvmReplCompilerBase.create(
hostConfiguration.withDefaultsFrom(defaultJvmScriptingHostConfiguration)
)
)
compilers.last()
}()
}
when (val res = runBlocking { replCompiler.compile(listOf(snippet), scriptCompilationConfiguration) }) {
is ResultWithDiagnostics.Success -> {
val lineId = LineId(codeLine.no, 0, snippet.hashCode())
replCompilerState.apply {
lock.write {
val compilerHistory = history as JvmReplCompilerStageHistory<*>
compilerHistory.push(lineId, replCompiler.history.last().item)
}
}
ReplCompileResult.CompiledClasses(
lineId,
replCompiler.history.map { it.id },
snippet.name!!,
emptyList(),
res.value.get().resultField != null,
emptyList(),
res.value.get().resultField?.second?.typeName,
res.value
)
}
else -> {
val message = res.reports.joinToString("\n") { it.message }
if (res.isIncomplete()) {
ReplCompileResult.Incomplete(message)
} else {
ReplCompileResult.Error(message)
}
}
}
}
override fun compile(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCompileResult = state.lock.write {
val replCompilerState = state.asState(JvmReplCompilerState::class.java)
val compilation = replCompilerState.getCompilationState(scriptCompilationConfiguration)
val snippet = codeLine.toSourceCode(scriptCompilationConfiguration)
val snippetId = ReplSnippetIdImpl(codeLine.no, codeLine.generation, snippet)
when (val res = replCompilerProxy.compileReplSnippet(compilation, snippet, snippetId, replCompilerState.history)) {
is ResultWithDiagnostics.Success ->
ReplCompileResult.CompiledClasses(
LineId(codeLine),
replCompilerState.history.map { it.id },
snippet.name!!,
emptyList(),
res.value.resultField != null,
emptyList(),
res.value.resultField?.second?.typeName,
res.value
)
else -> ReplCompileResult.Error(res.reports.joinToString("\n") { it.message })
}
companion object {
fun historiesEq(history1: IReplStageHistory<*>, history2: IReplStageHistory<*>) =
history1.count() == history2.count() &&
history1.zip(history2).all {
val (it1, it2) = it
it1.item === it2.item
}
}
}
@@ -7,6 +7,7 @@ package kotlin.script.experimental.jvmhost.repl
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.cli.common.repl.ReplEvaluator
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.write
import kotlin.reflect.KClass
@@ -15,6 +16,7 @@ import kotlin.script.experimental.jvm.BasicJvmScriptEvaluator
import kotlin.script.experimental.jvm.baseClassLoader
import kotlin.script.experimental.jvm.impl.KJvmCompiledScript
import kotlin.script.experimental.jvm.jvm
import kotlin.script.experimental.util.LinkedSnippetImpl
/**
* REPL Evaluation wrapper for "legacy" REPL APIs defined in the org.jetbrains.kotlin.cli.common.repl package
@@ -35,8 +37,12 @@ class JvmReplEvaluator(
): ReplEvalResult = state.lock.write {
val evalState = state.asState(JvmReplEvaluatorState::class.java)
val history = evalState.history as ReplStageHistoryWithReplace
val compiledScript = (compileResult.data as? KJvmCompiledScript)
?: return ReplEvalResult.Error.CompileTime("Unable to access compiled script: ${compileResult.data}")
val compiledScriptList = (compileResult.data as? LinkedSnippetImpl<*>)
?: return ReplEvalResult.Error.CompileTime("Unable to access compiled list script: ${compileResult.data}")
val compiledScript = (compiledScriptList.get() as? KJvmCompiledScript)
?: return ReplEvalResult.Error.CompileTime("Unable to access compiled script: ${compiledScriptList.get()}")
val lastSnippetClass = history.peek()?.item?.first
val historyBeforeSnippet = history.previousItems(compileResult.lineId).map { it.second }.toList()
@@ -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
}
}