package function import error.LispException import function.builtin.cons.LENGTH.getLength import sexpression.Cons import sexpression.DisplayName import sexpression.SExpression import java.math.BigInteger class ArgumentValidator(private val functionName: String) { private var firstArgumentType: Class = SExpression::class.java private var trailingArgumentType: Class = SExpression::class.java private var excludedFirstArgumentType: Class? = null private var excludedTrailingArgumentType: Class? = null private var maximumNumberOfArguments: BigInteger? = null private var minimumNumberOfArguments: BigInteger? = null fun setFirstArgumentExpectedType(argumentType: Class) { this.firstArgumentType = argumentType } fun setTrailingArgumentExpectedType(argumentType: Class) { this.trailingArgumentType = argumentType } fun setEveryArgumentExpectedType(argumentType: Class) { this.firstArgumentType = argumentType this.trailingArgumentType = argumentType } fun setFirstArgumentExcludedType(argumentType: Class) { this.excludedFirstArgumentType = argumentType } fun setTrailingArgumentExcludedType(argumentType: Class) { this.excludedTrailingArgumentType = argumentType } fun setEveryArgumentExcludedType(argumentType: Class) { this.excludedFirstArgumentType = argumentType this.excludedTrailingArgumentType = argumentType } fun setMaximumNumberOfArguments(maximumNumberOfArguments: Int) { this.maximumNumberOfArguments = BigInteger.valueOf(maximumNumberOfArguments.toLong()) } fun setMinimumNumberOfArguments(minimumNumberOfArguments: Int) { this.minimumNumberOfArguments = BigInteger.valueOf(minimumNumberOfArguments.toLong()) } fun setExactNumberOfArguments(exactNumberOfArguments: Int) { this.minimumNumberOfArguments = BigInteger.valueOf(exactNumberOfArguments.toLong()) this.maximumNumberOfArguments = BigInteger.valueOf(exactNumberOfArguments.toLong()) } fun validate(argumentList: Cons) { validateListNotDotted(argumentList) validateListLength(argumentList) validateArgumentTypes(argumentList) } private fun validateListNotDotted(argumentList: Cons) { if (isArgumentListDotted(argumentList)) throw DottedArgumentListException(functionName, argumentList) } private fun isArgumentListDotted(argumentList: Cons) = argumentList.isCons && !argumentList.last().rest.isNull private fun validateListLength(argumentList: Cons) { if (containsTooFewArguments(argumentList)) throw TooFewArgumentsException(functionName, argumentList) else if (containsTooManyArguments(argumentList)) throw TooManyArgumentsException(functionName, argumentList) } private fun containsTooFewArguments(argumentList: Cons) = isMinimum() && isLengthLessThanMinimum(argumentList) private fun isMinimum() = minimumNumberOfArguments != null private fun isLengthLessThanMinimum(argumentList: Cons) = getLength(argumentList) < minimumNumberOfArguments!! private fun containsTooManyArguments(argumentList: Cons) = isMaximum() && isLengthGreaterThanMaximum(argumentList) private fun isMaximum() = maximumNumberOfArguments != null private fun isLengthGreaterThanMaximum(argumentList: Cons) = getLength(argumentList) > maximumNumberOfArguments!! private fun validateArgumentTypes(argumentList: Cons) { validateFirstArgument(argumentList) validateTrailingArguments(argumentList) } private fun validateFirstArgument(argumentList: Cons) { if (!isFirstArgumentValid(argumentList)) throw BadArgumentTypeException(functionName, argumentList.first, firstArgumentType) } private fun isFirstArgumentValid(argumentList: Cons) = argumentList.isNull || isExpectedFirstArgumentType(argumentList.first) private fun isExpectedFirstArgumentType(firstArgument: SExpression) = firstArgumentType.isInstance(firstArgument) && !isExcludedFirstArgumentType(firstArgument) private fun isExcludedFirstArgumentType(firstArgument: SExpression) = excludedFirstArgumentType != null && excludedFirstArgumentType!!.isInstance(firstArgument) private fun validateTrailingArguments(argumentList: Cons) { val cons = argumentList.rest as Cons cons.forEach { if (!isExpectedTrailingArgumentType(it.first)) throw BadArgumentTypeException(functionName, it.first, trailingArgumentType) } } private fun isExpectedTrailingArgumentType(trailingArgument: SExpression) = trailingArgumentType.isInstance(trailingArgument) && !isExcludedTrailingArgumentType(trailingArgument) private fun isExcludedTrailingArgumentType(trailingArgument: SExpression) = excludedTrailingArgumentType != null && excludedTrailingArgumentType!!.isInstance(trailingArgument) class TooFewArgumentsException(functionName: String, argumentList: Cons) : LispException() { override val message = "too few arguments given to $functionName: $argumentList" } class TooManyArgumentsException(functionName: String, argumentList: Cons) : LispException() { override val message = "too many arguments given to $functionName: $argumentList" } class DottedArgumentListException(functionName: String, argumentList: Cons) : LispException() { override val message = "dotted argument list given to $functionName: $argumentList" } class BadArgumentTypeException(functionName: String, argument: SExpression, expectedType: Class) : LispException() { private val expectedTypeName = expectedType.getAnnotation(DisplayName::class.java)?.value ?: "unknown" override val message = "$functionName: $argument is not the expected type of '$expectedTypeName'" } }