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" }) @FunctionNames({ "EQUAL" })
public class EQUAL extends LispFunction { public class EQUAL extends LispFunction {
public static boolean isEqual(SExpression firstArgument, SExpression secondArgument) {
return firstArgument.toString().equals(secondArgument.toString());
}
private ArgumentValidator argumentValidator; private ArgumentValidator argumentValidator;
public EQUAL() { public EQUAL() {
@ -27,8 +31,4 @@ public class EQUAL extends LispFunction {
return isEqual(firstArgument, secondArgument) ? Symbol.T : Nil.getInstance(); 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(AND.class);
allBuiltIns.add(APPLY.class); allBuiltIns.add(APPLY.class);
allBuiltIns.add(ATOM.class); allBuiltIns.add(ATOM.class);
allBuiltIns.add(CASE.class);
allBuiltIns.add(COND.class); allBuiltIns.add(COND.class);
allBuiltIns.add(CONS.class); allBuiltIns.add(CONS.class);
allBuiltIns.add(DEFINE_MACRO.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 { public class CONDTester {
@Test @Test
public void testCondWithNoArguments() { public void condWithNoArguments() {
String input = "(cond)"; String input = "(cond)";
assertSExpressionsMatch(parseString("nil"), evaluateString(input)); assertSExpressionsMatch(parseString("nil"), evaluateString(input));
} }
@Test @Test
public void testCondWithTrue() { public void condWithTrue() {
String input = "(cond (T))"; String input = "(cond (T))";
assertSExpressionsMatch(parseString("T"), evaluateString(input)); assertSExpressionsMatch(parseString("T"), evaluateString(input));
} }
@Test @Test
public void testCondWithNumber() { public void condWithNumber() {
String input = "(cond ((+ 1 2)))"; String input = "(cond ((+ 1 2)))";
assertSExpressionsMatch(parseString("3"), evaluateString(input)); assertSExpressionsMatch(parseString("3"), evaluateString(input));
} }
@Test @Test
public void testCondWithSingleExpression() { public void condWithSingleExpression() {
String input = "(cond (T \"true\"))"; String input = "(cond (T \"true\"))";
assertSExpressionsMatch(parseString("\"true\""), evaluateString(input)); assertSExpressionsMatch(parseString("\"true\""), evaluateString(input));
} }
@Test @Test
public void testCondWithMultipleExpressions() { public void condWithMultipleExpressions() {
String input = "(cond ((= 1 2) 2) ((= 1 2) 2) ((= 1 1) 3))"; String input = "(cond ((= 1 2) 2) ((= 1 2) 2) ((= 1 1) 3))";
assertSExpressionsMatch(parseString("3"), evaluateString(input)); assertSExpressionsMatch(parseString("3"), evaluateString(input));
} }
@Test @Test
public void testCondWithMultipleConditionsMatching_ReturnFirstOne() { public void condWithMultipleConditionsMatching_ReturnFirstOne() {
String input = "(cond ((= 1 1) 2) ((= 1 1) 3))"; String input = "(cond ((= 1 1) 2) ((= 1 1) 3))";
assertSExpressionsMatch(parseString("2"), evaluateString(input)); assertSExpressionsMatch(parseString("2"), evaluateString(input));
} }
@Test @Test
public void testCondWithMultipleConditionsMatching_OnlyEvaluatesFirstOne() { public void condWithMultipleConditionsMatching_OnlyEvaluatesFirstOne() {
String input = "(cond ((= 1 1) 2) ((= 1 1) x))"; String input = "(cond ((= 1 1) 2) ((= 1 1) x))";
assertSExpressionsMatch(parseString("2"), evaluateString(input)); assertSExpressionsMatch(parseString("2"), evaluateString(input));
} }
@Test @Test
public void testCondWithMultipleResultValues_OnlyReturnsLast() { public void condWithMultipleResultValues_OnlyReturnsLast() {
String input = "(cond ((= 1 1) 2 3 4))"; String input = "(cond ((= 1 1) 2 3 4))";
assertSExpressionsMatch(parseString("4"), evaluateString(input)); assertSExpressionsMatch(parseString("4"), evaluateString(input));
} }
@Test @Test
public void testCondWithNoConditionMatching_ReturnsNil() { public void condWithNoConditionMatching_ReturnsNil() {
String input = "(cond ((= 1 2) T) ((= 1 3) T))"; String input = "(cond ((= 1 2) T) ((= 1 3) T))";
assertSExpressionsMatch(parseString("nil"), evaluateString(input)); assertSExpressionsMatch(parseString("nil"), evaluateString(input));
} }
@Test(expected = BadArgumentTypeException.class) @Test(expected = BadArgumentTypeException.class)
public void testCondWithNilArgument_ThrowsException() { public void condWithNilArgument_ThrowsException() {
evaluateString("(cond ())"); evaluateString("(cond ())");
} }
@Test(expected = BadArgumentTypeException.class) @Test(expected = BadArgumentTypeException.class)
public void testCondWithNonListArgument_ThrowsException() { public void condWithNonListArgument_ThrowsException() {
evaluateString("(cond o)"); evaluateString("(cond o)");
} }
@Test(expected = DottedArgumentListException.class) @Test(expected = DottedArgumentListException.class)
public void testCondWithDottedArgumentList_ThrowsException() { public void condWithDottedArgumentList_ThrowsException() {
evaluateString("(apply 'cond (cons '(nil T) 'b))"); evaluateString("(apply 'cond (cons '(nil T) 'b))");
} }

View File

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

View File

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

View File

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

View File

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