Implement Invocable on base evaluator and locally-evaluating JSR223 sample engines

fixes #KT-14707
This commit is contained in:
Ilya Chernikov
2016-12-01 12:30:30 +01:00
parent fbd4c6eb61
commit c82e91eafe
9 changed files with 166 additions and 46 deletions
@@ -24,13 +24,12 @@ import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
import kotlin.reflect.*
import kotlin.reflect.jvm.javaType
open class GenericReplCompiledEvaluator(baseClasspath: Iterable<File>,
baseClassloader: ClassLoader?,
val scriptArgs: Array<Any?>? = null,
val scriptArgsTypes: Array<Class<*>>? = null
) : ReplCompiledEvaluator, ReplInvoker {
) : ReplCompiledEvaluator, ReplScriptInvoker {
private var classLoader: org.jetbrains.kotlin.cli.common.repl.ReplClassLoader = makeReplClassLoader(baseClassloader, baseClasspath)
private val classLoaderLock = ReentrantReadWriteLock()
@@ -101,36 +100,39 @@ open class GenericReplCompiledEvaluator(baseClasspath: Iterable<File>,
return if (hasResult) ReplEvalResult.ValueResult(compiledLoadedClassesHistory.lines, rv) else ReplEvalResult.UnitResult(compiledLoadedClassesHistory.lines)
}
override fun <T: Any> getInterface(klass: KClass<T>): ReplInvokeResult = evalStateLock.read {
val (_, instance) = compiledLoadedClassesHistory.values.lastOrNull() ?: return ReplInvokeResult.Error.NoSuchEntity("no script ")
override fun <T: Any> getInterface(klass: KClass<T>): ReplScriptInvokeResult = evalStateLock.read {
val (_, instance) = compiledLoadedClassesHistory.values.lastOrNull() ?: return ReplScriptInvokeResult.Error.NoSuchEntity("no script ")
return getInterface(instance, klass)
}
override fun <T: Any> getInterface(receiver: Any, klass: KClass<T>): ReplInvokeResult = evalStateLock.read {
return ReplInvokeResult.ValueResult(klass.safeCast(receiver))
override fun <T: Any> getInterface(receiver: Any, klass: KClass<T>): ReplScriptInvokeResult = evalStateLock.read {
return ReplScriptInvokeResult.ValueResult(klass.safeCast(receiver))
}
override fun invokeMethod(receiver: Any, name: String, vararg args: Any?): ReplInvokeResult = evalStateLock.read {
override fun invokeMethod(receiver: Any, name: String, vararg args: Any?): ReplScriptInvokeResult = evalStateLock.read {
return invokeImpl(receiver.javaClass.kotlin, receiver, name, args)
}
override fun invokeFunction(name: String, vararg args: Any?): ReplInvokeResult = evalStateLock.read {
val (klass, instance) = compiledLoadedClassesHistory.values.lastOrNull() ?: return ReplInvokeResult.Error.NoSuchEntity("no script ")
override fun invokeFunction(name: String, vararg args: Any?): ReplScriptInvokeResult = evalStateLock.read {
val (klass, instance) = compiledLoadedClassesHistory.values.lastOrNull() ?: return ReplScriptInvokeResult.Error.NoSuchEntity("no script ")
return invokeImpl(klass.kotlin, instance, name, args)
}
private fun invokeImpl(receiverClass: KClass<*>, receiverInstance: Any, name: String, args: Array<out Any?>): ReplInvokeResult {
val (fn, mapping) = receiverClass.functions.filter { it.name == name }.findMapping(args.toList()) ?:
receiverClass.memberExtensionFunctions.filter { it.name == name }.findMapping(listOf<Any?>(receiverInstance) + args) ?:
return ReplInvokeResult.Error.NoSuchEntity("no suitable function '$name' found")
private fun invokeImpl(receiverClass: KClass<*>, receiverInstance: Any, name: String, args: Array<out Any?>): ReplScriptInvokeResult {
val candidates = receiverClass.memberFunctions.filter { it.name == name } +
receiverClass.memberExtensionFunctions.filter { it.name == name }
val (fn, mapping) = candidates.findMapping(args.toList()) ?:
candidates.findMapping(listOf<Any?>(receiverInstance) + args) ?:
return ReplScriptInvokeResult.Error.NoSuchEntity("no suitable function '$name' found")
val res = try {
evalWithIO { fn.callBy(mapping) }
}
catch (e: Throwable) {
// ignore everything in the stack trace until this constructor call
return ReplInvokeResult.Error.Runtime(renderReplStackTrace(e.cause!!, startFromMethodName = "${fn.name}"), e as? Exception)
return ReplScriptInvokeResult.Error.Runtime(renderReplStackTrace(e.cause!!, startFromMethodName = "${fn.name}"), e as? Exception)
}
return if (fn.returnType.classifier == Unit::class) ReplInvokeResult.UnitResult else ReplInvokeResult.ValueResult(res)
return if (fn.returnType.classifier == Unit::class) ReplScriptInvokeResult.UnitResult else ReplScriptInvokeResult.ValueResult(res)
}
companion object {
@@ -60,23 +60,48 @@ abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEn
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun invokeMethod(p0: Any?, p1: String?, vararg p2: Any?): Any {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun <T : Any?> getInterface(p0: Class<T>?): T {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun <T : Any?> getInterface(p0: Any?, p1: Class<T>?): T {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun invokeFunction(p0: String?, vararg p1: Any?): Any {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun createBindings(): Bindings = SimpleBindings()
override fun getFactory(): ScriptEngineFactory = myFactory
}
@Suppress("unused") // used externally (kotlin.script.utils)
interface KotlinJsr223JvmInvocableScriptEngine : Invocable {
val replScriptInvoker: ReplScriptInvoker
override fun invokeFunction(name: String?, vararg args: Any?): Any? {
if (name == null) throw java.lang.NullPointerException("function name cannot be null")
return processInvokeResult(replScriptInvoker.invokeFunction(name, *args), isMethod = true)
}
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 processInvokeResult(replScriptInvoker.invokeMethod(thiz, name, *args), isMethod = true)
}
override fun <T : Any> getInterface(clasz: Class<T>?): T? {
if (clasz == null) throw IllegalArgumentException("class object cannot be null")
if (!clasz.isInterface) throw IllegalArgumentException("expecting interface")
return processInvokeResult(replScriptInvoker.getInterface(clasz.kotlin), isMethod = false) as? T
}
override fun <T : Any> getInterface(thiz: Any?, clasz: Class<T>?): T? {
if (thiz == null) throw IllegalArgumentException("object cannot be null")
if (clasz == null) throw IllegalArgumentException("class object cannot be null")
if (!clasz.isInterface) throw IllegalArgumentException("expecting interface")
return processInvokeResult(replScriptInvoker.getInterface(thiz, clasz.kotlin), isMethod = false) as? T
}
private fun processInvokeResult(res: ReplScriptInvokeResult, isMethod: Boolean): Any? =
when (res) {
is ReplScriptInvokeResult.Error.NoSuchEntity -> throw if (isMethod) NoSuchMethodException(res.message) else IllegalArgumentException(res.message)
is ReplScriptInvokeResult.Error.CompileTime -> throw IllegalArgumentException(res.message) // should not happen in the current code, so leaving it here despite the contradiction with Invocable's specs
is ReplScriptInvokeResult.Error.Runtime -> throw ScriptException(res.message)
is ReplScriptInvokeResult.Error -> throw ScriptException(res.message)
is ReplScriptInvokeResult.UnitResult -> Unit // TODO: check if it is suitable replacement for java's Void
is ReplScriptInvokeResult.ValueResult -> res.value
}
}
@@ -73,12 +73,12 @@ sealed class ReplCompileResult(val updatedHistory: List<ReplCodeLine>) : Seriali
}
}
sealed class ReplInvokeResult : Serializable {
class ValueResult(val value: Any?) : ReplInvokeResult() {
sealed class ReplScriptInvokeResult : Serializable {
class ValueResult(val value: Any?) : ReplScriptInvokeResult() {
override fun toString(): String = "Result: $value"
}
object UnitResult : ReplInvokeResult()
sealed class Error(val message: String) : ReplInvokeResult() {
object UnitResult : ReplScriptInvokeResult()
sealed class Error(val message: String) : ReplScriptInvokeResult() {
class Runtime(message: String, val cause: Exception? = null) : Error(message)
class NoSuchEntity(message: String) : Error(message)
class CompileTime(message: String, val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION) : Error(message)
@@ -117,11 +117,16 @@ interface ReplCompiler : ReplChecker {
fun compile(codeLine: ReplCodeLine, history: List<ReplCodeLine>): ReplCompileResult
}
interface ReplInvoker {
fun <T: Any> getInterface(clasz: KClass<T>): ReplInvokeResult
fun <T: Any> getInterface(receiver: Any, clasz: KClass<T>): ReplInvokeResult
fun invokeMethod(receiver: Any, name: String, vararg args: Any?): ReplInvokeResult
fun invokeFunction(name: String, vararg args: Any?): ReplInvokeResult
interface ReplScriptInvoker {
fun <T: Any> getInterface(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 invokeFunction(name: String, vararg args: Any?): ReplScriptInvokeResult
}
// TODO this is a bit cumbersome, consider some other ways to access an invoker
interface ReplScriptInvokerProxy {
val scriptInvoker: ReplScriptInvoker
}
interface ReplCompiledEvaluator {