commit cee46a41b9305800c2a9dc5bb9a84ad2e745ff37 Author: Mike Cifelli Date: Wed Dec 7 14:16:45 2016 -0500 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a61292a --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +build/ +doc/ +jar/ +*.swp diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..5756cd6 --- /dev/null +++ b/build.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/design.txt b/design.txt new file mode 100644 index 0000000..d68ca95 --- /dev/null +++ b/design.txt @@ -0,0 +1,47 @@ +Mike Cifelli +CIS 443 - Programming Languages +Lisp Interpreter Design Document + + My implementation of LispScanner takes in an InputStream in its constructor +that it will use to retrieve the Lisp tokens. It then creates a +BufferedInputStream around this input stream so I can be sure that it will +support the 'mark' and 'reset' methods (which the LispScanner requires to +operate). A LispFilterStream is then created with this BufferedInputStream so I +can retrieve all of the bytes from the original input stream without having to +worry about dealing with Lisp comments. + When the LispScanner looks for the next Lisp token to return it uses a +switch statement to determine the type of the next token (or to skip over +whitespace). In the case of an identifier or number the scanner has to keep +accumulating characters until it sees one that can not be a part of the number +or identifier. Once one is found it has obviously been read from the input +stream and this is not desirable as it is not part of the current token. This +is where I made use of the 'mark' and 'reset' methods in the scanner. I mark +the position before I read each character and when one is found that is not +part of the current token I reset the input stream to its last position before +the token is returned. This effectively unreads the last character so the input +stream is in the proper position for the scanner's next read. + + In the design of the LispParser I had some difficulty in implementing the +'eof' method. This was due to the fact that in order to determine if a +LispScanner was at the end of the input stream you have to read in a token. +However, I did not want this method to read in a token since this would +normally be part of an S-expression. Unfortunately, this meant that I would not +be able to detect the end-of-file token until the 'getSExpr' method read it in. +This is too late since the 'eof' method is used to determine when to stop +calling the 'getSExpr' method. + My solution involved reading a token in the 'eof' method and then storing +it in a variable. This would only be done once, until the token was used in the +'getSExpr' method. I also stored any exceptions that were thrown during the +read so that the 'eof' method would not give the impression of having read in +any tokens. Any exception thrown in the 'eof' method during the retrieval of a +token is stored and thrown the next time the 'getSExpr' method is called. + + During the evaluation phase of the Lisp interpreter, I made use of the +command pattern during function calls. By creating a LispFunction interface, I +was able to place all of the built-in functions into a hash table mapping +function names to the appropriate LispFunction. By looking up functions in this +hash table, the proper function could be easily called during the evaluation of +a list. + + FINAL NOTE: The function table is located in the EVAL class and the symbol +table is located in the SETF class. diff --git a/error/ErrorManager.java b/error/ErrorManager.java new file mode 100644 index 0000000..255c61d --- /dev/null +++ b/error/ErrorManager.java @@ -0,0 +1,50 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter Phase 1 - Lexical Analysis + */ + +package error; + +import java.text.MessageFormat; + +/** + * ErrorManager is an error handling class for a Lisp interpreter. + */ +public class ErrorManager { + + /** + * The lowest "criticality" level of an error that will cause the currently + * running program to terminate. + */ + public static final int CRITICAL_LEVEL = 3; + + public static final String ANSI_RESET = "\u001B[0m"; + public static final String ANSI_RED = "\u001B[31m"; + public static final String ANSI_YELLOW = "\u001B[33m"; + public static final String ANSI_PURPLE = "\u001B[35m"; + + /** + * Prints out the specified error message to the console and decides + * whether or not to terminate the currently running program. + * + * @param message + * the error message + * @param level + * the "criticality" level of the error + * @postcondition + * If level >= CRITICAL_LEVEL the currently running + * program has been terminated. + */ + public static void generateError(String message, int level) { + String color = (level >= CRITICAL_LEVEL) ? ANSI_PURPLE : ANSI_RED; + String formattedMessage = MessageFormat.format("{0}error: {1}{2}", color, message, ANSI_RESET); + + System.out.println(formattedMessage); + + if (level >= CRITICAL_LEVEL) { + System.exit(1); + } + } + +} diff --git a/error/package.html b/error/package.html new file mode 100644 index 0000000..b8e7489 --- /dev/null +++ b/error/package.html @@ -0,0 +1,3 @@ + + Provides a class for managing errors in the Lisp Interpreter. + diff --git a/eval/APPLY.java b/eval/APPLY.java new file mode 100644 index 0000000..f7fa819 --- /dev/null +++ b/eval/APPLY.java @@ -0,0 +1,74 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 1 + */ + +package eval; + +import parser.*; + +/** + * APPLY represents the APPLY function in Lisp. + */ +public class APPLY extends LispFunction { + + /** + * Call APPLY with the specified argument list. + * + * @param argList + * the list of arguments to be sent to APPLY (MUST BE A PROPER LIST) + * @return + * the result of evaluating APPLY on argList + */ + public static SExpression apply(Cons argList) { + return new APPLY().call(argList); + } + + // The number of arguments that APPLY takes. + private static final int NUM_ARGS = 2; + + public SExpression call(Cons argList) { + // retrieve the number of arguments passed to APPLY + 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("APPLY"), argList); + String errMsg = "too " + + ((argListLength > NUM_ARGS) ? "many" : "few") + + " arguments given to APPLY: " + originalSExpr; + + throw new RuntimeException(errMsg); + } + + SExpression car = argList.getCar(); // function name + Cons cdr = (Cons) argList.getCdr(); + SExpression cadr = cdr.getCar(); // argument list + + // make sure the second argument is a list + if (cadr.listp()) { + LispFunction function = EVAL.lookupFunction(car.toString()); + + if (function == null) { + // check if the car of the list is a lambda expression + if (car.functionp()) { + function = ((LambdaExpression) car).getFunction(); + } else if (LAMBDA.isLambdaExpression(car)) { + Cons lexpr = (Cons) car; + + function = LAMBDA.createFunction(lexpr); + } else { + throw new RuntimeException("undefined function " + car); + } + } + + // apply the given function to the given argument list + return function.call((Cons) cadr); + } + + // the second argument is not a list + throw new RuntimeException("APPLY: " + cadr + " is not a list"); + } + +} diff --git a/eval/ATOM.java b/eval/ATOM.java new file mode 100644 index 0000000..fbee068 --- /dev/null +++ b/eval/ATOM.java @@ -0,0 +1,38 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 1 + */ + +package eval; + +import parser.*; + +/** + * ATOM represents the ATOM function in Lisp. + */ +public class ATOM extends LispFunction { + + // The number of arguments that ATOM takes. + private static final int NUM_ARGS = 1; + + public SExpression call(Cons argList) { + // retrieve the number of arguments passed to ATOM + 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("ATOM"), argList); + String errMsg = "too " + + ((argListLength > NUM_ARGS) ? "many" : "few") + + " arguments given to ATOM: " + originalSExpr; + + throw new RuntimeException(errMsg); + } + + SExpression arg = argList.getCar(); + + return (arg.atomp() ? Symbol.T : Nil.getUniqueInstance()); + } + +} diff --git a/eval/CAR.java b/eval/CAR.java new file mode 100644 index 0000000..3b75078 --- /dev/null +++ b/eval/CAR.java @@ -0,0 +1,46 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 1 + */ + +package eval; + +import parser.*; + +/** + * CAR represents the CAR function in Lisp. + */ +public class CAR extends LispFunction { + + // The number of arguments that CAR takes. + private static final int NUM_ARGS = 1; + + public SExpression call(Cons argList) { + // retrieve the number of arguments passed to CAR + 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("CAR"), argList); + String errMsg = "too " + + ((argListLength > NUM_ARGS) ? "many" : "few") + + " arguments given to CAR: " + originalSExpr; + + throw new RuntimeException(errMsg); + } + + SExpression argCar = argList.getCar(); + + // make sure that the argument is a list + if (argCar.listp()) { + Cons arg = (Cons) argCar; + + return arg.getCar(); + } + + // the argument is not a list + throw new RuntimeException("CAR: " + argCar + " is not a list"); + } + +} diff --git a/eval/CDR.java b/eval/CDR.java new file mode 100644 index 0000000..0cc281e --- /dev/null +++ b/eval/CDR.java @@ -0,0 +1,46 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 1 + */ + +package eval; + +import parser.*; + +/** + * CDR represents the CDR function in Lisp. + */ +public class CDR extends LispFunction { + + // The number of arguments that CDR takes. + private static final int NUM_ARGS = 1; + + public SExpression call(Cons argList) { + // retrieve the number of arguments passed to CDR + 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("CDR"), argList); + String errMsg = "too " + + ((argListLength > NUM_ARGS) ? "many" : "few") + + " arguments given to CDR: " + originalSExpr; + + throw new RuntimeException(errMsg); + } + + SExpression argCar = argList.getCar(); + + // make sure that the argument is a list + if (argCar.listp()) { + Cons arg = (Cons) argCar; + + return arg.getCdr(); + } + + // the argument is not a list + throw new RuntimeException("CDR: " + argCar + " is not a list"); + } + +} diff --git a/eval/COND.java b/eval/COND.java new file mode 100644 index 0000000..9bd4e79 --- /dev/null +++ b/eval/COND.java @@ -0,0 +1,73 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 1 + */ + +package eval; + +import parser.*; + +/** + * COND represents the COND form in Lisp. + */ +public class COND extends LispFunction { + + public SExpression call(Cons argList) { + if (argList.nullp()) { + // return NIL if there are were no arguments passed to COND + return Nil.getUniqueInstance(); + } + + SExpression argCar = argList.getCar(); // first clause + Cons argCdr = (Cons) argList.getCdr(); // list of remaining clauses + + // make sure the first clause is a list and is not NIL + if (argCar.consp()) { + Cons clause = (Cons) argCar; + SExpression test = EVAL.eval(clause.getCar()); + + if (test != Nil.getUniqueInstance()) { + // the car of this clause is true, so we evaluate its cdr + + SExpression cdr = clause.getCdr(); + SExpression retval = test; + + // evaluate all the S-expressions in the cdr of the clause + while (cdr.consp()) { + retval = EVAL.eval(((Cons) cdr).getCar()); + cdr = ((Cons) cdr).getCdr(); + } + + // return the value of the last S-expression evaluated + return retval; + } + + // the car of this clause is false, so we test any remaining + // clauses + + // check if the list of remaining clauses is a list and is not NIL + if (argCdr.consp()) { + return call(argCdr); + } + + // there are no remaining clauses, so we return NIL + return Nil.getUniqueInstance(); + } + + throw new RuntimeException("COND: clause " + argCar + + " should be a list"); + } + + /** + * Determine if the arguments passed to this Lisp function should be + * evaluated. + * + * @return + * false + */ + public boolean evaluateArguments() { + return false; + } + +} diff --git a/eval/CONS.java b/eval/CONS.java new file mode 100644 index 0000000..db967a7 --- /dev/null +++ b/eval/CONS.java @@ -0,0 +1,44 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 1 + */ + +package eval; + +import parser.*; + +/** + * CONS represents the CONS function in Lisp. + */ +public class CONS extends LispFunction { + + // The number of arguments that CONS takes. + private static final int NUM_ARGS = 2; + + public Cons call(Cons argList) { + // retrieve the number of arguments passed to CONS + 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("CONS"), argList); + String errMsg = "too " + + ((argListLength > NUM_ARGS) ? "many" : "few") + + " arguments given to CONS: " + originalSExpr; + + throw new RuntimeException(errMsg); + } + + // the car of the CONS cell we are going to create + SExpression argOne = argList.getCar(); + + Cons cdr = (Cons) argList.getCdr(); + + // the cdr of the CONS cell we are going to create + SExpression argTwo = cdr.getCar(); + + return new Cons(argOne, argTwo); + } + +} diff --git a/eval/DEFUN.java b/eval/DEFUN.java new file mode 100644 index 0000000..c435737 --- /dev/null +++ b/eval/DEFUN.java @@ -0,0 +1,82 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 2 + */ + +package eval; + +import parser.*; +import java.util.HashMap; + +/** + * DEFUN represents the DEFUN form in Lisp. + */ +public class DEFUN extends LispFunction { + + // The minimum number of arguments that DEFUN takes. + private static final int MIN_ARGS = 3; + + public SExpression call(Cons argList) { + // retrieve the number of arguments passed to DEFUN + int argListLength = LENGTH.getLength(argList); + + // make sure we have received the proper number of arguments + if (argListLength < MIN_ARGS) { + Cons originalSExpr = new Cons(new Symbol("DEFUN"), argList); + String errMsg = "too few arguments given to DEFUN: " + + originalSExpr; + + throw new RuntimeException(errMsg); + } + + SExpression name = argList.getCar(); // name of the function + + // make sure the function name is a symbol + if (! name.symbolp()) { + throw new RuntimeException("DEFUN: " + name + " is not a symbol"); + } + + Cons cdr = (Cons) argList.getCdr(); + SExpression cadr = cdr.getCar(); + + // 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(); + + HashMap functionTable = EVAL.getFunctionTable(); + + // give a warning if this function has already been defined + if (functionTable.containsKey(name.toString())) { + System.out.println("WARNING: redefining function " + + name.toString()); + } + + // place the function in the function table + functionTable.put(name.toString(), + new UDFunction(name.toString(), lambdaList, body)); + + return name; + } + + /** + * Determine if the arguments passed to this Lisp function should be + * evaluated. + * + * @return + * false + */ + public boolean evaluateArguments() { + return false; + } + +} diff --git a/eval/DIVIDE.java b/eval/DIVIDE.java new file mode 100644 index 0000000..0794f57 --- /dev/null +++ b/eval/DIVIDE.java @@ -0,0 +1,60 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 1 + */ + +package eval; + +import parser.*; + +/** + * DIVIDE represents the '/' function in Lisp. + */ +public class DIVIDE extends LispFunction { + + public SExpression call(Cons argList) { + // make sure we have received at least one argument + if (argList.nullp()) { + Cons originalSExpr = new Cons(new Symbol("/"), argList); + + throw new RuntimeException("too few arguments given to /: " + + originalSExpr); + } + + SExpression argFirst = argList.getCar(); + Cons argRest = (Cons) argList.getCdr(); + + // make sure that the first argument is a number + if (argFirst.numberp()) { + LispNumber num1 = (LispNumber) argFirst; + + if (argRest.nullp()) { + // there is only one argument, so return the multiplicative + // inverse of the number + return new LispNumber(1 / num1.getValue()); + } + + SExpression argSecond = argRest.getCar(); + + // make sure that the next argument is a number as well + if (argSecond.numberp()) { + LispNumber num2 = (LispNumber) argSecond; + LispNumber quotient = new LispNumber(num1.getValue() / + num2.getValue()); + SExpression argCddr = argRest.getCdr(); + + if (argCddr.consp()) { + return call(new Cons(quotient, argCddr)); + } + + return quotient; + } + + throw new RuntimeException("/: " + argSecond + " is not a number"); + } + + throw new RuntimeException("/: " + argFirst + " is not a number"); + } + +} diff --git a/eval/EQ.java b/eval/EQ.java new file mode 100644 index 0000000..0227525 --- /dev/null +++ b/eval/EQ.java @@ -0,0 +1,45 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 1 + */ + +package eval; + +import parser.*; + +/** + * EQ represents the EQ function in Lisp. + */ +public class EQ extends LispFunction { + + // The number of arguments that EQ takes. + private static final int NUM_ARGS = 2; + + public SExpression call(Cons argList) { + // retrieve the number of arguments passed to EQ + 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("EQ"), argList); + String errMsg = "too " + + ((argListLength > NUM_ARGS) ? "many" : "few") + + " arguments given to EQ: " + originalSExpr; + + throw new RuntimeException(errMsg); + } + + SExpression argOne = argList.getCar(); // first argument + Cons cdr = (Cons) argList.getCdr(); + SExpression argTwo = cdr.getCar(); // second argumnet + + if (argOne.atomp() && argTwo.atomp()) { + return ((argOne.toString().equals(argTwo.toString())) + ? Symbol.T : Nil.getUniqueInstance()); + } + + return ((argOne == argTwo) ? Symbol.T : Nil.getUniqueInstance()); + } + +} diff --git a/eval/EQUAL.java b/eval/EQUAL.java new file mode 100644 index 0000000..0e4e02e --- /dev/null +++ b/eval/EQUAL.java @@ -0,0 +1,58 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 1 + */ + +package eval; + +import parser.*; + +/** + * EQUAL represents the EQUAL function in Lisp. + */ +public class EQUAL extends LispFunction { + + // The number of arguments that EQUAL takes. + private static final int NUM_ARGS = 2; + + public SExpression call(Cons argList) { + // retrieve the number of arguments passed to EQUAL + 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("EQUAL"), argList); + String errMsg = "too " + + ((argListLength > NUM_ARGS) ? "many" : "few") + + " arguments given to EQUAL: " + originalSExpr; + + throw new RuntimeException(errMsg); + } + + SExpression argOne = argList.getCar(); // first argument + Cons cdr = (Cons) argList.getCdr(); + SExpression argTwo = cdr.getCar(); // second argumnet + + if (argOne.consp() && argTwo.consp()) { + Cons listOne = (Cons) argOne; + Cons listTwo = (Cons) argTwo; + SExpression listOneCar = listOne.getCar(); + SExpression listTwoCar = listTwo.getCar(); + SExpression listOneCdr = listOne.getCdr(); + SExpression listTwoCdr = listTwo.getCdr(); + + SExpression carEqual = + call(new Cons(listOneCar, LIST.makeList(listTwoCar))); + SExpression cdrEqual = + call(new Cons(listOneCdr, LIST.makeList(listTwoCdr))); + + return (((carEqual == Symbol.T) && (cdrEqual == Symbol.T)) + ? Symbol.T : Nil.getUniqueInstance()); + } + + return ((argOne.toString().equals(argTwo.toString())) + ? Symbol.T : Nil.getUniqueInstance()); + } + +} diff --git a/eval/EQUALSP.java b/eval/EQUALSP.java new file mode 100644 index 0000000..1c6fb65 --- /dev/null +++ b/eval/EQUALSP.java @@ -0,0 +1,55 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 1 + */ + +package eval; + +import parser.*; + +/** + * EQUALSP represents the '=' function in Lisp. + */ +public class EQUALSP extends LispFunction { + + public SExpression call(Cons argList) { + // make sure we have received at least one argument + if (argList.nullp()) { + Cons originalSExpr = new Cons(new Symbol("="), argList); + + throw new RuntimeException("too few arguments given to =: " + + originalSExpr); + } + + SExpression firstArg = argList.getCar(); + Cons argRest = (Cons) argList.getCdr(); + + // make sure that the first argument is a number + if (firstArg.numberp()) { + LispNumber num1 = (LispNumber) firstArg; + + if (argRest.nullp()) { + return Symbol.T; + } + + SExpression secondArg = argRest.getCar(); + + // make sure that the second argument is a number as well + if (secondArg.numberp()) { + LispNumber num2 = (LispNumber) secondArg; + + if (num1.getValue() == num2.getValue()) { + return call(argRest); + } + + return Nil.getUniqueInstance(); + } + + throw new RuntimeException("=: " + secondArg + " is not a number"); + } + + throw new RuntimeException("=: " + firstArg + " is not a number"); + } + +} diff --git a/eval/EVAL.java b/eval/EVAL.java new file mode 100644 index 0000000..2eacfc2 --- /dev/null +++ b/eval/EVAL.java @@ -0,0 +1,251 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 1 + */ + +package eval; + +import parser.*; +import java.util.HashMap; + +/** + * EVAL represents the EVAL function in Lisp. + */ +public class EVAL extends LispFunction { + + // A table to contain all the built-in and user-defined Lisp functions. + private static HashMap functionTable = + new HashMap(); + + static { + // place all of the built-in functions into the function table + 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()); + } + + /** + * Retrieve the function table. + * + * @return + * the function table + */ + public static HashMap getFunctionTable() { + return functionTable; + } + + /** + * Look up a function by its name. + * + * @param functionName + * the name of the function to look up + * @return + * the function with the name functionName if it exists; null + * otherwise + */ + public static LispFunction lookupFunction(String functionName) { + return functionTable.get(functionName); + } + + /** + * Look up a symbol's value using its name. + * + * @param symbolName + * the name of the symbol to look up (must not be null) + * @return + * the value of symbolName if it has one; null otherwise + */ + public static SExpression lookupSymbol(String symbolName) { + if (symbolName.equals("NIL")) { + return Nil.getUniqueInstance(); + } else if (symbolName.equals("T")) { + return Symbol.T; + } else if (symbolName.startsWith(":")) { + return new Symbol(symbolName); + } + + return SETF.lookup(symbolName); + } + + /** + * Determine if the given list is dotted. + * + * @param list + * the list to be tested (must not be null) + * @return + * true if list is dotted; false + * otherwise + */ + public static boolean isDotted(Cons list) { + if (list.nullp()) { + return false; + } + + SExpression cdr = list.getCdr(); + + if (cdr.listp()) { + return isDotted((Cons) cdr); + } + + // the cdr of 'list' is not a list, therefore it is dotted + return true; + } + + /** + * Evaluate the given S-expression. + * + * @param sexpr + * the S-expression to evaluate + * @return + * the value of sexpr + */ + public static SExpression eval(SExpression sexpr) { + Cons expList = LIST.makeList(sexpr); + EVAL evalFunction = new EVAL(); + + return evalFunction.call(expList); + } + + // The number of arguments that EVAL takes. + private static final int NUM_ARGS = 1; + + 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); + } + + SExpression arg = argList.getCar(); + + if (arg.listp()) { + if (arg.consp()) { + return evaluateList((Cons) arg); + } + + return arg; // 'arg' is NIL + } + + if (arg.symbolp()) { + SExpression symbolValue = lookupSymbol(arg.toString()); + + if (symbolValue != null) { + return symbolValue; + } + + throw new RuntimeException("variable " + arg + " has no value"); + } + + return arg; // 'arg' is a NUMBER or a STRING + } + + // Evaluate the specified list. + // + // Parameters: list - the list to evaluate + // Returns: the value of 'list' + // Precondition: 'list' must not be null. + private SExpression evaluateList(Cons list) { + SExpression car = list.getCar(); + SExpression cdr = list.getCdr(); + + LispFunction function = lookupFunction(car.toString()); + + if (function == null) { + // check if the car of the list is a lambda expression + if (car.functionp()) { + function = ((LambdaExpression) car).getFunction(); + } else if (LAMBDA.isLambdaExpression(car)) { + Cons lexpr = (Cons) car; + + function = LAMBDA.createFunction(lexpr); + } else { + throw new RuntimeException("undefined function " + car); + } + } + + // make sure the list of arguments for 'function' is a list + if (cdr.listp()) { + Cons args = (Cons) cdr; + + // make sure the list of arguments is not dotted + if (isDotted(args)) { + throw new RuntimeException("argument list given to " + car + + " is dotted: " + list); + } + + // 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); + } + + // Evaluate a list of arguments for a function. + // + // Parameters: arguments - a list of arguments for a function + // Returns: a list consisting of the values of the S-expressions found in + // 'arguments' + // Precondition: 'arguments' must not be null. + private Cons evaluateArgList(Cons arguments) { + if (arguments.nullp()) { + return Nil.getUniqueInstance(); + } + + 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.getUniqueInstance()); + } + +} diff --git a/eval/EXIT.java b/eval/EXIT.java new file mode 100644 index 0000000..1def13f --- /dev/null +++ b/eval/EXIT.java @@ -0,0 +1,26 @@ +package eval; + +import parser.*; + +public class EXIT extends LispFunction { + + // The number of arguments that EXIT takes. + private static final int NUM_ARGS = 0; + + 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); + } + + System.exit(0); + return null; + } + +} diff --git a/eval/FUNCALL.java b/eval/FUNCALL.java new file mode 100644 index 0000000..395dd88 --- /dev/null +++ b/eval/FUNCALL.java @@ -0,0 +1,31 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 2 + */ + +package eval; + +import parser.*; + +/** + * FUNCALL represents the FUNCALL function in Lisp. + */ +public class FUNCALL extends LispFunction { + + public SExpression call(Cons argList) { + // make sure we have received at least one argument + if (argList.nullp()) { + Cons originalSExpr = new Cons(new Symbol("FUNCALL"), argList); + + throw new RuntimeException("too few arguments given to FUNCALL: " + + originalSExpr); + } + + SExpression cdr = argList.getCdr(); + Cons applyArgs = new Cons(argList.getCar(), LIST.makeList(cdr)); + + return APPLY.apply(applyArgs); + } + +} diff --git a/eval/GREATERP.java b/eval/GREATERP.java new file mode 100644 index 0000000..2dfc7aa --- /dev/null +++ b/eval/GREATERP.java @@ -0,0 +1,55 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 1 + */ + +package eval; + +import parser.*; + +/** + * GREATERP represents the '>' function in Lisp. + */ +public class GREATERP extends LispFunction { + + public SExpression call(Cons argList) { + // make sure we have received at least one argument + if (argList.nullp()) { + Cons originalSExpr = new Cons(new Symbol(">"), argList); + + throw new RuntimeException("too few arguments given to >: " + + originalSExpr); + } + + SExpression firstArg = argList.getCar(); + Cons argRest = (Cons) argList.getCdr(); + + // make sure that the first argument is a number + if (firstArg.numberp()) { + LispNumber num1 = (LispNumber) firstArg; + + if (argRest.nullp()) { + return Symbol.T; + } + + SExpression secondArg = argRest.getCar(); + + // make sure that the second argument is a number as well + if (secondArg.numberp()) { + LispNumber num2 = (LispNumber) secondArg; + + if (num1.getValue() > num2.getValue()) { + return call(argRest); + } + + return Nil.getUniqueInstance(); + } + + throw new RuntimeException(">: " + secondArg + " is not a number"); + } + + throw new RuntimeException(">: " + firstArg + " is not a number"); + } + +} diff --git a/eval/LAMBDA.java b/eval/LAMBDA.java new file mode 100644 index 0000000..eaec557 --- /dev/null +++ b/eval/LAMBDA.java @@ -0,0 +1,108 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 2 + */ + +package eval; + +import parser.*; + +/** + * LAMBDA represents the LAMBDA form in Lisp. + */ +public class LAMBDA extends LispFunction { + + /** + * Determine if the given S-expression is a lambda expression. + * + * @param sexpr + * the S-expression to test (must not be null) + * @return + * true if sexpr is a valid lambda expression; + * false otherwise + */ + public static boolean isLambdaExpression(SExpression sexpr) { + if (sexpr.consp()) { + SExpression first = ((Cons) sexpr).getCar(); + + return "LAMBDA".equals(first.toString()); + } + + return false; + } + + /** + * Create an internal representation of a user-defined function from the + * specified lambda expression. + * + * @param lexpr + * the lambda expression to create the function from (must not be null) + * @return + * an internal representation of a user-defined function created from + * lexpr + * @throws RuntimeException + * Indicates that lexpr is not a valid lambda expression. + */ + public static UDFunction createFunction(Cons lexpr) { + LAMBDA lambda = new LAMBDA(); + SExpression cdr = lexpr.getCdr(); + + // make sure lexpr is a proper list + if (! cdr.consp()) { + throw new RuntimeException("invalid lambda expression"); + } else if (EVAL.isDotted((Cons) cdr)) { + throw new RuntimeException("dotted lambda expression " + lexpr); + } + + Cons rest = (Cons) cdr; + + return lambda.call(rest).getFunction(); + } + + // The minimum number of arguments that LAMBDA takes. + private static final int MIN_ARGS = 2; + + public LambdaExpression call(Cons argList) { + // retrieve the number of arguments passed to LAMBDA + int argListLength = LENGTH.getLength(argList); + + // make sure we have received the proper number of arguments + if (argListLength < MIN_ARGS) { + Cons originalSExpr = new Cons(new Symbol("LAMBDA"), argList); + String errMsg = "too few arguments given to LAMBDA: " + + originalSExpr; + + throw new RuntimeException(errMsg); + } + + SExpression car = argList.getCar(); + + // make sure the list of arguments is a proper list + if (! car.listp()) { + throw new RuntimeException("LAMBDA: " + car + " is not a list"); + } else if (EVAL.isDotted((Cons) car)) { + throw new RuntimeException("LAMBDA: " + car + + " must be a proper list"); + } + + Cons lambdaList = (Cons) car; + Cons body = (Cons) argList.getCdr(); + Cons lexpr = new Cons(new Symbol("LAMBDA"), argList); + UDFunction function = new UDFunction(":LAMBDA", lambdaList, body); + + return new LambdaExpression(lexpr, function); + } + + /** + * Determine if the arguments passed to this Lisp function should be + * evaluated. + * + * @return + * false + */ + public boolean evaluateArguments() { + return false; + } + +} diff --git a/eval/LENGTH.java b/eval/LENGTH.java new file mode 100644 index 0000000..dcab21b --- /dev/null +++ b/eval/LENGTH.java @@ -0,0 +1,69 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 1 + */ + +package eval; + +import parser.*; + +/** + * LENGTH represents the LENGTH function in Lisp. + */ +public class LENGTH extends LispFunction { + + /** + * Returns the length of the given list. + * + * @param list + * the list to determine the length of + * @return + * the length of list + */ + public static int getLength(Cons list) { + LENGTH lengthFunction = new LENGTH(); + LispNumber length = lengthFunction.call(LIST.makeList(list)); + + return length.getValue(); + } + + public LispNumber call(Cons argList) { + // make sure we have received at least one argument + if (argList.nullp()) { + Cons originalSExpr = new Cons(new Symbol("LENGTH"), argList); + + throw new RuntimeException("too few arguments given to LENGTH: " + + originalSExpr); + } + + SExpression argCar = argList.getCar(); + SExpression argCdr = argList.getCdr(); + + // make sure we have received only one argument + if (! argCdr.nullp()) { + Cons originalSExpr = new Cons(new Symbol("LENGTH"), argList); + + throw new RuntimeException("too many arguments given to LENGTH: " + + originalSExpr); + } + + // make sure that the argument is a list + if (argCar.listp()) { + Cons arg = (Cons) argCar; + + if (arg.nullp()) { + return new LispNumber(0); + } + + Cons cdr = LIST.makeList(arg.getCdr()); + LispNumber cdrLength = call(cdr); + + return new LispNumber(1 + cdrLength.getValue()); + } + + throw new RuntimeException("LENGTH: a proper list must not end with " + + argCar); + } + +} diff --git a/eval/LESSP.java b/eval/LESSP.java new file mode 100644 index 0000000..162d8c6 --- /dev/null +++ b/eval/LESSP.java @@ -0,0 +1,55 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 1 + */ + +package eval; + +import parser.*; + +/** + * LESSP represents the '<' function in Lisp. + */ +public class LESSP extends LispFunction { + + public SExpression call(Cons argList) { + // make sure we have received at least one argument + if (argList.nullp()) { + Cons originalSExpr = new Cons(new Symbol("<"), argList); + + throw new RuntimeException("too few arguments given to <: " + + originalSExpr); + } + + SExpression firstArg = argList.getCar(); + Cons argRest = (Cons) argList.getCdr(); + + // make sure that the first argument is a number + if (firstArg.numberp()) { + LispNumber num1 = (LispNumber) firstArg; + + if (argRest.nullp()) { + return Symbol.T; + } + + SExpression secondArg = argRest.getCar(); + + // make sure that the second argument is a number as well + if (secondArg.numberp()) { + LispNumber num2 = (LispNumber) secondArg; + + if (num1.getValue() < num2.getValue()) { + return call(argRest); + } + + return Nil.getUniqueInstance(); + } + + throw new RuntimeException("<: " + secondArg + " is not a number"); + } + + throw new RuntimeException("<: " + firstArg + " is not a number"); + } + +} diff --git a/eval/LET.java b/eval/LET.java new file mode 100644 index 0000000..becdbdd --- /dev/null +++ b/eval/LET.java @@ -0,0 +1,116 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 2 + */ + +package eval; + +import parser.*; + +/** + * LET represents the LET form in Lisp. + */ +public class LET extends LispFunction { + + public SExpression call(Cons argList) { + // make sure we have received at least one argument + if (argList.nullp()) { + throw new RuntimeException("too few arguments given to LET"); + } + + // create a new symbol table on top of the current environment to add + // all the local variables to + SymbolTable environment = new SymbolTable(SETF.getEnvironment()); + + SExpression car = argList.getCar(); + Cons cdr = (Cons) argList.getCdr(); + + addVariablesToTable(environment, car); + SETF.setEnvironment(environment); + + SExpression retval = Nil.getUniqueInstance(); + + // evaluate all S-expression in the body + while (cdr.consp()) { + retval = EVAL.eval(cdr.getCar()); + cdr = (Cons) cdr.getCdr(); + } + + // restore the environment to its original value + SETF.setEnvironment(environment.getParent()); + + return retval; + } + + // Add a list of variables and their values to the specified symbol table. + // + // Parameters: environment - the symbol table to add the variables and + // their values to (must not be null) + // vars - a list of variable/value pairs (must be either a + // proper list of pairs or NIL) + // Throws: RuntimeException - Indicates that 'vars' is not a proper list or + // that it contains an member that is not a + // variable/value pair. + // Precondition: 'environment' and 'vars' must not be null. + // Postcondition: All of the variables in 'vars' have been placed into + // 'environment' with their values. + private void addVariablesToTable(SymbolTable environment, + SExpression vars) { + // makes sure the list of variable/value pairs is a list + if (! vars.listp()) { + throw new RuntimeException("LET: " + vars + + " is not a properly formatted" + + " variable/value pair list"); + } + + // add all variables in 'vars' to 'environment' + while (vars.consp()) { + Cons varList = (Cons) vars; + SExpression varListCar = varList.getCar(); + + // make sure this variable/value pair is a list + if (! varListCar.consp()) { + throw new RuntimeException("LET: " + varListCar + + " is not a properly formatted" + + " variable/value pair"); + } + + Cons varSpec = (Cons) varListCar; + SExpression symbol = varSpec.getCar(); + SExpression varSpecCdr = varSpec.getCdr(); + + // make sure this variable pair has a value associated with it + if (! varSpecCdr.consp()) { + throw new RuntimeException("LET: illegal variable " + + "specification " + varSpec); + } + + Cons varValue = (Cons) varSpecCdr; + SExpression value = varValue.getCar(); + + // make sure there are no more members of this variable/value pair + // and that 'symbol' is actually a symbol + if ((! varValue.getCdr().nullp()) || (! symbol.symbolp())) { + throw new RuntimeException("LET: illegal variable " + + "specification " + varSpec); + } + + environment.put(symbol.toString(), value); + + vars = varList.getCdr(); + } + } + + /** + * Determine if the arguments passed to this Lisp function should be + * evaluated. + * + * @return + * false + */ + public boolean evaluateArguments() { + return false; + } + +} diff --git a/eval/LIST.java b/eval/LIST.java new file mode 100644 index 0000000..d801e6e --- /dev/null +++ b/eval/LIST.java @@ -0,0 +1,40 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 1 + */ + +package eval; + +import parser.*; + +/** + * LIST represents the LIST function in Lisp. + */ +public class LIST extends LispFunction { + + /** + * Places the given S-expression into a list. + * + * @param sexpr + * the S-expression to be placed into a list + * @return + * a list with sexpr as the car and NIL as the cdr. + */ + public static Cons makeList(SExpression sexpr) { + return new Cons(sexpr, Nil.getUniqueInstance()); + } + + public Cons call(Cons argList) { + if (argList.nullp()) { + // return NIL if there were no arguments passed to LIST + return Nil.getUniqueInstance(); + } + + SExpression argCar = argList.getCar(); + Cons argCdr = (Cons) argList.getCdr(); + + return new Cons(argCar, call(argCdr)); + } + +} diff --git a/eval/LISTP.java b/eval/LISTP.java new file mode 100644 index 0000000..b2ad0ce --- /dev/null +++ b/eval/LISTP.java @@ -0,0 +1,38 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 1 + */ + +package eval; + +import parser.*; + +/** + * LISTP represents the LISTP function in Lisp. + */ +public class LISTP extends LispFunction { + + // The number of arguments that LISTP takes. + private static final int NUM_ARGS = 1; + + public SExpression call(Cons argList) { + // retrieve the number of arguments passed to LISTP + 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("LISTP"), argList); + String errMsg = "too " + + ((argListLength > NUM_ARGS) ? "many" : "few") + + " arguments given to LISTP: " + originalSExpr; + + throw new RuntimeException(errMsg); + } + + SExpression arg = argList.getCar(); + + return (arg.listp() ? Symbol.T : Nil.getUniqueInstance()); + } + +} diff --git a/eval/LOAD.java b/eval/LOAD.java new file mode 100644 index 0000000..4a94235 --- /dev/null +++ b/eval/LOAD.java @@ -0,0 +1,87 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 2 + */ + +package eval; + +import parser.*; +import java.io.*; + +/** + * LOAD represents the LOAD function in Lisp. + */ +public class LOAD extends LispFunction { + // The number of arguments that LOAD takes. + private static final int NUM_ARGS = 1; + + public SExpression call(Cons argList) { + // retrieve the number of arguments passed to LOAD + 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("LOAD"), argList); + String errMsg = "too " + + ((argListLength > NUM_ARGS) ? "many" : "few") + + " arguments given to LOAD: " + originalSExpr; + + throw new RuntimeException(errMsg); + } + + SExpression argCar = argList.getCar(); + + // make sure the argument is a string + if (! argCar.stringp()) { + throw new RuntimeException("LOAD: " + argCar + " is not a string"); + } + + LispString quotedName = (LispString) argCar; + String fileName = quotedName.toString(); + + // remove the surrounding quotes from the file name + fileName = fileName.substring(1, (fileName.length() - 1)); + + return processFile(fileName); + } + + // Evaluate all the S-expressions found in the file with the specified + // name. + // + // Parameters: fileName - the name of the file to be evaluated + // Returns: 'T' if the file was processed successfully; 'NIL' otherwise + private SExpression processFile(String fileName) { + LispParser parser = null; + + // attempt to create a new 'LispParser' on the specified file + try { + parser = new LispParser(new FileInputStream(fileName), fileName); + } catch (FileNotFoundException e) { + System.out.println("LOAD: could not open " + fileName); + + return Nil.getUniqueInstance(); + } + + // attempt to evaluate all the S-expressions contained in the file + while (! parser.eof()) { + try { + SExpression sexpr = parser.getSExpr(); + + EVAL.eval(sexpr); + } catch (RuntimeException e) { + System.out.println("LOAD: " + e.getMessage()); + + return Nil.getUniqueInstance(); + } catch (IOException e) { + System.out.println("LOAD: " + e.getMessage()); + + return Nil.getUniqueInstance(); + } + } + + // success! + return Symbol.T; + } + +} diff --git a/eval/LambdaExpression.java b/eval/LambdaExpression.java new file mode 100644 index 0000000..570d162 --- /dev/null +++ b/eval/LambdaExpression.java @@ -0,0 +1,73 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 2 + */ + +package eval; + +import parser.*; + +/** + * This class represents a Lisp FUNCTION in the PL-Lisp implementation. + */ +public class LambdaExpression extends SExpression { + + private Cons lexpr; + private UDFunction function; + + /** + * Create a new FUNCTION with the specified lambda expression and + * internal representation. + * + * @param lexpr + * the lambda expression of this FUNCTION + * @param function + * the internal representation of this FUNCTION + */ + public LambdaExpression(Cons lexpr, UDFunction function) { + this.lexpr = lexpr; + this.function = function; + } + + /** + * Test if this S-expression is a FUNCTION. + * + * @return + * true + */ + public boolean functionp() { + return true; + } + + /** + * Retrieve the lambda expression of this FUNCTION. + * + * @return + * the lambda expression of this FUNCTION + */ + public Cons getLExpression() { + return lexpr; + } + + /** + * Retrieve the internal representation of this FUNCTION. + * + * @return + * the user-defined function of this FUNCTION + */ + public UDFunction getFunction() { + return function; + } + + /** + * Returns a string representation of this FUNCTION. + * + * @return + * a string representation of this FUNCTION + */ + public String toString() { + return lexpr.toString(); + } + +} diff --git a/eval/LispFunction.java b/eval/LispFunction.java new file mode 100644 index 0000000..c861fa2 --- /dev/null +++ b/eval/LispFunction.java @@ -0,0 +1,44 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 1 + */ + +package eval; + +import parser.*; + +/** + * A LispFunction is an internal representation of a built-in + * function in the Lisp programming language. + */ +public abstract class LispFunction { + + /** + * Call this Lisp function with the given list of arguments. + * + * @param argList + * the list of arguments to pass to this function (MUST BE A PROPER LIST) + * @return + * the resulting S-expression of calling this function with the specified + * arguments + * @throws RuntimeException + * Indicates that an incorrect number of arguments has been passed to this + * function or that one of the arguments is not of the expected type. + */ + public abstract SExpression call(Cons argList); + + /** + * Determine if the arguments passed to this Lisp function should be + * evaluated. A subclass should override this method to return + * false if it does not want its arguments to be evaluated + * prior to being passed. + * + * @return + * true + */ + public boolean evaluateArguments() { + return true; + } + +} diff --git a/eval/MINUS.java b/eval/MINUS.java new file mode 100644 index 0000000..a913624 --- /dev/null +++ b/eval/MINUS.java @@ -0,0 +1,60 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 1 + */ + +package eval; + +import parser.*; + +/** + * MINUS represents the '-' function in Lisp. + */ +public class MINUS extends LispFunction { + + public SExpression call(Cons argList) { + // make sure we have received at least one argument + if (argList.nullp()) { + Cons originalSExpr = new Cons(new Symbol("-"), argList); + + throw new RuntimeException("too few arguments given to -: " + + originalSExpr); + } + + SExpression argFirst = argList.getCar(); + Cons argRest = (Cons) argList.getCdr(); + + // make sure that the first argument is a number + if (argFirst.numberp()) { + LispNumber num1 = (LispNumber) argFirst; + + if (argRest.nullp()) { + // there is only one argument, so return the additive + // inverse of the number + return new LispNumber(- num1.getValue()); + } + + SExpression argSecond = argRest.getCar(); + + // make sure that the next argument is a number as well + if (argSecond.numberp()) { + LispNumber num2 = (LispNumber) argSecond; + LispNumber difference = new LispNumber(num1.getValue() - + num2.getValue()); + SExpression argCddr = argRest.getCdr(); + + if (argCddr.consp()) { + return call(new Cons(difference, argCddr)); + } + + return difference; + } + + throw new RuntimeException("-: " + argSecond + " is not a number"); + } + + throw new RuntimeException("-: " + argFirst + " is not a number"); + } + +} diff --git a/eval/MULTIPLY.java b/eval/MULTIPLY.java new file mode 100644 index 0000000..ac15950 --- /dev/null +++ b/eval/MULTIPLY.java @@ -0,0 +1,34 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 1 + */ + +package eval; + +import parser.*; + +/** + * MULTIPLY represents the '*' function in Lisp. + */ +public class MULTIPLY extends LispFunction { + + public LispNumber call(Cons argList) { + if (argList.nullp()) { + return new LispNumber(1); + } + + SExpression argFirst = argList.getCar(); + Cons argRest = (Cons) argList.getCdr(); + + if (argFirst.numberp()) { + LispNumber num1 = (LispNumber) argFirst; + LispNumber num2 = call(argRest); + + return new LispNumber(num1.getValue() * num2.getValue()); + } + + throw new RuntimeException("*: " + argFirst + " is not a number"); + } + +} diff --git a/eval/NULL.java b/eval/NULL.java new file mode 100644 index 0000000..fd30367 --- /dev/null +++ b/eval/NULL.java @@ -0,0 +1,38 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 2 + */ + +package eval; + +import parser.*; + +/** + * NULL represents the NULL function in Lisp. + */ +public class NULL extends LispFunction { + + // The number of arguments that NULL takes. + private static final int NUM_ARGS = 1; + + public SExpression call(Cons argList) { + // retrieve the number of arguments passed to NULL + 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("NULL"), argList); + String errMsg = "too " + + ((argListLength > NUM_ARGS) ? "many" : "few") + + " arguments given to NULL: " + originalSExpr; + + throw new RuntimeException(errMsg); + } + + SExpression arg = argList.getCar(); + + return (arg.nullp() ? Symbol.T : Nil.getUniqueInstance()); + } + +} diff --git a/eval/PLUS.java b/eval/PLUS.java new file mode 100644 index 0000000..144f46c --- /dev/null +++ b/eval/PLUS.java @@ -0,0 +1,34 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 1 + */ + +package eval; + +import parser.*; + +/** + * PLUS represents the '+' function in Lisp. + */ +public class PLUS extends LispFunction { + + public LispNumber call(Cons argList) { + if (argList.nullp()) { + return new LispNumber(0); + } + + SExpression argFirst = argList.getCar(); + Cons argRest = (Cons) argList.getCdr(); + + if (argFirst.numberp()) { + LispNumber num1 = (LispNumber) argFirst; + LispNumber num2 = call(argRest); + + return new LispNumber(num1.getValue() + num2.getValue()); + } + + throw new RuntimeException("+: " + argFirst + " is not a number"); + } + +} diff --git a/eval/PRINT.java b/eval/PRINT.java new file mode 100644 index 0000000..f9fedb5 --- /dev/null +++ b/eval/PRINT.java @@ -0,0 +1,40 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 2 + */ + +package eval; + +import parser.*; + +/** + * PRINT represents the PRINT function in Lisp. + */ +public class PRINT extends LispFunction { + + // The number of arguments that PRINT takes. + private static final int NUM_ARGS = 1; + + public SExpression call(Cons argList) { + // retrieve the number of arguments passed to PRINT + 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("PRINT"), argList); + String errMsg = "too " + + ((argListLength > NUM_ARGS) ? "many" : "few") + + " arguments given to PRINT: " + originalSExpr; + + throw new RuntimeException(errMsg); + } + + SExpression arg = argList.getCar(); + + System.out.println(arg); + + return arg; + } + +} diff --git a/eval/QUOTE.java b/eval/QUOTE.java new file mode 100644 index 0000000..960ab3e --- /dev/null +++ b/eval/QUOTE.java @@ -0,0 +1,47 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 1 + */ + +package eval; + +import parser.*; + +/** + * QUOTE represents the QUOTE form in Lisp. + */ +public class QUOTE extends LispFunction { + + // The number of arguments that QUOTE takes. + private static final int NUM_ARGS = 1; + + public SExpression call(Cons argList) { + // retrieve the number of arguments passed to QUOTE + int argListLength = LENGTH.getLength(argList); + + // make sure we have received exactly one argument + if (argListLength != NUM_ARGS) { + Cons originalSExpr = new Cons(new Symbol("QUOTE"), argList); + String errMsg = "too " + + ((argListLength > NUM_ARGS) ? "many" : "few") + + " arguments given to QUOTE: " + originalSExpr; + + throw new RuntimeException(errMsg); + } + + return argList.getCar(); + } + + /** + * Determine if the arguments passed to this Lisp function should be + * evaluated. + * + * @return + * false + */ + public boolean evaluateArguments() { + return false; + } + +} diff --git a/eval/SETF.java b/eval/SETF.java new file mode 100644 index 0000000..90c25d4 --- /dev/null +++ b/eval/SETF.java @@ -0,0 +1,112 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 2 + */ + +package eval; + +import parser.*; + +/** + * SETF represents the SETF form in Lisp. + */ +public class SETF extends LispFunction { + + private static SymbolTable environment = new SymbolTable(); + + /** + * Look up the value of a symbol using its name. + * + * @param symbolName + * the name of the symbol to look up + * @return + * the value of symbolName if it has one; null otherwise + */ + public static SExpression lookup(String symbolName) { + SymbolTable current = environment; + + while (current != null) { + if (current.contains(symbolName)) { + return current.get(symbolName); + } + + current = current.getParent(); + } + + return null; + } + + /** + * Set the current environment to the specified value. + * + * @param newEnvironment + * the value to set the environment to + */ + public static void setEnvironment(SymbolTable newEnvironment) { + environment = newEnvironment; + } + + /** + * Retrieve the current environment. + * + * @return + * the current environment + */ + public static SymbolTable getEnvironment() { + return environment; + } + + // The number of arguments that SETF takes. + private static final int NUM_ARGS = 2; + + public SExpression call(Cons argList) { + // retrieve the number of arguments passed to SETF + 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("SETF"), argList); + String errMsg = "too " + + ((argListLength > NUM_ARGS) ? "many" : "few") + + " arguments given to SETF: " + originalSExpr; + + throw new RuntimeException(errMsg); + } + + SExpression symbol = argList.getCar(); + + // make sure the first argument is a symbol + if (! symbol.symbolp()) { + throw new RuntimeException("SETF: " + symbol + " is not a symbol"); + } + + Cons cdr = (Cons) argList.getCdr(); + SExpression value = EVAL.eval(cdr.getCar()); + + SymbolTable current = environment; + + // set 'current' to the symbol table that contains 'symbol' or the + // global symbol table if 'symbol' is not in the environment + while ((! current.contains(symbol.toString())) && + (current.getParent() != null)) { + current = current.getParent(); + } + + current.put(symbol.toString(), value); + + return value; + } + + /** + * Determine if the arguments passed to this Lisp function should be + * evaluated. + * + * @return + * false + */ + public boolean evaluateArguments() { + return false; + } + +} diff --git a/eval/SYMBOL_FUNCTION.java b/eval/SYMBOL_FUNCTION.java new file mode 100644 index 0000000..4a591ee --- /dev/null +++ b/eval/SYMBOL_FUNCTION.java @@ -0,0 +1,65 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 2 + */ + +package eval; + +import parser.*; + +/** + * SYMBOL_FUNCTION represents the SYMBOL-FUNCTION function in + * Lisp. + */ +public class SYMBOL_FUNCTION extends LispFunction { + + // The number of arguments that SYMBOL-FUNCTION takes. + private static final int NUM_ARGS = 1; + + public SExpression call(Cons argList) { + // retrieve the number of arguments passed to SYMBOL-FUNCTION + 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("SYMBOL-FUNCTION"), + argList); + String errMsg = "too " + + ((argListLength > NUM_ARGS) ? "many" : "few") + + " arguments given to SYMBOL-FUNCTION: " + + originalSExpr; + + throw new RuntimeException(errMsg); + } + + SExpression arg = argList.getCar(); + + // make sure the argument is a symbol + if (arg.symbolp()) { + LispFunction function = EVAL.lookupFunction(arg.toString()); + + // make sure the function actually exists + if (function != null) { + if (function instanceof UDFunction) { + // this is a user-defined function + + UDFunction udFunction = (UDFunction) function; + + return udFunction.getLexpr(); + } + + // this is a built-in function + + return new Symbol("SUBR-" + arg.toString()); + } + + throw new RuntimeException("SYMBOL-FUNCTION: undefined function " + + arg); + } + + throw new RuntimeException("SYMBOL-FUNCTION: " + arg + + " is not a symbol"); + } + +} diff --git a/eval/SymbolTable.java b/eval/SymbolTable.java new file mode 100644 index 0000000..aa72b28 --- /dev/null +++ b/eval/SymbolTable.java @@ -0,0 +1,90 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 2 + */ + +package eval; + +import parser.*; +import java.util.HashMap; + +/** + * A SymbolTable maps symbol names to values. + */ +public class SymbolTable { + + private HashMap table; + private SymbolTable parent; + + /** + * Create a new symbol table with no parent. + */ + public SymbolTable() { + this(null); + } + + /** + * Create a new symbol table with the specified parent. + * + * @param parent + * the parent of this symbol table + */ + public SymbolTable(SymbolTable parent) { + this.table = new HashMap(); + this.parent = parent; + } + + /** + * Determine if the specified symbol name is in this symbol table. + * + * @param symbolName + * the name of the symbol to look up + * @return + * true if the symbol is in this symbol table; + * false otherwise + */ + public boolean contains(String symbolName) { + return table.containsKey(symbolName); + } + + /** + * Returns the value to which the specified symbol name is mapped in this + * symbol table. + * + * @param symbolName + * the name of the symbol whose associated value is to be returned + * @return + * the value to which this symbol table maps symbolName, or + * null if no mapping exists + */ + public SExpression get(String symbolName) { + return table.get(symbolName); + } + + /** + * Associates the specified symbol name with the specified value in this + * symbol table. If the symbol table previously contained a mapping for + * this symbol name, the old value has been replaced. + * + * @param symbolName + * the name of the symbol with which the specified value is to be + * associated + * @param value + * the value to be associated with the specified symbol name + */ + public void put(String symbolName, SExpression value) { + table.put(symbolName, value); + } + + /** + * Returns the parent of this symbol table. + * + * @return + * the parent of this symbol table + */ + public SymbolTable getParent() { + return parent; + } + +} diff --git a/eval/UDFunction.java b/eval/UDFunction.java new file mode 100644 index 0000000..44e02c8 --- /dev/null +++ b/eval/UDFunction.java @@ -0,0 +1,122 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 2 + */ + +package eval; + +import parser.*; +import java.util.ArrayList; + +/** + * A UDFunction is an internal representation of a user-defined + * function in the Lisp programming language. + */ +public class UDFunction extends LispFunction { + + // the number of arguments that this user-defined function takes. + private final int NUM_ARGS; + + private String name; + private Cons body; + private Cons lexpr; + private SymbolTable environment; + private ArrayList parameters; + + /** + * Create a new user-defined function with the specified name, lambda list + * and body. + * + * @param name + * the name of this user-defined function + * @param lambdaList + * a list of the formal parameters of this user-defined function (MUST BE + * A PROPER LIST) + * @param body + * the body of this user-defined function (MUST BE A PROPER LIST) + */ + public UDFunction(String name, Cons lambdaList, Cons body) { + this.name = name; + this.body = body; + this.lexpr = new Cons(new Symbol(name), new Cons(lambdaList, body)); + this.environment = SETF.getEnvironment(); + this.parameters = new ArrayList(); + + // retrieve the names of all the formal parameters of this function + while (lambdaList.consp()) { + this.parameters.add(lambdaList.getCar().toString()); + + lambdaList = (Cons) lambdaList.getCdr(); + } + + this.NUM_ARGS = this.parameters.size(); + } + + public SExpression call(Cons argList) { + // retrieve the number of arguments passed to this function + 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(name), argList); + String errMsg = "too " + + ((argListLength > NUM_ARGS) ? "many" : "few") + + " arguments given to " + name + ": " + + originalSExpr; + + throw new RuntimeException(errMsg); + } + + // push a new symbol table onto this function's environment (for its + // parameters) + environment = new SymbolTable(environment); + + // bind the values of the arguments to the formal parameter names + for (String param : parameters) { + SExpression currentArg = argList.getCar(); + + environment.put(param, currentArg); + + argList = (Cons) argList.getCdr(); + } + + // store the environment of the S-expression that called this function + // (the current environment) + SymbolTable currentEnvironment = SETF.getEnvironment(); + + // replace the current environment with the environment of this + // function + SETF.setEnvironment(environment); + + Cons currentSExpression = body; + SExpression retval = null; + + // evaluate all the S-expressions making up this function's body + while (currentSExpression.consp()) { + retval = EVAL.eval(currentSExpression.getCar()); + currentSExpression = (Cons) currentSExpression.getCdr(); + } + + // replace the environment of the S-expression that called this + // function + SETF.setEnvironment(currentEnvironment); + + // remove the bindings of the arguments to the formal parameter names + // in the environment of this function + environment = new SymbolTable(environment.getParent()); + + return retval; + } + + /** + * Return the lambda expression that represents this user-defined function. + * + * @return + * the lambda expression that represents this user-defined function + */ + public Cons getLexpr() { + return lexpr; + } + +} diff --git a/eval/package.html b/eval/package.html new file mode 100644 index 0000000..e9c971d --- /dev/null +++ b/eval/package.html @@ -0,0 +1,4 @@ + + Provides functions and forms to be used during the evaluation of an + S-expression. + diff --git a/extend.lisp b/extend.lisp new file mode 100644 index 0000000..403108d --- /dev/null +++ b/extend.lisp @@ -0,0 +1,50 @@ +(defun extend-null (the-list) + (cond + ((equal (length the-list) 0) t) + (t nil) + ) +) + +(defun mapcar (function-name the-list) + (cond + ((null the-list) nil) + (t (cons (funcall function-name (first the-list)) + (mapcar function-name (rest the-list)))) + ) +) + +(defun maplist (function-name the-list) + (cond + ((null the-list) nil) + (t (cons (funcall function-name the-list) + (maplist function-name (rest the-list)))) + ) +) + +(defun extend-apply (function-name param-list) + (eval (cons function-name param-list))) + +(defun append (listA listB) + (cond + ((null listA) listB) + (t (cons (first listA) (append (rest listA) listB))) + ) +) + +(defun second (listA) (first (rest listA))) +(defun third (listA) (first (rest (rest listA)))) +(defun fourth (listA) (first (rest (rest (rest listA))))) +(defun fifth (listA) (first (rest (rest (rest (rest listA)))))) +(defun sixth (listA) (first (rest (rest (rest (rest (rest listA))))))) +(defun seventh (listA) (first (rest (rest (rest (rest (rest (rest listA)))))))) +(defun eighth (listA) (first (rest (rest (rest (rest (rest (rest (rest listA))))))))) +(defun ninth (listA) (first (rest (rest (rest (rest (rest (rest (rest (rest listA)))))))))) +(defun tenth (listA) (first (rest (rest (rest (rest (rest (rest (rest (rest (rest listA))))))))))) + +(defun nth (n listA) + (cond + ((equal 0 n) (first listA)) + (t (nth (- n 1) (rest listA))) + ) +) + diff --git a/fact.lisp b/fact.lisp new file mode 100644 index 0000000..2ed5fc4 --- /dev/null +++ b/fact.lisp @@ -0,0 +1,3 @@ +(defun fact (x) + (cond ((> 2 x) 1) + (t (* x (fact (- x 1)))))) diff --git a/main/LispInterpreter.java b/main/LispInterpreter.java new file mode 100644 index 0000000..3fbb5d6 --- /dev/null +++ b/main/LispInterpreter.java @@ -0,0 +1,91 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter 1 + */ + +package main; + +import parser.*; +import eval.*; +import error.ErrorManager; +import java.io.*; +import java.text.MessageFormat; + +/** + * LispInterpreter is an interpreter for the Lisp programming + * language. It takes the name of a file as a command-line argument, evaluates + * the S-expressions found in the file and then prints the results to the + * console. If no file name is provided at the command-line, this program will + * read from standard input. + */ +public class LispInterpreter { + + private static final String GREETING = "SUNY Potsdam Lisp Interpreter - Version 1.0.1"; + private static final String PROMPT = "~ "; + + public static final String ANSI_RESET = "\u001B[0m"; + public static final String ANSI_GREEN = "\u001B[32m"; + + /** + * Evaluate the S-expressions found in the file given as a command-line + * argument and print the results to the console. If no file name was + * given, retrieve the S-expressions from standard input. + * + * @param args + * the command-line arguments: + *
    + *
  • args[0] - file name (optional)
  • + *
+ */ + public static void main(String[] args) { + LispParser parser = null; + boolean interactive = false; + + if (args.length > 0) { + // a file name was given at the command-line, attempt to create a + // 'LispParser' on it + try { + parser = new LispParser(new FileInputStream(args[0]), args[0]); + } catch (FileNotFoundException e) { + ErrorManager.generateError(e.getMessage(), ErrorManager.CRITICAL_LEVEL); + } + } else { + // no file name was given, create a 'LispParser' on standard input + parser = new LispParser(System.in, "System.in"); + interactive = true; + + System.out.println(GREETING); + System.out.println(); + System.out.print(PROMPT); + } + + while (! parser.eof()) { + try { + SExpression sexpr = parser.getSExpr(); + String result = MessageFormat.format("{0}{1}{2}", ANSI_GREEN, EVAL.eval(sexpr), ANSI_RESET); + + LispInterpreter.erasePrompt(interactive); + System.out.println(result); + } catch (RuntimeException e) { + LispInterpreter.erasePrompt(interactive); + ErrorManager.generateError(e.getMessage(), 2); + } catch (IOException e) { + ErrorManager.generateError(e.getMessage(), ErrorManager.CRITICAL_LEVEL); + } + + if (interactive) { + System.out.print(PROMPT); + } + } + } + + private static void erasePrompt(boolean interactive) { + if (interactive) { + for (int i = 0; i < PROMPT.length(); i++) { + System.out.print("\b"); + } + } + } + +} diff --git a/main/LispInterpreter2.java b/main/LispInterpreter2.java new file mode 100644 index 0000000..0c6726a --- /dev/null +++ b/main/LispInterpreter2.java @@ -0,0 +1,50 @@ +package main; + +import parser.*; +import eval.*; +import error.ErrorManager; +import java.io.*; +import java.net.*; +import remotefs.RemoteFileInputStream; + +public class LispInterpreter2 { + + public static final String HOST_NAME = "localhost"; + public static final int PORT_NUM = 5150; + + public static void main(String[] args) { + LispParser parser = null; + + if (args.length > 0) { + try { + InetAddress host = InetAddress.getByName(HOST_NAME); + RemoteFileInputStream remoteIn = + new RemoteFileInputStream(host, PORT_NUM, args[0]); + + parser = new LispParser(remoteIn, args[0]); + } catch (UnknownHostException e) { + ErrorManager.generateError(e.getMessage(), + ErrorManager.CRITICAL_LEVEL); + } catch (FileNotFoundException e) { + ErrorManager.generateError(e.getMessage(), + ErrorManager.CRITICAL_LEVEL); + } + } else { + parser = new LispParser(System.in, "System.in"); + } + + while (! parser.eof()) { + try { + SExpression sexpr = parser.getSExpr(); + + System.out.println(EVAL.eval(sexpr)); + } catch (RuntimeException e) { + ErrorManager.generateError(e.getMessage(), 2); + } catch (IOException e) { + ErrorManager.generateError(e.getMessage(), + ErrorManager.CRITICAL_LEVEL); + } + } + } + +} diff --git a/main/LispParserDriver.java b/main/LispParserDriver.java new file mode 100644 index 0000000..d0ffc46 --- /dev/null +++ b/main/LispParserDriver.java @@ -0,0 +1,65 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Parser + */ + +package main; + +import parser.*; +import error.ErrorManager; +import java.io.*; + +/** + * LispParserDriver is a program that takes the name of a file + * as a command-line argument, creates an internal representation of the + * S-expressions found in the file and prints them to the console. If no file + * name is provided at the command-line, this program will read from standard + * input. + */ +public class LispParserDriver { + + /** + * Create internal representations of the S-expressions found in the file + * whose name was given as a command-line argument and print them to the + * console. If no file name was given, retrieve the S-expressions from + * standard input. + * + * @param args + * the command-line arguments: + *
    + *
  • args[0] - file name (optional)
  • + *
+ */ + public static void main(String[] args) { + LispParser parser = null; + + if (args.length > 0) { + // a file name was given at the command-line, attempt to create a + // 'LispParser' on it + try { + parser = new LispParser(new FileInputStream(args[0]), args[0]); + } catch (FileNotFoundException e) { + ErrorManager.generateError(e.getMessage(), + ErrorManager.CRITICAL_LEVEL); + } + } else { + // no file name was given, create a 'LispParser' on standard input + parser = new LispParser(System.in, "System.in"); + } + + while (! parser.eof()) { + try { + SExpression sexpr = parser.getSExpr(); + + System.out.println(sexpr.toString()); + } catch (RuntimeException e) { + ErrorManager.generateError(e.getMessage(), 2); + } catch (IOException e) { + ErrorManager.generateError(e.getMessage(), + ErrorManager.CRITICAL_LEVEL); + } + } + } + +} diff --git a/main/LispScannerDriver.java b/main/LispScannerDriver.java new file mode 100644 index 0000000..13447c3 --- /dev/null +++ b/main/LispScannerDriver.java @@ -0,0 +1,69 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter Phase 1 - Lexical Analysis + */ + +package main; + +import scanner.*; +import error.ErrorManager; +import java.io.*; + +/** + * LispScannerDriver is a program that takes the name of a file + * as a command-line argument, retrieves all of the Lisp tokens from the file + * and prints them to the console. If no file name is provided at the + * command-line, this program will read from standard input. + */ +public class LispScannerDriver { + + /** + * Obtain the Lisp tokens from the file whose name was given as a + * command-line argument and print them to the console. If no file name was + * given, retrieve the tokens from standard input. + * + * @param args + * the command-line arguments: + *
    + *
  • args[0] - file name (optional)
  • + *
+ */ + public static void main(String[] args) { + LispScanner in = null; + + if (args.length > 0) { + // a file name was given at the command-line, attempt to create a + // 'LispScanner' on it + try { + in = new LispScanner(new FileInputStream(args[0]), args[0]); + } catch (FileNotFoundException e) { + ErrorManager.generateError(e.getMessage(), + ErrorManager.CRITICAL_LEVEL); + } + } else { + // no file name was given, create a 'LispScanner' on standard input + in = new LispScanner(System.in, "System.in"); + } + + Token t = null; + + do { + try { + t = in.nextToken(); + + System.out.printf("%-15s%-25s%5d%5d%25s\n", t.getType(), + t.getText(), + t.getLine(), + t.getColumn(), + t.getFName()); + } catch (RuntimeException e) { + ErrorManager.generateError(e.getMessage(), 2); + } catch (IOException e) { + ErrorManager.generateError(e.getMessage(), + ErrorManager.CRITICAL_LEVEL); + } + } while ((t == null) || (t.getType() != Token.Type.EOF)); + } + +} diff --git a/main/package.html b/main/package.html new file mode 100644 index 0000000..bfdbdab --- /dev/null +++ b/main/package.html @@ -0,0 +1,3 @@ + + Provides test drivers for the various stages of the Lisp Interpreter. + diff --git a/parser/Atom.java b/parser/Atom.java new file mode 100644 index 0000000..465de81 --- /dev/null +++ b/parser/Atom.java @@ -0,0 +1,46 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Parser + */ + +package parser; + +/** + * This class represents an ATOM in the PL-Lisp implementation. + */ +public class Atom extends SExpression { + + private String text; + + /** + * Create a new ATOM with the specified text. + * + * @param text + * the text representing this ATOM + */ + public Atom(String text) { + this.text = text; + } + + /** + * Test if this S-expression is an ATOM. + * + * @return + * true + */ + public boolean atomp() { + return true; + } + + /** + * Returns a string representation of this ATOM. + * + * @return + * a string representation of this ATOM + */ + public String toString() { + return text; + } + +} diff --git a/parser/Cons.java b/parser/Cons.java new file mode 100644 index 0000000..2402a99 --- /dev/null +++ b/parser/Cons.java @@ -0,0 +1,111 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Parser + */ + +package parser; + +/** + * This class represents a Lisp CONS cell in the PL-Lisp implementation. + */ +public class Cons extends SExpression { + + private SExpression car; + private SExpression cdr; + + /** + * Create a new CONS cell with the specified car and cdr. + * + * @param car + * the car of this CONS cell + * @param cdr + * the cdr of this CONS cell + */ + public Cons(SExpression car, SExpression cdr) { + this.car = car; + this.cdr = cdr; + } + + /** + * Retrieve the car of this CONS cell. + * + * @return + * the car of this CONS cell + */ + public SExpression getCar() { + return car; + } + + /** + * Retrieve the cdr of this CONS cell. + * + * @return + * the cdr of this CONS cell + */ + public SExpression getCdr() { + return cdr; + } + + /** + * Set the car of this CONS cell to the specified value. + * + * @param newCar + * the value to assign to the car of this CONS cell + */ + public void setCar(SExpression newCar) { + car = newCar; + } + + /** + * Set the cdr of this CONS cell to the specified value. + * + * @param newCdr + * the value to assign to the cdr of this CONS cell + */ + public void setCdr(SExpression newCdr) { + cdr = newCdr; + } + + /** + * Test if this S-expression is a CONS cell. + * + * @return + * true + */ + public boolean consp() { + return true; + } + + /** + * Returns a string representation of this CONS cell. + * + * @return + * a string representation of this CONS cell + */ + public String toString() { + return ("(" + toStringAux()); + } + + // Returns a string representation of the car of a CONS cell followed by + // its cdr. If the cdr of this CONS cell is not a CONS cell itself, this + // method places a ')' at the end of its return value. Also, if the cdr of + // this CONS cell is NIL, it will not be included in the return value of + // this method. When used in conjunction with the 'toString' method of this + // class, this method provides a means for creating the correct string + // representation of a list. + // + // Returns: a string representation of the car of a CONS cell followed by + // its cdr + private String toStringAux() { + if (cdr.nullp()) { + return (car.toString() + ")"); + } else if (cdr.consp()) { + return (car.toString() + " " + ((Cons) cdr).toStringAux()); + } + + // the cdr of this CONS cell is not a list + return (car.toString() + " . " + cdr.toString() + ")"); + } + +} diff --git a/parser/LispNumber.java b/parser/LispNumber.java new file mode 100644 index 0000000..34c3bde --- /dev/null +++ b/parser/LispNumber.java @@ -0,0 +1,67 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Parser + */ + +package parser; + +/** + * This class represents a NUMBER in the PL-Lisp implementation. + */ +public class LispNumber extends Atom { + + private int value; + + /** + * Create a new NUMBER with the specified text. + * + * @param text + * the text representing this NUMBER + * @throws IllegalArgumentException + * Indicates that text does not represent a valid integer. + */ + public LispNumber(String text) { + super(text.replaceFirst("^0+(?!$)", "")); + + try { + this.value = Integer.parseInt(text); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(text + + " is not a valid integer"); + } + } + + /** + * Create a new NUMBER with the specified value. + * + * @param value + * the integer value of this NUMBER + */ + public LispNumber(int value) { + super(Integer.toString(value)); + + this.value = value; + } + + /** + * Test if this S-expression is a NUMBER. + * + * @return + * true + */ + public boolean numberp() { + return true; + } + + /** + * Retrieve the integer value of this NUMBER. + * + * @return + * the integer value of this NUMBER + */ + public int getValue() { + return value; + } + +} diff --git a/parser/LispParser.java b/parser/LispParser.java new file mode 100644 index 0000000..9a7733f --- /dev/null +++ b/parser/LispParser.java @@ -0,0 +1,192 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Parser + */ + +package parser; + +import scanner.*; +import java.io.*; + +/** + * A LispParser converts a stream of bytes into internal + * representations of Lisp S-expressions. When the end of stream has been + * reached the eof method of this parser will return true. + */ +public class LispParser { + + private LispScanner scanner; + private Token nextToken; + + // A field to store an exception that has been thrown in the 'eof' method + // as a result of reading in the next token from 'scanner'. + private Exception delayedException; + + // A field to notify us if the next token has already been stored in + // 'nextToken' by the 'eof' method. + private boolean nextTokenStored; + + /** + * Create a new LispParser that produces S-expressions from + * the specified input stream. + * + * @param in + * the input stream to obtain S-expressions from (must not be + * null) + * @param fileName + * the name of the file that in is reading from + */ + public LispParser(InputStream in, String fileName) { + scanner = new LispScanner(in, fileName); + nextToken = null; + delayedException = null; + nextTokenStored = false; + } + + /** + * Determing if this parser has reached the end of its underlying input + * stream. + * + * @return + * true if this parser has reached the end of its underlying + * input stream; false otherwise + */ + public boolean eof() { + if (! nextTokenStored) { + // attempt to read the next token from 'scanner' and store it in + // 'nextToken' + try { + nextToken = scanner.nextToken(); + nextTokenStored = true; + } catch (Exception e) { + // this method should give the illusion of not actually reading + // a token, so we store any exceptions thrown as a result of + // reading in the next token from 'scanner' (it will be thrown + // the next time the 'getSExpr' method is called) + delayedException = e; + + if (nextToken == null) { + // we have not successfully read in any tokens yet, so we + // could not have read in an end-of-file token + + return false; + } + } + } + + return (nextToken.getType() == Token.Type.EOF); + } + + /** + * Returns the next S-expression from this parser. + * + * @return + * the next S-expression from this parser + * @throws RuntimeException + * Indicates that an illegal S-expression was encountered in the + * underlying input stream. + * @throws IOException + * Indicates that an I/O error has occurred. + */ + public SExpression getSExpr() throws IOException { + if (delayedException != null) { + // the 'eof' method has stored an exception for us to throw + + // determine the type of the stored exception and throw it! + if (delayedException instanceof IOException) { + IOException e = (IOException) delayedException; + + // remove the exception from 'delayedException' + delayedException = null; + + throw e; + } else if (delayedException instanceof RuntimeException) { + RuntimeException e = (RuntimeException) delayedException; + + // remove the exception from 'delayedException' + delayedException = null; + + throw e; + } + } + + if (! nextTokenStored) { + // the next token has not been stored in 'nextToken' by the 'eof' + // method + + nextToken = scanner.nextToken(); + } else { + // the 'eof' method has been called and has read in the next token + // already to determine if we have reached the end-of-file + + nextTokenStored = false; + } + + return sExpr(); + } + + // sExpr ::= NUMBER | IDENTIFIER | RESERVED | STRING | QUOTE_MARK sExpr | + // LEFT_PAREN sExprTail + // + // Returns: an S-expression that matches the rules given above + // Throws: RuntimeException - Indicates that an illegal S-expression was + // encountered in the underlying input stream. + // Throws: IOException - Indicates that an I/O error has occurred. + // Precondition: 'nextToken' is not null. + private SExpression sExpr() throws IOException { + // determine the type of 'nextToken' and create the appropriate + // S-expression + switch (nextToken.getType()) { + case NUMBER: + return new LispNumber(nextToken.getText()); + case IDENTIFIER: + case RESERVED: + return new Symbol(nextToken.getText()); + case STRING: + return new LispString(nextToken.getText()); + case QUOTE_MARK: + nextToken = scanner.nextToken(); + SExpression arg = sExpr(); + + return new Cons(new Symbol("QUOTE"), + new Cons(arg, Nil.getUniqueInstance())); + case LEFT_PAREN: + return sExprTail(); + case RIGHT_PAREN: + throw new RuntimeException("expression begins with \')\'" + + " - line " + nextToken.getLine() + + " column " + nextToken.getColumn()); + case EOF: + throw new RuntimeException("end-of-file encountered" + + " - line " + nextToken.getLine() + + " column " + nextToken.getColumn()); + default: // unrecognized token type + throw new RuntimeException("unrecognizable token" + + " - line " + nextToken.getLine() + + " column " + nextToken.getColumn()); + } + } + + // sExprTail ::= RIGHT_PAREN | sExpr sExprTail + // + // Returns: an S-expression that matches the rules given above + // Throws: IOException - Indicates that an I/O error has occurred. + // Precondition: 'scanner' is not null. + private SExpression sExprTail() throws IOException { + nextToken = scanner.nextToken(); + + // determine the type of 'nextToken' and create the appropriate + // S-expression + switch (nextToken.getType()) { + case RIGHT_PAREN: + return Nil.getUniqueInstance(); + default: + SExpression car = sExpr(); + SExpression cdr = sExprTail(); + + return new Cons(car, cdr); + } + } + +} diff --git a/parser/LispString.java b/parser/LispString.java new file mode 100644 index 0000000..66b56a3 --- /dev/null +++ b/parser/LispString.java @@ -0,0 +1,34 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Parser + */ + +package parser; + +/** + * This class represents a STRING in the PL-Lisp implementation. + */ +public class LispString extends Atom { + + /** + * Create a new STRING with the specified text. + * + * @param text + * the text representing this STRING + */ + public LispString(String text) { + super(text); + } + + /** + * Test if this S-expression is a STRING. + * + * @return + * true + */ + public boolean stringp() { + return true; + } + +} diff --git a/parser/Nil.java b/parser/Nil.java new file mode 100644 index 0000000..5344053 --- /dev/null +++ b/parser/Nil.java @@ -0,0 +1,104 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Parser + */ + +package parser; + +/** + * This class represents NIL in the PL-Lisp implementation. + */ +public class Nil extends Cons { + + private static Nil uniqueInstance = new Nil(); + + /** + * Retrieve the single unique instance of NIL. + * + * @return + * the single unique instance of NIL + */ + public static Nil getUniqueInstance() { + return uniqueInstance; + } + + // We are using the Singleton pattern, so the constructor for 'Nil' is + // private. + private Nil() { + super(null, null); + + // the car and cdr of NIL both refer to NIL + super.setCar(this); + super.setCdr(this); + } + + /** + * Test if this S-expression is NULL. + * + * @return + * true + */ + public boolean nullp() { + return true; + } + + /** + * Test if this S-expression is an ATOM. + * + * @return + * true + */ + public boolean atomp() { + return true; + } + + /** + * Test if this S-expression is a CONS cell. + * + * @return + * false + */ + public boolean consp() { + return false; + } + + /** + * Test if this S-expression is a SYMBOL. + * + * @return + * true + */ + public boolean symbolp() { + return true; + } + + /** + * Set the car of this CONS cell to the specified value. This method does + * nothing (the car of NIL can not be changed). + * + * @param newCar + * the value to assign to the car of this CONS cell + */ + public void setCar(SExpression newCar) {} + + /** + * Set the cdr of this CONS cell to the specified value. This method does + * nothing (the cdr of NIL can not be changed). + * + * @param newCdr + * the value to assign to the cdr of this CONS cell + */ + public void setCdr(SExpression newCdr) {} + + /** + * Returns a string representation of NIL. + * + * @return + * a string representation of NIL + */ + public String toString() { + return "NIL"; + } + +} diff --git a/parser/SExpression.java b/parser/SExpression.java new file mode 100644 index 0000000..bb8aaa4 --- /dev/null +++ b/parser/SExpression.java @@ -0,0 +1,122 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Parser + */ + +package parser; + +/** + * This is the base class for memory in the PL-Lisp implementation. + */ +public class SExpression { + + // for mark and sweep garbage collection + private boolean marked = false; + + /** + * Determine if this SExpression is marked. + * + * @return + * true if this SExpression is marked; + * false otherwise + */ + public final boolean isMarked() { + return marked; + } + + /** + * Set the marked status of this S-expression to the specified value. + * + * @param value + * the value to assign to this S-expression's marked status + */ + public final void setMarked(final boolean value) { + marked = value; + } + + // Lisp type predicates; + // by default, all return false (an SExpression effectively has NO type); + // overridden in subclasses to describe their Lisp type + + /** + * Test if this S-expression is NULL. + * + * @return + * false + */ + public boolean nullp() { + return false; + } + + /** + * Test if this S-expression is an ATOM. + * + * @return + * false + */ + public boolean atomp() { + return false; + } + + /** + * Test if this S-expression is a CONS cell. + * + * @return + * false + */ + public boolean consp() { + return false; + } + + /** + * Test if this S-expression is a LIST. + * + * @return + * the value of (consp() || nullp()) + */ + public boolean listp() { + return (consp() || nullp()); + } + + /** + * Test if this S-expression is a NUMBER. + * + * @return + * false + */ + public boolean numberp() { + return false; + } + + /** + * Test if this S-expression is a SYMBOL. + * + * @return + * false + */ + public boolean symbolp() { + return false; + } + + /** + * Test if this S-expression is a FUNCTION. + * + * @return + * false + */ + public boolean functionp() { + return false; + } + + /** + * Test if this S-expression is a STRING. + * + * @return + * false + */ + public boolean stringp() { + return false; + } + +} diff --git a/parser/Symbol.java b/parser/Symbol.java new file mode 100644 index 0000000..96f1a96 --- /dev/null +++ b/parser/Symbol.java @@ -0,0 +1,37 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Parser + */ + +package parser; + +/** + * This class represents a SYMBOL in the PL-Lisp implementation. + */ +public class Symbol extends Atom { + + /** This SYMBOL represents TRUE in the PL-Lisp implementation. */ + public static final Symbol T = new Symbol("T"); + + /** + * Create a new SYMBOL with the specified text. + * + * @param text + * the text representing this SYMBOL + */ + public Symbol(String text) { + super(text.toUpperCase()); + } + + /** + * Test if this S-expression is a SYMBOL. + * + * @return + * true + */ + public boolean symbolp() { + return true; + } + +} diff --git a/parser/package.html b/parser/package.html new file mode 100644 index 0000000..ed4dbdd --- /dev/null +++ b/parser/package.html @@ -0,0 +1,4 @@ + + Provides the classes necessary for creating an internal representation of + the Lisp programming language. + diff --git a/reverse.lisp b/reverse.lisp new file mode 100644 index 0000000..6b751ff --- /dev/null +++ b/reverse.lisp @@ -0,0 +1,23 @@ +(defun reverse (the-list) + (cond + (the-list (append (reverse (rest the-list)) + (list (first the-list)) + ) + ) + ) +) + +(defun deep-reverse (the-list) + (cond + (the-list (append (deep-reverse (rest the-list)) + (list (cond + ((listp (first the-list)) + (deep-reverse (first the-list)) + ) + (t (first the-list)) + ) + ) + ) + ) + ) +) diff --git a/scanner/LispFilterStream.java b/scanner/LispFilterStream.java new file mode 100644 index 0000000..d9838dc --- /dev/null +++ b/scanner/LispFilterStream.java @@ -0,0 +1,160 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter Phase 1 - Lexical Analysis + */ + +package scanner; + +import java.io.InputStream; +import java.io.FilterInputStream; +import java.io.IOException; + +/** + * A LispFilterStream is an input stream that returns the bytes + * from an underlying input stream with all Lisp comments removed and replaced + * with newlines. + */ +public class LispFilterStream extends FilterInputStream { + + private boolean inQuote; + + /** + * Creates a LispFilterStream with the specified underlying + * InputStream. + * + * @param in + * the underlying input stream (must not be null) + */ + public LispFilterStream(InputStream in) { + super(in); + + inQuote = false; + } + + /** + * Reads the next byte of data from this input stream. The value byte is + * returned as an int in the range 0 to + * 255. If no byte is available because the end of the stream + * has been reached, the value -1 is returned. + * + * @return + * the next byte of data, or -1 if the end of the stream has + * been reached. + * @throws IOException + * Indicates that an I/O error has occurred. + */ + @Override + public int read() throws IOException { + int next = super.read(); + + if ((next == ';') && (! inQuote)) { + // we have entered a comment, consume all the bytes from the + // underlying input stream until we reach a newline character or + // the end of the stream + while ((next != '\n') && (next != -1)) { + next = super.read(); + } + } else if (next == '\n') { + inQuote = false; + } else if (next == '\"') { // we have entered or left a quoted string + inQuote = (! inQuote); + } + + return next; + } + + /** + * Reads up to the specified number of data bytes from this input stream + * into an array of bytes starting at the specified offset. If no bytes are + * available because the end of this stream has been reached then + * -1 is returned. Also, If the specified number of bytes is + * more than the number of remaining data bytes in this input stream, then + * only the number of remaining data bytes are copied into the byte array. + * + * @param b + * the buffer into which the data bytes are read (must not be + * null) + * @param off + * the start offset in b at which the data is written (must + * be >= 0 and < b.length) + * @param len + * the maximum number of bytes to read into b (len + + * off must be < b.length) + * @return + * the total number of bytes read into the buffer, or -1 if + * there is no more data because the end of the stream has been reached + * @throws IOException + * Indicates that the first byte could not be read into b + * for some reason other than reaching the end of the stream. + * @throws IndexOutOfBoundsException + * Indicates that this method attempted to access an index in + * b that was either negative or greater than or equal to + * b.length as a result of the given parameters. + * @throws NullPointerException + * Indicates that b is null. + */ + @Override + public int read(byte[] b, int off, int len) throws IOException { + int bytesRead = 0; + + // make sure we are supposed to read at least one byte into 'b' + if (len > 0) { + int next = read(); + + if (next == -1) { + // there are no more bytes to read from this input stream + + return -1; + } + + int i = off; + + while (next != -1) { + ++bytesRead; + + b[i++] = (byte) next; + + if (i >= (off + len)) { // we have read 'len' bytes into 'b' + break; + } + + try { + next = read(); + } catch (IOException e) { + // treat this exception like an end of stream + + break; + } + } + } + + return bytesRead; + } + + /** + * Skip over and discard the specified number of bytes from this input + * stream. This method may, for a variety of reasons, end up skipping some + * smaller number of bytes, possibly 0. The actual number of + * bytes skipped is returned. + * + * @param n + * the number of bytes to be skipped + * @return + * the actual number of bytes skipped + * @throws IOException + * Indicates that an I/O error has occurred. + */ + @Override + public long skip (long n) throws IOException { + long bytesSkipped = 0; + + while ((n > 0) && (read() != -1)) { + ++bytesSkipped; + --n; + } + + return bytesSkipped; + } + +} diff --git a/scanner/LispScanner.java b/scanner/LispScanner.java new file mode 100644 index 0000000..df6df9b --- /dev/null +++ b/scanner/LispScanner.java @@ -0,0 +1,320 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter Phase 1 - Lexical Analysis + */ + +package scanner; + +import java.io.InputStream; +import java.io.BufferedInputStream; +import java.io.IOException; + +/** + * A LispScanner converts a stream of bytes into a stream of Lisp + * tokens. When the end of stream has been reached a token with a type of + * Token.Type.EOF is returned from the nextToken + * method of this scanner. + */ +public class LispScanner { + + private LispFilterStream inStream; + private Token currToken; + private String fileName; + private int line; + private int column; + + /** + * Create a new LispScanner that produces Lisp tokens from the + * specified input stream. + * + * @param in + * the input stream to obtain Lisp tokens from (must not be + * null) + * @param fileName + * the name of the file that in is reading from + */ + public LispScanner(InputStream in, String fileName) { + this.inStream = new LispFilterStream(new BufferedInputStream(in)); + this.currToken = null; + this.fileName = fileName; + this.line = 1; + this.column = 0; + } + + /** + * Returns the same Lisp token returned from the last call to the + * nextToken method of this scanner. In the case that no calls + * to nextToken have been made yet, this method returns + * null. + * + * @return + * the last Lisp token returned from this scanner or null (if + * no tokens have been returned from this scanner yet) + */ + public Token getCurrToken() { + return currToken; + } + + /** + * Returns the next Lisp token from this scanner. + * + * @return + * the next Lisp token from this scanner. + * @throws RuntimeException + * Indicates that an illegal character or an unterminated quoted string + * was encountered in the input stream (not counting comments). + * @throws IOException + * Indicates that an I/O error has occurred. + */ + public Token nextToken() throws IOException { + currToken = retrieveNextToken(); + + return currToken; + } + + // Retrieve the next Lisp token from 'inStream'. + // + // Returns: the next Lisp token found in 'inStream' + // Precondition: 'inStream' must not be null. + // Throws: RuntimeException - Indicates that an illegal character or an + // unterminated quoted string was encountered in + // 'inStream'. + // Throws: IOException - Indicates that an I/O error has occurred. + private Token retrieveNextToken() throws IOException { + int c; + + while ((c = inStream.read()) != -1) { + char nextChar = (char) c; + + ++column; + + // determine the type of the Lisp token from the character obtained + // from 'inStream' + switch (nextChar) { + case '\n': + // we have hit a new line so increment 'line' and reset + // 'column' + ++line; + column = 0; + + break; + case '(': + return new Token(Token.Type.LEFT_PAREN, + "(", + fileName, + line, + column); + case ')': + return new Token(Token.Type.RIGHT_PAREN, + ")", + fileName, + line, + column); + case '\'': + return new Token(Token.Type.QUOTE_MARK, + "\'", + fileName, + line, + column); + case '\"': + return retrieveString(nextChar); + default: + if (Character.isWhitespace(nextChar)) { // skip whitespace + continue; + } else if (Character.isDigit(nextChar)) { // number + return retrieveNumber(nextChar); + } else if (isLegalIdChar(nextChar)) { // identifier + return retrieveIdentifier(nextChar); + } else { + // 'nextChar' can not start any Lisp token + + throw new RuntimeException("illegal character " + + "\'" + nextChar + "\'" + + " - line " + line + + " column " + column); + } + } + } + + // we have reached the end of 'inStream' so we return an end-of-file + // token + return new Token(Token.Type.EOF, "EOF", fileName, line, column); + } + + // Retrieve a quoted string token from 'inStream'. + // + // Parameters: firstDoubleQuote - the opening double quote of this quoted + // string + // Returns: a quoted string token obtained from 'instream' + // Throws: RuntimeException - Indicates that this quoted string was + // missing its terminating double quote. + // Throws: IOException - Indicates that an I/O error has occurred. + // Precondition: 'firstDoubleQuote' must be the leading double quote + // character of this quoted string and 'inStream' must not + // be null. + private Token retrieveString(char firstDoubleQuote) throws IOException { + StringBuffer text = new StringBuffer(); + int startLine = line; + int startColumn = column; + char prevChar = firstDoubleQuote; + + text.append(firstDoubleQuote); + + int c; + + while ((c = inStream.read()) != -1) { + char nextChar = (char) c; + + ++column; + text.append(nextChar); + + switch(nextChar) { + case '\n': + ++line; + column = 0; + + break; + case '\"': + if (prevChar != '\\') { + // we have found the terminating double quote + + return new Token(Token.Type.STRING, + text.toString(), + fileName, + startLine, + startColumn); + } + + // this is an escaped double quote + } + + prevChar = nextChar; + } + + // the end of 'inStream' was reached before the terminating double + // quote + + throw new RuntimeException("unterminated quoted string" + + " - line " + startLine + + " column " + startColumn); + } + + // Retrieve a number token from 'inStream'. + // + // Parameters: firstDigit - the first digit of this number + // Returns: a number token obtained from 'inStream' + // Throws: IOException - Indicates that an I/O error has occurred. + // Precondition: 'firstDigit' must be the first digit of this number and + // 'inStream' must not be null. + private Token retrieveNumber(char firstDigit) throws IOException { + StringBuffer text = new StringBuffer(); + int startColumn = column; + + text.append(firstDigit); + inStream.mark(1); + + int c; + + while ((c = inStream.read()) != -1) { + char nextChar = (char) c; + + if (Character.isDigit(nextChar)) { + // 'nextChar' is a digit in this number + + text.append(nextChar); + ++column; + } else { + // we have reached the end of the number + + inStream.reset(); // unread the last character + + return new Token(Token.Type.NUMBER, + text.toString(), + fileName, + line, + startColumn); + } + + inStream.mark(1); + } + + // there are no more bytes to be read from 'inStream' after this number + // token + + return new Token(Token.Type.NUMBER, + text.toString(), + fileName, + line, + startColumn); + } + + // Retrieve an identifier token from 'inStream'. + // + // Parameters: firstChar - the first character of this identifier + // Returns: an identifier token obtained from 'inStream' + // Throws: IOException - Indicates that an I/O error has occurred. + // Precondition: 'firsChar' must be the first character of this identifier + // and 'inStream' must not be null. + private Token retrieveIdentifier(char firstChar) throws IOException { + StringBuffer text = new StringBuffer(); + int startColumn = column; + + text.append(firstChar); + inStream.mark(1); + + int c; + + while ((c = inStream.read()) != -1) { + char nextChar = (char) c; + + if (isLegalIdChar(nextChar)) { + // 'nextChar' is part of the identifier + + text.append(nextChar); + ++column; + } else { + // we have reached the end of this identifier + + inStream.reset(); // unread the last character + + return new Token(Token.Type.IDENTIFIER, + text.toString(), + fileName, + line, + startColumn); + } + + inStream.mark(1); + } + + // there are no more bytes to be read from 'inStream' after this + // identifier token + + return new Token(Token.Type.IDENTIFIER, + text.toString(), + fileName, + line, + startColumn); + } + + // Test if a character is legal to be contained within an identifier in + // Lisp. + // + // Returns: 'true' if the character can be found within an identifier in + // Lisp; 'false' otherwise + private boolean isLegalIdChar(char c) { + return ((! Character.isWhitespace(c)) && (c != '\"') + && (c != '\'') + && (c != '\\') + && (c != '`') + && (c != '(') + && (c != ')') + && (c != '[') + && (c != ']') + && (c != '#') + && (c != '.') + && (c != ';')); + } + +} diff --git a/scanner/Token.java b/scanner/Token.java new file mode 100644 index 0000000..c410102 --- /dev/null +++ b/scanner/Token.java @@ -0,0 +1,126 @@ +/* + * Name: Mike Cifelli + * Course: CIS 443 - Programming Languages + * Assignment: Lisp Interpreter Phase 1 - Lexical Analysis + */ + +package scanner; + +/** + * A Token represents a token in Common Lisp. + */ +public class Token { + + /** + * An enumeration representing all of the types of tokens found in Common + * Lisp. + */ + public enum Type { + /** A left parenthesis token */ + LEFT_PAREN, + + /** A right parenthesis token */ + RIGHT_PAREN, + + /** A quoted string token */ + STRING, + + /** A quote mark */ + QUOTE_MARK, + + /** A number token */ + NUMBER, + + /** A reserved word token */ + RESERVED, + + /** An identifier token */ + IDENTIFIER, + + /** An end-of-file token */ + EOF + } + + private Type type; + private String text; + private String fName; + private int line; + private int column; + + /** + * Create a new token with the specified type, text, file name, line number + * and column number. + * + * @param type + * the type of this token + * @param text + * the text associated with this token + * @param fName + * the name of the file that this token is located in + * @param line + * the line number that this token is found on + * @param column + * the column number that this token is found on + */ + public Token(Type type, String text, String fName, int line, int column) { + this.type = type; + this.text = text; + this.fName = fName; + this.line = line; + this.column = column; + } + + /** + * Accessor method to determine the type of this token. + * + * @return + * the type of this token + */ + public Type getType() { + return type; + } + + /** + * Accessor method to determine the text associated with this token. + * + * @return + * the text associated with this token + */ + public String getText() { + return text; + } + + /** + * Accessor method to determine the name of the file that this token was + * located in. + * + * @return + * the name of the file that this token was located in + */ + public String getFName() { + return fName; + } + + /** + * Accessor method to determine the line number that this token was found + * on. + * + * @return + * the line number this token was found on + */ + public int getLine() { + return line; + } + + /** + * Accessor method to determine the column number that this token was found + * on. + * + * @return + * the column number this token was found on + */ + public int getColumn() { + return column; + } + +} diff --git a/scanner/package.html b/scanner/package.html new file mode 100644 index 0000000..6be4f13 --- /dev/null +++ b/scanner/package.html @@ -0,0 +1,4 @@ + + Provides the classes necessary to perform a lexical analysis of the Lisp + programming language. + diff --git a/test.lisp b/test.lisp new file mode 100644 index 0000000..16aa63f --- /dev/null +++ b/test.lisp @@ -0,0 +1,15 @@ +;; A list containing the values of single-letter Roman numerals. +(setf *roman-number-list* + '((I 1) (V 5) (X 10) (L 50) (C 100) (D 500) (M 1000))) + +;; Converts a single Roman numeral letter into its equivalent decimal value. +(defun letter-to-decimal (letter) + (car (cdr ((lambda (lst f) + (cond ((null lst) ()) + ((eq (car (car lst)) letter) (car lst)) + (t (funcall f (cdr lst) f)))) + *roman-number-list* + (lambda (lst f) + (cond ((null lst) ()) + ((eq (car (car lst)) letter) (car lst)) + (t (funcall f (cdr lst) f))))))))