Added unit tests and refactored lambda

This commit is contained in:
Mike Cifelli 2017-01-27 14:31:41 -05:00
parent 79648cd96f
commit 27fdc7b328
6 changed files with 105 additions and 110 deletions

View File

@ -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.

View File

@ -1,5 +1,6 @@
package function; package function;
import java.math.BigInteger;
import java.text.MessageFormat; import java.text.MessageFormat;
import error.LispException; import error.LispException;
@ -11,8 +12,8 @@ public class ArgumentValidator {
private Class<? extends SExpression> firstArgumentType; private Class<? extends SExpression> firstArgumentType;
private Class<? extends SExpression> trailingArgumentType; private Class<? extends SExpression> trailingArgumentType;
private String functionName; private String functionName;
private Integer maximumNumberOfArguments; private BigInteger maximumNumberOfArguments;
private Integer minimumNumberOfArguments; private BigInteger minimumNumberOfArguments;
private boolean isNilAcceptable; private boolean isNilAcceptable;
public ArgumentValidator(String functionName) { public ArgumentValidator(String functionName) {
@ -38,16 +39,16 @@ public class ArgumentValidator {
} }
public void setMaximumNumberOfArguments(int maximumNumberOfArguments) { public void setMaximumNumberOfArguments(int maximumNumberOfArguments) {
this.maximumNumberOfArguments = maximumNumberOfArguments; this.maximumNumberOfArguments = BigInteger.valueOf(maximumNumberOfArguments);
} }
public void setMinimumNumberOfArguments(int minimumNumberOfArguments) { public void setMinimumNumberOfArguments(int minimumNumberOfArguments) {
this.minimumNumberOfArguments = minimumNumberOfArguments; this.minimumNumberOfArguments = BigInteger.valueOf(minimumNumberOfArguments);
} }
public void setExactNumberOfArguments(int exactNumberOfArguments) { public void setExactNumberOfArguments(int exactNumberOfArguments) {
this.minimumNumberOfArguments = exactNumberOfArguments; this.minimumNumberOfArguments = BigInteger.valueOf(exactNumberOfArguments);
this.maximumNumberOfArguments = exactNumberOfArguments; this.maximumNumberOfArguments = BigInteger.valueOf(exactNumberOfArguments);
} }
public void doNotAcceptNil() { public void doNotAcceptNil() {
@ -78,11 +79,13 @@ public class ArgumentValidator {
} }
private boolean containsTooFewArguments(Cons argumentList) { 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) { 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) { private void validateArgumentTypes(Cons argumentList) {

View File

@ -51,21 +51,6 @@ public class EVAL extends LispFunction {
return SETF.lookupSymbolValue(symbolName); 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) { public static SExpression eval(SExpression sExpression) {
Cons argumentList = LIST.makeList(sExpression); Cons argumentList = LIST.makeList(sExpression);
EVAL eval = new EVAL(); EVAL eval = new EVAL();

View File

@ -7,12 +7,11 @@ import sexpression.*;
public class LENGTH extends LispFunction { public class LENGTH extends LispFunction {
public static int getLength(Cons list) { public static BigInteger getLength(Cons list) {
LENGTH lengthFunction = new LENGTH(); LENGTH lengthFunction = new LENGTH();
LispNumber length = lengthFunction.callWithoutArgumentValidation(LIST.makeList(list)); LispNumber length = lengthFunction.callWithoutArgumentValidation(LIST.makeList(list));
return length.getValue().intValue(); // TODO - return BigInteger when all built-ins use return length.getValue();
// ArgumentValidator
} }
private ArgumentValidator argumentValidator; private ArgumentValidator argumentValidator;

View File

@ -1,8 +1,7 @@
package function.builtin.special; package function.builtin.special;
import function.*; import function.*;
import function.builtin.*; import function.builtin.cons.LIST;
import function.builtin.cons.LENGTH;
import sexpression.*; import sexpression.*;
public class LAMBDA extends LispFunction { public class LAMBDA extends LispFunction {
@ -17,52 +16,40 @@ public class LAMBDA extends LispFunction {
return false; return false;
} }
public static UserDefinedFunction createFunction(Cons lexpr) { public static UserDefinedFunction createFunction(Cons lambdaExpression) {
LAMBDA lambda = new LAMBDA(); SExpression cdr = lambdaExpression.getCdr();
SExpression cdr = lexpr.getCdr();
// make sure lexpr is a proper list ArgumentValidator lambdaValidator = new ArgumentValidator(":LAMBDA");
if (!cdr.consp()) { lambdaValidator.setEveryArgumentExpectedType(Cons.class);
throw new RuntimeException("invalid lambda expression"); lambdaValidator.validate(LIST.makeList(cdr));
} else if (EVAL.isDotted((Cons) cdr)) {
throw new RuntimeException("dotted lambda expression " + lexpr); LambdaExpression lambda = new LAMBDA().call((Cons) cdr);
return lambda.getFunction();
} }
Cons rest = (Cons) cdr; private ArgumentValidator argumentValidator;
return lambda.call(rest).getFunction(); public LAMBDA() {
this.argumentValidator = new ArgumentValidator("LAMBDA");
this.argumentValidator.setFirstArgumentExpectedType(Cons.class);
this.argumentValidator.setMinimumNumberOfArguments(2);
} }
// The minimum number of arguments that LAMBDA takes. public LambdaExpression call(Cons argumentList) {
private static final int MIN_ARGS = 2; argumentValidator.validate(argumentList);
public LambdaExpression call(Cons argList) {
// retrieve the number of arguments passed to LAMBDA
int argListLength = LENGTH.getLength(argList);
// 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");
}
SExpression car = argumentList.getCar();
Cons lambdaList = (Cons) car; Cons lambdaList = (Cons) car;
Cons body = (Cons) argList.getCdr(); Cons body = (Cons) argumentList.getCdr();
Cons lexpr = new Cons(new Symbol("LAMBDA"), argList);
UserDefinedFunction function = new UserDefinedFunction(":LAMBDA", lambdaList, body); 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() { public boolean evaluateArguments() {

View File

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