Refactor REPL infrastructure for passing evaluation/invoke wrapper to every eval/invoke function

This commit is contained in:
Ilya Chernikov
2016-12-07 13:32:46 +01:00
parent cb7f22ffec
commit 1cc85df78e
5 changed files with 62 additions and 41 deletions
@@ -41,7 +41,13 @@ open class GenericReplCompiledEvaluator(baseClasspath: Iterable<File>,
private val compiledLoadedClassesHistory = ReplHistory<ClassWithInstance>() private val compiledLoadedClassesHistory = ReplHistory<ClassWithInstance>()
override fun eval(codeLine: ReplCodeLine, history: List<ReplCodeLine>, compiledClasses: List<CompiledClassData>, hasResult: Boolean, classpathAddendum: List<File>): ReplEvalResult /*= evalStateLock.write*/ { 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 { checkAndUpdateReplHistoryCollection(compiledLoadedClassesHistory, history)?.let {
return@eval ReplEvalResult.HistoryMismatch(compiledLoadedClassesHistory.lines, it) return@eval ReplEvalResult.HistoryMismatch(compiledLoadedClassesHistory.lines, it)
} }
@@ -85,7 +91,7 @@ open class GenericReplCompiledEvaluator(baseClasspath: Iterable<File>,
val scriptInstanceConstructor = scriptClass.getConstructor(*constructorParams) val scriptInstanceConstructor = scriptClass.getConstructor(*constructorParams)
val scriptInstance = val scriptInstance =
try { try {
evalWithIO { scriptInstanceConstructor.newInstance(*constructorArgs) } invokeWrapper?.invoke { scriptInstanceConstructor.newInstance(*constructorArgs) } ?: scriptInstanceConstructor.newInstance(*constructorArgs)
} }
catch (e: Throwable) { catch (e: Throwable) {
// ignore everything in the stack trace until this constructor call // ignore everything in the stack trace until this constructor call
@@ -109,16 +115,16 @@ open class GenericReplCompiledEvaluator(baseClasspath: Iterable<File>,
return ReplScriptInvokeResult.ValueResult(klass.safeCast(receiver)) return ReplScriptInvokeResult.ValueResult(klass.safeCast(receiver))
} }
override fun invokeMethod(receiver: Any, name: String, vararg args: Any?): ReplScriptInvokeResult = evalStateLock.read { override fun invokeMethod(receiver: Any, name: String, vararg args: Any?, invokeWrapper: InvokeWrapper?): ReplScriptInvokeResult = evalStateLock.read {
return invokeImpl(receiver.javaClass.kotlin, receiver, name, args) return invokeImpl(receiver.javaClass.kotlin, receiver, name, args, invokeWrapper)
} }
override fun invokeFunction(name: String, vararg args: Any?): ReplScriptInvokeResult = evalStateLock.read { override fun invokeFunction(name: String, vararg args: Any?, invokeWrapper: InvokeWrapper?): ReplScriptInvokeResult = evalStateLock.read {
val (klass, instance) = compiledLoadedClassesHistory.values.lastOrNull() ?: return ReplScriptInvokeResult.Error.NoSuchEntity("no script ") val (klass, instance) = compiledLoadedClassesHistory.values.lastOrNull() ?: return ReplScriptInvokeResult.Error.NoSuchEntity("no script ")
return invokeImpl(klass.kotlin, instance, name, args) return invokeImpl(klass.kotlin, instance, name, args, invokeWrapper)
} }
private fun invokeImpl(receiverClass: KClass<*>, receiverInstance: Any, name: String, args: Array<out Any?>): ReplScriptInvokeResult { private fun invokeImpl(receiverClass: KClass<*>, receiverInstance: Any, name: String, args: Array<out Any?>, invokeWrapper: InvokeWrapper?): ReplScriptInvokeResult {
val candidates = receiverClass.memberFunctions.filter { it.name == name } + val candidates = receiverClass.memberFunctions.filter { it.name == name } +
receiverClass.memberExtensionFunctions.filter { it.name == name } receiverClass.memberExtensionFunctions.filter { it.name == name }
@@ -126,7 +132,7 @@ open class GenericReplCompiledEvaluator(baseClasspath: Iterable<File>,
candidates.findMapping(listOf<Any?>(receiverInstance) + args) ?: candidates.findMapping(listOf<Any?>(receiverInstance) + args) ?:
return ReplScriptInvokeResult.Error.NoSuchEntity("no suitable function '$name' found") return ReplScriptInvokeResult.Error.NoSuchEntity("no suitable function '$name' found")
val res = try { val res = try {
evalWithIO { fn.callBy(mapping) } invokeWrapper?.invoke { fn.callBy(mapping) } ?: fn.callBy(mapping)
} }
catch (e: Throwable) { catch (e: Throwable) {
// ignore everything in the stack trace until this constructor call // ignore everything in the stack trace until this constructor call
@@ -120,8 +120,8 @@ interface ReplCompiler : ReplChecker {
interface ReplScriptInvoker { interface ReplScriptInvoker {
fun <T: Any> getInterface(clasz: KClass<T>): ReplScriptInvokeResult fun <T: Any> getInterface(clasz: KClass<T>): ReplScriptInvokeResult
fun <T: Any> getInterface(receiver: Any, clasz: KClass<T>): ReplScriptInvokeResult fun <T: Any> getInterface(receiver: Any, clasz: KClass<T>): ReplScriptInvokeResult
fun invokeMethod(receiver: Any, name: String, vararg args: Any?): ReplScriptInvokeResult fun invokeMethod(receiver: Any, name: String, vararg args: Any?, invokeWrapper: InvokeWrapper? = null): ReplScriptInvokeResult
fun invokeFunction(name: String, vararg args: Any?): ReplScriptInvokeResult fun invokeFunction(name: String, vararg args: Any?, invokeWrapper: InvokeWrapper? = null): ReplScriptInvokeResult
} }
// TODO this is a bit cumbersome, consider some other ways to access an invoker // TODO this is a bit cumbersome, consider some other ways to access an invoker
@@ -131,17 +131,24 @@ interface ReplScriptInvokerProxy {
interface ReplCompiledEvaluator { interface ReplCompiledEvaluator {
fun eval(codeLine: ReplCodeLine, history: List<ReplCodeLine>, compiledClasses: List<CompiledClassData>, hasResult: Boolean, classpathAddendum: List<File>): ReplEvalResult fun eval(codeLine: ReplCodeLine,
history: List<ReplCodeLine>,
// override to capture output compiledClasses: List<CompiledClassData>,
fun<T> evalWithIO(body: () -> T): T = body() hasResult: Boolean,
classpathAddendum: List<File>,
invokeWrapper: InvokeWrapper? = null
): ReplEvalResult
} }
interface ReplEvaluator : ReplChecker { interface ReplEvaluator : ReplChecker {
fun eval(codeLine: ReplCodeLine, history: List<ReplCodeLine>): ReplEvalResult fun eval(codeLine: ReplCodeLine,
history: List<ReplCodeLine>,
// override to capture output invokeWrapper: InvokeWrapper? = null
fun<T> evalWithIO(body: () -> T): T = body() ): ReplEvalResult
}
interface InvokeWrapper {
operator fun<T> invoke(body: () -> T): T // e.g. for capturing io
} }
@@ -191,18 +191,18 @@ open class GenericRepl(
override val scriptInvoker: ReplScriptInvoker get() = compiledEvaluator override val scriptInvoker: ReplScriptInvoker get() = compiledEvaluator
@Synchronized @Synchronized
override fun eval(codeLine: ReplCodeLine, history: List<ReplCodeLine>): ReplEvalResult = override fun eval(codeLine: ReplCodeLine, history: List<ReplCodeLine>, invokeWrapper: InvokeWrapper?): ReplEvalResult =
compileAndEval(this, compiledEvaluator, codeLine, history) compileAndEval(this, compiledEvaluator, codeLine, history, invokeWrapper)
} }
fun compileAndEval(replCompiler: ReplCompiler, replCompiledEvaluator: ReplCompiledEvaluator, codeLine: ReplCodeLine, history: List<ReplCodeLine>): ReplEvalResult = fun compileAndEval(replCompiler: ReplCompiler, replCompiledEvaluator: ReplCompiledEvaluator, codeLine: ReplCodeLine, history: List<ReplCodeLine>, invokeWrapper: InvokeWrapper?): ReplEvalResult =
replCompiler.compile(codeLine, history).let { replCompiler.compile(codeLine, history).let {
when (it) { when (it) {
is ReplCompileResult.Incomplete -> ReplEvalResult.Incomplete(it.updatedHistory) is ReplCompileResult.Incomplete -> ReplEvalResult.Incomplete(it.updatedHistory)
is ReplCompileResult.HistoryMismatch -> ReplEvalResult.HistoryMismatch(it.updatedHistory, it.lineNo) is ReplCompileResult.HistoryMismatch -> ReplEvalResult.HistoryMismatch(it.updatedHistory, it.lineNo)
is ReplCompileResult.Error -> ReplEvalResult.Error.CompileTime(it.updatedHistory, it.message, it.location) 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) is ReplCompileResult.CompiledClasses -> replCompiledEvaluator.eval(codeLine, history, it.classes, it.hasResult, it.classpathAddendum, invokeWrapper)
} }
} }
@@ -138,7 +138,8 @@ class KotlinRemoteReplEvaluator(
operationsTracer = operationsTracer operationsTracer = operationsTracer
), ReplEvaluator { ), ReplEvaluator {
override fun eval(codeLine: ReplCodeLine, history: List<ReplCodeLine>): ReplEvalResult { // TODO: invokeWrapper is ignored here, and in the daemon the session wrapper is used instead; So consider to make it per call (avoid performance penalties though)
override fun eval(codeLine: ReplCodeLine, history: List<ReplCodeLine>, invokeWrapper: InvokeWrapper?): ReplEvalResult {
return compileService.remoteReplLineEval(sessionId, codeLine, history).get() return compileService.remoteReplLineEval(sessionId, codeLine, history).get()
} }
} }
@@ -115,32 +115,32 @@ open class KotlinJvmReplService(
else GenericReplCompiler(disposable, scriptDef, configuration, messageCollector) else GenericReplCompiler(disposable, scriptDef, configuration, messageCollector)
} }
private val compiledEvaluator : GenericReplCompiledEvaluator by lazy { private val invokeWrapper : InvokeWrapper? by lazy {
if (evalOutputStream == null && evalErrorStream == null && evalInputStream == null) if (evalOutputStream == null && evalErrorStream == null && evalInputStream == null) null
GenericReplCompiledEvaluator(configuration.jvmClasspathRoots, null, scriptArgs, scriptArgsTypes) else object : InvokeWrapper {
else object : GenericReplCompiledEvaluator(configuration.jvmClasspathRoots, null, scriptArgs, scriptArgsTypes) { val out = evalOutputStream?.let { PrintStream(BufferedOutputStream(RemoteOutputStreamClient(it, DummyProfiler()), REMOTE_STREAM_BUFFER_SIZE)) }
val out = PrintStream(BufferedOutputStream(RemoteOutputStreamClient(evalOutputStream!!, DummyProfiler()), REMOTE_STREAM_BUFFER_SIZE)) val err = evalErrorStream?.let { PrintStream(BufferedOutputStream(RemoteOutputStreamClient(it, DummyProfiler()), REMOTE_STREAM_BUFFER_SIZE)) }
val err = PrintStream(BufferedOutputStream(RemoteOutputStreamClient(evalErrorStream!!, DummyProfiler()), REMOTE_STREAM_BUFFER_SIZE)) val `in` = evalInputStream?.let { BufferedInputStream(RemoteInputStreamClient(it, DummyProfiler()), REMOTE_STREAM_BUFFER_SIZE) }
val `in` = BufferedInputStream(RemoteInputStreamClient(evalInputStream!!, DummyProfiler()), REMOTE_STREAM_BUFFER_SIZE) override operator fun<T> invoke(body: () -> T): T {
override fun<T> evalWithIO(body: () -> T): T { val prevOut = swapOrNull(out, { System.out }, { System.setOut(it) })
val prevOut = System.out val prevErr = swapOrNull(err, { System.err }, { System.setErr(it) })
System.setOut(out) val prevIn = swapOrNull(`in`, { System.`in` }, { System.setIn(it) })
val prevErr = System.err
System.setErr(err)
val prevIn = System.`in`
System.setIn(`in`)
try { try {
return body() return body()
} }
finally { finally {
System.setIn(prevIn) prevIn?.let { System.setIn(prevIn) }
System.setErr(prevErr) prevErr?.let { System.setErr(prevErr) }
System.setOut(prevOut) prevOut?.let { System.setOut(prevOut) }
} }
} }
} }
} }
private val compiledEvaluator : GenericReplCompiledEvaluator by lazy {
GenericReplCompiledEvaluator(configuration.jvmClasspathRoots, null, scriptArgs, scriptArgsTypes)
}
override fun check(codeLine: ReplCodeLine, history: List<ReplCodeLine>): ReplCheckResult { override fun check(codeLine: ReplCodeLine, history: List<ReplCodeLine>): ReplCheckResult {
operationsTracer?.before("check") operationsTracer?.before("check")
try { try {
@@ -167,10 +167,10 @@ open class KotlinJvmReplService(
} }
} }
override fun eval(codeLine: ReplCodeLine, history: List<ReplCodeLine>): ReplEvalResult = synchronized(this) { override fun eval(codeLine: ReplCodeLine, history: List<ReplCodeLine>, invokeWrapper: InvokeWrapper?): ReplEvalResult = synchronized(this) {
operationsTracer?.before("eval") operationsTracer?.before("eval")
try { try {
return replCompiler?.let { compileAndEval(it, compiledEvaluator, codeLine, history) } return replCompiler?.let { compileAndEval(it, compiledEvaluator, codeLine, history, invokeWrapper) }
?: ReplEvalResult.Error.CompileTime(history, ?: ReplEvalResult.Error.CompileTime(history,
messageCollector.firstErrorMessage ?: "Unknown error", messageCollector.firstErrorMessage ?: "Unknown error",
messageCollector.firstErrorLocation ?: CompilerMessageLocation.NO_LOCATION) messageCollector.firstErrorLocation ?: CompilerMessageLocation.NO_LOCATION)
@@ -180,3 +180,10 @@ open class KotlinJvmReplService(
} }
} }
} }
private inline fun<T> swapOrNull(value: T?, get: () -> T, set: (T) -> Unit): T? =
value?.let {
val prevValue = get()
set(value)
prevValue
}