From 97b3857c004f11fd642bcf09c97774262fc94cea Mon Sep 17 00:00:00 2001 From: Giorgio Garofalo Date: Tue, 30 Jul 2024 13:31:36 +0200 Subject: [PATCH] Improve RegularArgumentsBinder code --- .../call/binding/RegularArgumentsBinder.kt | 127 +++++++++++------- 1 file changed, 75 insertions(+), 52 deletions(-) diff --git a/core/src/main/kotlin/eu/iamgio/quarkdown/function/call/binding/RegularArgumentsBinder.kt b/core/src/main/kotlin/eu/iamgio/quarkdown/function/call/binding/RegularArgumentsBinder.kt index 91052307..a32fa49f 100644 --- a/core/src/main/kotlin/eu/iamgio/quarkdown/function/call/binding/RegularArgumentsBinder.kt +++ b/core/src/main/kotlin/eu/iamgio/quarkdown/function/call/binding/RegularArgumentsBinder.kt @@ -2,6 +2,7 @@ package eu.iamgio.quarkdown.function.call.binding import eu.iamgio.quarkdown.function.FunctionParameter import eu.iamgio.quarkdown.function.call.FunctionCall +import eu.iamgio.quarkdown.function.call.FunctionCallArgument import eu.iamgio.quarkdown.function.error.InvalidArgumentCountException import eu.iamgio.quarkdown.function.error.MismatchingArgumentTypeException import eu.iamgio.quarkdown.function.error.UnnamedArgumentAfterNamedException @@ -18,65 +19,87 @@ import kotlin.reflect.full.isSubclassOf * @see InjectedArgumentsBinder for the injected argument subset */ class RegularArgumentsBinder(private val call: FunctionCall<*>) : ArgumentsBinder { + private var encounteredNamedArgument = false + + // todo add documentation + + private fun findParameter( + argument: FunctionCallArgument, + argumentIndex: Int, + parameters: List>, + ): FunctionParameter<*> = + when { + // A body parameter is always the last one in the function signature. + argument.isBody -> parameters.lastOrNull() + // A non-body parameter that refers to a parameter by its name. + argument.isNamed -> { + encounteredNamedArgument = true + parameters.find { it.name == argument.name } + ?: throw UnresolvedParameterException(argument, call) + } + // Non-body, unnamed parameters follow the index and cannot appear after a named argument has been encountered. + !encounteredNamedArgument -> parameters.getOrNull(argumentIndex) + // Unnamed arguments cannot appear after a named one. + else -> throw UnnamedArgumentAfterNamedException(call) + } ?: throw InvalidArgumentCountException(call) // Error if args count > params count. + + private fun getStaticallyTypedArgument( + parameter: FunctionParameter<*>, + argument: FunctionCallArgument, + ): FunctionCallArgument { + // The value held by the argument. + // If the argument is dynamic, it is converted to a static type. + val value = argument.value + + return when { + // If the expected type is dynamic, the argument is wrapped into a dynamic value. + // For instance, custom functions defined from a Quarkdown function have dynamic-type parameters. + parameter.type == DynamicValue::class -> { + argument.copy(expression = DynamicValue(value.unwrappedValue)) + } + + // The value is dynamic and must be converted to a static type. + value is DynamicValue -> { + // The dynamic value is converted into the expected parameter type. + // Throws error if the conversion could not happen. + val staticValue = + DynamicValueConverter(value).convertTo(parameter.type, call.context) + ?: throw MismatchingArgumentTypeException(call, parameter, argument) + + argument.copy(expression = staticValue) + } + + // If the expected type is a string but the argument isn't, + // it is automatically converted to a string. + value !is StringValue && parameter.type == String::class -> { + argument.copy(expression = ValueFactory.string(value.unwrappedValue.toString())) + } + + else -> argument + } + } + + private fun checkTypeMatch( + parameter: FunctionParameter<*>, + argument: FunctionCallArgument, + ) { + if (argument.value.unwrappedValue!!::class.isSubclassOf(parameter.type)) return + if (argument.value::class.isSubclassOf(parameter.type)) return + + throw MismatchingArgumentTypeException(call, parameter, argument) + } + override fun createBindings(parameters: List>) = buildMap { - var encounteredNamedArgument = false - call.arguments.forEachIndexed { index, argument -> // Corresponding parameter. - val parameter = - when { - // A body parameter is always the last one in the function signature. - argument.isBody -> parameters.lastOrNull() - // A non-body parameter that refers to a parameter by its name. - argument.isNamed -> { - encounteredNamedArgument = true - parameters.find { it.name == argument.name } - ?: throw UnresolvedParameterException(argument, call) - } - // Non-body, unnamed parameters follow the index and cannot appear after a named argument has been encountered. - !encounteredNamedArgument -> parameters.getOrNull(index) - // Unnamed arguments cannot appear after a named one. - else -> throw UnnamedArgumentAfterNamedException(call) - } ?: throw InvalidArgumentCountException(call) // Error if args count > params count. - - val value = argument.value + val parameter = findParameter(argument, index, parameters) + // The type of dynamic arguments is determined. - val staticArgument = - when { - // If the expected type is dynamic, the argument is wrapped into a dynamic value. - // For instance, custom functions defined from a Quarkdown function have dynamic-type parameters. - parameter.type == DynamicValue::class -> { - argument.copy(expression = DynamicValue(value.unwrappedValue)) - } - - // The value is dynamic and must be converted to a static type. - value is DynamicValue -> { - // The dynamic value is converted into the expected parameter type. - // Throws error if the conversion could not happen. - val staticValue = - DynamicValueConverter(value).convertTo(parameter.type, call.context) - ?: throw MismatchingArgumentTypeException(call, parameter, argument) - - argument.copy(expression = staticValue) - } - - // If the expected type is a string but the argument isn't, - // it is automatically converted to a string. - value !is StringValue && parameter.type == String::class -> { - argument.copy(expression = ValueFactory.string(value.unwrappedValue.toString())) - } - - else -> argument - } + val staticArgument = getStaticallyTypedArgument(parameter, argument) // Type match check. - if ( - !staticArgument.value.unwrappedValue!!::class.isSubclassOf(parameter.type) && - !staticArgument.value::class.isSubclassOf(parameter.type) - ) { - throw MismatchingArgumentTypeException(call, parameter, staticArgument) - } + checkTypeMatch(parameter, staticArgument) // Add link. this[parameter] = staticArgument