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:
Mike Cifelli 2017-03-01 11:11:59 -05:00
parent 42191ec69d
commit 64e18fe076
12 changed files with 234 additions and 31 deletions

View File

@ -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();

View File

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

View File

@ -3,3 +3,6 @@ Test
---
| import |
| fixture |
| script | lisp interpreter fixture |
| reset |

View File

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

View File

@ -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);
}
}
}

View File

@ -35,4 +35,5 @@ public class ExecutionContext {
return null;
}
}

View File

@ -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() {

View File

@ -9,7 +9,7 @@ import sexpression.*;
public class LispFunctionTester {
@Test
public void testEvaluateArguments() {
public void evaluateArguments() {
LispFunction lispFunction = new LispFunction() {
@Override

View 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());
}
}

View File

@ -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());
}
}

View File

@ -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)");
}
}

View File

@ -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)");
}
}