From 1f0cac50d23a1588bbd8db06d5cd01129ad7d7ef Mon Sep 17 00:00:00 2001 From: Ilya Chernikov Date: Mon, 26 Mar 2018 18:42:18 +0200 Subject: [PATCH] Refactor scriptDefinition in the common part - get rid of selector (because of confusions), move it's data into definition itself and make it more generic with heterogeneous map - make main KotlinScript annotation accept complete definition - make additional annotation for building definition from parts - add ScriptingEnvironment and pass it as a constructor parameter to the script handlers - rename some classes for clarity --- .../annotations/scriptAnnotations.kt | 35 ++++++++++---- .../experimental/api/scriptCompilation.kt | 2 +- .../experimental/api/scriptConfiguration.kt | 11 +++-- .../api/scriptConfigurationParams.kt | 3 ++ .../experimental/api/scriptDefinition.kt | 13 ++++-- .../api/scriptDefinitionProperties.kt | 28 +++++++++++ .../experimental/api/scriptEvaluation.kt | 6 ++- .../experimental/api/scriptSelection.kt | 19 -------- .../script/experimental/api/scriptUtil.kt | 1 + .../experimental/api/scriptingEnvironment.kt | 19 ++++++++ .../script/experimental/basic/basicScript.kt | 33 ++++--------- .../definitions/scriptDefinitions.kt | 46 +++++++++++++++---- .../experimental/host/BasicScriptingHost.kt | 6 +-- 13 files changed, 147 insertions(+), 75 deletions(-) create mode 100644 libraries/scripting/common/src/kotlin/script/experimental/api/scriptDefinitionProperties.kt delete mode 100644 libraries/scripting/common/src/kotlin/script/experimental/api/scriptSelection.kt create mode 100644 libraries/scripting/common/src/kotlin/script/experimental/api/scriptingEnvironment.kt diff --git a/libraries/scripting/common/src/kotlin/script/experimental/annotations/scriptAnnotations.kt b/libraries/scripting/common/src/kotlin/script/experimental/annotations/scriptAnnotations.kt index cd5258ad930..a87b03ccbdd 100644 --- a/libraries/scripting/common/src/kotlin/script/experimental/annotations/scriptAnnotations.kt +++ b/libraries/scripting/common/src/kotlin/script/experimental/annotations/scriptAnnotations.kt @@ -8,20 +8,37 @@ * that can be found in the license/LICENSE.txt file. */ +@file:Suppress("unused") + package kotlin.script.experimental.annotations import kotlin.reflect.KClass -import kotlin.script.experimental.api.ScriptConfigurator -import kotlin.script.experimental.api.ScriptRunner -import kotlin.script.experimental.api.ScriptSelector -import kotlin.script.experimental.basic.DefaultScriptSelector -import kotlin.script.experimental.basic.DummyRunner -import kotlin.script.experimental.basic.PassThroughConfigurator +import kotlin.script.experimental.api.ScriptCompilationConfigurator +import kotlin.script.experimental.api.ScriptDefinition +import kotlin.script.experimental.api.ScriptEvaluator +import kotlin.script.experimental.definitions.ScriptDefinitionFromAnnotatedBaseClass @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) annotation class KotlinScript( - val selector: KClass = DefaultScriptSelector::class, - val configurator: KClass = PassThroughConfigurator::class, - val runner: KClass> = DummyRunner::class + val definition: KClass = ScriptDefinitionFromAnnotatedBaseClass::class ) + +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +annotation class KotlinScriptFileExtension( + val extension: String +) + +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +annotation class KotlinScriptCompilationConfigurator( + val compilationConfigurator: KClass +) + +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +annotation class KotlinScriptEvaluator( + val evaluator: KClass> +) + diff --git a/libraries/scripting/common/src/kotlin/script/experimental/api/scriptCompilation.kt b/libraries/scripting/common/src/kotlin/script/experimental/api/scriptCompilation.kt index d542927287e..a23b8f9398a 100644 --- a/libraries/scripting/common/src/kotlin/script/experimental/api/scriptCompilation.kt +++ b/libraries/scripting/common/src/kotlin/script/experimental/api/scriptCompilation.kt @@ -9,7 +9,7 @@ interface ScriptCompiler { suspend fun compile( configuration: ScriptCompileConfiguration, - configurator: ScriptConfigurator? = null + configurator: ScriptCompilationConfigurator? = null ): ResultWithDiagnostics> } diff --git a/libraries/scripting/common/src/kotlin/script/experimental/api/scriptConfiguration.kt b/libraries/scripting/common/src/kotlin/script/experimental/api/scriptConfiguration.kt index 14f068e0c47..ccb06252eb5 100644 --- a/libraries/scripting/common/src/kotlin/script/experimental/api/scriptConfiguration.kt +++ b/libraries/scripting/common/src/kotlin/script/experimental/api/scriptConfiguration.kt @@ -7,14 +7,19 @@ package kotlin.script.experimental.api + typealias ScriptCompileConfiguration = HeterogeneousMap typealias ProcessedScriptData = HeterogeneousMap -interface ScriptConfigurator { - // with null scriptSource should return a generic configuration for the script type - suspend fun baseConfiguration(scriptSource: ScriptSource?) : ResultWithDiagnostics +interface ScriptCompilationConfigurator { + + // constructor(environment: ScriptingEnvironment) // the constructor is expected from implementations + + val defaultConfiguration: ScriptCompileConfiguration + + suspend fun baseConfiguration(scriptSource: ScriptSource): ResultWithDiagnostics suspend fun refineConfiguration( configuration: ScriptCompileConfiguration, diff --git a/libraries/scripting/common/src/kotlin/script/experimental/api/scriptConfigurationParams.kt b/libraries/scripting/common/src/kotlin/script/experimental/api/scriptConfigurationParams.kt index 55e5c985e74..14ebb39945f 100644 --- a/libraries/scripting/common/src/kotlin/script/experimental/api/scriptConfigurationParams.kt +++ b/libraries/scripting/common/src/kotlin/script/experimental/api/scriptConfigurationParams.kt @@ -10,6 +10,9 @@ package kotlin.script.experimental.api import kotlin.reflect.KClass object ScriptCompileConfigurationParams { + + val baseClass by typedKey>() + val scriptSourceFragments by typedKey() val scriptSignature by typedKey() diff --git a/libraries/scripting/common/src/kotlin/script/experimental/api/scriptDefinition.kt b/libraries/scripting/common/src/kotlin/script/experimental/api/scriptDefinition.kt index fbca9b07902..2f0dd6123a3 100644 --- a/libraries/scripting/common/src/kotlin/script/experimental/api/scriptDefinition.kt +++ b/libraries/scripting/common/src/kotlin/script/experimental/api/scriptDefinition.kt @@ -7,12 +7,15 @@ package kotlin.script.experimental.api -import kotlin.reflect.KClass interface ScriptDefinition { - val baseClass: KClass<*> - val selector: ScriptSelector - val configurator: ScriptConfigurator - val runner: ScriptRunner<*>? + + // constructor(environment: ScriptingEnvironment) // the constructor is expected from implementations + + val properties: ScriptDefinitionPropertiesBag + + val compilationConfigurator: ScriptCompilationConfigurator + + val evaluator: ScriptEvaluator<*>? } diff --git a/libraries/scripting/common/src/kotlin/script/experimental/api/scriptDefinitionProperties.kt b/libraries/scripting/common/src/kotlin/script/experimental/api/scriptDefinitionProperties.kt new file mode 100644 index 00000000000..835139cb357 --- /dev/null +++ b/libraries/scripting/common/src/kotlin/script/experimental/api/scriptDefinitionProperties.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +@file:Suppress("unused") + +package kotlin.script.experimental.api + + +typealias ScriptDefinitionPropertiesBag = HeterogeneousMap + + +object ScriptDefinitionProperties { + + val name by typedKey() + + val fileExtension by typedKey() + + val makeTitle by typedKey<(String) -> String>() + + open class Builder : HeterogeneousMapBuilder() { + inline fun makeTitle(noinline fn: (String) -> String) { + add(makeTitle to fn) + } + } +} + diff --git a/libraries/scripting/common/src/kotlin/script/experimental/api/scriptEvaluation.kt b/libraries/scripting/common/src/kotlin/script/experimental/api/scriptEvaluation.kt index dd83e403248..bba602d2b91 100644 --- a/libraries/scripting/common/src/kotlin/script/experimental/api/scriptEvaluation.kt +++ b/libraries/scripting/common/src/kotlin/script/experimental/api/scriptEvaluation.kt @@ -27,9 +27,11 @@ typealias ScriptEvaluationEnvironment = HeterogeneousMap data class EvaluationResult(val returnValue: Any?, val environment: ScriptEvaluationEnvironment) // NOTE: name inconsistency: run vs evaluate -interface ScriptRunner { +interface ScriptEvaluator { - suspend fun run( + // constructor(environment: ScriptingEnvironment) // the constructor is expected from implementations + + suspend fun eval( compiledScript: CompiledScript, scriptEvaluationEnvironment: ScriptEvaluationEnvironment ): ResultWithDiagnostics diff --git a/libraries/scripting/common/src/kotlin/script/experimental/api/scriptSelection.kt b/libraries/scripting/common/src/kotlin/script/experimental/api/scriptSelection.kt deleted file mode 100644 index e976969cdcd..00000000000 --- a/libraries/scripting/common/src/kotlin/script/experimental/api/scriptSelection.kt +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license - * that can be found in the license/LICENSE.txt file. - */ - -@file:Suppress("unused") - -package kotlin.script.experimental.api - -interface ScriptSelector { - - val name: String - - val fileExtension: String // for preliminary selection by file type, e.g. in ide - - fun makeScriptName(scriptFileName: String?): String - - fun isKnownScript(script: ScriptSource): Boolean -} diff --git a/libraries/scripting/common/src/kotlin/script/experimental/api/scriptUtil.kt b/libraries/scripting/common/src/kotlin/script/experimental/api/scriptUtil.kt index 272b8b058a7..403da7e7f4c 100644 --- a/libraries/scripting/common/src/kotlin/script/experimental/api/scriptUtil.kt +++ b/libraries/scripting/common/src/kotlin/script/experimental/api/scriptUtil.kt @@ -31,6 +31,7 @@ class HeterogeneousMap(private val data: Map, Any?> = hashMapOf()) { fun HeterogeneousMap.cloneWith(vararg pairs: Pair, Any?>) = HeterogeneousMap(this, *pairs) +fun HeterogeneousMap.cloneWith(pairs: Iterable, Any?>>) = HeterogeneousMap(this, pairs) open class HeterogeneousMapBuilder { val pairs: MutableList, Any?>> = arrayListOf() diff --git a/libraries/scripting/common/src/kotlin/script/experimental/api/scriptingEnvironment.kt b/libraries/scripting/common/src/kotlin/script/experimental/api/scriptingEnvironment.kt new file mode 100644 index 00000000000..3c4ddcc3dd0 --- /dev/null +++ b/libraries/scripting/common/src/kotlin/script/experimental/api/scriptingEnvironment.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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.api + +import kotlin.reflect.KClass + +typealias ScriptingEnvironment = HeterogeneousMap + +object ScriptingEnvironmentParams { + + // required by definitions that extract data from script base class annotations + val baseClass by typedKey>() + + open class Builder : HeterogeneousMapBuilder() +} + diff --git a/libraries/scripting/common/src/kotlin/script/experimental/basic/basicScript.kt b/libraries/scripting/common/src/kotlin/script/experimental/basic/basicScript.kt index 7e96750fe78..05d5c2e3a56 100644 --- a/libraries/scripting/common/src/kotlin/script/experimental/basic/basicScript.kt +++ b/libraries/scripting/common/src/kotlin/script/experimental/basic/basicScript.kt @@ -5,34 +5,17 @@ package kotlin.script.experimental.basic -import kotlin.reflect.KClass -import kotlin.reflect.full.findAnnotation import kotlin.script.experimental.api.* -@Target(AnnotationTarget.CLASS) -@Retention(AnnotationRetention.RUNTIME) -annotation class KotlinScriptFileExtension(val extension: String) +class PassThroughCompilationConfigurator(val environment: ScriptingEnvironment) : ScriptCompilationConfigurator { -open class DefaultScriptSelector(val baseClass: KClass? = null) : ScriptSelector { + override val defaultConfiguration = ScriptCompileConfiguration( + ScriptCompileConfigurationParams.baseClass to environment[ScriptingEnvironmentParams.baseClass] + ) - override val name: String = "Kotlin script" - - override val fileExtension: String = baseClass?.findAnnotation()?.extension ?: "kts" - - override fun makeScriptName(scriptFileName: String?): String = scriptFileName?.removeSuffix(".$fileExtension") ?: "Kotlin script" - - override fun isKnownScript(script: ScriptSource): Boolean = - script.location?.file?.endsWith(fileExtension) ?: true -} - -class PassThroughConfigurator(val baseClass: KClass? = null) : ScriptConfigurator { - - override suspend fun baseConfiguration(scriptSource: ScriptSource?): ResultWithDiagnostics = - (when (scriptSource) { - null -> ScriptCompileConfiguration() - else -> ScriptCompileConfiguration(scriptSource.toConfigEntry()) - }).asSuccess() + override suspend fun baseConfiguration(scriptSource: ScriptSource): ResultWithDiagnostics = + defaultConfiguration.asSuccess() override suspend fun refineConfiguration( configuration: ScriptCompileConfiguration, @@ -41,8 +24,8 @@ class PassThroughConfigurator(val baseClass: KClass? = null) : ScriptConfig configuration.asSuccess() } -class DummyRunner(val baseClass: KClass? = null) : ScriptRunner { - override suspend fun run( +class DummyEvaluator(val environment: ScriptingEnvironment) : ScriptEvaluator { + override suspend fun eval( compiledScript: CompiledScript, scriptEvaluationEnvironment: ScriptEvaluationEnvironment ): ResultWithDiagnostics = diff --git a/libraries/scripting/common/src/kotlin/script/experimental/definitions/scriptDefinitions.kt b/libraries/scripting/common/src/kotlin/script/experimental/definitions/scriptDefinitions.kt index 4153707fca9..92889aae11d 100644 --- a/libraries/scripting/common/src/kotlin/script/experimental/definitions/scriptDefinitions.kt +++ b/libraries/scripting/common/src/kotlin/script/experimental/definitions/scriptDefinitions.kt @@ -6,23 +6,53 @@ package kotlin.script.experimental.definitions import kotlin.reflect.KClass +import kotlin.reflect.full.findAnnotation import kotlin.reflect.full.primaryConstructor import kotlin.script.experimental.annotations.KotlinScript -import kotlin.script.experimental.api.ScriptDefinition +import kotlin.script.experimental.annotations.KotlinScriptCompilationConfigurator +import kotlin.script.experimental.annotations.KotlinScriptEvaluator +import kotlin.script.experimental.annotations.KotlinScriptFileExtension +import kotlin.script.experimental.api.* +import kotlin.script.experimental.basic.DummyEvaluator +import kotlin.script.experimental.basic.PassThroughCompilationConfigurator -open class ScriptDefinitionFromAnnotatedBaseClass(final override val baseClass: KClass<*>) : ScriptDefinition { - private val annotation = baseClass.java.getAnnotation(KotlinScript::class.java) - ?: throw IllegalArgumentException("Expecting KotlinScript on the $baseClass") +private const val ERROR_MSG_PREFIX = "Unable to construct script definition: " - override val selector = annotation.selector.instantiateScriptHandler() - override val configurator = annotation.configurator.instantiateScriptHandler() - override val runner = annotation.runner.instantiateScriptHandler() +open class ScriptDefinitionFromAnnotatedBaseClass(val environment: ScriptingEnvironment) : ScriptDefinition { + + private val baseClass: KClass<*> = environment.getOrNull(ScriptingEnvironmentParams.baseClass) + ?: throw IllegalArgumentException("${ERROR_MSG_PREFIX}Expecting baseClass parameter in the scripting environment") + + private val mainAnnotation = baseClass.findAnnotation() + ?: throw IllegalArgumentException("${ERROR_MSG_PREFIX}Expecting KotlinScript on the $baseClass") + + private val explicitDefinition: ScriptDefinition? = + mainAnnotation.definition.takeIf { it != this::class }?.let { it.instantiateScriptHandler() } + + override val properties = (explicitDefinition?.properties ?: ScriptDefinitionPropertiesBag()).also { properties -> + val toAdd = arrayListOf, Any>>() + baseClass.findAnnotation()?.let { toAdd += ScriptDefinitionProperties.fileExtension to it } + if (properties.getOrNull(ScriptDefinitionProperties.name) == null) { + toAdd += ScriptDefinitionProperties.name to baseClass.simpleName!! + } + properties.cloneWith(toAdd) + } + + override val compilationConfigurator = + baseClass.findAnnotation()?.compilationConfigurator?.instantiateScriptHandler() + ?: explicitDefinition?.compilationConfigurator + ?: PassThroughCompilationConfigurator::class.instantiateScriptHandler() + + override val evaluator = + baseClass.findAnnotation()?.evaluator?.instantiateScriptHandler() + ?: explicitDefinition?.evaluator + ?: DummyEvaluator::class.instantiateScriptHandler() private fun KClass.instantiateScriptHandler(): T { val fqn = this.qualifiedName!! val klass: KClass = (baseClass.java.classLoader.loadClass(fqn) as Class).kotlin // TODO: fix call after deciding on constructor parameters - return klass.objectInstance ?: klass.primaryConstructor!!.call(baseClass) + return klass.objectInstance ?: klass.primaryConstructor!!.call(environment) } } diff --git a/libraries/scripting/common/src/kotlin/script/experimental/host/BasicScriptingHost.kt b/libraries/scripting/common/src/kotlin/script/experimental/host/BasicScriptingHost.kt index bb2a8dacec8..e3726e9b082 100644 --- a/libraries/scripting/common/src/kotlin/script/experimental/host/BasicScriptingHost.kt +++ b/libraries/scripting/common/src/kotlin/script/experimental/host/BasicScriptingHost.kt @@ -23,9 +23,9 @@ import kotlinx.coroutines.experimental.runBlocking import kotlin.script.experimental.api.* abstract class BasicScriptingHost( - val configurator: ScriptConfigurator, + val configurator: ScriptCompilationConfigurator, val compiler: ScriptCompiler, - val runner: ScriptRunner + val evaluator: ScriptEvaluator ) { open fun runInCoroutineContext(block: suspend CoroutineScope.() -> T): T = runBlocking { block() } @@ -36,7 +36,7 @@ abstract class BasicScriptingHost( is ResultWithDiagnostics.Failure -> compiled is ResultWithDiagnostics.Success -> { val compiledScript = compiled.value!! as CompiledScript - runner.run(compiledScript, environment) + evaluator.eval(compiledScript, environment) } } }