package lex;
import java.io.*;
import java.util.*;
import errors.*;
/** The CharStream class reads a text file and presents "significant"
* characters one at a time via the currentChar() method.
* Comments are ignored (treated as white-space) and multiple white-space
* characters are treated as a single space character.
*
* When the CharStream has reached end of the file the
* currentChar() method will return CharStream.EOF.
*/
public class CharStream
{
/** Character returned for all white-space characters. */
public static final char BLANK = ' ';
/** Character returned when end of file is reached. This is also the
* value that BufferedReader.read() returns when it reaches
* the end of file.
*/
public static final char EOF = (char)-1;
/** Character used to mark the start of a comment. */
private static final char L_CURLY = '{';
/** Character used to mark the end of a comment. */
private static final char R_CURLY = '}';
/** A list of the valid characters that may appear in the source code. */
private static final String VALID_CHARS =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890" +
".,;:<>/*[]+-=(){\t ";
/** Used to read the source code file. */
private BufferedReader reader = null;
/** The current line being processed. */
private String currentLine = null;
/** The current character that has been read from the file. */
private char currentChar = 0;
/** Index of the current character in the current line. */
private int charIndex;
/** The current line number being read. */
private int lineNumber = 0;
/** Stack used to hold characters that are "put back". */
private Stack stack = new Stack();
/** Default constructor. */
public CharStream()
{
super();
}
/** Creates a CharStream object and opens filename for reading. The
* isOpen() method should be checked after constructing a
* CharStream object with this constructor to ensure that a
* LexicalError wasn't encounted during the
* skipWhiteSpace() method .
*
* @see LexicalError
* @see skipWhiteSpace
*/
public CharStream(String filename)
{
super();
open(filename);
}
/**
* Attempts to open the given file. Returns true if the file was opened
* successfully, or false otherwise.
*
* @param filename String The name of the file to open
* @return boolean True if the file was opened, false otherwise.
*/
public boolean open(String filename)
{
try
{
reader = new BufferedReader(new FileReader(filename));
lineNumber = 1;
currentLine = reader.readLine();
currentChar = getChar();
skipWhiteSpace();
}
catch (Exception ex)
{
System.out.println(ex);
ex.printStackTrace(System.out);
reader = null;
}
return reader != null;
}
/** Returns true if a file has be opened, false otherwise. */
public boolean isOpen() { return reader != null; }
/** Returns the number of the line currently being scanned. Returns
* 0 (zero) if no file is currently open.
*/
public int lineNumber() { return lineNumber; }
/** Prints the current line to std output. */
public void dumpLine()
{
System.out.println(currentLine);
}
/** Returns the current line. */
public String getCurrentLine() { return currentLine; }
/** Closes the currently open file. Has no effect if no file is open. */
public void close()
{
if (reader != null)
{
currentChar = (char) EOF;
lineNumber = 0;
try
{
reader.close();
}
catch (IOException ex)
{
System.err.println(ex);
ex.printStackTrace();
}
}
}
/** Returns a character to the stream. The character returned to the
* stream will be the next character returned by the currentChar<>
* method.
*/
public void pushBack(int ch)
{
stack.push((char)ch);
}
/** Returns true if the character ch is allowed to appear in a source
* file. Returns false otherwise.
*/
public boolean valid(char ch)
{
return VALID_CHARS.indexOf(ch) >= 0;
}
/** Returns the next character from the file, or CharStream.EOF if the
* end of file has been reached.
*/
public char currentChar() throws LexicalError
{
// Return characters from the stack if there are any.
if (!stack.empty())
{
return stack.pop();
}
// If the current character is a white space character then skip over
// any following white space characters and return CharStream.BLANK.
if (Character.isWhitespace(currentChar) || currentChar == L_CURLY)
{
skipWhiteSpace();
return BLANK;
}
// Otherwise we will return the current character and prime the
// currentChar variable for the next read.
char ch = currentChar;
currentChar = getChar();
// Make sure ch is a valid character.
if (ch != EOF && !valid(ch))
{
throw LexicalError.IllegalCharacter(ch, lineNumber);
}
return ch;
}
/** Skips over consecutive white space characters. Comments are treated
* as white space.
*/
protected void skipWhiteSpace() throws LexicalError
{
while (currentChar == L_CURLY || Character.isWhitespace(currentChar))
{
if (currentChar == L_CURLY)
{
skipComment();
}
else
{
currentChar = getChar();
}
}
}
/** Skips characters until the right brace is encountered. */
protected void skipComment() throws LexicalError
{
currentChar = getChar();
while (currentChar != EOF && currentChar != R_CURLY)
{
if (currentChar == L_CURLY)
{
throw LexicalError.BadComment(lineNumber);
}
currentChar = getChar();
}
if (currentChar == EOF)
{
throw LexicalError.UnterminatedComment(lineNumber);
}
currentChar = getChar();
}
private void getLine()
{
while (currentLine != null && charIndex >= currentLine.length())
{
charIndex = 0;
++lineNumber;
try
{
currentLine = reader.readLine();
}
catch (IOException ex)
{
System.out.println(ex);
ex.printStackTrace(System.out);
currentLine = null;
}
}
}
/** Reads a character from the source file. If an IOException occurs
* during the read an EOF will be returned.
*/
private char getChar()
{
if (currentLine != null && charIndex >= currentLine.length())
{
getLine();
return '\n';
}
if (currentLine == null)
{
return EOF;
}
return currentLine.charAt(charIndex++);
// char ch;
// try
// {
// ch = (char) reader.read();
// if (ch == '\n')
// {
// ++lineNumber;
// }
// return ch;
// }
// catch (IOException ex)
// {
// System.out.println(ex);
// ex.printStackTrace(System.out);
// return (char) EOF;
// }
}
}