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)"); + } + +}