diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandDescriptor.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandDescriptor.kt new file mode 100644 index 000000000..79f13cd71 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandDescriptor.kt @@ -0,0 +1,71 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.command.descriptor + +import net.mamoe.mirai.console.util.safeCast +import kotlin.reflect.KClass +import kotlin.reflect.KType +import kotlin.reflect.typeOf + +@ExperimentalCommandDescriptors +public interface CommandDescriptor { + public val overloads: List +} + +@ExperimentalCommandDescriptors +public interface CommandSignatureVariant { + public val valueParameters: List> +} + + +/** + * Inherited instances must be [CommandValueParameter] + */ +@ExperimentalCommandDescriptors +public interface ICommandParameter { + public val name: String + public val type: KType + public val defaultValue: T? + + public companion object { + @get:JvmStatic + @ExperimentalCommandDescriptors + public val ICommandParameter<*>.isOptional: Boolean + get() = this.defaultValue != null + + } +} + +@ExperimentalCommandDescriptors +public sealed class CommandValueParameter : ICommandParameter { + init { + @Suppress("LeakingThis") + require(type.classifier?.safeCast>()?.isInstance(defaultValue) == true) { + "defaultValue is not instance of type" + } + } + + public class StringConstant( + public override val name: String, + public override val defaultValue: String, + ) : CommandValueParameter() { + override val type: KType get() = STRING_TYPE + + private companion object { + @OptIn(ExperimentalStdlibApi::class) + val STRING_TYPE = typeOf() + } + } + + /** + * Extended by [CommandArgumentParser] + */ + public abstract class Extended : CommandValueParameter() +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/Exceptions.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/Exceptions.kt new file mode 100644 index 000000000..f67e712bd --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/Exceptions.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +@file:Suppress("MemberVisibilityCanBePrivate", "unused") + +package net.mamoe.mirai.console.command.descriptor + +import net.mamoe.mirai.console.command.parse.CommandCall +import net.mamoe.mirai.console.command.parse.CommandValueArgument +import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip +import net.mamoe.mirai.console.internal.data.classifierAsKClassOrNull +import kotlin.reflect.KType + + +internal val KType.qualifiedName: String + get() = this.classifierAsKClassOrNull()?.qualifiedNameOrTip ?: classifier.toString() + +@ExperimentalCommandDescriptors +public open class NoValueArgumentMappingException( + public val argument: CommandValueArgument, + public val forType: KType, +) : CommandResolutionException("Cannot find a CommandArgument mapping for ${forType.qualifiedName}") + +@ExperimentalCommandDescriptors +public open class UnresolvedCommandCallException( + public val call: CommandCall, +) : CommandResolutionException("Unresolved call: $call") + +public open class CommandResolutionException : RuntimeException { + public constructor() : super() + public constructor(message: String?) : super(message) + public constructor(message: String?, cause: Throwable?) : super(message, cause) + public constructor(cause: Throwable?) : super(cause) +} diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/ExperimentalCommandDescriptors.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/ExperimentalCommandDescriptors.kt new file mode 100644 index 000000000..31ee99b7c --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/ExperimentalCommandDescriptors.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.command.descriptor + +import net.mamoe.mirai.console.util.ConsoleExperimentalApi +import kotlin.annotation.AnnotationTarget.* + +/** + * 标记一个实验性的指令解释器 API. + * + * 这些 API 不具有稳定性, 且可能会在任意时刻更改. + * 不建议在发行版本中使用这些 API. + * + * @since 1.0-RC + */ +@Retention(AnnotationRetention.BINARY) +@RequiresOptIn(level = RequiresOptIn.Level.WARNING) +@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR) +@MustBeDocumented +@ConsoleExperimentalApi +@ExperimentalCommandDescriptors +public annotation class ExperimentalCommandDescriptors( + val message: String = "Command descriptors are an experimental API.", +) \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/TypeVariant.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/TypeVariant.kt new file mode 100644 index 000000000..58c7cd35d --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/TypeVariant.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.command.descriptor + +import kotlin.reflect.KType +import kotlin.reflect.typeOf + +@ExperimentalCommandDescriptors +public interface TypeVariant { + public val outType: KType + + public fun mapValue(valueParameter: String): OutType + + public companion object { + @OptIn(ExperimentalStdlibApi::class) + @JvmSynthetic + public inline operator fun invoke(crossinline block: (valueParameter: String) -> OutType): TypeVariant { + return object : TypeVariant { + override val outType: KType = typeOf() + override fun mapValue(valueParameter: String): OutType = block(valueParameter) + } + } + } +} + +@ExperimentalCommandDescriptors +public object StringTypeVariant : TypeVariant { + @OptIn(ExperimentalStdlibApi::class) + override val outType: KType = typeOf() + override fun mapValue(valueParameter: String): String = valueParameter +} diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/parse/CommandCall.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/parse/CommandCall.kt new file mode 100644 index 000000000..e386faea6 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/parse/CommandCall.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +@file:OptIn(ExperimentalStdlibApi::class) + +package net.mamoe.mirai.console.command.parse + +import net.mamoe.mirai.console.command.CommandSender +import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors +import net.mamoe.mirai.console.command.descriptor.UnresolvedCommandCallException +import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall +import net.mamoe.mirai.console.extensions.CommandCallResolverProvider +import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage + +@ExperimentalCommandDescriptors +public interface CommandCall { + public val caller: CommandSender + + public val calleeName: String + public val valueArguments: List + + public companion object { + @JvmStatic + public fun CommandCall.resolveOrNull(): ResolvedCommandCall? { + GlobalComponentStorage.run { + CommandCallResolverProvider.useExtensions { provider -> + provider.instance.resolve(this@resolveOrNull)?.let { return it } + } + } + return null + } + + @JvmStatic + public fun CommandCall.resolve(): ResolvedCommandCall { + return resolveOrNull() ?: throw UnresolvedCommandCallException(this) + } + } +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/parse/CommandCallParser.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/parse/CommandCallParser.kt new file mode 100644 index 000000000..ed850f154 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/parse/CommandCallParser.kt @@ -0,0 +1,29 @@ +package net.mamoe.mirai.console.command.parse + +import net.mamoe.mirai.console.command.CommandSender +import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors +import net.mamoe.mirai.console.extensions.CommandCallParserProvider +import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage +import net.mamoe.mirai.console.util.ConsoleExperimentalApi +import net.mamoe.mirai.message.data.MessageChain + +/** + * @see CommandCallParserProvider + */ +@ConsoleExperimentalApi +@ExperimentalCommandDescriptors +public interface CommandCallParser { + public fun parse(sender: CommandSender, message: MessageChain): CommandCall? + + public companion object { + @JvmStatic + public fun MessageChain.parseCommandCall(sender: CommandSender): CommandCall? { + GlobalComponentStorage.run { + CommandCallParserProvider.useExtensions { provider -> + provider.instance.parse(sender, this@parseCommandCall)?.let { return it } + } + } + return null + } + } +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/parse/CommandValueArgument.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/parse/CommandValueArgument.kt new file mode 100644 index 000000000..9d46cf11c --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/parse/CommandValueArgument.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.command.parse + +import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors +import net.mamoe.mirai.console.command.descriptor.NoValueArgumentMappingException +import net.mamoe.mirai.console.command.descriptor.StringTypeVariant +import net.mamoe.mirai.console.command.descriptor.TypeVariant +import kotlin.reflect.full.isSubtypeOf +import kotlin.reflect.typeOf + +@ExperimentalCommandDescriptors +public interface CommandValueArgument { + public val value: String + public val typeVariants: List> +} + +@ExperimentalCommandDescriptors +public data class InvariantCommandValueArgument( + public override val value: String, +) : CommandValueArgument { + override val typeVariants: List> = listOf(StringTypeVariant) +} + +@ExperimentalCommandDescriptors +public fun CommandValueArgument.mapValue(typeVariant: TypeVariant): T = typeVariant.mapValue(this.value) + + +@OptIn(ExperimentalStdlibApi::class) +@ExperimentalCommandDescriptors +public inline fun CommandValueArgument.mapToType(): T = + mapToTypeOrNull() ?: throw NoValueArgumentMappingException(this, typeOf()) + +@ExperimentalCommandDescriptors +public inline fun CommandValueArgument.mapToTypeOrNull(): T? { + @OptIn(ExperimentalStdlibApi::class) + val expectingType = typeOf() + val result = typeVariants + .filter { it.outType.isSubtypeOf(expectingType) } + .also { + if (it.isEmpty()) return null + } + .reduce { acc, typeVariant -> + if (acc.outType.isSubtypeOf(typeVariant.outType)) + acc + else typeVariant + } + return result.mapValue(value) as T +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/CommandCallResolver.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/CommandCallResolver.kt new file mode 100644 index 000000000..2f66ff76b --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/CommandCallResolver.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.command.resolve + +import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors +import net.mamoe.mirai.console.command.parse.CommandCall + +public interface CommandCallResolver { + @ExperimentalCommandDescriptors + public fun resolve(call: CommandCall): ResolvedCommandCall? +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/ResolvedCommandCall.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/ResolvedCommandCall.kt new file mode 100644 index 000000000..464c0dcea --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/ResolvedCommandCall.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.command.resolve; + +import net.mamoe.mirai.console.command.Command +import net.mamoe.mirai.console.command.CommandSender +import net.mamoe.mirai.console.command.descriptor.CommandDescriptor +import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors +import net.mamoe.mirai.console.command.parse.CommandValueArgument + +@ExperimentalCommandDescriptors +public interface ResolvedCommandCall { + public val caller: CommandSender + + public val callee: Command + public val calleeDescriptor: CommandDescriptor + public val valueArguments: List +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/CommandParser.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/CommandParser.kt new file mode 100644 index 000000000..532f2e9c0 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/CommandParser.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.extensions + +import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors +import net.mamoe.mirai.console.command.parse.CommandCallParser +import net.mamoe.mirai.console.extension.AbstractExtensionPoint +import net.mamoe.mirai.console.extension.InstanceExtension + +@ExperimentalCommandDescriptors +public interface CommandCallParserProvider : InstanceExtension { + public companion object ExtensionPoint : AbstractExtensionPoint(CommandCallParserProvider::class) +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/CommandResolverExtension.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/CommandResolverExtension.kt new file mode 100644 index 000000000..a7551dc06 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/CommandResolverExtension.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.extensions + +import net.mamoe.mirai.console.command.resolve.CommandCallResolver +import net.mamoe.mirai.console.extension.AbstractExtensionPoint +import net.mamoe.mirai.console.extension.InstanceExtension + +public interface CommandCallResolverProvider : InstanceExtension { + public companion object ExtensionPoint : AbstractExtensionPoint(CommandCallResolverProvider::class) +} + diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/reflectionUtils.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/reflectionUtils.kt index e9952dd86..226dce6cc 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/reflectionUtils.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/reflectionUtils.kt @@ -41,8 +41,8 @@ internal inline fun newPluginDataInstanceUsingReflectio ?: createInstanceOrNull() ?: throw IllegalArgumentException( "Cannot create PluginData instance. " + - "PluginDataHolder supports PluginData implemented as an object " + - "or the ones with a constructor which either has no parameters or all parameters of which are optional, by default newPluginDataInstance implementation." + "PluginDataHolder supports PluginData implemented as an object " + + "or the ones with a constructor which either has no parameters or all parameters of which are optional, by default newPluginDataInstance implementation." ) } } @@ -54,6 +54,12 @@ internal fun KType.classifierAsKClass() = when (val t = classifier) { else -> error("Only KClass supported as classifier, got $t") } as KClass +@Suppress("UNCHECKED_CAST") +internal fun KType.classifierAsKClassOrNull() = when (val t = classifier) { + is KClass<*> -> t + else -> null +} as KClass? + @JvmSynthetic internal fun KClass.createInstanceOrNull(): T? { val noArgsConstructor = constructors.singleOrNull { it.parameters.all(KParameter::isOptional) } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/StandardUtils.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/StandardUtils.kt new file mode 100644 index 000000000..c2eb22a0c --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/StandardUtils.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.util + +import kotlin.contracts.contract + +public inline fun Any?.safeCast(): T? { + contract { + returnsNotNull() implies (this@safeCast is T) + } + return this as? T +} + +public inline fun Any?.cast(): T { + contract { + returns() implies (this@cast is T) + } + return this as T +} \ No newline at end of file diff --git a/tools/gradle-plugin/src/main/kotlin/net/mamoe/mirai/console/gradle/VersionConstants.kt b/tools/gradle-plugin/src/main/kotlin/net/mamoe/mirai/console/gradle/VersionConstants.kt index 2db0c82e2..c4fc5e2bf 100644 --- a/tools/gradle-plugin/src/main/kotlin/net/mamoe/mirai/console/gradle/VersionConstants.kt +++ b/tools/gradle-plugin/src/main/kotlin/net/mamoe/mirai/console/gradle/VersionConstants.kt @@ -10,6 +10,6 @@ package net.mamoe.mirai.console.gradle internal object VersionConstants { - const val CONSOLE_VERSION = "1.0-RC-dev-28" // value is written here automatically during build + const val CONSOLE_VERSION = "1.0-RC-dev-29" // value is written here automatically during build const val CORE_VERSION = "1.3.0" // value is written here automatically during build } \ No newline at end of file