diff --git a/src/main/kotlin/function/builtin/special/COND.java b/src/main/kotlin/function/builtin/special/COND.java deleted file mode 100644 index 5194bd4..0000000 --- a/src/main/kotlin/function/builtin/special/COND.java +++ /dev/null @@ -1,67 +0,0 @@ -package function.builtin.special; - -import function.ArgumentValidator; -import function.FunctionNames; -import function.LispSpecialFunction; -import recursion.TailCall; -import sexpression.Cons; -import sexpression.Nil; -import sexpression.SExpression; - -import static function.builtin.Eval.eval; -import static recursion.TailCalls.done; -import static recursion.TailCalls.tailCall; - -@FunctionNames({ "COND" }) -public class COND extends LispSpecialFunction { - - private ArgumentValidator argumentValidator; - - public COND(String name) { - this.argumentValidator = new ArgumentValidator(name); - this.argumentValidator.setEveryArgumentExpectedType(Cons.class); - this.argumentValidator.setEveryArgumentExcludedType(Nil.class); - } - - @Override - public SExpression call(Cons argumentList) { - argumentValidator.validate(argumentList); - - return callTailRecursive(argumentList).invoke(); - } - - private TailCall callTailRecursive(Cons argumentList) { - if (argumentList.isNull()) - return done(Nil.INSTANCE); - - Cons clause = (Cons) argumentList.getFirst(); - Cons remainingClauses = (Cons) argumentList.getRest(); - SExpression test = eval(clause.getFirst()); - - if (isTestSuccessful(test)) - return done(evaluateConsequents(clause.getRest(), test)); - - return tailCall(() -> callTailRecursive(remainingClauses)); - } - - private boolean isTestSuccessful(SExpression test) { - return test != Nil.INSTANCE; - } - - private SExpression evaluateConsequents(SExpression consequentList, SExpression test) { - SExpression lastConsequentValue = test; - - for (; consequentList.isCons(); consequentList = advanceCons(consequentList)) - lastConsequentValue = eval(getFirst(consequentList)); - - return lastConsequentValue; - } - - private SExpression advanceCons(SExpression knownCons) { - return ((Cons) knownCons).getRest(); - } - - private SExpression getFirst(SExpression knownCons) { - return ((Cons) knownCons).getFirst(); - } -} diff --git a/src/main/kotlin/function/builtin/special/Case.kt b/src/main/kotlin/function/builtin/special/Case.kt index 17809aa..a5a7192 100644 --- a/src/main/kotlin/function/builtin/special/Case.kt +++ b/src/main/kotlin/function/builtin/special/Case.kt @@ -57,5 +57,5 @@ class Case(name: String) : LispSpecialFunction() { } private fun evaluateConsequents(clause: Cons) = - clause.drop(1).fold(Nil as SExpression) { _, it -> eval(it.first) } + clause.drop(1).fold(Nil as SExpression) { _, cons -> eval(cons.first) } } diff --git a/src/main/kotlin/function/builtin/special/Cond.kt b/src/main/kotlin/function/builtin/special/Cond.kt new file mode 100644 index 0000000..c339542 --- /dev/null +++ b/src/main/kotlin/function/builtin/special/Cond.kt @@ -0,0 +1,41 @@ +package function.builtin.special + +import function.ArgumentValidator +import function.FunctionNames +import function.LispSpecialFunction +import function.builtin.Eval.Companion.eval +import sexpression.Cons +import sexpression.Nil +import sexpression.SExpression + +@FunctionNames("COND") +class Cond(name: String) : LispSpecialFunction() { + + private val argumentValidator = ArgumentValidator(name).apply { + setEveryArgumentExpectedType(Cons::class.java) + setEveryArgumentExcludedType(Nil::class.java) + } + + override fun call(argumentList: Cons): SExpression { + argumentValidator.validate(argumentList) + + return callTailRecursive(argumentList) + } + + private tailrec fun callTailRecursive(argumentList: Cons): SExpression { + if (argumentList.isNull) + return Nil + + val clause = argumentList.first as Cons + val remainingClauses = argumentList.rest as Cons + val test = eval(clause.first) + + return if (!test.isNull) + evaluateConsequents(clause, test) + else + callTailRecursive(remainingClauses) + } + + private fun evaluateConsequents(clause: Cons, test: SExpression) = + clause.drop(1).fold(test) { _, cons -> eval(cons.first) } +} diff --git a/src/test/kotlin/function/builtin/special/CONDTest.java b/src/test/kotlin/function/builtin/special/CONDTest.java deleted file mode 100644 index fd015c2..0000000 --- a/src/test/kotlin/function/builtin/special/CONDTest.java +++ /dev/null @@ -1,96 +0,0 @@ -package function.builtin.special; - -import function.ArgumentValidator.BadArgumentTypeException; -import function.ArgumentValidator.DottedArgumentListException; -import org.junit.Test; -import testutil.SymbolAndFunctionCleaner; - -import static testutil.TestUtilities.assertSExpressionsMatch; -import static testutil.TestUtilities.evaluateString; -import static testutil.TestUtilities.parseString; - -public class CONDTest extends SymbolAndFunctionCleaner { - - @Test - public void condWithNoArguments() { - String input = "(cond)"; - - assertSExpressionsMatch(parseString("nil"), evaluateString(input)); - } - - @Test - public void condWithTrue() { - String input = "(cond (T))"; - - assertSExpressionsMatch(parseString("T"), evaluateString(input)); - } - - @Test - public void condWithNumber() { - String input = "(cond ((+ 1 2)))"; - - assertSExpressionsMatch(parseString("3"), evaluateString(input)); - } - - @Test - public void condWithSingleClause() { - String input = "(cond (T \"true\"))"; - - assertSExpressionsMatch(parseString("\"true\""), evaluateString(input)); - } - - @Test - public void condWithMultipleClauses() { - String input = "(cond ((= 1 2) 2) ((= 1 2) 2) ((= 1 1) 3))"; - - assertSExpressionsMatch(parseString("3"), evaluateString(input)); - } - - @Test - public void condWithMultipleTrueTests_ReturnsFirstOne() { - String input = "(cond ((= 1 1) 2) ((= 1 1) 3))"; - - assertSExpressionsMatch(parseString("2"), evaluateString(input)); - } - - @Test - public void condWithMultipleTrueTests_OnlyEvaluatesFirstOne() { - String input = "(cond ((= 1 1) 2) ((= 1 1) x))"; - - assertSExpressionsMatch(parseString("2"), evaluateString(input)); - } - - @Test - public void condWithMultipleValuesInConsequent_OnlyReturnsLast() { - String input = "(cond ((= 1 1) 2 3 4))"; - - assertSExpressionsMatch(parseString("4"), evaluateString(input)); - } - - @Test - public void condWithNoTrueTest_ReturnsNil() { - String input = "(cond ((= 1 2) T) ((= 1 3) T))"; - - assertSExpressionsMatch(parseString("nil"), evaluateString(input)); - } - - @Test(expected = BadArgumentTypeException.class) - public void condWithEmptyListArgument_ThrowsException() { - evaluateString("(cond ())"); - } - - @Test(expected = BadArgumentTypeException.class) - public void condWithNilArgument_ThrowsException() { - evaluateString("(cond nil)"); - } - - @Test(expected = BadArgumentTypeException.class) - public void condWithNonListArgument_ThrowsException() { - evaluateString("(cond o)"); - } - - @Test(expected = DottedArgumentListException.class) - public void condWithDottedArgumentList_ThrowsException() { - evaluateString("(apply 'cond (cons '(nil T) 'b))"); - } -} diff --git a/src/test/kotlin/function/builtin/special/CondTest.kt b/src/test/kotlin/function/builtin/special/CondTest.kt new file mode 100644 index 0000000..bb0be0f --- /dev/null +++ b/src/test/kotlin/function/builtin/special/CondTest.kt @@ -0,0 +1,114 @@ +package function.builtin.special + +import function.ArgumentValidator.BadArgumentTypeException +import function.ArgumentValidator.DottedArgumentListException +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Test +import testutil.LispTestInstance +import testutil.SymbolAndFunctionCleaner + +import testutil.TestUtilities.assertSExpressionsMatch +import testutil.TestUtilities.evaluateString +import testutil.TestUtilities.parseString + +@LispTestInstance +class CondTest : SymbolAndFunctionCleaner() { + + @Test + fun condWithNoArguments() { + val input = "(cond)" + + assertSExpressionsMatch(parseString("nil"), evaluateString(input)) + } + + @Test + fun condWithTrue() { + val input = "(cond (T))" + + assertSExpressionsMatch(parseString("T"), evaluateString(input)) + } + + @Test + fun condWithNumber() { + val input = "(cond ((+ 1 2)))" + + assertSExpressionsMatch(parseString("3"), evaluateString(input)) + } + + @Test + fun condWithSingleClause() { + val input = "(cond (T \"true\"))" + + assertSExpressionsMatch(parseString("\"true\""), evaluateString(input)) + } + + @Test + fun condWithMultipleClauses() { + val input = "(cond ((= 1 2) 2) ((= 1 2) 2) ((= 1 1) 3))" + + assertSExpressionsMatch(parseString("3"), evaluateString(input)) + } + + @Test + fun condWithMultipleTrueTests_ReturnsFirstOne() { + val input = "(cond ((= 1 1) 2) ((= 1 1) 3))" + + assertSExpressionsMatch(parseString("2"), evaluateString(input)) + } + + @Test + fun condWithMultipleTrueTests_OnlyEvaluatesFirstOne() { + val input = "(cond ((= 1 1) 2) ((= 1 1) x))" + + assertSExpressionsMatch(parseString("2"), evaluateString(input)) + } + + @Test + fun condWithMultipleValuesInConsequent_OnlyReturnsLast() { + val input = "(cond ((= 1 1) 2 3 4))" + + assertSExpressionsMatch(parseString("4"), evaluateString(input)) + } + + @Test + fun condWithNoTrueTest_ReturnsNil() { + val input = "(cond ((= 1 2) T) ((= 1 3) T))" + + assertSExpressionsMatch(parseString("nil"), evaluateString(input)) + } + + @Test + fun condWithEmptyListArgument_ThrowsException() { + assertThrows(BadArgumentTypeException::class.java) { + evaluateString("(cond ())") + } + } + + @Test + fun condWithNilArgument_ThrowsException() { + assertThrows(BadArgumentTypeException::class.java) { + evaluateString("(cond nil)") + } + } + + @Test + fun condWithNonListArgument_ThrowsException() { + assertThrows(BadArgumentTypeException::class.java) { + evaluateString("(cond o)") + } + } + + @Test + fun condWithDottedArgumentList_ThrowsException() { + assertThrows(DottedArgumentListException::class.java) { + evaluateString("(apply 'cond (cons '(nil T) 'b))") + } + } + + @Test + fun condWithDottedClause() { + val input = "(eval `(cond ,(cons T 'pear)))" + + assertSExpressionsMatch(parseString("T"), evaluateString(input)) + } +}