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 java.io.*;
|
||||||
|
|
||||||
import interpreter.*;
|
import interpreter.*;
|
||||||
|
import table.*;
|
||||||
|
|
||||||
public class LispInterpreterFixture {
|
public class LispInterpreterFixture {
|
||||||
|
|
||||||
|
@ -20,6 +21,11 @@ public class LispInterpreterFixture {
|
||||||
interpreter = builder.build();
|
interpreter = builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
FunctionTable.reset();
|
||||||
|
ExecutionContext.getInstance().clearContext();
|
||||||
|
}
|
||||||
|
|
||||||
public String evaluate(String input) throws IOException {
|
public String evaluate(String input) throws IOException {
|
||||||
interpreter.setInput(new ByteArrayInputStream(input.getBytes()), "fitnesse");
|
interpreter.setInput(new ByteArrayInputStream(input.getBytes()), "fitnesse");
|
||||||
interpreter.interpret();
|
interpreter.interpret();
|
||||||
|
|
|
@ -5,7 +5,7 @@ Test
|
||||||
| # | Object with multiple methods |
|
| # | Object with multiple methods |
|
||||||
| show | evaluate |!-
|
| show | evaluate |!-
|
||||||
|
|
||||||
(defun counter-class-multi ()
|
(defun counter-class ()
|
||||||
(let ((counter 0))
|
(let ((counter 0))
|
||||||
(lambda (msg)
|
(lambda (msg)
|
||||||
(case msg
|
(case msg
|
||||||
|
@ -15,7 +15,7 @@ Test
|
||||||
(setq counter (- counter 1)))))))
|
(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) | 1 |
|
||||||
| check | evaluate | (funcall my-counter :inc) | 2 |
|
| check | evaluate | (funcall my-counter :inc) | 2 |
|
||||||
| check | evaluate | (funcall my-counter :inc) | 3 |
|
| check | evaluate | (funcall my-counter :inc) | 3 |
|
||||||
|
|
|
@ -3,3 +3,6 @@ Test
|
||||||
---
|
---
|
||||||
| import |
|
| import |
|
||||||
| fixture |
|
| 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.LexicalClosures||12:10:13 Mon, Feb 27, 2017|
|
||||||
|LispInterpreter.TestClosure||11:24:27 Mon, Feb 27, 2017|
|
|LispInterpreter.TestClosure||11:24:27 Mon, Feb 27, 2017|
|
||||||
|LispInterpreter.TestOne||09:26:08 Fri, Feb 24, 2017|
|
|LispInterpreter.TestOne||09:26:08 Fri, Feb 24, 2017|
|
||||||
|
|
|
@ -2,13 +2,17 @@ package function;
|
||||||
|
|
||||||
import static function.builtin.EVAL.eval;
|
import static function.builtin.EVAL.eval;
|
||||||
|
|
||||||
|
import java.text.MessageFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import error.LispException;
|
||||||
import sexpression.*;
|
import sexpression.*;
|
||||||
import table.*;
|
import table.*;
|
||||||
|
|
||||||
public class UserDefinedFunction extends LispFunction {
|
public class UserDefinedFunction extends LispFunction {
|
||||||
|
|
||||||
|
private static final String KEYWORD_REST = "&REST";
|
||||||
|
|
||||||
private String name;
|
private String name;
|
||||||
private Cons body;
|
private Cons body;
|
||||||
private Cons lambdaExpression;
|
private Cons lambdaExpression;
|
||||||
|
@ -16,6 +20,8 @@ public class UserDefinedFunction extends LispFunction {
|
||||||
private SymbolTable functionScope;
|
private SymbolTable functionScope;
|
||||||
private ArrayList<String> formalParameters;
|
private ArrayList<String> formalParameters;
|
||||||
private ArgumentValidator argumentValidator;
|
private ArgumentValidator argumentValidator;
|
||||||
|
private String keywordRestParameter;
|
||||||
|
private boolean isKeywordRest;
|
||||||
|
|
||||||
public UserDefinedFunction(String name, Cons lambdaList, Cons body) {
|
public UserDefinedFunction(String name, Cons lambdaList, Cons body) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
@ -23,12 +29,55 @@ public class UserDefinedFunction extends LispFunction {
|
||||||
this.lambdaExpression = new Cons(new Symbol(name), new Cons(lambdaList, body));
|
this.lambdaExpression = new Cons(new Symbol(name), new Cons(lambdaList, body));
|
||||||
this.executionContext = ExecutionContext.getInstance();
|
this.executionContext = ExecutionContext.getInstance();
|
||||||
this.functionScope = executionContext.getScope();
|
this.functionScope = executionContext.getScope();
|
||||||
|
this.keywordRestParameter = null;
|
||||||
|
this.isKeywordRest = false;
|
||||||
|
|
||||||
for (this.formalParameters = new ArrayList<>(); lambdaList.isCons(); lambdaList = (Cons) lambdaList.getRest())
|
createFormalParameters(lambdaList);
|
||||||
this.formalParameters.add(lambdaList.getFirst().toString());
|
setupArgumentValidator();
|
||||||
|
}
|
||||||
|
|
||||||
this.argumentValidator = new ArgumentValidator(this.name);
|
private void createFormalParameters(Cons lambdaList) {
|
||||||
this.argumentValidator.setExactNumberOfArguments(this.formalParameters.size());
|
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) {
|
public SExpression call(Cons argumentList) {
|
||||||
|
@ -57,6 +106,9 @@ public class UserDefinedFunction extends LispFunction {
|
||||||
argumentList = (Cons) argumentList.getRest();
|
argumentList = (Cons) argumentList.getRest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isKeywordRest)
|
||||||
|
executionScope.put(keywordRestParameter, argumentList);
|
||||||
|
|
||||||
return executionScope;
|
return executionScope;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,4 +125,22 @@ public class UserDefinedFunction extends LispFunction {
|
||||||
return lambdaExpression;
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,26 +13,6 @@ import function.builtin.special.*;
|
||||||
|
|
||||||
public class FunctionTable {
|
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<>();
|
private static Set<Class<? extends LispFunction>> allBuiltIns = new HashSet<>();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
@ -75,6 +55,26 @@ public class FunctionTable {
|
||||||
allBuiltIns.add(SYMBOL_FUNCTION.class);
|
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 uniqueInstance;
|
||||||
|
|
||||||
private static FunctionTable getUniqueInstance() {
|
private static FunctionTable getUniqueInstance() {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import sexpression.*;
|
||||||
public class LispFunctionTester {
|
public class LispFunctionTester {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEvaluateArguments() {
|
public void evaluateArguments() {
|
||||||
LispFunction lispFunction = new LispFunction() {
|
LispFunction lispFunction = new LispFunction() {
|
||||||
|
|
||||||
@Override
|
@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;
|
package function;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.*;
|
||||||
import static testutil.TestUtilities.assertSExpressionsMatch;
|
import static testutil.TestUtilities.assertSExpressionsMatch;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import error.ErrorManager;
|
||||||
import function.ArgumentValidator.*;
|
import function.ArgumentValidator.*;
|
||||||
|
import function.UserDefinedFunction.IllegalKeywordRestPositionException;
|
||||||
import sexpression.*;
|
import sexpression.*;
|
||||||
|
|
||||||
public class UserDefinedFunctionTester {
|
public class UserDefinedFunctionTester {
|
||||||
|
@ -23,14 +25,14 @@ public class UserDefinedFunctionTester {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNilFunctionCall() {
|
public void nilFunctionCall() {
|
||||||
UserDefinedFunction function = createNoArgumentFunctionThatReturnsNil();
|
UserDefinedFunction function = createNoArgumentFunctionThatReturnsNil();
|
||||||
|
|
||||||
assertEquals(Nil.getInstance(), function.call(Nil.getInstance()));
|
assertEquals(Nil.getInstance(), function.call(Nil.getInstance()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNilFunctionToString() {
|
public void nilFunctionToString() {
|
||||||
UserDefinedFunction function = createNoArgumentFunctionThatReturnsNil();
|
UserDefinedFunction function = createNoArgumentFunctionThatReturnsNil();
|
||||||
Cons expected = new Cons(new Symbol(FUNCTION_NAME),
|
Cons expected = new Cons(new Symbol(FUNCTION_NAME),
|
||||||
new Cons(Nil.getInstance(), new Cons(Nil.getInstance(), Nil.getInstance())));
|
new Cons(Nil.getInstance(), new Cons(Nil.getInstance(), Nil.getInstance())));
|
||||||
|
@ -63,4 +65,14 @@ public class UserDefinedFunctionTester {
|
||||||
function.call(Nil.getInstance());
|
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 environment.RuntimeEnvironment;
|
||||||
import error.ErrorManager;
|
import error.ErrorManager;
|
||||||
import function.ArgumentValidator.*;
|
import function.ArgumentValidator.*;
|
||||||
|
import function.UserDefinedFunction.IllegalKeywordRestPositionException;
|
||||||
import table.FunctionTable;
|
import table.FunctionTable;
|
||||||
|
|
||||||
public class DEFINE_MACROTester {
|
public class DEFINE_MACROTester {
|
||||||
|
@ -130,4 +131,46 @@ public class DEFINE_MACROTester {
|
||||||
evaluateString("(define-macro x)");
|
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 environment.RuntimeEnvironment;
|
||||||
import error.ErrorManager;
|
import error.ErrorManager;
|
||||||
import function.ArgumentValidator.*;
|
import function.ArgumentValidator.*;
|
||||||
|
import function.UserDefinedFunction.IllegalKeywordRestPositionException;
|
||||||
import table.FunctionTable;
|
import table.FunctionTable;
|
||||||
|
|
||||||
public class DEFUNTester {
|
public class DEFUNTester {
|
||||||
|
@ -124,4 +125,46 @@ public class DEFUNTester {
|
||||||
evaluateString("(defun x)");
|
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