Refactored the COND built-in function

This commit is contained in:
Mike Cifelli 2016-12-24 13:16:03 -05:00
parent a0583f808c
commit eb9f224c60
4 changed files with 157 additions and 48 deletions

View File

@ -50,26 +50,25 @@ public class ArgumentValidator {
public void validate(Cons argumentList) { public void validate(Cons argumentList) {
validateListNotDotted(argumentList); validateListNotDotted(argumentList);
validateListLength(argumentList);
if (containsTooFewArguments(argumentList))
throw new TooFewArgumentsException(functionName, argumentList);
else if (containsTooManyArguments(argumentList))
throw new TooManyArgumentsException(functionName, argumentList);
validateArgumentTypes(argumentList); validateArgumentTypes(argumentList);
} }
private void validateListNotDotted(Cons argumentList) { private void validateListNotDotted(Cons argumentList) {
Cons currentCons = argumentList; SExpression next = argumentList.getCdr();
SExpression nextCons = argumentList.getCdr();
while (!nextCons.nullp()) { for (Cons current = argumentList; next.consp(); next = current.getCdr())
if (!nextCons.consp()) current = (Cons) next;
throw new DottedArgumentListException(functionName, argumentList);
currentCons = (Cons) nextCons; if (!next.nullp())
nextCons = currentCons.getCdr(); 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) { private boolean containsTooFewArguments(Cons argumentList) {
@ -101,6 +100,11 @@ public class ArgumentValidator {
return trailingArgumentType.isInstance(remainingArgument); return trailingArgumentType.isInstance(remainingArgument);
} }
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 {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

View File

@ -1,53 +1,63 @@
package function.builtin; package function.builtin;
import function.LispFunction; import function.*;
import sexpression.*; import sexpression.*;
public class COND extends LispFunction { public class COND extends LispFunction {
public SExpression call(Cons argList) { private ArgumentValidator argumentValidator;
if (argList.nullp()) {
// return NIL if there are were no arguments passed to COND public COND() {
this.argumentValidator = new ArgumentValidator("COND");
this.argumentValidator.setEveryArgumentExpectedType(Cons.class);
}
public SExpression call(Cons argumentList) {
argumentValidator.validate(argumentList);
return callTailRecursive(argumentList);
}
private SExpression callTailRecursive(Cons argumentList) {
if (argumentList.nullp())
return Nil.getUniqueInstance(); return Nil.getUniqueInstance();
}
SExpression argCar = argList.getCar(); // first clause Cons clause = getFirstClauseAndValidateNotNil(argumentList);
Cons argCdr = (Cons) argList.getCdr(); // list of remaining clauses Cons remainingClauses = (Cons) argumentList.getCdr();
SExpression test = EVAL.eval(clause.getCar());
// make sure the first clause is a list and is not NIL if (isTestSuccessful(test))
if (argCar.consp()) { return evaluateResult(clause, test);
Cons clause = (Cons) argCar;
SExpression test = EVAL.eval(clause.getCar());
if (test != Nil.getUniqueInstance()) { return callTailRecursive(remainingClauses);
// the car of this clause is true, so we evaluate its cdr }
SExpression cdr = clause.getCdr(); private Cons getFirstClauseAndValidateNotNil(Cons argumentList) {
SExpression retval = test; Cons firstClause = (Cons) argumentList.getCar();
argumentValidator.validateListIsNotNil(firstClause);
// evaluate all the S-expressions in the cdr of the clause return firstClause;
while (cdr.consp()) { }
retval = EVAL.eval(((Cons) cdr).getCar());
cdr = ((Cons) cdr).getCdr();
}
// return the value of the last S-expression evaluated private boolean isTestSuccessful(SExpression test) {
return retval; return test != Nil.getUniqueInstance();
} }
// the car of this clause is false, so we test any remaining private SExpression evaluateResult(Cons clause, SExpression test) {
// clauses SExpression lastResultValue = test;
// check if the list of remaining clauses is a list and is not NIL for (SExpression result = clause.getCdr(); result.consp(); result = advanceCons(result))
if (argCdr.consp()) { lastResultValue = EVAL.eval(getCar(result));
return call(argCdr);
}
// there are no remaining clauses, so we return NIL return lastResultValue;
return Nil.getUniqueInstance(); }
}
throw new RuntimeException("COND: clause " + argCar + " should be a list"); private SExpression advanceCons(SExpression knownCons) {
return ((Cons) knownCons).getCdr();
}
private SExpression getCar(SExpression knownCons) {
return ((Cons) knownCons).getCar();
} }
public boolean evaluateArguments() { public boolean evaluateArguments() {

View File

@ -165,7 +165,8 @@ public class ArgumentValidatorTester {
try { try {
validator.validate(argumentList); validator.validate(argumentList);
} catch (BadArgumentTypeException e) { } catch (BadArgumentTypeException e) {
e.getMessage(); assertNotNull(e.getMessage());
assertTrue(e.getMessage().length() > 0);
} }
} }
@ -183,6 +184,13 @@ public class ArgumentValidatorTester {
validator.validate(argumentList); validator.validate(argumentList);
} }
@Test
public void DottedArgumentListException_HasCorrectSeverity() {
DottedArgumentListException e = new DottedArgumentListException("TEST", Nil.getUniqueInstance());
assertTrue(e.getSeverity() < ErrorManager.CRITICAL_LEVEL);
}
@Test @Test
public void dottedArgumentListException_HasMessageText() { public void dottedArgumentListException_HasMessageText() {
DottedArgumentListException e = new DottedArgumentListException("TEST", Nil.getUniqueInstance()); DottedArgumentListException e = new DottedArgumentListException("TEST", Nil.getUniqueInstance());
@ -191,4 +199,14 @@ public class ArgumentValidatorTester {
assertTrue(e.getMessage().length() > 0); assertTrue(e.getMessage().length() > 0);
} }
@Test(expected = BadArgumentTypeException.class)
public void validateListNotNil_ThrowsExceptionWithNilValue() {
validator.validateListIsNotNil(Nil.getUniqueInstance());
}
@Test
public void validateListNotNil_DoesNotThrowExceptionWithNonNilValue() {
validator.validateListIsNotNil(new Cons(Nil.getUniqueInstance(), Nil.getUniqueInstance()));
}
} }

View File

@ -0,0 +1,77 @@
package function.builtin;
import static testutil.TestUtilities.*;
import org.junit.Test;
import function.ArgumentValidator.BadArgumentTypeException;
public class CONDTester {
@Test
public void testCondWithNoArguments() {
String input = "(cond)";
assertSExpressionsMatch(evaluateString(input), parseString("nil"));
}
@Test
public void testCondWithTrue() {
String input = "(cond (T))";
assertSExpressionsMatch(evaluateString(input), parseString("T"));
}
@Test
public void testCondWithSingleExpression() {
String input = "(cond (T \"true\"))";
assertSExpressionsMatch(evaluateString(input), parseString("\"true\""));
}
@Test
public void testCondWithMultipleExpressions() {
String input = "(cond ((= 1 2) 2) ((= 1 2) 2) ((= 1 1) 3))";
assertSExpressionsMatch(evaluateString(input), parseString("3"));
}
@Test
public void testCondWithMultipleConditionsMatching_ReturnFirstOne() {
String input = "(cond ((= 1 1) 2) ((= 1 1) 3))";
assertSExpressionsMatch(evaluateString(input), parseString("2"));
}
@Test
public void testCondWithMultipleConditionsMatching_OnlyEvaluatesFirstOne() {
String input = "(cond ((= 1 1) 2) ((= 1 1) x))";
assertSExpressionsMatch(evaluateString(input), parseString("2"));
}
@Test
public void testCondWithMultipleResultValues_OnlyReturnsLast() {
String input = "(cond ((= 1 1) 2 3 4))";
assertSExpressionsMatch(evaluateString(input), parseString("4"));
}
@Test
public void testCondWithNoConditionMatching_ReturnsNil() {
String input = "(cond ((= 1 2) T) ((= 1 3) T))";
assertSExpressionsMatch(evaluateString(input), parseString("nil"));
}
@Test(expected = BadArgumentTypeException.class)
public void testCondWithNilArgument_ThrowsException() {
evaluateString("(cond ())");
}
@Test(expected = BadArgumentTypeException.class)
public void testCondWithNonListArgument_ThrowsException() {
evaluateString("(cond o)");
}
}