PR-1021: Merge Keplin project scripting code into Kotlin core.

Overhauls the scripting layers (GenericRepl and related, and JSR223 and related)
Adds repeating modes (none, only latest eval'd line, or random order)
Also adds better thread-safe IO capturing, default imports, SimpleRepl wrapper, more unit tests

NOTE: the script-util part of the pull request was rejected due to various problems and incompatibilities.
It may be incorporated into the code later.

(originally cherry picked from commit 6f7d517)
This commit is contained in:
apatrida
2017-01-18 04:17:41 +01:00
committed by Ilya Chernikov
parent 8caf607378
commit 5ad06e1e92
29 changed files with 1146 additions and 548 deletions
@@ -1,124 +0,0 @@
/*
* Copyright 2010-2016 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 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 GenericReplCompiledEvaluator(baseClasspath: Iterable<File>,
baseClassloader: ClassLoader?,
val scriptArgs: Array<Any?>? = null,
val scriptArgsTypes: Array<Class<*>>? = null
) : ReplCompiledEvaluator {
private var classLoader: org.jetbrains.kotlin.cli.common.repl.ReplClassLoader = makeReplClassLoader(baseClassloader, baseClasspath)
private val classLoaderLock = ReentrantReadWriteLock()
// TODO: consider to expose it as a part of (evaluator, invoker) interface
private val evalStateLock = ReentrantReadWriteLock()
private val compiledLoadedClassesHistory = ReplHistory<ClassWithInstance>()
override fun eval(codeLine: ReplCodeLine,
history: List<ReplCodeLine>,
compiledClasses: List<CompiledClassData>,
hasResult: Boolean,
classpathAddendum: List<File>,
invokeWrapper: InvokeWrapper?
): ReplEvalResult = evalStateLock.write {
checkAndUpdateReplHistoryCollection(compiledLoadedClassesHistory, history)?.let {
return@eval ReplEvalResult.HistoryMismatch(compiledLoadedClassesHistory.lines, it)
}
var mainLineClassName: String? = null
fun classNameFromPath(path: String) = JvmClassName.byInternalName(path.replaceFirst("\\.class$".toRegex(), ""))
fun processCompiledClasses() {
val expectedClassName = "Line${codeLine.no}"
compiledClasses.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)
}
}
classLoaderLock.read {
if (classpathAddendum.isNotEmpty()) {
classLoaderLock.write {
classLoader = makeReplClassLoader(classLoader, classpathAddendum)
}
}
processCompiledClasses()
}
fun compiledClassesNames() = compiledClasses.map { classNameFromPath(it.path).fqNameForClassNameWithoutDollars.asString() }
val scriptClass = classLoaderLock.read {
try {
classLoader.loadClass(mainLineClassName!!)
}
catch (e: Throwable) {
return ReplEvalResult.Error.Runtime(compiledLoadedClassesHistory.lines, "Error loading class $mainLineClassName: known classes: ${compiledClassesNames()}",
e as? Exception)
}
}
fun getConstructorParams(): Array<Class<*>> =
(compiledLoadedClassesHistory.values.map { it.klass.java } +
(scriptArgs?.mapIndexed { i, it -> scriptArgsTypes?.getOrNull(i) ?: it?.javaClass ?: Any::class.java } ?: emptyList())
).toTypedArray()
fun getConstructorArgs() = (compiledLoadedClassesHistory.values.map { it.instance } + scriptArgs.orEmpty()).toTypedArray()
val constructorParams: Array<Class<*>> = getConstructorParams()
val constructorArgs: Array<Any?> = getConstructorArgs()
val scriptInstanceConstructor = scriptClass.getConstructor(*constructorParams)
val scriptInstance =
try {
invokeWrapper?.invoke { scriptInstanceConstructor.newInstance(*constructorArgs) } ?: scriptInstanceConstructor.newInstance(*constructorArgs)
}
catch (e: Throwable) {
// ignore everything in the stack trace until this constructor call
return ReplEvalResult.Error.Runtime(compiledLoadedClassesHistory.lines, renderReplStackTrace(e.cause!!, startFromMethodName = "${scriptClass.name}.<init>"), e as? Exception)
}
compiledLoadedClassesHistory.add(codeLine, ClassWithInstance(scriptClass.kotlin, scriptInstance))
val rvField = scriptClass.getDeclaredField(SCRIPT_RESULT_FIELD_NAME).apply { isAccessible = true }
val rv: Any? = rvField.get(scriptInstance)
return if (hasResult) ReplEvalResult.ValueResult(compiledLoadedClassesHistory.lines, rv) else ReplEvalResult.UnitResult(compiledLoadedClassesHistory.lines)
}
override val lastEvaluatedScript: ClassWithInstance? get() = evalStateLock.read { compiledLoadedClassesHistory.values.lastOrNull() }
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))
@@ -0,0 +1,112 @@
/*
* 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.util.concurrent.locks.ReentrantReadWriteLock
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()) : ReplFullEvaluator {
val evaluator = GenericReplEvaluator(baseClasspath, baseClassloader, fallbackScriptArgs, repeatingMode, stateLock)
override fun compileAndEval(codeLine: ReplCodeLine, scriptArgs: ScriptArgsWithTypes?, verifyHistory: List<ReplCodeLine>?, invokeWrapper: InvokeWrapper?): ReplEvalResult {
return stateLock.write {
@Suppress("DEPRECATION")
val compiled = compiler.compile(codeLine, verifyHistory)
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.CompiledClasses -> {
@Suppress("DEPRECATION")
val result = eval(compiled, scriptArgs, invokeWrapper)
when (result) {
is ReplEvalResult.Error,
is ReplEvalResult.HistoryMismatch,
is ReplEvalResult.Incomplete -> {
result.completedEvalHistory.lastOrNull()?.let { compiler.resetToLine(it) }
result
}
is ReplEvalResult.ValueResult,
is ReplEvalResult.UnitResult ->
result
else -> throw IllegalStateException("Unknown evaluator result type ${compiled}")
}
}
else -> throw IllegalStateException("Unknown compiler result type ${compiled}")
}
}
}
override fun resetToLine(lineNumber: Int): List<ReplCodeLine> {
stateLock.write {
val removedCompiledLines = compiler.resetToLine(lineNumber)
val removedEvaluatorLines = evaluator.resetToLine(lineNumber)
removedCompiledLines.zip(removedEvaluatorLines).forEach {
if (it.first != it.second) {
throw IllegalStateException("History mistmatch when resetting lines")
}
}
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 {
return evaluator.eval(compileResult, scriptArgs, invokeWrapper)
}
override fun check(codeLine: ReplCodeLine): ReplCheckResult {
return compiler.check(codeLine)
}
override fun compileToEvaluable(codeLine: ReplCodeLine, defaultScriptArgs: ScriptArgsWithTypes?, verifyHistory: List<ReplCodeLine>?): Pair<ReplCompileResult, Evaluable?> {
val compiled = compiler.compile(codeLine, verifyHistory)
return if (compiled is ReplCompileResult.CompiledClasses) {
Pair(compiled, DelayedEvaluation(compiled, stateLock, evaluator, defaultScriptArgs ?: fallbackScriptArgs))
}
else {
Pair(compiled, null)
}
}
class DelayedEvaluation(override val compiledCode: ReplCompileResult.CompiledClasses,
private val stateLock: ReentrantReadWriteLock,
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) }
}
}
}
@@ -0,0 +1,209 @@
/*
* Copyright 2010-2016 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 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?,
protected val fallbackScriptArgs: ScriptArgsWithTypes? = null,
protected val repeatingMode: ReplRepeatingMode = ReplRepeatingMode.REPEAT_ONLY_MOST_RECENT,
protected val stateLock: ReentrantReadWriteLock = ReentrantReadWriteLock()) : ReplEvaluator {
private val topClassLoader: ReplClassLoader = makeReplClassLoader(baseClassloader, baseClasspath)
private val evaluatedHistory = ReplHistory<EvalClassWithInstanceAndLoader>()
override fun resetToLine(lineNumber: Int): List<ReplCodeLine> {
return stateLock.write {
evaluatedHistory.resetToLine(lineNumber)
}.map { it.first }
}
override val history: List<ReplCodeLine> get() = stateLock.read { evaluatedHistory.copySources() }
override val currentClasspath: List<File> get() = stateLock.read {
evaluatedHistory.copyValues().lastOrNull()?.let { it.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.replaceFirst("\\.class$".toRegex(), ""))
fun compiledClassesNames() = compileResult.classes.map { classNameFromPath(it.path).fqNameForClassNameWithoutDollars.asString() }
val expectedClassName = compileResult.generatedClassname
compileResult.classes.filter { it.path.endsWith(".class") }
.forEach {
val className = classNameFromPath(it.path)
if (className.internalName == expectedClassName || className.internalName.endsWith("/$expectedClassName")) {
mainLineClassName = className.internalName.replace('/', '.')
}
classLoader.addClass(className, it.bytes)
}
val scriptClass = try {
classLoader.loadClass(mainLineClassName!!)
}
catch (t: Throwable) {
throw Exception("Error loading class $mainLineClassName: known classes: ${compiledClassesNames()}", t)
}
Pair(classLoader, scriptClass)
}
}
override fun eval(compileResult: ReplCompileResult.CompiledClasses,
scriptArgs: ScriptArgsWithTypes?,
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)
})
val historyActor: HistoryActions = when (repeatingMode) {
ReplRepeatingMode.NONE -> defaultHistoryActor
ReplRepeatingMode.REPEAT_ONLY_MOST_RECENT -> {
val lastItem = evaluatedHistory.lastItem()
if (lastItem == null || lastItem.first.source != compileResult.compiledCodeLine.source) {
defaultHistoryActor
}
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)
})
}
}
ReplRepeatingMode.REPEAT_ANY_PREVIOUS -> {
if (evaluatedHistory.isEmpty() || !evaluatedHistory.contains(compileResult.compiledCodeLine.source)) {
defaultHistoryActor
}
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)
})
}
}
}
val firstMismatch = historyActor.verify(verifyHistory)
if (firstMismatch != null) {
return@eval ReplEvalResult.HistoryMismatch(evaluatedHistory.copySources(), firstMismatch)
}
val (classLoader, scriptClass) = try {
historyActor.processClasses(compileResult)
}
catch (e: Exception) {
return@eval ReplEvalResult.Error.Runtime(evaluatedHistory.copySources(),
e.message!!, e)
}
val currentScriptArgs = scriptArgs ?: fallbackScriptArgs
val useScriptArgs = currentScriptArgs?.scriptArgs
val useScriptArgsTypes = currentScriptArgs?.scriptArgsTypes?.map { it.java }
val constructorParams: Array<Class<*>> = (historyActor.effectiveHistory.map { it.klass.java } +
(useScriptArgs?.mapIndexed { i, it -> useScriptArgsTypes?.getOrNull(i) ?: it?.javaClass ?: Any::class.java } ?: emptyList())
).toTypedArray()
val constructorArgs: Array<Any?> = (historyActor.effectiveHistory.map { it.instance } + useScriptArgs.orEmpty()).toTypedArray()
val scriptInstanceConstructor = scriptClass.getConstructor(*constructorParams)
historyActor.addPlaceholder(compileResult.compiledCodeLine, EvalClassWithInstanceAndLoader(scriptClass.kotlin, null, classLoader, invokeWrapper))
val scriptInstance =
try {
if (invokeWrapper != null) invokeWrapper.invoke { scriptInstanceConstructor.newInstance(*constructorArgs) }
else scriptInstanceConstructor.newInstance(*constructorArgs)
}
catch (e: Throwable) {
historyActor.removePlaceholder(compileResult.compiledCodeLine)
// 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)
}
historyActor.removePlaceholder(compileResult.compiledCodeLine)
historyActor.addFinal(compileResult.compiledCodeLine, 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())
}
}
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))
}
@@ -17,60 +17,117 @@
package org.jetbrains.kotlin.cli.common.repl
import org.jetbrains.kotlin.utils.tryCreateCallableMapping
import java.lang.reflect.Proxy
import javax.script.Invocable
import javax.script.ScriptException
import kotlin.reflect.*
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KParameter
import kotlin.reflect.full.functions
import kotlin.reflect.full.safeCast
@Suppress("unused") // used externally (kotlin.script.utils)
interface KotlinJsr223JvmInvocableScriptEngine : Invocable {
val replScriptEvaluator: ReplEvaluatorBase
val replScriptEvaluator: ReplEvaluatorExposedInternalHistory
fun <T: Any> getInterface(klass: KClass<T>): Any? {
val (_, instance) = replScriptEvaluator.lastEvaluatedScript ?: throw IllegalArgumentException("no script ")
return getInterface(instance, klass)
}
fun <T: Any> getInterface(receiver: Any, klass: KClass<T>): Any? {
return klass.safeCast(receiver)
private fun prioritizedHistory(receiverClass: KClass<*>?, receiverInstance: Any?): List<EvalClassWithInstanceAndLoader> {
return replScriptEvaluator.lastEvaluatedScripts.map { it.second }.filter { it.instance != null }.reversed().assertNotEmpty("no script ").let { history ->
if (receiverInstance != null) {
val receiverKlass = receiverClass ?: receiverInstance.javaClass.kotlin
val receiverInHistory = history.find { it.instance == receiverInstance } ?:
EvalClassWithInstanceAndLoader(receiverKlass, receiverInstance, receiverKlass.java.classLoader, history.first().invokeWrapper)
listOf(receiverInHistory) + history.filterNot { it == receiverInHistory }
}
else {
history
}
}
}
override fun invokeFunction(name: String?, vararg args: Any?): Any? {
if (name == null) throw java.lang.NullPointerException("function name cannot be null")
val (klass, instance) = replScriptEvaluator.lastEvaluatedScript ?: throw IllegalArgumentException("no script ")
return invokeImpl(klass, instance, name, args, invokeWrapper = null)
return invokeImpl(prioritizedHistory(null, null), name, args)
}
override fun invokeMethod(thiz: Any?, name: String?, vararg args: Any?): Any? {
if (name == null) throw java.lang.NullPointerException("method name cannot be null")
if (thiz == null) throw IllegalArgumentException("cannot invoke method on the null object")
return invokeImpl(thiz.javaClass.kotlin, thiz, name, args, invokeWrapper = null)
return invokeImpl(prioritizedHistory(thiz.javaClass.kotlin, thiz), name, args)
}
private fun invokeImpl(prioritizedCallOrder: List<EvalClassWithInstanceAndLoader>, name: String, args: Array<out Any?>): Any? {
// TODO: cache the method lookups?
val (fn, mapping, invokeWrapper) = prioritizedCallOrder.asSequence().map { attempt ->
val candidates = attempt.klass.functions.filter { it.name == name }
candidates.findMapping(listOf<Any?>(attempt.instance) + args)?.let {
Triple(it.first, it.second, attempt.invokeWrapper)
}
}.filterNotNull().firstOrNull() ?: throw NoSuchMethodException("no suitable function '$name' found")
val res = try {
if (invokeWrapper != null) {
invokeWrapper.invoke {
fn.callBy(mapping)
}
}
else {
fn.callBy(mapping)
}
}
catch (e: Throwable) {
// ignore everything in the stack trace until this constructor call
throw ScriptException(renderReplStackTrace(e.cause!!, startFromMethodName = fn.name))
}
return if (fn.returnType.classifier == Unit::class) Unit else res
}
override fun <T : Any> getInterface(clasz: Class<T>?): T? {
val (_, instance) = replScriptEvaluator.lastEvaluatedScript ?: throw IllegalArgumentException("no script ")
return getInterface(instance, clasz)
return proxyInterface(null, clasz)
}
override fun <T : Any> getInterface(thiz: Any?, clasz: Class<T>?): T? {
if (thiz == null) throw IllegalArgumentException("object cannot be null")
return proxyInterface(thiz, clasz)
}
private fun <T : Any> proxyInterface(thiz: Any?, clasz: Class<T>?): T? {
replScriptEvaluator.lastEvaluatedScripts.assertNotEmpty("no script")
val priority = prioritizedHistory(thiz?.javaClass?.kotlin, thiz)
if (clasz == null) throw IllegalArgumentException("class object cannot be null")
if (!clasz.isInterface) throw IllegalArgumentException("expecting interface")
return clasz.kotlin.safeCast(thiz)
// TODO: cache the method lookups?
val proxy = Proxy.newProxyInstance(Thread.currentThread().contextClassLoader, arrayOf(clasz)) { _, method, args ->
invokeImpl(priority, method.name, args ?: emptyArray())
}
return clasz.kotlin.safeCast(proxy)
}
}
private fun invokeImpl(receiverClass: KClass<*>, receiverInstance: Any, name: String, args: Array<out Any?>, invokeWrapper: InvokeWrapper?): Any? {
private fun invokeImpl(prioritizedCallOrder: List<EvalClassWithInstanceAndLoader>, name: String, args: Array<out Any?>): Any? {
// TODO: cache the method lookups?
val (fn, mapping, invokeWrapper) = prioritizedCallOrder.asSequence().map { attempt ->
val candidates = attempt.klass.functions.filter { it.name == name }
candidates.findMapping(listOf<Any?>(attempt.instance) + args)?.let {
Triple(it.first, it.second, attempt.invokeWrapper)
}
}.filterNotNull().firstOrNull() ?: throw NoSuchMethodException("no suitable function '$name' found")
val candidates = receiverClass.functions.filter { it.name == name }
val (fn, mapping) = candidates.findMapping(listOf<Any?>(receiverInstance) + args) ?:
throw NoSuchMethodException("no suitable function '$name' found")
val res = try {
invokeWrapper?.invoke {
if (invokeWrapper != null) {
invokeWrapper.invoke {
fn.callBy(mapping)
}
}
else {
fn.callBy(mapping)
} ?: fn.callBy(mapping)
}
}
catch (e: Throwable) {
// ignore everything in the stack trace until this constructor call
@@ -18,25 +18,27 @@ 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 javax.script.*
val KOTLIN_SCRIPT_HISTORY_BINDINGS_KEY = "kotlin.script.history"
// TODO consider additional error handling
@Suppress("UNCHECKED_CAST")
val Bindings.kotlinScriptHistory: MutableList<ReplCodeLine>
get() = getOrPut(KOTLIN_SCRIPT_HISTORY_BINDINGS_KEY, { arrayListOf<ReplCodeLine>() }) as MutableList<ReplCodeLine>
abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEngineFactory) : AbstractScriptEngine(), ScriptEngine, Compilable {
private var lineCount = 0
protected var codeLineNumber = AtomicInteger(0)
protected abstract val replCompiler: ReplCompiler
protected abstract val replCompiler: ReplCompileAction
protected abstract val replScriptEvaluator: ReplFullEvaluator
protected abstract val replEvaluator: ReplCompiledEvaluator
override fun eval(script: String, context: ScriptContext): Any? = compileAndEval(script, context)
override fun eval(script: String, context: ScriptContext): Any? = compile(script, context).eval(context)
override fun eval(script: Reader, context: ScriptContext): Any? = compile(script.readText(), context).eval()
override fun eval(script: Reader, context: ScriptContext): Any? = compileAndEval(script.readText(), context)
override fun compile(script: String): CompiledScript = compile(script, getContext())
@@ -46,42 +48,56 @@ abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEn
override fun getFactory(): ScriptEngineFactory = myFactory
open fun compile(script: String, context: ScriptContext): CompiledScript {
lineCount += 1
fun nextCodeLine(code: String) = ReplCodeLine(codeLineNumber.incrementAndGet(), code)
val codeLine = ReplCodeLine(lineCount, script)
open fun overrideScriptArgs(context: ScriptContext): ScriptArgsWithTypes? = null
open fun compileAndEval(script: String, context: ScriptContext): Any? {
val codeLine = nextCodeLine(script)
val history = context.getBindings(ScriptContext.ENGINE_SCOPE).kotlinScriptHistory
val result = replScriptEvaluator.compileAndEval(codeLine, scriptArgs = overrideScriptArgs(context), verifyHistory = history)
val ret = when (result) {
is ReplEvalResult.ValueResult -> result.value
is ReplEvalResult.UnitResult -> null
is ReplEvalResult.Error -> throw ScriptException(result.message)
is ReplEvalResult.Incomplete -> throw ScriptException("error: incomplete code")
is ReplEvalResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${result.lineNo}")
}
context.getBindings(ScriptContext.ENGINE_SCOPE).put(KOTLIN_SCRIPT_HISTORY_BINDINGS_KEY, ArrayList(result.completedEvalHistory))
return ret
}
open fun compile(script: String, context: ScriptContext): CompiledScript {
val codeLine = nextCodeLine(script)
val history = context.getBindings(ScriptContext.ENGINE_SCOPE).kotlinScriptHistory
val compileResult = replCompiler.compile(codeLine, history)
val compiled = when (compileResult) {
is ReplCompileResult.Error -> throw ScriptException("Error${compileResult.locationString()}: ${compileResult.message}")
val result = replCompiler.compile(codeLine, history)
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: ${compileResult.lineNo}")
is ReplCompileResult.CompiledClasses -> compileResult
is ReplCompileResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${result.lineNo}")
is ReplCompileResult.CompiledClasses -> result
}
context.getBindings(ScriptContext.ENGINE_SCOPE).put(KOTLIN_SCRIPT_HISTORY_BINDINGS_KEY, ArrayList(result.compiledHistory))
return CompiledKotlinScript(this, codeLine, compiled)
}
open fun eval(compiledScript: CompiledKotlinScript, context: ScriptContext): Any? {
val history = context.getBindings(ScriptContext.ENGINE_SCOPE).kotlinScriptHistory
val evalResult = try {
replEvaluator.eval(compiledScript.codeLine, history, compiledScript.compiledData.classes, compiledScript.compiledData.hasResult, compiledScript.compiledData.classpathAddendum)
val result = try {
replScriptEvaluator.eval(compiledScript.compiledData, scriptArgs = overrideScriptArgs(context))
}
catch (e: Exception) {
throw ScriptException(e)
}
val ret = when (evalResult) {
is ReplEvalResult.ValueResult -> evalResult.value
val ret = when (result) {
is ReplEvalResult.ValueResult -> result.value
is ReplEvalResult.UnitResult -> null
is ReplEvalResult.Error -> throw ScriptException(evalResult.message)
is ReplEvalResult.Error -> throw ScriptException(result.message)
is ReplEvalResult.Incomplete -> throw ScriptException("error: incomplete code")
is ReplEvalResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${evalResult.lineNo}")
is ReplEvalResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${result.lineNo}")
}
history.add(compiledScript.codeLine)
context.getBindings(ScriptContext.ENGINE_SCOPE).put(KOTLIN_SCRIPT_HISTORY_BINDINGS_KEY, ArrayList(result.completedEvalHistory))
return ret
}
@@ -28,105 +28,173 @@ data class ReplCodeLine(val no: Int, val code: String) : Serializable {
}
}
data class ClassWithInstance(val klass: KClass<*>, val instance: Any)
// TODO: consider storing code hash where source is not needed
data class CompiledReplCodeLine(val className: String, val source: ReplCodeLine) : Serializable {
companion object {
private val serialVersionUID: Long = 8228307678L
}
}
data class CompiledClassData(val path: String, val bytes: ByteArray) : Serializable {
override fun equals(other: Any?): Boolean = (other as? CompiledClassData)?.let { path == it.path && Arrays.equals(bytes, it.bytes) } ?: false
override fun hashCode(): Int = path.hashCode() + Arrays.hashCode(bytes)
companion object {
private val serialVersionUID: Long = 8228357578L
}
}
sealed class ReplCheckResult(val updatedHistory: List<ReplCodeLine>) : Serializable {
class Ok(updatedHistory: List<ReplCodeLine>) : ReplCheckResult(updatedHistory)
class Incomplete(updatedHistory: List<ReplCodeLine>) : ReplCheckResult(updatedHistory)
class Error(updatedHistory: List<ReplCodeLine>,
val message: String,
val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION
) : ReplCheckResult(updatedHistory)
{
interface ReplCheckAction {
fun check(codeLine: ReplCodeLine): ReplCheckResult
}
sealed class ReplCheckResult : Serializable {
class Ok : ReplCheckResult()
class Incomplete : ReplCheckResult()
class Error(val message: String,
val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION) : ReplCheckResult() {
override fun toString(): String = "Error(message = \"$message\""
}
companion object {
private val serialVersionUID: Long = 8228357578L
private val serialVersionUID: Long = 8228307678L
}
}
sealed class ReplCompileResult(val updatedHistory: List<ReplCodeLine>) : Serializable {
class CompiledClasses(updatedHistory: List<ReplCodeLine>,
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>
}
interface ReplCompileAction {
fun compile(codeLine: ReplCodeLine, verifyHistory: List<ReplCodeLine>? = null): ReplCompileResult
}
sealed class ReplCompileResult(val compiledHistory: List<ReplCodeLine>) : Serializable {
class CompiledClasses(compiledHistory: List<ReplCodeLine>,
val compiledCodeLine: CompiledReplCodeLine,
val generatedClassname: String,
val classes: List<CompiledClassData>,
val hasResult: Boolean,
val classpathAddendum: List<File>
) : ReplCompileResult(updatedHistory)
class Incomplete(updatedHistory: List<ReplCodeLine>) : ReplCompileResult(updatedHistory)
class HistoryMismatch(updatedHistory: List<ReplCodeLine>, val lineNo: Int): ReplCompileResult(updatedHistory)
class Error(updatedHistory: List<ReplCodeLine>,
val classpathAddendum: List<File>) : ReplCompileResult(compiledHistory)
class Incomplete(compiledHistory: List<ReplCodeLine>) : ReplCompileResult(compiledHistory)
class HistoryMismatch(compiledHistory: List<ReplCodeLine>, val lineNo: Int) : ReplCompileResult(compiledHistory)
class Error(compiledHistory: List<ReplCodeLine>,
val message: String,
val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION
) : ReplCompileResult(updatedHistory)
{
val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION) : ReplCompileResult(compiledHistory) {
override fun toString(): String = "Error(message = \"$message\""
}
companion object {
private val serialVersionUID: Long = 8228357578L
private val serialVersionUID: Long = 8228307678L
}
}
sealed class ReplEvalResult(val updatedHistory: List<ReplCodeLine>) : Serializable {
class ValueResult(updatedHistory: List<ReplCodeLine>, val value: Any?) : ReplEvalResult(updatedHistory) {
interface ReplCompiler : ReplResettableCodeLine, ReplCodeLineHistory, ReplCompileAction, ReplCheckAction
typealias EvalHistoryType = Pair<CompiledReplCodeLine, EvalClassWithInstanceAndLoader>
interface ReplEvaluatorExposedInternalHistory {
val lastEvaluatedScripts: List<EvalHistoryType>
}
interface ReplClasspath {
val currentClasspath: List<File>
}
data class EvalClassWithInstanceAndLoader(val klass: KClass<*>, val instance: Any?, val classLoader: ClassLoader, val invokeWrapper: InvokeWrapper?)
interface ReplEvalAction {
fun eval(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) {
override fun toString(): String = "Result: $value"
}
class UnitResult(updatedHistory: List<ReplCodeLine>) : ReplEvalResult(updatedHistory)
class Incomplete(updatedHistory: List<ReplCodeLine>) : ReplEvalResult(updatedHistory)
class HistoryMismatch(updatedHistory: List<ReplCodeLine>, val lineNo: Int): ReplEvalResult(updatedHistory)
sealed class Error(updatedHistory: List<ReplCodeLine>, val message: String) : ReplEvalResult(updatedHistory) {
class Runtime(updatedHistory: List<ReplCodeLine>, message: String, val cause: Exception? = null) : Error(updatedHistory, message)
class CompileTime(updatedHistory: List<ReplCodeLine>,
class UnitResult(completedEvalHistory: List<ReplCodeLine>) : ReplEvalResult(completedEvalHistory)
class Incomplete(completedEvalHistory: List<ReplCodeLine>) : ReplEvalResult(completedEvalHistory)
class HistoryMismatch(completedEvalHistory: List<ReplCodeLine>, val lineNo: Int) : ReplEvalResult(completedEvalHistory)
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)
class CompileTime(completedEvalHistory: List<ReplCodeLine>,
message: String,
val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION
) : Error(updatedHistory, message)
val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION) : Error(completedEvalHistory, message)
override fun toString(): String = "${this::class.simpleName}Error(message = \"$message\""
}
companion object {
private val serialVersionUID: Long = 8228357578L
private val serialVersionUID: Long = 8228307678L
}
}
interface ReplChecker {
fun check(codeLine: ReplCodeLine, history: List<ReplCodeLine>): ReplCheckResult
interface ReplEvaluator : ReplResettableCodeLine, ReplCodeLineHistory, ReplEvaluatorExposedInternalHistory, ReplEvalAction, ReplClasspath
interface ReplAtomicEvalAction {
fun compileAndEval(codeLine: ReplCodeLine,
scriptArgs: ScriptArgsWithTypes? = null,
verifyHistory: List<ReplCodeLine>? = null,
invokeWrapper: InvokeWrapper? = null): ReplEvalResult
}
interface ReplCompiler : ReplChecker {
fun compile(codeLine: ReplCodeLine, history: List<ReplCodeLine>): ReplCompileResult
interface ReplAtomicEvaluator : ReplResettableCodeLine, ReplCombinedHistory, ReplEvaluatorExposedInternalHistory, ReplAtomicEvalAction, ReplCheckAction, ReplClasspath
interface ReplDelayedEvalAction {
fun compileToEvaluable(codeLine: ReplCodeLine, defaultScriptArgs: ScriptArgsWithTypes? = null, verifyHistory: List<ReplCodeLine>?): Pair<ReplCompileResult, Evaluable?>
}
interface ReplEvaluatorBase {
val lastEvaluatedScript: ClassWithInstance?
interface Evaluable {
val compiledCode: ReplCompileResult.CompiledClasses
fun eval(scriptArgs: ScriptArgsWithTypes? = null, invokeWrapper: InvokeWrapper? = null): ReplEvalResult
}
interface ReplCompiledEvaluator : ReplEvaluatorBase {
interface ReplFullEvaluator : ReplEvaluator, ReplAtomicEvaluator, ReplDelayedEvalAction, ReplCombinedHistory
fun eval(codeLine: ReplCodeLine,
history: List<ReplCodeLine>,
compiledClasses: List<CompiledClassData>,
hasResult: Boolean,
classpathAddendum: List<File>,
invokeWrapper: InvokeWrapper? = null
): ReplEvalResult
/**
* 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 {
companion object {
private val serialVersionUID: Long = 8529357500L
}
}
interface ReplEvaluator : ReplChecker, ReplEvaluatorBase {
fun eval(codeLine: ReplCodeLine,
history: List<ReplCodeLine>,
invokeWrapper: InvokeWrapper? = null
): ReplEvalResult
interface ScriptTemplateEmptyArgsProvider {
val defaultEmptyArgs: ScriptArgsWithTypes?
}
class SimpleScriptTemplateEmptyArgsProvider(override val defaultEmptyArgs: ScriptArgsWithTypes? = null) : ScriptTemplateEmptyArgsProvider
enum class ReplRepeatingMode {
NONE,
REPEAT_ONLY_MOST_RECENT,
REPEAT_ANY_PREVIOUS
}
interface InvokeWrapper {
operator fun<T> invoke(body: () -> T): T // e.g. for capturing io
operator fun <T> invoke(body: () -> T): T // e.g. for capturing io
}
@@ -0,0 +1,105 @@
/*
* 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.concurrent.ConcurrentLinkedDeque
typealias CompiledHistoryItem<T> = Pair<CompiledReplCodeLine, T>
typealias SourceHistoryItem<T> = Pair<ReplCodeLine, T>
typealias CompiledHistoryStorage<T> = ConcurrentLinkedDeque<CompiledHistoryItem<T>>
typealias CompiledHistoryList<T> = List<CompiledHistoryItem<T>>
typealias SourceHistoryList<T> = List<SourceHistoryItem<T>>
typealias SourceList = List<ReplCodeLine>
/*
Not thread safe, the caller is assumed to lock access. Even though ConcurrentLinkedDeque is used, there are mutliple
actions that require locking.
*/
class ReplHistory<T>(startingHistory: CompiledHistoryList<T> = emptyList()) : Serializable {
private val history: CompiledHistoryStorage<T> = ConcurrentLinkedDeque(startingHistory)
fun isEmpty(): Boolean = history.isEmpty()
fun isNotEmpty(): Boolean = history.isNotEmpty()
fun add(line: CompiledReplCodeLine, value: T) {
history.add(line to value)
}
/* remove last line only if it is the line we think it is */
fun removeLast(line: CompiledReplCodeLine): Boolean {
return if (history.peekLast().first == line) {
history.removeLast()
true
}
else {
false
}
}
/* resets back to a previous line number and returns the lines removed */
fun resetToLine(lineNumber: Int): SourceHistoryList<T> {
val removed = arrayListOf<SourceHistoryItem<T>>()
while ((history.peekLast()?.first?.source?.no ?: -1) > lineNumber) {
removed.add(history.removeLast().let { Pair(it.first.source, it.second) })
}
return removed.reversed()
}
fun resetToLine(line: ReplCodeLine): SourceHistoryList<T> {
return resetToLine(line.no)
}
fun resetToLine(line: CompiledReplCodeLine): CompiledHistoryList<T> {
val removed = arrayListOf<CompiledHistoryItem<T>>()
while ((history.peekLast()?.first?.source?.no ?: -1) > line.source.no) {
removed.add(history.removeLast())
}
return removed.reversed()
}
fun contains(line: ReplCodeLine): Boolean = history.any { it.first.source == line }
fun contains(line: CompiledReplCodeLine): Boolean = history.any { it.first == line }
fun lastItem(): CompiledHistoryItem<T>? = history.peekLast()
fun lastCodeLine(): CompiledReplCodeLine? = lastItem()?.first
fun lastValue(): T? = lastItem()?.second
fun checkHistoryIsInSync(compareHistory: SourceList?): Boolean {
return firstMismatchingHistory(compareHistory) == null
}
// return from the compareHistory the first line that does not match or null
fun firstMismatchingHistory(compareHistory: SourceList?): Int? {
if (compareHistory == null) return null
val firstMismatch = history.zip(compareHistory).firstOrNull { it.first.first.source != it.second }?.second?.no
if (compareHistory.size == history.size) return firstMismatch
if (compareHistory.size > history.size) return compareHistory[history.size].no
return history.toList()[compareHistory.size].first.source.no
}
fun copySources(): SourceList = history.map { it.first.source }
fun copyValues(): List<T> = history.map { it.second }
fun copyAll(): CompiledHistoryList<T> = history.toList()
companion object {
private val serialVersionUID: Long = 8328353000L
}
}
@@ -17,44 +17,11 @@
package org.jetbrains.kotlin.cli.common.repl
import com.google.common.base.Throwables
import java.io.Serializable
import java.io.File
import java.net.URLClassLoader
// TODO: thread safety!!
data class ReplHistory<T>(
val lines: MutableList<ReplCodeLine> = arrayListOf(),
val values: MutableList<T> = arrayListOf()
) : Serializable
{
init { assert(isValid()) }
fun isValid() = lines.size == values.size
fun add(line: ReplCodeLine, value: T) {
lines.add(line)
values.add(value)
}
fun trimAt(idx: Int) {
lines.dropLast(lines.size - idx)
values.dropLast(lines.size - idx)
}
companion object {
private val serialVersionUID: Long = 8228357578L
}
}
fun <T> checkAndUpdateReplHistoryCollection(history: ReplHistory<T>, linesHistory: Iterable<ReplCodeLine>): Int? {
assert(history.isValid())
val linesHistoryIt = linesHistory.iterator()
var idx = 0
while (linesHistoryIt.hasNext()) {
val curLine = linesHistoryIt.next()
if (history.lines[idx] != curLine) return curLine.no
idx += 1
}
history.trimAt(idx)
return null
}
fun makeSriptBaseName(codeLine: ReplCodeLine, generation: Long) =
"Line_${codeLine.no}" + if (generation > 1) "_gen_${generation}" else ""
fun renderReplStackTrace(cause: Throwable, startFromMethodName: String): String {
val newTrace = arrayListOf<StackTraceElement>()
@@ -76,3 +43,22 @@ fun renderReplStackTrace(cause: Throwable, startFromMethodName: String): String
return Throwables.getStackTraceAsString(cause)
}
fun NO_ACTION(): Unit = Unit
fun <T> NO_ACTION_THAT_RETURNS(v: T): T = v
internal fun ClassLoader.listAllUrlsAsFiles(): List<File> {
val parents = generateSequence(this) { loader -> loader.parent }.filterIsInstance(URLClassLoader::class.java)
return parents.fold(emptyList<File>()) { accum, loader ->
loader.listLocalUrlsAsFiles() + accum
}.distinct()
}
internal fun URLClassLoader.listLocalUrlsAsFiles(): List<File> {
return this.urLs.map { it.toString().removePrefix("file:") }.filterNotNull().map { File(it) }
}
internal fun <T : Any> List<T>.assertNotEmpty(error: String): List<T> {
if (this.isEmpty()) throw IllegalStateException(error)
return this
}
@@ -17,43 +17,71 @@
package org.jetbrains.kotlin.cli.jvm.repl
import com.intellij.openapi.Disposable
import com.intellij.openapi.diagnostic.Logger
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
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
private val logger = Logger.getInstance(GenericRepl::class.java)
open class GenericRepl(
open class GenericRepl protected constructor(
disposable: Disposable,
scriptDefinition: KotlinScriptDefinition,
compilerConfiguration: CompilerConfiguration,
messageCollector: MessageCollector,
baseClassloader: ClassLoader?,
scriptArgs: Array<Any?>? = null,
scriptArgsTypes: Array<Class<*>>? = null
) : ReplEvaluator, GenericReplCompiler(disposable, scriptDefinition, compilerConfiguration, messageCollector) {
protected val fallbackScriptArgs: ScriptArgsWithTypes?,
protected val repeatingMode: ReplRepeatingMode = ReplRepeatingMode.NONE,
protected val stateLock: ReentrantReadWriteLock
) : 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())
private val compiledEvaluator = GenericReplCompiledEvaluator(compilerConfiguration.jvmClasspathRoots, baseClassloader, scriptArgs, scriptArgsTypes)
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)
override val lastEvaluatedScript: ClassWithInstance? get() = compiledEvaluator.lastEvaluatedScript
override fun resetToLine(lineNumber: Int): List<ReplCodeLine> {
return evaluator.resetToLine(lineNumber)
}
@Synchronized
override fun eval(codeLine: ReplCodeLine, history: List<ReplCodeLine>, invokeWrapper: InvokeWrapper?): ReplEvalResult =
compileAndEval(this, compiledEvaluator, codeLine, history, invokeWrapper)
}
override fun resetToLine(line: ReplCodeLine): List<ReplCodeLine> {
return evaluator.resetToLine(line)
}
override val history: List<ReplCodeLine> get() = evaluator.history
override val compiledHistory: List<ReplCodeLine> get() = compiler.history
override val evaluatedHistory: List<ReplCodeLine> get() = evaluator.history
fun compileAndEval(replCompiler: ReplCompiler, replCompiledEvaluator: ReplCompiledEvaluator, codeLine: ReplCodeLine, history: List<ReplCodeLine>, invokeWrapper: InvokeWrapper?): ReplEvalResult =
replCompiler.compile(codeLine, history).let {
when (it) {
is ReplCompileResult.Incomplete -> ReplEvalResult.Incomplete(it.updatedHistory)
is ReplCompileResult.HistoryMismatch -> ReplEvalResult.HistoryMismatch(it.updatedHistory, it.lineNo)
is ReplCompileResult.Error -> ReplEvalResult.Error.CompileTime(it.updatedHistory, it.message, it.location)
is ReplCompileResult.CompiledClasses -> replCompiledEvaluator.eval(codeLine, history, it.classes, it.hasResult, it.classpathAddendum, invokeWrapper)
}
}
override val currentClasspath: List<File>
get() = evaluator.currentClasspath
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)
}
@@ -14,9 +14,14 @@
* limitations under the License.
*/
@file:Suppress("UNUSED_PARAMETER")
package org.jetbrains.kotlin.cli.jvm.repl
import com.intellij.psi.search.GlobalSearchScope
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
@@ -30,25 +35,36 @@ import org.jetbrains.kotlin.resolve.*
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfoFactory
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
import org.jetbrains.kotlin.resolve.jvm.TopDownAnalyzerFacadeForJVM
import org.jetbrains.kotlin.resolve.lazy.ResolveSession
import org.jetbrains.kotlin.resolve.lazy.*
import org.jetbrains.kotlin.resolve.lazy.data.KtClassLikeInfo
import org.jetbrains.kotlin.resolve.lazy.declarations.*
import org.jetbrains.kotlin.resolve.repl.ReplState
import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyScriptDescriptor
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 CliReplAnalyzerEngine(environment: KotlinCoreEnvironment) {
class GenericReplAnalyzer(environment: KotlinCoreEnvironment,
protected val stateLock: ReentrantReadWriteLock = ReentrantReadWriteLock()) : ReplResettableCodeLine {
private val topDownAnalysisContext: TopDownAnalysisContext
private val topDownAnalyzer: LazyTopDownAnalyzer
private val resolveSession: ResolveSession
private val scriptDeclarationFactory: ScriptMutableDeclarationProviderFactory
private val replState = ResetableReplState()
val module: ModuleDescriptorImpl
private val replState = ReplState()
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
// to be found via ResolveSession. The latter is true as long as light classes are not needed in REPL (which is currently true
// because no symbol declared in the REPL session can be used from Java)
// to be found via ResolveSession. The latter is true as long as light classes are not needed in NONE (which is currently true
// because no symbol declared in the NONE session can be used from Java)
val container = TopDownAnalyzerFacadeForJVM.createContainer(
environment.project,
emptyList(),
@@ -78,18 +94,26 @@ class CliReplAnalyzerEngine(environment: KotlinCoreEnvironment) {
}
}
fun analyzeReplLine(psiFile: KtFile, priority: Int): ReplLineAnalysisResult {
topDownAnalysisContext.scripts.clear()
trace.clearDiagnostics()
psiFile.script!!.putUserData(ScriptPriorities.PRIORITY_KEY, priority)
return doAnalyze(psiFile)
override fun resetToLine(lineNumber: Int): List<ReplCodeLine> {
return stateLock.write { replState.resetToLine(lineNumber) }
}
private fun doAnalyze(linePsi: KtFile): ReplLineAnalysisResult {
override fun resetToLine(line: ReplCodeLine): List<ReplCodeLine> = resetToLine(line.no)
fun analyzeReplLine(psiFile: KtFile, codeLine: ReplCodeLine): ReplLineAnalysisResult {
return stateLock.write {
topDownAnalysisContext.scripts.clear()
trace.clearDiagnostics()
psiFile.script!!.putUserData(ScriptPriorities.PRIORITY_KEY, codeLine.no)
doAnalyze(psiFile, codeLine)
}
}
private fun doAnalyze(linePsi: KtFile, codeLine: ReplCodeLine): ReplLineAnalysisResult {
scriptDeclarationFactory.setDelegateFactory(FileBasedDeclarationProviderFactory(resolveSession.storageManager, listOf(linePsi)))
replState.submitLine(linePsi)
replState.submitLine(linePsi, codeLine)
val context = topDownAnalyzer.analyzeDeclarations(topDownAnalysisContext.topDownAnalysisMode, listOf(linePsi))
@@ -100,12 +124,12 @@ class CliReplAnalyzerEngine(environment: KotlinCoreEnvironment) {
val diagnostics = trace.bindingContext.diagnostics
val hasErrors = diagnostics.any { it.severity == Severity.ERROR }
if (hasErrors) {
replState.lineFailure(linePsi)
replState.lineFailure(linePsi, codeLine)
return ReplLineAnalysisResult.WithErrors(diagnostics)
}
else {
val scriptDescriptor = context.scripts[linePsi.script]!!
replState.lineSuccess(linePsi, scriptDescriptor)
replState.lineSuccess(linePsi, codeLine, scriptDescriptor)
return ReplLineAnalysisResult.Successful(scriptDescriptor, diagnostics)
}
@@ -153,4 +177,60 @@ class CliReplAnalyzerEngine(environment: KotlinCoreEnvironment) {
}
}
}
class ResetableReplState() {
private val successfulLines = ReplHistory<LineInfo.SuccessfulLine>()
private val submittedLines = hashMapOf<KtFile, LineInfo>()
fun resetToLine(lineNumber: Int): List<ReplCodeLine> {
val removed = successfulLines.resetToLine(lineNumber)
removed.forEach { submittedLines.remove(it.second.linePsi) }
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
ktFile.fileScopesCustomizer = object : FileScopesCustomizer {
override fun createFileScopes(fileScopeFactory: FileScopeFactory): FileScopes {
return lineInfo(ktFile)?.let { computeFileScopes(it, fileScopeFactory) } ?: fileScopeFactory.createScopesForFile(ktFile)
}
}
}
fun lineSuccess(ktFile: KtFile, codeLine: ReplCodeLine, scriptDescriptor: LazyScriptDescriptor) {
val successfulLine = LineInfo.SuccessfulLine(ktFile, successfulLines.lastValue(), scriptDescriptor)
submittedLines[ktFile] = successfulLine
successfulLines.add(CompiledReplCodeLine(ktFile.name, codeLine), successfulLine)
}
fun lineFailure(ktFile: KtFile, codeLine: ReplCodeLine) {
submittedLines[ktFile] = LineInfo.FailedLine(ktFile, successfulLines.lastValue())
}
private fun lineInfo(ktFile: KtFile) = submittedLines[ktFile]
// use sealed?
private sealed class LineInfo {
abstract val linePsi: KtFile
abstract val parentLine: SuccessfulLine?
class SubmittedLine(override val linePsi: KtFile, override val parentLine: SuccessfulLine?) : LineInfo()
class SuccessfulLine(override val linePsi: KtFile, override val parentLine: SuccessfulLine?, val lineDescriptor: LazyScriptDescriptor) : LineInfo()
class FailedLine(override val linePsi: KtFile, override val parentLine: SuccessfulLine?) : LineInfo()
}
private fun computeFileScopes(lineInfo: LineInfo, fileScopeFactory: FileScopeFactory): FileScopes? {
// create scope that wraps previous line lexical scope and adds imports from this line
val lexicalScopeAfterLastLine = lineInfo.parentLine?.lineDescriptor?.scopeForInitializerResolution ?: return null
val lastLineImports = lexicalScopeAfterLastLine.parentsWithSelf.first { it is ImportingScope } as ImportingScope
val scopesForThisLine = fileScopeFactory.createScopesForFile(lineInfo.linePsi, lastLineImports)
val combinedLexicalScopes = lexicalScopeAfterLastLine.replaceImportingScopes(scopesForThisLine.importingScope)
return FileScopes(combinedLexicalScopes, scopesForThisLine.importingScope, scopesForThisLine.importResolver)
}
}
}
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.cli.jvm.repl
import com.intellij.openapi.Disposable
import com.intellij.openapi.vfs.CharsetToolkit
import com.intellij.psi.PsiFileFactory
@@ -25,8 +26,8 @@ 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.ReplChecker
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
import org.jetbrains.kotlin.cli.common.repl.makeSriptBaseName
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
@@ -37,14 +38,18 @@ 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
open class GenericReplChecker(
disposable: Disposable,
val scriptDefinition: KotlinScriptDefinition,
val compilerConfiguration: CompilerConfiguration,
messageCollector: MessageCollector
) : ReplChecker {
protected val environment = run {
messageCollector: MessageCollector,
protected val stateLock: ReentrantReadWriteLock = ReentrantReadWriteLock()
) {
internal val environment = run {
compilerConfiguration.apply {
add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, scriptDefinition)
put<MessageCollector>(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector)
@@ -53,41 +58,43 @@ open class GenericReplChecker(
KotlinCoreEnvironment.createForProduction(disposable, compilerConfiguration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
}
protected val psiFileFactory: PsiFileFactoryImpl = PsiFileFactory.getInstance(environment.project) as PsiFileFactoryImpl
private val psiFileFactory: PsiFileFactoryImpl = PsiFileFactory.getInstance(environment.project) as PsiFileFactoryImpl
// "line" - is the unit of evaluation here, could in fact consists of several character lines
protected class LineState(
internal class LineState(
val codeLine: ReplCodeLine,
val psiFile: KtFile,
val errorHolder: DiagnosticMessageHolder)
protected var lineState: LineState? = null
private var _lineState: LineState? = null
internal val lineState: LineState? get() = stateLock.read { _lineState }
fun createDiagnosticHolder() = ReplTerminalDiagnosticMessageHolder()
@Synchronized
override fun check(codeLine: ReplCodeLine, history: List<ReplCodeLine>): ReplCheckResult {
val virtualFile =
LightVirtualFile("line${codeLine.no}${KotlinParserDefinition.STD_SCRIPT_EXT}", KotlinLanguage.INSTANCE, codeLine.code).apply {
charset = CharsetToolkit.UTF8_CHARSET
}
val psiFile: KtFile = psiFileFactory.trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false) as KtFile?
?: error("Script file not analyzed at line ${codeLine.no}: ${codeLine.code}")
fun check(codeLine: ReplCodeLine, generation: Long): ReplCheckResult {
stateLock.write {
val scriptFileName = makeSriptBaseName(codeLine, generation)
val virtualFile =
LightVirtualFile("${scriptFileName}${KotlinParserDefinition.STD_SCRIPT_EXT}", KotlinLanguage.INSTANCE, codeLine.code).apply {
charset = CharsetToolkit.UTF8_CHARSET
}
val psiFile: KtFile = psiFileFactory.trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false) as KtFile?
?: error("Script file not analyzed at line ${codeLine.no}: ${codeLine.code}")
val errorHolder = createDiagnosticHolder()
val errorHolder = createDiagnosticHolder()
val syntaxErrorReport = AnalyzerWithCompilerReport.Companion.reportSyntaxErrors(psiFile, errorHolder)
val syntaxErrorReport = AnalyzerWithCompilerReport.reportSyntaxErrors(psiFile, errorHolder)
if (!syntaxErrorReport.isHasErrors) {
lineState = LineState(codeLine, psiFile, errorHolder)
}
if (!syntaxErrorReport.isHasErrors) {
_lineState = LineState(codeLine, psiFile, errorHolder)
}
return when {
syntaxErrorReport.isHasErrors && syntaxErrorReport.isAllErrorsAtEof -> ReplCheckResult.Incomplete(history)
syntaxErrorReport.isHasErrors -> ReplCheckResult.Error(history, errorHolder.renderedDiagnostics)
else -> ReplCheckResult.Ok(history)
return when {
syntaxErrorReport.isHasErrors && syntaxErrorReport.isAllErrorsAtEof -> ReplCheckResult.Incomplete()
syntaxErrorReport.isHasErrors -> ReplCheckResult.Error(errorHolder.renderedDiagnostics)
else -> ReplCheckResult.Ok()
}
}
}
}
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.cli.jvm.repl
import com.intellij.openapi.Disposable
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
@@ -29,78 +30,115 @@ 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
open class GenericReplCompiler(
disposable: Disposable,
scriptDefinition: KotlinScriptDefinition,
compilerConfiguration: CompilerConfiguration,
messageCollector: MessageCollector
) : ReplCompiler, GenericReplChecker(disposable, scriptDefinition, compilerConfiguration, messageCollector) {
private val analyzerEngine = CliReplAnalyzerEngine(environment)
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)
private val analyzerEngine = GenericReplAnalyzer(checker.environment, stateLock)
private var lastDependencies: KotlinScriptExternalDependencies? = null
private val descriptorsHistory = ReplHistory<ScriptDescriptor>()
@Synchronized
override fun compile(codeLine: ReplCodeLine, history: List<ReplCodeLine>): ReplCompileResult {
checkAndUpdateReplHistoryCollection(descriptorsHistory, history)?.let {
return@compile ReplCompileResult.HistoryMismatch(descriptorsHistory.lines, it)
}
private val generation = AtomicLong(1)
val (psiFile, errorHolder) = run {
if (lineState == null || lineState!!.codeLine != codeLine) {
val res = check(codeLine, history)
when (res) {
is ReplCheckResult.Incomplete -> return@compile ReplCompileResult.Incomplete(res.updatedHistory)
is ReplCheckResult.Error -> return@compile ReplCompileResult.Error(res.updatedHistory, res.message, res.location)
is ReplCheckResult.Ok -> {} // continue
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 mistmatch when resetting lines")
}
}
Pair(lineState!!.psiFile, lineState!!.errorHolder)
removedCompiledLines
}.map { it.first }
}
override val history: List<ReplCodeLine> get() = stateLock.read { descriptorsHistory.copySources() }
override fun check(codeLine: ReplCodeLine): ReplCheckResult {
return checker.check(codeLine, generation.get())
}
override fun compile(codeLine: ReplCodeLine, verifyHistory: List<ReplCodeLine>?): ReplCompileResult {
stateLock.write {
val firstMismatch = descriptorsHistory.firstMismatchingHistory(verifyHistory)
if (firstMismatch != null) {
return@compile ReplCompileResult.HistoryMismatch(descriptorsHistory.copySources(), firstMismatch)
}
val currentGeneration = generation.get()
val (psiFile, errorHolder) = run {
if (checker.lineState == null || checker.lineState!!.codeLine != codeLine) {
val res = checker.check(codeLine, currentGeneration)
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.Ok -> NO_ACTION()
}
}
Pair(checker.lineState!!.psiFile, checker.lineState!!.errorHolder)
}
val newDependencies = scriptDefinition.getDependenciesFor(psiFile, checker.environment.project, lastDependencies)
var classpathAddendum: List<File>? = null
if (lastDependencies != newDependencies) {
lastDependencies = newDependencies
classpathAddendum = newDependencies?.let { checker.environment.updateClasspath(it.classpath.map(::JvmClasspathRoot)) }
}
val analysisResult = 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
else -> error("Unexpected result ${analysisResult.javaClass}")
}
val state = GenerationState(
psiFile.project,
ClassBuilderFactories.binaries(false),
analyzerEngine.module,
analyzerEngine.trace.bindingContext,
listOf(psiFile),
compilerConfiguration
)
state.replSpecific.scriptResultFieldName = SCRIPT_RESULT_FIELD_NAME
state.replSpecific.earlierScriptsForReplInterpreter = descriptorsHistory.copyValues()
state.beforeCompile()
KotlinCodegenFacade.generatePackage(
state,
psiFile.script!!.getContainingKtFile().packageFqName,
setOf(psiFile.script!!.getContainingKtFile()),
org.jetbrains.kotlin.codegen.CompilationErrorHandler.THROW_EXCEPTION)
val generatedClassname = makeSriptBaseName(codeLine, currentGeneration)
val compiledCodeLine = CompiledReplCodeLine(generatedClassname, codeLine)
descriptorsHistory.add(compiledCodeLine, scriptDescriptor)
return ReplCompileResult.CompiledClasses(descriptorsHistory.copySources(),
compiledCodeLine,
generatedClassname,
state.factory.asList().map { CompiledClassData(it.relativePath, it.asByteArray()) },
state.replSpecific.hasResult,
classpathAddendum ?: emptyList())
}
val newDependencies = scriptDefinition.getDependenciesFor(psiFile, environment.project, lastDependencies)
var classpathAddendum: List<File>? = null
if (lastDependencies != newDependencies) {
lastDependencies = newDependencies
classpathAddendum = newDependencies?.let { environment.updateClasspath(it.classpath.map(::JvmClasspathRoot)) }
}
val analysisResult = analyzerEngine.analyzeReplLine(psiFile, codeLine.no)
AnalyzerWithCompilerReport.Companion.reportDiagnostics(analysisResult.diagnostics, errorHolder)
val scriptDescriptor = when (analysisResult) {
is CliReplAnalyzerEngine.ReplLineAnalysisResult.WithErrors -> return ReplCompileResult.Error(descriptorsHistory.lines, errorHolder.renderedDiagnostics)
is CliReplAnalyzerEngine.ReplLineAnalysisResult.Successful -> analysisResult.scriptDescriptor
else -> error("Unexpected result ${analysisResult.javaClass}")
}
val state = GenerationState(
psiFile.project,
ClassBuilderFactories.binaries(false),
analyzerEngine.module,
analyzerEngine.trace.bindingContext,
listOf(psiFile),
compilerConfiguration
)
state.replSpecific.scriptResultFieldName = SCRIPT_RESULT_FIELD_NAME
state.replSpecific.earlierScriptsForReplInterpreter = descriptorsHistory.values
state.beforeCompile()
KotlinCodegenFacade.generatePackage(
state,
psiFile.script!!.getContainingKtFile().packageFqName,
setOf(psiFile.script!!.getContainingKtFile()),
org.jetbrains.kotlin.codegen.CompilationErrorHandler.THROW_EXCEPTION)
descriptorsHistory.add(codeLine, scriptDescriptor)
return ReplCompileResult.CompiledClasses(descriptorsHistory.lines,
state.factory.asList().map { CompiledClassData(it.relativePath, it.asByteArray()) },
state.replSpecific.hasResult,
classpathAddendum ?: emptyList())
}
companion object {
private val SCRIPT_RESULT_FIELD_NAME = "\$\$result"
}
}
}
@@ -24,11 +24,10 @@ import com.intellij.psi.impl.PsiFileFactoryImpl
import com.intellij.testFramework.LightVirtualFile
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.cli.common.repl.ReplClassLoader
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
import org.jetbrains.kotlin.cli.jvm.repl.CliReplAnalyzerEngine.ReplLineAnalysisResult.Successful
import org.jetbrains.kotlin.cli.jvm.repl.CliReplAnalyzerEngine.ReplLineAnalysisResult.WithErrors
import org.jetbrains.kotlin.codegen.ClassBuilderFactories
import org.jetbrains.kotlin.codegen.CompilationErrorHandler
import org.jetbrains.kotlin.codegen.KotlinCodegenFacade
@@ -65,7 +64,7 @@ class ReplInterpreter(
}
private val psiFileFactory: PsiFileFactoryImpl = PsiFileFactory.getInstance(environment.project) as PsiFileFactoryImpl
private val analyzerEngine = CliReplAnalyzerEngine(environment)
private val analyzerEngine = GenericReplAnalyzer(environment)
fun eval(line: String): LineResult {
++lineNumber
@@ -99,11 +98,11 @@ class ReplInterpreter(
return LineResult.Error.CompileTime(errorHolder.renderedDiagnostics)
}
val analysisResult = analyzerEngine.analyzeReplLine(psiFile, lineNumber)
val analysisResult = analyzerEngine.analyzeReplLine(psiFile, ReplCodeLine(lineNumber, "fake line"))
AnalyzerWithCompilerReport.reportDiagnostics(analysisResult.diagnostics, errorHolder)
val scriptDescriptor = when (analysisResult) {
is WithErrors -> return LineResult.Error.CompileTime(errorHolder.renderedDiagnostics)
is Successful -> analysisResult.scriptDescriptor
is GenericReplAnalyzer.ReplLineAnalysisResult.WithErrors -> return LineResult.Error.CompileTime(errorHolder.renderedDiagnostics)
is GenericReplAnalyzer.ReplLineAnalysisResult.Successful -> analysisResult.scriptDescriptor
else -> error("Unexpected result ${analysisResult.javaClass}")
}