Added more unit tests and refactored the code for several built in functions
This commit is contained in:
parent
e543de6f12
commit
ba59b2a8cf
|
@ -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()) {
|
|
||||||
// there is only one argument, so return the additive
|
|
||||||
// inverse of the number
|
|
||||||
return new LispNumber(BigInteger.ZERO.subtract(num1.getValue()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 (remainingArguments.nullp())
|
||||||
if (argSecond.numberp()) {
|
return additiveInverse(number1);
|
||||||
LispNumber num2 = (LispNumber) argSecond;
|
|
||||||
LispNumber difference = new LispNumber(num1.getValue().subtract(num2.getValue()));
|
|
||||||
SExpression argCddr = argRest.getCdr();
|
|
||||||
|
|
||||||
if (argCddr.consp()) {
|
SExpression secondArgument = remainingArguments.getCar();
|
||||||
return call(new Cons(difference, argCddr));
|
LispNumber number2 = (LispNumber) secondArgument;
|
||||||
}
|
LispNumber difference = new LispNumber(number1.getValue().subtract(number2.getValue()));
|
||||||
|
SExpression remainingNumbers = remainingArguments.getCdr();
|
||||||
|
|
||||||
|
if (!remainingNumbers.consp())
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
public MULTIPLY() {
|
||||||
|
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.ONE, argumentList));
|
||||||
LispNumber num1 = (LispNumber) argFirst;
|
|
||||||
LispNumber num2 = call(argRest);
|
|
||||||
|
|
||||||
return new LispNumber(num1.getValue().multiply(num2.getValue()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
public PLUS() {
|
||||||
|
this.argumentValidator = new ArgumentValidator("+");
|
||||||
|
this.argumentValidator.setEveryArgumentExpectedType(LispNumber.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!argList.getCdr().listp())
|
public LispNumber call(Cons argumentList) {
|
||||||
throw new RuntimeException("+: " + argList + " is dotted");
|
argumentValidator.validate(argumentList);
|
||||||
|
|
||||||
SExpression argFirst = argList.getCar();
|
return callTailRecursive(new Cons(LispNumber.ZERO, argumentList));
|
||||||
Cons argRest = (Cons) argList.getCdr();
|
|
||||||
|
|
||||||
if (argFirst.numberp()) {
|
|
||||||
LispNumber num1 = (LispNumber) argFirst;
|
|
||||||
LispNumber num2 = call(argRest);
|
|
||||||
|
|
||||||
return new LispNumber(num1.getValue().add(num2.getValue()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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("(-)");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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)");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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)");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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)");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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"));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue