diff --git a/pom.xml b/pom.xml index 2eee15b..cefebdc 100644 --- a/pom.xml +++ b/pom.xml @@ -8,18 +8,9 @@ transcendental-lisp 1.2.1 - - - - kotlin-eap - Kotlin EAP - https://dl.bintray.com/kotlin/kotlin-eap - - - UTF-8 - 1.3.0-rc-190 + 1.3.0 5.3.1 false diff --git a/src/main/kotlin/function/builtin/Eval.kt b/src/main/kotlin/function/builtin/Eval.kt index fce2880..0034853 100644 --- a/src/main/kotlin/function/builtin/Eval.kt +++ b/src/main/kotlin/function/builtin/Eval.kt @@ -7,7 +7,7 @@ import function.LispFunction import function.builtin.cons.LIST.makeList import function.builtin.special.Lambda.Lambda.createFunction import function.builtin.special.Lambda.Lambda.isLambdaExpression -import function.builtin.special.RECUR.RecurNotInTailPositionException +import function.builtin.special.Recur.RecurNotInTailPositionException import sexpression.BackquoteExpression import sexpression.Cons import sexpression.LambdaExpression diff --git a/src/main/kotlin/function/builtin/special/RECUR.java b/src/main/kotlin/function/builtin/special/RECUR.java deleted file mode 100644 index 6a362a0..0000000 --- a/src/main/kotlin/function/builtin/special/RECUR.java +++ /dev/null @@ -1,90 +0,0 @@ -package function.builtin.special; - -import error.LispException; -import function.ArgumentValidator; -import function.FunctionNames; -import function.LispSpecialFunction; -import sexpression.Cons; -import sexpression.SExpression; -import table.ExecutionContext; - -import static function.builtin.Eval.evaluateFunctionArgumentList; - -@FunctionNames({ "RECUR" }) -public class RECUR extends LispSpecialFunction { - - private ArgumentValidator argumentValidator; - private ExecutionContext executionContext; - - public RECUR(String name) { - this.argumentValidator = new ArgumentValidator(name); - this.executionContext = ExecutionContext.INSTANCE; - } - - @Override - public SExpression call(Cons argumentList) { - verifyValidRecurCall(); - argumentValidator.validate(argumentList); - Cons recurArguments = getRecurArguments(argumentList); - executionContext.setRecur(); - - return recurArguments; - } - - private void verifyValidRecurCall() { - if (!executionContext.isInFunctionCall()) - throw new RecurOutsideOfFunctionException(); - - if (executionContext.isRecurInitializing()) - throw new NestedRecurException(); - } - - private Cons getRecurArguments(Cons argumentList) { - Cons recurArguments = argumentList; - - try { - executionContext.setRecurInitializing(); - - if (isRecurArgumentListEvaluated()) - recurArguments = evaluateFunctionArgumentList(argumentList); - } finally { - executionContext.clearRecurInitializing(); - } - - return recurArguments; - } - - private boolean isRecurArgumentListEvaluated() { - return executionContext.getCurrentFunction().isArgumentListEvaluated(); - } - - public static class RecurOutsideOfFunctionException extends LispException { - - private static final long serialVersionUID = 1L; - - @Override - public String getMessage() { - return "recur called outide of function"; - } - } - - public static class NestedRecurException extends LispException { - - private static final long serialVersionUID = 1L; - - @Override - public String getMessage() { - return "nested call to recur"; - } - } - - public static class RecurNotInTailPositionException extends LispException { - - private static final long serialVersionUID = 1L; - - @Override - public String getMessage() { - return "recur not in tail position"; - } - } -} diff --git a/src/main/kotlin/function/builtin/special/Recur.kt b/src/main/kotlin/function/builtin/special/Recur.kt new file mode 100644 index 0000000..eecf84a --- /dev/null +++ b/src/main/kotlin/function/builtin/special/Recur.kt @@ -0,0 +1,66 @@ +package function.builtin.special + +import error.LispException +import function.ArgumentValidator +import function.FunctionNames +import function.LispSpecialFunction +import function.builtin.Eval.Companion.evaluateFunctionArgumentList +import sexpression.Cons +import sexpression.SExpression +import table.ExecutionContext + +@FunctionNames("RECUR") +class Recur(name: String) : LispSpecialFunction() { + + private val argumentValidator = ArgumentValidator(name) + + private fun isRecurArgumentListEvaluated() = + ExecutionContext.getCurrentFunction().isArgumentListEvaluated + + override fun call(argumentList: Cons): SExpression { + verifyValidRecurCall() + argumentValidator.validate(argumentList) + val recurArguments = getRecurArguments(argumentList) + ExecutionContext.setRecur() + + return recurArguments + } + + private fun verifyValidRecurCall() { + if (!ExecutionContext.isInFunctionCall()) + throw RecurOutsideOfFunctionException() + + if (ExecutionContext.isRecurInitializing()) + throw NestedRecurException() + } + + private fun getRecurArguments(argumentList: Cons): Cons { + var recurArguments = argumentList + + try { + ExecutionContext.setRecurInitializing() + + if (isRecurArgumentListEvaluated()) + recurArguments = evaluateFunctionArgumentList(argumentList) + } finally { + ExecutionContext.clearRecurInitializing() + } + + return recurArguments + } + + class RecurOutsideOfFunctionException : LispException() { + + override val message = "recur called outside of function" + } + + class NestedRecurException : LispException() { + + override val message = "nested call to recur" + } + + class RecurNotInTailPositionException : LispException() { + + override val message = "recur not in tail position" + } +} diff --git a/src/test/kotlin/function/builtin/EvalTest.kt b/src/test/kotlin/function/builtin/EvalTest.kt index 198200a..e5b6384 100644 --- a/src/test/kotlin/function/builtin/EvalTest.kt +++ b/src/test/kotlin/function/builtin/EvalTest.kt @@ -9,7 +9,7 @@ import function.builtin.Eval.UndefinedFunctionException import function.builtin.Eval.UndefinedSymbolException import function.builtin.Eval.UnmatchedAtSignException import function.builtin.Eval.UnmatchedCommaException -import function.builtin.special.RECUR.RecurNotInTailPositionException +import function.builtin.special.Recur.RecurNotInTailPositionException import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Assertions.assertThrows import org.junit.jupiter.api.Test diff --git a/src/test/kotlin/function/builtin/special/RECURTest.java b/src/test/kotlin/function/builtin/special/RECURTest.java deleted file mode 100644 index 56bb983..0000000 --- a/src/test/kotlin/function/builtin/special/RECURTest.java +++ /dev/null @@ -1,192 +0,0 @@ -package function.builtin.special; - -import function.ArgumentValidator.BadArgumentTypeException; -import function.ArgumentValidator.TooManyArgumentsException; -import function.builtin.special.RECUR.NestedRecurException; -import function.builtin.special.RECUR.RecurNotInTailPositionException; -import function.builtin.special.RECUR.RecurOutsideOfFunctionException; -import org.junit.Test; -import sexpression.SExpression; -import testutil.SymbolAndFunctionCleaner; - -import static org.junit.Assert.fail; -import static testutil.TestUtilities.assertIsErrorWithMessage; -import static testutil.TestUtilities.assertSExpressionsMatch; -import static testutil.TestUtilities.evaluateString; -import static testutil.TestUtilities.parseString; - -public class RECURTest extends SymbolAndFunctionCleaner { - - @Test(expected = RecurOutsideOfFunctionException.class) - public void recurOutsideOfFunction_ThrowsException() { - evaluateString("(recur)"); - } - - @Test(expected = RecurOutsideOfFunctionException.class) - public void recurOutsideOfFunction_AfterFunctionCall_ThrowsException() { - evaluateString("(defun f (n) (if (= n 0) 'ZERO n))"); - evaluateString("(f 2)"); - evaluateString("(recur)"); - } - - @Test(expected = BadArgumentTypeException.class) - public void recurInSpecialFunction_DoesNotEvaluateArguments() { - evaluateString("(define-special tail-recursive (n) (if (= n 0) 'PASS (recur (- n 1))))"); - evaluateString("(tail-recursive 900)"); - } - - @Test(expected = BadArgumentTypeException.class) - public void recurInMacro_DoesNotEvaluateArguments() { - evaluateString("(defmacro tail-recursive (n) (if (= n 0) 'PASS (recur (- n 1))))"); - evaluateString("(tail-recursive 900)"); - } - - @Test(expected = NestedRecurException.class) - public void nestedRecur_ThrowsException() { - evaluateString("(defun tail-recursive (n) (if (= n 0) 'PASS (recur (recur (- n 1)))))"); - evaluateString("(tail-recursive 900)"); - } - - @Test(expected = TooManyArgumentsException.class) - public void functionCallValidatesRecurArguments() { - evaluateString("(defun tail-recursive (n) (if (= n 0) 'PASS (recur (- n 1) 23)))"); - evaluateString("(tail-recursive 900)"); - } - - @Test(expected = RecurNotInTailPositionException.class) - public void recurInNonTailPositionInArgumentList() { - evaluateString("(defun tail-recursive (n) (if (= n 0) 'PASS (list (recur (- n 1)))))"); - evaluateString("(tail-recursive 900)"); - } - - @Test(expected = RecurNotInTailPositionException.class) - public void recurInNonTailPositionInBegin() { - evaluateString("(defun tail-recursive (n) (begin (recur) 2))"); - evaluateString("(tail-recursive 900)"); - } - - @Test(expected = RecurNotInTailPositionException.class) - public void recurInNonTailPositionInApply() { - evaluateString("(defun tail-recursive (n) (if (= n 0) 'PASS (apply 'list (recur (- n 1)))))"); - evaluateString("(tail-recursive 900)"); - } - - @Test - public void recurCallsCurrentFunction() { - evaluateString("(defun tail-recursive (n) (if (= n 0) 'PASS (recur (- n 1))))"); - assertSExpressionsMatch(parseString("PASS"), evaluateString("(tail-recursive 900)")); - } - - @Test - public void recurCallsCurrentFunction_InBegin() { - evaluateString("(defun tail-recursive (n) (if (> n 1) (begin 1 2 (recur (- n 1))) 'PASS))"); - assertSExpressionsMatch(parseString("PASS"), evaluateString("(tail-recursive 900)")); - } - - @Test - public void recurInTailPositionWithApply() { - evaluateString("(defun tail-recursive (n) (if (= n 0) 'PASS (apply 'recur (list (- n 1)))))"); - assertSExpressionsMatch(parseString("PASS"), evaluateString("(tail-recursive 900)")); - } - - @Test - public void recurInTailPositionWithFuncall() { - evaluateString("(defun tail-recursive (n) (if (= n 0) 'PASS (call 'recur (- n 1))))"); - assertSExpressionsMatch(parseString("PASS"), evaluateString("(tail-recursive 900)")); - } - - @Test - public void recurCallsCurrentFunction_WithApply() { - evaluateString("(defun tail-recursive (n) (if (= n 0) 'PASS (recur (- n 1))))"); - assertSExpressionsMatch(parseString("PASS"), evaluateString("(apply 'tail-recursive '(900))")); - } - - @Test - public void recurCallsCurrentFunction_WithFuncall() { - evaluateString("(defun tail-recursive (n) (if (= n 0) 'PASS (recur (- n 1))))"); - assertSExpressionsMatch(parseString("PASS"), evaluateString("(call 'tail-recursive '900)")); - } - - @Test - public void recurWorksAfterFailure() { - evaluateString("(defun bad-tail-recursive (n) (if (= n 0) 'PASS (list (recur (- n 1)))))"); - evaluateString("(defun tail-recursive (n) (if (= n 0) 'PASS (recur (- n 1))))"); - - try { - evaluateString("(bad-tail-recursive 900)"); - fail("expectedException"); - } catch (RecurNotInTailPositionException e) { - } - - assertSExpressionsMatch(parseString("PASS"), evaluateString("(tail-recursive 900)")); - } - - @Test - public void recurWorksAfterFailure2() { - evaluateString("(defun bad-tail-recursive (n) (begin (recur) 2))"); - evaluateString("(defun tail-recursive (n) (if (= n 0) 'PASS (recur (- n 1))))"); - - try { - evaluateString("(bad-tail-recursive 900)"); - fail("expectedException"); - } catch (RecurNotInTailPositionException e) { - } - - assertSExpressionsMatch(parseString("PASS"), evaluateString("(tail-recursive 900)")); - } - - @Test - public void recurWorksAfterNestedFailure() { - evaluateString("(defun bad-tail-recursive (n) (if (= n 0) 'PASS (recur (recur (- n 1)))))"); - evaluateString("(defun tail-recursive (n) (if (= n 0) 'PASS (recur (- n 1))))"); - - try { - evaluateString("(bad-tail-recursive 900)"); - fail("expectedException"); - } catch (NestedRecurException e) { - } - - assertSExpressionsMatch(parseString("PASS"), evaluateString("(tail-recursive 900)")); - } - - @Test - public void recurWithNoArgs_AltersGlobalVariable() { - evaluateString("(defun tail-recursive () (if (= n 0) 'PASS (begin (setq n (- n 1)) (recur))))"); - evaluateString("(setq n 200)"); - assertSExpressionsMatch(parseString("PASS"), evaluateString("(tail-recursive)")); - } - - @Test - public void recurWithLambda() { - SExpression lambdaTailCall = evaluateString("((lambda (n) (if (= n 0) 'PASS (recur (- n 1)))) 2020)"); - assertSExpressionsMatch(parseString("PASS"), lambdaTailCall); - } - - @Test - public void recurWithNestedLambda() { - evaluateString("(defun nested-tail () ((lambda (n) (if (= n 0) 'PASS (recur (- n 1)))) 2020))"); - assertSExpressionsMatch(parseString("PASS"), evaluateString("(nested-tail)")); - } - - @Test - public void recurWithNestedTailRecursiveFunction() { - evaluateString("(defun one (n) (if (= n 0) 0 (recur (- n 1))))"); - evaluateString("(defun two (n) (if (= n 0) 'PASS (recur (one n))))"); - assertSExpressionsMatch(parseString("PASS"), evaluateString("(two 20)")); - } - - @Test - public void nestedRecurException_HasCorrectAttributes() { - assertIsErrorWithMessage(new NestedRecurException()); - } - - @Test - public void recrOutsideOfFunctionException_HasCorrectAttributes() { - assertIsErrorWithMessage(new RecurOutsideOfFunctionException()); - } - - @Test - public void recurNotInTailPositionException_HasCorrectAttributes() { - assertIsErrorWithMessage(new RecurNotInTailPositionException()); - } -} diff --git a/src/test/kotlin/function/builtin/special/RecurTest.kt b/src/test/kotlin/function/builtin/special/RecurTest.kt new file mode 100644 index 0000000..ae5a2e0 --- /dev/null +++ b/src/test/kotlin/function/builtin/special/RecurTest.kt @@ -0,0 +1,219 @@ +package function.builtin.special + +import function.ArgumentValidator.BadArgumentTypeException +import function.ArgumentValidator.TooManyArgumentsException +import function.builtin.special.Recur.NestedRecurException +import function.builtin.special.Recur.RecurNotInTailPositionException +import function.builtin.special.Recur.RecurOutsideOfFunctionException +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.fail +import testutil.LispTestInstance +import testutil.SymbolAndFunctionCleaner +import testutil.TestUtilities.assertIsErrorWithMessage +import testutil.TestUtilities.assertSExpressionsMatch +import testutil.TestUtilities.evaluateString +import testutil.TestUtilities.parseString + +@LispTestInstance +class RecurTest : SymbolAndFunctionCleaner() { + + @Test + fun recurOutsideOfFunction_ThrowsException() { + assertThrows(RecurOutsideOfFunctionException::class.java) { + evaluateString("(recur)") + } + } + + @Test + fun recurOutsideOfFunction_AfterFunctionCall_ThrowsException() { + evaluateString("(defun f (n) (if (= n 0) 'ZERO n))") + evaluateString("(f 2)") + + assertThrows(RecurOutsideOfFunctionException::class.java) { + evaluateString("(recur)") + } + } + + @Test + fun recurInSpecialFunction_DoesNotEvaluateArguments() { + evaluateString("(define-special tail-recursive (n) (if (= n 0) 'PASS (recur (- n 1))))") + + assertThrows(BadArgumentTypeException::class.java) { + evaluateString("(tail-recursive 900)") + } + } + + @Test + fun recurInMacro_DoesNotEvaluateArguments() { + evaluateString("(defmacro tail-recursive (n) (if (= n 0) 'PASS (recur (- n 1))))") + + assertThrows(BadArgumentTypeException::class.java) { + evaluateString("(tail-recursive 900)") + } + } + + @Test + fun nestedRecur_ThrowsException() { + evaluateString("(defun tail-recursive (n) (if (= n 0) 'PASS (recur (recur (- n 1)))))") + + assertThrows(NestedRecurException::class.java) { + evaluateString("(tail-recursive 900)") + } + } + + @Test + fun functionCallValidatesRecurArguments() { + evaluateString("(defun tail-recursive (n) (if (= n 0) 'PASS (recur (- n 1) 23)))") + + assertThrows(TooManyArgumentsException::class.java) { + evaluateString("(tail-recursive 900)") + } + } + + @Test + fun recurInNonTailPositionInArgumentList() { + evaluateString("(defun tail-recursive (n) (if (= n 0) 'PASS (list (recur (- n 1)))))") + + assertThrows(RecurNotInTailPositionException::class.java) { + evaluateString("(tail-recursive 900)") + } + } + + @Test + fun recurInNonTailPositionInBegin() { + evaluateString("(defun tail-recursive (n) (begin (recur) 2))") + + assertThrows(RecurNotInTailPositionException::class.java) { + evaluateString("(tail-recursive 900)") + } + } + + @Test + fun recurInNonTailPositionInApply() { + evaluateString("(defun tail-recursive (n) (if (= n 0) 'PASS (apply 'list (recur (- n 1)))))") + + assertThrows(RecurNotInTailPositionException::class.java) { + evaluateString("(tail-recursive 900)") + } + } + + @Test + fun recurCallsCurrentFunction() { + evaluateString("(defun tail-recursive (n) (if (= n 0) 'PASS (recur (- n 1))))") + assertSExpressionsMatch(parseString("PASS"), evaluateString("(tail-recursive 900)")) + } + + @Test + fun recurCallsCurrentFunction_InBegin() { + evaluateString("(defun tail-recursive (n) (if (> n 1) (begin 1 2 (recur (- n 1))) 'PASS))") + assertSExpressionsMatch(parseString("PASS"), evaluateString("(tail-recursive 900)")) + } + + @Test + fun recurInTailPositionWithApply() { + evaluateString("(defun tail-recursive (n) (if (= n 0) 'PASS (apply 'recur (list (- n 1)))))") + assertSExpressionsMatch(parseString("PASS"), evaluateString("(tail-recursive 900)")) + } + + @Test + fun recurInTailPositionWithFuncall() { + evaluateString("(defun tail-recursive (n) (if (= n 0) 'PASS (call 'recur (- n 1))))") + assertSExpressionsMatch(parseString("PASS"), evaluateString("(tail-recursive 900)")) + } + + @Test + fun recurCallsCurrentFunction_WithApply() { + evaluateString("(defun tail-recursive (n) (if (= n 0) 'PASS (recur (- n 1))))") + assertSExpressionsMatch(parseString("PASS"), evaluateString("(apply 'tail-recursive '(900))")) + } + + @Test + fun recurCallsCurrentFunction_WithFuncall() { + evaluateString("(defun tail-recursive (n) (if (= n 0) 'PASS (recur (- n 1))))") + assertSExpressionsMatch(parseString("PASS"), evaluateString("(call 'tail-recursive '900)")) + } + + @Test + fun recurWorksAfterFailure() { + evaluateString("(defun bad-tail-recursive (n) (if (= n 0) 'PASS (list (recur (- n 1)))))") + evaluateString("(defun tail-recursive (n) (if (= n 0) 'PASS (recur (- n 1))))") + + try { + evaluateString("(bad-tail-recursive 900)") + fail("expectedException") + } catch (e: RecurNotInTailPositionException) { + } + + assertSExpressionsMatch(parseString("PASS"), evaluateString("(tail-recursive 900)")) + } + + @Test + fun recurWorksAfterFailure2() { + evaluateString("(defun bad-tail-recursive (n) (begin (recur) 2))") + evaluateString("(defun tail-recursive (n) (if (= n 0) 'PASS (recur (- n 1))))") + + try { + evaluateString("(bad-tail-recursive 900)") + fail("expectedException") + } catch (e: RecurNotInTailPositionException) { + } + + assertSExpressionsMatch(parseString("PASS"), evaluateString("(tail-recursive 900)")) + } + + @Test + fun recurWorksAfterNestedFailure() { + evaluateString("(defun bad-tail-recursive (n) (if (= n 0) 'PASS (recur (recur (- n 1)))))") + evaluateString("(defun tail-recursive (n) (if (= n 0) 'PASS (recur (- n 1))))") + + try { + evaluateString("(bad-tail-recursive 900)") + fail("expectedException") + } catch (e: NestedRecurException) { + } + + assertSExpressionsMatch(parseString("PASS"), evaluateString("(tail-recursive 900)")) + } + + @Test + fun recurWithNoArgs_AltersGlobalVariable() { + evaluateString("(defun tail-recursive () (if (= n 0) 'PASS (begin (setq n (- n 1)) (recur))))") + evaluateString("(setq n 200)") + assertSExpressionsMatch(parseString("PASS"), evaluateString("(tail-recursive)")) + } + + @Test + fun recurWithLambda() { + val lambdaTailCall = evaluateString("((lambda (n) (if (= n 0) 'PASS (recur (- n 1)))) 2020)") + assertSExpressionsMatch(parseString("PASS"), lambdaTailCall) + } + + @Test + fun recurWithNestedLambda() { + evaluateString("(defun nested-tail () ((lambda (n) (if (= n 0) 'PASS (recur (- n 1)))) 2020))") + assertSExpressionsMatch(parseString("PASS"), evaluateString("(nested-tail)")) + } + + @Test + fun recurWithNestedTailRecursiveFunction() { + evaluateString("(defun one (n) (if (= n 0) 0 (recur (- n 1))))") + evaluateString("(defun two (n) (if (= n 0) 'PASS (recur (one n))))") + assertSExpressionsMatch(parseString("PASS"), evaluateString("(two 20)")) + } + + @Test + fun nestedRecurException_HasCorrectAttributes() { + assertIsErrorWithMessage(NestedRecurException()) + } + + @Test + fun recurOutsideOfFunctionException_HasCorrectAttributes() { + assertIsErrorWithMessage(RecurOutsideOfFunctionException()) + } + + @Test + fun recurNotInTailPositionException_HasCorrectAttributes() { + assertIsErrorWithMessage(RecurNotInTailPositionException()) + } +}