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 java.math.BigInteger;
import function.LispFunction; import function.*;
import sexpression.*; import sexpression.*;
/**
* <code>MINUS</code> represents the '-' function in Lisp.
*/
public class MINUS extends LispFunction { public class MINUS extends LispFunction {
public SExpression call(Cons argList) { private ArgumentValidator argumentValidator;
// make sure we have received at least one argument
if (argList.nullp()) {
Cons originalSExpr = new Cons(new Symbol("-"), argList);
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(); public SExpression call(Cons argumentList) {
Cons argRest = (Cons) argList.getCdr(); argumentValidator.validate(argumentList);
// make sure that the first argument is a number return callTailRecursive(argumentList);
if (argFirst.numberp()) { }
LispNumber num1 = (LispNumber) argFirst;
if (argRest.nullp()) { private SExpression callTailRecursive(Cons argumentList) {
// there is only one argument, so return the additive Cons remainingArguments = (Cons) argumentList.getCdr();
// inverse of the number SExpression firstArgument = argumentList.getCar();
return new LispNumber(BigInteger.ZERO.subtract(num1.getValue())); 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 SExpression secondArgument = remainingArguments.getCar();
if (argSecond.numberp()) { LispNumber number2 = (LispNumber) secondArgument;
LispNumber num2 = (LispNumber) argSecond; LispNumber difference = new LispNumber(number1.getValue().subtract(number2.getValue()));
LispNumber difference = new LispNumber(num1.getValue().subtract(num2.getValue())); SExpression remainingNumbers = remainingArguments.getCdr();
SExpression argCddr = argRest.getCdr();
if (argCddr.consp()) { if (!remainingNumbers.consp())
return call(new Cons(difference, argCddr)); return difference;
}
return difference; return callTailRecursive(new Cons(difference, remainingNumbers));
} }
throw new RuntimeException("-: " + argSecond + " is not a number"); private LispNumber additiveInverse(LispNumber number) {
} return new LispNumber(BigInteger.ZERO.subtract(number.getValue()));
throw new RuntimeException("-: " + argFirst + " is not a number");
} }
} }

View File

@ -1,31 +1,40 @@
package function.builtin; package function.builtin;
import java.math.BigInteger; import function.*;
import function.LispFunction;
import sexpression.*; import sexpression.*;
/**
* <code>MULTIPLY</code> represents the '*' function in Lisp.
*/
public class MULTIPLY extends LispFunction { public class MULTIPLY extends LispFunction {
public LispNumber call(Cons argList) { private ArgumentValidator argumentValidator;
if (argList.nullp()) {
return new LispNumber(BigInteger.ONE);
}
SExpression argFirst = argList.getCar(); public MULTIPLY() {
Cons argRest = (Cons) argList.getCdr(); this.argumentValidator = new ArgumentValidator("*");
this.argumentValidator.setEveryArgumentExpectedType(LispNumber.class);
}
if (argFirst.numberp()) { public LispNumber call(Cons argumentList) {
LispNumber num1 = (LispNumber) argFirst; argumentValidator.validate(argumentList);
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; package function.builtin;
import function.LispFunction; import function.*;
import sexpression.*; import sexpression.*;
/**
* <code>NULL</code> represents the NULL function in Lisp.
*/
public class NULL extends LispFunction { public class NULL extends LispFunction {
// The number of arguments that NULL takes. private ArgumentValidator argumentValidator;
private static final int NUM_ARGS = 1;
public SExpression call(Cons argList) { public NULL() {
// retrieve the number of arguments passed to NULL this.argumentValidator = new ArgumentValidator("NULL");
int argListLength = LENGTH.getLength(argList); this.argumentValidator.setExactNumberOfArguments(1);
}
// make sure we have received the proper number of arguments public SExpression call(Cons argumentList) {
if (argListLength != NUM_ARGS) { argumentValidator.validate(argumentList);
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); return argumentList.getCar().nullp() ? Symbol.T : Nil.getInstance();
}
SExpression arg = argList.getCar();
return (arg.nullp() ? Symbol.T : Nil.getInstance());
} }
} }

View File

@ -1,31 +1,40 @@
package function.builtin; package function.builtin;
import java.math.BigInteger; import function.*;
import function.LispFunction;
import sexpression.*; import sexpression.*;
public class PLUS extends LispFunction { public class PLUS extends LispFunction {
public LispNumber call(Cons argList) { private ArgumentValidator argumentValidator;
if (argList.nullp()) {
return new LispNumber(BigInteger.ZERO);
}
if (!argList.getCdr().listp()) public PLUS() {
throw new RuntimeException("+: " + argList + " is dotted"); this.argumentValidator = new ArgumentValidator("+");
this.argumentValidator.setEveryArgumentExpectedType(LispNumber.class);
}
SExpression argFirst = argList.getCar(); public LispNumber call(Cons argumentList) {
Cons argRest = (Cons) argList.getCdr(); argumentValidator.validate(argumentList);
if (argFirst.numberp()) { return callTailRecursive(new Cons(LispNumber.ZERO, argumentList));
LispNumber num1 = (LispNumber) argFirst; }
LispNumber num2 = call(argRest);
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));
} }
} }

View File

@ -8,6 +8,9 @@ import error.LispException;
@DisplayName("number") @DisplayName("number")
public class LispNumber extends Atom { 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; private BigInteger value;
public LispNumber(String text) { public LispNumber(String text) {

View File

@ -24,16 +24,12 @@ public class LISTPTester {
@Test(expected = TooFewArgumentsException.class) @Test(expected = TooFewArgumentsException.class)
public void testListpWithTooFewArguments() { public void testListpWithTooFewArguments() {
String input = "(listp)"; evaluateString("(listp)");
assertSExpressionsMatch(evaluateString(input), parseString("NIL"));
} }
@Test(expected = TooManyArgumentsException.class) @Test(expected = TooManyArgumentsException.class)
public void testListpWithTooManyArguments() { public void testListpWithTooManyArguments() {
String input = "(listp '() '())"; evaluateString("(listp '() '())");
assertSExpressionsMatch(evaluateString(input), parseString("NIL"));
} }
} }

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() { public void testComplexConsToString() {
String expected = "(1 A \"string\")"; String expected = "(1 A \"string\")";
Cons list = new Cons(new LispNumber("1"), Cons list = new Cons(new LispNumber("1"),
new Cons(new Symbol("a"), new Cons(new Symbol("a"), new Cons(new LispString("\"string\""), Nil.getInstance())));
new Cons(new LispString("\"string\""), Nil.getInstance())));
assertSExpressionMatchesString(expected, list); assertSExpressionMatchesString(expected, list);
} }
@ -99,10 +98,8 @@ public class SExpressionTester {
@Test @Test
public void testLambdaExpressionGetFunction() { public void testLambdaExpressionGetFunction() {
String expected = "(LAMBDA)"; String expected = "(LAMBDA)";
UserDefinedFunction function = new UserDefinedFunction(expected, Nil.getInstance(), UserDefinedFunction function = new UserDefinedFunction(expected, Nil.getInstance(), Nil.getInstance());
Nil.getInstance()); LambdaExpression lambda = new LambdaExpression(new Cons(new Symbol("lambda"), Nil.getInstance()), function);
LambdaExpression lambda = new LambdaExpression(new Cons(new Symbol("lambda"), Nil.getInstance()),
function);
assertEquals(function, lambda.getFunction()); assertEquals(function, lambda.getFunction());
} }
@ -167,4 +164,9 @@ public class SExpressionTester {
} }
} }
@Test
public void testLispNumberConstants() {
assertEquals(LispNumber.ONE.getValue(), new BigInteger("1"));
}
} }