diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ScriptCodegen.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/ScriptCodegen.kt index 130d2c5eccd..b8b87b86214 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ScriptCodegen.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ScriptCodegen.kt @@ -143,7 +143,7 @@ class ScriptCodegen private constructor( iv.invokespecial("java/lang/Object", "", "()V", false) } else { val ctorDesc = superclass.unsubstitutedPrimaryConstructor - ?: throw RuntimeException("Primary constructor not found for script template " + superclass.toString()) + ?: throw RuntimeException("Primary constructor not found for script template " + superclass.toString()) iv.load(0, classType) @@ -151,8 +151,8 @@ class ScriptCodegen private constructor( for (superclassParam in ctorDesc.valueParameters) { val valueParam = valueParameters.first { it.name == superclassParam.name } val paramType = typeMapper.mapType(valueParam.type) - iv.load(valueParam!!.index + scriptContext.ctorValueParametersStart + 1, paramType) - frameMap.enterTemp(paramType) + val idx = frameMap.enter(valueParam, paramType) + iv.load(idx, paramType) } val ctorMethod = typeMapper.mapToCallableMethod(ctorDesc, false) @@ -166,14 +166,14 @@ class ScriptCodegen private constructor( iv.load(0, classType) scriptDescriptor.implicitReceivers.forEachIndexed { receiverIndex, receiver -> - val receiversParamIndex = frameMap.enterTemp(AsmUtil.getArrayType(OBJECT_TYPE)) + val receiversParamIndex = frameMap.enter(receiver, AsmUtil.getArrayType(OBJECT_TYPE)) val name = scriptContext.getImplicitReceiverName(receiverIndex) genFieldFromParam(typeMapper.mapClass(receiver), receiversParamIndex, name) } scriptDescriptor.scriptProvidedProperties.forEachIndexed { envVarIndex, envVar -> val fieldClassType = typeMapper.mapType(envVar) - val envVarParamIndex = frameMap.enterTemp(fieldClassType) + val envVarParamIndex = frameMap.enter(envVar, fieldClassType) val name = scriptContext.getProvidedPropertyName(envVarIndex) genFieldFromParam(fieldClassType, envVarParamIndex, name) } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/context/ScriptContext.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/context/ScriptContext.kt index 1839984289e..8e7f491626b 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/context/ScriptContext.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/context/ScriptContext.kt @@ -67,10 +67,8 @@ class ScriptContext( } } - val ctorValueParametersStart = if (earlierScripts.isNotEmpty()) 1 else 0 - private val ctorImplicitReceiversParametersStart = - ctorValueParametersStart + (scriptDescriptor.getSuperClassNotAny()?.unsubstitutedPrimaryConstructor?.valueParameters?.size ?: 0) + (scriptDescriptor.getSuperClassNotAny()?.unsubstitutedPrimaryConstructor?.valueParameters?.size ?: 0) private val ctorProvidedPropertiesParametersStart = ctorImplicitReceiversParametersStart + scriptDescriptor.implicitReceivers.size diff --git a/libraries/scripting/jsr223-test/test/kotlin/script/experimental/jsr223/test/KotlinJsr223ScriptEngineIT.kt b/libraries/scripting/jsr223-test/test/kotlin/script/experimental/jsr223/test/KotlinJsr223ScriptEngineIT.kt index 502381a1d7e..ab1ea04fdfe 100644 --- a/libraries/scripting/jsr223-test/test/kotlin/script/experimental/jsr223/test/KotlinJsr223ScriptEngineIT.kt +++ b/libraries/scripting/jsr223-test/test/kotlin/script/experimental/jsr223/test/KotlinJsr223ScriptEngineIT.kt @@ -179,6 +179,61 @@ obj Assert.assertEquals(111, result2) } + @Test + fun testEvalWithContextDirect() { + val engine = ScriptEngineManager().getEngineByExtension("kts")!! + + engine.put("z", 33) + + engine.eval("val x = 10 + z") + + 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 + boundValue", engine.createBindings().apply { + put("boundValue", 100) + }) + Assert.assertEquals(111, result2) + } + + @Test + fun testEvalWithContextNamesWithSymbols() { + val engine = ScriptEngineManager().getEngineByExtension("kts")!! + + engine.put("\u263a", 2) + engine.put("a.b", 3) + engine.put("c:d", 5) + engine.put("e;f", 7) + engine.put("g\$h", 11) + engine.put("il", 17) + engine.put("m[n", 19) + engine.put("o]p", 23) + engine.put("q/r", 29) + engine.put("s\\t", 31) + engine.put("u v", 37) + engine.put(" ", 41) + engine.put(" ", 43) + + Assert.assertEquals(4, engine.eval("`\u263a` * 2")) + Assert.assertEquals(5, engine.eval("2 + `a\\,b`")) + Assert.assertEquals(2, engine.eval("`a\\,b` - 1")) + Assert.assertEquals(6, engine.eval("1 + `c\\!d`")) + Assert.assertEquals(7, engine.eval("`e\\?f`")) + Assert.assertEquals(11, engine.eval("`g\\%h`")) + Assert.assertEquals(13, engine.eval("`i\\^j`")) + Assert.assertEquals(17, engine.eval("`k\\_l`")) + Assert.assertEquals(19, engine.eval("`m\\{n`")) + Assert.assertEquals(23, engine.eval("`o\\}p`")) + Assert.assertEquals(29, engine.eval("`q\\|r`")) + Assert.assertEquals(31, engine.eval("`s\\-t`")) + Assert.assertEquals(37, engine.eval("`u v`")) + Assert.assertEquals(41, engine.eval("`_`")) + Assert.assertEquals(43, engine.eval("`____`")) + } + @Test fun testSimpleEvalInEval() { val engine = ScriptEngineManager().getEngineByExtension("kts")!! diff --git a/libraries/scripting/jsr223/src/kotlin/script/experimental/jsr223/KotlinJsr223DefaultScript.kt b/libraries/scripting/jsr223/src/kotlin/script/experimental/jsr223/KotlinJsr223DefaultScript.kt index 0fe3705df3f..9a14df0af06 100644 --- a/libraries/scripting/jsr223/src/kotlin/script/experimental/jsr223/KotlinJsr223DefaultScript.kt +++ b/libraries/scripting/jsr223/src/kotlin/script/experimental/jsr223/KotlinJsr223DefaultScript.kt @@ -8,12 +8,20 @@ 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.ScriptContext import javax.script.ScriptEngine import kotlin.script.experimental.annotations.KotlinScript +import kotlin.script.experimental.api.* +import kotlin.script.experimental.jvmhost.jsr223.getScriptContext +import kotlin.script.experimental.jvmhost.jsr223.importAllBindings +import kotlin.script.experimental.jvmhost.jsr223.jsr223 import kotlin.script.templates.standard.ScriptTemplateWithBindings @Suppress("unused") -@KotlinScript +@KotlinScript( + compilationConfiguration = KotlinJsr223DefaultScriptCompilationConfiguration::class, + evaluationConfiguration = KotlinJsr223DefaultScriptEvaluationConfiguration::class +) abstract class KotlinJsr223DefaultScript(val jsr223Bindings: Bindings) : ScriptTemplateWithBindings(jsr223Bindings) { private val myEngine: ScriptEngine? get() = bindings[KOTLIN_SCRIPT_ENGINE_BINDINGS_KEY]?.let { it as? ScriptEngine } @@ -47,3 +55,62 @@ abstract class KotlinJsr223DefaultScript(val jsr223Bindings: Bindings) : ScriptT fun createBindings(): Bindings = withMyEngine { it.createBindings() } } + +object KotlinJsr223DefaultScriptCompilationConfiguration : ScriptCompilationConfiguration( + { + refineConfiguration { + beforeCompiling { context -> + val jsr223context = context.compilationConfiguration[ScriptCompilationConfiguration.jsr223.getScriptContext]?.invoke() + if (jsr223context != null && context.compilationConfiguration[ScriptCompilationConfiguration.jsr223.importAllBindings] == true) { + val updatedProperties = + context.compilationConfiguration[ScriptCompilationConfiguration.providedProperties]?.toMutableMap() ?: hashMapOf() + val allBindings = (jsr223context.getBindings(ScriptContext.GLOBAL_SCOPE).toMutableMap() ?: hashMapOf()).apply { + val engineBindings = jsr223context.getBindings(ScriptContext.ENGINE_SCOPE) + if (engineBindings != null) + putAll(engineBindings) + } + for ((k, v) in allBindings) { + // only adding bindings that are not already defined + if (!updatedProperties.containsKey(k)) { + // TODO: add only valid names + // TODO: find out how it's implemented in other jsr223 engines for typed languages, since this approach prevent certain usage scenarios, e.g. assigning back value of a "sibling" type + updatedProperties[k] = KotlinType(v::class) + } + } + ScriptCompilationConfiguration(context.compilationConfiguration) { + providedProperties(updatedProperties) + }.asSuccess() + } else context.compilationConfiguration.asSuccess() + } + } + jsr223 { + importAllBindings(true) + } + } +) + +object KotlinJsr223DefaultScriptEvaluationConfiguration : ScriptEvaluationConfiguration( + { + refineConfigurationBeforeEvaluate { context -> + val jsr223context = context.evaluationConfiguration[ScriptEvaluationConfiguration.jsr223.getScriptContext]?.invoke() + val knownProperties = context.compiledScript.compilationConfiguration[ScriptCompilationConfiguration.providedProperties] + if (jsr223context != null && knownProperties != null && knownProperties.isNotEmpty()) { + val updatedProperties = + context.evaluationConfiguration[ScriptEvaluationConfiguration.providedProperties]?.toMutableMap() ?: hashMapOf() + val engineBindings = jsr223context.getBindings(ScriptContext.ENGINE_SCOPE) + val globalBindings = jsr223context.getBindings(ScriptContext.GLOBAL_SCOPE) + for (prop in knownProperties) { + val v = when { + engineBindings?.containsKey(prop.key) == true -> engineBindings[prop.key] + globalBindings?.containsKey(prop.key) == true -> globalBindings[prop.key] + else -> return@refineConfigurationBeforeEvaluate ResultWithDiagnostics.Failure("Property ${prop.key} is not found in the bindings".asErrorDiagnostics()) + } + updatedProperties[prop.key] = v + } + ScriptEvaluationConfiguration(context.evaluationConfiguration) { + providedProperties(updatedProperties) + }.asSuccess() + } else context.evaluationConfiguration.asSuccess() + } + } +) diff --git a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/jsr223/KotlinJsr223HostConfiguration.kt b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/jsr223/KotlinJsr223HostConfiguration.kt new file mode 100644 index 00000000000..730f9dfca13 --- /dev/null +++ b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/jsr223/KotlinJsr223HostConfiguration.kt @@ -0,0 +1,54 @@ +/* + * 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.jvmhost.jsr223 + +import javax.script.ScriptContext +import kotlin.script.experimental.api.* +import kotlin.script.experimental.host.ScriptingHostConfiguration +import kotlin.script.experimental.host.ScriptingHostConfigurationKeys +import kotlin.script.experimental.util.PropertiesCollection + +interface Jsr223HostConfigurationKeys + +open class Jsr223HostConfigurationBuilder : PropertiesCollection.Builder(), + Jsr223HostConfigurationKeys { + companion object : Jsr223HostConfigurationBuilder() +} + +val ScriptingHostConfigurationKeys.jsr223 get() = Jsr223HostConfigurationBuilder() + +val Jsr223HostConfigurationKeys.getScriptContext by PropertiesCollection.key<() -> ScriptContext?>() + + +interface Jsr223CompilationConfigurationKeys + +open class Jsr223CompilationConfigurationBuilder : PropertiesCollection.Builder(), + Jsr223CompilationConfigurationKeys { + companion object : Jsr223CompilationConfigurationBuilder() +} + +val ScriptCompilationConfigurationKeys.jsr223 get() = Jsr223CompilationConfigurationBuilder() + +val Jsr223CompilationConfigurationKeys.getScriptContext by PropertiesCollection.key<() -> ScriptContext?> { + get(ScriptCompilationConfiguration.hostConfiguration)?.get(ScriptingHostConfiguration.jsr223.getScriptContext) +} + +val Jsr223CompilationConfigurationKeys.importAllBindings by PropertiesCollection.key(false) + +interface Jsr223EvaluationConfigurationKeys + +open class Jsr223EvaluationConfigurationBuilder : PropertiesCollection.Builder(), + Jsr223EvaluationConfigurationKeys { + companion object : Jsr223EvaluationConfigurationBuilder() +} + +val ScriptEvaluationConfigurationKeys.jsr223 get() = Jsr223EvaluationConfigurationBuilder() + +val Jsr223EvaluationConfigurationKeys.getScriptContext by PropertiesCollection.key<() -> ScriptContext?> { + get(ScriptEvaluationConfiguration.hostConfiguration)?.get(ScriptingHostConfiguration.jsr223.getScriptContext) +} + + diff --git a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/jsr223/KotlinJsr223ScriptEngineImpl.kt b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/jsr223/KotlinJsr223ScriptEngineImpl.kt index 571192e0f71..8a585571807 100644 --- a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/jsr223/KotlinJsr223ScriptEngineImpl.kt +++ b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/jsr223/KotlinJsr223ScriptEngineImpl.kt @@ -12,7 +12,10 @@ import javax.script.ScriptContext import javax.script.ScriptEngineFactory import kotlin.script.experimental.api.ScriptCompilationConfiguration import kotlin.script.experimental.api.ScriptEvaluationConfiguration +import kotlin.script.experimental.api.hostConfiguration +import kotlin.script.experimental.host.ScriptingHostConfiguration import kotlin.script.experimental.jvm.baseClassLoader +import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration import kotlin.script.experimental.jvm.jvm import kotlin.script.experimental.jvmhost.repl.JvmReplCompiler import kotlin.script.experimental.jvmhost.repl.JvmReplEvaluator @@ -22,13 +25,35 @@ import kotlin.script.experimental.jvmhost.repl.JvmReplEvaluatorState class KotlinJsr223ScriptEngineImpl( factory: ScriptEngineFactory, - val compilationConfiguration: ScriptCompilationConfiguration, - val evaluationConfiguration: ScriptEvaluationConfiguration + baseCompilationConfiguration: ScriptCompilationConfiguration, + baseEvaluationConfiguration: ScriptEvaluationConfiguration ) : KotlinJsr223JvmScriptEngineBase(factory), KotlinJsr223InvocableScriptEngine { + @Volatile + private var lastScriptContext: ScriptContext? = null + + val jsr223HostConfiguration = ScriptingHostConfiguration(defaultJvmScriptingHostConfiguration) { + jsr223 { + getScriptContext { lastScriptContext ?: getContext() } + } + } + + val compilationConfiguration by lazy { + ScriptCompilationConfiguration(baseCompilationConfiguration) { + hostConfiguration(jsr223HostConfiguration) + } + } + + val evaluationConfiguration by lazy { + ScriptEvaluationConfiguration(baseEvaluationConfiguration) { + hostConfiguration(jsr223HostConfiguration) + } + } + override val replCompiler: ReplCompiler by lazy { JvmReplCompiler(compilationConfiguration) } + private val localEvaluator by lazy { GenericReplCompilingEvaluatorBase(replCompiler, JvmReplEvaluator(evaluationConfiguration)) } @@ -50,5 +75,15 @@ class KotlinJsr223ScriptEngineImpl( override val baseClassLoader: ClassLoader get() = evaluationConfiguration[ScriptEvaluationConfiguration.jvm.baseClassLoader]!! + + override fun compileAndEval(script: String, context: ScriptContext): Any? { + // TODO: find a way to pass context to evaluation directly and avoid this hack + lastScriptContext = context + return try { + super.compileAndEval(script, context) + } finally { + lastScriptContext = null + } + } } diff --git a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/repl/legacyReplEvaluation.kt b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/repl/legacyReplEvaluation.kt index 7fda64da0e3..3e7dc8333f7 100644 --- a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/repl/legacyReplEvaluation.kt +++ b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/repl/legacyReplEvaluation.kt @@ -71,7 +71,11 @@ class JvmReplEvaluator( } else -> throw IllegalStateException("Expecting value with script instance, got $retVal") } - else -> ReplEvalResult.Error.Runtime(res.reports.joinToString("\n") { it.message + (it.exception?.let { e -> ": $e" } ?: "") }) + else -> + ReplEvalResult.Error.Runtime( + res.reports.joinToString("\n") { it.message + (it.exception?.let { e -> ": $e" } ?: "") }, + res.reports.find { it.exception != null }?.exception as? Exception + ) } } } diff --git a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/util/identifiers.kt b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/util/identifiers.kt new file mode 100644 index 00000000000..2d5b46f97d7 --- /dev/null +++ b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/util/identifiers.kt @@ -0,0 +1,27 @@ +/* + * 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.jvm.util + +fun String.toValidJvmIdentifier(): String = + if (isBlank()) "_".repeat(length) + else buildString(length) { + for (ch in this@toValidJvmIdentifier) { + // encoding is taken from https://blogs.oracle.com/jrose/symbolic-freedom-in-the-vm + when (ch) { + '/' -> append("\\|") + '.' -> append("\\,") + ';' -> append("\\?") + '$' -> append("\\%") + '<' -> append("\\^") + '>' -> append("\\_") + '[' -> append("\\{") + ']' -> append("\\}") + ':' -> append("\\!") + '\\' -> append("\\-") + else -> append(ch) + } + } + } \ No newline at end of file