From f9dedab8c8b817da088d15bc2502e3a1d80a0ca1 Mon Sep 17 00:00:00 2001 From: Ilya Chernikov Date: Fri, 3 Feb 2017 18:14:03 +0100 Subject: [PATCH] Extract repl state as a separate object --- .../cli/common/repl/AggregatedReplState.kt | 79 +++++ .../kotlin/cli/common/repl/BasicReplState.kt | 69 +++++ .../cli/common/repl/GenericEvaluatorState.kt | 38 +++ .../repl/GenericReplCompilingEvaluator.kt | 83 ++--- .../cli/common/repl/GenericReplEvaluator.kt | 291 ++++++++++-------- .../repl/KotlinJsr223JvmScriptEngineBase.kt | 36 +-- .../kotlin/cli/common/repl/ReplApi.kt | 102 +++--- .../kotlin/cli/common/repl/ReplState.kt | 83 +++++ .../cli/jvm/repl/GenericCompilerState.kt | 63 ++++ .../kotlin/cli/jvm/repl/GenericRepl.kt | 58 +--- .../kotlin/cli/jvm/repl/GenericReplChecker.kt | 35 +-- .../cli/jvm/repl/GenericReplCompiler.kt | 104 +++---- ...ricReplAnalyzer.kt => ReplCodeAnalyzer.kt} | 33 +- .../kotlin/cli/jvm/repl/ReplInterpreter.kt | 6 +- .../KotlinJsr223JvmScriptEngine4Idea.kt | 2 +- 15 files changed, 666 insertions(+), 416 deletions(-) create mode 100644 compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/AggregatedReplState.kt create mode 100644 compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/BasicReplState.kt create mode 100644 compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/GenericEvaluatorState.kt create mode 100644 compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/ReplState.kt create mode 100644 compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericCompilerState.kt rename compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/{GenericReplAnalyzer.kt => ReplCodeAnalyzer.kt} (90%) diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/AggregatedReplState.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/AggregatedReplState.kt new file mode 100644 index 00000000000..c8c3a6ff5b5 --- /dev/null +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/AggregatedReplState.kt @@ -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(val history1: IReplStageHistory, val history2: IReplStageHistory, override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()) + : IReplStageHistory>, AbstractList>>() +{ + override val size: Int + get() = minOf(history1.size, history2.size) + + override fun push(id: ILineId, item: Pair) { + lock.write { + assertSameSize() + history1.push(id, item.first) + history2.push(id, item.second) + } + } + + override fun get(index: Int): ReplHistoryRecord> = 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>? = 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 = 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, r2: ReplHistoryRecord) { + if (r1.id != r2.id) throw IllegalStateException("Aggregated history mismatch: ${r1.id} != ${r2.id}") + } +} + +open class AggregatedReplStageState(val state1: IReplStageState, val state2: IReplStageState, override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()) + : IReplStageState> +{ + override val history: IReplStageHistory> = AggregatedReplStateHistory(state1.history, state2.history, lock) + + override fun > asState(): StateT = (state1 as? StateT) ?: (state2 as StateT) ?: super.asState() +} + diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/BasicReplState.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/BasicReplState.kt new file mode 100644 index 00000000000..a93a8ac686d --- /dev/null +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/BasicReplState.kt @@ -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(override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()) : IReplStageHistory, ArrayList>() { + + override fun push(id: ILineId, item: T) { + lock.write { + add(ReplHistoryRecord(id, item)) + } + } + + override fun pop(): ReplHistoryRecord? = lock.write { if (isEmpty()) null else removeAt(lastIndex) } + + override fun resetTo(id: ILineId): Iterable { + 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: IReplStageState { + + override val lock = ReentrantReadWriteLock() + + override val history: BasicReplStageHistory = BasicReplStageHistory(lock) +} diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/GenericEvaluatorState.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/GenericEvaluatorState.kt new file mode 100644 index 00000000000..4aa677e333e --- /dev/null +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/GenericEvaluatorState.kt @@ -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, baseClassloader: ClassLoader?, override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()) + : IReplStageState +{ + override val history: IReplStageHistory = BasicReplStageHistory(lock) + + val topClassLoader: ReplClassLoader = makeReplClassLoader(baseClassloader, baseClasspath) + + val currentClasspath: List get() = lock.read { + history.peek()?.item?.classLoader?.listAllUrlsAsFiles() + ?: topClassLoader.listAllUrlsAsFiles() + } +} + +internal fun makeReplClassLoader(baseClassloader: ClassLoader?, baseClasspath: Iterable) = + ReplClassLoader(URLClassLoader(baseClasspath.map { it.toURI().toURL() }.toTypedArray(), baseClassloader)) diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/GenericReplCompilingEvaluator.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/GenericReplCompilingEvaluator.kt index 1d9d6487d61..92d22a939a0 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/GenericReplCompilingEvaluator.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/GenericReplCompilingEvaluator.kt @@ -22,27 +22,36 @@ import kotlin.concurrent.write class GenericReplCompilingEvaluator(val compiler: ReplCompiler, baseClasspath: Iterable, - 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?, 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>() + 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 { - 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 = resetToLine(line.no) - - override val lastEvaluatedScripts: List get() = evaluator.lastEvaluatedScripts - override val history: List get() = evaluator.history - override val currentClasspath: List get() = evaluator.currentClasspath - - override val compiledHistory: List get() = compiler.history - override val evaluatedHistory: List 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?): Pair { - val compiled = compiler.compile(codeLine, verifyHistory) + override fun compileToEvaluable(state: IReplStageState<*>, codeLine: ReplCodeLine, defaultScriptArgs: ScriptArgsWithTypes?): Pair { + 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) } -} \ No newline at end of file +} diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/GenericReplEvaluator.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/GenericReplEvaluator.kt index 778e2495c01..b07c00a756b 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/GenericReplEvaluator.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/GenericReplEvaluator.kt @@ -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, - baseClassloader: ClassLoader?, +open class GenericReplEvaluator(val baseClasspath: Iterable, + 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() - private val evaluatedHistory = ReplHistory() +// override val history: List get() = stateLock.read { evaluatedHistory.copySources() } - override fun resetToLine(lineNumber: Int): List { - return stateLock.write { - evaluatedHistory.resetToLine(lineNumber) - }.map { it.first } - } +// private class HistoryActions( +// val effectiveHistory: List, +// 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> +// ) - override val history: List get() = stateLock.read { evaluatedHistory.copySources() } + override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> = GenericReplEvaluatorState(baseClasspath, baseClassloader, lock) - override val currentClasspath: List get() = stateLock.read { - evaluatedHistory.copyValues().lastOrNull()?.classLoader?.listAllUrlsAsFiles() - ?: topClassLoader.listAllUrlsAsFiles() - } - - private class HistoryActions(val effectiveHistory: List, - 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>) - - private fun prependClassLoaderWithNewClasses(effectiveHistory: List, compileResult: ReplCompileResult.CompiledClasses): Pair> { - 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() +// 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, // 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, 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}."), e as? Exception) + return@eval ReplEvalResult.Error.Runtime(renderReplStackTrace(e.cause!!, startFromMethodName = "${scriptClass.name}."), 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 get() { - return stateLock.read { evaluatedHistory.copyAll() } - } - companion object { private val SCRIPT_RESULT_FIELD_NAME = "\$\$result" } - private fun makeReplClassLoader(baseClassloader: ClassLoader?, baseClasspath: Iterable) = - ReplClassLoader(URLClassLoader(baseClasspath.map { it.toURI().toURL() }.toTypedArray(), baseClassloader)) } +private open class HistoryActionsForNoRepeat(val state: GenericReplEvaluatorState) { + + open val effectiveHistory: List get() = state.history.map { it.item } + + open fun firstMismatch(other: IReplStageHistory): Pair?, ReplHistoryRecord?>? = 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> = prependClassLoaderWithNewClasses(effectiveHistory, compileResult) + + private fun prependClassLoaderWithNewClasses(effectiveHistory: List, + compileResult: ReplCompileResult.CompiledClasses + ): Pair> { + 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 get() = super.effectiveHistory.dropLast(1) + + override fun firstMismatch(other: IReplStageHistory): Pair?, ReplHistoryRecord?>? = + 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> = + currentLast.item.classLoader to currentLast.item.klass.java +} + +private open class HistoryActionsForRepeatAny(state: GenericReplEvaluatorState, val matchingLine: ReplHistoryRecord): HistoryActionsForNoRepeat(state) { + + override val effectiveHistory: List get() = state.history.takeWhile { it.id != matchingLine.id }.map { it.item } + + override fun firstMismatch(other: IReplStageHistory): Pair?, ReplHistoryRecord?>? = + 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> = + matchingLine.item.classLoader to matchingLine.item.klass.java +} diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/KotlinJsr223JvmScriptEngineBase.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/KotlinJsr223JvmScriptEngineBase.kt index 40ec7c2d853..2965d042c74 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/KotlinJsr223JvmScriptEngineBase.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/KotlinJsr223JvmScriptEngineBase.kt @@ -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 +var Bindings.kotlinScriptState: IReplStageState<*> @Suppress("UNCHECKED_CAST") - get() = getOrPut(KOTLIN_SCRIPT_HISTORY_BINDINGS_KEY, { arrayListOf() }) as MutableList - 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) { - 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 } diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/ReplApi.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/ReplApi.kt index 77d0e2577bf..8c2cd52fef7 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/ReplApi.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/ReplApi.kt @@ -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 - - fun resetToLine(line: ReplCodeLine): List = resetToLine(line.no) -} - -interface ReplCodeLineHistory { - val history: List -} - -interface ReplCombinedHistory { - val compiledHistory: List - val evaluatedHistory: List -} +// --- compile interface ReplCompileAction { - fun compile(codeLine: ReplCodeLine, verifyHistory: List? = null): ReplCompileResult + fun compile(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCompileResult } -sealed class ReplCompileResult(val compiledHistory: List) : Serializable { - class CompiledClasses(compiledHistory: List, - val compiledCodeLine: CompiledReplCodeLine, - val generatedClassname: String, +sealed class ReplCompileResult : Serializable { + class CompiledClasses(val lineId: LineId, + val mainClassName: String, val classes: List, val hasResult: Boolean, - val classpathAddendum: List) : ReplCompileResult(compiledHistory) + val classpathAddendum: List) : ReplCompileResult() - class Incomplete(compiledHistory: List) : ReplCompileResult(compiledHistory) + class Incomplete : ReplCompileResult() - class HistoryMismatch(compiledHistory: List, val lineNo: Int) : ReplCompileResult(compiledHistory) + class HistoryMismatch(val lineNo: Int) : ReplCompileResult() - class Error(compiledHistory: List, - 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) : Serial } } -interface ReplCompiler : ReplResettableCodeLine, ReplCodeLineHistory, ReplCompileAction, ReplCheckAction +interface ReplCompiler : ReplCompileAction, ReplCheckAction, CreateReplStageStateAction -typealias EvalHistoryType = Pair - -interface ReplEvaluatorExposedInternalHistory { - val lastEvaluatedScripts: List -} - -interface ReplClasspath { - val currentClasspath: List -} +// --- 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) : Serializable { - class ValueResult(completedEvalHistory: List, 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) : ReplEvalResult(completedEvalHistory) + class UnitResult : ReplEvalResult() - class Incomplete(completedEvalHistory: List) : ReplEvalResult(completedEvalHistory) + class Incomplete : ReplEvalResult() - class HistoryMismatch(completedEvalHistory: List, val lineNo: Int) : ReplEvalResult(completedEvalHistory) + class HistoryMismatch(val lineNo: Int) : ReplEvalResult() - sealed class Error(completedEvalHistory: List, val message: String) : ReplEvalResult(completedEvalHistory) { - class Runtime(completedEvalHistory: List, 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, - 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) : 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? = 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?): Pair + fun compileToEvaluable(state: IReplStageState<*>, + codeLine: ReplCodeLine, + defaultScriptArgs: ScriptArgsWithTypes? = null): Pair } +// 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, val scriptArgsTypes: Array>) : 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, diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/ReplState.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/ReplState.kt new file mode 100644 index 00000000000..e639151ddff --- /dev/null +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/ReplState.kt @@ -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 { + val no: Int +} + +data class ReplHistoryRecord (val id: ILineId, val item: T) + +interface IReplStageHistory : List> { + + fun backwardIterator(): Iterator> = lock.read { asReversed().iterator() } // TODO: check perf + + fun peek(): ReplHistoryRecord? = lock.read { lastOrNull() } + + fun push(id: ILineId, item: T) + + fun pop(): ReplHistoryRecord? + + fun verifiedPop(id: ILineId): ReplHistoryRecord? = lock.write { + if (lastOrNull()?.id == id) pop() + else null + } + + fun resetTo(id: ILineId): Iterable + + fun firstMismatch(other: IReplStageHistory): Pair?, ReplHistoryRecord?>? = + lock.read { other.lock.read { + iterator().asSequence().zip(other.asSequence()).firstOrNull { it.first.id != it.second.id }?.let { it.first to it.second } + } } + + fun firstMismatchFiltered(other: IReplStageHistory, predicate: (ReplHistoryRecord) -> Boolean): Pair?, ReplHistoryRecord?>? = + 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 firstMismatchWhile(other: IReplStageHistory, predicate: (ReplHistoryRecord) -> Boolean): Pair?, ReplHistoryRecord?>? = + 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 matches(other: IReplStageHistory): Boolean = + lock.read { other.lock.read { + size == other.size && firstMismatch(other) == null + } } + + fun isProperPrefix(other: IReplStageHistory): Boolean = + lock.read { other.lock.read { + size == other.size - 1 && firstMismatch(other) == null + } } + + val lock: ReentrantReadWriteLock +} + +interface IReplStageState { + val history: IReplStageHistory + + val lock: ReentrantReadWriteLock + + fun > asState(): StateT = (this as? StateT) ?: throw IllegalArgumentException("$this is not an expected instance of IReplStageState") +} + + diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericCompilerState.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericCompilerState.kt new file mode 100644 index 00000000000..4d15482d883 --- /dev/null +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericCompilerState.kt @@ -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(state.lock) { + + override fun resetTo(id: ILineId): Iterable { + 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 { + + // "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, GenericReplCheckerState() { + + override val history: IReplStageHistory = ReplCompilerStageHistory(this) + + val analyzerEngine = ReplCodeAnalyzer(environment) + + var lastDependencies: KotlinScriptExternalDependencies? = null +} diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericRepl.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericRepl.kt index 8ebe861c283..ef46c900f60 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericRepl.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericRepl.kt @@ -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 { - return evaluator.resetToLine(lineNumber) - } + override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> = AggregatedReplStageState(compiler.createState(lock), evaluator.createState(lock), lock) - override fun resetToLine(line: ReplCodeLine): List { - return evaluator.resetToLine(line) - } + override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult = compiler.check(state, codeLine) - override val history: List get() = evaluator.history - override val compiledHistory: List get() = compiler.history - override val evaluatedHistory: List get() = evaluator.history + override fun compile(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCompileResult = compiler.compile(state, codeLine) - override val currentClasspath: List - 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 - get() = evaluator.lastEvaluatedScripts - - override fun check(codeLine: ReplCodeLine): ReplCheckResult { - return compiler.check(codeLine) - } - - override fun compile(codeLine: ReplCodeLine, verifyHistory: List?): 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?, 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) } \ No newline at end of file diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericReplChecker.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericReplChecker.kt index 64914ea00c5..e9e92ccc6e1 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericReplChecker.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericReplChecker.kt @@ -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() + 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 { diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericReplCompiler.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericReplCompiler.kt index 0a163aa8663..213e0c788eb 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericReplCompiler.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericReplCompiler.kt @@ -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() + 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() - override fun resetToLine(lineNumber: Int): List { - 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 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?): 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? = 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()) } } diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericReplAnalyzer.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplCodeAnalyzer.kt similarity index 90% rename from compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericReplAnalyzer.kt rename to compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplCodeAnalyzer.kt index ffa45c6a0a6..69593c28039 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/GenericReplAnalyzer.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplCodeAnalyzer.kt @@ -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 { - return stateLock.write { replState.resetToLine(lineNumber) } - } - - override fun resetToLine(line: ReplCodeLine): List = resetToLine(line.no) + fun resetToLine(lineNumber: Int): List = 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() private val submittedLines = hashMapOf() @@ -189,8 +178,6 @@ class GenericReplAnalyzer(environment: KotlinCoreEnvironment, return removed.map { it.first } } - fun resetToLine(line: ReplCodeLine): List = resetToLine(line.no) - fun submitLine(ktFile: KtFile, codeLine: ReplCodeLine) { val line = LineInfo.SubmittedLine(ktFile, successfulLines.lastValue()) submittedLines[ktFile] = line diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplInterpreter.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplInterpreter.kt index 6b863726e30..dae3b6a81b7 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplInterpreter.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplInterpreter.kt @@ -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}") } diff --git a/idea/idea-repl/src/org/jetbrains/kotlin/jsr223/KotlinJsr223JvmScriptEngine4Idea.kt b/idea/idea-repl/src/org/jetbrains/kotlin/jsr223/KotlinJsr223JvmScriptEngine4Idea.kt index 438df3332d6..a1c5312324d 100644 --- a/idea/idea-repl/src/org/jetbrains/kotlin/jsr223/KotlinJsr223JvmScriptEngine4Idea.kt +++ b/idea/idea-repl/src/org/jetbrains/kotlin/jsr223/KotlinJsr223JvmScriptEngine4Idea.kt @@ -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 }