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 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"));
|
||||||
|
|
|
@ -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)");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)";
|
||||||
|
|
Loading…
Reference in New Issue