From 6bc488d95d6558d436ac7ab9d8f586fe6992eb93 Mon Sep 17 00:00:00 2001 From: Ilya Chernikov Date: Tue, 20 Sep 2016 14:20:18 +0200 Subject: [PATCH] Implement JSR 223 engine and factory for IDEA using daemon repl --- idea/idea-repl/idea-repl.iml | 3 + .../services/javax.script.ScriptEngineFactory | 1 + .../KotlinJvmJsr223ScriptEngine4Idea.kt | 104 ++++++++++++++++++ ...mJsr223StandardScriptEngineFactory4Idea.kt | 60 ++++++++++ 4 files changed, 168 insertions(+) create mode 100644 idea/idea-repl/src/META-INF/services/javax.script.ScriptEngineFactory create mode 100644 idea/idea-repl/src/org/jetbrains/kotlin/jsr223/KotlinJvmJsr223ScriptEngine4Idea.kt create mode 100644 idea/idea-repl/src/org/jetbrains/kotlin/jsr223/KotlinJvmJsr223StandardScriptEngineFactory4Idea.kt diff --git a/idea/idea-repl/idea-repl.iml b/idea/idea-repl/idea-repl.iml index 89c3d9819c8..5c700bafce3 100644 --- a/idea/idea-repl/idea-repl.iml +++ b/idea/idea-repl/idea-repl.iml @@ -12,5 +12,8 @@ + + + \ No newline at end of file diff --git a/idea/idea-repl/src/META-INF/services/javax.script.ScriptEngineFactory b/idea/idea-repl/src/META-INF/services/javax.script.ScriptEngineFactory new file mode 100644 index 00000000000..8765a13624f --- /dev/null +++ b/idea/idea-repl/src/META-INF/services/javax.script.ScriptEngineFactory @@ -0,0 +1 @@ +org.jetbrains.kotlin.jsr223.KotlinJvmJsr223StandardScriptEngineFactory4Idea diff --git a/idea/idea-repl/src/org/jetbrains/kotlin/jsr223/KotlinJvmJsr223ScriptEngine4Idea.kt b/idea/idea-repl/src/org/jetbrains/kotlin/jsr223/KotlinJvmJsr223ScriptEngine4Idea.kt new file mode 100644 index 00000000000..4e0fc751bd5 --- /dev/null +++ b/idea/idea-repl/src/org/jetbrains/kotlin/jsr223/KotlinJvmJsr223ScriptEngine4Idea.kt @@ -0,0 +1,104 @@ +/* + * 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.jsr223 + +import com.intellij.openapi.Disposable +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation +import org.jetbrains.kotlin.cli.common.repl.GenericReplCompiledEvaluator +import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine +import org.jetbrains.kotlin.cli.common.repl.ReplCompileResult +import org.jetbrains.kotlin.cli.common.repl.ReplEvalResult +import org.jetbrains.kotlin.daemon.client.* +import org.jetbrains.kotlin.daemon.common.* +import org.jetbrains.kotlin.utils.PathUtil +import java.io.File +import java.io.Reader +import javax.script.* + +class KotlinJvmJsr223ScriptEngine4Idea( + disposable: Disposable, + private val factory: ScriptEngineFactory, + templateClasspath: List, + templateClassName: String +) : AbstractScriptEngine(), ScriptEngine { + + private val daemon by lazy { + val path = PathUtil.getKotlinPathsForIdeaPlugin().compilerPath + assert(path.exists()) + val compilerId = CompilerId.makeCompilerId(path) + val daemonOptions = configureDaemonOptions() + val daemonJVMOptions = DaemonJVMOptions() + + val daemonReportMessages = arrayListOf() + + 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}" }) + } + + private val replCompiler by lazy { + daemon.let { + KotlinRemoteReplCompiler(disposable, + it, + makeAutodeletingFlagFile("idea-jsr223-repl-session"), + CompileService.TargetPlatform.JVM, + templateClasspath, + templateClassName, + System.out) + } + } + + val localEvaluator by lazy { GenericReplCompiledEvaluator(emptyList(), Thread.currentThread().contextClassLoader) } + + private var lineCount = 0 + + private val history = arrayListOf() + + override fun eval(script: String, context: ScriptContext?): Any? { + + fun ReplCompileResult.Error.locationString() = if (location == CompilerMessageLocation.NO_LOCATION) "" + else " at ${location.line}:${location.column}:" + + lineCount += 1 + // TODO bind to context + val codeLine = ReplCodeLine(lineCount, script) + 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 + } + + val evalResult = localEvaluator.eval(codeLine, history, compiled.classes, compiled.hasResult, compiled.newClasspath) + val ret = when (evalResult) { + is ReplEvalResult.ValueResult -> evalResult.value + is ReplEvalResult.UnitResult -> null + is ReplEvalResult.Error -> throw ScriptException(evalResult.message) + 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 + return ret + } + + override fun eval(script: Reader, context: ScriptContext?): Any? = eval(script.readText(), context) + + override fun createBindings(): Bindings = SimpleBindings() + + override fun getFactory(): ScriptEngineFactory = factory +} diff --git a/idea/idea-repl/src/org/jetbrains/kotlin/jsr223/KotlinJvmJsr223StandardScriptEngineFactory4Idea.kt b/idea/idea-repl/src/org/jetbrains/kotlin/jsr223/KotlinJvmJsr223StandardScriptEngineFactory4Idea.kt new file mode 100644 index 00000000000..200676a1575 --- /dev/null +++ b/idea/idea-repl/src/org/jetbrains/kotlin/jsr223/KotlinJvmJsr223StandardScriptEngineFactory4Idea.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.jsr223 + +import com.intellij.openapi.util.Disposer +import org.jetbrains.kotlin.cli.common.KotlinVersion +import org.jetbrains.kotlin.utils.PathUtil +import javax.script.ScriptEngine +import javax.script.ScriptEngineFactory + +@Suppress("unused") // used in javax.script.ScriptEngineFactory META-INF file +class KotlinJvmJsr223StandardScriptEngineFactory4Idea : ScriptEngineFactory { + + override fun getLanguageName(): String = "kotlin" + override fun getLanguageVersion(): String = KotlinVersion.VERSION + override fun getEngineName(): String = "kotlin" + override fun getEngineVersion(): String = KotlinVersion.VERSION + override fun getExtensions(): List = listOf("kts") + override fun getMimeTypes(): List = listOf("text/x-kotlin") + override fun getNames(): List = listOf("kotlin") + + override fun getScriptEngine(): ScriptEngine = + KotlinJvmJsr223ScriptEngine4Idea( + Disposer.newDisposable(), + this, + listOf(PathUtil.getKotlinPathsForIdeaPlugin().runtimePath), + "kotlin.script.StandardScriptTemplate") + + override fun getOutputStatement(toDisplay: String?): String = "print(\"$toDisplay\")" + override fun getMethodCallSyntax(obj: String, m: String, vararg args: String): String = "$obj.$m(${args.joinToString()})" + + override fun getProgram(vararg statements: String): String { + val sep = System.getProperty("line.separator") + return statements.joinToString(sep) + sep + } + + override fun getParameter(key: String?): Any? = + when (key) { + ScriptEngine.NAME -> engineName + ScriptEngine.LANGUAGE -> languageName + ScriptEngine.LANGUAGE_VERSION -> languageVersion + ScriptEngine.ENGINE -> engineName + ScriptEngine.ENGINE_VERSION -> engineVersion + else -> null + } +}