From ef83281e97ffd4998a88f5c8b68fb906d60f81f6 Mon Sep 17 00:00:00 2001
From: Him188 <Him188@mamoe.net>
Date: Fri, 15 May 2020 00:57:50 +0800
Subject: [PATCH] Redesign CommandDescriptor

---
 .../mamoe/mirai/console/command/Command.kt    | 62 +++------------
 .../console/command/CommandDescriptor.kt      | 46 +++++++----
 .../mirai/console/command/CommandManager.kt   | 77 +++++++++----------
 .../mirai/console/command/CommandParam.kt     |  3 +
 .../console/command/CommandParserContext.kt   |  7 +-
 5 files changed, 89 insertions(+), 106 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 c36120749..fd650dff1 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,12 +11,8 @@
 
 package net.mamoe.mirai.console.command
 
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
-import net.mamoe.mirai.console.MiraiConsole
-import net.mamoe.mirai.console.plugins.PluginBase
+import net.mamoe.mirai.console.command.CommandDescriptor.SubCommandDescriptor
 import net.mamoe.mirai.message.data.Message
-import java.lang.reflect.Member
 import kotlin.reflect.KProperty
 
 internal const val FOR_BINARY_COMPATIBILITY = "for binary compatibility"
@@ -42,14 +38,18 @@ interface Command {
      */
 }
 
+internal fun Command.matchChild(args: List<Any>): SubCommandDescriptor {
+
+}
+
 
 /**
- * 指令实际参数列表. 参数顺序与 [Command.descriptor] 的 [CommandDescriptor.params] 相同.
+ * 解析完成的指令实际参数列表. 参数顺序与 [Command.descriptor] 的 [CommandDescriptor.params] 相同.
  */
 class CommandArgs private constructor(
     @JvmField
     internal val values: List<Any>,
-    private val fromCommand: Command
+    private val fromCommand: SubCommandDescriptor
 ) : List<Any> by values {
     /**
      * 获取第一个类型为 [R] 的参数
@@ -71,7 +71,7 @@ class CommandArgs private constructor(
      * @throws NoSuchElementException 找不到这个名称的参数时抛出
      */
     operator fun get(name: String?): Any {
-        val index = fromCommand.descriptor.params.indexOfFirst { it.name == name }
+        val index = fromCommand.params.indexOfFirst { it.name == name }
         if (index == -1) {
             throw NoSuchElementException("Cannot find argument named $name")
         }
@@ -93,12 +93,12 @@ class CommandArgs private constructor(
     inline operator fun <reified R : Any> getValue(thisRef: Any?, property: KProperty<*>): R = getReified()
 
     companion object {
-        fun parseFrom(command: Command, sender: CommandSender, rawArgs: List<Any>): CommandArgs {
-            val params = command.descriptor.params
+        fun parseFrom(command: SubCommandDescriptor, sender: CommandSender, rawArgs: List<Any>): CommandArgs {
+            val params = command.params
 
             require(rawArgs.size >= params.size) { "No enough rawArgs: required ${params.size}, found only ${rawArgs.size}" }
 
-            command.descriptor.params.asSequence().zip(rawArgs.asSequence()).map { (commandParam, any) ->
+            command.params.asSequence().zip(rawArgs.asSequence()).map { (commandParam, any) ->
                 command.parserFor(commandParam)?.parse(any, sender)
                     ?: error("Could not find a parser for param named ${commandParam.name}, typed ${commandParam.type.qualifiedName}")
             }.toList().let { bakedArgs ->
@@ -106,44 +106,4 @@ class CommandArgs private constructor(
             }
         }
     }
-}
-
-inline val Command.fullName get() = descriptor.fullName
-inline val Command.usage get() = descriptor.usage
-inline val Command.params get() = descriptor.params
-inline val Command.description get() = descriptor.description
-inline val Command.context get() = descriptor.context
-inline val Command.aliases get() = descriptor.aliases
-inline val Command.permission get() = descriptor.permission
-inline val Command.allNames get() = descriptor.allNames
-
-abstract class PluginCommand(
-    final override val owner: PluginBase,
-    descriptor: CommandDescriptor
-) : AbstractCommand(descriptor)
-
-internal abstract class ConsoleCommand(
-    descriptor: CommandDescriptor
-) : AbstractCommand(descriptor) {
-    final override val owner: MiraiConsole get() = MiraiConsole
-}
-
-sealed class AbstractCommand(
-    final override val descriptor: CommandDescriptor
-) : Command
-
-
-/**
- * For Java
- */
-@Suppress("unused")
-abstract class BlockingCommand(
-    owner: PluginBase,
-    descriptor: CommandDescriptor
-) : PluginCommand(owner, descriptor) {
-    final override suspend fun CommandSender.onCommand(args: CommandArgs): Boolean {
-        return withContext(Dispatchers.IO) { onCommandBlocking(this@onCommand, args) }
-    }
-
-    abstract fun onCommandBlocking(sender: CommandSender, args: CommandArgs): Boolean
 }
\ No newline at end of file
diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandDescriptor.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandDescriptor.kt
index 43a0b6d84..0a538400b 100644
--- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandDescriptor.kt
+++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandDescriptor.kt
@@ -13,7 +13,7 @@ import net.mamoe.mirai.message.data.SingleMessage
  */
 class CommandDescriptor(
     /**
-     * 子指令列表
+     * 子指令列表. 第一个元素为默认值.
      */
     val subCommands: List<SubCommandDescriptor>,
     /**
@@ -25,8 +25,8 @@ class CommandDescriptor(
 ) {
     /** 子指令描述 */
     inner class SubCommandDescriptor(
-        /** 为空字符串时代表默认 */
-        val name: String,
+        /** 子指令名, 如 "/mute group add" 中的 "group add". 当表示默认指令时为父指令名. */
+        val subName: String,
         /** 用法说明 */
         val usage: String,
         /** 指令参数列表, 有顺序. */
@@ -40,8 +40,20 @@ class CommandDescriptor(
          * @see CommandPermission.or 要求其中一个权限
          * @see CommandPermission.and 同时要求两个权限
          */
-        val permission: CommandPermission = CommandPermission.Default
-    )
+        val permission: CommandPermission = CommandPermission.Default,
+        /** 指令执行器 */
+        val onCommand: suspend (sender: CommandSender, args: CommandArgs) -> Boolean
+    ) {
+        init {
+            require(!(subName.startsWith(' ') || subName.endsWith(' '))) { "subName mustn't start or end with a caret" }
+            require(subName.isValidSubName()) { "subName mustn't contain any of $ILLEGAL_SUB_NAME_CHARS" }
+        }
+
+        @JvmField
+        internal val bakedSubNames: Array<Array<String>> =
+            listOf(subName, *aliases).map { it.bakeSubName() }.toTypedArray()
+        internal inline val parent: CommandDescriptor get() = this@CommandDescriptor
+    }
 
     /**
      * 指令参数解析器环境.
@@ -49,6 +61,13 @@ class CommandDescriptor(
     val context: CommandParserContext = CommandParserContext.Builtins + overrideContext
 }
 
+internal val CommandDescriptor.base: CommandDescriptor.SubCommandDescriptor get() = subCommands[0]
+
+
+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()
+
 
 /**
  * 检查指令参数数量是否足够, 类型是否匹配.
@@ -213,15 +232,16 @@ inline class ParamBlock internal constructor(@PublishedApi internal val list: Mu
 /// internal
 
 
-internal fun Any.flattenCommandComponents(): Sequence<String> = when (this) {
-    is Array<*> -> this.asSequence().flatMap {
-        it?.flattenCommandComponents() ?: throw java.lang.IllegalArgumentException("unexpected null value")
+internal fun Any.flattenCommandComponents(): List<String> {
+    val list = ArrayList<String>()
+    when (this) {
+        is String -> list.addAll(split(' ').filterNot { it.isBlank() })
+        is PlainText -> list.addAll(content.flattenCommandComponents())
+        is SingleMessage -> list.add(this.toString())
+        is MessageChain -> this.asSequence().forEach { list.addAll(it.flattenCommandComponents()) }
+        else -> throw IllegalArgumentException("Illegal component: $this")
     }
-    is String -> splitToSequence(' ').filterNot { it.isBlank() }
-    is PlainText -> content.flattenCommandComponents()
-    is SingleMessage -> sequenceOf(this.toString())
-    is MessageChain -> this.asSequence().flatMap { it.flattenCommandComponents() }
-    else -> throw IllegalArgumentException("Illegal component: $this")
+    return list
 }
 
 internal fun Any.checkFullName(errorHint: String): Array<String> {
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 c8b05be87..ec319625a 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
@@ -7,7 +7,7 @@ import kotlinx.atomicfu.locks.withLock
 import net.mamoe.mirai.console.plugins.PluginBase
 import net.mamoe.mirai.message.data.Message
 import net.mamoe.mirai.message.data.MessageChain
-import java.util.*
+import net.mamoe.mirai.utils.MiraiExperimentalAPI
 import java.util.concurrent.locks.ReentrantLock
 
 sealed class CommandOwner
@@ -30,16 +30,13 @@ fun CommandOwner.unregisterAllCommands() {
 }
 
 /**
- * 注册一个指令. 若此指令已经注册或有已经注册的指令与 [allNames] 重名, 返回 `false`
+ * 注册一个指令. 若此指令已经注册或有已经注册的指令与 [SubCommandDescriptor] 重名, 返回 `false`
  */
 fun Command.register(): Boolean = InternalCommandManager.modifyLock.withLock {
     if (findDuplicate() != null) {
         return false
     }
     InternalCommandManager.registeredCommands.add(this@register)
-    for (name in this.allNames) {
-        InternalCommandManager.nameToCommandMap[name] = this@register
-    }
     return true
 }
 
@@ -47,25 +44,24 @@ fun Command.register(): Boolean = InternalCommandManager.modifyLock.withLock {
  * 查找是否有重名的指令. 返回重名的指令.
  */
 fun Command.findDuplicate(): Command? {
-    return InternalCommandManager.nameToCommandMap.entries.firstOrNull { (names, _) ->
-        this.allNames.any { it.contentEquals(names) }
-    }?.value
+    return InternalCommandManager.registeredCommands.firstOrNull {
+        it.descriptor.base.bakedSubNames intersects this.descriptor.base.bakedSubNames
+    }
+}
+
+private infix fun <T> Array<T>.intersects(other: Array<T>): Boolean {
+    val max = this.size.coerceAtMost(other.size)
+    for (i in 0 until max) {
+        if (this[i] == other[i]) return true
+    }
+    return false
 }
 
 /**
  * 取消注册这个指令. 若指令未注册, 返回 `false`
  */
 fun Command.unregister(): Boolean = InternalCommandManager.modifyLock.withLock {
-    if (!InternalCommandManager.registeredCommands.contains(this)) {
-        return false
-    }
-    InternalCommandManager.registeredCommands.remove(this)
-    for (name in this.allNames) {
-        InternalCommandManager.nameToCommandMap.entries.removeIf {
-            it.key.contentEquals(this.fullName)
-        }
-    }
-    return true
+    return InternalCommandManager.registeredCommands.remove(this)
 }
 
 /**
@@ -73,8 +69,11 @@ fun Command.unregister(): Boolean = InternalCommandManager.modifyLock.withLock {
  * @param args 接受 [String] 或 [Message]
  * @return 是否成功解析到指令. 返回 `false` 代表无任何指令匹配
  */
+@MiraiExperimentalAPI
 suspend fun CommandSender.executeCommand(vararg args: Any): Boolean {
-    return args.flattenCommandComponents().toList().executeCommand(this)
+    val command = InternalCommandManager.matchCommand(args[0].toString()) ?: return false
+
+    return args.flattenCommandComponents().executeCommand(this)
 }
 
 /**
@@ -82,15 +81,15 @@ suspend fun CommandSender.executeCommand(vararg args: Any): Boolean {
  * @return 是否成功解析到指令. 返回 `false` 代表无任何指令匹配
  */
 suspend fun MessageChain.executeAsCommand(sender: CommandSender): Boolean {
-    return this.flattenCommandComponents().toList().executeCommand(sender)
+    return this.flattenCommandComponents().executeCommand(sender)
 }
 
 /**
  * 检查指令参数并直接执行一个指令.
  */
-suspend inline fun CommandSender.execute(command: Command, args: CommandArgs): Boolean = with(command) {
-    checkArgs(args)
-    return this@execute.onCommand(args)
+suspend inline fun CommandSender.execute(command: CommandDescriptor.SubCommandDescriptor, args: CommandArgs): Boolean {
+    command.checkArgs(args)
+    return command.onCommand(this@execute, args)
 }
 
 /**
@@ -99,19 +98,19 @@ suspend inline fun CommandSender.execute(command: Command, args: CommandArgs): B
 suspend inline fun Command.execute(sender: CommandSender, args: CommandArgs): Boolean = sender.execute(this, args)
 
 /**
- * 解析并执行一个指令.
- * @param args 接受 [String] 或 [Message]
+ * 核心执行指令
  */
-suspend fun CommandSender.execute(vararg args: Any): Boolean = args.toList().executeCommand(this)
-
-
-internal suspend fun List<Any>.executeCommand(sender: CommandSender): Boolean {
-    val command = InternalCommandManager.matchCommand(this) ?: return false
-    return command.run {
-        sender.onCommand(
+internal suspend fun List<Any>.executeCommand(origin: String, sender: CommandSender): Boolean {
+    if (this.isEmpty()) return false
+    val command = InternalCommandManager.matchCommand(origin) ?: return false
+    TODO()
+    /*
+    command.descriptor.subCommands.forEach { sub ->
+    }.run {
+        sender.onDefault(
             CommandArgs.parseFrom(command, sender, this@executeCommand.drop(command.fullName.size))
         )
-    }
+    }*/
 }
 
 internal infix fun Array<String>.matchesBeginning(list: List<Any>): Boolean {
@@ -125,18 +124,16 @@ internal object InternalCommandManager {
     @JvmField
     internal val registeredCommands: MutableList<Command> = mutableListOf()
 
-    @JvmField
-    internal val nameToCommandMap: TreeMap<Array<String>, Command> = TreeMap(Comparator.comparingInt { it.size })
-
     @JvmField
     internal val modifyLock = ReentrantLock()
 
     internal var _commandPrefix: String = "/"
 
-    internal fun matchCommand(splitted: List<Any>): Command? {
-        nameToCommandMap.entries.forEach {
-            if (it.key matchesBeginning splitted) return it.value
+    internal fun matchCommand(name: String): Command? {
+        return registeredCommands.firstOrNull { command ->
+            command.descriptor.base.bakedSubNames.any {
+                name.startsWith(it[0]) && (name.length <= it[0].length || name[it[0].length] == ' ') // 判断跟随空格
+            }
         }
-        return null
     }
 }
\ No newline at end of file
diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandParam.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandParam.kt
index b29876c87..2cee303a6 100644
--- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandParam.kt
+++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandParam.kt
@@ -41,4 +41,7 @@ fun <T : Any> CommandParam<T>.parserFrom(command: Command): CommandArgParser<T>?
 fun <T : Any> CommandParam<T>.parserFrom(descriptor: CommandDescriptor): CommandArgParser<T>? =
     descriptor.parserFor(this)
 
+fun <T : Any> CommandParam<T>.parserFrom(descriptor: CommandDescriptor.SubCommandDescriptor): CommandArgParser<T>? =
+    descriptor.parserFor(this)
+
 fun <T : Any> CommandParam<T>.parserFrom(context: CommandParserContext): CommandArgParser<T>? = context.parserFor(this)
\ No newline at end of file
diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandParserContext.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandParserContext.kt
index 71ecae55e..c02e017a7 100644
--- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandParserContext.kt
+++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandParserContext.kt
@@ -61,10 +61,13 @@ fun <T : Any> CommandParserContext.parserFor(param: CommandParam<T>): CommandArg
     param.overrideParser ?: this[param.type]
 
 fun <T : Any> CommandDescriptor.parserFor(param: CommandParam<T>): CommandArgParser<T>? =
-    param.overrideParser ?: this.context.parserFor(param)
+    this.context.parserFor(param)
+
+fun <T : Any> CommandDescriptor.SubCommandDescriptor.parserFor(param: CommandParam<T>): CommandArgParser<T>? =
+    this.parent.parserFor(param)
 
 fun <T : Any> Command.parserFor(param: CommandParam<T>): CommandArgParser<T>? =
-    param.overrideParser ?: this.descriptor.parserFor(param)
+    this.descriptor.parserFor(param)
 
 /**
  * 合并两个 [CommandParserContext], [replacer] 将会替换 [this] 中重复的 parser.