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

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