Added unit tests and refactored the length built in function

This commit is contained in:
Mike Cifelli 2017-01-15 14:23:46 -05:00
parent 4b75b13485
commit 38db0862ff
7 changed files with 76 additions and 42 deletions

View File

@ -2,62 +2,49 @@ package function.builtin.cons;
import java.math.BigInteger;
import function.LispFunction;
import function.*;
import sexpression.*;
/**
* <code>LENGTH</code> represents the LENGTH function in Lisp.
*/
public class LENGTH extends LispFunction {
/**
* Returns the length of the given list.
*
* @param list
* the list to determine the length of
* @return the length of <code>list</code>
*/
public static int getLength(Cons list) {
LENGTH lengthFunction = new LENGTH();
LispNumber length = lengthFunction.call(LIST.makeList(list));
LispNumber length = lengthFunction.callWithoutArgumentValidation(LIST.makeList(list));
return length.getValue().intValue(); // TODO - return BigInteger when all built-ins use
// ArgumentValidator
}
public LispNumber call(Cons argList) {
// make sure we have received at least one argument
if (argList.nullp()) {
Cons originalSExpr = new Cons(new Symbol("LENGTH"), argList);
private ArgumentValidator argumentValidator;
throw new RuntimeException("too few arguments given to LENGTH: " + originalSExpr);
}
public LENGTH() {
this.argumentValidator = new ArgumentValidator("LENGTH");
this.argumentValidator.setExactNumberOfArguments(1);
this.argumentValidator.setEveryArgumentExpectedType(Cons.class);
}
SExpression argCar = argList.getCar();
SExpression argCdr = argList.getCdr();
public LispNumber call(Cons argumentList) {
argumentValidator.validate(argumentList);
// make sure we have received only one argument
if (!argCdr.nullp()) {
Cons originalSExpr = new Cons(new Symbol("LENGTH"), argList);
return callTailRecursive(BigInteger.ZERO, argumentList);
}
throw new RuntimeException("too many arguments given to LENGTH: " + originalSExpr);
}
private LispNumber callWithoutArgumentValidation(Cons argumentList) {
return callTailRecursive(BigInteger.ZERO, argumentList);
}
// make sure that the argument is a list
if (argCar.listp()) {
Cons arg = (Cons) argCar;
private LispNumber callTailRecursive(BigInteger accumulatedLength, Cons argumentList) {
Cons list = (Cons) argumentList.getCar();
Cons restOfList = LIST.makeList(list.getCdr());
if (arg.nullp()) {
return new LispNumber(BigInteger.ZERO);
}
if (list.nullp())
return new LispNumber(accumulatedLength);
Cons cdr = LIST.makeList(arg.getCdr());
LispNumber cdrLength = call(cdr);
return callTailRecursive(increment(accumulatedLength), restOfList);
}
return new LispNumber(BigInteger.ONE.add(cdrLength.getValue()));
}
throw new RuntimeException("LENGTH: a proper list must not end with " + argCar);
private BigInteger increment(BigInteger number) {
return number.add(BigInteger.ONE);
}
}

View File

@ -17,7 +17,7 @@ public class DIVIDE extends LispFunction {
this.mathFunction = new MathFunction(this::getReciprocal, this::divide);
}
public SExpression call(Cons argumentList) {
public LispNumber call(Cons argumentList) {
argumentValidator.validate(argumentList);
return mathFunction.callTailRecursive(argumentList);

View File

@ -17,7 +17,7 @@ public class MINUS extends LispFunction {
this.mathFunction = new MathFunction(this::additiveInverse, this::subtract);
}
public SExpression call(Cons argumentList) {
public LispNumber call(Cons argumentList) {
argumentValidator.validate(argumentList);
return mathFunction.callTailRecursive(argumentList);

View File

@ -14,7 +14,7 @@ public class MULTIPLY extends LispFunction {
this.mathFunction = new MathFunction(number -> number, this::multiply);
}
public SExpression call(Cons argumentList) {
public LispNumber call(Cons argumentList) {
argumentValidator.validate(argumentList);
return mathFunction.callTailRecursive(new Cons(LispNumber.ONE, argumentList));

View File

@ -15,7 +15,7 @@ class MathFunction {
this.multipleValueOperation = multipleValueOperation;
}
public SExpression callTailRecursive(Cons argumentList) {
public LispNumber callTailRecursive(Cons argumentList) {
Cons remainingArguments = (Cons) argumentList.getCdr();
SExpression firstArgument = argumentList.getCar();
LispNumber number1 = (LispNumber) firstArgument;

View File

@ -14,7 +14,7 @@ public class PLUS extends LispFunction {
this.mathFunction = new MathFunction(number -> number, this::add);
}
public SExpression call(Cons argumentList) {
public LispNumber call(Cons argumentList) {
argumentValidator.validate(argumentList);
return mathFunction.callTailRecursive(new Cons(LispNumber.ZERO, argumentList));

View File

@ -0,0 +1,47 @@
package function.builtin.cons;
import static testutil.TestUtilities.*;
import org.junit.Test;
import function.ArgumentValidator.*;
public class LENGTHTester {
@Test
public void testLengthOfNil() {
String input = "(length '())";
assertSExpressionsMatch(evaluateString(input), parseString("0"));
}
@Test
public void testLengthOfListOfOneElement() {
String input = "(length '(1))";
assertSExpressionsMatch(evaluateString(input), parseString("1"));
}
@Test
public void testLengthOfListOfManyElements() {
String input = "(length '(1 2 3 4 5))";
assertSExpressionsMatch(evaluateString(input), parseString("5"));
}
@Test(expected = BadArgumentTypeException.class)
public void testLengthWithNonList() {
evaluateString("(length 'x)");
}
@Test(expected = TooManyArgumentsException.class)
public void testLengthWithTooManyArguments() {
evaluateString("(length '(1 2) '(1 2))");
}
@Test(expected = TooFewArgumentsException.class)
public void testLengthWithTooFewArguments() {
evaluateString("(length)");
}
}