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;
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<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() {
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);
// 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);
public EVAL() {
this.argumentValidator = new ArgumentValidator("EVAL");
this.argumentValidator.setExactNumberOfArguments(1);
}
SExpression arg = argList.getCar();
public SExpression call(Cons argumentList) {
argumentValidator.validate(argumentList);
if (arg.listp()) {
if (arg.consp()) {
return evaluateList((Cons) arg);
SExpression argument = argumentList.getCar();
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()) {
SExpression symbolValue = lookupSymbol(arg.toString());
if (symbolValue != null) {
if (symbolValue != null)
return symbolValue;
throw new UndefinedSymbolException(argument);
}
throw new RuntimeException("variable " + arg + " has no value");
}
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));
}
// 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());
public static class UndefinedFunctionException extends LispException {
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;
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;
}

View File

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

View File

@ -5,19 +5,8 @@ import function.builtin.*;
import function.builtin.cons.LENGTH;
import sexpression.*;
/**
* <code>LAMBDA</code> 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 <code>true</code> if <code>sexpr</code> is a valid lambda expression;
* <code>false</code> 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 <code>lexpr</code>
* @throws RuntimeException
* Indicates that <code>lexpr</code> 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 <code>false</code>
*/
public boolean evaluateArguments() {
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("()"));
}
@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 ())");
}
}

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