Convert Case to kotlin

This commit is contained in:
Mike Cifelli 2018-10-27 15:56:52 -04:00
parent 8ca2eb199c
commit 9998a88569
5 changed files with 288 additions and 281 deletions

11
pom.xml
View File

@ -8,9 +8,18 @@
<artifactId>transcendental-lisp</artifactId> <artifactId>transcendental-lisp</artifactId>
<version>1.2.1</version> <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> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kotlin.version>1.2.71</kotlin.version> <kotlin.version>1.3.0-rc-190</kotlin.version>
<junit5.version>5.3.1</junit5.version> <junit5.version>5.3.1</junit5.version>
<skipTests>false</skipTests> <skipTests>false</skipTests>
</properties> </properties>

View File

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

View File

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

View File

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

View File

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