All Downloads are FREE. Search and download functionalities are using the official Maven repository.

command.descriptor.CommandParameter.kt Maven / Gradle / Ivy

There is a newer version: 2.16.0
Show newest version
/*
 * 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 through the following link.
 *
 * https://github.com/mamoe/mirai/blob/master/LICENSE
 */

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.parse.CommandValueArgument
import net.mamoe.mirai.console.command.resolve.ResolvedCommandValueArgument
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 net.mamoe.mirai.message.data.content
import kotlin.reflect.KClass
import kotlin.reflect.KType
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.full.isSubtypeOf
import kotlin.reflect.typeOf


/**
 * Inherited instances must be [CommandValueParameter] or [CommandReceiverParameter]
 */
@ExperimentalCommandDescriptors
public interface CommandParameter {
    public val name: String?

    public val isOptional: Boolean

    /**
     * Reified type of [T]
     */
    public val type: KType
}

@ExperimentalCommandDescriptors
public abstract class AbstractCommandParameter : CommandParameter {
    override fun toString(): String = buildString {
        append(name)
        append(": ")
        append(type.classifierAsKClass().simpleName)
        append(if (type.isMarkedNullable) "?" else "")
    }
}

/**
 * Inherited instances must be [AbstractCommandValueParameter].
 *
 * ### Implementation details
 *
 * [CommandValueParameter] should:
 * - implement [equals], [hashCode] since used in [ResolvedCommandValueArgument].
 * - implement [toString] to produce user-friendly textual representation of this parameter with type info.
 *
 */
@ExperimentalCommandDescriptors
public interface CommandValueParameter : CommandParameter {

    public val isVararg: Boolean

    /**
     * Checks whether this [CommandValueParameter] accepts [argument].
     *
     * An [argument] can be accepted if:
     * - [CommandValueArgument.type] is subtype of, or equals to [CommandValueParameter.type] (nullability considered), or
     * - [CommandValueArgument.typeVariants] produces
     *
     * @return `true` if [argument] may be accepted through any approach mentioned above.
     *
     * @see accepting
     */
    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 data class WithTypeConversion(
        public val typeVariant: TypeVariant<*>,
    ) : ArgumentAcceptance(20)

    public data class WithContextualConversion(
        public val parser: CommandValueArgumentParser<*>,
    ) : ArgumentAcceptance(10)

    public data class ResolutionAmbiguity(
        public val candidates: List>,
    ) : 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 data class CommandReceiverParameter(
    override val isOptional: Boolean,
    override val type: KType,
) : CommandParameter, AbstractCommandParameter() {
    override val name: String get() = NAME

    init {
        val classifier = type.classifier
        require(classifier is KClass<*>) {
            "CommandReceiverParameter.type.classifier must be KClass."
        }
        require(classifier.isSubclassOf(CommandSender::class)) {
            "CommandReceiverParameter.type.classifier must be subclass of CommandSender."
        }
    }

    public companion object {
        public const val NAME: String = ""
    }
}


internal val ANY_TYPE = typeOf0()
internal val ARRAY_OUT_ANY_TYPE = typeOf0>()

@ExperimentalCommandDescriptors
public sealed class AbstractCommandValueParameter : CommandValueParameter, AbstractCommandParameter() {
    override fun toString(): String = buildString {
        if (isVararg) append("vararg ")
        append(super.toString())
        if (isOptional) {
            append(" = ...")
        }
    }

    public override fun accepting(argument: CommandValueArgument, commandArgumentContext: CommandArgumentContext?): ArgumentAcceptance {
        if (isVararg) {
            val arrayElementType = this.type.arguments.single() // Array
            return acceptingImpl(arrayElementType.type ?: ANY_TYPE, argument, commandArgumentContext)
        }

        return acceptingImpl(this.type, argument, commandArgumentContext)
    }

    protected open fun acceptingImpl(
        expectingType: KType,
        argument: CommandValueArgument,
        commandArgumentContext: CommandArgumentContext?,
    ): ArgumentAcceptance {
        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
    }

    @ConsoleExperimentalApi
    public data class StringConstant(
        @ConsoleExperimentalApi
        public override val name: String?,
        public val expectingValue: String,
        public val ignoreCase: Boolean,
    ) : AbstractCommandValueParameter() {
        public override val type: KType get() = STRING_TYPE
        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>"

        override fun acceptingImpl(expectingType: KType, argument: CommandValueArgument, commandArgumentContext: CommandArgumentContext?): ArgumentAcceptance {
            return if (argument.value.content.equals(expectingValue, ignoreCase)) {
                ArgumentAcceptance.Direct
            } else ArgumentAcceptance.Impossible
        }

        private companion object {
            @OptIn(ExperimentalStdlibApi::class)
            val STRING_TYPE = typeOf()
        }
    }

    /**
     * @see createOptional
     * @see createRequired
     */
    public data class UserDefinedType(
        public override val name: String?,
        public override val isOptional: Boolean,
        public override val isVararg: Boolean,
        public override val type: KType,
    ) : AbstractCommandValueParameter() {
        override fun toString(): String = super.toString()

        init {
            requireNotNull(type.classifierAsKClassOrNull()) {
                "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 {
            @JvmStatic
            public inline fun  createOptional(name: String, isVararg: Boolean): UserDefinedType {
                @OptIn(ExperimentalStdlibApi::class)
                return UserDefinedType(name, true, isVararg, typeOf())
            }

            @JvmStatic
            public inline fun  createRequired(name: String, isVararg: Boolean): UserDefinedType {
                @OptIn(ExperimentalStdlibApi::class)
                return UserDefinedType(name, false, isVararg, typeOf())
            }
        }
    }

    /**
     * Extended by [CommandValueArgumentParser]
     */
    @ConsoleExperimentalApi
    public abstract class Extended : AbstractCommandValueParameter() // For implementer: take care of toString()
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy