193 lines
6.6 KiB
Java
193 lines
6.6 KiB
Java
/*
|
|
* Name: Mike Cifelli
|
|
* Course: CIS 443 - Programming Languages
|
|
* Assignment: Lisp Parser
|
|
*/
|
|
|
|
package parser;
|
|
|
|
import scanner.*;
|
|
import java.io.*;
|
|
|
|
/**
|
|
* A <code>LispParser</code> converts a stream of bytes into internal
|
|
* representations of Lisp S-expressions. When the end of stream has been
|
|
* reached the <code>eof</code> 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 <code>LispParser</code> that produces S-expressions from
|
|
* the specified input stream.
|
|
*
|
|
* @param in
|
|
* the input stream to obtain S-expressions from (must not be
|
|
* <code>null</code>)
|
|
* @param fileName
|
|
* the name of the file that <code>in</code> 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
|
|
* <code>true</code> if this parser has reached the end of its underlying
|
|
* input stream; <code>false</code> 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);
|
|
}
|
|
}
|
|
|
|
}
|