package net.rubygrapefruit.docs.parser; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; import java.util.List; public class Buffer implements CharStream, MarkableStream { private final Reader reader; private final char[] buffer; private final List<Mark> marks = new ArrayList<Mark>(); private int firstMark = 0; private int cursor = 0; private int currentLine = 1; private int currentCol = 1; private int endBuffer = 0; private int startProduction = 0; private int endProduction = 0; private int startLine = 0; private int startColumn = 0; private int endLine = 0; private int endColumn = 0; public Buffer(Reader reader) { this(reader, 8192); } public Buffer(Reader reader, int bufferLen) { this.reader = reader; buffer = new char[bufferLen]; } public String getValue() { return new String(buffer, startProduction, endProduction - startProduction); } public int getEndColumn() { return endColumn; } public int getStartColumn() { return startColumn; } public int getStartLine() { return startLine; } public int getEndLine() { return endLine; } public void start() { if (marks.isEmpty()) { firstMark = cursor; } marks.add(new Mark(cursor, currentLine, currentCol)); startProduction = -1; endProduction = -1; startColumn = -1; startLine = -1; endColumn = -1; endLine = -1; } public boolean commit() { Mark mark = marks.remove(marks.size() - 1); int startThisToken = mark.offset; startLine = mark.line; startColumn = mark.col; startProduction = startThisToken; endProduction = cursor; endLine = currentLine; endColumn = currentCol - 1; return cursor > startThisToken; } public void rollback() { rewind(); marks.remove(marks.size() - 1); } public void rewind() { cursor = marks.get(marks.size() - 1).offset; } public void accept() { if (marks.size() != 1) { return; } firstMark = cursor; marks.get(0).moveTo(cursor); } public boolean consume(Production<? super CharStream> production) { start(); production.match(this); return commit(); } public <T> T consume(ValueProducingProduction<? super CharStream, T> production) { start(); T value = production.match(this); if (value == null) { rollback(); } else { commit(); } return value; } private boolean lookingAt(char... candidates) { int ch = peek(); if (ch < 0) { return false; } for (int i = 0; i < candidates.length; i++) { char candidate = candidates[i]; if (ch == candidate) { return true; } } return false; } public boolean consumeRange(char from, char to) { int ch = peek(); if (ch >= from && ch <= to) { next(); return true; } return false; } public boolean consume(char... candidates) { if (lookingAt(candidates)) { next(); return true; } return false; } public boolean consumeAnyExcept(char... candidates) { int ch = peek(); if (ch < 0) { return false; } for (int i = 0; i < candidates.length; i++) { char candidate = candidates[i]; if (ch == candidate) { return false; } } next(); return true; } private int peek() { if (cursor == endBuffer) { if (firstMark == 0 && endBuffer == buffer.length) { throw new UnsupportedOperationException("Buffer overflow not implemented yet."); } // Move any consumed characters to the start of the buffer System.arraycopy(buffer, firstMark, buffer, 0, endBuffer - firstMark); cursor -= firstMark; endBuffer -= firstMark; for (int i = 0; i < marks.size(); i++) { marks.get(i).retarget(firstMark); } firstMark = 0; // Read the next chunk int nread; try { nread = reader.read(buffer, endBuffer, buffer.length - endBuffer); } catch (IOException e) { throw new RuntimeException(e); } if (nread < 0) { return -1; } endBuffer += nread; } return buffer[cursor]; } private void next() { boolean hasCr = false; if (buffer[cursor] == '\n') { currentCol = 1; currentLine++; } else if (buffer[cursor] == '\r') { hasCr = true; } else { currentCol++; } cursor++; if (hasCr) { if (peek() != '\n') { currentCol = 1; currentLine++; } } } private static class Mark { int offset; final int col; final int line; boolean moved; private Mark(int offset, int line, int col) { this.offset = offset; this.line = line; this.col = col; } public void retarget(int offset) { this.offset -= offset; } public void moveTo(int offset) { this.offset = offset; moved = true; } } }