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)") + } + } +}