Implement direct bindings to script properties mapping in the JSR-223 implementation

fix infrastructure and codegen parts
This commit is contained in:
Ilya Chernikov
2019-07-02 12:00:58 +02:00
parent 4940f64265
commit cfceeb1e8c
8 changed files with 252 additions and 12 deletions
@@ -143,7 +143,7 @@ class ScriptCodegen private constructor(
iv.invokespecial("java/lang/Object", "<init>", "()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)
}
@@ -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
@@ -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("i<j", 13)
engine.put("k>l", 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")!!
@@ -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()
}
}
)
@@ -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<Boolean>(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)
}
@@ -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
}
}
}
@@ -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
)
}
}
}
@@ -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)
}
}
}