Added unit tests and refactored lambda
This commit is contained in:
parent
79648cd96f
commit
27fdc7b328
47
design.txt
47
design.txt
|
@ -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.
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 ArgumentValidator argumentValidator;
|
||||||
private static final int MIN_ARGS = 2;
|
|
||||||
|
|
||||||
public LambdaExpression call(Cons argList) {
|
public LAMBDA() {
|
||||||
// retrieve the number of arguments passed to LAMBDA
|
this.argumentValidator = new ArgumentValidator("LAMBDA");
|
||||||
int argListLength = LENGTH.getLength(argList);
|
this.argumentValidator.setFirstArgumentExpectedType(Cons.class);
|
||||||
|
this.argumentValidator.setMinimumNumberOfArguments(2);
|
||||||
|
}
|
||||||
|
|
||||||
// make sure we have received the proper number of arguments
|
public LambdaExpression call(Cons argumentList) {
|
||||||
if (argListLength < MIN_ARGS) {
|
argumentValidator.validate(argumentList);
|
||||||
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() {
|
||||||
|
|
|
@ -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 ())");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue