import java.io.*; import java.util.*; /** * Parse an input stream containing an FM language source file. * Implements a recursive descent predictive parser according * to the FM language grammar. * * @author Hyung-Joon Kim */ public class Parser { //----------------------------------------------------------------- /** * theToken is always the Token that we should look at next. * When a parse method returns, theToken contains a reference * to the token that follows the end of the non-terminal that * was just parsed. */ private Token theToken; /** prevToken is always the last Token that has been matched. * This means that when matchToken succeeds, prevToken contains * a reference to the Token that was just matched. */ private Token prevToken; /** * The CompilerIO object that we can use to write on the * output stream if needed. */ private CompilerIO cio; /** * The Scanner that actually reads the input stream and * reports the tokens that it finds. */ private Scanner scan; /** * This status variable is set to true before the parse * begins. If any of the parse methods encounter a problem, * they set this variable to false. After the parse is * complete, the status is returned to the caller. */ private boolean status; /** This Throwable is used to get stack trace information */ private Throwable tracer = new Throwable(); /** Enable / disable method entry/exit tracing. */ private boolean showMethods = false; /** Enable / disable Symbol entry display. */ private boolean showSymbols = false; /** Enable / disable code generation. */ private boolean showCode = false; /** * HashMap containing int arrays of Token types, keyed by the * non-terminal whose FIRST set each array represents. */ private HashMap firstSets; /** Symbol table manager. */ private SymbolTable symbolTable; /** * Define the symbol tables that we use in the parser. * This number is the symbol table index. */ private static final int SC_KEYWORD = 0; private static final int SC_VARIABLE = 1; //----------------------------------------------------------------- /** * Construct a new Parser object. Remember the CompilerIO object * so that we can use it for output in the parser. Remember the * SymbolTable object so that we can use it to store information * about program symbols. Remember the Scanner object that supplies * us with a Token stream to parse. * Initialize the HashMap firstSets to hold int[] objects containing * the Token identifier values for the FIRST sets of various non-terminals. * The name of the associated non-terminal is used as the key. * The pageNumber variable is declared and initialized to 0 in the * SymbolTable. * @param io the CompilerIO object to use in reading and writing files. * @param t the SymbolTable manager. Table 0 is the reserved words * table, table 1 is used for program symbols. * @param s the lexical scanner that actually reads the input file */ public Parser(CompilerIO io, SymbolTable t, Scanner s) { cio = io; scan = s; symbolTable = t; Symbol pageNumber = new Symbol("pageNumber",Symbol.VARIABLE); pageNumber.putAttribute("value",new Integer(0)); symbolTable.putSymbol(SC_VARIABLE,"pageNumber",pageNumber); firstSets = new HashMap(); firstSets.put("prologBlock",new int[] {Token.KW_PROLOG}); firstSets.put("prologStatement",new int[] {Token.ID}); firstSets.put("pageBlocksTail",new int[] {Token.KW_SHOW}); firstSets.put("pageBlock",new int[] {Token.KW_SHOW}); firstSets.put("pageStatement",new int[] {Token.LCURLY,Token.KW_IF,Token.ID}); firstSets.put("exprTail",new int[] {Token.OP_ADD,Token.OP_SUB}); firstSets.put("termTail",new int[] {Token.OP_MUL,Token.OP_DIV}); firstSets.put("factor",new int[] {Token.INTEGER,Token.REAL,Token.LPAREN,Token.ID}); firstSets.put("expr",new int[] {Token.INTEGER,Token.REAL,Token.LPAREN,Token.ID}); firstSets.put("callEnd",new int[] {Token.DOT, Token.LPAREN}); firstSets.put("assignEnd",new int[] {Token.OP_ASSIGN}); firstSets.put("exprListTail",new int[] {Token.COMMA}); } /** * Starting with the first Token in the input stream, * try to derive a parse tree for the entire program. * @return true if parsed correctly, false if error * @throws IOException */ public boolean parse() throws IOException { status = true; prevToken = null; generateCode("%!PS-Adobe-3.0"); theToken = scan.nextToken(); parseProgram(); return status; } /** * Parse non-terminal: program. *
	* program ::= movie name { movieBody } EOF
	* 
* @throws IOException */ private void parseProgram() throws IOException { traceEntry(); try { matchToken(Token.KW_MOVIE); matchToken(Token.ID); generateMovieStart(prevToken.getLabel()); matchToken(Token.LCURLY); parseMovieBody(); matchToken(Token.RCURLY); matchToken(Token.EOF); generateMovieEnd(); } catch (SyntaxException e) { processSyntaxException(e); } traceExit(); } /** * Parse non-terminal: movieBody. *
	* movieBody ::= prologBlock pageBlocks
	* movieBody ::= pageBlocks
	* 
* @throws IOException */ private void parseMovieBody() throws SyntaxException, IOException { traceEntry(); generatePrologStart(); if (isFirst(theToken, "prologBlock")) { parsePrologBlock(); } generatePrologEnd(); parsePageBlocks(); traceExit(); } /** * Parse non-terminal: prologBlock. *
	* prologBlock ::= prolog { prologStatements }
	* 
*/ private void parsePrologBlock() throws SyntaxException { traceEntry(); matchToken(Token.KW_PROLOG); matchToken(Token.LCURLY); parsePrologStatements(); matchToken(Token.RCURLY); traceExit(); } /** * Parse non-terminal: prologStatements. *
	* prologStatements ::= prologStatement prologTail
	* 
*/ private void parsePrologStatements() throws SyntaxException { traceEntry(); parsePrologStatement(); parsePrologTail(); traceExit(); } /** * Parse non-terminal: prologTail. *
	* prologTail ::= prologStatement prologTail
	* prologTail ::= epsilon
	* 
*/ private void parsePrologTail() throws SyntaxException { traceEntry(); if (isFirst(theToken, "prologStatement")) { parsePrologStatement(); parsePrologTail(); } else { // epsilon -- do nothing } traceExit(); } /** * Parse non-terminal: prologStatement. *
	* prologStatement ::= variableDeclaration
	* 
*/ private void parsePrologStatement() throws SyntaxException { traceEntry(); parseVariableDeclaration(); traceExit(); } /** * Parse non-terminal: variableDeclaration. *
	* variableDeclaration ::= id : type ( );
	* variableDeclaration ::= id : type ( exprList );
	* 
*/ private void parseVariableDeclaration() throws SyntaxException { traceEntry(); // Match name of variable and add to symbol table matchToken(Token.ID); Symbol var = new Symbol(prevToken.getLabel(),Symbol.VARIABLE); symbolTable.putSymbol(SC_VARIABLE,var.getLabel(),var); matchToken(Token.COLON); generateCode("/"+var.getLabel()); // ps literal symbol // Get type of variable and add this as an attribute of the variable matchToken(Token.ID); String type = prevToken.getLabel(); var.putAttribute("CLASSNAME",type); // Get arguments, if any matchToken(Token.LPAREN); if (theToken.getType()!=Token.RPAREN) { parseExprList(); } matchToken(Token.RPAREN); generateCode(type+"."+type+" def"); // call constructor and store result traceSymbol(var); matchToken(Token.SEMICOLON); traceExit(); } /** * Parse non-terminal: pageBlocks. *
	* pageBlocks ::= pageBlock pageBlocksTail
	* 
*/ private void parsePageBlocks() throws SyntaxException { traceEntry(); parsePageBlock(); parsePageBlocksTail(); traceExit(); } /** * Parse non-terminal: pageBlocksTail. *
	* pageBlocksTail ::= pageBlock pageBlocksTail
	* pageBlocksTail ::= epsilon
	* 
*/ private void parsePageBlocksTail() throws SyntaxException { traceEntry(); if (isFirst(theToken,"pageBlock")) { parsePageBlock(); parsePageBlocksTail(); } else { // epsilon } traceExit(); } /** * Parse non-terminal: pageBlock. *
	* pageBlock ::= show ( integer ) { pageStatements }
	* 
*/ private void parsePageBlock() throws SyntaxException { traceEntry(); matchToken(Token.KW_SHOW); matchToken(Token.LPAREN); matchToken(Token.INTEGER); // Store the number of pages to be generated so that // we can use for loop control. int numPages = prevToken.getIntValue(); matchToken(Token.RPAREN); matchToken(Token.LCURLY); // Open the buffer in order to emit the pagestatement to it. cio.openBuffer(); parsePageStatements(); // Close the buffer cio.closeBuffer(); matchToken(Token.RCURLY); // Get the current pageNumber, which is updated in SymbolTable and in // the generated Postscript code before each page is written to the // output file. Symbol curPage = symbolTable.getSymbol(SC_VARIABLE, "pageNumber"); int count = ((Integer)curPage.getAttribute("value")).intValue(); // Generate the code for each stand-alone page in Postscript if (showCode) { // %%Page: x.rel cur // rel := relative page number in the current page block // cur := current page number with all pages previously written for (int i = 1 ; i <= numPages; i++) { generateCode("%%Page: x"+"."+i+" "+(count+i)); generateCode("save"); generateCode("/pageNumber "+(count+i)+" def"); cio.emitBuffer(); generateCode("restore"); generateCode("showpage"); } // Update the value of the current page number so that it will be // used appropriately for the next pageBlock, if any. curPage.putAttribute("value", new Integer(count+numPages)); } traceExit(); } /** * Parse non-terminal: pageStatements. *
	* pageStatements ::= pageStatement pageTail
	* 
*/ private void parsePageStatements() throws SyntaxException { traceEntry(); parsePageStatement(); parsePageTail(); traceExit(); } /** * Parse non-terminal: pageTail. *
	* pageTail ::= pageStatement pageTail
	* pageTail ::= epsilon
	* 
*/ private void parsePageTail() throws SyntaxException { traceEntry(); if (isFirst(theToken,"pageStatement")) { parsePageStatement(); parsePageTail(); } traceExit(); } /** * Parse non-terminal: pageStatement. *
	* pageStatement::= { pageStatements }
	* pageStatement::= expr;
	* pageStatement::= id = expr;
	* pageStatement::= if (boolExpr) pageStatement
	* pageStatement::= if (boolExpr) pageStatement else pageStatement
	* 
*/ private void parsePageStatement() throws SyntaxException { traceEntry(); switch (theToken.getType()) { case Token.LCURLY: matchToken(Token.LCURLY); parsePageStatements(); matchToken(Token.RCURLY); break; case Token.ID: { // Either a method call or an assignment matchToken(Token.ID); Token t = prevToken; // remember the id at the beginning if (isFirst(theToken,"callEnd")) { // start of a method call or ... parseCallEnd(t); matchToken(Token.SEMICOLON); } else { // ... an assignment statement matchToken(Token.OP_ASSIGN); generateCode("/"+t.getLabel()); parseExpr(); generateCode("def"); matchToken(Token.SEMICOLON); } break; } default: matchToken(Token.KW_IF); matchToken(Token.LPAREN); parseBoolExpr(); matchToken(Token.RPAREN); generateCode("{"); parsePageStatement(); // the consequent generateCode("}"); if (theToken.getType() == Token.KW_ELSE) { matchToken(Token.KW_ELSE); generateCode("{"); parsePageStatement(); // the else part generateCode("}"); generateCode("ifelse"); break; } generateCode("if"); } traceExit(); } /** * Parse non-terminal: expr. *
	* expr ::= term exprTail
	* 
*/ private void parseExpr() throws SyntaxException { traceEntry(); parseTerm(); parseExprTail(); traceExit(); } /** * Parse non-terminal: exprTail. *
	* exprTail ::= + term exprTail
	* exprTail ::= - term exprTail
	* exprTail ::= epsilon
	* 
*/ private void parseExprTail() throws SyntaxException { traceEntry(); Token opToken = theToken; // remember the current Token if (theToken.getType() == Token.OP_ADD) { matchToken(Token.OP_ADD); parseTerm(); generateCode(operator(opToken)); parseExprTail(); } else if (theToken.getType() == Token.OP_SUB) { matchToken(Token.OP_SUB); parseTerm(); generateCode(operator(opToken)); parseExprTail(); } else { // epsilon } traceExit(); } /** * Parse non-terminal: term. *
	* term ::= factor termTail
	* 
*/ private void parseTerm() throws SyntaxException { traceEntry(); parseFactor(); parseTermTail(); traceExit(); } /** * Parse non-terminal: termTail. *
	* termTail ::= * factor termTail
	* termTail ::= / factor termTail
	* termTail ::= epsilon
	* 
*/ private void parseTermTail() throws SyntaxException { traceEntry(); Token opToken = theToken; if (theToken.getType() == Token.OP_MUL) { matchToken(Token.OP_MUL); parseFactor(); generateCode(operator(opToken)); parseTermTail(); } else if (theToken.getType() == Token.OP_DIV) { matchToken(Token.OP_DIV); parseFactor(); generateCode(operator(opToken)); parseTermTail(); } else { // epsilon } traceExit(); } /** * Parse non-terminal: factor. *
	* factor ::= integer
	* factor ::= real
	* factor ::= ( expr )
	* factor ::= id
	* factor ::= id callEnd
    * 
    * We read ahead on id to determine if methodCall or not
	* 
*/ private void parseFactor() throws SyntaxException { traceEntry(); switch (theToken.getType()) { case Token.INTEGER: matchToken(Token.INTEGER); generateCode(Integer.toString(prevToken.getIntValue())); break; case Token.REAL: matchToken(Token.REAL); generateCode(Double.toString(prevToken.getDoubleValue())); break; case Token.LPAREN: matchToken(Token.LPAREN); parseExpr(); matchToken(Token.RPAREN); break; default: matchToken(Token.ID); Token t = prevToken; // remember the ID Token for later use. if (isFirst(theToken,"callEnd")) { parseCallEnd(t); } else { Symbol var = symbolTable.getSymbol(SC_VARIABLE, t.getLabel()); if (var == null) { throw new SyntaxException("Variable must be declared" + "before use: " + t.getLabel()); } generateCode(t.getLabel()); } break; } traceExit(); } /** * Parse non-terminal: callEnd * @param t1 the Token.ID that started the methodCall *
	* callEnd ::= () | (exprList) | .id() | .id(exprList)
	* 
*/ private void parseCallEnd(Token t1) throws SyntaxException { traceEntry(); /* NOTE -- the passed parameter 't' is the ID Token that should have * been matched in the previous parsing method - caller. */ switch (theToken.getType()) { case Token.LPAREN: // id() or id(exprList) matchToken(Token.LPAREN); if (theToken.getType() != Token.RPAREN) { parseExprList(); } matchToken(Token.RPAREN); generateCode(t1.getLabel()); break; default: // id.id() or id.id(exprList) matchToken(Token.DOT); matchToken(Token.ID); Token t2 = prevToken; matchToken(Token.LPAREN); if (theToken.getType() != Token.RPAREN) { parseExprList(); } matchToken(Token.RPAREN); // Generate the code for the instance methods Symbol var = symbolTable.getSymbol(SC_VARIABLE, t1.getLabel()); if (var == null) { throw new SyntaxException("Variable must be declared before" + " use: " + t1.getLabel()); } String type = (String)var.getAttribute("CLASSNAME"); if (type == null) { throw new SyntaxException("Object reference must have defined" + " class type: " + t1.getLabel()); } generateCode(t1.getLabel()); // the variable generateCode(type+"."+t2.getLabel()); // the class method break; } traceExit(); } /** * Parse non-terminal: exprList. *
	* exprList ::= expr exprListTail
	* 
*/ private void parseExprList() throws SyntaxException { traceEntry(); parseExpr(); parseExprListTail(); traceExit(); } /** * Parse non-terminal: exprListTail. *
	* exprListTail ::= , expr exprListTail
	* exprListTail ::= epsilon
	* 
*/ private void parseExprListTail() throws SyntaxException { traceEntry(); if (isFirst(theToken,"exprListTail")) { matchToken(Token.COMMA); parseExpr(); parseExprListTail(); } else { // epsilon -- do nothing } traceExit(); } /** * Parse non-terminal: boolExpr. *
	* boolExpr ::= relExpr
	* boolExpr ::= !(relExpr)
	* 
*/ private void parseBoolExpr() throws SyntaxException { traceEntry(); if (theToken.getType() == Token.OP_NOT) { matchToken(Token.OP_NOT); Token opToken = prevToken; // remember the operator Token matchToken(Token.LPAREN); parseRelExpr(); matchToken(Token.RPAREN); generateCode(operator(opToken)); } else { parseRelExpr(); } traceExit(); } /** * Parse non-terminal: relExpr. *
	* relExpr ::= expr == expr
	* relExpr ::= expr > expr
	* relExpr ::= expr < expr
	* 
*/ private void parseRelExpr() throws SyntaxException { traceEntry(); parseExpr(); Token opToken = theToken; switch (theToken.getType()) { case Token.OP_EQ: matchToken(Token.OP_EQ); parseExpr(); break; case Token.OP_LT: matchToken(Token.OP_LT); parseExpr(); break; default: matchToken(Token.OP_GT); parseExpr(); break; } generateCode(operator(opToken)); traceExit(); } /** * Emit a code string. * @param code the string to emit if showCode is true. */ private void generateCode(String code) { if (showCode) { cio.emit(code); } } /** * Convert an operator token into the appropriate postscript name * @param t the Token containing the operator * @return the equivalent postscript operator */ private String operator(Token t) { String s = "UNKNOWNOPERATOR"; if (t == null) return s; int op = t.getType(); if (op == Token.OP_NOT) s = "not"; else if (op == Token.OP_EQ) s = "eq"; else if (op == Token.OP_LT) s = "lt"; else if (op == Token.OP_GT) s = "gt"; else if (op == Token.OP_ADD) s = "add"; else if (op == Token.OP_SUB) s = "sub"; else if (op == Token.OP_MUL) s = "mul"; else if (op == Token.OP_DIV) s = "div"; return s; } /** * Format and emit the postscript title block section * @param title the title of this movie */ private void generateMovieStart(String title) { if (showCode) { cio.emit("%%Title: "+title); cio.emit("%%Pages: (atend)"); cio.emit("%%EndComments"); } } /** * Format and emit the postscript trailer */ private void generateMovieEnd() { if (showCode) { Symbol pc = symbolTable.getSymbol(SC_VARIABLE,"pageNumber"); int count = ((Integer)pc.getAttribute("value")).intValue(); cio.emit("%%Trailer"); cio.emit("%%Pages: "+Integer.toString(count)); cio.emit("%%EOF"); } } /** * Format and emit the contents of 'FlipPrologStart.ps' * @throws IOException * */ private void generatePrologStart() throws IOException { if (showCode) { cio.emitFile("FlipPrologStart.ps"); } } /** * Format and emit the contents of 'FlipPrologEnd.ps' * @throws IOException */ private void generatePrologEnd() throws IOException { if (showCode) { cio.emitFile("FlipPrologEnd.ps"); } } /** * Check to see if tokens of the given type are in the FIRST set * of the given non-terminal. * @param t the Token to check to see if its type is in the FIRST set * @param nonterm the String name of the non-terminal whose FIRST set * is to be checked * @return true if the given Token can be the first Token in a string * generated by this non-terminal, false if it cannot be the first * Token. */ private boolean isFirst(Token t,String nonterm) { int[] first = (int[])firstSets.get(nonterm); if (first == null) { // We didn't find the firstSet for this non-terminal -- probably means that 'nonterm' // is mis-spelled throw new java.lang.RuntimeException("Could find firstSet for non-terminal '"+nonterm+"'"); } for (int i=0; i