package function.builtin; import error.LispException; import function.ArgumentValidator; import function.FunctionNames; import function.LispFunction; import function.builtin.special.RECUR.RecurNotInTailPositionException; import sexpression.BackquoteExpression; import sexpression.Cons; import sexpression.LambdaExpression; import sexpression.SExpression; import sexpression.Symbol; import table.ExecutionContext; import table.FunctionTable; import static function.builtin.cons.LIST.makeList; import static function.builtin.special.LAMBDA.Lambda; import static java.text.MessageFormat.format; import static sexpression.Nil.NIL; import static sexpression.Symbol.T; @FunctionNames({ "EVAL" }) public class EVAL extends LispFunction { private static ExecutionContext executionContext = ExecutionContext.INSTANCE; public static SExpression eval(SExpression sExpression) { Cons argumentList = makeList(sExpression); try { return lookupEval().call(argumentList); } catch (LispException e) { executionContext.restoreGlobalScope(); throw e; } } public static SExpression applyFunction(LispFunction function, Cons argumentList) { return lookupEval().applyFunctionWithoutEvaluatingArguments(function, argumentList); } public static Cons evaluateFunctionArgumentList(Cons argumentList) { return lookupEval().evaluateArgumentList(argumentList); } private static EVAL lookupEval() { return (EVAL) FunctionTable.INSTANCE.lookupFunction("EVAL"); } public static LispFunction lookupFunctionOrLambda(SExpression functionExpression) { LispFunction function = FunctionTable.INSTANCE.lookupFunction(functionExpression.toString()); if (function == null) function = createLambdaFunction(functionExpression); return function; } private static LispFunction createLambdaFunction(SExpression lambdaExpression) { if (lambdaExpression.isFunction()) return ((LambdaExpression) lambdaExpression).getFunction(); else if (Lambda.isLambdaExpression(lambdaExpression)) return Lambda.createFunction((Cons) lambdaExpression); else throw new UndefinedFunctionException(lambdaExpression); } public static SExpression lookupSymbol(String symbolName) { if (symbolName.equals("NIL")) return NIL; else if (symbolName.equals("T")) return T; else if (symbolName.startsWith(":")) return new Symbol(symbolName); return ExecutionContext.INSTANCE.lookupSymbolValue(symbolName); } private ArgumentValidator argumentValidator; public EVAL(String name) { this.argumentValidator = new ArgumentValidator(name); this.argumentValidator.setExactNumberOfArguments(1); } @Override public SExpression call(Cons argumentList) { verifyNotRecurring(); argumentValidator.validate(argumentList); SExpression argument = argumentList.getFirst(); return evaluateExpression(argument); } private void verifyNotRecurring() { if (executionContext.isRecur()) { executionContext.clearRecur(); throw new RecurNotInTailPositionException(); } } private SExpression evaluateExpression(SExpression argument) { if (argument.isList()) return evaluateList(argument); else if (argument.isSymbol()) return evaluateSymbol(argument); else if (argument.isBackquote()) return evaluateBackTick(argument); else if (argument.isComma()) throw new UnmatchedCommaException(); else if (argument.isAtSign()) throw new UnmatchedAtSignException(); return argument; // NUMBER or STRING } private SExpression evaluateList(SExpression argument) { if (argument.isCons()) return evaluateFunction((Cons) argument); return argument; // NIL } private SExpression evaluateFunction(Cons list) { SExpression functionName = list.getFirst(); SExpression arguments = list.getRest(); LispFunction function = lookupFunctionOrLambda(functionName); validateFunctionList(list, functionName); return callFunction(function, (Cons) arguments); } private void validateFunctionList(Cons list, SExpression functionName) { ArgumentValidator functionListValidator = new ArgumentValidator(functionName.toString()); functionListValidator.validate(list); } private SExpression callFunction(LispFunction function, Cons argumentList) { if (function.isArgumentListEvaluated()) argumentList = evaluateArgumentList(argumentList); return applyFunctionWithoutEvaluatingArguments(function, argumentList); } private SExpression applyFunctionWithoutEvaluatingArguments(LispFunction function, Cons argumentList) { verifyNotRecurring(); SExpression result = function.call(argumentList); if (function.isMacro()) result = eval(result); return result; } private Cons evaluateArgumentList(Cons arguments) { if (arguments.isNull()) return NIL; SExpression first = eval(arguments.getFirst()); SExpression rest = arguments.getRest(); return new Cons(first, evaluateArgumentList((Cons) rest)); } private SExpression evaluateSymbol(SExpression argument) { SExpression symbolValue = lookupSymbol(argument.toString()); if (symbolValue != null) return symbolValue; throw new UndefinedSymbolException(argument); } private SExpression evaluateBackTick(SExpression argument) { BackquoteEvaluator evaluator = new BackquoteEvaluator((BackquoteExpression) argument); return evaluator.evaluate(); } public static class UndefinedFunctionException extends LispException { private static final long serialVersionUID = 1L; private SExpression function; public UndefinedFunctionException(SExpression function) { this.function = function; } @Override public String getMessage() { return format("undefined function: {0}", function); } } public static class UndefinedSymbolException extends LispException { private static final long serialVersionUID = 1L; private SExpression symbol; public UndefinedSymbolException(SExpression symbol) { this.symbol = symbol; } @Override public String getMessage() { return format("symbol {0} has no value", symbol); } } public static class UnmatchedCommaException extends LispException { private static final long serialVersionUID = 1L; @Override public String getMessage() { return "unmatched comma"; } } public static class UnmatchedAtSignException extends LispException { private static final long serialVersionUID = 1L; @Override public String getMessage() { return "unmatched at sign"; } } }