Added unit tests and refactored setf

This commit is contained in:
Mike Cifelli 2017-01-26 12:30:38 -05:00
parent 6ed24e85fe
commit 919644d9c0
5 changed files with 133 additions and 87 deletions

View File

@ -20,7 +20,7 @@ public class UserDefinedFunction extends LispFunction {
this.name = name; this.name = name;
this.body = body; this.body = body;
this.lambdaExpression = new Cons(new Symbol(name), new Cons(lambdaList, body)); this.lambdaExpression = new Cons(new Symbol(name), new Cons(lambdaList, body));
this.environment = SETF.getEnvironment(); this.environment = SETF.getSymbolTable();
for (this.formalParameters = new ArrayList<>(); lambdaList.consp(); lambdaList = (Cons) lambdaList.getCdr()) for (this.formalParameters = new ArrayList<>(); lambdaList.consp(); lambdaList = (Cons) lambdaList.getCdr())
this.formalParameters.add(lambdaList.getCar().toString()); this.formalParameters.add(lambdaList.getCar().toString());
@ -45,11 +45,11 @@ public class UserDefinedFunction extends LispFunction {
// store the environment of the S-expression that called this function // store the environment of the S-expression that called this function
// (the current environment) // (the current environment)
SymbolTable currentEnvironment = SETF.getEnvironment(); SymbolTable currentEnvironment = SETF.getSymbolTable();
// replace the current environment with the environment of this // replace the current environment with the environment of this
// function // function
SETF.setEnvironment(environment); SETF.setSymbolTable(environment);
Cons currentSExpression = body; Cons currentSExpression = body;
SExpression retval = null; SExpression retval = null;
@ -62,7 +62,7 @@ public class UserDefinedFunction extends LispFunction {
// replace the environment of the S-expression that called this // replace the environment of the S-expression that called this
// function // function
SETF.setEnvironment(currentEnvironment); SETF.setSymbolTable(currentEnvironment);
// remove the bindings of the arguments to the formal parameter names // remove the bindings of the arguments to the formal parameter names
// in the environment of this function // in the environment of this function

View File

@ -48,7 +48,7 @@ public class EVAL extends LispFunction {
return new Symbol(symbolName); return new Symbol(symbolName);
} }
return SETF.lookup(symbolName); return SETF.lookupSymbolValue(symbolName);
} }
public static boolean isDotted(Cons list) { public static boolean isDotted(Cons list) {

View File

@ -18,13 +18,13 @@ public class LET extends LispFunction {
// create a new symbol table on top of the current environment to add // create a new symbol table on top of the current environment to add
// all the local variables to // all the local variables to
SymbolTable environment = new SymbolTable(SETF.getEnvironment()); SymbolTable environment = new SymbolTable(SETF.getSymbolTable());
SExpression car = argList.getCar(); SExpression car = argList.getCar();
Cons cdr = (Cons) argList.getCdr(); Cons cdr = (Cons) argList.getCdr();
addVariablesToTable(environment, car); addVariablesToTable(environment, car);
SETF.setEnvironment(environment); SETF.setSymbolTable(environment);
SExpression retval = Nil.getInstance(); SExpression retval = Nil.getInstance();
@ -35,7 +35,7 @@ public class LET extends LispFunction {
} }
// restore the environment to its original value // restore the environment to its original value
SETF.setEnvironment(environment.getParent()); SETF.setSymbolTable(environment.getParent());
return retval; return retval;
} }

View File

@ -1,108 +1,68 @@
package function.builtin.special; package function.builtin.special;
import function.*; import function.*;
import function.builtin.*; import function.builtin.EVAL;
import function.builtin.cons.LENGTH;
import sexpression.*; import sexpression.*;
import table.SymbolTable; import table.SymbolTable;
/**
* <code>SETF</code> represents the SETF form in Lisp.
*/
public class SETF extends LispFunction { public class SETF extends LispFunction {
private static SymbolTable environment = new SymbolTable(); private static SymbolTable symbolTable = new SymbolTable();
/** public static SExpression lookupSymbolValue(String symbolName) {
* Look up the value of a symbol using its name. for (SymbolTable t = symbolTable; t != null; t = t.getParent())
* if (t.contains(symbolName))
* @param symbolName return t.get(symbolName);
* the name of the symbol to look up
* @return
* the value of <code>symbolName</code> if it has one; null otherwise
*/
public static SExpression lookup(String symbolName) {
SymbolTable current = environment;
while (current != null) {
if (current.contains(symbolName)) {
return current.get(symbolName);
}
current = current.getParent();
}
return null; return null;
} }
/** public static void setSymbolTable(SymbolTable newSymbolTable) {
* Set the current environment to the specified value. symbolTable = newSymbolTable;
*
* @param newEnvironment
* the value to set the environment to
*/
public static void setEnvironment(SymbolTable newEnvironment) {
environment = newEnvironment;
} }
/** public static SymbolTable getSymbolTable() {
* Retrieve the current environment. return symbolTable;
*
* @return
* the current environment
*/
public static SymbolTable getEnvironment() {
return environment;
} }
// The number of arguments that SETF takes. private ArgumentValidator argumentValidator;
private static final int NUM_ARGS = 2;
public SExpression call(Cons argList) { public SETF() {
// retrieve the number of arguments passed to SETF this.argumentValidator = new ArgumentValidator("SETF");
int argListLength = LENGTH.getLength(argList); this.argumentValidator.setExactNumberOfArguments(2);
this.argumentValidator.setFirstArgumentExpectedType(Symbol.class);
// make sure we have received the proper number of arguments
if (argListLength != NUM_ARGS) {
Cons originalSExpr = new Cons(new Symbol("SETF"), argList);
String errMsg = "too " +
((argListLength > NUM_ARGS) ? "many" : "few") +
" arguments given to SETF: " + originalSExpr;
throw new RuntimeException(errMsg);
} }
SExpression symbol = argList.getCar(); public SExpression call(Cons argumentList) {
argumentValidator.validate(argumentList);
// make sure the first argument is a symbol Cons cdr = (Cons) argumentList.getCdr();
if (! symbol.symbolp()) { SExpression symbol = argumentList.getCar();
throw new RuntimeException("SETF: " + symbol + " is not a symbol");
}
Cons cdr = (Cons) argList.getCdr();
SExpression value = EVAL.eval(cdr.getCar()); SExpression value = EVAL.eval(cdr.getCar());
SymbolTable current = environment; SymbolTable table = findTableForSymbol(symbol);
table.put(symbol.toString(), value);
// set 'current' to the symbol table that contains 'symbol' or the
// global symbol table if 'symbol' is not in the environment
while ((! current.contains(symbol.toString())) &&
(current.getParent() != null)) {
current = current.getParent();
}
current.put(symbol.toString(), value);
return value; return value;
} }
/** private SymbolTable findTableForSymbol(SExpression symbol) {
* Determine if the arguments passed to this Lisp function should be SymbolTable table = symbolTable;
* evaluated.
* while (!isSymbolInTable(symbol, table) && !isGlobalTable(table))
* @return table = table.getParent();
* <code>false</code>
*/ return table;
}
private boolean isSymbolInTable(SExpression symbol, SymbolTable table) {
return table.contains(symbol.toString());
}
private boolean isGlobalTable(SymbolTable table) {
return table.getParent() == null;
}
public boolean evaluateArguments() { public boolean evaluateArguments() {
return false; return false;
} }

View File

@ -0,0 +1,86 @@
package function.builtin.special;
import static org.junit.Assert.assertNull;
import static testutil.TestUtilities.*;
import org.junit.*;
import function.ArgumentValidator.*;
import function.builtin.EVAL.UndefinedSymbolException;
import sexpression.LispNumber;
import table.SymbolTable;
public class SETFTester {
@Before
public void setUp() {
SETF.setSymbolTable(new SymbolTable());
}
@Test
public void testSetf() {
evaluateString("(setf a 23)");
assertSExpressionsMatch(evaluateString("23"), evaluateString("a"));
}
@Test
public void lookupDefinedSymbol() {
evaluateString("(setf a 23)");
assertSExpressionsMatch(evaluateString("23"), SETF.lookupSymbolValue("A"));
}
@Test
public void lookupUndefinedSymbol() {
assertNull(SETF.lookupSymbolValue("A"));
}
@Test
public void setfGlobalVariable() {
evaluateString("(setf a 23)");
SymbolTable global = SETF.getSymbolTable();
SETF.setSymbolTable(new SymbolTable(global));
evaluateString("(setf a 94)");
SETF.setSymbolTable(global);
assertSExpressionsMatch(evaluateString("94"), evaluateString("a"));
}
@Test(expected = UndefinedSymbolException.class)
public void setfLocalVariableDefined_DoesNotSetGlobal() {
SymbolTable global = SETF.getSymbolTable();
SymbolTable local = new SymbolTable(global);
local.put("A", new LispNumber("99"));
SETF.setSymbolTable(local);
evaluateString("(setf a 94)");
SETF.setSymbolTable(global);
evaluateString("a");
}
@Test
public void setfLocalVariableUndefined_SetsGlobal() {
SymbolTable global = SETF.getSymbolTable();
SymbolTable local = new SymbolTable(global);
SETF.setSymbolTable(local);
evaluateString("(setf a 94)");
SETF.setSymbolTable(global);
assertSExpressionsMatch(evaluateString("94"), evaluateString("a"));
}
@Test(expected = BadArgumentTypeException.class)
public void testSetfWithNonSymbol() {
evaluateString("(setf 1 2)");
}
@Test(expected = TooFewArgumentsException.class)
public void testSetfWithTooFewArguments() {
evaluateString("(setf x)");
}
@Test(expected = TooManyArgumentsException.class)
public void testSetfWithTooManyArguments() {
evaluateString("(setf a b c)");
}
}