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>
<classpathentry kind="src" path="src"/>
<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">
<attributes>
<attribute name="owner.project.facets" value="java"/>
</attributes>
</classpathentry>
<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="output" path="build/classes"/>
</classpath>

View File

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

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.SuiteSetUp||14:27:52 Wed, Feb 22, 2017|
|LispInterpreter||14:24:41 Wed, Feb 22, 2017|

View File

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

View File

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

View File

@ -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)");
}

View File

@ -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)"));
}
}