Convert Recur to kotlin

This commit is contained in:
Mike Cifelli 2018-10-29 19:47:25 -04:00
parent 73bcd4da38
commit b4de8fc3ea
7 changed files with 288 additions and 294 deletions

11
pom.xml
View File

@ -8,18 +8,9 @@
<artifactId>transcendental-lisp</artifactId>
<version>1.2.1</version>
<!-- TODO - remove after kotlin 1.3 is released -->
<repositories>
<repository>
<id>kotlin-eap</id>
<name>Kotlin EAP</name>
<url>https://dl.bintray.com/kotlin/kotlin-eap</url>
</repository>
</repositories>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kotlin.version>1.3.0-rc-190</kotlin.version>
<kotlin.version>1.3.0</kotlin.version>
<junit5.version>5.3.1</junit5.version>
<skipTests>false</skipTests>
</properties>

View File

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

View File

@ -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";
}
}
}

View File

@ -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"
}
}

View File

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

View File

@ -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());
}
}

View File

@ -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())
}
}