transcendental-lisp/src/main/kotlin/function/ArgumentValidator.java
2018-03-23 18:12:47 -04:00

235 lines
8.5 KiB
Java

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<? extends SExpression> firstArgumentType;
private Class<? extends SExpression> trailingArgumentType;
private Class<? extends SExpression> excludedFirstArgumentType;
private Class<? extends SExpression> 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<? extends SExpression> argumentType) {
this.firstArgumentType = argumentType;
}
public void setTrailingArgumentExpectedType(Class<? extends SExpression> argumentType) {
this.trailingArgumentType = argumentType;
}
public void setEveryArgumentExpectedType(Class<? extends SExpression> argumentType) {
this.firstArgumentType = argumentType;
this.trailingArgumentType = argumentType;
}
public void setFirstArgumentExcludedType(Class<? extends SExpression> argumentType) {
this.excludedFirstArgumentType = argumentType;
}
public void setTrailingArgumentExcludedType(Class<? extends SExpression> argumentType) {
this.excludedTrailingArgumentType = argumentType;
}
public void setEveryArgumentExcludedType(Class<? extends SExpression> 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<? extends SExpression> expectedType;
public BadArgumentTypeException(String functionName, SExpression argument,
Class<? extends SExpression> 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();
}
}
}