Extract repl state as a separate object

This commit is contained in:
Ilya Chernikov
2017-02-03 18:14:03 +01:00
parent 3eb5b3e08c
commit f9dedab8c8
15 changed files with 666 additions and 416 deletions
@@ -0,0 +1,79 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.common.repl
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
open class AggregatedReplStateHistory<T1, T2>(val history1: IReplStageHistory<T1>, val history2: IReplStageHistory<T2>, override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock())
: IReplStageHistory<Pair<T1, T2>>, AbstractList<ReplHistoryRecord<Pair<T1, T2>>>()
{
override val size: Int
get() = minOf(history1.size, history2.size)
override fun push(id: ILineId, item: Pair<T1, T2>) {
lock.write {
assertSameSize()
history1.push(id, item.first)
history2.push(id, item.second)
}
}
override fun get(index: Int): ReplHistoryRecord<Pair<T1, T2>> = lock.read {
assertSameSize()
val r1 = history1[index]
val r2 = history2[index]
assertSameId(r1, r2)
ReplHistoryRecord(r1.id, r1.item to r2.item)
}
override fun pop(): ReplHistoryRecord<Pair<T1, T2>>? = lock.write {
assertSameSize()
val r1 = history1.pop()
val r2 = history2.pop()
if (r1 == null && r2 == null) return null
if (r1 == null || r2 == null) throw IllegalStateException("Aggregated history mismatch: $r1 vs $r2")
assertSameId(r1, r2)
ReplHistoryRecord(r1.id, r1.item to r2.item)
}
override fun resetTo(id: ILineId): Iterable<ILineId> = lock.write {
assertSameSize()
val i1 = history1.resetTo(id).toList()
val i2 = history2.resetTo(id).toList()
if (i1 != i2) throw IllegalStateException("Aggregated history resetted lines mismatch: $i1 != $i2")
i1
}
private fun assertSameSize() {
if (history1.size != history2.size) throw IllegalStateException("Aggregated history sizes mismatch: ${history1.size} != ${history2.size}")
}
private fun assertSameId(r1: ReplHistoryRecord<T1>, r2: ReplHistoryRecord<T2>) {
if (r1.id != r2.id) throw IllegalStateException("Aggregated history mismatch: ${r1.id} != ${r2.id}")
}
}
open class AggregatedReplStageState<T1, T2>(val state1: IReplStageState<T1>, val state2: IReplStageState<T2>, override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock())
: IReplStageState<Pair<T1, T2>>
{
override val history: IReplStageHistory<Pair<T1, T2>> = AggregatedReplStateHistory(state1.history, state2.history, lock)
override fun <StateT : IReplStageState<*>> asState(): StateT = (state1 as? StateT) ?: (state2 as StateT) ?: super.asState()
}
@@ -0,0 +1,69 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.common.repl
import java.io.Serializable
import java.util.*
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.write
data class LineId(override val no: Int, private val codeHash: Int) : ILineId, Serializable {
constructor(no: Int, code: String): this(no, code.hashCode())
constructor(codeLine: ReplCodeLine): this(codeLine.no, codeLine.code.hashCode())
override fun compareTo(other: ILineId): Int = (other as? LineId)?.let {
val c = no.compareTo(it.no)
if (c == 0) codeHash.compareTo(it.codeHash)
else c
} ?: -1 // TODO: check if it doesn't break something
companion object {
private val serialVersionUID: Long = 8328353000L
}
}
open class BasicReplStageHistory<T>(override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()) : IReplStageHistory<T>, ArrayList<ReplHistoryRecord<T>>() {
override fun push(id: ILineId, item: T) {
lock.write {
add(ReplHistoryRecord(id, item))
}
}
override fun pop(): ReplHistoryRecord<T>? = lock.write { if (isEmpty()) null else removeAt(lastIndex) }
override fun resetTo(id: ILineId): Iterable<ILineId> {
lock.write {
val idx = indexOfFirst { it.id == id }
if (idx < 0) throw java.util.NoSuchElementException("Cannot rest to inexistent line ${id.no}")
if (idx < lastIndex) {
val removed = asSequence().drop(idx + 1).map { it.id }.toList()
removeRange(idx + 1, lastIndex)
return removed
}
else return emptyList()
}
}
}
class BasicReplStageState<HistoryItemT>: IReplStageState<HistoryItemT> {
override val lock = ReentrantReadWriteLock()
override val history: BasicReplStageHistory<HistoryItemT> = BasicReplStageHistory(lock)
}
@@ -0,0 +1,38 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.common.repl
import java.io.File
import java.net.URLClassLoader
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
open class GenericReplEvaluatorState(baseClasspath: Iterable<File>, baseClassloader: ClassLoader?, override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock())
: IReplStageState<EvalClassWithInstanceAndLoader>
{
override val history: IReplStageHistory<EvalClassWithInstanceAndLoader> = BasicReplStageHistory(lock)
val topClassLoader: ReplClassLoader = makeReplClassLoader(baseClassloader, baseClasspath)
val currentClasspath: List<File> get() = lock.read {
history.peek()?.item?.classLoader?.listAllUrlsAsFiles()
?: topClassLoader.listAllUrlsAsFiles()
}
}
internal fun makeReplClassLoader(baseClassloader: ClassLoader?, baseClasspath: Iterable<File>) =
ReplClassLoader(URLClassLoader(baseClasspath.map { it.toURI().toURL() }.toTypedArray(), baseClassloader))
@@ -22,27 +22,36 @@ import kotlin.concurrent.write
class GenericReplCompilingEvaluator(val compiler: ReplCompiler, class GenericReplCompilingEvaluator(val compiler: ReplCompiler,
baseClasspath: Iterable<File>, baseClasspath: Iterable<File>,
baseClassloader: ClassLoader?, baseClassloader: ClassLoader? = Thread.currentThread().contextClassLoader,
protected val fallbackScriptArgs: ScriptArgsWithTypes? = null, private val fallbackScriptArgs: ScriptArgsWithTypes? = null,
repeatingMode: ReplRepeatingMode = ReplRepeatingMode.REPEAT_ONLY_MOST_RECENT, repeatingMode: ReplRepeatingMode = ReplRepeatingMode.REPEAT_ONLY_MOST_RECENT
protected val stateLock: ReentrantReadWriteLock = ReentrantReadWriteLock()
) : ReplFullEvaluator { ) : ReplFullEvaluator {
val evaluator = GenericReplEvaluator(baseClasspath, baseClassloader, fallbackScriptArgs, repeatingMode, stateLock) val evaluator = GenericReplEvaluator(baseClasspath, baseClassloader, fallbackScriptArgs, repeatingMode)
override fun compileAndEval(codeLine: ReplCodeLine, scriptArgs: ScriptArgsWithTypes?, verifyHistory: List<ReplCodeLine>?, invokeWrapper: InvokeWrapper?): ReplEvalResult { override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> = AggregatedReplStageState(compiler.createState(lock), evaluator.createState(lock), lock)
return stateLock.write {
val compiled = compiler.compile(codeLine, verifyHistory) override fun compileAndEval(state: IReplStageState<*>, codeLine: ReplCodeLine, scriptArgs: ScriptArgsWithTypes?, invokeWrapper: InvokeWrapper?): ReplEvalResult {
return state.lock.write {
val aggregatedState = state.asState<AggregatedReplStageState<*, *>>()
val compiled = compiler.compile(state, codeLine)
when (compiled) { when (compiled) {
is ReplCompileResult.Error -> ReplEvalResult.Error.CompileTime(compiled.compiledHistory, compiled.message, compiled.location) is ReplCompileResult.Error -> ReplEvalResult.Error.CompileTime(compiled.message, compiled.location)
is ReplCompileResult.HistoryMismatch -> ReplEvalResult.HistoryMismatch(compiled.compiledHistory, compiled.lineNo) is ReplCompileResult.HistoryMismatch -> ReplEvalResult.HistoryMismatch(compiled.lineNo)
is ReplCompileResult.Incomplete -> ReplEvalResult.Incomplete(compiled.compiledHistory) is ReplCompileResult.Incomplete -> ReplEvalResult.Incomplete()
is ReplCompileResult.CompiledClasses -> { is ReplCompileResult.CompiledClasses -> {
val result = eval(compiled, scriptArgs, invokeWrapper) val result = eval(state, compiled, scriptArgs, invokeWrapper)
when (result) { when (result) {
is ReplEvalResult.Error, is ReplEvalResult.Error,
is ReplEvalResult.HistoryMismatch, is ReplEvalResult.HistoryMismatch,
is ReplEvalResult.Incomplete -> { is ReplEvalResult.Incomplete -> {
result.completedEvalHistory.lastOrNull()?.let { compiler.resetToLine(it) } aggregatedState.apply {
lock.write {
if (state1.history.size > state2.history.size) {
state2.history.peek()?.let { state1.history.resetTo(it.id) }
assert(state1.history.size == state2.history.size)
}
}
}
result result
} }
is ReplEvalResult.ValueResult, is ReplEvalResult.ValueResult,
@@ -56,49 +65,25 @@ class GenericReplCompilingEvaluator(val compiler: ReplCompiler,
} }
} }
override fun resetToLine(lineNumber: Int): List<ReplCodeLine> { override fun eval(state: IReplStageState<*>, compileResult: ReplCompileResult.CompiledClasses, scriptArgs: ScriptArgsWithTypes?, invokeWrapper: InvokeWrapper?): ReplEvalResult =
stateLock.write { evaluator.eval(state, compileResult, scriptArgs, invokeWrapper)
val removedCompiledLines = compiler.resetToLine(lineNumber)
val removedEvaluatorLines = evaluator.resetToLine(lineNumber)
removedCompiledLines.zip(removedEvaluatorLines).forEach { override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult = compiler.check(state, codeLine)
if (it.first != it.second) {
throw IllegalStateException("History mismatch when resetting lines")
}
}
return removedCompiledLines override fun compileToEvaluable(state: IReplStageState<*>, codeLine: ReplCodeLine, defaultScriptArgs: ScriptArgsWithTypes?): Pair<ReplCompileResult, Evaluable?> {
} val compiled = compiler.compile(state, codeLine)
}
override fun resetToLine(line: ReplCodeLine): List<ReplCodeLine> = resetToLine(line.no)
override val lastEvaluatedScripts: List<EvalHistoryType> get() = evaluator.lastEvaluatedScripts
override val history: List<ReplCodeLine> get() = evaluator.history
override val currentClasspath: List<File> get() = evaluator.currentClasspath
override val compiledHistory: List<ReplCodeLine> get() = compiler.history
override val evaluatedHistory: List<ReplCodeLine> get() = evaluator.history
override fun eval(compileResult: ReplCompileResult.CompiledClasses, scriptArgs: ScriptArgsWithTypes?, invokeWrapper: InvokeWrapper?): ReplEvalResult =
evaluator.eval(compileResult, scriptArgs, invokeWrapper)
override fun check(codeLine: ReplCodeLine): ReplCheckResult = compiler.check(codeLine)
override fun compileToEvaluable(codeLine: ReplCodeLine, defaultScriptArgs: ScriptArgsWithTypes?, verifyHistory: List<ReplCodeLine>?): Pair<ReplCompileResult, Evaluable?> {
val compiled = compiler.compile(codeLine, verifyHistory)
return when (compiled) { return when (compiled) {
is ReplCompileResult.CompiledClasses -> Pair(compiled, DelayedEvaluation(compiled, stateLock, evaluator, defaultScriptArgs ?: fallbackScriptArgs)) // TODO: seems usafe when delayed evaluation may happen after some more compileAndEval calls on the same state; check and fix or protect
is ReplCompileResult.CompiledClasses -> Pair(compiled, DelayedEvaluation(state, compiled, evaluator, defaultScriptArgs ?: fallbackScriptArgs))
else -> Pair(compiled, null) else -> Pair(compiled, null)
} }
} }
class DelayedEvaluation(override val compiledCode: ReplCompileResult.CompiledClasses, class DelayedEvaluation(private val state: IReplStageState<*>,
private val stateLock: ReentrantReadWriteLock, override val compiledCode: ReplCompileResult.CompiledClasses,
private val evaluator: ReplEvaluator, private val evaluator: ReplEvaluator,
private val defaultScriptArgs: ScriptArgsWithTypes?) : Evaluable { private val defaultScriptArgs: ScriptArgsWithTypes?) : Evaluable {
override fun eval(scriptArgs: ScriptArgsWithTypes?, invokeWrapper: InvokeWrapper?): ReplEvalResult { override fun eval(scriptArgs: ScriptArgsWithTypes?, invokeWrapper: InvokeWrapper?): ReplEvalResult =
return stateLock.write { evaluator.eval(compiledCode, scriptArgs ?: defaultScriptArgs, invokeWrapper) } evaluator.eval(state, compiledCode, scriptArgs ?: defaultScriptArgs, invokeWrapper)
}
} }
} }
@@ -18,144 +18,111 @@ package org.jetbrains.kotlin.cli.common.repl
import org.jetbrains.kotlin.resolve.jvm.JvmClassName import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import java.io.File import java.io.File
import java.net.URLClassLoader
import java.util.concurrent.locks.ReentrantReadWriteLock import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write import kotlin.concurrent.write
open class GenericReplEvaluator(baseClasspath: Iterable<File>, open class GenericReplEvaluator(val baseClasspath: Iterable<File>,
baseClassloader: ClassLoader?, val baseClassloader: ClassLoader? = Thread.currentThread().contextClassLoader,
protected val fallbackScriptArgs: ScriptArgsWithTypes? = null, protected val fallbackScriptArgs: ScriptArgsWithTypes? = null,
protected val repeatingMode: ReplRepeatingMode = ReplRepeatingMode.REPEAT_ONLY_MOST_RECENT, protected val repeatingMode: ReplRepeatingMode = ReplRepeatingMode.REPEAT_ONLY_MOST_RECENT
protected val stateLock: ReentrantReadWriteLock = ReentrantReadWriteLock()
) : ReplEvaluator { ) : ReplEvaluator {
private val topClassLoader: ReplClassLoader = makeReplClassLoader(baseClassloader, baseClasspath) // private val evaluatedHistory = ReplHistory<EvalClassWithInstanceAndLoader>()
private val evaluatedHistory = ReplHistory<EvalClassWithInstanceAndLoader>() // override val history: List<ReplCodeLine> get() = stateLock.read { evaluatedHistory.copySources() }
override fun resetToLine(lineNumber: Int): List<ReplCodeLine> { // private class HistoryActions(
return stateLock.write { // val effectiveHistory: List<EvalClassWithInstanceAndLoader>,
evaluatedHistory.resetToLine(lineNumber) // val verify: (compareHistory: SourceList?) -> Int?,
}.map { it.first } // val addPlaceholder: (line: CompiledReplCodeLine, value: EvalClassWithInstanceAndLoader) -> Unit,
} // val removePlaceholder: (line: CompiledReplCodeLine) -> Boolean,
// val addFinal: (line: CompiledReplCodeLine, value: EvalClassWithInstanceAndLoader) -> Unit,
// val processClasses: (compileResult: ReplCompileResult.CompiledClasses) -> Pair<ClassLoader, Class<out Any>>
// )
override val history: List<ReplCodeLine> get() = stateLock.read { evaluatedHistory.copySources() } override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> = GenericReplEvaluatorState(baseClasspath, baseClassloader, lock)
override val currentClasspath: List<File> get() = stateLock.read { override fun eval(state: IReplStageState<*>,
evaluatedHistory.copyValues().lastOrNull()?.classLoader?.listAllUrlsAsFiles() compileResult: ReplCompileResult.CompiledClasses,
?: topClassLoader.listAllUrlsAsFiles()
}
private class HistoryActions(val effectiveHistory: List<EvalClassWithInstanceAndLoader>,
val verify: (compareHistory: SourceList?) -> Int?,
val addPlaceholder: (line: CompiledReplCodeLine, value: EvalClassWithInstanceAndLoader) -> Unit,
val removePlaceholder: (line: CompiledReplCodeLine) -> Boolean,
val addFinal: (line: CompiledReplCodeLine, value: EvalClassWithInstanceAndLoader) -> Unit,
val processClasses: (compileResult: ReplCompileResult.CompiledClasses) -> Pair<ClassLoader, Class<out Any>>)
private fun prependClassLoaderWithNewClasses(effectiveHistory: List<EvalClassWithInstanceAndLoader>, compileResult: ReplCompileResult.CompiledClasses): Pair<ClassLoader, Class<out Any>> {
return stateLock.write {
var mainLineClassName: String? = null
val classLoader = makeReplClassLoader(effectiveHistory.lastOrNull()?.classLoader ?: topClassLoader, compileResult.classpathAddendum)
fun classNameFromPath(path: String) = JvmClassName.byInternalName(path.removeSuffix(".class"))
fun compiledClassesNames() = compileResult.classes.map { classNameFromPath(it.path).internalName.replace('/', '.') }
val expectedClassName = compileResult.generatedClassname
compileResult.classes.filter { it.path.endsWith(".class") }
.forEach {
val className = classNameFromPath(it.path)
if (className.internalName == expectedClassName || className.internalName.endsWith("/$expectedClassName")) {
mainLineClassName = className.internalName.replace('/', '.')
}
classLoader.addClass(className, it.bytes)
}
val scriptClass = try {
classLoader.loadClass(mainLineClassName!!)
}
catch (t: Throwable) {
throw Exception("Error loading class $mainLineClassName: known classes: ${compiledClassesNames()}", t)
}
Pair(classLoader, scriptClass)
}
}
override fun eval(compileResult: ReplCompileResult.CompiledClasses,
scriptArgs: ScriptArgsWithTypes?, scriptArgs: ScriptArgsWithTypes?,
invokeWrapper: InvokeWrapper?): ReplEvalResult { invokeWrapper: InvokeWrapper?): ReplEvalResult {
stateLock.write { state.lock.write {
val verifyHistory = compileResult.compiledHistory.dropLast(1) val evalState = state.asState<GenericReplEvaluatorState>()
val defaultHistoryActor = HistoryActions( // val verifyHistory = compileResult.state.dropLast(1)
effectiveHistory = evaluatedHistory.copyValues(), // val defaultHistoryActor = HistoryActions(
verify = { line -> evaluatedHistory.firstMismatchingHistory(line) }, // effectiveHistory = evalState.history.map { it.item },
addPlaceholder = { line, value -> evaluatedHistory.add(line, value) }, // verify = { line -> evalState.history.firstMismatchingHistory(line) },
removePlaceholder = { line -> evaluatedHistory.removeLast(line) }, // addPlaceholder = { line, value -> evalState.history.add(line, value) },
addFinal = { line, value -> evaluatedHistory.add(line, value) }, // removePlaceholder = { line -> evalState.history.removeLast(line) },
processClasses = { compiled -> // addFinal = { line, value -> evalState.history.add(line, value) },
prependClassLoaderWithNewClasses(evaluatedHistory.copyValues(), compiled) // processClasses = { compiled ->
}) // prependClassLoaderWithNewClasses(evalState.history.copyValues(), compiled, )
// })
val historyActor: HistoryActions = when (repeatingMode) { val historyActor = when (repeatingMode) {
ReplRepeatingMode.NONE -> defaultHistoryActor ReplRepeatingMode.NONE -> HistoryActionsForNoRepeat(evalState)
ReplRepeatingMode.REPEAT_ONLY_MOST_RECENT -> { ReplRepeatingMode.REPEAT_ONLY_MOST_RECENT -> {
val lastItem = evaluatedHistory.lastItem() val lastItem = evalState.history.peek()
if (lastItem == null || lastItem.first.source != compileResult.compiledCodeLine.source) { if (lastItem == null || lastItem.id != compileResult.lineId) {
defaultHistoryActor HistoryActionsForNoRepeat(evalState)
} }
else { else {
val trimmedHistory = ReplHistory(evaluatedHistory.copyAll().dropLast(1)) HistoryActionsForRepeatRecentOnly(evalState)
HistoryActions( // val trimmedHistory = ReplHistory(evalState.history.copyAll().dropLast(1))
effectiveHistory = trimmedHistory.copyValues(), // HistoryActions
verify = { trimmedHistory.firstMismatchingHistory(it) }, // effectiveHistory = trimmedHistory.copyValues(),
addPlaceholder = { _, _ -> NO_ACTION() }, // verify = { trimmedHistory.firstMismatchingHistory(it) },
removePlaceholder = { NO_ACTION_THAT_RETURNS(true) }, // addPlaceholder = { _, _ -> NO_ACTION() },
addFinal = { line, value -> // removePlaceholder = { NO_ACTION_THAT_RETURNS(true) },
evaluatedHistory.removeLast(line) // addFinal = { line, value ->
evaluatedHistory.add(line, value) // evalState.history.removeLast(line)
}, // evalState.history.add(line, value)
processClasses = { _ -> // },
Pair(lastItem.second.classLoader, lastItem.second.klass.java) // processClasses = { _ ->
}) // Pair(lastItem.second.classLoader, lastItem.second.klass.java)
// })
} }
} }
ReplRepeatingMode.REPEAT_ANY_PREVIOUS -> { ReplRepeatingMode.REPEAT_ANY_PREVIOUS -> {
if (evaluatedHistory.isEmpty() || !evaluatedHistory.contains(compileResult.compiledCodeLine.source)) { val matchingItem = evalState.history.firstOrNull { it.id == compileResult.lineId }
defaultHistoryActor if (matchingItem == null) {
HistoryActionsForNoRepeat(evalState)
} }
else { else {
val historyCopy = evaluatedHistory.copyAll() HistoryActionsForRepeatAny(evalState, matchingItem)
val matchingItem = historyCopy.first { it.first.source == compileResult.compiledCodeLine.source } // val historyCopy = evalState.history.copyAll()
val trimmedHistory = ReplHistory(evaluatedHistory.copyAll().takeWhile { it != matchingItem }) // val matchingItem = historyCopy.first { it.first.source == compileResult.compiledCodeLine.source }
HistoryActions( // val trimmedHistory = ReplHistory(evalState.history.copyAll().takeWhile { it != matchingItem })
effectiveHistory = trimmedHistory.copyValues(), // HistoryActions(
verify = { trimmedHistory.firstMismatchingHistory(it) }, // effectiveHistory = trimmedHistory.copyValues(),
addPlaceholder = { _, _ -> NO_ACTION() }, // verify = { trimmedHistory.firstMismatchingHistory(it) },
removePlaceholder = { NO_ACTION_THAT_RETURNS(true) }, // addPlaceholder = { _, _ -> NO_ACTION() },
addFinal = { line, value -> // removePlaceholder = { NO_ACTION_THAT_RETURNS(true) },
val extraLines = evaluatedHistory.resetToLine(line) // addFinal = { line, value ->
evaluatedHistory.removeLast(line) // val extraLines = evalState.history.resetToLine(line)
evaluatedHistory.add(line, value) // evalState.history.removeLast(line)
extraLines.forEach { // evalState.history.add(line, value)
evaluatedHistory.add(it.first, it.second) // extraLines.forEach {
} // evalState.history.add(it.first, it.second)
}, // }
processClasses = { _ -> // },
Pair(matchingItem.second.classLoader, matchingItem.second.klass.java) // processClasses = { _ ->
}) // Pair(matchingItem.second.classLoader, matchingItem.second.klass.java)
// })
} }
} }
} }
val firstMismatch = historyActor.verify(verifyHistory) // val firstMismatch = historyActor.firstMismatch(verifyHistory)
if (firstMismatch != null) { // if (firstMismatch != null) {
return@eval ReplEvalResult.HistoryMismatch(evaluatedHistory.copySources(), firstMismatch) // return@eval ReplEvalResult.HistoryMismatch(firstMismatch)
} // }
val (classLoader, scriptClass) = try { val (classLoader, scriptClass) = try {
historyActor.processClasses(compileResult) historyActor.processClasses(compileResult)
} }
catch (e: Exception) { catch (e: Exception) {
return@eval ReplEvalResult.Error.Runtime(evaluatedHistory.copySources(), e.message ?: "unknown", e) return@eval ReplEvalResult.Error.Runtime(e.message ?: "unknown", e)
} }
val currentScriptArgs = scriptArgs ?: fallbackScriptArgs val currentScriptArgs = scriptArgs ?: fallbackScriptArgs
@@ -170,7 +137,7 @@ open class GenericReplEvaluator(baseClasspath: Iterable<File>,
// TODO: try/catch ? // TODO: try/catch ?
val scriptInstanceConstructor = scriptClass.getConstructor(*constructorParams) val scriptInstanceConstructor = scriptClass.getConstructor(*constructorParams)
historyActor.addPlaceholder(compileResult.compiledCodeLine, EvalClassWithInstanceAndLoader(scriptClass.kotlin, null, classLoader, invokeWrapper)) historyActor.addPlaceholder(compileResult.lineId, EvalClassWithInstanceAndLoader(scriptClass.kotlin, null, classLoader, invokeWrapper))
val scriptInstance = val scriptInstance =
try { try {
@@ -178,33 +145,113 @@ open class GenericReplEvaluator(baseClasspath: Iterable<File>,
else scriptInstanceConstructor.newInstance(*constructorArgs) else scriptInstanceConstructor.newInstance(*constructorArgs)
} }
catch (e: Throwable) { catch (e: Throwable) {
historyActor.removePlaceholder(compileResult.compiledCodeLine) historyActor.removePlaceholder(compileResult.lineId)
// ignore everything in the stack trace until this constructor call // ignore everything in the stack trace until this constructor call
return@eval ReplEvalResult.Error.Runtime(evaluatedHistory.copySources(), return@eval ReplEvalResult.Error.Runtime(renderReplStackTrace(e.cause!!, startFromMethodName = "${scriptClass.name}.<init>"), e as? Exception)
renderReplStackTrace(e.cause!!, startFromMethodName = "${scriptClass.name}.<init>"), e as? Exception)
} }
historyActor.removePlaceholder(compileResult.compiledCodeLine) historyActor.removePlaceholder(compileResult.lineId)
historyActor.addFinal(compileResult.compiledCodeLine, EvalClassWithInstanceAndLoader(scriptClass.kotlin, scriptInstance, classLoader, invokeWrapper)) historyActor.addFinal(compileResult.lineId, EvalClassWithInstanceAndLoader(scriptClass.kotlin, scriptInstance, classLoader, invokeWrapper))
val resultField = scriptClass.getDeclaredField(SCRIPT_RESULT_FIELD_NAME).apply { isAccessible = true } val resultField = scriptClass.getDeclaredField(SCRIPT_RESULT_FIELD_NAME).apply { isAccessible = true }
val resultValue: Any? = resultField.get(scriptInstance) val resultValue: Any? = resultField.get(scriptInstance)
return if (compileResult.hasResult) ReplEvalResult.ValueResult(evaluatedHistory.copySources(), resultValue) return if (compileResult.hasResult) ReplEvalResult.ValueResult(resultValue)
else ReplEvalResult.UnitResult(evaluatedHistory.copySources()) else ReplEvalResult.UnitResult()
} }
} }
override val lastEvaluatedScripts: List<EvalHistoryType> get() {
return stateLock.read { evaluatedHistory.copyAll() }
}
companion object { companion object {
private val SCRIPT_RESULT_FIELD_NAME = "\$\$result" private val SCRIPT_RESULT_FIELD_NAME = "\$\$result"
} }
private fun makeReplClassLoader(baseClassloader: ClassLoader?, baseClasspath: Iterable<File>) =
ReplClassLoader(URLClassLoader(baseClasspath.map { it.toURI().toURL() }.toTypedArray(), baseClassloader))
} }
private open class HistoryActionsForNoRepeat(val state: GenericReplEvaluatorState) {
open val effectiveHistory: List<EvalClassWithInstanceAndLoader> get() = state.history.map { it.item }
open fun<OtherT> firstMismatch(other: IReplStageHistory<OtherT>): Pair<ReplHistoryRecord<EvalClassWithInstanceAndLoader>?, ReplHistoryRecord<OtherT>?>? = state.history.firstMismatch(other)
open fun addPlaceholder(lineId: ILineId, value: EvalClassWithInstanceAndLoader) { state.history.push(lineId, value) }
open fun removePlaceholder(lineId: ILineId): Boolean = state.history.verifiedPop(lineId) != null
open fun addFinal(lineId: ILineId, value: EvalClassWithInstanceAndLoader) { state.history.push(lineId, value) }
open fun processClasses(compileResult: ReplCompileResult.CompiledClasses): Pair<ClassLoader, Class<out Any>> = prependClassLoaderWithNewClasses(effectiveHistory, compileResult)
private fun prependClassLoaderWithNewClasses(effectiveHistory: List<EvalClassWithInstanceAndLoader>,
compileResult: ReplCompileResult.CompiledClasses
): Pair<ClassLoader, Class<out Any>> {
var mainLineClassName: String? = null
val classLoader = makeReplClassLoader(effectiveHistory.lastOrNull()?.classLoader ?: state.topClassLoader, compileResult.classpathAddendum)
fun classNameFromPath(path: String) = JvmClassName.byInternalName(path.removeSuffix(".class"))
fun compiledClassesNames() = compileResult.classes.map { classNameFromPath(it.path).internalName.replace('/', '.') }
val expectedClassName = compileResult.mainClassName
compileResult.classes.filter { it.path.endsWith(".class") }
.forEach {
val className = classNameFromPath(it.path)
if (className.internalName == expectedClassName || className.internalName.endsWith("/$expectedClassName")) {
mainLineClassName = className.internalName.replace('/', '.')
}
classLoader.addClass(className, it.bytes)
}
val scriptClass = try {
classLoader.loadClass(mainLineClassName!!)
}
catch (t: Throwable) {
throw Exception("Error loading class $mainLineClassName: known classes: ${compiledClassesNames()}", t)
}
return Pair(classLoader, scriptClass)
}
}
private open class HistoryActionsForRepeatRecentOnly(state: GenericReplEvaluatorState) : HistoryActionsForNoRepeat(state) {
val currentLast = state.history.peek()!!
override val effectiveHistory: List<EvalClassWithInstanceAndLoader> get() = super.effectiveHistory.dropLast(1)
override fun<OtherT> firstMismatch(other: IReplStageHistory<OtherT>): Pair<ReplHistoryRecord<EvalClassWithInstanceAndLoader>?, ReplHistoryRecord<OtherT>?>? =
state.history.firstMismatchFiltered(other) { it.id != currentLast.id }
override fun addPlaceholder(lineId: ILineId, value: EvalClassWithInstanceAndLoader) {}
override fun removePlaceholder(lineId: ILineId): Boolean = true
override fun addFinal(lineId: ILineId, value: EvalClassWithInstanceAndLoader) {
state.history.pop()
state.history.push(lineId, value)
}
override fun processClasses(compileResult: ReplCompileResult.CompiledClasses): Pair<ClassLoader, Class<out Any>> =
currentLast.item.classLoader to currentLast.item.klass.java
}
private open class HistoryActionsForRepeatAny(state: GenericReplEvaluatorState, val matchingLine: ReplHistoryRecord<EvalClassWithInstanceAndLoader>): HistoryActionsForNoRepeat(state) {
override val effectiveHistory: List<EvalClassWithInstanceAndLoader> get() = state.history.takeWhile { it.id != matchingLine.id }.map { it.item }
override fun<OtherT> firstMismatch(other: IReplStageHistory<OtherT>): Pair<ReplHistoryRecord<EvalClassWithInstanceAndLoader>?, ReplHistoryRecord<OtherT>?>? =
state.history.firstMismatchWhile(other) { it.id != matchingLine.id }
override fun addPlaceholder(lineId: ILineId, value: EvalClassWithInstanceAndLoader) {}
override fun removePlaceholder(lineId: ILineId): Boolean = true
override fun addFinal(lineId: ILineId, value: EvalClassWithInstanceAndLoader) {
val extraLines = state.history.takeLastWhile { it.id == matchingLine.id }
state.history.resetTo(lineId)
state.history.pop()
state.history.push(lineId, value)
extraLines.forEach {
state.history.push(it.id, it.item)
}
}
override fun processClasses(compileResult: ReplCompileResult.CompiledClasses): Pair<ClassLoader, Class<out Any>> =
matchingLine.item.classLoader to matchingLine.item.klass.java
}
@@ -19,16 +19,17 @@ package org.jetbrains.kotlin.cli.common.repl
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
import java.io.Reader import java.io.Reader
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.locks.ReentrantReadWriteLock
import javax.script.* import javax.script.*
val KOTLIN_SCRIPT_HISTORY_BINDINGS_KEY = "kotlin.script.history" val KOTLIN_SCRIPT_STATE_BINDINGS_KEY = "kotlin.script.state"
val KOTLIN_SCRIPT_LINE_NUMBER_BINDINGS_KEY = "kotlin.script.line.number" val KOTLIN_SCRIPT_LINE_NUMBER_BINDINGS_KEY = "kotlin.script.line.number"
// TODO consider additional error handling // TODO consider additional error handling
var Bindings.kotlinScriptHistory: MutableList<ReplCodeLine> var Bindings.kotlinScriptState: IReplStageState<*>
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
get() = getOrPut(KOTLIN_SCRIPT_HISTORY_BINDINGS_KEY, { arrayListOf<ReplCodeLine>() }) as MutableList<ReplCodeLine> get() = get(KOTLIN_SCRIPT_STATE_BINDINGS_KEY) as IReplStageState<*>
set(v) { put(KOTLIN_SCRIPT_HISTORY_BINDINGS_KEY, v) } set(v) { put(KOTLIN_SCRIPT_STATE_BINDINGS_KEY, v) }
val Bindings.kotlinScriptLineNumber: AtomicInteger val Bindings.kotlinScriptLineNumber: AtomicInteger
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
@@ -36,8 +37,8 @@ val Bindings.kotlinScriptLineNumber: AtomicInteger
abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEngineFactory) : AbstractScriptEngine(), ScriptEngine, Compilable { abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEngineFactory) : AbstractScriptEngine(), ScriptEngine, Compilable {
protected abstract val replCompiler: ReplCompileAction protected abstract val replCompiler: ReplCompiler
protected abstract val replScriptEvaluator: ReplFullEvaluator protected abstract val replEvaluator: ReplFullEvaluator
override fun eval(script: String, context: ScriptContext): Any? = compileAndEval(script, context) override fun eval(script: String, context: ScriptContext): Any? = compileAndEval(script, context)
@@ -52,20 +53,18 @@ abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEn
override fun getFactory(): ScriptEngineFactory = myFactory override fun getFactory(): ScriptEngineFactory = myFactory
// the parameter could be used in the future when we decide to keep state completely in the context and solve appropriate problems (now e.g. replCompiler keeps separate state) // the parameter could be used in the future when we decide to keep state completely in the context and solve appropriate problems (now e.g. replCompiler keeps separate state)
fun nextCodeLine(@Suppress("UNUSED_PARAMETER") context: ScriptContext, code: String) = ReplCodeLine(this.context.getBindings(ScriptContext.ENGINE_SCOPE).kotlinScriptLineNumber.incrementAndGet(), code) fun nextCodeLine(context: ScriptContext, code: String) = ReplCodeLine(context.getBindings(ScriptContext.ENGINE_SCOPE).kotlinScriptLineNumber.incrementAndGet(), code)
private fun getCurrentHistory(@Suppress("UNUSED_PARAMETER") context: ScriptContext) = this.context.getBindings(ScriptContext.ENGINE_SCOPE).kotlinScriptHistory protected open fun createState(lock: ReentrantReadWriteLock = ReentrantReadWriteLock()): IReplStageState<*> = AggregatedReplStageState(replCompiler.createState(lock), replEvaluator.createState(lock), lock)
private fun setContextHistory(@Suppress("UNUSED_PARAMETER") context: ScriptContext, history: ArrayList<ReplCodeLine>) { private fun getCurrentState(context: ScriptContext) = context.getBindings(ScriptContext.ENGINE_SCOPE).getOrPut(KOTLIN_SCRIPT_STATE_BINDINGS_KEY, { replEvaluator.createState() }) as IReplStageState<*>
this.context.getBindings(ScriptContext.ENGINE_SCOPE).kotlinScriptHistory = history
}
open fun overrideScriptArgs(context: ScriptContext): ScriptArgsWithTypes? = null open fun overrideScriptArgs(context: ScriptContext): ScriptArgsWithTypes? = null
open fun compileAndEval(script: String, context: ScriptContext): Any? { open fun compileAndEval(script: String, context: ScriptContext): Any? {
val codeLine = nextCodeLine(context, script) val codeLine = nextCodeLine(context, script)
val history = getCurrentHistory(context) val state = getCurrentState(context)
val result = replScriptEvaluator.compileAndEval(codeLine, scriptArgs = overrideScriptArgs(context), verifyHistory = history) val result = replEvaluator.compileAndEval(state, codeLine, scriptArgs = overrideScriptArgs(context))
val ret = when (result) { val ret = when (result) {
is ReplEvalResult.ValueResult -> result.value is ReplEvalResult.ValueResult -> result.value
is ReplEvalResult.UnitResult -> null is ReplEvalResult.UnitResult -> null
@@ -73,29 +72,27 @@ abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEn
is ReplEvalResult.Incomplete -> throw ScriptException("error: incomplete code") is ReplEvalResult.Incomplete -> throw ScriptException("error: incomplete code")
is ReplEvalResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${result.lineNo}") is ReplEvalResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${result.lineNo}")
} }
setContextHistory(context, ArrayList(result.completedEvalHistory))
return ret return ret
} }
open fun compile(script: String, context: ScriptContext): CompiledScript { open fun compile(script: String, context: ScriptContext): CompiledScript {
val codeLine = nextCodeLine(context, script) val codeLine = nextCodeLine(context, script)
val history = getCurrentHistory(context) val state = getCurrentState(context)
val result = replCompiler.compile(codeLine, history) val result = replCompiler.compile(state, codeLine)
val compiled = when (result) { val compiled = when (result) {
is ReplCompileResult.Error -> throw ScriptException("Error${result.locationString()}: ${result.message}") is ReplCompileResult.Error -> throw ScriptException("Error${result.locationString()}: ${result.message}")
is ReplCompileResult.Incomplete -> throw ScriptException("error: incomplete code") is ReplCompileResult.Incomplete -> throw ScriptException("error: incomplete code")
is ReplCompileResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${result.lineNo}") is ReplCompileResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${result.lineNo}")
is ReplCompileResult.CompiledClasses -> result is ReplCompileResult.CompiledClasses -> result
} }
// TODO: check if it is ok to keep compiled history in the same place as compiledEval one
setContextHistory(context, ArrayList(result.compiledHistory))
return CompiledKotlinScript(this, codeLine, compiled) return CompiledKotlinScript(this, codeLine, compiled)
} }
open fun eval(compiledScript: CompiledKotlinScript, context: ScriptContext): Any? { open fun eval(compiledScript: CompiledKotlinScript, context: ScriptContext): Any? {
val state = getCurrentState(context)
val result = try { val result = try {
replScriptEvaluator.eval(compiledScript.compiledData, scriptArgs = overrideScriptArgs(context)) replEvaluator.eval(state, compiledScript.compiledData, scriptArgs = overrideScriptArgs(context))
} }
catch (e: Exception) { catch (e: Exception) {
throw ScriptException(e) throw ScriptException(e)
@@ -108,7 +105,6 @@ abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEn
is ReplEvalResult.Incomplete -> throw ScriptException("error: incomplete code") is ReplEvalResult.Incomplete -> throw ScriptException("error: incomplete code")
is ReplEvalResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${result.lineNo}") is ReplEvalResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${result.lineNo}")
} }
setContextHistory(context, ArrayList(result.completedEvalHistory))
return ret return ret
} }
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
import java.io.File import java.io.File
import java.io.Serializable import java.io.Serializable
import java.util.* import java.util.*
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.reflect.KClass import kotlin.reflect.KClass
data class ReplCodeLine(val no: Int, val code: String) : Serializable { data class ReplCodeLine(val no: Int, val code: String) : Serializable {
@@ -43,9 +44,14 @@ data class CompiledClassData(val path: String, val bytes: ByteArray) : Serializa
} }
} }
interface CreateReplStageStateAction {
fun createState(lock: ReentrantReadWriteLock = ReentrantReadWriteLock()): IReplStageState<*>
}
// --- check
interface ReplCheckAction { interface ReplCheckAction {
fun check(codeLine: ReplCodeLine): ReplCheckResult fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult
} }
sealed class ReplCheckResult : Serializable { sealed class ReplCheckResult : Serializable {
@@ -63,40 +69,25 @@ sealed class ReplCheckResult : Serializable {
} }
} }
interface ReplResettableCodeLine { // --- compile
fun resetToLine(lineNumber: Int): List<ReplCodeLine>
fun resetToLine(line: ReplCodeLine): List<ReplCodeLine> = resetToLine(line.no)
}
interface ReplCodeLineHistory {
val history: List<ReplCodeLine>
}
interface ReplCombinedHistory {
val compiledHistory: List<ReplCodeLine>
val evaluatedHistory: List<ReplCodeLine>
}
interface ReplCompileAction { interface ReplCompileAction {
fun compile(codeLine: ReplCodeLine, verifyHistory: List<ReplCodeLine>? = null): ReplCompileResult fun compile(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCompileResult
} }
sealed class ReplCompileResult(val compiledHistory: List<ReplCodeLine>) : Serializable { sealed class ReplCompileResult : Serializable {
class CompiledClasses(compiledHistory: List<ReplCodeLine>, class CompiledClasses(val lineId: LineId,
val compiledCodeLine: CompiledReplCodeLine, val mainClassName: String,
val generatedClassname: String,
val classes: List<CompiledClassData>, val classes: List<CompiledClassData>,
val hasResult: Boolean, val hasResult: Boolean,
val classpathAddendum: List<File>) : ReplCompileResult(compiledHistory) val classpathAddendum: List<File>) : ReplCompileResult()
class Incomplete(compiledHistory: List<ReplCodeLine>) : ReplCompileResult(compiledHistory) class Incomplete : ReplCompileResult()
class HistoryMismatch(compiledHistory: List<ReplCodeLine>, val lineNo: Int) : ReplCompileResult(compiledHistory) class HistoryMismatch(val lineNo: Int) : ReplCompileResult()
class Error(compiledHistory: List<ReplCodeLine>, class Error(val message: String,
val message: String, val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION) : ReplCompileResult() {
val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION) : ReplCompileResult(compiledHistory) {
override fun toString(): String = "Error(message = \"$message\"" override fun toString(): String = "Error(message = \"$message\""
} }
@@ -105,43 +96,35 @@ sealed class ReplCompileResult(val compiledHistory: List<ReplCodeLine>) : Serial
} }
} }
interface ReplCompiler : ReplResettableCodeLine, ReplCodeLineHistory, ReplCompileAction, ReplCheckAction interface ReplCompiler : ReplCompileAction, ReplCheckAction, CreateReplStageStateAction
typealias EvalHistoryType = Pair<CompiledReplCodeLine, EvalClassWithInstanceAndLoader> // --- eval
interface ReplEvaluatorExposedInternalHistory {
val lastEvaluatedScripts: List<EvalHistoryType>
}
interface ReplClasspath {
val currentClasspath: List<File>
}
data class EvalClassWithInstanceAndLoader(val klass: KClass<*>, val instance: Any?, val classLoader: ClassLoader, val invokeWrapper: InvokeWrapper?) data class EvalClassWithInstanceAndLoader(val klass: KClass<*>, val instance: Any?, val classLoader: ClassLoader, val invokeWrapper: InvokeWrapper?)
interface ReplEvalAction { interface ReplEvalAction {
fun eval(compileResult: ReplCompileResult.CompiledClasses, fun eval(state: IReplStageState<*>,
compileResult: ReplCompileResult.CompiledClasses,
scriptArgs: ScriptArgsWithTypes? = null, scriptArgs: ScriptArgsWithTypes? = null,
invokeWrapper: InvokeWrapper? = null): ReplEvalResult invokeWrapper: InvokeWrapper? = null): ReplEvalResult
} }
sealed class ReplEvalResult(val completedEvalHistory: List<ReplCodeLine>) : Serializable { sealed class ReplEvalResult : Serializable {
class ValueResult(completedEvalHistory: List<ReplCodeLine>, val value: Any?) : ReplEvalResult(completedEvalHistory) { class ValueResult(val value: Any?) : ReplEvalResult() {
override fun toString(): String = "Result: $value" override fun toString(): String = "Result: $value"
} }
class UnitResult(completedEvalHistory: List<ReplCodeLine>) : ReplEvalResult(completedEvalHistory) class UnitResult : ReplEvalResult()
class Incomplete(completedEvalHistory: List<ReplCodeLine>) : ReplEvalResult(completedEvalHistory) class Incomplete : ReplEvalResult()
class HistoryMismatch(completedEvalHistory: List<ReplCodeLine>, val lineNo: Int) : ReplEvalResult(completedEvalHistory) class HistoryMismatch(val lineNo: Int) : ReplEvalResult()
sealed class Error(completedEvalHistory: List<ReplCodeLine>, val message: String) : ReplEvalResult(completedEvalHistory) { sealed class Error(val message: String) : ReplEvalResult() {
class Runtime(completedEvalHistory: List<ReplCodeLine>, message: String, val cause: Exception? = null) : Error(completedEvalHistory, message) class Runtime(message: String, val cause: Exception? = null) : Error(message)
class CompileTime(completedEvalHistory: List<ReplCodeLine>, class CompileTime(message: String,
message: String, val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION) : Error(message)
val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION) : Error(completedEvalHistory, message)
override fun toString(): String = "${this::class.simpleName}Error(message = \"$message\"" override fun toString(): String = "${this::class.simpleName}Error(message = \"$message\""
} }
@@ -151,43 +134,44 @@ sealed class ReplEvalResult(val completedEvalHistory: List<ReplCodeLine>) : Seri
} }
} }
interface ReplEvaluator : ReplResettableCodeLine, ReplCodeLineHistory, ReplEvaluatorExposedInternalHistory, ReplEvalAction, ReplClasspath interface ReplEvaluator : ReplEvalAction, CreateReplStageStateAction
// --- compileAdnEval
interface ReplAtomicEvalAction { interface ReplAtomicEvalAction {
fun compileAndEval(codeLine: ReplCodeLine, fun compileAndEval(state: IReplStageState<*>,
codeLine: ReplCodeLine,
scriptArgs: ScriptArgsWithTypes? = null, scriptArgs: ScriptArgsWithTypes? = null,
verifyHistory: List<ReplCodeLine>? = null,
invokeWrapper: InvokeWrapper? = null): ReplEvalResult invokeWrapper: InvokeWrapper? = null): ReplEvalResult
} }
interface ReplAtomicEvaluator : ReplResettableCodeLine, ReplCombinedHistory, ReplEvaluatorExposedInternalHistory, ReplAtomicEvalAction, ReplCheckAction, ReplClasspath interface ReplAtomicEvaluator : ReplAtomicEvalAction, ReplCheckAction
interface ReplDelayedEvalAction { interface ReplDelayedEvalAction {
fun compileToEvaluable(codeLine: ReplCodeLine, defaultScriptArgs: ScriptArgsWithTypes? = null, verifyHistory: List<ReplCodeLine>?): Pair<ReplCompileResult, Evaluable?> fun compileToEvaluable(state: IReplStageState<*>,
codeLine: ReplCodeLine,
defaultScriptArgs: ScriptArgsWithTypes? = null): Pair<ReplCompileResult, Evaluable?>
} }
// other
interface Evaluable { interface Evaluable {
val compiledCode: ReplCompileResult.CompiledClasses val compiledCode: ReplCompileResult.CompiledClasses
fun eval(scriptArgs: ScriptArgsWithTypes? = null, invokeWrapper: InvokeWrapper? = null): ReplEvalResult fun eval(scriptArgs: ScriptArgsWithTypes? = null, invokeWrapper: InvokeWrapper? = null): ReplEvalResult
} }
interface ReplFullEvaluator : ReplEvaluator, ReplAtomicEvaluator, ReplDelayedEvalAction, ReplCombinedHistory interface ReplFullEvaluator : ReplEvaluator, ReplAtomicEvaluator, ReplDelayedEvalAction
/** /**
* Keep args and arg types together, so as a whole they are present or absent * Keep args and arg types together, so as a whole they are present or absent
*/ */
class ScriptArgsWithTypes(val scriptArgs: Array<out Any?>, val scriptArgsTypes: Array<out KClass<out Any>>) : Serializable { class ScriptArgsWithTypes(val scriptArgs: Array<out Any?>, val scriptArgsTypes: Array<out KClass<out Any>>) : Serializable {
init { assert(scriptArgs.size == scriptArgsTypes.size) }
companion object { companion object {
private val serialVersionUID: Long = 8529357500L private val serialVersionUID: Long = 8529357500L
} }
} }
interface ScriptTemplateEmptyArgsProvider {
val defaultEmptyArgs: ScriptArgsWithTypes?
}
class SimpleScriptTemplateEmptyArgsProvider(override val defaultEmptyArgs: ScriptArgsWithTypes? = null) : ScriptTemplateEmptyArgsProvider
enum class ReplRepeatingMode { enum class ReplRepeatingMode {
NONE, NONE,
REPEAT_ONLY_MOST_RECENT, REPEAT_ONLY_MOST_RECENT,
@@ -0,0 +1,83 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.common.repl
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
interface ILineId : Comparable<ILineId> {
val no: Int
}
data class ReplHistoryRecord<out T> (val id: ILineId, val item: T)
interface IReplStageHistory<T> : List<ReplHistoryRecord<T>> {
fun backwardIterator(): Iterator<ReplHistoryRecord<T>> = lock.read { asReversed().iterator() } // TODO: check perf
fun peek(): ReplHistoryRecord<T>? = lock.read { lastOrNull() }
fun push(id: ILineId, item: T)
fun pop(): ReplHistoryRecord<T>?
fun verifiedPop(id: ILineId): ReplHistoryRecord<T>? = lock.write {
if (lastOrNull()?.id == id) pop()
else null
}
fun resetTo(id: ILineId): Iterable<ILineId>
fun <OtherT> firstMismatch(other: IReplStageHistory<OtherT>): Pair<ReplHistoryRecord<T>?, ReplHistoryRecord<OtherT>?>? =
lock.read { other.lock.read {
iterator().asSequence().zip(other.asSequence()).firstOrNull { it.first.id != it.second.id }?.let { it.first to it.second }
} }
fun<OtherT> firstMismatchFiltered(other: IReplStageHistory<OtherT>, predicate: (ReplHistoryRecord<T>) -> Boolean): Pair<ReplHistoryRecord<T>?, ReplHistoryRecord<OtherT>?>? =
lock.read { other.lock.read {
iterator().asSequence().filter(predicate).zip(other.asSequence()).firstOrNull { it.first.id != it.second.id }?.let { it.first to it.second }
} }
fun<OtherT> firstMismatchWhile(other: IReplStageHistory<OtherT>, predicate: (ReplHistoryRecord<T>) -> Boolean): Pair<ReplHistoryRecord<T>?, ReplHistoryRecord<OtherT>?>? =
lock.read { other.lock.read {
iterator().asSequence().takeWhile(predicate).zip(other.asSequence()).firstOrNull { it.first.id != it.second.id }?.let { it.first to it.second }
} }
fun <OtherT> matches(other: IReplStageHistory<OtherT>): Boolean =
lock.read { other.lock.read {
size == other.size && firstMismatch(other) == null
} }
fun <OtherT> isProperPrefix(other: IReplStageHistory<OtherT>): Boolean =
lock.read { other.lock.read {
size == other.size - 1 && firstMismatch(other) == null
} }
val lock: ReentrantReadWriteLock
}
interface IReplStageState<T> {
val history: IReplStageHistory<T>
val lock: ReentrantReadWriteLock
fun <StateT : IReplStageState<*>> asState(): StateT = (this as? StateT) ?: throw IllegalArgumentException("$this is not an expected instance of IReplStageState")
}
@@ -0,0 +1,63 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.jvm.repl
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.repl.messages.DiagnosticMessageHolder
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.script.KotlinScriptExternalDependencies
import java.util.concurrent.atomic.AtomicLong
import java.util.concurrent.locks.ReentrantReadWriteLock
class ReplCompilerStageHistory(private val state: GenericReplCompilerState) : BasicReplStageHistory<ScriptDescriptor>(state.lock) {
override fun resetTo(id: ILineId): Iterable<ILineId> {
state.generation.incrementAndGet()
val removedCompiledLines = super.resetTo(id)
val removedAnalyzedLines = state.analyzerEngine.resetToLine(id.no)
removedCompiledLines.zip(removedAnalyzedLines).forEach {
if (it.first != LineId(it.second)) {
throw IllegalStateException("History mismatch when resetting lines: ${it.first.no} != ${it.second}")
}
}
return removedCompiledLines
}
}
abstract class GenericReplCheckerState: IReplStageState<ScriptDescriptor> {
// "line" - is the unit of evaluation here, could in fact consists of several character lines
class LineState(
val codeLine: ReplCodeLine,
val psiFile: KtFile,
val errorHolder: DiagnosticMessageHolder)
val generation = AtomicLong(1)
var lastLineState: LineState? = null // for transferring state to the compiler in most typical case
}
class GenericReplCompilerState(environment: KotlinCoreEnvironment, override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()) : IReplStageState<ScriptDescriptor>, GenericReplCheckerState() {
override val history: IReplStageHistory<ScriptDescriptor> = ReplCompilerStageHistory(this)
val analyzerEngine = ReplCodeAnalyzer(environment)
var lastDependencies: KotlinScriptExternalDependencies? = null
}
@@ -22,8 +22,6 @@ import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.script.KotlinScriptDefinition import org.jetbrains.kotlin.script.KotlinScriptDefinition
import java.io.File
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.locks.ReentrantReadWriteLock import java.util.concurrent.locks.ReentrantReadWriteLock
open class GenericRepl protected constructor( open class GenericRepl protected constructor(
@@ -32,56 +30,22 @@ open class GenericRepl protected constructor(
compilerConfiguration: CompilerConfiguration, compilerConfiguration: CompilerConfiguration,
messageCollector: MessageCollector, messageCollector: MessageCollector,
baseClassloader: ClassLoader?, baseClassloader: ClassLoader?,
protected val fallbackScriptArgs: ScriptArgsWithTypes?, protected val fallbackScriptArgs: ScriptArgsWithTypes? = null,
protected val repeatingMode: ReplRepeatingMode = ReplRepeatingMode.NONE, protected val repeatingMode: ReplRepeatingMode = ReplRepeatingMode.NONE
protected val stateLock: ReentrantReadWriteLock
) : ReplCompiler, ReplEvaluator, ReplAtomicEvaluator { ) : ReplCompiler, ReplEvaluator, ReplAtomicEvaluator {
constructor (
disposable: Disposable,
scriptDefinition: KotlinScriptDefinition,
compilerConfiguration: CompilerConfiguration,
messageCollector: MessageCollector,
baseClassloader: ClassLoader?,
fallbackScriptArgs: ScriptArgsWithTypes? = null
) : this(disposable, scriptDefinition, compilerConfiguration, messageCollector, baseClassloader, fallbackScriptArgs, stateLock = ReentrantReadWriteLock())
protected val compiler: ReplCompiler by lazy { GenericReplCompiler(disposable, scriptDefinition, compilerConfiguration, messageCollector, stateLock) } protected val compiler: ReplCompiler by lazy { GenericReplCompiler(disposable, scriptDefinition, compilerConfiguration, messageCollector) }
protected val evaluator: ReplFullEvaluator by lazy { GenericReplCompilingEvaluator(compiler, compilerConfiguration.jvmClasspathRoots, baseClassloader, fallbackScriptArgs, repeatingMode, stateLock) } protected val evaluator: ReplFullEvaluator by lazy { GenericReplCompilingEvaluator(compiler, compilerConfiguration.jvmClasspathRoots, baseClassloader, fallbackScriptArgs, repeatingMode) }
protected var codeLineNumber = AtomicInteger(0)
override fun resetToLine(lineNumber: Int): List<ReplCodeLine> { override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> = AggregatedReplStageState(compiler.createState(lock), evaluator.createState(lock), lock)
return evaluator.resetToLine(lineNumber)
}
override fun resetToLine(line: ReplCodeLine): List<ReplCodeLine> { override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult = compiler.check(state, codeLine)
return evaluator.resetToLine(line)
}
override val history: List<ReplCodeLine> get() = evaluator.history override fun compile(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCompileResult = compiler.compile(state, codeLine)
override val compiledHistory: List<ReplCodeLine> get() = compiler.history
override val evaluatedHistory: List<ReplCodeLine> get() = evaluator.history
override val currentClasspath: List<File> override fun eval(state: IReplStageState<*>, compileResult: ReplCompileResult.CompiledClasses, scriptArgs: ScriptArgsWithTypes?, invokeWrapper: InvokeWrapper?): ReplEvalResult =
get() = evaluator.currentClasspath evaluator.eval(state, compileResult, scriptArgs, invokeWrapper)
override val lastEvaluatedScripts: List<EvalHistoryType> override fun compileAndEval(state: IReplStageState<*>, codeLine: ReplCodeLine, scriptArgs: ScriptArgsWithTypes?, invokeWrapper: InvokeWrapper?): ReplEvalResult =
get() = evaluator.lastEvaluatedScripts evaluator.compileAndEval(state, codeLine, scriptArgs, invokeWrapper)
override fun check(codeLine: ReplCodeLine): ReplCheckResult {
return compiler.check(codeLine)
}
override fun compile(codeLine: ReplCodeLine, verifyHistory: List<ReplCodeLine>?): ReplCompileResult {
return compiler.compile(codeLine, verifyHistory)
}
override fun eval(compileResult: ReplCompileResult.CompiledClasses, scriptArgs: ScriptArgsWithTypes?, invokeWrapper: InvokeWrapper?): ReplEvalResult {
return evaluator.eval(compileResult, scriptArgs, invokeWrapper)
}
override fun compileAndEval(codeLine: ReplCodeLine, scriptArgs: ScriptArgsWithTypes?, verifyHistory: List<ReplCodeLine>?, invokeWrapper: InvokeWrapper?): ReplEvalResult {
return evaluator.compileAndEval(codeLine, scriptArgs, verifyHistory, invokeWrapper)
}
fun nextCodeLine(code: String) = ReplCodeLine(codeLineNumber.incrementAndGet(), code)
} }
@@ -26,22 +26,18 @@ import com.intellij.testFramework.LightVirtualFile
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.repl.ReplCheckResult import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
import org.jetbrains.kotlin.cli.common.repl.makeScriptBaseName
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.repl.messages.DiagnosticMessageHolder
import org.jetbrains.kotlin.cli.jvm.repl.messages.ReplTerminalDiagnosticMessageHolder import org.jetbrains.kotlin.cli.jvm.repl.messages.ReplTerminalDiagnosticMessageHolder
import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.JvmTarget import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.idea.KotlinLanguage import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.parsing.KotlinParserDefinition import org.jetbrains.kotlin.parsing.KotlinParserDefinition
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.script.KotlinScriptDefinition import org.jetbrains.kotlin.script.KotlinScriptDefinition
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write import kotlin.concurrent.write
const val KOTLIN_REPL_JVM_TARGET_PROPERTY = "kotlin.repl.jvm.target" const val KOTLIN_REPL_JVM_TARGET_PROPERTY = "kotlin.repl.jvm.target"
@@ -50,9 +46,9 @@ open class GenericReplChecker(
disposable: Disposable, disposable: Disposable,
val scriptDefinition: KotlinScriptDefinition, val scriptDefinition: KotlinScriptDefinition,
val compilerConfiguration: CompilerConfiguration, val compilerConfiguration: CompilerConfiguration,
messageCollector: MessageCollector, messageCollector: MessageCollector
protected val stateLock: ReentrantReadWriteLock = ReentrantReadWriteLock() ) : ReplCheckAction {
) {
internal val environment = run { internal val environment = run {
compilerConfiguration.apply { compilerConfiguration.apply {
add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, scriptDefinition) add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, scriptDefinition)
@@ -70,21 +66,12 @@ open class GenericReplChecker(
private val psiFileFactory: PsiFileFactoryImpl = PsiFileFactory.getInstance(environment.project) as PsiFileFactoryImpl private val psiFileFactory: PsiFileFactoryImpl = PsiFileFactory.getInstance(environment.project) as PsiFileFactoryImpl
// "line" - is the unit of evaluation here, could in fact consists of several character lines internal fun createDiagnosticHolder() = ReplTerminalDiagnosticMessageHolder()
internal class LineState(
val codeLine: ReplCodeLine,
val psiFile: KtFile,
val errorHolder: DiagnosticMessageHolder)
private var _lineState: LineState? = null override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult {
state.lock.write {
internal val lineState: LineState? get() = stateLock.read { _lineState } val checkerState = state.asState<GenericReplCheckerState>()
val scriptFileName = makeScriptBaseName(codeLine, checkerState.generation.get())
fun createDiagnosticHolder() = ReplTerminalDiagnosticMessageHolder()
fun check(codeLine: ReplCodeLine, generation: Long): ReplCheckResult {
stateLock.write {
val scriptFileName = makeScriptBaseName(codeLine, generation)
val virtualFile = val virtualFile =
LightVirtualFile("$scriptFileName${KotlinParserDefinition.STD_SCRIPT_EXT}", KotlinLanguage.INSTANCE, StringUtil.convertLineSeparators(codeLine.code)).apply { LightVirtualFile("$scriptFileName${KotlinParserDefinition.STD_SCRIPT_EXT}", KotlinLanguage.INSTANCE, StringUtil.convertLineSeparators(codeLine.code)).apply {
charset = CharsetToolkit.UTF8_CHARSET charset = CharsetToolkit.UTF8_CHARSET
@@ -97,7 +84,7 @@ open class GenericReplChecker(
val syntaxErrorReport = AnalyzerWithCompilerReport.reportSyntaxErrors(psiFile, errorHolder) val syntaxErrorReport = AnalyzerWithCompilerReport.reportSyntaxErrors(psiFile, errorHolder)
if (!syntaxErrorReport.isHasErrors) { if (!syntaxErrorReport.isHasErrors) {
_lineState = LineState(codeLine, psiFile, errorHolder) checkerState.lastLineState = GenericReplCheckerState.LineState(codeLine, psiFile, errorHolder)
} }
return when { return when {
@@ -26,115 +26,83 @@ import org.jetbrains.kotlin.codegen.ClassBuilderFactories
import org.jetbrains.kotlin.codegen.KotlinCodegenFacade import org.jetbrains.kotlin.codegen.KotlinCodegenFacade
import org.jetbrains.kotlin.codegen.state.GenerationState import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.script.KotlinScriptDefinition import org.jetbrains.kotlin.script.KotlinScriptDefinition
import org.jetbrains.kotlin.script.KotlinScriptExternalDependencies
import java.io.File import java.io.File
import java.util.concurrent.atomic.AtomicLong
import java.util.concurrent.locks.ReentrantReadWriteLock import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write import kotlin.concurrent.write
// WARNING: not thread safe, assuming external synchronization
open class GenericReplCompiler(disposable: Disposable, open class GenericReplCompiler(disposable: Disposable,
protected val scriptDefinition: KotlinScriptDefinition, protected val scriptDefinition: KotlinScriptDefinition,
protected val compilerConfiguration: CompilerConfiguration, protected val compilerConfiguration: CompilerConfiguration,
messageCollector: MessageCollector, messageCollector: MessageCollector
protected val stateLock: ReentrantReadWriteLock = ReentrantReadWriteLock()) : ReplCompiler { ) : ReplCompiler {
private val checker = GenericReplChecker(disposable, scriptDefinition, compilerConfiguration, messageCollector, stateLock)
private val analyzerEngine = GenericReplAnalyzer(checker.environment, stateLock) private val checker = GenericReplChecker(disposable, scriptDefinition, compilerConfiguration, messageCollector)
private var lastDependencies: KotlinScriptExternalDependencies? = null override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> = GenericReplCompilerState(checker.environment, lock)
private val descriptorsHistory = ReplHistory<ScriptDescriptor>() override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult = checker.check(state, codeLine)
private val generation = AtomicLong(1) override fun compile(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCompileResult {
state.lock.write {
val compilerState = state.asState<GenericReplCompilerState>()
override fun resetToLine(lineNumber: Int): List<ReplCodeLine> { val currentGeneration = compilerState.generation.get()
return stateLock.write {
generation.incrementAndGet()
val removedCompiledLines = descriptorsHistory.resetToLine(lineNumber)
val removedAnalyzedLines = analyzerEngine.resetToLine(lineNumber)
removedCompiledLines.zip(removedAnalyzedLines).forEach {
if (it.first.first != it.second) {
throw IllegalStateException("History mismatch when resetting lines")
}
}
removedCompiledLines
}.map { it.first }
}
override val history: List<ReplCodeLine> get() = stateLock.read { descriptorsHistory.copySources() }
override fun check(codeLine: ReplCodeLine): ReplCheckResult = stateLock.read {
return checker.check(codeLine, generation.get())
}
override fun compile(codeLine: ReplCodeLine, verifyHistory: List<ReplCodeLine>?): ReplCompileResult {
stateLock.write {
val firstMismatch = descriptorsHistory.firstMismatchingHistory(verifyHistory)
if (firstMismatch != null) {
return@compile ReplCompileResult.HistoryMismatch(descriptorsHistory.copySources(), firstMismatch)
}
val currentGeneration = generation.get()
val (psiFile, errorHolder) = run { val (psiFile, errorHolder) = run {
if (checker.lineState == null || checker.lineState!!.codeLine != codeLine) { if (compilerState.lastLineState == null || compilerState.lastLineState!!.codeLine != codeLine) {
val res = checker.check(codeLine, currentGeneration) val res = checker.check(state, codeLine)
when (res) { when (res) {
is ReplCheckResult.Incomplete -> return@compile ReplCompileResult.Incomplete(descriptorsHistory.copySources()) is ReplCheckResult.Incomplete -> return@compile ReplCompileResult.Incomplete()
is ReplCheckResult.Error -> return@compile ReplCompileResult.Error(descriptorsHistory.copySources(), res.message, res.location) is ReplCheckResult.Error -> return@compile ReplCompileResult.Error(res.message, res.location)
is ReplCheckResult.Ok -> {} // continue is ReplCheckResult.Ok -> {} // continue
} }
} }
Pair(checker.lineState!!.psiFile, checker.lineState!!.errorHolder) Pair(compilerState.lastLineState!!.psiFile, compilerState.lastLineState!!.errorHolder)
} }
val newDependencies = scriptDefinition.getDependenciesFor(psiFile, checker.environment.project, lastDependencies) val newDependencies = scriptDefinition.getDependenciesFor(psiFile, checker.environment.project, compilerState.lastDependencies)
var classpathAddendum: List<File>? = null var classpathAddendum: List<File>? = null
if (lastDependencies != newDependencies) { if (compilerState.lastDependencies != newDependencies) {
lastDependencies = newDependencies compilerState.lastDependencies = newDependencies
classpathAddendum = newDependencies?.let { checker.environment.updateClasspath(it.classpath.map(::JvmClasspathRoot)) } classpathAddendum = newDependencies?.let { checker.environment.updateClasspath(it.classpath.map(::JvmClasspathRoot)) }
} }
val analysisResult = analyzerEngine.analyzeReplLine(psiFile, codeLine) val analysisResult = compilerState.analyzerEngine.analyzeReplLine(psiFile, codeLine)
AnalyzerWithCompilerReport.reportDiagnostics(analysisResult.diagnostics, errorHolder) AnalyzerWithCompilerReport.reportDiagnostics(analysisResult.diagnostics, errorHolder)
val scriptDescriptor = when (analysisResult) { val scriptDescriptor = when (analysisResult) {
is GenericReplAnalyzer.ReplLineAnalysisResult.WithErrors -> return ReplCompileResult.Error(descriptorsHistory.copySources(), errorHolder.renderedDiagnostics) is ReplCodeAnalyzer.ReplLineAnalysisResult.WithErrors -> return ReplCompileResult.Error(errorHolder.renderedDiagnostics)
is GenericReplAnalyzer.ReplLineAnalysisResult.Successful -> analysisResult.scriptDescriptor is ReplCodeAnalyzer.ReplLineAnalysisResult.Successful -> analysisResult.scriptDescriptor
else -> error("Unexpected result ${analysisResult.javaClass}") else -> error("Unexpected result ${analysisResult.javaClass}")
} }
val state = GenerationState( val generationState = GenerationState(
psiFile.project, psiFile.project,
ClassBuilderFactories.binaries(false), ClassBuilderFactories.binaries(false),
analyzerEngine.module, compilerState.analyzerEngine.module,
analyzerEngine.trace.bindingContext, compilerState.analyzerEngine.trace.bindingContext,
listOf(psiFile), listOf(psiFile),
compilerConfiguration compilerConfiguration
) )
state.replSpecific.scriptResultFieldName = SCRIPT_RESULT_FIELD_NAME generationState.replSpecific.scriptResultFieldName = SCRIPT_RESULT_FIELD_NAME
state.replSpecific.earlierScriptsForReplInterpreter = descriptorsHistory.copyValues() generationState.replSpecific.earlierScriptsForReplInterpreter = compilerState.history.map { it.item }
state.beforeCompile() generationState.beforeCompile()
KotlinCodegenFacade.generatePackage( KotlinCodegenFacade.generatePackage(
state, generationState,
psiFile.script!!.getContainingKtFile().packageFqName, psiFile.script!!.containingKtFile.packageFqName,
setOf(psiFile.script!!.getContainingKtFile()), setOf(psiFile.script!!.containingKtFile),
org.jetbrains.kotlin.codegen.CompilationErrorHandler.THROW_EXCEPTION) org.jetbrains.kotlin.codegen.CompilationErrorHandler.THROW_EXCEPTION)
val generatedClassname = makeScriptBaseName(codeLine, currentGeneration) val generatedClassname = makeScriptBaseName(codeLine, currentGeneration)
val compiledCodeLine = CompiledReplCodeLine(generatedClassname, codeLine) compilerState.history.push(LineId(codeLine), scriptDescriptor)
descriptorsHistory.add(compiledCodeLine, scriptDescriptor)
return ReplCompileResult.CompiledClasses(descriptorsHistory.copySources(), return ReplCompileResult.CompiledClasses(LineId(codeLine),
compiledCodeLine,
generatedClassname, generatedClassname,
state.factory.asList().map { CompiledClassData(it.relativePath, it.asByteArray()) }, generationState.factory.asList().map { CompiledClassData(it.relativePath, it.asByteArray()) },
state.replSpecific.hasResult, generationState.replSpecific.hasResult,
classpathAddendum ?: emptyList()) classpathAddendum ?: emptyList())
} }
} }
@@ -21,7 +21,6 @@ package org.jetbrains.kotlin.cli.jvm.repl
import org.jetbrains.kotlin.cli.common.repl.CompiledReplCodeLine import org.jetbrains.kotlin.cli.common.repl.CompiledReplCodeLine
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
import org.jetbrains.kotlin.cli.common.repl.ReplHistory import org.jetbrains.kotlin.cli.common.repl.ReplHistory
import org.jetbrains.kotlin.cli.common.repl.ReplResettableCodeLine
import org.jetbrains.kotlin.cli.jvm.compiler.CliLightClassGenerationSupport import org.jetbrains.kotlin.cli.jvm.compiler.CliLightClassGenerationSupport
import org.jetbrains.kotlin.cli.jvm.compiler.JvmPackagePartProvider import org.jetbrains.kotlin.cli.jvm.compiler.JvmPackagePartProvider
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
@@ -43,23 +42,18 @@ import org.jetbrains.kotlin.resolve.scopes.ImportingScope
import org.jetbrains.kotlin.resolve.scopes.utils.parentsWithSelf import org.jetbrains.kotlin.resolve.scopes.utils.parentsWithSelf
import org.jetbrains.kotlin.resolve.scopes.utils.replaceImportingScopes import org.jetbrains.kotlin.resolve.scopes.utils.replaceImportingScopes
import org.jetbrains.kotlin.script.ScriptPriorities import org.jetbrains.kotlin.script.ScriptPriorities
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
class GenericReplAnalyzer(environment: KotlinCoreEnvironment, class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) {
protected val stateLock: ReentrantReadWriteLock = ReentrantReadWriteLock()) : ReplResettableCodeLine {
private val topDownAnalysisContext: TopDownAnalysisContext private val topDownAnalysisContext: TopDownAnalysisContext
private val topDownAnalyzer: LazyTopDownAnalyzer private val topDownAnalyzer: LazyTopDownAnalyzer
private val resolveSession: ResolveSession private val resolveSession: ResolveSession
private val scriptDeclarationFactory: ScriptMutableDeclarationProviderFactory private val scriptDeclarationFactory: ScriptMutableDeclarationProviderFactory
private val replState = ResettableReplState() private val replState = ResettableAnalyzerState()
val module: ModuleDescriptorImpl val module: ModuleDescriptorImpl
get() = stateLock.read { field }
val trace: BindingTraceContext = CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace() val trace: BindingTraceContext = CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace()
get() = stateLock.read { field }
init { init {
// Module source scope is empty because all binary classes are in the dependency module, and all source classes are guaranteed // Module source scope is empty because all binary classes are in the dependency module, and all source classes are guaranteed
@@ -94,21 +88,15 @@ class GenericReplAnalyzer(environment: KotlinCoreEnvironment,
} }
} }
override fun resetToLine(lineNumber: Int): List<ReplCodeLine> { fun resetToLine(lineNumber: Int): List<ReplCodeLine> = replState.resetToLine(lineNumber)
return stateLock.write { replState.resetToLine(lineNumber) }
}
override fun resetToLine(line: ReplCodeLine): List<ReplCodeLine> = resetToLine(line.no)
fun analyzeReplLine(psiFile: KtFile, codeLine: ReplCodeLine): ReplLineAnalysisResult { fun analyzeReplLine(psiFile: KtFile, codeLine: ReplCodeLine): ReplLineAnalysisResult {
return stateLock.write { topDownAnalysisContext.scripts.clear()
topDownAnalysisContext.scripts.clear() trace.clearDiagnostics()
trace.clearDiagnostics()
psiFile.script!!.putUserData(ScriptPriorities.PRIORITY_KEY, codeLine.no) psiFile.script!!.putUserData(ScriptPriorities.PRIORITY_KEY, codeLine.no)
doAnalyze(psiFile, codeLine) return doAnalyze(psiFile, codeLine)
}
} }
private fun doAnalyze(linePsi: KtFile, codeLine: ReplCodeLine): ReplLineAnalysisResult { private fun doAnalyze(linePsi: KtFile, codeLine: ReplCodeLine): ReplLineAnalysisResult {
@@ -179,7 +167,8 @@ class GenericReplAnalyzer(environment: KotlinCoreEnvironment,
} }
// TODO: merge with org.jetbrains.kotlin.resolve.repl.ReplState when switching to new REPL infrastruct everywhere // TODO: merge with org.jetbrains.kotlin.resolve.repl.ReplState when switching to new REPL infrastruct everywhere
class ResettableReplState { // TODO: review it's place in the extracted state infrastruct (now the analyzer itself is a part of the state
class ResettableAnalyzerState {
private val successfulLines = ReplHistory<LineInfo.SuccessfulLine>() private val successfulLines = ReplHistory<LineInfo.SuccessfulLine>()
private val submittedLines = hashMapOf<KtFile, LineInfo>() private val submittedLines = hashMapOf<KtFile, LineInfo>()
@@ -189,8 +178,6 @@ class GenericReplAnalyzer(environment: KotlinCoreEnvironment,
return removed.map { it.first } return removed.map { it.first }
} }
fun resetToLine(line: ReplCodeLine): List<ReplCodeLine> = resetToLine(line.no)
fun submitLine(ktFile: KtFile, codeLine: ReplCodeLine) { fun submitLine(ktFile: KtFile, codeLine: ReplCodeLine) {
val line = LineInfo.SubmittedLine(ktFile, successfulLines.lastValue()) val line = LineInfo.SubmittedLine(ktFile, successfulLines.lastValue())
submittedLines[ktFile] = line submittedLines[ktFile] = line
@@ -64,7 +64,7 @@ class ReplInterpreter(
} }
private val psiFileFactory: PsiFileFactoryImpl = PsiFileFactory.getInstance(environment.project) as PsiFileFactoryImpl private val psiFileFactory: PsiFileFactoryImpl = PsiFileFactory.getInstance(environment.project) as PsiFileFactoryImpl
private val analyzerEngine = GenericReplAnalyzer(environment) private val analyzerEngine = ReplCodeAnalyzer(environment)
fun eval(line: String): LineResult { fun eval(line: String): LineResult {
++lineNumber ++lineNumber
@@ -101,8 +101,8 @@ class ReplInterpreter(
val analysisResult = analyzerEngine.analyzeReplLine(psiFile, ReplCodeLine(lineNumber, "fake line")) val analysisResult = analyzerEngine.analyzeReplLine(psiFile, ReplCodeLine(lineNumber, "fake line"))
AnalyzerWithCompilerReport.reportDiagnostics(analysisResult.diagnostics, errorHolder) AnalyzerWithCompilerReport.reportDiagnostics(analysisResult.diagnostics, errorHolder)
val scriptDescriptor = when (analysisResult) { val scriptDescriptor = when (analysisResult) {
is GenericReplAnalyzer.ReplLineAnalysisResult.WithErrors -> return LineResult.Error.CompileTime(errorHolder.renderedDiagnostics) is ReplCodeAnalyzer.ReplLineAnalysisResult.WithErrors -> return LineResult.Error.CompileTime(errorHolder.renderedDiagnostics)
is GenericReplAnalyzer.ReplLineAnalysisResult.Successful -> analysisResult.scriptDescriptor is ReplCodeAnalyzer.ReplLineAnalysisResult.Successful -> analysisResult.scriptDescriptor
else -> error("Unexpected result ${analysisResult.javaClass}") else -> error("Unexpected result ${analysisResult.javaClass}")
} }
@@ -68,5 +68,5 @@ class KotlinJsr223JvmScriptEngine4Idea(
val localEvaluator: ReplFullEvaluator by lazy { GenericReplCompilingEvaluator(replCompiler, templateClasspath, Thread.currentThread().contextClassLoader) } val localEvaluator: ReplFullEvaluator by lazy { GenericReplCompilingEvaluator(replCompiler, templateClasspath, Thread.currentThread().contextClassLoader) }
override val replScriptEvaluator: ReplFullEvaluator get() = localEvaluator override val replEvaluator: ReplFullEvaluator get() = localEvaluator
} }