Implement JSR-223 functionality on top of the "new" REPL
This commit is contained in:
+15
-12
@@ -60,18 +60,21 @@ open class BasicReplStageHistory<T>(override val lock: ReentrantReadWriteLock =
|
||||
|
||||
override fun resetTo(id: ILineId): Iterable<ILineId> {
|
||||
lock.write {
|
||||
val idx = indexOfFirst { it.id == id }
|
||||
if (idx < 0) throw java.util.NoSuchElementException("Cannot rest to inexistent line ${id.no}")
|
||||
return if (idx < lastIndex) {
|
||||
val removed = asSequence().drop(idx + 1).map { it.id }.toList()
|
||||
removeRange(idx + 1, size)
|
||||
currentGeneration.incrementAndGet()
|
||||
removed
|
||||
}
|
||||
else {
|
||||
currentGeneration.incrementAndGet()
|
||||
emptyList()
|
||||
}
|
||||
return tryResetTo(id) ?: throw NoSuchElementException("Cannot reset to non-existent line ${id.no}")
|
||||
}
|
||||
}
|
||||
|
||||
protected fun tryResetTo(id: ILineId): List<ILineId>? {
|
||||
val idx = indexOfFirst { it.id == id }
|
||||
if (idx < 0) return null
|
||||
return if (idx < lastIndex) {
|
||||
val removed = asSequence().drop(idx + 1).map { it.id }.toList()
|
||||
removeRange(idx + 1, size)
|
||||
currentGeneration.incrementAndGet()
|
||||
removed
|
||||
} else {
|
||||
currentGeneration.incrementAndGet()
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+21
-11
@@ -20,13 +20,11 @@ import java.io.File
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||
import kotlin.concurrent.write
|
||||
|
||||
class GenericReplCompilingEvaluator(val compiler: ReplCompiler,
|
||||
baseClasspath: Iterable<File>,
|
||||
baseClassloader: ClassLoader? = Thread.currentThread().contextClassLoader,
|
||||
private val fallbackScriptArgs: ScriptArgsWithTypes? = null,
|
||||
repeatingMode: ReplRepeatingMode = ReplRepeatingMode.REPEAT_ONLY_MOST_RECENT
|
||||
open class GenericReplCompilingEvaluatorBase(
|
||||
val compiler: ReplCompiler,
|
||||
val evaluator: ReplEvaluator,
|
||||
private val fallbackScriptArgs: ScriptArgsWithTypes? = null
|
||||
) : ReplFullEvaluator {
|
||||
private val evaluator = GenericReplEvaluator(baseClasspath, baseClassloader, fallbackScriptArgs, repeatingMode)
|
||||
|
||||
override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> = AggregatedReplStageState(compiler.createState(lock), evaluator.createState(lock), lock)
|
||||
|
||||
@@ -75,7 +73,7 @@ class GenericReplCompilingEvaluator(val compiler: ReplCompiler,
|
||||
}
|
||||
|
||||
override fun eval(state: IReplStageState<*>, compileResult: ReplCompileResult.CompiledClasses, scriptArgs: ScriptArgsWithTypes?, invokeWrapper: InvokeWrapper?): ReplEvalResult =
|
||||
evaluator.eval(state, compileResult, scriptArgs, invokeWrapper)
|
||||
evaluator.eval(state, compileResult, scriptArgs, invokeWrapper)
|
||||
|
||||
override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult = compiler.check(state, codeLine)
|
||||
|
||||
@@ -93,12 +91,24 @@ class GenericReplCompilingEvaluator(val compiler: ReplCompiler,
|
||||
private val evaluator: ReplEvaluator,
|
||||
private val defaultScriptArgs: ScriptArgsWithTypes?) : Evaluable {
|
||||
override fun eval(scriptArgs: ScriptArgsWithTypes?, invokeWrapper: InvokeWrapper?): ReplEvalResult =
|
||||
evaluator.eval(state, compiledCode, scriptArgs ?: defaultScriptArgs, invokeWrapper)
|
||||
evaluator.eval(state, compiledCode, scriptArgs ?: defaultScriptArgs, invokeWrapper)
|
||||
}
|
||||
}
|
||||
|
||||
class GenericReplCompilingEvaluator(
|
||||
compiler: ReplCompiler,
|
||||
baseClasspath: Iterable<File>,
|
||||
baseClassloader: ClassLoader? = Thread.currentThread().contextClassLoader,
|
||||
fallbackScriptArgs: ScriptArgsWithTypes? = null,
|
||||
repeatingMode: ReplRepeatingMode = ReplRepeatingMode.REPEAT_ONLY_MOST_RECENT
|
||||
) : GenericReplCompilingEvaluatorBase(
|
||||
compiler,
|
||||
GenericReplEvaluator(baseClasspath, baseClassloader, fallbackScriptArgs, repeatingMode),
|
||||
fallbackScriptArgs
|
||||
)
|
||||
|
||||
private fun AggregatedReplStageState<*, *>.adjustHistories(): Iterable<ILineId>? =
|
||||
state2.history.peek()?.let {
|
||||
state1.history.resetTo(it.id)
|
||||
}
|
||||
state2.history.peek()?.let {
|
||||
state1.history.resetTo(it.id)
|
||||
}
|
||||
?: state1.history.reset()
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
|
||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
import org.gradle.jvm.tasks.Jar
|
||||
|
||||
description = "Kotlin Scripting JSR-223 support"
|
||||
|
||||
plugins { java }
|
||||
|
||||
val packedJars by configurations.creating
|
||||
|
||||
dependencies {
|
||||
packedJars(project(":kotlin-scripting-jsr223")) { isTransitive = false }
|
||||
runtime(project(":kotlin-script-runtime"))
|
||||
runtime(kotlinStdlib())
|
||||
runtime(project(":kotlin-scripting-common"))
|
||||
runtime(project(":kotlin-scripting-jvm"))
|
||||
runtime(project(":kotlin-script-util"))
|
||||
runtime(projectRuntimeJar(":kotlin-compiler-embeddable"))
|
||||
runtime(project(":kotlin-scripting-compiler-embeddable"))
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
"main" {}
|
||||
"test" {}
|
||||
}
|
||||
|
||||
publish()
|
||||
|
||||
noDefaultJar()
|
||||
|
||||
runtimeJar(rewriteDepsToShadedCompiler(
|
||||
task<ShadowJar>("shadowJar") {
|
||||
from(packedJars)
|
||||
}
|
||||
))
|
||||
sourcesJar()
|
||||
javadocJar()
|
||||
@@ -0,0 +1,32 @@
|
||||
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
}
|
||||
|
||||
jvmTarget = "1.6"
|
||||
|
||||
dependencies {
|
||||
compile(project(":kotlin-script-runtime"))
|
||||
compile(kotlinStdlib())
|
||||
compile(project(":kotlin-scripting-common"))
|
||||
compile(project(":kotlin-scripting-jvm"))
|
||||
compile(project(":kotlin-scripting-jvm-host"))
|
||||
compile(project(":kotlin-script-util"))
|
||||
compile(project(":kotlin-scripting-compiler"))
|
||||
compile(project(":kotlin-scripting-compiler-impl"))
|
||||
compileOnly(project(":compiler:cli"))
|
||||
compileOnly(project(":kotlin-reflect-api"))
|
||||
compileOnly(intellijCoreDep())
|
||||
runtime(projectRuntimeJar(":kotlin-compiler"))
|
||||
runtime(project(":kotlin-reflect"))
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
"main" { projectDefault() }
|
||||
"test" { projectDefault() }
|
||||
}
|
||||
|
||||
publish()
|
||||
|
||||
standardPublicJars()
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
kotlin.script.experimental.jsr223.KotlinJsr223JvmScriptEngineFactory
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package kotlin.script.experimental.jsr223
|
||||
|
||||
import org.jetbrains.kotlin.cli.common.repl.KOTLIN_SCRIPT_ENGINE_BINDINGS_KEY
|
||||
import org.jetbrains.kotlin.cli.common.repl.KOTLIN_SCRIPT_STATE_BINDINGS_KEY
|
||||
import javax.script.Bindings
|
||||
import javax.script.ScriptEngine
|
||||
import kotlin.script.experimental.annotations.KotlinScript
|
||||
import kotlin.script.templates.ScriptTemplateDefinition
|
||||
import kotlin.script.templates.standard.ScriptTemplateWithBindings
|
||||
|
||||
@Suppress("unused")
|
||||
@KotlinScript
|
||||
abstract class KotlinJsr223DefaultScript(val jsr223Bindings: Bindings) : ScriptTemplateWithBindings(jsr223Bindings) {
|
||||
|
||||
private val myEngine: ScriptEngine? get() = bindings[KOTLIN_SCRIPT_ENGINE_BINDINGS_KEY]?.let { it as? ScriptEngine }
|
||||
|
||||
private inline fun <T> withMyEngine(body: (ScriptEngine) -> T): T =
|
||||
myEngine?.let(body) ?: throw IllegalStateException("Script engine for `eval` call is not found")
|
||||
|
||||
fun eval(script: String, newBindings: Bindings): Any? =
|
||||
withMyEngine {
|
||||
val savedState =
|
||||
newBindings[KOTLIN_SCRIPT_STATE_BINDINGS_KEY]?.takeIf { it === this.jsr223Bindings[KOTLIN_SCRIPT_STATE_BINDINGS_KEY] }
|
||||
?.apply {
|
||||
newBindings[KOTLIN_SCRIPT_STATE_BINDINGS_KEY] = null
|
||||
}
|
||||
val res = it.eval(script, newBindings)
|
||||
savedState?.apply {
|
||||
newBindings[KOTLIN_SCRIPT_STATE_BINDINGS_KEY] = savedState
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fun eval(script: String): Any? =
|
||||
withMyEngine {
|
||||
val savedState = jsr223Bindings.remove(KOTLIN_SCRIPT_STATE_BINDINGS_KEY)
|
||||
val res = it.eval(script, jsr223Bindings)
|
||||
savedState?.apply {
|
||||
jsr223Bindings[KOTLIN_SCRIPT_STATE_BINDINGS_KEY] = savedState
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fun createBindings(): Bindings = withMyEngine { it.createBindings() }
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package kotlin.script.experimental.jsr223
|
||||
|
||||
import org.jetbrains.kotlin.cli.common.repl.InvokeWrapper
|
||||
import org.jetbrains.kotlin.cli.common.repl.renderReplStackTrace
|
||||
import org.jetbrains.kotlin.utils.tryCreateCallableMapping
|
||||
import java.lang.reflect.Proxy
|
||||
import javax.script.Invocable
|
||||
import javax.script.ScriptException
|
||||
import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.KParameter
|
||||
import kotlin.reflect.full.functions
|
||||
import kotlin.reflect.full.safeCast
|
||||
|
||||
interface KotlinJsr223InvocableScriptEngine : Invocable {
|
||||
|
||||
val invokeWrapper: InvokeWrapper?
|
||||
|
||||
val backwardInstancesHistory: Sequence<Any>
|
||||
|
||||
val baseClassLoader: ClassLoader
|
||||
|
||||
fun instancesForInvokeSearch(requestedReceiver: Any) =
|
||||
sequenceOf(requestedReceiver) + backwardInstancesHistory.filterNot { it == requestedReceiver }
|
||||
|
||||
override fun invokeFunction(name: String?, vararg args: Any?): Any? {
|
||||
if (name == null) throw java.lang.NullPointerException("function name cannot be null")
|
||||
return invokeImpl(backwardInstancesHistory, 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(instancesForInvokeSearch(thiz), name, args)
|
||||
}
|
||||
|
||||
private fun invokeImpl(possibleReceivers: Sequence<Any>, name: String, args: Array<out Any?>): Any? {
|
||||
// TODO: cache the method lookups?
|
||||
|
||||
val (fn, mapping) = possibleReceivers.mapNotNull { instance ->
|
||||
val candidates = instance::class.functions.filter { it.name == name }
|
||||
candidates.findMapping(listOf(instance) + args)
|
||||
}.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? {
|
||||
return proxyInterface(backwardInstancesHistory, clasz)
|
||||
}
|
||||
|
||||
override fun <T : Any> getInterface(thiz: Any?, clasz: Class<T>?): T? {
|
||||
if (thiz == null) throw IllegalArgumentException("object cannot be null")
|
||||
return proxyInterface(instancesForInvokeSearch(thiz), clasz)
|
||||
}
|
||||
|
||||
private fun <T : Any> proxyInterface(possibleReceivers: Sequence<Any>, clasz: Class<T>?): T? {
|
||||
if (clasz == null) throw IllegalArgumentException("class object cannot be null")
|
||||
if (!clasz.isInterface) throw IllegalArgumentException("expecting interface")
|
||||
|
||||
// TODO: cache the method lookups?
|
||||
|
||||
val proxy = Proxy.newProxyInstance(baseClassLoader, arrayOf(clasz)) { _, method, args ->
|
||||
invokeImpl(possibleReceivers, method.name, args ?: emptyArray())
|
||||
}
|
||||
return clasz.kotlin.safeCast(proxy)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Iterable<KFunction<*>>.findMapping(args: List<Any?>): Pair<KFunction<*>, Map<KParameter, Any?>>? {
|
||||
for (fn in this) {
|
||||
val mapping = tryCreateCallableMapping(fn, args)
|
||||
if (mapping != null) return fn to mapping
|
||||
}
|
||||
return null
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package kotlin.script.experimental.jsr223
|
||||
|
||||
import org.jetbrains.kotlin.cli.common.repl.*
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||
import javax.script.ScriptContext
|
||||
import javax.script.ScriptEngineFactory
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.script.experimental.api.ScriptEvaluationConfiguration
|
||||
import kotlin.script.experimental.jvm.baseClassLoader
|
||||
import kotlin.script.experimental.jvm.dependenciesFromCurrentContext
|
||||
import kotlin.script.experimental.jvm.jvm
|
||||
import kotlin.script.experimental.jvmhost.createJvmCompilationConfigurationFromTemplate
|
||||
import kotlin.script.experimental.jvmhost.createJvmEvaluationConfigurationFromTemplate
|
||||
import kotlin.script.experimental.jvmhost.repl.JvmReplCompiler
|
||||
import kotlin.script.experimental.jvmhost.repl.JvmReplEvaluator
|
||||
import kotlin.script.experimental.jvmhost.repl.JvmReplEvaluatorState
|
||||
|
||||
class KotlinJsr223ScriptEngine(
|
||||
factory: ScriptEngineFactory,
|
||||
val getScriptArgs: (ScriptContext, Array<out KClass<out Any>>?) -> ScriptArgsWithTypes?,
|
||||
val scriptArgsTypes: Array<out KClass<out Any>>?
|
||||
) : KotlinJsr223JvmScriptEngineBase(factory), KotlinJsr223InvocableScriptEngine {
|
||||
|
||||
val compilationConfiguration = createJvmCompilationConfigurationFromTemplate<KotlinJsr223DefaultScript> {
|
||||
jvm {
|
||||
dependenciesFromCurrentContext(wholeClasspath = true)
|
||||
}
|
||||
}
|
||||
|
||||
val evaluationConfiguration = createJvmEvaluationConfigurationFromTemplate<KotlinJsr223DefaultScript>()
|
||||
|
||||
override val replCompiler: ReplCompiler by lazy {
|
||||
JvmReplCompiler(compilationConfiguration)
|
||||
}
|
||||
private val localEvaluator by lazy {
|
||||
GenericReplCompilingEvaluatorBase(replCompiler, JvmReplEvaluator(evaluationConfiguration))
|
||||
}
|
||||
|
||||
override val replEvaluator: ReplFullEvaluator get() = localEvaluator
|
||||
|
||||
internal val state: IReplStageState<*> get() = getCurrentState(getContext())
|
||||
|
||||
override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> = replEvaluator.createState(lock)
|
||||
|
||||
override fun overrideScriptArgs(context: ScriptContext): ScriptArgsWithTypes? = getScriptArgs(context, scriptArgsTypes)
|
||||
|
||||
override val invokeWrapper: InvokeWrapper?
|
||||
get() = null
|
||||
|
||||
override val backwardInstancesHistory: Sequence<Any>
|
||||
get() = getCurrentState(getContext()).asState(JvmReplEvaluatorState::class.java).history.asReversed().asSequence().map { it.item }
|
||||
|
||||
override val baseClassLoader: ClassLoader
|
||||
get() = evaluationConfiguration[ScriptEvaluationConfiguration.jvm.baseClassLoader]!!
|
||||
}
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
@file:Suppress("unused")
|
||||
|
||||
// could be used externally in javax.script.ScriptEngineFactory META-INF file
|
||||
|
||||
package kotlin.script.experimental.jsr223
|
||||
|
||||
import org.jetbrains.kotlin.cli.common.repl.KotlinJsr223JvmScriptEngineFactoryBase
|
||||
import org.jetbrains.kotlin.cli.common.repl.ScriptArgsWithTypes
|
||||
import org.jetbrains.kotlin.script.util.*
|
||||
import javax.script.Bindings
|
||||
import javax.script.ScriptContext
|
||||
import javax.script.ScriptEngine
|
||||
|
||||
class KotlinJsr223JvmScriptEngineFactory : KotlinJsr223JvmScriptEngineFactoryBase() {
|
||||
|
||||
override fun getScriptEngine(): ScriptEngine =
|
||||
KotlinJsr223ScriptEngine(
|
||||
this,
|
||||
{ ctx, types -> ScriptArgsWithTypes(arrayOf(ctx.getBindings(ScriptContext.ENGINE_SCOPE)), types ?: emptyArray()) },
|
||||
arrayOf(Bindings::class)
|
||||
)
|
||||
}
|
||||
|
||||
+214
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package kotlin.script.experimental.jsr223.test
|
||||
|
||||
import org.jetbrains.kotlin.cli.common.environment.setIdeaIoUseFallback
|
||||
import org.jetbrains.kotlin.config.KotlinCompilerVersion
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import javax.script.*
|
||||
import kotlin.script.experimental.jsr223.KotlinJsr223ScriptEngine
|
||||
|
||||
class KotlinJsr223ScriptEngineIT {
|
||||
|
||||
init {
|
||||
setIdeaIoUseFallback()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEngineFactory() {
|
||||
val factory = ScriptEngineManager().getEngineByExtension("kts").factory
|
||||
Assert.assertNotNull(factory)
|
||||
factory!!.apply {
|
||||
Assert.assertEquals("kotlin", languageName)
|
||||
Assert.assertEquals(KotlinCompilerVersion.VERSION, languageVersion)
|
||||
Assert.assertEquals("kotlin", engineName)
|
||||
Assert.assertEquals(KotlinCompilerVersion.VERSION, engineVersion)
|
||||
Assert.assertEquals(listOf("kts"), extensions)
|
||||
Assert.assertEquals(listOf("text/x-kotlin"), mimeTypes)
|
||||
Assert.assertEquals(listOf("kotlin"), names)
|
||||
Assert.assertEquals("obj.method(arg1, arg2, arg3)", getMethodCallSyntax("obj", "method", "arg1", "arg2", "arg3"))
|
||||
Assert.assertEquals("print(\"Hello, world!\")", getOutputStatement("Hello, world!"))
|
||||
Assert.assertEquals(KotlinCompilerVersion.VERSION, getParameter(ScriptEngine.LANGUAGE_VERSION))
|
||||
val sep = System.getProperty("line.separator")
|
||||
val prog = arrayOf("val x: Int = 3", "var y = x + 2")
|
||||
Assert.assertEquals(prog.joinToString(sep) + sep, getProgram(*prog))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEngine() {
|
||||
val factory = ScriptEngineManager().getEngineByExtension("kts").factory
|
||||
Assert.assertNotNull(factory)
|
||||
val engine = factory!!.scriptEngine
|
||||
Assert.assertNotNull(engine as? KotlinJsr223ScriptEngine)
|
||||
Assert.assertSame(factory, engine!!.factory)
|
||||
val bindings = engine.createBindings()
|
||||
Assert.assertTrue(bindings is SimpleBindings)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSimpleEval() {
|
||||
val engine = ScriptEngineManager().getEngineByExtension("kts")!!
|
||||
val res1 = engine.eval("val x = 3")
|
||||
Assert.assertNull(res1)
|
||||
val res2 = engine.eval("x + 2")
|
||||
Assert.assertEquals(5, res2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEvalWithError() {
|
||||
val engine = ScriptEngineManager().getEngineByExtension("kts")!!
|
||||
|
||||
try {
|
||||
engine.eval("java.lang.fish")
|
||||
Assert.fail("Script error expected")
|
||||
} catch (e: ScriptException) {}
|
||||
|
||||
val res1 = engine.eval("val x = 3")
|
||||
Assert.assertNull(res1)
|
||||
|
||||
try {
|
||||
engine.eval("y")
|
||||
Assert.fail("Script error expected")
|
||||
} catch (e: ScriptException) {
|
||||
Assert.assertTrue(
|
||||
"Expected message to contain \"Unresolved reference: y\", actual: \"${e.message}\"",
|
||||
e.message?.contains("Unresolved reference: y") ?: false
|
||||
)
|
||||
}
|
||||
|
||||
val res3 = engine.eval("x + 2")
|
||||
Assert.assertEquals(5, res3)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEngineRepeatWithReset() {
|
||||
val code = "open class A {}\n" +
|
||||
"class B : A() {}"
|
||||
val engine = ScriptEngineManager().getEngineByExtension("kts") as KotlinJsr223ScriptEngine
|
||||
|
||||
val res1 = engine.eval(code)
|
||||
Assert.assertNull(res1)
|
||||
|
||||
engine.state.history.reset()
|
||||
|
||||
engine.eval(code)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInvocable() {
|
||||
val engine = ScriptEngineManager().getEngineByExtension("kts")!!
|
||||
val res1 = engine.eval("""
|
||||
fun fn(x: Int) = x + 2
|
||||
val obj = object {
|
||||
fun fn1(x: Int) = x + 3
|
||||
}
|
||||
obj
|
||||
""")
|
||||
Assert.assertNotNull(res1)
|
||||
val invocator = engine as? Invocable
|
||||
Assert.assertNotNull(invocator)
|
||||
assertThrows(NoSuchMethodException::class.java) {
|
||||
invocator!!.invokeFunction("fn1", 3)
|
||||
}
|
||||
val res2 = invocator!!.invokeFunction("fn", 3)
|
||||
Assert.assertEquals(5, res2)
|
||||
// TODO: fix and restore
|
||||
// assertThrows(NoSuchMethodException::class.java) {
|
||||
// invocator!!.invokeMethod(res1, "fn", 3)
|
||||
// }
|
||||
val res3 = invocator!!.invokeMethod(res1, "fn1", 3)
|
||||
Assert.assertEquals(6, res3)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSimpleCompilable() {
|
||||
val engine = ScriptEngineManager().getEngineByExtension("kts") as KotlinJsr223ScriptEngine
|
||||
val comp1 = engine.compile("val x = 3")
|
||||
val comp2 = engine.compile("x + 2")
|
||||
val res1 = comp1.eval()
|
||||
Assert.assertNull(res1)
|
||||
val res2 = comp2.eval()
|
||||
Assert.assertEquals(5, res2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMultipleCompilable() {
|
||||
val engine = ScriptEngineManager().getEngineByExtension("kts") as KotlinJsr223ScriptEngine
|
||||
val compiled1 = engine.compile("""listOf(1,2,3).joinToString(",")""")
|
||||
val compiled2 = engine.compile("""val x = bindings["boundValue"] as Int + bindings["z"] as Int""")
|
||||
val compiled3 = engine.compile("""x""")
|
||||
|
||||
Assert.assertEquals("1,2,3", compiled1.eval())
|
||||
Assert.assertEquals("1,2,3", compiled1.eval())
|
||||
Assert.assertEquals("1,2,3", compiled1.eval())
|
||||
Assert.assertEquals("1,2,3", compiled1.eval())
|
||||
|
||||
engine.getBindings(ScriptContext.ENGINE_SCOPE).apply {
|
||||
put("boundValue", 100)
|
||||
put("z", 33)
|
||||
}
|
||||
|
||||
compiled2.eval()
|
||||
|
||||
Assert.assertEquals(133, compiled3.eval())
|
||||
Assert.assertEquals(133, compiled3.eval())
|
||||
Assert.assertEquals(133, compiled3.eval())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEvalWithContext() {
|
||||
val engine = ScriptEngineManager().getEngineByExtension("kts")!!
|
||||
|
||||
engine.put("z", 33)
|
||||
|
||||
engine.eval("""val x = 10 + bindings["z"] as Int""")
|
||||
|
||||
val result = engine.eval("""x + 20""")
|
||||
Assert.assertEquals(63, result)
|
||||
|
||||
// in the current implementation the history is shared between contexts, so "x" could also be used in this line,
|
||||
// but this behaviour probably will not be preserved in the future, since contexts may become completely isolated
|
||||
val result2 = engine.eval("""11 + bindings["boundValue"] as Int""", engine.createBindings().apply {
|
||||
put("boundValue", 100)
|
||||
})
|
||||
Assert.assertEquals(111, result2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSimpleEvalInEval() {
|
||||
val engine = ScriptEngineManager().getEngineByExtension("kts")!!
|
||||
val res1 = engine.eval("val x = 3")
|
||||
Assert.assertNull(res1)
|
||||
val res2 = engine.eval("val y = eval(\"\$x + 2\") as Int\ny")
|
||||
Assert.assertEquals(5, res2)
|
||||
val res3 = engine.eval("y + 2")
|
||||
Assert.assertEquals(7, res3)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `kotlin script evaluation should support functional return types`() {
|
||||
val scriptEngine = ScriptEngineManager().getEngineByExtension("kts")!!
|
||||
|
||||
val script = "{1 + 2}"
|
||||
val result = scriptEngine.eval(script)
|
||||
|
||||
Assert.assertTrue(result is Function0<*>)
|
||||
Assert.assertEquals(3, (result as Function0<*>).invoke())
|
||||
}
|
||||
}
|
||||
|
||||
fun assertThrows(exceptionClass: Class<*>, body: () -> Unit) {
|
||||
try {
|
||||
body()
|
||||
Assert.fail("Expecting an exception of type ${exceptionClass.name}")
|
||||
} catch (e: Throwable) {
|
||||
if (!exceptionClass.isAssignableFrom(e.javaClass)) {
|
||||
Assert.fail("Expecting an exception of type ${exceptionClass.name} but got ${e.javaClass.name}")
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
-10
@@ -33,19 +33,24 @@ class JvmReplEvaluator(
|
||||
invokeWrapper: InvokeWrapper?
|
||||
): ReplEvalResult = state.lock.write {
|
||||
val evalState = state.asState(JvmReplEvaluatorState::class.java)
|
||||
val history = evalState.history as ReplStageHistoryWithReplace
|
||||
val compiledScript = (compileResult.data as? KJvmCompiledScript<*>)
|
||||
?: return ReplEvalResult.Error.CompileTime("Unable to access compiled script: ${compileResult.data}")
|
||||
|
||||
val lastSnippetInstance = evalState.history.peek()?.item
|
||||
val lastSnippetInstance = history.peek()?.item
|
||||
val historyBeforeSnippet = history.previousItems(compileResult.lineId)
|
||||
val currentConfiguration = ScriptEvaluationConfiguration(baseScriptEvaluationConfiguration) {
|
||||
if (evalState.history.isNotEmpty()) {
|
||||
previousSnippets.put(evalState.history.map { it.item })
|
||||
if (historyBeforeSnippet.any()) {
|
||||
previousSnippets.put(historyBeforeSnippet.toList())
|
||||
}
|
||||
if (lastSnippetInstance != null) {
|
||||
jvm {
|
||||
baseClassLoader(lastSnippetInstance::class.java.classLoader)
|
||||
}
|
||||
}
|
||||
if (scriptArgs != null) {
|
||||
constructorArgs(*scriptArgs.scriptArgs)
|
||||
}
|
||||
}
|
||||
|
||||
val res = runBlocking { scriptEvaluator(compiledScript, currentConfiguration) }
|
||||
@@ -53,7 +58,7 @@ class JvmReplEvaluator(
|
||||
when (res) {
|
||||
is ResultWithDiagnostics.Success -> when (val retVal = res.value.returnValue) {
|
||||
is ResultValue.Value -> {
|
||||
evalState.history.push(compileResult.lineId, retVal.scriptInstance)
|
||||
history.replaceOrPush(compileResult.lineId, retVal.scriptInstance)
|
||||
// TODO: the latter check is temporary while the result is used to return the instance too
|
||||
if (retVal.type.isNotBlank())
|
||||
ReplEvalResult.ValueResult(retVal.name, retVal.value, retVal.type)
|
||||
@@ -61,12 +66,12 @@ class JvmReplEvaluator(
|
||||
ReplEvalResult.UnitResult()
|
||||
}
|
||||
is ResultValue.UnitValue -> {
|
||||
evalState.history.push(compileResult.lineId, retVal.scriptInstance)
|
||||
history.replaceOrPush(compileResult.lineId, retVal.scriptInstance)
|
||||
ReplEvalResult.UnitResult()
|
||||
}
|
||||
else -> throw IllegalStateException("Expecting value with script instance, got $retVal")
|
||||
}
|
||||
else -> ReplEvalResult.Error.Runtime(res.reports.joinToString("\n") { it.message })
|
||||
else -> ReplEvalResult.Error.Runtime(res.reports.joinToString("\n") { it.message + (it.exception?.let { e -> ": $e" } ?: "") })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,10 +80,29 @@ open class JvmReplEvaluatorState(
|
||||
scriptEvaluationConfiguration: ScriptEvaluationConfiguration,
|
||||
override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()
|
||||
) : IReplStageState<Any> {
|
||||
|
||||
override val history: IReplStageHistory<Any> = BasicReplStageHistory(lock)
|
||||
override val history: IReplStageHistory<Any> = ReplStageHistoryWithReplace(lock)
|
||||
|
||||
override val currentGeneration: Int get() = (history as BasicReplStageHistory<*>).currentGeneration.get()
|
||||
|
||||
val topClassLoader: ClassLoader = scriptEvaluationConfiguration[ScriptEvaluationConfiguration.jvm.baseClassLoader]!!
|
||||
}
|
||||
|
||||
open class ReplStageHistoryWithReplace<T>(lock: ReentrantReadWriteLock = ReentrantReadWriteLock()) : BasicReplStageHistory<T>(lock) {
|
||||
|
||||
fun replace(id: ILineId, item: T): Boolean = lock.write {
|
||||
for (idx in indices) {
|
||||
if (get(idx).id == id) {
|
||||
set(idx, ReplHistoryRecord(id, item))
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun replaceOrPush(id: ILineId, item: T) {
|
||||
if (!replace(id, item)) {
|
||||
tryResetTo(id)
|
||||
push(id, item)
|
||||
}
|
||||
}
|
||||
|
||||
fun previousItems(id: ILineId): Sequence<T> = asSequence().takeWhile { it.id.no < id.no }.map { it.item }
|
||||
}
|
||||
+1
@@ -160,6 +160,7 @@ class SubpluginsIT : BaseGradleIT() {
|
||||
assertCompiledKotlinSources(project.relativize(bobGreet, aliceGreet, worldGreet, greetScriptTemplateKt))
|
||||
}
|
||||
} else {
|
||||
assertFailed()
|
||||
val usedGradleVersion =
|
||||
GradleVersion.version(
|
||||
System.getProperty("kotlin.gradle.version.for.tests")
|
||||
|
||||
@@ -185,6 +185,8 @@ include ":kotlin-build-common",
|
||||
":kotlin-scripting-compiler-embeddable",
|
||||
":kotlin-scripting-compiler-impl",
|
||||
":kotlin-scripting-compiler-impl-embeddable",
|
||||
":kotlin-scripting-jsr223",
|
||||
":kotlin-scripting-jsr223-embeddable",
|
||||
":kotlin-scripting-idea",
|
||||
":kotlin-main-kts",
|
||||
":kotlin-main-kts-test",
|
||||
@@ -357,6 +359,8 @@ project(':kotlin-scripting-common').projectDir = "$rootDir/libraries/scripting/c
|
||||
project(':kotlin-scripting-jvm').projectDir = "$rootDir/libraries/scripting/jvm" as File
|
||||
project(':kotlin-scripting-jvm-host').projectDir = "$rootDir/libraries/scripting/jvm-host" as File
|
||||
project(':kotlin-scripting-jvm-host-embeddable').projectDir = "$rootDir/libraries/scripting/jvm-host-embeddable" as File
|
||||
project(':kotlin-scripting-jsr223').projectDir = "$rootDir/libraries/scripting/jsr223" as File
|
||||
project(':kotlin-scripting-jsr223-embeddable').projectDir = "$rootDir/libraries/scripting/jsr223-embeddable" as File
|
||||
project(':kotlin-scripting-intellij').projectDir = "$rootDir/libraries/scripting/intellij" as File
|
||||
project(':kotlin-scripting-compiler').projectDir = "$rootDir/plugins/scripting/scripting-compiler" as File
|
||||
project(':kotlin-scripting-compiler-embeddable').projectDir = "$rootDir/plugins/scripting/scripting-compiler-embeddable" as File
|
||||
|
||||
Reference in New Issue
Block a user