package function; import java.text.MessageFormat; import error.LispException; import function.builtin.LENGTH; import sexpression.*; public class ArgumentValidator { private Class firstArgumentType; private Class trailingArgumentType; private String functionName; private Integer maximumNumberOfArguments; private Integer minimumNumberOfArguments; private boolean isNilAcceptable; public ArgumentValidator(String functionName) { this.firstArgumentType = SExpression.class; this.trailingArgumentType = SExpression.class; this.functionName = functionName; this.minimumNumberOfArguments = null; this.maximumNumberOfArguments = null; this.isNilAcceptable = true; } 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 setMaximumNumberOfArguments(int maximumNumberOfArguments) { this.maximumNumberOfArguments = maximumNumberOfArguments; } public void setMinimumNumberOfArguments(int minimumNumberOfArguments) { this.minimumNumberOfArguments = minimumNumberOfArguments; } public void setExactNumberOfArguments(int exactNumberOfArguments) { this.minimumNumberOfArguments = exactNumberOfArguments; this.maximumNumberOfArguments = exactNumberOfArguments; } public void doNotAcceptNil() { this.isNilAcceptable = false; } public void validate(Cons argumentList) { validateListNotDotted(argumentList); validateListLength(argumentList); validateArgumentTypes(argumentList); } private void validateListNotDotted(Cons argumentList) { SExpression next = argumentList.getCdr(); for (Cons current = argumentList; next.consp(); next = current.getCdr()) current = (Cons) next; if (!next.nullp()) 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 (minimumNumberOfArguments != null) && (LENGTH.getLength(argumentList) < minimumNumberOfArguments); } private boolean containsTooManyArguments(Cons argumentList) { return (maximumNumberOfArguments != null) && (LENGTH.getLength(argumentList) > maximumNumberOfArguments); } private void validateArgumentTypes(Cons argumentList) { validateFirstArgument(argumentList); validateTrailingArguments(argumentList); } private void validateFirstArgument(Cons argumentList) { if (!isFirstArgumentValid(argumentList)) throw new BadArgumentTypeException(functionName, argumentList.getCar(), firstArgumentType); } private boolean isFirstArgumentValid(Cons argumentList) { return argumentList.nullp() || isExpectedFirstArgumentType(argumentList.getCar()); } private boolean isExpectedFirstArgumentType(SExpression firstArgument) { return firstArgumentType.isInstance(firstArgument) && !isDisallowedNil(firstArgument); } private boolean isDisallowedNil(SExpression argument) { return !isNilAcceptable && argument.nullp(); } private void validateTrailingArguments(Cons argumentList) { for (Cons cons = (Cons) argumentList.getCdr(); !cons.nullp(); cons = (Cons) cons.getCdr()) if (!isExpectedTrailingArgumentType(cons.getCar())) throw new BadArgumentTypeException(functionName, cons.getCar(), trailingArgumentType); } private boolean isExpectedTrailingArgumentType(SExpression trailingArgument) { return trailingArgumentType.isInstance(trailingArgument) && !isDisallowedNil(trailingArgument); } public static class TooFewArgumentsException extends LispException { private static final long serialVersionUID = 1L; private String functionName; private Cons originalSExpression; public TooFewArgumentsException(String functionName, Cons argumentList) { this.functionName = functionName; this.originalSExpression = new Cons(new Symbol(this.functionName), argumentList); } @Override public String getMessage() { return MessageFormat.format("too few arguments given to {0}: {1}", functionName, originalSExpression); } } public static class TooManyArgumentsException extends LispException { private static final long serialVersionUID = 1L; private String functionName; private Cons originalSExpression; public TooManyArgumentsException(String functionName, Cons argumentList) { this.functionName = functionName; this.originalSExpression = new Cons(new Symbol(this.functionName), argumentList); } @Override public String getMessage() { return MessageFormat.format("too many arguments given to {0}: {1}", functionName, originalSExpression); } } public static class DottedArgumentListException extends LispException { private static final long serialVersionUID = 1L; private String functionName; private Cons originalSExpression; public DottedArgumentListException(String functionName, Cons argumentList) { this.functionName = functionName; this.originalSExpression = new Cons(new Symbol(this.functionName), argumentList); } @Override public String getMessage() { return MessageFormat.format("dotted argument list given to {0}: {1}", functionName, originalSExpression); } } public static class BadArgumentTypeException extends LispException { private static final long serialVersionUID = 1L; private String functionName; private String argument; private Class expected; public BadArgumentTypeException(String functionName, SExpression argument, Class expected) { this.functionName = functionName; this.argument = argument.toString(); this.expected = expected; } @Override public String getMessage() { DisplayName displayName = expected.getAnnotation(DisplayName.class); String expectedType = (displayName == null) ? "unknown" : displayName.value(); return MessageFormat.format("{0}: {1} is not the expected type of ''{2}''", functionName, argument, expectedType); } } }