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
This commit is contained in:
Ilya Chernikov
2018-03-26 18:42:18 +02:00
parent 2c3a50e4b3
commit 1f0cac50d2
13 changed files with 147 additions and 75 deletions
@@ -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<out ScriptSelector> = DefaultScriptSelector::class,
val configurator: KClass<out ScriptConfigurator> = PassThroughConfigurator::class,
val runner: KClass<out ScriptRunner<*>> = DummyRunner::class
val definition: KClass<out ScriptDefinition> = 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<out ScriptCompilationConfigurator>
)
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class KotlinScriptEvaluator(
val evaluator: KClass<out ScriptEvaluator<*>>
)
@@ -9,7 +9,7 @@ interface ScriptCompiler {
suspend fun compile(
configuration: ScriptCompileConfiguration,
configurator: ScriptConfigurator? = null
configurator: ScriptCompilationConfigurator? = null
): ResultWithDiagnostics<CompiledScript<*>>
}
@@ -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<ScriptCompileConfiguration>
interface ScriptCompilationConfigurator {
// constructor(environment: ScriptingEnvironment) // the constructor is expected from implementations
val defaultConfiguration: ScriptCompileConfiguration
suspend fun baseConfiguration(scriptSource: ScriptSource): ResultWithDiagnostics<ScriptCompileConfiguration>
suspend fun refineConfiguration(
configuration: ScriptCompileConfiguration,
@@ -10,6 +10,9 @@ package kotlin.script.experimental.api
import kotlin.reflect.KClass
object ScriptCompileConfigurationParams {
val baseClass by typedKey<KClass<*>>()
val scriptSourceFragments by typedKey<ScriptSourceFragments>()
val scriptSignature by typedKey<ScriptSignature>()
@@ -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<*>?
}
@@ -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<String>()
val fileExtension by typedKey<String>()
val makeTitle by typedKey<(String) -> String>()
open class Builder : HeterogeneousMapBuilder() {
inline fun <reified T> makeTitle(noinline fn: (String) -> String) {
add(makeTitle to fn)
}
}
}
@@ -27,9 +27,11 @@ typealias ScriptEvaluationEnvironment = HeterogeneousMap
data class EvaluationResult(val returnValue: Any?, val environment: ScriptEvaluationEnvironment)
// NOTE: name inconsistency: run vs evaluate
interface ScriptRunner<in ScriptBase : Any> {
interface ScriptEvaluator<in ScriptBase : Any> {
suspend fun run(
// constructor(environment: ScriptingEnvironment) // the constructor is expected from implementations
suspend fun eval(
compiledScript: CompiledScript<ScriptBase>,
scriptEvaluationEnvironment: ScriptEvaluationEnvironment
): ResultWithDiagnostics<EvaluationResult>
@@ -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
}
@@ -31,6 +31,7 @@ class HeterogeneousMap(private val data: Map<TypedKey<*>, Any?> = hashMapOf()) {
fun HeterogeneousMap.cloneWith(vararg pairs: Pair<TypedKey<*>, Any?>) = HeterogeneousMap(this, *pairs)
fun HeterogeneousMap.cloneWith(pairs: Iterable<Pair<TypedKey<*>, Any?>>) = HeterogeneousMap(this, pairs)
open class HeterogeneousMapBuilder {
val pairs: MutableList<Pair<TypedKey<*>, Any?>> = arrayListOf()
@@ -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<KClass<*>>()
open class Builder : HeterogeneousMapBuilder()
}
@@ -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<Any>? = null) : ScriptSelector {
override val defaultConfiguration = ScriptCompileConfiguration(
ScriptCompileConfigurationParams.baseClass to environment[ScriptingEnvironmentParams.baseClass]
)
override val name: String = "Kotlin script"
override val fileExtension: String = baseClass?.findAnnotation<KotlinScriptFileExtension>()?.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<Any>? = null) : ScriptConfigurator {
override suspend fun baseConfiguration(scriptSource: ScriptSource?): ResultWithDiagnostics<ScriptCompileConfiguration> =
(when (scriptSource) {
null -> ScriptCompileConfiguration()
else -> ScriptCompileConfiguration(scriptSource.toConfigEntry())
}).asSuccess()
override suspend fun baseConfiguration(scriptSource: ScriptSource): ResultWithDiagnostics<ScriptCompileConfiguration> =
defaultConfiguration.asSuccess()
override suspend fun refineConfiguration(
configuration: ScriptCompileConfiguration,
@@ -41,8 +24,8 @@ class PassThroughConfigurator(val baseClass: KClass<Any>? = null) : ScriptConfig
configuration.asSuccess()
}
class DummyRunner<ScriptBase : Any>(val baseClass: KClass<ScriptBase>? = null) : ScriptRunner<ScriptBase> {
override suspend fun run(
class DummyEvaluator<ScriptBase : Any>(val environment: ScriptingEnvironment) : ScriptEvaluator<ScriptBase> {
override suspend fun eval(
compiledScript: CompiledScript<ScriptBase>,
scriptEvaluationEnvironment: ScriptEvaluationEnvironment
): ResultWithDiagnostics<EvaluationResult> =
@@ -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<KotlinScript>()
?: 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<Pair<TypedKey<*>, Any>>()
baseClass.findAnnotation<KotlinScriptFileExtension>()?.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<KotlinScriptCompilationConfigurator>()?.compilationConfigurator?.instantiateScriptHandler()
?: explicitDefinition?.compilationConfigurator
?: PassThroughCompilationConfigurator::class.instantiateScriptHandler()
override val evaluator =
baseClass.findAnnotation<KotlinScriptEvaluator>()?.evaluator?.instantiateScriptHandler()
?: explicitDefinition?.evaluator
?: DummyEvaluator::class.instantiateScriptHandler()
private fun <T : Any> KClass<T>.instantiateScriptHandler(): T {
val fqn = this.qualifiedName!!
val klass: KClass<T> = (baseClass.java.classLoader.loadClass(fqn) as Class<T>).kotlin
// TODO: fix call after deciding on constructor parameters
return klass.objectInstance ?: klass.primaryConstructor!!.call(baseClass)
return klass.objectInstance ?: klass.primaryConstructor!!.call(environment)
}
}
@@ -23,9 +23,9 @@ import kotlinx.coroutines.experimental.runBlocking
import kotlin.script.experimental.api.*
abstract class BasicScriptingHost<ScriptBase : Any>(
val configurator: ScriptConfigurator,
val configurator: ScriptCompilationConfigurator,
val compiler: ScriptCompiler,
val runner: ScriptRunner<ScriptBase>
val evaluator: ScriptEvaluator<ScriptBase>
) {
open fun <T> runInCoroutineContext(block: suspend CoroutineScope.() -> T): T = runBlocking { block() }
@@ -36,7 +36,7 @@ abstract class BasicScriptingHost<ScriptBase : Any>(
is ResultWithDiagnostics.Failure -> compiled
is ResultWithDiagnostics.Success -> {
val compiledScript = compiled.value!! as CompiledScript<ScriptBase>
runner.run(compiledScript, environment)
evaluator.eval(compiledScript, environment)
}
}
}