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    }