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 }