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"));
+ }
+
}