From 5f6873e3477989b3bab44b60efd954646f0c7939 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sat, 19 Sep 2020 15:56:57 +0800 Subject: [PATCH 01/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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] From 3ed018c4f86169b73d01556d4a2070ab8de5fd77 Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Sat, 24 Oct 2020 21:24:52 +0800 Subject: [PATCH 29/41] Review plugin --- .../internal/plugin/PluginManagerImpl.kt | 24 +++++----- .../net/mamoe/mirai/console/plugin/Plugin.kt | 45 +++++++++++-------- .../mirai/console/plugin/PluginManager.kt | 43 ++++++++++-------- .../console/plugin/loader/PluginLoader.kt | 13 +++--- 4 files changed, 70 insertions(+), 55 deletions(-) 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 68045ff93..d26feac23 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 @@ -20,6 +20,7 @@ import net.mamoe.mirai.console.internal.data.mkdir import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage import net.mamoe.mirai.console.plugin.Plugin import net.mamoe.mirai.console.plugin.PluginManager +import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.safeLoader import net.mamoe.mirai.console.plugin.description.PluginDependency import net.mamoe.mirai.console.plugin.description.PluginDescription import net.mamoe.mirai.console.plugin.jvm.JvmPlugin @@ -60,18 +61,17 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol override val pluginLoaders: List<PluginLoader<*, *>> get() = _pluginLoaders.toList() - override val Plugin.description: PluginDescription - get() = if (this is JvmPlugin) { - this.safeLoader.getPluginDescription(this) - } else resolvedPlugins.firstOrNull { it == this } - ?.loader?.cast<PluginLoader<Plugin, PluginDescription>>() - ?.getPluginDescription(this) - ?: error("Plugin is unloaded") + override fun getPluginDescription(plugin: Plugin): PluginDescription = if (plugin is JvmPlugin) { + plugin.safeLoader.getPluginDescription(plugin) + } else resolvedPlugins.firstOrNull { it == plugin } + ?.loader?.cast<PluginLoader<Plugin, PluginDescription>>() + ?.getPluginDescription(plugin) + ?: error("Plugin is unloaded") init { MiraiConsole.coroutineContext[Job]!!.invokeOnCompletion { - plugins.forEach { it.disable() } + plugins.forEach { disablePlugin(it) } } } @@ -98,10 +98,10 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol this.enable(plugin as P) }.fold( onSuccess = { - logger.info { "Successfully enabled plugin ${plugin.description.name}" } + logger.info { "Successfully enabled plugin ${getPluginDescription(plugin).name}" } }, onFailure = { - logger.info { "Cannot enable plugin ${plugin.description.name}" } + logger.info { "Cannot enable plugin ${getPluginDescription(plugin).name}" } throw it } ) @@ -166,7 +166,7 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol } internal fun enableAllLoadedPlugins() { - resolvedPlugins.forEach { it.enable() } + resolvedPlugins.forEach { enablePlugin(it) } } @kotlin.jvm.Throws(PluginLoadException::class) @@ -180,7 +180,7 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol private fun List<PluginLoader<*, *>>.listAndSortAllPlugins(): List<PluginDescriptionWithLoader> { return flatMap { loader -> - loader.listPlugins().map { plugin -> plugin.description.wrapWith(loader, plugin) } + loader.listPlugins().map { plugin -> getPluginDescription(plugin).wrapWith(loader, plugin) } }.sortByDependencies() } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt index 8db84c890..9fd35e08b 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt @@ -12,23 +12,22 @@ package net.mamoe.mirai.console.plugin import net.mamoe.mirai.console.command.CommandOwner -import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.disable -import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.enable -import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.safeLoader +import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.getPluginDescription import net.mamoe.mirai.console.plugin.description.PluginDependency import net.mamoe.mirai.console.plugin.description.PluginDescription import net.mamoe.mirai.console.plugin.jvm.JvmPlugin import net.mamoe.mirai.console.plugin.loader.PluginLoader import net.mamoe.mirai.console.util.SemVersion +import kotlin.DeprecationLevel.ERROR /** * 表示一个 mirai-console 插件. * - * @see PluginManager.enable 启用一个插件 - * @see PluginManager.disable 禁用一个插件 + * @see PluginManager.enablePlugin 启用一个插件 + * @see PluginManager.disablePlugin 禁用一个插件 * @see PluginManager.description 获取一个插件的 [描述][PluginDescription] * - * @see PluginDescription 插件描述, 需由 [PluginLoader] 帮助提供([PluginLoader.description]) + * @see PluginDescription 插件描述, 需由 [PluginLoader] 帮助提供([PluginLoader.getPluginDescription]) * @see JvmPlugin Java, Kotlin 或其他 JVM 平台插件 * @see PluginFileExtensions 支持文件系统存储的扩展 * @@ -38,8 +37,8 @@ public interface Plugin : CommandOwner { /** * 判断此插件是否已启用 * - * @see PluginManager.enable 启用一个插件 - * @see PluginManager.disable 禁用一个插件 + * @see PluginManager.enablePlugin 启用一个插件 + * @see PluginManager.disablePlugin 禁用一个插件 */ public val isEnabled: Boolean @@ -49,32 +48,42 @@ public interface Plugin : CommandOwner { public val loader: PluginLoader<*, *> } -/** - * 获取 [PluginDescription] - */ -public inline val Plugin.description: PluginDescription get() = this.safeLoader.getPluginDescription(this) +@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") +@kotlin.internal.LowPriorityInOverloadResolution +@Deprecated( + "Moved to companion for a better Java API. ", + ReplaceWith("this.description", "net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.description"), + level = ERROR +) +public inline val Plugin.description: PluginDescription + get() = getPluginDescription(this) // resolved to net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.getDescription /** - * 获取 [PluginDescription.name`] + * 获取 [PluginDescription.name] */ -public inline val Plugin.name: String get() = this.description.name +public inline val Plugin.name: String get() = getPluginDescription(this).name + +/** + * 获取 [PluginDescription.id] + */ +public inline val Plugin.id: String get() = getPluginDescription(this).id /** * 获取 [PluginDescription.version] */ -public inline val Plugin.version: SemVersion get() = this.description.version +public inline val Plugin.version: SemVersion get() = getPluginDescription(this).version /** * 获取 [PluginDescription.info] */ -public inline val Plugin.info: String get() = this.description.info +public inline val Plugin.info: String get() = getPluginDescription(this).info /** * 获取 [PluginDescription.author] */ -public inline val Plugin.author: String get() = this.description.author +public inline val Plugin.author: String get() = getPluginDescription(this).author /** * 获取 [PluginDescription.dependencies] */ -public inline val Plugin.dependencies: Set<PluginDependency> get() = this.description.dependencies +public inline val Plugin.dependencies: Set<PluginDependency> get() = getPluginDescription(this).dependencies diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginManager.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginManager.kt index 309f5acc1..e5ad8140f 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginManager.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginManager.kt @@ -105,45 +105,52 @@ public interface PluginManager { /** * 获取插件的 [描述][PluginDescription], 通过 [PluginLoader.getPluginDescription] */ - public val Plugin.description: PluginDescription + public fun getPluginDescription(plugin: Plugin): PluginDescription /** * 禁用这个插件 * * @see PluginLoader.disable */ - public fun Plugin.disable(): Unit = safeLoader.disable(this) + public fun disablePlugin(plugin: Plugin): Unit = plugin.safeLoader.disable(plugin) /** * 加载这个插件 * * @see PluginLoader.load */ - public fun Plugin.load(): Unit = safeLoader.load(this) + public fun loadPlugin(plugin: Plugin): Unit = plugin.safeLoader.load(plugin) /** * 启用这个插件 * * @see PluginLoader.enable */ - public fun Plugin.enable(): Unit = safeLoader.enable(this) - - /** - * 经过泛型类型转换的 [Plugin.loader] - */ - @get:JvmSynthetic - @Suppress("UNCHECKED_CAST") - public val <P : Plugin> P.safeLoader: PluginLoader<P, PluginDescription> - get() = this.loader as PluginLoader<P, PluginDescription> + public fun enablePlugin(plugin: Plugin): Unit = plugin.safeLoader.enable(plugin) // endregion public companion object INSTANCE : PluginManager by PluginManagerImpl { - // due to Kotlin's bug - public override val Plugin.description: PluginDescription get() = PluginManagerImpl.run { description } - public override fun Plugin.disable(): Unit = PluginManagerImpl.run { disable() } - public override fun Plugin.enable(): Unit = PluginManagerImpl.run { enable() } - public override fun Plugin.load(): Unit = PluginManagerImpl.run { load() } - public override val <P : Plugin> P.safeLoader: PluginLoader<P, PluginDescription> get() = PluginManagerImpl.run { safeLoader } + /** + * 经过泛型类型转换的 [Plugin.loader] + */ + @get:JvmSynthetic + @Suppress("UNCHECKED_CAST") + public inline val <P : Plugin> P.safeLoader: PluginLoader<P, PluginDescription> + get() = this.loader as PluginLoader<P, PluginDescription> + + + @get:JvmSynthetic + public inline val Plugin.description: PluginDescription + get() = getPluginDescription(this) + + @JvmSynthetic + public inline fun Plugin.disable(): Unit = disablePlugin(this) + + @JvmSynthetic + public inline fun Plugin.enable(): Unit = enablePlugin(this) + + @JvmSynthetic + public inline fun Plugin.load(): Unit = loadPlugin(this) } } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/loader/PluginLoader.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/loader/PluginLoader.kt index 280ce144b..40a2ad152 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/loader/PluginLoader.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/loader/PluginLoader.kt @@ -14,8 +14,7 @@ package net.mamoe.mirai.console.plugin.loader import net.mamoe.mirai.console.extensions.PluginLoaderProvider import net.mamoe.mirai.console.plugin.Plugin import net.mamoe.mirai.console.plugin.PluginManager -import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.disable -import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.enable +import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.enablePlugin import net.mamoe.mirai.console.plugin.description.PluginDescription import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader @@ -66,9 +65,9 @@ public interface PluginLoader<P : Plugin, D : PluginDescription> { public fun getPluginDescription(plugin: P): D /** - * 主动加载一个插件 (实例), 但不 [启用][enable] 它. 返回加载成功的主类实例 + * 主动加载一个插件 (实例), 但不 [启用][enablePlugin] 它. 返回加载成功的主类实例 * - * **实现注意**: Console 不会把一个已经启用了的插件再次调用 [load] 或 [enable], 但不排除意外情况. 实现本函数时应在这种情况时立即抛出异常 [IllegalStateException]. + * **实现注意**: Console 不会把一个已经启用了的插件再次调用 [load] 或 [enablePlugin], 但不排除意外情况. 实现本函数时应在这种情况时立即抛出异常 [IllegalStateException]. * * **实现细节**: 此函数只允许抛出 [PluginLoadException] 作为正常失败原因, 其他任意异常都属于意外错误. * 当异常发生时, 插件将会直接被放弃加载, 并影响依赖它的其他插件. @@ -82,7 +81,7 @@ public interface PluginLoader<P : Plugin, D : PluginDescription> { /** * 主动启用这个插件. * - * **实现注意**: Console 不会把一个已经启用了的插件再次调用 [load] 或 [enable], 但不排除意外情况. 实现本函数时应在这种情况时立即抛出异常 [IllegalStateException]. + * **实现注意**: Console 不会把一个已经启用了的插件再次调用 [load] 或 [enablePlugin], 但不排除意外情况. 实现本函数时应在这种情况时立即抛出异常 [IllegalStateException]. * * **实现细节**: 此函数可抛出 [PluginLoadException] 作为正常失败原因, 其他任意异常都属于意外错误. * 当异常发生时, 插件将会直接被放弃加载, 并影响依赖它的其他插件. @@ -90,7 +89,7 @@ public interface PluginLoader<P : Plugin, D : PluginDescription> { * @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如找不到主类等). * @throws IllegalStateException 在插件已经被加载时抛出. 这属于意料之外的情况. * - * @see PluginManager.enable + * @see PluginManager.enablePlugin */ @Throws(IllegalStateException::class, PluginLoadException::class) public fun enable(plugin: P) @@ -103,7 +102,7 @@ public interface PluginLoader<P : Plugin, D : PluginDescription> { * * @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如找不到主类等). * - * @see PluginManager.disable + * @see PluginManager.disablePlugin */ @Throws(IllegalStateException::class, PluginLoadException::class) public fun disable(plugin: P) From 321aa74a669fe34d7d184eb607323dafd699206b Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Sat, 24 Oct 2020 21:26:57 +0800 Subject: [PATCH 30/41] Mark PluginPermissionIdRequestType as experimental --- .../net/mamoe/mirai/console/permission/PermissionService.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 477afdc26..fcd3ee841 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 @@ -17,8 +17,8 @@ import net.mamoe.mirai.console.extensions.PermissionServiceProvider import net.mamoe.mirai.console.internal.permission.checkType import net.mamoe.mirai.console.permission.Permission.Companion.parentsWithSelf import net.mamoe.mirai.console.plugin.Plugin -import net.mamoe.mirai.console.plugin.description -import net.mamoe.mirai.console.plugin.name +import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.description +import net.mamoe.mirai.console.util.ConsoleExperimentalApi import kotlin.reflect.KClass /** @@ -93,6 +93,7 @@ public interface PermissionService<P : Permission> { ): P /** 为 [Plugin] 分配一个 [PermissionId] */ + @ConsoleExperimentalApi public fun allocatePermissionIdForPlugin( plugin: Plugin, @ResolveContext(COMMAND_NAME) permissionName: String, @@ -127,6 +128,7 @@ public interface PermissionService<P : Permission> { public fun cancel(permitteeId: PermitteeId, permission: P, recursive: Boolean) /** [Plugin] 尝试分配的 [PermissionId] 来源 */ + @ConsoleExperimentalApi public enum class PluginPermissionIdRequestType { /** For [Plugin.parentPermission] */ PLUGIN_ROOT_PERMISSION, From df8b819d37bfe9132440256a491e66cc8bcce141 Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Sat, 24 Oct 2020 21:37:33 +0800 Subject: [PATCH 31/41] Test yaml on save, close #203, close #191 --- .../console/internal/data/MultiFilePluginDataStorageImpl.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 800229946..e8226315d 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 @@ -73,7 +73,9 @@ internal open class MultiFilePluginDataStorageImpl( public override fun store(holder: PluginDataHolder, instance: PluginData) { getPluginDataFile(holder, instance).writeText( kotlin.runCatching { - yaml.encodeToString(instance.updaterSerializer, Unit) + yaml.encodeToString(instance.updaterSerializer, Unit).also { + yaml.decodeAnyFromString(it) // test yaml + } }.recoverCatching { // Just use mainLogger for convenience. MiraiConsole.mainLogger.warning( From 0c98abbb31816282e21dcac11f795d72b9cf8876 Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Sat, 24 Oct 2020 21:41:02 +0800 Subject: [PATCH 32/41] Bump versions; 1.0-RC-dev-32 --- buildSrc/src/main/kotlin/Versions.kt | 6 +++--- .../net/mamoe/mirai/console/gradle/VersionConstants.kt | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 37a6ea152..ac6523639 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -8,8 +8,8 @@ */ object Versions { - const val core = "1.3.0" - const val console = "1.0-RC-dev-31" + const val core = "1.3.2" + const val console = "1.0-RC-dev-32" const val consoleGraphical = "0.0.7" const val consoleTerminal = console @@ -19,7 +19,7 @@ object Versions { const val coroutines = "1.3.9" const val collectionsImmutable = "0.3.2" const val serialization = "1.0.0-RC" - const val ktor = "1.4.0" + const val ktor = "1.4.1" const val atomicFU = "0.14.4" const val androidGradle = "3.6.2" 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 6c293ee10..8dd24fdf1 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-30" // value is written here automatically during build - const val CORE_VERSION = "1.3.0" // value is written here automatically during build + const val CONSOLE_VERSION = "1.0-RC-dev-32" // value is written here automatically during build + const val CORE_VERSION = "1.3.2" // value is written here automatically during build } \ No newline at end of file From 0007a97d669dd35225f036326b91106b7a4ed58e Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Sun, 25 Oct 2020 13:00:55 +0800 Subject: [PATCH 33/41] Review SemVersion: Add SemVersion.equals; Amend hashCode; Add docs. --- .../mamoe/mirai/console/util/SemVersion.kt | 85 ++++++++++++++----- 1 file changed, 66 insertions(+), 19 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt index 89519e813..5e23cb505 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt @@ -47,10 +47,10 @@ import kotlin.LazyThreadSafetyMode.PUBLICATION * ``` * 其中 identifier 和 metadata 都是可选的. * - * 对于核心版本号, 此实现稍微比 semver 宽松一些, 允许 x.y 的存在. + * 对于核心版本号, 此实现稍微比语义化版本规范宽松一些, 允许 x.y 的存在. * - * @see Requirement - * @see SemVersion.invoke + * @see Requirement 版本号要修 + * @see SemVersion.invoke 由字符串解析 */ @Serializable(with = SemVersion.SemVersionAsStringSerializer::class) public data class SemVersion @@ -69,6 +69,15 @@ internal constructor( /** 版本号元数据, 不参与版本号对比([compareTo]), 但是参与版本号严格对比([equals]) */ public val metadata: String? = null, ) : Comparable<SemVersion> { + + init { + require(major >= 0) { "major must >= 0" } + require(minor >= 0) { "minor must >= 0" } + if (patch != null) require(patch >= 0) { "patch must >= 0" } + if (identifier != null) require(identifier.none(Char::isWhitespace)) { "identifier must not contain whitespace" } + if (metadata != null) require(metadata.none(Char::isWhitespace)) { "metadata must not contain whitespace" } + } + /** * 一条依赖规则 * @see [parseRangeRequirement] @@ -103,9 +112,9 @@ internal constructor( * - 如果不确定版本号是否合法, 可以使用 [regex101.com](https://regex101.com/r/vkijKf/1/) 进行检查 * - 此实现使用的正则表达式为 `^(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$` */ - @Throws(IllegalArgumentException::class, NumberFormatException::class) @JvmStatic @JvmName("parse") + @Throws(IllegalArgumentException::class, NumberFormatException::class) public operator fun invoke(@ResolveContext(PLUGIN_VERSION) version: String): SemVersion = SemVersionInternal.parse(version) /** @@ -138,13 +147,14 @@ internal constructor( * - 如果目标版本号携带有先行版本号, 请不要忘记先行版本号 * - 因为 `()` 已经用于数学区间, 使用 `{}` 替代 `()` */ - @Throws(IllegalArgumentException::class) @JvmStatic + @Throws(IllegalArgumentException::class) public fun parseRangeRequirement(@ResolveContext(VERSION_REQUIREMENT) requirement: String): Requirement = SemVersionInternal.parseRangeRequirement(requirement) /** @see [Requirement.test] */ @JvmStatic + @Throws(IllegalArgumentException::class, NumberFormatException::class) public fun Requirement.test(@ResolveContext(PLUGIN_VERSION) version: String): Boolean = test(invoke(version)) /** @@ -157,6 +167,7 @@ internal constructor( * 当满足 [requirement] 时返回 true, 否则返回 false */ @JvmStatic + @Throws(IllegalArgumentException::class) public fun SemVersion.satisfies(@ResolveContext(VERSION_REQUIREMENT) requirement: String): Boolean = parseRangeRequirement(requirement).test(this) /** for Kotlin only */ @@ -185,7 +196,10 @@ internal constructor( } } - override fun toString(): String = toString + /** + * 返回类似 `1.0.0-M4+c25733b8` 的字符串. + */ + public override fun toString(): String = toString /** * 将 [SemVersion] 转为 Kotlin data class 风格的 [String] @@ -194,27 +208,60 @@ internal constructor( return "SemVersion(major=$major, minor=$minor, patch=$patch, identifier=$identifier, metadata=$metadata)" } - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as SemVersion - - return compareTo(other) == 0 && other.identifier == identifier && other.metadata == metadata + /** + * 比较 `this` 和 [other]. + * + * @param deep 为 `true` 时进行深度比较, 相当于 [equals]. 为 `false` 时相当于 `compareTo(other) == 0` + * @see compareTo + */ + public fun equals(other: SemVersion, deep: Boolean): Boolean { + return if (deep) { + (other.major == major + && other.minor == minor + && other.patch == patch + && other.identifier == identifier + && other.metadata == metadata) + } else { + this.compareTo(other) == 0 + } } - override fun hashCode(): Int { - var result = major shl minor - result *= (patch ?: 1) + /** + * 深度比较 `this` 和 [other], 当且仅当 [major], [patch], [minor], [identifier], [metadata] 完全相同时返回 `true`. + * + * 如: `1.0.0-RC` != `1.0-RC` + * + * @see compareTo + */ + public override fun equals(other: Any?): Boolean { + if (other === null) return false + if (this === other) return true + if (javaClass != other.javaClass) return false + return equals(other as SemVersion, deep = true) + } + + public override fun hashCode(): Int { + var result = major.hashCode() + result = 31 * result + minor.hashCode() + result = 31 * result + (patch?.hashCode() ?: 0) result = 31 * result + (identifier?.hashCode() ?: 0) result = 31 * result + (metadata?.hashCode() ?: 0) return result } /** - * Compares this object with the specified object for order. Returns zero if this object is equal - * to the specified [other] object, a negative number if it's less than [other], or a positive number - * if it's greater than [other]. + * 比较 `this` 和 [other] 的实际版本大小. + * + * 如: + * - `SemVersion("1.0.0-RC").compareTo(SemVersion("1.0-RC")) == 0` (然而对他们进行 [equals] 判断会返回 `false`) + * - `SemVersion("1.3.0") > SemVersion("1.1.0") == true` (因为 1.3.0 比 1.1.0 更高) + * + * + * @return 当 `this` 比 [other] 更高时返回一个正数. + * 当 `this` 比 [other] 更低时返回一个负数. + * 当 `this` 与 [other] 版本大小相等时返回 0. + * + * @see equals */ public override operator fun compareTo(other: SemVersion): Int { return SemVersionInternal.run { compareInternal(this@SemVersion, other) } From 717c908ccfa9624c62602432318c29374f1a52b8 Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Sun, 25 Oct 2020 13:16:27 +0800 Subject: [PATCH 34/41] Support multiple ResolveContext kinds in single declaration --- .../mamoe/mirai/console/command/RawCommand.kt | 2 +- .../console/command/java/JCompositeCommand.kt | 2 + .../mirai/console/command/java/JRawCommand.kt | 2 + .../console/command/java/JSimpleCommand.kt | 5 +- .../console/compiler/common/ResolveContext.kt | 68 +++++++++++++++---- .../internal/plugin/JvmPluginInternal.kt | 2 +- .../console/permission/PermissionService.kt | 14 +--- .../plugin/description/PluginDescription.kt | 2 +- .../console/plugin/jvm/AbstractJvmPlugin.kt | 2 +- .../plugin/jvm/JvmPluginDescription.kt | 6 +- .../mamoe/mirai/console/util/SemVersion.kt | 8 +-- .../compiler/common/resolve/resolveTypes.kt | 14 ++-- .../ContextualParametersChecker.kt | 10 ++- .../diagnostics/PluginDataValuesChecker.kt | 4 +- .../intellij/diagnostics/diagnosticsUtil.kt | 4 +- .../console/intellij/resolve/resolveIdea.kt | 4 +- 16 files changed, 93 insertions(+), 56 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 2486f6f42..56bb6a968 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,7 +11,6 @@ package net.mamoe.mirai.console.command -import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand import net.mamoe.mirai.console.command.descriptor.* import net.mamoe.mirai.console.command.java.JRawCommand import net.mamoe.mirai.console.compiler.common.ResolveContext @@ -51,6 +50,7 @@ public abstract class RawCommand( /** 指令父权限 */ parentPermission: Permission = owner.parentPermission, /** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */ + @OptIn(ExperimentalCommandDescriptors::class) public override val prefixOptional: Boolean = false, ) : Command { public override val permission: Permission by lazy { createOrFindCommandPermission(parentPermission) } 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 fa0b9203a..775542e99 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,6 +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.descriptor.ExperimentalCommandDescriptors 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 @@ -85,6 +86,7 @@ public abstract class JCompositeCommand protected set /** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */ + @ExperimentalCommandDescriptors public final override var prefixOptional: Boolean = false protected set 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 19cf70d2a..539c409a1 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 @@ -13,6 +13,7 @@ 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.command.descriptor.ExperimentalCommandDescriptors 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 @@ -70,6 +71,7 @@ public abstract class JRawCommand protected set /** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */ + @ExperimentalCommandDescriptors public final override var prefixOptional: Boolean = false protected set } \ 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 4333bfa45..bb56daeba 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 @@ -10,10 +10,10 @@ package net.mamoe.mirai.console.command.java 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.descriptor.CommandArgumentContext +import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors 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 @@ -52,9 +52,10 @@ public abstract class JSimpleCommand( ) : SimpleCommand(owner, primaryName, secondaryNames = secondaryNames, parentPermission = basePermission) { public override var description: String = super.description protected set - public override var permission: Permission = super.permission protected set + + @ExperimentalCommandDescriptors public override var prefixOptional: Boolean = super.prefixOptional protected set public override var context: CommandArgumentContext = super.context diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt index a10aa00d8..350e20ada 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt @@ -11,22 +11,21 @@ package net.mamoe.mirai.console.compiler.common -import net.mamoe.mirai.console.util.ConsoleExperimentalApi +import net.mamoe.mirai.console.command.Command +import net.mamoe.mirai.console.data.PluginData +import net.mamoe.mirai.console.data.value +import net.mamoe.mirai.console.permission.PermissionId +import net.mamoe.mirai.console.plugin.description.PluginDescription +import net.mamoe.mirai.console.util.SemVersion import kotlin.annotation.AnnotationTarget.* /** * 标记一个参数的语境类型, 用于帮助编译器和 IntelliJ 插件进行语境推断. */ -@ConsoleExperimentalApi -@Target( - VALUE_PARAMETER, - PROPERTY, FIELD, - FUNCTION, - TYPE, TYPE_PARAMETER -) +@Target(VALUE_PARAMETER, PROPERTY, FIELD, FUNCTION, TYPE, TYPE_PARAMETER) @Retention(AnnotationRetention.BINARY) public annotation class ResolveContext( - val kind: Kind, + vararg val kinds: Kind, ) { /** * 元素数量可能在任意时间被改动 @@ -36,18 +35,57 @@ public annotation class ResolveContext( // ConstantKind /////////////////////////////////////////////////////////////////////////// - PLUGIN_ID, // ILLEGAL_PLUGIN_DESCRIPTION - PLUGIN_NAME, // ILLEGAL_PLUGIN_DESCRIPTION - PLUGIN_VERSION, // ILLEGAL_PLUGIN_DESCRIPTION + /* + * WARNING: IF YOU CHANGE NAMES HERE, + * YOU SHOULD ALSO CHANGE THEIR COUNTERPARTS AT net.mamoe.mirai.console.compiler.common.resolve.ResolveContextKind + */ + /** + * @see PluginDescription.id + */ + PLUGIN_ID, // ILLEGAL_PLUGIN_DESCRIPTION + + /** + * @see PluginDescription.name + */ + PLUGIN_NAME, // ILLEGAL_PLUGIN_DESCRIPTION + + /** + * @see PluginDescription.version + * @see SemVersion.Companion.invoke + */ + SEMANTIC_VERSION, // ILLEGAL_PLUGIN_DESCRIPTION + + /** + * @see SemVersion.Companion.parseRangeRequirement + */ VERSION_REQUIREMENT, // ILLEGAL_VERSION_REQUIREMENT // TODO + /** + * @see Command.allNames + */ COMMAND_NAME, // ILLEGAL_COMMAND_NAME - PERMISSION_NAMESPACE, // ILLEGAL_COMMAND_NAMESPACE - PERMISSION_NAME, // ILLEGAL_COMMAND_NAME - PERMISSION_ID, // ILLEGAL_COMMAND_ID + /** + * @see PermissionId.name + */ + PERMISSION_NAMESPACE, // ILLEGAL_PERMISSION_NAMESPACE + /** + * @see PermissionId.name + */ + PERMISSION_NAME, // ILLEGAL_PERMISSION_NAME + + /** + * @see PermissionId.parseFromString + */ + PERMISSION_ID, // ILLEGAL_PERMISSION_ID + + /** + * 标注一个泛型, 要求这个泛型必须拥有一个公开无参 (或所有参数都可选) 构造器. + * + * @see PluginData.value + */ RESTRICTED_NO_ARG_CONSTRUCTOR, // NOT_CONSTRUCTABLE_TYPE } } \ No newline at end of file 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 448f3feba..33ce08713 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.PLUGIN_ROOT_PERMISSION), + PermissionService.INSTANCE.allocatePermissionIdForPlugin(this, "*"), "The base permission" ) } 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 fcd3ee841..363393092 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 @@ -97,8 +97,7 @@ public interface PermissionService<P : Permission> { public fun allocatePermissionIdForPlugin( plugin: Plugin, @ResolveContext(COMMAND_NAME) permissionName: String, - reason: PluginPermissionIdRequestType - ): PermissionId = allocatePermissionIdForPluginDefaultImplement(plugin, permissionName, reason) + ): PermissionId = allocatePermissionIdForPluginDefaultImplement(plugin, permissionName) /////////////////////////////////////////////////////////////////////////// @@ -127,16 +126,6 @@ public interface PermissionService<P : Permission> { @Throws(UnsupportedOperationException::class) public fun cancel(permitteeId: PermitteeId, permission: P, recursive: Boolean) - /** [Plugin] 尝试分配的 [PermissionId] 来源 */ - @ConsoleExperimentalApi - public enum class PluginPermissionIdRequestType { - /** For [Plugin.parentPermission] */ - PLUGIN_ROOT_PERMISSION, - - /** For [Plugin.permissionId] */ - NORMAL - } - public companion object { internal var instanceField: PermissionService<*>? = null @@ -155,7 +144,6 @@ public interface PermissionService<P : Permission> { internal fun PermissionService<*>.allocatePermissionIdForPluginDefaultImplement( plugin: Plugin, @ResolveContext(COMMAND_NAME) permissionName: String, - reason: PluginPermissionIdRequestType ) = PermissionId( plugin.description.id.toLowerCase(), permissionName.toLowerCase() diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDescription.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDescription.kt index ca9267495..7c84a2807 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDescription.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDescription.kt @@ -92,7 +92,7 @@ public interface PluginDescription { * * @see Semver 语义化版本. 允许 [宽松][Semver.SemverType.LOOSE] 类型版本. */ - @ResolveContext(PLUGIN_VERSION) + @ResolveContext(SEMANTIC_VERSION) public val version: SemVersion /** 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 ce5f98ce3..1f19786fa 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.NORMAL) + PermissionService.INSTANCE.allocatePermissionIdForPlugin(this, name) /** * 重载 [PluginData] diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt index edf5d99af..b147e8873 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt @@ -43,7 +43,7 @@ public interface JvmPluginDescription : PluginDescription { /** * @see [PluginDescription.version] */ - @ResolveContext(PLUGIN_VERSION) version: String, + @ResolveContext(SEMANTIC_VERSION) version: String, /** * @see [PluginDescription.name] */ @@ -102,7 +102,7 @@ public class JvmPluginDescriptionBuilder( ) { public constructor( @ResolveContext(PLUGIN_ID) id: String, - @ResolveContext(PLUGIN_VERSION) version: String, + @ResolveContext(SEMANTIC_VERSION) version: String, ) : this(id, SemVersion(version)) private var name: String = id @@ -115,7 +115,7 @@ public class JvmPluginDescriptionBuilder( apply { this.name = value.trim() } @ILoveKuriyamaMiraiForever - public fun version(@ResolveContext(PLUGIN_VERSION) value: String): JvmPluginDescriptionBuilder = + public fun version(@ResolveContext(SEMANTIC_VERSION) value: String): JvmPluginDescriptionBuilder = apply { this.version = SemVersion(value) } @ILoveKuriyamaMiraiForever diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt index 5e23cb505..779b97e5d 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt @@ -21,7 +21,7 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import kotlinx.serialization.builtins.serializer import net.mamoe.mirai.console.compiler.common.ResolveContext -import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.PLUGIN_VERSION +import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.SEMANTIC_VERSION import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.VERSION_REQUIREMENT import net.mamoe.mirai.console.internal.data.map import net.mamoe.mirai.console.internal.util.semver.SemVersionInternal @@ -115,7 +115,7 @@ internal constructor( @JvmStatic @JvmName("parse") @Throws(IllegalArgumentException::class, NumberFormatException::class) - public operator fun invoke(@ResolveContext(PLUGIN_VERSION) version: String): SemVersion = SemVersionInternal.parse(version) + public operator fun invoke(@ResolveContext(SEMANTIC_VERSION) version: String): SemVersion = SemVersionInternal.parse(version) /** * 解析一条依赖需求描述, 在无法解析的时候抛出 [IllegalArgumentException] @@ -155,7 +155,7 @@ internal constructor( /** @see [Requirement.test] */ @JvmStatic @Throws(IllegalArgumentException::class, NumberFormatException::class) - public fun Requirement.test(@ResolveContext(PLUGIN_VERSION) version: String): Boolean = test(invoke(version)) + public fun Requirement.test(@ResolveContext(SEMANTIC_VERSION) version: String): Boolean = test(invoke(version)) /** * 当满足 [requirement] 时返回 true, 否则返回 false @@ -178,7 +178,7 @@ internal constructor( /** for Kotlin only */ @JvmStatic @JvmSynthetic - public operator fun Requirement.contains(@ResolveContext(PLUGIN_VERSION) version: String): Boolean = test(version) + public operator fun Requirement.contains(@ResolveContext(SEMANTIC_VERSION) version: String): Boolean = test(version) } @Transient diff --git a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/resolveTypes.kt b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/resolveTypes.kt index 59f356cc9..b09530ae4 100644 --- a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/resolveTypes.kt +++ b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/resolveTypes.kt @@ -13,6 +13,7 @@ import net.mamoe.mirai.console.compiler.common.castOrNull import net.mamoe.mirai.console.compiler.common.firstValue import org.jetbrains.kotlin.descriptors.annotations.Annotated import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.resolve.constants.ArrayValue import org.jetbrains.kotlin.resolve.constants.EnumValue /////////////////////////////////////////////////////////////////////////// @@ -73,11 +74,14 @@ enum class ResolveContextKind { } } -fun Annotated.isResolveContext(kind: ResolveContextKind) = this.resolveContextKind == kind - -val Annotated.resolveContextKind: ResolveContextKind? +val Annotated.resolveContextKinds: List<ResolveContextKind>? get() { val ann = this.findAnnotation(RESOLVE_CONTEXT_FQ_NAME) ?: return null - val (_, enumEntryName) = ann.allValueArguments.firstValue().castOrNull<EnumValue>()?.value ?: return null // undetermined kind - return ResolveContextKind.valueOf(enumEntryName.asString()) + val kinds = + ann.allValueArguments.firstValue().castOrNull<ArrayValue>()?.value?.mapNotNull { it.castOrNull<EnumValue>()?.value } + ?: return null // undetermined kind + + return kinds.map { (_, enumEntryName) -> + ResolveContextKind.valueOf(enumEntryName.asString()) + } } \ No newline at end of file diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/ContextualParametersChecker.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/ContextualParametersChecker.kt index ecf455dc4..ab82110a8 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/ContextualParametersChecker.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/ContextualParametersChecker.kt @@ -12,7 +12,7 @@ package net.mamoe.mirai.console.intellij.diagnostics import com.intellij.psi.PsiElement import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.* import net.mamoe.mirai.console.compiler.common.resolve.ResolveContextKind -import net.mamoe.mirai.console.compiler.common.resolve.resolveContextKind +import net.mamoe.mirai.console.compiler.common.resolve.resolveContextKinds import net.mamoe.mirai.console.intellij.resolve.resolveAllCalls import net.mamoe.mirai.console.intellij.resolve.resolveStringConstantValues import net.mamoe.mirai.console.intellij.resolve.valueParametersWithArguments @@ -134,12 +134,18 @@ class ContextualParametersChecker : DeclarationChecker { context: DeclarationCheckerContext, ) { declaration.resolveAllCalls(context.bindingContext) + .asSequence() .flatMap { call -> call.valueParametersWithArguments().asSequence() } .mapNotNull { (p, a) -> - p.resolveContextKind?.let(checkersMap::get)?.let { it to a } + p.resolveContextKinds + ?.map(checkersMap::get) + ?.mapNotNull { + if (it == null) null else it to a + } } + .flatMap { it.asSequence() } .mapNotNull { (kind, argument) -> argument.resolveStringConstantValues()?.let { const -> Triple(kind, argument, const) diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDataValuesChecker.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDataValuesChecker.kt index 4130b6ce3..862e29d89 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDataValuesChecker.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDataValuesChecker.kt @@ -31,12 +31,12 @@ class PluginDataValuesChecker : DeclarationChecker { declaration.resolveAllCallsWithElement(bindingContext) .filter { (call) -> call.isCalling(PLUGIN_DATA_VALUE_FUNCTIONS_FQ_FQ_NAME) } .filter { (call) -> - call.resultingDescriptor.resolveContextKind == ResolveContextKind.RESTRICTED_NO_ARG_CONSTRUCTOR + call.resultingDescriptor.resolveContextKinds?.contains(ResolveContextKind.RESTRICTED_NO_ARG_CONSTRUCTOR) == true }.flatMap { (call, element) -> call.typeArguments.entries.associateWith { element }.asSequence() }.filter { (e, _) -> val (p, t) = e - (p.isReified || p.resolveContextKind == ResolveContextKind.RESTRICTED_NO_ARG_CONSTRUCTOR) + (p.isReified || p.resolveContextKinds?.contains(ResolveContextKind.RESTRICTED_NO_ARG_CONSTRUCTOR) == true) && t is SimpleType }.forEach { (e, callExpr) -> val (_, type) = e diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/diagnosticsUtil.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/diagnosticsUtil.kt index 6fd2874b4..1c7d4911f 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/diagnosticsUtil.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/diagnosticsUtil.kt @@ -15,7 +15,6 @@ import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext -import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode fun DeclarationCheckerContext.report(diagnostic: Diagnostic) { return this.trace.report(diagnostic) @@ -25,7 +24,6 @@ val DeclarationCheckerContext.bindingContext get() = this.trace.bindingContext fun KtElement?.getResolvedCallOrResolveToCall( context: DeclarationCheckerContext, - bodyResolveMode: BodyResolveMode = BodyResolveMode.PARTIAL, ): ResolvedCall<out CallableDescriptor>? { - return this.getResolvedCallOrResolveToCall(context.bindingContext, bodyResolveMode) + return this.getResolvedCallOrResolveToCall(context.bindingContext) } \ No newline at end of file diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt index 723da4408..a8afeb64b 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt @@ -32,7 +32,6 @@ import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.constants.ArrayValue import org.jetbrains.kotlin.resolve.constants.ConstantValue import org.jetbrains.kotlin.resolve.constants.StringValue -import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance @@ -125,9 +124,8 @@ inline fun <reified E> PsiElement.findChild(): E? = this.children.find { it is E fun KtElement?.getResolvedCallOrResolveToCall( context: BindingContext, - bodyResolveMode: BodyResolveMode = BodyResolveMode.PARTIAL, ): ResolvedCall<out CallableDescriptor>? { - return this?.getCall(context)?.getResolvedCall(context)// ?: this?.resolveToCall(bodyResolveMode) + return this?.getCall(context)?.getResolvedCall(context) } val ResolvedCall<out CallableDescriptor>.valueParameters: List<ValueParameterDescriptor> get() = this.resultingDescriptor.valueParameters From 5c16e685b36308e3e353eabbf747e4fbea96d6a5 Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Sun, 25 Oct 2020 13:29:06 +0800 Subject: [PATCH 35/41] Mark ValueDescription with SerialInfo --- .../kotlin/net/mamoe/mirai/console/data/ValueDescription.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/ValueDescription.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/ValueDescription.kt index f014df959..907944766 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/ValueDescription.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/ValueDescription.kt @@ -9,6 +9,8 @@ package net.mamoe.mirai.console.data +import kotlinx.serialization.SerialInfo + /** * 序列化之后的注释. * @@ -30,6 +32,7 @@ package net.mamoe.mirai.console.data * a: b * ``` */ +@SerialInfo @Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) public annotation class ValueDescription(val value: String) \ No newline at end of file From d1ebe44f3efbc5e96e55676d957407a28ed4e55b Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Sun, 25 Oct 2020 14:27:27 +0800 Subject: [PATCH 36/41] Review CommandArgumentContext and command --- .../mirai/console/command/BuiltInCommands.kt | 8 +- .../mamoe/mirai/console/command/Command.kt | 3 - .../descriptor/CommandArgumentContext.kt | 38 ++++-- .../CommandArgumentParserBuiltins.kt | 68 +++++----- .../CommandArgumentParserException.kt | 3 +- .../descriptor/CommandValueArgumentParser.kt | 125 +++++++++--------- .../mirai/console/command/java/JCommand.kt | 25 ---- .../console/command/java/JCompositeCommand.kt | 2 - .../mirai/console/command/java/JRawCommand.kt | 2 - .../console/command/java/JSimpleCommand.kt | 2 - .../resolve/BuiltInCommandCallResolver.kt | 2 +- .../command/resolve/ResolvedCommandCall.kt | 1 + 12 files changed, 137 insertions(+), 142 deletions(-) delete mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JCommand.kt 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 f37299e00..3bef2710d 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,11 @@ 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.descriptor.* +import net.mamoe.mirai.console.command.descriptor.CommandArgumentParserException +import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser.Companion.map +import net.mamoe.mirai.console.command.descriptor.PermissionIdValueArgumentParser +import net.mamoe.mirai.console.command.descriptor.PermitteeIdValueArgumentParser +import net.mamoe.mirai.console.command.descriptor.buildCommandArgumentContext 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 @@ -154,7 +158,7 @@ public object BuiltInCommands { Permission::class with PermissionIdValueArgumentParser.map { id -> kotlin.runCatching { id.findCorrespondingPermissionOrFail() - }.getOrElse { illegalArgument("指令不存在: $id", it) } + }.getOrElse { throw CommandArgumentParserException("指令不存在: $id", it) } } }, ), BuiltInCommandInternal { 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 9d0036771..578cb9630 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 @@ -14,7 +14,6 @@ package net.mamoe.mirai.console.command 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 @@ -31,8 +30,6 @@ import net.mamoe.mirai.console.util.ConsoleExperimentalApi * @see SimpleCommand 简单的, 支持参数自动解析的指令 * * @see CommandArgumentContextAware - * - * @see JCommand 为 Java 用户添加协程帮助的 [Command] */ public interface Command { /** 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 acc3d3d25..c662a2997 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 @@ -23,6 +23,8 @@ import net.mamoe.mirai.contact.* import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.MessageContent import net.mamoe.mirai.message.data.PlainText +import kotlin.contracts.InvocationKind.EXACTLY_ONCE +import kotlin.contracts.contract import kotlin.internal.LowPriorityInOverloadResolution import kotlin.reflect.KClass import kotlin.reflect.full.isSubclassOf @@ -49,9 +51,17 @@ public interface CommandArgumentContext { public data class ParserPair<T : Any>( val klass: KClass<T>, val parser: CommandValueArgumentParser<T>, - ) + ) { + public companion object { + @JvmStatic + public fun <T : Any> ParserPair<T>.toPair(): Pair<KClass<T>, CommandValueArgumentParser<T>> = klass to parser + } + } - public operator fun <T : Any> get(klass: KClass<out T>): CommandValueArgumentParser<T>? + /** + * 获取一个 [kClass] 类型的解析器. + */ + public operator fun <T : Any> get(kClass: KClass<T>): CommandValueArgumentParser<T>? public fun toList(): List<ParserPair<*>> @@ -59,7 +69,7 @@ public interface CommandArgumentContext { /** * For Java callers. * - * @see [EmptyCommandArgumentContext] + * @see EmptyCommandArgumentContext */ @JvmStatic public val EMPTY: CommandArgumentContext = EmptyCommandArgumentContext @@ -108,6 +118,9 @@ public interface CommandArgumentContextAware { public val context: CommandArgumentContext } +/** + * @see CommandArgumentContext.EMPTY + */ public object EmptyCommandArgumentContext : CommandArgumentContext by SimpleCommandArgumentContext(listOf()) /** @@ -117,8 +130,8 @@ public operator fun CommandArgumentContext.plus(replacer: CommandArgumentContext if (replacer == EmptyCommandArgumentContext) return this if (this == EmptyCommandArgumentContext) return replacer return object : CommandArgumentContext { - override fun <T : Any> get(klass: KClass<out T>): CommandValueArgumentParser<T>? = - replacer[klass] ?: this@plus[klass] + override fun <T : Any> get(kClass: KClass<T>): CommandValueArgumentParser<T>? = + replacer[kClass] ?: this@plus[kClass] override fun toList(): List<ParserPair<*>> = replacer.toList() + this@plus.toList() } @@ -132,9 +145,9 @@ public operator fun CommandArgumentContext.plus(replacer: List<ParserPair<*>>): if (this == EmptyCommandArgumentContext) return SimpleCommandArgumentContext(replacer) return object : CommandArgumentContext { @Suppress("UNCHECKED_CAST") - override fun <T : Any> get(klass: KClass<out T>): CommandValueArgumentParser<T>? = - replacer.firstOrNull { klass.isSubclassOf(it.klass) }?.parser as CommandValueArgumentParser<T>? - ?: this@plus[klass] + override fun <T : Any> get(kClass: KClass<T>): CommandValueArgumentParser<T>? = + replacer.firstOrNull { kClass.isSubclassOf(it.klass) }?.parser as CommandValueArgumentParser<T>? + ?: this@plus[kClass] override fun toList(): List<ParserPair<*>> = replacer.toList() + this@plus.toList() } @@ -149,9 +162,9 @@ public operator fun CommandArgumentContext.plus(replacer: List<ParserPair<*>>): public class SimpleCommandArgumentContext( public val list: List<ParserPair<*>>, ) : CommandArgumentContext { - override fun <T : Any> get(klass: KClass<out T>): CommandValueArgumentParser<T>? = - (this.list.firstOrNull { klass == it.klass }?.parser - ?: this.list.firstOrNull { klass.isSubclassOf(it.klass) }?.parser) as CommandValueArgumentParser<T>? + override fun <T : Any> get(kClass: KClass<T>): CommandValueArgumentParser<T>? = + (this.list.firstOrNull { kClass == it.klass }?.parser + ?: this.list.firstOrNull { kClass.isSubclassOf(it.klass) }?.parser) as CommandValueArgumentParser<T>? override fun toList(): List<ParserPair<*>> = list } @@ -192,6 +205,9 @@ public class SimpleCommandArgumentContext( */ @JvmSynthetic public fun buildCommandArgumentContext(block: CommandArgumentContextBuilder.() -> Unit): CommandArgumentContext { + contract { + callsInPlace(block, EXACTLY_ONCE) + } return CommandArgumentContextBuilder().apply(block).build() } 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 a1fc279e3..20d0665b6 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 @@ -7,6 +7,8 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:Suppress("EXPOSED_SUPER_CLASS") + package net.mamoe.mirai.console.command.descriptor import net.mamoe.mirai.Bot @@ -25,7 +27,7 @@ import net.mamoe.mirai.message.data.* /** * 使用 [String.toInt] 解析 */ -public object IntValueArgumentParser : InternalCommandValueArgumentParserExtensions<Int> { +public object IntValueArgumentParser : InternalCommandValueArgumentParserExtensions<Int>() { public override fun parse(raw: String, sender: CommandSender): Int = raw.toIntOrNull() ?: illegalArgument("无法解析 $raw 为整数") } @@ -33,7 +35,7 @@ public object IntValueArgumentParser : InternalCommandValueArgumentParserExtensi /** * 使用 [String.toLong] 解析 */ -public object LongValueArgumentParser : InternalCommandValueArgumentParserExtensions<Long> { +public object LongValueArgumentParser : InternalCommandValueArgumentParserExtensions<Long>() { public override fun parse(raw: String, sender: CommandSender): Long = raw.toLongOrNull() ?: illegalArgument("无法解析 $raw 为长整数") } @@ -41,7 +43,7 @@ public object LongValueArgumentParser : InternalCommandValueArgumentParserExtens /** * 使用 [String.toShort] 解析 */ -public object ShortValueArgumentParser : InternalCommandValueArgumentParserExtensions<Short> { +public object ShortValueArgumentParser : InternalCommandValueArgumentParserExtensions<Short>() { public override fun parse(raw: String, sender: CommandSender): Short = raw.toShortOrNull() ?: illegalArgument("无法解析 $raw 为短整数") } @@ -49,7 +51,7 @@ public object ShortValueArgumentParser : InternalCommandValueArgumentParserExten /** * 使用 [String.toByte] 解析 */ -public object ByteValueArgumentParser : InternalCommandValueArgumentParserExtensions<Byte> { +public object ByteValueArgumentParser : InternalCommandValueArgumentParserExtensions<Byte>() { public override fun parse(raw: String, sender: CommandSender): Byte = raw.toByteOrNull() ?: illegalArgument("无法解析 $raw 为字节") } @@ -57,7 +59,7 @@ public object ByteValueArgumentParser : InternalCommandValueArgumentParserExtens /** * 使用 [String.toDouble] 解析 */ -public object DoubleValueArgumentParser : InternalCommandValueArgumentParserExtensions<Double> { +public object DoubleValueArgumentParser : InternalCommandValueArgumentParserExtensions<Double>() { public override fun parse(raw: String, sender: CommandSender): Double = raw.toDoubleOrNull() ?: illegalArgument("无法解析 $raw 为小数") } @@ -65,7 +67,7 @@ public object DoubleValueArgumentParser : InternalCommandValueArgumentParserExte /** * 使用 [String.toFloat] 解析 */ -public object FloatValueArgumentParser : InternalCommandValueArgumentParserExtensions<Float> { +public object FloatValueArgumentParser : InternalCommandValueArgumentParserExtensions<Float>() { public override fun parse(raw: String, sender: CommandSender): Float = raw.toFloatOrNull() ?: illegalArgument("无法解析 $raw 为小数") } @@ -73,14 +75,14 @@ public object FloatValueArgumentParser : InternalCommandValueArgumentParserExten /** * 直接返回 [String], 或取用 [SingleMessage.contentToString] */ -public object StringValueArgumentParser : InternalCommandValueArgumentParserExtensions<String> { +public object StringValueArgumentParser : InternalCommandValueArgumentParserExtensions<String>() { public override fun parse(raw: String, sender: CommandSender): String = raw } /** * 解析 [String] 通过 [Image]. */ -public object ImageValueArgumentParser : InternalCommandValueArgumentParserExtensions<Image> { +public object ImageValueArgumentParser : InternalCommandValueArgumentParserExtensions<Image>() { public override fun parse(raw: String, sender: CommandSender): Image { return kotlin.runCatching { Image(raw) @@ -95,7 +97,7 @@ public object ImageValueArgumentParser : InternalCommandValueArgumentParserExten } } -public object PlainTextValueArgumentParser : InternalCommandValueArgumentParserExtensions<PlainText> { +public object PlainTextValueArgumentParser : InternalCommandValueArgumentParserExtensions<PlainText>() { public override fun parse(raw: String, sender: CommandSender): PlainText { return PlainText(raw) } @@ -109,7 +111,7 @@ public object PlainTextValueArgumentParser : InternalCommandValueArgumentParserE /** * 当字符串内容为(不区分大小写) "true", "yes", "enabled" */ -public object BooleanValueArgumentParser : InternalCommandValueArgumentParserExtensions<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 +123,7 @@ public object BooleanValueArgumentParser : InternalCommandValueArgumentParserExt /** * 根据 [Bot.id] 解析一个登录后的 [Bot] */ -public object ExistingBotValueArgumentParser : InternalCommandValueArgumentParserExtensions<Bot> { +public object ExistingBotValueArgumentParser : InternalCommandValueArgumentParserExtensions<Bot>() { public override fun parse(raw: String, sender: CommandSender): Bot = if (raw == "~") sender.inferBotOrFail() else raw.findBotOrFail() @@ -136,7 +138,7 @@ public object ExistingBotValueArgumentParser : InternalCommandValueArgumentParse /** * 解析任意一个存在的好友. */ -public object ExistingFriendValueArgumentParser : InternalCommandValueArgumentParserExtensions<Friend> { +public object ExistingFriendValueArgumentParser : InternalCommandValueArgumentParserExtensions<Friend>() { private val syntax = """ - `botId.friendId` - `botId.friendNick` (模糊搜索, 寻找最优匹配) @@ -175,7 +177,7 @@ public object ExistingFriendValueArgumentParser : InternalCommandValueArgumentPa /** * 解析任意一个存在的群. */ -public object ExistingGroupValueArgumentParser : InternalCommandValueArgumentParserExtensions<Group> { +public object ExistingGroupValueArgumentParser : InternalCommandValueArgumentParserExtensions<Group>() { private val syntax = """ - `botId.groupId` - `~` (指代指令调用人自己所在群. 仅群聊天环境下) @@ -202,7 +204,7 @@ public object ExistingGroupValueArgumentParser : InternalCommandValueArgumentPar } } -public object ExistingUserValueArgumentParser : InternalCommandValueArgumentParserExtensions<User> { +public object ExistingUserValueArgumentParser : InternalCommandValueArgumentParserExtensions<User>() { private val syntax: String = """ - `botId.groupId.memberId` - `botId.groupId.memberCard` (模糊搜索, 寻找最优匹配) @@ -246,7 +248,7 @@ public object ExistingUserValueArgumentParser : InternalCommandValueArgumentPars } -public object ExistingContactValueArgumentParser : InternalCommandValueArgumentParserExtensions<Contact> { +public object ExistingContactValueArgumentParser : InternalCommandValueArgumentParserExtensions<Contact>() { private val syntax: String = """ - `botId.groupId.memberId` - `botId.groupId.memberCard` (模糊搜索, 寻找最优匹配) @@ -286,7 +288,7 @@ public object ExistingContactValueArgumentParser : InternalCommandValueArgumentP /** * 解析任意一个群成员. */ -public object ExistingMemberValueArgumentParser : InternalCommandValueArgumentParserExtensions<Member> { +public object ExistingMemberValueArgumentParser : InternalCommandValueArgumentParserExtensions<Member>() { private val syntax: String = """ - `botId.groupId.memberId` - `botId.groupId.memberCard` (模糊搜索, 寻找最优匹配) @@ -333,7 +335,7 @@ public object ExistingMemberValueArgumentParser : InternalCommandValueArgumentPa } } -public object PermissionIdValueArgumentParser : CommandValueArgumentParser<PermissionId> { +public object PermissionIdValueArgumentParser : InternalCommandValueArgumentParserExtensions<PermissionId>() { override fun parse(raw: String, sender: CommandSender): PermissionId { return kotlin.runCatching { PermissionId.parseFromString(raw) }.getOrElse { illegalArgument("无法解析 $raw 为 PermissionId") @@ -341,7 +343,7 @@ public object PermissionIdValueArgumentParser : CommandValueArgumentParser<Permi } } -public object PermitteeIdValueArgumentParser : CommandValueArgumentParser<PermitteeId> { +public object PermitteeIdValueArgumentParser : InternalCommandValueArgumentParserExtensions<PermitteeId>() { override fun parse(raw: String, sender: CommandSender): PermitteeId { return if (raw == "~") sender.permitteeId else kotlin.runCatching { AbstractPermitteeId.parseFromString(raw) }.getOrElse { @@ -363,26 +365,26 @@ public object RawContentValueArgumentParser : CommandValueArgumentParser<Message override fun parse(raw: MessageContent, sender: CommandSender): MessageContent = raw } -internal interface InternalCommandValueArgumentParserExtensions<T : Any> : CommandValueArgumentParser<T> { - fun String.parseToLongOrFail(): Long = toLongOrNull() ?: illegalArgument("无法解析 $this 为整数") +internal abstract class InternalCommandValueArgumentParserExtensions<T : Any> : AbstractCommandValueArgumentParser<T>() { + private fun String.parseToLongOrFail(): Long = toLongOrNull() ?: illegalArgument("无法解析 $this 为整数") - fun Long.findBotOrFail(): Bot = Bot.getInstanceOrNull(this) ?: illegalArgument("无法找到 Bot: $this") + protected fun Long.findBotOrFail(): Bot = Bot.getInstanceOrNull(this) ?: illegalArgument("无法找到 Bot: $this") - fun String.findBotOrFail(): Bot = + protected fun String.findBotOrFail(): Bot = Bot.getInstanceOrNull(this.parseToLongOrFail()) ?: illegalArgument("无法找到 Bot: $this") - fun Bot.findGroupOrFail(id: Long): Group = getGroupOrNull(id) ?: illegalArgument("无法找到群: $this") + protected fun Bot.findGroupOrFail(id: Long): Group = getGroupOrNull(id) ?: illegalArgument("无法找到群: $this") - fun Bot.findGroupOrFail(id: String): Group = + protected fun Bot.findGroupOrFail(id: String): Group = getGroupOrNull(id.parseToLongOrFail()) ?: illegalArgument("无法找到群: $this") - fun Bot.findFriendOrFail(id: String): Friend = + protected fun Bot.findFriendOrFail(id: String): Friend = getFriendOrNull(id.parseToLongOrFail()) ?: illegalArgument("无法找到好友: $this") - fun Bot.findMemberOrFail(id: String): Friend = + protected fun Bot.findMemberOrFail(id: String): Friend = getFriendOrNull(id.parseToLongOrFail()) ?: illegalArgument("无法找到群员: $this") - fun Group.findMemberOrFail(idOrCard: String): Member { + protected fun Group.findMemberOrFail(idOrCard: String): Member { if (idOrCard == "\$") return members.randomOrNull() ?: illegalArgument("当前语境下无法推断随机群员") idOrCard.toLongOrNull()?.let { getOrNull(it) }?.let { return it } this.members.singleOrNull { it.nameCardOrNick.contains(idOrCard) }?.let { return it } @@ -405,23 +407,21 @@ internal interface InternalCommandValueArgumentParserExtensions<T : Any> : Comma } } - fun CommandSender.inferBotOrFail(): Bot = + protected fun CommandSender.inferBotOrFail(): Bot = (this as? UserCommandSender)?.bot ?: Bot.botInstancesSequence.singleOrNull() ?: illegalArgument("当前语境下无法推断目标 Bot, 因为目前有多个 Bot 在线.") - fun CommandSender.inferGroupOrFail(): Group = + protected fun CommandSender.inferGroupOrFail(): Group = inferGroup() ?: illegalArgument("当前语境下无法推断目标群") - fun CommandSender.inferGroup(): Group? = (this as? GroupAwareCommandSender)?.group + protected fun CommandSender.inferGroup(): Group? = (this as? GroupAwareCommandSender)?.group - fun CommandSender.inferFriendOrFail(): Friend = + protected fun CommandSender.inferFriendOrFail(): Friend = (this as? FriendCommandSender)?.user ?: illegalArgument("当前语境下无法推断目标好友") } -internal fun Double.toDecimalPlace(n: Int): String { - return "%.${n}f".format(this) -} +internal fun Double.toDecimalPlace(n: Int): String = "%.${n}f".format(this) internal fun String.truncate(lengthLimit: Int, replacement: String = "..."): String = buildString { var lengthSum = 0 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 25a59067d..b0bf45647 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 @@ -12,6 +12,7 @@ package net.mamoe.mirai.console.command.descriptor import net.mamoe.mirai.console.command.IllegalCommandArgumentException +import net.mamoe.mirai.console.command.descriptor.AbstractCommandValueArgumentParser.Companion.illegalArgument /** * 在解析参数时遇到的 _正常_ 错误. 如参数不符合规范等. @@ -20,7 +21,7 @@ import net.mamoe.mirai.console.command.IllegalCommandArgumentException * * @see IllegalCommandArgumentException * @see CommandValueArgumentParser - * @see CommandValueArgumentParser.illegalArgument + * @see AbstractCommandValueArgumentParser.illegalArgument */ public class CommandArgumentParserException : IllegalCommandArgumentException { public constructor() : super() 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 ff82ed6db..75c373519 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 @@ -16,10 +16,9 @@ import net.mamoe.mirai.console.command.CommandManager 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.descriptor.CommandValueArgumentParser.Companion.parse import net.mamoe.mirai.contact.* -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.* import kotlin.contracts.InvocationKind import kotlin.contracts.contract @@ -78,68 +77,76 @@ public interface CommandValueArgumentParser<out T : Any> { */ @Throws(CommandArgumentParserException::class) public fun parse(raw: MessageContent, sender: CommandSender): T = parse(raw.content, sender) + + public companion object { + /** + * 解析一个字符串或 [SingleMessage] 为 [T] 类型参数 + * + * @throws IllegalArgumentException 当 [raw] 既不是 [SingleMessage], 也不是 [String] 时抛出. + * + * @see CommandValueArgumentParser.parse + */ + @JvmStatic + @Throws(IllegalArgumentException::class) + public fun <T : Any> CommandValueArgumentParser<T>.parse(raw: Message, sender: CommandSender): T { + return when (raw) { + is PlainText -> parse(raw.content, sender) + is MessageContent -> parse(raw, sender) + else -> throw IllegalArgumentException("Illegal raw argument type: ${raw::class.qualifiedName}") + } + } + + /** + * 使用原 [this] 解析, 成功后使用 [mapper] 映射为另一个类型. + */ + @JvmStatic + public fun <Original : Any, Result : Any> CommandValueArgumentParser<Original>.map( + mapper: MappingCommandValueArgumentParser<Original, Result>.(Original) -> Result, + ): CommandValueArgumentParser<Result> = MappingCommandValueArgumentParser(this, mapper) + } } /** - * 使用原 [this] 解析, 成功后使用 [mapper] 映射为另一个类型. + * @see CommandValueArgumentParser 的基础实现. */ -public fun <T : Any, R : Any> CommandValueArgumentParser<T>.map( - mapper: CommandValueArgumentParser<R>.(T) -> R, -): CommandValueArgumentParser<R> = MappingCommandValueArgumentParser(this, mapper) +public abstract class AbstractCommandValueArgumentParser<T : Any> : CommandValueArgumentParser<T> { + public companion object { + /** + * 抛出一个 [CommandArgumentParserException] 的捷径 + * + * @throws CommandArgumentParserException + */ + @JvmStatic + @JvmSynthetic + @Throws(CommandArgumentParserException::class) + protected inline fun CommandValueArgumentParser<*>.illegalArgument(message: String, cause: Throwable? = null): Nothing = + throw CommandArgumentParserException(message, cause) -private class MappingCommandValueArgumentParser<T : Any, R : Any>( + /** + * 检查参数 [condition]. 当它为 `false` 时调用 [message] 并以其返回值作为消息, 抛出异常 [CommandArgumentParserException] + * + * @throws CommandArgumentParserException + */ + @JvmStatic + @Throws(CommandArgumentParserException::class) + @JvmSynthetic + protected inline fun CommandValueArgumentParser<*>.checkArgument( + condition: Boolean, + crossinline message: () -> String = { "Check failed." }, + ) { + contract { + returns() implies condition + callsInPlace(message, InvocationKind.AT_MOST_ONCE) + } + if (!condition) illegalArgument(message()) + } + } +} + +public class MappingCommandValueArgumentParser<T : Any, R : Any>( private val original: CommandValueArgumentParser<T>, - private val mapper: CommandValueArgumentParser<R>.(T) -> R, -) : CommandValueArgumentParser<R> { + private val mapper: MappingCommandValueArgumentParser<T, R>.(T) -> R, +) : AbstractCommandValueArgumentParser<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)) -} - -/** - * 解析一个字符串或 [SingleMessage] 为 [T] 类型参数 - * - * @throws IllegalArgumentException 当 [raw] 既不是 [SingleMessage], 也不是 [String] 时抛出. - */ -@JvmSynthetic -@Throws(IllegalArgumentException::class) -public fun <T : Any> CommandValueArgumentParser<T>.parse(raw: Any, sender: CommandSender): T { - contract { - returns() implies (raw is String || raw is SingleMessage) - } - - return when (raw) { - is String -> parse(raw, sender) - is MessageContent -> parse(raw, sender) - else -> throw IllegalArgumentException("Illegal raw argument type: ${raw::class.qualifiedName}") - } -} - -/** - * 抛出一个 [CommandArgumentParserException] 的捷径 - * - * @throws CommandArgumentParserException - */ -@Suppress("unused") -@JvmSynthetic -@Throws(CommandArgumentParserException::class) -public inline fun CommandValueArgumentParser<*>.illegalArgument(message: String, cause: Throwable? = null): Nothing { - throw CommandArgumentParserException(message, cause) -} - -/** - * 检查参数 [condition]. 当它为 `false` 时调用 [message] 并以其返回值作为消息, 抛出异常 [CommandArgumentParserException] - * - * @throws CommandArgumentParserException - */ -@Throws(CommandArgumentParserException::class) -@JvmSynthetic -public inline fun CommandValueArgumentParser<*>.checkArgument( - condition: Boolean, - crossinline message: () -> String = { "Check failed." }, -) { - contract { - returns() implies condition - callsInPlace(message, InvocationKind.AT_MOST_ONCE) - } - if (!condition) illegalArgument(message()) } \ No newline at end of file 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 deleted file mode 100644 index af4fbbe0e..000000000 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JCommand.kt +++ /dev/null @@ -1,25 +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.command.java - -import net.mamoe.mirai.console.command.Command -import net.mamoe.mirai.console.util.ConsoleExperimentalApi - -/** - * 为 Java 用户添加协程帮助的 [Command]. - * - * 注意, [JSimpleCommand], [JCompositeCommand], [JRawCommand] 都不实现这个接口. [JCommand] 只设计为 Java 使用者自己实现 [Command] 相关内容. - * - * @see Command - */ -@ConsoleExperimentalApi("Not yet supported") -public interface JCommand : Command { - // 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 775542e99..b3828418d 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 @@ -18,7 +18,6 @@ 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 /** * 复合指令. 指令注册时候会通过反射构造指令解析器. @@ -70,7 +69,6 @@ import net.mamoe.mirai.console.util.ConsoleExperimentalApi * * @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 539c409a1..70b62e680 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 @@ -18,7 +18,6 @@ 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.console.util.ConsoleExperimentalApi /** * 供 Java 用户继承 @@ -46,7 +45,6 @@ import net.mamoe.mirai.console.util.ConsoleExperimentalApi * * @see JRawCommand */ -@ConsoleExperimentalApi("Not yet supported") public abstract class JRawCommand @JvmOverloads constructor( /** 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 bb56daeba..706a14df4 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,7 +17,6 @@ import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors 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 实现: @@ -43,7 +42,6 @@ import net.mamoe.mirai.console.util.ConsoleExperimentalApi * @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/resolve/BuiltInCommandCallResolver.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/BuiltInCommandCallResolver.kt index 98a203118..5869352f6 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 @@ -77,7 +77,7 @@ public object BuiltInCommandCallResolver : CommandCallResolver { } else { if (valueArguments.size > valueParameters.size && zipped.last().first.isVararg) { // merge vararg arguments - val (varargParameter, varargFirstArgument) + val (varargParameter, _) = zipped.removeLast() zipped.add(varargParameter to DefaultCommandValueArgument(valueArguments.drop(zipped.size).map { it.value }.asMessageChain())) 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 547dd736a..7b9a2db3b 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 @@ -13,6 +13,7 @@ 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.* +import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser.Companion.parse import net.mamoe.mirai.console.command.parse.CommandCall import net.mamoe.mirai.console.command.parse.CommandValueArgument import net.mamoe.mirai.console.command.parse.mapToTypeOrNull From 291035f978f4f39dafe23ceaaeed7ed2a2738e6e Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Sun, 25 Oct 2020 14:49:08 +0800 Subject: [PATCH 37/41] Review command: Add `CommandReflector.validate` to check declaration clashes; Rename CommandSignatureVariant to CommandSignature; Add docs; Cleanup code; --- .../mamoe/mirai/console/command/Command.kt | 4 +- .../mirai/console/command/CompositeCommand.kt | 6 +- .../mamoe/mirai/console/command/RawCommand.kt | 4 +- .../mirai/console/command/SimpleCommand.kt | 4 +- .../CommandArgumentParserException.kt | 31 --------- .../command/descriptor/CommandDescriptor.kt | 40 +++++++++--- .../console/command/descriptor/Exceptions.kt | 27 ++++++-- .../resolve/BuiltInCommandCallResolver.kt | 10 +-- .../command/resolve/ResolvedCommandCall.kt | 8 +-- .../internal/command/CommandReflector.kt | 45 ++++++++++--- .../command/CompositeCommand.CommandParam.kt | 63 ------------------- .../console/internal/command/internal.kt | 7 --- 12 files changed, 109 insertions(+), 140 deletions(-) delete mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentParserException.kt delete mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommand.CommandParam.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 578cb9630..a73e2f842 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 @@ -12,7 +12,7 @@ package net.mamoe.mirai.console.command import net.mamoe.mirai.console.command.descriptor.CommandArgumentContextAware -import net.mamoe.mirai.console.command.descriptor.CommandSignatureVariant +import net.mamoe.mirai.console.command.descriptor.CommandSignature import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME @@ -52,7 +52,7 @@ public interface Command { */ @ConsoleExperimentalApi("Property name is experimental") @ExperimentalCommandDescriptors - public val overloads: List<CommandSignatureVariant> + public val overloads: List<CommandSignature> /** * 用法说明, 用于发送给用户. [usage] 一般包含 [description]. 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 a7582efc7..efec2b5d1 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 @@ -95,8 +95,10 @@ public abstract class CompositeCommand( private val reflector by lazy { CommandReflector(this, CompositeCommandSubCommandAnnotationResolver) } @ExperimentalCommandDescriptors - public final override val overloads: List<CommandSignatureVariantFromKFunction> by lazy { - reflector.findSubCommands() + public final override val overloads: List<CommandSignatureFromKFunction> by lazy { + reflector.findSubCommands().also { + reflector.validate(it) + } } /** 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 56bb6a968..4b17e51e2 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 @@ -56,8 +56,8 @@ public abstract class RawCommand( public override val permission: Permission by lazy { createOrFindCommandPermission(parentPermission) } @ExperimentalCommandDescriptors - override val overloads: List<CommandSignatureVariant> = listOf( - CommandSignatureVariantImpl( + override val overloads: List<CommandSignature> = listOf( + CommandSignatureImpl( receiverParameter = CommandReceiverParameter(false, typeOf0<CommandSender>()), valueParameters = listOf(AbstractCommandValueParameter.UserDefinedType.createRequired<Array<out Message>>("args", true)) ) { call -> 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 14861f717..48ce42f7c 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 @@ -17,7 +17,6 @@ package net.mamoe.mirai.console.command -import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand import net.mamoe.mirai.console.command.descriptor.* import net.mamoe.mirai.console.command.java.JSimpleCommand import net.mamoe.mirai.console.compiler.common.ResolveContext @@ -67,8 +66,9 @@ public abstract class SimpleCommand( private val reflector by lazy { CommandReflector(this, SimpleCommandSubCommandAnnotationResolver) } @ExperimentalCommandDescriptors - public final override val overloads: List<CommandSignatureVariantFromKFunction> by lazy { + public final override val overloads: List<CommandSignatureFromKFunction> by lazy { reflector.findSubCommands().also { + reflector.validate(it) if (it.isEmpty()) throw IllegalCommandDeclarationException(this, "SimpleCommand must have at least one subcommand, whereas zero present.") } 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 deleted file mode 100644 index b0bf45647..000000000 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentParserException.kt +++ /dev/null @@ -1,31 +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("unused") - -package net.mamoe.mirai.console.command.descriptor - -import net.mamoe.mirai.console.command.IllegalCommandArgumentException -import net.mamoe.mirai.console.command.descriptor.AbstractCommandValueArgumentParser.Companion.illegalArgument - -/** - * 在解析参数时遇到的 _正常_ 错误. 如参数不符合规范等. - * - * [message] 将会发送给指令调用方. - * - * @see IllegalCommandArgumentException - * @see CommandValueArgumentParser - * @see AbstractCommandValueArgumentParser.illegalArgument - */ -public class CommandArgumentParserException : IllegalCommandArgumentException { - public constructor() : super() - public constructor(message: String?) : super(message) - public constructor(message: String?, cause: Throwable?) : super(message, cause) - public constructor(cause: Throwable?) : super(cause) -} \ No newline at end of file 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 556a44e6c..b83e6907a 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 @@ -26,26 +26,46 @@ import kotlin.reflect.full.isSubtypeOf import kotlin.reflect.typeOf /** - * @see CommandSignatureVariantImpl + * 指令签名. 表示指令定义的需要的参数. + * + * @see AbstractCommandSignature */ @ExperimentalCommandDescriptors -public interface CommandSignatureVariant { +public interface CommandSignature { + /** + * 接收者参数, 为 [CommandSender] 子类 + */ @ConsoleExperimentalApi public val receiverParameter: CommandReceiverParameter<out CommandSender>? + /** + * 形式 值参数. + */ public val valueParameters: List<AbstractCommandValueParameter<*>> + /** + * 调用这个指令. + */ public suspend fun call(resolvedCommandCall: ResolvedCommandCall) } +/** + * 来自 [KFunction] 反射得到的 [CommandSignature] + * + * @see CommandSignatureFromKFunctionImpl + */ @ConsoleExperimentalApi @ExperimentalCommandDescriptors -public interface CommandSignatureVariantFromKFunction : CommandSignatureVariant { +public interface CommandSignatureFromKFunction : CommandSignature { public val originFunction: KFunction<*> } +/** + * @see CommandSignatureImpl + * @see CommandSignatureFromKFunctionImpl + */ @ExperimentalCommandDescriptors -public abstract class AbstractCommandSignatureVariant : CommandSignatureVariant { +public abstract class AbstractCommandSignature : CommandSignature { override fun toString(): String { val receiverParameter = receiverParameter return if (receiverParameter == null) { @@ -57,11 +77,11 @@ public abstract class AbstractCommandSignatureVariant : CommandSignatureVariant } @ExperimentalCommandDescriptors -public open class CommandSignatureVariantImpl( +public open class CommandSignatureImpl( override val receiverParameter: CommandReceiverParameter<out CommandSender>?, override val valueParameters: List<AbstractCommandValueParameter<*>>, - private val onCall: suspend CommandSignatureVariantImpl.(resolvedCommandCall: ResolvedCommandCall) -> Unit, -) : CommandSignatureVariant, AbstractCommandSignatureVariant() { + private val onCall: suspend CommandSignatureImpl.(resolvedCommandCall: ResolvedCommandCall) -> Unit, +) : CommandSignature, AbstractCommandSignature() { override suspend fun call(resolvedCommandCall: ResolvedCommandCall) { return onCall(resolvedCommandCall) } @@ -69,12 +89,12 @@ public open class CommandSignatureVariantImpl( @ConsoleExperimentalApi @ExperimentalCommandDescriptors -public open class CommandSignatureVariantFromKFunctionImpl( +public open class CommandSignatureFromKFunctionImpl( override val receiverParameter: CommandReceiverParameter<out CommandSender>?, override val valueParameters: List<AbstractCommandValueParameter<*>>, override val originFunction: KFunction<*>, - private val onCall: suspend CommandSignatureVariantFromKFunctionImpl.(resolvedCommandCall: ResolvedCommandCall) -> Unit, -) : CommandSignatureVariantFromKFunction, AbstractCommandSignatureVariant() { + private val onCall: suspend CommandSignatureFromKFunctionImpl.(resolvedCommandCall: ResolvedCommandCall) -> Unit, +) : CommandSignatureFromKFunction, AbstractCommandSignature() { override suspend fun call(resolvedCommandCall: ResolvedCommandCall) { return onCall(resolvedCommandCall) } 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 30d167657..b92ab8b68 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 @@ -11,7 +11,9 @@ package net.mamoe.mirai.console.command.descriptor -import net.mamoe.mirai.console.command.parse.CommandCall +import net.mamoe.mirai.console.command.Command +import net.mamoe.mirai.console.command.IllegalCommandArgumentException +import net.mamoe.mirai.console.command.descriptor.AbstractCommandValueArgumentParser.Companion.illegalArgument import net.mamoe.mirai.console.command.parse.CommandValueArgument import net.mamoe.mirai.console.internal.data.classifierAsKClassOrNull import net.mamoe.mirai.console.internal.data.qualifiedNameOrTip @@ -28,9 +30,10 @@ public open class NoValueArgumentMappingException( ) : 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 CommandDeclarationClashException( + public val command: Command, + public val signatures: List<CommandSignature>, +) : CommandResolutionException("Command declaration clash: \n${signatures.joinToString("\n")}") public open class CommandResolutionException : RuntimeException { public constructor() : super() @@ -38,3 +41,19 @@ public open class CommandResolutionException : RuntimeException { public constructor(message: String?, cause: Throwable?) : super(message, cause) public constructor(cause: Throwable?) : super(cause) } + +/** + * 在解析参数时遇到的 _正常_ 错误. 如参数不符合规范等. + * + * [message] 将会发送给指令调用方. + * + * @see IllegalCommandArgumentException + * @see CommandValueArgumentParser + * @see AbstractCommandValueArgumentParser.illegalArgument + */ +public class CommandArgumentParserException : IllegalCommandArgumentException { + public constructor() : super() + public constructor(message: String?) : super(message) + public constructor(message: String?, cause: Throwable?) : super(message, cause) + public constructor(cause: Throwable?) : super(cause) +} \ 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 5869352f6..97774d55c 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 @@ -31,13 +31,13 @@ public object BuiltInCommandCallResolver : CommandCallResolver { return ResolvedCommandCallImpl(call.caller, callee, - signature.variant, + signature.signature, signature.zippedArguments.map { it.second }, context ?: EmptyCommandArgumentContext) } private data class ResolveData( - val variant: CommandSignatureVariant, + val signature: CommandSignature, val zippedArguments: List<Pair<AbstractCommandValueParameter<*>, CommandValueArgument>>, val argumentAcceptances: List<ArgumentAcceptanceWithIndex>, val remainingParameters: List<AbstractCommandValueParameter<*>>, @@ -69,7 +69,7 @@ public object BuiltInCommandCallResolver : CommandCallResolver { if (zipped.isEmpty()) { ResolveData( - variant = signature, + signature = signature, zippedArguments = emptyList(), argumentAcceptances = emptyList(), remainingParameters = remainingParameters, @@ -91,7 +91,7 @@ public object BuiltInCommandCallResolver : CommandCallResolver { } ResolveData( - variant = signature, + signature = signature, zippedArguments = zipped, argumentAcceptances = zipped.mapIndexed { index, (parameter, argument) -> val accepting = parameter.accepting(argument, context) @@ -163,7 +163,7 @@ fun main() { private fun List<ResolveData>.takeLongestMatches(): Collection<ResolveData> { if (isEmpty()) return emptyList() return associateWith { - it.variant.valueParameters.size - it.remainingOptionalCount * 1.001 // slightly lower priority with optional defaults. + it.signature.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 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 7b9a2db3b..5b48c74b9 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 @@ -37,9 +37,9 @@ public interface ResolvedCommandCall { public val callee: Command /** - * The callee [CommandSignatureVariant], specifically a sub command from [CompositeCommand] + * The callee [CommandSignature], specifically a sub command from [CompositeCommand] */ - public val calleeSignature: CommandSignatureVariant + public val calleeSignature: CommandSignature /** * Original arguments @@ -47,7 +47,7 @@ public interface ResolvedCommandCall { public val rawValueArguments: List<CommandValueArgument> /** - * Resolved value arguments arranged mapping the [CommandSignatureVariant.valueParameters] by index. + * Resolved value arguments arranged mapping the [CommandSignature.valueParameters] by index. * * **Implementation details**: Lazy calculation. */ @@ -73,7 +73,7 @@ public suspend inline fun ResolvedCommandCall.call() { public class ResolvedCommandCallImpl( override val caller: CommandSender, override val callee: Command, - override val calleeSignature: CommandSignatureVariant, + override val calleeSignature: CommandSignature, override val rawValueArguments: List<CommandValueArgument>, private val context: CommandArgumentContext, ) : ResolvedCommandCall { 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 ebba4b153..a85a7a8f8 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 @@ -11,6 +11,7 @@ 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.KType import kotlin.reflect.KVisibility import kotlin.reflect.full.* @@ -133,7 +134,7 @@ internal class CommandReflector( // if (isAbstract) illegalDeclaration("Command function cannot be abstract") } - fun generateUsage(overloads: Iterable<CommandSignatureVariantFromKFunction>): String { + fun generateUsage(overloads: Iterable<CommandSignatureFromKFunction>): String { return overloads.joinToString("\n") { subcommand -> buildString { if (command.prefixOptional) { @@ -173,17 +174,45 @@ internal class CommandReflector( } } - fun validate(variants: List<CommandSignatureVariantFromKFunctionImpl>) { + fun validate(signatures: List<CommandSignatureFromKFunctionImpl>) { - data class ErasedParameters( - val name: String, - val x: String, + data class ErasedParameterInfo( + val index: Int, + val name: String?, + val type: KType, // ignore nullability + val additional: String?, ) - variants + + data class ErasedVariantInfo( + val receiver: ErasedParameterInfo?, + val valueParameters: List<ErasedParameterInfo>, + ) + + fun CommandParameter<*>.toErasedParameterInfo(index: Int): ErasedParameterInfo { + return ErasedParameterInfo(index, + this.name, + this.type.withNullability(false), + if (this is AbstractCommandValueParameter.StringConstant) this.expectingValue else null) + } + + val candidates = signatures.map { variant -> + variant to ErasedVariantInfo( + variant.receiverParameter?.toErasedParameterInfo(0), + variant.valueParameters.mapIndexed { index, parameter -> parameter.toErasedParameterInfo(index) } + ) + } + + val groups = candidates.groupBy { it.second } + + val clashes = groups.entries.find { (_, value) -> + value.size > 1 + } ?: return + + throw CommandDeclarationClashException(command, clashes.value.map { it.first }) } @Throws(IllegalCommandDeclarationException::class) - fun findSubCommands(): List<CommandSignatureVariantFromKFunctionImpl> { + fun findSubCommands(): List<CommandSignatureFromKFunctionImpl> { return command::class.functions // exclude static later .asSequence() .filter { it.isSubCommandFunction() } @@ -198,7 +227,7 @@ internal class CommandReflector( val functionValueParameters = function.valueParameters.associateBy { it.toUserDefinedCommandParameter() } - CommandSignatureVariantFromKFunctionImpl( + CommandSignatureFromKFunctionImpl( receiverParameter = function.extensionReceiverParameter?.toCommandReceiverParameter(), valueParameters = functionNameAsValueParameter + functionValueParameters.keys, originFunction = function 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 deleted file mode 100644 index 8279d9f1c..000000000 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommand.CommandParam.kt +++ /dev/null @@ -1,63 +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("unused", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") - -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.KParameter -import kotlin.reflect.KType - -/* -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, - null - ) -} -*/ - -/** - * 指令形式参数. - */ -internal data class CommandParameter<T : Any>( - /** - * 参数名. 不允许重复. - */ - val name: String, - /** - * 参数类型. 将从 [CompositeCommand.context] 中寻找 [CommandValueArgumentParser] 解析. - */ - val type: KType, // exact type - val parameter: KParameter, // source parameter -) { - constructor(name: String, type: KType, parameter: KParameter, parser: CommandValueArgumentParser<T>) : this( - name, type, parameter - ) { - this._overrideParser = parser - } - - @Suppress("PropertyName") - @JvmField - internal var _overrideParser: CommandValueArgumentParser<T>? = null - - - /** - * 覆盖的 [CommandValueArgumentParser]. - * - * 如果非 `null`, 将不会从 [CommandArgumentContext] 寻找 [CommandValueArgumentParser] - */ - val overrideParser: CommandValueArgumentParser<T>? get() = _overrideParser -} - diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/internal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/internal.kt index ac83357ac..a1ebfbf2e 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/internal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/internal.kt @@ -18,13 +18,6 @@ import kotlin.math.max import kotlin.math.min -internal infix fun Array<String>.matchesBeginning(list: List<Any>): Boolean { - this.forEachIndexed { index, any -> - if (list[index] != any) return false - } - return true -} - internal infix fun Array<out String>.intersectsIgnoringCase(other: Array<out String>): Boolean { val max = this.size.coerceAtMost(other.size) for (i in 0 until max) { From 1f790158e6dda32bc8ace3dabb283b9d37ac7d93 Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Sun, 25 Oct 2020 20:59:56 +0800 Subject: [PATCH 38/41] Review PermissionService --- .../mirai/console/command/BuiltInCommands.kt | 10 +- .../MiraiConsoleImplementationBridge.kt | 4 +- .../console/permission/PermissionService.kt | 138 +++++++++++++++--- 3 files changed, 128 insertions(+), 24 deletions(-) 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 3bef2710d..fc4c406b6 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 @@ -25,10 +25,10 @@ import net.mamoe.mirai.console.internal.command.CommandManagerImpl.allRegistered import net.mamoe.mirai.console.internal.util.runIgnoreException import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.console.permission.PermissionService -import net.mamoe.mirai.console.permission.PermissionService.Companion.denyPermission +import net.mamoe.mirai.console.permission.PermissionService.Companion.cancel import net.mamoe.mirai.console.permission.PermissionService.Companion.findCorrespondingPermissionOrFail import net.mamoe.mirai.console.permission.PermissionService.Companion.getPermittedPermissions -import net.mamoe.mirai.console.permission.PermissionService.Companion.grantPermission +import net.mamoe.mirai.console.permission.PermissionService.Companion.permit import net.mamoe.mirai.console.permission.PermitteeId import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.ConsoleInternalApi @@ -170,7 +170,7 @@ public object BuiltInCommands { @Name("被许可人 ID") target: PermitteeId, @Name("权限 ID") permission: Permission, ) { - target.grantPermission(permission) + target.permit(permission) sendMessage("OK") } @@ -180,7 +180,7 @@ public object BuiltInCommands { @Name("被许可人 ID") target: PermitteeId, @Name("权限 ID") permission: Permission, ) { - target.denyPermission(permission, false) + target.cancel(permission, false) sendMessage("OK") } @@ -190,7 +190,7 @@ public object BuiltInCommands { @Name("被许可人 ID") target: PermitteeId, @Name("权限 ID") permission: Permission, ) { - target.denyPermission(permission, true) + target.cancel(permission, true) sendMessage("OK") } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt index 175af2331..e3ebe4c50 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt @@ -38,7 +38,7 @@ import net.mamoe.mirai.console.internal.permission.BuiltInPermissionService import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl import net.mamoe.mirai.console.internal.util.autoHexToBytes import net.mamoe.mirai.console.permission.PermissionService -import net.mamoe.mirai.console.permission.PermissionService.Companion.grantPermission +import net.mamoe.mirai.console.permission.PermissionService.Companion.permit import net.mamoe.mirai.console.permission.RootPermission import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.plugin.center.PluginCenter @@ -174,7 +174,7 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI } } - ConsoleCommandSender.grantPermission(RootPermission) + ConsoleCommandSender.permit(RootPermission) } phase `prepare commands`@{ 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 363393092..130fe28fd 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 @@ -27,7 +27,7 @@ import kotlin.reflect.KClass * ### 可扩展 * 权限服务可由插件扩展并覆盖默认实现. * - * [PermissionServiceProvider] + * @see PermissionServiceProvider 相应扩展 */ @PermissionImplementation public interface PermissionService<P : Permission> { @@ -50,11 +50,15 @@ public interface PermissionService<P : Permission> { /** * 获取所有已注册的指令列表. 应保证线程安全. + * + * 备注: Java 实现者使用 `CollectionsKt.asSequence(Collection)` 构造 [Sequence] */ public fun getRegisteredPermissions(): Sequence<P> /** * 获取 [PermitteeId] 和其父标识的所有被授予的所有直接和间接的权限列表 + * + * 备注: Java 实现者使用 `CollectionsKt.asSequence(Collection)` 构造 [Sequence] */ public fun getPermittedPermissions(permitteeId: PermitteeId): Sequence<P> @@ -83,7 +87,12 @@ public interface PermissionService<P : Permission> { * * @throws PermissionRegistryConflictException 当已存在一个 [PermissionId] 时抛出. * + * @param description 描述. 将会展示给用户. + * * @return 申请到的 [Permission] 实例 + * + * @see get 获取一个已注册的权限 + * @see getOrFail 获取一个已注册的权限 */ @Throws(PermissionRegistryConflictException::class) public fun register( @@ -97,7 +106,10 @@ public interface PermissionService<P : Permission> { public fun allocatePermissionIdForPlugin( plugin: Plugin, @ResolveContext(COMMAND_NAME) permissionName: String, - ): PermissionId = allocatePermissionIdForPluginDefaultImplement(plugin, permissionName) + ): PermissionId = PermissionId( + plugin.description.id.toLowerCase(), + permissionName.toLowerCase() + ) /////////////////////////////////////////////////////////////////////////// @@ -129,6 +141,9 @@ public interface PermissionService<P : Permission> { public companion object { internal var instanceField: PermissionService<*>? = null + /** + * [PermissionService] 实例 + */ @get:JvmName("getInstance") @JvmStatic public val INSTANCE: PermissionService<out Permission> @@ -136,82 +151,171 @@ public interface PermissionService<P : Permission> { /** * 获取一个权限, 失败时抛出 [NoSuchElementException] + * + * @see register 申请并注册一个权限 */ + @JvmStatic @Throws(NoSuchElementException::class) public fun <P : Permission> PermissionService<P>.getOrFail(id: PermissionId): P = get(id) ?: throw NoSuchElementException("Permission not found: $id") - internal fun PermissionService<*>.allocatePermissionIdForPluginDefaultImplement( - plugin: Plugin, - @ResolveContext(COMMAND_NAME) permissionName: String, - ) = PermissionId( - plugin.description.id.toLowerCase(), - permissionName.toLowerCase() - ) + /** + * @see findCorrespondingPermission + */ + @JvmStatic + public val PermissionId.correspondingPermission: Permission? + get() = findCorrespondingPermission() + /** + * @see get + */ + @JvmStatic public fun PermissionId.findCorrespondingPermission(): Permission? = INSTANCE[this] + /** + * @see getOrFail + * @throws NoSuchElementException + */ + @Throws(NoSuchElementException::class) + @JvmStatic public fun PermissionId.findCorrespondingPermissionOrFail(): Permission = INSTANCE.getOrFail(this) - public fun PermitteeId.grantPermission(permission: Permission) { + /** + * @see PermissionService.permit + */ + @JvmStatic + @JvmName("permit0") // clash, not JvmSynthetic to allow possible calls from Java. + public fun PermitteeId.permit(permission: Permission) { INSTANCE.checkType(permission::class).permit(this, permission) } - public fun PermitteeId.grantPermission(permissionId: PermissionId) { - grantPermission(permissionId.findCorrespondingPermissionOrFail()) + /** + * @see PermissionService.permit + * @throws NoSuchElementException + */ + @JvmStatic + @Throws(NoSuchElementException::class) + public fun PermitteeId.permit(permissionId: PermissionId) { + permit(permissionId.findCorrespondingPermissionOrFail()) } - public fun PermitteeId.denyPermission(permission: Permission, recursive: Boolean) { + /** + * @see PermissionService.cancel + */ + @JvmSynthetic + @JvmStatic + @JvmName("cancel0") // clash, not JvmSynthetic to allow possible calls from Java. + public fun PermitteeId.cancel(permission: Permission, recursive: Boolean) { INSTANCE.checkType(permission::class).cancel(this, permission, recursive) } - public fun PermitteeId.denyPermission(permissionId: PermissionId, recursive: Boolean) { - denyPermission(permissionId.findCorrespondingPermissionOrFail(), recursive) + /** + * @see PermissionService.cancel + * @throws NoSuchElementException + */ + @JvmStatic + @Throws(NoSuchElementException::class) + public fun PermitteeId.cancel(permissionId: PermissionId, recursive: Boolean) { + cancel(permissionId.findCorrespondingPermissionOrFail(), recursive) } + /** + * @see PermissionService.testPermission + */ + @JvmStatic public fun Permittee.hasPermission(permission: Permission): Boolean = permission.testPermission(this@hasPermission) + /** + * @see PermissionService.testPermission + */ + @JvmStatic public fun PermitteeId.hasPermission(permission: Permission): Boolean = permission.testPermission(this@hasPermission) + /** + * @see PermissionService.testPermission + * @throws NoSuchElementException + */ + @JvmStatic + @Throws(NoSuchElementException::class) public fun PermitteeId.hasPermission(permissionId: PermissionId): Boolean { val instance = permissionId.findCorrespondingPermissionOrFail() return INSTANCE.checkType(instance::class).testPermission(this@hasPermission, instance) } + /** + * @see PermissionService.testPermission + */ + @JvmStatic public fun Permittee.hasPermission(permissionId: PermissionId): Boolean = permissionId.testPermission(this@hasPermission) + + /** + * @see PermissionService.getPermittedPermissions + */ + @JvmStatic public fun Permittee.getPermittedPermissions(): Sequence<Permission> = INSTANCE.getPermittedPermissions(this@getPermittedPermissions.permitteeId) - public fun Permittee.grantPermission(vararg permissions: Permission) { + + /** + * @see PermissionService.permit + */ + @JvmStatic + public fun Permittee.permit(vararg permissions: Permission) { for (permission in permissions) { INSTANCE.checkType(permission::class).permit(this.permitteeId, permission) } } - public fun Permittee.denyPermission(vararg permissions: Permission, recursive: Boolean) { + /** + * @see PermissionService.cancel + */ + @JvmStatic + public fun Permittee.cancel(vararg permissions: Permission, recursive: Boolean) { for (permission in permissions) { INSTANCE.checkType(permission::class).cancel(this.permitteeId, permission, recursive) } } + /** + * @see PermissionService.getPermittedPermissions + */ + @JvmSynthetic + @JvmStatic + @JvmName("getPermittedPermissions0") // clash, not JvmSynthetic to allow possible calls from Java. public fun PermitteeId.getPermittedPermissions(): Sequence<Permission> = INSTANCE.getPermittedPermissions(this@getPermittedPermissions) + /** + * @see PermissionService.testPermission + */ + @JvmStatic public fun Permission.testPermission(permittee: Permittee): Boolean = INSTANCE.checkType(this::class).testPermission(permittee.permitteeId, this@testPermission) + /** + * @see PermissionService.testPermission + */ + @JvmStatic public fun Permission.testPermission(permitteeId: PermitteeId): Boolean = INSTANCE.checkType(this::class).testPermission(permitteeId, this@testPermission) + /** + * @see PermissionService.testPermission + */ + @JvmStatic public fun PermissionId.testPermission(permittee: Permittee): Boolean { val p = INSTANCE[this] ?: return false return p.testPermission(permittee) } + /** + * @see PermissionService.testPermission + */ + @JvmStatic public fun PermissionId.testPermission(permissible: PermitteeId): Boolean { val p = INSTANCE[this] ?: return false return p.testPermission(permissible) From 077e4055cc256bb9650c948445c707c7c219b654 Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Sun, 25 Oct 2020 21:10:45 +0800 Subject: [PATCH 39/41] Review extensions --- .../console/MiraiConsoleImplementation.kt | 20 +++++++++++++++++-- .../extension/PluginComponentStorage.kt | 1 + .../extensions/BotConfigurationAlterer.kt | 1 + .../extensions/PluginLoaderProvider.kt | 7 +++++++ .../extensions/PostStartupExtension.kt | 9 +++++++++ .../MiraiConsoleImplementationBridge.kt | 2 +- .../console/plugin/loader/PluginLoader.kt | 1 + 7 files changed, 38 insertions(+), 3 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt index a1fd6347a..c2cab53e7 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt @@ -30,6 +30,7 @@ import java.util.* import java.util.concurrent.locks.ReentrantLock import kotlin.annotation.AnnotationTarget.* import kotlin.coroutines.CoroutineContext +import kotlin.system.exitProcess /** @@ -176,7 +177,7 @@ public interface MiraiConsoleImplementation : CoroutineScope { /** * 可由前端调用, 获取当前的 [MiraiConsoleImplementation] 实例 * - * 必须在 [start] 之后才能使用. + * 必须在 [start] 之后才能使用, 否则抛出 [UninitializedPropertyAccessException] */ @JvmStatic @ConsoleFrontEndImplementation @@ -189,7 +190,22 @@ public interface MiraiConsoleImplementation : CoroutineScope { public fun MiraiConsoleImplementation.start(): Unit = initLock.withLock { if (::instance.isInitialized) error("Mirai Console is already initialized.") this@Companion.instance = this - MiraiConsoleImplementationBridge.doStart() + kotlin.runCatching { + MiraiConsoleImplementationBridge.doStart() + }.onFailure { e -> + kotlin.runCatching { + MiraiConsole.mainLogger.error("Failed to init MiraiConsole.", e) + }.onFailure { + e.printStackTrace() + } + + kotlin.runCatching { + MiraiConsole.cancel() + }.onFailure { + it.printStackTrace() + } + exitProcess(1) + } } } } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/PluginComponentStorage.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/PluginComponentStorage.kt index 2a6a8a63f..74acd845c 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/PluginComponentStorage.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/PluginComponentStorage.kt @@ -56,6 +56,7 @@ public class PluginComponentStorage( public fun contributeSingletonExtensionSelector(lazyInstance: () -> SingletonExtensionSelector): Unit = contribute(SingletonExtensionSelector, plugin, lazyInstance) + @Suppress("SpellCheckingInspection") // alterer /** 注册一个 [BotConfigurationAlterer] */ public fun contributeBotConfigurationAlterer(instance: BotConfigurationAlterer): Unit = contribute(BotConfigurationAlterer, plugin, lazyInstance = { instance }) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/BotConfigurationAlterer.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/BotConfigurationAlterer.kt index 45ce4f680..642d5ea9b 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/BotConfigurationAlterer.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/BotConfigurationAlterer.kt @@ -21,6 +21,7 @@ import net.mamoe.mirai.utils.BotConfiguration * * @see MiraiConsole.addBot */ +@Suppress("SpellCheckingInspection") // alterer public fun interface BotConfigurationAlterer : FunctionExtension { /** diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/PluginLoaderProvider.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/PluginLoaderProvider.kt index 510a1119b..3b96f4ac6 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/PluginLoaderProvider.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/PluginLoaderProvider.kt @@ -12,12 +12,19 @@ package net.mamoe.mirai.console.extensions import net.mamoe.mirai.console.extension.AbstractExtensionPoint import net.mamoe.mirai.console.extension.Extension import net.mamoe.mirai.console.extension.InstanceExtension +import net.mamoe.mirai.console.extension.PluginComponentStorage import net.mamoe.mirai.console.plugin.loader.PluginLoader /** * 提供扩展 [PluginLoader] * + * @see PluginComponentStorage.contributePluginLoader + * + * * @see Extension + * @see PluginLoader + * + * @see LazyPluginLoaderProviderImpl */ public interface PluginLoaderProvider : InstanceExtension<PluginLoader<*, *>> { public companion object ExtensionPoint : AbstractExtensionPoint<PluginLoaderProvider>(PluginLoaderProvider::class) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/PostStartupExtension.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/PostStartupExtension.kt index ee8a97f45..f18185e17 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/PostStartupExtension.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/PostStartupExtension.kt @@ -9,8 +9,11 @@ package net.mamoe.mirai.console.extensions +import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.extension.AbstractExtensionPoint +import net.mamoe.mirai.console.extension.ExtensionException import net.mamoe.mirai.console.extension.FunctionExtension +import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge /** * 在 Console 启动完成后立即在主线程调用的扩展. 用于进行一些必要的延迟初始化. @@ -20,7 +23,13 @@ import net.mamoe.mirai.console.extension.FunctionExtension public fun interface PostStartupExtension : FunctionExtension { /** * 将在 Console 主线程执行. + * + * @throws Exception 所有抛出的 [Exception] 都会被捕获并包装为 [ExtensionException] 抛出, 并停止 [MiraiConsole] + * + * #### 内部实现细节 + * 在 [MiraiConsoleImplementationBridge.doStart] 所有 [MiraiConsoleImplementationBridge.phase] 执行完成后顺序调用. */ + @Throws(Exception::class) public operator fun invoke() public companion object ExtensionPoint : AbstractExtensionPoint<PostStartupExtension>(PostStartupExtension::class) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt index e3ebe4c50..fd587247b 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt @@ -217,7 +217,7 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI } GlobalComponentStorage.run { - PostStartupExtension.useExtensions { it() } + PostStartupExtension.useExtensions { it() } // exceptions thrown will be caught by caller of `doStart`. } mainLogger.info { "mirai-console started successfully." } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/loader/PluginLoader.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/loader/PluginLoader.kt index 40a2ad152..208f4e743 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/loader/PluginLoader.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/loader/PluginLoader.kt @@ -37,6 +37,7 @@ import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader * 直接实现接口 [PluginLoader] 或 [FilePluginLoader], 并注册 [PluginLoaderProvider] * * @see JvmPluginLoader Jar 插件加载器 + * @see PluginLoaderProvider 扩展 */ public interface PluginLoader<P : Plugin, D : PluginDescription> { /** From ce0bf8d8beb627a68c869a959043a4bb08e86cb1 Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Sun, 25 Oct 2020 21:40:41 +0800 Subject: [PATCH 40/41] Review extension --- .../parse/SpaceSeparatedCommandCallParser.kt | 3 +- .../mirai/console/extension/ExtensionPoint.kt | 60 ++++++++++++++++--- .../extension/PluginComponentStorage.kt | 31 +++++++--- .../extensions/CommandCallParserProvider.kt | 16 ++++- .../extensions/CommandCallResolverProvider.kt | 5 +- .../extensions/PermissionServiceProvider.kt | 7 +-- .../MiraiConsoleImplementationBridge.kt | 4 +- .../extension/ComponentStorageInternal.kt | 2 +- .../console/permission/PermissionService.kt | 6 +- 9 files changed, 100 insertions(+), 34 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 8cb917344..b17cabdaf 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 @@ -3,6 +3,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.extensions.CommandCallParserProviderImpl import net.mamoe.mirai.console.internal.command.flattenCommandComponents import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.message.data.MessageChain @@ -22,5 +23,5 @@ public object SpaceSeparatedCommandCallParser : CommandCallParser { ) } - public object Provider : CommandCallParserProvider(SpaceSeparatedCommandCallParser) + public object Provider : CommandCallParserProvider by CommandCallParserProviderImpl(SpaceSeparatedCommandCallParser) } \ No newline at end of file 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 d076776c0..b5580db83 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 @@ -7,30 +7,72 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -@file:Suppress("unused", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") +@file:Suppress("unused", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "MemberVisibilityCanBePrivate") package net.mamoe.mirai.console.extension +import net.mamoe.mirai.console.extensions.SingletonExtensionSelector +import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage +import net.mamoe.mirai.console.util.ConsoleExperimentalApi import kotlin.reflect.KClass + /** - * 由 [Extension] 的 `companion` 实现. + * 由 [Extension] 的伴生对象实现. + * + * @see AbstractExtensionPoint */ public interface ExtensionPoint<T : Extension> { + /** + * 扩展实例 [T] 的类型 + */ public val extensionType: KClass<T> } -public open class AbstractExtensionPoint<T : Extension>( +public abstract 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] */ -public interface SingletonExtensionPoint<T : SingletonExtension<*>> : ExtensionPoint<T> \ No newline at end of file +public interface SingletonExtensionPoint<T : SingletonExtension<*>> : ExtensionPoint<T> + +/** + * 表示一个 [InstanceExtension] 的 [ExtensionPoint] + */ +public interface InstanceExtensionPoint<T : InstanceExtension<*>> : ExtensionPoint<T> + +/** + * 表示一个 [FunctionExtension] 的 [ExtensionPoint] + */ +public interface FunctionExtensionPoint<T : FunctionExtension> : ExtensionPoint<T> + + +public abstract class AbstractInstanceExtensionPoint<E : InstanceExtension<T>, T>( + extensionType: KClass<E>, + /** + * 内建的实现列表. + */ + @ConsoleExperimentalApi + public vararg val builtinImplementations: E, +) : AbstractExtensionPoint<E>(extensionType) + +public abstract class AbstractSingletonExtensionPoint<E : SingletonExtension<T>, T>( + extensionType: KClass<E>, + /** + * 内建的实现. + */ + @ConsoleExperimentalApi + public val builtinImplementation: T, +) : AbstractExtensionPoint<E>(extensionType), SingletonExtensionPoint<E> { + + /** + * 由 [SingletonExtensionSelector] 选择后的实例. + */ + @ConsoleExperimentalApi + public val selectedInstance: T by lazy { + GlobalComponentStorage.run { this@AbstractSingletonExtensionPoint.findSingletonInstance(extensionType, builtinImplementation) } + } +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/PluginComponentStorage.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/PluginComponentStorage.kt index 74acd845c..402b075df 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/PluginComponentStorage.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/PluginComponentStorage.kt @@ -9,6 +9,8 @@ package net.mamoe.mirai.console.extension +import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors +import net.mamoe.mirai.console.command.parse.CommandCallParser import net.mamoe.mirai.console.extensions.* import net.mamoe.mirai.console.internal.extension.AbstractConcurrentComponentStorage import net.mamoe.mirai.console.permission.PermissionService @@ -35,7 +37,7 @@ public class PluginComponentStorage( ): Unit = contribute(extensionPoint, plugin, lazyInstance) /** - * 注册一个扩展 + * 注册一个扩展. [E] 必须拥有伴生对象为 [ExtensionPoint]. */ public inline fun <reified E : Extension> contribute( noinline lazyInstance: () -> E, @@ -74,16 +76,14 @@ public class PluginComponentStorage( /** 注册一个 [PermissionServiceProvider] */ @OverloadResolutionByLambdaReturnType - public fun contributePermissionService( - lazyInstance: () -> PermissionService<*>, - ): Unit = contribute(PermissionServiceProvider, plugin, LazyPermissionServiceProviderImpl(lazyInstance)) + public fun contributePermissionService(lazyInstance: () -> PermissionService<*>): Unit = + contribute(PermissionServiceProvider, plugin, LazyPermissionServiceProviderImpl(lazyInstance)) /** 注册一个 [PermissionServiceProvider] */ @JvmName("contributePermissionServiceProvider") @OverloadResolutionByLambdaReturnType - public fun contributePermissionService( - lazyProvider: () -> PermissionServiceProvider, - ): Unit = contribute(PermissionServiceProvider, plugin, lazyProvider) + public fun contributePermissionService(lazyProvider: () -> PermissionServiceProvider): Unit = + contribute(PermissionServiceProvider, plugin, lazyProvider) ///////////////////////////////////// @@ -96,5 +96,20 @@ public class PluginComponentStorage( @JvmName("contributePluginLoaderProvider") @OverloadResolutionByLambdaReturnType public fun contributePluginLoader(lazyProvider: () -> PluginLoaderProvider): Unit = - contribute(PluginLoaderProvider, plugin, lazyProvider) + contribute(PluginLoaderProvider, plugin, lazyProvider) // lazy for safety + + ///////////////////////////////////// + + /** 注册一个 [CommandCallParserProvider] */ + @ExperimentalCommandDescriptors + @OverloadResolutionByLambdaReturnType + public fun contributeCommandCallParser(lazyInstance: () -> CommandCallParser): Unit = + contribute(CommandCallParserProvider, plugin, LazyCommandCallParserProviderImpl(lazyInstance)) + + /** 注册一个 [CommandCallParserProvider] */ + @ExperimentalCommandDescriptors + @JvmName("contributeCommandCallParserProvider") + @OverloadResolutionByLambdaReturnType + public fun contributeCommandCallParser(provider: CommandCallParserProvider): Unit = + contribute(CommandCallParserProvider, plugin, provider) } \ 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 4a19a0fac..399d748dd 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 @@ -12,14 +12,24 @@ 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.command.parse.SpaceSeparatedCommandCallParser +import net.mamoe.mirai.console.extension.AbstractInstanceExtensionPoint import net.mamoe.mirai.console.extension.InstanceExtension -import net.mamoe.mirai.console.extension.InstanceExtensionPoint /** * The provider of [CommandCallParser] */ @ExperimentalCommandDescriptors -public open class CommandCallParserProvider(override val instance: CommandCallParser) : InstanceExtension<CommandCallParser> { +public interface CommandCallParserProvider : InstanceExtension<CommandCallParser> { public companion object ExtensionPoint : - InstanceExtensionPoint<CommandCallParserProvider, CommandCallParser>(CommandCallParserProvider::class, SpaceSeparatedCommandCallParser.Provider) + AbstractInstanceExtensionPoint<CommandCallParserProvider, CommandCallParser>(CommandCallParserProvider::class, + SpaceSeparatedCommandCallParser.Provider) +} + + +@ExperimentalCommandDescriptors +public class CommandCallParserProviderImpl(override val instance: CommandCallParser) : CommandCallParserProvider + +@ExperimentalCommandDescriptors +public class LazyCommandCallParserProviderImpl(instanceCalculator: () -> CommandCallParser) : CommandCallParserProvider { + override val instance: CommandCallParser by lazy(instanceCalculator) } \ 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 e6e390d99..8be92c470 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 @@ -12,11 +12,12 @@ 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.AbstractInstanceExtensionPoint import net.mamoe.mirai.console.extension.InstanceExtension -import net.mamoe.mirai.console.extension.InstanceExtensionPoint @ExperimentalCommandDescriptors public open class CommandCallResolverProvider(override val instance: CommandCallResolver) : InstanceExtension<CommandCallResolver> { public companion object ExtensionPoint : - InstanceExtensionPoint<CommandCallResolverProvider, CommandCallResolver>(CommandCallResolverProvider::class, BuiltInCommandCallResolver.Provider) + AbstractInstanceExtensionPoint<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/PermissionServiceProvider.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/PermissionServiceProvider.kt index 0fa2b9f26..0415d1f8f 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/PermissionServiceProvider.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/PermissionServiceProvider.kt @@ -9,9 +9,9 @@ package net.mamoe.mirai.console.extensions -import net.mamoe.mirai.console.extension.AbstractExtensionPoint +import net.mamoe.mirai.console.extension.AbstractSingletonExtensionPoint import net.mamoe.mirai.console.extension.SingletonExtension -import net.mamoe.mirai.console.extension.SingletonExtensionPoint +import net.mamoe.mirai.console.internal.permission.BuiltInPermissionService import net.mamoe.mirai.console.permission.PermissionService /** @@ -21,8 +21,7 @@ import net.mamoe.mirai.console.permission.PermissionService */ public interface PermissionServiceProvider : SingletonExtension<PermissionService<*>> { public companion object ExtensionPoint : - AbstractExtensionPoint<PermissionServiceProvider>(PermissionServiceProvider::class), - SingletonExtensionPoint<PermissionServiceProvider> + AbstractSingletonExtensionPoint<PermissionServiceProvider, PermissionService<*>>(PermissionServiceProvider::class, BuiltInPermissionService) } /** diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt index fd587247b..6b98780cc 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt @@ -163,9 +163,7 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI phase `load PermissionService`@{ mainLogger.verbose { "Loading PermissionService..." } - PermissionService.instanceField = GlobalComponentStorage.run { - PermissionServiceProvider.findSingletonInstance(BuiltInPermissionService) - } + PermissionServiceProvider.selectedInstance // init PermissionService.INSTANCE.let { ps -> if (ps is BuiltInPermissionService) { 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 b3072ad49..803ccc8d4 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 @@ -54,7 +54,7 @@ internal abstract class AbstractConcurrentComponentStorage : ComponentStorage { internal fun <T : Extension> ExtensionPoint<out T>.getExtensions(): Set<ExtensionRegistry<T>> { val userDefined = instances.getOrPut(this, ::CopyOnWriteArraySet) as Set<ExtensionRegistry<T>> - val builtins = if (this is InstanceExtensionPoint<*, *>) { + val builtins = if (this is AbstractInstanceExtensionPoint<*, *>) { this.builtinImplementations.mapTo(HashSet()) { DataExtensionRegistry(null, it) } as Set<ExtensionRegistry<T>> } else null 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 130fe28fd..b86f97612 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 @@ -139,15 +139,15 @@ public interface PermissionService<P : Permission> { public fun cancel(permitteeId: PermitteeId, permission: P, recursive: Boolean) public companion object { - internal var instanceField: PermissionService<*>? = null - /** * [PermissionService] 实例 + * + * @see PermissionServiceProvider.selectedInstance */ @get:JvmName("getInstance") @JvmStatic public val INSTANCE: PermissionService<out Permission> - get() = instanceField ?: error("PermissionService is not yet initialized therefore cannot be used.") + get() = PermissionServiceProvider.selectedInstance /** * 获取一个权限, 失败时抛出 [NoSuchElementException] From 2957e42625f2c16137775d286eec919a03aae52f Mon Sep 17 00:00:00 2001 From: Karlatemp <karlatemp@vip.qq.com> Date: Sun, 25 Oct 2020 23:30:27 +0800 Subject: [PATCH 41/41] Fix SemVersion testing --- .../test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt index 3f74dd191..60881b310 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt @@ -22,7 +22,8 @@ internal class TestSemVersion { internal fun testCompare() { fun String.sem(): SemVersion = SemVersion.invoke(this) assert("1.0".sem() < "1.0.1".sem()) - assert("1.0.0".sem() == "1.0".sem()) + assert("1.0.0".sem() != "1.0".sem()) + assert("1.0.0".sem().compareTo("1.0".sem()) == 0) assert("1.1".sem() > "1.0.0".sem()) assert("1.0-M4".sem() < "1.0-M5".sem()) assert("1.0-M5-dev-7".sem() < "1.0-M5-dev-15".sem())