Update argument validation and added unit tests
Fixed some bugs in LET and LAMBDA Refactored the code in UserDefinedFunction
This commit is contained in:
parent
d7ca5d09da
commit
0a5228d5a7
|
@ -124,16 +124,16 @@ public class ArgumentValidator {
|
|||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private String functionName;
|
||||
private Cons originalSExpression;
|
||||
private Cons argumentList;
|
||||
|
||||
public TooFewArgumentsException(String functionName, Cons argumentList) {
|
||||
this.functionName = functionName;
|
||||
this.originalSExpression = new Cons(new Symbol(this.functionName), argumentList);
|
||||
this.argumentList = argumentList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return MessageFormat.format("too few arguments given to {0}: {1}", functionName, originalSExpression);
|
||||
return MessageFormat.format("too few arguments given to {0}: {1}", functionName, argumentList);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,16 +141,16 @@ public class ArgumentValidator {
|
|||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private String functionName;
|
||||
private Cons originalSExpression;
|
||||
private Cons argumentList;
|
||||
|
||||
public TooManyArgumentsException(String functionName, Cons argumentList) {
|
||||
this.functionName = functionName;
|
||||
this.originalSExpression = new Cons(new Symbol(this.functionName), argumentList);
|
||||
this.argumentList = argumentList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return MessageFormat.format("too many arguments given to {0}: {1}", functionName, originalSExpression);
|
||||
return MessageFormat.format("too many arguments given to {0}: {1}", functionName, argumentList);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,16 +158,16 @@ public class ArgumentValidator {
|
|||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private String functionName;
|
||||
private Cons originalSExpression;
|
||||
private Cons argumentList;
|
||||
|
||||
public DottedArgumentListException(String functionName, Cons argumentList) {
|
||||
this.functionName = functionName;
|
||||
this.originalSExpression = new Cons(new Symbol(this.functionName), argumentList);
|
||||
this.argumentList = argumentList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return MessageFormat.format("dotted argument list given to {0}: {1}", functionName, originalSExpression);
|
||||
return MessageFormat.format("dotted argument list given to {0}: {1}", functionName, argumentList);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ public class UserDefinedFunction extends LispFunction {
|
|||
private Cons body;
|
||||
private Cons lambdaExpression;
|
||||
private ExecutionContext executionContext;
|
||||
private SymbolTable scope;
|
||||
private SymbolTable functionScope;
|
||||
private ArrayList<String> formalParameters;
|
||||
private ArgumentValidator argumentValidator;
|
||||
|
||||
|
@ -21,7 +21,7 @@ public class UserDefinedFunction extends LispFunction {
|
|||
this.body = body;
|
||||
this.lambdaExpression = new Cons(new Symbol(name), new Cons(lambdaList, body));
|
||||
this.executionContext = ExecutionContext.getInstance();
|
||||
this.scope = executionContext.getScope();
|
||||
this.functionScope = executionContext.getScope();
|
||||
|
||||
for (this.formalParameters = new ArrayList<>(); lambdaList.consp(); lambdaList = (Cons) lambdaList.getCdr())
|
||||
this.formalParameters.add(lambdaList.getCar().toString());
|
||||
|
@ -33,49 +33,33 @@ public class UserDefinedFunction extends LispFunction {
|
|||
public SExpression call(Cons argumentList) {
|
||||
argumentValidator.validate(argumentList);
|
||||
|
||||
// bind the values of the arguments to the formal parameter names
|
||||
bindParameterValues(argumentList);
|
||||
|
||||
// store the environment of the S-expression that called this function
|
||||
// (the current environment)
|
||||
SymbolTable callingScope = executionContext.getScope();
|
||||
executionContext.setScope(functionScope);
|
||||
|
||||
// replace the current environment with the environment of this
|
||||
// function
|
||||
executionContext.setScope(scope);
|
||||
SExpression lastEvaluation = null;
|
||||
|
||||
Cons currentSExpression = body;
|
||||
SExpression lastValue = null;
|
||||
for (Cons expression = body; expression.consp(); expression = (Cons) expression.getCdr())
|
||||
lastEvaluation = EVAL.eval(expression.getCar());
|
||||
|
||||
// evaluate all the S-expressions making up this function's body
|
||||
while (currentSExpression.consp()) {
|
||||
lastValue = EVAL.eval(currentSExpression.getCar());
|
||||
currentSExpression = (Cons) currentSExpression.getCdr();
|
||||
}
|
||||
|
||||
// replace the environment of the S-expression that called this
|
||||
// function
|
||||
executionContext.setScope(callingScope);
|
||||
|
||||
// remove the bindings of the arguments to the formal parameter names
|
||||
// in the environment of this function
|
||||
releaseParameterValues();
|
||||
|
||||
return lastValue;
|
||||
return lastEvaluation;
|
||||
}
|
||||
|
||||
private void bindParameterValues(Cons argumentList) {
|
||||
scope = new SymbolTable(scope);
|
||||
functionScope = new SymbolTable(functionScope);
|
||||
|
||||
for (String parameter : formalParameters) {
|
||||
SExpression currentArg = argumentList.getCar();
|
||||
scope.put(parameter, currentArg);
|
||||
functionScope.put(parameter, currentArg);
|
||||
argumentList = (Cons) argumentList.getCdr();
|
||||
}
|
||||
}
|
||||
|
||||
private void releaseParameterValues() {
|
||||
scope = new SymbolTable(scope.getParent());
|
||||
functionScope = new SymbolTable(functionScope.getParent());
|
||||
}
|
||||
|
||||
public Cons getLambdaExpression() {
|
||||
|
|
|
@ -20,10 +20,10 @@ public class DEFUN extends LispFunction {
|
|||
this.argumentValidator.setMinimumNumberOfArguments(3);
|
||||
this.argumentValidator.setFirstArgumentExpectedType(Symbol.class);
|
||||
|
||||
this.lambdaListIsListValidator = new ArgumentValidator("DEFUN|lambda_list|");
|
||||
this.lambdaListIsListValidator = new ArgumentValidator("DEFUN|lambda-list|");
|
||||
this.lambdaListIsListValidator.setEveryArgumentExpectedType(Cons.class);
|
||||
|
||||
this.lambdaListValidator = new ArgumentValidator("DEFUN|lambda_list|");
|
||||
this.lambdaListValidator = new ArgumentValidator("DEFUN|parameter|");
|
||||
this.lambdaListValidator.setEveryArgumentExpectedType(Symbol.class);
|
||||
|
||||
this.environment = Environment.getInstance();
|
||||
|
|
|
@ -19,7 +19,7 @@ public class LAMBDA extends LispFunction {
|
|||
public static UserDefinedFunction createFunction(Cons lambdaExpression) {
|
||||
SExpression cdr = lambdaExpression.getCdr();
|
||||
|
||||
ArgumentValidator lambdaValidator = new ArgumentValidator(":LAMBDA");
|
||||
ArgumentValidator lambdaValidator = new ArgumentValidator("LAMBDA|create|");
|
||||
lambdaValidator.setEveryArgumentExpectedType(Cons.class);
|
||||
lambdaValidator.validate(LIST.makeList(cdr));
|
||||
|
||||
|
@ -29,11 +29,15 @@ public class LAMBDA extends LispFunction {
|
|||
}
|
||||
|
||||
private ArgumentValidator argumentValidator;
|
||||
private ArgumentValidator lambdaListValidator;
|
||||
|
||||
public LAMBDA() {
|
||||
this.argumentValidator = new ArgumentValidator("LAMBDA");
|
||||
this.argumentValidator.setFirstArgumentExpectedType(Cons.class);
|
||||
this.argumentValidator.setMinimumNumberOfArguments(2);
|
||||
|
||||
this.lambdaListValidator = new ArgumentValidator("LAMBDA|lambda-list|");
|
||||
this.lambdaListValidator.setEveryArgumentExpectedType(Symbol.class);
|
||||
}
|
||||
|
||||
public LambdaExpression call(Cons argumentList) {
|
||||
|
@ -43,6 +47,8 @@ public class LAMBDA extends LispFunction {
|
|||
Cons lambdaList = (Cons) car;
|
||||
Cons body = (Cons) argumentList.getCdr();
|
||||
|
||||
lambdaListValidator.validate(lambdaList);
|
||||
|
||||
UserDefinedFunction function = new UserDefinedFunction(":LAMBDA", lambdaList, body);
|
||||
|
||||
return new LambdaExpression(makeOriginalLambdaExpression(argumentList), function);
|
||||
|
|
|
@ -8,13 +8,22 @@ import table.*;
|
|||
public class LET extends LispFunction {
|
||||
|
||||
private ArgumentValidator argumentValidator;
|
||||
private ArgumentValidator variableDefinitionListValidator;
|
||||
private ArgumentValidator pairValidator;
|
||||
private ExecutionContext executionContext;
|
||||
private SymbolTable localScope;
|
||||
|
||||
public LET() {
|
||||
this.argumentValidator = new ArgumentValidator("LET");
|
||||
this.argumentValidator.setMinimumNumberOfArguments(1);
|
||||
this.argumentValidator.setFirstArgumentExpectedType(Cons.class);
|
||||
|
||||
this.variableDefinitionListValidator = new ArgumentValidator("LET|pair-list|");
|
||||
this.variableDefinitionListValidator.setEveryArgumentExpectedType(Cons.class);
|
||||
|
||||
this.pairValidator = new ArgumentValidator("LET|pair|");
|
||||
this.pairValidator.setExactNumberOfArguments(2);
|
||||
this.pairValidator.setFirstArgumentExpectedType(Symbol.class);
|
||||
|
||||
this.executionContext = ExecutionContext.getInstance();
|
||||
}
|
||||
|
||||
|
@ -27,15 +36,15 @@ public class LET extends LispFunction {
|
|||
}
|
||||
|
||||
private SExpression evaluateInScope(Cons variableDefinitions, Cons body) {
|
||||
createLocalScope(variableDefinitions);
|
||||
SymbolTable localScope = createLocalScope(variableDefinitions);
|
||||
SExpression lastEvaluation = evaluateBody(body);
|
||||
restorePreviousScope();
|
||||
restorePreviousScope(localScope);
|
||||
|
||||
return lastEvaluation;
|
||||
}
|
||||
|
||||
private SymbolTable createLocalScope(Cons variableDefinitions) {
|
||||
localScope = new SymbolTable(executionContext.getScope());
|
||||
SymbolTable localScope = new SymbolTable(executionContext.getScope());
|
||||
addVariablesToScope(localScope, variableDefinitions);
|
||||
executionContext.setScope(localScope);
|
||||
|
||||
|
@ -43,32 +52,20 @@ public class LET extends LispFunction {
|
|||
}
|
||||
|
||||
private void addVariablesToScope(SymbolTable scope, Cons variableDefinitions) {
|
||||
validateAllPairsAreLists(variableDefinitions);
|
||||
|
||||
for (; variableDefinitions.consp(); variableDefinitions = (Cons) variableDefinitions.getCdr()) {
|
||||
Cons symbolValuePair = (Cons) variableDefinitions.getCar();
|
||||
|
||||
validatePair(symbolValuePair);
|
||||
|
||||
Cons restOfPair = (Cons) symbolValuePair.getCdr();
|
||||
SExpression symbol = symbolValuePair.getCar();
|
||||
SExpression value = restOfPair.getCar();
|
||||
|
||||
scope.put(symbol.toString(), EVAL.eval(value));
|
||||
}
|
||||
}
|
||||
|
||||
private void validateAllPairsAreLists(Cons variableDefinitions) {
|
||||
ArgumentValidator variableDefinitionListValidator = new ArgumentValidator("LET|argumentList|");
|
||||
variableDefinitionListValidator.setEveryArgumentExpectedType(Cons.class);
|
||||
variableDefinitionListValidator.validate(variableDefinitions);
|
||||
|
||||
for (; variableDefinitions.consp(); variableDefinitions = (Cons) variableDefinitions.getCdr())
|
||||
addPairToScope((Cons) variableDefinitions.getCar(), scope);
|
||||
}
|
||||
|
||||
private void validatePair(Cons symbolValuePair) {
|
||||
ArgumentValidator pairValidator = new ArgumentValidator("LET|pair|");
|
||||
pairValidator.setExactNumberOfArguments(2);
|
||||
pairValidator.setFirstArgumentExpectedType(Symbol.class);
|
||||
pairValidator.validate((Cons) symbolValuePair);
|
||||
private void addPairToScope(Cons symbolValuePair, SymbolTable scope) {
|
||||
pairValidator.validate(symbolValuePair);
|
||||
|
||||
Cons restOfPair = (Cons) symbolValuePair.getCdr();
|
||||
SExpression symbol = symbolValuePair.getCar();
|
||||
SExpression value = restOfPair.getCar();
|
||||
|
||||
scope.put(symbol.toString(), EVAL.eval(value));
|
||||
}
|
||||
|
||||
private SExpression evaluateBody(Cons body) {
|
||||
|
@ -80,7 +77,7 @@ public class LET extends LispFunction {
|
|||
return lastEvaluation;
|
||||
}
|
||||
|
||||
private void restorePreviousScope() {
|
||||
private void restorePreviousScope(SymbolTable localScope) {
|
||||
executionContext.setScope(localScope.getParent());
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,12 @@ public class EVALTester {
|
|||
assertSExpressionsMatch(parseString(symbol), EVAL.lookupSymbol(symbol));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLookupNil() {
|
||||
String symbol = "NIL";
|
||||
assertSExpressionsMatch(parseString(symbol), EVAL.lookupSymbol(symbol));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLookupUndefinedSymbol() {
|
||||
assertNull(EVAL.lookupSymbol("undefined"));
|
||||
|
|
|
@ -46,6 +46,13 @@ public class LAMBDATester {
|
|||
evaluateString(input);
|
||||
}
|
||||
|
||||
@Test(expected = DottedArgumentListException.class)
|
||||
public void testLambdaWithDottedLambdaList() {
|
||||
String input = "(funcall 'lambda (cons 'a 'b) ())";
|
||||
|
||||
evaluateString(input);
|
||||
}
|
||||
|
||||
@Test(expected = DottedArgumentListException.class)
|
||||
public void testCreateFunctionWithDottedArgumentList() {
|
||||
Cons lambdaExpression = new Cons(new Symbol("LAMBDA"), new Cons(Nil.getInstance(), LispNumber.ONE));
|
||||
|
@ -60,9 +67,38 @@ public class LAMBDATester {
|
|||
LAMBDA.createFunction(lambdaExpression);
|
||||
}
|
||||
|
||||
@Test(expected = BadArgumentTypeException.class)
|
||||
public void testLambdaWithNonSymbolParameter() {
|
||||
evaluateString("(lambda (1) ())");
|
||||
}
|
||||
|
||||
@Test(expected = TooFewArgumentsException.class)
|
||||
public void testLambdaWithTooFewArguments() {
|
||||
evaluateString("(lambda ())");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void anonymousLambdaCall() {
|
||||
String input = "((lambda (x) x) 203)";
|
||||
|
||||
assertSExpressionsMatch(new LispNumber("203"), evaluateString(input));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void anonymousLambdaCallWithMultipleArguments() {
|
||||
String input = "((lambda (x y) (+ x y)) 203 2)";
|
||||
|
||||
assertSExpressionsMatch(new LispNumber("205"), evaluateString(input));
|
||||
}
|
||||
|
||||
@Test(expected = TooFewArgumentsException.class)
|
||||
public void anonymousLambdaCallWithTooFewArguments() {
|
||||
evaluateString("((lambda (x) x))");
|
||||
}
|
||||
|
||||
@Test(expected = TooManyArgumentsException.class)
|
||||
public void anonymousLambdaCallWithTooManyArguments() {
|
||||
evaluateString("((lambda (x y) x) 1 2 3)");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -65,6 +65,17 @@ public class LETTester {
|
|||
assertSExpressionsMatch(new LispNumber("2"), evaluateString(input));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nestedLetWithGlobals() {
|
||||
String before = "(setf x 92)";
|
||||
String input = "(let ((x 1)) (let ((y (+ 1 x))) y))";
|
||||
String after = "x";
|
||||
|
||||
assertSExpressionsMatch(new LispNumber("92"), evaluateString(before));
|
||||
assertSExpressionsMatch(new LispNumber("2"), evaluateString(input));
|
||||
assertSExpressionsMatch(new LispNumber("92"), evaluateString(after));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void alterGlobalVariableFromLet() {
|
||||
String before = "(setf x 1)";
|
||||
|
|
Loading…
Reference in New Issue