Added more unit tests and refactored the code for several built in functions

This commit is contained in:
Mike Cifelli 2017-01-13 14:05:33 -05:00
parent e543de6f12
commit ba59b2a8cf
11 changed files with 291 additions and 102 deletions

View File

@ -2,54 +2,46 @@ package function.builtin;
import java.math.BigInteger;
import function.LispFunction;
import function.*;
import sexpression.*;
/**
* <code>MINUS</code> 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;
if (argRest.nullp()) {
// there is only one argument, so return the additive
// inverse of the number
return new LispNumber(BigInteger.ZERO.subtract(num1.getValue()));
return callTailRecursive(argumentList);
}
SExpression argSecond = argRest.getCar();
private SExpression callTailRecursive(Cons argumentList) {
Cons remainingArguments = (Cons) argumentList.getCdr();
SExpression firstArgument = argumentList.getCar();
LispNumber number1 = (LispNumber) firstArgument;
// 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();
if (remainingArguments.nullp())
return additiveInverse(number1);
if (argCddr.consp()) {
return call(new Cons(difference, argCddr));
}
SExpression secondArgument = remainingArguments.getCar();
LispNumber number2 = (LispNumber) secondArgument;
LispNumber difference = new LispNumber(number1.getValue().subtract(number2.getValue()));
SExpression remainingNumbers = remainingArguments.getCdr();
if (!remainingNumbers.consp())
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()));
}
}

View File

@ -1,31 +1,40 @@
package function.builtin;
import java.math.BigInteger;
import function.LispFunction;
import function.*;
import sexpression.*;
/**
* <code>MULTIPLY</code> 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;
public MULTIPLY() {
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 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));
}
}

View File

@ -1,32 +1,21 @@
package function.builtin;
import function.LispFunction;
import function.*;
import sexpression.*;
/**
* <code>NULL</code> 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);
// 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;
throw new RuntimeException(errMsg);
public NULL() {
this.argumentValidator = new ArgumentValidator("NULL");
this.argumentValidator.setExactNumberOfArguments(1);
}
SExpression arg = argList.getCar();
public SExpression call(Cons argumentList) {
argumentValidator.validate(argumentList);
return (arg.nullp() ? Symbol.T : Nil.getInstance());
return argumentList.getCar().nullp() ? Symbol.T : Nil.getInstance();
}
}

View File

@ -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;
public PLUS() {
this.argumentValidator = new ArgumentValidator("+");
this.argumentValidator.setEveryArgumentExpectedType(LispNumber.class);
}
if (!argList.getCdr().listp())
throw new RuntimeException("+: " + argList + " is dotted");
public LispNumber call(Cons argumentList) {
argumentValidator.validate(argumentList);
SExpression argFirst = argList.getCar();
Cons argRest = (Cons) argList.getCdr();
if (argFirst.numberp()) {
LispNumber num1 = (LispNumber) argFirst;
LispNumber num2 = call(argRest);
return new LispNumber(num1.getValue().add(num2.getValue()));
return callTailRecursive(new Cons(LispNumber.ZERO, 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 sum = new LispNumber(number1.getValue().add(number2.getValue()));
SExpression remainingNumbers = remainingArguments.getCdr();
if (!remainingNumbers.consp())
return sum;
return callTailRecursive(new Cons(sum, remainingNumbers));
}
}

View File

@ -8,6 +8,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;
public LispNumber(String text) {

View File

@ -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 '() '())");
}
}

View File

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

View File

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

View File

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

View File

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

View File

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