170 lines
5.4 KiB
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);
|
|
}
|
|
}
|
|
}
|