Improved the argument list validation for COND

This commit is contained in:
Mike Cifelli 2016-12-25 12:49:18 -05:00
parent eb9f224c60
commit 37bc303fe8
5 changed files with 51 additions and 27 deletions

View File

@ -13,6 +13,7 @@ public class ArgumentValidator {
private String functionName;
private Integer maximumNumberOfArguments;
private Integer minimumNumberOfArguments;
private boolean isNilAcceptable;
public ArgumentValidator(String functionName) {
this.firstArgumentType = SExpression.class;
@ -20,6 +21,7 @@ public class ArgumentValidator {
this.functionName = functionName;
this.minimumNumberOfArguments = null;
this.maximumNumberOfArguments = null;
this.isNilAcceptable = true;
}
public void setFirstArgumentExpectedType(Class<? extends SExpression> argumentType) {
@ -48,6 +50,10 @@ public class ArgumentValidator {
this.maximumNumberOfArguments = exactNumberOfArguments;
}
public void doNotAcceptNil() {
this.isNilAcceptable = false;
}
public void validate(Cons argumentList) {
validateListNotDotted(argumentList);
validateListLength(argumentList);
@ -80,29 +86,35 @@ public class ArgumentValidator {
}
private void validateArgumentTypes(Cons argumentList) {
if (!isExpectedFirstArgumentType(argumentList.getCar()))
throw new BadArgumentTypeException(functionName, argumentList.getCar(), firstArgumentType);
validateFirstArgument(argumentList);
validateTrailingArguments(argumentList);
}
validateRemainingArguments(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);
return firstArgumentType.isInstance(firstArgument) && !isDisallowedNil(firstArgument);
}
private void validateRemainingArguments(Cons cons) {
for (cons = (Cons) cons.getCdr(); !cons.nullp(); cons = (Cons) cons.getCdr())
if (!isExpectedRemainingArgumentType(cons.getCar()))
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 isExpectedRemainingArgumentType(SExpression remainingArgument) {
return trailingArgumentType.isInstance(remainingArgument);
}
public void validateListIsNotNil(Cons list) {
if (list.nullp())
throw new BadArgumentTypeException(functionName, Nil.getUniqueInstance(), Cons.class);
private boolean isExpectedTrailingArgumentType(SExpression trailingArgument) {
return trailingArgumentType.isInstance(trailingArgument) && !isDisallowedNil(trailingArgument);
}
public static class TooFewArgumentsException extends LispException {

View File

@ -10,6 +10,7 @@ public class COND extends LispFunction {
public COND() {
this.argumentValidator = new ArgumentValidator("COND");
this.argumentValidator.setEveryArgumentExpectedType(Cons.class);
this.argumentValidator.doNotAcceptNil();
}
public SExpression call(Cons argumentList) {
@ -22,7 +23,7 @@ public class COND extends LispFunction {
if (argumentList.nullp())
return Nil.getUniqueInstance();
Cons clause = getFirstClauseAndValidateNotNil(argumentList);
Cons clause = (Cons) argumentList.getCar();
Cons remainingClauses = (Cons) argumentList.getCdr();
SExpression test = EVAL.eval(clause.getCar());
@ -32,13 +33,6 @@ public class COND extends LispFunction {
return callTailRecursive(remainingClauses);
}
private Cons getFirstClauseAndValidateNotNil(Cons argumentList) {
Cons firstClause = (Cons) argumentList.getCar();
argumentValidator.validateListIsNotNil(firstClause);
return firstClause;
}
private boolean isTestSuccessful(SExpression test) {
return test != Nil.getUniqueInstance();
}

View File

@ -200,13 +200,21 @@ public class ArgumentValidatorTester {
}
@Test(expected = BadArgumentTypeException.class)
public void validateListNotNil_ThrowsExceptionWithNilValue() {
validator.validateListIsNotNil(Nil.getUniqueInstance());
public void doNotAcceptNil_ThrowsExceptionOnNilArgument() {
validator.doNotAcceptNil();
validator.validate(new Cons(Symbol.T, new Cons(Nil.getUniqueInstance(), Nil.getUniqueInstance())));
}
@Test
public void validateListNotNil_DoesNotThrowExceptionWithNonNilValue() {
validator.validateListIsNotNil(new Cons(Nil.getUniqueInstance(), Nil.getUniqueInstance()));
public void doNotAcceptNil_AllowsEmptyArgumentList() {
validator.doNotAcceptNil();
validator.validate(Nil.getUniqueInstance());
}
@Test
public void doNotAcceptNil_AllowsProperList() {
validator.doNotAcceptNil();
validator.validate(new Cons(Symbol.T, new Cons(Symbol.T, Nil.getUniqueInstance())));
}
}

View File

@ -58,4 +58,9 @@ public class APPLYTester {
evaluateString("(apply '1)");
}
@Test(expected = DottedArgumentListException.class)
public void testCondWithDottedArgumentList_ThrowsException() {
evaluateString("(apply 'apply (cons 'T 'T))");
}
}

View File

@ -4,7 +4,7 @@ import static testutil.TestUtilities.*;
import org.junit.Test;
import function.ArgumentValidator.BadArgumentTypeException;
import function.ArgumentValidator.*;
public class CONDTester {
@ -74,4 +74,9 @@ public class CONDTester {
evaluateString("(cond o)");
}
@Test(expected = DottedArgumentListException.class)
public void testCondWithDottedArgumentList_ThrowsException() {
evaluateString("(apply 'cond (cons '(nil T) 'b))");
}
}