diff --git a/pom.xml b/pom.xml
index 0347720..2eee15b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -8,9 +8,18 @@
transcendental-lisp
1.2.1
+
+
+
+ kotlin-eap
+ Kotlin EAP
+ https://dl.bintray.com/kotlin/kotlin-eap
+
+
+
UTF-8
- 1.2.71
+ 1.3.0-rc-190
5.3.1
false
diff --git a/src/main/kotlin/function/builtin/special/CASE.java b/src/main/kotlin/function/builtin/special/CASE.java
deleted file mode 100644
index df994d2..0000000
--- a/src/main/kotlin/function/builtin/special/CASE.java
+++ /dev/null
@@ -1,84 +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 sexpression.Symbol;
-
-import static function.builtin.Eval.eval;
-import static function.builtin.predicate.EQUAL.isEqual;
-import static recursion.TailCalls.done;
-import static recursion.TailCalls.tailCall;
-
-@FunctionNames({ "CASE" })
-public class CASE extends LispSpecialFunction {
-
- private ArgumentValidator argumentValidator;
-
- public CASE(String name) {
- this.argumentValidator = new ArgumentValidator(name);
- this.argumentValidator.setMinimumNumberOfArguments(1);
- this.argumentValidator.setTrailingArgumentExpectedType(Cons.class);
- this.argumentValidator.setTrailingArgumentExcludedType(Nil.class);
- }
-
- @Override
- public SExpression call(Cons argumentList) {
- argumentValidator.validate(argumentList);
- SExpression key = eval(argumentList.getFirst());
-
- return callTailRecursive(key, (Cons) argumentList.getRest()).invoke();
- }
-
- private TailCall callTailRecursive(SExpression key, Cons argumentList) {
- if (argumentList.isNull())
- return done(Nil.INSTANCE);
-
- Cons clause = (Cons) argumentList.getFirst();
- Cons remainingClauses = (Cons) argumentList.getRest();
- SExpression keyList = clause.getFirst();
-
- if (isMatch(key, keyList))
- return done(evaluateConsequents(clause.getRest()));
-
- return tailCall(() -> callTailRecursive(key, remainingClauses));
- }
-
- private boolean isMatch(SExpression key, SExpression keyList) {
- if (keyList.isNull())
- return false;
- else if (keyList.isCons())
- return containsMatch(key, keyList);
-
- return isEqual(key, keyList) || isEqual(Symbol.Companion.getT(), keyList);
- }
-
- private boolean containsMatch(SExpression key, SExpression keyList) {
- for (; keyList.isCons(); keyList = advanceCons(keyList))
- if (isEqual(key, getFirst(keyList)))
- return true;
-
- return false;
- }
-
- private SExpression advanceCons(SExpression knownCons) {
- return ((Cons) knownCons).getRest();
- }
-
- private SExpression getFirst(SExpression knownCons) {
- return ((Cons) knownCons).getFirst();
- }
-
- private SExpression evaluateConsequents(SExpression consequentList) {
- SExpression lastConsequentValue = Nil.INSTANCE;
-
- for (; consequentList.isCons(); consequentList = advanceCons(consequentList))
- lastConsequentValue = eval(getFirst(consequentList));
-
- return lastConsequentValue;
- }
-}
diff --git a/src/main/kotlin/function/builtin/special/Case.kt b/src/main/kotlin/function/builtin/special/Case.kt
new file mode 100644
index 0000000..db65fa6
--- /dev/null
+++ b/src/main/kotlin/function/builtin/special/Case.kt
@@ -0,0 +1,72 @@
+package function.builtin.special
+
+import function.ArgumentValidator
+import function.FunctionNames
+import function.LispSpecialFunction
+import function.builtin.Eval.Companion.eval
+import function.builtin.predicate.EQUAL.isEqual
+import sexpression.Cons
+import sexpression.Nil
+import sexpression.SExpression
+import sexpression.Symbol
+
+@FunctionNames("CASE")
+class Case(name: String) : LispSpecialFunction() {
+
+ private val argumentValidator = ArgumentValidator(name).apply {
+ setMinimumNumberOfArguments(1)
+ setTrailingArgumentExpectedType(Cons::class.java)
+ setTrailingArgumentExcludedType(Nil::class.java)
+ }
+
+ override fun call(argumentList: Cons): SExpression {
+ argumentValidator.validate(argumentList)
+ val key = eval(argumentList.first)
+
+ return callTailRecursive(key, argumentList.rest as Cons)
+ }
+
+ private tailrec fun callTailRecursive(key: SExpression, argumentList: Cons): SExpression {
+ if (argumentList.isNull)
+ return Nil
+
+ val clause = argumentList.first as Cons
+ val remainingClauses = argumentList.rest as Cons
+ val keyList = clause.first
+
+ return if (isMatch(key, keyList))
+ evaluateConsequents(clause.rest)
+ else
+ callTailRecursive(key, remainingClauses)
+ }
+
+ private fun isMatch(key: SExpression, keyList: SExpression) = when {
+ keyList.isNull -> false
+ keyList.isCons -> containsMatch(key, keyList)
+ else -> isEqual(key, keyList) || isEqual(Symbol.T, keyList)
+ }
+
+ private fun containsMatch(key: SExpression, keyList: SExpression): Boolean {
+ if (keyList.isCons) {
+ (keyList as Cons).forEach {
+ if (isEqual(key, it.first)) {
+ return true
+ }
+ }
+ }
+
+ return false
+ }
+
+ private fun evaluateConsequents(consequentList: SExpression): SExpression {
+ var lastConsequentValue: SExpression = Nil
+
+ if (consequentList.isCons) {
+ (consequentList as Cons).forEach {
+ lastConsequentValue = eval(it.first)
+ }
+ }
+
+ return lastConsequentValue
+ }
+}
diff --git a/src/test/kotlin/function/builtin/special/CASETest.java b/src/test/kotlin/function/builtin/special/CASETest.java
deleted file mode 100644
index a113d97..0000000
--- a/src/test/kotlin/function/builtin/special/CASETest.java
+++ /dev/null
@@ -1,196 +0,0 @@
-package function.builtin.special;
-
-import function.ArgumentValidator.BadArgumentTypeException;
-import function.ArgumentValidator.TooFewArgumentsException;
-import org.junit.Test;
-import testutil.SymbolAndFunctionCleaner;
-
-import static testutil.TestUtilities.assertSExpressionsMatch;
-import static testutil.TestUtilities.evaluateString;
-import static testutil.TestUtilities.parseString;
-
-public class CASETest extends SymbolAndFunctionCleaner {
-
- @Test
- public void caseWithKeyOnly() {
- String input = "(case t)";
-
- assertSExpressionsMatch(parseString("nil"), evaluateString(input));
- }
-
- @Test
- public void caseWithEmptyConsequent() {
- String input = "(case :a ((:a)))";
-
- assertSExpressionsMatch(parseString("nil"), evaluateString(input));
- }
-
- @Test
- public void caseWithOneClause_Match() {
- String input = "(case :a ((:a) 'banana))";
-
- assertSExpressionsMatch(parseString("banana"), evaluateString(input));
- }
-
- @Test
- public void caseWithOneClause_NoMatch() {
- String input = "(case :a ((:b) 'banana))";
-
- assertSExpressionsMatch(parseString("nil"), evaluateString(input));
- }
-
- @Test
- public void caseWithSeveralClauses_Match() {
- String input = "(case :a ((:b) 'orange) ((:a) 'banana))";
-
- assertSExpressionsMatch(parseString("banana"), evaluateString(input));
- }
-
- @Test
- public void caseWithSeveralClauses_NoMatch() {
- String input = "(case :a ((:b) 'orange) ((:c) 'banana))";
-
- assertSExpressionsMatch(parseString("nil"), evaluateString(input));
- }
-
- @Test
- public void caseWithSeveralItemsInKeyList_Match() {
- String input = "(case :a ((:b :a) 'orange) ((:c :d) 'banana))";
-
- assertSExpressionsMatch(parseString("orange"), evaluateString(input));
- }
-
- @Test
- public void caseWithSeveralItemsInKeyList_NoMatch() {
- String input = "(case :a ((:b :f) 'orange) ((:c :d) 'banana))";
-
- assertSExpressionsMatch(parseString("nil"), evaluateString(input));
- }
-
- @Test
- public void caseWithSymbolicKeyList_Match() {
- String input = "(case :a (:a 'orange))";
-
- assertSExpressionsMatch(parseString("orange"), evaluateString(input));
- }
-
- @Test
- public void caseWithSymbolicKeyList_NoMatch() {
- String input = "(case :a (:b 'orange))";
-
- assertSExpressionsMatch(parseString("nil"), evaluateString(input));
- }
-
- @Test
- public void caseDoesNotEvaluateKeyList() {
- String input = "(case 'x ((x) t))";
-
- assertSExpressionsMatch(parseString("t"), evaluateString(input));
- }
-
- @Test
- public void caseWithEmptyKeyList() {
- String input = "(case nil (() 'orange))";
-
- assertSExpressionsMatch(parseString("nil"), evaluateString(input));
- }
-
- @Test
- public void caseWithNil() {
- String input = "(case nil ((nil) 'orange))";
-
- assertSExpressionsMatch(parseString("orange"), evaluateString(input));
- }
-
- @Test
- public void caseWithEmptyList() {
- String input = "(case () ((()) 'orange))";
-
- assertSExpressionsMatch(parseString("orange"), evaluateString(input));
- }
-
- @Test
- public void caseWithList() {
- String input = "(case '(5 4 3) (((1 2) (5 4 3)) 'orange))";
-
- assertSExpressionsMatch(parseString("orange"), evaluateString(input));
- }
-
- @Test
- public void caseWithDefaultClause() {
- String input = "(case nil (() 'banana) (t 'orange))";
-
- assertSExpressionsMatch(parseString("orange"), evaluateString(input));
- }
-
- @Test
- public void caseWithOutOfOrderDefaultClause() {
- String input = "(case :a (t 'orange) (:a 'banana))";
-
- assertSExpressionsMatch(parseString("orange"), evaluateString(input));
- }
-
- @Test
- public void caseWithKeyListContainingT() {
- String input = "(case t ((t) 'banana))";
-
- assertSExpressionsMatch(parseString("banana"), evaluateString(input));
- }
-
- @Test
- public void caseWithMultipleMatches_ReturnsFirst() {
- String input = "(case 2 ((0) 'banana) ((1) 'apple) ((2) 'avocado) ((2) 'greenbean))";
-
- assertSExpressionsMatch(parseString("avocado"), evaluateString(input));
- }
-
- @Test
- public void caseEvaluatesMultipleConsequents() {
- String input = "(case 2 (1 1) (2 (setq x 'x) (setq y 'y)) (3 3)))";
-
- evaluateString(input);
-
- assertSExpressionsMatch(parseString("x"), evaluateString("x"));
- assertSExpressionsMatch(parseString("y"), evaluateString("y"));
- }
-
- @Test
- public void caseReturnsValueOfLastConsequent() {
- String input = "(case 2 (1 1) (2 3 4 5) (3 3))";
-
- assertSExpressionsMatch(parseString("5"), evaluateString(input));
- }
-
- @Test
- public void caseOnlyEvaluatesConsequentInFirstMatchingClause() {
- String input = "(case 2 ((0) (setq zero 0)) ((1) (setq one 1)) ((2) (setq two '2)) ((2) (setq two 'two)))";
-
- evaluateString("(setq zero nil)");
- evaluateString("(setq one nil)");
- evaluateString(input);
-
- assertSExpressionsMatch(parseString("nil"), evaluateString("zero"));
- assertSExpressionsMatch(parseString("nil"), evaluateString("one"));
- assertSExpressionsMatch(parseString("2"), evaluateString("two"));
- }
-
- @Test(expected = TooFewArgumentsException.class)
- public void caseWithTooFewArguments() {
- evaluateString("(case)");
- }
-
- @Test(expected = BadArgumentTypeException.class)
- public void caseWithNonListClause() {
- evaluateString("(case :a t)");
- }
-
- @Test(expected = BadArgumentTypeException.class)
- public void caseWithEmptyClause() {
- evaluateString("(case :a ())");
- }
-
- @Test(expected = BadArgumentTypeException.class)
- public void caseWithNilClause() {
- evaluateString("(case :a nil)");
- }
-}
diff --git a/src/test/kotlin/function/builtin/special/CaseTest.kt b/src/test/kotlin/function/builtin/special/CaseTest.kt
new file mode 100644
index 0000000..df7da51
--- /dev/null
+++ b/src/test/kotlin/function/builtin/special/CaseTest.kt
@@ -0,0 +1,206 @@
+package function.builtin.special
+
+import function.ArgumentValidator.BadArgumentTypeException
+import function.ArgumentValidator.TooFewArgumentsException
+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 CaseTest : SymbolAndFunctionCleaner() {
+
+ @Test
+ fun caseWithKeyOnly() {
+ val input = "(case t)"
+
+ assertSExpressionsMatch(parseString("nil"), evaluateString(input))
+ }
+
+ @Test
+ fun caseWithEmptyConsequent() {
+ val input = "(case :a ((:a)))"
+
+ assertSExpressionsMatch(parseString("nil"), evaluateString(input))
+ }
+
+ @Test
+ fun caseWithOneClause_Match() {
+ val input = "(case :a ((:a) 'banana))"
+
+ assertSExpressionsMatch(parseString("banana"), evaluateString(input))
+ }
+
+ @Test
+ fun caseWithOneClause_NoMatch() {
+ val input = "(case :a ((:b) 'banana))"
+
+ assertSExpressionsMatch(parseString("nil"), evaluateString(input))
+ }
+
+ @Test
+ fun caseWithSeveralClauses_Match() {
+ val input = "(case :a ((:b) 'orange) ((:a) 'banana))"
+
+ assertSExpressionsMatch(parseString("banana"), evaluateString(input))
+ }
+
+ @Test
+ fun caseWithSeveralClauses_NoMatch() {
+ val input = "(case :a ((:b) 'orange) ((:c) 'banana))"
+
+ assertSExpressionsMatch(parseString("nil"), evaluateString(input))
+ }
+
+ @Test
+ fun caseWithSeveralItemsInKeyList_Match() {
+ val input = "(case :a ((:b :a) 'orange) ((:c :d) 'banana))"
+
+ assertSExpressionsMatch(parseString("orange"), evaluateString(input))
+ }
+
+ @Test
+ fun caseWithSeveralItemsInKeyList_NoMatch() {
+ val input = "(case :a ((:b :f) 'orange) ((:c :d) 'banana))"
+
+ assertSExpressionsMatch(parseString("nil"), evaluateString(input))
+ }
+
+ @Test
+ fun caseWithSymbolicKeyList_Match() {
+ val input = "(case :a (:a 'orange))"
+
+ assertSExpressionsMatch(parseString("orange"), evaluateString(input))
+ }
+
+ @Test
+ fun caseWithSymbolicKeyList_NoMatch() {
+ val input = "(case :a (:b 'orange))"
+
+ assertSExpressionsMatch(parseString("nil"), evaluateString(input))
+ }
+
+ @Test
+ fun caseDoesNotEvaluateKeyList() {
+ val input = "(case 'x ((x) t))"
+
+ assertSExpressionsMatch(parseString("t"), evaluateString(input))
+ }
+
+ @Test
+ fun caseWithEmptyKeyList() {
+ val input = "(case nil (() 'orange))"
+
+ assertSExpressionsMatch(parseString("nil"), evaluateString(input))
+ }
+
+ @Test
+ fun caseWithNil() {
+ val input = "(case nil ((nil) 'orange))"
+
+ assertSExpressionsMatch(parseString("orange"), evaluateString(input))
+ }
+
+ @Test
+ fun caseWithEmptyList() {
+ val input = "(case () ((()) 'orange))"
+
+ assertSExpressionsMatch(parseString("orange"), evaluateString(input))
+ }
+
+ @Test
+ fun caseWithList() {
+ val input = "(case '(5 4 3) (((1 2) (5 4 3)) 'orange))"
+
+ assertSExpressionsMatch(parseString("orange"), evaluateString(input))
+ }
+
+ @Test
+ fun caseWithDefaultClause() {
+ val input = "(case nil (() 'banana) (t 'orange))"
+
+ assertSExpressionsMatch(parseString("orange"), evaluateString(input))
+ }
+
+ @Test
+ fun caseWithOutOfOrderDefaultClause() {
+ val input = "(case :a (t 'orange) (:a 'banana))"
+
+ assertSExpressionsMatch(parseString("orange"), evaluateString(input))
+ }
+
+ @Test
+ fun caseWithKeyListContainingT() {
+ val input = "(case t ((t) 'banana))"
+
+ assertSExpressionsMatch(parseString("banana"), evaluateString(input))
+ }
+
+ @Test
+ fun caseWithMultipleMatches_ReturnsFirst() {
+ val input = "(case 2 ((0) 'banana) ((1) 'apple) ((2) 'avocado) ((2) 'greenbean))"
+
+ assertSExpressionsMatch(parseString("avocado"), evaluateString(input))
+ }
+
+ @Test
+ fun caseEvaluatesMultipleConsequents() {
+ val input = "(case 2 (1 1) (2 (setq x 'x) (setq y 'y)) (3 3)))"
+
+ evaluateString(input)
+
+ assertSExpressionsMatch(parseString("x"), evaluateString("x"))
+ assertSExpressionsMatch(parseString("y"), evaluateString("y"))
+ }
+
+ @Test
+ fun caseReturnsValueOfLastConsequent() {
+ val input = "(case 2 (1 1) (2 3 4 5) (3 3))"
+
+ assertSExpressionsMatch(parseString("5"), evaluateString(input))
+ }
+
+ @Test
+ fun caseOnlyEvaluatesConsequentInFirstMatchingClause() {
+ val input = "(case 2 ((0) (setq zero 0)) ((1) (setq one 1)) ((2) (setq two '2)) ((2) (setq two 'two)))"
+
+ evaluateString("(setq zero nil)")
+ evaluateString("(setq one nil)")
+ evaluateString(input)
+
+ assertSExpressionsMatch(parseString("nil"), evaluateString("zero"))
+ assertSExpressionsMatch(parseString("nil"), evaluateString("one"))
+ assertSExpressionsMatch(parseString("2"), evaluateString("two"))
+ }
+
+ @Test
+ fun caseWithTooFewArguments() {
+ assertThrows(TooFewArgumentsException::class.java) {
+ evaluateString("(case)")
+ }
+ }
+
+ @Test
+ fun caseWithNonListClause() {
+ assertThrows(BadArgumentTypeException::class.java) {
+ evaluateString("(case :a t)")
+ }
+ }
+
+ @Test
+ fun caseWithEmptyClause() {
+ assertThrows(BadArgumentTypeException::class.java) {
+ evaluateString("(case :a ())")
+ }
+ }
+
+ @Test
+ fun caseWithNilClause() {
+ assertThrows(BadArgumentTypeException::class.java) {
+ evaluateString("(case :a nil)")
+ }
+ }
+}