001 // Copyright 2008 Waterken Inc. under the terms of the MIT X license found at
002 // http://www.opensource.org/licenses/mit-license.html
003 package org.waterken.syntax;
004
005 import java.io.EOFException;
006 import java.io.IOException;
007 import java.io.Reader;
008
009 /**
010 * A text reader that keeps track of the current line and column number, as well
011 * as the most recent character {@linkplain #read read}.
012 */
013 public final class
014 SourceReader {
015 private final Reader in;
016 private int line;
017 private int column;
018 private int head;
019
020 /**
021 * Constructs an instance.
022 * @param in underlying character stream
023 */
024 public
025 SourceReader(final Reader in) {
026 this.in = in;
027 line = 0; // position (0, 0) means nothing has been read yet
028 column = 0;
029 head = '\n'; // will put the stream at (1, 1) on first read operation
030 }
031
032 /**
033 * Gets the line number of the {@linkplain #getHead head} character.
034 */
035 public int
036 getLine() { return line; }
037
038 /**
039 * Gets the column number of the {@linkplain #getHead head} character.
040 */
041 public int
042 getColumn() { return column; }
043
044 /**
045 * Gets the last {@link #read read} character, or {@code -1} for EOF.
046 */
047 public int
048 getHead() { return head; }
049
050 /**
051 * all Unicode line terminators
052 */
053 static public final String newLine = "\n\r\u0085\u000C\u2028\u2029";
054
055 /**
056 * Gets the next character in the stream.
057 * @throws EOFException attempt to read past EOF
058 * @throws IOException any I/O problem
059 */
060 public int
061 read() throws EOFException, IOException {
062 if (-1 == head) { throw new EOFException(); }
063
064 final int next = in.read();
065 if (-1 == newLine.indexOf(head) || ('\r' == head && '\n' == next)) {
066 column += 1;
067 } else {
068 line += 1;
069 column = 1;
070 }
071 return head = next;
072 }
073
074 /**
075 * Closes the text stream.
076 * @throws IOException any I/O problem
077 */
078 public void
079 close() throws IOException {
080 if (-1 != head) {
081 column += 1;
082 head = -1;
083 }
084 in.close();
085 }
086 }