From 5f6873e3477989b3bab44b60efd954646f0c7939 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sat, 19 Sep 2020 15:56:57 +0800 Subject: [PATCH 01/28] Change SubCommandDescriptor calling from `callSuspend` to `callSuspendBy` - Support optional argument now. --- .../description/CommandArgumentContext.kt | 3 + .../CommandArgumentParserBuiltins.kt | 25 ++--- .../command/CompositeCommand.CommandParam.kt | 16 +++- .../command/CompositeCommandInternal.kt | 92 ++++++++++++------- .../mirai/console/command/TestCommand.kt | 28 +++++- 5 files changed, 116 insertions(+), 48 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentContext.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentContext.kt index 8cf2456b5..60c2e3cd0 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentContext.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentContext.kt @@ -20,6 +20,7 @@ import net.mamoe.mirai.console.permission.PermissionId import net.mamoe.mirai.console.permission.PermitteeId import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.contact.* +import net.mamoe.mirai.message.data.MessageContent import kotlin.internal.LowPriorityInOverloadResolution import kotlin.reflect.KClass import kotlin.reflect.full.isSubclassOf @@ -84,6 +85,8 @@ public interface CommandArgumentContext { PermissionId::class with PermissionIdArgumentParser PermitteeId::class with PermitteeIdArgumentParser + + MessageContent::class with RawContentArgumentParser }) } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParserBuiltins.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParserBuiltins.kt index 9e5e22fe4..dacea5e9f 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParserBuiltins.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParserBuiltins.kt @@ -19,10 +19,7 @@ import net.mamoe.mirai.console.permission.PermitteeId import net.mamoe.mirai.contact.* import net.mamoe.mirai.getFriendOrNull import net.mamoe.mirai.getGroupOrNull -import net.mamoe.mirai.message.data.At -import net.mamoe.mirai.message.data.MessageContent -import net.mamoe.mirai.message.data.SingleMessage -import net.mamoe.mirai.message.data.content +import net.mamoe.mirai.message.data.* /** @@ -86,9 +83,9 @@ public object StringArgumentParser : InternalCommandArgumentParserExtensions { public override fun parse(raw: String, sender: CommandSender): Boolean = raw.trim().let { str -> str.equals("true", ignoreCase = true) - || str.equals("yes", ignoreCase = true) - || str.equals("enabled", ignoreCase = true) - || str.equals("on", ignoreCase = true) + || str.equals("yes", ignoreCase = true) + || str.equals("enabled", ignoreCase = true) + || str.equals("on", ignoreCase = true) } } @@ -331,6 +328,12 @@ public object PermitteeIdArgumentParser : CommandArgumentParser { } } +/** 直接返回原始参数 [MessageContent] */ +public object RawContentArgumentParser : CommandArgumentParser { + override fun parse(raw: String, sender: CommandSender): MessageContent = PlainText(raw) + override fun parse(raw: MessageContent, sender: CommandSender): MessageContent = raw +} + internal interface InternalCommandArgumentParserExtensions : CommandArgumentParser { fun String.parseToLongOrFail(): Long = toLongOrNull() ?: illegalArgument("无法解析 $this 为整数") @@ -365,10 +368,10 @@ internal interface InternalCommandArgumentParserExtensions : CommandArg } else { var index = 1 illegalArgument("无法找到成员 $idOrCard。 多个成员满足搜索结果或匹配度不足: \n\n" + - candidates.joinToString("\n", limit = 6) { - val percentage = (it.second * 100).toDecimalPlace(0) - "#${index++}(${percentage}%)${it.first.nameCardOrNick.truncate(10)}(${it.first.id})" // #1 15.4% - } + candidates.joinToString("\n", limit = 6) { + val percentage = (it.second * 100).toDecimalPlace(0) + "#${index++}(${percentage}%)${it.first.nameCardOrNick.truncate(10)}(${it.first.id})" // #1 15.4% + } ) } } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommand.CommandParam.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommand.CommandParam.kt index 8506d6a4a..d7b3868f5 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommand.CommandParam.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommand.CommandParam.kt @@ -14,20 +14,25 @@ package net.mamoe.mirai.console.internal.command import net.mamoe.mirai.console.command.CompositeCommand import net.mamoe.mirai.console.command.description.CommandArgumentParser import java.lang.reflect.Parameter +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract import kotlin.reflect.KClass +import kotlin.reflect.KParameter +/* internal fun Parameter.toCommandParam(): CommandParameter<*> { val name = getAnnotation(CompositeCommand.Name::class.java) return CommandParameter( name?.value ?: this.name ?: throw IllegalArgumentException("Cannot construct CommandParam from a unnamed param"), - this.type.kotlin + this.type.kotlin, + null ) } +*/ /** * 指令形式参数. - * @see toCommandParam */ internal data class CommandParameter( /** @@ -37,9 +42,12 @@ internal data class CommandParameter( /** * 参数类型. 将从 [CompositeCommand.context] 中寻找 [CommandArgumentParser] 解析. */ - val type: KClass // exact type + val type: KClass, // exact type + val parameter: KParameter, // source parameter ) { - constructor(name: String, type: KClass, parser: CommandArgumentParser) : this(name, type) { + constructor(name: String, type: KClass, parameter: KParameter, parser: CommandArgumentParser) : this( + name, type, parameter + ) { this._overrideParser = parser } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommandInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommandInternal.kt index f922229d4..257ec28e9 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommandInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommandInternal.kt @@ -21,7 +21,8 @@ import net.mamoe.mirai.message.data.* import kotlin.reflect.KAnnotatedElement import kotlin.reflect.KClass import kotlin.reflect.KFunction -import kotlin.reflect.full.callSuspend +import kotlin.reflect.KParameter +import kotlin.reflect.full.callSuspendBy import kotlin.reflect.full.declaredFunctions import kotlin.reflect.full.findAnnotation import kotlin.reflect.full.isSubclassOf @@ -131,8 +132,9 @@ internal abstract class AbstractReflectionCommand val params: Array>, val description: String, val permission: Permission, - val onCommand: suspend (sender: CommandSender, parsedArgs: Array) -> Boolean, + val onCommand: suspend (sender: CommandSender, parsedArgs: Map) -> Boolean, val context: CommandArgumentContext, + val argumentBuilder: (sender: CommandSender) -> MutableMap, ) { val usage: String = createUsage(this@AbstractReflectionCommand) @@ -151,21 +153,40 @@ internal abstract class AbstractReflectionCommand } } + private fun KParameter.isOptional(): Boolean { + return isOptional || this.type.isMarkedNullable + } + + val minimalArgumentsSize = params.count { + !it.parameter.isOptional() + } + @JvmField internal val bakedSubNames: Array> = names.map { it.bakeSubName() }.toTypedArray() - private fun parseArgs(sender: CommandSender, rawArgs: MessageChain, offset: Int): Array? { - if (rawArgs.size < offset + this.params.size) + private fun parseArgs(sender: CommandSender, rawArgs: MessageChain, offset: Int): MutableMap? { + if (rawArgs.size < offset + minimalArgumentsSize) return null //require(rawArgs.size >= offset + this.params.size) { "No enough args. Required ${params.size}, but given ${rawArgs.size - offset}" } - - return Array(this.params.size) { index -> - val param = params[index] - val rawArg = rawArgs[offset + index] - when (rawArg) { - is PlainText -> context[param.type]?.parse(rawArg.content, sender) - is MessageContent -> context[param.type]?.parse(rawArg, sender) - else -> throw IllegalArgumentException("Illegal Message kind: ${rawArg.kClassQualifiedNameOrTip}") - } ?: error("Cannot find a parser for $rawArg") + return argumentBuilder(sender).also { result -> + params.forEachIndexed { index, parameter -> + val rawArg = rawArgs.getOrNull(offset + index) + result[parameter.parameter] = when (rawArg) { + null -> { + val p = parameter.parameter + when { + p.isOptional -> return@forEachIndexed + p.type.isMarkedNullable -> { + result[parameter.parameter] = null + return@forEachIndexed + } + else -> null + } + } + is PlainText -> context[parameter.type]?.parse(rawArg.content, sender) + is MessageContent -> context[parameter.type]?.parse(rawArg, sender) + else -> throw IllegalArgumentException("Illegal Message kind: ${rawArg.kClassQualifiedNameOrTip}") + } ?: error("Cannot find a parser for $rawArg") + } } } } @@ -250,6 +271,10 @@ internal fun AbstractReflectionCommand.SubCommandDescriptor.createUsage(baseComm appendLine() }.trimEnd() +internal fun ((T1) -> R1).then(then: (T1, R1) -> R2): ((T1) -> R2) { + return { a -> then.invoke(a, (this@then(a))) } +} + internal fun AbstractReflectionCommand.createSubCommand( function: KFunction<*>, context: CommandArgumentContext, @@ -273,12 +298,17 @@ internal fun AbstractReflectionCommand.createSubCommand( check(!function.returnType.isMarkedNullable) { error("Return type of sub command ${function.name} must not be marked nullable in Kotlin, and must be marked with @NotNull or @NonNull explicitly in Java. (at ${this::class.qualifiedNameOrTip}.${function.name})") } - + var argumentBuilder: (sender: CommandSender) -> MutableMap = { HashMap() } val parameters = function.parameters.toMutableList() - if (notStatic) parameters.removeAt(0) // instance + if (notStatic) { + val type = parameters.removeAt(0) // instance + argumentBuilder = argumentBuilder.then { _, map -> + map[type] = this@createSubCommand + map + } + } - var hasSenderParam = false check(parameters.isNotEmpty()) { "Parameters of sub command ${function.name} must not be empty. (Must have CommandSender as its receiver or first parameter or absent, followed by naturally typed params) (at ${this::class.qualifiedNameOrTip}.${function.name})" } @@ -291,8 +321,11 @@ internal fun AbstractReflectionCommand.createSubCommand( (parameters.first()).let { receiver -> if ((receiver.type.classifier as? KClass<*>)?.isSubclassOf(CommandSender::class) == true) { - hasSenderParam = true - parameters.removeAt(0) + val senderType = parameters.removeAt(0) + argumentBuilder = argumentBuilder.then { sender, map -> + map[senderType] = sender + map + } } } @@ -313,37 +346,32 @@ internal fun AbstractReflectionCommand.createSubCommand( //map parameter val params = parameters.map { param -> - if (param.isOptional) error("optional parameters are not yet supported. (at ${this::class.qualifiedNameOrTip}.${function.name}.$param)") + // if (param.isOptional) error("optional parameters are not yet supported. (at ${this::class.qualifiedNameOrTip}.${function.name}.$param)") val paramName = param.findAnnotation()?.value ?: param.name ?: "unknown" CommandParameter( paramName, (param.type.classifier as? KClass<*>) - ?: throw IllegalArgumentException("unsolved type reference from param " + param.name + ". (at ${this::class.qualifiedNameOrTip}.${function.name}.$param)") + ?: throw IllegalArgumentException("unsolved type reference from param " + param.name + ". (at ${this::class.qualifiedNameOrTip}.${function.name}.$param)"), + param ) }.toTypedArray() + // TODO: 2020/09/19 检查 optional/nullable 是否都在最后 + return SubCommandDescriptor( commandName, params, subDescription, // overridePermission?.value permission,//overridePermission?.value?.let { PermissionService.INSTANCE[PermissionId.parseFromString(it)] } ?: permission, - onCommand = { sender: CommandSender, args: Array -> - val result = if (notStatic) { - if (hasSenderParam) { - function.isSuspend - function.callSuspend(this, sender, *args) - } else function.callSuspend(this, *args) - } else { - if (hasSenderParam) { - function.callSuspend(sender, *args) - } else function.callSuspend(*args) - } + onCommand = { sender: CommandSender, args: Map -> + val result = function.callSuspendBy(args) checkNotNull(result) { "sub command return value is null (at ${this::class.qualifiedName}.${function.name})" } result as? Boolean ?: true // Unit, void is considered as true. }, - context = context + context = context, + argumentBuilder = argumentBuilder ) } \ No newline at end of file diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt index 122475bed..d95998e7c 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt @@ -238,8 +238,34 @@ internal class TestCommand { } } } + + @Test + fun `test optional argument command`() { + runBlocking { + val optionCommand = object : CompositeCommand( + ConsoleCommandOwner, + "testOptional" + ) { + @SubCommand + fun optional(arg1: String, arg2: String = "Here is optional", arg3: String?) { + println(arg1) + println(arg2) + println(arg3) +// println(arg3) + Testing.ok(Unit) + } + } + optionCommand.withRegistration { + withTesting { + assertSuccess(sender.executeCommand("/testOptional optional 1")) + } + } + } + } } internal fun assertSuccess(result: CommandExecuteResult) { - assertTrue(result.isSuccess(), result.toString()) + if (result.isFailure()) { + throw result.exception ?: AssertionError(result.toString()) + } } \ No newline at end of file From 58d799581b7ff0ee7ef1b00bd4759437f45cd9a4 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sun, 20 Sep 2020 09:40:51 +0800 Subject: [PATCH 02/28] Add CommandExecuteResult.IllegalArgument --- .../console/command/CommandExecuteResult.kt | 31 ++++++++++++++++++- .../internal/command/CommandManagerImpl.kt | 4 +++ .../command/executeCommandInternal.kt | 11 +++++-- .../mirai/console/terminal/ConsoleThread.kt | 8 ++--- 4 files changed, 46 insertions(+), 8 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandExecuteResult.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandExecuteResult.kt index f2195ae3b..9076951b4 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandExecuteResult.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandExecuteResult.kt @@ -55,6 +55,21 @@ public sealed class CommandExecuteResult { public override val status: CommandExecuteStatus get() = CommandExecuteStatus.SUCCESSFUL } + /** 执行执行时发生了一个非法参数错误 */ + public class IllegalArgument( + /** 指令执行时发生的错误 */ + public override val exception: IllegalArgumentException, + /** 尝试执行的指令 */ + public override val command: Command, + /** 尝试执行的指令名 */ + public override val commandName: String, + /** 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String] */ + public override val args: MessageChain + ) : CommandExecuteResult() { + /** 指令最终执行状态, 总是 [CommandExecuteStatus.EXECUTION_EXCEPTION] */ + public override val status: CommandExecuteStatus get() = CommandExecuteStatus.ILLEGAL_ARGUMENT + } + /** 指令执行过程出现了错误 */ public class ExecutionFailed( /** 指令执行时发生的错误 */ @@ -119,7 +134,9 @@ public sealed class CommandExecuteResult { COMMAND_NOT_FOUND, /** 权限不足 */ - PERMISSION_DENIED + PERMISSION_DENIED, + /** 非法参数 */ + ILLEGAL_ARGUMENT, } } @@ -138,6 +155,18 @@ public fun CommandExecuteResult.isSuccess(): Boolean { return this is CommandExecuteResult.Success } +/** + * 当 [this] 为 [CommandExecuteResult.IllegalArgument] 时返回 `true` + */ +@JvmSynthetic +public fun CommandExecuteResult.isIllegalArgument(): Boolean { + contract { + returns(true) implies (this@isIllegalArgument is CommandExecuteResult.IllegalArgument) + returns(false) implies (this@isIllegalArgument !is CommandExecuteResult.IllegalArgument) + } + return this is CommandExecuteResult.IllegalArgument +} + /** * 当 [this] 为 [CommandExecuteResult.ExecutionFailed] 时返回 `true` */ diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandManagerImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandManagerImpl.kt index eb34fb34a..14dfa51b2 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandManagerImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandManagerImpl.kt @@ -72,6 +72,10 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine intercept() } } + is CommandExecuteResult.IllegalArgument -> { + result.exception.message?.let { sender.sendMessage(it) } + intercept() + } is CommandExecuteResult.Success -> { intercept() } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/executeCommandInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/executeCommandInternal.kt index 703a7e062..a9ff54cca 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/executeCommandInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/executeCommandInternal.kt @@ -36,11 +36,16 @@ internal suspend fun CommandSender.executeCommandInternal( args = args ) }, - onFailure = { - return CommandExecuteResult.ExecutionFailed( + onFailure = { exception -> + return if (exception is IllegalArgumentException) CommandExecuteResult.IllegalArgument( commandName = commandName, command = command, - exception = it, + exception = exception, + args = args + ) else CommandExecuteResult.ExecutionFailed( + commandName = commandName, + command = command, + exception = exception, args = args ) } diff --git a/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/ConsoleThread.kt b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/ConsoleThread.kt index a21b63c1e..021d58ae6 100644 --- a/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/ConsoleThread.kt +++ b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/ConsoleThread.kt @@ -15,11 +15,8 @@ import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.delay import kotlinx.coroutines.launch import net.mamoe.mirai.console.MiraiConsole -import net.mamoe.mirai.console.command.BuiltInCommands -import net.mamoe.mirai.console.command.CommandExecuteStatus -import net.mamoe.mirai.console.command.CommandManager +import net.mamoe.mirai.console.command.* import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand -import net.mamoe.mirai.console.command.ConsoleCommandSender import net.mamoe.mirai.console.terminal.noconsole.NoConsole import net.mamoe.mirai.console.util.ConsoleInternalApi import net.mamoe.mirai.console.util.requestInput @@ -65,6 +62,9 @@ internal fun startupConsoleThread() { when (result.status) { CommandExecuteStatus.SUCCESSFUL -> { } + CommandExecuteStatus.ILLEGAL_ARGUMENT -> { + result.exception?.message?.let { consoleLogger.warning(it) } + } CommandExecuteStatus.EXECUTION_EXCEPTION -> { result.exception?.let(consoleLogger::error) } From a19f3c7406280f2b9a408086e11aec8b16318f32 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 20 Sep 2020 22:02:51 +0800 Subject: [PATCH 03/28] Fix doc --- .../console/command/description/CommandArgumentContext.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentContext.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentContext.kt index 4fe1e305c..aeb0015e7 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentContext.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentContext.kt @@ -20,8 +20,8 @@ import net.mamoe.mirai.console.permission.PermissionId import net.mamoe.mirai.console.permission.PermitteeId import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.contact.* -import net.mamoe.mirai.message.data.MessageContent import net.mamoe.mirai.message.data.Image +import net.mamoe.mirai.message.data.MessageContent import net.mamoe.mirai.message.data.PlainText import kotlin.internal.LowPriorityInOverloadResolution import kotlin.reflect.KClass @@ -163,7 +163,7 @@ public class SimpleCommandArgumentContext( * ``` * val context = buildCommandArgumentContext { * Int::class with IntArgParser - * Member::class with ExistMemberArgParser + * Member::class with ExistingMemberArgParser * Group::class with { s: String, sender: CommandSender -> * Bot.getInstance(s.toLong()).getGroup(s.toLong()) * } From 5d962ea6d79599a85575b7d81601a55bec4847cb Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Thu, 24 Sep 2020 18:15:46 +0800 Subject: [PATCH 04/28] typo --- .../description/CommandArgumentParserBuiltins.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParserBuiltins.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParserBuiltins.kt index 1ad81847f..366600093 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParserBuiltins.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParserBuiltins.kt @@ -31,7 +31,7 @@ public object IntArgumentParser : InternalCommandArgumentParserExtensions { } /** - * 使用 [String.toInt] 解析 + * 使用 [String.toLong] 解析 */ public object LongArgumentParser : InternalCommandArgumentParserExtensions { public override fun parse(raw: String, sender: CommandSender): Long = @@ -39,7 +39,7 @@ public object LongArgumentParser : InternalCommandArgumentParserExtensions } /** - * 使用 [String.toInt] 解析 + * 使用 [String.toShort] 解析 */ public object ShortArgumentParser : InternalCommandArgumentParserExtensions { public override fun parse(raw: String, sender: CommandSender): Short = @@ -47,7 +47,7 @@ public object ShortArgumentParser : InternalCommandArgumentParserExtensions { public override fun parse(raw: String, sender: CommandSender): Byte = @@ -55,7 +55,7 @@ public object ByteArgumentParser : InternalCommandArgumentParserExtensions } /** - * 使用 [String.toInt] 解析 + * 使用 [String.toDouble] 解析 */ public object DoubleArgumentParser : InternalCommandArgumentParserExtensions { public override fun parse(raw: String, sender: CommandSender): Double = @@ -63,7 +63,7 @@ public object DoubleArgumentParser : InternalCommandArgumentParserExtensions { public override fun parse(raw: String, sender: CommandSender): Float = From c413e9f79d109bf150207fdb8330ff4b3dc063f9 Mon Sep 17 00:00:00 2001 From: Him188 Date: Thu, 1 Oct 2020 16:38:37 +0800 Subject: [PATCH 05/28] Rename command.description to command.descriptor for future use --- .../net/mamoe/mirai/console/command/BuiltInCommands.kt | 2 +- .../net/mamoe/mirai/console/command/CommandSender.kt | 2 +- .../net/mamoe/mirai/console/command/CompositeCommand.kt | 2 +- .../net/mamoe/mirai/console/command/SimpleCommand.kt | 2 +- .../{description => descriptor}/CommandArgumentContext.kt | 4 ++-- .../{description => descriptor}/CommandArgumentParser.kt | 2 +- .../CommandArgumentParserBuiltins.kt | 2 +- .../CommandArgumentParserException.kt | 2 +- .../mamoe/mirai/console/command/java/JCompositeCommand.kt | 2 +- .../mamoe/mirai/console/command/java/JSimpleCommand.kt | 2 +- .../internal/command/CompositeCommand.CommandParam.kt | 5 +---- .../console/internal/command/CompositeCommandInternal.kt | 4 ++-- .../kotlin/net/mamoe/mirai/console/command/TestCommand.kt | 4 ++-- docs/Commands.md | 8 ++++---- 14 files changed, 20 insertions(+), 23 deletions(-) rename backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/{description => descriptor}/CommandArgumentContext.kt (98%) rename backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/{description => descriptor}/CommandArgumentParser.kt (99%) rename backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/{description => descriptor}/CommandArgumentParserBuiltins.kt (99%) rename backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/{description => descriptor}/CommandArgumentParserException.kt (94%) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt index 1b24675be..d5c56ecfd 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt @@ -15,7 +15,7 @@ import kotlinx.coroutines.sync.withLock import net.mamoe.mirai.alsoLogin import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register -import net.mamoe.mirai.console.command.description.* +import net.mamoe.mirai.console.command.descriptor.* import net.mamoe.mirai.console.internal.command.CommandManagerImpl import net.mamoe.mirai.console.internal.command.CommandManagerImpl.allRegisteredCommands import net.mamoe.mirai.console.internal.util.runIgnoreException diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt index fa0aec048..219c2485e 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt @@ -25,7 +25,7 @@ import net.mamoe.mirai.console.command.CommandSender.Companion.asCommandSender import net.mamoe.mirai.console.command.CommandSender.Companion.asMemberCommandSender import net.mamoe.mirai.console.command.CommandSender.Companion.asTempCommandSender import net.mamoe.mirai.console.command.CommandSender.Companion.toCommandSender -import net.mamoe.mirai.console.command.description.CommandArgumentParserException +import net.mamoe.mirai.console.command.descriptor.CommandArgumentParserException import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip import net.mamoe.mirai.console.internal.data.castOrNull diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt index 926fcd19a..3dcb105b7 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt @@ -17,7 +17,7 @@ package net.mamoe.mirai.console.command -import net.mamoe.mirai.console.command.description.* +import net.mamoe.mirai.console.command.descriptor.* import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME import net.mamoe.mirai.console.internal.command.AbstractReflectionCommand diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt index 27bb0f6e3..1f4fef692 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt @@ -18,7 +18,7 @@ package net.mamoe.mirai.console.command import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand -import net.mamoe.mirai.console.command.description.* +import net.mamoe.mirai.console.command.descriptor.* import net.mamoe.mirai.console.command.java.JSimpleCommand import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentContext.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentContext.kt similarity index 98% rename from backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentContext.kt rename to backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentContext.kt index aeb0015e7..a169f30b1 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentContext.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentContext.kt @@ -9,13 +9,13 @@ @file:Suppress("NOTHING_TO_INLINE", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "unused", "MemberVisibilityCanBePrivate") -package net.mamoe.mirai.console.command.description +package net.mamoe.mirai.console.command.descriptor import net.mamoe.mirai.Bot import net.mamoe.mirai.console.command.CommandSender import net.mamoe.mirai.console.command.CompositeCommand import net.mamoe.mirai.console.command.SimpleCommand -import net.mamoe.mirai.console.command.description.CommandArgumentContext.ParserPair +import net.mamoe.mirai.console.command.descriptor.CommandArgumentContext.ParserPair import net.mamoe.mirai.console.permission.PermissionId import net.mamoe.mirai.console.permission.PermitteeId import net.mamoe.mirai.console.util.ConsoleExperimentalApi diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParser.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentParser.kt similarity index 99% rename from backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParser.kt rename to backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentParser.kt index 3d717cbb6..fb796109e 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParser.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentParser.kt @@ -9,7 +9,7 @@ @file:Suppress("NOTHING_TO_INLINE", "unused") -package net.mamoe.mirai.console.command.description +package net.mamoe.mirai.console.command.descriptor import net.mamoe.mirai.Bot import net.mamoe.mirai.console.command.CommandManager diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParserBuiltins.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentParserBuiltins.kt similarity index 99% rename from backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParserBuiltins.kt rename to backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentParserBuiltins.kt index 366600093..6eec15173 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParserBuiltins.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentParserBuiltins.kt @@ -7,7 +7,7 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -package net.mamoe.mirai.console.command.description +package net.mamoe.mirai.console.command.descriptor import net.mamoe.mirai.Bot import net.mamoe.mirai.console.command.* diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParserException.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentParserException.kt similarity index 94% rename from backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParserException.kt rename to backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentParserException.kt index 033d642df..bed270637 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParserException.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentParserException.kt @@ -9,7 +9,7 @@ @file:Suppress("unused") -package net.mamoe.mirai.console.command.description +package net.mamoe.mirai.console.command.descriptor /** * 在解析参数时遇到的 _正常_ 错误. 如参数不符合规范等. diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JCompositeCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JCompositeCommand.kt index d09d2275c..215944426 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JCompositeCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JCompositeCommand.kt @@ -13,7 +13,7 @@ import net.mamoe.mirai.console.command.BuiltInCommands import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.command.CommandOwner import net.mamoe.mirai.console.command.CompositeCommand -import net.mamoe.mirai.console.command.description.buildCommandArgumentContext +import net.mamoe.mirai.console.command.descriptor.buildCommandArgumentContext import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME import net.mamoe.mirai.console.permission.Permission diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JSimpleCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JSimpleCommand.kt index 98e650ebd..f0e9b9784 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JSimpleCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JSimpleCommand.kt @@ -13,7 +13,7 @@ import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand import net.mamoe.mirai.console.command.CommandOwner import net.mamoe.mirai.console.command.SimpleCommand -import net.mamoe.mirai.console.command.description.CommandArgumentContext +import net.mamoe.mirai.console.command.descriptor.CommandArgumentContext import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME import net.mamoe.mirai.console.permission.Permission diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommand.CommandParam.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommand.CommandParam.kt index d7b3868f5..72a34842c 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommand.CommandParam.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommand.CommandParam.kt @@ -12,10 +12,7 @@ package net.mamoe.mirai.console.internal.command import net.mamoe.mirai.console.command.CompositeCommand -import net.mamoe.mirai.console.command.description.CommandArgumentParser -import java.lang.reflect.Parameter -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.contract +import net.mamoe.mirai.console.command.descriptor.CommandArgumentParser import kotlin.reflect.KClass import kotlin.reflect.KParameter diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommandInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommandInternal.kt index 257ec28e9..ea9249e5b 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommandInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommandInternal.kt @@ -12,8 +12,8 @@ package net.mamoe.mirai.console.internal.command import net.mamoe.mirai.console.command.* -import net.mamoe.mirai.console.command.description.CommandArgumentContext -import net.mamoe.mirai.console.command.description.CommandArgumentContextAware +import net.mamoe.mirai.console.command.descriptor.CommandArgumentContext +import net.mamoe.mirai.console.command.descriptor.CommandArgumentContextAware import net.mamoe.mirai.console.internal.data.kClassQualifiedNameOrTip import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.console.permission.PermissionService.Companion.testPermission diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt index d95998e7c..99a4349db 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt @@ -22,8 +22,8 @@ import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register import net.mamoe.mirai.console.command.CommandManager.INSTANCE.registeredCommands import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregister import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregisterAllCommands -import net.mamoe.mirai.console.command.description.CommandArgumentParser -import net.mamoe.mirai.console.command.description.buildCommandArgumentContext +import net.mamoe.mirai.console.command.descriptor.CommandArgumentParser +import net.mamoe.mirai.console.command.descriptor.buildCommandArgumentContext import net.mamoe.mirai.console.initTestEnvironment import net.mamoe.mirai.console.internal.command.CommandManagerImpl import net.mamoe.mirai.console.internal.command.flattenCommandComponents diff --git a/docs/Commands.md b/docs/Commands.md index a1a9ce551..816884f77 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -37,9 +37,9 @@ [`RawCommand`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt [`CommandManager`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt [`CommandSender`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt -[`CommandArgumentParser`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParser.kt -[`CommandArgumentContext`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentContext.kt -[`CommandArgumentContext.BuiltIns`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentContext.kt#L66 +[`CommandArgumentParser`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentParser.kt +[`CommandArgumentContext`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentContext.kt +[`CommandArgumentContext.BuiltIns`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentContext.kt#L66 [`MessageScope`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/MessageScope.kt @@ -113,7 +113,7 @@ interface CommandArgumentParser { 支持原生数据类型,`Contact` 及其子类,`Bot`。 #### 构建 [`CommandArgumentContext`] -查看源码内注释:[CommandArgumentContext.kt: Line 146](../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentContext.kt#L146-L183) +查看源码内注释:[CommandArgumentContext.kt: Line 146](../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentContext.kt#L146-L183) ### 支持参数解析的 [`Command`] 实现 Mirai Console 内建 [`SimpleCommand`] 与 [`CompositeCommand`] 拥有 [`CommandArgumentContext`],在处理参数时会首先解析参数再传递给插件的实现。 From d6adb3c9eae4fc9dc9b2e16382132615f2760fc7 Mon Sep 17 00:00:00 2001 From: Him188 Date: Thu, 8 Oct 2020 10:20:53 +0800 Subject: [PATCH 06/28] Command descriptors --- .../command/descriptor/CommandDescriptor.kt | 71 +++++++++++++++++++ .../console/command/descriptor/Exceptions.kt | 40 +++++++++++ .../ExperimentalCommandDescriptors.kt | 31 ++++++++ .../console/command/descriptor/TypeVariant.kt | 38 ++++++++++ .../console/command/parse/CommandCall.kt | 44 ++++++++++++ .../command/parse/CommandCallParser.kt | 29 ++++++++ .../command/parse/CommandValueArgument.kt | 56 +++++++++++++++ .../command/resolve/CommandCallResolver.kt | 18 +++++ .../command/resolve/ResolvedCommandCall.kt | 25 +++++++ .../mirai/console/extensions/CommandParser.kt | 20 ++++++ .../extensions/CommandResolverExtension.kt | 19 +++++ .../console/internal/data/reflectionUtils.kt | 10 ++- .../mamoe/mirai/console/util/StandardUtils.kt | 26 +++++++ .../mirai/console/gradle/VersionConstants.kt | 2 +- 14 files changed, 426 insertions(+), 3 deletions(-) create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandDescriptor.kt create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/Exceptions.kt create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/ExperimentalCommandDescriptors.kt create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/TypeVariant.kt create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/parse/CommandCall.kt create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/parse/CommandCallParser.kt create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/parse/CommandValueArgument.kt create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/CommandCallResolver.kt create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/ResolvedCommandCall.kt create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/CommandParser.kt create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/CommandResolverExtension.kt create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/StandardUtils.kt 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 From 4ddee695310da55bd7e04a583365821138486866 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 9 Oct 2020 10:13:07 +0800 Subject: [PATCH 07/28] Use RawCommandArgument for CommandValueArgument --- .../mirai/console/command/descriptor/TypeVariant.kt | 12 +++++++----- .../console/command/parse/CommandValueArgument.kt | 11 +++++++++-- 2 files changed, 16 insertions(+), 7 deletions(-) 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 index 58c7cd35d..b4cadf0ce 100644 --- 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 @@ -9,6 +9,8 @@ package net.mamoe.mirai.console.command.descriptor +import net.mamoe.mirai.console.command.parse.RawCommandArgument +import net.mamoe.mirai.message.data.MessageContent import kotlin.reflect.KType import kotlin.reflect.typeOf @@ -16,23 +18,23 @@ import kotlin.reflect.typeOf public interface TypeVariant { public val outType: KType - public fun mapValue(valueParameter: String): OutType + public fun mapValue(valueParameter: MessageContent): OutType public companion object { @OptIn(ExperimentalStdlibApi::class) @JvmSynthetic - public inline operator fun invoke(crossinline block: (valueParameter: String) -> OutType): TypeVariant { + public inline operator fun invoke(crossinline block: (valueParameter: RawCommandArgument) -> OutType): TypeVariant { return object : TypeVariant { override val outType: KType = typeOf() - override fun mapValue(valueParameter: String): OutType = block(valueParameter) + override fun mapValue(valueParameter: MessageContent): OutType = block(valueParameter) } } } } @ExperimentalCommandDescriptors -public object StringTypeVariant : TypeVariant { +public object StringTypeVariant : TypeVariant { @OptIn(ExperimentalStdlibApi::class) override val outType: KType = typeOf() - override fun mapValue(valueParameter: String): String = valueParameter + override fun mapValue(valueParameter: RawCommandArgument): RawCommandArgument = valueParameter } 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 index 9d46cf11c..99c1e5e20 100644 --- 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 @@ -13,18 +13,25 @@ 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 net.mamoe.mirai.message.data.MessageContent import kotlin.reflect.full.isSubtypeOf import kotlin.reflect.typeOf + +/** + * For developing use, to be inlined in the future. + */ +public typealias RawCommandArgument = MessageContent + @ExperimentalCommandDescriptors public interface CommandValueArgument { - public val value: String + public val value: RawCommandArgument public val typeVariants: List> } @ExperimentalCommandDescriptors public data class InvariantCommandValueArgument( - public override val value: String, + public override val value: RawCommandArgument, ) : CommandValueArgument { override val typeVariants: List> = listOf(StringTypeVariant) } From 1e9b498ba93d360252b52f2f79ab06cc9bb69269 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 9 Oct 2020 10:47:49 +0800 Subject: [PATCH 08/28] Docs and API improvements --- .../console/command/parse/CommandCall.kt | 38 +++++++++---------- .../command/parse/CommandCallParser.kt | 17 ++++++++- .../command/parse/CommandValueArgument.kt | 5 ++- .../command/resolve/CommandCallResolver.kt | 20 +++++++++- .../command/resolve/ResolvedCommandCall.kt | 22 +++++++++++ ...Parser.kt => CommandCallParserProvider.kt} | 3 ++ ...sion.kt => CommandCallResolverProvider.kt} | 5 ++- .../extension/ComponentStorageInternal.kt | 3 ++ 8 files changed, 88 insertions(+), 25 deletions(-) rename backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/{CommandParser.kt => CommandCallParserProvider.kt} (95%) rename backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/{CommandResolverExtension.kt => CommandCallResolverProvider.kt} (87%) 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 index e386faea6..ef04564b1 100644 --- 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 @@ -11,34 +11,32 @@ package net.mamoe.mirai.console.command.parse +import net.mamoe.mirai.console.command.Command 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 +import net.mamoe.mirai.console.command.resolve.CommandCallResolver +import net.mamoe.mirai.console.util.ConsoleExperimentalApi +/** + * Unresolved [CommandCall]. + */ @ExperimentalCommandDescriptors public interface CommandCall { public val caller: CommandSender + /** + * One of callee [Command]'s [Command.allNames] + */ public val calleeName: String + + /** + * Explicit value arguments + */ 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) - } - } + /** + * Custom data for [CommandCallResolver] + */ + @ConsoleExperimentalApi + public val customData: Map } \ 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 index ed850f154..5be371aa8 100644 --- 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 @@ -2,20 +2,35 @@ 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.resolve.CommandCallResolver +import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall 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 + * Lexical and syntactical parser for transforming a [MessageChain] into [CommandCall] + * + * @see CommandCallResolver The call resolver for [CommandCall] to become [ResolvedCommandCall] + * @see CommandCallParserProvider The extension point */ @ConsoleExperimentalApi @ExperimentalCommandDescriptors public interface CommandCallParser { + + /** + * Lexically and syntactically parse a [message] into [CommandCall], but performs nothing about resolving a call. + * + * @return `null` if unable to parse (i.e. due to syntax errors). + */ public fun parse(sender: CommandSender, message: MessageChain): CommandCall? public companion object { + /** + * Calls [CommandCallParser]s provided by [CommandCallParserProvider] in [GlobalComponentStorage] sequentially, + * returning the first non-null result, `null` otherwise. + */ @JvmStatic public fun MessageChain.parseCommandCall(sender: CommandSender): CommandCall? { GlobalComponentStorage.run { 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 index 99c1e5e20..0524ce1ea 100644 --- 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 @@ -24,7 +24,10 @@ import kotlin.reflect.typeOf public typealias RawCommandArgument = MessageContent @ExperimentalCommandDescriptors -public interface CommandValueArgument { +public interface CommandArgument + +@ExperimentalCommandDescriptors +public interface CommandValueArgument : CommandArgument { public val value: RawCommandArgument public val typeVariants: List> } 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 index 2f66ff76b..e8767cc8e 100644 --- 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 @@ -11,8 +11,26 @@ package net.mamoe.mirai.console.command.resolve import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.parse.CommandCall +import net.mamoe.mirai.console.extensions.CommandCallResolverProvider +import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage +/** + * The resolver converting a [CommandCall] into [ResolvedCommandCall] based on registered [] + */ +@ExperimentalCommandDescriptors public interface CommandCallResolver { - @ExperimentalCommandDescriptors public fun resolve(call: CommandCall): ResolvedCommandCall? + + public companion object { + @JvmStatic + @JvmName("resolveCall") + public fun CommandCall.resolve(): ResolvedCommandCall? { + GlobalComponentStorage.run { + CommandCallResolverProvider.useExtensions { provider -> + provider.instance.resolve(this@resolve)?.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/resolve/ResolvedCommandCall.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/ResolvedCommandCall.kt index 464c0dcea..476ef273d 100644 --- 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 @@ -11,15 +11,37 @@ 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.CompositeCommand import net.mamoe.mirai.console.command.descriptor.CommandDescriptor +import net.mamoe.mirai.console.command.descriptor.CommandSignatureVariant import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors +import net.mamoe.mirai.console.command.parse.CommandCall import net.mamoe.mirai.console.command.parse.CommandValueArgument +/** + * The resolved [CommandCall]. + */ @ExperimentalCommandDescriptors public interface ResolvedCommandCall { public val caller: CommandSender + /** + * The callee [Command] + */ public val callee: Command + + /** + * The callee [CommandDescriptor], specifically a sub command from [CompositeCommand] + */ public val calleeDescriptor: CommandDescriptor + + /** + * The callee [CommandSignatureVariant] + */ + public val calleeSignature: CommandSignatureVariant + + /** + * Resolved value arguments arranged mapping the [CommandSignatureVariant.valueParameters] by index. + */ 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/CommandCallParserProvider.kt similarity index 95% rename from backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/CommandParser.kt rename to backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/CommandCallParserProvider.kt index 532f2e9c0..d72aaa27c 100644 --- 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/CommandCallParserProvider.kt @@ -14,6 +14,9 @@ import net.mamoe.mirai.console.command.parse.CommandCallParser import net.mamoe.mirai.console.extension.AbstractExtensionPoint import net.mamoe.mirai.console.extension.InstanceExtension +/** + * The resolver for a [CommandCall] + */ @ExperimentalCommandDescriptors public interface CommandCallParserProvider : InstanceExtension { public companion object ExtensionPoint : AbstractExtensionPoint(CommandCallParserProvider::class) 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/CommandCallResolverProvider.kt similarity index 87% rename from backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/CommandResolverExtension.kt rename to backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/CommandCallResolverProvider.kt index a7551dc06..85fcfb415 100644 --- 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/CommandCallResolverProvider.kt @@ -9,11 +9,12 @@ package net.mamoe.mirai.console.extensions +import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.resolve.CommandCallResolver import net.mamoe.mirai.console.extension.AbstractExtensionPoint import net.mamoe.mirai.console.extension.InstanceExtension +@ExperimentalCommandDescriptors public interface CommandCallResolverProvider : InstanceExtension { public companion object ExtensionPoint : AbstractExtensionPoint(CommandCallResolverProvider::class) -} - +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/extension/ComponentStorageInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/extension/ComponentStorageInternal.kt index 6527aadb3..d84fa4022 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/extension/ComponentStorageInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/extension/ComponentStorageInternal.kt @@ -20,6 +20,9 @@ import java.util.concurrent.CopyOnWriteArraySet import kotlin.contracts.contract import kotlin.reflect.KClass +/** + * The [ComponentStorage] containing all components provided by Mirai Console internals and installed plugins. + */ internal object GlobalComponentStorage : AbstractConcurrentComponentStorage() internal interface ExtensionRegistry { val plugin: Plugin From 86c3b18bca7005c09514d93c8319a9d73fdd461c Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 9 Oct 2020 12:35:16 +0800 Subject: [PATCH 09/28] CommandValueArgumentParser --- .../mirai/console/command/BuiltInCommands.kt | 4 +- .../mirai/console/command/CompositeCommand.kt | 2 +- .../mirai/console/command/SimpleCommand.kt | 2 +- .../descriptor/CommandArgumentContext.kt | 84 +++++++++---------- .../CommandArgumentParserBuiltins.kt | 50 +++++------ .../CommandArgumentParserException.kt | 4 +- .../command/descriptor/CommandDescriptor.kt | 2 +- ...arser.kt => CommandValueArgumentParser.kt} | 46 +++++----- .../console/command/descriptor/TypeVariant.kt | 7 +- .../command/parse/CommandValueArgument.kt | 13 ++- .../command/CompositeCommand.CommandParam.kt | 14 ++-- .../mirai/console/command/TestCommand.kt | 4 +- 12 files changed, 122 insertions(+), 110 deletions(-) rename backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/{CommandArgumentParser.kt => CommandValueArgumentParser.kt} (74%) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt index d5c56ecfd..f37299e00 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt @@ -150,8 +150,8 @@ public object BuiltInCommands { ConsoleCommandOwner, "permission", "权限", "perm", description = "管理权限", overrideContext = buildCommandArgumentContext { - PermitteeId::class with PermitteeIdArgumentParser - Permission::class with PermissionIdArgumentParser.map { id -> + PermitteeId::class with PermitteeIdValueArgumentParser + Permission::class with PermissionIdValueArgumentParser.map { id -> kotlin.runCatching { id.findCorrespondingPermissionOrFail() }.getOrElse { illegalArgument("指令不存在: $id", it) } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt index 3dcb105b7..77126b96b 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt @@ -99,7 +99,7 @@ public abstract class CompositeCommand( public override val usage: String get() = super.usage /** - * [CommandArgumentParser] 的环境 + * [CommandValueArgumentParser] 的环境 */ public final override val context: CommandArgumentContext = CommandArgumentContext.Builtins + overrideContext diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt index 1f4fef692..7a4a0e985 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt @@ -31,7 +31,7 @@ import net.mamoe.mirai.message.data.MessageChain * 简单的, 支持参数自动解析的指令. * * 要查看指令解析流程, 参考 [CommandManager.executeCommand] - * 要查看参数解析方式, 参考 [CommandArgumentParser] + * 要查看参数解析方式, 参考 [CommandValueArgumentParser] * * Kotlin 实现: * ``` diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentContext.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentContext.kt index a169f30b1..acc3d3d25 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentContext.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentContext.kt @@ -29,7 +29,7 @@ import kotlin.reflect.full.isSubclassOf /** - * 指令参数环境, 即 [CommandArgumentParser] 的集合, 用于 [CompositeCommand] 和 [SimpleCommand]. + * 指令参数环境, 即 [CommandValueArgumentParser] 的集合, 用于 [CompositeCommand] 和 [SimpleCommand]. * * 在指令解析时, 总是从 [CommandArgumentContextAware.context] 搜索相关解析器 * @@ -38,20 +38,20 @@ import kotlin.reflect.full.isSubclassOf * @see SimpleCommandArgumentContext 简单实现 * @see EmptyCommandArgumentContext 空实现, 类似 [emptyList] * - * @see CommandArgumentContext.Builtins 内建 [CommandArgumentParser] + * @see CommandArgumentContext.Builtins 内建 [CommandValueArgumentParser] * * @see buildCommandArgumentContext DSL 构造 */ public interface CommandArgumentContext { /** - * [KClass] 到 [CommandArgumentParser] 的匹配 + * [KClass] 到 [CommandValueArgumentParser] 的匹配 */ public data class ParserPair( val klass: KClass, - val parser: CommandArgumentParser, + val parser: CommandValueArgumentParser, ) - public operator fun get(klass: KClass): CommandArgumentParser? + public operator fun get(klass: KClass): CommandValueArgumentParser? public fun toList(): List> @@ -66,32 +66,32 @@ public interface CommandArgumentContext { } /** - * 内建的默认 [CommandArgumentParser] + * 内建的默认 [CommandValueArgumentParser] */ public object Builtins : CommandArgumentContext by (buildCommandArgumentContext { - Int::class with IntArgumentParser - Byte::class with ByteArgumentParser - Short::class with ShortArgumentParser - Boolean::class with BooleanArgumentParser - String::class with StringArgumentParser - Long::class with LongArgumentParser - Double::class with DoubleArgumentParser - Float::class with FloatArgumentParser + Int::class with IntValueArgumentParser + Byte::class with ByteValueArgumentParser + Short::class with ShortValueArgumentParser + Boolean::class with BooleanValueArgumentParser + String::class with StringValueArgumentParser + Long::class with LongValueArgumentParser + Double::class with DoubleValueArgumentParser + Float::class with FloatValueArgumentParser - Image::class with ImageArgumentParser - PlainText::class with PlainTextArgumentParser + Image::class with ImageValueArgumentParser + PlainText::class with PlainTextValueArgumentParser - Contact::class with ExistingContactArgumentParser - User::class with ExistingUserArgumentParser - Member::class with ExistingMemberArgumentParser - Group::class with ExistingGroupArgumentParser - Friend::class with ExistingFriendArgumentParser - Bot::class with ExistingBotArgumentParser + Contact::class with ExistingContactValueArgumentParser + User::class with ExistingUserValueArgumentParser + Member::class with ExistingMemberValueArgumentParser + Group::class with ExistingGroupValueArgumentParser + Friend::class with ExistingFriendValueArgumentParser + Bot::class with ExistingBotValueArgumentParser - PermissionId::class with PermissionIdArgumentParser - PermitteeId::class with PermitteeIdArgumentParser + PermissionId::class with PermissionIdValueArgumentParser + PermitteeId::class with PermitteeIdValueArgumentParser - MessageContent::class with RawContentArgumentParser + MessageContent::class with RawContentValueArgumentParser }) } @@ -103,7 +103,7 @@ public interface CommandArgumentContext { */ public interface CommandArgumentContextAware { /** - * [CommandArgumentParser] 的集合 + * [CommandValueArgumentParser] 的集合 */ public val context: CommandArgumentContext } @@ -117,7 +117,7 @@ public operator fun CommandArgumentContext.plus(replacer: CommandArgumentContext if (replacer == EmptyCommandArgumentContext) return this if (this == EmptyCommandArgumentContext) return replacer return object : CommandArgumentContext { - override fun get(klass: KClass): CommandArgumentParser? = + override fun get(klass: KClass): CommandValueArgumentParser? = replacer[klass] ?: this@plus[klass] override fun toList(): List> = replacer.toList() + this@plus.toList() @@ -132,8 +132,8 @@ public operator fun CommandArgumentContext.plus(replacer: List>): if (this == EmptyCommandArgumentContext) return SimpleCommandArgumentContext(replacer) return object : CommandArgumentContext { @Suppress("UNCHECKED_CAST") - override fun get(klass: KClass): CommandArgumentParser? = - replacer.firstOrNull { klass.isSubclassOf(it.klass) }?.parser as CommandArgumentParser? + override fun get(klass: KClass): CommandValueArgumentParser? = + replacer.firstOrNull { klass.isSubclassOf(it.klass) }?.parser as CommandValueArgumentParser? ?: this@plus[klass] override fun toList(): List> = replacer.toList() + this@plus.toList() @@ -149,9 +149,9 @@ public operator fun CommandArgumentContext.plus(replacer: List>): public class SimpleCommandArgumentContext( public val list: List>, ) : CommandArgumentContext { - override fun get(klass: KClass): CommandArgumentParser? = + override fun get(klass: KClass): CommandValueArgumentParser? = (this.list.firstOrNull { klass == it.klass }?.parser - ?: this.list.firstOrNull { klass.isSubclassOf(it.klass) }?.parser) as CommandArgumentParser? + ?: this.list.firstOrNull { klass.isSubclassOf(it.klass) }?.parser) as CommandValueArgumentParser? override fun toList(): List> = list } @@ -203,14 +203,14 @@ public class CommandArgumentContextBuilder : MutableList> by mutab * 添加一个指令解析器. */ @JvmName("add") - public infix fun Class.with(parser: CommandArgumentParser): CommandArgumentContextBuilder = + public infix fun Class.with(parser: CommandValueArgumentParser): CommandArgumentContextBuilder = this.kotlin with parser /** * 添加一个指令解析器 */ @JvmName("add") - public inline infix fun KClass.with(parser: CommandArgumentParser): CommandArgumentContextBuilder { + public inline infix fun KClass.with(parser: CommandValueArgumentParser): CommandArgumentContextBuilder { add(ParserPair(this, parser)) return this@CommandArgumentContextBuilder } @@ -221,9 +221,9 @@ public class CommandArgumentContextBuilder : MutableList> by mutab @JvmSynthetic @LowPriorityInOverloadResolution public inline infix fun KClass.with( - crossinline parser: CommandArgumentParser.(s: String, sender: CommandSender) -> T, + crossinline parser: CommandValueArgumentParser.(s: String, sender: CommandSender) -> T, ): CommandArgumentContextBuilder { - add(ParserPair(this, object : CommandArgumentParser { + add(ParserPair(this, object : CommandValueArgumentParser { override fun parse(raw: String, sender: CommandSender): T = parser(raw, sender) })) return this@CommandArgumentContextBuilder @@ -234,16 +234,16 @@ public class CommandArgumentContextBuilder : MutableList> by mutab */ @JvmSynthetic public inline infix fun KClass.with( - crossinline parser: CommandArgumentParser.(s: String) -> T, + crossinline parser: CommandValueArgumentParser.(s: String) -> T, ): CommandArgumentContextBuilder { - add(ParserPair(this, object : CommandArgumentParser { + add(ParserPair(this, object : CommandValueArgumentParser { override fun parse(raw: String, sender: CommandSender): T = parser(raw) })) return this@CommandArgumentContextBuilder } @JvmSynthetic - public inline fun add(parser: CommandArgumentParser): CommandArgumentContextBuilder { + public inline fun add(parser: CommandValueArgumentParser): CommandArgumentContextBuilder { add(ParserPair(T::class, parser)) return this@CommandArgumentContextBuilder } @@ -254,8 +254,8 @@ public class CommandArgumentContextBuilder : MutableList> by mutab @ConsoleExperimentalApi @JvmSynthetic public inline infix fun add( - crossinline parser: CommandArgumentParser<*>.(s: String) -> T, - ): CommandArgumentContextBuilder = T::class with object : CommandArgumentParser { + crossinline parser: CommandValueArgumentParser<*>.(s: String) -> T, + ): CommandArgumentContextBuilder = T::class with object : CommandValueArgumentParser { override fun parse(raw: String, sender: CommandSender): T = parser(raw) } @@ -266,8 +266,8 @@ public class CommandArgumentContextBuilder : MutableList> by mutab @JvmSynthetic @LowPriorityInOverloadResolution public inline infix fun add( - crossinline parser: CommandArgumentParser<*>.(s: String, sender: CommandSender) -> T, - ): CommandArgumentContextBuilder = T::class with object : CommandArgumentParser { + crossinline parser: CommandValueArgumentParser<*>.(s: String, sender: CommandSender) -> T, + ): CommandArgumentContextBuilder = T::class with object : CommandValueArgumentParser { override fun parse(raw: String, sender: CommandSender): T = parser(raw, sender) } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentParserBuiltins.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentParserBuiltins.kt index 6eec15173..a1fc279e3 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentParserBuiltins.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentParserBuiltins.kt @@ -25,7 +25,7 @@ import net.mamoe.mirai.message.data.* /** * 使用 [String.toInt] 解析 */ -public object IntArgumentParser : InternalCommandArgumentParserExtensions { +public object IntValueArgumentParser : InternalCommandValueArgumentParserExtensions { public override fun parse(raw: String, sender: CommandSender): Int = raw.toIntOrNull() ?: illegalArgument("无法解析 $raw 为整数") } @@ -33,7 +33,7 @@ public object IntArgumentParser : InternalCommandArgumentParserExtensions { /** * 使用 [String.toLong] 解析 */ -public object LongArgumentParser : InternalCommandArgumentParserExtensions { +public object LongValueArgumentParser : InternalCommandValueArgumentParserExtensions { public override fun parse(raw: String, sender: CommandSender): Long = raw.toLongOrNull() ?: illegalArgument("无法解析 $raw 为长整数") } @@ -41,7 +41,7 @@ public object LongArgumentParser : InternalCommandArgumentParserExtensions /** * 使用 [String.toShort] 解析 */ -public object ShortArgumentParser : InternalCommandArgumentParserExtensions { +public object ShortValueArgumentParser : InternalCommandValueArgumentParserExtensions { public override fun parse(raw: String, sender: CommandSender): Short = raw.toShortOrNull() ?: illegalArgument("无法解析 $raw 为短整数") } @@ -49,7 +49,7 @@ public object ShortArgumentParser : InternalCommandArgumentParserExtensions { +public object ByteValueArgumentParser : InternalCommandValueArgumentParserExtensions { public override fun parse(raw: String, sender: CommandSender): Byte = raw.toByteOrNull() ?: illegalArgument("无法解析 $raw 为字节") } @@ -57,7 +57,7 @@ public object ByteArgumentParser : InternalCommandArgumentParserExtensions /** * 使用 [String.toDouble] 解析 */ -public object DoubleArgumentParser : InternalCommandArgumentParserExtensions { +public object DoubleValueArgumentParser : InternalCommandValueArgumentParserExtensions { public override fun parse(raw: String, sender: CommandSender): Double = raw.toDoubleOrNull() ?: illegalArgument("无法解析 $raw 为小数") } @@ -65,7 +65,7 @@ public object DoubleArgumentParser : InternalCommandArgumentParserExtensions { +public object FloatValueArgumentParser : InternalCommandValueArgumentParserExtensions { public override fun parse(raw: String, sender: CommandSender): Float = raw.toFloatOrNull() ?: illegalArgument("无法解析 $raw 为小数") } @@ -73,14 +73,14 @@ public object FloatArgumentParser : InternalCommandArgumentParserExtensions { +public object StringValueArgumentParser : InternalCommandValueArgumentParserExtensions { public override fun parse(raw: String, sender: CommandSender): String = raw } /** * 解析 [String] 通过 [Image]. */ -public object ImageArgumentParser : InternalCommandArgumentParserExtensions { +public object ImageValueArgumentParser : InternalCommandValueArgumentParserExtensions { public override fun parse(raw: String, sender: CommandSender): Image { return kotlin.runCatching { Image(raw) @@ -95,7 +95,7 @@ public object ImageArgumentParser : InternalCommandArgumentParserExtensions { +public object PlainTextValueArgumentParser : InternalCommandValueArgumentParserExtensions { public override fun parse(raw: String, sender: CommandSender): PlainText { return PlainText(raw) } @@ -109,7 +109,7 @@ public object PlainTextArgumentParser : InternalCommandArgumentParserExtensions< /** * 当字符串内容为(不区分大小写) "true", "yes", "enabled" */ -public object BooleanArgumentParser : InternalCommandArgumentParserExtensions<Boolean> { +public object BooleanValueArgumentParser : InternalCommandValueArgumentParserExtensions<Boolean> { public override fun parse(raw: String, sender: CommandSender): Boolean = raw.trim().let { str -> str.equals("true", ignoreCase = true) || str.equals("yes", ignoreCase = true) @@ -121,7 +121,7 @@ public object BooleanArgumentParser : InternalCommandArgumentParserExtensions<Bo /** * 根据 [Bot.id] 解析一个登录后的 [Bot] */ -public object ExistingBotArgumentParser : InternalCommandArgumentParserExtensions<Bot> { +public object ExistingBotValueArgumentParser : InternalCommandValueArgumentParserExtensions<Bot> { public override fun parse(raw: String, sender: CommandSender): Bot = if (raw == "~") sender.inferBotOrFail() else raw.findBotOrFail() @@ -136,7 +136,7 @@ public object ExistingBotArgumentParser : InternalCommandArgumentParserExtension /** * 解析任意一个存在的好友. */ -public object ExistingFriendArgumentParser : InternalCommandArgumentParserExtensions<Friend> { +public object ExistingFriendValueArgumentParser : InternalCommandValueArgumentParserExtensions<Friend> { private val syntax = """ - `botId.friendId` - `botId.friendNick` (模糊搜索, 寻找最优匹配) @@ -175,7 +175,7 @@ public object ExistingFriendArgumentParser : InternalCommandArgumentParserExtens /** * 解析任意一个存在的群. */ -public object ExistingGroupArgumentParser : InternalCommandArgumentParserExtensions<Group> { +public object ExistingGroupValueArgumentParser : InternalCommandValueArgumentParserExtensions<Group> { private val syntax = """ - `botId.groupId` - `~` (指代指令调用人自己所在群. 仅群聊天环境下) @@ -202,7 +202,7 @@ public object ExistingGroupArgumentParser : InternalCommandArgumentParserExtensi } } -public object ExistingUserArgumentParser : InternalCommandArgumentParserExtensions<User> { +public object ExistingUserValueArgumentParser : InternalCommandValueArgumentParserExtensions<User> { private val syntax: String = """ - `botId.groupId.memberId` - `botId.groupId.memberCard` (模糊搜索, 寻找最优匹配) @@ -215,11 +215,11 @@ public object ExistingUserArgumentParser : InternalCommandArgumentParserExtensio """.trimIndent() override fun parse(raw: String, sender: CommandSender): User { - return parseImpl(sender, raw, ExistingMemberArgumentParser::parse, ExistingFriendArgumentParser::parse) + return parseImpl(sender, raw, ExistingMemberValueArgumentParser::parse, ExistingFriendValueArgumentParser::parse) } override fun parse(raw: MessageContent, sender: CommandSender): User { - return parseImpl(sender, raw, ExistingMemberArgumentParser::parse, ExistingFriendArgumentParser::parse) + return parseImpl(sender, raw, ExistingMemberValueArgumentParser::parse, ExistingFriendValueArgumentParser::parse) } private fun <T> parseImpl( @@ -246,7 +246,7 @@ public object ExistingUserArgumentParser : InternalCommandArgumentParserExtensio } -public object ExistingContactArgumentParser : InternalCommandArgumentParserExtensions<Contact> { +public object ExistingContactValueArgumentParser : InternalCommandValueArgumentParserExtensions<Contact> { private val syntax: String = """ - `botId.groupId.memberId` - `botId.groupId.memberCard` (模糊搜索, 寻找最优匹配) @@ -259,11 +259,11 @@ public object ExistingContactArgumentParser : InternalCommandArgumentParserExten """.trimIndent() override fun parse(raw: String, sender: CommandSender): Contact { - return parseImpl(sender, raw, ExistingUserArgumentParser::parse, ExistingGroupArgumentParser::parse) + return parseImpl(sender, raw, ExistingUserValueArgumentParser::parse, ExistingGroupValueArgumentParser::parse) } override fun parse(raw: MessageContent, sender: CommandSender): Contact { - return parseImpl(sender, raw, ExistingUserArgumentParser::parse, ExistingGroupArgumentParser::parse) + return parseImpl(sender, raw, ExistingUserValueArgumentParser::parse, ExistingGroupValueArgumentParser::parse) } private fun <T> parseImpl( @@ -286,7 +286,7 @@ public object ExistingContactArgumentParser : InternalCommandArgumentParserExten /** * 解析任意一个群成员. */ -public object ExistingMemberArgumentParser : InternalCommandArgumentParserExtensions<Member> { +public object ExistingMemberValueArgumentParser : InternalCommandValueArgumentParserExtensions<Member> { private val syntax: String = """ - `botId.groupId.memberId` - `botId.groupId.memberCard` (模糊搜索, 寻找最优匹配) @@ -333,7 +333,7 @@ public object ExistingMemberArgumentParser : InternalCommandArgumentParserExtens } } -public object PermissionIdArgumentParser : CommandArgumentParser<PermissionId> { +public object PermissionIdValueArgumentParser : CommandValueArgumentParser<PermissionId> { override fun parse(raw: String, sender: CommandSender): PermissionId { return kotlin.runCatching { PermissionId.parseFromString(raw) }.getOrElse { illegalArgument("无法解析 $raw 为 PermissionId") @@ -341,7 +341,7 @@ public object PermissionIdArgumentParser : CommandArgumentParser<PermissionId> { } } -public object PermitteeIdArgumentParser : CommandArgumentParser<PermitteeId> { +public object PermitteeIdValueArgumentParser : CommandValueArgumentParser<PermitteeId> { override fun parse(raw: String, sender: CommandSender): PermitteeId { return if (raw == "~") sender.permitteeId else kotlin.runCatching { AbstractPermitteeId.parseFromString(raw) }.getOrElse { @@ -351,19 +351,19 @@ public object PermitteeIdArgumentParser : CommandArgumentParser<PermitteeId> { override fun parse(raw: MessageContent, sender: CommandSender): PermitteeId { if (raw is At) { - return ExistingUserArgumentParser.parse(raw, sender).asCommandSender(false).permitteeId + return ExistingUserValueArgumentParser.parse(raw, sender).asCommandSender(false).permitteeId } return super.parse(raw, sender) } } /** 直接返回原始参数 [MessageContent] */ -public object RawContentArgumentParser : CommandArgumentParser<MessageContent> { +public object RawContentValueArgumentParser : CommandValueArgumentParser<MessageContent> { override fun parse(raw: String, sender: CommandSender): MessageContent = PlainText(raw) override fun parse(raw: MessageContent, sender: CommandSender): MessageContent = raw } -internal interface InternalCommandArgumentParserExtensions<T : Any> : CommandArgumentParser<T> { +internal interface InternalCommandValueArgumentParserExtensions<T : Any> : CommandValueArgumentParser<T> { fun String.parseToLongOrFail(): Long = toLongOrNull() ?: illegalArgument("无法解析 $this 为整数") fun Long.findBotOrFail(): Bot = Bot.getInstanceOrNull(this) ?: illegalArgument("无法找到 Bot: $this") diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentParserException.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentParserException.kt index bed270637..faae4cf1e 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentParserException.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentParserException.kt @@ -16,8 +16,8 @@ package net.mamoe.mirai.console.command.descriptor * * [message] 将会发送给指令调用方. * - * @see CommandArgumentParser - * @see CommandArgumentParser.illegalArgument + * @see CommandValueArgumentParser + * @see CommandValueArgumentParser.illegalArgument */ public class CommandArgumentParserException : RuntimeException { public constructor() : super() 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 index 79f13cd71..1e0a9af2a 100644 --- 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 @@ -65,7 +65,7 @@ public sealed class CommandValueParameter<T> : ICommandParameter<T> { } /** - * Extended by [CommandArgumentParser] + * Extended by [CommandValueArgumentParser] */ public abstract class Extended<T> : CommandValueParameter<T>() } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentParser.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser.kt similarity index 74% rename from backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentParser.kt rename to backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser.kt index fb796109e..f87d0d66d 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentParser.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser.kt @@ -32,27 +32,27 @@ import kotlin.contracts.contract * ``` * suspend fun CommandSender.mute(target: Member, duration: Int) * ``` - * [CommandManager] 总是从 [SimpleCommand.context] 搜索一个 [T] 为 [Member] 的 [CommandArgumentParser], 并调用其 [CommandArgumentParser.parse] + * [CommandManager] 总是从 [SimpleCommand.context] 搜索一个 [T] 为 [Member] 的 [CommandValueArgumentParser], 并调用其 [CommandValueArgumentParser.parse] * * ### 内建指令解析器 - * - 基础类型: [ByteArgumentParser], [ShortArgumentParser], [IntArgumentParser], [LongArgumentParser] - * [FloatArgumentParser], [DoubleArgumentParser], - * [BooleanArgumentParser], [StringArgumentParser] + * - 基础类型: [ByteValueArgumentParser], [ShortValueArgumentParser], [IntValueArgumentParser], [LongValueArgumentParser] + * [FloatValueArgumentParser], [DoubleValueArgumentParser], + * [BooleanValueArgumentParser], [StringValueArgumentParser] * - * - [Bot]: [ExistingBotArgumentParser] - * - [Friend]: [ExistingFriendArgumentParser] - * - [Group]: [ExistingGroupArgumentParser] - * - [Member]: [ExistingMemberArgumentParser] - * - [User]: [ExistingUserArgumentParser] - * - [Contact]: [ExistingContactArgumentParser] + * - [Bot]: [ExistingBotValueArgumentParser] + * - [Friend]: [ExistingFriendValueArgumentParser] + * - [Group]: [ExistingGroupValueArgumentParser] + * - [Member]: [ExistingMemberValueArgumentParser] + * - [User]: [ExistingUserValueArgumentParser] + * - [Contact]: [ExistingContactValueArgumentParser] * * * @see SimpleCommand 简单指令 * @see CompositeCommand 复合指令 * - * @see buildCommandArgumentContext 指令参数环境, 即 [CommandArgumentParser] 的集合 + * @see buildCommandArgumentContext 指令参数环境, 即 [CommandValueArgumentParser] 的集合 */ -public interface CommandArgumentParser<out T : Any> { +public interface CommandValueArgumentParser<out T : Any> { /** * 解析一个字符串为 [T] 类型参数 * @@ -84,14 +84,14 @@ public interface CommandArgumentParser<out T : Any> { /** * 使用原 [this] 解析, 成功后使用 [mapper] 映射为另一个类型. */ -public fun <T : Any, R : Any> CommandArgumentParser<T>.map( - mapper: CommandArgumentParser<R>.(T) -> R -): CommandArgumentParser<R> = MappingCommandArgumentParser(this, mapper) +public fun <T : Any, R : Any> CommandValueArgumentParser<T>.map( + mapper: CommandValueArgumentParser<R>.(T) -> R, +): CommandValueArgumentParser<R> = MappingCommandValueArgumentParser(this, mapper) -private class MappingCommandArgumentParser<T : Any, R : Any>( - private val original: CommandArgumentParser<T>, - private val mapper: CommandArgumentParser<R>.(T) -> R -) : CommandArgumentParser<R> { +private class MappingCommandValueArgumentParser<T : Any, R : Any>( + private val original: CommandValueArgumentParser<T>, + private val mapper: CommandValueArgumentParser<R>.(T) -> R, +) : CommandValueArgumentParser<R> { override fun parse(raw: String, sender: CommandSender): R = mapper(original.parse(raw, sender)) override fun parse(raw: MessageContent, sender: CommandSender): R = mapper(original.parse(raw, sender)) } @@ -103,7 +103,7 @@ private class MappingCommandArgumentParser<T : Any, R : Any>( */ @JvmSynthetic @Throws(IllegalArgumentException::class) -public fun <T : Any> CommandArgumentParser<T>.parse(raw: Any, sender: CommandSender): T { +public fun <T : Any> CommandValueArgumentParser<T>.parse(raw: Any, sender: CommandSender): T { contract { returns() implies (raw is String || raw is SingleMessage) } @@ -123,7 +123,7 @@ public fun <T : Any> CommandArgumentParser<T>.parse(raw: Any, sender: CommandSen @Suppress("unused") @JvmSynthetic @Throws(CommandArgumentParserException::class) -public inline fun CommandArgumentParser<*>.illegalArgument(message: String, cause: Throwable? = null): Nothing { +public inline fun CommandValueArgumentParser<*>.illegalArgument(message: String, cause: Throwable? = null): Nothing { throw CommandArgumentParserException(message, cause) } @@ -134,9 +134,9 @@ public inline fun CommandArgumentParser<*>.illegalArgument(message: String, caus */ @Throws(CommandArgumentParserException::class) @JvmSynthetic -public inline fun CommandArgumentParser<*>.checkArgument( +public inline fun CommandValueArgumentParser<*>.checkArgument( condition: Boolean, - crossinline message: () -> String = { "Check failed." } + crossinline message: () -> String = { "Check failed." }, ) { contract { returns() implies condition 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 index b4cadf0ce..fd234b803 100644 --- 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 @@ -16,6 +16,9 @@ import kotlin.reflect.typeOf @ExperimentalCommandDescriptors public interface TypeVariant<out OutType> { + /** + * The reified type of [OutType] + */ public val outType: KType public fun mapValue(valueParameter: MessageContent): OutType @@ -33,8 +36,8 @@ public interface TypeVariant<out OutType> { } @ExperimentalCommandDescriptors -public object StringTypeVariant : TypeVariant<RawCommandArgument> { +public object MessageContentTypeVariant : TypeVariant<MessageContent> { @OptIn(ExperimentalStdlibApi::class) override val outType: KType = typeOf<String>() - override fun mapValue(valueParameter: RawCommandArgument): RawCommandArgument = valueParameter + override fun mapValue(valueParameter: MessageContent): MessageContent = valueParameter } 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 index 0524ce1ea..3c473e21a 100644 --- 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 @@ -10,8 +10,8 @@ package net.mamoe.mirai.console.command.parse import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors +import net.mamoe.mirai.console.command.descriptor.MessageContentTypeVariant 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 net.mamoe.mirai.message.data.MessageContent import kotlin.reflect.full.isSubtypeOf @@ -23,20 +23,29 @@ import kotlin.reflect.typeOf */ public typealias RawCommandArgument = MessageContent +/** + * @see CommandValueArgument + */ @ExperimentalCommandDescriptors public interface CommandArgument +/** + * @see InvariantCommandValueArgument + */ @ExperimentalCommandDescriptors public interface CommandValueArgument : CommandArgument { public val value: RawCommandArgument public val typeVariants: List<TypeVariant<*>> } +/** + * The [CommandValueArgument] that doesn't vary in type (remaining [MessageContent]). + */ @ExperimentalCommandDescriptors public data class InvariantCommandValueArgument( public override val value: RawCommandArgument, ) : CommandValueArgument { - override val typeVariants: List<TypeVariant<*>> = listOf(StringTypeVariant) + override val typeVariants: List<TypeVariant<*>> = listOf(MessageContentTypeVariant) } @ExperimentalCommandDescriptors diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommand.CommandParam.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommand.CommandParam.kt index 72a34842c..16bae5667 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommand.CommandParam.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommand.CommandParam.kt @@ -12,7 +12,7 @@ package net.mamoe.mirai.console.internal.command import net.mamoe.mirai.console.command.CompositeCommand -import net.mamoe.mirai.console.command.descriptor.CommandArgumentParser +import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser import kotlin.reflect.KClass import kotlin.reflect.KParameter @@ -37,12 +37,12 @@ internal data class CommandParameter<T : Any>( */ val name: String, /** - * 参数类型. 将从 [CompositeCommand.context] 中寻找 [CommandArgumentParser] 解析. + * 参数类型. 将从 [CompositeCommand.context] 中寻找 [CommandValueArgumentParser] 解析. */ val type: KClass<T>, // exact type val parameter: KParameter, // source parameter ) { - constructor(name: String, type: KClass<T>, parameter: KParameter, parser: CommandArgumentParser<T>) : this( + constructor(name: String, type: KClass<T>, parameter: KParameter, parser: CommandValueArgumentParser<T>) : this( name, type, parameter ) { this._overrideParser = parser @@ -50,14 +50,14 @@ internal data class CommandParameter<T : Any>( @Suppress("PropertyName") @JvmField - internal var _overrideParser: CommandArgumentParser<T>? = null + internal var _overrideParser: CommandValueArgumentParser<T>? = null /** - * 覆盖的 [CommandArgumentParser]. + * 覆盖的 [CommandValueArgumentParser]. * - * 如果非 `null`, 将不会从 [CommandArgumentContext] 寻找 [CommandArgumentParser] + * 如果非 `null`, 将不会从 [CommandArgumentContext] 寻找 [CommandValueArgumentParser] */ - val overrideParser: CommandArgumentParser<T>? get() = _overrideParser + val overrideParser: CommandValueArgumentParser<T>? get() = _overrideParser } diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt index 99a4349db..b7937225a 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt @@ -22,7 +22,7 @@ import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register import net.mamoe.mirai.console.command.CommandManager.INSTANCE.registeredCommands import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregister import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregisterAllCommands -import net.mamoe.mirai.console.command.descriptor.CommandArgumentParser +import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser import net.mamoe.mirai.console.command.descriptor.buildCommandArgumentContext import net.mamoe.mirai.console.initTestEnvironment import net.mamoe.mirai.console.internal.command.CommandManagerImpl @@ -191,7 +191,7 @@ internal class TestCommand { ConsoleCommandOwner, "test22", overrideContext = buildCommandArgumentContext { - add(object : CommandArgumentParser<MyClass> { + add(object : CommandValueArgumentParser<MyClass> { override fun parse(raw: String, sender: CommandSender): MyClass { return MyClass(raw.toInt()) } From ccc9128023e57c5c0b7344031c7e6917e1445494 Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Fri, 9 Oct 2020 12:52:47 +0800 Subject: [PATCH 10/28] CommandParameter optionality and UserDefinedType --- .../command/descriptor/CommandDescriptor.kt | 44 ++++++++++++++----- .../console/command/parse/CommandCall.kt | 15 +++---- .../extensions/CommandCallParserProvider.kt | 2 +- 3 files changed, 42 insertions(+), 19 deletions(-) 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 index 1e0a9af2a..690694d4e 100644 --- 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 @@ -31,16 +31,18 @@ public interface CommandSignatureVariant { @ExperimentalCommandDescriptors public interface ICommandParameter<T : Any?> { public val name: String - public val type: KType + + /** + * If [isOptional] is `false`, [defaultValue] is always `null`. + * Otherwise [defaultValue] may be `null` iff [T] is nullable. + */ public val defaultValue: T? + public val isOptional: Boolean - public companion object { - @get:JvmStatic - @ExperimentalCommandDescriptors - public val ICommandParameter<*>.isOptional: Boolean - get() = this.defaultValue != null - - } + /** + * Reified type of [T] + */ + public val type: KType } @ExperimentalCommandDescriptors @@ -54,9 +56,10 @@ public sealed class CommandValueParameter<T> : ICommandParameter<T> { public class StringConstant( public override val name: String, - public override val defaultValue: String, ) : CommandValueParameter<String>() { - override val type: KType get() = STRING_TYPE + public override val type: KType get() = STRING_TYPE + public override val defaultValue: Nothing? get() = null + public override val isOptional: Boolean get() = false private companion object { @OptIn(ExperimentalStdlibApi::class) @@ -64,6 +67,27 @@ public sealed class CommandValueParameter<T> : ICommandParameter<T> { } } + public class UserDefinedType<T>( + public override val name: String, + public override val defaultValue: T?, + public override val isOptional: Boolean, + public override val type: KType, + ) : CommandValueParameter<T>() { + public companion object { + @JvmStatic + public inline fun <reified T : Any> createOptional(name: String, defaultValue: T): UserDefinedType<T> { + @OptIn(ExperimentalStdlibApi::class) + return UserDefinedType(name, defaultValue, true, typeOf<T>()) + } + + @JvmStatic + public inline fun <reified T : Any> createRequired(name: String): UserDefinedType<T> { + @OptIn(ExperimentalStdlibApi::class) + return UserDefinedType(name, null, false, typeOf<T>()) + } + } + } + /** * Extended by [CommandValueArgumentParser] */ 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 index ef04564b1..4bb43bb9e 100644 --- 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 @@ -14,8 +14,6 @@ package net.mamoe.mirai.console.command.parse import net.mamoe.mirai.console.command.Command import net.mamoe.mirai.console.command.CommandSender import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors -import net.mamoe.mirai.console.command.resolve.CommandCallResolver -import net.mamoe.mirai.console.util.ConsoleExperimentalApi /** * Unresolved [CommandCall]. @@ -33,10 +31,11 @@ public interface CommandCall { * Explicit value arguments */ public val valueArguments: List<CommandValueArgument> +} - /** - * Custom data for [CommandCallResolver] - */ - @ConsoleExperimentalApi - public val customData: Map<Any, Any> -} \ No newline at end of file +@ExperimentalCommandDescriptors +public class CommandCallImpl( + override val caller: CommandSender, + override val calleeName: String, + override val valueArguments: List<CommandValueArgument>, +) : CommandCall \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/CommandCallParserProvider.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/CommandCallParserProvider.kt index d72aaa27c..a3870a932 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/CommandCallParserProvider.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/CommandCallParserProvider.kt @@ -15,7 +15,7 @@ import net.mamoe.mirai.console.extension.AbstractExtensionPoint import net.mamoe.mirai.console.extension.InstanceExtension /** - * The resolver for a [CommandCall] + * The provider of [CommandCallParser] */ @ExperimentalCommandDescriptors public interface CommandCallParserProvider : InstanceExtension<CommandCallParser> { From 82de40414949f295dc802e84426c9a8a00b6e957 Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Thu, 15 Oct 2020 13:04:30 +0800 Subject: [PATCH 11/28] Add doc for TypeVariant --- .../mamoe/mirai/console/command/descriptor/TypeVariant.kt | 7 +++++++ 1 file changed, 7 insertions(+) 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 index fd234b803..81a47fc65 100644 --- 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 @@ -9,11 +9,18 @@ package net.mamoe.mirai.console.command.descriptor +import net.mamoe.mirai.console.command.parse.CommandCall +import net.mamoe.mirai.console.command.parse.CommandCallParser import net.mamoe.mirai.console.command.parse.RawCommandArgument import net.mamoe.mirai.message.data.MessageContent import kotlin.reflect.KType import kotlin.reflect.typeOf +/** + * Implicit type variant specified by [CommandCallParser]. + * + * [TypeVariant] is not necessary for all [CommandCall]s. + */ @ExperimentalCommandDescriptors public interface TypeVariant<out OutType> { /** From d4c147a8a96f2374aeba7282dae770325b79f05c Mon Sep 17 00:00:00 2001 From: Karlatemp <karlatemp@vip.qq.com> Date: Sat, 17 Oct 2020 11:47:40 +0800 Subject: [PATCH 12/28] IllegalCommandArgumentException --- .../console/command/CommandExecuteResult.kt | 8 ++--- .../mirai/console/command/CommandSender.kt | 7 +++++ .../IllegalCommandArgumentException.kt | 29 +++++++++++++++++++ .../CommandArgumentParserException.kt | 5 +++- .../command/executeCommandInternal.kt | 2 +- 5 files changed, 45 insertions(+), 6 deletions(-) create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/IllegalCommandArgumentException.kt diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandExecuteResult.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandExecuteResult.kt index 9076951b4..3ec14fe62 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandExecuteResult.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandExecuteResult.kt @@ -58,7 +58,7 @@ public sealed class CommandExecuteResult { /** 执行执行时发生了一个非法参数错误 */ public class IllegalArgument( /** 指令执行时发生的错误 */ - public override val exception: IllegalArgumentException, + public override val exception: IllegalCommandArgumentException, /** 尝试执行的指令 */ public override val command: Command, /** 尝试执行的指令名 */ @@ -180,7 +180,7 @@ public fun CommandExecuteResult.isExecutionException(): Boolean { } /** - * 当 [this] 为 [CommandExecuteResult.ExecutionFailed] 时返回 `true` + * 当 [this] 为 [CommandExecuteResult.PermissionDenied] 时返回 `true` */ @JvmSynthetic public fun CommandExecuteResult.isPermissionDenied(): Boolean { @@ -192,7 +192,7 @@ public fun CommandExecuteResult.isPermissionDenied(): Boolean { } /** - * 当 [this] 为 [CommandExecuteResult.ExecutionFailed] 时返回 `true` + * 当 [this] 为 [CommandExecuteResult.CommandNotFound] 时返回 `true` */ @JvmSynthetic public fun CommandExecuteResult.isCommandNotFound(): Boolean { @@ -204,7 +204,7 @@ public fun CommandExecuteResult.isCommandNotFound(): Boolean { } /** - * 当 [this] 为 [CommandExecuteResult.ExecutionFailed] 或 [CommandExecuteResult.CommandNotFound] 时返回 `true` + * 当 [this] 为 [CommandExecuteResult.ExecutionFailed], [CommandExecuteResult.IllegalArgument] 或 [CommandExecuteResult.CommandNotFound] 时返回 `true` */ @JvmSynthetic public fun CommandExecuteResult.isFailure(): Boolean { diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt index 219c2485e..0f8314384 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt @@ -282,6 +282,13 @@ public sealed class AbstractCommandSender : CommandSender, CoroutineScope { if (this is CommandSenderOnMessage<*>) { val cause = e.rootCauseOrSelf + // TODO: 2020/10/17 + // CommandArgumentParserException 作为 IllegalCommandArgumentException 不会再进入此函数 + // 已在 + // - [console] CommandManagerImpl.commandListener + // - [terminal] ConsoleThread.kt + // 处理 + val message = cause .takeIf { it is CommandArgumentParserException }?.message ?: "${cause::class.simpleName.orEmpty()}: ${cause.message}" diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/IllegalCommandArgumentException.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/IllegalCommandArgumentException.kt new file mode 100644 index 000000000..0e8936524 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/IllegalCommandArgumentException.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2019-2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + * + */ + +@file:Suppress("unused") + +package net.mamoe.mirai.console.command + +import net.mamoe.mirai.console.command.descriptor.CommandArgumentParserException + +/** + * 在处理参数时遇到的 _正常_ 错误. 如参数不符合规范, 参数值越界等. + * + * [message] 将会发送给指令调用方. + * + * @see CommandArgumentParserException + */ +public open class IllegalCommandArgumentException : IllegalArgumentException { + 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/CommandArgumentParserException.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentParserException.kt index faae4cf1e..25a59067d 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentParserException.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentParserException.kt @@ -11,15 +11,18 @@ package net.mamoe.mirai.console.command.descriptor +import net.mamoe.mirai.console.command.IllegalCommandArgumentException + /** * 在解析参数时遇到的 _正常_ 错误. 如参数不符合规范等. * * [message] 将会发送给指令调用方. * + * @see IllegalCommandArgumentException * @see CommandValueArgumentParser * @see CommandValueArgumentParser.illegalArgument */ -public class CommandArgumentParserException : RuntimeException { +public class CommandArgumentParserException : IllegalCommandArgumentException { public constructor() : super() public constructor(message: String?) : super(message) public constructor(message: String?, cause: Throwable?) : super(message, cause) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/executeCommandInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/executeCommandInternal.kt index a9ff54cca..cfd1648d7 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/executeCommandInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/executeCommandInternal.kt @@ -37,7 +37,7 @@ internal suspend fun CommandSender.executeCommandInternal( ) }, onFailure = { exception -> - return if (exception is IllegalArgumentException) CommandExecuteResult.IllegalArgument( + return if (exception is IllegalCommandArgumentException) CommandExecuteResult.IllegalArgument( commandName = commandName, command = command, exception = exception, From df461290c03812f4edaf9fb517dcddcf76cbef12 Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Sun, 18 Oct 2020 12:26:54 +0800 Subject: [PATCH 13/28] Command resolving --- .../mamoe/mirai/console/command/Command.kt | 35 ++-- .../console/command/CommandExecuteResult.kt | 4 + .../mirai/console/command/CommandManager.kt | 173 ++++++++++-------- .../mirai/console/command/CommandSender.kt | 1 - .../mirai/console/command/CompositeCommand.kt | 21 ++- .../mamoe/mirai/console/command/RawCommand.kt | 17 +- .../mirai/console/command/SimpleCommand.kt | 12 +- .../command/descriptor/CommandDescriptor.kt | 109 +++++++++-- .../mirai/console/command/java/JCommand.kt | 21 +-- .../console/command/java/JCompositeCommand.kt | 2 + .../mirai/console/command/java/JRawCommand.kt | 27 +-- .../console/command/java/JSimpleCommand.kt | 2 + .../command/parse/CommandCallParser.kt | 4 +- .../command/parse/CommandValueArgument.kt | 19 +- .../parse/SpaceSeparatedCommandCallParser.kt | 23 +++ .../resolve/BuiltInCommandCallResolver.kt | 121 ++++++++++++ .../command/resolve/CommandCallResolver.kt | 15 -- .../command/resolve/ResolvedCommandCall.kt | 46 ++++- .../internal/command/CommandManagerImpl.kt | 56 +----- .../command/CompositeCommand.CommandParam.kt | 6 +- .../command/CompositeCommandInternal.kt | 97 +++------- .../command/executeCommandInternal.kt | 67 ------- .../mamoe/mirai/console/util/MessageUtils.kt | 25 +++ .../mirai/console/command/TestCommand.kt | 1 - 24 files changed, 527 insertions(+), 377 deletions(-) create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/parse/SpaceSeparatedCommandCallParser.kt create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/BuiltInCommandCallResolver.kt delete mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/executeCommandInternal.kt create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/MessageUtils.kt diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt index 43346dcb9..5aac67c2d 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt @@ -11,15 +11,16 @@ package net.mamoe.mirai.console.command -import net.mamoe.kjbb.JvmBlockingBridge -import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register +import net.mamoe.mirai.console.command.descriptor.CommandArgumentContextAware +import net.mamoe.mirai.console.command.descriptor.CommandSignatureVariant +import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.java.JCommand import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.console.permission.PermissionId -import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.console.util.ConsoleExperimentalApi /** * 指令 @@ -30,6 +31,8 @@ import net.mamoe.mirai.message.data.MessageChain * @see CompositeCommand 复合指令 * @see SimpleCommand 简单的, 支持参数自动解析的指令 * + * @see CommandArgumentContextAware + * * @see JCommand 为 Java 用户添加协程帮助的 [Command] */ public interface Command { @@ -48,6 +51,13 @@ public interface Command { @ResolveContext(COMMAND_NAME) public val secondaryNames: Array<out String> + /** + * + */ + @ConsoleExperimentalApi("Property name is experimental") + @ExperimentalCommandDescriptors + public val overloads: List<CommandSignatureVariant> + /** * 用法说明, 用于发送给用户. [usage] 一般包含 [description]. */ @@ -80,16 +90,6 @@ public interface Command { */ public val owner: CommandOwner - /** - * 在指令被执行时调用. - * - * @param args 精确的指令参数. [MessageChain] 每个元素代表一个精确的参数. - * - * @see CommandManager.executeCommand 查看更多信息 - */ - @JvmBlockingBridge - public suspend fun CommandSender.onCommand(args: MessageChain) - public companion object { /** @@ -116,12 +116,3 @@ public interface Command { } } } - -/** - * 调用 [Command.onCommand] - * @see Command.onCommand - */ -@JvmSynthetic -public suspend inline fun Command.onCommand(sender: CommandSender, args: MessageChain): Unit = - sender.onCommand(args) - diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandExecuteResult.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandExecuteResult.kt index 9076951b4..0d084810a 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandExecuteResult.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandExecuteResult.kt @@ -12,6 +12,8 @@ package net.mamoe.mirai.console.command import net.mamoe.mirai.console.command.CommandExecuteResult.CommandExecuteStatus +import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors +import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.MessageChain import kotlin.contracts.contract @@ -21,6 +23,8 @@ import kotlin.contracts.contract * * @see CommandExecuteStatus */ +@ConsoleExperimentalApi("Not yet implemented") +@ExperimentalCommandDescriptors public sealed class CommandExecuteResult { /** 指令最终执行状态 */ public abstract val status: CommandExecuteStatus diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt index b2c058d78..8d99ab6cc 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt @@ -8,16 +8,26 @@ */ @file:Suppress( - "NOTHING_TO_INLINE", "unused", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE", + "NOTHING_TO_INLINE", "unused", "MemberVisibilityCanBePrivate", "INAPPLICABLE_JVM_NAME" ) @file:JvmName("CommandManagerKt") package net.mamoe.mirai.console.command -import net.mamoe.kjbb.JvmBlockingBridge +import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand +import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors +import net.mamoe.mirai.console.command.parse.CommandCall +import net.mamoe.mirai.console.command.parse.CommandCallParser +import net.mamoe.mirai.console.command.parse.CommandCallParser.Companion.parseCommandCall +import net.mamoe.mirai.console.command.resolve.CommandCallResolver +import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall +import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall.Companion.call +import net.mamoe.mirai.console.extensions.CommandCallResolverProvider import net.mamoe.mirai.console.internal.command.CommandManagerImpl import net.mamoe.mirai.console.internal.command.CommandManagerImpl.executeCommand +import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage +import net.mamoe.mirai.console.permission.PermissionService.Companion.testPermission import net.mamoe.mirai.message.data.* /** @@ -101,19 +111,45 @@ public interface CommandManager { * 注意: 字符串与消息元素之间不需要空格, 会被强制分割. 如 "bar[mirai:image:]" 会被分割为 "bar" 和 [Image] 类型的消息元素. * 3. 参数解析. 各类型指令实现不同. 详见 [RawCommand], [CompositeCommand], [SimpleCommand] * - * ### 未来的扩展 - * 在将来, 参数语法分析过程可能会被扩展, 允许插件自定义处理方式, 因此可能不会简单地使用空格分隔. + * ### 扩展 + * 参数语法分析过程可能会被扩展, 插件可以自定义处理方式, 因此可能不会简单地使用空格分隔. * * @param message 一条完整的指令. 如 "/managers add 123456.123456" * @param checkPermission 为 `true` 时检查权限 * + * @see CommandCallParser + * @see CommandCallResolver + * * @return 执行结果 */ - @JvmBlockingBridge - public suspend fun CommandSender.executeCommand( + // @JvmBlockingBridge + @OptIn(ExperimentalCommandDescriptors::class) + public suspend fun executeCommand( + caller: CommandSender, message: Message, checkPermission: Boolean = true, - ): CommandExecuteResult + ): CommandExecuteResult { + val call = message.asMessageChain().parseCommandCall(caller) ?: return CommandExecuteResult.CommandNotFound("") + val resolved = call.resolve() ?: return CommandExecuteResult.CommandNotFound(call.calleeName) + + val command = resolved.callee + + if (checkPermission && !command.permission.testPermission(caller)) { + return CommandExecuteResult.PermissionDenied(command, call.calleeName) + } + + return kotlin.runCatching { + resolved.call() + }.fold( + onSuccess = { + CommandExecuteResult.Success(resolved.callee, call.calleeName, EmptyMessageChain) + }, + onFailure = { + CommandExecuteResult.ExecutionFailed(it, resolved.callee, call.calleeName, EmptyMessageChain) + } + ) + } + /** * 解析并执行一个指令 @@ -124,35 +160,33 @@ public interface CommandManager { * @return 执行结果 * @see executeCommand */ - @JvmBlockingBridge + // @JvmBlockingBridge public suspend fun CommandSender.executeCommand( message: String, checkPermission: Boolean = true, - ): CommandExecuteResult = executeCommand(PlainText(message).asMessageChain(), checkPermission) + ): CommandExecuteResult = executeCommand(this, PlainText(message).asMessageChain(), checkPermission) + + @JvmName("resolveCall") + @ExperimentalCommandDescriptors + public fun CommandCall.resolve(): ResolvedCommandCall? { + GlobalComponentStorage.run { + CommandCallResolverProvider.useExtensions { provider -> + provider.instance.resolve(this@resolve)?.let { return it } + } + } + return null + } /** - * 执行一个确切的指令 - * @see executeCommand 获取更多信息 + * 从 [指令名称][commandName] 匹配对应的 [Command]. + * + * #### 实现细节 + * - [commandName] 带有 [commandPrefix] 时可以匹配到所有指令 + * - [commandName] 不带有 [commandPrefix] 时只能匹配到 [Command.prefixOptional] 的指令 + * + * @param commandName 可能带有或不带有 [commandPrefix]. */ - @JvmBlockingBridge - @JvmName("executeCommand") - public suspend fun Command.execute( - sender: CommandSender, - arguments: Message = EmptyMessageChain, - checkPermission: Boolean = true, - ): CommandExecuteResult - - /** - * 执行一个确切的指令 - * @see executeCommand 获取更多信息 - */ - @JvmBlockingBridge - @JvmName("executeCommand") - public suspend fun Command.execute( - sender: CommandSender, - arguments: String = "", - checkPermission: Boolean = true, - ): CommandExecuteResult = execute(sender, PlainText(arguments).asMessageChain(), checkPermission) + public fun matchCommand(commandName: String): Command? public companion object INSTANCE : CommandManager by CommandManagerImpl { // TODO: 2020/8/20 https://youtrack.jetbrains.com/issue/KT-41191 @@ -167,52 +201,37 @@ public interface CommandManager { override val allRegisteredCommands: List<Command> get() = CommandManagerImpl.allRegisteredCommands - - override suspend fun Command.execute( - sender: CommandSender, - arguments: Message, - checkPermission: Boolean, - ): CommandExecuteResult = - CommandManagerImpl.run { execute(sender, arguments = arguments, checkPermission = checkPermission) } - - override suspend fun CommandSender.executeCommand( - message: String, - checkPermission: Boolean, - ): CommandExecuteResult = CommandManagerImpl.run { executeCommand(message, checkPermission) } - - override suspend fun Command.execute( - sender: CommandSender, - arguments: String, - checkPermission: Boolean, - ): CommandExecuteResult = CommandManagerImpl.run { execute(sender, arguments, checkPermission) } - - override suspend fun CommandSender.executeCommand( - message: Message, - checkPermission: Boolean, - ): CommandExecuteResult = CommandManagerImpl.run { executeCommand(message, checkPermission) } - - /** - * 执行一个确切的指令 - * @see execute 获取更多信息 - */ - public suspend fun CommandSender.execute( - command: Command, - arguments: Message, - checkPermission: Boolean = true, - ): CommandExecuteResult { - return command.execute(this, arguments, checkPermission) - } - - /** - * 执行一个确切的指令 - * @see execute 获取更多信息 - */ - public suspend fun CommandSender.execute( - command: Command, - arguments: String, - checkPermission: Boolean = true, - ): CommandExecuteResult { - return command.execute(this, arguments, checkPermission) - } } +} + +/** + * 执行一个确切的指令 + * @see executeCommand 获取更多信息 + */ +// @JvmBlockingBridge +// @JvmName("executeCommand") +public suspend fun Command.execute( + sender: CommandSender, + arguments: String = "", + checkPermission: Boolean = true, +): CommandExecuteResult = execute(sender, PlainText(arguments).asMessageChain(), checkPermission) + +/** + * 执行一个确切的指令 + * @see executeCommand 获取更多信息 + */ +// @JvmBlockingBridge +// @JvmName("executeCommand") +public suspend fun Command.execute( + sender: CommandSender, + arguments: Message = EmptyMessageChain, + checkPermission: Boolean = true, +): CommandExecuteResult { + // TODO: 2020/10/18 net.mamoe.mirai.console.command.CommandManager.execute + val chain = buildMessageChain { + append(this@execute.primaryName) + append(' ') + append(arguments) + } + return CommandManager.executeCommand(sender, chain, checkPermission) } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt index 219c2485e..91b1e6d59 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt @@ -20,7 +20,6 @@ import kotlinx.coroutines.launch import net.mamoe.kjbb.JvmBlockingBridge import net.mamoe.mirai.Bot import net.mamoe.mirai.console.MiraiConsole -import net.mamoe.mirai.console.command.CommandManager.INSTANCE.execute import net.mamoe.mirai.console.command.CommandSender.Companion.asCommandSender import net.mamoe.mirai.console.command.CommandSender.Companion.asMemberCommandSender import net.mamoe.mirai.console.command.CommandSender.Companion.asTempCommandSender diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt index 77126b96b..7f29eb4a7 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt @@ -124,13 +124,26 @@ public abstract class CompositeCommand( @Target(AnnotationTarget.VALUE_PARAMETER) protected annotation class Name(val value: String) - public final override suspend fun CommandSender.onCommand(args: MessageChain) { - matchSubCommand(args)?.parseAndExecute(this, args, true) ?: kotlin.run { - defaultSubCommand.onCommand(this, args) + @OptIn(ExperimentalCommandDescriptors::class) + override val overloads: List<CommandSignatureVariant> by lazy { + subCommands.flatMap { desc -> + desc.bakedSubNames.map { names -> + CommandSignatureVariantImpl( + valueParameters = + names.mapIndexed { index, s -> CommandValueParameter.StringConstant("p$index", s) } + desc.params.map { + CommandValueParameter.UserDefinedType(it.name, null, + isOptional = false, + isVararg = false, + type = it.type) + }, + onCall = { resolvedCommandCall -> + desc.onCommand(resolvedCommandCall.caller, resolvedCommandCall.resolvedValueArguments.drop(names.size)) + } + ) + } } } - protected override suspend fun CommandSender.onDefault(rawArgs: MessageChain) { sendMessage(usage) } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt index a924e6982..75facfed8 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt @@ -11,14 +11,18 @@ package net.mamoe.mirai.console.command -import net.mamoe.mirai.console.command.CommandManager.INSTANCE.execute import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand +import net.mamoe.mirai.console.command.descriptor.CommandSignatureVariant +import net.mamoe.mirai.console.command.descriptor.CommandSignatureVariantImpl +import net.mamoe.mirai.console.command.descriptor.CommandValueParameter +import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.java.JRawCommand import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME import net.mamoe.mirai.console.internal.command.createOrFindCommandPermission import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.MessageChainBuilder /** * 无参数解析, 接收原生参数的指令. @@ -52,6 +56,15 @@ public abstract class RawCommand( ) : Command { public override val permission: Permission by lazy { createOrFindCommandPermission(parentPermission) } + @ExperimentalCommandDescriptors + override val overloads: List<CommandSignatureVariant> = listOf( + CommandSignatureVariantImpl(listOf(CommandValueParameter.UserDefinedType.createRequired<MessageChain>("args", true))) { call -> + val sender = call.caller + val arguments = call.rawValueArguments + sender.onCommand(arguments.mapTo(MessageChainBuilder()) { it.value }.build()) + } + ) + /** * 在指令被执行时调用. * @@ -59,7 +72,7 @@ public abstract class RawCommand( * * @see CommandManager.execute 查看更多信息 */ - public abstract override suspend fun CommandSender.onCommand(args: MessageChain) + public abstract suspend fun CommandSender.onCommand(args: MessageChain) } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt index 7a4a0e985..60e67ca32 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt @@ -61,6 +61,14 @@ public abstract class SimpleCommand( ) : Command, AbstractReflectionCommand(owner, primaryName, secondaryNames = secondaryNames, description, parentPermission, prefixOptional), CommandArgumentContextAware { + @ExperimentalCommandDescriptors + override val overloads: List<CommandSignatureVariant> = listOf( + CommandSignatureVariantImpl(listOf(CommandValueParameter.UserDefinedType.createRequired<MessageChain>("args", true))) { call -> + val sender = call.caller + subCommands.single().onCommand(sender, call.resolvedValueArguments) + } + ) + /** * 自动根据带有 [Handler] 注解的函数签名生成 [usage]. 也可以被覆盖. */ @@ -76,10 +84,6 @@ public abstract class SimpleCommand( */ public override val context: CommandArgumentContext = CommandArgumentContext.Builtins + overrideContext - public final override suspend fun CommandSender.onCommand(args: MessageChain) { - subCommands.single().parseAndExecute(this, args, false) - } - internal override fun checkSubCommand(subCommands: Array<SubCommandDescriptor>) { super.checkSubCommand(subCommands) check(subCommands.size == 1) { "There can only be exactly one function annotated with Handler at this moment as overloading is not yet supported." } 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 index 690694d4e..2fdc5f424 100644 --- 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 @@ -9,19 +9,39 @@ package net.mamoe.mirai.console.command.descriptor +import net.mamoe.kjbb.JvmBlockingBridge +import net.mamoe.mirai.console.command.descriptor.ArgumentAcceptance.Companion.isAcceptable +import net.mamoe.mirai.console.command.descriptor.CommandValueParameter.UserDefinedType.Companion.createOptional +import net.mamoe.mirai.console.command.descriptor.CommandValueParameter.UserDefinedType.Companion.createRequired +import net.mamoe.mirai.console.command.parse.CommandValueArgument +import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall +import net.mamoe.mirai.console.internal.data.classifierAsKClassOrNull +import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.safeCast import kotlin.reflect.KClass import kotlin.reflect.KType +import kotlin.reflect.full.isSubtypeOf import kotlin.reflect.typeOf -@ExperimentalCommandDescriptors -public interface CommandDescriptor { - public val overloads: List<CommandSignatureVariant> -} - +/** + * @see CommandSignatureVariantImpl + */ @ExperimentalCommandDescriptors public interface CommandSignatureVariant { public val valueParameters: List<CommandValueParameter<*>> + + @JvmBlockingBridge + public suspend fun call(resolvedCommandCall: ResolvedCommandCall) +} + +@ExperimentalCommandDescriptors +public class CommandSignatureVariantImpl( + override val valueParameters: List<CommandValueParameter<*>>, + private val onCall: suspend CommandSignatureVariantImpl.(resolvedCommandCall: ResolvedCommandCall) -> Unit, +) : CommandSignatureVariant { + override suspend fun call(resolvedCommandCall: ResolvedCommandCall) { + return onCall(resolvedCommandCall) + } } @@ -43,23 +63,85 @@ public interface ICommandParameter<T : Any?> { * Reified type of [T] */ public val type: KType + + public val isVararg: Boolean + + public fun accepts(argument: CommandValueArgument, commandArgumentContext: CommandArgumentContext?): Boolean = + accepting(argument, commandArgumentContext).isAcceptable + + public fun accepting(argument: CommandValueArgument, commandArgumentContext: CommandArgumentContext?): ArgumentAcceptance } +@ExperimentalCommandDescriptors +public sealed class ArgumentAcceptance( + /** + * Higher means more acceptable + */ + @ConsoleExperimentalApi + public val acceptanceLevel: Int, +) { + public object Direct : ArgumentAcceptance(Int.MAX_VALUE) + + public class WithTypeConversion( + public val typeVariant: TypeVariant<*>, + ) : ArgumentAcceptance(20) + + public class WithContextualConversion( + public val parser: CommandValueArgumentParser<*>, + ) : ArgumentAcceptance(10) + + public class ResolutionAmbiguity( + public val candidates: List<TypeVariant<*>>, + ) : ArgumentAcceptance(0) + + public object Impossible : ArgumentAcceptance(-1) + + public companion object { + @JvmStatic + public val ArgumentAcceptance.isAcceptable: Boolean + get() = acceptanceLevel > 0 + + @JvmStatic + public val ArgumentAcceptance.isNotAcceptable: Boolean + get() = acceptanceLevel <= 0 + } +} + + @ExperimentalCommandDescriptors public sealed class CommandValueParameter<T> : ICommandParameter<T> { - init { - @Suppress("LeakingThis") + internal fun validate() { // // TODO: 2020/10/18 net.mamoe.mirai.console.command.descriptor.CommandValueParameter.validate$mirai_console_mirai_console_main require(type.classifier?.safeCast<KClass<*>>()?.isInstance(defaultValue) == true) { "defaultValue is not instance of type" } } + + public override fun accepting(argument: CommandValueArgument, commandArgumentContext: CommandArgumentContext?): ArgumentAcceptance { + val expectingType = this.type + + if (argument.type.isSubtypeOf(expectingType)) return ArgumentAcceptance.Direct + + argument.typeVariants.associateWith { typeVariant -> + if (typeVariant.outType.isSubtypeOf(expectingType)) { + // TODO: 2020/10/11 resolution ambiguity + return ArgumentAcceptance.WithTypeConversion(typeVariant) + } + } + expectingType.classifierAsKClassOrNull()?.let { commandArgumentContext?.get(it) }?.let { parser -> + return ArgumentAcceptance.WithContextualConversion(parser) + } + return ArgumentAcceptance.Impossible + } + public class StringConstant( public override val name: String, + public val expectingValue: String, ) : CommandValueParameter<String>() { public override val type: KType get() = STRING_TYPE public override val defaultValue: Nothing? get() = null public override val isOptional: Boolean get() = false + public override val isVararg: Boolean get() = false private companion object { @OptIn(ExperimentalStdlibApi::class) @@ -67,23 +149,28 @@ public sealed class CommandValueParameter<T> : ICommandParameter<T> { } } + /** + * @see createOptional + * @see createRequired + */ public class UserDefinedType<T>( public override val name: String, public override val defaultValue: T?, public override val isOptional: Boolean, + public override val isVararg: Boolean, public override val type: KType, ) : CommandValueParameter<T>() { public companion object { @JvmStatic - public inline fun <reified T : Any> createOptional(name: String, defaultValue: T): UserDefinedType<T> { + public inline fun <reified T : Any> createOptional(name: String, isVararg: Boolean, defaultValue: T): UserDefinedType<T> { @OptIn(ExperimentalStdlibApi::class) - return UserDefinedType(name, defaultValue, true, typeOf<T>()) + return UserDefinedType(name, defaultValue, true, isVararg, typeOf<T>()) } @JvmStatic - public inline fun <reified T : Any> createRequired(name: String): UserDefinedType<T> { + public inline fun <reified T : Any> createRequired(name: String, isVararg: Boolean): UserDefinedType<T> { @OptIn(ExperimentalStdlibApi::class) - return UserDefinedType(name, null, false, typeOf<T>()) + return UserDefinedType(name, null, false, isVararg, typeOf<T>()) } } } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JCommand.kt index 1fbf748c0..af4fbbe0e 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JCommand.kt @@ -9,13 +9,8 @@ package net.mamoe.mirai.console.command.java -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext import net.mamoe.mirai.console.command.Command -import net.mamoe.mirai.console.command.CommandManager -import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand -import net.mamoe.mirai.console.command.CommandSender -import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.console.util.ConsoleExperimentalApi /** * 为 Java 用户添加协程帮助的 [Command]. @@ -24,17 +19,7 @@ import net.mamoe.mirai.message.data.MessageChain * * @see Command */ +@ConsoleExperimentalApi("Not yet supported") public interface JCommand : Command { - public override suspend fun CommandSender.onCommand(args: MessageChain) { - withContext(Dispatchers.IO) { onCommand(this@onCommand, args) } - } - - /** - * 在指令被执行时调用. - * - * @param args 精确的指令参数. [MessageChain] 每个元素代表一个精确的参数. - * - * @see CommandManager.executeCommand 查看更多信息 - */ - public fun onCommand(sender: CommandSender, args: MessageChain) // overrides blocking bridge + // TODO: 2020/10/18 JCommand } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JCompositeCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JCompositeCommand.kt index 215944426..fa0b9203a 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JCompositeCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JCompositeCommand.kt @@ -17,6 +17,7 @@ import net.mamoe.mirai.console.command.descriptor.buildCommandArgumentContext import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME import net.mamoe.mirai.console.permission.Permission +import net.mamoe.mirai.console.util.ConsoleExperimentalApi /** * 复合指令. 指令注册时候会通过反射构造指令解析器. @@ -68,6 +69,7 @@ import net.mamoe.mirai.console.permission.Permission * * @see buildCommandArgumentContext */ +@ConsoleExperimentalApi("Not yet supported") public abstract class JCompositeCommand @JvmOverloads constructor( owner: CommandOwner, diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JRawCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JRawCommand.kt index 84b06ea5c..19cf70d2a 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JRawCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JRawCommand.kt @@ -9,16 +9,15 @@ package net.mamoe.mirai.console.command.java -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import net.mamoe.mirai.console.command.* -import net.mamoe.mirai.console.command.CommandManager.INSTANCE.execute +import net.mamoe.mirai.console.command.BuiltInCommands +import net.mamoe.mirai.console.command.Command +import net.mamoe.mirai.console.command.CommandManager +import net.mamoe.mirai.console.command.CommandOwner import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME import net.mamoe.mirai.console.internal.command.createOrFindCommandPermission import net.mamoe.mirai.console.permission.Permission -import net.mamoe.mirai.message.data.MessageChain -import net.mamoe.mirai.message.data.SingleMessage +import net.mamoe.mirai.console.util.ConsoleExperimentalApi /** * 供 Java 用户继承 @@ -46,6 +45,7 @@ import net.mamoe.mirai.message.data.SingleMessage * * @see JRawCommand */ +@ConsoleExperimentalApi("Not yet supported") public abstract class JRawCommand @JvmOverloads constructor( /** @@ -72,19 +72,4 @@ public abstract class JRawCommand /** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */ public final override var prefixOptional: Boolean = false protected set - - /** - * 在指令被执行时调用. - * - * @param args 指令参数. 数组元素类型可能是 [SingleMessage] 或 [String]. 且已经以 ' ' 分割. - * - * @see CommandManager.execute 查看更多信息 - */ - @Suppress("INAPPLICABLE_JVM_NAME") - @JvmName("onCommand") - public abstract fun onCommand(sender: CommandSender, args: MessageChain) - - public final override suspend fun CommandSender.onCommand(args: MessageChain) { - withContext(Dispatchers.IO) { onCommand(this@onCommand, args) } - } } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JSimpleCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JSimpleCommand.kt index f0e9b9784..4333bfa45 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JSimpleCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JSimpleCommand.kt @@ -17,6 +17,7 @@ import net.mamoe.mirai.console.command.descriptor.CommandArgumentContext import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME import net.mamoe.mirai.console.permission.Permission +import net.mamoe.mirai.console.util.ConsoleExperimentalApi /** * Java 实现: @@ -42,6 +43,7 @@ import net.mamoe.mirai.console.permission.Permission * @see SimpleCommand * @see [CommandManager.executeCommand] */ +@ConsoleExperimentalApi("Not yet supported") public abstract class JSimpleCommand( owner: CommandOwner, @ResolveContext(COMMAND_NAME) primaryName: String, 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 index 5be371aa8..4d4bc6f59 100644 --- 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 @@ -14,6 +14,8 @@ import net.mamoe.mirai.message.data.MessageChain * * @see CommandCallResolver The call resolver for [CommandCall] to become [ResolvedCommandCall] * @see CommandCallParserProvider The extension point + * + * @see SpaceSeparatedCommandCallParser */ @ConsoleExperimentalApi @ExperimentalCommandDescriptors @@ -24,7 +26,7 @@ public interface CommandCallParser { * * @return `null` if unable to parse (i.e. due to syntax errors). */ - public fun parse(sender: CommandSender, message: MessageChain): CommandCall? + public fun parse(caller: CommandSender, message: MessageChain): CommandCall? public companion object { /** 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 index 3c473e21a..f9dfe4588 100644 --- 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 @@ -14,6 +14,7 @@ import net.mamoe.mirai.console.command.descriptor.MessageContentTypeVariant import net.mamoe.mirai.console.command.descriptor.NoValueArgumentMappingException import net.mamoe.mirai.console.command.descriptor.TypeVariant import net.mamoe.mirai.message.data.MessageContent +import kotlin.reflect.KType import kotlin.reflect.full.isSubtypeOf import kotlin.reflect.typeOf @@ -34,6 +35,7 @@ public interface CommandArgument */ @ExperimentalCommandDescriptors public interface CommandValueArgument : CommandArgument { + public val type: KType public val value: RawCommandArgument public val typeVariants: List<TypeVariant<*>> } @@ -45,6 +47,8 @@ public interface CommandValueArgument : CommandArgument { public data class InvariantCommandValueArgument( public override val value: RawCommandArgument, ) : CommandValueArgument { + @OptIn(ExperimentalStdlibApi::class) + override val type: KType = typeOf<MessageContent>() override val typeVariants: List<TypeVariant<*>> = listOf(MessageContentTypeVariant) } @@ -57,10 +61,14 @@ public fun <T> CommandValueArgument.mapValue(typeVariant: TypeVariant<T>): T = t public inline fun <reified T> CommandValueArgument.mapToType(): T = mapToTypeOrNull() ?: throw NoValueArgumentMappingException(this, typeOf<T>()) +@OptIn(ExperimentalStdlibApi::class) @ExperimentalCommandDescriptors -public inline fun <reified T> CommandValueArgument.mapToTypeOrNull(): T? { +public fun <T> CommandValueArgument.mapToType(type: KType): T = + mapToTypeOrNull(type) ?: throw NoValueArgumentMappingException(this, type) + +@ExperimentalCommandDescriptors +public fun <T> CommandValueArgument.mapToTypeOrNull(expectingType: KType): T? { @OptIn(ExperimentalStdlibApi::class) - val expectingType = typeOf<T>() val result = typeVariants .filter { it.outType.isSubtypeOf(expectingType) } .also { @@ -71,5 +79,12 @@ public inline fun <reified T> CommandValueArgument.mapToTypeOrNull(): T? { acc else typeVariant } + @Suppress("UNCHECKED_CAST") return result.mapValue(value) as T +} + +@ExperimentalCommandDescriptors +public inline fun <reified T> CommandValueArgument.mapToTypeOrNull(): T? { + @OptIn(ExperimentalStdlibApi::class) + return mapToTypeOrNull(typeOf<T>()) } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/parse/SpaceSeparatedCommandCallParser.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/parse/SpaceSeparatedCommandCallParser.kt new file mode 100644 index 000000000..62e6a5370 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/parse/SpaceSeparatedCommandCallParser.kt @@ -0,0 +1,23 @@ +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.internal.command.flattenCommandComponents +import net.mamoe.mirai.console.util.ConsoleExperimentalApi +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.MessageContent +import net.mamoe.mirai.message.data.content + +@ConsoleExperimentalApi +@ExperimentalCommandDescriptors +public object SpaceSeparatedCommandCallParser : CommandCallParser { + override fun parse(caller: CommandSender, message: MessageChain): CommandCall? { + val flatten = message.flattenCommandComponents().filterIsInstance<MessageContent>() + if (flatten.isEmpty()) return null + return CommandCallImpl( + caller = caller, + calleeName = flatten.first().content, + valueArguments = flatten.drop(1).map(::InvariantCommandValueArgument) + ) + } +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/BuiltInCommandCallResolver.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/BuiltInCommandCallResolver.kt new file mode 100644 index 000000000..5ff77e7dc --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/BuiltInCommandCallResolver.kt @@ -0,0 +1,121 @@ +package net.mamoe.mirai.console.command.resolve + +import net.mamoe.mirai.console.command.Command +import net.mamoe.mirai.console.command.CommandManager +import net.mamoe.mirai.console.command.descriptor.* +import net.mamoe.mirai.console.command.descriptor.ArgumentAcceptance.Companion.isNotAcceptable +import net.mamoe.mirai.console.command.parse.CommandCall +import net.mamoe.mirai.console.command.parse.CommandValueArgument +import net.mamoe.mirai.console.util.ConsoleExperimentalApi +import net.mamoe.mirai.console.util.cast +import net.mamoe.mirai.console.util.safeCast +import java.util.* + +@ConsoleExperimentalApi +@ExperimentalCommandDescriptors +public object BuiltInCommandCallResolver : CommandCallResolver { + override fun resolve(call: CommandCall): ResolvedCommandCall? { + val callee = CommandManager.matchCommand(call.calleeName) ?: return null + + val valueArguments = call.valueArguments + val context = callee.safeCast<CommandArgumentContextAware>()?.context + + val signature = resolveImpl(callee, valueArguments, context) ?: return null + + return ResolvedCommandCallImpl(call.caller, callee, signature, call.valueArguments) + } + + private data class ResolveData( + val variant: CommandSignatureVariant, + val argumentAcceptances: List<ArgumentAcceptanceWithIndex>, + ) + + private data class ArgumentAcceptanceWithIndex( + val index: Int, + val acceptance: ArgumentAcceptance, + ) + + private fun resolveImpl( + callee: Command, + valueArguments: List<CommandValueArgument>, + context: CommandArgumentContext?, + ): CommandSignatureVariant? { + + callee.overloads + .mapNotNull l@{ signature -> + val zipped = signature.valueParameters.zip(valueArguments) + + if (signature.valueParameters.drop(zipped.size).any { !it.isOptional }) return@l null // not enough args + + ResolveData(signature, zipped.mapIndexed { index, (parameter, argument) -> + val accepting = parameter.accepting(argument, context) + if (accepting.isNotAcceptable) { + return@l null // argument type not assignable + } + ArgumentAcceptanceWithIndex(index, accepting) + }) + } + .also { result -> result.singleOrNull()?.let { return it.variant } } + .takeLongestMatches() + .ifEmpty { return null } + .also { result -> result.singleOrNull()?.let { return it.variant } } + // take single ArgumentAcceptance.Direct + .also { list -> + + val candidates = list + .flatMap { phase -> + phase.argumentAcceptances.filter { it.acceptance is ArgumentAcceptance.Direct }.map { phase to it } + } + candidates.singleOrNull()?.let { return it.first.variant } // single Direct + if (candidates.distinctBy { it.second.index }.size != candidates.size) { + // Resolution ambiguity + /* + open class A + open class AA: A() + + open class C + open class CC: C() + + fun foo(a: A, c: CC) = 1 + fun foo(a: AA, c: C) = 1 + */ + // The call is foo(AA(), C()) or foo(A(), CC()) + return null + } + } + + return null + } + + /* + + +open class A +open class B : A() +open class C : A() +open class D : C() +open class BB : B() + +fun foo(a: A, c: C) = 1 +//fun foo(a: A, c: A) = 1 +//fun foo(a: A, c: C, def: Int = 0) = 1 +fun foo(a: B, c: C, d: D) = "" + +fun foo(b: BB, a: A, d: C) = 1.0 + + +fun main() { + val a = foo(D(), D()) // int + val b = foo(A(), C()) // int + val d = foo(BB(), c = C(), D()) // string +} + */ + + private fun List<ResolveData>.takeLongestMatches(): List<ResolveData> { + if (isEmpty()) return emptyList() + return associateByTo(TreeMap(Comparator.reverseOrder())) { it.variant.valueParameters.size }.let { m -> + val firstKey = m.keys.first().cast<Int>() + m.filter { it.key == firstKey }.map { it.value.cast() } + } + } +} \ 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 index e8767cc8e..6e8eda94e 100644 --- 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 @@ -11,8 +11,6 @@ package net.mamoe.mirai.console.command.resolve import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.parse.CommandCall -import net.mamoe.mirai.console.extensions.CommandCallResolverProvider -import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage /** * The resolver converting a [CommandCall] into [ResolvedCommandCall] based on registered [] @@ -20,17 +18,4 @@ import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage @ExperimentalCommandDescriptors public interface CommandCallResolver { public fun resolve(call: CommandCall): ResolvedCommandCall? - - public companion object { - @JvmStatic - @JvmName("resolveCall") - public fun CommandCall.resolve(): ResolvedCommandCall? { - GlobalComponentStorage.run { - CommandCallResolverProvider.useExtensions { provider -> - provider.instance.resolve(this@resolve)?.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/resolve/ResolvedCommandCall.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/ResolvedCommandCall.kt index 476ef273d..e0505279e 100644 --- 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 @@ -7,19 +7,23 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -package net.mamoe.mirai.console.command.resolve; +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.CompositeCommand -import net.mamoe.mirai.console.command.descriptor.CommandDescriptor import net.mamoe.mirai.console.command.descriptor.CommandSignatureVariant import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.parse.CommandCall import net.mamoe.mirai.console.command.parse.CommandValueArgument +import net.mamoe.mirai.console.command.parse.mapToType +import net.mamoe.mirai.console.util.ConsoleExperimentalApi +import kotlin.LazyThreadSafetyMode.PUBLICATION /** * The resolved [CommandCall]. + * + * @see ResolvedCommandCallImpl */ @ExperimentalCommandDescriptors public interface ResolvedCommandCall { @@ -31,17 +35,41 @@ public interface ResolvedCommandCall { public val callee: Command /** - * The callee [CommandDescriptor], specifically a sub command from [CompositeCommand] - */ - public val calleeDescriptor: CommandDescriptor - - /** - * The callee [CommandSignatureVariant] + * The callee [CommandSignatureVariant], specifically a sub command from [CompositeCommand] */ public val calleeSignature: CommandSignatureVariant + /** + * Original arguments + */ + public val rawValueArguments: List<CommandValueArgument> + /** * Resolved value arguments arranged mapping the [CommandSignatureVariant.valueParameters] by index. */ - public val valueArguments: List<CommandValueArgument> + @ConsoleExperimentalApi + public val resolvedValueArguments: List<Any?> + + public companion object { + @JvmStatic + @ExperimentalCommandDescriptors + public suspend fun ResolvedCommandCall.call() { + return this.calleeSignature.call(this) + } + } +} + +@ExperimentalCommandDescriptors +public class ResolvedCommandCallImpl( + override val caller: CommandSender, + override val callee: Command, + override val calleeSignature: CommandSignatureVariant, + override val rawValueArguments: List<CommandValueArgument>, +) : ResolvedCommandCall { + override val resolvedValueArguments: List<Any?> by lazy(PUBLICATION) { + calleeSignature.valueParameters.zip(rawValueArguments).map { (parameter, argument) -> + argument.mapToType(parameter.type) + // TODO: 2020/10/17 consider vararg and optional + } + } } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandManagerImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandManagerImpl.kt index 14dfa51b2..11b55aeb1 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandManagerImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandManagerImpl.kt @@ -16,17 +16,15 @@ import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.command.* import net.mamoe.mirai.console.command.Command.Companion.allNames import net.mamoe.mirai.console.command.CommandSender.Companion.toCommandSender +import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope import net.mamoe.mirai.event.Listener import net.mamoe.mirai.event.subscribeAlways import net.mamoe.mirai.message.MessageEvent -import net.mamoe.mirai.message.data.Message -import net.mamoe.mirai.message.data.MessageContent -import net.mamoe.mirai.message.data.asMessageChain import net.mamoe.mirai.message.data.content import net.mamoe.mirai.utils.MiraiLogger import java.util.concurrent.locks.ReentrantLock -internal object CommandManagerImpl : CommandManager, CoroutineScope by CoroutineScope(MiraiConsole.job) { +internal object CommandManagerImpl : CommandManager, CoroutineScope by MiraiConsole.childScope("CommandManagerImpl") { private val logger: MiraiLogger by lazy { MiraiConsole.createLogger("command") } @@ -48,11 +46,11 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine /** * 从原始的 command 中解析出 Command 对象 */ - internal fun matchCommand(rawCommand: String): Command? { - if (rawCommand.startsWith(commandPrefix)) { - return requiredPrefixCommandMap[rawCommand.substringAfter(commandPrefix).toLowerCase()] + override fun matchCommand(commandName: String): Command? { + if (commandName.startsWith(commandPrefix)) { + return requiredPrefixCommandMap[commandName.substringAfter(commandPrefix).toLowerCase()] } - return optionalPrefixCommandMap[rawCommand.toLowerCase()] + return optionalPrefixCommandMap[commandName.toLowerCase()] } internal val commandListener: Listener<MessageEvent> by lazy { @@ -65,7 +63,7 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine ) { val sender = this.toCommandSender() - when (val result = sender.executeCommand(message)) { + when (val result = executeCommand(sender, message)) { is CommandExecuteResult.PermissionDenied -> { if (!result.command.prefixOptional || message.content.startsWith(CommandManager.commandPrefix)) { sender.sendMessage("权限不足") @@ -152,44 +150,4 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine } override fun Command.isRegistered(): Boolean = this in _registeredCommands - - override suspend fun Command.execute( - sender: CommandSender, - arguments: Message, - checkPermission: Boolean - ): CommandExecuteResult { - return sender.executeCommandInternal( - this, - arguments.flattenCommandComponents(), - primaryName, - checkPermission - ) - } - - override suspend fun Command.execute( - sender: CommandSender, - arguments: String, - checkPermission: Boolean - ): CommandExecuteResult { - return sender.executeCommandInternal( - this, - arguments.flattenCommandComponents(), - primaryName, - checkPermission - ) - } - - override suspend fun CommandSender.executeCommand( - message: Message, - checkPermission: Boolean - ): CommandExecuteResult { - val msg = message.asMessageChain().filterIsInstance<MessageContent>() - if (msg.isEmpty()) return CommandExecuteResult.CommandNotFound("") - return executeCommandInternal(msg, msg[0].content.substringBefore(' '), checkPermission) - } - - override suspend fun CommandSender.executeCommand(message: String, checkPermission: Boolean): CommandExecuteResult { - if (message.isBlank()) return CommandExecuteResult.CommandNotFound("") - return executeCommandInternal(message, message.substringBefore(' '), checkPermission) - } } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommand.CommandParam.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommand.CommandParam.kt index 16bae5667..8279d9f1c 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommand.CommandParam.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommand.CommandParam.kt @@ -13,8 +13,8 @@ package net.mamoe.mirai.console.internal.command import net.mamoe.mirai.console.command.CompositeCommand import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser -import kotlin.reflect.KClass import kotlin.reflect.KParameter +import kotlin.reflect.KType /* internal fun Parameter.toCommandParam(): CommandParameter<*> { @@ -39,10 +39,10 @@ internal data class CommandParameter<T : Any>( /** * 参数类型. 将从 [CompositeCommand.context] 中寻找 [CommandValueArgumentParser] 解析. */ - val type: KClass<T>, // exact type + val type: KType, // exact type val parameter: KParameter, // source parameter ) { - constructor(name: String, type: KClass<T>, parameter: KParameter, parser: CommandValueArgumentParser<T>) : this( + constructor(name: String, type: KType, parameter: KParameter, parser: CommandValueArgumentParser<T>) : this( name, type, parameter ) { this._overrideParser = parser diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommandInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommandInternal.kt index ea9249e5b..ede2cef02 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommandInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommandInternal.kt @@ -12,12 +12,12 @@ package net.mamoe.mirai.console.internal.command import net.mamoe.mirai.console.command.* -import net.mamoe.mirai.console.command.descriptor.CommandArgumentContext -import net.mamoe.mirai.console.command.descriptor.CommandArgumentContextAware -import net.mamoe.mirai.console.internal.data.kClassQualifiedNameOrTip +import net.mamoe.mirai.console.command.descriptor.* import net.mamoe.mirai.console.permission.Permission -import net.mamoe.mirai.console.permission.PermissionService.Companion.testPermission -import net.mamoe.mirai.message.data.* +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.PlainText +import net.mamoe.mirai.message.data.SingleMessage +import net.mamoe.mirai.message.data.buildMessageChain import kotlin.reflect.KAnnotatedElement import kotlin.reflect.KClass import kotlin.reflect.KFunction @@ -89,6 +89,21 @@ internal abstract class AbstractReflectionCommand } + @OptIn(ExperimentalCommandDescriptors::class) + private fun <T : Any> CommandParameter<T>.toCommandValueParameter(): CommandValueParameter<T> { + return CommandValueParameter.UserDefinedType<T>(name, null, false, false, type) + } + + + @OptIn(ExperimentalCommandDescriptors::class) + override val overloads: List<CommandSignatureVariant> by lazy { + subCommands.map { desc -> + CommandSignatureVariantImpl(desc.params.map { it.toCommandValueParameter() }) { call -> + desc.onCommand(call.caller, call.resolvedValueArguments) + } + } + } + interface SubCommandAnnotationResolver { fun hasAnnotation(baseCommand: AbstractReflectionCommand, function: KFunction<*>): Boolean fun getSubCommandNames(baseCommand: AbstractReflectionCommand, function: KFunction<*>): Array<out String> @@ -132,27 +147,12 @@ internal abstract class AbstractReflectionCommand val params: Array<CommandParameter<*>>, val description: String, val permission: Permission, - val onCommand: suspend (sender: CommandSender, parsedArgs: Map<KParameter, Any?>) -> Boolean, + val onCommand: suspend (sender: CommandSender, parsedArgs: List<Any?>) -> Boolean, val context: CommandArgumentContext, val argumentBuilder: (sender: CommandSender) -> MutableMap<KParameter, Any?>, ) { val usage: String = createUsage(this@AbstractReflectionCommand) - internal suspend fun parseAndExecute( - sender: CommandSender, - argsWithSubCommandNameNotRemoved: MessageChain, - removeSubName: Boolean, - ) { - val args = parseArgs(sender, argsWithSubCommandNameNotRemoved, if (removeSubName) 1 else 0) - if (!this.permission.testPermission(sender)) { - sender.sendMessage(usage) // TODO: 2020/8/26 #127 - return - } - if (args == null || !onCommand(sender, args)) { - sender.sendMessage(usage) - } - } - private fun KParameter.isOptional(): Boolean { return isOptional || this.type.isMarkedNullable } @@ -163,49 +163,6 @@ internal abstract class AbstractReflectionCommand @JvmField internal val bakedSubNames: Array<Array<String>> = names.map { it.bakeSubName() }.toTypedArray() - private fun parseArgs(sender: CommandSender, rawArgs: MessageChain, offset: Int): MutableMap<KParameter, Any?>? { - if (rawArgs.size < offset + minimalArgumentsSize) - return null - //require(rawArgs.size >= offset + this.params.size) { "No enough args. Required ${params.size}, but given ${rawArgs.size - offset}" } - return argumentBuilder(sender).also { result -> - params.forEachIndexed { index, parameter -> - val rawArg = rawArgs.getOrNull(offset + index) - result[parameter.parameter] = when (rawArg) { - null -> { - val p = parameter.parameter - when { - p.isOptional -> return@forEachIndexed - p.type.isMarkedNullable -> { - result[parameter.parameter] = null - return@forEachIndexed - } - else -> null - } - } - is PlainText -> context[parameter.type]?.parse(rawArg.content, sender) - is MessageContent -> context[parameter.type]?.parse(rawArg, sender) - else -> throw IllegalArgumentException("Illegal Message kind: ${rawArg.kClassQualifiedNameOrTip}") - } ?: error("Cannot find a parser for $rawArg") - } - } - } - } - - /** - * @param rawArgs 元素类型必须为 [SingleMessage] 或 [String], 且已经经过扁平化处理. 否则抛出异常 [IllegalArgumentException] - */ - internal fun matchSubCommand(rawArgs: MessageChain): SubCommandDescriptor? { - val maxCount = rawArgs.size - var cur = 0 - bakedCommandNameToSubDescriptorArray.forEach { (name, descriptor) -> - if (name.size != cur) { - if (cur++ == maxCount) return null - } - if (name.contentEqualsOffset(rawArgs, length = cur)) { - return descriptor - } - } - return null } } @@ -349,23 +306,23 @@ internal fun AbstractReflectionCommand.createSubCommand( // if (param.isOptional) error("optional parameters are not yet supported. (at ${this::class.qualifiedNameOrTip}.${function.name}.$param)") val paramName = param.findAnnotation<CompositeCommand.Name>()?.value ?: param.name ?: "unknown" - CommandParameter( + CommandParameter<Any>( paramName, - (param.type.classifier as? KClass<*>) - ?: throw IllegalArgumentException("unsolved type reference from param " + param.name + ". (at ${this::class.qualifiedNameOrTip}.${function.name}.$param)"), + param.type, param ) }.toTypedArray() // TODO: 2020/09/19 检查 optional/nullable 是否都在最后 + @Suppress("UNCHECKED_CAST") return SubCommandDescriptor( commandName, - params, + params as Array<CommandParameter<*>>, subDescription, // overridePermission?.value permission,//overridePermission?.value?.let { PermissionService.INSTANCE[PermissionId.parseFromString(it)] } ?: permission, - onCommand = { sender: CommandSender, args: Map<KParameter, Any?> -> - val result = function.callSuspendBy(args) + onCommand = { _: CommandSender, args -> + val result = function.callSuspendBy(parameters.zip(args).toMap()) checkNotNull(result) { "sub command return value is null (at ${this::class.qualifiedName}.${function.name})" } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/executeCommandInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/executeCommandInternal.kt deleted file mode 100644 index a9ff54cca..000000000 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/executeCommandInternal.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2019-2020 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - */ - -package net.mamoe.mirai.console.internal.command - -import net.mamoe.mirai.console.command.* -import net.mamoe.mirai.console.permission.PermissionService.Companion.testPermission -import net.mamoe.mirai.message.data.MessageChain -import net.mamoe.mirai.message.data.asMessageChain - -@JvmSynthetic -@Throws(CommandExecutionException::class) -internal suspend fun CommandSender.executeCommandInternal( - command: Command, - args: MessageChain, - commandName: String, - checkPermission: Boolean, -): CommandExecuteResult { - if (checkPermission && !command.permission.testPermission(this)) { - return CommandExecuteResult.PermissionDenied(command, commandName) - } - - kotlin.runCatching { - command.onCommand(this, args) - }.fold( - onSuccess = { - return CommandExecuteResult.Success( - commandName = commandName, - command = command, - args = args - ) - }, - onFailure = { exception -> - return if (exception is IllegalArgumentException) CommandExecuteResult.IllegalArgument( - commandName = commandName, - command = command, - exception = exception, - args = args - ) else CommandExecuteResult.ExecutionFailed( - commandName = commandName, - command = command, - exception = exception, - args = args - ) - } - ) -} - - -@JvmSynthetic -internal suspend fun CommandSender.executeCommandInternal( - messages: Any, - commandName: String, - checkPermission: Boolean, -): CommandExecuteResult { - val command = - CommandManagerImpl.matchCommand(commandName) ?: return CommandExecuteResult.CommandNotFound(commandName) - val args = messages.flattenCommandComponents() - - return executeCommandInternal(command, args.drop(1).asMessageChain(), commandName, checkPermission) -} diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/MessageUtils.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/MessageUtils.kt new file mode 100644 index 000000000..475481b08 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/MessageUtils.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.util + +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.MessageContent + +@ConsoleExperimentalApi +public object MessageUtils { + @JvmStatic + public fun MessageChain.messageContentsSequence(): Sequence<MessageContent> = asSequence().filterIsInstance<MessageContent>() + + @JvmStatic + public fun MessageChain.firstContent(): MessageContent = messageContentsSequence().first() + + @JvmStatic + public fun MessageChain.firstContentOrNull(): MessageContent? = messageContentsSequence().firstOrNull() +} \ No newline at end of file diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt index b7937225a..bd3b1487f 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt @@ -16,7 +16,6 @@ import kotlinx.coroutines.runBlocking import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.Testing import net.mamoe.mirai.console.Testing.withTesting -import net.mamoe.mirai.console.command.CommandManager.INSTANCE.execute import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register import net.mamoe.mirai.console.command.CommandManager.INSTANCE.registeredCommands From 2084f8154f21afaaeb374cd69167a42a82d59483 Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Tue, 20 Oct 2020 13:27:13 +0800 Subject: [PATCH 14/28] Fix compilation --- .../mirai/console/command/CommandManager.kt | 57 +++++++++++-------- .../command/descriptor/CommandDescriptor.kt | 2 - .../command/resolve/ResolvedCommandCall.kt | 14 ++--- .../mirai/console/command/TestCommand.kt | 10 +++- 4 files changed, 47 insertions(+), 36 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt index 8d99ab6cc..17b992d79 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt @@ -15,6 +15,7 @@ package net.mamoe.mirai.console.command +import net.mamoe.kjbb.JvmBlockingBridge import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.parse.CommandCall @@ -22,7 +23,6 @@ import net.mamoe.mirai.console.command.parse.CommandCallParser import net.mamoe.mirai.console.command.parse.CommandCallParser.Companion.parseCommandCall import net.mamoe.mirai.console.command.resolve.CommandCallResolver import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall -import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall.Companion.call import net.mamoe.mirai.console.extensions.CommandCallResolverProvider import net.mamoe.mirai.console.internal.command.CommandManagerImpl import net.mamoe.mirai.console.internal.command.CommandManagerImpl.executeCommand @@ -122,35 +122,16 @@ public interface CommandManager { * * @return 执行结果 */ - // @JvmBlockingBridge + @JvmBlockingBridge @OptIn(ExperimentalCommandDescriptors::class) public suspend fun executeCommand( caller: CommandSender, message: Message, checkPermission: Boolean = true, ): CommandExecuteResult { - val call = message.asMessageChain().parseCommandCall(caller) ?: return CommandExecuteResult.CommandNotFound("") - val resolved = call.resolve() ?: return CommandExecuteResult.CommandNotFound(call.calleeName) - - val command = resolved.callee - - if (checkPermission && !command.permission.testPermission(caller)) { - return CommandExecuteResult.PermissionDenied(command, call.calleeName) - } - - return kotlin.runCatching { - resolved.call() - }.fold( - onSuccess = { - CommandExecuteResult.Success(resolved.callee, call.calleeName, EmptyMessageChain) - }, - onFailure = { - CommandExecuteResult.ExecutionFailed(it, resolved.callee, call.calleeName, EmptyMessageChain) - } - ) + return executeCommandImpl(this, message, caller, checkPermission) } - /** * 解析并执行一个指令 * @@ -160,7 +141,7 @@ public interface CommandManager { * @return 执行结果 * @see executeCommand */ - // @JvmBlockingBridge + @JvmBlockingBridge public suspend fun CommandSender.executeCommand( message: String, checkPermission: Boolean = true, @@ -191,6 +172,7 @@ public interface CommandManager { public companion object INSTANCE : CommandManager by CommandManagerImpl { // TODO: 2020/8/20 https://youtrack.jetbrains.com/issue/KT-41191 + override val CommandOwner.registeredCommands: List<Command> get() = CommandManagerImpl.run { this@registeredCommands.registeredCommands } override fun CommandOwner.unregisterAllCommands(): Unit = CommandManagerImpl.run { unregisterAllCommands() } override fun Command.register(override: Boolean): Boolean = CommandManagerImpl.run { register(override) } @@ -234,4 +216,31 @@ public suspend fun Command.execute( append(arguments) } return CommandManager.executeCommand(sender, chain, checkPermission) -} \ No newline at end of file +} + + +// Don't move into CommandManager, compilation error / VerifyError +@OptIn(ExperimentalCommandDescriptors::class) +internal suspend fun executeCommandImpl( + receiver: CommandManager, + message: Message, + caller: CommandSender, + checkPermission: Boolean, +): CommandExecuteResult = with(receiver) { + val call = message.asMessageChain().parseCommandCall(caller) ?: return CommandExecuteResult.CommandNotFound("") + val resolved = call.resolve() ?: return CommandExecuteResult.CommandNotFound(call.calleeName) + + val command = resolved.callee + + if (checkPermission && !command.permission.testPermission(caller)) { + return CommandExecuteResult.PermissionDenied(command, call.calleeName) + } + + return try { + resolved.calleeSignature.call(resolved) + CommandExecuteResult.Success(resolved.callee, call.calleeName, EmptyMessageChain) + } catch (e: Throwable) { + CommandExecuteResult.ExecutionFailed(e, resolved.callee, call.calleeName, EmptyMessageChain) + } +} + 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 index 2fdc5f424..3a59735ad 100644 --- 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 @@ -9,7 +9,6 @@ package net.mamoe.mirai.console.command.descriptor -import net.mamoe.kjbb.JvmBlockingBridge import net.mamoe.mirai.console.command.descriptor.ArgumentAcceptance.Companion.isAcceptable import net.mamoe.mirai.console.command.descriptor.CommandValueParameter.UserDefinedType.Companion.createOptional import net.mamoe.mirai.console.command.descriptor.CommandValueParameter.UserDefinedType.Companion.createRequired @@ -30,7 +29,6 @@ import kotlin.reflect.typeOf public interface CommandSignatureVariant { public val valueParameters: List<CommandValueParameter<*>> - @JvmBlockingBridge public suspend fun call(resolvedCommandCall: ResolvedCommandCall) } 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 index e0505279e..9639fc970 100644 --- 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 @@ -50,13 +50,13 @@ public interface ResolvedCommandCall { @ConsoleExperimentalApi public val resolvedValueArguments: List<Any?> - public companion object { - @JvmStatic - @ExperimentalCommandDescriptors - public suspend fun ResolvedCommandCall.call() { - return this.calleeSignature.call(this) - } - } + public companion object +} + +// Don't move into companion, compilation error +@ExperimentalCommandDescriptors +public suspend inline fun ResolvedCommandCall.call() { + return this@call.calleeSignature.call(this@call) } @ExperimentalCommandDescriptors diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt index bd3b1487f..90e55be2a 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt @@ -22,6 +22,7 @@ import net.mamoe.mirai.console.command.CommandManager.INSTANCE.registeredCommand import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregister import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregisterAllCommands import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser +import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.descriptor.buildCommandArgumentContext import net.mamoe.mirai.console.initTestEnvironment import net.mamoe.mirai.console.internal.command.CommandManagerImpl @@ -87,9 +88,11 @@ internal class TestCommand { @Test fun testSimpleExecute() = runBlocking { - assertEquals("test", withTesting<MessageChain> { - assertSuccess(TestSimpleCommand.execute(sender, "test")) - }.contentToString()) + TestSimpleCommand.withRegistration { + assertEquals("test", withTesting<MessageChain> { + assertSuccess(TestSimpleCommand.execute(sender, "test")) + }.contentToString()) + } } @Test @@ -263,6 +266,7 @@ internal class TestCommand { } } +@OptIn(ExperimentalCommandDescriptors::class) internal fun assertSuccess(result: CommandExecuteResult) { if (result.isFailure()) { throw result.exception ?: AssertionError(result.toString()) From 58af1b335449ad8f83191bc635883487faa67701 Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Tue, 20 Oct 2020 13:47:43 +0800 Subject: [PATCH 15/28] Introduce InstanceExtensionPoint --- .../parse/SpaceSeparatedCommandCallParser.kt | 3 ++ .../resolve/BuiltInCommandCallResolver.kt | 3 ++ .../mirai/console/extension/ExtensionPoint.kt | 6 +++ .../extensions/CommandCallParserProvider.kt | 8 ++-- .../extensions/CommandCallResolverProvider.kt | 8 ++-- .../extensions/SingletonExtensionSelector.kt | 6 +-- .../BuiltInSingletonExtensionSelector.kt | 2 +- .../extension/ComponentStorageInternal.kt | 42 +++++++++++++++---- .../internal/plugin/PluginManagerImpl.kt | 2 +- 9 files changed, 60 insertions(+), 20 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/parse/SpaceSeparatedCommandCallParser.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/parse/SpaceSeparatedCommandCallParser.kt index 62e6a5370..d5812ff66 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/parse/SpaceSeparatedCommandCallParser.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/parse/SpaceSeparatedCommandCallParser.kt @@ -2,6 +2,7 @@ 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.command.flattenCommandComponents import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.message.data.MessageChain @@ -20,4 +21,6 @@ public object SpaceSeparatedCommandCallParser : CommandCallParser { valueArguments = flatten.drop(1).map(::InvariantCommandValueArgument) ) } + + public object Provider : CommandCallParserProvider(SpaceSeparatedCommandCallParser) } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/BuiltInCommandCallResolver.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/BuiltInCommandCallResolver.kt index 5ff77e7dc..280c05a22 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/BuiltInCommandCallResolver.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/BuiltInCommandCallResolver.kt @@ -6,6 +6,7 @@ import net.mamoe.mirai.console.command.descriptor.* import net.mamoe.mirai.console.command.descriptor.ArgumentAcceptance.Companion.isNotAcceptable import net.mamoe.mirai.console.command.parse.CommandCall import net.mamoe.mirai.console.command.parse.CommandValueArgument +import net.mamoe.mirai.console.extensions.CommandCallResolverProvider import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.cast import net.mamoe.mirai.console.util.safeCast @@ -14,6 +15,8 @@ import java.util.* @ConsoleExperimentalApi @ExperimentalCommandDescriptors public object BuiltInCommandCallResolver : CommandCallResolver { + public object Provider : CommandCallResolverProvider(BuiltInCommandCallResolver) + override fun resolve(call: CommandCall): ResolvedCommandCall? { val callee = CommandManager.matchCommand(call.calleeName) ?: return null diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/ExtensionPoint.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/ExtensionPoint.kt index da9d0ff80..d076776c0 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/ExtensionPoint.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/ExtensionPoint.kt @@ -24,6 +24,12 @@ public open class AbstractExtensionPoint<T : Extension>( public override val extensionType: KClass<T>, ) : ExtensionPoint<T> +public open class InstanceExtensionPoint<E : InstanceExtension<T>, T>( + extensionType: KClass<E>, + public vararg val builtinImplementations: E, +) : AbstractExtensionPoint<E>(extensionType) + + /** * 表示一个 [SingletonExtension] 的 [ExtensionPoint] */ diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/CommandCallParserProvider.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/CommandCallParserProvider.kt index a3870a932..4a19a0fac 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/CommandCallParserProvider.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/CommandCallParserProvider.kt @@ -11,13 +11,15 @@ 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.command.parse.SpaceSeparatedCommandCallParser import net.mamoe.mirai.console.extension.InstanceExtension +import net.mamoe.mirai.console.extension.InstanceExtensionPoint /** * The provider of [CommandCallParser] */ @ExperimentalCommandDescriptors -public interface CommandCallParserProvider : InstanceExtension<CommandCallParser> { - public companion object ExtensionPoint : AbstractExtensionPoint<CommandCallParserProvider>(CommandCallParserProvider::class) +public open class CommandCallParserProvider(override val instance: CommandCallParser) : InstanceExtension<CommandCallParser> { + public companion object ExtensionPoint : + InstanceExtensionPoint<CommandCallParserProvider, CommandCallParser>(CommandCallParserProvider::class, SpaceSeparatedCommandCallParser.Provider) } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/CommandCallResolverProvider.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/CommandCallResolverProvider.kt index 85fcfb415..e6e390d99 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/CommandCallResolverProvider.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/CommandCallResolverProvider.kt @@ -10,11 +10,13 @@ package net.mamoe.mirai.console.extensions import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors +import net.mamoe.mirai.console.command.resolve.BuiltInCommandCallResolver import net.mamoe.mirai.console.command.resolve.CommandCallResolver -import net.mamoe.mirai.console.extension.AbstractExtensionPoint import net.mamoe.mirai.console.extension.InstanceExtension +import net.mamoe.mirai.console.extension.InstanceExtensionPoint @ExperimentalCommandDescriptors -public interface CommandCallResolverProvider : InstanceExtension<CommandCallResolver> { - public companion object ExtensionPoint : AbstractExtensionPoint<CommandCallResolverProvider>(CommandCallResolverProvider::class) +public open class CommandCallResolverProvider(override val instance: CommandCallResolver) : InstanceExtension<CommandCallResolver> { + public companion object ExtensionPoint : + InstanceExtensionPoint<CommandCallResolverProvider, CommandCallResolver>(CommandCallResolverProvider::class, BuiltInCommandCallResolver.Provider) } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/SingletonExtensionSelector.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/SingletonExtensionSelector.kt index e5fd07fb8..2dfce8f6c 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/SingletonExtensionSelector.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/SingletonExtensionSelector.kt @@ -29,7 +29,7 @@ import kotlin.reflect.KClass */ public interface SingletonExtensionSelector : FunctionExtension { public data class Registry<T : Extension>( - val plugin: Plugin, + val plugin: Plugin?, val extension: T, ) @@ -55,11 +55,11 @@ public interface SingletonExtensionSelector : FunctionExtension { instances.isEmpty() -> BuiltInSingletonExtensionSelector instances.size == 1 -> { instances.single().also { (plugin, ext) -> - MiraiConsole.mainLogger.info { "Loaded SingletonExtensionSelector: $ext from ${plugin.name}" } + MiraiConsole.mainLogger.info { "Loaded SingletonExtensionSelector: $ext from ${plugin?.name ?: "<builtin>"}" } }.extension } else -> { - error("Found too many SingletonExtensionSelectors: ${instances.joinToString { (p, i) -> "'$i' from '${p.name}'" }}. Check your plugins and ensure there is only one external SingletonExtensionSelectors") + error("Found too many SingletonExtensionSelectors: ${instances.joinToString { (p, i) -> "'$i' from '${p?.name ?: "<builtin>"}'" }}. Check your plugins and ensure there is only one external SingletonExtensionSelectors") } } } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/extension/BuiltInSingletonExtensionSelector.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/extension/BuiltInSingletonExtensionSelector.kt index 60bb09cd8..d096341ed 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/extension/BuiltInSingletonExtensionSelector.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/extension/BuiltInSingletonExtensionSelector.kt @@ -52,7 +52,7 @@ internal object BuiltInSingletonExtensionSelector : SingletonExtensionSelector { val candidatesList = candidates.toList() for ((index, candidate) in candidatesList.withIndex()) { - MiraiConsole.mainLogger.info { "${index + 1}. '${candidate.extension}' from '${candidate.plugin.name}'" } + MiraiConsole.mainLogger.info { "${index + 1}. '${candidate.extension}' from '${candidate.plugin?.name ?: "<builtin>"}'" } } MiraiConsole.mainLogger.info { "Please choose a number from 1 to ${candidatesList.count()}" } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/extension/ComponentStorageInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/extension/ComponentStorageInternal.kt index d84fa4022..b3072ad49 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/extension/ComponentStorageInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/extension/ComponentStorageInternal.kt @@ -25,10 +25,10 @@ import kotlin.reflect.KClass */ internal object GlobalComponentStorage : AbstractConcurrentComponentStorage() internal interface ExtensionRegistry<out E : Extension> { - val plugin: Plugin + val plugin: Plugin? val extension: E - operator fun component1(): Plugin { + operator fun component1(): Plugin? { return this.plugin } @@ -38,21 +38,27 @@ internal interface ExtensionRegistry<out E : Extension> { } internal class LazyExtensionRegistry<out E : Extension>( - override val plugin: Plugin, + override val plugin: Plugin?, initializer: () -> E, ) : ExtensionRegistry<E> { override val extension: E by lazy { initializer() } } internal data class DataExtensionRegistry<out E : Extension>( - override val plugin: Plugin, + override val plugin: Plugin?, override val extension: E, ) : ExtensionRegistry<E> internal abstract class AbstractConcurrentComponentStorage : ComponentStorage { @Suppress("UNCHECKED_CAST") internal fun <T : Extension> ExtensionPoint<out T>.getExtensions(): Set<ExtensionRegistry<T>> { - return instances.getOrPut(this, ::CopyOnWriteArraySet) as Set<ExtensionRegistry<T>> + val userDefined = instances.getOrPut(this, ::CopyOnWriteArraySet) as Set<ExtensionRegistry<T>> + + val builtins = if (this is InstanceExtensionPoint<*, *>) { + this.builtinImplementations.mapTo(HashSet()) { DataExtensionRegistry(null, it) } as Set<ExtensionRegistry<T>> + } else null + + return builtins?.plus(userDefined) ?: userDefined } internal fun mergeWith(another: AbstractConcurrentComponentStorage) { @@ -71,7 +77,7 @@ internal abstract class AbstractConcurrentComponentStorage : ComponentStorage { @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") @kotlin.internal.LowPriorityInOverloadResolution - internal inline fun <T : Extension> ExtensionPoint<out T>.withExtensions(block: T.(plugin: Plugin) -> Unit) { + internal inline fun <T : Extension> ExtensionPoint<out T>.withExtensions(block: T.(plugin: Plugin?) -> Unit) { contract { callsInPlace(block) } @@ -131,11 +137,11 @@ internal abstract class AbstractConcurrentComponentStorage : ComponentStorage { internal fun <T : Extension> ExtensionPoint<out T>.throwExtensionException( extension: T, - plugin: Plugin, + plugin: Plugin?, throwable: Throwable, ) { throw ExtensionException( - "Exception while executing extension '${extension.kClassQualifiedNameOrTip}' provided by plugin '${plugin.name}', registered for '${this.extensionType.qualifiedName}'", + "Exception while executing extension '${extension.kClassQualifiedNameOrTip}' provided by plugin '${plugin?.name ?: "<builtin>"}', registered for '${this.extensionType.qualifiedName}'", throwable ) } @@ -145,7 +151,7 @@ internal abstract class AbstractConcurrentComponentStorage : ComponentStorage { @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") @kotlin.internal.LowPriorityInOverloadResolution - internal inline fun <T : Extension> ExtensionPoint<T>.useExtensions(block: (extension: T, plugin: Plugin) -> Unit): Unit = + internal inline fun <T : Extension> ExtensionPoint<T>.useExtensions(block: (extension: T, plugin: Plugin?) -> Unit): Unit = withExtensions(block) val instances: MutableMap<ExtensionPoint<*>, MutableSet<ExtensionRegistry<*>>> = ConcurrentHashMap() @@ -157,6 +163,15 @@ internal abstract class AbstractConcurrentComponentStorage : ComponentStorage { instances.getOrPut(extensionPoint, ::CopyOnWriteArraySet).add(DataExtensionRegistry(plugin, extensionInstance)) } + @JvmName("contribute1") + fun <T : Extension> contribute( + extensionPoint: ExtensionPoint<T>, + plugin: Plugin?, + extensionInstance: T, + ) { + instances.getOrPut(extensionPoint, ::CopyOnWriteArraySet).add(DataExtensionRegistry(plugin, extensionInstance)) + } + override fun <T : Extension> contribute( extensionPoint: ExtensionPoint<T>, plugin: Plugin, @@ -164,4 +179,13 @@ internal abstract class AbstractConcurrentComponentStorage : ComponentStorage { ) { instances.getOrPut(extensionPoint, ::CopyOnWriteArraySet).add(LazyExtensionRegistry(plugin, lazyInstance)) } + + @JvmName("contribute1") + fun <T : Extension> contribute( + extensionPoint: ExtensionPoint<T>, + plugin: Plugin?, + lazyInstance: () -> T, + ) { + instances.getOrPut(extensionPoint, ::CopyOnWriteArraySet).add(LazyExtensionRegistry(plugin, lazyInstance)) + } } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginManagerImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginManagerImpl.kt index 2f15ba42a..68045ff93 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginManagerImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginManagerImpl.kt @@ -148,7 +148,7 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol var count = 0 GlobalComponentStorage.run { PluginLoaderProvider.useExtensions { ext, plugin -> - logger.info { "Loaded PluginLoader ${ext.instance} from ${plugin.name}" } + logger.info { "Loaded PluginLoader ${ext.instance} from ${plugin?.name ?: "<builtin>"}" } _pluginLoaders.add(ext.instance) count++ } From 3e2a5c382e79a56225c4d48d43307f19e0fdd4fe Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Tue, 20 Oct 2020 13:54:05 +0800 Subject: [PATCH 16/28] Introduce default type variants --- .../mirai/console/command/CommandManager.kt | 1 + .../console/command/descriptor/TypeVariant.kt | 19 ++++++++++++++++++- .../command/parse/CommandValueArgument.kt | 17 ++++++++++------- .../parse/SpaceSeparatedCommandCallParser.kt | 2 +- 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt index 17b992d79..73970d2d6 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt @@ -211,6 +211,7 @@ public suspend fun Command.execute( ): CommandExecuteResult { // TODO: 2020/10/18 net.mamoe.mirai.console.command.CommandManager.execute val chain = buildMessageChain { + append(CommandManager.commandPrefix) append(this@execute.primaryName) append(' ') append(arguments) 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 index 81a47fc65..86761807d 100644 --- 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 @@ -12,7 +12,10 @@ package net.mamoe.mirai.console.command.descriptor import net.mamoe.mirai.console.command.parse.CommandCall import net.mamoe.mirai.console.command.parse.CommandCallParser import net.mamoe.mirai.console.command.parse.RawCommandArgument +import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageContent +import net.mamoe.mirai.message.data.asMessageChain +import net.mamoe.mirai.message.data.content import kotlin.reflect.KType import kotlin.reflect.typeOf @@ -45,6 +48,20 @@ public interface TypeVariant<out OutType> { @ExperimentalCommandDescriptors public object MessageContentTypeVariant : TypeVariant<MessageContent> { @OptIn(ExperimentalStdlibApi::class) - override val outType: KType = typeOf<String>() + override val outType: KType = typeOf<MessageContent>() override fun mapValue(valueParameter: MessageContent): MessageContent = valueParameter } + +@ExperimentalCommandDescriptors +public object MessageChainTypeVariant : TypeVariant<MessageChain> { + @OptIn(ExperimentalStdlibApi::class) + override val outType: KType = typeOf<MessageChain>() + override fun mapValue(valueParameter: MessageContent): MessageChain = valueParameter.asMessageChain() +} + +@ExperimentalCommandDescriptors +public object ContentStringTypeVariant : TypeVariant<String> { + @OptIn(ExperimentalStdlibApi::class) + override val outType: KType = typeOf<String>() + override fun mapValue(valueParameter: MessageContent): String = valueParameter.content +} 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 index f9dfe4588..3f1f0e1a7 100644 --- 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 @@ -9,10 +9,8 @@ package net.mamoe.mirai.console.command.parse -import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors -import net.mamoe.mirai.console.command.descriptor.MessageContentTypeVariant -import net.mamoe.mirai.console.command.descriptor.NoValueArgumentMappingException -import net.mamoe.mirai.console.command.descriptor.TypeVariant +import net.mamoe.mirai.console.command.descriptor.* +import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.message.data.MessageContent import kotlin.reflect.KType import kotlin.reflect.full.isSubtypeOf @@ -31,7 +29,7 @@ public typealias RawCommandArgument = MessageContent public interface CommandArgument /** - * @see InvariantCommandValueArgument + * @see DefaultCommandValueArgument */ @ExperimentalCommandDescriptors public interface CommandValueArgument : CommandArgument { @@ -43,13 +41,18 @@ public interface CommandValueArgument : CommandArgument { /** * The [CommandValueArgument] that doesn't vary in type (remaining [MessageContent]). */ +@ConsoleExperimentalApi @ExperimentalCommandDescriptors -public data class InvariantCommandValueArgument( +public data class DefaultCommandValueArgument( public override val value: RawCommandArgument, ) : CommandValueArgument { @OptIn(ExperimentalStdlibApi::class) override val type: KType = typeOf<MessageContent>() - override val typeVariants: List<TypeVariant<*>> = listOf(MessageContentTypeVariant) + override val typeVariants: List<TypeVariant<*>> = listOf( + MessageContentTypeVariant, + MessageChainTypeVariant, + ContentStringTypeVariant, + ) } @ExperimentalCommandDescriptors diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/parse/SpaceSeparatedCommandCallParser.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/parse/SpaceSeparatedCommandCallParser.kt index d5812ff66..8cb917344 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/parse/SpaceSeparatedCommandCallParser.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/parse/SpaceSeparatedCommandCallParser.kt @@ -18,7 +18,7 @@ public object SpaceSeparatedCommandCallParser : CommandCallParser { return CommandCallImpl( caller = caller, calleeName = flatten.first().content, - valueArguments = flatten.drop(1).map(::InvariantCommandValueArgument) + valueArguments = flatten.drop(1).map(::DefaultCommandValueArgument) ) } From 3d4f31759fcaaa0cf465cb7af367fb872130c767 Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Tue, 20 Oct 2020 14:07:08 +0800 Subject: [PATCH 17/28] Support conversion with CommandArgumentContext --- .../console/command/parse/CommandValueArgument.kt | 6 ++++-- .../command/resolve/BuiltInCommandCallResolver.kt | 2 +- .../console/command/resolve/ResolvedCommandCall.kt | 9 +++++++-- .../net/mamoe/mirai/console/command/TestCommand.kt | 11 ++++++----- 4 files changed, 18 insertions(+), 10 deletions(-) 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 index 3f1f0e1a7..b20c14e00 100644 --- 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 @@ -7,6 +7,8 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:Suppress("unused") + package net.mamoe.mirai.console.command.parse import net.mamoe.mirai.console.command.descriptor.* @@ -74,8 +76,8 @@ public fun <T> CommandValueArgument.mapToTypeOrNull(expectingType: KType): T? { @OptIn(ExperimentalStdlibApi::class) val result = typeVariants .filter { it.outType.isSubtypeOf(expectingType) } - .also { - if (it.isEmpty()) return null + .ifEmpty { + return null } .reduce { acc, typeVariant -> if (acc.outType.isSubtypeOf(typeVariant.outType)) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/BuiltInCommandCallResolver.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/BuiltInCommandCallResolver.kt index 280c05a22..d1cdf647e 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/BuiltInCommandCallResolver.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/BuiltInCommandCallResolver.kt @@ -25,7 +25,7 @@ public object BuiltInCommandCallResolver : CommandCallResolver { val signature = resolveImpl(callee, valueArguments, context) ?: return null - return ResolvedCommandCallImpl(call.caller, callee, signature, call.valueArguments) + return ResolvedCommandCallImpl(call.caller, callee, signature, call.valueArguments, context ?: EmptyCommandArgumentContext) } private data class ResolveData( 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 index 9639fc970..2feaa94f9 100644 --- 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 @@ -12,11 +12,14 @@ 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.CompositeCommand +import net.mamoe.mirai.console.command.descriptor.CommandArgumentContext import net.mamoe.mirai.console.command.descriptor.CommandSignatureVariant import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors +import net.mamoe.mirai.console.command.descriptor.NoValueArgumentMappingException import net.mamoe.mirai.console.command.parse.CommandCall import net.mamoe.mirai.console.command.parse.CommandValueArgument -import net.mamoe.mirai.console.command.parse.mapToType +import net.mamoe.mirai.console.command.parse.mapToTypeOrNull +import net.mamoe.mirai.console.internal.data.classifierAsKClass import net.mamoe.mirai.console.util.ConsoleExperimentalApi import kotlin.LazyThreadSafetyMode.PUBLICATION @@ -65,10 +68,12 @@ public class ResolvedCommandCallImpl( override val callee: Command, override val calleeSignature: CommandSignatureVariant, override val rawValueArguments: List<CommandValueArgument>, + private val context: CommandArgumentContext, ) : ResolvedCommandCall { override val resolvedValueArguments: List<Any?> by lazy(PUBLICATION) { calleeSignature.valueParameters.zip(rawValueArguments).map { (parameter, argument) -> - argument.mapToType(parameter.type) + argument.mapToTypeOrNull(parameter.type) ?: context[parameter.type.classifierAsKClass()]?.parse(argument.value, caller) + ?: throw NoValueArgumentMappingException(argument, parameter.type) // TODO: 2020/10/17 consider vararg and optional } } diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt index 90e55be2a..76c680bbf 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt @@ -136,12 +136,13 @@ internal class TestCommand { @Test fun `executing command by string command`() = runBlocking { - TestCompositeCommand.register() - val result = withTesting<Int> { - assertSuccess(sender.executeCommand("/testComposite mute 1")) - } + TestCompositeCommand.withRegistration { + val result = withTesting<Int> { + assertSuccess(sender.executeCommand("/testComposite mute 1")) + } - assertEquals(1, result) + assertEquals(1, result) + } } @Test From 4c30e3d9d7a29fe0232cb03284c0bbe537472c25 Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Thu, 22 Oct 2020 13:40:22 +0800 Subject: [PATCH 18/28] Add docs --- .../console/command/resolve/BuiltInCommandCallResolver.kt | 3 +++ .../mirai/console/command/resolve/CommandCallResolver.kt | 4 ++++ .../kotlin/net/mamoe/mirai/console/util/StandardUtils.kt | 8 ++++++++ 3 files changed, 15 insertions(+) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/BuiltInCommandCallResolver.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/BuiltInCommandCallResolver.kt index d1cdf647e..58d420c56 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/BuiltInCommandCallResolver.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/BuiltInCommandCallResolver.kt @@ -12,6 +12,9 @@ import net.mamoe.mirai.console.util.cast import net.mamoe.mirai.console.util.safeCast import java.util.* +/** + * Builtin implementation of [CommandCallResolver] + */ @ConsoleExperimentalApi @ExperimentalCommandDescriptors public object BuiltInCommandCallResolver : CommandCallResolver { 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 index 6e8eda94e..b4671412f 100644 --- 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 @@ -11,9 +11,13 @@ package net.mamoe.mirai.console.command.resolve import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.parse.CommandCall +import net.mamoe.mirai.console.extensions.CommandCallResolverProvider /** * The resolver converting a [CommandCall] into [ResolvedCommandCall] based on registered [] + * + * @see CommandCallResolverProvider The provider to instances of this class + * @see BuiltInCommandCallResolver The builtin implementation */ @ExperimentalCommandDescriptors public interface CommandCallResolver { 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 index c2eb22a0c..0d7dc8630 100644 --- 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 @@ -11,6 +11,10 @@ package net.mamoe.mirai.console.util import kotlin.contracts.contract +/** + * Perform `this as? T`. + */ +@JvmSynthetic public inline fun <reified T : Any> Any?.safeCast(): T? { contract { returnsNotNull() implies (this@safeCast is T) @@ -18,6 +22,10 @@ public inline fun <reified T : Any> Any?.safeCast(): T? { return this as? T } +/** + * Perform `this as T`. + */ +@JvmSynthetic public inline fun <reified T : Any> Any?.cast(): T { contract { returns() implies (this@cast is T) From 084e2c5c55bf16a821f9d4be3eb889fb1bf1e509 Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Fri, 23 Oct 2020 13:40:16 +0800 Subject: [PATCH 19/28] Fix command call --- .../console/command/CommandExecuteResult.kt | 14 ++--- .../mirai/console/command/CommandManager.kt | 4 +- .../mirai/console/command/SimpleCommand.kt | 12 +++-- .../internal/command/CommandManagerImpl.kt | 6 +-- .../command/CompositeCommandInternal.kt | 10 ++-- .../mirai/console/command/TestCommand.kt | 51 +++++++++++-------- 6 files changed, 56 insertions(+), 41 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandExecuteResult.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandExecuteResult.kt index 94b9b1f66..9697dd4d7 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandExecuteResult.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandExecuteResult.kt @@ -90,9 +90,9 @@ public sealed class CommandExecuteResult { } /** 没有匹配的指令 */ - public class CommandNotFound( + public class UnresolvedCall( /** 尝试执行的指令名 */ - public override val commandName: String + public override val commandName: String, ) : CommandExecuteResult() { /** 指令执行时发生的错误, 总是 `null` */ public override val exception: Nothing? get() = null @@ -196,19 +196,19 @@ public fun CommandExecuteResult.isPermissionDenied(): Boolean { } /** - * 当 [this] 为 [CommandExecuteResult.CommandNotFound] 时返回 `true` + * 当 [this] 为 [CommandExecuteResult.UnresolvedCall] 时返回 `true` */ @JvmSynthetic public fun CommandExecuteResult.isCommandNotFound(): Boolean { contract { - returns(true) implies (this@isCommandNotFound is CommandExecuteResult.CommandNotFound) - returns(false) implies (this@isCommandNotFound !is CommandExecuteResult.CommandNotFound) + returns(true) implies (this@isCommandNotFound is CommandExecuteResult.UnresolvedCall) + returns(false) implies (this@isCommandNotFound !is CommandExecuteResult.UnresolvedCall) } - return this is CommandExecuteResult.CommandNotFound + return this is CommandExecuteResult.UnresolvedCall } /** - * 当 [this] 为 [CommandExecuteResult.ExecutionFailed], [CommandExecuteResult.IllegalArgument] 或 [CommandExecuteResult.CommandNotFound] 时返回 `true` + * 当 [this] 为 [CommandExecuteResult.ExecutionFailed], [CommandExecuteResult.IllegalArgument] 或 [CommandExecuteResult.UnresolvedCall] 时返回 `true` */ @JvmSynthetic public fun CommandExecuteResult.isFailure(): Boolean { diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt index 73970d2d6..f5d0ad07d 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt @@ -228,8 +228,8 @@ internal suspend fun executeCommandImpl( caller: CommandSender, checkPermission: Boolean, ): CommandExecuteResult = with(receiver) { - val call = message.asMessageChain().parseCommandCall(caller) ?: return CommandExecuteResult.CommandNotFound("") - val resolved = call.resolve() ?: return CommandExecuteResult.CommandNotFound(call.calleeName) + val call = message.asMessageChain().parseCommandCall(caller) ?: return CommandExecuteResult.UnresolvedCall("") + val resolved = call.resolve() ?: return CommandExecuteResult.UnresolvedCall(call.calleeName) val command = resolved.callee diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt index 60e67ca32..9288f3430 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt @@ -62,12 +62,16 @@ public abstract class SimpleCommand( CommandArgumentContextAware { @ExperimentalCommandDescriptors - override val overloads: List<CommandSignatureVariant> = listOf( - CommandSignatureVariantImpl(listOf(CommandValueParameter.UserDefinedType.createRequired<MessageChain>("args", true))) { call -> + override val overloads: List<CommandSignatureVariant> by lazy { + CommandSignatureVariantImpl( + valueParameters = subCommands.single().params.map { + CommandValueParameter.UserDefinedType(it.name, null, isOptional = false, isVararg = false, type = it.type) + } + ) { call -> val sender = call.caller subCommands.single().onCommand(sender, call.resolvedValueArguments) - } - ) + }.let { listOf(it) } + } /** * 自动根据带有 [Handler] 注解的函数签名生成 [usage]. 也可以被覆盖. diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandManagerImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandManagerImpl.kt index 11b55aeb1..dcffd9aaa 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandManagerImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandManagerImpl.kt @@ -81,7 +81,7 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by MiraiCons sender.catchExecutionException(result.exception) intercept() } - is CommandExecuteResult.CommandNotFound -> { + is CommandExecuteResult.UnresolvedCall -> { // noop } } @@ -140,11 +140,11 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by MiraiCons override fun Command.unregister(): Boolean = modifyLock.withLock { if (this.prefixOptional) { this.allNames.forEach { - optionalPrefixCommandMap.remove(it) + optionalPrefixCommandMap.remove(it.toLowerCase()) } } this.allNames.forEach { - requiredPrefixCommandMap.remove(it) + requiredPrefixCommandMap.remove(it.toLowerCase()) } _registeredCommands.remove(this) } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommandInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommandInternal.kt index ede2cef02..a401e3c19 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommandInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommandInternal.kt @@ -13,6 +13,7 @@ package net.mamoe.mirai.console.internal.command import net.mamoe.mirai.console.command.* import net.mamoe.mirai.console.command.descriptor.* +import net.mamoe.mirai.console.internal.command.hasAnnotation import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.PlainText @@ -22,10 +23,7 @@ import kotlin.reflect.KAnnotatedElement import kotlin.reflect.KClass import kotlin.reflect.KFunction import kotlin.reflect.KParameter -import kotlin.reflect.full.callSuspendBy -import kotlin.reflect.full.declaredFunctions -import kotlin.reflect.full.findAnnotation -import kotlin.reflect.full.isSubclassOf +import kotlin.reflect.full.* internal object CompositeCommandSubCommandAnnotationResolver : AbstractReflectionCommand.SubCommandAnnotationResolver { @@ -322,7 +320,9 @@ internal fun AbstractReflectionCommand.createSubCommand( subDescription, // overridePermission?.value permission,//overridePermission?.value?.let { PermissionService.INSTANCE[PermissionId.parseFromString(it)] } ?: permission, onCommand = { _: CommandSender, args -> - val result = function.callSuspendBy(parameters.zip(args).toMap()) + val p = parameters.zip(args).toMap(LinkedHashMap()) + if (notStatic) p[function.instanceParameter!!] = this@createSubCommand + val result = function.callSuspendBy(p) checkNotNull(result) { "sub command return value is null (at ${this::class.qualifiedName}.${function.name})" } diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt index 76c680bbf..4e11719bc 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt @@ -73,6 +73,7 @@ internal class TestCommand { fun testRegister() { try { ConsoleCommandOwner.unregisterAllCommands() // builtins + TestSimpleCommand.unregister() assertTrue(TestCompositeCommand.register()) assertFalse(TestCompositeCommand.register()) @@ -80,7 +81,9 @@ internal class TestCommand { assertEquals(1, ConsoleCommandOwner.registeredCommands.size) assertEquals(1, CommandManagerImpl._registeredCommands.size) - assertEquals(2, CommandManagerImpl.requiredPrefixCommandMap.size) + assertEquals(2, + CommandManagerImpl.requiredPrefixCommandMap.size, + CommandManagerImpl.requiredPrefixCommandMap.entries.joinToString { it.toString() }) } finally { TestCompositeCommand.unregister() } @@ -107,24 +110,28 @@ internal class TestCommand { @Test fun testSimpleArgsSplitting() = runBlocking { - assertEquals(arrayOf("test", "ttt", "tt").joinToString(), withTesting<MessageChain> { - assertSuccess(TestSimpleCommand.execute(sender, PlainText("test ttt tt"))) - }.joinToString()) + TestSimpleCommand.withRegistration { + assertEquals(arrayOf("test", "ttt", "tt").joinToString(), withTesting<MessageChain> { + assertSuccess(TestSimpleCommand.execute(sender, PlainText("test ttt tt"))) + }.joinToString()) + } } val image = Image("/f8f1ab55-bf8e-4236-b55e-955848d7069f") @Test fun `PlainText and Image args splitting`() = runBlocking { - val result = withTesting<MessageChain> { - assertSuccess(TestSimpleCommand.execute(sender, buildMessageChain { - +"test" - +image - +"tt" - })) + TestSimpleCommand.withRegistration { + val result = withTesting<MessageChain> { + assertSuccess(TestSimpleCommand.execute(sender, buildMessageChain { + +"test" + +image + +"tt" + })) + } + assertEquals<Any>(arrayOf("test", image, "tt").joinToString(), result.toTypedArray().joinToString()) + assertSame(image, result[1]) } - assertEquals<Any>(arrayOf("test", image, "tt").joinToString(), result.toTypedArray().joinToString()) - assertSame(image, result[1]) } @Test @@ -147,9 +154,11 @@ internal class TestCommand { @Test fun `composite command executing`() = runBlocking { - assertEquals(1, withTesting { - assertSuccess(TestCompositeCommand.execute(sender, "mute 1")) - }) + TestCompositeCommand.withRegistration { + assertEquals(1, withTesting { + assertSuccess(TestCompositeCommand.execute(sender, "mute 1")) + }) + } } @Test @@ -187,7 +196,7 @@ internal class TestCommand { fun `composite sub command parsing`() { runBlocking { class MyClass( - val value: Int + val value: Int, ) val composite = object : CompositeCommand( @@ -215,10 +224,12 @@ internal class TestCommand { composite.withRegistration { assertEquals(333, withTesting<MyClass> { execute(sender, "mute 333") }.value) assertEquals(2, withTesting<MyClass> { - execute(sender, buildMessageChain { - +"mute" - +image - }) + assertSuccess( + execute(sender, buildMessageChain { + +"mute" + +image + }) + ) }.value) } } From 8b75e47f58701c53758ff6c8b0d523273900ed4c Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Fri, 23 Oct 2020 21:00:25 +0800 Subject: [PATCH 20/28] Gradle 6.7 --- gradle.properties | 1 + gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 6f6711e1f..20544ecbe 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,3 @@ # style guide kotlin.code.style=official +org.gradle.vfs.watch=true diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 7743a94c2..32b5708ed 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ #Wed Mar 04 22:27:09 CST 2020 -distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStorePath=wrapper/dists From 4aa996a41703aca2885c37df647027a44b89d11e Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Fri, 23 Oct 2020 21:32:04 +0800 Subject: [PATCH 21/28] Rework command reflection: - Remove AbstractReflectionCommand - Introduce CommandReflector - Misc improvements --- .../mirai/console/command/CommandSender.kt | 2 +- .../mirai/console/command/CompositeCommand.kt | 46 +-- .../mamoe/mirai/console/command/RawCommand.kt | 11 +- .../mirai/console/command/SimpleCommand.kt | 49 ++- .../command/descriptor/CommandDescriptor.kt | 69 +++- .../console/command/descriptor/Exceptions.kt | 2 +- .../mirai/console/data/AutoSavePluginData.kt | 2 +- .../internal/command/CommandManagerImpl.kt | 6 +- .../internal/command/CommandReflector.kt | 215 +++++++++++ .../command/CompositeCommandInternal.kt | 334 ------------------ .../data/MultiFilePluginDataStorageImpl.kt | 1 - .../console/internal/data/reflectionUtils.kt | 11 +- .../internal/data/valueFromKTypeImpl.kt | 1 - .../mamoe/mirai/console/util/ContactUtils.kt | 2 +- .../mirai/console/command/TestCommand.kt | 2 +- 15 files changed, 332 insertions(+), 421 deletions(-) create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandReflector.kt delete mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommandInternal.kt diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt index 8c3b1d8ed..f3f9b55ee 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt @@ -26,8 +26,8 @@ import net.mamoe.mirai.console.command.CommandSender.Companion.asTempCommandSend import net.mamoe.mirai.console.command.CommandSender.Companion.toCommandSender import net.mamoe.mirai.console.command.descriptor.CommandArgumentParserException import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge -import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip import net.mamoe.mirai.console.internal.data.castOrNull +import net.mamoe.mirai.console.internal.data.qualifiedNameOrTip import net.mamoe.mirai.console.internal.plugin.rootCauseOrSelf import net.mamoe.mirai.console.permission.AbstractPermitteeId import net.mamoe.mirai.console.permission.Permittee diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt index 7f29eb4a7..c0b75ec89 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt @@ -20,11 +20,10 @@ package net.mamoe.mirai.console.command import net.mamoe.mirai.console.command.descriptor.* import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME -import net.mamoe.mirai.console.internal.command.AbstractReflectionCommand -import net.mamoe.mirai.console.internal.command.CompositeCommandSubCommandAnnotationResolver +import net.mamoe.mirai.console.internal.command.CommandReflector +import net.mamoe.mirai.console.internal.command.SimpleCommandSubCommandAnnotationResolver import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.console.util.ConsoleExperimentalApi -import net.mamoe.mirai.message.data.MessageChain import kotlin.annotation.AnnotationRetention.RUNTIME import kotlin.annotation.AnnotationTarget.FUNCTION @@ -90,13 +89,23 @@ public abstract class CompositeCommand( parentPermission: Permission = owner.parentPermission, prefixOptional: Boolean = false, overrideContext: CommandArgumentContext = EmptyCommandArgumentContext, -) : Command, AbstractReflectionCommand(owner, primaryName, secondaryNames = secondaryNames, description, parentPermission, prefixOptional), +) : Command, AbstractCommand(owner, primaryName, secondaryNames = secondaryNames, description, parentPermission, prefixOptional), CommandArgumentContextAware { + private val reflector by lazy { CommandReflector(this, SimpleCommandSubCommandAnnotationResolver) } + + @ExperimentalCommandDescriptors + public final override val overloads: List<CommandSignatureVariantFromKFunction> by lazy { + reflector.findSubCommands() + } + /** * 自动根据带有 [SubCommand] 注解的函数签名生成 [usage]. 也可以被覆盖. */ - public override val usage: String get() = super.usage + public override val usage: String by lazy { + @OptIn(ExperimentalCommandDescriptors::class) + reflector.generateUsage(overloads) + } /** * [CommandValueArgumentParser] 的环境 @@ -123,33 +132,6 @@ public abstract class CompositeCommand( @Retention(RUNTIME) @Target(AnnotationTarget.VALUE_PARAMETER) protected annotation class Name(val value: String) - - @OptIn(ExperimentalCommandDescriptors::class) - override val overloads: List<CommandSignatureVariant> by lazy { - subCommands.flatMap { desc -> - desc.bakedSubNames.map { names -> - CommandSignatureVariantImpl( - valueParameters = - names.mapIndexed { index, s -> CommandValueParameter.StringConstant("p$index", s) } + desc.params.map { - CommandValueParameter.UserDefinedType(it.name, null, - isOptional = false, - isVararg = false, - type = it.type) - }, - onCall = { resolvedCommandCall -> - desc.onCommand(resolvedCommandCall.caller, resolvedCommandCall.resolvedValueArguments.drop(names.size)) - } - ) - } - } - } - - protected override suspend fun CommandSender.onDefault(rawArgs: MessageChain) { - sendMessage(usage) - } - - internal final override val subCommandAnnotationResolver: SubCommandAnnotationResolver - get() = CompositeCommandSubCommandAnnotationResolver } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt index 75facfed8..a0af65241 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt @@ -12,14 +12,12 @@ package net.mamoe.mirai.console.command import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand -import net.mamoe.mirai.console.command.descriptor.CommandSignatureVariant -import net.mamoe.mirai.console.command.descriptor.CommandSignatureVariantImpl -import net.mamoe.mirai.console.command.descriptor.CommandValueParameter -import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors +import net.mamoe.mirai.console.command.descriptor.* import net.mamoe.mirai.console.command.java.JRawCommand import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME import net.mamoe.mirai.console.internal.command.createOrFindCommandPermission +import net.mamoe.mirai.console.internal.data.typeOf0 import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageChainBuilder @@ -58,7 +56,10 @@ public abstract class RawCommand( @ExperimentalCommandDescriptors override val overloads: List<CommandSignatureVariant> = listOf( - CommandSignatureVariantImpl(listOf(CommandValueParameter.UserDefinedType.createRequired<MessageChain>("args", true))) { call -> + CommandSignatureVariantImpl( + receiverParameter = CommandReceiverParameter(false, typeOf0<CommandSender>()), + valueParameters = listOf(CommandValueParameter.UserDefinedType.createRequired<MessageChain>("args", true)) + ) { call -> val sender = call.caller val arguments = call.rawValueArguments sender.onCommand(arguments.mapTo(MessageChainBuilder()) { it.value }.build()) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt index 9288f3430..14861f717 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt @@ -22,10 +22,13 @@ import net.mamoe.mirai.console.command.descriptor.* import net.mamoe.mirai.console.command.java.JSimpleCommand import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME -import net.mamoe.mirai.console.internal.command.AbstractReflectionCommand +import net.mamoe.mirai.console.internal.command.CommandReflector +import net.mamoe.mirai.console.internal.command.IllegalCommandDeclarationException import net.mamoe.mirai.console.internal.command.SimpleCommandSubCommandAnnotationResolver import net.mamoe.mirai.console.permission.Permission -import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.console.util.ConsoleExperimentalApi +import kotlin.annotation.AnnotationTarget.FUNCTION +import kotlin.annotation.AnnotationTarget.VALUE_PARAMETER /** * 简单的, 支持参数自动解析的指令. @@ -58,47 +61,41 @@ public abstract class SimpleCommand( parentPermission: Permission = owner.parentPermission, prefixOptional: Boolean = false, overrideContext: CommandArgumentContext = EmptyCommandArgumentContext, -) : Command, AbstractReflectionCommand(owner, primaryName, secondaryNames = secondaryNames, description, parentPermission, prefixOptional), +) : Command, AbstractCommand(owner, primaryName, secondaryNames = secondaryNames, description, parentPermission, prefixOptional), CommandArgumentContextAware { + private val reflector by lazy { CommandReflector(this, SimpleCommandSubCommandAnnotationResolver) } + @ExperimentalCommandDescriptors - override val overloads: List<CommandSignatureVariant> by lazy { - CommandSignatureVariantImpl( - valueParameters = subCommands.single().params.map { - CommandValueParameter.UserDefinedType(it.name, null, isOptional = false, isVararg = false, type = it.type) - } - ) { call -> - val sender = call.caller - subCommands.single().onCommand(sender, call.resolvedValueArguments) - }.let { listOf(it) } + public final override val overloads: List<CommandSignatureVariantFromKFunction> by lazy { + reflector.findSubCommands().also { + if (it.isEmpty()) + throw IllegalCommandDeclarationException(this, "SimpleCommand must have at least one subcommand, whereas zero present.") + } } /** * 自动根据带有 [Handler] 注解的函数签名生成 [usage]. 也可以被覆盖. */ - public override val usage: String get() = super.usage + public override val usage: String by lazy { + @OptIn(ExperimentalCommandDescriptors::class) + reflector.generateUsage(overloads) + } /** * 标注指令处理器 */ + @Target(FUNCTION) protected annotation class Handler + /** 参数名, 将参与构成 [usage] */ + @ConsoleExperimentalApi("Classname might change") + @Target(VALUE_PARAMETER) + protected annotation class Name(val value: String) + /** * 指令参数环境. 默认为 [CommandArgumentContext.Builtins] `+` `overrideContext` */ public override val context: CommandArgumentContext = CommandArgumentContext.Builtins + overrideContext - - internal override fun checkSubCommand(subCommands: Array<SubCommandDescriptor>) { - super.checkSubCommand(subCommands) - check(subCommands.size == 1) { "There can only be exactly one function annotated with Handler at this moment as overloading is not yet supported." } - } - - @Deprecated("prohibited", level = DeprecationLevel.HIDDEN) - internal override suspend fun CommandSender.onDefault(rawArgs: MessageChain) { - sendMessage(usage) - } - - internal final override val subCommandAnnotationResolver: SubCommandAnnotationResolver - get() = SimpleCommandSubCommandAnnotationResolver } 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 index 3a59735ad..723a4d94f 100644 --- 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 @@ -9,6 +9,7 @@ package net.mamoe.mirai.console.command.descriptor +import net.mamoe.mirai.console.command.CommandSender import net.mamoe.mirai.console.command.descriptor.ArgumentAcceptance.Companion.isAcceptable import net.mamoe.mirai.console.command.descriptor.CommandValueParameter.UserDefinedType.Companion.createOptional import net.mamoe.mirai.console.command.descriptor.CommandValueParameter.UserDefinedType.Companion.createRequired @@ -18,6 +19,7 @@ import net.mamoe.mirai.console.internal.data.classifierAsKClassOrNull import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.safeCast import kotlin.reflect.KClass +import kotlin.reflect.KFunction import kotlin.reflect.KType import kotlin.reflect.full.isSubtypeOf import kotlin.reflect.typeOf @@ -27,13 +29,23 @@ import kotlin.reflect.typeOf */ @ExperimentalCommandDescriptors public interface CommandSignatureVariant { + @ConsoleExperimentalApi + public val receiverParameter: CommandReceiverParameter<out CommandSender>? + public val valueParameters: List<CommandValueParameter<*>> public suspend fun call(resolvedCommandCall: ResolvedCommandCall) } +@ConsoleExperimentalApi @ExperimentalCommandDescriptors -public class CommandSignatureVariantImpl( +public interface CommandSignatureVariantFromKFunction : CommandSignatureVariant { + public val originFunction: KFunction<*> +} + +@ExperimentalCommandDescriptors +public open class CommandSignatureVariantImpl( + override val receiverParameter: CommandReceiverParameter<out CommandSender>?, override val valueParameters: List<CommandValueParameter<*>>, private val onCall: suspend CommandSignatureVariantImpl.(resolvedCommandCall: ResolvedCommandCall) -> Unit, ) : CommandSignatureVariant { @@ -42,25 +54,46 @@ public class CommandSignatureVariantImpl( } } +@ConsoleExperimentalApi +@ExperimentalCommandDescriptors +public open class CommandSignatureVariantFromKFunctionImpl( + override val receiverParameter: CommandReceiverParameter<out CommandSender>?, + override val valueParameters: List<CommandValueParameter<*>>, + override val originFunction: KFunction<*>, + private val onCall: suspend CommandSignatureVariantFromKFunctionImpl.(resolvedCommandCall: ResolvedCommandCall) -> Unit, +) : CommandSignatureVariantFromKFunction { + override suspend fun call(resolvedCommandCall: ResolvedCommandCall) { + return onCall(resolvedCommandCall) + } +} + /** - * Inherited instances must be [CommandValueParameter] + * Inherited instances must be [ICommandValueParameter] or [CommandReceiverParameter] */ @ExperimentalCommandDescriptors public interface ICommandParameter<T : Any?> { - public val name: String + public val name: String? - /** - * If [isOptional] is `false`, [defaultValue] is always `null`. - * Otherwise [defaultValue] may be `null` iff [T] is nullable. - */ - public val defaultValue: T? public val isOptional: Boolean /** * Reified type of [T] */ public val type: KType +} + +/** + * Inherited instances must be [CommandValueParameter] + */ +@ExperimentalCommandDescriptors +public interface ICommandValueParameter<T : Any?> : ICommandParameter<T> { + + /** + * If [isOptional] is `false`, [defaultValue] is always `null`. + * Otherwise [defaultValue] may be `null` iff [T] is nullable. + */ + public val defaultValue: T? public val isVararg: Boolean @@ -105,9 +138,21 @@ public sealed class ArgumentAcceptance( } } +@ExperimentalCommandDescriptors +public class CommandReceiverParameter<T : CommandSender>( + override val isOptional: Boolean, + override val type: KType, +) : ICommandParameter<T> { + override val name: String get() = PARAMETER_NAME + + public companion object { + public const val PARAMETER_NAME: String = "<receiver>" + } +} + @ExperimentalCommandDescriptors -public sealed class CommandValueParameter<T> : ICommandParameter<T> { +public sealed class CommandValueParameter<T> : ICommandValueParameter<T> { internal fun validate() { // // TODO: 2020/10/18 net.mamoe.mirai.console.command.descriptor.CommandValueParameter.validate$mirai_console_mirai_console_main require(type.classifier?.safeCast<KClass<*>>()?.isInstance(defaultValue) == true) { "defaultValue is not instance of type" @@ -132,8 +177,10 @@ public sealed class CommandValueParameter<T> : ICommandParameter<T> { return ArgumentAcceptance.Impossible } + @ConsoleExperimentalApi public class StringConstant( - public override val name: String, + @ConsoleExperimentalApi + public override val name: String?, public val expectingValue: String, ) : CommandValueParameter<String>() { public override val type: KType get() = STRING_TYPE @@ -152,7 +199,7 @@ public sealed class CommandValueParameter<T> : ICommandParameter<T> { * @see createRequired */ public class UserDefinedType<T>( - public override val name: String, + public override val name: String?, public override val defaultValue: T?, public override val isOptional: Boolean, public override val isVararg: Boolean, 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 index f67e712bd..30d167657 100644 --- 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 @@ -13,8 +13,8 @@ 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 net.mamoe.mirai.console.internal.data.qualifiedNameOrTip import kotlin.reflect.KType diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginData.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginData.kt index 0ab74fc19..f081af4d2 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginData.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginData.kt @@ -14,7 +14,7 @@ package net.mamoe.mirai.console.data import kotlinx.atomicfu.atomic import kotlinx.coroutines.* import net.mamoe.mirai.console.MiraiConsole -import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip +import net.mamoe.mirai.console.internal.data.qualifiedNameOrTip import net.mamoe.mirai.console.internal.plugin.updateWhen import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.utils.* diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandManagerImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandManagerImpl.kt index dcffd9aaa..25476b2b2 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandManagerImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandManagerImpl.kt @@ -16,6 +16,7 @@ import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.command.* import net.mamoe.mirai.console.command.Command.Companion.allNames import net.mamoe.mirai.console.command.CommandSender.Companion.toCommandSender +import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope import net.mamoe.mirai.event.Listener import net.mamoe.mirai.event.subscribeAlways @@ -24,6 +25,7 @@ import net.mamoe.mirai.message.data.content import net.mamoe.mirai.utils.MiraiLogger import java.util.concurrent.locks.ReentrantLock +@OptIn(ExperimentalCommandDescriptors::class) internal object CommandManagerImpl : CommandManager, CoroutineScope by MiraiConsole.childScope("CommandManagerImpl") { private val logger: MiraiLogger by lazy { MiraiConsole.createLogger("command") @@ -102,7 +104,9 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by MiraiCons } override fun Command.register(override: Boolean): Boolean { - if (this is CompositeCommand) this.subCommands // init lazy + if (this is CompositeCommand) { + this.overloads // init lazy + } kotlin.runCatching { this.permission // init lazy this.secondaryNames // init lazy diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandReflector.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandReflector.kt new file mode 100644 index 000000000..ed4b17f34 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandReflector.kt @@ -0,0 +1,215 @@ +package net.mamoe.mirai.console.internal.command + +import net.mamoe.mirai.console.command.* +import net.mamoe.mirai.console.command.descriptor.* +import net.mamoe.mirai.console.internal.data.classifierAsKClass +import net.mamoe.mirai.console.internal.data.classifierAsKClassOrNull +import net.mamoe.mirai.console.util.ConsoleExperimentalApi +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.PlainText +import net.mamoe.mirai.message.data.SingleMessage +import net.mamoe.mirai.message.data.buildMessageChain +import kotlin.reflect.KFunction +import kotlin.reflect.KParameter +import kotlin.reflect.KVisibility +import kotlin.reflect.full.* + + +internal val ILLEGAL_SUB_NAME_CHARS = "\\/!@#$%^&*()_+-={}[];':\",.<>?`~".toCharArray() + +internal fun Any.flattenCommandComponents(): MessageChain = buildMessageChain { + when (this@flattenCommandComponents) { + is PlainText -> this@flattenCommandComponents.content.splitToSequence(' ').filterNot { it.isBlank() } + .forEach { +PlainText(it) } + is CharSequence -> this@flattenCommandComponents.splitToSequence(' ').filterNot { it.isBlank() } + .forEach { +PlainText(it) } + is SingleMessage -> add(this@flattenCommandComponents) + is Array<*> -> this@flattenCommandComponents.forEach { if (it != null) addAll(it.flattenCommandComponents()) } + is Iterable<*> -> this@flattenCommandComponents.forEach { if (it != null) addAll(it.flattenCommandComponents()) } + else -> add(this@flattenCommandComponents.toString()) + } +} + +@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") +internal object CompositeCommandSubCommandAnnotationResolver : + SubCommandAnnotationResolver { + override fun hasAnnotation(ownerCommand: Command, function: KFunction<*>) = + function.hasAnnotation<CompositeCommand.SubCommand>() + + override fun getSubCommandNames(ownerCommand: Command, function: KFunction<*>): Array<out String> = + function.findAnnotation<CompositeCommand.SubCommand>()!!.value + + override fun getAnnotatedName(ownerCommand: Command, parameter: KParameter): String? = + parameter.findAnnotation<CompositeCommand.Name>()?.value + + override fun getDescription(ownerCommand: Command, function: KFunction<*>): String? = + function.findAnnotation<CompositeCommand.Description>()?.value +} + +@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") +internal object SimpleCommandSubCommandAnnotationResolver : + SubCommandAnnotationResolver { + override fun hasAnnotation(ownerCommand: Command, function: KFunction<*>) = + function.hasAnnotation<SimpleCommand.Handler>() + + override fun getSubCommandNames(ownerCommand: Command, function: KFunction<*>): Array<out String> = + ownerCommand.secondaryNames + + override fun getAnnotatedName(ownerCommand: Command, parameter: KParameter): String? = + parameter.findAnnotation<SimpleCommand.Name>()?.value + + override fun getDescription(ownerCommand: Command, function: KFunction<*>): String? = + ownerCommand.description +} + +internal interface SubCommandAnnotationResolver { + fun hasAnnotation(ownerCommand: Command, function: KFunction<*>): Boolean + fun getSubCommandNames(ownerCommand: Command, function: KFunction<*>): Array<out String> + fun getAnnotatedName(ownerCommand: Command, parameter: KParameter): String? + fun getDescription(ownerCommand: Command, function: KFunction<*>): String? +} + +@ConsoleExperimentalApi +public class IllegalCommandDeclarationException : Exception { + public override val message: String? + + public constructor( + ownerCommand: Command, + correspondingFunction: KFunction<*>, + message: String?, + ) : super("Illegal command declaration: ${correspondingFunction.name} declared in ${ownerCommand::class.qualifiedName}") { + this.message = message + } + + public constructor( + ownerCommand: Command, + message: String?, + ) : super("Illegal command declaration: ${ownerCommand::class.qualifiedName}") { + this.message = message + } +} + +@OptIn(ExperimentalCommandDescriptors::class) +internal class CommandReflector( + val command: Command, + val annotationResolver: SubCommandAnnotationResolver, +) { + + @Suppress("NOTHING_TO_INLINE") + private inline fun KFunction<*>.illegalDeclaration( + message: String, + ): Nothing { + throw IllegalCommandDeclarationException(command, this, message) + } + + private fun KFunction<*>.isSubCommandFunction(): Boolean = annotationResolver.hasAnnotation(command, this) + private fun KFunction<*>.checkExtensionReceiver() { + this.extensionReceiverParameter?.let { receiver -> + if (receiver.type.classifierAsKClassOrNull()?.isSubclassOf(CommandSender::class) != true) { + illegalDeclaration("Extension receiver parameter type is not subclass of CommandSender.") + } + } + } + + private fun KFunction<*>.checkNames() { + val names = annotationResolver.getSubCommandNames(command, this) + for (name in names) { + ILLEGAL_SUB_NAME_CHARS.find { it in name }?.let { + illegalDeclaration("'$it' is forbidden in command name.") + } + } + } + + private fun KFunction<*>.checkModifiers() { + if (isInline) illegalDeclaration("Command function cannot be inline") + if (visibility == KVisibility.PRIVATE) illegalDeclaration("Command function must be accessible from Mirai Console, that is, effectively public.") + if (this.hasAnnotation<JvmStatic>()) illegalDeclaration("Command function must not be static.") + + // should we allow abstract? + + // if (isAbstract) illegalDeclaration("Command function cannot be abstract") + } + + fun generateUsage(overloads: Iterable<CommandSignatureVariantFromKFunction>): String { + return overloads.joinToString("\n") { subcommand -> + buildString { + if (command.prefixOptional) { + append("(") + append(CommandManager.commandPrefix) + append(")") + } else { + append(CommandManager.commandPrefix) + } + if (command is CompositeCommand) { + append(command.primaryName) + append(" ") + } + append(subcommand.valueParameters.joinToString(" ") { it.render() }) + annotationResolver.getDescription(command, subcommand.originFunction).let { description -> + append(" ") + append(description) + } + } + } + } + + + companion object { + + private fun <T> CommandValueParameter<T>.render(): String { + return when (this) { + is CommandValueParameter.Extended, + is CommandValueParameter.UserDefinedType<*>, + -> { + "<${this.name ?: this.type.classifierAsKClass().simpleName}>" + } + is CommandValueParameter.StringConstant -> { + this.expectingValue + } + } + } + } + + @Throws(IllegalCommandDeclarationException::class) + fun findSubCommands(): List<CommandSignatureVariantFromKFunctionImpl> { + return command::class.functions // exclude static later + .asSequence() + .filter { it.isSubCommandFunction() } + .onEach { it.checkExtensionReceiver() } + .onEach { it.checkModifiers() } + .onEach { it.checkNames() } + .map { function -> + + val functionNameAsValueParameter = + annotationResolver.getSubCommandNames(command, function).map { createStringConstantParameter(it) } + + val functionValueParameters = + function.valueParameters.map { it.toUserDefinedCommandParameter() } + + CommandSignatureVariantFromKFunctionImpl( + receiverParameter = function.extensionReceiverParameter?.toCommandReceiverParameter(), + valueParameters = functionNameAsValueParameter + functionValueParameters, + originFunction = function + ) { call -> + function.callSuspend(command, *call.resolvedValueArguments.toTypedArray()) + } + }.toList() + } + + private fun KParameter.toCommandReceiverParameter(): CommandReceiverParameter<out CommandSender>? { + check(!this.isVararg) { "Receiver cannot be vararg." } + check(this.type.classifierAsKClass().isSubclassOf(CommandSender::class)) { "Receiver must be subclass of CommandSender" } + + return CommandReceiverParameter(this.type.isMarkedNullable, this.type) + } + + private fun createStringConstantParameter(expectingValue: String): CommandValueParameter.StringConstant { + return CommandValueParameter.StringConstant(null, expectingValue) + } + + private fun KParameter.toUserDefinedCommandParameter(): CommandValueParameter.UserDefinedType<KParameter> { + return CommandValueParameter.UserDefinedType(nameForCommandParameter(), this, this.isOptional, this.isVararg, this.type) + } + + private fun KParameter.nameForCommandParameter(): String? = annotationResolver.getAnnotatedName(command, this) ?: this.name +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommandInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommandInternal.kt deleted file mode 100644 index a401e3c19..000000000 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommandInternal.kt +++ /dev/null @@ -1,334 +0,0 @@ -/* - * Copyright 2019-2020 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - */ - -@file:Suppress("NOTHING_TO_INLINE", "MemberVisibilityCanBePrivate", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") - -package net.mamoe.mirai.console.internal.command - -import net.mamoe.mirai.console.command.* -import net.mamoe.mirai.console.command.descriptor.* -import net.mamoe.mirai.console.internal.command.hasAnnotation -import net.mamoe.mirai.console.permission.Permission -import net.mamoe.mirai.message.data.MessageChain -import net.mamoe.mirai.message.data.PlainText -import net.mamoe.mirai.message.data.SingleMessage -import net.mamoe.mirai.message.data.buildMessageChain -import kotlin.reflect.KAnnotatedElement -import kotlin.reflect.KClass -import kotlin.reflect.KFunction -import kotlin.reflect.KParameter -import kotlin.reflect.full.* - -internal object CompositeCommandSubCommandAnnotationResolver : - AbstractReflectionCommand.SubCommandAnnotationResolver { - override fun hasAnnotation(baseCommand: AbstractReflectionCommand, function: KFunction<*>) = - function.hasAnnotation<CompositeCommand.SubCommand>() - - override fun getSubCommandNames(baseCommand: AbstractReflectionCommand, function: KFunction<*>): Array<out String> = - function.findAnnotation<CompositeCommand.SubCommand>()!!.value -} - -internal object SimpleCommandSubCommandAnnotationResolver : - AbstractReflectionCommand.SubCommandAnnotationResolver { - override fun hasAnnotation(baseCommand: AbstractReflectionCommand, function: KFunction<*>) = - function.hasAnnotation<SimpleCommand.Handler>() - - override fun getSubCommandNames(baseCommand: AbstractReflectionCommand, function: KFunction<*>): Array<out String> = - baseCommand.secondaryNames -} - -internal abstract class AbstractReflectionCommand -@JvmOverloads constructor( - owner: CommandOwner, - primaryName: String, - secondaryNames: Array<out String>, - description: String = "<no description available>", - parentPermission: Permission = owner.parentPermission, - prefixOptional: Boolean = false, -) : Command, AbstractCommand( - owner, - primaryName = primaryName, - secondaryNames = secondaryNames, - description = description, - parentPermission = parentPermission, - prefixOptional = prefixOptional -), CommandArgumentContextAware { - internal abstract val subCommandAnnotationResolver: SubCommandAnnotationResolver - - @JvmField - @Suppress("PropertyName") - internal var _usage: String = "<not yet initialized>" - - override val usage: String // initialized by subCommand reflection - get() { - subCommands // ensure init - return _usage - } - - abstract suspend fun CommandSender.onDefault(rawArgs: MessageChain) - - internal val defaultSubCommand: DefaultSubCommandDescriptor by lazy { - DefaultSubCommandDescriptor( - "", - createOrFindCommandPermission(parentPermission), - onCommand = { sender: CommandSender, args: MessageChain -> - sender.onDefault(args) - } - ) - } - - internal open fun checkSubCommand(subCommands: Array<SubCommandDescriptor>) { - - } - - @OptIn(ExperimentalCommandDescriptors::class) - private fun <T : Any> CommandParameter<T>.toCommandValueParameter(): CommandValueParameter<T> { - return CommandValueParameter.UserDefinedType<T>(name, null, false, false, type) - } - - - @OptIn(ExperimentalCommandDescriptors::class) - override val overloads: List<CommandSignatureVariant> by lazy { - subCommands.map { desc -> - CommandSignatureVariantImpl(desc.params.map { it.toCommandValueParameter() }) { call -> - desc.onCommand(call.caller, call.resolvedValueArguments) - } - } - } - - interface SubCommandAnnotationResolver { - fun hasAnnotation(baseCommand: AbstractReflectionCommand, function: KFunction<*>): Boolean - fun getSubCommandNames(baseCommand: AbstractReflectionCommand, function: KFunction<*>): Array<out String> - } - - internal val subCommands: Array<SubCommandDescriptor> by lazy { - this::class.declaredFunctions.filter { subCommandAnnotationResolver.hasAnnotation(this, it) } - .also { subCommandFunctions -> - // overloading not yet supported - val overloadFunction = subCommandFunctions.groupBy { it.name }.entries.firstOrNull { it.value.size > 1 } - if (overloadFunction != null) { - error("Sub command overloading is not yet supported. (at ${this::class.qualifiedNameOrTip}.${overloadFunction.key})") - } - }.map { function -> - createSubCommand(function, context) - }.toTypedArray().also { - _usage = it.createUsage(this) - }.also { checkSubCommand(it) } - } - - internal val bakedCommandNameToSubDescriptorArray: Map<Array<String>, SubCommandDescriptor> by lazy { - kotlin.run { - val map = LinkedHashMap<Array<String>, SubCommandDescriptor>(subCommands.size * 2) - for (descriptor in subCommands) { - for (name in descriptor.bakedSubNames) { - map[name] = descriptor - } - } - map.toSortedMap { o1, o2 -> o1!!.contentHashCode() - o2!!.contentHashCode() } - } - } - - internal class DefaultSubCommandDescriptor( - val description: String, - val permission: Permission, - val onCommand: suspend (sender: CommandSender, rawArgs: MessageChain) -> Unit, - ) - - internal inner class SubCommandDescriptor( - val names: Array<out String>, - val params: Array<CommandParameter<*>>, - val description: String, - val permission: Permission, - val onCommand: suspend (sender: CommandSender, parsedArgs: List<Any?>) -> Boolean, - val context: CommandArgumentContext, - val argumentBuilder: (sender: CommandSender) -> MutableMap<KParameter, Any?>, - ) { - val usage: String = createUsage(this@AbstractReflectionCommand) - - private fun KParameter.isOptional(): Boolean { - return isOptional || this.type.isMarkedNullable - } - - val minimalArgumentsSize = params.count { - !it.parameter.isOptional() - } - - @JvmField - internal val bakedSubNames: Array<Array<String>> = names.map { it.bakeSubName() }.toTypedArray() - } -} - -internal fun <T> Array<T>.contentEqualsOffset(other: MessageChain, length: Int): Boolean { - repeat(length) { index -> - if (!other[index].toString().equals(this[index].toString(), ignoreCase = true)) { - return false - } - } - return true -} - -internal val ILLEGAL_SUB_NAME_CHARS = "\\/!@#$%^&*()_+-={}[];':\",.<>?`~".toCharArray() -internal fun String.isValidSubName(): Boolean = ILLEGAL_SUB_NAME_CHARS.none { it in this } -internal fun String.bakeSubName(): Array<String> = split(' ').filterNot { it.isBlank() }.toTypedArray() - -internal fun Any.flattenCommandComponents(): MessageChain = buildMessageChain { - when (this@flattenCommandComponents) { - is PlainText -> this@flattenCommandComponents.content.splitToSequence(' ').filterNot { it.isBlank() } - .forEach { +PlainText(it) } - is CharSequence -> this@flattenCommandComponents.splitToSequence(' ').filterNot { it.isBlank() } - .forEach { +PlainText(it) } - is SingleMessage -> add(this@flattenCommandComponents) - is Array<*> -> this@flattenCommandComponents.forEach { if (it != null) addAll(it.flattenCommandComponents()) } - is Iterable<*> -> this@flattenCommandComponents.forEach { if (it != null) addAll(it.flattenCommandComponents()) } - else -> add(this@flattenCommandComponents.toString()) - } -} - -internal inline fun <reified T : Annotation> KAnnotatedElement.hasAnnotation(): Boolean = - findAnnotation<T>() != null - -internal val KClass<*>.qualifiedNameOrTip: String get() = this.qualifiedName ?: "<anonymous class>" - -internal fun Array<AbstractReflectionCommand.SubCommandDescriptor>.createUsage(baseCommand: AbstractReflectionCommand): String = - buildString { - appendLine(baseCommand.description) - appendLine() - - for (subCommandDescriptor in this@createUsage) { - appendLine(subCommandDescriptor.usage) - } - }.trimEnd() - -internal fun AbstractReflectionCommand.SubCommandDescriptor.createUsage(baseCommand: AbstractReflectionCommand): String = - buildString { - if (baseCommand.prefixOptional) { - append("(") - append(CommandManager.commandPrefix) - append(")") - } else { - append(CommandManager.commandPrefix) - } - if (baseCommand is CompositeCommand) { - append(baseCommand.primaryName) - append(" ") - } - append(names.first()) - append(" ") - append(params.joinToString(" ") { "<${it.name}>" }) - append(" ") - append(description) - appendLine() - }.trimEnd() - -internal fun <T1, R1, R2> ((T1) -> R1).then(then: (T1, R1) -> R2): ((T1) -> R2) { - return { a -> then.invoke(a, (this@then(a))) } -} - -internal fun AbstractReflectionCommand.createSubCommand( - function: KFunction<*>, - context: CommandArgumentContext, -): AbstractReflectionCommand.SubCommandDescriptor { - val notStatic = !function.hasAnnotation<JvmStatic>() - //val overridePermission = null//function.findAnnotation<CompositeCommand.PermissionId>()//optional - val subDescription = - function.findAnnotation<CompositeCommand.Description>()?.value ?: "" - - fun KClass<*>.isValidReturnType(): Boolean { - return when (this) { - Boolean::class, Void::class, Unit::class, Nothing::class -> true - else -> false - } - } - - check((function.returnType.classifier as? KClass<*>)?.isValidReturnType() == true) { - error("Return type of sub command ${function.name} must be one of the following: kotlin.Boolean, java.lang.Boolean, kotlin.Unit (including implicit), kotlin.Nothing, boolean or void (at ${this::class.qualifiedNameOrTip}.${function.name})") - } - - check(!function.returnType.isMarkedNullable) { - error("Return type of sub command ${function.name} must not be marked nullable in Kotlin, and must be marked with @NotNull or @NonNull explicitly in Java. (at ${this::class.qualifiedNameOrTip}.${function.name})") - } - var argumentBuilder: (sender: CommandSender) -> MutableMap<KParameter, Any?> = { HashMap() } - val parameters = function.parameters.toMutableList() - - if (notStatic) { - val type = parameters.removeAt(0) // instance - argumentBuilder = argumentBuilder.then { _, map -> - map[type] = this@createSubCommand - map - } - } - - check(parameters.isNotEmpty()) { - "Parameters of sub command ${function.name} must not be empty. (Must have CommandSender as its receiver or first parameter or absent, followed by naturally typed params) (at ${this::class.qualifiedNameOrTip}.${function.name})" - } - - parameters.forEach { param -> - check(!param.isVararg) { - "Parameter $param must not be vararg. (at ${this::class.qualifiedNameOrTip}.${function.name}.$param)" - } - } - - (parameters.first()).let { receiver -> - if ((receiver.type.classifier as? KClass<*>)?.isSubclassOf(CommandSender::class) == true) { - val senderType = parameters.removeAt(0) - argumentBuilder = argumentBuilder.then { sender, map -> - map[senderType] = sender - map - } - } - } - - val commandName = - subCommandAnnotationResolver.getSubCommandNames(this, function) - .let { namesFromAnnotation -> - if (namesFromAnnotation.isNotEmpty()) { - namesFromAnnotation.map(String::toLowerCase).toTypedArray() - } else arrayOf(function.name.toLowerCase()) - }.also { names -> - names.forEach { - check(it.isValidSubName()) { - "Name of sub command ${function.name} is invalid" - } - } - } - - //map parameter - val params = parameters.map { param -> - - // if (param.isOptional) error("optional parameters are not yet supported. (at ${this::class.qualifiedNameOrTip}.${function.name}.$param)") - - val paramName = param.findAnnotation<CompositeCommand.Name>()?.value ?: param.name ?: "unknown" - CommandParameter<Any>( - paramName, - param.type, - param - ) - }.toTypedArray() - - // TODO: 2020/09/19 检查 optional/nullable 是否都在最后 - - @Suppress("UNCHECKED_CAST") - return SubCommandDescriptor( - commandName, - params as Array<CommandParameter<*>>, - subDescription, // overridePermission?.value - permission,//overridePermission?.value?.let { PermissionService.INSTANCE[PermissionId.parseFromString(it)] } ?: permission, - onCommand = { _: CommandSender, args -> - val p = parameters.zip(args).toMap(LinkedHashMap()) - if (notStatic) p[function.instanceParameter!!] = this@createSubCommand - val result = function.callSuspendBy(p) - - checkNotNull(result) { "sub command return value is null (at ${this::class.qualifiedName}.${function.name})" } - - result as? Boolean ?: true // Unit, void is considered as true. - }, - context = context, - argumentBuilder = argumentBuilder - ) -} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/MultiFilePluginDataStorageImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/MultiFilePluginDataStorageImpl.kt index 45fa24f88..800229946 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/MultiFilePluginDataStorageImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/MultiFilePluginDataStorageImpl.kt @@ -12,7 +12,6 @@ package net.mamoe.mirai.console.internal.data import kotlinx.serialization.json.Json import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.data.* -import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.SilentLogger 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 226dce6cc..54818777e 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 @@ -11,14 +11,15 @@ package net.mamoe.mirai.console.internal.data import net.mamoe.mirai.console.data.PluginData import net.mamoe.mirai.console.data.ValueName -import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip -import kotlin.reflect.KClass -import kotlin.reflect.KParameter -import kotlin.reflect.KProperty -import kotlin.reflect.KType +import kotlin.reflect.* import kotlin.reflect.full.findAnnotation import kotlin.reflect.full.isSubclassOf +internal val KClass<*>.qualifiedNameOrTip: String get() = this.qualifiedName ?: "<anonymous class>" + +internal inline fun <reified T : Annotation> KAnnotatedElement.hasAnnotation(): Boolean = + findAnnotation<T>() != null + @Suppress("UNCHECKED_CAST") internal inline fun <reified T : Any> KType.toKClass(): KClass<out T> { val clazz = requireNotNull(classifier as? KClass<T>) { "Unsupported classifier: $classifier" } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/valueFromKTypeImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/valueFromKTypeImpl.kt index 037c65ba5..02e5ddbe9 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/valueFromKTypeImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/valueFromKTypeImpl.kt @@ -16,7 +16,6 @@ import net.mamoe.mirai.console.data.PluginData import net.mamoe.mirai.console.data.SerializableValue.Companion.serializableValueWith import net.mamoe.mirai.console.data.SerializerAwareValue import net.mamoe.mirai.console.data.valueFromKType -import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip import kotlin.contracts.contract import kotlin.reflect.KClass import kotlin.reflect.KType diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/ContactUtils.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/ContactUtils.kt index 9b3aef0ed..bdd37219d 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/ContactUtils.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/ContactUtils.kt @@ -12,7 +12,7 @@ package net.mamoe.mirai.console.util import net.mamoe.mirai.Bot -import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip +import net.mamoe.mirai.console.internal.data.qualifiedNameOrTip import net.mamoe.mirai.contact.* /** diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt index 4e11719bc..1e5d26f64 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt @@ -261,7 +261,7 @@ internal class TestCommand { "testOptional" ) { @SubCommand - fun optional(arg1: String, arg2: String = "Here is optional", arg3: String?) { + fun optional(arg1: String, arg2: String = "Here is optional", arg3: String? = null) { println(arg1) println(arg2) println(arg3) From 151a5d5735d595218736477feed525c792c8c08a Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Sat, 24 Oct 2020 11:35:10 +0800 Subject: [PATCH 22/28] Fix optional resolving --- .../mirai/console/command/CompositeCommand.kt | 4 +- .../mamoe/mirai/console/command/RawCommand.kt | 2 +- .../command/descriptor/CommandDescriptor.kt | 105 ++++++++++++------ .../command/resolve/ResolvedCommandCall.kt | 21 ++-- .../internal/command/CommandReflector.kt | 43 +++++-- .../mirai/console/command/TestCommand.kt | 36 ++++-- 6 files changed, 148 insertions(+), 63 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt index c0b75ec89..a7582efc7 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt @@ -21,7 +21,7 @@ import net.mamoe.mirai.console.command.descriptor.* import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME import net.mamoe.mirai.console.internal.command.CommandReflector -import net.mamoe.mirai.console.internal.command.SimpleCommandSubCommandAnnotationResolver +import net.mamoe.mirai.console.internal.command.CompositeCommandSubCommandAnnotationResolver import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.console.util.ConsoleExperimentalApi import kotlin.annotation.AnnotationRetention.RUNTIME @@ -92,7 +92,7 @@ public abstract class CompositeCommand( ) : Command, AbstractCommand(owner, primaryName, secondaryNames = secondaryNames, description, parentPermission, prefixOptional), CommandArgumentContextAware { - private val reflector by lazy { CommandReflector(this, SimpleCommandSubCommandAnnotationResolver) } + private val reflector by lazy { CommandReflector(this, CompositeCommandSubCommandAnnotationResolver) } @ExperimentalCommandDescriptors public final override val overloads: List<CommandSignatureVariantFromKFunction> by lazy { diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt index a0af65241..0da2747ec 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt @@ -58,7 +58,7 @@ public abstract class RawCommand( override val overloads: List<CommandSignatureVariant> = listOf( CommandSignatureVariantImpl( receiverParameter = CommandReceiverParameter(false, typeOf0<CommandSender>()), - valueParameters = listOf(CommandValueParameter.UserDefinedType.createRequired<MessageChain>("args", true)) + valueParameters = listOf(AbstractCommandValueParameter.UserDefinedType.createRequired<MessageChain>("args", true)) ) { call -> val sender = call.caller val arguments = call.rawValueArguments 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 index 723a4d94f..805111f4a 100644 --- 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 @@ -10,14 +10,14 @@ package net.mamoe.mirai.console.command.descriptor import net.mamoe.mirai.console.command.CommandSender +import net.mamoe.mirai.console.command.descriptor.AbstractCommandValueParameter.UserDefinedType.Companion.createOptional +import net.mamoe.mirai.console.command.descriptor.AbstractCommandValueParameter.UserDefinedType.Companion.createRequired import net.mamoe.mirai.console.command.descriptor.ArgumentAcceptance.Companion.isAcceptable -import net.mamoe.mirai.console.command.descriptor.CommandValueParameter.UserDefinedType.Companion.createOptional -import net.mamoe.mirai.console.command.descriptor.CommandValueParameter.UserDefinedType.Companion.createRequired import net.mamoe.mirai.console.command.parse.CommandValueArgument import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall +import net.mamoe.mirai.console.internal.data.classifierAsKClass import net.mamoe.mirai.console.internal.data.classifierAsKClassOrNull import net.mamoe.mirai.console.util.ConsoleExperimentalApi -import net.mamoe.mirai.console.util.safeCast import kotlin.reflect.KClass import kotlin.reflect.KFunction import kotlin.reflect.KType @@ -32,7 +32,7 @@ public interface CommandSignatureVariant { @ConsoleExperimentalApi public val receiverParameter: CommandReceiverParameter<out CommandSender>? - public val valueParameters: List<CommandValueParameter<*>> + public val valueParameters: List<AbstractCommandValueParameter<*>> public suspend fun call(resolvedCommandCall: ResolvedCommandCall) } @@ -43,12 +43,24 @@ public interface CommandSignatureVariantFromKFunction : CommandSignatureVariant public val originFunction: KFunction<*> } +@ExperimentalCommandDescriptors +public abstract class AbstractCommandSignatureVariant : CommandSignatureVariant { + override fun toString(): String { + val receiverParameter = receiverParameter + return if (receiverParameter == null) { + "CommandSignatureVariant(${valueParameters.joinToString()})" + } else { + "CommandSignatureVariant($receiverParameter, ${valueParameters.joinToString()})" + } + } +} + @ExperimentalCommandDescriptors public open class CommandSignatureVariantImpl( override val receiverParameter: CommandReceiverParameter<out CommandSender>?, - override val valueParameters: List<CommandValueParameter<*>>, + override val valueParameters: List<AbstractCommandValueParameter<*>>, private val onCall: suspend CommandSignatureVariantImpl.(resolvedCommandCall: ResolvedCommandCall) -> Unit, -) : CommandSignatureVariant { +) : CommandSignatureVariant, AbstractCommandSignatureVariant() { override suspend fun call(resolvedCommandCall: ResolvedCommandCall) { return onCall(resolvedCommandCall) } @@ -58,10 +70,10 @@ public open class CommandSignatureVariantImpl( @ExperimentalCommandDescriptors public open class CommandSignatureVariantFromKFunctionImpl( override val receiverParameter: CommandReceiverParameter<out CommandSender>?, - override val valueParameters: List<CommandValueParameter<*>>, + override val valueParameters: List<AbstractCommandValueParameter<*>>, override val originFunction: KFunction<*>, private val onCall: suspend CommandSignatureVariantFromKFunctionImpl.(resolvedCommandCall: ResolvedCommandCall) -> Unit, -) : CommandSignatureVariantFromKFunction { +) : CommandSignatureVariantFromKFunction, AbstractCommandSignatureVariant() { override suspend fun call(resolvedCommandCall: ResolvedCommandCall) { return onCall(resolvedCommandCall) } @@ -69,10 +81,10 @@ public open class CommandSignatureVariantFromKFunctionImpl( /** - * Inherited instances must be [ICommandValueParameter] or [CommandReceiverParameter] + * Inherited instances must be [CommandValueParameter] or [CommandReceiverParameter] */ @ExperimentalCommandDescriptors -public interface ICommandParameter<T : Any?> { +public interface CommandParameter<T : Any?> { public val name: String? public val isOptional: Boolean @@ -83,17 +95,21 @@ public interface ICommandParameter<T : Any?> { public val type: KType } +@ExperimentalCommandDescriptors +public abstract class AbstractCommandParameter<T> : CommandParameter<T> { + override fun toString(): String = buildString { + append(name) + append(": ") + append(type.classifierAsKClass().simpleName) + append(if (type.isMarkedNullable) "?" else "") + } +} + /** - * Inherited instances must be [CommandValueParameter] + * Inherited instances must be [AbstractCommandValueParameter] */ @ExperimentalCommandDescriptors -public interface ICommandValueParameter<T : Any?> : ICommandParameter<T> { - - /** - * If [isOptional] is `false`, [defaultValue] is always `null`. - * Otherwise [defaultValue] may be `null` iff [T] is nullable. - */ - public val defaultValue: T? +public interface CommandValueParameter<T : Any?> : CommandParameter<T> { public val isVararg: Boolean @@ -142,9 +158,15 @@ public sealed class ArgumentAcceptance( public class CommandReceiverParameter<T : CommandSender>( override val isOptional: Boolean, override val type: KType, -) : ICommandParameter<T> { +) : CommandParameter<T>, AbstractCommandParameter<T>() { override val name: String get() = PARAMETER_NAME + init { + check(type.classifier is KClass<*>) { + "CommandReceiverParameter.type.classifier must be KClass." + } + } + public companion object { public const val PARAMETER_NAME: String = "<receiver>" } @@ -152,14 +174,15 @@ public class CommandReceiverParameter<T : CommandSender>( @ExperimentalCommandDescriptors -public sealed class CommandValueParameter<T> : ICommandValueParameter<T> { - internal fun validate() { // // TODO: 2020/10/18 net.mamoe.mirai.console.command.descriptor.CommandValueParameter.validate$mirai_console_mirai_console_main - require(type.classifier?.safeCast<KClass<*>>()?.isInstance(defaultValue) == true) { - "defaultValue is not instance of type" +public sealed class AbstractCommandValueParameter<T> : CommandValueParameter<T>, AbstractCommandParameter<T>() { + override fun toString(): String = buildString { + if (isVararg) append("vararg ") + append(super.toString()) + if (isOptional) { + append(" = ...") } } - public override fun accepting(argument: CommandValueArgument, commandArgumentContext: CommandArgumentContext?): ArgumentAcceptance { val expectingType = this.type @@ -182,12 +205,22 @@ public sealed class CommandValueParameter<T> : ICommandValueParameter<T> { @ConsoleExperimentalApi public override val name: String?, public val expectingValue: String, - ) : CommandValueParameter<String>() { + ) : AbstractCommandValueParameter<String>() { public override val type: KType get() = STRING_TYPE - public override val defaultValue: Nothing? get() = null public override val isOptional: Boolean get() = false public override val isVararg: Boolean get() = false + init { + require(expectingValue.isNotBlank()) { + "expectingValue must not be blank" + } + require(expectingValue.none(Char::isWhitespace)) { + "expectingValue must not contain whitespace" + } + } + + override fun toString(): String = "<$expectingValue>" + private companion object { @OptIn(ExperimentalStdlibApi::class) val STRING_TYPE = typeOf<String>() @@ -200,22 +233,27 @@ public sealed class CommandValueParameter<T> : ICommandValueParameter<T> { */ public class UserDefinedType<T>( public override val name: String?, - public override val defaultValue: T?, public override val isOptional: Boolean, public override val isVararg: Boolean, public override val type: KType, - ) : CommandValueParameter<T>() { + ) : AbstractCommandValueParameter<T>() { + init { + requireNotNull(type.classifierAsKClassOrNull()) { + "CommandReceiverParameter.type.classifier must be KClass." + } + } + public companion object { @JvmStatic - public inline fun <reified T : Any> createOptional(name: String, isVararg: Boolean, defaultValue: T): UserDefinedType<T> { + public inline fun <reified T : Any> createOptional(name: String, isVararg: Boolean): UserDefinedType<T> { @OptIn(ExperimentalStdlibApi::class) - return UserDefinedType(name, defaultValue, true, isVararg, typeOf<T>()) + return UserDefinedType(name, true, isVararg, typeOf<T>()) } @JvmStatic public inline fun <reified T : Any> createRequired(name: String, isVararg: Boolean): UserDefinedType<T> { @OptIn(ExperimentalStdlibApi::class) - return UserDefinedType(name, null, false, isVararg, typeOf<T>()) + return UserDefinedType(name, false, isVararg, typeOf<T>()) } } } @@ -223,5 +261,8 @@ public sealed class CommandValueParameter<T> : ICommandValueParameter<T> { /** * Extended by [CommandValueArgumentParser] */ - public abstract class Extended<T> : CommandValueParameter<T>() + @ConsoleExperimentalApi + public abstract class Extended<T> : AbstractCommandValueParameter<T>() { + abstract override fun toString(): String + } } \ 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 index 2feaa94f9..547dd736a 100644 --- 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 @@ -12,15 +12,13 @@ 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.CompositeCommand -import net.mamoe.mirai.console.command.descriptor.CommandArgumentContext -import net.mamoe.mirai.console.command.descriptor.CommandSignatureVariant -import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors -import net.mamoe.mirai.console.command.descriptor.NoValueArgumentMappingException +import 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.command.parse.mapToTypeOrNull import net.mamoe.mirai.console.internal.data.classifierAsKClass import net.mamoe.mirai.console.util.ConsoleExperimentalApi +import net.mamoe.mirai.console.util.cast import kotlin.LazyThreadSafetyMode.PUBLICATION /** @@ -49,13 +47,21 @@ public interface ResolvedCommandCall { /** * Resolved value arguments arranged mapping the [CommandSignatureVariant.valueParameters] by index. + * + * **Implementation details**: Lazy calculation. */ @ConsoleExperimentalApi - public val resolvedValueArguments: List<Any?> + public val resolvedValueArguments: List<ResolvedCommandValueArgument<*>> public companion object } +@ExperimentalCommandDescriptors +public data class ResolvedCommandValueArgument<T>( + val parameter: CommandValueParameter<T>, + val value: T, +) + // Don't move into companion, compilation error @ExperimentalCommandDescriptors public suspend inline fun ResolvedCommandCall.call() { @@ -70,11 +76,12 @@ public class ResolvedCommandCallImpl( override val rawValueArguments: List<CommandValueArgument>, private val context: CommandArgumentContext, ) : ResolvedCommandCall { - override val resolvedValueArguments: List<Any?> by lazy(PUBLICATION) { + override val resolvedValueArguments: List<ResolvedCommandValueArgument<*>> by lazy(PUBLICATION) { calleeSignature.valueParameters.zip(rawValueArguments).map { (parameter, argument) -> - argument.mapToTypeOrNull(parameter.type) ?: context[parameter.type.classifierAsKClass()]?.parse(argument.value, caller) + val value = argument.mapToTypeOrNull(parameter.type) ?: context[parameter.type.classifierAsKClass()]?.parse(argument.value, caller) ?: throw NoValueArgumentMappingException(argument, parameter.type) // TODO: 2020/10/17 consider vararg and optional + ResolvedCommandValueArgument(parameter.cast(), value) } } } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandReflector.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandReflector.kt index ed4b17f34..2dc504b85 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandReflector.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandReflector.kt @@ -156,20 +156,29 @@ internal class CommandReflector( companion object { - private fun <T> CommandValueParameter<T>.render(): String { + private fun <T> AbstractCommandValueParameter<T>.render(): String { return when (this) { - is CommandValueParameter.Extended, - is CommandValueParameter.UserDefinedType<*>, + is AbstractCommandValueParameter.Extended, + is AbstractCommandValueParameter.UserDefinedType<*>, -> { "<${this.name ?: this.type.classifierAsKClass().simpleName}>" } - is CommandValueParameter.StringConstant -> { + is AbstractCommandValueParameter.StringConstant -> { this.expectingValue } } } } + fun validate(variants: List<CommandSignatureVariantFromKFunctionImpl>) { + + data class ErasedParameters( + val name: String, + val x: String, + ) + variants + } + @Throws(IllegalCommandDeclarationException::class) fun findSubCommands(): List<CommandSignatureVariantFromKFunctionImpl> { return command::class.functions // exclude static later @@ -184,14 +193,26 @@ internal class CommandReflector( annotationResolver.getSubCommandNames(command, function).map { createStringConstantParameter(it) } val functionValueParameters = - function.valueParameters.map { it.toUserDefinedCommandParameter() } + function.valueParameters.associateBy { it.toUserDefinedCommandParameter() } CommandSignatureVariantFromKFunctionImpl( receiverParameter = function.extensionReceiverParameter?.toCommandReceiverParameter(), - valueParameters = functionNameAsValueParameter + functionValueParameters, + valueParameters = functionNameAsValueParameter + functionValueParameters.keys, originFunction = function ) { call -> - function.callSuspend(command, *call.resolvedValueArguments.toTypedArray()) + val args = LinkedHashMap<KParameter, Any?>() + + call.resolvedValueArguments.forEach { (commandParameter, value) -> + val functionParameter = + functionValueParameters[commandParameter] ?: error("Could not find a corresponding function parameter '${commandParameter.name}'") + args[functionParameter] = value + } + + val instanceParameter = function.instanceParameter + if (instanceParameter != null) { + args[instanceParameter] = command + } + function.callSuspendBy(args) } }.toList() } @@ -203,12 +224,12 @@ internal class CommandReflector( return CommandReceiverParameter(this.type.isMarkedNullable, this.type) } - private fun createStringConstantParameter(expectingValue: String): CommandValueParameter.StringConstant { - return CommandValueParameter.StringConstant(null, expectingValue) + private fun createStringConstantParameter(expectingValue: String): AbstractCommandValueParameter.StringConstant { + return AbstractCommandValueParameter.StringConstant(null, expectingValue) } - private fun KParameter.toUserDefinedCommandParameter(): CommandValueParameter.UserDefinedType<KParameter> { - return CommandValueParameter.UserDefinedType(nameForCommandParameter(), this, this.isOptional, this.isVararg, this.type) + private fun KParameter.toUserDefinedCommandParameter(): AbstractCommandValueParameter.UserDefinedType<*> { + return AbstractCommandValueParameter.UserDefinedType<Any?>(nameForCommandParameter(), this.isOptional, this.isVararg, this.type) // Any? is erased } private fun KParameter.nameForCommandParameter(): String? = annotationResolver.getAnnotatedName(command, this) ?: this.name diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt index 1e5d26f64..382d0a617 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt @@ -31,14 +31,22 @@ import net.mamoe.mirai.message.data.* import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test -import kotlin.test.* +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertSame +import kotlin.test.assertTrue object TestCompositeCommand : CompositeCommand( ConsoleCommandOwner, "testComposite", "tsC" ) { @SubCommand - fun mute(seconds: Int) { + fun mute(seconds: Int = 60) { + Testing.ok(seconds) + } + + @SubCommand + fun mute(target: Long, seconds: Int) { Testing.ok(seconds) } } @@ -54,6 +62,7 @@ internal val sender by lazy { ConsoleCommandSender } internal val owner by lazy { ConsoleCommandOwner } +@OptIn(ExperimentalCommandDescriptors::class) internal class TestCommand { companion object { @JvmStatic @@ -152,6 +161,13 @@ internal class TestCommand { } } + @Test + fun `composite command descriptors`() { + val overloads = TestCompositeCommand.overloads + assertEquals("CommandSignatureVariant(seconds: Int = ...)", overloads[0].toString()) + assertEquals("CommandSignatureVariant(target: Long, seconds: Int)", overloads[1].toString()) + } + @Test fun `composite command executing`() = runBlocking { TestCompositeCommand.withRegistration { @@ -176,19 +192,19 @@ internal class TestCommand { @Suppress("UNUSED_PARAMETER") @SubCommand - fun mute(seconds: Int, arg2: Int) { + fun mute(seconds: Int, arg2: Int = 1) { Testing.ok(2) } } - assertFailsWith<IllegalStateException> { - composite.register() - } - /* + composite.register() + + println(composite.overloads.joinToString()) + composite.withRegistration { - assertEquals(1, withTesting { execute(sender, "tr", "mute 123") }) // one args, resolves to mute(Int) - assertEquals(2, withTesting { execute(sender, "tr", "mute 123 123") }) - }*/ + assertEquals(1, withTesting { assertSuccess(composite.execute(sender, "mute 123")) }) // one arg, resolves to mute(Int) + assertEquals(2, withTesting { assertSuccess(composite.execute(sender, "mute 123 1")) }) // two arg, resolved to mute(Int, Int) + } } } From b3880093bffefe8a95731e0a83231ef9be9a6b09 Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Sat, 24 Oct 2020 11:41:32 +0800 Subject: [PATCH 23/28] Fix StringConstant params --- .../internal/command/CommandReflector.kt | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandReflector.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandReflector.kt index 2dc504b85..ebba4b153 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandReflector.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandReflector.kt @@ -36,8 +36,11 @@ internal object CompositeCommandSubCommandAnnotationResolver : override fun hasAnnotation(ownerCommand: Command, function: KFunction<*>) = function.hasAnnotation<CompositeCommand.SubCommand>() - override fun getSubCommandNames(ownerCommand: Command, function: KFunction<*>): Array<out String> = - function.findAnnotation<CompositeCommand.SubCommand>()!!.value + override fun getSubCommandNames(ownerCommand: Command, function: KFunction<*>): Array<out String> { + val annotated = function.findAnnotation<CompositeCommand.SubCommand>()!!.value + return if (annotated.isEmpty()) arrayOf(function.name) + else annotated + } override fun getAnnotatedName(ownerCommand: Command, parameter: KParameter): String? = parameter.findAnnotation<CompositeCommand.Name>()?.value @@ -190,7 +193,7 @@ internal class CommandReflector( .map { function -> val functionNameAsValueParameter = - annotationResolver.getSubCommandNames(command, function).map { createStringConstantParameter(it) } + annotationResolver.getSubCommandNames(command, function).mapIndexed { index, s -> createStringConstantParameter(index, s) } val functionValueParameters = function.valueParameters.associateBy { it.toUserDefinedCommandParameter() } @@ -202,7 +205,10 @@ internal class CommandReflector( ) { call -> val args = LinkedHashMap<KParameter, Any?>() - call.resolvedValueArguments.forEach { (commandParameter, value) -> + for ((commandParameter, value) in call.resolvedValueArguments) { + if (commandParameter is AbstractCommandValueParameter.StringConstant) { + continue + } val functionParameter = functionValueParameters[commandParameter] ?: error("Could not find a corresponding function parameter '${commandParameter.name}'") args[functionParameter] = value @@ -224,8 +230,8 @@ internal class CommandReflector( return CommandReceiverParameter(this.type.isMarkedNullable, this.type) } - private fun createStringConstantParameter(expectingValue: String): AbstractCommandValueParameter.StringConstant { - return AbstractCommandValueParameter.StringConstant(null, expectingValue) + private fun createStringConstantParameter(index: Int, expectingValue: String): AbstractCommandValueParameter.StringConstant { + return AbstractCommandValueParameter.StringConstant("#$index", expectingValue) } private fun KParameter.toUserDefinedCommandParameter(): AbstractCommandValueParameter.UserDefinedType<*> { From a486ceb602dac6cd76a1f9356112dff05ff5a716 Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Sat, 24 Oct 2020 12:00:56 +0800 Subject: [PATCH 24/28] Resolution with optional defaults --- .../resolve/BuiltInCommandCallResolver.kt | 39 ++++++++++++------- .../mirai/console/command/TestCommand.kt | 7 ++-- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/BuiltInCommandCallResolver.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/BuiltInCommandCallResolver.kt index 58d420c56..0409808ac 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/BuiltInCommandCallResolver.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/BuiltInCommandCallResolver.kt @@ -8,9 +8,7 @@ import net.mamoe.mirai.console.command.parse.CommandCall import net.mamoe.mirai.console.command.parse.CommandValueArgument import net.mamoe.mirai.console.extensions.CommandCallResolverProvider import net.mamoe.mirai.console.util.ConsoleExperimentalApi -import net.mamoe.mirai.console.util.cast import net.mamoe.mirai.console.util.safeCast -import java.util.* /** * Builtin implementation of [CommandCallResolver] @@ -34,7 +32,10 @@ public object BuiltInCommandCallResolver : CommandCallResolver { private data class ResolveData( val variant: CommandSignatureVariant, val argumentAcceptances: List<ArgumentAcceptanceWithIndex>, - ) + val remainingParameters: List<AbstractCommandValueParameter<*>>, + ) { + val remainingOptionalCount: Int = remainingParameters.count { it.isOptional } + } private data class ArgumentAcceptanceWithIndex( val index: Int, @@ -51,15 +52,21 @@ public object BuiltInCommandCallResolver : CommandCallResolver { .mapNotNull l@{ signature -> val zipped = signature.valueParameters.zip(valueArguments) - if (signature.valueParameters.drop(zipped.size).any { !it.isOptional }) return@l null // not enough args + val remaining = signature.valueParameters.drop(zipped.size) - ResolveData(signature, zipped.mapIndexed { index, (parameter, argument) -> - val accepting = parameter.accepting(argument, context) - if (accepting.isNotAcceptable) { - return@l null // argument type not assignable - } - ArgumentAcceptanceWithIndex(index, accepting) - }) + if (remaining.any { !it.isOptional }) return@l null // not enough args + + ResolveData( + variant = signature, + argumentAcceptances = zipped.mapIndexed { index, (parameter, argument) -> + val accepting = parameter.accepting(argument, context) + if (accepting.isNotAcceptable) { + return@l null // argument type not assignable + } + ArgumentAcceptanceWithIndex(index, accepting) + }, + remainingParameters = remaining + ) } .also { result -> result.singleOrNull()?.let { return it.variant } } .takeLongestMatches() @@ -117,11 +124,13 @@ fun main() { } */ - private fun List<ResolveData>.takeLongestMatches(): List<ResolveData> { + private fun List<ResolveData>.takeLongestMatches(): Collection<ResolveData> { if (isEmpty()) return emptyList() - return associateByTo(TreeMap(Comparator.reverseOrder())) { it.variant.valueParameters.size }.let { m -> - val firstKey = m.keys.first().cast<Int>() - m.filter { it.key == firstKey }.map { it.value.cast() } + return associateWith { + it.variant.valueParameters.size - it.remainingOptionalCount * 1.001 // slightly lower priority with optional defaults. + }.let { m -> + val maxMatch = m.values.maxByOrNull { it } + m.filter { it.value == maxMatch }.keys } } } \ No newline at end of file diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt index 382d0a617..70b65b298 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt @@ -164,8 +164,8 @@ internal class TestCommand { @Test fun `composite command descriptors`() { val overloads = TestCompositeCommand.overloads - assertEquals("CommandSignatureVariant(seconds: Int = ...)", overloads[0].toString()) - assertEquals("CommandSignatureVariant(target: Long, seconds: Int)", overloads[1].toString()) + assertEquals("CommandSignatureVariant(<mute>, seconds: Int = ...)", overloads[0].toString()) + assertEquals("CommandSignatureVariant(<mute>, target: Long, seconds: Int)", overloads[1].toString()) } @Test @@ -225,6 +225,7 @@ internal class TestCommand { } override fun parse(raw: MessageContent, sender: CommandSender): MyClass { + if (raw is PlainText) return parse(raw.content, sender) assertSame(image, raw) return MyClass(2) } @@ -238,7 +239,7 @@ internal class TestCommand { } composite.withRegistration { - assertEquals(333, withTesting<MyClass> { execute(sender, "mute 333") }.value) + assertEquals(333, withTesting<MyClass> { assertSuccess(execute(sender, "mute 333")) }.value) assertEquals(2, withTesting<MyClass> { assertSuccess( execute(sender, buildMessageChain { From 87b56ade12c63f7c757f721099c1286b926ca673 Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Sat, 24 Oct 2020 12:12:53 +0800 Subject: [PATCH 25/28] Fix CommandValueArgumentParser<T>.parse --- .../console/command/descriptor/CommandValueArgumentParser.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser.kt index 43cd2c984..ff82ed6db 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser.kt @@ -109,7 +109,7 @@ public fun <T : Any> CommandValueArgumentParser<T>.parse(raw: Any, sender: Comma return when (raw) { is String -> parse(raw, sender) - is SingleMessage -> parse(raw, sender) + is MessageContent -> parse(raw, sender) else -> throw IllegalArgumentException("Illegal raw argument type: ${raw::class.qualifiedName}") } } From d10f2b4bea2cf4ad147814c04358ebf7a66b51d8 Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Sat, 24 Oct 2020 13:14:25 +0800 Subject: [PATCH 26/28] Support vararg in command --- .../mamoe/mirai/console/command/RawCommand.kt | 7 +- .../command/descriptor/CommandDescriptor.kt | 23 +++++- .../console/command/descriptor/TypeVariant.kt | 25 ++++--- .../command/parse/CommandValueArgument.kt | 53 +++++++++++--- .../resolve/BuiltInCommandCallResolver.kt | 72 ++++++++++++++----- .../mamoe/mirai/console/TestMiraiConosle.kt | 2 +- .../mirai/console/command/TestCommand.kt | 46 ++++++++++-- .../mirai/console/terminal/ConsoleThread.kt | 8 ++- 8 files changed, 187 insertions(+), 49 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt index 0da2747ec..2486f6f42 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt @@ -19,8 +19,9 @@ import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME import net.mamoe.mirai.console.internal.command.createOrFindCommandPermission import net.mamoe.mirai.console.internal.data.typeOf0 import net.mamoe.mirai.console.permission.Permission +import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.MessageChain -import net.mamoe.mirai.message.data.MessageChainBuilder +import net.mamoe.mirai.message.data.buildMessageChain /** * 无参数解析, 接收原生参数的指令. @@ -58,11 +59,11 @@ public abstract class RawCommand( override val overloads: List<CommandSignatureVariant> = listOf( CommandSignatureVariantImpl( receiverParameter = CommandReceiverParameter(false, typeOf0<CommandSender>()), - valueParameters = listOf(AbstractCommandValueParameter.UserDefinedType.createRequired<MessageChain>("args", true)) + valueParameters = listOf(AbstractCommandValueParameter.UserDefinedType.createRequired<Array<out Message>>("args", true)) ) { call -> val sender = call.caller val arguments = call.rawValueArguments - sender.onCommand(arguments.mapTo(MessageChainBuilder()) { it.value }.build()) + sender.onCommand(buildMessageChain { arguments.forEach { +it.value } }) } ) 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 index 805111f4a..556a44e6c 100644 --- 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 @@ -17,6 +17,7 @@ import net.mamoe.mirai.console.command.parse.CommandValueArgument import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall import net.mamoe.mirai.console.internal.data.classifierAsKClass import net.mamoe.mirai.console.internal.data.classifierAsKClassOrNull +import net.mamoe.mirai.console.internal.data.typeOf0 import net.mamoe.mirai.console.util.ConsoleExperimentalApi import kotlin.reflect.KClass import kotlin.reflect.KFunction @@ -173,6 +174,9 @@ public class CommandReceiverParameter<T : CommandSender>( } +internal val ANY_TYPE = typeOf0<Any>() +internal val ARRAY_OUT_ANY_TYPE = typeOf0<Array<out Any?>>() + @ExperimentalCommandDescriptors public sealed class AbstractCommandValueParameter<T> : CommandValueParameter<T>, AbstractCommandParameter<T>() { override fun toString(): String = buildString { @@ -184,8 +188,19 @@ public sealed class AbstractCommandValueParameter<T> : CommandValueParameter<T>, } public override fun accepting(argument: CommandValueArgument, commandArgumentContext: CommandArgumentContext?): ArgumentAcceptance { - val expectingType = this.type + if (isVararg) { + val arrayElementType = this.type.arguments.single() // Array<T> + return acceptingImpl(arrayElementType.type ?: ANY_TYPE, argument, commandArgumentContext) + } + return acceptingImpl(this.type, argument, commandArgumentContext) + } + + private fun acceptingImpl( + expectingType: KType, + argument: CommandValueArgument, + commandArgumentContext: CommandArgumentContext?, + ): ArgumentAcceptance { if (argument.type.isSubtypeOf(expectingType)) return ArgumentAcceptance.Direct argument.typeVariants.associateWith { typeVariant -> @@ -239,8 +254,12 @@ public sealed class AbstractCommandValueParameter<T> : CommandValueParameter<T>, ) : AbstractCommandValueParameter<T>() { init { requireNotNull(type.classifierAsKClassOrNull()) { - "CommandReceiverParameter.type.classifier must be KClass." + "type.classifier must be KClass." } + if (isVararg) + check(type.isSubtypeOf(ARRAY_OUT_ANY_TYPE)) { + "type must be subtype of Array if vararg. Given $type." + } } public companion object { 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 index 86761807d..4e2792638 100644 --- 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 @@ -11,11 +11,10 @@ package net.mamoe.mirai.console.command.descriptor import net.mamoe.mirai.console.command.parse.CommandCall import net.mamoe.mirai.console.command.parse.CommandCallParser -import net.mamoe.mirai.console.command.parse.RawCommandArgument -import net.mamoe.mirai.message.data.MessageChain -import net.mamoe.mirai.message.data.MessageContent -import net.mamoe.mirai.message.data.asMessageChain -import net.mamoe.mirai.message.data.content +import net.mamoe.mirai.console.command.parse.CommandValueArgument +import net.mamoe.mirai.console.internal.data.castOrNull +import net.mamoe.mirai.console.internal.data.kClassQualifiedName +import net.mamoe.mirai.message.data.* import kotlin.reflect.KType import kotlin.reflect.typeOf @@ -31,15 +30,18 @@ public interface TypeVariant<out OutType> { */ public val outType: KType - public fun mapValue(valueParameter: MessageContent): OutType + /** + * @see CommandValueArgument.value + */ + public fun mapValue(valueParameter: Message): OutType public companion object { @OptIn(ExperimentalStdlibApi::class) @JvmSynthetic - public inline operator fun <reified OutType> invoke(crossinline block: (valueParameter: RawCommandArgument) -> OutType): TypeVariant<OutType> { + public inline operator fun <reified OutType> invoke(crossinline block: (valueParameter: Message) -> OutType): TypeVariant<OutType> { return object : TypeVariant<OutType> { override val outType: KType = typeOf<OutType>() - override fun mapValue(valueParameter: MessageContent): OutType = block(valueParameter) + override fun mapValue(valueParameter: Message): OutType = block(valueParameter) } } } @@ -49,19 +51,20 @@ public interface TypeVariant<out OutType> { public object MessageContentTypeVariant : TypeVariant<MessageContent> { @OptIn(ExperimentalStdlibApi::class) override val outType: KType = typeOf<MessageContent>() - override fun mapValue(valueParameter: MessageContent): MessageContent = valueParameter + override fun mapValue(valueParameter: Message): MessageContent = + valueParameter.castOrNull<MessageContent>() ?: error("Accepts MessageContent only but given ${valueParameter.kClassQualifiedName}") } @ExperimentalCommandDescriptors public object MessageChainTypeVariant : TypeVariant<MessageChain> { @OptIn(ExperimentalStdlibApi::class) override val outType: KType = typeOf<MessageChain>() - override fun mapValue(valueParameter: MessageContent): MessageChain = valueParameter.asMessageChain() + override fun mapValue(valueParameter: Message): MessageChain = valueParameter.asMessageChain() } @ExperimentalCommandDescriptors public object ContentStringTypeVariant : TypeVariant<String> { @OptIn(ExperimentalStdlibApi::class) override val outType: KType = typeOf<String>() - override fun mapValue(valueParameter: MessageContent): String = valueParameter.content + override fun mapValue(valueParameter: Message): String = valueParameter.content } 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 index b20c14e00..30e908e60 100644 --- 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 @@ -12,18 +12,18 @@ package net.mamoe.mirai.console.command.parse import net.mamoe.mirai.console.command.descriptor.* +import net.mamoe.mirai.console.internal.data.castOrInternalError +import net.mamoe.mirai.console.internal.data.classifierAsKClass import net.mamoe.mirai.console.util.ConsoleExperimentalApi +import net.mamoe.mirai.message.data.Message +import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageContent +import net.mamoe.mirai.message.data.SingleMessage import kotlin.reflect.KType import kotlin.reflect.full.isSubtypeOf import kotlin.reflect.typeOf -/** - * For developing use, to be inlined in the future. - */ -public typealias RawCommandArgument = MessageContent - /** * @see CommandValueArgument */ @@ -36,7 +36,12 @@ public interface CommandArgument @ExperimentalCommandDescriptors public interface CommandValueArgument : CommandArgument { public val type: KType - public val value: RawCommandArgument + + /** + * [MessageContent] if single argument + * [MessageChain] is vararg + */ + public val value: Message public val typeVariants: List<TypeVariant<*>> } @@ -46,7 +51,7 @@ public interface CommandValueArgument : CommandArgument { @ConsoleExperimentalApi @ExperimentalCommandDescriptors public data class DefaultCommandValueArgument( - public override val value: RawCommandArgument, + public override val value: Message, ) : CommandValueArgument { @OptIn(ExperimentalStdlibApi::class) override val type: KType = typeOf<MessageContent>() @@ -73,6 +78,38 @@ public fun <T> CommandValueArgument.mapToType(type: KType): T = @ExperimentalCommandDescriptors public fun <T> CommandValueArgument.mapToTypeOrNull(expectingType: KType): T? { + if (expectingType.isSubtypeOf(ARRAY_OUT_ANY_TYPE)) { + val arrayElementType = expectingType.arguments.single().type ?: ANY_TYPE + + val result = ArrayList<Any?>() + + when (val value = value) { + is MessageChain -> { + for (message in value) { + result.add(mapToTypeOrNullImpl(arrayElementType, message)) + } + } + else -> { // single + value.castOrInternalError<SingleMessage>() + result.add(mapToTypeOrNullImpl(arrayElementType, value)) + } + } + + + @Suppress("UNCHECKED_CAST") + return result.toArray(arrayElementType.createArray(result.size)) as T + } + + @Suppress("UNCHECKED_CAST") + return mapToTypeOrNullImpl(expectingType, value) as T +} + +private fun KType.createArray(size: Int): Array<Any?> { + return java.lang.reflect.Array.newInstance(this.classifierAsKClass().javaObjectType, size).castOrInternalError() +} + +@OptIn(ExperimentalCommandDescriptors::class) +private fun CommandValueArgument.mapToTypeOrNullImpl(expectingType: KType, value: Message): Any? { @OptIn(ExperimentalStdlibApi::class) val result = typeVariants .filter { it.outType.isSubtypeOf(expectingType) } @@ -85,7 +122,7 @@ public fun <T> CommandValueArgument.mapToTypeOrNull(expectingType: KType): T? { else typeVariant } @Suppress("UNCHECKED_CAST") - return result.mapValue(value) as T + return result.mapValue(value) } @ExperimentalCommandDescriptors diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/BuiltInCommandCallResolver.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/BuiltInCommandCallResolver.kt index 0409808ac..98a203118 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/BuiltInCommandCallResolver.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/BuiltInCommandCallResolver.kt @@ -6,9 +6,12 @@ import net.mamoe.mirai.console.command.descriptor.* import net.mamoe.mirai.console.command.descriptor.ArgumentAcceptance.Companion.isNotAcceptable import net.mamoe.mirai.console.command.parse.CommandCall import net.mamoe.mirai.console.command.parse.CommandValueArgument +import net.mamoe.mirai.console.command.parse.DefaultCommandValueArgument import net.mamoe.mirai.console.extensions.CommandCallResolverProvider import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.safeCast +import net.mamoe.mirai.message.data.EmptyMessageChain +import net.mamoe.mirai.message.data.asMessageChain /** * Builtin implementation of [CommandCallResolver] @@ -26,11 +29,16 @@ public object BuiltInCommandCallResolver : CommandCallResolver { val signature = resolveImpl(callee, valueArguments, context) ?: return null - return ResolvedCommandCallImpl(call.caller, callee, signature, call.valueArguments, context ?: EmptyCommandArgumentContext) + return ResolvedCommandCallImpl(call.caller, + callee, + signature.variant, + signature.zippedArguments.map { it.second }, + context ?: EmptyCommandArgumentContext) } private data class ResolveData( val variant: CommandSignatureVariant, + val zippedArguments: List<Pair<AbstractCommandValueParameter<*>, CommandValueArgument>>, val argumentAcceptances: List<ArgumentAcceptanceWithIndex>, val remainingParameters: List<AbstractCommandValueParameter<*>>, ) { @@ -46,32 +54,60 @@ public object BuiltInCommandCallResolver : CommandCallResolver { callee: Command, valueArguments: List<CommandValueArgument>, context: CommandArgumentContext?, - ): CommandSignatureVariant? { + ): ResolveData? { + callee.overloads .mapNotNull l@{ signature -> - val zipped = signature.valueParameters.zip(valueArguments) + val valueParameters = signature.valueParameters - val remaining = signature.valueParameters.drop(zipped.size) + val zipped = valueParameters.zip(valueArguments).toMutableList() - if (remaining.any { !it.isOptional }) return@l null // not enough args + val remainingParameters = valueParameters.drop(zipped.size).toMutableList() - ResolveData( - variant = signature, - argumentAcceptances = zipped.mapIndexed { index, (parameter, argument) -> - val accepting = parameter.accepting(argument, context) - if (accepting.isNotAcceptable) { - return@l null // argument type not assignable + if (remainingParameters.any { !it.isOptional && !it.isVararg }) return@l null // not enough args. // vararg can be empty. + + if (zipped.isEmpty()) { + ResolveData( + variant = signature, + zippedArguments = emptyList(), + argumentAcceptances = emptyList(), + remainingParameters = remainingParameters, + ) + } else { + if (valueArguments.size > valueParameters.size && zipped.last().first.isVararg) { + // merge vararg arguments + val (varargParameter, varargFirstArgument) + = zipped.removeLast() + + zipped.add(varargParameter to DefaultCommandValueArgument(valueArguments.drop(zipped.size).map { it.value }.asMessageChain())) + } else { + // add default empty vararg argument + val remainingVararg = remainingParameters.find { it.isVararg } + if (remainingVararg != null) { + zipped.add(remainingVararg to DefaultCommandValueArgument(EmptyMessageChain)) + remainingParameters.remove(remainingVararg) } - ArgumentAcceptanceWithIndex(index, accepting) - }, - remainingParameters = remaining - ) + } + + ResolveData( + variant = signature, + zippedArguments = zipped, + argumentAcceptances = zipped.mapIndexed { index, (parameter, argument) -> + val accepting = parameter.accepting(argument, context) + if (accepting.isNotAcceptable) { + return@l null // argument type not assignable + } + ArgumentAcceptanceWithIndex(index, accepting) + }, + remainingParameters = remainingParameters + ) + } } - .also { result -> result.singleOrNull()?.let { return it.variant } } + .also { result -> result.singleOrNull()?.let { return it } } .takeLongestMatches() .ifEmpty { return null } - .also { result -> result.singleOrNull()?.let { return it.variant } } + .also { result -> result.singleOrNull()?.let { return it } } // take single ArgumentAcceptance.Direct .also { list -> @@ -79,7 +115,7 @@ public object BuiltInCommandCallResolver : CommandCallResolver { .flatMap { phase -> phase.argumentAcceptances.filter { it.acceptance is ArgumentAcceptance.Direct }.map { phase to it } } - candidates.singleOrNull()?.let { return it.first.variant } // single Direct + candidates.singleOrNull()?.let { return it.first } // single Direct if (candidates.distinctBy { it.second.index }.size != candidates.size) { // Resolution ambiguity /* diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/TestMiraiConosle.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/TestMiraiConosle.kt index 969f03425..c1eb1145c 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/TestMiraiConosle.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/TestMiraiConosle.kt @@ -89,7 +89,7 @@ internal object Testing { internal var cont: Continuation<Any?>? = null @Suppress("UNCHECKED_CAST") - suspend fun <R> withTesting(timeout: Long = 5000L, block: suspend () -> Unit): R { + suspend fun <R> withTesting(timeout: Long = 50000L, block: suspend () -> Unit): R { @Suppress("RemoveExplicitTypeArguments") // bug return if (timeout != -1L) { withTimeout<R>(timeout) { diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt index 70b65b298..f4943150d 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt @@ -31,10 +31,7 @@ import net.mamoe.mirai.message.data.* import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertSame -import kotlin.test.assertTrue +import kotlin.test.* object TestCompositeCommand : CompositeCommand( ConsoleCommandOwner, @@ -293,6 +290,47 @@ internal class TestCommand { } } } + + @Test + fun `test vararg`() { + runBlocking { + val optionCommand = object : CompositeCommand( + ConsoleCommandOwner, + "test" + ) { + @SubCommand + fun vararg(arg1: Int, vararg x: String) { + assertEquals(1, arg1) + Testing.ok(x) + } + } + optionCommand.withRegistration { + assertArrayEquals( + emptyArray<String>(), + withTesting { + assertSuccess(sender.executeCommand("/test vararg 1")) + } + ) + + assertArrayEquals( + arrayOf("s"), + withTesting<Array<String>> { + assertSuccess(sender.executeCommand("/test vararg 1 s")) + } + ) + assertArrayEquals( + arrayOf("s", "s", "s"), + withTesting { + assertSuccess(sender.executeCommand("/test vararg 1 s s s")) + } + ) + } + } + } +} + +fun <T> assertArrayEquals(expected: Array<out T>, actual: Array<out T>, message: String? = null) { + asserter.assertEquals(message, expected.contentToString(), actual.contentToString()) } @OptIn(ExperimentalCommandDescriptors::class) diff --git a/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/ConsoleThread.kt b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/ConsoleThread.kt index 021d58ae6..179901546 100644 --- a/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/ConsoleThread.kt +++ b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/ConsoleThread.kt @@ -15,8 +15,12 @@ import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.delay import kotlinx.coroutines.launch import net.mamoe.mirai.console.MiraiConsole -import net.mamoe.mirai.console.command.* +import net.mamoe.mirai.console.command.BuiltInCommands +import net.mamoe.mirai.console.command.CommandExecuteStatus +import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand +import net.mamoe.mirai.console.command.ConsoleCommandSender +import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.terminal.noconsole.NoConsole import net.mamoe.mirai.console.util.ConsoleInternalApi import net.mamoe.mirai.console.util.requestInput @@ -26,7 +30,7 @@ import org.jline.reader.UserInterruptException val consoleLogger by lazy { DefaultLogger("console") } -@OptIn(ConsoleInternalApi::class, ConsoleTerminalExperimentalApi::class) +@OptIn(ConsoleInternalApi::class, ConsoleTerminalExperimentalApi::class, ExperimentalCommandDescriptors::class) internal fun startupConsoleThread() { if (terminal is NoConsole) return From b9580ffcbd078bf26e488ded92087d3ff62e2c62 Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Sat, 24 Oct 2020 21:19:50 +0800 Subject: [PATCH 27/28] Review command --- .../mamoe/mirai/console/command/Command.kt | 13 +- .../mirai/console/command/CommandManager.kt | 217 +++++++++--------- .../command/resolve/CommandCallResolver.kt | 16 ++ .../internal/command/CommandManagerImpl.kt | 95 +++++--- .../mirai/console/command/TestCommand.kt | 16 +- .../console/command/commanTestingUtil.kt | 4 +- .../mirai/console/terminal/ConsoleThread.kt | 6 +- 7 files changed, 212 insertions(+), 155 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt index 5aac67c2d..9d0036771 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt @@ -11,7 +11,6 @@ package net.mamoe.mirai.console.command -import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register import net.mamoe.mirai.console.command.descriptor.CommandArgumentContextAware import net.mamoe.mirai.console.command.descriptor.CommandSignatureVariant import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors @@ -25,7 +24,7 @@ import net.mamoe.mirai.console.util.ConsoleExperimentalApi /** * 指令 * - * @see CommandManager.register 注册这个指令 + * @see CommandManager.registerCommand 注册这个指令 * * @see RawCommand 无参数解析, 接收原生参数的指令 * @see CompositeCommand 复合指令 @@ -52,7 +51,7 @@ public interface Command { public val secondaryNames: Array<out String> /** - * + * 指令可能的参数列表. */ @ConsoleExperimentalApi("Property name is experimental") @ExperimentalCommandDescriptors @@ -64,12 +63,12 @@ public interface Command { public val usage: String /** - * 指令描述, 用于显示在 [BuiltInCommands.HelpCommand] + * 描述, 用于显示在 [BuiltInCommands.HelpCommand] */ public val description: String /** - * 此指令所分配的权限. + * 为此指令分配的权限. * * ### 实现约束 * - [Permission.id] 应由 [CommandOwner.permissionId] 创建. 因此保证相同的 [PermissionId.namespace] @@ -82,6 +81,8 @@ public interface Command { * * 会影响聊天语境中的解析. */ + @ExperimentalCommandDescriptors + @ConsoleExperimentalApi public val prefixOptional: Boolean /** @@ -109,7 +110,7 @@ public interface Command { public fun checkCommandName(@ResolveContext(COMMAND_NAME) name: String) { when { name.isBlank() -> throw IllegalArgumentException("Command name should not be blank.") - name.any { it.isWhitespace() } -> throw IllegalArgumentException("Spaces is not yet allowed in command name.") + name.any { it.isWhitespace() } -> throw IllegalArgumentException("Spaces are not yet allowed in command name.") name.contains(':') -> throw IllegalArgumentException("':' is forbidden in command name.") name.contains('.') -> throw IllegalArgumentException("'.' is forbidden in command name.") } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt index f5d0ad07d..f837ed105 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt @@ -20,27 +20,17 @@ import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.parse.CommandCall import net.mamoe.mirai.console.command.parse.CommandCallParser -import net.mamoe.mirai.console.command.parse.CommandCallParser.Companion.parseCommandCall import net.mamoe.mirai.console.command.resolve.CommandCallResolver -import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall -import net.mamoe.mirai.console.extensions.CommandCallResolverProvider import net.mamoe.mirai.console.internal.command.CommandManagerImpl import net.mamoe.mirai.console.internal.command.CommandManagerImpl.executeCommand -import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage -import net.mamoe.mirai.console.permission.PermissionService.Companion.testPermission +import net.mamoe.mirai.console.internal.command.executeCommandImpl +import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.message.data.* /** * 指令管理器 */ public interface CommandManager { - /** - * 获取已经注册了的属于这个 [CommandOwner] 的指令列表. - * - * @return 这一时刻的浅拷贝. - */ - public val CommandOwner.registeredCommands: List<Command> - /** * 获取所有已经注册了指令列表. * @@ -54,9 +44,17 @@ public interface CommandManager { public val commandPrefix: String /** - * 取消注册所有属于 [this] 的指令 + * 获取已经注册了的属于这个 [CommandOwner] 的指令列表. + * + * @return 这一时刻的浅拷贝. */ - public fun CommandOwner.unregisterAllCommands() + public fun getRegisteredCommands(owner: CommandOwner): List<Command> + + + /** + * 取消注册所有属于 [owner] 的指令 + */ + public fun unregisterAllCommands(owner: CommandOwner) /** * 注册一个指令. @@ -75,35 +73,31 @@ public interface CommandManager { * * 注意: [内建指令][BuiltInCommands] 也可以被覆盖. */ - @JvmName("registerCommand") - public fun Command.register(override: Boolean = false): Boolean + public fun registerCommand(command: Command, override: Boolean = false): Boolean /** * 查找并返回重名的指令. 返回重名指令. */ - @JvmName("findCommandDuplicate") - public fun Command.findDuplicate(): Command? + public fun findDuplicateCommand(command: Command): Command? /** * 取消注册这个指令. * * 若指令未注册, 返回 `false`. */ - @JvmName("unregisterCommand") - public fun Command.unregister(): Boolean + public fun unregisterCommand(command: Command): Boolean /** - * 当 [this] 已经 [注册][register] 时返回 `true` + * 当 [command] 已经 [注册][registerCommand] 时返回 `true` */ - @JvmName("isCommandRegistered") - public fun Command.isRegistered(): Boolean + public fun isCommandRegistered(command: Command): Boolean /** * 解析并执行一个指令. * - * 如要避免参数解析, 请使用 [Command.onCommand] - * * ### 指令解析流程 + * 1. [CommandCallParser] 将 [MessageChain] 解析为 [CommandCall] + * 2. [CommandCallResolver] 将 [CommandCall] 解析为 [] * 1. [message] 的第一个消息元素的 [内容][Message.contentToString] 被作为指令名, 在已注册指令列表中搜索. (包含 [Command.prefixOptional] 相关的处理) * 2. 参数语法分析. * 在当前的实现下, [message] 被以空格和 [SingleMessage] 分割. @@ -112,7 +106,7 @@ public interface CommandManager { * 3. 参数解析. 各类型指令实现不同. 详见 [RawCommand], [CompositeCommand], [SimpleCommand] * * ### 扩展 - * 参数语法分析过程可能会被扩展, 插件可以自定义处理方式, 因此可能不会简单地使用空格分隔. + * 参数语法分析过程可能会被扩展, 插件可以自定义处理方式 ([CommandCallParser]), 因此可能不会简单地使用空格分隔. * * @param message 一条完整的指令. 如 "/managers add 123456.123456" * @param checkPermission 为 `true` 时检查权限 @@ -120,42 +114,48 @@ public interface CommandManager { * @see CommandCallParser * @see CommandCallResolver * + * @see CommandSender.executeCommand + * @see Command.execute + * * @return 执行结果 */ + @ExperimentalCommandDescriptors @JvmBlockingBridge - @OptIn(ExperimentalCommandDescriptors::class) public suspend fun executeCommand( caller: CommandSender, message: Message, checkPermission: Boolean = true, ): CommandExecuteResult { - return executeCommandImpl(this, message, caller, checkPermission) + return executeCommandImpl(message, caller, checkPermission) } /** - * 解析并执行一个指令 + * 执行一个确切的指令 * - * @param message 一条完整的指令. 如 "/managers add 123456.123456" - * @param checkPermission 为 `true` 时检查权限 + * @param command 目标指令 + * @param arguments 参数列表 * - * @return 执行结果 - * @see executeCommand + * @see executeCommand 获取更多信息 + * @see Command.execute */ - @JvmBlockingBridge - public suspend fun CommandSender.executeCommand( - message: String, - checkPermission: Boolean = true, - ): CommandExecuteResult = executeCommand(this, PlainText(message).asMessageChain(), checkPermission) - - @JvmName("resolveCall") + @ConsoleExperimentalApi + @JvmName("executeCommand") @ExperimentalCommandDescriptors - public fun CommandCall.resolve(): ResolvedCommandCall? { - GlobalComponentStorage.run { - CommandCallResolverProvider.useExtensions { provider -> - provider.instance.resolve(this@resolve)?.let { return it } - } + @JvmSynthetic + public suspend fun executeCommand( + sender: CommandSender, + command: Command, + arguments: Message = EmptyMessageChain, + checkPermission: Boolean = true, + ): CommandExecuteResult { + // TODO: 2020/10/18 net.mamoe.mirai.console.command.CommandManager.execute + val chain = buildMessageChain { + append(CommandManager.commandPrefix) + append(command.primaryName) + append(' ') + append(arguments) } - return null + return CommandManager.executeCommand(sender, chain, checkPermission) } /** @@ -170,78 +170,89 @@ public interface CommandManager { public fun matchCommand(commandName: String): Command? public companion object INSTANCE : CommandManager by CommandManagerImpl { - // TODO: 2020/8/20 https://youtrack.jetbrains.com/issue/KT-41191 + /** + * @see CommandManager.getRegisteredCommands + */ + @get:JvmName("registeredCommands0") + @get:JvmSynthetic + public inline val CommandOwner.registeredCommands: List<Command> + get() = getRegisteredCommands(this) - override val CommandOwner.registeredCommands: List<Command> get() = CommandManagerImpl.run { this@registeredCommands.registeredCommands } - override fun CommandOwner.unregisterAllCommands(): Unit = CommandManagerImpl.run { unregisterAllCommands() } - override fun Command.register(override: Boolean): Boolean = CommandManagerImpl.run { register(override) } - override fun Command.findDuplicate(): Command? = CommandManagerImpl.run { findDuplicate() } - override fun Command.unregister(): Boolean = CommandManagerImpl.run { unregister() } - override fun Command.isRegistered(): Boolean = CommandManagerImpl.run { isRegistered() } - override val commandPrefix: String get() = CommandManagerImpl.commandPrefix - override val allRegisteredCommands: List<Command> - get() = CommandManagerImpl.allRegisteredCommands + /** + * @see CommandManager.registerCommand + */ + @JvmSynthetic + public inline fun Command.register(override: Boolean = false): Boolean = registerCommand(this, override) + + /** + * @see CommandManager.unregisterCommand + */ + @JvmSynthetic + public inline fun Command.unregister(): Boolean = unregisterCommand(this) + + /** + * @see CommandManager.isCommandRegistered + */ + @get:JvmSynthetic + public inline val Command.isRegistered: Boolean + get() = isCommandRegistered(this) + + /** + * @see CommandManager.unregisterAll + */ + @JvmSynthetic + public inline fun CommandOwner.unregisterAll(): Unit = unregisterAllCommands(this) + + /** + * @see CommandManager.findDuplicate + */ + @JvmSynthetic + public inline fun Command.findDuplicate(): Command? = findDuplicateCommand(this) } } /** - * 执行一个确切的指令 - * @see executeCommand 获取更多信息 + * 解析并执行一个指令 + * + * @param message 一条完整的指令. 如 "/managers add 123456.123456" + * @param checkPermission 为 `true` 时检查权限 + * + * @return 执行结果 + * @see executeCommand */ -// @JvmBlockingBridge -// @JvmName("executeCommand") -public suspend fun Command.execute( - sender: CommandSender, - arguments: String = "", +@JvmName("execute0") +@ExperimentalCommandDescriptors +@JvmSynthetic +public suspend inline fun CommandSender.executeCommand( + message: String, checkPermission: Boolean = true, -): CommandExecuteResult = execute(sender, PlainText(arguments).asMessageChain(), checkPermission) +): CommandExecuteResult = CommandManager.executeCommand(this, PlainText(message).asMessageChain(), checkPermission) + /** * 执行一个确切的指令 * @see executeCommand 获取更多信息 */ -// @JvmBlockingBridge -// @JvmName("executeCommand") -public suspend fun Command.execute( +@JvmName("execute0") +@ExperimentalCommandDescriptors +@JvmSynthetic +public suspend inline fun Command.execute( sender: CommandSender, arguments: Message = EmptyMessageChain, checkPermission: Boolean = true, -): CommandExecuteResult { - // TODO: 2020/10/18 net.mamoe.mirai.console.command.CommandManager.execute - val chain = buildMessageChain { - append(CommandManager.commandPrefix) - append(this@execute.primaryName) - append(' ') - append(arguments) - } - return CommandManager.executeCommand(sender, chain, checkPermission) -} - - -// Don't move into CommandManager, compilation error / VerifyError -@OptIn(ExperimentalCommandDescriptors::class) -internal suspend fun executeCommandImpl( - receiver: CommandManager, - message: Message, - caller: CommandSender, - checkPermission: Boolean, -): CommandExecuteResult = with(receiver) { - val call = message.asMessageChain().parseCommandCall(caller) ?: return CommandExecuteResult.UnresolvedCall("") - val resolved = call.resolve() ?: return CommandExecuteResult.UnresolvedCall(call.calleeName) - - val command = resolved.callee - - if (checkPermission && !command.permission.testPermission(caller)) { - return CommandExecuteResult.PermissionDenied(command, call.calleeName) - } - - return try { - resolved.calleeSignature.call(resolved) - CommandExecuteResult.Success(resolved.callee, call.calleeName, EmptyMessageChain) - } catch (e: Throwable) { - CommandExecuteResult.ExecutionFailed(e, resolved.callee, call.calleeName, EmptyMessageChain) - } -} +): CommandExecuteResult = CommandManager.executeCommand(sender, this, arguments, checkPermission) +/** + * 执行一个确切的指令 + * @see executeCommand 获取更多信息 + */ +@JvmName("execute0") +@ExperimentalCommandDescriptors +@JvmSynthetic +public suspend inline fun Command.execute( + sender: CommandSender, + arguments: String = "", + checkPermission: Boolean = true, +): CommandExecuteResult = execute(sender, PlainText(arguments), checkPermission) 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 index b4671412f..8022f532b 100644 --- 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 @@ -12,6 +12,8 @@ package net.mamoe.mirai.console.command.resolve import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.parse.CommandCall import net.mamoe.mirai.console.extensions.CommandCallResolverProvider +import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage +import net.mamoe.mirai.console.util.ConsoleExperimentalApi /** * The resolver converting a [CommandCall] into [ResolvedCommandCall] based on registered [] @@ -22,4 +24,18 @@ import net.mamoe.mirai.console.extensions.CommandCallResolverProvider @ExperimentalCommandDescriptors public interface CommandCallResolver { public fun resolve(call: CommandCall): ResolvedCommandCall? + + public companion object { + @JvmName("resolveCall") + @ConsoleExperimentalApi + @ExperimentalCommandDescriptors + public fun CommandCall.resolve(): ResolvedCommandCall? { + GlobalComponentStorage.run { + CommandCallResolverProvider.useExtensions { provider -> + provider.instance.resolve(this@resolve)?.let { return it } + } + } + return null + } + } } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandManagerImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandManagerImpl.kt index 25476b2b2..f0bc776ca 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandManagerImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandManagerImpl.kt @@ -15,12 +15,19 @@ import kotlinx.coroutines.CoroutineScope import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.command.* import net.mamoe.mirai.console.command.Command.Companion.allNames +import net.mamoe.mirai.console.command.CommandManager.INSTANCE.findDuplicate import net.mamoe.mirai.console.command.CommandSender.Companion.toCommandSender import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors +import net.mamoe.mirai.console.command.parse.CommandCallParser.Companion.parseCommandCall +import net.mamoe.mirai.console.command.resolve.CommandCallResolver.Companion.resolve +import net.mamoe.mirai.console.permission.PermissionService.Companion.testPermission import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope import net.mamoe.mirai.event.Listener import net.mamoe.mirai.event.subscribeAlways import net.mamoe.mirai.message.MessageEvent +import net.mamoe.mirai.message.data.EmptyMessageChain +import net.mamoe.mirai.message.data.Message +import net.mamoe.mirai.message.data.asMessageChain import net.mamoe.mirai.message.data.content import net.mamoe.mirai.utils.MiraiLogger import java.util.concurrent.locks.ReentrantLock @@ -94,64 +101,90 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by MiraiCons ///// IMPL - override val CommandOwner.registeredCommands: List<Command> get() = _registeredCommands.filter { it.owner == this } + override fun getRegisteredCommands(owner: CommandOwner): List<Command> = _registeredCommands.filter { it.owner == owner } override val allRegisteredCommands: List<Command> get() = _registeredCommands.toList() // copy override val commandPrefix: String get() = "/" - override fun CommandOwner.unregisterAllCommands() { - for (registeredCommand in registeredCommands) { - registeredCommand.unregister() + override fun unregisterAllCommands(owner: CommandOwner) { + for (registeredCommand in getRegisteredCommands(owner)) { + unregisterCommand(registeredCommand) } } - override fun Command.register(override: Boolean): Boolean { - if (this is CompositeCommand) { - this.overloads // init lazy + override fun registerCommand(command: Command, override: Boolean): Boolean { + if (command is CompositeCommand) { + command.overloads // init lazy } kotlin.runCatching { - this.permission // init lazy - this.secondaryNames // init lazy - this.description // init lazy - this.usage // init lazy + command.permission // init lazy + command.secondaryNames // init lazy + command.description // init lazy + command.usage // init lazy }.onFailure { - throw IllegalStateException("Failed to init command ${this@register}.", it) + throw IllegalStateException("Failed to init command ${command}.", it) } - modifyLock.withLock { + this@CommandManagerImpl.modifyLock.withLock { if (!override) { - if (findDuplicate() != null) return false + if (command.findDuplicate() != null) return false } - _registeredCommands.add(this@register) - if (this.prefixOptional) { - for (name in this.allNames) { + this@CommandManagerImpl._registeredCommands.add(command) + if (command.prefixOptional) { + for (name in command.allNames) { val lowerCaseName = name.toLowerCase() - optionalPrefixCommandMap[lowerCaseName] = this - requiredPrefixCommandMap[lowerCaseName] = this + this@CommandManagerImpl.optionalPrefixCommandMap[lowerCaseName] = command + this@CommandManagerImpl.requiredPrefixCommandMap[lowerCaseName] = command } } else { - for (name in this.allNames) { + for (name in command.allNames) { val lowerCaseName = name.toLowerCase() - optionalPrefixCommandMap.remove(lowerCaseName) // ensure resolution consistency - requiredPrefixCommandMap[lowerCaseName] = this + this@CommandManagerImpl.optionalPrefixCommandMap.remove(lowerCaseName) // ensure resolution consistency + this@CommandManagerImpl.requiredPrefixCommandMap[lowerCaseName] = command } } return true } } - override fun Command.findDuplicate(): Command? = - _registeredCommands.firstOrNull { it.allNames intersectsIgnoringCase this.allNames } + override fun findDuplicateCommand(command: Command): Command? = + _registeredCommands.firstOrNull { it.allNames intersectsIgnoringCase command.allNames } - override fun Command.unregister(): Boolean = modifyLock.withLock { - if (this.prefixOptional) { - this.allNames.forEach { + override fun unregisterCommand(command: Command): Boolean = modifyLock.withLock { + if (command.prefixOptional) { + command.allNames.forEach { optionalPrefixCommandMap.remove(it.toLowerCase()) } } - this.allNames.forEach { + command.allNames.forEach { requiredPrefixCommandMap.remove(it.toLowerCase()) } - _registeredCommands.remove(this) + _registeredCommands.remove(command) } - override fun Command.isRegistered(): Boolean = this in _registeredCommands -} \ No newline at end of file + override fun isCommandRegistered(command: Command): Boolean = command in _registeredCommands +} + + +// Don't move into CommandManager, compilation error / VerifyError +@OptIn(ExperimentalCommandDescriptors::class) +internal suspend fun executeCommandImpl( + message: Message, + caller: CommandSender, + checkPermission: Boolean, +): CommandExecuteResult { + val call = message.asMessageChain().parseCommandCall(caller) ?: return CommandExecuteResult.UnresolvedCall("") + val resolved = call.resolve() ?: return CommandExecuteResult.UnresolvedCall(call.calleeName) + + val command = resolved.callee + + if (checkPermission && !command.permission.testPermission(caller)) { + return CommandExecuteResult.PermissionDenied(command, call.calleeName) + } + + return try { + resolved.calleeSignature.call(resolved) + CommandExecuteResult.Success(resolved.callee, call.calleeName, EmptyMessageChain) + } catch (e: Throwable) { + CommandExecuteResult.ExecutionFailed(e, resolved.callee, call.calleeName, EmptyMessageChain) + } +} + diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt index f4943150d..bf3b7c796 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt @@ -16,11 +16,11 @@ import kotlinx.coroutines.runBlocking import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.Testing import net.mamoe.mirai.console.Testing.withTesting -import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand +import net.mamoe.mirai.console.command.CommandManager.INSTANCE.getRegisteredCommands import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register -import net.mamoe.mirai.console.command.CommandManager.INSTANCE.registeredCommands -import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregister +import net.mamoe.mirai.console.command.CommandManager.INSTANCE.registerCommand import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregisterAllCommands +import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregisterCommand import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.descriptor.buildCommandArgumentContext @@ -78,20 +78,20 @@ internal class TestCommand { @Test fun testRegister() { try { - ConsoleCommandOwner.unregisterAllCommands() // builtins - TestSimpleCommand.unregister() + unregisterAllCommands(ConsoleCommandOwner) // builtins + unregisterCommand(TestSimpleCommand) assertTrue(TestCompositeCommand.register()) assertFalse(TestCompositeCommand.register()) - assertEquals(1, ConsoleCommandOwner.registeredCommands.size) + assertEquals(1, getRegisteredCommands(ConsoleCommandOwner).size) assertEquals(1, CommandManagerImpl._registeredCommands.size) assertEquals(2, CommandManagerImpl.requiredPrefixCommandMap.size, CommandManagerImpl.requiredPrefixCommandMap.entries.joinToString { it.toString() }) } finally { - TestCompositeCommand.unregister() + unregisterCommand(TestCompositeCommand) } } @@ -194,7 +194,7 @@ internal class TestCommand { } } - composite.register() + registerCommand(composite) println(composite.overloads.joinToString()) diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/commanTestingUtil.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/commanTestingUtil.kt index 758a7848b..8863af745 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/commanTestingUtil.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/commanTestingUtil.kt @@ -10,13 +10,13 @@ package net.mamoe.mirai.console.command import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register -import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregister +import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregisterCommand inline fun <T : Command, R> T.withRegistration(block: T.() -> R): R { this.register() try { return block() } finally { - this.unregister() + unregisterCommand(this) } } \ No newline at end of file diff --git a/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/ConsoleThread.kt b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/ConsoleThread.kt index 179901546..5703eb512 100644 --- a/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/ConsoleThread.kt +++ b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/ConsoleThread.kt @@ -15,11 +15,7 @@ import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.delay import kotlinx.coroutines.launch import net.mamoe.mirai.console.MiraiConsole -import net.mamoe.mirai.console.command.BuiltInCommands -import net.mamoe.mirai.console.command.CommandExecuteStatus -import net.mamoe.mirai.console.command.CommandManager -import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand -import net.mamoe.mirai.console.command.ConsoleCommandSender +import net.mamoe.mirai.console.command.* import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.terminal.noconsole.NoConsole import net.mamoe.mirai.console.util.ConsoleInternalApi From 648f2bf75fbe21b120b71986b8ddd114d2571440 Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Sat, 24 Oct 2020 21:20:16 +0800 Subject: [PATCH 28/28] Review permission --- .../internal/plugin/JvmPluginInternal.kt | 2 +- .../mirai/console/permission/Permission.kt | 8 ++++--- .../mirai/console/permission/PermissionId.kt | 22 +++++++++---------- .../console/permission/PermissionService.kt | 4 ++-- .../console/plugin/jvm/AbstractJvmPlugin.kt | 2 +- 5 files changed, 20 insertions(+), 18 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt index 9284baaa1..643aa58e3 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt @@ -48,7 +48,7 @@ internal abstract class JvmPluginInternal( final override val parentPermission: Permission by lazy { PermissionService.INSTANCE.register( - PermissionService.INSTANCE.allocatePermissionIdForPlugin(this, "*", PermissionService.PluginPermissionIdRequestType.ROOT_PERMISSION), + PermissionService.INSTANCE.allocatePermissionIdForPlugin(this, "*", PermissionService.PluginPermissionIdRequestType.PLUGIN_ROOT_PERMISSION), "The base permission" ) } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/Permission.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/Permission.kt index b7c7cd80f..6a8b72425 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/Permission.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/Permission.kt @@ -7,6 +7,8 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:Suppress("unused") + package net.mamoe.mirai.console.permission import net.mamoe.mirai.console.command.BuiltInCommands @@ -65,10 +67,10 @@ public interface Permission { * @see RootPermission 推荐 Kotlin 用户使用. */ @JvmStatic - public fun getRootPermission(): Permission = PermissionService.INSTANCE.rootPermission + public fun getRootPermission(): Permission = RootPermission /** - * 递归获取 [Permission.parent], `permission.parent.parent`, permission.parent.parent` ... 直到 [Permission.parent] 为它自己. + * 递归获取 [Permission.parent], `permission.parent.parent`, permission.parent.parent.parent` ... 直到 [Permission.parent] 为它自己. */ @get:JvmStatic public val Permission.parentsWithSelf: Sequence<Permission> @@ -82,5 +84,5 @@ public interface Permission { * 根权限. 是所有权限的父权限. 权限 ID 为 "*:*" */ @get:JvmSynthetic -public val RootPermission: Permission +public inline val RootPermission: Permission // It might be removed in the future, so make it inline to avoid ABI changes. get() = PermissionService.INSTANCE.rootPermission \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionId.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionId.kt index 9a92e8be2..6cba1f417 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionId.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionId.kt @@ -35,20 +35,20 @@ public data class PermissionId( "' ' is not allowed in namespace" } require(!name.contains(' ')) { - "' ' is not allowed in id" + "' ' is not allowed in name" } require(!namespace.contains(':')) { "':' is not allowed in namespace" } require(!name.contains(':')) { - "':' is not allowed in id" + "':' is not allowed in name" } } public object PermissionIdAsStringSerializer : KSerializer<PermissionId> by String.serializer().map( serializer = { it.namespace + ":" + it.name }, - deserializer = { it.split(':').let { (namespace, id) -> PermissionId(namespace, id) } } + deserializer = ::parseFromString ) /** @@ -76,11 +76,11 @@ public data class PermissionId( */ @JvmStatic @Throws(IllegalArgumentException::class) - public fun checkPermissionIdName(@ResolveContext(PERMISSION_NAME) value: String) { + public fun checkPermissionIdName(@ResolveContext(PERMISSION_NAME) name: String) { when { - value.isBlank() -> throw IllegalArgumentException("PermissionId.name should not be blank.") - value.any { it.isWhitespace() } -> throw IllegalArgumentException("Spaces is not yet allowed in PermissionId.name.") - value.contains(':') -> throw IllegalArgumentException("':' is forbidden in PermissionId.name.") + name.isBlank() -> throw IllegalArgumentException("PermissionId.name should not be blank.") + name.any { it.isWhitespace() } -> throw IllegalArgumentException("Spaces are not yet allowed in PermissionId.name.") + name.contains(':') -> throw IllegalArgumentException("':' is forbidden in PermissionId.name.") } } @@ -89,11 +89,11 @@ public data class PermissionId( */ @JvmStatic @Throws(IllegalArgumentException::class) - public fun checkPermissionIdNamespace(@ResolveContext(PERMISSION_NAME) value: String) { + public fun checkPermissionIdNamespace(@ResolveContext(PERMISSION_NAME) namespace: String) { when { - value.isBlank() -> throw IllegalArgumentException("PermissionId.namespace should not be blank.") - value.any { it.isWhitespace() } -> throw IllegalArgumentException("Spaces is not yet allowed in PermissionId.namespace.") - value.contains(':') -> throw IllegalArgumentException("':' is forbidden in PermissionId.namespace.") + namespace.isBlank() -> throw IllegalArgumentException("PermissionId.namespace should not be blank.") + namespace.any { it.isWhitespace() } -> throw IllegalArgumentException("Spaces are not yet allowed in PermissionId.namespace.") + namespace.contains(':') -> throw IllegalArgumentException("':' is forbidden in PermissionId.namespace.") } } } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionService.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionService.kt index 32e3d57fd..e6e2033b1 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionService.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionService.kt @@ -128,10 +128,10 @@ public interface PermissionService<P : Permission> { /** [Plugin] 尝试分配的 [PermissionId] 来源 */ public enum class PluginPermissionIdRequestType { /** For [Plugin.parentPermission] */ - ROOT_PERMISSION, + PLUGIN_ROOT_PERMISSION, /** For [Plugin.permissionId] */ - PERMISSION_ID + NORMAL } public companion object { diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt index fb1f1849f..ce5f98ce3 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt @@ -39,7 +39,7 @@ public abstract class AbstractJvmPlugin @JvmOverloads constructor( public final override val loader: JvmPluginLoader get() = super<JvmPluginInternal>.loader public final override fun permissionId(name: String): PermissionId = - PermissionService.INSTANCE.allocatePermissionIdForPlugin(this, name, PermissionService.PluginPermissionIdRequestType.PERMISSION_ID) + PermissionService.INSTANCE.allocatePermissionIdForPlugin(this, name, PermissionService.PluginPermissionIdRequestType.NORMAL) /** * 重载 [PluginData]