transcendental-lisp/src/main/java/function/UserDefinedFunction.java

170 lines
5.4 KiB
Java

package function;
import error.LispException;
import recursion.TailCall;
import recursion.TailCalls;
import sexpression.Cons;
import sexpression.SExpression;
import sexpression.Symbol;
import table.ExecutionContext;
import table.SymbolTable;
import java.util.ArrayList;
import static function.builtin.EVAL.eval;
import static java.text.MessageFormat.format;
import static recursion.TailCalls.done;
import static sexpression.Nil.NIL;
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<String> 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) {
executionContext.pushFunctionCall(this);
SExpression result = callTailRecursive(argumentList).invoke();
executionContext.popFunctionCall();
return result;
}
private TailCall<SExpression> callTailRecursive(Cons argumentList) {
argumentValidator.validate(argumentList);
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;
for (Cons expression = body; expression.isCons(); 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);
}
}
}