Narrow the behavior of RECUR
This commit is contained in:
parent
b4229c6ac1
commit
6cf017734f
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
(defun many-years-with-no-interest-rate ()
|
(defun many-years-with-no-interest-rate ()
|
||||||
(setq compounder (interest-compounder 1000 0))
|
(setq compounder (interest-compounder 1000 0))
|
||||||
(call compounder :move-forward-years 83)
|
(call compounder :move-forward-years 1803)
|
||||||
(call assertions :assert= 1000 (call compounder :get-principal)))
|
(call assertions :assert= 1000 (call compounder :get-principal)))
|
||||||
|
|
||||||
(defun no-years-with-positive-interest-rate ()
|
(defun no-years-with-positive-interest-rate ()
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
(setq principal
|
(setq principal
|
||||||
(+ principal
|
(+ principal
|
||||||
(call static :percent-of-number principal interest-rate)))
|
(call static :percent-of-number principal interest-rate)))
|
||||||
(call private :compound-interest (- years 1)))))))
|
(recur (- years 1)))))))
|
||||||
|
|
||||||
(setq public
|
(setq public
|
||||||
(dlambda
|
(dlambda
|
||||||
|
|
|
@ -89,17 +89,16 @@ public class UserDefinedFunction extends LispFunction {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SExpression call(Cons argumentList) {
|
public SExpression call(Cons argumentList) {
|
||||||
return callTailRecursive(argumentList).invoke();
|
executionContext.pushFunctionCall(this);
|
||||||
|
SExpression result = callTailRecursive(argumentList).invoke();
|
||||||
|
executionContext.popFunctionCall();
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TailCall<SExpression> callTailRecursive(Cons argumentList) {
|
private TailCall<SExpression> callTailRecursive(Cons argumentList) {
|
||||||
argumentValidator.validate(argumentList);
|
argumentValidator.validate(argumentList);
|
||||||
|
|
||||||
// if recur
|
|
||||||
// clear recur indicator
|
|
||||||
// validate function list
|
|
||||||
// do not evaluate arguments - will have been done already if necessary
|
|
||||||
// tailCall(evaluatedArguments)
|
|
||||||
SExpression result = evaluateInFunctionScope(argumentList);
|
SExpression result = evaluateInFunctionScope(argumentList);
|
||||||
|
|
||||||
if (executionContext.isRecur()) {
|
if (executionContext.isRecur()) {
|
||||||
|
|
|
@ -12,7 +12,6 @@ import error.LispException;
|
||||||
import function.ArgumentValidator;
|
import function.ArgumentValidator;
|
||||||
import function.FunctionNames;
|
import function.FunctionNames;
|
||||||
import function.LispFunction;
|
import function.LispFunction;
|
||||||
import function.UserDefinedFunction;
|
|
||||||
import sexpression.BackquoteExpression;
|
import sexpression.BackquoteExpression;
|
||||||
import sexpression.Cons;
|
import sexpression.Cons;
|
||||||
import sexpression.LambdaExpression;
|
import sexpression.LambdaExpression;
|
||||||
|
@ -29,7 +28,7 @@ public class EVAL extends LispFunction {
|
||||||
return lookupFunction("EVAL").call(argumentList);
|
return lookupFunction("EVAL").call(argumentList);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Cons evalArgumentList(Cons argumentList) {
|
public static Cons evalRecurArgumentList(Cons argumentList) {
|
||||||
return ((EVAL) lookupFunction("EVAL")).evaluateArgumentList(argumentList);
|
return ((EVAL) lookupFunction("EVAL")).evaluateArgumentList(argumentList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,8 +115,11 @@ public class EVAL extends LispFunction {
|
||||||
}
|
}
|
||||||
|
|
||||||
private SExpression callFunction(LispFunction function, Cons argumentList) {
|
private SExpression callFunction(LispFunction function, Cons argumentList) {
|
||||||
if (function instanceof UserDefinedFunction)
|
|
||||||
executionContext.pushFunctionCall(function);
|
// if (executionContext.isRecur()) {
|
||||||
|
// executionContext.popFunctionCall(); // FIXME - clear this in repl?
|
||||||
|
// throw new RecurNotInTailPositionException();
|
||||||
|
// }
|
||||||
|
|
||||||
if (function.isArgumentListEvaluated())
|
if (function.isArgumentListEvaluated())
|
||||||
argumentList = evaluateArgumentList(argumentList);
|
argumentList = evaluateArgumentList(argumentList);
|
||||||
|
@ -127,9 +129,6 @@ public class EVAL extends LispFunction {
|
||||||
if (function.isMacro())
|
if (function.isMacro())
|
||||||
result = eval(result);
|
result = eval(result);
|
||||||
|
|
||||||
if (function instanceof UserDefinedFunction)
|
|
||||||
executionContext.popFunctionCall();
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,4 +207,14 @@ public class EVAL extends LispFunction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class RecurNotInTailPositionException extends LispException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessage() {
|
||||||
|
return "recur not in tail position";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package function.builtin.special;
|
package function.builtin.special;
|
||||||
|
|
||||||
import static function.builtin.EVAL.evalArgumentList;
|
import static function.builtin.EVAL.evalRecurArgumentList;
|
||||||
|
|
||||||
import error.LispException;
|
import error.LispException;
|
||||||
import function.ArgumentValidator;
|
import function.ArgumentValidator;
|
||||||
|
@ -24,19 +24,20 @@ public class RECUR extends LispSpecialFunction {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SExpression call(Cons argumentList) {
|
public SExpression call(Cons argumentList) {
|
||||||
|
if (executionContext.isRecur())
|
||||||
|
throw new NestedRecurException();
|
||||||
|
|
||||||
|
if (!executionContext.isInFunctionCall())
|
||||||
|
throw new RecurOutsideOfFunctionException();
|
||||||
|
|
||||||
argumentValidator.validate(argumentList);
|
argumentValidator.validate(argumentList);
|
||||||
|
|
||||||
if (!executionContext.isInFunctionCall()) {
|
executionContext.setRecur();
|
||||||
throw new RecurOutsideOfFunctionException();
|
|
||||||
}
|
|
||||||
|
|
||||||
LispFunction currentFunction = executionContext.getCurrentFunction();
|
LispFunction currentFunction = executionContext.getCurrentFunction();
|
||||||
Cons recurArguments = argumentList;
|
Cons recurArguments = argumentList;
|
||||||
|
|
||||||
if (currentFunction.isArgumentListEvaluated())
|
if (currentFunction.isArgumentListEvaluated())
|
||||||
recurArguments = evalArgumentList(argumentList);
|
recurArguments = evalRecurArgumentList(argumentList);
|
||||||
|
|
||||||
executionContext.setRecur();
|
|
||||||
|
|
||||||
return recurArguments;
|
return recurArguments;
|
||||||
}
|
}
|
||||||
|
@ -51,4 +52,14 @@ public class RECUR extends LispSpecialFunction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class NestedRecurException extends LispException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessage() {
|
||||||
|
return "nested call to recur";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,9 @@ import static testutil.TestUtilities.parseString;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import function.ArgumentValidator.BadArgumentTypeException;
|
import function.ArgumentValidator.BadArgumentTypeException;
|
||||||
|
import function.ArgumentValidator.TooManyArgumentsException;
|
||||||
|
import function.builtin.EVAL.RecurNotInTailPositionException;
|
||||||
|
import function.builtin.special.RECUR.NestedRecurException;
|
||||||
import function.builtin.special.RECUR.RecurOutsideOfFunctionException;
|
import function.builtin.special.RECUR.RecurOutsideOfFunctionException;
|
||||||
import testutil.SymbolAndFunctionCleaner;
|
import testutil.SymbolAndFunctionCleaner;
|
||||||
|
|
||||||
|
@ -24,16 +27,57 @@ public class RECURTest extends SymbolAndFunctionCleaner {
|
||||||
evaluateString("(recur)");
|
evaluateString("(recur)");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void recurCallsCurrentFunction() {
|
|
||||||
evaluateString("(defun tail-recursive (n) (if (= n 0) 'PASS (recur (- n 1))))");
|
|
||||||
assertSExpressionsMatch(parseString("PASS"), evaluateString("(tail-recursive 900)"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = BadArgumentTypeException.class)
|
@Test(expected = BadArgumentTypeException.class)
|
||||||
public void recurInSpecialFunction_DoesNotEvaluateArguments() {
|
public void recurInSpecialFunction_DoesNotEvaluateArguments() {
|
||||||
evaluateString("(define-special tail-recursive (n) (if (= n 0) 'PASS (recur (- n 1))))");
|
evaluateString("(define-special tail-recursive (n) (if (= n 0) 'PASS (recur (- n 1))))");
|
||||||
evaluateString("(tail-recursive 900)");
|
evaluateString("(tail-recursive 900)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(expected = BadArgumentTypeException.class)
|
||||||
|
public void recurInMacro_DoesNotEvaluateArguments() {
|
||||||
|
evaluateString("(defmacro tail-recursive (n) (if (= n 0) 'PASS (recur (- n 1))))");
|
||||||
|
evaluateString("(tail-recursive 900)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = NestedRecurException.class)
|
||||||
|
public void nestedRecur_ThrowsException() {
|
||||||
|
evaluateString("(defun tail-recursive (n) (if (= n 0) 'PASS (recur (recur (- n 1)))))");
|
||||||
|
evaluateString("(tail-recursive 900)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = TooManyArgumentsException.class)
|
||||||
|
public void functionCallValidatesRecurArguments() {
|
||||||
|
evaluateString("(defun tail-recursive (n) (if (= n 0) 'PASS (recur (- n 1) 23)))");
|
||||||
|
evaluateString("(tail-recursive 900)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = RecurNotInTailPositionException.class)
|
||||||
|
public void recurInNonTailPosition_ThrowsException() {
|
||||||
|
evaluateString("(defun tail-recursive (n) (if (= n 0) 'PASS (list (recur (- n 1)))))");
|
||||||
|
evaluateString("(tail-recursive 900)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void recurCallsCurrentFunction() {
|
||||||
|
evaluateString("(defun tail-recursive (n) (if (= n 0) 'PASS (recur (- n 1))))");
|
||||||
|
assertSExpressionsMatch(parseString("PASS"), evaluateString("(tail-recursive 900)"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void recurCallsCurrentFunction_WithApply() {
|
||||||
|
evaluateString("(defun tail-recursive (n) (if (= n 0) 'PASS (recur (- n 1))))");
|
||||||
|
assertSExpressionsMatch(parseString("PASS"), evaluateString("(apply 'tail-recursive '(900))"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// recur with funcall
|
||||||
|
|
||||||
|
// recur non-tail in apply call
|
||||||
|
|
||||||
|
// recur with no args, alters global variable
|
||||||
|
|
||||||
|
// recur with anonymous function
|
||||||
|
|
||||||
|
// recur with nested anonymous function
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue