Add remote repl support to the daemon, refactor repl classes accordingly, simple tests

This commit is contained in:
Ilya Chernikov
2016-09-16 17:38:00 +02:00
parent 042fc4fadc
commit 86ece30330
12 changed files with 648 additions and 111 deletions
@@ -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
@@ -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()})"