Add new experimental scripting API

This commit is contained in:
Ilya Chernikov
2018-02-05 15:39:26 +01:00
parent 4aec9499b5
commit aac7f97121
16 changed files with 509 additions and 0 deletions
@@ -0,0 +1,22 @@
/*
* 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.
*/
package kotlin.script.experimental.api
interface ScriptCompiler {
suspend fun compile(
configuration: ScriptCompileConfiguration,
configurator: ScriptConfigurator? = null
): ResultWithDiagnostics<CompiledScript<*>>
}
interface CompiledScript<out ScriptBase : Any> {
val configuration: ScriptCompileConfiguration
suspend fun instantiate(scriptEvaluationEnvironment: ScriptEvaluationEnvironment): ResultWithDiagnostics<ScriptBase>
}
@@ -0,0 +1,60 @@
/*
* 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
import kotlin.reflect.KClass
object ScriptCompileConfigurationParams {
val scriptSourceFragments by typedKey<ScriptSourceFragments>()
val scriptSignature by typedKey<ScriptSignature>()
val importedPackages by typedKey<Iterable<String>>()
val restrictions by typedKey<ResolvingRestrictions>()
val importedScripts by typedKey<Iterable<ScriptSource>>()
val dependencies by typedKey<Iterable<ScriptDependency>>()
val compilerOptions by typedKey<Iterable<String>>() // Q: CommonCompilerOptions instead?
val updateConfigurationOnAnnotations by typedKey<Iterable<KClass<out Annotation>>>()
val updateConfigurationOnSections by typedKey<Iterable<String>>()
}
typealias ScriptCompileConfiguration = HeterogeneousMap
fun ScriptSource.toScriptCompileConfiguration(vararg pairs: Pair<TypedKey<*>, Any?>) =
ScriptCompileConfiguration(ScriptCompileConfigurationParams.scriptSourceFragments to ScriptSourceFragments(this, null), *pairs)
object ProcessedScriptDataParams {
val annotations by typedKey<Iterable<Annotation>>()
val fragments by typedKey<Iterable<ScriptSourceNamedFragment>>()
}
typealias ProcessedScriptData = HeterogeneousMap
interface ScriptConfigurator {
// with null scriptSource should return a generic configuration for the script type
suspend fun baseConfiguration(scriptSource: ScriptSource?) : ResultWithDiagnostics<ScriptCompileConfiguration>
suspend fun refineConfiguration(
configuration: ScriptCompileConfiguration,
processedScriptData: ProcessedScriptData = ProcessedScriptData()
): ResultWithDiagnostics<ScriptCompileConfiguration>
}
fun ScriptSource?.toConfigEntry(): Pair<TypedKey<*>, Any?> =
ScriptCompileConfigurationParams.scriptSourceFragments to this?.let { ScriptSourceFragments(this, null) }
@@ -0,0 +1,58 @@
/*
* 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
import java.net.URL
import kotlin.reflect.KClass
import kotlin.reflect.KType
interface ScriptSource {
val location: URL?
val text: String?
data class Position(val line: Int, val col: Int, val absolutePos: Int? = null)
data class Range(val start: Position, val end: Position)
data class Location(val start: Position, val end: Position? = null)
}
data class ScriptSourceNamedFragment(val name: String?, val range: ScriptSource.Range)
open class ScriptSourceFragments(
val originalSource: ScriptSource,
val fragments: List<ScriptSourceNamedFragment>?)
open class ProvidedDeclarations(
val implicitReceivers: List<KType> = emptyList(), // previous scripts, etc.
val contextVariables: Map<String, KType> = emptyMap() // external variables
// Q: do we need context constants and/or types here, e.g.
// val contextConstants: Map<String, Any?> // or with KType as well
// val contextTypes: List<KType> // additional (to the classpath) types provided by the environment
// alternatively:
// val contextDeclarations: List<Tuple<DeclarationKind, String?, KType, Any?> // kind, name, type, value
// OR: it should be a HeterogeneousMap too
) {
object Empty : ProvidedDeclarations()
}
open class ScriptSignature(
val scriptBase: KClass<*>,
val providedDeclarations: ProvidedDeclarations
)
open class ResolvingRestrictions {
data class Rule(
val allow: Boolean,
val pattern: String // FQN wildcard
)
val rules: Iterable<Rule> = arrayListOf()
}
interface ScriptDependency {
// Q: anything generic here?
}
@@ -0,0 +1,18 @@
/*
* 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
import kotlin.reflect.KClass
interface ScriptDefinition {
val baseClass: KClass<*>
val selector: ScriptSelector
val configurator: ScriptConfigurator
val runner: ScriptRunner<*>?
}
@@ -0,0 +1,51 @@
/*
* 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
data class ScriptDiagnostic(
val message: String,
val severity: Severity = Severity.ERROR,
val location: ScriptSource.Location? = null,
val exception: Throwable? = null
) {
enum class Severity { ERROR, WARNING, INFO, DEBUG }
}
sealed class ResultWithDiagnostics<out R> {
abstract val reports: List<ScriptDiagnostic>
data class Success<out R>(
val value: R,
override val reports: List<ScriptDiagnostic> = listOf()
) : ResultWithDiagnostics<R>()
data class Failure(
override val reports: List<ScriptDiagnostic>
) : ResultWithDiagnostics<Nothing>() {
constructor(vararg reports: ScriptDiagnostic) : this(reports.asList())
}
}
operator fun <R> List<ScriptDiagnostic>.plus(res: ResultWithDiagnostics<R>): ResultWithDiagnostics<R> = when (res) {
is ResultWithDiagnostics.Success -> ResultWithDiagnostics.Success(res.value, this + res.reports)
is ResultWithDiagnostics.Failure -> ResultWithDiagnostics.Failure(this + res.reports)
}
fun <R : Any> R.asSuccess(reports: List<ScriptDiagnostic> = listOf()): ResultWithDiagnostics.Success<R> =
ResultWithDiagnostics.Success(this, reports)
fun Throwable.asDiagnostics(customMessage: String? = null, location: ScriptSource.Location? = null): ScriptDiagnostic =
ScriptDiagnostic(customMessage ?: message ?: "$this", ScriptDiagnostic.Severity.ERROR, location, this)
fun String.asErrorDiagnostics(location: ScriptSource.Location? = null): ScriptDiagnostic =
ScriptDiagnostic(this, ScriptDiagnostic.Severity.ERROR, location)
fun<R> ResultWithDiagnostics<R>.resultOrNull(): R? = when (this) {
is ResultWithDiagnostics.Success<R> -> value
else -> null
}
@@ -0,0 +1,28 @@
/*
* 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
object ScriptEvaluationEnvironmentParams {
val implicitReceivers by typedKey<List<Any>>()
val contextVariables by typedKey<Map<String, Any?>>() // external variables
val constructorArgs by typedKey<List<Any?>>()
val runArgs by typedKey<List<Any?>>()
}
typealias ScriptEvaluationEnvironment = HeterogeneousMap
data class EvaluationResult(val returnValue: Any?, val environment: ScriptEvaluationEnvironment)
// NOTE: name inconsistency: run vs evaluate
interface ScriptRunner<in ScriptBase : Any> {
suspend fun run(
compiledScript: CompiledScript<ScriptBase>,
scriptEvaluationEnvironment: ScriptEvaluationEnvironment
): ResultWithDiagnostics<EvaluationResult>
}
@@ -0,0 +1,19 @@
/*
* 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
}
@@ -0,0 +1,29 @@
/*
* 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.
*/
package kotlin.script.experimental.api
import kotlin.reflect.KProperty
data class TypedKey<T>(val name: String)
class TypedKeyDelegate<T> {
operator fun getValue(thisRef: Any?, property: KProperty<*>): TypedKey<T> = TypedKey(property.name)
}
fun <T> typedKey() = TypedKeyDelegate<T>()
class HeterogeneousMap(val data: Map<TypedKey<*>, Any?> = hashMapOf()) {
constructor(vararg pairs: Pair<TypedKey<*>, Any?>) : this(hashMapOf(*pairs))
}
fun HeterogeneousMap.cloneWith(vararg pairs: Pair<TypedKey<*>, Any?>) = HeterogeneousMap(HashMap(data).apply { putAll(pairs) })
operator fun <T> HeterogeneousMap.get(key: TypedKey<T>): T =
if (data.containsKey(key)) data[key] as T
else throw IllegalArgumentException("Unknown key $key")
fun <T> HeterogeneousMap.getOrNull(key: TypedKey<T>): T? = data[key] as T?