diff --git a/src/function/builtin/cons/LENGTH.java b/src/function/builtin/cons/LENGTH.java index 00bbbae..b32d5d1 100644 --- a/src/function/builtin/cons/LENGTH.java +++ b/src/function/builtin/cons/LENGTH.java @@ -2,62 +2,49 @@ package function.builtin.cons; import java.math.BigInteger; -import function.LispFunction; +import function.*; import sexpression.*; -/** - * LENGTH 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 list - */ 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); } } diff --git a/src/function/builtin/math/DIVIDE.java b/src/function/builtin/math/DIVIDE.java index 394dfa7..e2bd744 100644 --- a/src/function/builtin/math/DIVIDE.java +++ b/src/function/builtin/math/DIVIDE.java @@ -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); diff --git a/src/function/builtin/math/MINUS.java b/src/function/builtin/math/MINUS.java index 807e5a6..a84f101 100644 --- a/src/function/builtin/math/MINUS.java +++ b/src/function/builtin/math/MINUS.java @@ -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); diff --git a/src/function/builtin/math/MULTIPLY.java b/src/function/builtin/math/MULTIPLY.java index c48981d..75de4d6 100644 --- a/src/function/builtin/math/MULTIPLY.java +++ b/src/function/builtin/math/MULTIPLY.java @@ -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)); diff --git a/src/function/builtin/math/MathFunction.java b/src/function/builtin/math/MathFunction.java index e211e5a..5a5d443 100644 --- a/src/function/builtin/math/MathFunction.java +++ b/src/function/builtin/math/MathFunction.java @@ -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; diff --git a/src/function/builtin/math/PLUS.java b/src/function/builtin/math/PLUS.java index a96a450..3efb2a5 100644 --- a/src/function/builtin/math/PLUS.java +++ b/src/function/builtin/math/PLUS.java @@ -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)); diff --git a/test/function/builtin/cons/LENGTHTester.java b/test/function/builtin/cons/LENGTHTester.java new file mode 100644 index 0000000..5786680 --- /dev/null +++ b/test/function/builtin/cons/LENGTHTester.java @@ -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)"); + } + +}