diff --git a/src/function/UserDefinedFunction.java b/src/function/UserDefinedFunction.java
index eaa46c5..b8f3afe 100644
--- a/src/function/UserDefinedFunction.java
+++ b/src/function/UserDefinedFunction.java
@@ -20,7 +20,7 @@ public class UserDefinedFunction extends LispFunction {
this.name = name;
this.body = 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())
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
// (the current environment)
- SymbolTable currentEnvironment = SETF.getEnvironment();
+ SymbolTable currentEnvironment = SETF.getSymbolTable();
// replace the current environment with the environment of this
// function
- SETF.setEnvironment(environment);
+ SETF.setSymbolTable(environment);
Cons currentSExpression = body;
SExpression retval = null;
@@ -62,7 +62,7 @@ public class UserDefinedFunction extends LispFunction {
// replace the environment of the S-expression that called this
// function
- SETF.setEnvironment(currentEnvironment);
+ SETF.setSymbolTable(currentEnvironment);
// remove the bindings of the arguments to the formal parameter names
// in the environment of this function
diff --git a/src/function/builtin/EVAL.java b/src/function/builtin/EVAL.java
index 7cbf57f..199cb67 100644
--- a/src/function/builtin/EVAL.java
+++ b/src/function/builtin/EVAL.java
@@ -48,7 +48,7 @@ public class EVAL extends LispFunction {
return new Symbol(symbolName);
}
- return SETF.lookup(symbolName);
+ return SETF.lookupSymbolValue(symbolName);
}
public static boolean isDotted(Cons list) {
diff --git a/src/function/builtin/special/LET.java b/src/function/builtin/special/LET.java
index 86cdf2c..8fdcfbb 100644
--- a/src/function/builtin/special/LET.java
+++ b/src/function/builtin/special/LET.java
@@ -18,13 +18,13 @@ public class LET extends LispFunction {
// create a new symbol table on top of the current environment to add
// all the local variables to
- SymbolTable environment = new SymbolTable(SETF.getEnvironment());
+ SymbolTable environment = new SymbolTable(SETF.getSymbolTable());
SExpression car = argList.getCar();
Cons cdr = (Cons) argList.getCdr();
addVariablesToTable(environment, car);
- SETF.setEnvironment(environment);
+ SETF.setSymbolTable(environment);
SExpression retval = Nil.getInstance();
@@ -35,7 +35,7 @@ public class LET extends LispFunction {
}
// restore the environment to its original value
- SETF.setEnvironment(environment.getParent());
+ SETF.setSymbolTable(environment.getParent());
return retval;
}
diff --git a/src/function/builtin/special/SETF.java b/src/function/builtin/special/SETF.java
index 27e8021..202149d 100644
--- a/src/function/builtin/special/SETF.java
+++ b/src/function/builtin/special/SETF.java
@@ -1,108 +1,68 @@
package function.builtin.special;
import function.*;
-import function.builtin.*;
-import function.builtin.cons.LENGTH;
+import function.builtin.EVAL;
import sexpression.*;
import table.SymbolTable;
-/**
- * SETF
represents the SETF form in Lisp.
- */
public class SETF extends LispFunction {
- private static SymbolTable environment = new SymbolTable();
+ private static SymbolTable symbolTable = new SymbolTable();
- /**
- * Look up the value of a symbol using its name.
- *
- * @param symbolName
- * the name of the symbol to look up
- * @return
- * the value of symbolName
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();
- }
+ public static SExpression lookupSymbolValue(String symbolName) {
+ for (SymbolTable t = symbolTable; t != null; t = t.getParent())
+ if (t.contains(symbolName))
+ return t.get(symbolName);
return null;
}
- /**
- * Set the current environment to the specified value.
- *
- * @param newEnvironment
- * the value to set the environment to
- */
- public static void setEnvironment(SymbolTable newEnvironment) {
- environment = newEnvironment;
+ public static void setSymbolTable(SymbolTable newSymbolTable) {
+ symbolTable = newSymbolTable;
}
- /**
- * Retrieve the current environment.
- *
- * @return
- * the current environment
- */
- public static SymbolTable getEnvironment() {
- return environment;
+ public static SymbolTable getSymbolTable() {
+ return symbolTable;
}
- // The number of arguments that SETF takes.
- private static final int NUM_ARGS = 2;
+ private ArgumentValidator argumentValidator;
- public SExpression call(Cons argList) {
- // retrieve the number of arguments passed to SETF
- int argListLength = LENGTH.getLength(argList);
+ public SETF() {
+ this.argumentValidator = new ArgumentValidator("SETF");
+ 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;
+ public SExpression call(Cons argumentList) {
+ argumentValidator.validate(argumentList);
- throw new RuntimeException(errMsg);
- }
-
- SExpression symbol = argList.getCar();
-
- // make sure the first argument is a symbol
- if (! symbol.symbolp()) {
- throw new RuntimeException("SETF: " + symbol + " is not a symbol");
- }
-
- Cons cdr = (Cons) argList.getCdr();
+ Cons cdr = (Cons) argumentList.getCdr();
+ SExpression symbol = argumentList.getCar();
SExpression value = EVAL.eval(cdr.getCar());
- SymbolTable current = environment;
-
- // 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);
+ SymbolTable table = findTableForSymbol(symbol);
+ table.put(symbol.toString(), value);
return value;
}
- /**
- * Determine if the arguments passed to this Lisp function should be
- * evaluated.
- *
- * @return
- * false
- */
+ private SymbolTable findTableForSymbol(SExpression symbol) {
+ SymbolTable table = symbolTable;
+
+ while (!isSymbolInTable(symbol, table) && !isGlobalTable(table))
+ table = table.getParent();
+
+ 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() {
return false;
}
diff --git a/test/function/builtin/special/SETFTester.java b/test/function/builtin/special/SETFTester.java
new file mode 100644
index 0000000..aa55dab
--- /dev/null
+++ b/test/function/builtin/special/SETFTester.java
@@ -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)");
+ }
+
+}