User defined functions now set up their scope correctly

This commit is contained in:
Mike Cifelli 2017-02-27 12:00:24 -05:00
parent 462e5ea15e
commit bf40feadec
9 changed files with 111 additions and 49 deletions

View File

@ -2,14 +2,13 @@
<classpath> <classpath>
<classpathentry kind="src" path="src"/> <classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="test"/> <classpathentry kind="src" path="test"/>
<classpathentry kind="src" path="acceptance"/> <classpathentry kind="src" path="acctest"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes> <attributes>
<attribute name="owner.project.facets" value="java"/> <attribute name="owner.project.facets" value="java"/>
</attributes> </attributes>
</classpathentry> </classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/> <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
<classpathentry kind="lib" path="fitnesse/fitnesse-standalone.jar"/>
<classpathentry kind="lib" path="lib/recursion.jar"/> <classpathentry kind="lib" path="lib/recursion.jar"/>
<classpathentry kind="output" path="build/classes"/> <classpathentry kind="output" path="build/classes"/>
</classpath> </classpath>

View File

@ -6,23 +6,18 @@ import interpreter.*;
public class LispInterpreterFixture { public class LispInterpreterFixture {
private ByteArrayOutputStream outputStream; private static ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
private LispInterpreter interpreter; private static LispInterpreter interpreter = null;
public LispInterpreterFixture() throws IOException { static {
this.outputStream = new ByteArrayOutputStream();
this.interpreter = buildInterpreter();
}
private LispInterpreter buildInterpreter() {
LispInterpreterBuilder builder = LispInterpreterBuilderImpl.getInstance(); LispInterpreterBuilder builder = LispInterpreterBuilderImpl.getInstance();
builder.setOutput(new PrintStream(outputStream)); builder.setOutput(new PrintStream(outputStream));
builder.setErrorOutput(new PrintStream(outputStream)); builder.setErrorOutput(new PrintStream(outputStream));
builder.setNotInteractive(); builder.setNotInteractive();
builder.setTerminationFunction(() -> System.exit(0)); builder.setTerminationFunction(() -> {});
builder.setErrorTerminationFunction(() -> System.exit(1)); builder.setErrorTerminationFunction(() -> {});
return builder.build(); interpreter = builder.build();
} }
public String evaluate(String input) throws IOException { public String evaluate(String input) throws IOException {

View File

@ -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 |

View File

@ -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 |

View File

@ -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.TestOne||09:26:08 Fri, Feb 24, 2017|
|LispInterpreter.SuiteSetUp||14:27:52 Wed, Feb 22, 2017| |LispInterpreter.SuiteSetUp||14:27:52 Wed, Feb 22, 2017|
|LispInterpreter||14:24:41 Wed, Feb 22, 2017| |LispInterpreter||14:24:41 Wed, Feb 22, 2017|

View File

@ -1,3 +1,5 @@
(defun fact (x) (defun fact (x)
(cond ((> 2 x) 1) (if (< x 2) 1
(t (* x (fact (- x 1)))))) (* x (fact (- x 1)))
)
)

View File

@ -34,26 +34,30 @@ public class UserDefinedFunction extends LispFunction {
public SExpression call(Cons argumentList) { public SExpression call(Cons argumentList) {
argumentValidator.validate(argumentList); argumentValidator.validate(argumentList);
bindParameterValues(argumentList); return evaluateInFunctionScope(argumentList);
}
private SExpression evaluateInFunctionScope(Cons argumentList) {
SymbolTable callingScope = executionContext.getScope(); SymbolTable callingScope = executionContext.getScope();
executionContext.setScope(functionScope); SymbolTable executionScope = bindParameterValuesToFunctionScope(argumentList);
executionContext.setScope(executionScope);
SExpression lastEvaluation = evaluateBody(); SExpression lastEvaluation = evaluateBody();
executionContext.setScope(callingScope); executionContext.setScope(callingScope);
releaseParameterValues();
return lastEvaluation; return lastEvaluation;
} }
private void bindParameterValues(Cons argumentList) { private SymbolTable bindParameterValuesToFunctionScope(Cons argumentList) {
functionScope = new SymbolTable(functionScope); SymbolTable executionScope = new SymbolTable(functionScope);
for (String parameter : formalParameters) { for (String parameter : formalParameters) {
SExpression currentArg = argumentList.getFirst(); SExpression currentArg = argumentList.getFirst();
functionScope.put(parameter, currentArg); executionScope.put(parameter, currentArg);
argumentList = (Cons) argumentList.getRest(); argumentList = (Cons) argumentList.getRest();
} }
return executionScope;
} }
private SExpression evaluateBody() { private SExpression evaluateBody() {
@ -65,10 +69,6 @@ public class UserDefinedFunction extends LispFunction {
return lastEvaluation; return lastEvaluation;
} }
private void releaseParameterValues() {
functionScope = new SymbolTable(functionScope.getParent());
}
public Cons getLambdaExpression() { public Cons getLambdaExpression() {
return lambdaExpression; return lambdaExpression;
} }

View File

@ -42,7 +42,7 @@ public class DEFUNTester {
} }
@Test @Test
public void testDefun() { public void defun() {
String input = "(defun f () t)"; String input = "(defun f () t)";
assertSExpressionsMatch(parseString("f"), evaluateString(input)); assertSExpressionsMatch(parseString("f"), evaluateString(input));
@ -50,7 +50,7 @@ public class DEFUNTester {
} }
@Test @Test
public void testDefunWithEmptyBody() { public void defunWithEmptyBody() {
String input = "(defun f ())"; String input = "(defun f ())";
assertSExpressionsMatch(parseString("f"), evaluateString(input)); assertSExpressionsMatch(parseString("f"), evaluateString(input));
@ -58,11 +58,34 @@ public class DEFUNTester {
} }
@Test @Test
public void testDefunEvaluatesArguments() { public void defunEvaluatesArguments() {
evaluateString("(defun f (x) (car x))"); evaluateString("(defun f (x) (car x))");
assertSExpressionsMatch(parseString("1"), evaluateString("(f '(1 2 3))")); 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 @Test
public void redefineFunction_DisplaysWarning() { public void redefineFunction_DisplaysWarning() {
String input = "(defun myFunction () nil)"; String input = "(defun myFunction () nil)";
@ -82,22 +105,22 @@ public class DEFUNTester {
} }
@Test(expected = DottedArgumentListException.class) @Test(expected = DottedArgumentListException.class)
public void testDefunWithDottedLambdaList() { public void defunWithDottedLambdaList() {
evaluateString("(funcall 'defun 'x (cons 'a 'b) ())"); evaluateString("(funcall 'defun 'x (cons 'a 'b) ())");
} }
@Test(expected = BadArgumentTypeException.class) @Test(expected = BadArgumentTypeException.class)
public void testDefunWithNonSymbolName() { public void defunWithNonSymbolName() {
evaluateString("(defun 1 () ())"); evaluateString("(defun 1 () ())");
} }
@Test(expected = BadArgumentTypeException.class) @Test(expected = BadArgumentTypeException.class)
public void testDefunWithBadLambdaList() { public void defunWithBadLambdaList() {
evaluateString("(defun x a ())"); evaluateString("(defun x a ())");
} }
@Test(expected = TooFewArgumentsException.class) @Test(expected = TooFewArgumentsException.class)
public void testDefunWithTooFewArguments() { public void defunWithTooFewArguments() {
evaluateString("(defun x)"); evaluateString("(defun x)");
} }

View File

@ -11,14 +11,14 @@ import sexpression.*;
public class LAMBDATester { public class LAMBDATester {
@Test @Test
public void testLambda() { public void lambda() {
String input = "(lambda (x) x)"; String input = "(lambda (x) x)";
assertSExpressionsMatch(parseString("(LAMBDA (X) X)"), evaluateString(input)); assertSExpressionsMatch(parseString("(LAMBDA (X) X)"), evaluateString(input));
} }
@Test @Test
public void testLambdaWithNoBody() { public void lambdaWithNoBody() {
String input = "(lambda ())"; String input = "(lambda ())";
assertSExpressionsMatch(parseString("(LAMBDA ())"), evaluateString(input)); assertSExpressionsMatch(parseString("(LAMBDA ())"), evaluateString(input));
@ -38,7 +38,7 @@ public class LAMBDATester {
} }
@Test @Test
public void testCreateLambdaExpression() { public void createLambdaExpression() {
Cons lambdaExpression = new Cons(new Symbol("LAMBDA"), Cons lambdaExpression = new Cons(new Symbol("LAMBDA"),
new Cons(Nil.getInstance(), new Cons(Nil.getInstance(), Nil.getInstance()))); new Cons(Nil.getInstance(), new Cons(Nil.getInstance(), Nil.getInstance())));
@ -47,40 +47,40 @@ public class LAMBDATester {
} }
@Test(expected = DottedArgumentListException.class) @Test(expected = DottedArgumentListException.class)
public void testLambdaWithDottedArgumentList() { public void lambdaWithDottedArgumentList() {
String input = "(apply 'lambda (cons '(x) 1))"; String input = "(apply 'lambda (cons '(x) 1))";
evaluateString(input); evaluateString(input);
} }
@Test(expected = DottedArgumentListException.class) @Test(expected = DottedArgumentListException.class)
public void testLambdaWithDottedLambdaList() { public void lambdaWithDottedLambdaList() {
String input = "(funcall 'lambda (cons 'a 'b) ())"; String input = "(funcall 'lambda (cons 'a 'b) ())";
evaluateString(input); evaluateString(input);
} }
@Test(expected = DottedArgumentListException.class) @Test(expected = DottedArgumentListException.class)
public void testCreateFunctionWithDottedArgumentList() { public void createFunctionWithDottedArgumentList() {
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));
LAMBDA.createFunction(lambdaExpression); LAMBDA.createFunction(lambdaExpression);
} }
@Test(expected = BadArgumentTypeException.class) @Test(expected = BadArgumentTypeException.class)
public void testCreateFunctionWithNonList() { public void createFunctionWithNonList() {
Cons lambdaExpression = new Cons(new Symbol("LAMBDA"), LispNumber.ONE); Cons lambdaExpression = new Cons(new Symbol("LAMBDA"), LispNumber.ONE);
LAMBDA.createFunction(lambdaExpression); LAMBDA.createFunction(lambdaExpression);
} }
@Test(expected = BadArgumentTypeException.class) @Test(expected = BadArgumentTypeException.class)
public void testLambdaWithNonSymbolParameter() { public void lambdaWithNonSymbolParameter() {
evaluateString("(lambda (1) ())"); evaluateString("(lambda (1) ())");
} }
@Test(expected = TooFewArgumentsException.class) @Test(expected = TooFewArgumentsException.class)
public void testLambdaWithTooFewArguments() { public void lambdaWithTooFewArguments() {
evaluateString("(lambda)"); evaluateString("(lambda)");
} }
@ -108,4 +108,14 @@ public class LAMBDATester {
evaluateString("((lambda (x y) x) 1 2 3)"); 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)"));
}
} }