Implement RECUR
This commit is contained in:
parent
cd551c5a1c
commit
b4229c6ac1
|
@ -2,11 +2,14 @@ package function;
|
||||||
|
|
||||||
import static function.builtin.EVAL.eval;
|
import static function.builtin.EVAL.eval;
|
||||||
import static java.text.MessageFormat.format;
|
import static java.text.MessageFormat.format;
|
||||||
|
import static recursion.tail.TailCalls.done;
|
||||||
import static sexpression.Nil.NIL;
|
import static sexpression.Nil.NIL;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import error.LispException;
|
import error.LispException;
|
||||||
|
import recursion.tail.TailCall;
|
||||||
|
import recursion.tail.TailCalls;
|
||||||
import sexpression.Cons;
|
import sexpression.Cons;
|
||||||
import sexpression.SExpression;
|
import sexpression.SExpression;
|
||||||
import sexpression.Symbol;
|
import sexpression.Symbol;
|
||||||
|
@ -25,7 +28,7 @@ public class UserDefinedFunction extends LispFunction {
|
||||||
private ArrayList<String> formalParameters;
|
private ArrayList<String> formalParameters;
|
||||||
private ArgumentValidator argumentValidator;
|
private ArgumentValidator argumentValidator;
|
||||||
private String keywordRestParameter;
|
private String keywordRestParameter;
|
||||||
private boolean isKeywordRest;
|
private boolean isKeywordRestPresent;
|
||||||
|
|
||||||
public UserDefinedFunction(String name, Cons lambdaList, Cons body) {
|
public UserDefinedFunction(String name, Cons lambdaList, Cons body) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
@ -34,7 +37,7 @@ public class UserDefinedFunction extends LispFunction {
|
||||||
this.executionContext = ExecutionContext.getInstance();
|
this.executionContext = ExecutionContext.getInstance();
|
||||||
this.functionScope = executionContext.getScope();
|
this.functionScope = executionContext.getScope();
|
||||||
this.keywordRestParameter = null;
|
this.keywordRestParameter = null;
|
||||||
this.isKeywordRest = false;
|
this.isKeywordRestPresent = false;
|
||||||
|
|
||||||
createFormalParameters(lambdaList);
|
createFormalParameters(lambdaList);
|
||||||
setupArgumentValidator();
|
setupArgumentValidator();
|
||||||
|
@ -60,7 +63,7 @@ public class UserDefinedFunction extends LispFunction {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Cons extractKeywordRestParameter(Cons lambdaList) {
|
private Cons extractKeywordRestParameter(Cons lambdaList) {
|
||||||
isKeywordRest = true;
|
isKeywordRestPresent = true;
|
||||||
lambdaList = advanceCons(lambdaList);
|
lambdaList = advanceCons(lambdaList);
|
||||||
keywordRestParameter = lambdaList.getFirst().toString();
|
keywordRestParameter = lambdaList.getFirst().toString();
|
||||||
lambdaList = advanceCons(lambdaList);
|
lambdaList = advanceCons(lambdaList);
|
||||||
|
@ -78,7 +81,7 @@ public class UserDefinedFunction extends LispFunction {
|
||||||
private void setupArgumentValidator() {
|
private void setupArgumentValidator() {
|
||||||
argumentValidator = new ArgumentValidator(this.name);
|
argumentValidator = new ArgumentValidator(this.name);
|
||||||
|
|
||||||
if (isKeywordRest)
|
if (isKeywordRestPresent)
|
||||||
argumentValidator.setMinimumNumberOfArguments(this.formalParameters.size());
|
argumentValidator.setMinimumNumberOfArguments(this.formalParameters.size());
|
||||||
else
|
else
|
||||||
argumentValidator.setExactNumberOfArguments(this.formalParameters.size());
|
argumentValidator.setExactNumberOfArguments(this.formalParameters.size());
|
||||||
|
@ -86,9 +89,25 @@ public class UserDefinedFunction extends LispFunction {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SExpression call(Cons argumentList) {
|
public SExpression call(Cons argumentList) {
|
||||||
|
return callTailRecursive(argumentList).invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
private TailCall<SExpression> callTailRecursive(Cons argumentList) {
|
||||||
argumentValidator.validate(argumentList);
|
argumentValidator.validate(argumentList);
|
||||||
|
|
||||||
return evaluateInFunctionScope(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);
|
||||||
|
|
||||||
|
if (executionContext.isRecur()) {
|
||||||
|
executionContext.clearRecur();
|
||||||
|
return TailCalls.tailCall(() -> callTailRecursive((Cons) result));
|
||||||
|
}
|
||||||
|
|
||||||
|
return done(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private SExpression evaluateInFunctionScope(Cons argumentList) {
|
private SExpression evaluateInFunctionScope(Cons argumentList) {
|
||||||
|
@ -111,7 +130,7 @@ public class UserDefinedFunction extends LispFunction {
|
||||||
argumentList = (Cons) argumentList.getRest();
|
argumentList = (Cons) argumentList.getRest();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isKeywordRest)
|
if (isKeywordRestPresent)
|
||||||
executionScope.put(keywordRestParameter, argumentList);
|
executionScope.put(keywordRestParameter, argumentList);
|
||||||
|
|
||||||
return executionScope;
|
return executionScope;
|
||||||
|
@ -120,7 +139,21 @@ public class UserDefinedFunction extends LispFunction {
|
||||||
private SExpression evaluateBody() {
|
private SExpression evaluateBody() {
|
||||||
SExpression lastEvaluation = NIL;
|
SExpression lastEvaluation = NIL;
|
||||||
|
|
||||||
for (Cons expression = body; expression.isCons(); expression = (Cons) expression.getRest())
|
// recur sets indicator in execution context
|
||||||
|
// eval can't be called when recur indicator is set - or error occurs (and recur indicator
|
||||||
|
// is cleared)
|
||||||
|
// recur checks to see if in function call, if not an error occurs (stack of objects
|
||||||
|
// (function call types) in execution context)
|
||||||
|
// if not, set recur indicator somewhere, (can't be in symbol table because LET creates
|
||||||
|
// scopes as well)
|
||||||
|
//
|
||||||
|
// on exception - clear function call counter and recur indicator in main loop
|
||||||
|
// recur evaluates its arguments (or not, according to function type) in its scope, then
|
||||||
|
// returns the list
|
||||||
|
//
|
||||||
|
// eval adds function call type to execution context stack of function calls when user
|
||||||
|
// defined function encountered
|
||||||
|
for (Cons expression = body; expression.isCons() /* and not recur indicator */; expression = (Cons) expression.getRest())
|
||||||
lastEvaluation = eval(expression.getFirst());
|
lastEvaluation = eval(expression.getFirst());
|
||||||
|
|
||||||
return lastEvaluation;
|
return lastEvaluation;
|
||||||
|
|
|
@ -12,6 +12,7 @@ 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;
|
||||||
|
@ -28,6 +29,10 @@ public class EVAL extends LispFunction {
|
||||||
return lookupFunction("EVAL").call(argumentList);
|
return lookupFunction("EVAL").call(argumentList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Cons evalArgumentList(Cons argumentList) {
|
||||||
|
return ((EVAL) lookupFunction("EVAL")).evaluateArgumentList(argumentList);
|
||||||
|
}
|
||||||
|
|
||||||
public static LispFunction lookupFunctionOrLambda(SExpression functionExpression) {
|
public static LispFunction lookupFunctionOrLambda(SExpression functionExpression) {
|
||||||
LispFunction function = lookupFunction(functionExpression.toString());
|
LispFunction function = lookupFunction(functionExpression.toString());
|
||||||
|
|
||||||
|
@ -58,10 +63,12 @@ public class EVAL extends LispFunction {
|
||||||
}
|
}
|
||||||
|
|
||||||
private ArgumentValidator argumentValidator;
|
private ArgumentValidator argumentValidator;
|
||||||
|
private ExecutionContext executionContext;
|
||||||
|
|
||||||
public EVAL(String name) {
|
public EVAL(String name) {
|
||||||
this.argumentValidator = new ArgumentValidator(name);
|
this.argumentValidator = new ArgumentValidator(name);
|
||||||
this.argumentValidator.setExactNumberOfArguments(1);
|
this.argumentValidator.setExactNumberOfArguments(1);
|
||||||
|
this.executionContext = ExecutionContext.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -109,6 +116,9 @@ 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 (function.isArgumentListEvaluated())
|
if (function.isArgumentListEvaluated())
|
||||||
argumentList = evaluateArgumentList(argumentList);
|
argumentList = evaluateArgumentList(argumentList);
|
||||||
|
|
||||||
|
@ -117,6 +127,9 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ public class SET extends LispFunction {
|
||||||
private SymbolTable findScopeOfSymbol(SExpression symbol) {
|
private SymbolTable findScopeOfSymbol(SExpression symbol) {
|
||||||
SymbolTable table = executionContext.getScope();
|
SymbolTable table = executionContext.getScope();
|
||||||
|
|
||||||
while (!isSymbolInTable(symbol, table) && !isGlobalTable(table))
|
while (!isSymbolInTable(symbol, table) && !table.isGlobal())
|
||||||
table = table.getParent();
|
table = table.getParent();
|
||||||
|
|
||||||
return table;
|
return table;
|
||||||
|
@ -55,8 +55,4 @@ public class SET extends LispFunction {
|
||||||
return table.contains(symbol.toString());
|
return table.contains(symbol.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isGlobalTable(SymbolTable table) {
|
|
||||||
return table.getParent() == null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
package function.builtin.special;
|
||||||
|
|
||||||
|
import static function.builtin.EVAL.evalArgumentList;
|
||||||
|
|
||||||
|
import error.LispException;
|
||||||
|
import function.ArgumentValidator;
|
||||||
|
import function.FunctionNames;
|
||||||
|
import function.LispFunction;
|
||||||
|
import function.LispSpecialFunction;
|
||||||
|
import sexpression.Cons;
|
||||||
|
import sexpression.SExpression;
|
||||||
|
import table.ExecutionContext;
|
||||||
|
|
||||||
|
@FunctionNames({ "RECUR" })
|
||||||
|
public class RECUR extends LispSpecialFunction {
|
||||||
|
|
||||||
|
private ArgumentValidator argumentValidator;
|
||||||
|
private ExecutionContext executionContext;
|
||||||
|
|
||||||
|
public RECUR(String name) {
|
||||||
|
this.argumentValidator = new ArgumentValidator(name);
|
||||||
|
this.executionContext = ExecutionContext.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SExpression call(Cons argumentList) {
|
||||||
|
argumentValidator.validate(argumentList);
|
||||||
|
|
||||||
|
if (!executionContext.isInFunctionCall()) {
|
||||||
|
throw new RecurOutsideOfFunctionException();
|
||||||
|
}
|
||||||
|
|
||||||
|
LispFunction currentFunction = executionContext.getCurrentFunction();
|
||||||
|
Cons recurArguments = argumentList;
|
||||||
|
|
||||||
|
if (currentFunction.isArgumentListEvaluated())
|
||||||
|
recurArguments = evalArgumentList(argumentList);
|
||||||
|
|
||||||
|
executionContext.setRecur();
|
||||||
|
|
||||||
|
return recurArguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RecurOutsideOfFunctionException extends LispException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessage() {
|
||||||
|
return "recur called outide of function";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,5 +1,8 @@
|
||||||
package table;
|
package table;
|
||||||
|
|
||||||
|
import java.util.Stack;
|
||||||
|
|
||||||
|
import function.LispFunction;
|
||||||
import sexpression.SExpression;
|
import sexpression.SExpression;
|
||||||
|
|
||||||
public class ExecutionContext {
|
public class ExecutionContext {
|
||||||
|
@ -11,9 +14,13 @@ public class ExecutionContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
private SymbolTable scope;
|
private SymbolTable scope;
|
||||||
|
private Stack<LispFunction> functionCalls;
|
||||||
|
private boolean recur;
|
||||||
|
|
||||||
private ExecutionContext() {
|
private ExecutionContext() {
|
||||||
this.scope = new SymbolTable();
|
this.scope = new SymbolTable();
|
||||||
|
this.functionCalls = new Stack<>();
|
||||||
|
this.clearRecur();
|
||||||
}
|
}
|
||||||
|
|
||||||
public SymbolTable getScope() {
|
public SymbolTable getScope() {
|
||||||
|
@ -26,6 +33,8 @@ public class ExecutionContext {
|
||||||
|
|
||||||
public void clearContext() {
|
public void clearContext() {
|
||||||
this.scope = new SymbolTable();
|
this.scope = new SymbolTable();
|
||||||
|
this.functionCalls = new Stack<>();
|
||||||
|
this.clearRecur();
|
||||||
}
|
}
|
||||||
|
|
||||||
public SExpression lookupSymbolValue(String symbolName) {
|
public SExpression lookupSymbolValue(String symbolName) {
|
||||||
|
@ -36,4 +45,32 @@ public class ExecutionContext {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void pushFunctionCall(LispFunction function) {
|
||||||
|
functionCalls.push(function);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void popFunctionCall() {
|
||||||
|
functionCalls.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isInFunctionCall() {
|
||||||
|
return !functionCalls.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public LispFunction getCurrentFunction() {
|
||||||
|
return functionCalls.peek();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRecur() {
|
||||||
|
return recur;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRecur() {
|
||||||
|
recur = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearRecur() {
|
||||||
|
recur = false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,7 @@ import function.builtin.special.LET_STAR;
|
||||||
import function.builtin.special.OR;
|
import function.builtin.special.OR;
|
||||||
import function.builtin.special.PROGN;
|
import function.builtin.special.PROGN;
|
||||||
import function.builtin.special.QUOTE;
|
import function.builtin.special.QUOTE;
|
||||||
|
import function.builtin.special.RECUR;
|
||||||
import function.builtin.special.SETQ;
|
import function.builtin.special.SETQ;
|
||||||
|
|
||||||
public class FunctionTable {
|
public class FunctionTable {
|
||||||
|
@ -94,6 +95,7 @@ public class FunctionTable {
|
||||||
allBuiltIns.add(PRINT.class);
|
allBuiltIns.add(PRINT.class);
|
||||||
allBuiltIns.add(PROGN.class);
|
allBuiltIns.add(PROGN.class);
|
||||||
allBuiltIns.add(QUOTE.class);
|
allBuiltIns.add(QUOTE.class);
|
||||||
|
allBuiltIns.add(RECUR.class);
|
||||||
allBuiltIns.add(REST.class);
|
allBuiltIns.add(REST.class);
|
||||||
allBuiltIns.add(SET.class);
|
allBuiltIns.add(SET.class);
|
||||||
allBuiltIns.add(SETQ.class);
|
allBuiltIns.add(SETQ.class);
|
||||||
|
|
|
@ -34,4 +34,8 @@ public class SymbolTable {
|
||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isGlobal() {
|
||||||
|
return parent == null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
package function.builtin.special;
|
||||||
|
|
||||||
|
import static testutil.TestUtilities.assertSExpressionsMatch;
|
||||||
|
import static testutil.TestUtilities.evaluateString;
|
||||||
|
import static testutil.TestUtilities.parseString;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import function.ArgumentValidator.BadArgumentTypeException;
|
||||||
|
import function.builtin.special.RECUR.RecurOutsideOfFunctionException;
|
||||||
|
import testutil.SymbolAndFunctionCleaner;
|
||||||
|
|
||||||
|
public class RECURTest extends SymbolAndFunctionCleaner {
|
||||||
|
|
||||||
|
@Test(expected = RecurOutsideOfFunctionException.class)
|
||||||
|
public void recurOutsideOfFunction_ThrowsException() {
|
||||||
|
evaluateString("(recur)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = RecurOutsideOfFunctionException.class)
|
||||||
|
public void recurOutsideOfFunction_AfterFunctionCall_ThrowsException() {
|
||||||
|
evaluateString("(defun f (n) (if (= n 0) 'ZERO n))");
|
||||||
|
evaluateString("(f 2)");
|
||||||
|
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)
|
||||||
|
public void recurInSpecialFunction_DoesNotEvaluateArguments() {
|
||||||
|
evaluateString("(define-special tail-recursive (n) (if (= n 0) 'PASS (recur (- n 1))))");
|
||||||
|
evaluateString("(tail-recursive 900)");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue