Add remote repl support to the daemon, refactor repl classes accordingly, simple tests
This commit is contained in:
+8
-12
@@ -23,25 +23,28 @@ import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||
import kotlin.concurrent.read
|
||||
import kotlin.concurrent.write
|
||||
|
||||
open class GenericReplCompiledEvaluator(baseClasspath: Iterable<File>) : ReplCompiledEvaluator {
|
||||
|
||||
private val classpath = baseClasspath.toMutableList()
|
||||
open class GenericReplCompiledEvaluator(baseClasspath: Iterable<File>, baseClassloader: ClassLoader?) : ReplCompiledEvaluator {
|
||||
|
||||
private var classLoader: org.jetbrains.kotlin.cli.common.repl.ReplClassLoader =
|
||||
org.jetbrains.kotlin.cli.common.repl.ReplClassLoader(URLClassLoader(classpath.map { it.toURI().toURL() }.toTypedArray(), Thread.currentThread().contextClassLoader))
|
||||
org.jetbrains.kotlin.cli.common.repl.ReplClassLoader(URLClassLoader(baseClasspath.map { it.toURI().toURL() }.toTypedArray(), baseClassloader))
|
||||
private val classLoaderLock = ReentrantReadWriteLock()
|
||||
|
||||
private class ClassWithInstance(val klass: Class<*>, val instance: Any)
|
||||
|
||||
private val compiledLoadedClassesHistory = arrayListOf<Pair<ReplCodeLine, ClassWithInstance>>()
|
||||
|
||||
override fun eval(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>, compiledClasses: List<CompiledClassData>, hasResult: Boolean): ReplEvalResult {
|
||||
override fun eval(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>, compiledClasses: List<CompiledClassData>, hasResult: Boolean, newClasspath: List<File>): ReplEvalResult {
|
||||
|
||||
checkAndUpdateReplHistoryCollection(compiledLoadedClassesHistory, history)?.let {
|
||||
return@eval ReplEvalResult.HistoryMismatch(it)
|
||||
}
|
||||
|
||||
classLoaderLock.read {
|
||||
if (newClasspath.isNotEmpty()) {
|
||||
classLoaderLock.write {
|
||||
classLoader = org.jetbrains.kotlin.cli.common.repl.ReplClassLoader(URLClassLoader(newClasspath.map { it.toURI().toURL() }.toTypedArray(), classLoader))
|
||||
}
|
||||
}
|
||||
compiledClasses.filter { it.path.endsWith(".class") }
|
||||
.forEach {
|
||||
classLoader.addClass(JvmClassName.byInternalName(it.path.replaceFirst("\\.class$".toRegex(), "")), it.bytes)
|
||||
@@ -71,13 +74,6 @@ open class GenericReplCompiledEvaluator(baseClasspath: Iterable<File>) : ReplCom
|
||||
return if (hasResult) ReplEvalResult.ValueResult(rv) else ReplEvalResult.UnitResult
|
||||
}
|
||||
|
||||
fun dependenciesAdded(newClasspath: List<File>) {
|
||||
classLoaderLock.write {
|
||||
classpath.addAll(newClasspath)
|
||||
classLoader = org.jetbrains.kotlin.cli.common.repl.ReplClassLoader(URLClassLoader(newClasspath.map { it.toURI().toURL() }.toTypedArray(), classLoader))
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val SCRIPT_RESULT_FIELD_NAME = "\$\$result"
|
||||
}
|
||||
|
||||
@@ -16,38 +16,62 @@
|
||||
|
||||
package org.jetbrains.kotlin.cli.common.repl
|
||||
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
|
||||
import java.io.File
|
||||
import java.io.Serializable
|
||||
import java.util.*
|
||||
|
||||
data class ReplCodeLine(val no: Int, val code: String)
|
||||
data class ReplCodeLine(val no: Int, val code: String) : Serializable {
|
||||
companion object {
|
||||
private val serialVersionUID: Long = 8228357578L // just a random number, should be changed when serialized data is changed
|
||||
}
|
||||
}
|
||||
|
||||
data class CompiledClassData(val path: String, val bytes: ByteArray) : Serializable {
|
||||
override fun equals(other: Any?): Boolean = (other as? CompiledClassData)?.let { path == it.path && Arrays.equals(bytes, it.bytes) } ?: false
|
||||
override fun hashCode(): Int = path.hashCode() + Arrays.hashCode(bytes)
|
||||
companion object {
|
||||
private val serialVersionUID: Long = 8228357578L // just a random number, should be changed when serialized data is changed
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ReplCheckResult : Serializable {
|
||||
object Ok : ReplCheckResult()
|
||||
object Incomplete : ReplCheckResult()
|
||||
class Error(val message: String) : ReplCheckResult()
|
||||
class Error(val message: String, val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION) : ReplCheckResult() {
|
||||
override fun toString(): String = "Error(message = \"$message\""
|
||||
}
|
||||
companion object {
|
||||
private val serialVersionUID: Long = 8228357578L // just a random number, should be changed when serialized data is changed
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ReplCompileResult : Serializable {
|
||||
class CompiledClasses(val classes: List<CompiledClassData>, val hasResult: Boolean) : ReplCompileResult()
|
||||
class CompiledClasses(val classes: List<CompiledClassData>, val hasResult: Boolean, val newClasspath: List<File>) : ReplCompileResult()
|
||||
object Incomplete : ReplCompileResult()
|
||||
class HistoryMismatch(val lineNo: Int): ReplCompileResult()
|
||||
class Error(val message: String) : ReplCompileResult()
|
||||
class Error(val message: String, val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION) : ReplCompileResult() {
|
||||
override fun toString(): String = "Error(message = \"$message\""
|
||||
}
|
||||
companion object {
|
||||
private val serialVersionUID: Long = 8228357578L // just a random number, should be changed when serialized data is changed
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ReplEvalResult : Serializable {
|
||||
class ValueResult(val value: Any?) : ReplEvalResult()
|
||||
class ValueResult(val value: Any?) : ReplEvalResult() {
|
||||
override fun toString(): String = "Result: $value"
|
||||
}
|
||||
object UnitResult : ReplEvalResult()
|
||||
object Incomplete : ReplEvalResult()
|
||||
class HistoryMismatch(val lineNo: Int): ReplEvalResult()
|
||||
sealed class Error(val message: String) : ReplEvalResult() {
|
||||
class Runtime(message: String) : Error(message)
|
||||
class CompileTime(message: String) : Error(message)
|
||||
class CompileTime(message: String, val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION) : Error(message)
|
||||
override fun toString(): String = "${this.javaClass.kotlin.simpleName}Error(message = \"$message\""
|
||||
}
|
||||
companion object {
|
||||
private val serialVersionUID: Long = 8228357578L // just a random number, should be changed when serialized data is changed
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,14 +81,11 @@ interface ReplChecker {
|
||||
|
||||
interface ReplCompiler : ReplChecker {
|
||||
fun compile(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplCompileResult
|
||||
|
||||
// a callback
|
||||
fun dependenciesAdded(classpath: List<File>)
|
||||
}
|
||||
|
||||
interface ReplCompiledEvaluator {
|
||||
|
||||
fun eval(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>, compiledClasses: List<CompiledClassData>, hasResult: Boolean): ReplEvalResult
|
||||
fun eval(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>, compiledClasses: List<CompiledClassData>, hasResult: Boolean, newClasspath: List<File>): ReplEvalResult
|
||||
|
||||
// override to capture output
|
||||
fun<T> evalWithIO(body: () -> T): T = body()
|
||||
@@ -75,8 +96,6 @@ interface ReplEvaluator : ReplChecker {
|
||||
|
||||
fun eval(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplEvalResult
|
||||
|
||||
fun dependenciesAdded(classpath: List<File>)
|
||||
|
||||
// override to capture output
|
||||
fun<T> evalWithIO(body: () -> T): T = body()
|
||||
}
|
||||
@@ -77,8 +77,6 @@ open class GenericReplChecker(
|
||||
|
||||
protected var lineState: LineState? = null
|
||||
|
||||
val classpath: MutableList<File> = compilerConfiguration.jvmClasspathRoots.toMutableList()
|
||||
|
||||
fun createDiagnosticHolder() = ReplTerminalDiagnosticMessageHolder()
|
||||
|
||||
override fun check(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplCheckResult {
|
||||
@@ -108,7 +106,7 @@ open class GenericReplChecker(
|
||||
}
|
||||
|
||||
|
||||
abstract class GenericReplCompiler(
|
||||
open class GenericReplCompiler(
|
||||
disposable: Disposable,
|
||||
scriptDefinition: KotlinScriptDefinition,
|
||||
compilerConfiguration: CompilerConfiguration,
|
||||
@@ -132,7 +130,7 @@ abstract class GenericReplCompiler(
|
||||
val res = check(codeLine, history)
|
||||
when (res) {
|
||||
ReplCheckResult.Incomplete -> return@compile ReplCompileResult.Incomplete
|
||||
is ReplCheckResult.Error -> return@compile ReplCompileResult.Error(res.message)
|
||||
is ReplCheckResult.Error -> return@compile ReplCompileResult.Error(res.message, res.location)
|
||||
ReplCheckResult.Ok -> {} // continue
|
||||
}
|
||||
}
|
||||
@@ -140,14 +138,6 @@ abstract class GenericReplCompiler(
|
||||
}
|
||||
|
||||
val newDependencies = scriptDefinition.getDependenciesFor(psiFile, environment.project, lastDependencies)
|
||||
newDependencies?.let {
|
||||
logger.debug("found dependencies: ${it.classpath}")
|
||||
val newCp = environment.updateClasspath(it.classpath.map(::JvmClasspathRoot))
|
||||
if (newCp != null && newCp.isNotEmpty()) {
|
||||
logger.debug("new dependencies: $newCp")
|
||||
dependenciesAdded(newCp)
|
||||
}
|
||||
}
|
||||
if (lastDependencies != newDependencies) {
|
||||
lastDependencies = newDependencies
|
||||
}
|
||||
@@ -179,7 +169,9 @@ abstract class GenericReplCompiler(
|
||||
|
||||
descriptorsHistory.add(codeLine to scriptDescriptor)
|
||||
|
||||
return ReplCompileResult.CompiledClasses(state.factory.asList().map { CompiledClassData(it.relativePath, it.asByteArray()) }, state.replSpecific.hasResult)
|
||||
return ReplCompileResult.CompiledClasses(state.factory.asList().map { CompiledClassData(it.relativePath, it.asByteArray()) },
|
||||
state.replSpecific.hasResult,
|
||||
newDependencies?.let { environment.updateClasspath(it.classpath.map(::JvmClasspathRoot)) } ?: emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,28 +185,26 @@ open class GenericRepl(
|
||||
disposable: Disposable,
|
||||
scriptDefinition: KotlinScriptDefinition,
|
||||
compilerConfiguration: CompilerConfiguration,
|
||||
messageCollector: MessageCollector
|
||||
messageCollector: MessageCollector,
|
||||
baseClassloader: ClassLoader?
|
||||
) : ReplEvaluator, GenericReplCompiler(disposable, scriptDefinition, compilerConfiguration, messageCollector) {
|
||||
|
||||
private val compiledEvaluator = GenericReplCompiledEvaluator(classpath)
|
||||
private val compiledEvaluator = GenericReplCompiledEvaluator(compilerConfiguration.jvmClasspathRoots, baseClassloader)
|
||||
|
||||
override fun eval(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplEvalResult {
|
||||
synchronized(this) {
|
||||
|
||||
val (compiledClasses, hasResult) = compile(codeLine, history).let {
|
||||
when (it) {
|
||||
ReplCompileResult.Incomplete -> return@eval ReplEvalResult.Incomplete
|
||||
is ReplCompileResult.HistoryMismatch -> return@eval ReplEvalResult.HistoryMismatch(it.lineNo)
|
||||
is ReplCompileResult.Error -> return@eval ReplEvalResult.Error.CompileTime(it.message)
|
||||
is ReplCompileResult.CompiledClasses -> it.classes to it.hasResult
|
||||
}
|
||||
}
|
||||
|
||||
return compiledEvaluator.eval(codeLine, history, compiledClasses, hasResult)
|
||||
}
|
||||
}
|
||||
|
||||
override fun dependenciesAdded(classpath: List<File>) {
|
||||
compiledEvaluator.dependenciesAdded(classpath)
|
||||
override fun eval(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplEvalResult = synchronized(this) {
|
||||
return compileAndEval(this, compiledEvaluator, codeLine, history)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun compileAndEval(replCompiler: ReplCompiler, replCompiledEvaluator: ReplCompiledEvaluator, codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplEvalResult =
|
||||
replCompiler.compile(codeLine, history).let {
|
||||
when (it) {
|
||||
ReplCompileResult.Incomplete -> ReplEvalResult.Incomplete
|
||||
is ReplCompileResult.HistoryMismatch -> ReplEvalResult.HistoryMismatch(it.lineNo)
|
||||
is ReplCompileResult.Error -> ReplEvalResult.Error.CompileTime(it.message, it.location)
|
||||
is ReplCompileResult.CompiledClasses -> replCompiledEvaluator.eval(codeLine, history, it.classes, it.hasResult, it.newClasspath)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -32,7 +32,8 @@ class KotlinJsr232ScriptEngine(
|
||||
disposable: Disposable,
|
||||
private val factory: ScriptEngineFactory,
|
||||
private val scriptDefinition: KotlinScriptDefinition,
|
||||
private val compilerConfiguration: CompilerConfiguration
|
||||
private val compilerConfiguration: CompilerConfiguration,
|
||||
baseClassLoader: ClassLoader
|
||||
) : AbstractScriptEngine(), ScriptEngine {
|
||||
|
||||
data class MessageCollectorReport(val severity: CompilerMessageSeverity, val message: String, val location: CompilerMessageLocation)
|
||||
@@ -72,7 +73,7 @@ class KotlinJsr232ScriptEngine(
|
||||
}
|
||||
}
|
||||
|
||||
private val repl = object : GenericRepl(disposable, scriptDefinition, compilerConfiguration, messageCollector) {}
|
||||
private val repl = object : GenericRepl(disposable, scriptDefinition, compilerConfiguration, messageCollector, baseClassLoader) {}
|
||||
|
||||
private var lineCount = 0
|
||||
|
||||
|
||||
+2
-1
@@ -49,7 +49,8 @@ class KotlinJsr232StandardScriptEngineFactory: ScriptEngineFactory {
|
||||
// TODO: addJvmClasspathRoots(config.classpath)
|
||||
put(CommonConfigurationKeys.MODULE_NAME, "kotlin-script")
|
||||
put(JVMConfigurationKeys.INCLUDE_RUNTIME, true)
|
||||
})
|
||||
},
|
||||
Thread.currentThread().contextClassLoader)
|
||||
|
||||
override fun getOutputStatement(toDisplay: String?): String = "print(\"$toDisplay\")"
|
||||
override fun getMethodCallSyntax(obj: String, m: String, vararg args: String): String = "$obj.$m(${args.joinToString()})"
|
||||
|
||||
Reference in New Issue
Block a user