Add new REPL API JVM implementation
This commit is contained in:
committed by
Ilya Chernikov
parent
4c2c44b106
commit
d2fec96f38
-1
@@ -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
|
||||
|
||||
+5
-5
@@ -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 {
|
||||
|
||||
+91
-104
@@ -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)
|
||||
|
||||
+1
@@ -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() {
|
||||
|
||||
|
||||
+1
-2
@@ -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
|
||||
|
||||
+3
-2
@@ -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 {
|
||||
|
||||
+4
-4
@@ -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 }
|
||||
|
||||
+67
-37
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-2
@@ -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
|
||||
+8
-4
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user