Skip to content

Commit

Permalink
Improve RegularArgumentsBinder code
Browse files Browse the repository at this point in the history
  • Loading branch information
iamgio committed Jul 30, 2024
1 parent 12dc7e9 commit 97b3857
Showing 1 changed file with 75 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<*>>,
): 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<FunctionParameter<*>>) =
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
Expand Down

0 comments on commit 97b3857

Please sign in to comment.