Update argument validation and added unit tests

Fixed some bugs in LET and LAMBDA

Refactored the code in UserDefinedFunction
This commit is contained in:
Mike Cifelli 2017-02-04 13:51:10 -05:00
parent d7ca5d09da
commit 0a5228d5a7
8 changed files with 106 additions and 66 deletions

View File

@ -124,16 +124,16 @@ public class ArgumentValidator {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private String functionName; private String functionName;
private Cons originalSExpression; private Cons argumentList;
public TooFewArgumentsException(String functionName, Cons argumentList) { public TooFewArgumentsException(String functionName, Cons argumentList) {
this.functionName = functionName; this.functionName = functionName;
this.originalSExpression = new Cons(new Symbol(this.functionName), argumentList); this.argumentList = argumentList;
} }
@Override @Override
public String getMessage() { 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 static final long serialVersionUID = 1L;
private String functionName; private String functionName;
private Cons originalSExpression; private Cons argumentList;
public TooManyArgumentsException(String functionName, Cons argumentList) { public TooManyArgumentsException(String functionName, Cons argumentList) {
this.functionName = functionName; this.functionName = functionName;
this.originalSExpression = new Cons(new Symbol(this.functionName), argumentList); this.argumentList = argumentList;
} }
@Override @Override
public String getMessage() { 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 static final long serialVersionUID = 1L;
private String functionName; private String functionName;
private Cons originalSExpression; private Cons argumentList;
public DottedArgumentListException(String functionName, Cons argumentList) { public DottedArgumentListException(String functionName, Cons argumentList) {
this.functionName = functionName; this.functionName = functionName;
this.originalSExpression = new Cons(new Symbol(this.functionName), argumentList); this.argumentList = argumentList;
} }
@Override @Override
public String getMessage() { 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);
} }
} }

View File

@ -12,7 +12,7 @@ public class UserDefinedFunction extends LispFunction {
private Cons body; private Cons body;
private Cons lambdaExpression; private Cons lambdaExpression;
private ExecutionContext executionContext; private ExecutionContext executionContext;
private SymbolTable scope; private SymbolTable functionScope;
private ArrayList<String> formalParameters; private ArrayList<String> formalParameters;
private ArgumentValidator argumentValidator; private ArgumentValidator argumentValidator;
@ -21,7 +21,7 @@ public class UserDefinedFunction extends LispFunction {
this.body = body; this.body = body;
this.lambdaExpression = new Cons(new Symbol(name), new Cons(lambdaList, body)); this.lambdaExpression = new Cons(new Symbol(name), new Cons(lambdaList, body));
this.executionContext = ExecutionContext.getInstance(); this.executionContext = ExecutionContext.getInstance();
this.scope = executionContext.getScope(); this.functionScope = executionContext.getScope();
for (this.formalParameters = new ArrayList<>(); lambdaList.consp(); lambdaList = (Cons) lambdaList.getCdr()) for (this.formalParameters = new ArrayList<>(); lambdaList.consp(); lambdaList = (Cons) lambdaList.getCdr())
this.formalParameters.add(lambdaList.getCar().toString()); this.formalParameters.add(lambdaList.getCar().toString());
@ -33,49 +33,33 @@ public class UserDefinedFunction extends LispFunction {
public SExpression call(Cons argumentList) { public SExpression call(Cons argumentList) {
argumentValidator.validate(argumentList); argumentValidator.validate(argumentList);
// bind the values of the arguments to the formal parameter names
bindParameterValues(argumentList); bindParameterValues(argumentList);
// store the environment of the S-expression that called this function
// (the current environment)
SymbolTable callingScope = executionContext.getScope(); SymbolTable callingScope = executionContext.getScope();
executionContext.setScope(functionScope);
// replace the current environment with the environment of this SExpression lastEvaluation = null;
// function
executionContext.setScope(scope);
Cons currentSExpression = body; for (Cons expression = body; expression.consp(); expression = (Cons) expression.getCdr())
SExpression lastValue = null; 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); executionContext.setScope(callingScope);
// remove the bindings of the arguments to the formal parameter names
// in the environment of this function
releaseParameterValues(); releaseParameterValues();
return lastValue; return lastEvaluation;
} }
private void bindParameterValues(Cons argumentList) { private void bindParameterValues(Cons argumentList) {
scope = new SymbolTable(scope); functionScope = new SymbolTable(functionScope);
for (String parameter : formalParameters) { for (String parameter : formalParameters) {
SExpression currentArg = argumentList.getCar(); SExpression currentArg = argumentList.getCar();
scope.put(parameter, currentArg); functionScope.put(parameter, currentArg);
argumentList = (Cons) argumentList.getCdr(); argumentList = (Cons) argumentList.getCdr();
} }
} }
private void releaseParameterValues() { private void releaseParameterValues() {
scope = new SymbolTable(scope.getParent()); functionScope = new SymbolTable(functionScope.getParent());
} }
public Cons getLambdaExpression() { public Cons getLambdaExpression() {

View File

@ -20,10 +20,10 @@ public class DEFUN extends LispFunction {
this.argumentValidator.setMinimumNumberOfArguments(3); this.argumentValidator.setMinimumNumberOfArguments(3);
this.argumentValidator.setFirstArgumentExpectedType(Symbol.class); 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.lambdaListIsListValidator.setEveryArgumentExpectedType(Cons.class);
this.lambdaListValidator = new ArgumentValidator("DEFUN|lambda_list|"); this.lambdaListValidator = new ArgumentValidator("DEFUN|parameter|");
this.lambdaListValidator.setEveryArgumentExpectedType(Symbol.class); this.lambdaListValidator.setEveryArgumentExpectedType(Symbol.class);
this.environment = Environment.getInstance(); this.environment = Environment.getInstance();

View File

@ -19,7 +19,7 @@ public class LAMBDA extends LispFunction {
public static UserDefinedFunction createFunction(Cons lambdaExpression) { public static UserDefinedFunction createFunction(Cons lambdaExpression) {
SExpression cdr = lambdaExpression.getCdr(); SExpression cdr = lambdaExpression.getCdr();
ArgumentValidator lambdaValidator = new ArgumentValidator(":LAMBDA"); ArgumentValidator lambdaValidator = new ArgumentValidator("LAMBDA|create|");
lambdaValidator.setEveryArgumentExpectedType(Cons.class); lambdaValidator.setEveryArgumentExpectedType(Cons.class);
lambdaValidator.validate(LIST.makeList(cdr)); lambdaValidator.validate(LIST.makeList(cdr));
@ -29,11 +29,15 @@ public class LAMBDA extends LispFunction {
} }
private ArgumentValidator argumentValidator; private ArgumentValidator argumentValidator;
private ArgumentValidator lambdaListValidator;
public LAMBDA() { public LAMBDA() {
this.argumentValidator = new ArgumentValidator("LAMBDA"); this.argumentValidator = new ArgumentValidator("LAMBDA");
this.argumentValidator.setFirstArgumentExpectedType(Cons.class); this.argumentValidator.setFirstArgumentExpectedType(Cons.class);
this.argumentValidator.setMinimumNumberOfArguments(2); this.argumentValidator.setMinimumNumberOfArguments(2);
this.lambdaListValidator = new ArgumentValidator("LAMBDA|lambda-list|");
this.lambdaListValidator.setEveryArgumentExpectedType(Symbol.class);
} }
public LambdaExpression call(Cons argumentList) { public LambdaExpression call(Cons argumentList) {
@ -43,6 +47,8 @@ public class LAMBDA extends LispFunction {
Cons lambdaList = (Cons) car; Cons lambdaList = (Cons) car;
Cons body = (Cons) argumentList.getCdr(); Cons body = (Cons) argumentList.getCdr();
lambdaListValidator.validate(lambdaList);
UserDefinedFunction function = new UserDefinedFunction(":LAMBDA", lambdaList, body); UserDefinedFunction function = new UserDefinedFunction(":LAMBDA", lambdaList, body);
return new LambdaExpression(makeOriginalLambdaExpression(argumentList), function); return new LambdaExpression(makeOriginalLambdaExpression(argumentList), function);

View File

@ -8,13 +8,22 @@ import table.*;
public class LET extends LispFunction { public class LET extends LispFunction {
private ArgumentValidator argumentValidator; private ArgumentValidator argumentValidator;
private ArgumentValidator variableDefinitionListValidator;
private ArgumentValidator pairValidator;
private ExecutionContext executionContext; private ExecutionContext executionContext;
private SymbolTable localScope;
public LET() { public LET() {
this.argumentValidator = new ArgumentValidator("LET"); this.argumentValidator = new ArgumentValidator("LET");
this.argumentValidator.setMinimumNumberOfArguments(1); this.argumentValidator.setMinimumNumberOfArguments(1);
this.argumentValidator.setFirstArgumentExpectedType(Cons.class); 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(); this.executionContext = ExecutionContext.getInstance();
} }
@ -27,15 +36,15 @@ public class LET extends LispFunction {
} }
private SExpression evaluateInScope(Cons variableDefinitions, Cons body) { private SExpression evaluateInScope(Cons variableDefinitions, Cons body) {
createLocalScope(variableDefinitions); SymbolTable localScope = createLocalScope(variableDefinitions);
SExpression lastEvaluation = evaluateBody(body); SExpression lastEvaluation = evaluateBody(body);
restorePreviousScope(); restorePreviousScope(localScope);
return lastEvaluation; return lastEvaluation;
} }
private SymbolTable createLocalScope(Cons variableDefinitions) { private SymbolTable createLocalScope(Cons variableDefinitions) {
localScope = new SymbolTable(executionContext.getScope()); SymbolTable localScope = new SymbolTable(executionContext.getScope());
addVariablesToScope(localScope, variableDefinitions); addVariablesToScope(localScope, variableDefinitions);
executionContext.setScope(localScope); executionContext.setScope(localScope);
@ -43,12 +52,14 @@ public class LET extends LispFunction {
} }
private void addVariablesToScope(SymbolTable scope, Cons variableDefinitions) { private void addVariablesToScope(SymbolTable scope, Cons variableDefinitions) {
validateAllPairsAreLists(variableDefinitions); variableDefinitionListValidator.validate(variableDefinitions);
for (; variableDefinitions.consp(); variableDefinitions = (Cons) variableDefinitions.getCdr()) { for (; variableDefinitions.consp(); variableDefinitions = (Cons) variableDefinitions.getCdr())
Cons symbolValuePair = (Cons) variableDefinitions.getCar(); addPairToScope((Cons) variableDefinitions.getCar(), scope);
}
validatePair(symbolValuePair); private void addPairToScope(Cons symbolValuePair, SymbolTable scope) {
pairValidator.validate(symbolValuePair);
Cons restOfPair = (Cons) symbolValuePair.getCdr(); Cons restOfPair = (Cons) symbolValuePair.getCdr();
SExpression symbol = symbolValuePair.getCar(); SExpression symbol = symbolValuePair.getCar();
@ -56,20 +67,6 @@ public class LET extends LispFunction {
scope.put(symbol.toString(), EVAL.eval(value)); 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);
}
private void validatePair(Cons symbolValuePair) {
ArgumentValidator pairValidator = new ArgumentValidator("LET|pair|");
pairValidator.setExactNumberOfArguments(2);
pairValidator.setFirstArgumentExpectedType(Symbol.class);
pairValidator.validate((Cons) symbolValuePair);
}
private SExpression evaluateBody(Cons body) { private SExpression evaluateBody(Cons body) {
SExpression lastEvaluation = Nil.getInstance(); SExpression lastEvaluation = Nil.getInstance();
@ -80,7 +77,7 @@ public class LET extends LispFunction {
return lastEvaluation; return lastEvaluation;
} }
private void restorePreviousScope() { private void restorePreviousScope(SymbolTable localScope) {
executionContext.setScope(localScope.getParent()); executionContext.setScope(localScope.getParent());
} }

View File

@ -37,6 +37,12 @@ public class EVALTester {
assertSExpressionsMatch(parseString(symbol), EVAL.lookupSymbol(symbol)); assertSExpressionsMatch(parseString(symbol), EVAL.lookupSymbol(symbol));
} }
@Test
public void testLookupNil() {
String symbol = "NIL";
assertSExpressionsMatch(parseString(symbol), EVAL.lookupSymbol(symbol));
}
@Test @Test
public void testLookupUndefinedSymbol() { public void testLookupUndefinedSymbol() {
assertNull(EVAL.lookupSymbol("undefined")); assertNull(EVAL.lookupSymbol("undefined"));

View File

@ -46,6 +46,13 @@ public class LAMBDATester {
evaluateString(input); evaluateString(input);
} }
@Test(expected = DottedArgumentListException.class)
public void testLambdaWithDottedLambdaList() {
String input = "(funcall 'lambda (cons 'a 'b) ())";
evaluateString(input);
}
@Test(expected = DottedArgumentListException.class) @Test(expected = DottedArgumentListException.class)
public void testCreateFunctionWithDottedArgumentList() { public void testCreateFunctionWithDottedArgumentList() {
Cons lambdaExpression = new Cons(new Symbol("LAMBDA"), new Cons(Nil.getInstance(), LispNumber.ONE)); Cons lambdaExpression = new Cons(new Symbol("LAMBDA"), new Cons(Nil.getInstance(), LispNumber.ONE));
@ -60,9 +67,38 @@ public class LAMBDATester {
LAMBDA.createFunction(lambdaExpression); LAMBDA.createFunction(lambdaExpression);
} }
@Test(expected = BadArgumentTypeException.class)
public void testLambdaWithNonSymbolParameter() {
evaluateString("(lambda (1) ())");
}
@Test(expected = TooFewArgumentsException.class) @Test(expected = TooFewArgumentsException.class)
public void testLambdaWithTooFewArguments() { public void testLambdaWithTooFewArguments() {
evaluateString("(lambda ())"); 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)");
}
} }

View File

@ -65,6 +65,17 @@ public class LETTester {
assertSExpressionsMatch(new LispNumber("2"), evaluateString(input)); 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 @Test
public void alterGlobalVariableFromLet() { public void alterGlobalVariableFromLet() {
String before = "(setf x 1)"; String before = "(setf x 1)";