/**** *

* ------------------------------------------------
* SIMPLE INTRODUCTORY VERSION,
* WITH ASSIGNMENT, ADDITION, AND MULTIPLICATION,
* ON INTEGER TYPES ONLY
* ------------------------------------------------
*

*

* PascalInterpreter is a parse-tree interpreter for a subset of the Pascal * programming language. Its primary public method is eval, which takes a * parse tree and symbol table, and evaluates the tree if it is an executable * portion of a program. *

* The interpreter works with an Object-valued program memory array. Prior to * calling eval, the initInterpreter method is used to allocate array storage * and perform other initialization. The constructor calls initInterpreter, * and it may be called subsequently for reinitialization. *

* Calls to eval use the memory array as the working store for the program * being evaluated. The memory retains its state between calls to eval, unless * initInterpreter is called again, or an already-allocated memory is * re-initialized to null using the clearMemory method. *

* All of the public methods called by eval are publicly accessible. * However, they are likely of lesser value to outside callers, given that eval * dispatches to the appropriate submethods based on the type of tree it * receives. *

* See the method comments for further details. * */ public class PascalInterpreter { /** * Construct this with the given static pool and stack size. Call * initInterpter to do the work. */ public PascalInterpreter (int staticSize, int stackSize) { initInterpreter(staticSize, stackSize); } /** * Evaluate the given tree node, in the context of the given symbol table. * The tree must be that of an executable Pascal construct, which is one of * the following: a list of statements, a single statement, or an * expression. *

* For statement evaluation, the output of eval is a modified program * memory. For expression evaluation, output is the returned Value, and a * modified memory if the expression has side effects. The evaluation of * statements and statement lists always produces a null return value. * * CSC 330 NOTE: The statement structure of EJay is a bit different than * Pascal. In particular, data decls are intermingled with stmts in a * block. This is not the case in a Pascal compound statement. So, you'll * have to do things a bit differently at the top level of the EJay eval * method than is done here for Pascal. One thing for sure, you should not * print out an error if you see a data decl here. You should either pick * out the stmts in advance, or ignore data decls if they come up. * * ANOTHER 330 NOTE: To simplify Assignment 4, no nested blocks will be * part of the test data, so you don't have to deal with them here. */ public Value eval(TreeNode node, SymbolTable symtab) { if (isStmtList(node)) { evalStmtList((TreeNodeList) node, symtab); return null; } else if (isStmt(node)) { evalStmt(node, symtab); return null; } else if (isExpr(node)) { return evalExpr(node, symtab); } else { Utilities.error(node, "Node type " + symNames.map[node.id] + " is unevaluatable."); return null; } } /** * Evaluate a stmtlist by looping through each and call eval on it. */ public void evalStmtList(TreeNodeList nodes, SymbolTable symtab) { TreeNode node; TreeNodeList rest; boolean done = false; for (node = nodes.node, rest = nodes.siblings; !done; ) { eval(node, symtab); if (rest == null) { done = true; } else { node = rest.node; rest = rest.siblings; } } } /** * Evaluate an individual Pascal statement, which is one of null, * assignment, if, procedure call, or compound. */ public void evalStmt(TreeNode stmt, SymbolTable symtab) { switch (stmt.id) { case 0: break; case sym.ASSMNT: evalAssmnt((TreeNode2) stmt, symtab); break; case sym.IF: evalIf((TreeNode3) stmt, symtab); break; case sym.PROCEDURE: evalProcCall((TreeNode2) stmt, symtab); break; case sym.BEGIN: eval(((TreeNode1) stmt).child, symtab); } } /** * Evaluate an assignment statement by evaluating the LHS as an l-value and * the RHS as an r-value expression. If the type of the LHS and RHS are * the same, then assign the r-value to the memory location designated by * the l-value. If the LHS does not designate a memory location, output an * error message. Also output an error if the LHS and RHS types differ. */ public void evalAssmnt(TreeNode2 assmntStmt, SymbolTable symtab) { LValue lval = evalIdentLValue((LeafNode) assmntStmt.child1, symtab); Value rval = evalExpr(assmntStmt.child2, symtab); /* * Outta here if the LHS does not designate a memory address. */ if (lval == null) { Utilities.error(assmntStmt.child1, "The LHS of an assignment must be an assignable designator."); return; } /* * Also outta here if RHS is null, which means there was an error * during its evaluation. The error message(s) for this will have * already occurred, so don't do any more. */ if (rval == null) { return; } /* * Do the assignment if the LHS and RHS are the same type. This is the * BOTTOM LINE when it comes to the evaluation of assignment. * * Have a close look here -- we're defining assignment in EJay using * assignment in Java. It's taken a bit of work to get here, but * ultimately this is what interpretation is about. I.e., the * fundamental constructs of the language being interpreted are defined * in terms of the fundamental constructs of the language in which the * interpreter is implemented. Pretty curious, actually. */ if (Types.equiv(lval.type, rval.type)) { memory[lval.getVal()] = rval.val; } else { Utilities.error(assmntStmt, "The type of the LHS and RHS of an assignment must be the same."); } } /** * Eval an if by first evaluating its test expr. Then, eval the then part * of the statement if the test expr is true, else eval the else part if * present, or return if the if part is false. If the value is not * boolean, or its value is null, then output and error and return without * evaluating either part of the statement. */ public void evalIf(TreeNode3 ifStmt, SymbolTable symtab) { Value ifTest = evalExpr(ifStmt.child1, symtab); if (Types.isBool(ifTest.type)) { /* evaluate the then or else, as appropriate */ } else { Utilities.error(ifStmt.child1, "The type of the expression in an if statement must be boolean"); } } /** * Eval a proc call by performing these steps. * * (1) looking up the procedure name in the symtab
* (2) pushing an activation record onto the stack
* (3) binding actuals to formals, in the callING environment
* (4) eval'ing the proc body, in the callED environment
* (5) popping the activation record and returning the value, if any
* * Output an error message under the following circumstances: * * (1) the proc name lookup fails or is not that of a procedure
* (2) the number or types of actuals and formals do not agree
*/ public Value evalProcCall(TreeNode2 procCall, SymbolTable symtab) { // // CSC 330 NOTE: The implementation details of this are left for your // work in assignment 4. We'll discuss conceptual details in class. // return null; } /** * Eval an expr by switching on its node id. This will the id of a literal * value, an identifier, or an operator. All cases are handled in the * appropriate methods. */ public Value evalExpr(TreeNode expr, SymbolTable symtab) { switch (expr.id) { case sym.INT: return evalInt((LeafNode) expr); // // Other literals go here. // case sym.IDENT: return evalIdent((LeafNode) expr, symtab); case sym.PLUS: return evalPlus((TreeNode2) expr, symtab); case sym.TIMES: return evalTimes((TreeNode2) expr, symtab); // // Other binary and unary exprs go here. // } return null; // to shut the compiler up } /** * Eval an addition expression by evaluating both operands, adding them * together, and returning a Value for the result. Output an error if both * operands are not the same numeric type, and return null. Also, return * null if either or both operands evaluates to null. */ public Value evalPlus(TreeNode2 expr, SymbolTable symtab) { Value v1 = eval(expr.child1, symtab); Value v2 = eval(expr.child2, symtab); /* * Propagate a null return from either or both args. No new error * message is necessary here. */ if ((v1 == null) || (v2 == null)) { return null; } /* * Output an error if both operands aren't the same numeric type. */ if (! ((v1.type.id == v2.type.id) && Types.equiv(v1.type, v2.type))) { Utilities.error(expr, "Operands of '+' must be the same numeric type."); } /* * Add the value of v2 to v1, and return v1. In order to cast out in * Java, we have to separate the int and float cases. Note that in * Java 1.5, these casts are not necessary. * * This is another BOTTOM LINE place, where some real work is getting * done. Here we are implementing EJay addition directly in terms of * Java's addition. */ if (Types.isInt(v1.type)) { v1.val = new Integer(((Integer)v1.val).intValue() + ((Integer) v2.val).intValue()); } else { v1.val = new Float(((Float)v1.val).floatValue() + ((Float) v2.val).floatValue()); } return v1; } /** * A la evalPlus. */ public Value evalTimes(TreeNode2 expr, SymbolTable symtab) { Value v1 = eval(expr.child1, symtab); Value v2 = eval(expr.child2, symtab); if ((v1 == null) || (v2 == null)) { return null; } if (! ((v1.type.id == v2.type.id) && Types.equiv(v1.type, v2.type))) { Utilities.error(expr, "Operands of '*' must be the same numeric type."); } if (Types.isInt(v1.type)) { v1.val = new Integer(((Integer)v1.val).intValue() * ((Integer) v2.val).intValue()); } else { v1.val = new Float(((Float)v1.val).floatValue() * ((Float) v2.val).floatValue()); } return v1; } /** * Evaluate a designator expression as an r-value. In Pascal, a designator * is either an identifier for an array reference. In the case of * identifier, this method just passes the buck to evalIdent. The case of * an array ref will be dealt with in the next version of the example (Week * 6). */ public Value evalDesig(TreeNode desig, SymbolTable symtab) { if (isIdent(desig)) { return evalIdent((LeafNode) desig, symtab); } // // CSC 330 NOTE: Compute the LValue for an array or struct selector. // return null; } /** * Eval the given ident as an r-value. First, look it up in the given * symtab. If an entry is found, get the address of the ident via getAddr. * Then return a new Value that contains the memory value at the computed * address, and the type of the ident. If the ident lookup fails, or if * the entry is not a variable, then output an error message and return * null. */ public Value evalIdent(LeafNode ident, SymbolTable symtab) { SymbolTableEntry entry = symtab.lookup((String) ident.value); if (entry == null) { Utilities.error(ident, "Identifier " + ident.value + " is not defined."); return null; } if (! entry.getClass().getName().equals("VariableEntry")) { Utilities.error(ident, "Identifier " + ident.value + " is not a variable."); return null; } int addr = getAddr((VariableEntry) entry); return new Value(memory[addr], entry.type); } /** * Eval the given designator as an l-value. This follows the same * procedure as evalDesig, except here a new LValue is returned with the * address value, instead of the contents of memory at that address. */ public LValue evalDesigLValue(TreeNode desig, SymbolTable symtab) { if (isIdent(desig)) { return evalIdentLValue((LeafNode) desig, symtab); } // // CSC 330 NOTE: Compute the LValue for an array or struct selector. // return null; } /** * Eval the given ident as an l-value. This follows the same procedure as * evalIdent, except here a new LValue is returned with the address value, * instead of the contents of memory at that address. */ public LValue evalIdentLValue(LeafNode ident, SymbolTable symtab) { SymbolTableEntry entry = symtab.lookup((String) ident.value); if (entry == null) { Utilities.error(ident, "Identifier " + ident.value + " is not defined."); return null; } if (! entry.getClass().getName().equals("VariableEntry")) { Utilities.error(ident, "Identifier " + ident.value + " is not a variable."); return null; } int addr = getAddr((VariableEntry) entry); return new LValue(new Integer(addr), entry.type); } /** * Eval an integer literal by making a new value for it. */ public Value evalInt(LeafNode integer) { return new Value(integer.value, Types.IntType); } /** * Return true if the given node is an identifier. */ public boolean isIdent(TreeNode node) { return node.id == sym.IDENT; } /** * Get the memory address of the given variable entry. If the entry is at * level 0, then the address is relative to the 0th element of the static * pool. If the lexical level of the entry is > 0, then the address is * relative to the current tos. */ public int getAddr(VariableEntry entry) { if (entry.level == 0) { return entry.memoryLocation; } else { return entry.memoryLocation + tos; } } /** * Return true if the given tree node is a statement list. */ public boolean isStmtList(TreeNode node) { return node.getClass().getName().equals("TreeNodeList"); } /** * Return true if the given tree node is a statement. */ public boolean isStmt(TreeNode node) { return (node.id == sym.ASSMNT) || (node.id == sym.IF) || (node.id == sym.PROCEDURE) || (node.id == sym.BEGIN) || (node.id == 0); // a null stmt } /** * Return true if the given tree node is an expr. */ public boolean isExpr(TreeNode node) { return (node.id == sym.IDENT) || (node.id == sym.REAL) || (node.id == sym.INT) || (node.id == sym.CHAR) || (node.id == sym.LEFT_BRKT) || (node.id == sym.LESS) || (node.id == sym.GTR) || (node.id == sym.EQ) || (node.id == sym.LESS_EQ) || (node.id == sym.GTR_EQ) || (node.id == sym.NOT_EQ) || (node.id == sym.PLUS) || (node.id == sym.MINUS) || (node.id == sym.OR) || (node.id == sym.TIMES) || (node.id == sym.DIVIDE) || (node.id == sym.AND) || (node.id == sym.NOT); } /** * Initialize the interpreter by allocating the memory and initializing the * top-of-stack pointer (tos). tos is initialized to the value of the * memory index after the end of the static pool, which is the value of the * given staticSize parameter. The total size of memory is staticSize + * stackSize. The stackSize and stackSize data fields are also * initialized, for subsequent runtime reference. */ public void initInterpreter(int staticSize, int stackSize) { memory = Memory.allocate(staticSize + stackSize); tos = staticSize; curActRecSize = 0; this.staticSize = staticSize; this.stackSize = stackSize; } /** * Clear an already-allocated memory by setting all of its elements to * null. Initialize the tos to staticSize, i.e., the stack is empty. */ public void clearMemory() { Memory.clear(memory); tos = staticSize; curActRecSize = 0; } /** * Dump all of currently used memory to stdout, both static pool and * stack. */ public void dumpMemory() { System.out.println(); dumpStaticPool(); dumpStack(); } /** * Dump the static pool to stdout. */ public void dumpStaticPool() { System.out.println("Static Pool:"); Memory.dump(memory, staticSize); System.out.println(); } /** * Dump the stack to stdout, starting with tos as the relative 0 address in * the dump. */ public void dumpStack() { System.out.print("Stack:"); if (tos == staticSize) { System.out.println(" empty"); } else { System.out.println(); Memory.dumpRelative(memory, staticSize + 1, tos + curActRecSize); } } /** Program memory, organized logically with a static pool on the top, and * a growable stack starting after the last allocated element of static * memory. Organizing static and stack storage into a single common memory * pool allows uniform handling of static and stack addresses. Viz., both * types of address are an integer index into the common memory. Static * addresses are relative to offset 0. Stack addresses are relative to the * current value of the tos index. */ public Object[] memory; /** Current top-of-stack index. */ public int tos; /** The fixed size of the static memory pool, as initialized by * initInterpreter. */ public int staticSize; /** The maximum size of the stack, as initialized by initInterpreter. */ public int stackSize; /** Size of the most recently pushed activation. This is only used as a * convenience for full stack dumps. */ public int curActRecSize; }