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
|
||||
|
|
|
@ -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