package function.builtin.special; import static function.builtin.EVAL.eval; import static function.builtin.predicate.EQUAL.isEqual; import static sexpression.Nil.NIL; import static sexpression.Symbol.T; import function.ArgumentValidator; import function.FunctionNames; import function.LispSpecialFunction; import sexpression.Cons; import sexpression.Nil; import sexpression.SExpression; @FunctionNames({ "CASE" }) public class CASE extends LispSpecialFunction { private ArgumentValidator argumentValidator; public CASE(String name) { this.argumentValidator = new ArgumentValidator(name); this.argumentValidator.setMinimumNumberOfArguments(1); this.argumentValidator.setTrailingArgumentExpectedType(Cons.class); this.argumentValidator.setTrailingArgumentExcludedType(Nil.class); } @Override public SExpression call(Cons argumentList) { argumentValidator.validate(argumentList); SExpression key = eval(argumentList.getFirst()); return callTailRecursive(key, (Cons) argumentList.getRest()); } private SExpression callTailRecursive(SExpression key, Cons argumentList) { if (argumentList.isNull()) return NIL; Cons clause = (Cons) argumentList.getFirst(); Cons remainingClauses = (Cons) argumentList.getRest(); SExpression keyList = clause.getFirst(); if (isMatch(key, keyList)) return evaluateConsequents(clause.getRest()); return callTailRecursive(key, remainingClauses); } private boolean isMatch(SExpression key, SExpression keyList) { if (keyList.isNull()) return false; else if (keyList.isCons()) return containsMatch(key, keyList); return isEqual(key, keyList) || isEqual(T, keyList); } private boolean containsMatch(SExpression key, SExpression keyList) { for (; keyList.isCons(); keyList = advanceCons(keyList)) if (isEqual(key, getFirst(keyList))) return true; return false; } private SExpression advanceCons(SExpression knownCons) { return ((Cons) knownCons).getRest(); } private SExpression getFirst(SExpression knownCons) { return ((Cons) knownCons).getFirst(); } private SExpression evaluateConsequents(SExpression consequentList) { SExpression lastConsequentValue = NIL; for (; consequentList.isCons(); consequentList = advanceCons(consequentList)) lastConsequentValue = eval(getFirst(consequentList)); return lastConsequentValue; } }