From 0a5228d5a762eaf687e810839c1dba99a414c9df Mon Sep 17 00:00:00 2001 From: Mike Cifelli Date: Sat, 4 Feb 2017 13:51:10 -0500 Subject: [PATCH] Update argument validation and added unit tests Fixed some bugs in LET and LAMBDA Refactored the code in UserDefinedFunction --- src/function/ArgumentValidator.java | 18 +++---- src/function/UserDefinedFunction.java | 36 ++++--------- src/function/builtin/special/DEFUN.java | 4 +- src/function/builtin/special/LAMBDA.java | 8 ++- src/function/builtin/special/LET.java | 53 +++++++++---------- test/function/builtin/EVALTester.java | 6 +++ .../builtin/special/LAMBDATester.java | 36 +++++++++++++ test/function/builtin/special/LETTester.java | 11 ++++ 8 files changed, 106 insertions(+), 66 deletions(-) diff --git a/src/function/ArgumentValidator.java b/src/function/ArgumentValidator.java index f12951c..1460783 100644 --- a/src/function/ArgumentValidator.java +++ b/src/function/ArgumentValidator.java @@ -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); } } diff --git a/src/function/UserDefinedFunction.java b/src/function/UserDefinedFunction.java index 9623c45..525a803 100644 --- a/src/function/UserDefinedFunction.java +++ b/src/function/UserDefinedFunction.java @@ -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 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() { diff --git a/src/function/builtin/special/DEFUN.java b/src/function/builtin/special/DEFUN.java index afbcf36..2e0b7b3 100644 --- a/src/function/builtin/special/DEFUN.java +++ b/src/function/builtin/special/DEFUN.java @@ -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(); diff --git a/src/function/builtin/special/LAMBDA.java b/src/function/builtin/special/LAMBDA.java index 74b0230..384ba93 100644 --- a/src/function/builtin/special/LAMBDA.java +++ b/src/function/builtin/special/LAMBDA.java @@ -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); diff --git a/src/function/builtin/special/LET.java b/src/function/builtin/special/LET.java index 302b1bd..ef4f9b4 100644 --- a/src/function/builtin/special/LET.java +++ b/src/function/builtin/special/LET.java @@ -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()); } diff --git a/test/function/builtin/EVALTester.java b/test/function/builtin/EVALTester.java index e1f0e78..1e9e77b 100644 --- a/test/function/builtin/EVALTester.java +++ b/test/function/builtin/EVALTester.java @@ -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")); diff --git a/test/function/builtin/special/LAMBDATester.java b/test/function/builtin/special/LAMBDATester.java index 7e29c0e..47885d0 100644 --- a/test/function/builtin/special/LAMBDATester.java +++ b/test/function/builtin/special/LAMBDATester.java @@ -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)"); + } + } diff --git a/test/function/builtin/special/LETTester.java b/test/function/builtin/special/LETTester.java index bfc3814..6d1e0ca 100644 --- a/test/function/builtin/special/LETTester.java +++ b/test/function/builtin/special/LETTester.java @@ -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)";