diff --git a/src/function/builtin/special/LET.java b/src/function/builtin/special/LET.java index f477c0a..e8ab7da 100644 --- a/src/function/builtin/special/LET.java +++ b/src/function/builtin/special/LET.java @@ -13,7 +13,7 @@ public class LET extends LispSpecialFunction { private ArgumentValidator argumentValidator; private ArgumentValidator variableDefinitionListValidator; private ArgumentValidator pairValidator; - private ExecutionContext executionContext; + protected ExecutionContext executionContext; public LET(String name) { this.argumentValidator = new ArgumentValidator(name); @@ -47,7 +47,7 @@ public class LET extends LispSpecialFunction { return lastEvaluation; } - private SymbolTable createLocalScope(Cons variableDefinitions) { + protected SymbolTable createLocalScope(Cons variableDefinitions) { SymbolTable localScope = new SymbolTable(executionContext.getScope()); addVariablesToScope(localScope, variableDefinitions); executionContext.setScope(localScope); @@ -55,7 +55,7 @@ public class LET extends LispSpecialFunction { return localScope; } - private void addVariablesToScope(SymbolTable scope, Cons variableDefinitions) { + protected void addVariablesToScope(SymbolTable scope, Cons variableDefinitions) { variableDefinitionListValidator.validate(variableDefinitions); for (; variableDefinitions.isCons(); variableDefinitions = (Cons) variableDefinitions.getRest()) diff --git a/src/function/builtin/special/LET_STAR.java b/src/function/builtin/special/LET_STAR.java new file mode 100644 index 0000000..1583037 --- /dev/null +++ b/src/function/builtin/special/LET_STAR.java @@ -0,0 +1,22 @@ +package function.builtin.special; + +import function.FunctionNames; +import sexpression.Cons; +import table.SymbolTable; + +@FunctionNames({ "LET*" }) +public class LET_STAR extends LET { + + public LET_STAR(String name) { + super(name); + } + + protected SymbolTable createLocalScope(Cons variableDefinitions) { + SymbolTable localScope = new SymbolTable(executionContext.getScope()); + executionContext.setScope(localScope); + addVariablesToScope(localScope, variableDefinitions); + + return localScope; + } + +} diff --git a/src/table/FunctionTable.java b/src/table/FunctionTable.java index c1d1579..acd84c0 100644 --- a/src/table/FunctionTable.java +++ b/src/table/FunctionTable.java @@ -38,6 +38,7 @@ public class FunctionTable { allBuiltIns.add(LENGTH.class); allBuiltIns.add(NUMERIC_LESS.class); allBuiltIns.add(LET.class); + allBuiltIns.add(LET_STAR.class); allBuiltIns.add(LIST.class); allBuiltIns.add(LISTP.class); allBuiltIns.add(LOAD.class); diff --git a/test/function/builtin/predicate/EQTester.java b/test/function/builtin/predicate/EQTester.java index b1daf17..f81af2e 100644 --- a/test/function/builtin/predicate/EQTester.java +++ b/test/function/builtin/predicate/EQTester.java @@ -23,7 +23,6 @@ public class EQTester { assertT(evaluateString(input)); } - @Test public void eqWithUnequalAtoms() { String input = "(eq 1 2)"; diff --git a/test/function/builtin/special/LETTester.java b/test/function/builtin/special/LETTester.java index 8cb2d7a..ae8f852 100644 --- a/test/function/builtin/special/LETTester.java +++ b/test/function/builtin/special/LETTester.java @@ -162,4 +162,11 @@ public class LETTester { evaluateString("(apply 'let (cons (cons (cons 'a 'b) nil) nil))"); } + @Test(expected = UndefinedSymbolException.class) + public void letEvaluatesSymbolsInParallel() { + String input = "(let ((x 1) (y (+ x 1))) (+ x y))"; + + assertSExpressionsMatch(new LispNumber("2"), evaluateString(input)); + } + } diff --git a/test/function/builtin/special/LET_STARTester.java b/test/function/builtin/special/LET_STARTester.java new file mode 100644 index 0000000..baa4262 --- /dev/null +++ b/test/function/builtin/special/LET_STARTester.java @@ -0,0 +1,172 @@ +package function.builtin.special; + +import static sexpression.Nil.NIL; +import static testutil.TestUtilities.*; + +import org.junit.*; + +import function.ArgumentValidator.*; +import function.builtin.EVAL.UndefinedSymbolException; +import sexpression.*; +import table.ExecutionContext; + +public class LET_STARTester { + + private ExecutionContext executionContext; + + public LET_STARTester() { + this.executionContext = ExecutionContext.getInstance(); + } + + @Before + public void setUp() { + executionContext.clearContext(); + } + + @After + public void tearDown() { + 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, evaluateString(input)); + } + + @Test + public void letStarWithSymbolsOnly_SetsValuesToNil() { + String input = "(let* ((x) (y)) (list x y))"; + + assertSExpressionsMatch(new Cons(NIL, new Cons(NIL, NIL)), evaluateString(input)); + } + + @Test + public void letStarWithSetf_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 letStarWithNestedSetf_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 nestedLetWithGlobals() { + String before = "(setf x 92)"; + String input = "(let* ((x 1)) (let* ((y (+ 1 x))) y))"; + String after = "x"; + + assertSExpressionsMatch(new LispNumber("92"), evaluateString(before)); + assertSExpressionsMatch(new LispNumber("2"), evaluateString(input)); + assertSExpressionsMatch(new LispNumber("92"), evaluateString(after)); + } + + @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 letStarDoesNotSetGlobalVariable() { + String input = "(let* ((x 1)) nil)"; + + evaluateString(input); + evaluateString("x"); + } + + @Test(expected = BadArgumentTypeException.class) + public void letStarWithNonList() { + evaluateString("(let* a)"); + } + + @Test(expected = BadArgumentTypeException.class) + public void letStarWithNoPairs() { + evaluateString("(let* (a))"); + } + + @Test(expected = TooFewArgumentsException.class) + public void letStarWithTooFewItemsInPair() { + evaluateString("(let* (()))"); + } + + @Test(expected = TooManyArgumentsException.class) + public void letStarWithTooManyItemsInPair() { + evaluateString("(let* ((a b c)))"); + } + + @Test(expected = BadArgumentTypeException.class) + public void letStarWithNonSymbolInPair() { + evaluateString("(let* ((1 b)))"); + } + + @Test(expected = TooFewArgumentsException.class) + public void letStarWithTooFewArguments() { + evaluateString("(let*)"); + } + + @Test(expected = DottedArgumentListException.class) + public void letStarWithDottedArgumentList() { + evaluateString("(apply 'let* (cons 'a 'b))"); + } + + @Test(expected = DottedArgumentListException.class) + public void letStarWithDottedPairList() { + evaluateString("(apply 'let* (cons (cons 'a 'b) nil))"); + } + + @Test(expected = DottedArgumentListException.class) + public void letStarWithDottedPair() { + evaluateString("(apply 'let* (cons (cons (cons 'a 'b) nil) nil))"); + } + + @Test + public void letStarEvaluatesSymbolsInSequence() { + String input = "(let* ((x 1) (y (+ x 1))) (+ x y))"; + + assertSExpressionsMatch(new LispNumber("3"), evaluateString(input)); + } + +} diff --git a/test/function/builtin/special/PROGNTester.java b/test/function/builtin/special/PROGNTester.java index 06316d2..88a6c16 100644 --- a/test/function/builtin/special/PROGNTester.java +++ b/test/function/builtin/special/PROGNTester.java @@ -27,7 +27,6 @@ public class PROGNTester { assertSExpressionsMatch(parseString("5"), evaluateString("(begin 1 2 3 4 5)")); } - @Test public void prognEvaluatesArgument() { assertSExpressionsMatch(parseString("1"), evaluateString("(progn (car '(1 2 3)))"));