package function; import error.LispException; import sexpression.Cons; import sexpression.DisplayName; import sexpression.SExpression; import java.math.BigInteger; import static function.builtin.cons.LENGTH.getLength; import static java.text.MessageFormat.format; public class ArgumentValidator { private Class firstArgumentType; private Class trailingArgumentType; private Class excludedFirstArgumentType; private Class excludedTrailingArgumentType; private String functionName; private BigInteger maximumNumberOfArguments; private BigInteger minimumNumberOfArguments; public ArgumentValidator(String functionName) { this.firstArgumentType = SExpression.class; this.trailingArgumentType = SExpression.class; this.excludedFirstArgumentType = null; this.excludedTrailingArgumentType = null; this.functionName = functionName; this.minimumNumberOfArguments = null; this.maximumNumberOfArguments = null; } public void setFirstArgumentExpectedType(Class argumentType) { this.firstArgumentType = argumentType; } public void setTrailingArgumentExpectedType(Class argumentType) { this.trailingArgumentType = argumentType; } public void setEveryArgumentExpectedType(Class argumentType) { this.firstArgumentType = argumentType; this.trailingArgumentType = argumentType; } public void setFirstArgumentExcludedType(Class argumentType) { this.excludedFirstArgumentType = argumentType; } public void setTrailingArgumentExcludedType(Class argumentType) { this.excludedTrailingArgumentType = argumentType; } public void setEveryArgumentExcludedType(Class argumentType) { this.excludedFirstArgumentType = argumentType; this.excludedTrailingArgumentType = argumentType; } public void setMaximumNumberOfArguments(int maximumNumberOfArguments) { this.maximumNumberOfArguments = BigInteger.valueOf(maximumNumberOfArguments); } public void setMinimumNumberOfArguments(int minimumNumberOfArguments) { this.minimumNumberOfArguments = BigInteger.valueOf(minimumNumberOfArguments); } public void setExactNumberOfArguments(int exactNumberOfArguments) { this.minimumNumberOfArguments = BigInteger.valueOf(exactNumberOfArguments); this.maximumNumberOfArguments = BigInteger.valueOf(exactNumberOfArguments); } public void validate(Cons argumentList) { validateListNotDotted(argumentList); validateListLength(argumentList); validateArgumentTypes(argumentList); } private void validateListNotDotted(Cons argumentList) { SExpression next = argumentList.getRest(); for (Cons current = argumentList; next.isCons(); next = current.getRest()) current = (Cons) next; if (!next.isNull()) throw new DottedArgumentListException(functionName, argumentList); } private void validateListLength(Cons argumentList) { if (containsTooFewArguments(argumentList)) throw new TooFewArgumentsException(functionName, argumentList); else if (containsTooManyArguments(argumentList)) throw new TooManyArgumentsException(functionName, argumentList); } private boolean containsTooFewArguments(Cons argumentList) { return isMinimum() && isLengthLessThanMinimum(argumentList); } private boolean isMinimum() { return minimumNumberOfArguments != null; } private boolean isLengthLessThanMinimum(Cons argumentList) { return getLength(argumentList).compareTo(minimumNumberOfArguments) < 0; } private boolean containsTooManyArguments(Cons argumentList) { return isMaximum() && isLengthGreaterThanMaximum(argumentList); } private boolean isMaximum() { return maximumNumberOfArguments != null; } private boolean isLengthGreaterThanMaximum(Cons argumentList) { return getLength(argumentList).compareTo(maximumNumberOfArguments) > 0; } private void validateArgumentTypes(Cons argumentList) { validateFirstArgument(argumentList); validateTrailingArguments(argumentList); } private void validateFirstArgument(Cons argumentList) { if (!isFirstArgumentValid(argumentList)) throw new BadArgumentTypeException(functionName, argumentList.getFirst(), firstArgumentType); } private boolean isFirstArgumentValid(Cons argumentList) { return argumentList.isNull() || isExpectedFirstArgumentType(argumentList.getFirst()); } private boolean isExpectedFirstArgumentType(SExpression firstArgument) { return firstArgumentType.isInstance(firstArgument) && !isExcludedFirstArgumentType(firstArgument); } private boolean isExcludedFirstArgumentType(SExpression firstArgument) { return excludedFirstArgumentType != null && excludedFirstArgumentType.isInstance(firstArgument); } private void validateTrailingArguments(Cons argumentList) { for (Cons cons = (Cons) argumentList.getRest(); !cons.isNull(); cons = (Cons) cons.getRest()) if (!isExpectedTrailingArgumentType(cons.getFirst())) throw new BadArgumentTypeException(functionName, cons.getFirst(), trailingArgumentType); } private boolean isExpectedTrailingArgumentType(SExpression trailingArgument) { return trailingArgumentType.isInstance(trailingArgument) && !isExcludedTrailingArgumentType(trailingArgument); } private boolean isExcludedTrailingArgumentType(SExpression trailingArgument) { return excludedTrailingArgumentType != null && excludedTrailingArgumentType.isInstance(trailingArgument); } public static class TooFewArgumentsException extends LispException { private static final long serialVersionUID = 1L; private String functionName; private Cons argumentList; public TooFewArgumentsException(String functionName, Cons argumentList) { this.functionName = functionName; this.argumentList = argumentList; } @Override public String getMessage() { return format("too few arguments given to {0}: {1}", functionName, argumentList); } } public static class TooManyArgumentsException extends LispException { private static final long serialVersionUID = 1L; private String functionName; private Cons argumentList; public TooManyArgumentsException(String functionName, Cons argumentList) { this.functionName = functionName; this.argumentList = argumentList; } @Override public String getMessage() { return format("too many arguments given to {0}: {1}", functionName, argumentList); } } public static class DottedArgumentListException extends LispException { private static final long serialVersionUID = 1L; private String functionName; private Cons argumentList; public DottedArgumentListException(String functionName, Cons argumentList) { this.functionName = functionName; this.argumentList = argumentList; } @Override public String getMessage() { return format("dotted argument list given to {0}: {1}", functionName, argumentList); } } public static class BadArgumentTypeException extends LispException { private static final long serialVersionUID = 1L; private String functionName; private String argument; private Class expectedType; public BadArgumentTypeException(String functionName, SExpression argument, Class expectedType) { this.functionName = functionName; this.argument = argument.toString(); this.expectedType = expectedType; } @Override public String getMessage() { return format("{0}: {1} is not the expected type of ''{2}''", functionName, argument, getExpectedTypeName()); } private String getExpectedTypeName() { DisplayName displayName = expectedType.getAnnotation(DisplayName.class); return (displayName == null) ? "unknown" : displayName.value(); } } }