Implement script evaluation configuration refinement

This commit is contained in:
Ilya Chernikov
2019-04-01 16:10:31 +02:00
parent c51d4a4405
commit fa58b41e43
3 changed files with 82 additions and 40 deletions
@@ -92,10 +92,18 @@ class ScriptCollectedData(properties: Map<PropertiesCollection.Key<*>, Any>) : P
val ScriptCollectedDataKeys.foundAnnotations by PropertiesCollection.key<List<Annotation>>() val ScriptCollectedDataKeys.foundAnnotations by PropertiesCollection.key<List<Annotation>>()
/** /**
* The facade to the script data for refinement callbacks * The facade to the script data for compilation configuration refinement callbacks
*/ */
class ScriptConfigurationRefinementContext( class ScriptConfigurationRefinementContext(
val script: SourceCode, val script: SourceCode,
val compilationConfiguration: ScriptCompilationConfiguration, val compilationConfiguration: ScriptCompilationConfiguration,
val collectedData: ScriptCollectedData? = null val collectedData: ScriptCollectedData? = null
) )
/**
* The facade to the script data for evaluation configuration refinement callbacks
*/
class ScriptEvaluationConfigurationRefinementContext(
val compiledScript: CompiledScript<*>,
val evaluationConfiguration: ScriptEvaluationConfiguration
)
@@ -7,6 +7,7 @@
package kotlin.script.experimental.api package kotlin.script.experimental.api
import java.io.Serializable
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.script.experimental.util.PropertiesCollection import kotlin.script.experimental.util.PropertiesCollection
@@ -52,7 +53,12 @@ val ScriptEvaluationConfigurationKeys.constructorArgs by PropertiesCollection.ke
* only create/evaluate instances if not found, and evaluator will put newly created instances into the map * only create/evaluate instances if not found, and evaluator will put newly created instances into the map
* This allows to have a single instance of the script if it is imported several times via different import paths. * This allows to have a single instance of the script if it is imported several times via different import paths.
*/ */
val ScriptEvaluationConfigurationKeys.scriptsInstancesSharingMap by PropertiesCollection.key<MutableMap<KClass<*>, Any>>() val ScriptEvaluationConfigurationKeys.scriptsInstancesSharingMap by PropertiesCollection.key<MutableMap<KClass<*>, EvaluationResult>>()
/**
* The callback that will be called on the script compilation immediately before starting the compilation
*/
val ScriptEvaluationConfigurationKeys.refineConfigurationBeforeEvaluate by PropertiesCollection.key<RefineEvaluationConfigurationData>()
/** /**
* A helper to enable scriptsInstancesSharingMap with default implementation * A helper to enable scriptsInstancesSharingMap with default implementation
@@ -63,6 +69,25 @@ fun ScriptEvaluationConfiguration.Builder.enableScriptsInstancesSharing() {
} }
} }
/**
* A helper to enable passing lambda directly to the refinement "keyword"
*/
fun ScriptEvaluationConfiguration.Builder.refineConfigurationBeforeEvaluate(handler: RefineScriptEvaluationConfigurationHandler) {
set(ScriptEvaluationConfiguration.refineConfigurationBeforeEvaluate, RefineEvaluationConfigurationData(handler))
}
/**
* The refinement callback function signature
*/
typealias RefineScriptEvaluationConfigurationHandler =
(ScriptEvaluationConfigurationRefinementContext) -> ResultWithDiagnostics<ScriptEvaluationConfiguration>
data class RefineEvaluationConfigurationData(
val handler: RefineScriptEvaluationConfigurationHandler
) : Serializable {
companion object { private const val serialVersionUID: Long = 1L }
}
/** /**
* The script evaluation result value * The script evaluation result value
*/ */
@@ -7,6 +7,7 @@
package kotlin.script.experimental.jvmhost package kotlin.script.experimental.jvmhost
import kotlin.reflect.KClass
import kotlin.script.experimental.api.* import kotlin.script.experimental.api.*
import kotlin.script.experimental.util.PropertiesCollection import kotlin.script.experimental.util.PropertiesCollection
@@ -30,8 +31,8 @@ open class BasicJvmScriptEvaluator : ScriptEvaluator {
scriptEvaluationConfiguration: ScriptEvaluationConfiguration? scriptEvaluationConfiguration: ScriptEvaluationConfiguration?
): ResultWithDiagnostics<EvaluationResult> = ): ResultWithDiagnostics<EvaluationResult> =
try { try {
val actualEvaluationConfiguration = scriptEvaluationConfiguration ?: ScriptEvaluationConfiguration() val actualEvalConfiguration = scriptEvaluationConfiguration ?: ScriptEvaluationConfiguration()
compiledScript.getClass(actualEvaluationConfiguration).onSuccess { scriptClass -> compiledScript.getClass(actualEvalConfiguration).onSuccess { scriptClass ->
// in the future, when (if) we'll stop to compile everything into constructor // in the future, when (if) we'll stop to compile everything into constructor
// run as SAM // run as SAM
// return res // return res
@@ -39,55 +40,63 @@ open class BasicJvmScriptEvaluator : ScriptEvaluator {
// for other scripts we need evaluation configuration with actualClassloader set, // for other scripts we need evaluation configuration with actualClassloader set,
// so they are loaded in the same classloader as the "main" script // so they are loaded in the same classloader as the "main" script
val updatedEvalConfiguration = val updatedEvalConfiguration =
if (actualEvaluationConfiguration.containsKey(ScriptEvaluationConfiguration.jvm.actualClassLoader)) if (actualEvalConfiguration.containsKey(ScriptEvaluationConfiguration.jvm.actualClassLoader))
actualEvaluationConfiguration actualEvalConfiguration
else else
ScriptEvaluationConfiguration(actualEvaluationConfiguration) { ScriptEvaluationConfiguration(actualEvalConfiguration) {
ScriptEvaluationConfiguration.jvm.actualClassLoader(scriptClass.java.classLoader) ScriptEvaluationConfiguration.jvm.actualClassLoader(scriptClass.java.classLoader)
} }
val sharedScripts = actualEvaluationConfiguration[ScriptEvaluationConfiguration.scriptsInstancesSharingMap] val sharedScripts = actualEvalConfiguration[ScriptEvaluationConfiguration.scriptsInstancesSharingMap]
val instanceFromShared = sharedScripts?.get(scriptClass) sharedScripts?.get(scriptClass)?.asSuccess()
?: compiledScript.otherScripts.mapSuccess {
if (instanceFromShared != null) {
instanceFromShared.asSuccess(updatedEvalConfiguration)
} else {
val args = ArrayList<Any?>()
updatedEvalConfiguration[ScriptEvaluationConfiguration.constructorArgs]?.let {
args.addAll(it)
}
actualEvaluationConfiguration[ScriptEvaluationConfiguration.implicitReceivers]?.let {
args.addAll(it)
}
actualEvaluationConfiguration[ScriptEvaluationConfiguration.providedProperties]?.forEach {
args.add(it.value)
}
compiledScript.otherScripts.mapSuccess {
invoke(it, updatedEvalConfiguration) invoke(it, updatedEvalConfiguration)
}.onSuccess { importedScriptsEvalResults -> }.onSuccess { importedScriptsEvalResults ->
importedScriptsEvalResults.forEach { val refinedEvalConfiguration =
args.add((it.returnValue as ResultValue.Value).scriptInstance) updatedEvalConfiguration[ScriptEvaluationConfiguration.refineConfigurationBeforeEvaluate]
?.handler?.invoke(ScriptEvaluationConfigurationRefinementContext(compiledScript, updatedEvalConfiguration))
?.onFailure {
return@invoke ResultWithDiagnostics.Failure(it.reports)
}
?.resultOrNull()
?: updatedEvalConfiguration
scriptClass.evalWithConfigAndOtherScriptsResults(refinedEvalConfiguration, importedScriptsEvalResults).let {
sharedScripts?.put(scriptClass, it)
ResultWithDiagnostics.Success(it)
} }
val ctor = scriptClass.java.constructors.single()
val instance = ctor.newInstance(*args.toArray())
sharedScripts?.put(scriptClass, instance)
instance.asSuccess(updatedEvalConfiguration)
} }
}
} }
} catch (e: Throwable) { } catch (e: Throwable) {
ResultWithDiagnostics.Failure(e.asDiagnostics("Error evaluating script", path = compiledScript.sourceLocationId)) ResultWithDiagnostics.Failure(e.asDiagnostics("Error evaluating script", path = compiledScript.sourceLocationId))
} }
private fun KClass<*>.evalWithConfigAndOtherScriptsResults(
refinedEvalConfiguration: ScriptEvaluationConfiguration,
importedScriptsEvalResults: List<EvaluationResult>
): EvaluationResult {
val args = ArrayList<Any?>()
refinedEvalConfiguration[ScriptEvaluationConfiguration.constructorArgs]?.let {
args.addAll(it)
}
refinedEvalConfiguration[ScriptEvaluationConfiguration.implicitReceivers]?.let {
args.addAll(it)
}
refinedEvalConfiguration[ScriptEvaluationConfiguration.providedProperties]?.forEach {
args.add(it.value)
}
importedScriptsEvalResults.forEach {
args.add((it.returnValue as ResultValue.Value).scriptInstance)
}
val ctor = java.constructors.single()
val instance = ctor.newInstance(*args.toArray())
return EvaluationResult(ResultValue.Value("", instance, "", instance), refinedEvalConfiguration)
}
} }
private fun Any.asSuccess(updatedEvalConfiguration: ScriptEvaluationConfiguration) =
// TODO: fix result value when ready
ResultWithDiagnostics.Success(EvaluationResult(ResultValue.Value("", this, "", this), updatedEvalConfiguration))