Command resolving

This commit is contained in:
Him188 2020-10-18 12:26:54 +08:00
parent ccc9128023
commit df461290c0
24 changed files with 527 additions and 377 deletions

View File

@ -11,15 +11,16 @@
package net.mamoe.mirai.console.command 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.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.command.java.JCommand
import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.console.permission.Permission
import net.mamoe.mirai.console.permission.PermissionId 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 CompositeCommand 复合指令
* @see SimpleCommand 简单的, 支持参数自动解析的指令 * @see SimpleCommand 简单的, 支持参数自动解析的指令
* *
* @see CommandArgumentContextAware
*
* @see JCommand Java 用户添加协程帮助的 [Command] * @see JCommand Java 用户添加协程帮助的 [Command]
*/ */
public interface Command { public interface Command {
@ -48,6 +51,13 @@ public interface Command {
@ResolveContext(COMMAND_NAME) @ResolveContext(COMMAND_NAME)
public val secondaryNames: Array<out String> public val secondaryNames: Array<out String>
/**
*
*/
@ConsoleExperimentalApi("Property name is experimental")
@ExperimentalCommandDescriptors
public val overloads: List<CommandSignatureVariant>
/** /**
* 用法说明, 用于发送给用户. [usage] 一般包含 [description]. * 用法说明, 用于发送给用户. [usage] 一般包含 [description].
*/ */
@ -80,16 +90,6 @@ public interface Command {
*/ */
public val owner: CommandOwner public val owner: CommandOwner
/**
* 在指令被执行时调用.
*
* @param args 精确的指令参数. [MessageChain] 每个元素代表一个精确的参数.
*
* @see CommandManager.executeCommand 查看更多信息
*/
@JvmBlockingBridge
public suspend fun CommandSender.onCommand(args: MessageChain)
public companion object { 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)

View File

@ -12,6 +12,8 @@
package net.mamoe.mirai.console.command package net.mamoe.mirai.console.command
import net.mamoe.mirai.console.command.CommandExecuteResult.CommandExecuteStatus 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.Message
import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageChain
import kotlin.contracts.contract import kotlin.contracts.contract
@ -21,6 +23,8 @@ import kotlin.contracts.contract
* *
* @see CommandExecuteStatus * @see CommandExecuteStatus
*/ */
@ConsoleExperimentalApi("Not yet implemented")
@ExperimentalCommandDescriptors
public sealed class CommandExecuteResult { public sealed class CommandExecuteResult {
/** 指令最终执行状态 */ /** 指令最终执行状态 */
public abstract val status: CommandExecuteStatus public abstract val status: CommandExecuteStatus

View File

@ -8,16 +8,26 @@
*/ */
@file:Suppress( @file:Suppress(
"NOTHING_TO_INLINE", "unused", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE", "NOTHING_TO_INLINE", "unused",
"MemberVisibilityCanBePrivate", "INAPPLICABLE_JVM_NAME" "MemberVisibilityCanBePrivate", "INAPPLICABLE_JVM_NAME"
) )
@file:JvmName("CommandManagerKt") @file:JvmName("CommandManagerKt")
package net.mamoe.mirai.console.command 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
import net.mamoe.mirai.console.internal.command.CommandManagerImpl.executeCommand 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.* import net.mamoe.mirai.message.data.*
/** /**
@ -101,19 +111,45 @@ public interface CommandManager {
* 注意: 字符串与消息元素之间不需要空格, 会被强制分割. "bar[mirai:image:]" 会被分割为 "bar" [Image] 类型的消息元素. * 注意: 字符串与消息元素之间不需要空格, 会被强制分割. "bar[mirai:image:]" 会被分割为 "bar" [Image] 类型的消息元素.
* 3. 参数解析. 各类型指令实现不同. 详见 [RawCommand], [CompositeCommand], [SimpleCommand] * 3. 参数解析. 各类型指令实现不同. 详见 [RawCommand], [CompositeCommand], [SimpleCommand]
* *
* ### 未来的扩展 * ### 扩展
* 在将来, 参数语法分析过程可能会被扩展, 允许插件自定义处理方式, 因此可能不会简单地使用空格分隔. * 参数语法分析过程可能会被扩展, 插件可以自定义处理方式, 因此可能不会简单地使用空格分隔.
* *
* @param message 一条完整的指令. "/managers add 123456.123456" * @param message 一条完整的指令. "/managers add 123456.123456"
* @param checkPermission `true` 时检查权限 * @param checkPermission `true` 时检查权限
* *
* @see CommandCallParser
* @see CommandCallResolver
*
* @return 执行结果 * @return 执行结果
*/ */
@JvmBlockingBridge // @JvmBlockingBridge
public suspend fun CommandSender.executeCommand( @OptIn(ExperimentalCommandDescriptors::class)
public suspend fun executeCommand(
caller: CommandSender,
message: Message, message: Message,
checkPermission: Boolean = true, 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 执行结果 * @return 执行结果
* @see executeCommand * @see executeCommand
*/ */
@JvmBlockingBridge // @JvmBlockingBridge
public suspend fun CommandSender.executeCommand( public suspend fun CommandSender.executeCommand(
message: String, message: String,
checkPermission: Boolean = true, 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
}
/** /**
* 执行一个确切的指令 * [指令名称][commandName] 匹配对应的 [Command].
* @see executeCommand 获取更多信息 *
* #### 实现细节
* - [commandName] 带有 [commandPrefix] 时可以匹配到所有指令
* - [commandName] 不带有 [commandPrefix] 时只能匹配到 [Command.prefixOptional] 的指令
*
* @param commandName 可能带有或不带有 [commandPrefix].
*/ */
@JvmBlockingBridge public fun matchCommand(commandName: String): Command?
@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 companion object INSTANCE : CommandManager by CommandManagerImpl { public companion object INSTANCE : CommandManager by CommandManagerImpl {
// TODO: 2020/8/20 https://youtrack.jetbrains.com/issue/KT-41191 // TODO: 2020/8/20 https://youtrack.jetbrains.com/issue/KT-41191
@ -167,52 +201,37 @@ public interface CommandManager {
override val allRegisteredCommands: List<Command> override val allRegisteredCommands: List<Command>
get() = CommandManagerImpl.allRegisteredCommands 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 获取更多信息 * @see executeCommand 获取更多信息
*/ */
public suspend fun CommandSender.execute( // @JvmBlockingBridge
command: Command, // @JvmName("executeCommand")
arguments: String, 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, checkPermission: Boolean = true,
): CommandExecuteResult { ): CommandExecuteResult {
return command.execute(this, arguments, checkPermission) // 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)
} }

View File

@ -20,7 +20,6 @@ import kotlinx.coroutines.launch
import net.mamoe.kjbb.JvmBlockingBridge import net.mamoe.kjbb.JvmBlockingBridge
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.MiraiConsole 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.asCommandSender
import net.mamoe.mirai.console.command.CommandSender.Companion.asMemberCommandSender 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.asTempCommandSender

View File

@ -124,12 +124,25 @@ public abstract class CompositeCommand(
@Target(AnnotationTarget.VALUE_PARAMETER) @Target(AnnotationTarget.VALUE_PARAMETER)
protected annotation class Name(val value: String) protected annotation class Name(val value: String)
public final override suspend fun CommandSender.onCommand(args: MessageChain) { @OptIn(ExperimentalCommandDescriptors::class)
matchSubCommand(args)?.parseAndExecute(this, args, true) ?: kotlin.run { override val overloads: List<CommandSignatureVariant> by lazy {
defaultSubCommand.onCommand(this, args) 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) { protected override suspend fun CommandSender.onDefault(rawArgs: MessageChain) {
sendMessage(usage) sendMessage(usage)

View File

@ -11,14 +11,18 @@
package net.mamoe.mirai.console.command 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.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.command.java.JRawCommand
import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME 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.command.createOrFindCommandPermission
import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.console.permission.Permission
import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageChainBuilder
/** /**
* 无参数解析, 接收原生参数的指令. * 无参数解析, 接收原生参数的指令.
@ -52,6 +56,15 @@ public abstract class RawCommand(
) : Command { ) : Command {
public override val permission: Permission by lazy { createOrFindCommandPermission(parentPermission) } 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 查看更多信息 * @see CommandManager.execute 查看更多信息
*/ */
public abstract override suspend fun CommandSender.onCommand(args: MessageChain) public abstract suspend fun CommandSender.onCommand(args: MessageChain)
} }

View File

@ -61,6 +61,14 @@ public abstract class SimpleCommand(
) : Command, AbstractReflectionCommand(owner, primaryName, secondaryNames = secondaryNames, description, parentPermission, prefixOptional), ) : Command, AbstractReflectionCommand(owner, primaryName, secondaryNames = secondaryNames, description, parentPermission, prefixOptional),
CommandArgumentContextAware { 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]. 也可以被覆盖. * 自动根据带有 [Handler] 注解的函数签名生成 [usage]. 也可以被覆盖.
*/ */
@ -76,10 +84,6 @@ public abstract class SimpleCommand(
*/ */
public override val context: CommandArgumentContext = CommandArgumentContext.Builtins + overrideContext 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>) { internal override fun checkSubCommand(subCommands: Array<SubCommandDescriptor>) {
super.checkSubCommand(subCommands) 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." } check(subCommands.size == 1) { "There can only be exactly one function annotated with Handler at this moment as overloading is not yet supported." }

View File

@ -9,19 +9,39 @@
package net.mamoe.mirai.console.command.descriptor 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 net.mamoe.mirai.console.util.safeCast
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.KType import kotlin.reflect.KType
import kotlin.reflect.full.isSubtypeOf
import kotlin.reflect.typeOf import kotlin.reflect.typeOf
@ExperimentalCommandDescriptors /**
public interface CommandDescriptor { * @see CommandSignatureVariantImpl
public val overloads: List<CommandSignatureVariant> */
}
@ExperimentalCommandDescriptors @ExperimentalCommandDescriptors
public interface CommandSignatureVariant { public interface CommandSignatureVariant {
public val valueParameters: List<CommandValueParameter<*>> 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] * Reified type of [T]
*/ */
public val type: KType 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 @ExperimentalCommandDescriptors
public sealed class CommandValueParameter<T> : ICommandParameter<T> { public sealed class CommandValueParameter<T> : ICommandParameter<T> {
init { internal fun validate() { // // TODO: 2020/10/18 net.mamoe.mirai.console.command.descriptor.CommandValueParameter.validate$mirai_console_mirai_console_main
@Suppress("LeakingThis")
require(type.classifier?.safeCast<KClass<*>>()?.isInstance(defaultValue) == true) { require(type.classifier?.safeCast<KClass<*>>()?.isInstance(defaultValue) == true) {
"defaultValue is not instance of type" "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 class StringConstant(
public override val name: String, public override val name: String,
public val expectingValue: String,
) : CommandValueParameter<String>() { ) : CommandValueParameter<String>() {
public 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 defaultValue: Nothing? get() = null
public override val isOptional: Boolean get() = false public override val isOptional: Boolean get() = false
public override val isVararg: Boolean get() = false
private companion object { private companion object {
@OptIn(ExperimentalStdlibApi::class) @OptIn(ExperimentalStdlibApi::class)
@ -67,23 +149,28 @@ public sealed class CommandValueParameter<T> : ICommandParameter<T> {
} }
} }
/**
* @see createOptional
* @see createRequired
*/
public class UserDefinedType<T>( public class UserDefinedType<T>(
public override val name: String, public override val name: String,
public override val defaultValue: T?, public override val defaultValue: T?,
public override val isOptional: Boolean, public override val isOptional: Boolean,
public override val isVararg: Boolean,
public override val type: KType, public override val type: KType,
) : CommandValueParameter<T>() { ) : CommandValueParameter<T>() {
public companion object { public companion object {
@JvmStatic @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) @OptIn(ExperimentalStdlibApi::class)
return UserDefinedType(name, defaultValue, true, typeOf<T>()) return UserDefinedType(name, defaultValue, true, isVararg, typeOf<T>())
} }
@JvmStatic @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) @OptIn(ExperimentalStdlibApi::class)
return UserDefinedType(name, null, false, typeOf<T>()) return UserDefinedType(name, null, false, isVararg, typeOf<T>())
} }
} }
} }

View File

@ -9,13 +9,8 @@
package net.mamoe.mirai.console.command.java 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.Command
import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.message.data.MessageChain
/** /**
* Java 用户添加协程帮助的 [Command]. * Java 用户添加协程帮助的 [Command].
@ -24,17 +19,7 @@ import net.mamoe.mirai.message.data.MessageChain
* *
* @see Command * @see Command
*/ */
@ConsoleExperimentalApi("Not yet supported")
public interface JCommand : Command { public interface JCommand : Command {
public override suspend fun CommandSender.onCommand(args: MessageChain) { // TODO: 2020/10/18 JCommand
withContext(Dispatchers.IO) { onCommand(this@onCommand, args) }
}
/**
* 在指令被执行时调用.
*
* @param args 精确的指令参数. [MessageChain] 每个元素代表一个精确的参数.
*
* @see CommandManager.executeCommand 查看更多信息
*/
public fun onCommand(sender: CommandSender, args: MessageChain) // overrides blocking bridge
} }

View File

@ -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
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
import net.mamoe.mirai.console.permission.Permission 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 * @see buildCommandArgumentContext
*/ */
@ConsoleExperimentalApi("Not yet supported")
public abstract class JCompositeCommand public abstract class JCompositeCommand
@JvmOverloads constructor( @JvmOverloads constructor(
owner: CommandOwner, owner: CommandOwner,

View File

@ -9,16 +9,15 @@
package net.mamoe.mirai.console.command.java package net.mamoe.mirai.console.command.java
import kotlinx.coroutines.Dispatchers import net.mamoe.mirai.console.command.BuiltInCommands
import kotlinx.coroutines.withContext import net.mamoe.mirai.console.command.Command
import net.mamoe.mirai.console.command.* import net.mamoe.mirai.console.command.CommandManager
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.execute import net.mamoe.mirai.console.command.CommandOwner
import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME 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.command.createOrFindCommandPermission
import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.console.permission.Permission
import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.message.data.SingleMessage
/** /**
* Java 用户继承 * Java 用户继承
@ -46,6 +45,7 @@ import net.mamoe.mirai.message.data.SingleMessage
* *
* @see JRawCommand * @see JRawCommand
*/ */
@ConsoleExperimentalApi("Not yet supported")
public abstract class JRawCommand public abstract class JRawCommand
@JvmOverloads constructor( @JvmOverloads constructor(
/** /**
@ -72,19 +72,4 @@ public abstract class JRawCommand
/** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */ /** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */
public final override var prefixOptional: Boolean = false public final override var prefixOptional: Boolean = false
protected set 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) }
}
} }

View File

@ -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
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.console.permission.Permission
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
/** /**
* Java 实现: * Java 实现:
@ -42,6 +43,7 @@ import net.mamoe.mirai.console.permission.Permission
* @see SimpleCommand * @see SimpleCommand
* @see [CommandManager.executeCommand] * @see [CommandManager.executeCommand]
*/ */
@ConsoleExperimentalApi("Not yet supported")
public abstract class JSimpleCommand( public abstract class JSimpleCommand(
owner: CommandOwner, owner: CommandOwner,
@ResolveContext(COMMAND_NAME) primaryName: String, @ResolveContext(COMMAND_NAME) primaryName: String,

View File

@ -14,6 +14,8 @@ import net.mamoe.mirai.message.data.MessageChain
* *
* @see CommandCallResolver The call resolver for [CommandCall] to become [ResolvedCommandCall] * @see CommandCallResolver The call resolver for [CommandCall] to become [ResolvedCommandCall]
* @see CommandCallParserProvider The extension point * @see CommandCallParserProvider The extension point
*
* @see SpaceSeparatedCommandCallParser
*/ */
@ConsoleExperimentalApi @ConsoleExperimentalApi
@ExperimentalCommandDescriptors @ExperimentalCommandDescriptors
@ -24,7 +26,7 @@ public interface CommandCallParser {
* *
* @return `null` if unable to parse (i.e. due to syntax errors). * @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 { public companion object {
/** /**

View File

@ -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.NoValueArgumentMappingException
import net.mamoe.mirai.console.command.descriptor.TypeVariant import net.mamoe.mirai.console.command.descriptor.TypeVariant
import net.mamoe.mirai.message.data.MessageContent import net.mamoe.mirai.message.data.MessageContent
import kotlin.reflect.KType
import kotlin.reflect.full.isSubtypeOf import kotlin.reflect.full.isSubtypeOf
import kotlin.reflect.typeOf import kotlin.reflect.typeOf
@ -34,6 +35,7 @@ public interface CommandArgument
*/ */
@ExperimentalCommandDescriptors @ExperimentalCommandDescriptors
public interface CommandValueArgument : CommandArgument { public interface CommandValueArgument : CommandArgument {
public val type: KType
public val value: RawCommandArgument public val value: RawCommandArgument
public val typeVariants: List<TypeVariant<*>> public val typeVariants: List<TypeVariant<*>>
} }
@ -45,6 +47,8 @@ public interface CommandValueArgument : CommandArgument {
public data class InvariantCommandValueArgument( public data class InvariantCommandValueArgument(
public override val value: RawCommandArgument, public override val value: RawCommandArgument,
) : CommandValueArgument { ) : CommandValueArgument {
@OptIn(ExperimentalStdlibApi::class)
override val type: KType = typeOf<MessageContent>()
override val typeVariants: List<TypeVariant<*>> = listOf(MessageContentTypeVariant) 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 = public inline fun <reified T> CommandValueArgument.mapToType(): T =
mapToTypeOrNull() ?: throw NoValueArgumentMappingException(this, typeOf<T>()) mapToTypeOrNull() ?: throw NoValueArgumentMappingException(this, typeOf<T>())
@ExperimentalCommandDescriptors
public inline fun <reified T> CommandValueArgument.mapToTypeOrNull(): T? {
@OptIn(ExperimentalStdlibApi::class) @OptIn(ExperimentalStdlibApi::class)
val expectingType = typeOf<T>() @ExperimentalCommandDescriptors
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 result = typeVariants val result = typeVariants
.filter { it.outType.isSubtypeOf(expectingType) } .filter { it.outType.isSubtypeOf(expectingType) }
.also { .also {
@ -71,5 +79,12 @@ public inline fun <reified T> CommandValueArgument.mapToTypeOrNull(): T? {
acc acc
else typeVariant else typeVariant
} }
@Suppress("UNCHECKED_CAST")
return result.mapValue(value) as T return result.mapValue(value) as T
} }
@ExperimentalCommandDescriptors
public inline fun <reified T> CommandValueArgument.mapToTypeOrNull(): T? {
@OptIn(ExperimentalStdlibApi::class)
return mapToTypeOrNull(typeOf<T>())
}

View File

@ -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)
)
}
}

View File

@ -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() }
}
}
}

View File

@ -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.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.command.parse.CommandCall 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 [] * The resolver converting a [CommandCall] into [ResolvedCommandCall] based on registered []
@ -20,17 +18,4 @@ import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
@ExperimentalCommandDescriptors @ExperimentalCommandDescriptors
public interface CommandCallResolver { public interface CommandCallResolver {
public fun resolve(call: CommandCall): ResolvedCommandCall? 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
}
}
} }

View File

@ -7,19 +7,23 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * 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.Command
import net.mamoe.mirai.console.command.CommandSender import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.CompositeCommand 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.CommandSignatureVariant
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.command.parse.CommandCall import net.mamoe.mirai.console.command.parse.CommandCall
import net.mamoe.mirai.console.command.parse.CommandValueArgument 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]. * The resolved [CommandCall].
*
* @see ResolvedCommandCallImpl
*/ */
@ExperimentalCommandDescriptors @ExperimentalCommandDescriptors
public interface ResolvedCommandCall { public interface ResolvedCommandCall {
@ -31,17 +35,41 @@ public interface ResolvedCommandCall {
public val callee: Command public val callee: Command
/** /**
* The callee [CommandDescriptor], specifically a sub command from [CompositeCommand] * The callee [CommandSignatureVariant], specifically a sub command from [CompositeCommand]
*/
public val calleeDescriptor: CommandDescriptor
/**
* The callee [CommandSignatureVariant]
*/ */
public val calleeSignature: CommandSignatureVariant public val calleeSignature: CommandSignatureVariant
/**
* Original arguments
*/
public val rawValueArguments: List<CommandValueArgument>
/** /**
* Resolved value arguments arranged mapping the [CommandSignatureVariant.valueParameters] by index. * 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
}
}
} }

View File

@ -16,17 +16,15 @@ import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.command.* import net.mamoe.mirai.console.command.*
import net.mamoe.mirai.console.command.Command.Companion.allNames import net.mamoe.mirai.console.command.Command.Companion.allNames
import net.mamoe.mirai.console.command.CommandSender.Companion.toCommandSender 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.Listener
import net.mamoe.mirai.event.subscribeAlways import net.mamoe.mirai.event.subscribeAlways
import net.mamoe.mirai.message.MessageEvent 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.message.data.content
import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.MiraiLogger
import java.util.concurrent.locks.ReentrantLock 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 { private val logger: MiraiLogger by lazy {
MiraiConsole.createLogger("command") MiraiConsole.createLogger("command")
} }
@ -48,11 +46,11 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine
/** /**
* 从原始的 command 中解析出 Command 对象 * 从原始的 command 中解析出 Command 对象
*/ */
internal fun matchCommand(rawCommand: String): Command? { override fun matchCommand(commandName: String): Command? {
if (rawCommand.startsWith(commandPrefix)) { if (commandName.startsWith(commandPrefix)) {
return requiredPrefixCommandMap[rawCommand.substringAfter(commandPrefix).toLowerCase()] return requiredPrefixCommandMap[commandName.substringAfter(commandPrefix).toLowerCase()]
} }
return optionalPrefixCommandMap[rawCommand.toLowerCase()] return optionalPrefixCommandMap[commandName.toLowerCase()]
} }
internal val commandListener: Listener<MessageEvent> by lazy { internal val commandListener: Listener<MessageEvent> by lazy {
@ -65,7 +63,7 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine
) { ) {
val sender = this.toCommandSender() val sender = this.toCommandSender()
when (val result = sender.executeCommand(message)) { when (val result = executeCommand(sender, message)) {
is CommandExecuteResult.PermissionDenied -> { is CommandExecuteResult.PermissionDenied -> {
if (!result.command.prefixOptional || message.content.startsWith(CommandManager.commandPrefix)) { if (!result.command.prefixOptional || message.content.startsWith(CommandManager.commandPrefix)) {
sender.sendMessage("权限不足") sender.sendMessage("权限不足")
@ -152,44 +150,4 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine
} }
override fun Command.isRegistered(): Boolean = this in _registeredCommands 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)
}
} }

View File

@ -13,8 +13,8 @@ package net.mamoe.mirai.console.internal.command
import net.mamoe.mirai.console.command.CompositeCommand import net.mamoe.mirai.console.command.CompositeCommand
import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser
import kotlin.reflect.KClass
import kotlin.reflect.KParameter import kotlin.reflect.KParameter
import kotlin.reflect.KType
/* /*
internal fun Parameter.toCommandParam(): CommandParameter<*> { internal fun Parameter.toCommandParam(): CommandParameter<*> {
@ -39,10 +39,10 @@ internal data class CommandParameter<T : Any>(
/** /**
* 参数类型. 将从 [CompositeCommand.context] 中寻找 [CommandValueArgumentParser] 解析. * 参数类型. 将从 [CompositeCommand.context] 中寻找 [CommandValueArgumentParser] 解析.
*/ */
val type: KClass<T>, // exact type val type: KType, // exact type
val parameter: KParameter, // source parameter 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 name, type, parameter
) { ) {
this._overrideParser = parser this._overrideParser = parser

View File

@ -12,12 +12,12 @@
package net.mamoe.mirai.console.internal.command package net.mamoe.mirai.console.internal.command
import net.mamoe.mirai.console.command.* import net.mamoe.mirai.console.command.*
import net.mamoe.mirai.console.command.descriptor.CommandArgumentContext import net.mamoe.mirai.console.command.descriptor.*
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.Permission
import net.mamoe.mirai.console.permission.PermissionService.Companion.testPermission import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.* 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.KAnnotatedElement
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.KFunction 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 { interface SubCommandAnnotationResolver {
fun hasAnnotation(baseCommand: AbstractReflectionCommand, function: KFunction<*>): Boolean fun hasAnnotation(baseCommand: AbstractReflectionCommand, function: KFunction<*>): Boolean
fun getSubCommandNames(baseCommand: AbstractReflectionCommand, function: KFunction<*>): Array<out String> fun getSubCommandNames(baseCommand: AbstractReflectionCommand, function: KFunction<*>): Array<out String>
@ -132,27 +147,12 @@ internal abstract class AbstractReflectionCommand
val params: Array<CommandParameter<*>>, val params: Array<CommandParameter<*>>,
val description: String, val description: String,
val permission: Permission, 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 context: CommandArgumentContext,
val argumentBuilder: (sender: CommandSender) -> MutableMap<KParameter, Any?>, val argumentBuilder: (sender: CommandSender) -> MutableMap<KParameter, Any?>,
) { ) {
val usage: String = createUsage(this@AbstractReflectionCommand) 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 { private fun KParameter.isOptional(): Boolean {
return isOptional || this.type.isMarkedNullable return isOptional || this.type.isMarkedNullable
} }
@ -163,49 +163,6 @@ internal abstract class AbstractReflectionCommand
@JvmField @JvmField
internal val bakedSubNames: Array<Array<String>> = names.map { it.bakeSubName() }.toTypedArray() 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)") // 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" val paramName = param.findAnnotation<CompositeCommand.Name>()?.value ?: param.name ?: "unknown"
CommandParameter( CommandParameter<Any>(
paramName, paramName,
(param.type.classifier as? KClass<*>) param.type,
?: throw IllegalArgumentException("unsolved type reference from param " + param.name + ". (at ${this::class.qualifiedNameOrTip}.${function.name}.$param)"),
param param
) )
}.toTypedArray() }.toTypedArray()
// TODO: 2020/09/19 检查 optional/nullable 是否都在最后 // TODO: 2020/09/19 检查 optional/nullable 是否都在最后
@Suppress("UNCHECKED_CAST")
return SubCommandDescriptor( return SubCommandDescriptor(
commandName, commandName,
params, params as Array<CommandParameter<*>>,
subDescription, // overridePermission?.value subDescription, // overridePermission?.value
permission,//overridePermission?.value?.let { PermissionService.INSTANCE[PermissionId.parseFromString(it)] } ?: permission, permission,//overridePermission?.value?.let { PermissionService.INSTANCE[PermissionId.parseFromString(it)] } ?: permission,
onCommand = { sender: CommandSender, args: Map<KParameter, Any?> -> onCommand = { _: CommandSender, args ->
val result = function.callSuspendBy(args) val result = function.callSuspendBy(parameters.zip(args).toMap())
checkNotNull(result) { "sub command return value is null (at ${this::class.qualifiedName}.${function.name})" } checkNotNull(result) { "sub command return value is null (at ${this::class.qualifiedName}.${function.name})" }

View File

@ -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)
}

View File

@ -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()
}

View File

@ -16,7 +16,6 @@ import kotlinx.coroutines.runBlocking
import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.Testing import net.mamoe.mirai.console.Testing
import net.mamoe.mirai.console.Testing.withTesting 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.executeCommand
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register 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.registeredCommands