Started major refactoring of several built in functions

This commit is contained in:
Mike Cifelli 2017-01-16 13:38:49 -05:00
parent 35ef281733
commit 217c215efe
8 changed files with 293 additions and 168 deletions

View File

@ -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<String, LispFunction> functionTable = new HashMap<String, LispFunction>();
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());
}
}

View File

@ -1,62 +1,22 @@
package function.builtin; package function.builtin;
import static function.FunctionTable.functionTable;
import java.text.MessageFormat;
import java.util.HashMap; import java.util.HashMap;
import function.LispFunction; import error.LispException;
import function.builtin.cons.*; import function.*;
import function.builtin.math.*; import function.builtin.cons.LIST;
import function.builtin.predicate.*;
import function.builtin.special.*; import function.builtin.special.*;
import sexpression.*; import sexpression.*;
public class EVAL extends LispFunction { public class EVAL extends LispFunction {
private static HashMap<String, LispFunction> functionTable = new HashMap<String, LispFunction>();
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<String, LispFunction> getFunctionTable() { public static HashMap<String, LispFunction> getFunctionTable() {
return functionTable; return functionTable;
} }
public static LispFunction lookupFunction(String functionName) {
return functionTable.get(functionName);
}
public static LispFunction lookupFunctionOrLambda(SExpression functionExpression) { public static LispFunction lookupFunctionOrLambda(SExpression functionExpression) {
LispFunction function = lookupFunction(functionExpression.toString()); LispFunction function = lookupFunction(functionExpression.toString());
@ -66,13 +26,17 @@ public class EVAL extends LispFunction {
return function; return function;
} }
public static LispFunction lookupFunction(String functionName) {
return functionTable.get(functionName);
}
private static LispFunction createLambdaFunction(SExpression lambdaExpression) { private static LispFunction createLambdaFunction(SExpression lambdaExpression) {
if (lambdaExpression.functionp()) if (lambdaExpression.functionp())
return ((LambdaExpression) lambdaExpression).getFunction(); return ((LambdaExpression) lambdaExpression).getFunction();
else if (LAMBDA.isLambdaExpression(lambdaExpression)) else if (LAMBDA.isLambdaExpression(lambdaExpression))
return LAMBDA.createFunction((Cons) lambdaExpression); return LAMBDA.createFunction((Cons) lambdaExpression);
else else
throw new RuntimeException("undefined function " + lambdaExpression); throw new UndefinedFunctionException(lambdaExpression);
} }
public static SExpression lookupSymbol(String symbolName) { public static SExpression lookupSymbol(String symbolName) {
@ -102,94 +66,98 @@ public class EVAL extends LispFunction {
return true; return true;
} }
public static SExpression eval(SExpression sexpr) { public static SExpression eval(SExpression sExpression) {
Cons expList = LIST.makeList(sexpr); Cons argumentList = LIST.makeList(sExpression);
EVAL evalFunction = new EVAL(); 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) { public EVAL() {
// retrieve the number of arguments passed to EVAL this.argumentValidator = new ArgumentValidator("EVAL");
int argListLength = LENGTH.getLength(argList); 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;
throw new RuntimeException(errMsg);
} }
SExpression arg = argList.getCar(); public SExpression call(Cons argumentList) {
argumentValidator.validate(argumentList);
if (arg.listp()) { SExpression argument = argumentList.getCar();
if (arg.consp()) {
return evaluateList((Cons) arg); if (argument.listp()) {
if (argument.consp())
return evaluateList((Cons) argument);
return argument; // NIL
} }
return arg; // 'arg' is NIL if (argument.symbolp()) {
} SExpression symbolValue = lookupSymbol(argument.toString());
if (arg.symbolp()) { if (symbolValue != null)
SExpression symbolValue = lookupSymbol(arg.toString());
if (symbolValue != null) {
return symbolValue; return symbolValue;
throw new UndefinedSymbolException(argument);
} }
throw new RuntimeException("variable " + arg + " has no value"); return argument; // NUMBER or STRING
}
return arg; // 'arg' is a NUMBER or a STRING
} }
private SExpression evaluateList(Cons list) { private SExpression evaluateList(Cons list) {
SExpression car = list.getCar(); SExpression functionName = list.getCar();
SExpression cdr = list.getCdr(); 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 Cons argumentList = (Cons) arguments;
if (cdr.listp()) {
Cons args = (Cons) cdr;
// make sure the list of arguments is not dotted if (function.evaluateArguments())
if (isDotted(args)) { argumentList = evaluateArgList(argumentList);
throw new RuntimeException("argument list given to " + car + " is dotted: " + list);
}
// determine if we should evaluate the arguments that will be return function.call(argumentList);
// 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);
} }
private Cons evaluateArgList(Cons arguments) { private Cons evaluateArgList(Cons arguments) {
if (arguments.nullp()) { if (arguments.nullp())
return Nil.getInstance(); return Nil.getInstance();
}
SExpression car = eval(arguments.getCar()); SExpression car = eval(arguments.getCar());
SExpression cdr = arguments.getCdr(); SExpression cdr = arguments.getCdr();
if (cdr.listp()) {
return new Cons(car, evaluateArgList((Cons) cdr)); return new Cons(car, evaluateArgList((Cons) cdr));
} }
// remove any parameters found after a dot (put here in case the check public static class UndefinedFunctionException extends LispException {
// for a dotted parameter list is not done prior to this call)
return new Cons(car, Nil.getInstance()); private static final long serialVersionUID = 1L;
private SExpression function;
public UndefinedFunctionException(SExpression function) {
this.function = function;
}
@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);
}
} }
} }

View File

@ -1,27 +1,21 @@
package function.builtin; package function.builtin;
import function.LispFunction; import function.*;
import function.builtin.cons.LENGTH;
import sexpression.*; import sexpression.*;
public class EXIT extends LispFunction { public class EXIT extends LispFunction {
// The number of arguments that EXIT takes. private ArgumentValidator argumentValidator;
private static final int NUM_ARGS = 0;
public SExpression call(Cons argList) { public EXIT() {
// retrieve the number of arguments passed to EXIT this.argumentValidator = new ArgumentValidator("EXIT");
int argListLength = LENGTH.getLength(argList); this.argumentValidator.setMaximumNumberOfArguments(0);
// 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 SExpression call(Cons argumentList) {
argumentValidator.validate(argumentList);
System.exit(0); System.exit(0);
return null; return null;
} }

View File

@ -4,48 +4,47 @@ import java.util.HashMap;
import function.*; import function.*;
import function.builtin.EVAL; import function.builtin.EVAL;
import function.builtin.cons.LIST;
import sexpression.*; import sexpression.*;
public class DEFUN extends LispFunction { public class DEFUN extends LispFunction {
private ArgumentValidator argumentValidator; private ArgumentValidator argumentValidator;
private ArgumentValidator lambdaListIsListValidator;
private ArgumentValidator lambdaListValidator;
public DEFUN() { public DEFUN() {
this.argumentValidator = new ArgumentValidator("DEFUN"); this.argumentValidator = new ArgumentValidator("DEFUN");
this.argumentValidator.setMinimumNumberOfArguments(3); this.argumentValidator.setMinimumNumberOfArguments(3);
this.argumentValidator.setFirstArgumentExpectedType(Symbol.class); 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) { public SExpression call(Cons argumentList) {
argumentValidator.validate(argumentList); argumentValidator.validate(argumentList);
Cons cdr = (Cons) argumentList.getCdr(); Cons remainingArguments = (Cons) argumentList.getCdr();
SExpression name = argumentList.getCar(); // name of the function SExpression functionName = argumentList.getCar();
SExpression cadr = cdr.getCar(); SExpression secondArgument = remainingArguments.getCar();
lambdaListIsListValidator.validate(LIST.makeList(secondArgument));
// make sure the list of arguments (lambda list) is a proper list Cons lambdaList = (Cons) secondArgument;
if (!cadr.listp()) { lambdaListValidator.validate(lambdaList);
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 functionBody = (Cons) remainingArguments.getCdr();
HashMap<String, LispFunction> functionTable = EVAL.getFunctionTable(); HashMap<String, LispFunction> functionTable = EVAL.getFunctionTable();
// give a warning if this function has already been defined if (functionTable.containsKey(functionName.toString()))
if (functionTable.containsKey(name.toString())) { System.out.println("WARNING: redefining function " + functionName.toString());
System.out.println("WARNING: redefining function " + name.toString());
}
// place the function in the function table functionTable.put(functionName.toString(), new UserDefinedFunction(functionName.toString(), lambdaList, functionBody));
functionTable.put(name.toString(), new UserDefinedFunction(name.toString(), lambdaList, body));
return name; return functionName;
} }
public boolean evaluateArguments() { public boolean evaluateArguments() {

View File

@ -5,19 +5,8 @@ import function.builtin.*;
import function.builtin.cons.LENGTH; import function.builtin.cons.LENGTH;
import sexpression.*; import sexpression.*;
/**
* <code>LAMBDA</code> represents the LAMBDA form in Lisp.
*/
public class LAMBDA extends LispFunction { 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 <code>true</code> if <code>sexpr</code> is a valid lambda expression;
* <code>false</code> otherwise
*/
public static boolean isLambdaExpression(SExpression sexpr) { public static boolean isLambdaExpression(SExpression sexpr) {
if (sexpr.consp()) { if (sexpr.consp()) {
SExpression first = ((Cons) sexpr).getCar(); SExpression first = ((Cons) sexpr).getCar();
@ -28,16 +17,6 @@ public class LAMBDA extends LispFunction {
return false; 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 <code>lexpr</code>
* @throws RuntimeException
* Indicates that <code>lexpr</code> is not a valid lambda expression.
*/
public static UserDefinedFunction createFunction(Cons lexpr) { public static UserDefinedFunction createFunction(Cons lexpr) {
LAMBDA lambda = new LAMBDA(); LAMBDA lambda = new LAMBDA();
SExpression cdr = lexpr.getCdr(); SExpression cdr = lexpr.getCdr();
@ -86,11 +65,6 @@ public class LAMBDA extends LispFunction {
return new LambdaExpression(lexpr, function); return new LambdaExpression(lexpr, function);
} }
/**
* Determine if the arguments passed to this Lisp function should be evaluated.
*
* @return <code>false</code>
*/
public boolean evaluateArguments() { public boolean evaluateArguments() {
return false; return false;
} }

View File

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

View File

@ -16,14 +16,26 @@ public class DEFUNTester {
assertSExpressionsMatch(evaluateString("(f)"), parseString("()")); 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) @Test(expected = BadArgumentTypeException.class)
public void testDefunWithNonSymbolName() { public void testDefunWithNonSymbolName() {
evaluateString("(defun 1 () ())"); evaluateString("(defun 1 () ())");
} }
@Test(expected = BadArgumentTypeException.class)
public void testDefunWithBadLambdaList() {
evaluateString("(defun x a ())");
}
@Test(expected = TooFewArgumentsException.class) @Test(expected = TooFewArgumentsException.class)
public void testApplyWithTooFewArguments() { public void testApplyWithTooFewArguments() {
evaluateString("(defun 1 ())"); evaluateString("(defun x ())");
} }
} }

View File

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