package function; import static function.builtin.EVAL.eval; import static java.text.MessageFormat.format; import static recursion.tail.TailCalls.done; import static sexpression.Nil.NIL; import java.util.ArrayList; import error.LispException; import recursion.tail.TailCall; import recursion.tail.TailCalls; import sexpression.Cons; import sexpression.SExpression; import sexpression.Symbol; import table.ExecutionContext; import table.SymbolTable; public class UserDefinedFunction extends LispFunction { private static final String KEYWORD_REST = "&REST"; private String name; private Cons body; private Cons lambdaExpression; private ExecutionContext executionContext; private SymbolTable functionScope; private ArrayList formalParameters; private ArgumentValidator argumentValidator; private String keywordRestParameter; private boolean isKeywordRestPresent; public UserDefinedFunction(String name, Cons lambdaList, Cons body) { this.name = name; this.body = body; this.lambdaExpression = new Cons(new Symbol(name), new Cons(lambdaList, body)); this.executionContext = ExecutionContext.getInstance(); this.functionScope = executionContext.getScope(); this.keywordRestParameter = null; this.isKeywordRestPresent = false; createFormalParameters(lambdaList); setupArgumentValidator(); } private void createFormalParameters(Cons lambdaList) { for (formalParameters = new ArrayList<>(); lambdaList.isCons(); lambdaList = advanceCons(lambdaList)) { String parameter = lambdaList.getFirst().toString(); if (isKeywordRest(parameter)) lambdaList = extractKeywordRestParameter(lambdaList); else formalParameters.add(parameter); } } private Cons advanceCons(Cons cons) { return (Cons) cons.getRest(); } private boolean isKeywordRest(String parameter) { return KEYWORD_REST.equals(parameter); } private Cons extractKeywordRestParameter(Cons lambdaList) { isKeywordRestPresent = true; lambdaList = advanceCons(lambdaList); keywordRestParameter = lambdaList.getFirst().toString(); lambdaList = advanceCons(lambdaList); if (containsMoreParameters(lambdaList)) throw new IllegalKeywordRestPositionException(this.name, lambdaList); return lambdaList; } private boolean containsMoreParameters(Cons lambdaList) { return lambdaList.isCons(); } private void setupArgumentValidator() { argumentValidator = new ArgumentValidator(this.name); if (isKeywordRestPresent) argumentValidator.setMinimumNumberOfArguments(this.formalParameters.size()); else argumentValidator.setExactNumberOfArguments(this.formalParameters.size()); } @Override public SExpression call(Cons argumentList) { return callTailRecursive(argumentList).invoke(); } private TailCall callTailRecursive(Cons 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); if (executionContext.isRecur()) { executionContext.clearRecur(); return TailCalls.tailCall(() -> callTailRecursive((Cons) result)); } return done(result); } private SExpression evaluateInFunctionScope(Cons argumentList) { SymbolTable callingScope = executionContext.getScope(); SymbolTable executionScope = bindParameterValuesToFunctionScope(argumentList); executionContext.setScope(executionScope); SExpression lastEvaluation = evaluateBody(); executionContext.setScope(callingScope); return lastEvaluation; } private SymbolTable bindParameterValuesToFunctionScope(Cons argumentList) { SymbolTable executionScope = new SymbolTable(functionScope); for (String parameter : formalParameters) { SExpression currentArg = argumentList.getFirst(); executionScope.put(parameter, currentArg); argumentList = (Cons) argumentList.getRest(); } if (isKeywordRestPresent) executionScope.put(keywordRestParameter, argumentList); return executionScope; } private SExpression evaluateBody() { SExpression lastEvaluation = NIL; // 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()); return lastEvaluation; } public Cons getLambdaExpression() { return lambdaExpression; } public static class IllegalKeywordRestPositionException extends LispException { private static final long serialVersionUID = 1L; private String functionName; private Cons parameters; public IllegalKeywordRestPositionException(String functionName, Cons parameters) { this.functionName = functionName; this.parameters = parameters; } @Override public String getMessage() { return format("unexpected parameters following ''&rest'' in definition of {0}: {1}", functionName, parameters); } } }