diff --git a/src/function/builtin/cons/APPEND.java b/src/function/builtin/cons/APPEND.java index fd4c5a1..3ffd146 100644 --- a/src/function/builtin/cons/APPEND.java +++ b/src/function/builtin/cons/APPEND.java @@ -1,6 +1,7 @@ package function.builtin.cons; import static sexpression.Nil.NIL; +import static table.FunctionTable.lookupFunction; import function.ArgumentValidator; import function.FunctionNames; @@ -11,20 +12,47 @@ import sexpression.Cons; public class APPEND extends LispFunction { public static Cons append(Cons firstList, Cons secondList) { - Cons appendedLists = copy(firstList); + return lookupAppend().appendLists(firstList, secondList); + } + + private static APPEND lookupAppend() { + return (APPEND) lookupFunction("APPEND"); + } + + private ArgumentValidator argumentValidator; + private ArgumentValidator firstListValidator; + + public APPEND(String name) { + this.argumentValidator = new ArgumentValidator(name); + this.argumentValidator.setExactNumberOfArguments(2); + this.argumentValidator.setEveryArgumentExpectedType(Cons.class); + this.firstListValidator = new ArgumentValidator(name + "|first-list|"); + } + + @Override + public Cons call(Cons argumentList) { + argumentValidator.validate(argumentList); + + Cons rest = (Cons) argumentList.getRest(); + Cons firstList = (Cons) argumentList.getFirst(); + Cons secondList = (Cons) rest.getFirst(); + + return appendLists(firstList, secondList); + } + + private Cons appendLists(Cons firstList, Cons secondList) { + firstListValidator.validate(firstList); if (firstList.isNull()) - return copy(secondList); + return secondList; - getLastItem(appendedLists).setRest(copy(secondList)); + Cons appendedLists = copy(firstList); + getLastItem(appendedLists).setRest(secondList); return appendedLists; } - private static Cons copy(Cons list) { - if (list.isNull()) - return NIL; - + private Cons copy(Cons list) { Cons newList = new Cons(list.getFirst(), NIL); Cons builder = newList; @@ -36,7 +64,7 @@ public class APPEND extends LispFunction { return newList; } - private static Cons getLastItem(Cons list) { + private Cons getLastItem(Cons list) { Cons tail = list; while (tail.getRest().isCons()) @@ -44,33 +72,4 @@ public class APPEND extends LispFunction { return tail; } - - private ArgumentValidator argumentValidator; - private String name; - - public APPEND(String name) { - this.name = name; - this.argumentValidator = new ArgumentValidator(name); - this.argumentValidator.setExactNumberOfArguments(2); - this.argumentValidator.setEveryArgumentExpectedType(Cons.class); - } - - @Override - public Cons call(Cons argumentList) { - argumentValidator.validate(argumentList); - - Cons rest = (Cons) argumentList.getRest(); - Cons firstList = (Cons) argumentList.getFirst(); - Cons secondList = (Cons) rest.getFirst(); - validateLists(firstList, secondList); - - return append(firstList, secondList); - } - - private void validateLists(Cons firstList, Cons secondList) { - ArgumentValidator properListValidatorOne = new ArgumentValidator(name + "|list-one|"); - ArgumentValidator properListValidatorTwo = new ArgumentValidator(name + "|list-two|"); - properListValidatorOne.validate(firstList); - properListValidatorTwo.validate(secondList); - } } diff --git a/src/function/builtin/cons/LENGTH.java b/src/function/builtin/cons/LENGTH.java index 57260f4..2acade0 100644 --- a/src/function/builtin/cons/LENGTH.java +++ b/src/function/builtin/cons/LENGTH.java @@ -3,6 +3,7 @@ package function.builtin.cons; import static function.builtin.cons.LIST.makeList; import static recursion.TailCalls.done; import static recursion.TailCalls.tailCall; +import static table.FunctionTable.lookupFunction; import java.math.BigInteger; @@ -17,25 +18,13 @@ import sexpression.LispNumber; public class LENGTH extends LispFunction { public static BigInteger getLength(Cons list) { - return callWithoutArgumentValidation(makeList(list)).getValue(); + LispNumber length = lookupLength().callWithoutArgumentValidation(makeList(list)); + + return length.getValue(); } - private static LispNumber callWithoutArgumentValidation(Cons argumentList) { - return callTailRecursive(BigInteger.ZERO, argumentList).invoke(); - } - - private static TailCall callTailRecursive(BigInteger accumulatedLength, Cons argumentList) { - Cons list = (Cons) argumentList.getFirst(); - Cons restOfList = makeList(list.getRest()); - - if (list.isNull()) - return done(new LispNumber(accumulatedLength)); - - return tailCall(() -> callTailRecursive(increment(accumulatedLength), restOfList)); - } - - private static BigInteger increment(BigInteger number) { - return number.add(BigInteger.ONE); + private static LENGTH lookupLength() { + return (LENGTH) lookupFunction("LENGTH"); } private ArgumentValidator argumentValidator; @@ -53,4 +42,22 @@ public class LENGTH extends LispFunction { return callTailRecursive(BigInteger.ZERO, argumentList).invoke(); } + private LispNumber callWithoutArgumentValidation(Cons argumentList) { + return callTailRecursive(BigInteger.ZERO, argumentList).invoke(); + } + + private TailCall callTailRecursive(BigInteger accumulatedLength, Cons argumentList) { + Cons list = (Cons) argumentList.getFirst(); + Cons restOfList = makeList(list.getRest()); + + if (list.isNull()) + return done(new LispNumber(accumulatedLength)); + + return tailCall(() -> callTailRecursive(increment(accumulatedLength), restOfList)); + } + + private BigInteger increment(BigInteger number) { + return number.add(BigInteger.ONE); + } + } diff --git a/src/table/SymbolTable.java b/src/table/SymbolTable.java index 9afc329..0088983 100644 --- a/src/table/SymbolTable.java +++ b/src/table/SymbolTable.java @@ -1,5 +1,6 @@ package table; +import static function.builtin.cons.APPEND.append; import static function.builtin.cons.LIST.makeList; import static sexpression.Nil.NIL; @@ -8,7 +9,6 @@ import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; -import function.builtin.cons.APPEND; import sexpression.Cons; import sexpression.SExpression; import sexpression.Symbol; @@ -51,7 +51,7 @@ public class SymbolTable { Cons context = NIL; for (Entry binding : getSortedBindings()) - context = APPEND.append(context, makeList(makeSymbolValuePair(binding))); + context = append(context, makeList(makeSymbolValuePair(binding))); return context; } diff --git a/test/function/builtin/cons/APPENDTest.java b/test/function/builtin/cons/APPENDTest.java index 38ce2b2..5ed6fa9 100644 --- a/test/function/builtin/cons/APPENDTest.java +++ b/test/function/builtin/cons/APPENDTest.java @@ -43,22 +43,22 @@ public class APPENDTest extends SymbolAndFunctionCleaner { } @Test - public void appendMakesCopies() { + public void appendMakesCopyOfFirstList() { evaluateString("(setq x '(1 2 3))"); evaluateString("(setq y '(4 5 6))"); evaluateString("(append x y)"); assertSExpressionsMatch(parseString("(1 2 3)"), evaluateString("x")); - assertSExpressionsMatch(parseString("(4 5 6)"), evaluateString("y")); + } + + @Test + public void appendAllowsDottedSecondList() { + String input = "(append '() (cons 3 4))"; + assertSExpressionsMatch(evaluateString("(cons 3 4)"), evaluateString(input)); } @Test(expected = DottedArgumentListException.class) public void appendWithDottedFirstList() { - evaluateString("(append '(1 2) (cons 3 4))"); - } - - @Test(expected = DottedArgumentListException.class) - public void appendWithDottedSecondList() { evaluateString("(append (cons 1 2) '(3 4))"); }