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;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.text.MessageFormat;
|
||||
|
||||
import error.LispException;
|
||||
|
@ -11,8 +12,8 @@ public class ArgumentValidator {
|
|||
private Class<? extends SExpression> firstArgumentType;
|
||||
private Class<? extends SExpression> 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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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