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,
baseClasspath: Iterable<File>,
baseClassloader: ClassLoader?,
protected val fallbackScriptArgs: ScriptArgsWithTypes? = null,
repeatingMode: ReplRepeatingMode = ReplRepeatingMode.REPEAT_ONLY_MOST_RECENT,
protected val stateLock: ReentrantReadWriteLock = ReentrantReadWriteLock()
baseClassloader: ClassLoader? = Thread.currentThread().contextClassLoader,
private val fallbackScriptArgs: ScriptArgsWithTypes? = null,
repeatingMode: ReplRepeatingMode = ReplRepeatingMode.REPEAT_ONLY_MOST_RECENT
) : 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 {
return stateLock.write {
val compiled = compiler.compile(codeLine, verifyHistory)
override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> = AggregatedReplStageState(compiler.createState(lock), evaluator.createState(lock), lock)
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) {
is ReplCompileResult.Error -> ReplEvalResult.Error.CompileTime(compiled.compiledHistory, compiled.message, compiled.location)
is ReplCompileResult.HistoryMismatch -> ReplEvalResult.HistoryMismatch(compiled.compiledHistory, compiled.lineNo)
is ReplCompileResult.Incomplete -> ReplEvalResult.Incomplete(compiled.compiledHistory)
is ReplCompileResult.Error -> ReplEvalResult.Error.CompileTime(compiled.message, compiled.location)
is ReplCompileResult.HistoryMismatch -> ReplEvalResult.HistoryMismatch(compiled.lineNo)
is ReplCompileResult.Incomplete -> ReplEvalResult.Incomplete()
is ReplCompileResult.CompiledClasses -> {
val result = eval(compiled, scriptArgs, invokeWrapper)
val result = eval(state, compiled, scriptArgs, invokeWrapper)
when (result) {
is ReplEvalResult.Error,
is ReplEvalResult.HistoryMismatch,
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
}
is ReplEvalResult.ValueResult,
@@ -56,49 +65,25 @@ class GenericReplCompilingEvaluator(val compiler: ReplCompiler,
}
}
override fun resetToLine(lineNumber: Int): List<ReplCodeLine> {
stateLock.write {
val removedCompiledLines = compiler.resetToLine(lineNumber)
val removedEvaluatorLines = evaluator.resetToLine(lineNumber)
override fun eval(state: IReplStageState<*>, compileResult: ReplCompileResult.CompiledClasses, scriptArgs: ScriptArgsWithTypes?, invokeWrapper: InvokeWrapper?): ReplEvalResult =
evaluator.eval(state, compileResult, scriptArgs, invokeWrapper)
removedCompiledLines.zip(removedEvaluatorLines).forEach {
if (it.first != it.second) {
throw IllegalStateException("History mismatch when resetting lines")
}
}
override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult = compiler.check(state, codeLine)
return removedCompiledLines
}
}
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)
override fun compileToEvaluable(state: IReplStageState<*>, codeLine: ReplCodeLine, defaultScriptArgs: ScriptArgsWithTypes?): Pair<ReplCompileResult, Evaluable?> {
val compiled = compiler.compile(state, codeLine)
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)
}
}
class DelayedEvaluation(override val compiledCode: ReplCompileResult.CompiledClasses,
private val stateLock: ReentrantReadWriteLock,
class DelayedEvaluation(private val state: IReplStageState<*>,
override val compiledCode: ReplCompileResult.CompiledClasses,
private val evaluator: ReplEvaluator,
private val defaultScriptArgs: ScriptArgsWithTypes?) : Evaluable {
override fun eval(scriptArgs: ScriptArgsWithTypes?, invokeWrapper: InvokeWrapper?): ReplEvalResult {
return stateLock.write { evaluator.eval(compiledCode, scriptArgs ?: defaultScriptArgs, invokeWrapper) }
}
override fun eval(scriptArgs: ScriptArgsWithTypes?, invokeWrapper: InvokeWrapper?): ReplEvalResult =
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 java.io.File
import java.net.URLClassLoader
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
open class GenericReplEvaluator(baseClasspath: Iterable<File>,
baseClassloader: ClassLoader?,
open class GenericReplEvaluator(val baseClasspath: Iterable<File>,
val baseClassloader: ClassLoader? = Thread.currentThread().contextClassLoader,
protected val fallbackScriptArgs: ScriptArgsWithTypes? = null,
protected val repeatingMode: ReplRepeatingMode = ReplRepeatingMode.REPEAT_ONLY_MOST_RECENT,
protected val stateLock: ReentrantReadWriteLock = ReentrantReadWriteLock()
protected val repeatingMode: ReplRepeatingMode = ReplRepeatingMode.REPEAT_ONLY_MOST_RECENT
) : 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> {
return stateLock.write {
evaluatedHistory.resetToLine(lineNumber)
}.map { it.first }
}
// 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>>
// )
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 {
evaluatedHistory.copyValues().lastOrNull()?.classLoader?.listAllUrlsAsFiles()
?: 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,
override fun eval(state: IReplStageState<*>,
compileResult: ReplCompileResult.CompiledClasses,
scriptArgs: ScriptArgsWithTypes?,
invokeWrapper: InvokeWrapper?): ReplEvalResult {
stateLock.write {
val verifyHistory = compileResult.compiledHistory.dropLast(1)
val defaultHistoryActor = HistoryActions(
effectiveHistory = evaluatedHistory.copyValues(),
verify = { line -> evaluatedHistory.firstMismatchingHistory(line) },
addPlaceholder = { line, value -> evaluatedHistory.add(line, value) },
removePlaceholder = { line -> evaluatedHistory.removeLast(line) },
addFinal = { line, value -> evaluatedHistory.add(line, value) },
processClasses = { compiled ->
prependClassLoaderWithNewClasses(evaluatedHistory.copyValues(), compiled)
})
state.lock.write {
val evalState = state.asState<GenericReplEvaluatorState>()
// val verifyHistory = compileResult.state.dropLast(1)
// val defaultHistoryActor = HistoryActions(
// effectiveHistory = evalState.history.map { it.item },
// verify = { line -> evalState.history.firstMismatchingHistory(line) },
// addPlaceholder = { line, value -> evalState.history.add(line, value) },
// removePlaceholder = { line -> evalState.history.removeLast(line) },
// addFinal = { line, value -> evalState.history.add(line, value) },
// processClasses = { compiled ->
// prependClassLoaderWithNewClasses(evalState.history.copyValues(), compiled, )
// })
val historyActor: HistoryActions = when (repeatingMode) {
ReplRepeatingMode.NONE -> defaultHistoryActor
val historyActor = when (repeatingMode) {
ReplRepeatingMode.NONE -> HistoryActionsForNoRepeat(evalState)
ReplRepeatingMode.REPEAT_ONLY_MOST_RECENT -> {
val lastItem = evaluatedHistory.lastItem()
if (lastItem == null || lastItem.first.source != compileResult.compiledCodeLine.source) {
defaultHistoryActor
val lastItem = evalState.history.peek()
if (lastItem == null || lastItem.id != compileResult.lineId) {
HistoryActionsForNoRepeat(evalState)
}
else {
val trimmedHistory = ReplHistory(evaluatedHistory.copyAll().dropLast(1))
HistoryActions(
effectiveHistory = trimmedHistory.copyValues(),
verify = { trimmedHistory.firstMismatchingHistory(it) },
addPlaceholder = { _, _ -> NO_ACTION() },
removePlaceholder = { NO_ACTION_THAT_RETURNS(true) },
addFinal = { line, value ->
evaluatedHistory.removeLast(line)
evaluatedHistory.add(line, value)
},
processClasses = { _ ->
Pair(lastItem.second.classLoader, lastItem.second.klass.java)
})
HistoryActionsForRepeatRecentOnly(evalState)
// val trimmedHistory = ReplHistory(evalState.history.copyAll().dropLast(1))
// HistoryActions
// effectiveHistory = trimmedHistory.copyValues(),
// verify = { trimmedHistory.firstMismatchingHistory(it) },
// addPlaceholder = { _, _ -> NO_ACTION() },
// removePlaceholder = { NO_ACTION_THAT_RETURNS(true) },
// addFinal = { line, value ->
// evalState.history.removeLast(line)
// evalState.history.add(line, value)
// },
// processClasses = { _ ->
// Pair(lastItem.second.classLoader, lastItem.second.klass.java)
// })
}
}
ReplRepeatingMode.REPEAT_ANY_PREVIOUS -> {
if (evaluatedHistory.isEmpty() || !evaluatedHistory.contains(compileResult.compiledCodeLine.source)) {
defaultHistoryActor
val matchingItem = evalState.history.firstOrNull { it.id == compileResult.lineId }
if (matchingItem == null) {
HistoryActionsForNoRepeat(evalState)
}
else {
val historyCopy = evaluatedHistory.copyAll()
val matchingItem = historyCopy.first { it.first.source == compileResult.compiledCodeLine.source }
val trimmedHistory = ReplHistory(evaluatedHistory.copyAll().takeWhile { it != matchingItem })
HistoryActions(
effectiveHistory = trimmedHistory.copyValues(),
verify = { trimmedHistory.firstMismatchingHistory(it) },
addPlaceholder = { _, _ -> NO_ACTION() },
removePlaceholder = { NO_ACTION_THAT_RETURNS(true) },
addFinal = { line, value ->
val extraLines = evaluatedHistory.resetToLine(line)
evaluatedHistory.removeLast(line)
evaluatedHistory.add(line, value)
extraLines.forEach {
evaluatedHistory.add(it.first, it.second)
}
},
processClasses = { _ ->
Pair(matchingItem.second.classLoader, matchingItem.second.klass.java)
})
HistoryActionsForRepeatAny(evalState, matchingItem)
// val historyCopy = evalState.history.copyAll()
// val matchingItem = historyCopy.first { it.first.source == compileResult.compiledCodeLine.source }
// val trimmedHistory = ReplHistory(evalState.history.copyAll().takeWhile { it != matchingItem })
// HistoryActions(
// effectiveHistory = trimmedHistory.copyValues(),
// verify = { trimmedHistory.firstMismatchingHistory(it) },
// addPlaceholder = { _, _ -> NO_ACTION() },
// removePlaceholder = { NO_ACTION_THAT_RETURNS(true) },
// addFinal = { line, value ->
// val extraLines = evalState.history.resetToLine(line)
// evalState.history.removeLast(line)
// evalState.history.add(line, value)
// extraLines.forEach {
// evalState.history.add(it.first, it.second)
// }
// },
// processClasses = { _ ->
// Pair(matchingItem.second.classLoader, matchingItem.second.klass.java)
// })
}
}
}
val firstMismatch = historyActor.verify(verifyHistory)
if (firstMismatch != null) {
return@eval ReplEvalResult.HistoryMismatch(evaluatedHistory.copySources(), firstMismatch)
}
// val firstMismatch = historyActor.firstMismatch(verifyHistory)
// if (firstMismatch != null) {
// return@eval ReplEvalResult.HistoryMismatch(firstMismatch)
// }
val (classLoader, scriptClass) = try {
historyActor.processClasses(compileResult)
}
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
@@ -170,7 +137,7 @@ open class GenericReplEvaluator(baseClasspath: Iterable<File>,
// TODO: try/catch ?
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 =
try {
@@ -178,33 +145,113 @@ open class GenericReplEvaluator(baseClasspath: Iterable<File>,
else scriptInstanceConstructor.newInstance(*constructorArgs)
}
catch (e: Throwable) {
historyActor.removePlaceholder(compileResult.compiledCodeLine)
historyActor.removePlaceholder(compileResult.lineId)
// ignore everything in the stack trace until this constructor call
return@eval ReplEvalResult.Error.Runtime(evaluatedHistory.copySources(),
renderReplStackTrace(e.cause!!, startFromMethodName = "${scriptClass.name}.<init>"), e as? Exception)
return@eval ReplEvalResult.Error.Runtime(renderReplStackTrace(e.cause!!, startFromMethodName = "${scriptClass.name}.<init>"), e as? Exception)
}
historyActor.removePlaceholder(compileResult.compiledCodeLine)
historyActor.addFinal(compileResult.compiledCodeLine, EvalClassWithInstanceAndLoader(scriptClass.kotlin, scriptInstance, classLoader, invokeWrapper))
historyActor.removePlaceholder(compileResult.lineId)
historyActor.addFinal(compileResult.lineId, EvalClassWithInstanceAndLoader(scriptClass.kotlin, scriptInstance, classLoader, invokeWrapper))
val resultField = scriptClass.getDeclaredField(SCRIPT_RESULT_FIELD_NAME).apply { isAccessible = true }
val resultValue: Any? = resultField.get(scriptInstance)
return if (compileResult.hasResult) ReplEvalResult.ValueResult(evaluatedHistory.copySources(), resultValue)
else ReplEvalResult.UnitResult(evaluatedHistory.copySources())
return if (compileResult.hasResult) ReplEvalResult.ValueResult(resultValue)
else ReplEvalResult.UnitResult()
}
}
override val lastEvaluatedScripts: List<EvalHistoryType> get() {
return stateLock.read { evaluatedHistory.copyAll() }
}
companion object {
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 java.io.Reader
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.locks.ReentrantReadWriteLock
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"
// TODO consider additional error handling
var Bindings.kotlinScriptHistory: MutableList<ReplCodeLine>
var Bindings.kotlinScriptState: IReplStageState<*>
@Suppress("UNCHECKED_CAST")
get() = getOrPut(KOTLIN_SCRIPT_HISTORY_BINDINGS_KEY, { arrayListOf<ReplCodeLine>() }) as MutableList<ReplCodeLine>
set(v) { put(KOTLIN_SCRIPT_HISTORY_BINDINGS_KEY, v) }
get() = get(KOTLIN_SCRIPT_STATE_BINDINGS_KEY) as IReplStageState<*>
set(v) { put(KOTLIN_SCRIPT_STATE_BINDINGS_KEY, v) }
val Bindings.kotlinScriptLineNumber: AtomicInteger
@Suppress("UNCHECKED_CAST")
@@ -36,8 +37,8 @@ val Bindings.kotlinScriptLineNumber: AtomicInteger
abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEngineFactory) : AbstractScriptEngine(), ScriptEngine, Compilable {
protected abstract val replCompiler: ReplCompileAction
protected abstract val replScriptEvaluator: ReplFullEvaluator
protected abstract val replCompiler: ReplCompiler
protected abstract val replEvaluator: ReplFullEvaluator
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
// 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>) {
this.context.getBindings(ScriptContext.ENGINE_SCOPE).kotlinScriptHistory = history
}
private fun getCurrentState(context: ScriptContext) = context.getBindings(ScriptContext.ENGINE_SCOPE).getOrPut(KOTLIN_SCRIPT_STATE_BINDINGS_KEY, { replEvaluator.createState() }) as IReplStageState<*>
open fun overrideScriptArgs(context: ScriptContext): ScriptArgsWithTypes? = null
open fun compileAndEval(script: String, context: ScriptContext): Any? {
val codeLine = nextCodeLine(context, script)
val history = getCurrentHistory(context)
val result = replScriptEvaluator.compileAndEval(codeLine, scriptArgs = overrideScriptArgs(context), verifyHistory = history)
val state = getCurrentState(context)
val result = replEvaluator.compileAndEval(state, codeLine, scriptArgs = overrideScriptArgs(context))
val ret = when (result) {
is ReplEvalResult.ValueResult -> result.value
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.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${result.lineNo}")
}
setContextHistory(context, ArrayList(result.completedEvalHistory))
return ret
}
open fun compile(script: String, context: ScriptContext): CompiledScript {
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) {
is ReplCompileResult.Error -> throw ScriptException("Error${result.locationString()}: ${result.message}")
is ReplCompileResult.Incomplete -> throw ScriptException("error: incomplete code")
is ReplCompileResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${result.lineNo}")
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)
}
open fun eval(compiledScript: CompiledKotlinScript, context: ScriptContext): Any? {
val state = getCurrentState(context)
val result = try {
replScriptEvaluator.eval(compiledScript.compiledData, scriptArgs = overrideScriptArgs(context))
replEvaluator.eval(state, compiledScript.compiledData, scriptArgs = overrideScriptArgs(context))
}
catch (e: Exception) {
throw ScriptException(e)
@@ -108,7 +105,6 @@ abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEn
is ReplEvalResult.Incomplete -> throw ScriptException("error: incomplete code")
is ReplEvalResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${result.lineNo}")
}
setContextHistory(context, ArrayList(result.completedEvalHistory))
return ret
}
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
import java.io.File
import java.io.Serializable
import java.util.*
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.reflect.KClass
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 {
fun check(codeLine: ReplCodeLine): ReplCheckResult
fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult
}
sealed class ReplCheckResult : Serializable {
@@ -63,40 +69,25 @@ sealed class ReplCheckResult : Serializable {
}
}
interface ReplResettableCodeLine {
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>
}
// --- compile
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 {
class CompiledClasses(compiledHistory: List<ReplCodeLine>,
val compiledCodeLine: CompiledReplCodeLine,
val generatedClassname: String,
sealed class ReplCompileResult : Serializable {
class CompiledClasses(val lineId: LineId,
val mainClassName: String,
val classes: List<CompiledClassData>,
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>,
val message: String,
val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION) : ReplCompileResult(compiledHistory) {
class Error(val message: String,
val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION) : ReplCompileResult() {
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>
interface ReplEvaluatorExposedInternalHistory {
val lastEvaluatedScripts: List<EvalHistoryType>
}
interface ReplClasspath {
val currentClasspath: List<File>
}
// --- eval
data class EvalClassWithInstanceAndLoader(val klass: KClass<*>, val instance: Any?, val classLoader: ClassLoader, val invokeWrapper: InvokeWrapper?)
interface ReplEvalAction {
fun eval(compileResult: ReplCompileResult.CompiledClasses,
fun eval(state: IReplStageState<*>,
compileResult: ReplCompileResult.CompiledClasses,
scriptArgs: ScriptArgsWithTypes? = null,
invokeWrapper: InvokeWrapper? = null): ReplEvalResult
}
sealed class ReplEvalResult(val completedEvalHistory: List<ReplCodeLine>) : Serializable {
class ValueResult(completedEvalHistory: List<ReplCodeLine>, val value: Any?) : ReplEvalResult(completedEvalHistory) {
sealed class ReplEvalResult : Serializable {
class ValueResult(val value: Any?) : ReplEvalResult() {
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) {
class Runtime(completedEvalHistory: List<ReplCodeLine>, message: String, val cause: Exception? = null) : Error(completedEvalHistory, message)
sealed class Error(val message: String) : ReplEvalResult() {
class Runtime(message: String, val cause: Exception? = null) : Error(message)
class CompileTime(completedEvalHistory: List<ReplCodeLine>,
message: String,
val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION) : Error(completedEvalHistory, message)
class CompileTime(message: String,
val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION) : Error(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 {
fun compileAndEval(codeLine: ReplCodeLine,
fun compileAndEval(state: IReplStageState<*>,
codeLine: ReplCodeLine,
scriptArgs: ScriptArgsWithTypes? = null,
verifyHistory: List<ReplCodeLine>? = null,
invokeWrapper: InvokeWrapper? = null): ReplEvalResult
}
interface ReplAtomicEvaluator : ReplResettableCodeLine, ReplCombinedHistory, ReplEvaluatorExposedInternalHistory, ReplAtomicEvalAction, ReplCheckAction, ReplClasspath
interface ReplAtomicEvaluator : ReplAtomicEvalAction, ReplCheckAction
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 {
val compiledCode: ReplCompileResult.CompiledClasses
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
*/
class ScriptArgsWithTypes(val scriptArgs: Array<out Any?>, val scriptArgsTypes: Array<out KClass<out Any>>) : Serializable {
init { assert(scriptArgs.size == scriptArgsTypes.size) }
companion object {
private val serialVersionUID: Long = 8529357500L
}
}
interface ScriptTemplateEmptyArgsProvider {
val defaultEmptyArgs: ScriptArgsWithTypes?
}
class SimpleScriptTemplateEmptyArgsProvider(override val defaultEmptyArgs: ScriptArgsWithTypes? = null) : ScriptTemplateEmptyArgsProvider
enum class ReplRepeatingMode {
NONE,
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.config.CompilerConfiguration
import org.jetbrains.kotlin.script.KotlinScriptDefinition
import java.io.File
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.locks.ReentrantReadWriteLock
open class GenericRepl protected constructor(
@@ -32,56 +30,22 @@ open class GenericRepl protected constructor(
compilerConfiguration: CompilerConfiguration,
messageCollector: MessageCollector,
baseClassloader: ClassLoader?,
protected val fallbackScriptArgs: ScriptArgsWithTypes?,
protected val repeatingMode: ReplRepeatingMode = ReplRepeatingMode.NONE,
protected val stateLock: ReentrantReadWriteLock
protected val fallbackScriptArgs: ScriptArgsWithTypes? = null,
protected val repeatingMode: ReplRepeatingMode = ReplRepeatingMode.NONE
) : 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 evaluator: ReplFullEvaluator by lazy { GenericReplCompilingEvaluator(compiler, compilerConfiguration.jvmClasspathRoots, baseClassloader, fallbackScriptArgs, repeatingMode, stateLock) }
protected var codeLineNumber = AtomicInteger(0)
protected val compiler: ReplCompiler by lazy { GenericReplCompiler(disposable, scriptDefinition, compilerConfiguration, messageCollector) }
protected val evaluator: ReplFullEvaluator by lazy { GenericReplCompilingEvaluator(compiler, compilerConfiguration.jvmClasspathRoots, baseClassloader, fallbackScriptArgs, repeatingMode) }
override fun resetToLine(lineNumber: Int): List<ReplCodeLine> {
return evaluator.resetToLine(lineNumber)
}
override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> = AggregatedReplStageState(compiler.createState(lock), evaluator.createState(lock), lock)
override fun resetToLine(line: ReplCodeLine): List<ReplCodeLine> {
return evaluator.resetToLine(line)
}
override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult = compiler.check(state, codeLine)
override val history: List<ReplCodeLine> get() = evaluator.history
override val compiledHistory: List<ReplCodeLine> get() = compiler.history
override val evaluatedHistory: List<ReplCodeLine> get() = evaluator.history
override fun compile(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCompileResult = compiler.compile(state, codeLine)
override val currentClasspath: List<File>
get() = evaluator.currentClasspath
override fun eval(state: IReplStageState<*>, compileResult: ReplCompileResult.CompiledClasses, scriptArgs: ScriptArgsWithTypes?, invokeWrapper: InvokeWrapper?): ReplEvalResult =
evaluator.eval(state, compileResult, scriptArgs, invokeWrapper)
override val lastEvaluatedScripts: List<EvalHistoryType>
get() = evaluator.lastEvaluatedScripts
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)
override fun compileAndEval(state: IReplStageState<*>, codeLine: ReplCodeLine, scriptArgs: ScriptArgsWithTypes?, invokeWrapper: InvokeWrapper?): ReplEvalResult =
evaluator.compileAndEval(state, codeLine, scriptArgs, invokeWrapper)
}
@@ -26,22 +26,18 @@ import com.intellij.testFramework.LightVirtualFile
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.repl.ReplCheckResult
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
import org.jetbrains.kotlin.cli.common.repl.makeScriptBaseName
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
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.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.parsing.KotlinParserDefinition
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.script.KotlinScriptDefinition
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
const val KOTLIN_REPL_JVM_TARGET_PROPERTY = "kotlin.repl.jvm.target"
@@ -50,9 +46,9 @@ open class GenericReplChecker(
disposable: Disposable,
val scriptDefinition: KotlinScriptDefinition,
val compilerConfiguration: CompilerConfiguration,
messageCollector: MessageCollector,
protected val stateLock: ReentrantReadWriteLock = ReentrantReadWriteLock()
) {
messageCollector: MessageCollector
) : ReplCheckAction {
internal val environment = run {
compilerConfiguration.apply {
add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, scriptDefinition)
@@ -70,21 +66,12 @@ open class GenericReplChecker(
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 class LineState(
val codeLine: ReplCodeLine,
val psiFile: KtFile,
val errorHolder: DiagnosticMessageHolder)
internal fun createDiagnosticHolder() = ReplTerminalDiagnosticMessageHolder()
private var _lineState: LineState? = null
internal val lineState: LineState? get() = stateLock.read { _lineState }
fun createDiagnosticHolder() = ReplTerminalDiagnosticMessageHolder()
fun check(codeLine: ReplCodeLine, generation: Long): ReplCheckResult {
stateLock.write {
val scriptFileName = makeScriptBaseName(codeLine, generation)
override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult {
state.lock.write {
val checkerState = state.asState<GenericReplCheckerState>()
val scriptFileName = makeScriptBaseName(codeLine, checkerState.generation.get())
val virtualFile =
LightVirtualFile("$scriptFileName${KotlinParserDefinition.STD_SCRIPT_EXT}", KotlinLanguage.INSTANCE, StringUtil.convertLineSeparators(codeLine.code)).apply {
charset = CharsetToolkit.UTF8_CHARSET
@@ -97,7 +84,7 @@ open class GenericReplChecker(
val syntaxErrorReport = AnalyzerWithCompilerReport.reportSyntaxErrors(psiFile, errorHolder)
if (!syntaxErrorReport.isHasErrors) {
_lineState = LineState(codeLine, psiFile, errorHolder)
checkerState.lastLineState = GenericReplCheckerState.LineState(codeLine, psiFile, errorHolder)
}
return when {
@@ -26,115 +26,83 @@ import org.jetbrains.kotlin.codegen.ClassBuilderFactories
import org.jetbrains.kotlin.codegen.KotlinCodegenFacade
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.script.KotlinScriptDefinition
import org.jetbrains.kotlin.script.KotlinScriptExternalDependencies
import java.io.File
import java.util.concurrent.atomic.AtomicLong
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
// WARNING: not thread safe, assuming external synchronization
open class GenericReplCompiler(disposable: Disposable,
protected val scriptDefinition: KotlinScriptDefinition,
protected val compilerConfiguration: CompilerConfiguration,
messageCollector: MessageCollector,
protected val stateLock: ReentrantReadWriteLock = ReentrantReadWriteLock()) : ReplCompiler {
private val checker = GenericReplChecker(disposable, scriptDefinition, compilerConfiguration, messageCollector, stateLock)
messageCollector: MessageCollector
) : ReplCompiler {
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> {
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 currentGeneration = compilerState.generation.get()
val (psiFile, errorHolder) = run {
if (checker.lineState == null || checker.lineState!!.codeLine != codeLine) {
val res = checker.check(codeLine, currentGeneration)
if (compilerState.lastLineState == null || compilerState.lastLineState!!.codeLine != codeLine) {
val res = checker.check(state, codeLine)
when (res) {
is ReplCheckResult.Incomplete -> return@compile ReplCompileResult.Incomplete(descriptorsHistory.copySources())
is ReplCheckResult.Error -> return@compile ReplCompileResult.Error(descriptorsHistory.copySources(), res.message, res.location)
is ReplCheckResult.Incomplete -> return@compile ReplCompileResult.Incomplete()
is ReplCheckResult.Error -> return@compile ReplCompileResult.Error(res.message, res.location)
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
if (lastDependencies != newDependencies) {
lastDependencies = newDependencies
if (compilerState.lastDependencies != newDependencies) {
compilerState.lastDependencies = newDependencies
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)
val scriptDescriptor = when (analysisResult) {
is GenericReplAnalyzer.ReplLineAnalysisResult.WithErrors -> return ReplCompileResult.Error(descriptorsHistory.copySources(), errorHolder.renderedDiagnostics)
is GenericReplAnalyzer.ReplLineAnalysisResult.Successful -> analysisResult.scriptDescriptor
is ReplCodeAnalyzer.ReplLineAnalysisResult.WithErrors -> return ReplCompileResult.Error(errorHolder.renderedDiagnostics)
is ReplCodeAnalyzer.ReplLineAnalysisResult.Successful -> analysisResult.scriptDescriptor
else -> error("Unexpected result ${analysisResult.javaClass}")
}
val state = GenerationState(
val generationState = GenerationState(
psiFile.project,
ClassBuilderFactories.binaries(false),
analyzerEngine.module,
analyzerEngine.trace.bindingContext,
compilerState.analyzerEngine.module,
compilerState.analyzerEngine.trace.bindingContext,
listOf(psiFile),
compilerConfiguration
)
state.replSpecific.scriptResultFieldName = SCRIPT_RESULT_FIELD_NAME
state.replSpecific.earlierScriptsForReplInterpreter = descriptorsHistory.copyValues()
state.beforeCompile()
generationState.replSpecific.scriptResultFieldName = SCRIPT_RESULT_FIELD_NAME
generationState.replSpecific.earlierScriptsForReplInterpreter = compilerState.history.map { it.item }
generationState.beforeCompile()
KotlinCodegenFacade.generatePackage(
state,
psiFile.script!!.getContainingKtFile().packageFqName,
setOf(psiFile.script!!.getContainingKtFile()),
generationState,
psiFile.script!!.containingKtFile.packageFqName,
setOf(psiFile.script!!.containingKtFile),
org.jetbrains.kotlin.codegen.CompilationErrorHandler.THROW_EXCEPTION)
val generatedClassname = makeScriptBaseName(codeLine, currentGeneration)
val compiledCodeLine = CompiledReplCodeLine(generatedClassname, codeLine)
descriptorsHistory.add(compiledCodeLine, scriptDescriptor)
compilerState.history.push(LineId(codeLine), scriptDescriptor)
return ReplCompileResult.CompiledClasses(descriptorsHistory.copySources(),
compiledCodeLine,
return ReplCompileResult.CompiledClasses(LineId(codeLine),
generatedClassname,
state.factory.asList().map { CompiledClassData(it.relativePath, it.asByteArray()) },
state.replSpecific.hasResult,
classpathAddendum ?: emptyList())
generationState.factory.asList().map { CompiledClassData(it.relativePath, it.asByteArray()) },
generationState.replSpecific.hasResult,
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.ReplCodeLine
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.JvmPackagePartProvider
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.replaceImportingScopes
import org.jetbrains.kotlin.script.ScriptPriorities
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
class GenericReplAnalyzer(environment: KotlinCoreEnvironment,
protected val stateLock: ReentrantReadWriteLock = ReentrantReadWriteLock()) : ReplResettableCodeLine {
class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) {
private val topDownAnalysisContext: TopDownAnalysisContext
private val topDownAnalyzer: LazyTopDownAnalyzer
private val resolveSession: ResolveSession
private val scriptDeclarationFactory: ScriptMutableDeclarationProviderFactory
private val replState = ResettableReplState()
private val replState = ResettableAnalyzerState()
val module: ModuleDescriptorImpl
get() = stateLock.read { field }
val trace: BindingTraceContext = CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace()
get() = stateLock.read { field }
init {
// 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> {
return stateLock.write { replState.resetToLine(lineNumber) }
}
override fun resetToLine(line: ReplCodeLine): List<ReplCodeLine> = resetToLine(line.no)
fun resetToLine(lineNumber: Int): List<ReplCodeLine> = replState.resetToLine(lineNumber)
fun analyzeReplLine(psiFile: KtFile, codeLine: ReplCodeLine): ReplLineAnalysisResult {
return stateLock.write {
topDownAnalysisContext.scripts.clear()
trace.clearDiagnostics()
topDownAnalysisContext.scripts.clear()
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 {
@@ -179,7 +167,8 @@ class GenericReplAnalyzer(environment: KotlinCoreEnvironment,
}
// 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 submittedLines = hashMapOf<KtFile, LineInfo>()
@@ -189,8 +178,6 @@ class GenericReplAnalyzer(environment: KotlinCoreEnvironment,
return removed.map { it.first }
}
fun resetToLine(line: ReplCodeLine): List<ReplCodeLine> = resetToLine(line.no)
fun submitLine(ktFile: KtFile, codeLine: ReplCodeLine) {
val line = LineInfo.SubmittedLine(ktFile, successfulLines.lastValue())
submittedLines[ktFile] = line
@@ -64,7 +64,7 @@ class ReplInterpreter(
}
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 {
++lineNumber
@@ -101,8 +101,8 @@ class ReplInterpreter(
val analysisResult = analyzerEngine.analyzeReplLine(psiFile, ReplCodeLine(lineNumber, "fake line"))
AnalyzerWithCompilerReport.reportDiagnostics(analysisResult.diagnostics, errorHolder)
val scriptDescriptor = when (analysisResult) {
is GenericReplAnalyzer.ReplLineAnalysisResult.WithErrors -> return LineResult.Error.CompileTime(errorHolder.renderedDiagnostics)
is GenericReplAnalyzer.ReplLineAnalysisResult.Successful -> analysisResult.scriptDescriptor
is ReplCodeAnalyzer.ReplLineAnalysisResult.WithErrors -> return LineResult.Error.CompileTime(errorHolder.renderedDiagnostics)
is ReplCodeAnalyzer.ReplLineAnalysisResult.Successful -> analysisResult.scriptDescriptor
else -> error("Unexpected result ${analysisResult.javaClass}")
}
@@ -68,5 +68,5 @@ class KotlinJsr223JvmScriptEngine4Idea(
val localEvaluator: ReplFullEvaluator by lazy { GenericReplCompilingEvaluator(replCompiler, templateClasspath, Thread.currentThread().contextClassLoader) }
override val replScriptEvaluator: ReplFullEvaluator get() = localEvaluator
override val replEvaluator: ReplFullEvaluator get() = localEvaluator
}