The class inheritance hierarchy I used is as follows:
Exp: Implements all of the productions for Exp. The only method defined by Exp is the abstract method eval, which takes a rowIndex as a parameter and returns a double:
abstract double eval(int rowIndex) throws UndefinedCellException;The rowIndex is needed in case the expression has generic references. In this case the rowIndex is used to convert the generic reference to a concrete reference. For example, suppose the user has input the expression:
a[id=1-3] = b[id] + c[id]and that the user wants to evaluate a[2]. a[1], a[2], and a[3] should all point to the same expression tree which means that 2 must be passed to eval so that when b[id] and c[id] get evaluated, they can use the 2 to retrieve b[2] and c[2] respectively.
BinaryExp: Implements all of the productions that have a left and right operand for an Expression. Its subclasses are:
AndExp DivideExp EqualsExp GreaterEqualExp GreaterThanExp LessEqualExp LessThanExp MinusExp MultiplyExp NotEqualsExp OrExp PlusExp
ConditionalExp: Implements the conditional (exp ? exp : exp) construct.
FctExp: The superclass for the min, max, and sum functions. FctExp maintains a parameter list of VarExp objects. Each VarExp object represents one of the cells referenced in the parameter list. For example, given the call "sum(a[i], b[1-3, 4, 8-9])", the parameter list would consist of VarExp objects for a[i], b[1], b[2], b[3], b[4], b[8], and b[9]. The appropriate function iterates through this list and calculates the appropriate result. The parameter list is constructed as follows:
NumberExp: Wraps a number
UnaryMinusExp: The class that implements a unary minus operator. Its eval function negates an expression.
VarExp: A variable expression is a reference to either a specific cell (e.g., b[3]) or a generic cell (e.g., b[id]). A VarExp keeps track of the column's name (e.g., b) and its row index. If the reference is a generic reference then it sets the row index to -1. Here is the API:
class VarExp extends Exp { // the number of rows for each column in the spreadsheet static final int NUMROWS = 100; // The hash table is keyed on the name of a column. Each column name has // a value which is an array of Cell objects, one for each row in the // column static Hashtable<String, Cell[]> varTable = new Hashtable<String, Cell[]>(); String colName; int row; static Cell getCell(String colName, int rowIndex) throws UndefinedCellException: returns a Cell object by using colName to retrieve the column's Cell array and then using the rowIndex to retrieve the specific Cell object. Throws an UndefinedCellException if the column's name is not included in the hash table or if the entry in the Cell array is null. static void setCell(String colName, int rowIndex, Exp formula): gets the Cell object corresponding to colName/rowIndex by retrieving the column's Cell array from the hash table and then using the row index to retrieve the actual Cell object. Then it sets the Cell's formula variable to point to formula. public double eval(int rowNum) throws UndefinedCellException: Finds the appropriate Cell object using getCell and then calls the Cell's eval method. If the VarExp is a generic reference (i.e., its row index is -1) then eval uses the rowNum that is passed in to locate the cell. Otherwise eval uses rowIndex stored by VarExp. A Cell's eval method may throw an UndefinedCellException and VarExp's eval method does not handle it so the method header specifies that UndefinedCellException gets thrown. public VarExp(String id, int rowNum) { colName = id; row = rowNum; }Cell (no superclass): used to record the value of a cell, the expression used to compute the cell's value, whether or not the cell has been evaluated, and whether or not the cell's value is undefined. Here is the API for a Cell:
class Cell { Exp formula; boolean upToDate = false; // useful for the next project assignment boolean undef = false; // whether the cell's value is undefined double value = 0; public Cell(Exp expression) { formula = expression; } public void setCell(Exp expression) { formula = expression; upToDate = false; undef = false; } double eval(int rowNum) throws UndefinedCellException: If the cell's undef flag is true then the eval method throws an UndefinedCellException. Otherwise the eval method checks if the cell's value is up-to-dateby checking the upToDate flag. If the cell's value is not up-to-date, then the eval method marks it up-to-date, marks its value as undefined, and evaluates the formula. If the formula successfully evaluated, the cell is marked as defined. The reason for marking it undefined before the evaluation is that if the evaluation of the formula fails, then the cell's value should be undefined. Once the cell's value is up-to-date, its value is returned.RowRef (no superclass): Keeps track of the beginning and end of a range of rows (e.g., 3-6).
UndefinedCellException: thrown when a formula is being evaluated and it references a cell that does not yet exist, or a cell whose value is marked undefined. An UndefinedCellException contains a cell name and a row number. An UndefinedCellException is caught by the parser when it tries to evaluate a formula. When caught, the parser prints out a message saying that the given cell is undefined and then a message indicating that the cell whose formula is being evaluated is undefined.
interpreter.java reads lines of input, creates a parser object to parse each line of input, and parses the input. The parser parses the formula, and if it is correct, assigns it to a cell(s). The parser also evaluates the formula for each cell to which the formula is assigned. Here is the code I used for interpeter.java:
package formula; import java.io.*; import java.util.*; import org.antlr.runtime.*; class interpreter { public static void main(String args[]) { interpreter singleton = new interpreter(); singleton.execute(); } interpreter() {} void execute() { java.util.Scanner input = new java.util.Scanner(System.in); FormulaParser formulaParser; FormulaLexer lex; while (true) { try { // read a formula expression and evaluate it System.out.print(">>> "); lex = new FormulaLexer(new ANTLRReaderStream(new StringReader(input.nextLine() + "\n"))); CommonTokenStream tokens = new CommonTokenStream(lex); formulaParser = new FormulaParser(tokens); formulaParser.prog(); } catch (RecognitionException e) { e.printStackTrace(); } catch (java.util.NoSuchElementException e) { break; } catch (Exception e) {System.out.println(e);} } } }
I performed error handling as follows:
@rulecatch { catch (RecognitionException re) { errorFree = false; reportError(re); recover(input,re); } }The rulecatch block allows you to specify catch statements for different exceptions thrown by antlr or by your own code. The last two lines in this catch statement are the default lines used by the parser. The first line is mine. RecognitionError is the superclass of all exceptions thrown by the antlr parser.