Grand refactoring of the scripting DSL + many minor usability refactorings around it

This commit is contained in:
Ilya Chernikov
2018-07-30 17:55:13 +02:00
parent 45eaca1290
commit 74f8a541c0
41 changed files with 655 additions and 619 deletions
@@ -33,10 +33,11 @@ sealed class ResultWithDiagnostics<out R> {
// call chaining
suspend fun <R1, R2> ResultWithDiagnostics<R1>.onSuccess(body: suspend (R1) -> ResultWithDiagnostics<R2>): ResultWithDiagnostics<R2> = when (this) {
is ResultWithDiagnostics.Success -> this.reports + body(this.value)
is ResultWithDiagnostics.Failure -> this
}
suspend fun <R1, R2> ResultWithDiagnostics<R1>.onSuccess(body: suspend (R1) -> ResultWithDiagnostics<R2>): ResultWithDiagnostics<R2> =
when (this) {
is ResultWithDiagnostics.Success -> this.reports + body(this.value)
is ResultWithDiagnostics.Failure -> this
}
suspend fun <R> ResultWithDiagnostics<R>.onFailure(body: suspend (ResultWithDiagnostics<R>) -> Unit): ResultWithDiagnostics<R> {
if (this is ResultWithDiagnostics.Failure) {
@@ -52,7 +53,7 @@ operator fun <R> List<ScriptDiagnostic>.plus(res: ResultWithDiagnostics<R>): Res
// results creation
fun <R : Any> R.asSuccess(reports: List<ScriptDiagnostic> = listOf()): ResultWithDiagnostics.Success<R> =
fun <R> R.asSuccess(reports: List<ScriptDiagnostic> = listOf()): ResultWithDiagnostics.Success<R> =
ResultWithDiagnostics.Success(this, reports)
fun Throwable.asDiagnostics(customMessage: String? = null, location: ScriptSource.Location? = null): ScriptDiagnostic =
@@ -1,212 +0,0 @@
/*
* 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
import kotlin.reflect.KProperty
import kotlin.reflect.KType
import kotlin.script.experimental.util.ChainedPropertyBag
import kotlin.script.experimental.util.TypedKey
interface PropertiesGroup
open class PropertiesBuilder(val props: ScriptingProperties)
class PropertiesBuilderDelegate<T: PropertiesBuilder>(val kclass: KClass<T>) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): KClass<T> = kclass
}
inline fun <reified T : PropertiesBuilder> propertiesBuilder() = PropertiesBuilderDelegate(T::class)
fun buildScriptingProperties(body: ScriptingProperties.() -> Unit): ChainedPropertyBag =
ScriptingProperties(body).build()
open class ScriptingProperties(body: ScriptingProperties.() -> Unit = {}) {
var parentPropertiesBag: ChainedPropertyBag? = null
var parentPropertiesBuilder: ScriptingProperties? = null
val data = HashMap<TypedKey<*>, Any?>()
init {
body()
setupOnce() // TODO: does it work?
}
private var isSetUp = false
fun setupOnce() {
if (!isSetUp) setup()
}
open fun setup() {}
fun build(): ChainedPropertyBag =
ChainedPropertyBag.createOptimized(parentPropertiesBag ?: parentPropertiesBuilder?.build(), data)
// --------------------------
// DSL:
// generic invoke for properties groups, allowing to use syntax:
// PropertiesGroup {
// ...
// }
inline operator fun <T : PropertiesGroup> T.invoke(body: T.() -> Unit) = body()
// generic invoke for properties builder - for extending dsl with complex builders
inline operator fun <reified T : PropertiesBuilder> KClass<T>.invoke(body: T.() -> Unit) {
constructors.first().call(this@ScriptingProperties).body()
}
// chaining:
private fun chain(propsBag: ChainedPropertyBag?, propsBuilder: ScriptingProperties?, replaceParent: Boolean) {
assert(propsBag == null || propsBuilder == null)
if (!replaceParent && parentPropertiesBag != null || parentPropertiesBuilder != null)
throw RuntimeException("Parent already defined for properties being build: ${parentPropertiesBag ?: parentPropertiesBuilder}")
parentPropertiesBag = propsBag
parentPropertiesBuilder = propsBuilder
}
fun chain(props: ScriptingProperties, replaceParent: Boolean = false) {
chain(null, props, replaceParent)
}
fun chain(props: ChainedPropertyBag, replaceParent: Boolean = false) {
chain(props, null, replaceParent)
}
// inclusion:
fun include(props: ScriptingProperties) {
props.setupOnce()
data.putAll(props.data)
}
fun include(props: ChainedPropertyBag) {
data.putAll(props.data)
}
// builders for known property types, allowing to use syntax
// propertyKey(value...)
// or
// propertyKey<Type>()
// generic:
inline operator fun <reified T> TypedKey<T>.invoke(v: T) {
data[this] = v
}
// for KotlinType:
inline operator fun <reified K> TypedKey<KotlinType>.invoke() {
data[this] = KotlinType(K::class)
}
operator fun TypedKey<KotlinType>.invoke(kclass: KClass<*>) {
data[this] = KotlinType(kclass)
}
operator fun TypedKey<KotlinType>.invoke(ktype: KType) {
data[this] = KotlinType(ktype)
}
operator fun TypedKey<KotlinType>.invoke(fqname: String) {
data[this] = KotlinType(fqname)
}
// for list of KotlinTypes
@JvmName("invoke_kotlintype_list_from_generic")
inline operator fun <reified K> TypedKey<in List<KotlinType>>.invoke() {
data.addToListProperty(this, KotlinType(K::class))
}
operator fun TypedKey<List<KotlinType>>.invoke(vararg classes: KClass<*>) {
data.addToListProperty(this, classes.map { KotlinType(it) })
}
operator fun TypedKey<List<KotlinType>>.invoke(vararg types: KType) {
data.addToListProperty(this, types.map { KotlinType(it) })
}
operator fun TypedKey<List<KotlinType>>.invoke(vararg fqnames: String) {
data.addToListProperty(this, fqnames.map { KotlinType(it) })
}
// generic for list
inline operator fun <reified E> TypedKey<List<E>>.invoke(vararg vs: E) {
data.addToListProperty(this, vs.toList())
}
// for map of generic keys to KotlinTypes:
@JvmName("invoke_kotlintype_map_from_kclass")
inline operator fun <reified K> TypedKey<Map<K, KotlinType>>.invoke(vararg classes: Pair<K, KClass<*>>) {
data.addToMapProperty(this, classes.map { (k, v) -> k to KotlinType(v) })
}
@JvmName("invoke_kotlintype_map_from_ktype")
inline operator fun <reified K> TypedKey<Map<K, KotlinType>>.invoke(vararg types: Pair<K, KType>) {
data.addToMapProperty(this, types.map { (k, v) -> k to KotlinType(v) })
}
@JvmName("invoke_kotlintype_map_from_fqname")
inline operator fun <reified K> TypedKey<Map<K, KotlinType>>.invoke(vararg fqnames: Pair<K, String>) {
data.addToMapProperty(this, fqnames.map { (k, v) -> k to KotlinType(v) })
}
// generic for maps:
inline operator fun <reified K, reified V> TypedKey<Map<K, V>>.invoke(vararg vs: Pair<K, V>) {
data.addToMapProperty(this, vs.asIterable())
}
// for strings and list of strings that could be converted from other types
@JvmName("invoke_string_fqn_from_generic")
inline operator fun <reified K> TypedKey<String>.invoke() {
data[this] = K::class.qualifiedName!!
}
@JvmName("invoke_string_fqn_from_reflected_class")
operator fun TypedKey<String>.invoke(kclass: KClass<*>) {
data[this] = kclass.qualifiedName!!
}
@JvmName("invoke_string_list_fqn_from_generic")
inline operator fun <reified K> TypedKey<in List<String>>.invoke() {
data.addToListProperty(this, K::class.qualifiedName!!)
}
@JvmName("invoke_string_list_fqn_from_reflected_class")
operator fun TypedKey<in List<String>>.invoke(vararg kclasses: KClass<*>) {
data.addToListProperty(this, kclasses.map { it.qualifiedName!! })
}
}
fun <V> HashMap<TypedKey<*>, Any?>.addToListProperty(key: TypedKey<in List<V>>, values: Iterable<V>) {
val newValues = get(key)?.let { (it as List<V>) + values } ?: values.toList()
put(key, newValues)
}
fun <V> HashMap<TypedKey<*>, Any?>.addToListProperty(key: TypedKey<in List<V>>, vararg values: V) {
val newValues = get(key)?.let { (it as List<V>) + values } ?: values.toList()
put(key, newValues)
}
fun <K, V> HashMap<TypedKey<*>, Any?>.addToMapProperty(key: TypedKey<in Map<K, V>>, values: Map<K, V>) {
val newValues = get(key)?.let { (it as Map<K, V>) + values } ?: values
put(key, newValues)
}
fun <K, V> HashMap<TypedKey<*>, Any?>.addToMapProperty(key: TypedKey<in Map<K, V>>, values: Iterable<Pair<K, V>>) {
val newValues = get(key)?.let { (it as Map<K, V>) + values } ?: values
put(key, newValues)
}
@@ -17,7 +17,8 @@ interface ScriptCompiler {
interface CompiledScript<out ScriptBase : Any> {
val configuration: ScriptCompileConfiguration
val definition: ScriptDefinition
val additionalConfiguration: ScriptCompileConfiguration?
suspend fun instantiate(scriptEvaluationEnvironment: ScriptEvaluationEnvironment): ResultWithDiagnostics<ScriptBase>
suspend fun instantiate(scriptEvaluationEnvironment: ScriptEvaluationEnvironment?): ResultWithDiagnostics<ScriptBase>
}
@@ -7,30 +7,54 @@
package kotlin.script.experimental.api
import kotlin.script.experimental.util.ChainedPropertyBag
import kotlin.script.experimental.util.typedKey
import kotlin.script.experimental.util.PropertiesCollection
typealias ScriptCompileConfiguration = ChainedPropertyBag
interface ScriptCompileConfiguration : PropertiesCollection {
companion object : ScriptCompileConfiguration {
object ScriptCompileConfigurationProperties : PropertiesGroup {
class Builder internal constructor() : PropertiesCollection.Builder(), ScriptCompileConfiguration {
override val properties = data
}
val sourceFragments by typedKey<List<ScriptSourceNamedFragment>>()
fun create(body: Builder.() -> Unit): ScriptCompileConfiguration = Builder().apply(body)
}
}
val ScriptingProperties.compilationConfiguration get() = ScriptCompileConfigurationProperties
val ScriptCompileConfiguration.sourceFragments by PropertiesCollection.key<List<ScriptSourceNamedFragment>>()
typealias ProcessedScriptData = ChainedPropertyBag
val ScriptCompileConfiguration.scriptBodyTarget by PropertiesCollection.keyCopy(ScriptDefinition.scriptBodyTarget)
object ProcessedScriptDataProperties : PropertiesGroup {
val foundAnnotations by typedKey<List<Annotation>>()
val ScriptCompileConfiguration.scriptImplicitReceivers by PropertiesCollection.keyCopy(ScriptDefinition.scriptImplicitReceivers)
val foundFragments by typedKey<List<ScriptSourceNamedFragment>>()
val ScriptCompileConfiguration.contextVariables by PropertiesCollection.keyCopy(ScriptDefinition.contextVariables)
val ScriptCompileConfiguration.defaultImports by PropertiesCollection.keyCopy(ScriptDefinition.defaultImports)
val ScriptCompileConfiguration.restrictions by PropertiesCollection.keyCopy(ScriptDefinition.restrictions)
val ScriptCompileConfiguration.importedScripts by PropertiesCollection.keyCopy(ScriptDefinition.importedScripts)
val ScriptCompileConfiguration.dependencies by PropertiesCollection.keyCopy(ScriptDefinition.dependencies)
val ScriptCompileConfiguration.compilerOptions by PropertiesCollection.keyCopy(ScriptDefinition.compilerOptions)
interface ProcessedScriptData : PropertiesCollection {
companion object : ProcessedScriptData
}
val ProcessedScriptData.foundAnnotations by PropertiesCollection.key<List<Annotation>>()
val ProcessedScriptData.foundFragments by PropertiesCollection.key<List<ScriptSourceNamedFragment>>()
interface RefineScriptCompilationConfigurationHandler {
suspend operator fun invoke(
scriptSource: ScriptSource,
configuration: ScriptCompileConfiguration,
processedScriptData: ProcessedScriptData = ProcessedScriptData()
): ResultWithDiagnostics<ScriptCompileConfiguration>
scriptDefinition: ScriptDefinition,
configuration: ScriptCompileConfiguration?,
processedScriptData: ProcessedScriptData? = null
): ResultWithDiagnostics<ScriptCompileConfiguration?>
}
@@ -8,73 +8,73 @@
package kotlin.script.experimental.api
import kotlin.reflect.KClass
import kotlin.script.experimental.util.ChainedPropertyBag
import kotlin.script.experimental.util.typedKey
import kotlin.script.experimental.util.PropertiesCollection
typealias ScriptDefinition = ChainedPropertyBag
interface ScriptDefinition : PropertiesCollection {
object ScriptDefinitionProperties : PropertiesGroup {
companion object : ScriptDefinition {
val name by typedKey<String>() // Name of the script type, by default "Kotlin script"
class Builder internal constructor() : PropertiesCollection.Builder(), ScriptDefinition {
override val properties = data
}
val fileExtension by typedKey<String>() // default: "kts"
val baseClass by typedKey<KotlinType>() // script base class
val scriptBodyTarget by typedKey<ScriptBodyTarget>()
val scriptImplicitReceivers by typedKey<List<KotlinType>>() // in the order from outer to inner scope
val contextVariables by typedKey<Map<String, KotlinType>>() // external variables
val defaultImports by typedKey<List<String>>()
val restrictions by typedKey<List<ResolvingRestrictionRule>>()
val importedScripts by typedKey<List<ScriptSource>>()
val dependencies by typedKey<List<ScriptDependency>>()
val generatedClassAnnotations by typedKey<List<Annotation>>()
val generatedMethodAnnotations by typedKey<List<Annotation>>()
val compilerOptions by typedKey<List<String>>() // Q: CommonCompilerOptions instead?
val refineConfigurationHandler by typedKey<RefineScriptCompilationConfigurationHandler>() // dynamic configurator
val refineConfigurationBeforeParsing by typedKey<Boolean>() // default: false
val refineConfigurationOnAnnotations by typedKey<List<KotlinType>>()
val refineConfigurationOnSections by typedKey<List<String>>()
// DSL:
val refineConfiguration by propertiesBuilder<RefineConfigurationBuilder>()
}
// DSL --------------------
val ScriptingProperties.scriptDefinition get() = ScriptDefinitionProperties
@Suppress("MemberVisibilityCanBePrivate")
class RefineConfigurationBuilder(props: ScriptingProperties) : PropertiesBuilder(props) {
inline operator fun invoke(body: RefineConfigurationBuilder.() -> Unit) {
body()
fun create(body: Builder.() -> Unit): ScriptDefinition = Builder().apply(body)
}
object Default : ScriptDefinition
}
val ScriptDefinition.name by PropertiesCollection.key<String>("Kotlin script") // Name of the script type
val ScriptDefinition.fileExtension by PropertiesCollection.key<String>("kts") // file extension
val ScriptDefinition.baseClass by PropertiesCollection.key<KotlinType>() // script base class
val ScriptDefinition.scriptBodyTarget by PropertiesCollection.key<ScriptBodyTarget>(ScriptBodyTarget.Constructor)
val ScriptDefinition.scriptImplicitReceivers by PropertiesCollection.key<List<KotlinType>>() // in the order from outer to inner scope
val ScriptDefinition.contextVariables by PropertiesCollection.key<Map<String, KotlinType>>() // external variables
val ScriptDefinition.defaultImports by PropertiesCollection.key<List<String>>()
val ScriptDefinition.restrictions by PropertiesCollection.key<List<ResolvingRestrictionRule>>()
val ScriptDefinition.importedScripts by PropertiesCollection.key<List<ScriptSource>>()
val ScriptDefinition.dependencies by PropertiesCollection.key<List<ScriptDependency>>()
val ScriptDefinition.generatedClassAnnotations by PropertiesCollection.key<List<Annotation>>()
val ScriptDefinition.generatedMethodAnnotations by PropertiesCollection.key<List<Annotation>>()
val ScriptDefinition.compilerOptions by PropertiesCollection.key<List<String>>() // Q: CommonCompilerOptions instead?
val ScriptDefinition.refineConfigurationHandler by PropertiesCollection.key<RefineScriptCompilationConfigurationHandler>() // dynamic configurator
val ScriptDefinition.refineConfigurationBeforeParsing by PropertiesCollection.key<Boolean>() // default: false
val ScriptDefinition.refineConfigurationOnAnnotations by PropertiesCollection.key<List<KotlinType>>()
val ScriptDefinition.refineConfigurationOnSections by PropertiesCollection.key<List<String>>()
// DSL:
val ScriptDefinition.refineConfiguration get() = RefineConfigurationBuilder()
class RefineConfigurationBuilder : PropertiesCollection.Builder() {
fun handler(fn: RefineScriptCompilationConfigurationHandler) {
props.data[ScriptDefinitionProperties.refineConfigurationHandler] = fn
set(ScriptDefinition.refineConfigurationHandler, fn)
}
fun triggerBeforeParsing(value: Boolean = true) {
props.data[ScriptDefinitionProperties.refineConfigurationBeforeParsing] = value
set(ScriptDefinition.refineConfigurationBeforeParsing, value)
}
fun triggerOnAnnotations(annotations: Iterable<KotlinType>) {
props.data.addToListProperty(ScriptDefinitionProperties.refineConfigurationOnAnnotations, annotations)
ScriptDefinition.refineConfigurationOnAnnotations.append(annotations)
}
fun triggerOnAnnotations(vararg annotations: KotlinType) {
@@ -90,11 +90,11 @@ class RefineConfigurationBuilder(props: ScriptingProperties) : PropertiesBuilder
}
fun triggerOnSections(sections: Iterable<String>) {
props.data.addToListProperty(ScriptDefinitionProperties.refineConfigurationOnSections, sections)
ScriptDefinition.refineConfigurationOnSections.append(sections)
}
fun triggerOnSections(vararg sections: String) {
props.data.addToListProperty(ScriptDefinitionProperties.refineConfigurationOnSections, *sections)
ScriptDefinition.refineConfigurationOnSections.append(*sections)
}
}
@@ -7,20 +7,27 @@
package kotlin.script.experimental.api
import kotlin.script.experimental.util.ChainedPropertyBag
import kotlin.script.experimental.util.typedKey
import kotlin.script.experimental.util.PropertiesCollection
object ScriptEvaluationEnvironmentParams {
val implicitReceivers by typedKey<List<Any>>()
interface ScriptEvaluationEnvironment : PropertiesCollection {
val contextVariables by typedKey<Map<String, Any?>>() // external variables
companion object : ScriptEvaluationEnvironment {
val constructorArgs by typedKey<List<Any?>>()
class Builder internal constructor() : PropertiesCollection.Builder(), ScriptEvaluationEnvironment {
override val properties = data
}
val runArgs by typedKey<List<Any?>>()
fun create(body: Builder.() -> Unit): ScriptEvaluationEnvironment = Builder().apply(body)
}
}
typealias ScriptEvaluationEnvironment = ChainedPropertyBag
val ScriptEvaluationEnvironment.implicitReceivers by PropertiesCollection.key<List<Any>>()
val ScriptEvaluationEnvironment.contextVariables by PropertiesCollection.key<Map<String, Any?>>() // external variables
val ScriptEvaluationEnvironment.constructorArgs by PropertiesCollection.key<List<Any?>>()
val ScriptEvaluationEnvironment.runArgs by PropertiesCollection.key<List<Any?>>()
sealed class ResultValue {
class Value(val name: String, val value: Any?, val type: String) : ResultValue() {
@@ -30,12 +37,12 @@ sealed class ResultValue {
object Unit : ResultValue()
}
data class EvaluationResult(val returnValue: ResultValue, val environment: ScriptEvaluationEnvironment)
data class EvaluationResult(val returnValue: ResultValue, val environment: ScriptEvaluationEnvironment?)
interface ScriptEvaluator {
suspend operator fun invoke(
compiledScript: CompiledScript<*>,
scriptEvaluationEnvironment: ScriptEvaluationEnvironment
scriptEvaluationEnvironment: ScriptEvaluationEnvironment?
): ResultWithDiagnostics<EvaluationResult>
}
@@ -6,33 +6,38 @@
package kotlin.script.experimental.api
import kotlin.reflect.KClass
import kotlin.script.experimental.util.ChainedPropertyBag
import kotlin.script.experimental.util.typedKey
import kotlin.script.experimental.util.PropertiesCollection
import kotlin.script.experimental.util.getOrNull
typealias ScriptingEnvironment = ChainedPropertyBag
interface ScriptingEnvironment : PropertiesCollection {
object ScriptingEnvironmentProperties : PropertiesGroup {
companion object : ScriptingEnvironment {
// should contain all dependencies needed for baseClass and compilationConfigurator
val configurationDependencies by typedKey<List<ScriptDependency>>()
class Builder internal constructor() : PropertiesCollection.Builder(), ScriptingEnvironment {
override val properties = data
}
// do not use configurationDependencies as script dependencies, so only the dependencies defined by compilationConfigurator will be used
// (NOTE: in this case they should include the dependencies for the base class anyway, since this class is needed for script
// compilation and instantiation, but compilationConfigurator could be excluded)
val isolatedDependencies by typedKey(false)
// a "class loader" for KotlinTypes
val getScriptingClass by typedKey<GetScriptingClass>()
fun create(body: Builder.() -> Unit): ScriptingEnvironment = Builder().apply(body)
}
}
val ScriptingProperties.scriptingEnvironment get() = ScriptDefinitionProperties
// should contain all dependencies needed for baseClass and compilationConfigurator
val ScriptingEnvironment.configurationDependencies by PropertiesCollection.key<List<ScriptDependency>>()
// do not use configurationDependencies as script dependencies, so only the dependencies defined by compilationConfigurator will be used
// (NOTE: in this case they should include the dependencies for the base class anyway, since this class is needed for script
// compilation and instantiation, but compilationConfigurator could be excluded)
val ScriptingEnvironment.isolatedDependencies by PropertiesCollection.key<Boolean>(false)
// a "class loader" for KotlinTypes
val ScriptingEnvironment.getScriptingClass by PropertiesCollection.key<GetScriptingClass>()
interface GetScriptingClass {
operator fun invoke(classType: KotlinType, contextClass: KClass<*>, environment: ScriptingEnvironment): KClass<*>
}
fun ScriptingEnvironment.getScriptingClass(type: KotlinType, contextClass: KClass<*>): KClass<*> {
val getClass = getOrNull(ScriptingEnvironmentProperties.getScriptingClass)
val getClass = getOrNull(ScriptingEnvironment.getScriptingClass)
?: throw IllegalArgumentException("Expecting 'getScriptingClass' property in the scripting environment: unable to load scripting class $type")
return getClass(type, contextClass, this)
}