Implement direct bindings to script properties mapping in the JSR-223 implementation
fix infrastructure and codegen parts
This commit is contained in:
@@ -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
|
||||
|
||||
+55
@@ -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")!!
|
||||
|
||||
+68
-1
@@ -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()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
+54
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
+37
-2
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
-1
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user