/* * 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); } } }