From 27fdc7b3280c29dfe14b72344b1fef87986b6a82 Mon Sep 17 00:00:00 2001 From: Mike Cifelli Date: Fri, 27 Jan 2017 14:31:41 -0500 Subject: [PATCH] Added unit tests and refactored lambda --- design.txt | 47 ------------- src/function/ArgumentValidator.java | 19 +++--- src/function/builtin/EVAL.java | 15 ---- src/function/builtin/cons/LENGTH.java | 5 +- src/function/builtin/special/LAMBDA.java | 61 +++++++---------- .../builtin/special/LAMBDATester.java | 68 +++++++++++++++++++ 6 files changed, 105 insertions(+), 110 deletions(-) delete mode 100644 design.txt create mode 100644 test/function/builtin/special/LAMBDATester.java diff --git a/design.txt b/design.txt deleted file mode 100644 index d68ca95..0000000 --- a/design.txt +++ /dev/null @@ -1,47 +0,0 @@ -Mike Cifelli -CIS 443 - Programming Languages -Lisp Interpreter Design Document - - My implementation of LispScanner takes in an InputStream in its constructor -that it will use to retrieve the Lisp tokens. It then creates a -BufferedInputStream around this input stream so I can be sure that it will -support the 'mark' and 'reset' methods (which the LispScanner requires to -operate). A LispFilterStream is then created with this BufferedInputStream so I -can retrieve all of the bytes from the original input stream without having to -worry about dealing with Lisp comments. - When the LispScanner looks for the next Lisp token to return it uses a -switch statement to determine the type of the next token (or to skip over -whitespace). In the case of an identifier or number the scanner has to keep -accumulating characters until it sees one that can not be a part of the number -or identifier. Once one is found it has obviously been read from the input -stream and this is not desirable as it is not part of the current token. This -is where I made use of the 'mark' and 'reset' methods in the scanner. I mark -the position before I read each character and when one is found that is not -part of the current token I reset the input stream to its last position before -the token is returned. This effectively unreads the last character so the input -stream is in the proper position for the scanner's next read. - - In the design of the LispParser I had some difficulty in implementing the -'eof' method. This was due to the fact that in order to determine if a -LispScanner was at the end of the input stream you have to read in a token. -However, I did not want this method to read in a token since this would -normally be part of an S-expression. Unfortunately, this meant that I would not -be able to detect the end-of-file token until the 'getSExpr' method read it in. -This is too late since the 'eof' method is used to determine when to stop -calling the 'getSExpr' method. - My solution involved reading a token in the 'eof' method and then storing -it in a variable. This would only be done once, until the token was used in the -'getSExpr' method. I also stored any exceptions that were thrown during the -read so that the 'eof' method would not give the impression of having read in -any tokens. Any exception thrown in the 'eof' method during the retrieval of a -token is stored and thrown the next time the 'getSExpr' method is called. - - During the evaluation phase of the Lisp interpreter, I made use of the -command pattern during function calls. By creating a LispFunction interface, I -was able to place all of the built-in functions into a hash table mapping -function names to the appropriate LispFunction. By looking up functions in this -hash table, the proper function could be easily called during the evaluation of -a list. - - FINAL NOTE: The function table is located in the EVAL class and the symbol -table is located in the SETF class. diff --git a/src/function/ArgumentValidator.java b/src/function/ArgumentValidator.java index 4df0a41..f12951c 100644 --- a/src/function/ArgumentValidator.java +++ b/src/function/ArgumentValidator.java @@ -1,5 +1,6 @@ package function; +import java.math.BigInteger; import java.text.MessageFormat; import error.LispException; @@ -11,8 +12,8 @@ public class ArgumentValidator { private Class firstArgumentType; private Class trailingArgumentType; private String functionName; - private Integer maximumNumberOfArguments; - private Integer minimumNumberOfArguments; + private BigInteger maximumNumberOfArguments; + private BigInteger minimumNumberOfArguments; private boolean isNilAcceptable; public ArgumentValidator(String functionName) { @@ -38,16 +39,16 @@ public class ArgumentValidator { } public void setMaximumNumberOfArguments(int maximumNumberOfArguments) { - this.maximumNumberOfArguments = maximumNumberOfArguments; + this.maximumNumberOfArguments = BigInteger.valueOf(maximumNumberOfArguments); } public void setMinimumNumberOfArguments(int minimumNumberOfArguments) { - this.minimumNumberOfArguments = minimumNumberOfArguments; + this.minimumNumberOfArguments = BigInteger.valueOf(minimumNumberOfArguments); } public void setExactNumberOfArguments(int exactNumberOfArguments) { - this.minimumNumberOfArguments = exactNumberOfArguments; - this.maximumNumberOfArguments = exactNumberOfArguments; + this.minimumNumberOfArguments = BigInteger.valueOf(exactNumberOfArguments); + this.maximumNumberOfArguments = BigInteger.valueOf(exactNumberOfArguments); } public void doNotAcceptNil() { @@ -78,11 +79,13 @@ public class ArgumentValidator { } private boolean containsTooFewArguments(Cons argumentList) { - return (minimumNumberOfArguments != null) && (LENGTH.getLength(argumentList) < minimumNumberOfArguments); + return (minimumNumberOfArguments != null) + && (LENGTH.getLength(argumentList).compareTo(minimumNumberOfArguments) < 0); } private boolean containsTooManyArguments(Cons argumentList) { - return (maximumNumberOfArguments != null) && (LENGTH.getLength(argumentList) > maximumNumberOfArguments); + return (maximumNumberOfArguments != null) + && (LENGTH.getLength(argumentList).compareTo(maximumNumberOfArguments) > 0); } private void validateArgumentTypes(Cons argumentList) { diff --git a/src/function/builtin/EVAL.java b/src/function/builtin/EVAL.java index 199cb67..8fd9d3b 100644 --- a/src/function/builtin/EVAL.java +++ b/src/function/builtin/EVAL.java @@ -51,21 +51,6 @@ public class EVAL extends LispFunction { return SETF.lookupSymbolValue(symbolName); } - public static boolean isDotted(Cons list) { - if (list.nullp()) { - return false; - } - - SExpression cdr = list.getCdr(); - - if (cdr.listp()) { - return isDotted((Cons) cdr); - } - - // the cdr of 'list' is not a list, therefore it is dotted - return true; - } - public static SExpression eval(SExpression sExpression) { Cons argumentList = LIST.makeList(sExpression); EVAL eval = new EVAL(); diff --git a/src/function/builtin/cons/LENGTH.java b/src/function/builtin/cons/LENGTH.java index b32d5d1..e7baf49 100644 --- a/src/function/builtin/cons/LENGTH.java +++ b/src/function/builtin/cons/LENGTH.java @@ -7,12 +7,11 @@ import sexpression.*; public class LENGTH extends LispFunction { - public static int getLength(Cons list) { + public static BigInteger getLength(Cons list) { LENGTH lengthFunction = new LENGTH(); LispNumber length = lengthFunction.callWithoutArgumentValidation(LIST.makeList(list)); - return length.getValue().intValue(); // TODO - return BigInteger when all built-ins use - // ArgumentValidator + return length.getValue(); } private ArgumentValidator argumentValidator; diff --git a/src/function/builtin/special/LAMBDA.java b/src/function/builtin/special/LAMBDA.java index d0bc71a..74b0230 100644 --- a/src/function/builtin/special/LAMBDA.java +++ b/src/function/builtin/special/LAMBDA.java @@ -1,8 +1,7 @@ package function.builtin.special; import function.*; -import function.builtin.*; -import function.builtin.cons.LENGTH; +import function.builtin.cons.LIST; import sexpression.*; public class LAMBDA extends LispFunction { @@ -17,52 +16,40 @@ public class LAMBDA extends LispFunction { return false; } - public static UserDefinedFunction createFunction(Cons lexpr) { - LAMBDA lambda = new LAMBDA(); - SExpression cdr = lexpr.getCdr(); + public static UserDefinedFunction createFunction(Cons lambdaExpression) { + SExpression cdr = lambdaExpression.getCdr(); - // make sure lexpr is a proper list - if (!cdr.consp()) { - throw new RuntimeException("invalid lambda expression"); - } else if (EVAL.isDotted((Cons) cdr)) { - throw new RuntimeException("dotted lambda expression " + lexpr); - } + ArgumentValidator lambdaValidator = new ArgumentValidator(":LAMBDA"); + lambdaValidator.setEveryArgumentExpectedType(Cons.class); + lambdaValidator.validate(LIST.makeList(cdr)); - Cons rest = (Cons) cdr; + LambdaExpression lambda = new LAMBDA().call((Cons) cdr); - return lambda.call(rest).getFunction(); + return lambda.getFunction(); } - // The minimum number of arguments that LAMBDA takes. - private static final int MIN_ARGS = 2; + private ArgumentValidator argumentValidator; - public LambdaExpression call(Cons argList) { - // retrieve the number of arguments passed to LAMBDA - int argListLength = LENGTH.getLength(argList); + public LAMBDA() { + this.argumentValidator = new ArgumentValidator("LAMBDA"); + this.argumentValidator.setFirstArgumentExpectedType(Cons.class); + this.argumentValidator.setMinimumNumberOfArguments(2); + } - // make sure we have received the proper number of arguments - if (argListLength < MIN_ARGS) { - Cons originalSExpr = new Cons(new Symbol("LAMBDA"), argList); - String errMsg = "too few arguments given to LAMBDA: " + originalSExpr; - - throw new RuntimeException(errMsg); - } - - SExpression car = argList.getCar(); - - // make sure the list of arguments is a proper list - if (!car.listp()) { - throw new RuntimeException("LAMBDA: " + car + " is not a list"); - } else if (EVAL.isDotted((Cons) car)) { - throw new RuntimeException("LAMBDA: " + car + " must be a proper list"); - } + public LambdaExpression call(Cons argumentList) { + argumentValidator.validate(argumentList); + SExpression car = argumentList.getCar(); Cons lambdaList = (Cons) car; - Cons body = (Cons) argList.getCdr(); - Cons lexpr = new Cons(new Symbol("LAMBDA"), argList); + Cons body = (Cons) argumentList.getCdr(); + UserDefinedFunction function = new UserDefinedFunction(":LAMBDA", lambdaList, body); - return new LambdaExpression(lexpr, function); + return new LambdaExpression(makeOriginalLambdaExpression(argumentList), function); + } + + private Cons makeOriginalLambdaExpression(Cons argumentList) { + return new Cons(new Symbol("LAMBDA"), argumentList); } public boolean evaluateArguments() { diff --git a/test/function/builtin/special/LAMBDATester.java b/test/function/builtin/special/LAMBDATester.java new file mode 100644 index 0000000..7e29c0e --- /dev/null +++ b/test/function/builtin/special/LAMBDATester.java @@ -0,0 +1,68 @@ +package function.builtin.special; + +import static org.junit.Assert.*; +import static testutil.TestUtilities.*; + +import org.junit.Test; + +import function.ArgumentValidator.*; +import sexpression.*; + +public class LAMBDATester { + + @Test + public void testLambda() { + String input = "(lambda (x) x)"; + + assertSExpressionsMatch(parseString("(LAMBDA (X) X)"), evaluateString(input)); + } + + @Test + public void lambdaExpressionIsLambdaExpression() { + Cons lambdaExpression = new Cons(new Symbol("LAMBDA"), + new Cons(Nil.getInstance(), new Cons(Nil.getInstance(), Nil.getInstance()))); + + assertTrue(LAMBDA.isLambdaExpression(lambdaExpression)); + } + + @Test + public void somethingElseIsNotLambdaExpression() { + assertFalse(LAMBDA.isLambdaExpression(Symbol.T)); + } + + @Test + public void testCreateLambdaExpression() { + Cons lambdaExpression = new Cons(new Symbol("LAMBDA"), + new Cons(Nil.getInstance(), new Cons(Nil.getInstance(), Nil.getInstance()))); + + assertSExpressionsMatch(parseString("(:LAMBDA () ())"), + LAMBDA.createFunction(lambdaExpression).getLambdaExpression()); + } + + @Test(expected = DottedArgumentListException.class) + public void testLambdaWithDottedArgumentList() { + String input = "(apply 'lambda (cons '(x) 1))"; + + evaluateString(input); + } + + @Test(expected = DottedArgumentListException.class) + public void testCreateFunctionWithDottedArgumentList() { + Cons lambdaExpression = new Cons(new Symbol("LAMBDA"), new Cons(Nil.getInstance(), LispNumber.ONE)); + + LAMBDA.createFunction(lambdaExpression); + } + + @Test(expected = BadArgumentTypeException.class) + public void testCreateFunctionWithNonList() { + Cons lambdaExpression = new Cons(new Symbol("LAMBDA"), LispNumber.ONE); + + LAMBDA.createFunction(lambdaExpression); + } + + @Test(expected = TooFewArgumentsException.class) + public void testLambdaWithTooFewArguments() { + evaluateString("(lambda ())"); + } + +}