diff --git a/.classpath b/.classpath index d1e6294..de63c7f 100644 --- a/.classpath +++ b/.classpath @@ -12,7 +12,7 @@ - + diff --git a/lisp/lang/functions.lisp b/lisp/lang/functions.lisp index c984c47..c684278 100644 --- a/lisp/lang/functions.lisp +++ b/lisp/lang/functions.lisp @@ -37,12 +37,12 @@ accumulator)) accumulator)) -(defun append (list-one list-two) - (append-tail (reverse list-one) list-two)) +; (defun append (list-one list-two) +; (append-tail (reverse list-one) list-two)) -(defun append-tail (reversed-list list-two) - (if (null reversed-list) list-two - (recur (rest reversed-list) (cons (car reversed-list) list-two)))) +; (defun append-tail (reversed-list list-two) +; (if (null reversed-list) list-two +; (recur (rest reversed-list) (cons (car reversed-list) list-two)))) (defun reverse (the-list) (reverse-tail () the-list)) diff --git a/src/function/builtin/APPLY.java b/src/function/builtin/APPLY.java index 62c6a00..3fd6002 100644 --- a/src/function/builtin/APPLY.java +++ b/src/function/builtin/APPLY.java @@ -13,12 +13,12 @@ import sexpression.SExpression; @FunctionNames({ "APPLY" }) public class APPLY extends LispFunction { - private ArgumentValidator argumentValidator; - public static SExpression apply(Cons argumentList) { return lookupFunction("APPLY").call(argumentList); } + private ArgumentValidator argumentValidator; + public APPLY(String name) { this.argumentValidator = new ArgumentValidator(name); this.argumentValidator.setExactNumberOfArguments(2); diff --git a/src/function/builtin/cons/APPEND.java b/src/function/builtin/cons/APPEND.java new file mode 100644 index 0000000..2e95e02 --- /dev/null +++ b/src/function/builtin/cons/APPEND.java @@ -0,0 +1,67 @@ +package function.builtin.cons; + +import static sexpression.Nil.NIL; + +import function.ArgumentValidator; +import function.FunctionNames; +import function.LispFunction; +import sexpression.Cons; + +@FunctionNames({ "APPEND" }) +public class APPEND extends LispFunction { + + public static Cons append(Cons firstList, Cons secondList) { + Cons appendedLists = copy(firstList); + + if (firstList.isNull()) + return copy(secondList); + + getLastItem(appendedLists).setRest(copy(secondList)); + + return appendedLists; + } + + private static Cons copy(Cons list) { + if (list.isNull()) + return NIL; + + Cons newList = new Cons(list.getFirst(), NIL); + Cons builder = newList; + + for (Cons iterator = (Cons) list.getRest(); iterator.isCons(); iterator = (Cons) iterator.getRest()) { + builder.setRest(new Cons(iterator.getFirst(), NIL)); + builder = (Cons) builder.getRest(); + } + + return newList; + } + + private static Cons getLastItem(Cons list) { + Cons tail = list; + + while (tail.getRest().isCons()) + tail = (Cons) tail.getRest(); + + return tail; + } + + private ArgumentValidator argumentValidator; + + public APPEND(String 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(); + + return append(firstList, secondList); + } + +} diff --git a/src/function/builtin/cons/LENGTH.java b/src/function/builtin/cons/LENGTH.java index 2acade0..57260f4 100644 --- a/src/function/builtin/cons/LENGTH.java +++ b/src/function/builtin/cons/LENGTH.java @@ -3,7 +3,6 @@ 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; @@ -18,13 +17,25 @@ import sexpression.LispNumber; public class LENGTH extends LispFunction { public static BigInteger getLength(Cons list) { - LispNumber length = lookupLength().callWithoutArgumentValidation(makeList(list)); - - return length.getValue(); + return callWithoutArgumentValidation(makeList(list)).getValue(); } - private static LENGTH lookupLength() { - return (LENGTH) lookupFunction("LENGTH"); + 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 ArgumentValidator argumentValidator; @@ -42,22 +53,4 @@ 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/FunctionTable.java b/src/table/FunctionTable.java index 8839e06..8a82eba 100644 --- a/src/table/FunctionTable.java +++ b/src/table/FunctionTable.java @@ -21,6 +21,7 @@ import function.builtin.PRINT; import function.builtin.SET; import function.builtin.SYMBOLS; import function.builtin.SYMBOL_FUNCTION; +import function.builtin.cons.APPEND; import function.builtin.cons.CONS; import function.builtin.cons.FIRST; import function.builtin.cons.LENGTH; @@ -63,6 +64,7 @@ public class FunctionTable { static { allBuiltIns.add(AND.class); + allBuiltIns.add(APPEND.class); allBuiltIns.add(APPLY.class); allBuiltIns.add(ATOM.class); allBuiltIns.add(CASE.class); diff --git a/src/table/SymbolTable.java b/src/table/SymbolTable.java index ec1bcb2..9afc329 100644 --- a/src/table/SymbolTable.java +++ b/src/table/SymbolTable.java @@ -8,6 +8,7 @@ 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; @@ -50,7 +51,7 @@ public class SymbolTable { Cons context = NIL; for (Entry binding : getSortedBindings()) - context = append(context, makeList(makeSymbolValuePair(binding))); + context = APPEND.append(context, makeList(makeSymbolValuePair(binding))); return context; } @@ -63,19 +64,4 @@ public class SymbolTable { return new Cons(new Symbol(binding.getKey()), makeList(binding.getValue())); } - // TODO - extract into built in append function - private Cons append(Cons firstList, Cons secondList) { - if (firstList.isNull()) - return secondList; // FIXME - copy - - Cons appendedList = firstList; // FIXME - copy - Cons appender = appendedList; - - for (; !((Cons) appender.getRest()).isNull(); appender = (Cons) appender.getRest()) {} - - appender.setRest(secondList); // FIXME - copy - - return appendedList; - } - } diff --git a/test/function/builtin/cons/APPENDTest.java b/test/function/builtin/cons/APPENDTest.java new file mode 100644 index 0000000..e6b3e1a --- /dev/null +++ b/test/function/builtin/cons/APPENDTest.java @@ -0,0 +1,68 @@ +package function.builtin.cons; + +import static testutil.TestUtilities.assertSExpressionsMatch; +import static testutil.TestUtilities.evaluateString; +import static testutil.TestUtilities.parseString; + +import org.junit.Test; + +import function.ArgumentValidator.BadArgumentTypeException; +import function.ArgumentValidator.TooFewArgumentsException; +import function.ArgumentValidator.TooManyArgumentsException; +import testutil.SymbolAndFunctionCleaner; + +public class APPENDTest extends SymbolAndFunctionCleaner { + + @Test + public void appendNil() { + String input = "(append () ())"; + + assertSExpressionsMatch(parseString("nil"), evaluateString(input)); + } + + @Test + public void appendNilToList() { + String input = "(append () '(1 2 3))"; + + assertSExpressionsMatch(parseString("(1 2 3)"), evaluateString(input)); + } + + @Test + public void appendListToNil() { + String input = "(append '(1 2 3) ())"; + + assertSExpressionsMatch(parseString("(1 2 3)"), evaluateString(input)); + } + + @Test + public void appendTwoLists() { + String input = "(append '(1 2 3) '(4 5 6))"; + + assertSExpressionsMatch(parseString("(1 2 3 4 5 6)"), evaluateString(input)); + } + + @Test + public void appendMakesCopies() { + 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(expected = TooManyArgumentsException.class) + public void appendWithTooManyArguments() { + evaluateString("(append () () ())"); + } + + @Test(expected = TooFewArgumentsException.class) + public void appendWithTooFewArguments() { + evaluateString("(append ())"); + } + + @Test(expected = BadArgumentTypeException.class) + public void appendWithBadArgumentType() { + evaluateString("(append 1 '(2))"); + } +} diff --git a/test/function/builtin/cons/CONSTest.java b/test/function/builtin/cons/CONSTest.java index af713b6..5ba1014 100644 --- a/test/function/builtin/cons/CONSTest.java +++ b/test/function/builtin/cons/CONSTest.java @@ -51,16 +51,12 @@ public class CONSTest extends SymbolAndFunctionCleaner { @Test(expected = TooManyArgumentsException.class) public void consWithTooManyArguments() { - String input = "(cons 1 2 3)"; - - evaluateString(input); + evaluateString("(cons 1 2 3)"); } @Test(expected = TooFewArgumentsException.class) public void consWithTooFewArguments() { - String input = "(cons 1)"; - - evaluateString(input); + evaluateString("(cons 1)"); } } diff --git a/test/function/builtin/test-files/load-good.lisp b/test/function/builtin/test-files/load-good.lisp index 403108d..ad42d27 100644 --- a/test/function/builtin/test-files/load-good.lisp +++ b/test/function/builtin/test-files/load-good.lisp @@ -24,13 +24,6 @@ (defun extend-apply (function-name param-list) (eval (cons function-name param-list))) -(defun append (listA listB) - (cond - ((null listA) listB) - (t (cons (first listA) (append (rest listA) listB))) - ) -) - (defun second (listA) (first (rest listA))) (defun third (listA) (first (rest (rest listA)))) (defun fourth (listA) (first (rest (rest (rest listA)))))