Resolution with optional defaults

This commit is contained in:
Him188 2020-10-24 12:00:56 +08:00
parent b3880093bf
commit a486ceb602
2 changed files with 28 additions and 18 deletions

View File

@ -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.command.parse.CommandValueArgument
import net.mamoe.mirai.console.extensions.CommandCallResolverProvider import net.mamoe.mirai.console.extensions.CommandCallResolverProvider
import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.console.util.cast
import net.mamoe.mirai.console.util.safeCast import net.mamoe.mirai.console.util.safeCast
import java.util.*
/** /**
* Builtin implementation of [CommandCallResolver] * Builtin implementation of [CommandCallResolver]
@ -34,7 +32,10 @@ public object BuiltInCommandCallResolver : CommandCallResolver {
private data class ResolveData( private data class ResolveData(
val variant: CommandSignatureVariant, val variant: CommandSignatureVariant,
val argumentAcceptances: List<ArgumentAcceptanceWithIndex>, val argumentAcceptances: List<ArgumentAcceptanceWithIndex>,
) val remainingParameters: List<AbstractCommandValueParameter<*>>,
) {
val remainingOptionalCount: Int = remainingParameters.count { it.isOptional }
}
private data class ArgumentAcceptanceWithIndex( private data class ArgumentAcceptanceWithIndex(
val index: Int, val index: Int,
@ -51,15 +52,21 @@ public object BuiltInCommandCallResolver : CommandCallResolver {
.mapNotNull l@{ signature -> .mapNotNull l@{ signature ->
val zipped = signature.valueParameters.zip(valueArguments) 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) -> 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) val accepting = parameter.accepting(argument, context)
if (accepting.isNotAcceptable) { if (accepting.isNotAcceptable) {
return@l null // argument type not assignable return@l null // argument type not assignable
} }
ArgumentAcceptanceWithIndex(index, accepting) ArgumentAcceptanceWithIndex(index, accepting)
}) },
remainingParameters = remaining
)
} }
.also { result -> result.singleOrNull()?.let { return it.variant } } .also { result -> result.singleOrNull()?.let { return it.variant } }
.takeLongestMatches() .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() if (isEmpty()) return emptyList()
return associateByTo(TreeMap(Comparator.reverseOrder())) { it.variant.valueParameters.size }.let { m -> return associateWith {
val firstKey = m.keys.first().cast<Int>() it.variant.valueParameters.size - it.remainingOptionalCount * 1.001 // slightly lower priority with optional defaults.
m.filter { it.key == firstKey }.map { it.value.cast() } }.let { m ->
val maxMatch = m.values.maxByOrNull { it }
m.filter { it.value == maxMatch }.keys
} }
} }
} }

View File

@ -164,8 +164,8 @@ internal class TestCommand {
@Test @Test
fun `composite command descriptors`() { fun `composite command descriptors`() {
val overloads = TestCompositeCommand.overloads val overloads = TestCompositeCommand.overloads
assertEquals("CommandSignatureVariant(seconds: Int = ...)", overloads[0].toString()) assertEquals("CommandSignatureVariant(<mute>, seconds: Int = ...)", overloads[0].toString())
assertEquals("CommandSignatureVariant(target: Long, seconds: Int)", overloads[1].toString()) assertEquals("CommandSignatureVariant(<mute>, target: Long, seconds: Int)", overloads[1].toString())
} }
@Test @Test
@ -225,6 +225,7 @@ internal class TestCommand {
} }
override fun parse(raw: MessageContent, sender: CommandSender): MyClass { override fun parse(raw: MessageContent, sender: CommandSender): MyClass {
if (raw is PlainText) return parse(raw.content, sender)
assertSame(image, raw) assertSame(image, raw)
return MyClass(2) return MyClass(2)
} }
@ -238,7 +239,7 @@ internal class TestCommand {
} }
composite.withRegistration { 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> { assertEquals(2, withTesting<MyClass> {
assertSuccess( assertSuccess(
execute(sender, buildMessageChain { execute(sender, buildMessageChain {