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)))))