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 {
|
||||
|
||||
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() {
|
||||
|
|
|
@ -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