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> {
|
override fun resetTo(id: ILineId): Iterable<ILineId> {
|
||||||
lock.write {
|
lock.write {
|
||||||
val idx = indexOfFirst { it.id == id }
|
return tryResetTo(id) ?: throw NoSuchElementException("Cannot reset to non-existent line ${id.no}")
|
||||||
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)
|
protected fun tryResetTo(id: ILineId): List<ILineId>? {
|
||||||
currentGeneration.incrementAndGet()
|
val idx = indexOfFirst { it.id == id }
|
||||||
removed
|
if (idx < 0) return null
|
||||||
}
|
return if (idx < lastIndex) {
|
||||||
else {
|
val removed = asSequence().drop(idx + 1).map { it.id }.toList()
|
||||||
currentGeneration.incrementAndGet()
|
removeRange(idx + 1, size)
|
||||||
emptyList()
|
currentGeneration.incrementAndGet()
|
||||||
}
|
removed
|
||||||
|
} else {
|
||||||
|
currentGeneration.incrementAndGet()
|
||||||
|
emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-11
@@ -20,13 +20,11 @@ import java.io.File
|
|||||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||||
import kotlin.concurrent.write
|
import kotlin.concurrent.write
|
||||||
|
|
||||||
class GenericReplCompilingEvaluator(val compiler: ReplCompiler,
|
open class GenericReplCompilingEvaluatorBase(
|
||||||
baseClasspath: Iterable<File>,
|
val compiler: ReplCompiler,
|
||||||
baseClassloader: ClassLoader? = Thread.currentThread().contextClassLoader,
|
val evaluator: ReplEvaluator,
|
||||||
private val fallbackScriptArgs: ScriptArgsWithTypes? = null,
|
private val fallbackScriptArgs: ScriptArgsWithTypes? = null
|
||||||
repeatingMode: ReplRepeatingMode = ReplRepeatingMode.REPEAT_ONLY_MOST_RECENT
|
|
||||||
) : ReplFullEvaluator {
|
) : ReplFullEvaluator {
|
||||||
private val evaluator = GenericReplEvaluator(baseClasspath, baseClassloader, fallbackScriptArgs, repeatingMode)
|
|
||||||
|
|
||||||
override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> = AggregatedReplStageState(compiler.createState(lock), evaluator.createState(lock), lock)
|
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 =
|
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)
|
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 evaluator: ReplEvaluator,
|
||||||
private val defaultScriptArgs: ScriptArgsWithTypes?) : Evaluable {
|
private val defaultScriptArgs: ScriptArgsWithTypes?) : Evaluable {
|
||||||
override fun eval(scriptArgs: ScriptArgsWithTypes?, invokeWrapper: InvokeWrapper?): ReplEvalResult =
|
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>? =
|
private fun AggregatedReplStageState<*, *>.adjustHistories(): Iterable<ILineId>? =
|
||||||
state2.history.peek()?.let {
|
state2.history.peek()?.let {
|
||||||
state1.history.resetTo(it.id)
|
state1.history.resetTo(it.id)
|
||||||
}
|
}
|
||||||
?: state1.history.reset()
|
?: 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?
|
invokeWrapper: InvokeWrapper?
|
||||||
): ReplEvalResult = state.lock.write {
|
): ReplEvalResult = state.lock.write {
|
||||||
val evalState = state.asState(JvmReplEvaluatorState::class.java)
|
val evalState = state.asState(JvmReplEvaluatorState::class.java)
|
||||||
|
val history = evalState.history as ReplStageHistoryWithReplace
|
||||||
val compiledScript = (compileResult.data as? KJvmCompiledScript<*>)
|
val compiledScript = (compileResult.data as? KJvmCompiledScript<*>)
|
||||||
?: return ReplEvalResult.Error.CompileTime("Unable to access compiled script: ${compileResult.data}")
|
?: 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) {
|
val currentConfiguration = ScriptEvaluationConfiguration(baseScriptEvaluationConfiguration) {
|
||||||
if (evalState.history.isNotEmpty()) {
|
if (historyBeforeSnippet.any()) {
|
||||||
previousSnippets.put(evalState.history.map { it.item })
|
previousSnippets.put(historyBeforeSnippet.toList())
|
||||||
}
|
}
|
||||||
if (lastSnippetInstance != null) {
|
if (lastSnippetInstance != null) {
|
||||||
jvm {
|
jvm {
|
||||||
baseClassLoader(lastSnippetInstance::class.java.classLoader)
|
baseClassLoader(lastSnippetInstance::class.java.classLoader)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (scriptArgs != null) {
|
||||||
|
constructorArgs(*scriptArgs.scriptArgs)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val res = runBlocking { scriptEvaluator(compiledScript, currentConfiguration) }
|
val res = runBlocking { scriptEvaluator(compiledScript, currentConfiguration) }
|
||||||
@@ -53,7 +58,7 @@ class JvmReplEvaluator(
|
|||||||
when (res) {
|
when (res) {
|
||||||
is ResultWithDiagnostics.Success -> when (val retVal = res.value.returnValue) {
|
is ResultWithDiagnostics.Success -> when (val retVal = res.value.returnValue) {
|
||||||
is ResultValue.Value -> {
|
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
|
// TODO: the latter check is temporary while the result is used to return the instance too
|
||||||
if (retVal.type.isNotBlank())
|
if (retVal.type.isNotBlank())
|
||||||
ReplEvalResult.ValueResult(retVal.name, retVal.value, retVal.type)
|
ReplEvalResult.ValueResult(retVal.name, retVal.value, retVal.type)
|
||||||
@@ -61,12 +66,12 @@ class JvmReplEvaluator(
|
|||||||
ReplEvalResult.UnitResult()
|
ReplEvalResult.UnitResult()
|
||||||
}
|
}
|
||||||
is ResultValue.UnitValue -> {
|
is ResultValue.UnitValue -> {
|
||||||
evalState.history.push(compileResult.lineId, retVal.scriptInstance)
|
history.replaceOrPush(compileResult.lineId, retVal.scriptInstance)
|
||||||
ReplEvalResult.UnitResult()
|
ReplEvalResult.UnitResult()
|
||||||
}
|
}
|
||||||
else -> throw IllegalStateException("Expecting value with script instance, got $retVal")
|
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,
|
scriptEvaluationConfiguration: ScriptEvaluationConfiguration,
|
||||||
override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()
|
override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()
|
||||||
) : IReplStageState<Any> {
|
) : IReplStageState<Any> {
|
||||||
|
override val history: IReplStageHistory<Any> = ReplStageHistoryWithReplace(lock)
|
||||||
override val history: IReplStageHistory<Any> = BasicReplStageHistory(lock)
|
|
||||||
|
|
||||||
override val currentGeneration: Int get() = (history as BasicReplStageHistory<*>).currentGeneration.get()
|
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))
|
assertCompiledKotlinSources(project.relativize(bobGreet, aliceGreet, worldGreet, greetScriptTemplateKt))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
assertFailed()
|
||||||
val usedGradleVersion =
|
val usedGradleVersion =
|
||||||
GradleVersion.version(
|
GradleVersion.version(
|
||||||
System.getProperty("kotlin.gradle.version.for.tests")
|
System.getProperty("kotlin.gradle.version.for.tests")
|
||||||
|
|||||||
@@ -185,6 +185,8 @@ include ":kotlin-build-common",
|
|||||||
":kotlin-scripting-compiler-embeddable",
|
":kotlin-scripting-compiler-embeddable",
|
||||||
":kotlin-scripting-compiler-impl",
|
":kotlin-scripting-compiler-impl",
|
||||||
":kotlin-scripting-compiler-impl-embeddable",
|
":kotlin-scripting-compiler-impl-embeddable",
|
||||||
|
":kotlin-scripting-jsr223",
|
||||||
|
":kotlin-scripting-jsr223-embeddable",
|
||||||
":kotlin-scripting-idea",
|
":kotlin-scripting-idea",
|
||||||
":kotlin-main-kts",
|
":kotlin-main-kts",
|
||||||
":kotlin-main-kts-test",
|
":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').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').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-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-intellij').projectDir = "$rootDir/libraries/scripting/intellij" as File
|
||||||
project(':kotlin-scripting-compiler').projectDir = "$rootDir/plugins/scripting/scripting-compiler" 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
|
project(':kotlin-scripting-compiler-embeddable').projectDir = "$rootDir/plugins/scripting/scripting-compiler-embeddable" as File
|
||||||
|
|||||||
Reference in New Issue
Block a user