Reintroduce history check between compile and eval, place generation into code line and id

This commit is contained in:
Ilya Chernikov
2017-02-07 12:35:04 +01:00
parent 7b2ea001c1
commit b23911fd59
16 changed files with 82 additions and 89 deletions
@@ -80,5 +80,9 @@ open class AggregatedReplStageState<T1, T2>(val state1: IReplStageState<T1>, val
target.isAssignableFrom(state2::class.java) -> state2 as StateT
else -> super.asState(target)
}
override fun getNextLineNo() = state1.getNextLineNo()
override val currentGeneration: Int get() = state1.currentGeneration
}
@@ -18,18 +18,19 @@ package org.jetbrains.kotlin.cli.common.repl
import java.io.Serializable
import java.util.*
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.write
data class LineId(override val no: Int, private val codeHash: Int) : ILineId, Serializable {
data class LineId(override val no: Int, override val generation: 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())
constructor(no: Int, generation: Int, code: String): this(no, generation, code.hashCode())
constructor(codeLine: ReplCodeLine): this(codeLine.no, codeLine.generation, 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
no.compareTo(it.no).takeIf { it != 0 }
?: generation.compareTo(it.generation).takeIf { it != 0 }
?: codeHash.compareTo(it.codeHash)
} ?: -1 // TODO: check if it doesn't break something
companion object {
@@ -39,6 +40,8 @@ data class LineId(override val no: Int, private val codeHash: Int) : ILineId, Se
open class BasicReplStageHistory<T>(override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()) : IReplStageHistory<T>, ArrayList<ReplHistoryRecord<T>>() {
val currentGeneration = AtomicInteger(REPL_CODE_LINE_FIRST_GEN)
override fun push(id: ILineId, item: T) {
lock.write {
add(ReplHistoryRecord(id, item))
@@ -54,6 +57,7 @@ open class BasicReplStageHistory<T>(override val lock: ReentrantReadWriteLock =
if (idx < lastIndex) {
val removed = asSequence().drop(idx + 1).map { it.id }.toList()
removeRange(idx + 1, lastIndex)
currentGeneration.incrementAndGet()
return removed
}
else return emptyList()
@@ -61,7 +65,9 @@ open class BasicReplStageHistory<T>(override val lock: ReentrantReadWriteLock =
}
}
class BasicReplStageState<HistoryItemT>: IReplStageState<HistoryItemT> {
open class BasicReplStageState<HistoryItemT>: IReplStageState<HistoryItemT> {
override val currentGeneration: Int get() = history.currentGeneration.get()
override val lock = ReentrantReadWriteLock()
@@ -26,6 +26,8 @@ open class GenericReplEvaluatorState(baseClasspath: Iterable<File>, baseClassloa
{
override val history: IReplStageHistory<EvalClassWithInstanceAndLoader> = BasicReplStageHistory(lock)
override val currentGeneration: Int get() = (history as BasicReplStageHistory<*>).currentGeneration.get()
val topClassLoader: ReplClassLoader = makeReplClassLoader(baseClassloader, baseClasspath)
val currentClasspath: List<File> get() = lock.read {
@@ -36,7 +36,6 @@ class GenericReplCompilingEvaluator(val compiler: ReplCompiler,
val compiled = compiler.compile(state, codeLine)
when (compiled) {
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(state, compiled, scriptArgs, invokeWrapper)
@@ -113,10 +113,10 @@ open class GenericReplEvaluator(val baseClasspath: Iterable<File>,
}
}
// val firstMismatch = historyActor.firstMismatch(verifyHistory)
// if (firstMismatch != null) {
// return@eval ReplEvalResult.HistoryMismatch(firstMismatch)
// }
val firstMismatch = historyActor.firstMismatch(compileResult.previousLines.asSequence())
if (firstMismatch != null) {
return@eval ReplEvalResult.HistoryMismatch(firstMismatch.first?.id?.no ?: firstMismatch.second?.no ?: -1 /* means error? */)
}
val (classLoader, scriptClass) = try {
historyActor.processClasses(compileResult)
@@ -172,7 +172,7 @@ private open class HistoryActionsForNoRepeat(val state: GenericReplEvaluatorStat
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 firstMismatch(other: Sequence<ILineId>): Pair<ReplHistoryRecord<EvalClassWithInstanceAndLoader>?, ILineId?>? = state.history.firstMismatch(other)
open fun addPlaceholder(lineId: ILineId, value: EvalClassWithInstanceAndLoader) { state.history.push(lineId, value) }
@@ -215,7 +215,7 @@ private open class HistoryActionsForRepeatRecentOnly(state: GenericReplEvaluator
override val effectiveHistory: List<EvalClassWithInstanceAndLoader> get() = super.effectiveHistory.dropLast(1)
override fun<OtherT> firstMismatch(other: IReplStageHistory<OtherT>): Pair<ReplHistoryRecord<EvalClassWithInstanceAndLoader>?, ReplHistoryRecord<OtherT>?>? =
override fun firstMismatch(other: Sequence<ILineId>): Pair<ReplHistoryRecord<EvalClassWithInstanceAndLoader>?, ILineId?>? =
state.history.firstMismatchFiltered(other) { it.id != currentLast.id }
override fun addPlaceholder(lineId: ILineId, value: EvalClassWithInstanceAndLoader) {}
@@ -235,7 +235,7 @@ private open class HistoryActionsForRepeatAny(state: GenericReplEvaluatorState,
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>?>? =
override fun firstMismatch(other: Sequence<ILineId>): Pair<ReplHistoryRecord<EvalClassWithInstanceAndLoader>?, ILineId?>? =
state.history.firstMismatchWhile(other) { it.id != matchingLine.id }
override fun addPlaceholder(lineId: ILineId, value: EvalClassWithInstanceAndLoader) {}
@@ -23,17 +23,6 @@ import java.util.concurrent.locks.ReentrantReadWriteLock
import javax.script.*
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.kotlinScriptState: IReplStageState<*>
@Suppress("UNCHECKED_CAST")
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")
get() = getOrPut(KOTLIN_SCRIPT_LINE_NUMBER_BINDINGS_KEY, { AtomicInteger(0) }) as AtomicInteger
abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEngineFactory) : AbstractScriptEngine(), ScriptEngine, Compilable {
@@ -53,7 +42,7 @@ 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(context: ScriptContext, code: String) = ReplCodeLine(context.getBindings(ScriptContext.ENGINE_SCOPE).kotlinScriptLineNumber.incrementAndGet(), code)
fun nextCodeLine(context: ScriptContext, code: String) = getCurrentState(context).let { ReplCodeLine(it.getNextLineNo(), it.currentGeneration, code) }
protected open fun createState(lock: ReentrantReadWriteLock = ReentrantReadWriteLock()): IReplStageState<*> = AggregatedReplStageState(replCompiler.createState(lock), replEvaluator.createState(lock), lock)
@@ -83,7 +72,6 @@ abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEn
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
}
return CompiledKotlinScript(this, codeLine, compiled)
@@ -23,7 +23,10 @@ import java.util.*
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.reflect.KClass
data class ReplCodeLine(val no: Int, val code: String) : Serializable {
const val REPL_CODE_LINE_FIRST_NO = 1
const val REPL_CODE_LINE_FIRST_GEN = 1
data class ReplCodeLine(val no: Int, val generation: Int, val code: String) : Serializable {
companion object {
private val serialVersionUID: Long = 8228357578L
}
@@ -77,6 +80,7 @@ interface ReplCompileAction {
sealed class ReplCompileResult : Serializable {
class CompiledClasses(val lineId: LineId,
val previousLines: List<ILineId>,
val mainClassName: String,
val classes: List<CompiledClassData>,
val hasResult: Boolean,
@@ -84,8 +88,6 @@ sealed class ReplCompileResult : Serializable {
class Incomplete : ReplCompileResult()
class HistoryMismatch(val lineNo: Int) : ReplCompileResult()
class Error(val message: String,
val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION) : ReplCompileResult() {
override fun toString(): String = "Error(message = \"$message\""
@@ -23,14 +23,13 @@ import kotlin.concurrent.write
interface ILineId : Comparable<ILineId> {
val no: Int
val generation: 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)
@@ -44,31 +43,6 @@ interface IReplStageHistory<T> : List<ReplHistoryRecord<T>> {
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
}
@@ -77,9 +51,28 @@ interface IReplStageState<T> {
val lock: ReentrantReadWriteLock
val currentGeneration: Int
fun getNextLineNo(): Int = history.peek()?.id?.no?.let { it + 1 } ?: REPL_CODE_LINE_FIRST_NO // TODO: it should be more robust downstream (e.g. use atomic)
fun <StateT : IReplStageState<*>> asState(target: Class<out StateT>): StateT =
if (target.isAssignableFrom(this::class.java)) this as StateT
else throw IllegalArgumentException("$this is not an expected instance of IReplStageState")
}
fun <T> IReplStageHistory<T>.firstMismatch(other: Sequence<ILineId>): Pair<ReplHistoryRecord<T>?, ILineId?>? =
lock.read {
iterator().asSequence().zip(other.asSequence()).firstOrNull { it.first.id != it.second }?.let { it.first to it.second }
}
fun<T> IReplStageHistory<T>.firstMismatchFiltered(other: Sequence<ILineId>, predicate: (ReplHistoryRecord<T>) -> Boolean): Pair<ReplHistoryRecord<T>?, ILineId?>? =
lock.read {
iterator().asSequence().filter(predicate).zip(other.asSequence()).firstOrNull { it.first.id != it.second }?.let { it.first to it.second }
}
fun<T> IReplStageHistory<T>.firstMismatchWhile(other: Sequence<ILineId>, predicate: (ReplHistoryRecord<T>) -> Boolean): Pair<ReplHistoryRecord<T>?, ILineId?>? =
lock.read {
iterator().asSequence().takeWhile(predicate).zip(other.asSequence()).firstOrNull { it.first.id != it.second }?.let { it.first to it.second }
}
@@ -20,8 +20,8 @@ import com.google.common.base.Throwables
import java.io.File
import java.net.URLClassLoader
fun makeScriptBaseName(codeLine: ReplCodeLine, generation: Long) =
"Line_${codeLine.no}${if (generation > 1) "_gen_$generation" else ""}"
fun makeScriptBaseName(codeLine: ReplCodeLine) =
"Line_${codeLine.no}${if (codeLine.generation > REPL_CODE_LINE_FIRST_GEN) "_gen_${codeLine.generation}" else ""}"
fun renderReplStackTrace(cause: Throwable, startFromMethodName: String): String {
val newTrace = arrayListOf<StackTraceElement>()