From cb7f22ffecb533d1db4a190ee919508c8ece8ea4 Mon Sep 17 00:00:00 2001 From: Ilya Chernikov Date: Tue, 6 Dec 2016 19:14:05 +0100 Subject: [PATCH] Refactor JSR223 to support Compilable interface, drop daemon eval engine and sample, simplify --- .../KotlinJsr223JvmInvocableScriptEngine.kt | 60 ++++++++ .../repl/KotlinJsr223JvmScriptEngineBase.kt | 116 ++++++++-------- .../KotlinJsr223JvmScriptEngine4Idea.kt | 27 +--- .../jsr223/KotlinJsr223ScriptEngineIT.kt | 2 +- .../pom.xml | 91 ------------- .../services/javax.script.ScriptEngineFactory | 1 - .../jsr223/KotlinJsr223ScriptEngineIT.kt | 68 ---------- libraries/pom.xml | 1 - ...otlinJsr223JvmDaemonCompileScriptEngine.kt | 76 +++++++++++ .../KotlinJsr223JvmDaemonScriptEngines.kt | 128 ------------------ .../KotlinJsr223JvmLocalScriptEngine.kt | 72 ++-------- ...KotlinJsr223ScriptEngineFactoryExamples.kt | 16 +-- .../kotlin/script/util/ScriptUtilIT.kt | 20 ++- 13 files changed, 227 insertions(+), 451 deletions(-) create mode 100644 compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/KotlinJsr223JvmInvocableScriptEngine.kt delete mode 100644 libraries/examples/kotlin-jsr223-daemon-remote-eval-example/pom.xml delete mode 100644 libraries/examples/kotlin-jsr223-daemon-remote-eval-example/src/main/resources/META-INF/services/javax.script.ScriptEngineFactory delete mode 100644 libraries/examples/kotlin-jsr223-daemon-remote-eval-example/src/test/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223ScriptEngineIT.kt create mode 100644 libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223JvmDaemonCompileScriptEngine.kt delete mode 100644 libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223JvmDaemonScriptEngines.kt diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/KotlinJsr223JvmInvocableScriptEngine.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/KotlinJsr223JvmInvocableScriptEngine.kt new file mode 100644 index 00000000000..4a343da17e1 --- /dev/null +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/KotlinJsr223JvmInvocableScriptEngine.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.cli.common.repl + +import javax.script.Invocable +import javax.script.ScriptException + +@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 getInterface(clasz: Class?): 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 getInterface(thiz: Any?, clasz: Class?): 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 + } +} \ No newline at end of file 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 486c7266d28..193cc5d2b53 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 @@ -16,27 +16,63 @@ package org.jetbrains.kotlin.cli.common.repl +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation import java.io.Reader import javax.script.* -//val KOTLIN_SCRIPT_HISTORY_BINDINGS_KEY = "kotlin.script.history" -// -//val Bindings.kotlinScriptHistory: +val KOTLIN_SCRIPT_HISTORY_BINDINGS_KEY = "kotlin.script.history" + +// TODO consider additional error handling +val Bindings.kotlinScriptHistory: MutableList + get() = getOrPut(KOTLIN_SCRIPT_HISTORY_BINDINGS_KEY, { arrayListOf() }) as MutableList abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEngineFactory) : AbstractScriptEngine(), ScriptEngine, Compilable { - protected var lineCount = 0 - protected val history = arrayListOf() + private var lineCount = 0 - abstract fun eval(codeLine: ReplCodeLine, history: List): ReplEvalResult + protected abstract val replCompiler: ReplCompiler - override fun eval(script: String, context: ScriptContext?): Any? { + protected abstract val replEvaluator: ReplCompiledEvaluator + override fun eval(script: String, context: ScriptContext): Any? = compile(script, context).eval(context) + + override fun eval(script: Reader, context: ScriptContext): Any? = compile(script.readText(), context).eval() + + override fun compile(script: String): CompiledScript = compile(script, getContext()) + + override fun compile(script: Reader): CompiledScript = compile(script.readText(), getContext()) + + override fun createBindings(): Bindings = SimpleBindings() + + override fun getFactory(): ScriptEngineFactory = myFactory + + open fun compile(script: String, context: ScriptContext): CompiledScript { lineCount += 1 - // TODO bind to context - val codeLine = ReplCodeLine(lineCount, script) - val evalResult = eval(codeLine, history) + val codeLine = ReplCodeLine(lineCount, script) + val history = context.getBindings(ScriptContext.ENGINE_SCOPE).kotlinScriptHistory + + val compileResult = replCompiler.compile(codeLine, history) + + val compiled = when (compileResult) { + is ReplCompileResult.Error -> throw ScriptException("Error${compileResult.locationString()}: ${compileResult.message}") + is ReplCompileResult.Incomplete -> throw ScriptException("error: incomplete code") + is ReplCompileResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${compileResult.lineNo}") + is ReplCompileResult.CompiledClasses -> compileResult + } + + return CompiledKotlinScript(this, codeLine, compiled) + } + + open fun eval(compiledScript: CompiledKotlinScript, context: ScriptContext): Any? { + val history = context.getBindings(ScriptContext.ENGINE_SCOPE).kotlinScriptHistory + + val evalResult = try { + replEvaluator.eval(compiledScript.codeLine, history, compiledScript.compiledData.classes, compiledScript.compiledData.hasResult, compiledScript.compiledData.classpathAddendum) + } + catch (e: Exception) { + throw ScriptException(e) + } val ret = when (evalResult) { is ReplEvalResult.ValueResult -> evalResult.value @@ -45,63 +81,17 @@ abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEn is ReplEvalResult.Incomplete -> throw ScriptException("error: incomplete code") is ReplEvalResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${evalResult.lineNo}") } - history.add(codeLine) - // TODO update context + history.add(compiledScript.codeLine) return ret } - override fun eval(script: Reader, context: ScriptContext?): Any? = eval(script.readText(), context) - - override fun compile(p0: String?): CompiledScript { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + class CompiledKotlinScript(val engine: KotlinJsr223JvmScriptEngineBase, val codeLine: ReplCodeLine, val compiledData: ReplCompileResult.CompiledClasses) : CompiledScript() { + override fun eval(context: ScriptContext): Any? = engine.eval(this, context) + override fun getEngine(): ScriptEngine = engine } - - override fun compile(p0: Reader?): CompiledScript { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - override fun createBindings(): Bindings = SimpleBindings() - - override fun getFactory(): ScriptEngineFactory = myFactory } +private fun ReplCompileResult.Error.locationString() = + if (location == CompilerMessageLocation.NO_LOCATION) "" + else " at ${location.line}:${location.column}" -@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 getInterface(clasz: Class?): 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 getInterface(thiz: Any?, clasz: Class?): 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 - } -} 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 93d3e011654..1e1b51c7243 100644 --- a/idea/idea-repl/src/org/jetbrains/kotlin/jsr223/KotlinJsr223JvmScriptEngine4Idea.kt +++ b/idea/idea-repl/src/org/jetbrains/kotlin/jsr223/KotlinJsr223JvmScriptEngine4Idea.kt @@ -17,8 +17,10 @@ package org.jetbrains.kotlin.jsr223 import com.intellij.openapi.Disposable -import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation -import org.jetbrains.kotlin.cli.common.repl.* +import org.jetbrains.kotlin.cli.common.repl.GenericReplCompiledEvaluator +import org.jetbrains.kotlin.cli.common.repl.KotlinJsr223JvmScriptEngineBase +import org.jetbrains.kotlin.cli.common.repl.ReplCompiledEvaluator +import org.jetbrains.kotlin.cli.common.repl.ReplCompiler import org.jetbrains.kotlin.daemon.client.DaemonReportMessage import org.jetbrains.kotlin.daemon.client.DaemonReportingTargets import org.jetbrains.kotlin.daemon.client.KotlinCompilerClient @@ -52,7 +54,7 @@ class KotlinJsr223JvmScriptEngine4Idea( ?: throw ScriptException("Unable to connect to repl server:" + daemonReportMessages.joinToString("\n ", prefix = "\n ") { "${it.category.name} ${it.message}" }) } - private val replCompiler by lazy { + override val replCompiler: ReplCompiler by lazy { daemon.let { KotlinRemoteReplCompiler(disposable, it, @@ -65,22 +67,7 @@ class KotlinJsr223JvmScriptEngine4Idea( } // TODO: bindings passing works only once on the first eval, subsequent setContext/setBindings call have no effect. Consider making it dynamic, but take history into account - val localEvaluator by lazy { GenericReplCompiledEvaluator(templateClasspath, Thread.currentThread().contextClassLoader, getScriptArgs(getContext()), scriptArgsTypes) } + val localEvaluator: ReplCompiledEvaluator by lazy { GenericReplCompiledEvaluator(templateClasspath, Thread.currentThread().contextClassLoader, getScriptArgs(getContext()), scriptArgsTypes) } - override fun eval(codeLine: ReplCodeLine, history: List): ReplEvalResult { - - fun ReplCompileResult.Error.locationString() = - if (location == CompilerMessageLocation.NO_LOCATION) "" - else " at ${location.line}:${location.column}" - - val compileResult = replCompiler.compile(codeLine, history) - val compiled = when (compileResult) { - is ReplCompileResult.Error -> throw ScriptException("Error${compileResult.locationString()}: ${compileResult.message}") - is ReplCompileResult.Incomplete -> throw ScriptException("error: incomplete code") - is ReplCompileResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${compileResult.lineNo}") - is ReplCompileResult.CompiledClasses -> compileResult - } - - return localEvaluator.eval(codeLine, history, compiled.classes, compiled.hasResult, compiled.classpathAddendum) - } + override val replEvaluator: ReplCompiledEvaluator get() = localEvaluator } 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 0a300da751f..a75b2d31096 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 @@ -52,7 +52,7 @@ class KotlinJsr223ScriptEngineIT { val factory = ScriptEngineManager().getEngineByExtension("kts").factory Assert.assertNotNull(factory) val engine = factory!!.scriptEngine - Assert.assertNotNull(engine as? KotlinJsr223JvmDaemonLocalEvalScriptEngine) + Assert.assertNotNull(engine as? KotlinJsr223JvmDaemonCompileScriptEngine) Assert.assertSame(factory, engine!!.factory) val bindings = engine.createBindings() Assert.assertTrue(bindings is SimpleBindings) diff --git a/libraries/examples/kotlin-jsr223-daemon-remote-eval-example/pom.xml b/libraries/examples/kotlin-jsr223-daemon-remote-eval-example/pom.xml deleted file mode 100644 index 77a9416aaf4..00000000000 --- a/libraries/examples/kotlin-jsr223-daemon-remote-eval-example/pom.xml +++ /dev/null @@ -1,91 +0,0 @@ - - - - 4.0.0 - - 1.4.1 - 3.0.4 - - - - org.jetbrains.kotlin - kotlin-project - 1.1-SNAPSHOT - ../../pom.xml - - - kotlin-jsr223-daemon-remote-eval-example - jar - - Sample Kotlin JSR 223 scripting jar with daemon (out-of-process) compilation and evaluation - - - - org.jetbrains.kotlin - kotlin-runtime - ${project.version} - - - org.jetbrains.kotlin - kotlin-script-runtime - ${project.version} - - - org.jetbrains.kotlin - kotlin-compiler - ${project.version} - - - org.jetbrains.kotlin - kotlin-script-util - ${project.version} - - - - - ${project.basedir}/src/test/kotlin - - - - src/main/resources - true - - - - - - kotlin-maven-plugin - org.jetbrains.kotlin - ${project.version} - - - - compile - compile - compile - - - - test-compile - test-compile - test-compile - - - - - maven-failsafe-plugin - 2.6 - - - - integration-test - verify - - - - - - - diff --git a/libraries/examples/kotlin-jsr223-daemon-remote-eval-example/src/main/resources/META-INF/services/javax.script.ScriptEngineFactory b/libraries/examples/kotlin-jsr223-daemon-remote-eval-example/src/main/resources/META-INF/services/javax.script.ScriptEngineFactory deleted file mode 100644 index e41de55be55..00000000000 --- a/libraries/examples/kotlin-jsr223-daemon-remote-eval-example/src/main/resources/META-INF/services/javax.script.ScriptEngineFactory +++ /dev/null @@ -1 +0,0 @@ -org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmDaemonRemoteEvalScriptEngineFactory diff --git a/libraries/examples/kotlin-jsr223-daemon-remote-eval-example/src/test/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223ScriptEngineIT.kt b/libraries/examples/kotlin-jsr223-daemon-remote-eval-example/src/test/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223ScriptEngineIT.kt deleted file mode 100644 index ea5f196220c..00000000000 --- a/libraries/examples/kotlin-jsr223-daemon-remote-eval-example/src/test/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223ScriptEngineIT.kt +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2010-2016 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jetbrains.kotlin.script.jsr223 - -import org.jetbrains.kotlin.config.KotlinCompilerVersion -import org.junit.Assert -import org.junit.Test -import javax.script.ScriptEngine -import javax.script.ScriptEngineManager -import javax.script.SimpleBindings - -class KotlinJsr223ScriptEngineIT { - - @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? KotlinJsr223JvmDaemonRemoteEvalScriptEngine) - 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) - } -} diff --git a/libraries/pom.xml b/libraries/pom.xml index 5cfee5efafc..4c79e4a4f84 100644 --- a/libraries/pom.xml +++ b/libraries/pom.xml @@ -121,7 +121,6 @@ examples/browser-example-with-library examples/kotlin-jsr223-local-example examples/kotlin-jsr223-daemon-local-eval-example - examples/kotlin-jsr223-daemon-remote-eval-example tools/kotlin-gradle-plugin-integration-tests 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 new file mode 100644 index 00000000000..9309f1fa37b --- /dev/null +++ b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223JvmDaemonCompileScriptEngine.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.script.jsr223 + +import com.intellij.openapi.Disposable +import org.jetbrains.kotlin.cli.common.repl.* +import org.jetbrains.kotlin.daemon.client.DaemonReportMessage +import org.jetbrains.kotlin.daemon.client.DaemonReportingTargets +import org.jetbrains.kotlin.daemon.client.KotlinCompilerClient +import org.jetbrains.kotlin.daemon.client.KotlinRemoteReplCompiler +import org.jetbrains.kotlin.daemon.common.* +import java.io.File +import java.io.OutputStream +import javax.script.ScriptContext +import javax.script.ScriptEngineFactory +import javax.script.ScriptException + +class KotlinJsr223JvmDaemonCompileScriptEngine( + disposable: Disposable, + factory: ScriptEngineFactory, + compilerJar: File, + templateClasspath: List, + templateClassName: String, + getScriptArgs: (ScriptContext) -> Array?, + scriptArgsTypes: Array>?, + compilerOut: OutputStream = System.err +) : KotlinJsr223JvmScriptEngineBase(factory), KotlinJsr223JvmInvocableScriptEngine { + + private val daemon by lazy { connectToCompileService(compilerJar) } + + override val replCompiler by lazy { + daemon.let { + KotlinRemoteReplCompiler( + disposable, + it, + makeAutodeletingFlagFile("jsr223-repl-session"), + CompileService.TargetPlatform.JVM, + templateClasspath, + templateClassName, + compilerOut) + } + } + + // TODO: bindings passing works only once on the first eval, subsequent setContext/setBindings call have no effect. Consider making it dynamic, but take history into account + val localEvaluator by lazy { GenericReplCompiledEvaluator(templateClasspath, Thread.currentThread().contextClassLoader, getScriptArgs(getContext()), scriptArgsTypes) } + + override val replScriptInvoker: ReplScriptInvoker get() = localEvaluator + + override val replEvaluator: ReplCompiledEvaluator get() = localEvaluator +} + + +private fun connectToCompileService(compilerJar: File): CompileService { + val compilerId = CompilerId.makeCompilerId(compilerJar) + val daemonOptions = configureDaemonOptions() + val daemonJVMOptions = DaemonJVMOptions() + + val daemonReportMessages = arrayListOf() + + return KotlinCompilerClient.connectToCompileService(compilerId, daemonJVMOptions, daemonOptions, DaemonReportingTargets(null, daemonReportMessages), true, true) + ?: throw ScriptException("Unable to connect to repl server:" + daemonReportMessages.joinToString("\n ", prefix = "\n ") { "${it.category.name} ${it.message}" }) +} diff --git a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223JvmDaemonScriptEngines.kt b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223JvmDaemonScriptEngines.kt deleted file mode 100644 index d47680d9774..00000000000 --- a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223JvmDaemonScriptEngines.kt +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2010-2016 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jetbrains.kotlin.script.jsr223 - -import com.intellij.openapi.Disposable -import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation -import org.jetbrains.kotlin.cli.common.repl.* -import org.jetbrains.kotlin.daemon.client.* -import org.jetbrains.kotlin.daemon.common.* -import java.io.File -import java.io.InputStream -import java.io.OutputStream -import javax.script.ScriptContext -import javax.script.ScriptEngineFactory -import javax.script.ScriptException - -class KotlinJsr223JvmDaemonLocalEvalScriptEngine( - disposable: Disposable, - factory: ScriptEngineFactory, - compilerJar: File, - templateClasspath: List, - templateClassName: String, - getScriptArgs: (ScriptContext) -> Array?, - scriptArgsTypes: Array>?, - compilerOut: OutputStream = System.err -) : KotlinJsr223JvmScriptEngineBase(factory), KotlinJsr223JvmInvocableScriptEngine { - - private val daemon by lazy { connectToCompileService(compilerJar) } - - private val replCompiler by lazy { - daemon.let { - KotlinRemoteReplCompiler( - disposable, - it, - makeAutodeletingFlagFile("jsr223-repl-session"), - CompileService.TargetPlatform.JVM, - templateClasspath, - templateClassName, - compilerOut) - } - } - - // TODO: bindings passing works only once on the first eval, subsequent setContext/setBindings call have no effect. Consider making it dynamic, but take history into account - val localEvaluator by lazy { GenericReplCompiledEvaluator(templateClasspath, Thread.currentThread().contextClassLoader, getScriptArgs(getContext()), scriptArgsTypes) } - - override val replScriptInvoker: ReplScriptInvoker - get() = localEvaluator - - override fun eval(codeLine: ReplCodeLine, history: List): ReplEvalResult { - - fun ReplCompileResult.Error.locationString() = if (location == CompilerMessageLocation.NO_LOCATION) "" - else " at ${location.line}:${location.column}:" - - val compileResult = replCompiler.compile(codeLine, history) - val compiled = when (compileResult) { - is ReplCompileResult.Error -> throw ScriptException("Error${compileResult.locationString()}: ${compileResult.message}") - is ReplCompileResult.Incomplete -> throw ScriptException("error: incomplete code") - is ReplCompileResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${compileResult.lineNo}") - is ReplCompileResult.CompiledClasses -> compileResult - } - - return localEvaluator.eval(codeLine, history, compiled.classes, compiled.hasResult, compiled.classpathAddendum) - } -} - - -class KotlinJsr223JvmDaemonRemoteEvalScriptEngine( - disposable: Disposable, - factory: ScriptEngineFactory, - compilerJar: File, - templateClasspath: List, - templateClassName: String, - getScriptArgs: (ScriptContext) -> Array?, - scriptArgsTypes: Array>?, - compilerOutputStream: OutputStream = System.err, - evallOutputStream: OutputStream = System.out, - evalErrrorStream: OutputStream = System.err, - evalInputStream: InputStream = System.`in` -) : KotlinJsr223JvmScriptEngineBase(factory) { - - private val daemon by lazy { connectToCompileService(compilerJar) } - - // TODO: bindings passing works only once on the first eval, subsequent setContext/setBindings call have no effect. Consider making it dynamic, but take history into account - private val repl by lazy { - daemon.let { - KotlinRemoteReplEvaluator( - disposable, - it, - makeAutodeletingFlagFile("jsr223-repl-session"), - CompileService.TargetPlatform.JVM, - templateClasspath, - templateClassName, - getScriptArgs(getContext()), - scriptArgsTypes, - compilerOutputStream, - evallOutputStream, - evalErrrorStream, - evalInputStream) - } - } - - override fun eval(codeLine: ReplCodeLine, history: List): ReplEvalResult = repl.eval(codeLine, history) -} - -private fun connectToCompileService(compilerJar: File): CompileService { - val compilerId = CompilerId.makeCompilerId(compilerJar) - val daemonOptions = configureDaemonOptions() - val daemonJVMOptions = DaemonJVMOptions() - - val daemonReportMessages = arrayListOf() - - return KotlinCompilerClient.connectToCompileService(compilerId, daemonJVMOptions, daemonOptions, DaemonReportingTargets(null, daemonReportMessages), true, true) - ?: throw ScriptException("Unable to connect to repl server:" + daemonReportMessages.joinToString("\n ", prefix = "\n ") { "${it.category.name} ${it.message}" }) -} 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 ad97af53831..21b37be5270 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 @@ -17,13 +17,11 @@ package org.jetbrains.kotlin.script.jsr223 import com.intellij.openapi.Disposable -import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation -import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity -import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.cli.common.messages.MessageRenderer +import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector import org.jetbrains.kotlin.cli.common.repl.* import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots -import org.jetbrains.kotlin.cli.jvm.repl.GenericRepl +import org.jetbrains.kotlin.cli.jvm.repl.GenericReplCompiler import org.jetbrains.kotlin.config.CommonConfigurationKeys import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.script.KotlinScriptDefinition @@ -33,7 +31,6 @@ import java.io.File import java.net.URLClassLoader import javax.script.ScriptContext import javax.script.ScriptEngineFactory -import javax.script.ScriptException class KotlinJsr223JvmLocalScriptEngine( disposable: Disposable, @@ -44,52 +41,19 @@ class KotlinJsr223JvmLocalScriptEngine( scriptArgsTypes: Array>? ) : KotlinJsr223JvmScriptEngineBase(factory), KotlinJsr223JvmInvocableScriptEngine { - data class MessageCollectorReport(val severity: CompilerMessageSeverity, val message: String, val location: CompilerMessageLocation) - - private val messageCollector = object : MessageCollector { - - private val messageRenderer = MessageRenderer.WITHOUT_PATHS - private var hasErrors = false - private val reports = arrayListOf() - - override fun clear() { - reports.clear() - } - - override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation) { - hasErrors = hasErrors or severity.isError - reports.add(MessageCollectorReport(severity, message, location)) - } - - override fun hasErrors(): Boolean = hasErrors - - fun resetAndThrowOnErrors() { - try { - if (hasErrors) { - val firstErr = reports.firstOrNull { it.severity.isError } - if (firstErr != null) - throw ScriptException(messageRenderer.render(firstErr.severity, firstErr.message, firstErr.location), firstErr.location.path, firstErr.location.line, firstErr.location.column) - else - throw ScriptException(reports.joinToString("\n") { messageRenderer.render(it.severity, it.message, it.location) }) - } - } - finally { - clear() - hasErrors = false - } - } + override val replCompiler: ReplCompiler by lazy { + GenericReplCompiler( + disposable, + makeScriptDefinition(templateClasspath, templateClassName), + makeCompilerConfiguration(), + PrintingMessageCollector(System.out, MessageRenderer.WITHOUT_PATHS, false)) } + // TODO: bindings passing works only once on the first eval, subsequent setContext/setBindings call have no effect. Consider making it dynamic, but take history into account + val localEvaluator by lazy { GenericReplCompiledEvaluator(templateClasspath, Thread.currentThread().contextClassLoader, getScriptArgs(getContext()), scriptArgsTypes) } - private val repl by lazy { - GenericRepl( - disposable, - makeScriptDefinition(templateClasspath, templateClassName), - makeCompilerConfiguration(), - messageCollector, - Thread.currentThread().contextClassLoader, - getScriptArgs(getContext()), - scriptArgsTypes) - } + override val replScriptInvoker: ReplScriptInvoker get() = localEvaluator + + override val replEvaluator: ReplCompiledEvaluator get() = localEvaluator private fun makeScriptDefinition(templateClasspath: List, templateClassName: String): KotlinScriptDefinition { val classloader = URLClassLoader(templateClasspath.map { it.toURI().toURL() }.toTypedArray(), this.javaClass.classLoader) @@ -102,14 +66,4 @@ class KotlinJsr223JvmLocalScriptEngine( addJvmClasspathRoots(templateClasspath) put(CommonConfigurationKeys.MODULE_NAME, "kotlin-script") } - - override val replScriptInvoker: ReplScriptInvoker - get() = repl.scriptInvoker - - - override fun eval(codeLine: ReplCodeLine, history: List): ReplEvalResult { - val evalResult = repl.eval(codeLine, history) - messageCollector.resetAndThrowOnErrors() - return evalResult - } } 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 48cb9836bf5..48179257e48 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 @@ -48,7 +48,7 @@ class KotlinJsr223JvmLocalScriptEngineFactory : KotlinJsr223JvmScriptEngineFacto class KotlinJsr223JvmDaemonLocalEvalScriptEngineFactory : KotlinJsr223JvmScriptEngineFactoryBase() { override fun getScriptEngine(): ScriptEngine = - KotlinJsr223JvmDaemonLocalEvalScriptEngine( + KotlinJsr223JvmDaemonCompileScriptEngine( Disposer.newDisposable(), this, kotlinCompilerJar, @@ -59,20 +59,6 @@ class KotlinJsr223JvmDaemonLocalEvalScriptEngineFactory : KotlinJsr223JvmScriptE ) } -class KotlinJsr223JvmDaemonRemoteEvalScriptEngineFactory : KotlinJsr223JvmScriptEngineFactoryBase() { - - override fun getScriptEngine(): ScriptEngine = - KotlinJsr223JvmDaemonRemoteEvalScriptEngine( - Disposer.newDisposable(), - this, - kotlinCompilerJar, - scriptCompilationClasspathFromContext(), - "kotlin.script.templates.standard.ScriptTemplateWithBindings", - ::makeSerializableArgumentsForTemplateWithBindings, - arrayOf(Map::class.java) - ) -} - private fun makeSerializableArgumentsForTemplateWithBindings(ctx: ScriptContext): Array { val bindings = ctx.getBindings(ScriptContext.ENGINE_SCOPE) val serializableBindings = linkedMapOf() diff --git a/libraries/tools/kotlin-script-util/src/test/kotlin/org/jetbrains/kotlin/script/util/ScriptUtilIT.kt b/libraries/tools/kotlin-script-util/src/test/kotlin/org/jetbrains/kotlin/script/util/ScriptUtilIT.kt index cc2a1c775a5..3ac7a43fece 100644 --- a/libraries/tools/kotlin-script-util/src/test/kotlin/org/jetbrains/kotlin/script/util/ScriptUtilIT.kt +++ b/libraries/tools/kotlin-script-util/src/test/kotlin/org/jetbrains/kotlin/script/util/ScriptUtilIT.kt @@ -38,14 +38,13 @@ import org.jetbrains.kotlin.utils.PathUtil import org.jetbrains.kotlin.utils.PathUtil.getResourcePathForClass import org.junit.Assert import org.junit.Test -import java.io.ByteArrayOutputStream -import java.io.File -import java.io.PrintStream +import java.io.* import java.net.URI import java.util.jar.Manifest import kotlin.reflect.KClass import kotlin.test.* + class ScriptUtilIT { companion object { @@ -89,7 +88,14 @@ done @Test fun testResolveStdJUnitHelloWorld() { - Assert.assertNull(compileScript("args-junit-hello-world.kts", StandardArgsScriptTemplateWithLocalResolving::class)) + val savedErr = System.err + try { + System.setErr(PrintStream(NullOutputStream())) + Assert.assertNull(compileScript("args-junit-hello-world.kts", StandardArgsScriptTemplateWithLocalResolving::class)) + } + finally { + System.setErr(savedErr) + } val scriptClass = compileScript("args-junit-hello-world.kts", StandardArgsScriptTemplateWithMavenResolving::class) if (scriptClass == null) { @@ -196,3 +202,9 @@ done return outStream.toString() } } + +private class NullOutputStream : OutputStream() { + override fun write(b: Int) { } + override fun write(b: ByteArray) { } + override fun write(b: ByteArray, off: Int, len: Int) { } +} \ No newline at end of file