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
This commit is contained in:
		
							parent
							
								
									42191ec69d
								
							
						
					
					
						commit
						64e18fe076
					
				| @ -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(); | ||||
|  | ||||
| @ -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 | | ||||
|  | ||||
| @ -3,3 +3,6 @@ Test | ||||
| --- | ||||
| | import  | | ||||
| | fixture | | ||||
| 
 | ||||
| | script | lisp interpreter fixture | | ||||
| | reset                             | | ||||
|  | ||||
| @ -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| | ||||
|  | ||||
| @ -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<String> 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); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -35,4 +35,5 @@ public class ExecutionContext { | ||||
| 
 | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -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<Class<? extends LispFunction>> builtIns) { | ||||
|         getUniqueInstance().initializeFunctionTable(builtIns); | ||||
|     } | ||||
| 
 | ||||
|     private static Set<Class<? extends LispFunction>> 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<Class<? extends LispFunction>> builtIns) { | ||||
|         getUniqueInstance().initializeFunctionTable(builtIns); | ||||
|     } | ||||
| 
 | ||||
|     private static FunctionTable uniqueInstance; | ||||
| 
 | ||||
|     private static FunctionTable getUniqueInstance() { | ||||
|  | ||||
| @ -9,7 +9,7 @@ import sexpression.*; | ||||
| public class LispFunctionTester { | ||||
| 
 | ||||
|     @Test | ||||
|     public void testEvaluateArguments() { | ||||
|     public void evaluateArguments() { | ||||
|         LispFunction lispFunction = new LispFunction() { | ||||
| 
 | ||||
|             @Override | ||||
|  | ||||
							
								
								
									
										24
									
								
								test/function/LispSpecialFunctionTester.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								test/function/LispSpecialFunctionTester.java
									
									
									
									
									
										Normal file
									
								
							| @ -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()); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -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()); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -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)"); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -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)"); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user