Added unit tests and refactored the let form

Wrote a lisp program to calculate the number of bi-weekly paydays in a year
This commit is contained in:
Mike Cifelli 2017-02-04 12:03:16 -05:00
parent db2817f7be
commit d7ca5d09da
3 changed files with 213 additions and 73 deletions

11
lisp/paydays.lisp Normal file
View File

@ -0,0 +1,11 @@
(let ((*first-payday-day* 6)
(*leap-year* 0)
(*days-in-year* 365)
(*two-weeks* 14))
(+ 1
(/ (- (+ *days-in-year*
*leap-year*)
*first-payday-day*)
*two-weeks*))
)

View File

@ -6,94 +6,82 @@ import sexpression.*;
import table.*;
public class LET extends LispFunction {
private ArgumentValidator argumentValidator;
private ExecutionContext executionContext;
private SymbolTable localScope;
public LET() {
this.argumentValidator = new ArgumentValidator("LET");
this.argumentValidator.setMinimumNumberOfArguments(1);
this.argumentValidator.setFirstArgumentExpectedType(Cons.class);
this.executionContext = ExecutionContext.getInstance();
}
public SExpression call(Cons argList) {
// make sure we have received at least one argument
if (argList.nullp()) {
throw new RuntimeException("too few arguments given to LET");
}
public SExpression call(Cons argumentList) {
argumentValidator.validate(argumentList);
Cons variableDefinitions = (Cons) argumentList.getCar();
Cons body = (Cons) argumentList.getCdr();
// create a new symbol table on top of the current environment to add
// all the local variables to
SymbolTable environment = new SymbolTable(executionContext.getScope());
SExpression car = argList.getCar();
Cons cdr = (Cons) argList.getCdr();
addVariablesToTable(environment, car);
executionContext.setScope(environment);
SExpression retval = Nil.getInstance();
// evaluate all S-expression in the body
while (cdr.consp()) {
retval = EVAL.eval(cdr.getCar());
cdr = (Cons) cdr.getCdr();
}
// restore the environment to its original value
executionContext.setScope(environment.getParent());
return retval;
return evaluateInScope(variableDefinitions, body);
}
// Add a list of variables and their values to the specified symbol table.
//
// Parameters: environment - the symbol table to add the variables and
// their values to (must not be null)
// vars - a list of variable/value pairs (must be either a
// proper list of pairs or NIL)
// Throws: RuntimeException - Indicates that 'vars' is not a proper list or
// that it contains an member that is not a
// variable/value pair.
// Precondition: 'environment' and 'vars' must not be null.
// Postcondition: All of the variables in 'vars' have been placed into
// 'environment' with their values.
private void addVariablesToTable(SymbolTable environment, SExpression vars) {
// makes sure the list of variable/value pairs is a list
if (!vars.listp()) {
throw new RuntimeException("LET: " + vars + " is not a properly formatted" + " variable/value pair list");
private SExpression evaluateInScope(Cons variableDefinitions, Cons body) {
createLocalScope(variableDefinitions);
SExpression lastEvaluation = evaluateBody(body);
restorePreviousScope();
return lastEvaluation;
}
private SymbolTable createLocalScope(Cons variableDefinitions) {
localScope = new SymbolTable(executionContext.getScope());
addVariablesToScope(localScope, variableDefinitions);
executionContext.setScope(localScope);
return localScope;
}
private void addVariablesToScope(SymbolTable scope, Cons variableDefinitions) {
validateAllPairsAreLists(variableDefinitions);
for (; variableDefinitions.consp(); variableDefinitions = (Cons) variableDefinitions.getCdr()) {
Cons symbolValuePair = (Cons) variableDefinitions.getCar();
validatePair(symbolValuePair);
Cons restOfPair = (Cons) symbolValuePair.getCdr();
SExpression symbol = symbolValuePair.getCar();
SExpression value = restOfPair.getCar();
scope.put(symbol.toString(), EVAL.eval(value));
}
}
// add all variables in 'vars' to 'environment'
while (vars.consp()) {
Cons varList = (Cons) vars;
SExpression varListCar = varList.getCar();
private void validateAllPairsAreLists(Cons variableDefinitions) {
ArgumentValidator variableDefinitionListValidator = new ArgumentValidator("LET|argumentList|");
variableDefinitionListValidator.setEveryArgumentExpectedType(Cons.class);
variableDefinitionListValidator.validate(variableDefinitions);
}
// make sure this variable/value pair is a list
if (!varListCar.consp()) {
throw new RuntimeException("LET: " + varListCar + " is not a properly formatted"
+ " variable/value pair");
}
private void validatePair(Cons symbolValuePair) {
ArgumentValidator pairValidator = new ArgumentValidator("LET|pair|");
pairValidator.setExactNumberOfArguments(2);
pairValidator.setFirstArgumentExpectedType(Symbol.class);
pairValidator.validate((Cons) symbolValuePair);
}
Cons varSpec = (Cons) varListCar;
SExpression symbol = varSpec.getCar();
SExpression varSpecCdr = varSpec.getCdr();
private SExpression evaluateBody(Cons body) {
SExpression lastEvaluation = Nil.getInstance();
// make sure this variable pair has a value associated with it
if (!varSpecCdr.consp()) {
throw new RuntimeException("LET: illegal variable " + "specification " + varSpec);
}
for (; body.consp(); body = (Cons) body.getCdr())
lastEvaluation = EVAL.eval(body.getCar());
Cons varValue = (Cons) varSpecCdr;
SExpression value = varValue.getCar();
return lastEvaluation;
}
// make sure there are no more members of this variable/value pair
// and that 'symbol' is actually a symbol
if ((!varValue.getCdr().nullp()) || (!symbol.symbolp())) {
throw new RuntimeException("LET: illegal variable " + "specification " + varSpec);
}
environment.put(symbol.toString(), value);
vars = varList.getCdr();
}
private void restorePreviousScope() {
executionContext.setScope(localScope.getParent());
}
public boolean evaluateArguments() {

View File

@ -0,0 +1,141 @@
package function.builtin.special;
import static testutil.TestUtilities.*;
import org.junit.*;
import function.ArgumentValidator.*;
import function.builtin.EVAL.UndefinedSymbolException;
import sexpression.*;
import table.ExecutionContext;
public class LETTester {
private ExecutionContext executionContext;
public LETTester() {
this.executionContext = ExecutionContext.getInstance();
}
@Before
public void setUp() {
executionContext.clearContext();
}
@Test
public void simpleLet() {
String input = "(let ((x 1)) x)";
assertSExpressionsMatch(new LispNumber("1"), evaluateString(input));
}
@Test
public void emptyLet_ReturnsNil() {
String input = "(let ())";
assertSExpressionsMatch(Nil.getInstance(), evaluateString(input));
}
@Test
public void letWithSetf_DoesNotAlterGlobalVariable() {
String before = "(setf x 22)";
String input = "(let ((x 1)) x)";
String after = "x";
assertSExpressionsMatch(new LispNumber("22"), evaluateString(before));
assertSExpressionsMatch(new LispNumber("1"), evaluateString(input));
assertSExpressionsMatch(new LispNumber("22"), evaluateString(after));
}
@Test
public void letWithNestedSetf_DoesNotAlterGlobalVariable() {
String before = "(setf x 22)";
String input = "(let ((x 33)) (setf x 44) x)";
String after = "x";
assertSExpressionsMatch(new LispNumber("22"), evaluateString(before));
assertSExpressionsMatch(new LispNumber("44"), evaluateString(input));
assertSExpressionsMatch(new LispNumber("22"), evaluateString(after));
}
@Test
public void nestedLet() {
String input = "(let ((x 1)) (let ((y (+ 1 x))) y))";
assertSExpressionsMatch(new LispNumber("2"), evaluateString(input));
}
@Test
public void alterGlobalVariableFromLet() {
String before = "(setf x 1)";
String input = "(let ((y 1)) (setf x 2))";
String after = "x";
assertSExpressionsMatch(new LispNumber("1"), evaluateString(before));
assertSExpressionsMatch(new LispNumber("2"), evaluateString(input));
assertSExpressionsMatch(new LispNumber("2"), evaluateString(after));
}
@Test
public void accessGlobalVariableFromLet() {
String before = "(setf x 1)";
String input = "(let () x)";
assertSExpressionsMatch(new LispNumber("1"), evaluateString(before));
assertSExpressionsMatch(new LispNumber("1"), evaluateString(input));
}
@Test(expected = UndefinedSymbolException.class)
public void letDoesNotSetGlobalVariable() {
String input = "(let ((x 1)) nil)";
evaluateString(input);
evaluateString("x");
}
@Test(expected = BadArgumentTypeException.class)
public void testLetWithNonList() {
evaluateString("(let a)");
}
@Test(expected = BadArgumentTypeException.class)
public void testLetWithNoPairs() {
evaluateString("(let (a))");
}
@Test(expected = TooFewArgumentsException.class)
public void testLetWithTooFewItemsInPair() {
evaluateString("(let ((a)))");
}
@Test(expected = TooManyArgumentsException.class)
public void testLetWithTooManyItemsInPair() {
evaluateString("(let ((a b c)))");
}
@Test(expected = BadArgumentTypeException.class)
public void testLetWithNonSymbolInPair() {
evaluateString("(let ((1 b)))");
}
@Test(expected = TooFewArgumentsException.class)
public void testLetWithTooFewArguments() {
evaluateString("(let)");
}
@Test(expected = DottedArgumentListException.class)
public void testLetWithDottedArgumentList() {
evaluateString("(apply 'let (cons 'a 'b))");
}
@Test(expected = DottedArgumentListException.class)
public void testLetWithDottedPairList() {
evaluateString("(apply 'let (cons (cons 'a 'b) nil))");
}
@Test(expected = DottedArgumentListException.class)
public void testLetWithDottedPair() {
evaluateString("(apply 'let (cons (cons (cons 'a 'b) nil) nil))");
}
}