diff --git a/src/function/builtin/MINUS.java b/src/function/builtin/MINUS.java index d9b4165..9bace55 100644 --- a/src/function/builtin/MINUS.java +++ b/src/function/builtin/MINUS.java @@ -2,54 +2,46 @@ package function.builtin; import java.math.BigInteger; -import function.LispFunction; +import function.*; import sexpression.*; -/** - * MINUS represents the '-' function in Lisp. - */ public class MINUS extends LispFunction { - public SExpression call(Cons argList) { - // make sure we have received at least one argument - if (argList.nullp()) { - Cons originalSExpr = new Cons(new Symbol("-"), argList); + private ArgumentValidator argumentValidator; - throw new RuntimeException("too few arguments given to -: " + originalSExpr); - } + public MINUS() { + this.argumentValidator = new ArgumentValidator("-"); + this.argumentValidator.setMinimumNumberOfArguments(1); + this.argumentValidator.setEveryArgumentExpectedType(LispNumber.class); + } - SExpression argFirst = argList.getCar(); - Cons argRest = (Cons) argList.getCdr(); + public SExpression call(Cons argumentList) { + argumentValidator.validate(argumentList); - // make sure that the first argument is a number - if (argFirst.numberp()) { - LispNumber num1 = (LispNumber) argFirst; + return callTailRecursive(argumentList); + } - if (argRest.nullp()) { - // there is only one argument, so return the additive - // inverse of the number - return new LispNumber(BigInteger.ZERO.subtract(num1.getValue())); - } + private SExpression callTailRecursive(Cons argumentList) { + Cons remainingArguments = (Cons) argumentList.getCdr(); + SExpression firstArgument = argumentList.getCar(); + LispNumber number1 = (LispNumber) firstArgument; - SExpression argSecond = argRest.getCar(); + if (remainingArguments.nullp()) + return additiveInverse(number1); - // make sure that the next argument is a number as well - if (argSecond.numberp()) { - LispNumber num2 = (LispNumber) argSecond; - LispNumber difference = new LispNumber(num1.getValue().subtract(num2.getValue())); - SExpression argCddr = argRest.getCdr(); + SExpression secondArgument = remainingArguments.getCar(); + LispNumber number2 = (LispNumber) secondArgument; + LispNumber difference = new LispNumber(number1.getValue().subtract(number2.getValue())); + SExpression remainingNumbers = remainingArguments.getCdr(); - if (argCddr.consp()) { - return call(new Cons(difference, argCddr)); - } + if (!remainingNumbers.consp()) + return difference; - return difference; - } + return callTailRecursive(new Cons(difference, remainingNumbers)); + } - throw new RuntimeException("-: " + argSecond + " is not a number"); - } - - throw new RuntimeException("-: " + argFirst + " is not a number"); + private LispNumber additiveInverse(LispNumber number) { + return new LispNumber(BigInteger.ZERO.subtract(number.getValue())); } } diff --git a/src/function/builtin/MULTIPLY.java b/src/function/builtin/MULTIPLY.java index d5cfcd3..eceb095 100644 --- a/src/function/builtin/MULTIPLY.java +++ b/src/function/builtin/MULTIPLY.java @@ -1,31 +1,40 @@ package function.builtin; -import java.math.BigInteger; - -import function.LispFunction; +import function.*; import sexpression.*; -/** - * MULTIPLY represents the '*' function in Lisp. - */ public class MULTIPLY extends LispFunction { - public LispNumber call(Cons argList) { - if (argList.nullp()) { - return new LispNumber(BigInteger.ONE); - } + private ArgumentValidator argumentValidator; - SExpression argFirst = argList.getCar(); - Cons argRest = (Cons) argList.getCdr(); + public MULTIPLY() { + this.argumentValidator = new ArgumentValidator("*"); + this.argumentValidator.setEveryArgumentExpectedType(LispNumber.class); + } - if (argFirst.numberp()) { - LispNumber num1 = (LispNumber) argFirst; - LispNumber num2 = call(argRest); + public LispNumber call(Cons argumentList) { + argumentValidator.validate(argumentList); - return new LispNumber(num1.getValue().multiply(num2.getValue())); - } + return callTailRecursive(new Cons(LispNumber.ONE, argumentList)); + } - throw new RuntimeException("*: " + argFirst + " is not a number"); + private LispNumber callTailRecursive(Cons argumentList) { + Cons remainingArguments = (Cons) argumentList.getCdr(); + SExpression firstArgument = argumentList.getCar(); + LispNumber number1 = (LispNumber) firstArgument; + + if (remainingArguments.nullp()) + return number1; + + SExpression secondArgument = remainingArguments.getCar(); + LispNumber number2 = (LispNumber) secondArgument; + LispNumber product = new LispNumber(number1.getValue().multiply(number2.getValue())); + SExpression remainingNumbers = remainingArguments.getCdr(); + + if (!remainingNumbers.consp()) + return product; + + return callTailRecursive(new Cons(product, remainingNumbers)); } } diff --git a/src/function/builtin/NULL.java b/src/function/builtin/NULL.java index 50dc455..650858f 100644 --- a/src/function/builtin/NULL.java +++ b/src/function/builtin/NULL.java @@ -1,32 +1,21 @@ package function.builtin; -import function.LispFunction; +import function.*; import sexpression.*; -/** - * NULL represents the NULL function in Lisp. - */ public class NULL extends LispFunction { - // The number of arguments that NULL takes. - private static final int NUM_ARGS = 1; + private ArgumentValidator argumentValidator; - public SExpression call(Cons argList) { - // retrieve the number of arguments passed to NULL - int argListLength = LENGTH.getLength(argList); + public NULL() { + this.argumentValidator = new ArgumentValidator("NULL"); + this.argumentValidator.setExactNumberOfArguments(1); + } - // make sure we have received the proper number of arguments - if (argListLength != NUM_ARGS) { - Cons originalSExpr = new Cons(new Symbol("NULL"), argList); - String errMsg = "too " + ((argListLength > NUM_ARGS) ? "many" : "few") + " arguments given to NULL: " - + originalSExpr; + public SExpression call(Cons argumentList) { + argumentValidator.validate(argumentList); - throw new RuntimeException(errMsg); - } - - SExpression arg = argList.getCar(); - - return (arg.nullp() ? Symbol.T : Nil.getInstance()); + return argumentList.getCar().nullp() ? Symbol.T : Nil.getInstance(); } } diff --git a/src/function/builtin/PLUS.java b/src/function/builtin/PLUS.java index ce93494..db24685 100644 --- a/src/function/builtin/PLUS.java +++ b/src/function/builtin/PLUS.java @@ -1,31 +1,40 @@ package function.builtin; -import java.math.BigInteger; - -import function.LispFunction; +import function.*; import sexpression.*; public class PLUS extends LispFunction { - public LispNumber call(Cons argList) { - if (argList.nullp()) { - return new LispNumber(BigInteger.ZERO); - } + private ArgumentValidator argumentValidator; - if (!argList.getCdr().listp()) - throw new RuntimeException("+: " + argList + " is dotted"); + public PLUS() { + this.argumentValidator = new ArgumentValidator("+"); + this.argumentValidator.setEveryArgumentExpectedType(LispNumber.class); + } - SExpression argFirst = argList.getCar(); - Cons argRest = (Cons) argList.getCdr(); + public LispNumber call(Cons argumentList) { + argumentValidator.validate(argumentList); - if (argFirst.numberp()) { - LispNumber num1 = (LispNumber) argFirst; - LispNumber num2 = call(argRest); + return callTailRecursive(new Cons(LispNumber.ZERO, argumentList)); + } - return new LispNumber(num1.getValue().add(num2.getValue())); - } + private LispNumber callTailRecursive(Cons argumentList) { + Cons remainingArguments = (Cons) argumentList.getCdr(); + SExpression firstArgument = argumentList.getCar(); + LispNumber number1 = (LispNumber) firstArgument; - throw new RuntimeException("+: " + argFirst + " is not a number"); + if (remainingArguments.nullp()) + return number1; + + SExpression secondArgument = remainingArguments.getCar(); + LispNumber number2 = (LispNumber) secondArgument; + LispNumber sum = new LispNumber(number1.getValue().add(number2.getValue())); + SExpression remainingNumbers = remainingArguments.getCdr(); + + if (!remainingNumbers.consp()) + return sum; + + return callTailRecursive(new Cons(sum, remainingNumbers)); } } diff --git a/src/sexpression/LispNumber.java b/src/sexpression/LispNumber.java index 36d0a6a..6ceb8b9 100644 --- a/src/sexpression/LispNumber.java +++ b/src/sexpression/LispNumber.java @@ -7,6 +7,9 @@ import error.LispException; @DisplayName("number") public class LispNumber extends Atom { + + public static final LispNumber ZERO = new LispNumber(BigInteger.ZERO); + public static final LispNumber ONE = new LispNumber(BigInteger.ONE); private BigInteger value; diff --git a/test/function/builtin/LISTPTester.java b/test/function/builtin/LISTPTester.java index 48bab95..de9f12b 100644 --- a/test/function/builtin/LISTPTester.java +++ b/test/function/builtin/LISTPTester.java @@ -24,16 +24,12 @@ public class LISTPTester { @Test(expected = TooFewArgumentsException.class) public void testListpWithTooFewArguments() { - String input = "(listp)"; - - assertSExpressionsMatch(evaluateString(input), parseString("NIL")); + evaluateString("(listp)"); } @Test(expected = TooManyArgumentsException.class) public void testListpWithTooManyArguments() { - String input = "(listp '() '())"; - - assertSExpressionsMatch(evaluateString(input), parseString("NIL")); + evaluateString("(listp '() '())"); } } diff --git a/test/function/builtin/MINUSTester.java b/test/function/builtin/MINUSTester.java new file mode 100644 index 0000000..1bc7711 --- /dev/null +++ b/test/function/builtin/MINUSTester.java @@ -0,0 +1,50 @@ +package function.builtin; + +import static testutil.TestUtilities.*; + +import org.junit.Test; + +import function.ArgumentValidator.*; +import sexpression.LispNumber; + +public class MINUSTester { + + @Test + public void testMinusWithOneNumber() { + String input = "(- 27)"; + + assertSExpressionsMatch(evaluateString(input), new LispNumber("-27")); + } + + @Test + public void testMinusWithTwoNumbers() { + String input = "(- 5 3)"; + + assertSExpressionsMatch(evaluateString(input), new LispNumber("2")); + } + + @Test + public void testMinusWithManyNumbers_PositiveResult() { + String input = "(- 200 100 10 5)"; + + assertSExpressionsMatch(evaluateString(input), new LispNumber("85")); + } + + @Test + public void testMinusWithManyNumbers_NegativeResult() { + String input = "(- 100 200 20 5)"; + + assertSExpressionsMatch(evaluateString(input), new LispNumber("-125")); + } + + @Test(expected = BadArgumentTypeException.class) + public void testMinusWithNonNumber() { + evaluateString("(- 'a 'b)"); + } + + @Test(expected = TooFewArgumentsException.class) + public void testMinusWithTooFewArguments() { + evaluateString("(-)"); + } + +} diff --git a/test/function/builtin/MULTIPLYTester.java b/test/function/builtin/MULTIPLYTester.java new file mode 100644 index 0000000..f17e24c --- /dev/null +++ b/test/function/builtin/MULTIPLYTester.java @@ -0,0 +1,52 @@ +package function.builtin; + +import static testutil.TestUtilities.*; + +import org.junit.Test; + +import function.ArgumentValidator.BadArgumentTypeException; +import sexpression.LispNumber; + +public class MULTIPLYTester { + + @Test + public void testMultiplyWithNoArguments() { + String input = "(*)"; + + assertSExpressionsMatch(evaluateString(input), new LispNumber("1")); + } + + @Test + public void testMultiplyWithOneNumber() { + String input = "(* 8)"; + + assertSExpressionsMatch(evaluateString(input), new LispNumber("8")); + } + + @Test + public void testMultiplyWithTwoNumbers() { + String input = "(* 5 3)"; + + assertSExpressionsMatch(evaluateString(input), new LispNumber("15")); + } + + @Test + public void testMultiplyWithManyNumbers_PositiveResult() { + String input = "(* 2 3 5 1)"; + + assertSExpressionsMatch(evaluateString(input), new LispNumber("30")); + } + + @Test + public void testMultiplyWithManyNumbers_NegativeResult() { + String input = "(* 3 (- 2) 10 2)"; + + assertSExpressionsMatch(evaluateString(input), new LispNumber("-120")); + } + + @Test(expected = BadArgumentTypeException.class) + public void testMultiplyWithNonNumber() { + evaluateString("(* 'a 'b)"); + } + +} diff --git a/test/function/builtin/NULLTester.java b/test/function/builtin/NULLTester.java new file mode 100644 index 0000000..5a0198a --- /dev/null +++ b/test/function/builtin/NULLTester.java @@ -0,0 +1,35 @@ +package function.builtin; + +import static testutil.TestUtilities.*; + +import org.junit.Test; + +import function.ArgumentValidator.*; + +public class NULLTester { + + @Test + public void testNilIsNull() { + String input = "(null ())"; + + assertSExpressionsMatch(evaluateString(input), parseString("T")); + } + + @Test + public void testListIsNotNull() { + String input = "(null '(1))"; + + assertSExpressionsMatch(evaluateString(input), parseString("NIL")); + } + + @Test(expected = TooFewArgumentsException.class) + public void testNullWithTooFewArguments() { + evaluateString("(null)"); + } + + @Test(expected = TooManyArgumentsException.class) + public void testNullWithTooManyArguments() { + evaluateString("(null 1 2)"); + } + +} diff --git a/test/function/builtin/PLUSTester.java b/test/function/builtin/PLUSTester.java new file mode 100644 index 0000000..96b6c38 --- /dev/null +++ b/test/function/builtin/PLUSTester.java @@ -0,0 +1,52 @@ +package function.builtin; + +import static testutil.TestUtilities.*; + +import org.junit.Test; + +import function.ArgumentValidator.BadArgumentTypeException; +import sexpression.LispNumber; + +public class PLUSTester { + + @Test + public void testPlusWithNoArguments() { + String input = "(+)"; + + assertSExpressionsMatch(evaluateString(input), new LispNumber("0")); + } + + @Test + public void testPlusWithOneNumber() { + String input = "(+ 27)"; + + assertSExpressionsMatch(evaluateString(input), new LispNumber("27")); + } + + @Test + public void testPlusWithTwoNumbers() { + String input = "(+ 5 3)"; + + assertSExpressionsMatch(evaluateString(input), new LispNumber("8")); + } + + @Test + public void testPlusWithManyNumbers_PositiveResult() { + String input = "(+ 200 100 10 5)"; + + assertSExpressionsMatch(evaluateString(input), new LispNumber("315")); + } + + @Test + public void testPlusWithManyNumbers_NegativeResult() { + String input = "(+ 100 (- 200) 20 5)"; + + assertSExpressionsMatch(evaluateString(input), new LispNumber("-75")); + } + + @Test(expected = BadArgumentTypeException.class) + public void testPlusWithNonNumber() { + evaluateString("(+ 'a 'b)"); + } + +} diff --git a/test/sexpression/SExpressionTester.java b/test/sexpression/SExpressionTester.java index 4e9a332..6f5a786 100644 --- a/test/sexpression/SExpressionTester.java +++ b/test/sexpression/SExpressionTester.java @@ -66,8 +66,7 @@ public class SExpressionTester { public void testComplexConsToString() { String expected = "(1 A \"string\")"; Cons list = new Cons(new LispNumber("1"), - new Cons(new Symbol("a"), - new Cons(new LispString("\"string\""), Nil.getInstance()))); + new Cons(new Symbol("a"), new Cons(new LispString("\"string\""), Nil.getInstance()))); assertSExpressionMatchesString(expected, list); } @@ -99,10 +98,8 @@ public class SExpressionTester { @Test public void testLambdaExpressionGetFunction() { String expected = "(LAMBDA)"; - UserDefinedFunction function = new UserDefinedFunction(expected, Nil.getInstance(), - Nil.getInstance()); - LambdaExpression lambda = new LambdaExpression(new Cons(new Symbol("lambda"), Nil.getInstance()), - function); + UserDefinedFunction function = new UserDefinedFunction(expected, Nil.getInstance(), Nil.getInstance()); + LambdaExpression lambda = new LambdaExpression(new Cons(new Symbol("lambda"), Nil.getInstance()), function); assertEquals(function, lambda.getFunction()); } @@ -167,4 +164,9 @@ public class SExpressionTester { } } + @Test + public void testLispNumberConstants() { + assertEquals(LispNumber.ONE.getValue(), new BigInteger("1")); + } + }