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:
parent
db2817f7be
commit
d7ca5d09da
|
@ -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*))
|
||||||
|
)
|
|
@ -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() {
|
||||||
|
|
|
@ -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))");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue