From 217c215efe6831595c70dbe5f71796188d784c80 Mon Sep 17 00:00:00 2001 From: Mike Cifelli Date: Mon, 16 Jan 2017 13:38:49 -0500 Subject: [PATCH] Started major refactoring of several built in functions --- src/function/FunctionTable.java | 51 +++++ src/function/builtin/EVAL.java | 178 +++++++----------- src/function/builtin/EXIT.java | 24 +-- src/function/builtin/special/DEFUN.java | 41 ++-- src/function/builtin/special/LAMBDA.java | 26 --- test/function/builtin/EVALTester.java | 92 +++++++++ .../function/builtin/special/DEFUNTester.java | 14 +- .../function/builtin/special/QUOTETester.java | 35 ++++ 8 files changed, 293 insertions(+), 168 deletions(-) create mode 100644 src/function/FunctionTable.java create mode 100644 test/function/builtin/EVALTester.java create mode 100644 test/function/builtin/special/QUOTETester.java diff --git a/src/function/FunctionTable.java b/src/function/FunctionTable.java new file mode 100644 index 0000000..4aad6a3 --- /dev/null +++ b/src/function/FunctionTable.java @@ -0,0 +1,51 @@ +package function; + +import java.util.HashMap; + +import function.builtin.*; +import function.builtin.cons.*; +import function.builtin.math.*; +import function.builtin.predicate.*; +import function.builtin.special.*; + +public class FunctionTable { + + public static final HashMap functionTable = new HashMap(); + + static { + functionTable.put("*", new MULTIPLY()); + functionTable.put("+", new PLUS()); + functionTable.put("-", new MINUS()); + functionTable.put("/", new DIVIDE()); + functionTable.put("<", new LESSP()); + functionTable.put("=", new EQUALSP()); + functionTable.put(">", new GREATERP()); + functionTable.put("APPLY", new APPLY()); + functionTable.put("ATOM", new ATOM()); + functionTable.put("CAR", new CAR()); + functionTable.put("CDR", new CDR()); + functionTable.put("COND", new COND()); + functionTable.put("CONS", new CONS()); + functionTable.put("DEFUN", new DEFUN()); + functionTable.put("EQ", new EQ()); + functionTable.put("EQUAL", new EQUAL()); + functionTable.put("EVAL", new EVAL()); + functionTable.put("EXIT", new EXIT()); + functionTable.put("FIRST", new CAR()); + functionTable.put("FUNCALL", new FUNCALL()); + functionTable.put("GREATERP", new GREATERP()); + functionTable.put("LAMBDA", new LAMBDA()); + functionTable.put("LENGTH", new LENGTH()); + functionTable.put("LET", new LET()); + functionTable.put("LIST", new LIST()); + functionTable.put("LISTP", new LISTP()); + functionTable.put("LOAD", new LOAD()); + functionTable.put("NULL", new NULL()); + functionTable.put("PRINT", new PRINT()); + functionTable.put("QUOTE", new QUOTE()); + functionTable.put("REST", new CDR()); + functionTable.put("SETF", new SETF()); + functionTable.put("SYMBOL-FUNCTION", new SYMBOL_FUNCTION()); + } + +} diff --git a/src/function/builtin/EVAL.java b/src/function/builtin/EVAL.java index b377722..7cbf57f 100644 --- a/src/function/builtin/EVAL.java +++ b/src/function/builtin/EVAL.java @@ -1,62 +1,22 @@ package function.builtin; +import static function.FunctionTable.functionTable; + +import java.text.MessageFormat; import java.util.HashMap; -import function.LispFunction; -import function.builtin.cons.*; -import function.builtin.math.*; -import function.builtin.predicate.*; +import error.LispException; +import function.*; +import function.builtin.cons.LIST; import function.builtin.special.*; import sexpression.*; public class EVAL extends LispFunction { - private static HashMap functionTable = new HashMap(); - - static { - functionTable.put("*", new MULTIPLY()); - functionTable.put("+", new PLUS()); - functionTable.put("-", new MINUS()); - functionTable.put("/", new DIVIDE()); - functionTable.put("<", new LESSP()); - functionTable.put("=", new EQUALSP()); - functionTable.put(">", new GREATERP()); - functionTable.put("APPLY", new APPLY()); - functionTable.put("ATOM", new ATOM()); - functionTable.put("CAR", new CAR()); - functionTable.put("CDR", new CDR()); - functionTable.put("COND", new COND()); - functionTable.put("CONS", new CONS()); - functionTable.put("DEFUN", new DEFUN()); - functionTable.put("EQ", new EQ()); - functionTable.put("EQUAL", new EQUAL()); - functionTable.put("EVAL", new EVAL()); - functionTable.put("EXIT", new EXIT()); - functionTable.put("FIRST", new CAR()); - functionTable.put("FUNCALL", new FUNCALL()); - functionTable.put("GREATERP", new GREATERP()); - functionTable.put("LAMBDA", new LAMBDA()); - functionTable.put("LENGTH", new LENGTH()); - functionTable.put("LET", new LET()); - functionTable.put("LIST", new LIST()); - functionTable.put("LISTP", new LISTP()); - functionTable.put("LOAD", new LOAD()); - functionTable.put("NULL", new NULL()); - functionTable.put("PRINT", new PRINT()); - functionTable.put("QUOTE", new QUOTE()); - functionTable.put("REST", new CDR()); - functionTable.put("SETF", new SETF()); - functionTable.put("SYMBOL-FUNCTION", new SYMBOL_FUNCTION()); - } - public static HashMap getFunctionTable() { return functionTable; } - public static LispFunction lookupFunction(String functionName) { - return functionTable.get(functionName); - } - public static LispFunction lookupFunctionOrLambda(SExpression functionExpression) { LispFunction function = lookupFunction(functionExpression.toString()); @@ -66,13 +26,17 @@ public class EVAL extends LispFunction { return function; } + public static LispFunction lookupFunction(String functionName) { + return functionTable.get(functionName); + } + private static LispFunction createLambdaFunction(SExpression lambdaExpression) { if (lambdaExpression.functionp()) return ((LambdaExpression) lambdaExpression).getFunction(); else if (LAMBDA.isLambdaExpression(lambdaExpression)) return LAMBDA.createFunction((Cons) lambdaExpression); else - throw new RuntimeException("undefined function " + lambdaExpression); + throw new UndefinedFunctionException(lambdaExpression); } public static SExpression lookupSymbol(String symbolName) { @@ -102,94 +66,98 @@ public class EVAL extends LispFunction { return true; } - public static SExpression eval(SExpression sexpr) { - Cons expList = LIST.makeList(sexpr); - EVAL evalFunction = new EVAL(); + public static SExpression eval(SExpression sExpression) { + Cons argumentList = LIST.makeList(sExpression); + EVAL eval = new EVAL(); - return evalFunction.call(expList); + return eval.call(argumentList); } - private static final int NUM_ARGS = 1; + private ArgumentValidator argumentValidator; - public SExpression call(Cons argList) { - // retrieve the number of arguments passed to EVAL - int argListLength = LENGTH.getLength(argList); + public EVAL() { + this.argumentValidator = new ArgumentValidator("EVAL"); + this.argumentValidator.setExactNumberOfArguments(1); + } - // make sure we have received the proper number of arguments - if (argListLength != NUM_ARGS) { - Cons originalSExpr = new Cons(new Symbol("EVAL"), argList); - String errMsg = "too " + ((argListLength > NUM_ARGS) ? "many" : "few") + " arguments given to EVAL: " - + originalSExpr; + public SExpression call(Cons argumentList) { + argumentValidator.validate(argumentList); - throw new RuntimeException(errMsg); + SExpression argument = argumentList.getCar(); + + if (argument.listp()) { + if (argument.consp()) + return evaluateList((Cons) argument); + + return argument; // NIL } - SExpression arg = argList.getCar(); + if (argument.symbolp()) { + SExpression symbolValue = lookupSymbol(argument.toString()); - if (arg.listp()) { - if (arg.consp()) { - return evaluateList((Cons) arg); - } - - return arg; // 'arg' is NIL - } - - if (arg.symbolp()) { - SExpression symbolValue = lookupSymbol(arg.toString()); - - if (symbolValue != null) { + if (symbolValue != null) return symbolValue; - } - throw new RuntimeException("variable " + arg + " has no value"); + throw new UndefinedSymbolException(argument); } - return arg; // 'arg' is a NUMBER or a STRING + return argument; // NUMBER or STRING } private SExpression evaluateList(Cons list) { - SExpression car = list.getCar(); - SExpression cdr = list.getCdr(); + SExpression functionName = list.getCar(); + SExpression arguments = list.getCdr(); + LispFunction function = lookupFunctionOrLambda(functionName); - LispFunction function = lookupFunctionOrLambda(car); + ArgumentValidator functionListValidator = new ArgumentValidator(functionName.toString()); + functionListValidator.validate(list); - // make sure the list of arguments for 'function' is a list - if (cdr.listp()) { - Cons args = (Cons) cdr; + Cons argumentList = (Cons) arguments; - // make sure the list of arguments is not dotted - if (isDotted(args)) { - throw new RuntimeException("argument list given to " + car + " is dotted: " + list); - } + if (function.evaluateArguments()) + argumentList = evaluateArgList(argumentList); - // determine if we should evaluate the arguments that will be - // passed to 'function' - if (function.evaluateArguments()) { - args = evaluateArgList(args); - } - - return function.call(args); - } - - // the list of arguments is not a list! - throw new RuntimeException("argument list given to " + car + " is dotted: " + list); + return function.call(argumentList); } private Cons evaluateArgList(Cons arguments) { - if (arguments.nullp()) { + if (arguments.nullp()) return Nil.getInstance(); - } SExpression car = eval(arguments.getCar()); SExpression cdr = arguments.getCdr(); - if (cdr.listp()) { - return new Cons(car, evaluateArgList((Cons) cdr)); + return new Cons(car, evaluateArgList((Cons) cdr)); + } + + public static class UndefinedFunctionException extends LispException { + + private static final long serialVersionUID = 1L; + private SExpression function; + + public UndefinedFunctionException(SExpression function) { + this.function = function; } - // remove any parameters found after a dot (put here in case the check - // for a dotted parameter list is not done prior to this call) - return new Cons(car, Nil.getInstance()); + @Override + public String getMessage() { + return MessageFormat.format("undefined function: {0}", function); + } + } + + public static class UndefinedSymbolException extends LispException { + + private static final long serialVersionUID = 1L; + private SExpression symbol; + + public UndefinedSymbolException(SExpression symbol) { + this.symbol = symbol; + } + + @Override + public String getMessage() { + return MessageFormat.format("symbol {0} has no value", symbol); + } } } diff --git a/src/function/builtin/EXIT.java b/src/function/builtin/EXIT.java index 6413ac3..3043572 100644 --- a/src/function/builtin/EXIT.java +++ b/src/function/builtin/EXIT.java @@ -1,27 +1,21 @@ package function.builtin; -import function.LispFunction; -import function.builtin.cons.LENGTH; +import function.*; import sexpression.*; public class EXIT extends LispFunction { - // The number of arguments that EXIT takes. - private static final int NUM_ARGS = 0; + private ArgumentValidator argumentValidator; - public SExpression call(Cons argList) { - // retrieve the number of arguments passed to EXIT - int argListLength = LENGTH.getLength(argList); - - // make sure we have received the proper number of arguments - if (argListLength > NUM_ARGS) { - Cons originalSExpr = new Cons(new Symbol("EXIT"), argList); - String errMsg = "too many arguments given to EXIT: " + originalSExpr; - - throw new RuntimeException(errMsg); - } + public EXIT() { + this.argumentValidator = new ArgumentValidator("EXIT"); + this.argumentValidator.setMaximumNumberOfArguments(0); + } + public SExpression call(Cons argumentList) { + argumentValidator.validate(argumentList); System.exit(0); + return null; } diff --git a/src/function/builtin/special/DEFUN.java b/src/function/builtin/special/DEFUN.java index 638c888..6f1a86a 100644 --- a/src/function/builtin/special/DEFUN.java +++ b/src/function/builtin/special/DEFUN.java @@ -4,48 +4,47 @@ import java.util.HashMap; import function.*; import function.builtin.EVAL; +import function.builtin.cons.LIST; import sexpression.*; public class DEFUN extends LispFunction { private ArgumentValidator argumentValidator; + private ArgumentValidator lambdaListIsListValidator; + private ArgumentValidator lambdaListValidator; public DEFUN() { this.argumentValidator = new ArgumentValidator("DEFUN"); this.argumentValidator.setMinimumNumberOfArguments(3); this.argumentValidator.setFirstArgumentExpectedType(Symbol.class); + + this.lambdaListIsListValidator = new ArgumentValidator("DEFUN|lambda_list|"); + this.lambdaListIsListValidator.setEveryArgumentExpectedType(Cons.class); + + this.lambdaListValidator = new ArgumentValidator("DEFUN|lambda_list|"); + this.lambdaListValidator.setEveryArgumentExpectedType(Symbol.class); } public SExpression call(Cons argumentList) { argumentValidator.validate(argumentList); - Cons cdr = (Cons) argumentList.getCdr(); - SExpression name = argumentList.getCar(); // name of the function - SExpression cadr = cdr.getCar(); + Cons remainingArguments = (Cons) argumentList.getCdr(); + SExpression functionName = argumentList.getCar(); + SExpression secondArgument = remainingArguments.getCar(); + lambdaListIsListValidator.validate(LIST.makeList(secondArgument)); - // make sure the list of arguments (lambda list) is a proper list - if (!cadr.listp()) { - throw new RuntimeException("DEFUN: " + cadr + " is not a list"); - } else if (EVAL.isDotted((Cons) cadr)) { - throw new RuntimeException("DEFUN: " + cadr + " is not a proper list"); - } - - Cons lambdaList = (Cons) cadr; // lambda list of the function - - // list of S-expressions making up the body of the function - Cons body = (Cons) cdr.getCdr(); + Cons lambdaList = (Cons) secondArgument; + lambdaListValidator.validate(lambdaList); + Cons functionBody = (Cons) remainingArguments.getCdr(); HashMap functionTable = EVAL.getFunctionTable(); - // give a warning if this function has already been defined - if (functionTable.containsKey(name.toString())) { - System.out.println("WARNING: redefining function " + name.toString()); - } + if (functionTable.containsKey(functionName.toString())) + System.out.println("WARNING: redefining function " + functionName.toString()); - // place the function in the function table - functionTable.put(name.toString(), new UserDefinedFunction(name.toString(), lambdaList, body)); + functionTable.put(functionName.toString(), new UserDefinedFunction(functionName.toString(), lambdaList, functionBody)); - return name; + return functionName; } public boolean evaluateArguments() { diff --git a/src/function/builtin/special/LAMBDA.java b/src/function/builtin/special/LAMBDA.java index 30aa5c6..d0bc71a 100644 --- a/src/function/builtin/special/LAMBDA.java +++ b/src/function/builtin/special/LAMBDA.java @@ -5,19 +5,8 @@ import function.builtin.*; import function.builtin.cons.LENGTH; import sexpression.*; -/** - * LAMBDA represents the LAMBDA form in Lisp. - */ public class LAMBDA extends LispFunction { - /** - * Determine if the given S-expression is a lambda expression. - * - * @param sexpr - * the S-expression to test (must not be null) - * @return true if sexpr is a valid lambda expression; - * false otherwise - */ public static boolean isLambdaExpression(SExpression sexpr) { if (sexpr.consp()) { SExpression first = ((Cons) sexpr).getCar(); @@ -28,16 +17,6 @@ public class LAMBDA extends LispFunction { return false; } - /** - * Create an internal representation of a user-defined function from the specified lambda - * expression. - * - * @param lexpr - * the lambda expression to create the function from (must not be null) - * @return an internal representation of a user-defined function created from lexpr - * @throws RuntimeException - * Indicates that lexpr is not a valid lambda expression. - */ public static UserDefinedFunction createFunction(Cons lexpr) { LAMBDA lambda = new LAMBDA(); SExpression cdr = lexpr.getCdr(); @@ -86,11 +65,6 @@ public class LAMBDA extends LispFunction { return new LambdaExpression(lexpr, function); } - /** - * Determine if the arguments passed to this Lisp function should be evaluated. - * - * @return false - */ public boolean evaluateArguments() { return false; } diff --git a/test/function/builtin/EVALTester.java b/test/function/builtin/EVALTester.java new file mode 100644 index 0000000..def4bf4 --- /dev/null +++ b/test/function/builtin/EVALTester.java @@ -0,0 +1,92 @@ +package function.builtin; + +import static org.junit.Assert.*; +import static testutil.TestUtilities.*; + +import org.junit.Test; + +import function.ArgumentValidator.*; +import function.builtin.EVAL.*; +import sexpression.Nil; + +public class EVALTester { + + @Test + public void testEval() { + String input = "(eval 9)"; + + assertSExpressionsMatch(evaluateString(input), parseString("9")); + } + + @Test + public void testEvalNil() { + String input = "(eval ())"; + + assertSExpressionsMatch(evaluateString(input), parseString("()")); + } + + @Test + public void testLookupSymbol() { + String symbol = ":symbol"; + assertSExpressionsMatch(EVAL.lookupSymbol(symbol), parseString(symbol)); + } + + @Test + public void testLookupT() { + String symbol = "T"; + assertSExpressionsMatch(EVAL.lookupSymbol(symbol), parseString(symbol)); + } + + @Test + public void testLookupUndefinedSymbol() { + assertNull(EVAL.lookupSymbol("undefined")); + } + + @Test(expected = UndefinedFunctionException.class) + public void testEvalUndefinedFunction() { + String input = "(funcall 'eval '(undefined))"; + + evaluateString(input); + } + + @Test(expected = UndefinedSymbolException.class) + public void testEvalUndefinedSymbol() { + String input = "(eval undefined)"; + + evaluateString(input); + } + + @Test(expected = DottedArgumentListException.class) + public void testEvalWithDottedLambdaList() { + String input = "(funcall 'eval (cons '+ 1))"; + + evaluateString(input); + } + + @Test(expected = TooManyArgumentsException.class) + public void testEvalWithTooManyArguments() { + evaluateString("(eval '1 '2 '3)"); + } + + @Test(expected = TooFewArgumentsException.class) + public void testEvalWithTooFewArguments() { + evaluateString("(eval)"); + } + + @Test + public void undefinedFunctionException_HasMessageText() { + UndefinedFunctionException e = new UndefinedFunctionException(Nil.getInstance()); + + assertNotNull(e.getMessage()); + assertTrue(e.getMessage().length() > 0); + } + + @Test + public void undefinedSymbolException_HasMessageText() { + UndefinedSymbolException e = new UndefinedSymbolException(Nil.getInstance()); + + assertNotNull(e.getMessage()); + assertTrue(e.getMessage().length() > 0); + } + +} diff --git a/test/function/builtin/special/DEFUNTester.java b/test/function/builtin/special/DEFUNTester.java index bac39a2..f580e01 100644 --- a/test/function/builtin/special/DEFUNTester.java +++ b/test/function/builtin/special/DEFUNTester.java @@ -16,14 +16,26 @@ public class DEFUNTester { assertSExpressionsMatch(evaluateString("(f)"), parseString("()")); } + @Test(expected = DottedArgumentListException.class) + public void testDefunWithDottedLambdaList() { + String input = "(funcall 'defun 'x (cons 'a 'b) ())"; + + evaluateString(input); + } + @Test(expected = BadArgumentTypeException.class) public void testDefunWithNonSymbolName() { evaluateString("(defun 1 () ())"); } + @Test(expected = BadArgumentTypeException.class) + public void testDefunWithBadLambdaList() { + evaluateString("(defun x a ())"); + } + @Test(expected = TooFewArgumentsException.class) public void testApplyWithTooFewArguments() { - evaluateString("(defun 1 ())"); + evaluateString("(defun x ())"); } } diff --git a/test/function/builtin/special/QUOTETester.java b/test/function/builtin/special/QUOTETester.java new file mode 100644 index 0000000..fe042e3 --- /dev/null +++ b/test/function/builtin/special/QUOTETester.java @@ -0,0 +1,35 @@ +package function.builtin.special; + +import static testutil.TestUtilities.*; + +import org.junit.Test; + +import function.ArgumentValidator.*; + +public class QUOTETester { + + @Test + public void testQuoteSymbol() { + String input = "'a"; + + assertSExpressionsMatch(evaluateString(input), parseString("a")); + } + + @Test + public void testQuoteList() { + String input = "'(l i s t)"; + + assertSExpressionsMatch(evaluateString(input), parseString("(l i s t)")); + } + + @Test(expected = TooFewArgumentsException.class) + public void testQuoteWithTooFewArguments() { + evaluateString("(quote)"); + } + + @Test(expected = TooManyArgumentsException.class) + public void testQuoteWithTooManyArguments() { + evaluateString("(quote a b)"); + } + +}