From d3682b7f7d376e9362e8789f64fddb04b3ec4da4 Mon Sep 17 00:00:00 2001 From: Ilya Chernikov Date: Sat, 11 Feb 2017 23:38:45 +0100 Subject: [PATCH] Implement JSR 223 script template with `eval` functions, some fixes --- .../kotlin/cli/common/repl/BasicReplState.kt | 2 +- .../repl/GenericReplCompilingEvaluator.kt | 4 +- .../repl/KotlinJsr223JvmScriptEngineBase.kt | 15 +++++-- .../KotlinJsr223JvmScriptEngine4Idea.kt | 3 ++ .../jsr223/KotlinJsr223ScriptEngineIT.kt | 11 +++++ .../jsr223/KotlinJsr223ScriptEngineIT.kt | 11 +++++ ...otlinJsr223JvmDaemonCompileScriptEngine.kt | 3 ++ .../KotlinJsr223JvmLocalScriptEngine.kt | 3 ++ ...KotlinJsr223ScriptEngineFactoryExamples.kt | 17 ++++---- .../KotlinStandardJsr223ScriptTemplate.kt | 41 +++++++++++++++++++ 10 files changed, 96 insertions(+), 14 deletions(-) create mode 100644 libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinStandardJsr223ScriptTemplate.kt diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/BasicReplState.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/BasicReplState.kt index cc7b3536e1d..d0c5a13ad5f 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/BasicReplState.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/BasicReplState.kt @@ -55,7 +55,7 @@ open class BasicReplStageHistory(override val lock: ReentrantReadWriteLock = if (idx < 0) throw java.util.NoSuchElementException("Cannot rest to inexistent line ${id.no}") if (idx < lastIndex) { val removed = asSequence().drop(idx + 1).map { it.id }.toList() - removeRange(idx + 1, lastIndex) + removeRange(idx + 1, size) currentGeneration.incrementAndGet() return removed } diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/GenericReplCompilingEvaluator.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/GenericReplCompilingEvaluator.kt index 691ba5ac5c4..9e731e8174b 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/GenericReplCompilingEvaluator.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/GenericReplCompilingEvaluator.kt @@ -46,7 +46,9 @@ class GenericReplCompilingEvaluator(val compiler: ReplCompiler, aggregatedState.apply { lock.write { if (state1.history.size > state2.history.size) { - state2.history.peek()?.let { state1.history.resetTo(it.id) } + state2.history.peek()?.let { + state1.history.resetTo(it.id) + } assert(state1.history.size == state2.history.size) } } diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/KotlinJsr223JvmScriptEngineBase.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/KotlinJsr223JvmScriptEngineBase.kt index cc065c9daa1..02664d504e3 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/KotlinJsr223JvmScriptEngineBase.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/KotlinJsr223JvmScriptEngineBase.kt @@ -21,7 +21,8 @@ import java.io.Reader import java.util.concurrent.locks.ReentrantReadWriteLock import javax.script.* -val KOTLIN_SCRIPT_STATE_BINDINGS_KEY = "kotlin.script.state" +const val KOTLIN_SCRIPT_STATE_BINDINGS_KEY = "kotlin.script.state" +const val KOTLIN_SCRIPT_ENGINE_BINDINGS_KEY = "kotlin.script.engine" abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEngineFactory) : AbstractScriptEngine(), ScriptEngine, Compilable { @@ -36,16 +37,22 @@ abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEn override fun compile(script: Reader): CompiledScript = compile(script.readText(), getContext()) - override fun createBindings(): Bindings = SimpleBindings() + override fun createBindings(): Bindings = SimpleBindings().apply { put(KOTLIN_SCRIPT_ENGINE_BINDINGS_KEY, this) } override fun getFactory(): ScriptEngineFactory = myFactory // the parameter could be used in the future when we decide to keep state completely in the context and solve appropriate problems (now e.g. replCompiler keeps separate state) fun nextCodeLine(context: ScriptContext, code: String) = getCurrentState(context).let { ReplCodeLine(it.getNextLineNo(), it.currentGeneration, code) } - protected open fun createState(lock: ReentrantReadWriteLock = ReentrantReadWriteLock()): IReplStageState<*> = AggregatedReplStageState(replCompiler.createState(lock), replEvaluator.createState(lock), lock) + protected abstract fun createState(lock: ReentrantReadWriteLock = ReentrantReadWriteLock()): IReplStageState<*> - protected fun getCurrentState(context: ScriptContext) = context.getBindings(ScriptContext.ENGINE_SCOPE).getOrPut(KOTLIN_SCRIPT_STATE_BINDINGS_KEY, { replEvaluator.createState() }) as IReplStageState<*> + protected fun getCurrentState(context: ScriptContext) = + context.getBindings(ScriptContext.ENGINE_SCOPE) + .getOrPut(KOTLIN_SCRIPT_STATE_BINDINGS_KEY, { + // TODO: check why createBinding is not called on creating default context, so the engine is not set + context.getBindings(ScriptContext.ENGINE_SCOPE).put(KOTLIN_SCRIPT_ENGINE_BINDINGS_KEY, this@KotlinJsr223JvmScriptEngineBase) + createState() + }) as IReplStageState<*> open fun overrideScriptArgs(context: ScriptContext): ScriptArgsWithTypes? = null diff --git a/idea/idea-repl/src/org/jetbrains/kotlin/jsr223/KotlinJsr223JvmScriptEngine4Idea.kt b/idea/idea-repl/src/org/jetbrains/kotlin/jsr223/KotlinJsr223JvmScriptEngine4Idea.kt index 185cc9168e9..6af9257e87f 100644 --- a/idea/idea-repl/src/org/jetbrains/kotlin/jsr223/KotlinJsr223JvmScriptEngine4Idea.kt +++ b/idea/idea-repl/src/org/jetbrains/kotlin/jsr223/KotlinJsr223JvmScriptEngine4Idea.kt @@ -27,6 +27,7 @@ import org.jetbrains.kotlin.daemon.client.KotlinRemoteReplCompilerClient import org.jetbrains.kotlin.daemon.common.* import org.jetbrains.kotlin.utils.PathUtil import java.io.File +import java.util.concurrent.locks.ReentrantReadWriteLock import javax.script.ScriptContext import javax.script.ScriptEngineFactory import javax.script.ScriptException @@ -76,6 +77,8 @@ class KotlinJsr223JvmScriptEngine4Idea( override val replEvaluator: ReplFullEvaluator get() = localEvaluator + override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> = replEvaluator.createState(lock) + private class MyMessageCollector : MessageCollector { var _hasErrors: Boolean = false diff --git a/libraries/examples/kotlin-jsr223-daemon-local-eval-example/src/test/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223ScriptEngineIT.kt b/libraries/examples/kotlin-jsr223-daemon-local-eval-example/src/test/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223ScriptEngineIT.kt index fa31e6abb28..655fcbd995a 100644 --- a/libraries/examples/kotlin-jsr223-daemon-local-eval-example/src/test/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223ScriptEngineIT.kt +++ b/libraries/examples/kotlin-jsr223-daemon-local-eval-example/src/test/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223ScriptEngineIT.kt @@ -111,6 +111,17 @@ obj }) 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("eval(\"\$x + 2\")") + Assert.assertEquals(5, res2) + val res3 = engine.eval("x + 4") + Assert.assertEquals(7, res3) + } } fun assertThrows(exceptionClass: Class<*>, body: () -> Unit) { diff --git a/libraries/examples/kotlin-jsr223-local-example/src/test/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223ScriptEngineIT.kt b/libraries/examples/kotlin-jsr223-local-example/src/test/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223ScriptEngineIT.kt index 793eee87849..b98146dedd9 100644 --- a/libraries/examples/kotlin-jsr223-local-example/src/test/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223ScriptEngineIT.kt +++ b/libraries/examples/kotlin-jsr223-local-example/src/test/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223ScriptEngineIT.kt @@ -111,6 +111,17 @@ obj }) 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("eval(\"\$x + 2\")") + Assert.assertEquals(5, res2) + val res3 = engine.eval("x + 4") + Assert.assertEquals(7, res3) + } } fun assertThrows(exceptionClass: Class<*>, body: () -> Unit) { diff --git a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223JvmDaemonCompileScriptEngine.kt b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223JvmDaemonCompileScriptEngine.kt index 3838e75d261..ff99fed4470 100644 --- a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223JvmDaemonCompileScriptEngine.kt +++ b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223JvmDaemonCompileScriptEngine.kt @@ -28,6 +28,7 @@ import org.jetbrains.kotlin.daemon.common.* import java.io.File import java.io.OutputStream import java.io.PrintStream +import java.util.concurrent.locks.ReentrantReadWriteLock import javax.script.ScriptContext import javax.script.ScriptEngineFactory import javax.script.ScriptException @@ -68,6 +69,8 @@ class KotlinJsr223JvmDaemonCompileScriptEngine( override val state: IReplStageState<*> get() = getCurrentState(getContext()) + override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> = replEvaluator.createState(lock) + override fun overrideScriptArgs(context: ScriptContext): ScriptArgsWithTypes? = getScriptArgs(context, scriptArgsTypes) private fun connectToCompileService(compilerJar: File): CompileService { diff --git a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223JvmLocalScriptEngine.kt b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223JvmLocalScriptEngine.kt index 281b5605e85..77a40ba0350 100644 --- a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223JvmLocalScriptEngine.kt +++ b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223JvmLocalScriptEngine.kt @@ -29,6 +29,7 @@ import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromAnnotatedTemplate import org.jetbrains.kotlin.utils.PathUtil import java.io.File import java.net.URLClassLoader +import java.util.concurrent.locks.ReentrantReadWriteLock import javax.script.ScriptContext import javax.script.ScriptEngineFactory import kotlin.reflect.KClass @@ -56,6 +57,8 @@ class KotlinJsr223JvmLocalScriptEngine( override val state: IReplStageState<*> get() = getCurrentState(getContext()) + override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> = replEvaluator.createState(lock) + override fun overrideScriptArgs(context: ScriptContext): ScriptArgsWithTypes? = getScriptArgs(context, scriptArgsTypes) private fun makeScriptDefinition(templateClasspath: List, templateClassName: String): KotlinScriptDefinition { diff --git a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223ScriptEngineFactoryExamples.kt b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223ScriptEngineFactoryExamples.kt index 6d01caf8a4d..1430884e568 100644 --- a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223ScriptEngineFactoryExamples.kt +++ b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223ScriptEngineFactoryExamples.kt @@ -29,6 +29,7 @@ import org.jetbrains.kotlin.script.util.manifestClassPath import org.jetbrains.kotlin.utils.PathUtil.* import java.io.File import java.io.FileNotFoundException +import javax.script.Bindings import javax.script.ScriptContext import javax.script.ScriptEngine import kotlin.script.templates.standard.ScriptTemplateWithArgs @@ -39,10 +40,10 @@ class KotlinJsr223JvmLocalScriptEngineFactory : KotlinJsr223JvmScriptEngineFacto KotlinJsr223JvmLocalScriptEngine( Disposer.newDisposable(), this, - scriptCompilationClasspathFromContext(), - "kotlin.script.templates.standard.ScriptTemplateWithBindings", + scriptCompilationClasspathFromContext("kotlin-script-util.jar"), + KotlinStandardJsr223ScriptTemplate::class.qualifiedName!!, { ctx, types -> ScriptArgsWithTypes(arrayOf(ctx.getBindings(ScriptContext.ENGINE_SCOPE)), types ?: emptyArray()) }, - arrayOf(Map::class) + arrayOf(Bindings::class) ) } @@ -53,10 +54,10 @@ class KotlinJsr223JvmDaemonLocalEvalScriptEngineFactory : KotlinJsr223JvmScriptE Disposer.newDisposable(), this, kotlinCompilerJar, - scriptCompilationClasspathFromContext(), - "kotlin.script.templates.standard.ScriptTemplateWithBindings", + scriptCompilationClasspathFromContext("kotlin-script-util.jar"), + KotlinStandardJsr223ScriptTemplate::class.qualifiedName!!, { ctx, types -> ScriptArgsWithTypes(arrayOf(ctx.getBindings(ScriptContext.ENGINE_SCOPE)), types ?: emptyArray()) }, - arrayOf(Map::class) + arrayOf(Bindings::class) ) } @@ -77,9 +78,9 @@ private fun contextClasspath(keyName: String, classLoader: ClassLoader): List = +private fun scriptCompilationClasspathFromContext(keyName: String, classLoader: ClassLoader = Thread.currentThread().contextClassLoader): List = (System.getProperty("kotlin.script.classpath")?.split(File.pathSeparator)?.map(::File) - ?: classpathFromClassloader(classLoader) + ?: contextClasspath(keyName, classLoader) ).let { it?.plus(kotlinScriptStandardJars) ?: kotlinScriptStandardJars } diff --git a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinStandardJsr223ScriptTemplate.kt b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinStandardJsr223ScriptTemplate.kt new file mode 100644 index 00000000000..1df06cc5428 --- /dev/null +++ b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinStandardJsr223ScriptTemplate.kt @@ -0,0 +1,41 @@ +package org.jetbrains.kotlin.script.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 org.jetbrains.kotlin.script.ScriptTemplateDefinition +import javax.script.Bindings +import javax.script.ScriptEngine + +@Suppress("unused") +@ScriptTemplateDefinition +abstract class KotlinStandardJsr223ScriptTemplate(val bindings: Bindings) { + + private val myEngine: ScriptEngine? get() = bindings[KOTLIN_SCRIPT_ENGINE_BINDINGS_KEY]?.let { it as? ScriptEngine } + + private inline fun 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.bindings[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 = bindings.remove(KOTLIN_SCRIPT_STATE_BINDINGS_KEY) + val res = it.eval(script, bindings) + savedState?.apply { + bindings[KOTLIN_SCRIPT_STATE_BINDINGS_KEY] = savedState + } + res + } + + fun createBindings(): Bindings = withMyEngine { it.createBindings() } +} \ No newline at end of file