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:
-124
@@ -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))
|
||||
|
||||
+112
@@ -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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
+209
@@ -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))
|
||||
}
|
||||
|
||||
+78
-21
@@ -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
|
||||
|
||||
+41
-25
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user