From bf40feadec510cc7b11c3a260db29fc091ac5159 Mon Sep 17 00:00:00 2001 From: Mike Cifelli Date: Mon, 27 Feb 2017 12:00:24 -0500 Subject: [PATCH] User defined functions now set up their scope correctly --- .classpath | 3 +- .../fixture/LispInterpreterFixture.java | 17 +++------ .../FitNesseRoot/LispInterpreter/Closure.wiki | 7 ---- .../LispInterpreter/LexicalClosures.wiki | 38 +++++++++++++++++++ fitnesse/FitNesseRoot/RecentChanges.wiki | 2 + lisp/fact.lisp | 6 ++- src/function/UserDefinedFunction.java | 22 +++++------ .../function/builtin/special/DEFUNTester.java | 37 ++++++++++++++---- .../builtin/special/LAMBDATester.java | 28 +++++++++----- 9 files changed, 111 insertions(+), 49 deletions(-) rename {acceptance => acctest}/fixture/LispInterpreterFixture.java (58%) delete mode 100644 fitnesse/FitNesseRoot/LispInterpreter/Closure.wiki create mode 100644 fitnesse/FitNesseRoot/LispInterpreter/LexicalClosures.wiki diff --git a/.classpath b/.classpath index dbaa807..1b824bc 100644 --- a/.classpath +++ b/.classpath @@ -2,14 +2,13 @@ - + - diff --git a/acceptance/fixture/LispInterpreterFixture.java b/acctest/fixture/LispInterpreterFixture.java similarity index 58% rename from acceptance/fixture/LispInterpreterFixture.java rename to acctest/fixture/LispInterpreterFixture.java index d2e61ce..da91fd9 100644 --- a/acceptance/fixture/LispInterpreterFixture.java +++ b/acctest/fixture/LispInterpreterFixture.java @@ -6,23 +6,18 @@ import interpreter.*; public class LispInterpreterFixture { - private ByteArrayOutputStream outputStream; - private LispInterpreter interpreter; + private static ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + private static LispInterpreter interpreter = null; - public LispInterpreterFixture() throws IOException { - this.outputStream = new ByteArrayOutputStream(); - this.interpreter = buildInterpreter(); - } - - private LispInterpreter buildInterpreter() { + static { LispInterpreterBuilder builder = LispInterpreterBuilderImpl.getInstance(); builder.setOutput(new PrintStream(outputStream)); builder.setErrorOutput(new PrintStream(outputStream)); builder.setNotInteractive(); - builder.setTerminationFunction(() -> System.exit(0)); - builder.setErrorTerminationFunction(() -> System.exit(1)); + builder.setTerminationFunction(() -> {}); + builder.setErrorTerminationFunction(() -> {}); - return builder.build(); + interpreter = builder.build(); } public String evaluate(String input) throws IOException { diff --git a/fitnesse/FitNesseRoot/LispInterpreter/Closure.wiki b/fitnesse/FitNesseRoot/LispInterpreter/Closure.wiki deleted file mode 100644 index 08289a5..0000000 --- a/fitnesse/FitNesseRoot/LispInterpreter/Closure.wiki +++ /dev/null @@ -1,7 +0,0 @@ ---- -Test ---- -| script | lisp interpreter fixture | -| evaluate | (defun adderx (x) (lambda (y) (+ x y))) | -| evaluate | (setf adder20 (adderx 20)) | -| check | evaluate | (funcall adder20 2) | 22 | \ No newline at end of file diff --git a/fitnesse/FitNesseRoot/LispInterpreter/LexicalClosures.wiki b/fitnesse/FitNesseRoot/LispInterpreter/LexicalClosures.wiki new file mode 100644 index 0000000..74d0a3c --- /dev/null +++ b/fitnesse/FitNesseRoot/LispInterpreter/LexicalClosures.wiki @@ -0,0 +1,38 @@ +--- +Test +--- +| script | lisp interpreter fixture | +| evaluate | (defun adderx (x) (lambda (y) (+ x y))) | +| evaluate | (setf adder20 (adderx 20)) | +| check | evaluate | (funcall adder20 2) | 22 | + + +| script | lisp interpreter fixture | +| evaluate |{{{!- + +(let ((direction 'up)) + (defun toggle-counter-direction () + (setq direction + (if (eq direction 'up) + 'down + 'up))) + + (defun counter-class () + (let ((counter 0)) + (lambda () + (if (eq direction 'up) + (setq counter (+ counter 1)) + (setq counter (- counter 1))))))) + -!}}}| +| evaluate | (setq my-counter (counter-class)) | +| check | evaluate | (funcall my-counter) | 1 | +| check | evaluate | (funcall my-counter) | 2 | +| check | evaluate | (funcall my-counter) | 3 | +| evaluate | (toggle-counter-direction) | +| check | evaluate | (funcall my-counter) | 2 | +| check | evaluate | (funcall my-counter) | 1 | +| check | evaluate | (funcall my-counter) | 0 | +| evaluate | (toggle-counter-direction) | +| check | evaluate | (funcall my-counter) | 1 | +| check | evaluate | (funcall my-counter) | 2 | +| check | evaluate | (funcall my-counter) | 3 | \ No newline at end of file diff --git a/fitnesse/FitNesseRoot/RecentChanges.wiki b/fitnesse/FitNesseRoot/RecentChanges.wiki index de0bfa9..59ef060 100644 --- a/fitnesse/FitNesseRoot/RecentChanges.wiki +++ b/fitnesse/FitNesseRoot/RecentChanges.wiki @@ -1,3 +1,5 @@ +|LispInterpreter.LexicalClosures||11:52:29 Mon, Feb 27, 2017| +|LispInterpreter.TestClosure||11:24:27 Mon, Feb 27, 2017| |LispInterpreter.TestOne||09:26:08 Fri, Feb 24, 2017| |LispInterpreter.SuiteSetUp||14:27:52 Wed, Feb 22, 2017| |LispInterpreter||14:24:41 Wed, Feb 22, 2017| diff --git a/lisp/fact.lisp b/lisp/fact.lisp index 2ed5fc4..f639712 100644 --- a/lisp/fact.lisp +++ b/lisp/fact.lisp @@ -1,3 +1,5 @@ (defun fact (x) - (cond ((> 2 x) 1) - (t (* x (fact (- x 1)))))) + (if (< x 2) 1 + (* x (fact (- x 1))) + ) +) diff --git a/src/function/UserDefinedFunction.java b/src/function/UserDefinedFunction.java index 6648453..3194861 100644 --- a/src/function/UserDefinedFunction.java +++ b/src/function/UserDefinedFunction.java @@ -34,26 +34,30 @@ public class UserDefinedFunction extends LispFunction { public SExpression call(Cons argumentList) { argumentValidator.validate(argumentList); - bindParameterValues(argumentList); + return evaluateInFunctionScope(argumentList); + } + + private SExpression evaluateInFunctionScope(Cons argumentList) { SymbolTable callingScope = executionContext.getScope(); - executionContext.setScope(functionScope); + SymbolTable executionScope = bindParameterValuesToFunctionScope(argumentList); + executionContext.setScope(executionScope); SExpression lastEvaluation = evaluateBody(); - executionContext.setScope(callingScope); - releaseParameterValues(); return lastEvaluation; } - private void bindParameterValues(Cons argumentList) { - functionScope = new SymbolTable(functionScope); + private SymbolTable bindParameterValuesToFunctionScope(Cons argumentList) { + SymbolTable executionScope = new SymbolTable(functionScope); for (String parameter : formalParameters) { SExpression currentArg = argumentList.getFirst(); - functionScope.put(parameter, currentArg); + executionScope.put(parameter, currentArg); argumentList = (Cons) argumentList.getRest(); } + + return executionScope; } private SExpression evaluateBody() { @@ -65,10 +69,6 @@ public class UserDefinedFunction extends LispFunction { return lastEvaluation; } - private void releaseParameterValues() { - functionScope = new SymbolTable(functionScope.getParent()); - } - public Cons getLambdaExpression() { return lambdaExpression; } diff --git a/test/function/builtin/special/DEFUNTester.java b/test/function/builtin/special/DEFUNTester.java index 0bd3424..e8e985d 100644 --- a/test/function/builtin/special/DEFUNTester.java +++ b/test/function/builtin/special/DEFUNTester.java @@ -42,7 +42,7 @@ public class DEFUNTester { } @Test - public void testDefun() { + public void defun() { String input = "(defun f () t)"; assertSExpressionsMatch(parseString("f"), evaluateString(input)); @@ -50,7 +50,7 @@ public class DEFUNTester { } @Test - public void testDefunWithEmptyBody() { + public void defunWithEmptyBody() { String input = "(defun f ())"; assertSExpressionsMatch(parseString("f"), evaluateString(input)); @@ -58,11 +58,34 @@ public class DEFUNTester { } @Test - public void testDefunEvaluatesArguments() { + public void defunEvaluatesArguments() { evaluateString("(defun f (x) (car x))"); assertSExpressionsMatch(parseString("1"), evaluateString("(f '(1 2 3))")); } + @Test + public void defunRecursiveFunction() { + evaluateString("(defun fact (x) (if (< x 2) 1 (* x (fact (- x 1)))))"); + assertSExpressionsMatch(parseString("120"), evaluateString("(fact 5)")); + } + + @Test + public void defunTailRecursiveFunction() { + evaluateString("(defun fact-tail (x acc) (if (< x 2) acc (fact-tail (- x 1) (* x acc))))"); + assertSExpressionsMatch(parseString("120"), evaluateString("(fact-tail 5 1)")); + } + + @Test + public void defunSimpleClass() { + evaluateString("(defun counter-class () (let ((counter 0)) (lambda () (setf counter (+ 1 counter)))))"); + evaluateString("(setf my-counter (counter-class))"); + + assertSExpressionsMatch(parseString("1"), evaluateString("(funcall my-counter)")); + assertSExpressionsMatch(parseString("2"), evaluateString("(funcall my-counter)")); + assertSExpressionsMatch(parseString("3"), evaluateString("(funcall my-counter)")); + assertSExpressionsMatch(parseString("4"), evaluateString("(funcall my-counter)")); + } + @Test public void redefineFunction_DisplaysWarning() { String input = "(defun myFunction () nil)"; @@ -82,22 +105,22 @@ public class DEFUNTester { } @Test(expected = DottedArgumentListException.class) - public void testDefunWithDottedLambdaList() { + public void defunWithDottedLambdaList() { evaluateString("(funcall 'defun 'x (cons 'a 'b) ())"); } @Test(expected = BadArgumentTypeException.class) - public void testDefunWithNonSymbolName() { + public void defunWithNonSymbolName() { evaluateString("(defun 1 () ())"); } @Test(expected = BadArgumentTypeException.class) - public void testDefunWithBadLambdaList() { + public void defunWithBadLambdaList() { evaluateString("(defun x a ())"); } @Test(expected = TooFewArgumentsException.class) - public void testDefunWithTooFewArguments() { + public void defunWithTooFewArguments() { evaluateString("(defun x)"); } diff --git a/test/function/builtin/special/LAMBDATester.java b/test/function/builtin/special/LAMBDATester.java index f1a9f13..9dbe5ba 100644 --- a/test/function/builtin/special/LAMBDATester.java +++ b/test/function/builtin/special/LAMBDATester.java @@ -11,14 +11,14 @@ import sexpression.*; public class LAMBDATester { @Test - public void testLambda() { + public void lambda() { String input = "(lambda (x) x)"; assertSExpressionsMatch(parseString("(LAMBDA (X) X)"), evaluateString(input)); } @Test - public void testLambdaWithNoBody() { + public void lambdaWithNoBody() { String input = "(lambda ())"; assertSExpressionsMatch(parseString("(LAMBDA ())"), evaluateString(input)); @@ -38,7 +38,7 @@ public class LAMBDATester { } @Test - public void testCreateLambdaExpression() { + public void createLambdaExpression() { Cons lambdaExpression = new Cons(new Symbol("LAMBDA"), new Cons(Nil.getInstance(), new Cons(Nil.getInstance(), Nil.getInstance()))); @@ -47,40 +47,40 @@ public class LAMBDATester { } @Test(expected = DottedArgumentListException.class) - public void testLambdaWithDottedArgumentList() { + public void lambdaWithDottedArgumentList() { String input = "(apply 'lambda (cons '(x) 1))"; evaluateString(input); } @Test(expected = DottedArgumentListException.class) - public void testLambdaWithDottedLambdaList() { + public void lambdaWithDottedLambdaList() { String input = "(funcall 'lambda (cons 'a 'b) ())"; evaluateString(input); } @Test(expected = DottedArgumentListException.class) - public void testCreateFunctionWithDottedArgumentList() { + public void createFunctionWithDottedArgumentList() { Cons lambdaExpression = new Cons(new Symbol("LAMBDA"), new Cons(Nil.getInstance(), LispNumber.ONE)); LAMBDA.createFunction(lambdaExpression); } @Test(expected = BadArgumentTypeException.class) - public void testCreateFunctionWithNonList() { + public void createFunctionWithNonList() { Cons lambdaExpression = new Cons(new Symbol("LAMBDA"), LispNumber.ONE); LAMBDA.createFunction(lambdaExpression); } @Test(expected = BadArgumentTypeException.class) - public void testLambdaWithNonSymbolParameter() { + public void lambdaWithNonSymbolParameter() { evaluateString("(lambda (1) ())"); } @Test(expected = TooFewArgumentsException.class) - public void testLambdaWithTooFewArguments() { + public void lambdaWithTooFewArguments() { evaluateString("(lambda)"); } @@ -108,4 +108,14 @@ public class LAMBDATester { evaluateString("((lambda (x y) x) 1 2 3)"); } + @Test + public void lexicalClosure() { + evaluateString("(setf increment-count (let ((counter 0)) (lambda () (setf counter (+ 1 counter)))))"); + + assertSExpressionsMatch(parseString("1"), evaluateString("(funcall increment-count)")); + assertSExpressionsMatch(parseString("2"), evaluateString("(funcall increment-count)")); + assertSExpressionsMatch(parseString("3"), evaluateString("(funcall increment-count)")); + assertSExpressionsMatch(parseString("4"), evaluateString("(funcall increment-count)")); + } + }