From 64e18fe076dc96948e08fe6c2271b5c7584a83b9 Mon Sep 17 00:00:00 2001 From: Mike Cifelli Date: Wed, 1 Mar 2017 11:11:59 -0500 Subject: [PATCH] Resolves #12 - &rest keyword added to lambda lists Added unit tests and cleaned up some code The setup for acceptance tests now cleans up the environment --- acctest/fixture/LispInterpreterFixture.java | 6 ++ .../MultipleMethodClosure.wiki | 4 +- .../FitNesseRoot/LispInterpreter/SetUp.wiki | 3 + fitnesse/FitNesseRoot/RecentChanges.wiki | 3 +- src/function/UserDefinedFunction.java | 78 ++++++++++++++++++- src/table/ExecutionContext.java | 1 + src/table/FunctionTable.java | 40 +++++----- test/function/LispFunctionTester.java | 2 +- test/function/LispSpecialFunctionTester.java | 24 ++++++ test/function/UserDefinedFunctionTester.java | 18 ++++- .../builtin/special/DEFINE_MACROTester.java | 43 ++++++++++ .../function/builtin/special/DEFUNTester.java | 43 ++++++++++ 12 files changed, 234 insertions(+), 31 deletions(-) create mode 100644 test/function/LispSpecialFunctionTester.java diff --git a/acctest/fixture/LispInterpreterFixture.java b/acctest/fixture/LispInterpreterFixture.java index da91fd9..0449d42 100644 --- a/acctest/fixture/LispInterpreterFixture.java +++ b/acctest/fixture/LispInterpreterFixture.java @@ -3,6 +3,7 @@ package fixture; import java.io.*; import interpreter.*; +import table.*; public class LispInterpreterFixture { @@ -20,6 +21,11 @@ public class LispInterpreterFixture { interpreter = builder.build(); } + public void reset() { + FunctionTable.reset(); + ExecutionContext.getInstance().clearContext(); + } + public String evaluate(String input) throws IOException { interpreter.setInput(new ByteArrayInputStream(input.getBytes()), "fitnesse"); interpreter.interpret(); diff --git a/fitnesse/FitNesseRoot/LispInterpreter/MultipleMethodClosure.wiki b/fitnesse/FitNesseRoot/LispInterpreter/MultipleMethodClosure.wiki index fdfe34a..7d2e32e 100644 --- a/fitnesse/FitNesseRoot/LispInterpreter/MultipleMethodClosure.wiki +++ b/fitnesse/FitNesseRoot/LispInterpreter/MultipleMethodClosure.wiki @@ -5,7 +5,7 @@ Test | # | Object with multiple methods | | show | evaluate |!- -(defun counter-class-multi () +(defun counter-class () (let ((counter 0)) (lambda (msg) (case msg @@ -15,7 +15,7 @@ Test (setq counter (- counter 1))))))) -!| -| show | evaluate | (setq my-counter (counter-class-multi)) | +| show | evaluate | (setq my-counter (counter-class)) | | check | evaluate | (funcall my-counter :inc) | 1 | | check | evaluate | (funcall my-counter :inc) | 2 | | check | evaluate | (funcall my-counter :inc) | 3 | diff --git a/fitnesse/FitNesseRoot/LispInterpreter/SetUp.wiki b/fitnesse/FitNesseRoot/LispInterpreter/SetUp.wiki index bb35658..71d1164 100644 --- a/fitnesse/FitNesseRoot/LispInterpreter/SetUp.wiki +++ b/fitnesse/FitNesseRoot/LispInterpreter/SetUp.wiki @@ -3,3 +3,6 @@ Test --- | import | | fixture | + +| script | lisp interpreter fixture | +| reset | diff --git a/fitnesse/FitNesseRoot/RecentChanges.wiki b/fitnesse/FitNesseRoot/RecentChanges.wiki index be31fa4..4c3733e 100644 --- a/fitnesse/FitNesseRoot/RecentChanges.wiki +++ b/fitnesse/FitNesseRoot/RecentChanges.wiki @@ -1,4 +1,5 @@ -|LispInterpreter.MultipleMethodClosure||11:52:11 Tue, Feb 28, 2017| +|LispInterpreter.MultipleMethodClosure||11:04:53 Wed, Mar 01, 2017| +|LispInterpreter.SetUp||11:04:36 Wed, Mar 01, 2017| |LispInterpreter.LexicalClosures||12:10:13 Mon, Feb 27, 2017| |LispInterpreter.TestClosure||11:24:27 Mon, Feb 27, 2017| |LispInterpreter.TestOne||09:26:08 Fri, Feb 24, 2017| diff --git a/src/function/UserDefinedFunction.java b/src/function/UserDefinedFunction.java index 3194861..82b72f7 100644 --- a/src/function/UserDefinedFunction.java +++ b/src/function/UserDefinedFunction.java @@ -2,13 +2,17 @@ package function; import static function.builtin.EVAL.eval; +import java.text.MessageFormat; import java.util.ArrayList; +import error.LispException; import sexpression.*; import table.*; public class UserDefinedFunction extends LispFunction { + private static final String KEYWORD_REST = "&REST"; + private String name; private Cons body; private Cons lambdaExpression; @@ -16,6 +20,8 @@ public class UserDefinedFunction extends LispFunction { private SymbolTable functionScope; private ArrayList formalParameters; private ArgumentValidator argumentValidator; + private String keywordRestParameter; + private boolean isKeywordRest; public UserDefinedFunction(String name, Cons lambdaList, Cons body) { this.name = name; @@ -23,12 +29,55 @@ public class UserDefinedFunction extends LispFunction { this.lambdaExpression = new Cons(new Symbol(name), new Cons(lambdaList, body)); this.executionContext = ExecutionContext.getInstance(); this.functionScope = executionContext.getScope(); + this.keywordRestParameter = null; + this.isKeywordRest = false; - for (this.formalParameters = new ArrayList<>(); lambdaList.isCons(); lambdaList = (Cons) lambdaList.getRest()) - this.formalParameters.add(lambdaList.getFirst().toString()); + createFormalParameters(lambdaList); + setupArgumentValidator(); + } - this.argumentValidator = new ArgumentValidator(this.name); - this.argumentValidator.setExactNumberOfArguments(this.formalParameters.size()); + private void createFormalParameters(Cons lambdaList) { + for (formalParameters = new ArrayList<>(); lambdaList.isCons(); lambdaList = advanceCons(lambdaList)) { + String parameter = lambdaList.getFirst().toString(); + + if (isKeywordRest(parameter)) + lambdaList = extractKeywordRestParameter(lambdaList); + else + formalParameters.add(parameter); + } + } + + private Cons advanceCons(Cons cons) { + return (Cons) cons.getRest(); + } + + private boolean isKeywordRest(String parameter) { + return KEYWORD_REST.equals(parameter); + } + + private Cons extractKeywordRestParameter(Cons lambdaList) { + isKeywordRest = true; + lambdaList = advanceCons(lambdaList); + keywordRestParameter = lambdaList.getFirst().toString(); + lambdaList = advanceCons(lambdaList); + + if (containsMoreParameters(lambdaList)) + throw new IllegalKeywordRestPositionException(this.name, lambdaList); + + return lambdaList; + } + + private boolean containsMoreParameters(Cons lambdaList) { + return lambdaList.isCons(); + } + + private void setupArgumentValidator() { + argumentValidator = new ArgumentValidator(this.name); + + if (isKeywordRest) + argumentValidator.setMinimumNumberOfArguments(this.formalParameters.size()); + else + argumentValidator.setExactNumberOfArguments(this.formalParameters.size()); } public SExpression call(Cons argumentList) { @@ -57,6 +106,9 @@ public class UserDefinedFunction extends LispFunction { argumentList = (Cons) argumentList.getRest(); } + if (isKeywordRest) + executionScope.put(keywordRestParameter, argumentList); + return executionScope; } @@ -73,4 +125,22 @@ public class UserDefinedFunction extends LispFunction { return lambdaExpression; } + public static class IllegalKeywordRestPositionException extends LispException { + + private static final long serialVersionUID = 1L; + private String functionName; + private Cons parameters; + + public IllegalKeywordRestPositionException(String functionName, Cons parameters) { + this.functionName = functionName; + this.parameters = parameters; + } + + @Override + public String getMessage() { + return MessageFormat.format("unexpected parameters following ''&rest'' in definition of {0}: {1}", + functionName, parameters); + } + } + } diff --git a/src/table/ExecutionContext.java b/src/table/ExecutionContext.java index 3b7a57a..71c1215 100644 --- a/src/table/ExecutionContext.java +++ b/src/table/ExecutionContext.java @@ -35,4 +35,5 @@ public class ExecutionContext { return null; } + } diff --git a/src/table/FunctionTable.java b/src/table/FunctionTable.java index 43a57ed..46dd7c7 100644 --- a/src/table/FunctionTable.java +++ b/src/table/FunctionTable.java @@ -13,26 +13,6 @@ import function.builtin.special.*; public class FunctionTable { - public static LispFunction lookupFunction(String functionName) { - return getTable().get(functionName); - } - - public static boolean isAlreadyDefined(String functionName) { - return getTable().containsKey(functionName); - } - - public static void defineFunction(String functionName, LispFunction function) { - getTable().put(functionName, function); - } - - public static void reset() { - getUniqueInstance().initializeFunctionTable(allBuiltIns); - } - - static void reset(Set> builtIns) { - getUniqueInstance().initializeFunctionTable(builtIns); - } - private static Set> allBuiltIns = new HashSet<>(); static { @@ -75,6 +55,26 @@ public class FunctionTable { allBuiltIns.add(SYMBOL_FUNCTION.class); } + public static LispFunction lookupFunction(String functionName) { + return getTable().get(functionName); + } + + public static boolean isAlreadyDefined(String functionName) { + return getTable().containsKey(functionName); + } + + public static void defineFunction(String functionName, LispFunction function) { + getTable().put(functionName, function); + } + + public static void reset() { + getUniqueInstance().initializeFunctionTable(allBuiltIns); + } + + static void reset(Set> builtIns) { + getUniqueInstance().initializeFunctionTable(builtIns); + } + private static FunctionTable uniqueInstance; private static FunctionTable getUniqueInstance() { diff --git a/test/function/LispFunctionTester.java b/test/function/LispFunctionTester.java index f4541e7..050cf0c 100644 --- a/test/function/LispFunctionTester.java +++ b/test/function/LispFunctionTester.java @@ -9,7 +9,7 @@ import sexpression.*; public class LispFunctionTester { @Test - public void testEvaluateArguments() { + public void evaluateArguments() { LispFunction lispFunction = new LispFunction() { @Override diff --git a/test/function/LispSpecialFunctionTester.java b/test/function/LispSpecialFunctionTester.java new file mode 100644 index 0000000..fa40608 --- /dev/null +++ b/test/function/LispSpecialFunctionTester.java @@ -0,0 +1,24 @@ +package function; + +import static org.junit.Assert.assertFalse; + +import org.junit.Test; + +import sexpression.*; + +public class LispSpecialFunctionTester { + + @Test + public void evaluateArguments() { + LispFunction lispFunction = new LispSpecialFunction() { + + @Override + public SExpression call(Cons argList) { + return null; + } + }; + + assertFalse(lispFunction.evaluateArguments()); + } + +} diff --git a/test/function/UserDefinedFunctionTester.java b/test/function/UserDefinedFunctionTester.java index d17036b..980e880 100644 --- a/test/function/UserDefinedFunctionTester.java +++ b/test/function/UserDefinedFunctionTester.java @@ -1,11 +1,13 @@ package function; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; import static testutil.TestUtilities.assertSExpressionsMatch; import org.junit.Test; +import error.ErrorManager; import function.ArgumentValidator.*; +import function.UserDefinedFunction.IllegalKeywordRestPositionException; import sexpression.*; public class UserDefinedFunctionTester { @@ -23,14 +25,14 @@ public class UserDefinedFunctionTester { } @Test - public void testNilFunctionCall() { + public void nilFunctionCall() { UserDefinedFunction function = createNoArgumentFunctionThatReturnsNil(); assertEquals(Nil.getInstance(), function.call(Nil.getInstance())); } @Test - public void testNilFunctionToString() { + public void nilFunctionToString() { UserDefinedFunction function = createNoArgumentFunctionThatReturnsNil(); Cons expected = new Cons(new Symbol(FUNCTION_NAME), new Cons(Nil.getInstance(), new Cons(Nil.getInstance(), Nil.getInstance()))); @@ -63,4 +65,14 @@ public class UserDefinedFunctionTester { function.call(Nil.getInstance()); } + @Test + public void illegalKeywordRestPositionException_HasCorrectAttributes() { + IllegalKeywordRestPositionException e = new IllegalKeywordRestPositionException(FUNCTION_NAME, + Nil.getInstance()); + + assertNotNull(e.getMessage()); + assertTrue(e.getMessage().length() > 0); + assertEquals(ErrorManager.Severity.ERROR, e.getSeverity()); + } + } diff --git a/test/function/builtin/special/DEFINE_MACROTester.java b/test/function/builtin/special/DEFINE_MACROTester.java index 45e5ceb..d54abc1 100644 --- a/test/function/builtin/special/DEFINE_MACROTester.java +++ b/test/function/builtin/special/DEFINE_MACROTester.java @@ -10,6 +10,7 @@ import org.junit.*; import environment.RuntimeEnvironment; import error.ErrorManager; import function.ArgumentValidator.*; +import function.UserDefinedFunction.IllegalKeywordRestPositionException; import table.FunctionTable; public class DEFINE_MACROTester { @@ -130,4 +131,46 @@ public class DEFINE_MACROTester { evaluateString("(define-macro x)"); } + @Test(expected = TooFewArgumentsException.class) + public void defineMacroAndCallWithTooFewArguments() { + evaluateString("(define-macro x (a b))"); + evaluateString("(x a)"); + } + + @Test(expected = TooManyArgumentsException.class) + public void defineMacroAndCallWithTooManyArguments() { + evaluateString("(define-macro x (a b))"); + evaluateString("(x a b c)"); + } + + @Test + public void defineMacroWithKeywordRestParameter() { + evaluateString("(define-macro f (&rest x) (car x))"); + assertSExpressionsMatch(parseString("1"), evaluateString("(f 1 2 3 4 5)")); + } + + @Test + public void defineMacroWithNormalAndKeywordRestParameter() { + evaluateString("(define-macro f (a &rest b) (cons a b))"); + assertSExpressionsMatch(parseString("(1 2 3 4 5)"), evaluateString("(f 1 2 3 4 5)")); + } + + @Test(expected = IllegalKeywordRestPositionException.class) + public void defineMacroWithParametersFollowingKeywordRest() { + evaluateString("(define-macro f (a &rest b c) (cons a b))"); + evaluateString("(f 1 2 3)"); + } + + @Test + public void defineMacroWithKeywordRest_CallWithNoArguments() { + evaluateString("(define-macro f (&rest a) (car a))"); + assertSExpressionsMatch(parseString("nil"), evaluateString("(f)")); + } + + @Test(expected = TooFewArgumentsException.class) + public void defineMacroWithNormalAndKeywordRest_CallWithNoArguments() { + evaluateString("(define-macro f (a &rest b) a)"); + evaluateString("(f)"); + } + } diff --git a/test/function/builtin/special/DEFUNTester.java b/test/function/builtin/special/DEFUNTester.java index e8e985d..bf1a852 100644 --- a/test/function/builtin/special/DEFUNTester.java +++ b/test/function/builtin/special/DEFUNTester.java @@ -10,6 +10,7 @@ import org.junit.*; import environment.RuntimeEnvironment; import error.ErrorManager; import function.ArgumentValidator.*; +import function.UserDefinedFunction.IllegalKeywordRestPositionException; import table.FunctionTable; public class DEFUNTester { @@ -124,4 +125,46 @@ public class DEFUNTester { evaluateString("(defun x)"); } + @Test(expected = TooFewArgumentsException.class) + public void defunFunctionAndCallWithTooFewArguments() { + evaluateString("(defun x (a b))"); + evaluateString("(x 'a)"); + } + + @Test(expected = TooManyArgumentsException.class) + public void defunFunctionAndCallWithTooManyArguments() { + evaluateString("(defun x (a b))"); + evaluateString("(x 'a 'b 'c)"); + } + + @Test + public void defunWithKeywordRestParameter() { + evaluateString("(defun f (&rest x) (car x))"); + assertSExpressionsMatch(parseString("1"), evaluateString("(f 1 2 3 4 5)")); + } + + @Test + public void defunWithNormalAndKeywordRestParameter() { + evaluateString("(defun f (a &rest b) (cons a b))"); + assertSExpressionsMatch(parseString("(1 2 3 4 5)"), evaluateString("(f 1 2 3 4 5)")); + } + + @Test(expected = IllegalKeywordRestPositionException.class) + public void defunWithParametersFollowingKeywordRest() { + evaluateString("(defun f (a &rest b c) (cons a b))"); + evaluateString("(f 1 2 3)"); + } + + @Test + public void defunWithKeywordRest_CallWithNoArguments() { + evaluateString("(defun f (&rest a) (car a))"); + assertSExpressionsMatch(parseString("nil"), evaluateString("(f)")); + } + + @Test(expected = TooFewArgumentsException.class) + public void defunWithNormalAndKeywordRest_CallWithNoArguments() { + evaluateString("(defun f (a &rest b) a)"); + evaluateString("(f)"); + } + }