diff --git a/project.properties b/project.properties index ff41943..5b7271b 100755 --- a/project.properties +++ b/project.properties @@ -1,13 +1,13 @@ -#Wed Nov 25 16:01:45 CST 2009 -build.dir=build -src.dir=src -grammar.output.dir=${src.dir}/edu/utexas/cs345/jdblisp/parser -build.jar=${build.dir}/JCLisp-${application.version}.${build.number}.jar -build.number=27 -dist.dir=dist -javacc.home=${lib.dir}/javacc -lib.dir=lib -dist.jar=${dist.dir}/JCLisp-${application.version}.jar -build.classes.dir=${build.dir}/classes -grammar.file=${src.dir}/edu/utexas/cs345/jdblisp/Parser.jj -application.version=0.1.0 +#Mon Nov 30 20:37:52 CST 2009 +build.dir=build +src.dir=src +grammar.output.dir=${src.dir}/edu/utexas/cs345/jdblisp/parser +build.jar=${build.dir}/JCLisp-${application.version}.${build.number}.jar +build.number=46 +dist.dir=dist +javacc.home=${lib.dir}/javacc +dist.jar=${dist.dir}/JCLisp-${application.version}.jar +lib.dir=lib +grammar.file=${src.dir}/edu/utexas/cs345/jdblisp/Parser.jj +build.classes.dir=${build.dir}/classes +application.version=0.1.0 diff --git a/src/edu/utexas/cs345/jdblisp/ClosureSymbolTable.java b/src/edu/utexas/cs345/jdblisp/ClosureSymbolTable.java new file mode 100644 index 0000000..ab4f100 --- /dev/null +++ b/src/edu/utexas/cs345/jdblisp/ClosureSymbolTable.java @@ -0,0 +1,58 @@ +package edu.utexas.cs345.jdblisp; + +import java.util.Map; + +/** + * ClosureSymbolTable + * @author Jonathan Bernard (jdbernard@gmail.com) + */ +public class ClosureSymbolTable extends SymbolTable { + + private SymbolTable lexicalScope; + + public ClosureSymbolTable(SymbolTable lexicalScope, + SymbolTable closedScope) { + super(closedScope); + this.lexicalScope = lexicalScope; + } + + /** + * + */ + @Override + public FormEntry lookupFunction(Symbol s) throws LispException { + + // lookup function in the closed binding first + FormEntry fe = super.lookupFunction(s); + + // lookup in the lexical scope if it is not found in the closure + if (fe == null && lexicalScope != null) + fe = lexicalScope.lookupFunction(s); + + // return the result (may be null if not found anywhere) + return fe; + } + + /** + * Lookup a variable bound to the given + * {@link edu.utexas.cs345.jdblisp.Symbol}. The lookup is performed first + * in the closed scope and then the lexical scope if the variable is not + * found in the closed scope. + * @param s The symbol to lookup. + * @return The variable bound to the symbol, or null if the symbol + * is unbound. + */ + @Override + public VariableEntry lookupVariable(Symbol s) throws LispException { + + // lookup variable in the closed binding first + VariableEntry ve = super.lookupVariable(s); + + // lookup in the lexical scope if it is not found in the closure + if (ve == null && lexicalScope != null) + ve = lexicalScope.lookupVariable(s); + + // return the result (may be null if not found anywhere) + return ve; + } +} diff --git a/src/edu/utexas/cs345/jdblisp/FormEntry.java b/src/edu/utexas/cs345/jdblisp/FormEntry.java index bfaacca..7457abd 100644 --- a/src/edu/utexas/cs345/jdblisp/FormEntry.java +++ b/src/edu/utexas/cs345/jdblisp/FormEntry.java @@ -4,13 +4,13 @@ package edu.utexas.cs345.jdblisp; * FormEntry * @author Jonathan Bernard(jdbernard@gmail.com) */ -public abstract class FormEntry implements SExp { +public abstract class FormEntry implements SExp, SymbolTableEntry { - public final Symbol name; + public final Symbol symbol; public HelpTopic helpinfo; - public FormEntry(Symbol name, HelpTopic helpinfo) { - this.name = name; + public FormEntry(Symbol symbol, HelpTopic helpinfo) { + this.symbol = symbol; this.helpinfo = helpinfo; } @@ -19,4 +19,5 @@ public abstract class FormEntry implements SExp { public SExp eval(SymbolTable symbolTable) { return this; } + public Symbol symbol() { return symbol; } } diff --git a/src/edu/utexas/cs345/jdblisp/FunctionEntry.java b/src/edu/utexas/cs345/jdblisp/FunctionEntry.java index 816e674..6412518 100755 --- a/src/edu/utexas/cs345/jdblisp/FunctionEntry.java +++ b/src/edu/utexas/cs345/jdblisp/FunctionEntry.java @@ -13,26 +13,26 @@ public class FunctionEntry extends FormEntry { //private Logger traceLog = Logger.getLogger(getClass()); - public FunctionEntry(Symbol name, Symbol[] parameters, SExp body) { - super(name, null); + public FunctionEntry(Symbol symbol, Symbol[] parameters, SExp body) { + super(symbol, null); // build invocation help string - String invokation = "(" + name.name; + String invokation = "(" + symbol.name; for (Symbol param : parameters) invokation += " <" + param.name + ">"; invokation += ")"; String bodyInfo = "Function body: " + body.toString(); // build help topic - FormHelpTopic helpinfo = new FormHelpTopic(name.name, null, invokation, bodyInfo); + FormHelpTopic helpinfo = new FormHelpTopic(symbol.name, null, invokation, bodyInfo); this.parameters = parameters; this.body = body; this.helpinfo = helpinfo; } - public FunctionEntry(Symbol name, Symbol[] parameters, SExp body, + public FunctionEntry(Symbol symbol, Symbol[] parameters, SExp body, HelpTopic helpinfo) { - super(name, helpinfo); + super(symbol, helpinfo); this.parameters = parameters; this.body = body; } @@ -42,10 +42,10 @@ public class FunctionEntry extends FormEntry { public void enableTrace(boolean enable) { this.traceEnabled = enable; } public String display(String offset) { - return offset + "Function: " + name.toString(); + return offset + "Function: " + symbol.toString(); } - public String toString() { return ""; } + public String toString() { return ""; } public SExp call(SymbolTable symbolTable, Seq arguments) throws LispException { @@ -53,7 +53,7 @@ public class FunctionEntry extends FormEntry { SExp evaluatedArg, retVal; if (traceEnabled) - traceString = "(" + name.name; + traceString = "(" + symbol.name; // bind arguments to parameters SymbolTable localScope = new SymbolTable(symbolTable); @@ -63,11 +63,12 @@ public class FunctionEntry extends FormEntry { // too few arguments if (arguments == null) throw new InvalidArgumentQuantityException( - parameters.length, i); + toString(), parameters.length, i); // bind one arg to param evaluatedArg = arguments.car.eval(symbolTable); - localScope.bind(parameters[i], new VariableEntry(evaluatedArg)); + localScope.bind(parameters[i], new VariableEntry(parameters[i], + evaluatedArg)); if (traceEnabled) traceString += " " + evaluatedArg.toString(); @@ -77,8 +78,8 @@ public class FunctionEntry extends FormEntry { // too many arguments if (arguments != null) - throw new InvalidArgumentQuantityException(parameters.length, - (i + arguments.length())); + throw new InvalidArgumentQuantityException( + toString(),parameters.length, (i + arguments.length())); if (traceEnabled) traceString += ")"; if (traceEnabled) System.out.println(traceString); @@ -87,7 +88,7 @@ public class FunctionEntry extends FormEntry { retVal = body.eval(localScope); if (traceEnabled) - traceString = name.name + " returned " + retVal.toString(); + traceString = symbol.name + " returned " + retVal.toString(); if (traceEnabled) System.out.println(traceString); return retVal; diff --git a/src/edu/utexas/cs345/jdblisp/InvalidArgumentQuantityException.java b/src/edu/utexas/cs345/jdblisp/InvalidArgumentQuantityException.java index c7fb241..22c6c01 100755 --- a/src/edu/utexas/cs345/jdblisp/InvalidArgumentQuantityException.java +++ b/src/edu/utexas/cs345/jdblisp/InvalidArgumentQuantityException.java @@ -7,16 +7,16 @@ package edu.utexas.cs345.jdblisp; */ public class InvalidArgumentQuantityException extends LispException { - public InvalidArgumentQuantityException(int expected, int actual) { - super ("Invalid number of arguments: " + actual + public InvalidArgumentQuantityException(String invokeName, int expected, int actual) { + super ("Invalid number of arguments to " + invokeName + ": " + actual + " (expected " + expected + ")."); } - public InvalidArgumentQuantityException(int actual) { - super ("Invalid number of arguments: " + actual); + public InvalidArgumentQuantityException(String invokeName, int expected) { + super ("Invalid number of arguments to " + invokeName + ": expected " + expected); } - public InvalidArgumentQuantityException(String message) { - super ("Invalid number of arguments: " + message); + public InvalidArgumentQuantityException(String invokeName, String message) { + super ("Invalid number of arguments to " + invokeName + ": " + message); } } diff --git a/src/edu/utexas/cs345/jdblisp/LISPRuntime.java b/src/edu/utexas/cs345/jdblisp/LISPRuntime.java index bc2a9f8..cb8c0a5 100755 --- a/src/edu/utexas/cs345/jdblisp/LISPRuntime.java +++ b/src/edu/utexas/cs345/jdblisp/LISPRuntime.java @@ -122,9 +122,11 @@ public class LISPRuntime { void signalStop() { stop = true; } private static SymbolTable defineGlobalConstants() { + Symbol T = new Symbol("T"); + Symbol NIL = new Symbol("NIL"); SymbolTable constantsTable = new SymbolTable(); - constantsTable.bind(new Symbol("T"), new VariableEntry(SExp.T, true)); - constantsTable.bind(new Symbol("NIL"), new VariableEntry(SExp.NIL, true)); + constantsTable.bind(T, new VariableEntry(T, SExp.T, true)); + constantsTable.bind(NIL, new VariableEntry(NIL, SExp.NIL, true)); return constantsTable; } diff --git a/src/edu/utexas/cs345/jdblisp/Lambda.java b/src/edu/utexas/cs345/jdblisp/Lambda.java index 3bdb4fa..a552d86 100644 --- a/src/edu/utexas/cs345/jdblisp/Lambda.java +++ b/src/edu/utexas/cs345/jdblisp/Lambda.java @@ -8,14 +8,14 @@ public class Lambda extends FunctionEntry { private SymbolTable closure; public Lambda(Symbol[] parameters, SExp body, SymbolTable closure) { - super(new Symbol(""), parameters, body); + super(new Symbol("L()"), parameters, body); this.closure = closure; } public Lambda(Symbol[] parameters, SExp body, SymbolTable closure, HelpTopic helpinfo) { - super(new Symbol(""), parameters, body, helpinfo); + super(new Symbol("L()"), parameters, body, helpinfo); this.closure = closure; } @@ -31,6 +31,7 @@ public class Lambda extends FunctionEntry { return sb.toString(); } + @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(" { return new Num(n.abs()); } + public Num remainder(Num divisor) { + return new Num(n.remainder(divisor.n)); + } + @Override public int compareTo(Num that) { return this.n.compareTo(that.n); diff --git a/src/edu/utexas/cs345/jdblisp/Parser.jj b/src/edu/utexas/cs345/jdblisp/Parser.jj index 75aa6e8..3dc193f 100755 --- a/src/edu/utexas/cs345/jdblisp/Parser.jj +++ b/src/edu/utexas/cs345/jdblisp/Parser.jj @@ -41,7 +41,7 @@ TOKEN : /* LITERALS & SYMBOLS */ | < STRG: "\"" (~["\""])* "\"" > | < SYMB: (["A"-"Z", "a"-"z", "_", "+", "-", "*", "/", "=", ">", "<"])+ (["A"-"Z", "a"-"z", "0"-"9", - "_", "+", "-", "*", "/", "=", ">", "<"])? > + "_", "+", "-", "*", "/", "=", ">", "<"])* > } /** diff --git a/src/edu/utexas/cs345/jdblisp/SExp.java b/src/edu/utexas/cs345/jdblisp/SExp.java index 0b3dda1..cb6e2f3 100755 --- a/src/edu/utexas/cs345/jdblisp/SExp.java +++ b/src/edu/utexas/cs345/jdblisp/SExp.java @@ -26,4 +26,5 @@ public interface SExp { public String display(String offset) { return offset + "NIL\n"; } public String toString() { return "NIL"; } }; + } diff --git a/src/edu/utexas/cs345/jdblisp/SpecialFormEntry.java b/src/edu/utexas/cs345/jdblisp/SpecialFormEntry.java index 0203867..f5362b7 100755 --- a/src/edu/utexas/cs345/jdblisp/SpecialFormEntry.java +++ b/src/edu/utexas/cs345/jdblisp/SpecialFormEntry.java @@ -10,22 +10,12 @@ import java.util.ArrayList; public abstract class SpecialFormEntry extends FormEntry { protected LISPRuntime environment; - protected int expectedArgumentCount; - protected Class[] expectedArgumentTypes; public SpecialFormEntry(Symbol name, LISPRuntime environment, - HelpTopic helpinfo, int expectedArgCount, Class... expectedArgTypes) { + HelpTopic helpinfo) { super(name, helpinfo); - // three cases: no arguments, so EAT == null || 0; arg types homogenous - // so EAT == 1; arg types heterogenous, EAT == numArgs - assert (expectedArgumentTypes == null || - expectedArgumentTypes.length <= 1 || - expectedArgumentTypes.length == expectedArgumentCount); - this.environment = environment; - this.expectedArgumentCount = expectedArgCount; - this.expectedArgumentTypes = expectedArgTypes; } public abstract SExp call(SymbolTable symbolTable, Seq arguments) @@ -33,63 +23,48 @@ public abstract class SpecialFormEntry extends FormEntry { @Override public String display(String offset) { - return offset + "Special Form Entry: " + name.toString(); + return offset + "Special Form Entry: " + symbol.toString(); } @Override public String toString() { - return ""; + return ""; } - protected void checkArguments(Seq arguments) throws LispException { + static final Symbol LTE = new Symbol("<="); + static final Symbol LT = new Symbol("<"); + static final Symbol NUMEQ = new Symbol("="); + static final Symbol NUMNOTEQ = new Symbol("/="); + static final Symbol GT = new Symbol(">"); + static final Symbol GTE = new Symbol(">="); + static final Symbol DIV = new Symbol("/"); + static final Symbol DIF = new Symbol("-"); + static final Symbol MUL = new Symbol("*"); + static final Symbol SUM = new Symbol("+"); + static final Symbol CAR = new Symbol("CAR"); + static final Symbol CDR = new Symbol("CDR"); + static final Symbol CONS = new Symbol("CONS"); + static final Symbol DEFUN = new Symbol("DEFUN"); + static final Symbol DEFPARAMETER = new Symbol("DEFPARAMETER"); + static final Symbol DEFVAR = new Symbol("DEFVAR"); + static final Symbol ENABLEDEBUGAST = new Symbol("ENABLE-DEBUG-AST"); + static final Symbol FUNCTION = new Symbol("FUNCTION"); + static final Symbol FUNCALL = new Symbol("FUNCALL"); + static final Symbol GETF = new Symbol("GETF"); + static final Symbol HELP = new Symbol("HELP"); + static final Symbol IF = new Symbol("IF"); + static final Symbol LAMBDA = new Symbol("LAMBDA"); + static final Symbol LET = new Symbol("LET"); + static final Symbol LET_STAR = new Symbol("LET*"); + static final Symbol LIST = new Symbol("LIST"); + static final Symbol MOD = new Symbol("MOD"); + static final Symbol QUOTE = new Symbol("QUOTE"); + static final Symbol PROGN = new Symbol("PROGN"); + static final Symbol REM = new Symbol("REM"); + static final Symbol SETQ = new Symbol("SETQ"); + static final Symbol TRACE = new Symbol("TRACE"); + static final Symbol QUIT = new Symbol("QUIT"); - boolean argsUnbounded = expectedArgumentCount < 0; - int expectedArgs = Math.abs(expectedArgumentCount); - int actualArgs; - - // first case: expect 0 arguments - if (expectedArgs == 0) { - if (arguments != null) - throw new InvalidArgumentQuantityException(0, arguments.length()); - return; - } - - // expect at least one arg, err if none given - if (arguments == null) - throw new InvalidArgumentQuantityException(expectedArgs, 0); - - actualArgs = arguments.length(); - - // there should be at least as many actual argument as expected - if ( actualArgs < expectedArgs || - (actualArgs > expectedArgs && !argsUnbounded)) { - if (argsUnbounded) - throw new InvalidArgumentQuantityException("expected at least " - + expectedArgs + " arguments"); - else - throw new InvalidArgumentQuantityException(expectedArgs, actualArgs); - } - - assert (expectedArgumentTypes != null && - expectedArgumentTypes.length > 0); - - // at this point, we know that the amount of arguments is valid - for (int i = 0; arguments != null; ++i) { - Class expectedType = - (i >= expectedArgumentTypes.length ? - expectedArgumentTypes[expectedArgumentTypes.length - 1] : - expectedArgumentTypes[i]); - - // check the type of the argument - if (!expectedType.isAssignableFrom(arguments.car.getClass())) - throw new TypeException(arguments.car, expectedType); - - // next argument - arguments = arguments.cdr; - } - - return; // no error, good times - } // ------------------------ // SPECIAL FORMS DEFINITION // ------------------------ @@ -107,15 +82,13 @@ public abstract class SpecialFormEntry extends FormEntry { // --- final SpecialFormEntry LTE = new SpecialFormEntry( - new Symbol("<="), - environment, + SpecialFormEntry.LTE, environment, new FormHelpTopic("<=", "Less than or equal to", "(<= *) => ", "The value of <= is true if the numbers are in monotonically " + "nondecreasing order; otherwise it is false.", "number", "a real", - "result", "a boolean"), - -2, Num.class) + "result", "a boolean")) { public SExp call(SymbolTable symbolTable, Seq arguments) throws LispException { @@ -123,17 +96,22 @@ public abstract class SpecialFormEntry extends FormEntry { Num current; Num next; - checkArguments(arguments); + // check that there is at least one argument + if (arguments == null) + throw new InvalidArgumentQuantityException(toString(), + "at least one argument required."); // get first number - current = (Num) arguments.car; + current = TypeUtil.attemptCast(Num.class, + arguments.car.eval(symbolTable)); // advance to next argument arguments = arguments.cdr; while(arguments != null) { // get next number - next = (Num) arguments.car; + next = TypeUtil.attemptCast(Num.class, + arguments.car.eval(symbolTable)); // current > next, return false if (current.compareTo(next) > 0) return SExp.NIL; @@ -155,15 +133,13 @@ public abstract class SpecialFormEntry extends FormEntry { // -- final SpecialFormEntry LT = new SpecialFormEntry( - new Symbol("<"), - environment, + SpecialFormEntry.LT, environment, new FormHelpTopic("<", "Less than", "(< *) => ", "The value of < is true if the numbers are in monotonically " + "increasing order; otherwise it is false.", "number", "a real", - "result", "a boolean"), - -2, Num.class) + "result", "a boolean")) { public SExp call(SymbolTable symbolTable, Seq arguments) throws LispException { @@ -171,17 +147,21 @@ public abstract class SpecialFormEntry extends FormEntry { Num current; Num next; - checkArguments(arguments); + if (arguments == null) + throw new InvalidArgumentQuantityException(toString(), + "at least one argument is required."); // get first number - current = (Num) arguments.car; + current = TypeUtil.attemptCast(Num.class, + arguments.car.eval(symbolTable)); // advance to next argument arguments = arguments.cdr; while(arguments != null) { // get next number - next = (Num) arguments.car; + next = TypeUtil.attemptCast(Num.class, + arguments.car.eval(symbolTable)); // current >= next, return false if (current.compareTo(next) >= 0) return SExp.NIL; @@ -203,14 +183,12 @@ public abstract class SpecialFormEntry extends FormEntry { // --------- final SpecialFormEntry NUMEQ = new SpecialFormEntry( - new Symbol("="), - environment, + SpecialFormEntry.NUMEQ, environment, new FormHelpTopic("=", "Equal to", "(= *) => ", "The value of = is true if all numbers are the same in value.", "number", "a number", - "result", "a boolean"), - -2, Num.class) + "result", "a boolean")) { public SExp call(SymbolTable symbolTable, Seq arguments) throws LispException { @@ -218,17 +196,21 @@ public abstract class SpecialFormEntry extends FormEntry { Num current; Num next; - checkArguments(arguments); + if (arguments == null) + throw new InvalidArgumentQuantityException(toString(), + "at least one argument is required."); // get first number - current = (Num) arguments.car; + current = TypeUtil.attemptCast(Num.class, + arguments.car.eval(symbolTable)); // advance to next argument arguments = arguments.cdr; while(arguments != null) { // get next number - next = (Num) arguments.car; + next = TypeUtil.attemptCast(Num.class, + arguments.car.eval(symbolTable)); // current != next, return false if (current.compareTo(next) != 0) return SExp.NIL; @@ -250,14 +232,12 @@ public abstract class SpecialFormEntry extends FormEntry { // ------------- final SpecialFormEntry NUMNOTEQ = new SpecialFormEntry( - new Symbol("/="), - environment, + SpecialFormEntry.NUMNOTEQ, environment, new FormHelpTopic("/=", "Not equal to", "(/= *) => ", "The value of /= is true if no two numbers are the same in value.", "number", "a number", - "result", "a boolean"), - -2, Num.class) + "result", "a boolean")) { public SExp call(SymbolTable symbolTable, Seq arguments) throws LispException { @@ -265,17 +245,21 @@ public abstract class SpecialFormEntry extends FormEntry { Num current; Num next; - checkArguments(arguments); + if (arguments == null) + throw new InvalidArgumentQuantityException(toString(), + "at least one argument is required."); // get first number - current = (Num) arguments.car; + current = TypeUtil.attemptCast(Num.class, + arguments.car.eval(symbolTable)); // advance to next argument arguments = arguments.cdr; while(arguments != null) { // get next number - next = (Num) arguments.car; + next = TypeUtil.attemptCast(Num.class, + arguments.car.eval(symbolTable)); // current == next, return false if (current.compareTo(next) == 0) return SExp.NIL; @@ -297,15 +281,13 @@ public abstract class SpecialFormEntry extends FormEntry { // -- final SpecialFormEntry GT = new SpecialFormEntry( - new Symbol(">"), - environment, + SpecialFormEntry.GT, environment, new FormHelpTopic(">", "Greater than", "(> *) => ", "The value of > is true if the numbers are in monotonically " + "decreasing order; otherwise it is false.", "number", "a number", - "result", "a boolean"), - -2, Num.class) + "result", "a boolean")) { public SExp call(SymbolTable symbolTable, Seq arguments) throws LispException { @@ -313,17 +295,21 @@ public abstract class SpecialFormEntry extends FormEntry { Num current; Num next; - checkArguments(arguments); + if (arguments == null) + throw new InvalidArgumentQuantityException(toString(), + "at least one argument is required."); // get first number - current = (Num) arguments.car; + current = TypeUtil.attemptCast(Num.class, + arguments.car.eval(symbolTable)); // advance to next argument arguments = arguments.cdr; while(arguments != null) { // get next number - next = (Num) arguments.car; + next = TypeUtil.attemptCast(Num.class, + arguments.car.eval(symbolTable)); // current <= next, return false if (current.compareTo(next) <= 0) return SExp.NIL; @@ -345,15 +331,13 @@ public abstract class SpecialFormEntry extends FormEntry { // --- final SpecialFormEntry GTE = new SpecialFormEntry( - new Symbol(">="), - environment, + SpecialFormEntry.GTE, environment, new FormHelpTopic(">=", "Greater than or equal to", "(>= *) => ", "The value of > is true if the numbers are in monotonically " + "non-increasing order; otherwise it is false.", "number", "a number", - "result", "a boolean"), - -2, Num.class) + "result", "a boolean")) { public SExp call(SymbolTable symbolTable, Seq arguments) throws LispException { @@ -361,17 +345,21 @@ public abstract class SpecialFormEntry extends FormEntry { Num current; Num next; - checkArguments(arguments); + if (arguments == null) + throw new InvalidArgumentQuantityException(toString(), + "at least one argument is required."); // get first number - current = (Num) arguments.car; + current = TypeUtil.attemptCast(Num.class, + arguments.car.eval(symbolTable)); // advance to next argument arguments = arguments.cdr; while(arguments != null) { // get next number - next = (Num) arguments.car; + next = TypeUtil.attemptCast(Num.class, + arguments.car.eval(symbolTable)); // current < next, return false if (current.compareTo(next) < 0) return SExp.NIL; @@ -393,8 +381,7 @@ public abstract class SpecialFormEntry extends FormEntry { // --- final SpecialFormEntry DIV = new SpecialFormEntry( - new Symbol("/"), - environment, + SpecialFormEntry.DIV, environment, new FormHelpTopic("/", "Divide several expressions.", "(- divisor) | (- dividend [... ])", "Perform division. If there is only one argument passed " @@ -407,8 +394,7 @@ public abstract class SpecialFormEntry extends FormEntry { + "the number which is diveded.", "divisor_1 ... divisor_n", "Divisors are the numbers dividing " + "the dividend and may be any expression that evaluates " - + "to a number."), - -1, Num.class) + + "to a number.")) { public SExp call(SymbolTable symbolTable, Seq arguments) throws LispException { @@ -416,10 +402,13 @@ public abstract class SpecialFormEntry extends FormEntry { Num dividend = new Num("1"); Num firstArg; - checkArguments(arguments); + if (arguments == null) + throw new InvalidArgumentQuantityException(toString(), + "at least one argument is required."); // case: only one argument: 1 / arg - firstArg = (Num) arguments.car.eval(symbolTable); + firstArg = TypeUtil.attemptCast(Num.class, + arguments.car.eval(symbolTable)); dividend = dividend.divideBy(firstArg); @@ -431,8 +420,9 @@ public abstract class SpecialFormEntry extends FormEntry { // variable number of arguments [0..inf) while (arguments != null) { - dividend = dividend.divideBy( - (Num) arguments.car.eval(symbolTable)); + dividend = dividend.divideBy(TypeUtil.attemptCast( + Num.class, + arguments.car.eval(symbolTable))); arguments = arguments.cdr; } @@ -445,8 +435,7 @@ public abstract class SpecialFormEntry extends FormEntry { // --- final SpecialFormEntry DIF = new SpecialFormEntry( - new Symbol("-"), - environment, + SpecialFormEntry.DIF, environment, new FormHelpTopic("-", "Subtract several expressions.", "(- subtrahend) | (- [... ])", "Perform a subtraction. If there is only one argument passed " @@ -459,19 +448,21 @@ public abstract class SpecialFormEntry extends FormEntry { + "the number from which the others are subtracted.", "subtrahend_1 ... subtrahend_n", "Subtrahends are numbers " + "subtracted from the minuend and may be any expression " - + "that evaluates to a number."), - -1, Num.class) + + "that evaluates to a number.")) { public SExp call(SymbolTable symbolTable, Seq arguments) throws LispException { Num difference = new Num("0"); - checkArguments(arguments); + if (arguments == null) + throw new InvalidArgumentQuantityException(toString(), + "at least one argument is required."); // case: only one argument: 0 - arg - difference = difference.subtract( - (Num) arguments.car.eval(symbolTable)); + difference = difference.subtract(TypeUtil.attemptCast( + Num.class, + arguments.car.eval(symbolTable))); arguments = arguments.cdr; if (arguments == null) return difference; @@ -481,8 +472,9 @@ public abstract class SpecialFormEntry extends FormEntry { // variable number of arguments [0..inf) while (arguments != null) { - difference = difference.subtract( - (Num) arguments.car.eval(symbolTable)); + difference = difference.subtract(TypeUtil.attemptCast( + Num.class, + arguments.car.eval(symbolTable))); arguments = arguments.cdr; } @@ -495,8 +487,7 @@ public abstract class SpecialFormEntry extends FormEntry { // --- final SpecialFormEntry MUL = new SpecialFormEntry( - new Symbol("*"), - environment, + SpecialFormEntry.MUL, environment, new FormHelpTopic("*", "Multiply several expressions.", "(+ [ ... ])", "Compute the product of the zero or more expressions passed" @@ -504,8 +495,7 @@ public abstract class SpecialFormEntry extends FormEntry { + "before being bound to function parameters. The" + " expressions passed to multiply must evaluate to numbers.", "multiplicand_1 ... multiplicand_n", "Multiplicands may be " - + "any expression that evaluates to a number."), - -1, Num.class) // not technically correct, * accepts 0 arguments + + "any expression that evaluates to a number.")) { public SExp call(SymbolTable symbolTable, Seq arguments) throws LispException { @@ -514,12 +504,8 @@ public abstract class SpecialFormEntry extends FormEntry { // variable number of arguments [0..inf) while (arguments != null) { - try { - product = product.multiply( - (Num) arguments.car.eval(symbolTable)); - } catch (ClassCastException cce) { - throw new TypeException(arguments.car, Num.class); - } + product = product.multiply(TypeUtil.attemptCast( + Num.class, arguments.car.eval(symbolTable))); arguments = arguments.cdr; } @@ -532,8 +518,7 @@ public abstract class SpecialFormEntry extends FormEntry { // --- final SpecialFormEntry SUM = new SpecialFormEntry( - new Symbol("+"), - environment, + SpecialFormEntry.SUM, environment, new FormHelpTopic("+", "Sum several expressions.", "(+ [ ... ])", "Compute the summation of the zero or more expressions passed" @@ -541,8 +526,7 @@ public abstract class SpecialFormEntry extends FormEntry { + "before being bound to function parameters. The" + " expressions passed to sum must evaluate to numbers.", "addend_1 ... addend_n", "Addends may be any expression that " - + "evaluates to a number."), - -1, Num.class) // not technically correct, + accepts 0 arguments + + "evaluates to a number.")) { public SExp call(SymbolTable symbolTable, Seq arguments) throws LispException { @@ -551,11 +535,8 @@ public abstract class SpecialFormEntry extends FormEntry { // variable number of arguments [0..inf) while (arguments != null) { - try { - sum = sum.add((Num) arguments.car.eval(symbolTable)); - } catch (ClassCastException cce) { - throw new TypeException(arguments.car, Num.class); - } + sum = sum.add(TypeUtil.attemptCast( + Num.class,arguments.car.eval(symbolTable))); arguments = arguments.cdr; } @@ -569,21 +550,25 @@ public abstract class SpecialFormEntry extends FormEntry { // TODO + // --- + // CDR + // --- + + // TODO + // ---- // CONS // ---- final SpecialFormEntry CONS = new SpecialFormEntry( - new Symbol("CONS"), - environment, + SpecialFormEntry.CONS, environment, new FormHelpTopic("CONS", "create a cons", "(cons ) => ", "Creates a fresh cons, the car of which is object-1 and the " + "cdr of which is object-2.", "object-1", "an object", "object-2", "an object", - "cons", "a cons"), - 2, SExp.class) + "cons", "a cons")) { public SExp call(SymbolTable symbolTable, Seq arguments) throws LispException { @@ -591,7 +576,8 @@ public abstract class SpecialFormEntry extends FormEntry { SExp object1, object2; Cons cons; - checkArguments(arguments); + if (arguments == null || arguments.cdr == null) + throw new InvalidArgumentQuantityException(toString(), 2); // get the two objects object1 = arguments.car.eval(symbolTable); @@ -609,17 +595,16 @@ public abstract class SpecialFormEntry extends FormEntry { // ----- final SpecialFormEntry DEFUN = new SpecialFormEntry( - new Symbol("DEFUN"), - environment, + SpecialFormEntry.DEFUN, environment, new FormHelpTopic("DEFUN", "Define a (global) function.", "(defun () )", "Create a function binding. This will replace any existing binding.", - "name", "the symbol to bind to the function definition.", + "name", "the symbol to bind to the function definition, " + + "not evaluated.", "param-list", "a list of symbols that will be bound in the " + "function scope to the arguments passed to the function.", "func-body", "an sexpression evaluated when the function is " - + "called."), - 3, Symbol.class, List.class, SExp.class) + + "called.")) { public SExp call(SymbolTable symbolTable, Seq arguments) throws LispException { @@ -628,25 +613,32 @@ public abstract class SpecialFormEntry extends FormEntry { ArrayList parameters = new ArrayList(); SExp body; - checkArguments(arguments); + // check for the correct number of arguments + if (arguments == null) + throw new InvalidArgumentQuantityException( + toString(), 3, 0); + if (arguments.length() != 3) + throw new InvalidArgumentQuantityException( + toString(), 3, arguments.length()); // TODO: check to see if a function for this symbol exists // and warn if so // first argument: Symbol for function name - functionName = (Symbol) arguments.car; + functionName = TypeUtil.attemptCast( + Symbol.class, arguments.car); // second argument, parameter list arguments = arguments.cdr; assert (arguments != null); // read parameters - Seq paramSeq = ((List) arguments.car).seq; - while (paramSeq != null) { - if (!(paramSeq.car instanceof Symbol)) - throw new TypeException(paramSeq.car, Symbol.class); + Seq paramSeq = TypeUtil.attemptCast( + List.class, arguments.car).seq; - parameters.add((Symbol) paramSeq.car); + while (paramSeq != null) { + parameters.add(TypeUtil.attemptCast( + Symbol.class, paramSeq.car)); paramSeq = paramSeq.cdr; } @@ -671,9 +663,8 @@ public abstract class SpecialFormEntry extends FormEntry { // DEFPARAMETER // ------------ - final SpecialFormEntry DEFPARAMETER = new SpecialFormEntry( - new Symbol("DEFPARAMETER"), - environment, + final SpecialFormEntry DEFPARAM = new SpecialFormEntry( + SpecialFormEntry.DEFPARAMETER, environment, new FormHelpTopic("DEFPARAMETER", "define a dynamic variable", "(defparameter [ []]) => ", "defparameter establishes name as a dynamic variable. " @@ -684,8 +675,7 @@ public abstract class SpecialFormEntry extends FormEntry { + "already bound.)", "name", "a symbol; not evaluated. ", "initial-value", "a form, always evaluated", - "documentation", "a string; not evaluated."), - -1, Symbol.class, SExp.class, Str.class) + "documentation", "a string; not evaluated.")) { public SExp call(SymbolTable symbolTable, Seq arguments) throws LispException { @@ -694,10 +684,12 @@ public abstract class SpecialFormEntry extends FormEntry { SExp initValue = null; HelpTopic helpinfo = null; - checkArguments(arguments); + if (arguments == null || arguments.length() < 1) + throw new InvalidArgumentQuantityException( + toString(), 1, 0); // first argument: variable name - name = (Symbol) arguments.car; + name = TypeUtil.attemptCast(Symbol.class, arguments.car); // second argument: initial value arguments = arguments.cdr; @@ -708,11 +700,12 @@ public abstract class SpecialFormEntry extends FormEntry { arguments = arguments.cdr; if (arguments != null) helpinfo = new HelpTopic(name.toString(), "variable", - ((Str) arguments.car).value); + TypeUtil.attemptCast( + Str.class, arguments.car).value); } - symbolTable.bind(name, - new VariableEntry(initValue, false, helpinfo)); + environment.globalSymbolTable.bind(name, + new VariableEntry(name, initValue, false, helpinfo)); return name; @@ -724,8 +717,7 @@ public abstract class SpecialFormEntry extends FormEntry { // ------ final SpecialFormEntry DEFVAR = new SpecialFormEntry( - new Symbol("DEFVAR"), - environment, + SpecialFormEntry.DEFVAR, environment, new FormHelpTopic("DEFVAR", "define a variable", "(defvar [ []]) => ", "defvar establishes name as a dynamic variable and assigns " @@ -736,8 +728,7 @@ public abstract class SpecialFormEntry extends FormEntry { "name", "a symbol; not evaluated. ", "initial-value", "a form, evaluated only if name is not " + "already bound.", - "documentation", "a string; not evaluated."), - -1, Symbol.class, SExp.class, Str.class) + "documentation", "a string; not evaluated.")) { public SExp call(SymbolTable symbolTable, Seq arguments) throws LispException { @@ -746,17 +737,19 @@ public abstract class SpecialFormEntry extends FormEntry { SExp initValue = null; HelpTopic helpinfo = null; - checkArguments(arguments); + if (arguments == null || arguments.length() < 1) + throw new InvalidArgumentQuantityException( + toString(), 1, 0); // first argument: variable name - name = (Symbol) arguments.car; + name = TypeUtil.attemptCast(Symbol.class, arguments.car); // if this variable is already defined, return without // setting it if (symbolTable.lookupVariable(name) != null) return arguments.car; - return DEFPARAMETER.call(symbolTable, arguments); + return DEFPARAM.call(symbolTable, arguments); } }; @@ -765,8 +758,7 @@ public abstract class SpecialFormEntry extends FormEntry { // ---------------- final SpecialFormEntry ENABLEDEBUGAST = new SpecialFormEntry( - new Symbol("ENABLE-DEBUG-AST"), - environment, + SpecialFormEntry.ENABLEDEBUGAST, environment, new FormHelpTopic("ENABLE-DEBUG-AST", "Enable debug information: abstract syntax tree.", "(enable-debug-ast [])", @@ -774,7 +766,7 @@ public abstract class SpecialFormEntry extends FormEntry { + "representation of the abstract syntax tree generated " + "by the parser for each sexpression it parses.", "enable", "NIL = disabled, anything else = enabled. No " - + "argument = enabled."), -1) + + "argument = enabled.")) { public SExp call(SymbolTable symbolTable, Seq arguments) throws LispException { @@ -797,24 +789,25 @@ public abstract class SpecialFormEntry extends FormEntry { // -------- final SpecialFormEntry FUNCTION = new SpecialFormEntry( - new Symbol("FUNCTION"), - environment, + SpecialFormEntry.FUNCTION, environment, new FormHelpTopic("FUNCTION", "retrieve a function", "(function ) => ", "function returns the function specified by the symbol passed " + " as an argument. #'funcname is equivalent to (function " + " funcname).", "func-name", "a symbol naming a function", - "function", "a function"), - 1, Symbol.class) + "function", "a function")) { public SExp call(SymbolTable symbolTable, Seq arguments) throws LispException { FormEntry fe = null; - checkArguments(arguments); + if (arguments == null) + throw new InvalidArgumentQuantityException( + toString(), 1, 0); - fe = symbolTable.lookupFunction((Symbol) arguments.car); + fe = symbolTable.lookupFunction(TypeUtil.attemptCast( + Symbol.class, arguments.car)); if (fe == null) throw new UndefinedFunctionException((Symbol) arguments.car); @@ -828,8 +821,7 @@ public abstract class SpecialFormEntry extends FormEntry { // ------- final SpecialFormEntry FUNCALL = new SpecialFormEntry( - new Symbol("FUNCALL"), - environment, + SpecialFormEntry.FUNCALL, environment, new FormHelpTopic("FUNCALL", "make a function call", "(funcall *) => ", "funcall applies function to args. If function is a symbol, " @@ -837,13 +829,14 @@ public abstract class SpecialFormEntry extends FormEntry { + "functional value in the global environment.", "function", "a function designator", "arg", "an object", - "results", "the result of the function call"), - -1, SExp.class) + "results", "the result of the function call")) { public SExp call(SymbolTable symbolTable, Seq arguments) throws LispException { - checkArguments(arguments); + if (arguments == null) + throw new InvalidArgumentQuantityException(toString(), + "at least one argument is required."); // first argument: function designator SExp func = arguments.car.eval(symbolTable); @@ -874,8 +867,7 @@ public abstract class SpecialFormEntry extends FormEntry { // ---- final SpecialFormEntry GETF = new SpecialFormEntry( - new Symbol("GETF"), - environment, + SpecialFormEntry.GETF, environment, new FormHelpTopic("GETF", "", "(getf []) => ", "getf finds a property on the plist whose property indicator " @@ -887,8 +879,7 @@ public abstract class SpecialFormEntry extends FormEntry { "plist", "a property list.", "indicator", "an object", "default", "an object. The default is NIL", - "value", "an object"), - -2, List.class, SExp.class, SExp.class) + "value", "an object")) { public SExp call(SymbolTable symbolTable, Seq arguments) throws LispException { @@ -898,11 +889,13 @@ public abstract class SpecialFormEntry extends FormEntry { SExp indicator; SExp retVal = SExp.NIL; - checkArguments(arguments); + if (arguments == null || arguments.length() < 2) + throw new InvalidArgumentQuantityException( + toString(), 2, 0); // first argument: property list plistEval = arguments.car.eval(symbolTable); - plistSeq = ((List) plistEval).seq; + plistSeq = TypeUtil.attemptCast(List.class, plistEval).seq; // second argument: indicator arguments = arguments.cdr; @@ -933,15 +926,13 @@ public abstract class SpecialFormEntry extends FormEntry { // ---- final SpecialFormEntry HELP = new SpecialFormEntry( - new Symbol("HELP"), - environment, + SpecialFormEntry.HELP, environment, new FormHelpTopic("HELP", "Display online help information for a topic.", "(help [*])", null, "topic", - "either a string representing the topic to lookup or a symbol"), - -1, SExp.class) // technically can accept 0 arguments + "either a string representing the topic to lookup or a symbol")) { public SExp call(SymbolTable symbolTable, Seq arguments) throws LispException { @@ -951,7 +942,7 @@ public abstract class SpecialFormEntry extends FormEntry { // no arguments: print help for HELP if (arguments == null) return this.call(symbolTable, - new Seq(new Symbol("HELP"), null)); + new Seq(SpecialFormEntry.HELP, null)); while (arguments != null) { // try to find the topic or function help @@ -986,8 +977,7 @@ public abstract class SpecialFormEntry extends FormEntry { // -- final SpecialFormEntry IF = new SpecialFormEntry( - new Symbol("IF"), - environment, + SpecialFormEntry.IF, environment, new FormHelpTopic("IF", "conditional code execution", "(if []) => result*", @@ -1001,13 +991,13 @@ public abstract class SpecialFormEntry extends FormEntry { "else-form", "a form. The default is nil. ", "results", "if the test-form yielded true, the values " + "returned by the then-form; otherwise, the values " - + "returned by the else-form."), - -2, SExp.class) + + "returned by the else-form.")) { public SExp call(SymbolTable symbolTable, Seq arguments) throws LispException { - checkArguments(arguments); + if (arguments == null || arguments.length() < 2) + throw new InvalidArgumentQuantityException(toString(), 2); // evaluate test form SExp testResult = arguments.car.eval(symbolTable); @@ -1016,11 +1006,12 @@ public abstract class SpecialFormEntry extends FormEntry { arguments = arguments.cdr; // if false, advance to else-form - if (testResult == null || testResult == SExp.NIL) arguments = arguments.cdr; + if (testResult == null || testResult == SExp.NIL) + arguments = arguments.cdr; if (arguments == null) return SExp.NIL; - return arguments.eval(symbolTable); + return arguments.car.eval(symbolTable); } }; @@ -1029,16 +1020,14 @@ public abstract class SpecialFormEntry extends FormEntry { // ------ final SpecialFormEntry LAMBDA = new SpecialFormEntry( - new Symbol("LAMBDA"), - environment, + SpecialFormEntry.LAMBDA, environment, new FormHelpTopic("LAMDA", "create an anonymous function over the current lexical closure", "(lambda
) => ", "", "param-list", "a list of symbols", "form", "a form", - "lambda", "a function"), - 2, List.class, SExp.class) + "lambda", "a function")) { public SExp call(SymbolTable symbolTable, Seq arguments) throws LispException { @@ -1047,15 +1036,14 @@ public abstract class SpecialFormEntry extends FormEntry { SExp body; Seq paramSeq; - checkArguments(arguments); + if (arguments == null || arguments.length() != 2) + throw new InvalidArgumentQuantityException(toString(), 2); // first parameter: parameters to the lambda - paramSeq = ((List) arguments.car).seq; + paramSeq = TypeUtil.attemptCast(List.class, arguments.car).seq; while (paramSeq != null) { - if (!(paramSeq.car instanceof Symbol)) - throw new TypeException(paramSeq.car, Symbol.class); - - parameters.add((Symbol) paramSeq.car); + parameters.add(TypeUtil.attemptCast( + Symbol.class, paramSeq.car)); paramSeq = paramSeq.cdr; } @@ -1075,8 +1063,7 @@ public abstract class SpecialFormEntry extends FormEntry { // --- final SpecialFormEntry LET = new SpecialFormEntry( - new Symbol("LET"), - environment, + SpecialFormEntry.LET, environment, new FormHelpTopic("LET", "create new lexical variable bindings", "(let (([ ])*) *) => ", "let creates new variable bindings and executes a series of " @@ -1094,8 +1081,7 @@ public abstract class SpecialFormEntry extends FormEntry { "var", "a symbol", "init-form", "a form", "form", "a form", - "result", "the value returned by the last form"), - -1, List.class, SExp.class) + "result", "the value returned by the last form")) { public SExp call(SymbolTable symbolTable, Seq arguments) throws LispException { @@ -1106,9 +1092,11 @@ public abstract class SpecialFormEntry extends FormEntry { ArrayList values = new ArrayList(); SExp retVal = SExp.NIL; - checkArguments(arguments); + if (arguments == null || arguments.length() < 1) + throw new InvalidArgumentQuantityException(toString(), 1); - letBinding = ((List) arguments.car).seq; + letBinding = TypeUtil.attemptCast( + List.class, arguments.car).seq; while (letBinding != null) { @@ -1118,14 +1106,13 @@ public abstract class SpecialFormEntry extends FormEntry { + letBinding.car.toString()); // get the symbol - Seq binding = ((List) letBinding.car).seq; + Seq binding = TypeUtil.attemptCast( + List.class, letBinding.car).seq; + if (binding == null) throw new LispException(""); // TODO - if (!(binding.car instanceof Symbol)) - throw new TypeException(binding.car, Symbol.class); - - symbols.add((Symbol) binding.car); + symbols.add(TypeUtil.attemptCast(Symbol.class, binding.car)); // get and evaluate the value binding = binding.cdr; @@ -1141,7 +1128,7 @@ public abstract class SpecialFormEntry extends FormEntry { for (int i = 0; i < symbols.size(); ++i) newScope.bind(symbols.get(i), - new VariableEntry(values.get(i))); + new VariableEntry(symbols.get(i), values.get(i))); // evaluate all forms with the new scope arguments = arguments.cdr; @@ -1160,8 +1147,7 @@ public abstract class SpecialFormEntry extends FormEntry { // ---- final SpecialFormEntry LET_STAR = new SpecialFormEntry( - new Symbol("LET*"), - environment, + SpecialFormEntry.LET_STAR, environment, new FormHelpTopic("LET*", "create new lexical variable bindings", "(let (([ ])*) *) => ", "let* creates new variable bindings and executes a series of " @@ -1180,8 +1166,7 @@ public abstract class SpecialFormEntry extends FormEntry { "var", "a symbol", "init-form", "a form", "form", "a form", - "result", "the value returned by the last form"), - -1, List.class, SExp.class) + "result", "the value returned by the last form")) { public SExp call(SymbolTable symbolTable, Seq arguments) throws LispException { @@ -1192,9 +1177,11 @@ public abstract class SpecialFormEntry extends FormEntry { SExp initvalue; SExp retVal = SExp.NIL; - checkArguments(arguments); + if (arguments == null || arguments.length() < 1) + throw new InvalidArgumentQuantityException(toString(), 1); - letBinding = ((List) arguments.car).seq; + letBinding = TypeUtil.attemptCast( + List.class, arguments.car).seq; // perform actual binding in a new scope newScope = new SymbolTable(symbolTable); @@ -1207,14 +1194,13 @@ public abstract class SpecialFormEntry extends FormEntry { + letBinding.car.toString()); // get the symbol - Seq binding = ((List) letBinding.car).seq; + Seq binding = TypeUtil.attemptCast( + List.class, letBinding.car).seq; + if (binding == null) throw new LispException(""); // TODO - if (!(binding.car instanceof Symbol)) - throw new TypeException(binding.car, Symbol.class); - - var = (Symbol) binding.car; + var = TypeUtil.attemptCast(Symbol.class, binding.car); // get and evaluate the value // include already bound variables from the let* in the @@ -1223,7 +1209,7 @@ public abstract class SpecialFormEntry extends FormEntry { if (binding == null) initvalue = SExp.NIL; else initvalue = binding.car.eval(newScope); - newScope.bind(var, new VariableEntry(initvalue)); + newScope.bind(var, new VariableEntry(var, initvalue)); // next let binding letBinding = letBinding.cdr; @@ -1246,14 +1232,12 @@ public abstract class SpecialFormEntry extends FormEntry { // ---- final SpecialFormEntry LIST = new SpecialFormEntry( - new Symbol("LIST"), - environment, + SpecialFormEntry.LIST, environment, new FormHelpTopic("LIST", "create a list", "(list *) => list", "list returns a list containing the supplied objects.", "object", "an object.", - "list", "a list."), - -1, SExp.class) // actually accepts 0 or more args, not 1 or more + "list", "a list.")) { public SExp call(SymbolTable symbolTable, Seq arguments) throws LispException { @@ -1278,25 +1262,60 @@ public abstract class SpecialFormEntry extends FormEntry { } }; + // --- + // MOD + // --- + + // TODO: this does not follow the Common Lisp standard (which requires + // FLOOR, TRUNCATE, and others to be defined). Fix in future when the + // required functionsa re defined. + final SpecialFormEntry MOD = new SpecialFormEntry( + SpecialFormEntry.MOD, environment, + new FormHelpTopic("MOD", "modulus", + "(mod ) => ", + "mod performs the operation floor on number and divisor and " + + "returns the remainder of the floor operation. mod is " + + "the modulus function when number and divisor are " + + "integers. ", + "number", "a real.", + "divisor", "a real.", + "result", "a real.")) + { + public SExp call(SymbolTable symbolTable, Seq arguments) + throws LispException { + + Num dividend, divisor; + + if (arguments == null || arguments.length() != 2) + throw new InvalidArgumentQuantityException(toString(), 2); + + dividend = TypeUtil.attemptCast(Num.class, + arguments.car.eval(symbolTable)); + divisor = TypeUtil.attemptCast(Num.class, + arguments.cdr.car); + + return dividend.remainder(divisor); + } + }; + // ----- // QUOTE // ----- final SpecialFormEntry QUOTE = new SpecialFormEntry( - new Symbol("QUOTE"), - environment, + SpecialFormEntry.QUOTE, environment, new FormHelpTopic("QUOTE", "Return objects unevaluated.", "(quote ) => ", "The quote special operator just returns object. The " + "consequences are undefined if literal objects (including " + "quoted objects) are destructively modified. ", - "object", "an object; not evaluated."), - 1, SExp.class) + "object", "an object; not evaluated.")) { public SExp call(SymbolTable symbolTable, Seq arguments) throws LispException { if (arguments == null) - throw new InvalidArgumentQuantityException(1, 0); + throw new InvalidArgumentQuantityException( + toString(), 1, 0); return arguments.car; } @@ -1307,8 +1326,7 @@ public abstract class SpecialFormEntry extends FormEntry { // ----- final SpecialFormEntry PROGN = new SpecialFormEntry( - new Symbol("PROGN"), - environment, + SpecialFormEntry.PROGN, environment, new FormHelpTopic("PROGN", "evaluate forms in the order they are given", "(progn *) => *", @@ -1318,8 +1336,7 @@ public abstract class SpecialFormEntry extends FormEntry { + "within that progn are considered by the compiler to be " + "top level forms. ", "form", "a list of forms", - "result", "the value of the last form"), - -1, SExp.class) // actually accepts 0 or more args + "result", "the value of the last form")) { public SExp call(SymbolTable symbolTable, Seq arguments) throws LispException { @@ -1341,8 +1358,7 @@ public abstract class SpecialFormEntry extends FormEntry { // ---- final SpecialFormEntry SETQ = new SpecialFormEntry( - new Symbol("SETQ"), - environment, + SpecialFormEntry.SETQ, environment, new FormHelpTopic("SETQ", "Assigns values to variables.", "(setq [ ]*)", "Assigns values to variables. (setq var1 form1 var2 " @@ -1356,8 +1372,7 @@ public abstract class SpecialFormEntry extends FormEntry { + "(not setq) had been used. ", "name", "a symbol naming a variable other than a constant variable", - "form", "a form"), - -1, SExp.class) //actually accepts an even number of args + "form", "a form")) { public SExp call(SymbolTable symbolTable, Seq arguments) throws LispException { @@ -1365,19 +1380,17 @@ public abstract class SpecialFormEntry extends FormEntry { Symbol variableName; SExp variableValue = SExp.NIL; - if (arguments.length() % 2 != 0) - throw new InvalidArgumentQuantityException( - "there must be an even number of arguments " + if (arguments == null || arguments.length() % 2 != 0) + throw new InvalidArgumentQuantityException(toString(), + "there must be a positive, even number of arguments " + "(name, value pairs)"); // treat each pair while (arguments != null) { // first argument of pair: Symbol for variable name - if (!(arguments.car instanceof Symbol)) - throw new TypeException(arguments.car, Symbol.class); - - variableName = (Symbol) arguments.car; + variableName = TypeUtil.attemptCast( + Symbol.class, arguments.car); // TODO: check for redifinition of variable and warn or err // if the variable is a constant @@ -1389,7 +1402,7 @@ public abstract class SpecialFormEntry extends FormEntry { variableValue = arguments.car.eval(symbolTable); symbolTable.rebind(variableName, - new VariableEntry(variableValue)); + new VariableEntry(variableName, variableValue)); arguments = arguments.cdr; } @@ -1403,14 +1416,12 @@ public abstract class SpecialFormEntry extends FormEntry { // ----- final SpecialFormEntry TRACE = new SpecialFormEntry( - new Symbol("TRACE"), - environment, + SpecialFormEntry.TRACE, environment, new FormHelpTopic("TRACE", "enable trace information for a function", "(trace )", "Turn on trace information for a function.", - "funcname", "the name of the function to trace"), - -1, Symbol.class) //actually accepts 0 or 1 arg + "funcname", "the name of the function to trace")) { public SExp call(SymbolTable symbolTable, Seq arguments) throws LispException { @@ -1418,15 +1429,14 @@ public abstract class SpecialFormEntry extends FormEntry { if (arguments == null || arguments.car == null) return SExp.NIL; - if (!(arguments.car instanceof Symbol)) - throw new LispException(arguments.car.toString() - + " is not a valid function name."); + // only parameter: symbol to trace + Symbol symbol = TypeUtil.attemptCast( + Symbol.class, arguments.car); - FormEntry fe = symbolTable.lookupFunction((Symbol) arguments.car); + FormEntry fe = symbolTable.lookupFunction(symbol); if (fe == null) - throw new UndefinedFunctionException( - ((Symbol) arguments.car)); + throw new UndefinedFunctionException(symbol); if (fe instanceof FunctionEntry) ((FunctionEntry) fe).enableTrace(true); @@ -1442,49 +1452,47 @@ public abstract class SpecialFormEntry extends FormEntry { // ---- final SpecialFormEntry QUIT = new SpecialFormEntry( - new Symbol("QUIT"), - environment, + SpecialFormEntry.QUIT, environment, new FormHelpTopic("QUIT", "Exit the interpreter.", "(quit)", - ""), - 0, SExp.class) + "")) { public SExp call(SymbolTable symbolTable, Seq arguments) throws LispException { - checkArguments(arguments); environment.signalStop(); return SExp.NIL; } }; - environment.globalSymbolTable.bind(LTE.name, LTE); - environment.globalSymbolTable.bind(LT.name, LT); - environment.globalSymbolTable.bind(NUMEQ.name, NUMEQ); - environment.globalSymbolTable.bind(NUMNOTEQ.name, NUMNOTEQ); - environment.globalSymbolTable.bind(GT.name, GT); - environment.globalSymbolTable.bind(GTE.name, GTE); - environment.globalSymbolTable.bind(DIF.name, DIF); - environment.globalSymbolTable.bind(DIV.name, DIV); - environment.globalSymbolTable.bind(MUL.name, MUL); - environment.globalSymbolTable.bind(SUM.name, SUM); - environment.globalSymbolTable.bind(CONS.name, CONS); - environment.globalSymbolTable.bind(DEFUN.name, DEFUN); - environment.globalSymbolTable.bind(DEFPARAMETER.name, DEFPARAMETER); - environment.globalSymbolTable.bind(DEFVAR.name, DEFVAR); - environment.globalSymbolTable.bind(ENABLEDEBUGAST.name, ENABLEDEBUGAST); - environment.globalSymbolTable.bind(FUNCALL.name, FUNCALL); - environment.globalSymbolTable.bind(FUNCTION.name, FUNCTION); - environment.globalSymbolTable.bind(GETF.name, GETF); - environment.globalSymbolTable.bind(HELP.name, HELP); - environment.globalSymbolTable.bind(IF.name, IF); - environment.globalSymbolTable.bind(LAMBDA.name, LAMBDA); - environment.globalSymbolTable.bind(LET.name, LET); - environment.globalSymbolTable.bind(LET_STAR.name, LET_STAR); - environment.globalSymbolTable.bind(LIST.name, LIST); - environment.globalSymbolTable.bind(QUOTE.name, QUOTE); - environment.globalSymbolTable.bind(PROGN.name, PROGN); - environment.globalSymbolTable.bind(SETQ.name, SETQ); - environment.globalSymbolTable.bind(TRACE.name, TRACE); - environment.globalSymbolTable.bind(QUIT.name, QUIT); + environment.globalSymbolTable.bind(LTE.symbol, LTE); + environment.globalSymbolTable.bind(LT.symbol, LT); + environment.globalSymbolTable.bind(NUMEQ.symbol, NUMEQ); + environment.globalSymbolTable.bind(NUMNOTEQ.symbol, NUMNOTEQ); + environment.globalSymbolTable.bind(GT.symbol, GT); + environment.globalSymbolTable.bind(GTE.symbol, GTE); + environment.globalSymbolTable.bind(DIF.symbol, DIF); + environment.globalSymbolTable.bind(DIV.symbol, DIV); + environment.globalSymbolTable.bind(MUL.symbol, MUL); + environment.globalSymbolTable.bind(SUM.symbol, SUM); + environment.globalSymbolTable.bind(CONS.symbol, CONS); + environment.globalSymbolTable.bind(DEFUN.symbol, DEFUN); + environment.globalSymbolTable.bind(DEFPARAM.symbol, DEFPARAM); + environment.globalSymbolTable.bind(DEFVAR.symbol, DEFVAR); + environment.globalSymbolTable.bind(ENABLEDEBUGAST.symbol, ENABLEDEBUGAST); + environment.globalSymbolTable.bind(FUNCALL.symbol, FUNCALL); + environment.globalSymbolTable.bind(FUNCTION.symbol, FUNCTION); + environment.globalSymbolTable.bind(GETF.symbol, GETF); + environment.globalSymbolTable.bind(HELP.symbol, HELP); + environment.globalSymbolTable.bind(IF.symbol, IF); + environment.globalSymbolTable.bind(LAMBDA.symbol, LAMBDA); + environment.globalSymbolTable.bind(LET.symbol, LET); + environment.globalSymbolTable.bind(LET_STAR.symbol, LET_STAR); + environment.globalSymbolTable.bind(LIST.symbol, LIST); + environment.globalSymbolTable.bind(MOD.symbol, MOD); + environment.globalSymbolTable.bind(QUOTE.symbol, QUOTE); + environment.globalSymbolTable.bind(PROGN.symbol, PROGN); + environment.globalSymbolTable.bind(SETQ.symbol, SETQ); + environment.globalSymbolTable.bind(TRACE.symbol, TRACE); + environment.globalSymbolTable.bind(QUIT.symbol, QUIT); } } diff --git a/src/edu/utexas/cs345/jdblisp/SymbolTable.java b/src/edu/utexas/cs345/jdblisp/SymbolTable.java index ebbebf0..350bc15 100755 --- a/src/edu/utexas/cs345/jdblisp/SymbolTable.java +++ b/src/edu/utexas/cs345/jdblisp/SymbolTable.java @@ -6,23 +6,47 @@ import java.util.Map; /** * SymbolTable * @author Jonathan Bernard (jdbernard@gmail.com) + * A SymbolTable holds the bindings of function definitions and variables for + * a lexical scope. Functions and variables are maintained seperately, so you + * can have a variable and function with the same symbol. */ public class SymbolTable { private Map functions = new HashMap(); - private Map variables = new HashMap(); + private Map variables = + new HashMap(); + private SymbolTable enclosingTable; private boolean locked = false; + /** + * Create a new, unlocked, and empty symbol table. + */ public SymbolTable() { this(null, false); } + /** + * Create a new unlocked SymbolTable enclosed by the given table. + * @param table The outer, enclosing table. + */ public SymbolTable(SymbolTable table) { this(table, false); } + /** + * Create a new SymbolTable enclosed by the given table. + * @param table The outer, enclosing table. + * @param locked If this table is locked. + */ public SymbolTable(SymbolTable table, boolean locked) { this.enclosingTable = table; this.locked = locked; } + /** + * Bind a value to a (potentially new) function definition named by the + * {@link edu.utexas.cs345.jdblisp.Symbol} s. + * @param s The {@link edu.utexas.cs345.jdblisp.Symbol} to bind to. + * @param f The {@link edu.utexas.cs345.jdblisp.FormEntry} representing + * the function definition. + */ public Symbol bind(Symbol s, FormEntry f) { if (functions.get(s) != null) { // TODO: warning: function redefinition @@ -32,6 +56,13 @@ public class SymbolTable { return s; } + /** + * Bind a value to a (potentially new) variable named by the + * {@link edu.utexas.cs345.jdblisp.Symbol} s. + * @param s The {@link edu.utexas.cs345.jdblisp.Symbol} to bind. + * @param c The {@link edu.utexas.cs345.jdblisp.VariableEntry} representing + * the value to bind. + */ public Symbol bind(Symbol s, VariableEntry v) { if (variables.get(s) != null) { // TODO: warning: variable redefinition @@ -42,6 +73,15 @@ public class SymbolTable { return s; } + /** + * Bind a new value to an existing variable named by the + * {@link edu.utexas.cs345.jdblisp.Symbol} s. + * @param s The {@link edu.utexas.cs345.jdblisp.Symbol} to bind. + * @param v The {@link edu.utexas.cs345.jdblisp.VariableEntry} representing + * the new value to bind. + * @throws edu.utexas.cs345.jdblisp.LispException if the variable is not + * defined or if the variable was defined as a constant. + */ public Symbol rebind(Symbol s, VariableEntry v) throws LispException { // look up the variable in the current table @@ -74,6 +114,12 @@ public class SymbolTable { return s; } + /** + * Lookup a function definition in this symbol table. + * @param s The symbol representing the function in question. + * @return The function definiton of the function bound to the Symbol s + * or null if there is no function bound to the symbol. + */ public FormEntry lookupFunction(Symbol s) throws LispException { FormEntry fe = functions.get(s); @@ -87,6 +133,12 @@ public class SymbolTable { return enclosingTable.lookupFunction(s); } + /** + * Lookup a variable definition in this symbol table. + * @param s The symbol representing the variable. + * @return The variable entry for the given symbol or null if there + * is no variable bound to the symbol. + */ public VariableEntry lookupVariable(Symbol s) throws LispException { VariableEntry ve = variables.get(s); @@ -100,4 +152,31 @@ public class SymbolTable { return enclosingTable.lookupVariable(s); } + /** + * DEBUG ONLY: traverses the entire scope history. + */ + public String toString() { + return toString(""); + } + + private String toString(String enclosedTable) { + StringBuilder sb = new StringBuilder(); + sb.append("{").append(enclosedTable).append("} V: "); + + for (VariableEntry ve : variables.values()) + sb.append(ve.symbol.toString()).append("=") + .append(ve.value.toString()).append(" "); + + sb.append("F: "); + + for (FormEntry fe : functions.values()) + sb.append(fe.symbol.toString()).append("=") + .append(fe.toString()).append(" "); + + sb.append("}"); + + if (enclosingTable == null) return sb.toString(); + + return enclosingTable.toString(sb.toString()); + } } diff --git a/src/edu/utexas/cs345/jdblisp/SymbolTableEntry.java b/src/edu/utexas/cs345/jdblisp/SymbolTableEntry.java new file mode 100644 index 0000000..45be40e --- /dev/null +++ b/src/edu/utexas/cs345/jdblisp/SymbolTableEntry.java @@ -0,0 +1,5 @@ +package edu.utexas.cs345.jdblisp; + +public interface SymbolTableEntry { + Symbol symbol(); +} diff --git a/src/edu/utexas/cs345/jdblisp/TypeUtil.java b/src/edu/utexas/cs345/jdblisp/TypeUtil.java new file mode 100644 index 0000000..5a9841e --- /dev/null +++ b/src/edu/utexas/cs345/jdblisp/TypeUtil.java @@ -0,0 +1,24 @@ +package edu.utexas.cs345.jdblisp; + +/** + * TypeUtil + * @author Jonathan Bernard (jdbernard@gmail.com) + */ +public class TypeUtil { + + /** + * Attempt to perform a LISP type conversion and create an appropriate + * error if the type conversion fails. + * @param desiredClass The class to cast to. + * @param value The value to cast. + * @return The value cast to the desired class. + * @throws {@link edu.utexas.cs345.jdblisp.TypeException} if the cast fails. + */ + static T attemptCast(Class desiredClass, SExp value) + throws TypeException { + try { return desiredClass.cast(value); } + catch (ClassCastException cce) { + throw new TypeException(value, desiredClass); + } + } +} diff --git a/src/edu/utexas/cs345/jdblisp/VariableEntry.java b/src/edu/utexas/cs345/jdblisp/VariableEntry.java index 1224b79..1639768 100755 --- a/src/edu/utexas/cs345/jdblisp/VariableEntry.java +++ b/src/edu/utexas/cs345/jdblisp/VariableEntry.java @@ -4,22 +4,29 @@ package edu.utexas.cs345.jdblisp; * VariableEntry * @author Jonathan Bernard (jdbernard@gmail.com) */ -public class VariableEntry { +public class VariableEntry implements SymbolTableEntry { + public final Symbol symbol; public final SExp value; public final boolean isConstant; public final HelpTopic helpinfo; - public VariableEntry(SExp value) { this(value, false); } - - public VariableEntry(SExp value, boolean constant) { - this(value, constant, null); + public VariableEntry(Symbol symbol, SExp value) { + this(symbol, value, false); } - public VariableEntry(SExp value, boolean constant, HelpTopic helpinfo) { + public VariableEntry(Symbol symbol, SExp value, boolean constant) { + this(symbol, value, constant, null); + } + + public VariableEntry(Symbol symbol, SExp value, boolean constant, + HelpTopic helpinfo) { + this.symbol = symbol; this.value = value; this.isConstant = constant; this.helpinfo = helpinfo; } + + public Symbol symbol() { return symbol; } } diff --git a/src/lisp-samples/tests.lisp b/src/lisp-samples/tests.lisp index 76f2237..246396f 100644 --- a/src/lisp-samples/tests.lisp +++ b/src/lisp-samples/tests.lisp @@ -1,7 +1,17 @@ +;;; - TEST NUMERIC COMPARISONS + +(defvar one 1) + +(defvar two 2) + (<= 1 1 2 3 5 8 13) ; => T (<= 1 2 3 4) ; => T +(<= one one two) ; => T + +(<= one two ((lambda (x) (+ x 2)) one)) ; => T + (<= 1 2 3 2) ; => NIL (<= 1 "Hi") ; => TYPE-ERROR @@ -16,6 +26,10 @@ (< 1 3) ; => T +(< one two) ; => T + +(< one two ((lambda (x) (+ x 2)) one)) ; => T + (< 2 1) ; => NIL (< 1 "hi") ; type error @@ -24,6 +38,10 @@ (= 1 1) ; => T +(= one one) ; => T + +(= two ((lambda (x) (+ x 2)) 0)) ; => T + (= 1.0 -1.0) ; => NIL (= 0.0 -0.0) ; => T @@ -31,3 +49,173 @@ (= 1 2) ; => NIL (= 7 7 7) ; => T + +(> 3 2 1) ; => T + +(> 14 10) ; => T + +(> two one) ; => T + +(> two one ((lambda (x) (- x 2)) one)) ; => T + +(> 10 14) ; => NIL + +(> 1 2 3) ; => NIL + +(> 8 7 6 4 5 3 2) ; => NIL + +(> 5 "hi") ; type error + +(>) ; not enough args + +(>= 7 7 7) ; => T + +(>= 8 6 4 2 2 1) ; => T + +(>= 2 1) ; => T + +(>= two two one one) ; => T + +(>= two ((lambda (x) (+ x 1)) one) one) ; => T + +(>= 8 6 4 2 3 1) ; => NIL + +(>= 3 5) ; => NIL + +(>= 3 "hi") ; type error + +(>=) ; not enough args + +;;; - TEST NUMERIC OPERATIONS + +(+) ; => 0 + +(+ 5) ; => 5 + +(+ 1 5) ; => 6 + +(+ 30.05 0.7 1.25) ; => 32 + +(+ one two) ; => 3 + +(+ 5 "hi") ; => error: type + +(-) ; => error: arg # + +(- 5) ; => -5 + +(- 7 2) ; => 5 + +(- 2 7) ; => -5 + +(- 25 3 4 5 6 7) ; => 0 + +(- 100 75.4 24.6) ; => 0 + +(- two one one one) ; => -1 + +(*) ; => 1 + +(* 5) ; => 5 + +(* 1 5) ; => 5 + +(* 5 5) ; => 25 + +(* two two) ; => 4 + +(* 2 3 4) ; => 24 + +(* -23 43) ; => -989 + +(* 5 "hi") ; => error: type + +(/) ; => error: arg # + +(/ 5) ; => 0.2 + +(/ 10 2) ; => 5 + +(/ two one) ; => 2 + +(/ 100 5 2) ; => 10 + +;; (/ 33 3) ; => 11 + +;;; - TEST LAMBDAs + +(defvar sq (lambda (x) (* x x))) ; => SQ + +sq ; => + +(funcall sq 5) ; => 25 + +#'sq ; => undefined function SQ + +(sq 5) ; => undefined function SQ + +((lambda (x) (funcall sq x)) 6) ; => 36 + +;; - test persistent values of lexical closure +(let* ((x 2) (sqx (lambda () (setq x (* x x))))) + (funcall sqx) ; local x = 4 after this call + (defparameter *sqx* sqx)) + +(funcall *sqx*) ; => 16 + +;; - check binding (the lexical closure overrides the calling context) +(let ((x 1)) (funcall *sqx*)) ; warning, x defined but unsed => 256 + +;; - check binding (the lexical closure includes non-local variables) +(let ((x 1)) + (let ((y 2)) (defparameter *sqy* (lambda () (setq x (* x x)))))) + +(funcall *sqy*) ; => 1 + +(let ((x 2)) (funcall *sqy*)) ; => 1 + +;; - check binding (the lexical closue does NOT include unreferenced variables) +(let ((a 50) (b 60) (c 7)) + (defparameter *sqz* (lambda (fun2arg) (+ c (funcall fun2arg))))) ; => *SQZ* + +(let ((a 0) (b 0) (c 0)) + (funcall *sqz* (lambda () (+ a b)))) ; => 7 + +;; - check binding (the lexical closure includes function definitions) +(defparameter funlam + (let ((x 5)) + (defun f1 (i) (+ i 1)) + (lambda () (f1 x)))) ; => FUNLAM + + +(funcall funlam) ; => 6 + +;;; SAMPLE ALGORITHMS + +(defun factorial (n) (if (<= n 1) + 1 + (+ + (factorial (- n 1)) + (factorial (- n 2))))) + +(factorial 0) ; => 1 + +(factorial 1) ; => 1 + +(factorial 2) ; => 2 + +(factorial 3) ; => 3 + +(factorial 4) ; => 5 + +(factorial 5) ; => 8 + +(factorial 6) ; => 13 + +(factorial 20) ; => 10946 + +(defun collatz (n) (if (= n 1) + 1 + (if (= 0 (mod n 2)) + (+ 1 (collatz (/ n 2))) + (+ 1 (collatz (+ (* n 3) 1))))))