diff --git a/fitnesse/FitNesseRoot/RecentChanges.wiki b/fitnesse/FitNesseRoot/RecentChanges.wiki index b223b61..34a01b7 100644 --- a/fitnesse/FitNesseRoot/RecentChanges.wiki +++ b/fitnesse/FitNesseRoot/RecentChanges.wiki @@ -1,3 +1,4 @@ +|TranscendentalLisp.Recursion||17:56:26 Sat, Nov 18, 2017| |TranscendentalLisp||16:15:14 Fri, Mar 17, 2017| |TranscendentalLisp.Macros||10:10:15 Mon, Mar 13, 2017| |TranscendentalLisp.MacroTests||10:07:00 Mon, Mar 13, 2017| diff --git a/fitnesse/FitNesseRoot/TranscendentalLisp/Recursion.wiki b/fitnesse/FitNesseRoot/TranscendentalLisp/Recursion.wiki new file mode 100644 index 0000000..f9f194c --- /dev/null +++ b/fitnesse/FitNesseRoot/TranscendentalLisp/Recursion.wiki @@ -0,0 +1,17 @@ +--- +Test +--- +Test recursion capabilities of various functions. + +| script | lisp interpreter fixture | +| show | evaluate text | (load "lisp/random/list-builder.lisp") | +| check | evaluate text | (setq big-list (list-doubler '(1 1 1 1 1 1 1 1) 11)) | =~/1\)$/ | +| check | evaluate text | (length big-list) | 16384 | +| check | evaluate text | (length (apply 'list big-list)) | 16384 | +| check | evaluate text | (apply '/ big-list) | 1 | +| check | evaluate text | (apply '* big-list) | 1 | +| check | evaluate text | (apply '+ big-list) | 16384 | +| check | evaluate text | (apply '- big-list) | -16382 | +| check | evaluate text | (apply '= big-list) | T | +| check | evaluate text | (apply '> (decreasing-list 0 10000)) | T | +| check | evaluate text | (apply '< (increasing-list 10000 10000)) | T | diff --git a/lisp/random/list-builder.lisp b/lisp/random/list-builder.lisp index b831bb5..dd2b479 100644 --- a/lisp/random/list-builder.lisp +++ b/lisp/random/list-builder.lisp @@ -3,3 +3,17 @@ (defun list-doubler (seed times-to-double) (if (< times-to-double 1) seed (recur (append seed seed) (- times-to-double 1)))) + +(defun decreasing-list (end size) + (decreasing-list-tail (list end) (+ end 1) size)) + +(defun decreasing-list-tail (seed end size) + (if (< size 1) seed + (recur (cons (+ (car seed) 1) seed) (+ end 1) (- size 1)))) + +(defun increasing-list (end size) + (increasing-list-tail (list end) (+ end 1) size)) + +(defun increasing-list-tail (seed end size) + (if (< size 1) seed + (recur (cons (- (car seed) 1) seed) (- end 1) (- size 1)))) diff --git a/lisp/tail/length-recursion.lisp b/lisp/tail/length-recursion.lisp deleted file mode 100644 index fe6adeb..0000000 --- a/lisp/tail/length-recursion.lisp +++ /dev/null @@ -1,11 +0,0 @@ -(load "../lang/functions.lisp") - -(defun build (n lst) - (if (= n 0) lst - (build (- n 1) (cons (car lst) lst)))) - -(defun build2 (n lst) - (if (= n 0) lst - (build2 (- n 1) (build 200 lst)))) - -(length (build2 200 (build2 200 (build2 200 (build2 200 '(1 1 1 1 0 0 0 0)))))) diff --git a/lisp/tail/to-string-recursion.lisp b/lisp/tail/to-string-recursion.lisp deleted file mode 100644 index f2d9c69..0000000 --- a/lisp/tail/to-string-recursion.lisp +++ /dev/null @@ -1,11 +0,0 @@ -(load "../lang/functions.lisp") - -(defun build (n lst) - (if (= n 0) lst - (build (- n 1) (cons (car lst) lst)))) - -(defun build2 (n lst) - (if (= n 0) lst - (build2 (- n 1) (build 200 lst)))) - -(build2 200 (build2 200 (build2 200 (build2 200 '(1 1 1 1 0 0 0 0))))) diff --git a/src/function/builtin/cons/LIST.java b/src/function/builtin/cons/LIST.java index 4f39c59..a83271d 100644 --- a/src/function/builtin/cons/LIST.java +++ b/src/function/builtin/cons/LIST.java @@ -25,17 +25,7 @@ public class LIST extends LispFunction { public Cons call(Cons argumentList) { argumentValidator.validate(argumentList); - return callRecursive(argumentList); - } - - private Cons callRecursive(Cons argumentList) { - if (argumentList.isNull()) - return NIL; - - SExpression firstArgument = argumentList.getFirst(); - Cons remainingArguments = (Cons) argumentList.getRest(); - - return new Cons(firstArgument, callRecursive(remainingArguments)); + return argumentList; } } diff --git a/src/function/builtin/math/DIVIDE.java b/src/function/builtin/math/DIVIDE.java index 9e48dbe..bee44d9 100644 --- a/src/function/builtin/math/DIVIDE.java +++ b/src/function/builtin/math/DIVIDE.java @@ -2,6 +2,7 @@ package function.builtin.math; import java.math.BigInteger; +import error.LispException; import function.ArgumentValidator; import function.FunctionNames; import function.LispFunction; @@ -25,7 +26,11 @@ public class DIVIDE extends LispFunction { public LispNumber call(Cons argumentList) { argumentValidator.validate(argumentList); - return mathFunction.callTailRecursive(argumentList); + try { + return mathFunction.callTailRecursive(argumentList).invoke(); + } catch (ArithmeticException e) { + throw new DivideByZeroException(); + } } private LispNumber getReciprocal(LispNumber number) { @@ -36,4 +41,14 @@ public class DIVIDE extends LispFunction { return new LispNumber(number1.getValue().divide(number2.getValue())); } + public static class DivideByZeroException extends LispException { + + private static final long serialVersionUID = 1L; + + @Override + public String getMessage() { + return "divide by zero"; + } + } + } diff --git a/src/function/builtin/math/MINUS.java b/src/function/builtin/math/MINUS.java index b55e5ca..56d725e 100644 --- a/src/function/builtin/math/MINUS.java +++ b/src/function/builtin/math/MINUS.java @@ -25,7 +25,7 @@ public class MINUS extends LispFunction { public LispNumber call(Cons argumentList) { argumentValidator.validate(argumentList); - return mathFunction.callTailRecursive(argumentList); + return mathFunction.callTailRecursive(argumentList).invoke(); } private LispNumber additiveInverse(LispNumber number) { diff --git a/src/function/builtin/math/MULTIPLY.java b/src/function/builtin/math/MULTIPLY.java index c8fe151..cbbe45b 100644 --- a/src/function/builtin/math/MULTIPLY.java +++ b/src/function/builtin/math/MULTIPLY.java @@ -24,7 +24,7 @@ public class MULTIPLY extends LispFunction { public LispNumber call(Cons argumentList) { argumentValidator.validate(argumentList); - return mathFunction.callTailRecursive(new Cons(ONE, argumentList)); + return mathFunction.callTailRecursive(new Cons(ONE, argumentList)).invoke(); } private LispNumber multiply(LispNumber number1, LispNumber number2) { diff --git a/src/function/builtin/math/MathFunction.java b/src/function/builtin/math/MathFunction.java index 34677e1..ec3520a 100644 --- a/src/function/builtin/math/MathFunction.java +++ b/src/function/builtin/math/MathFunction.java @@ -1,8 +1,12 @@ package function.builtin.math; +import static recursion.TailCalls.done; +import static recursion.TailCalls.tailCall; + import java.util.function.BiFunction; import java.util.function.Function; +import recursion.TailCall; import sexpression.Cons; import sexpression.LispNumber; import sexpression.SExpression; @@ -18,13 +22,13 @@ class MathFunction { this.multipleValueOperation = multipleValueOperation; } - public LispNumber callTailRecursive(Cons argumentList) { + public TailCall callTailRecursive(Cons argumentList) { Cons remainingArguments = (Cons) argumentList.getRest(); SExpression firstArgument = argumentList.getFirst(); LispNumber number1 = (LispNumber) firstArgument; if (remainingArguments.isNull()) - return singleValueOperation.apply(number1); + return done(singleValueOperation.apply(number1)); SExpression secondArgument = remainingArguments.getFirst(); LispNumber number2 = (LispNumber) secondArgument; @@ -32,9 +36,9 @@ class MathFunction { SExpression remainingNumbers = remainingArguments.getRest(); if (remainingNumbers.isNull()) - return operationResult; + return done(operationResult); - return callTailRecursive(new Cons(operationResult, remainingNumbers)); + return tailCall(() -> callTailRecursive(new Cons(operationResult, remainingNumbers))); } } diff --git a/src/function/builtin/math/PLUS.java b/src/function/builtin/math/PLUS.java index 1e04fa8..16677b5 100644 --- a/src/function/builtin/math/PLUS.java +++ b/src/function/builtin/math/PLUS.java @@ -24,7 +24,7 @@ public class PLUS extends LispFunction { public LispNumber call(Cons argumentList) { argumentValidator.validate(argumentList); - return mathFunction.callTailRecursive(new Cons(ZERO, argumentList)); + return mathFunction.callTailRecursive(new Cons(ZERO, argumentList)).invoke(); } private LispNumber add(LispNumber number1, LispNumber number2) { diff --git a/src/function/builtin/predicate/NUMERIC_EQUAL.java b/src/function/builtin/predicate/NUMERIC_EQUAL.java index 07ac521..76de5e4 100644 --- a/src/function/builtin/predicate/NUMERIC_EQUAL.java +++ b/src/function/builtin/predicate/NUMERIC_EQUAL.java @@ -1,11 +1,14 @@ package function.builtin.predicate; +import static recursion.TailCalls.done; +import static recursion.TailCalls.tailCall; import static sexpression.Nil.NIL; import static sexpression.Symbol.T; import function.ArgumentValidator; import function.FunctionNames; import function.LispFunction; +import recursion.TailCall; import sexpression.Cons; import sexpression.LispNumber; import sexpression.SExpression; @@ -25,14 +28,14 @@ public class NUMERIC_EQUAL extends LispFunction { public SExpression call(Cons argumentList) { argumentValidator.validate(argumentList); - return callTailRecursive(argumentList); + return callTailRecursive(argumentList).invoke(); } - private SExpression callTailRecursive(Cons argumentList) { + private TailCall callTailRecursive(Cons argumentList) { Cons remainingArguments = (Cons) argumentList.getRest(); if (remainingArguments.isNull()) - return T; + return done(T); SExpression firstArgument = argumentList.getFirst(); LispNumber number1 = (LispNumber) firstArgument; @@ -40,9 +43,9 @@ public class NUMERIC_EQUAL extends LispFunction { LispNumber number2 = (LispNumber) secondArgument; if (!isEqual(number1, number2)) - return NIL; + return done(NIL); - return callTailRecursive(remainingArguments); + return tailCall(() -> callTailRecursive(remainingArguments)); } private boolean isEqual(LispNumber number1, LispNumber number2) { diff --git a/src/function/builtin/predicate/NUMERIC_GREATER.java b/src/function/builtin/predicate/NUMERIC_GREATER.java index 2b12a88..5dbe2e4 100644 --- a/src/function/builtin/predicate/NUMERIC_GREATER.java +++ b/src/function/builtin/predicate/NUMERIC_GREATER.java @@ -1,11 +1,14 @@ package function.builtin.predicate; +import static recursion.TailCalls.done; +import static recursion.TailCalls.tailCall; import static sexpression.Nil.NIL; import static sexpression.Symbol.T; import function.ArgumentValidator; import function.FunctionNames; import function.LispFunction; +import recursion.TailCall; import sexpression.Cons; import sexpression.LispNumber; import sexpression.SExpression; @@ -25,14 +28,14 @@ public class NUMERIC_GREATER extends LispFunction { public SExpression call(Cons argumentList) { argumentValidator.validate(argumentList); - return callTailRecursive(argumentList); + return callTailRecursive(argumentList).invoke(); } - private SExpression callTailRecursive(Cons argumentList) { + private TailCall callTailRecursive(Cons argumentList) { Cons remainingArguments = (Cons) argumentList.getRest(); if (remainingArguments.isNull()) - return T; + return done(T); SExpression firstArgument = argumentList.getFirst(); SExpression secondArgument = remainingArguments.getFirst(); @@ -40,9 +43,9 @@ public class NUMERIC_GREATER extends LispFunction { LispNumber number2 = (LispNumber) secondArgument; if (!isFirstGreater(number1, number2)) - return NIL; + return done(NIL); - return callTailRecursive(remainingArguments); + return tailCall(() -> callTailRecursive(remainingArguments)); } private boolean isFirstGreater(LispNumber number1, LispNumber number2) { diff --git a/src/function/builtin/predicate/NUMERIC_LESS.java b/src/function/builtin/predicate/NUMERIC_LESS.java index f47a362..d70950d 100644 --- a/src/function/builtin/predicate/NUMERIC_LESS.java +++ b/src/function/builtin/predicate/NUMERIC_LESS.java @@ -1,11 +1,14 @@ package function.builtin.predicate; +import static recursion.TailCalls.done; +import static recursion.TailCalls.tailCall; import static sexpression.Nil.NIL; import static sexpression.Symbol.T; import function.ArgumentValidator; import function.FunctionNames; import function.LispFunction; +import recursion.TailCall; import sexpression.Cons; import sexpression.LispNumber; import sexpression.SExpression; @@ -25,14 +28,14 @@ public class NUMERIC_LESS extends LispFunction { public SExpression call(Cons argumentList) { argumentValidator.validate(argumentList); - return callTailRecursive(argumentList); + return callTailRecursive(argumentList).invoke(); } - private SExpression callTailRecursive(Cons argumentList) { + private TailCall callTailRecursive(Cons argumentList) { Cons remainingArguments = (Cons) argumentList.getRest(); if (remainingArguments.isNull()) - return T; + return done(T); SExpression firstArgument = argumentList.getFirst(); SExpression secondArgument = remainingArguments.getFirst(); @@ -40,9 +43,9 @@ public class NUMERIC_LESS extends LispFunction { LispNumber number2 = (LispNumber) secondArgument; if (!isFirstLesser(number1, number2)) - return NIL; + return done(NIL); - return callTailRecursive(remainingArguments); + return tailCall(() -> callTailRecursive(remainingArguments)); } private boolean isFirstLesser(LispNumber number1, LispNumber number2) { diff --git a/test/function/builtin/math/DIVIDETest.java b/test/function/builtin/math/DIVIDETest.java index 5cc9f59..92208e1 100644 --- a/test/function/builtin/math/DIVIDETest.java +++ b/test/function/builtin/math/DIVIDETest.java @@ -1,5 +1,6 @@ package function.builtin.math; +import static testutil.TestUtilities.assertIsErrorWithMessage; import static testutil.TestUtilities.assertSExpressionsMatch; import static testutil.TestUtilities.evaluateString; import static testutil.TestUtilities.parseString; @@ -8,6 +9,7 @@ import org.junit.Test; import function.ArgumentValidator.BadArgumentTypeException; import function.ArgumentValidator.TooFewArgumentsException; +import function.builtin.math.DIVIDE.DivideByZeroException; import testutil.SymbolAndFunctionCleaner; public class DIVIDETest extends SymbolAndFunctionCleaner { @@ -64,4 +66,14 @@ public class DIVIDETest extends SymbolAndFunctionCleaner { evaluateString("(/)"); } + @Test(expected = DivideByZeroException.class) + public void divideByZero() { + evaluateString("(/ 2 0)"); + } + + @Test + public void divideByZeroException_HasCorrectAttributes() { + assertIsErrorWithMessage(new DivideByZeroException()); + } + }