Resolves #11 - The CASE special function has been added

Refactored some unit tests
This commit is contained in:
Mike Cifelli 2017-02-28 15:01:05 -05:00
parent 544df91c27
commit 05f75b627b
9 changed files with 291 additions and 44 deletions

View File

@ -6,6 +6,10 @@ import sexpression.*;
@FunctionNames({ "EQUAL" })
public class EQUAL extends LispFunction {
public static boolean isEqual(SExpression firstArgument, SExpression secondArgument) {
return firstArgument.toString().equals(secondArgument.toString());
}
private ArgumentValidator argumentValidator;
public EQUAL() {
@ -27,8 +31,4 @@ public class EQUAL extends LispFunction {
return isEqual(firstArgument, secondArgument) ? Symbol.T : Nil.getInstance();
}
private boolean isEqual(SExpression firstArgument, SExpression secondArgument) {
return firstArgument.toString().equals(secondArgument.toString());
}
}

View File

@ -0,0 +1,80 @@
package function.builtin.special;
import static function.builtin.EVAL.eval;
import static function.builtin.predicate.EQUAL.isEqual;
import function.*;
import sexpression.*;
@FunctionNames({ "CASE" })
public class CASE extends LispSpecialFunction {
private ArgumentValidator argumentValidator;
public CASE() {
this.argumentValidator = new ArgumentValidator("CASE");
this.argumentValidator.setMinimumNumberOfArguments(1);
this.argumentValidator.setTrailingArgumentExpectedType(Cons.class);
this.argumentValidator.doNotAcceptNil();
}
public SExpression call(Cons argumentList) {
argumentValidator.validate(argumentList);
SExpression key = eval(argumentList.getFirst());
return callTailRecursive(key, (Cons) argumentList.getRest());
}
private SExpression callTailRecursive(SExpression key, Cons argumentList) {
if (argumentList.isNull())
return Nil.getInstance();
Cons clause = (Cons) argumentList.getFirst();
Cons remainingClauses = (Cons) argumentList.getRest();
SExpression keyList = clause.getFirst();
if (isMatch(key, keyList))
return evaluateResult(clause);
return 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) || isKeyListT(keyList);
}
private boolean containsMatch(SExpression key, SExpression keyList) {
for (; keyList.isCons(); keyList = advanceCons(keyList))
if (isEqual(key, getFirst(keyList)))
return true;
return false;
}
private boolean isKeyListT(SExpression keyList) {
return isEqual(Symbol.T, keyList);
}
private SExpression advanceCons(SExpression knownCons) {
return ((Cons) knownCons).getRest();
}
private SExpression getFirst(SExpression knownCons) {
return ((Cons) knownCons).getFirst();
}
private SExpression evaluateResult(Cons clause) {
SExpression lastResultValue = Nil.getInstance();
for (SExpression result = clause.getRest(); result.isCons(); result = advanceCons(result))
lastResultValue = eval(getFirst(result));
return lastResultValue;
}
}

View File

@ -39,6 +39,7 @@ public class FunctionTable {
allBuiltIns.add(AND.class);
allBuiltIns.add(APPLY.class);
allBuiltIns.add(ATOM.class);
allBuiltIns.add(CASE.class);
allBuiltIns.add(COND.class);
allBuiltIns.add(CONS.class);
allBuiltIns.add(DEFINE_MACRO.class);

View File

@ -0,0 +1,149 @@
package function.builtin.special;
import static testutil.TestUtilities.*;
import org.junit.*;
import function.ArgumentValidator.*;
import table.ExecutionContext;
public class CASETester {
private ExecutionContext executionContext;
public CASETester() {
this.executionContext = ExecutionContext.getInstance();
}
@Before
public void setUp() {
executionContext.clearContext();
}
@After
public void tearDown() {
executionContext.clearContext();
}
@Test
public void caseWithOnlySwitchExpression() {
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 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 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 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 caseOnlyEvaluatesFirstMatchingClause() {
String input = "(case 2 ((0) (setf zero 0)) ((1) (setf one 1)) ((2) (setf two '2)) ((2) (setf two 'two)))";
evaluateString("(setf zero nil)");
evaluateString("(setf 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)");
}
}

View File

@ -9,80 +9,80 @@ import function.ArgumentValidator.*;
public class CONDTester {
@Test
public void testCondWithNoArguments() {
public void condWithNoArguments() {
String input = "(cond)";
assertSExpressionsMatch(parseString("nil"), evaluateString(input));
}
@Test
public void testCondWithTrue() {
public void condWithTrue() {
String input = "(cond (T))";
assertSExpressionsMatch(parseString("T"), evaluateString(input));
}
@Test
public void testCondWithNumber() {
public void condWithNumber() {
String input = "(cond ((+ 1 2)))";
assertSExpressionsMatch(parseString("3"), evaluateString(input));
}
@Test
public void testCondWithSingleExpression() {
public void condWithSingleExpression() {
String input = "(cond (T \"true\"))";
assertSExpressionsMatch(parseString("\"true\""), evaluateString(input));
}
@Test
public void testCondWithMultipleExpressions() {
public void condWithMultipleExpressions() {
String input = "(cond ((= 1 2) 2) ((= 1 2) 2) ((= 1 1) 3))";
assertSExpressionsMatch(parseString("3"), evaluateString(input));
}
@Test
public void testCondWithMultipleConditionsMatching_ReturnFirstOne() {
public void condWithMultipleConditionsMatching_ReturnFirstOne() {
String input = "(cond ((= 1 1) 2) ((= 1 1) 3))";
assertSExpressionsMatch(parseString("2"), evaluateString(input));
}
@Test
public void testCondWithMultipleConditionsMatching_OnlyEvaluatesFirstOne() {
public void condWithMultipleConditionsMatching_OnlyEvaluatesFirstOne() {
String input = "(cond ((= 1 1) 2) ((= 1 1) x))";
assertSExpressionsMatch(parseString("2"), evaluateString(input));
}
@Test
public void testCondWithMultipleResultValues_OnlyReturnsLast() {
public void condWithMultipleResultValues_OnlyReturnsLast() {
String input = "(cond ((= 1 1) 2 3 4))";
assertSExpressionsMatch(parseString("4"), evaluateString(input));
}
@Test
public void testCondWithNoConditionMatching_ReturnsNil() {
public void condWithNoConditionMatching_ReturnsNil() {
String input = "(cond ((= 1 2) T) ((= 1 3) T))";
assertSExpressionsMatch(parseString("nil"), evaluateString(input));
}
@Test(expected = BadArgumentTypeException.class)
public void testCondWithNilArgument_ThrowsException() {
public void condWithNilArgument_ThrowsException() {
evaluateString("(cond ())");
}
@Test(expected = BadArgumentTypeException.class)
public void testCondWithNonListArgument_ThrowsException() {
public void condWithNonListArgument_ThrowsException() {
evaluateString("(cond o)");
}
@Test(expected = DottedArgumentListException.class)
public void testCondWithDottedArgumentList_ThrowsException() {
public void condWithDottedArgumentList_ThrowsException() {
evaluateString("(apply 'cond (cons '(nil T) 'b))");
}

View File

@ -42,7 +42,7 @@ public class DEFINE_MACROTester {
}
@Test
public void testDefineMacro() {
public void defineMacro() {
String input = "(define-macro f () t)";
assertSExpressionsMatch(parseString("f"), evaluateString(input));
@ -50,7 +50,7 @@ public class DEFINE_MACROTester {
}
@Test
public void testDefineMacroWithEmptyBody() {
public void defineMacroWithEmptyBody() {
String input = "(define-macro f ())";
assertSExpressionsMatch(parseString("f"), evaluateString(input));
@ -58,26 +58,26 @@ public class DEFINE_MACROTester {
}
@Test
public void testDefineMacroDoesNotEvaluatesArguments() {
public void defineMacroDoesNotEvaluatesArguments() {
evaluateString("(define-macro f (x) (car x))");
assertSExpressionsMatch(parseString("quote"), evaluateString("(f '(1 2 3))"));
}
@Test
public void testDefineMacroAdd() {
public void defineMacroAdd() {
evaluateString("(define-macro f (x) (+ (eval x) 23))");
assertSExpressionsMatch(parseString("27"), evaluateString("(f (+ 2 2))"));
}
@Test
public void testDefineMacroSetVariable() {
public void defineMacroSetVariable() {
evaluateString("(define-macro f (x) (set x 23))");
evaluateString("(f y)");
assertSExpressionsMatch(parseString("23"), evaluateString("y"));
}
@Test
public void testDefineMacroVariableCapture() {
public void defineMacroVariableCapture() {
evaluateString("(setf x 0)");
evaluateString("(define-macro f (x) (set x 23))");
evaluateString("(f x)");
@ -85,7 +85,7 @@ public class DEFINE_MACROTester {
}
@Test
public void testDefineMacroAvoidVariableCaptureConvention() {
public void defineMacroAvoidVariableCaptureConvention() {
evaluateString("(setf x 0)");
evaluateString("(define-macro f (-x-) (set -x- 23))");
evaluateString("(f x)");
@ -111,22 +111,22 @@ public class DEFINE_MACROTester {
}
@Test(expected = DottedArgumentListException.class)
public void testDefineMacroWithDottedLambdaList() {
public void defineMacroWithDottedLambdaList() {
evaluateString("(funcall 'define-macro 'x (cons 'a 'b) ())");
}
@Test(expected = BadArgumentTypeException.class)
public void testDefineMacroWithNonSymbolName() {
public void defineMacroWithNonSymbolName() {
evaluateString("(define-macro 1 () ())");
}
@Test(expected = BadArgumentTypeException.class)
public void testDefineMacroWithBadLambdaList() {
public void defineMacroWithBadLambdaList() {
evaluateString("(define-macro x a ())");
}
@Test(expected = TooFewArgumentsException.class)
public void testDefineMacroWithTooFewArguments() {
public void defineMacroWithTooFewArguments() {
evaluateString("(define-macro x)");
}

View File

@ -110,47 +110,47 @@ public class LETTester {
}
@Test(expected = BadArgumentTypeException.class)
public void testLetWithNonList() {
public void letWithNonList() {
evaluateString("(let a)");
}
@Test(expected = BadArgumentTypeException.class)
public void testLetWithNoPairs() {
public void letWithNoPairs() {
evaluateString("(let (a))");
}
@Test(expected = TooFewArgumentsException.class)
public void testLetWithTooFewItemsInPair() {
public void letWithTooFewItemsInPair() {
evaluateString("(let ((a)))");
}
@Test(expected = TooManyArgumentsException.class)
public void testLetWithTooManyItemsInPair() {
public void letWithTooManyItemsInPair() {
evaluateString("(let ((a b c)))");
}
@Test(expected = BadArgumentTypeException.class)
public void testLetWithNonSymbolInPair() {
public void letWithNonSymbolInPair() {
evaluateString("(let ((1 b)))");
}
@Test(expected = TooFewArgumentsException.class)
public void testLetWithTooFewArguments() {
public void letWithTooFewArguments() {
evaluateString("(let)");
}
@Test(expected = DottedArgumentListException.class)
public void testLetWithDottedArgumentList() {
public void letWithDottedArgumentList() {
evaluateString("(apply 'let (cons 'a 'b))");
}
@Test(expected = DottedArgumentListException.class)
public void testLetWithDottedPairList() {
public void letWithDottedPairList() {
evaluateString("(apply 'let (cons (cons 'a 'b) nil))");
}
@Test(expected = DottedArgumentListException.class)
public void testLetWithDottedPair() {
public void letWithDottedPair() {
evaluateString("(apply 'let (cons (cons (cons 'a 'b) nil) nil))");
}

View File

@ -9,26 +9,26 @@ import function.ArgumentValidator.*;
public class QUOTETester {
@Test
public void testQuoteSymbol() {
public void quoteSymbol() {
String input = "'a";
assertSExpressionsMatch(parseString("a"), evaluateString(input));
}
@Test
public void testQuoteList() {
public void quoteList() {
String input = "'(l i s t)";
assertSExpressionsMatch(parseString("(l i s t)"), evaluateString(input));
}
@Test(expected = TooFewArgumentsException.class)
public void testQuoteWithTooFewArguments() {
public void quoteWithTooFewArguments() {
evaluateString("(quote)");
}
@Test(expected = TooManyArgumentsException.class)
public void testQuoteWithTooManyArguments() {
public void quoteWithTooManyArguments() {
evaluateString("(quote a b)");
}

View File

@ -29,11 +29,17 @@ public class SETFTester {
}
@Test
public void testSetf() {
public void setf() {
evaluateString("(setf a 23)");
assertSExpressionsMatch(new LispNumber("23"), evaluateString("a"));
}
@Test
public void setq() {
evaluateString("(setq a 23)");
assertSExpressionsMatch(new LispNumber("23"), evaluateString("a"));
}
@Test
public void lookupDefinedSymbol() {
evaluateString("(setf a 23)");
@ -56,6 +62,17 @@ public class SETFTester {
assertSExpressionsMatch(new LispNumber("94"), evaluateString("a"));
}
@Test
public void setfLocalVariable() {
SymbolTable global = executionContext.getScope();
SymbolTable local = new SymbolTable(global);
local.put("A", new LispNumber("99"));
executionContext.setScope(local);
evaluateString("(setf a 94)");
assertSExpressionsMatch(new LispNumber("94"), evaluateString("a"));
}
@Test(expected = UndefinedSymbolException.class)
public void setfLocalVariableDefined_DoesNotSetGlobal() {
SymbolTable global = executionContext.getScope();
@ -80,17 +97,17 @@ public class SETFTester {
}
@Test(expected = BadArgumentTypeException.class)
public void testSetfWithNonSymbol() {
public void setfWithNonSymbol() {
evaluateString("(setf 1 2)");
}
@Test(expected = TooFewArgumentsException.class)
public void testSetfWithTooFewArguments() {
public void setfWithTooFewArguments() {
evaluateString("(setf x)");
}
@Test(expected = TooManyArgumentsException.class)
public void testSetfWithTooManyArguments() {
public void setfWithTooManyArguments() {
evaluateString("(setf a b c)");
}