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

View File

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

View File

@ -200,13 +200,21 @@ public class ArgumentValidatorTester {
} }
@Test(expected = BadArgumentTypeException.class) @Test(expected = BadArgumentTypeException.class)
public void validateListNotNil_ThrowsExceptionWithNilValue() { public void doNotAcceptNil_ThrowsExceptionOnNilArgument() {
validator.validateListIsNotNil(Nil.getUniqueInstance()); validator.doNotAcceptNil();
validator.validate(new Cons(Symbol.T, new Cons(Nil.getUniqueInstance(), Nil.getUniqueInstance())));
} }
@Test @Test
public void validateListNotNil_DoesNotThrowExceptionWithNonNilValue() { public void doNotAcceptNil_AllowsEmptyArgumentList() {
validator.validateListIsNotNil(new Cons(Nil.getUniqueInstance(), Nil.getUniqueInstance())); 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)"); 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 org.junit.Test;
import function.ArgumentValidator.BadArgumentTypeException; import function.ArgumentValidator.*;
public class CONDTester { public class CONDTester {
@ -74,4 +74,9 @@ public class CONDTester {
evaluateString("(cond o)"); evaluateString("(cond o)");
} }
@Test(expected = DottedArgumentListException.class)
public void testCondWithDottedArgumentList_ThrowsException() {
evaluateString("(apply 'cond (cons '(nil T) 'b))");
}
} }