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