Mercurial > hg > truffle
diff graal/com.oracle.truffle.api/src/com/oracle/truffle/api/source/Source.java @ 16130:6f7d3f3703d3
Truffle/Source:
- LineLocation and LineBreakpoint no longer implement Comparable
- TextMap now internal to the Source factory
author | Michael Van De Vanter <michael.van.de.vanter@oracle.com> |
---|---|
date | Mon, 16 Jun 2014 20:52:06 -0700 |
parents | 74e142bd2b12 |
children | 7109baa7b9eb |
line wrap: on
line diff
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/source/Source.java Mon Jun 16 23:07:45 2014 +0200 +++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/source/Source.java Mon Jun 16 20:52:06 2014 -0700 @@ -194,10 +194,10 @@ return builder.toString(); } - protected Source() { + Source() { } - protected TextMap textMap = null; + private TextMap textMap = null; protected abstract void reset(); @@ -266,23 +266,38 @@ /** * Given a 0-based character offset, return the 1-based number of the line that includes the * position. + * + * @throws IllegalArgumentException if the offset is outside the text contents */ - public final int getLineNumber(int offset) { + public final int getLineNumber(int offset) throws IllegalArgumentException { return checkTextMap().offsetToLine(offset); } /** - * Given a 1-based line number, return the 0-based offset of the first character in the line. + * Given a 0-based character offset, return the 1-based number of the column at the position. + * + * @throws IllegalArgumentException if the offset is outside the text contents */ - public final int getLineStartOffset(int lineNumber) { + public final int getColumnNumber(int offset) throws IllegalArgumentException { + return checkTextMap().offsetToCol(offset); + } + + /** + * Given a 1-based line number, return the 0-based offset of the first character in the line. + * + * @throws IllegalArgumentException if there is no such line in the text + */ + public final int getLineStartOffset(int lineNumber) throws IllegalArgumentException { return checkTextMap().lineStartOffset(lineNumber); } /** * The number of characters (not counting a possible terminating newline) in a (1-based) * numbered line. + * + * @throws IllegalArgumentException if there is no such line in the text */ - public final int getLineLength(int lineNumber) { + public final int getLineLength(int lineNumber) throws IllegalArgumentException { return checkTextMap().lineLength(lineNumber); } @@ -809,15 +824,152 @@ return source.equals(other.source); } - @Override - public int compareTo(Object o) { - final LineLocationImpl other = (LineLocationImpl) o; - final int nameOrder = source.getName().compareTo(other.source.getName()); - if (nameOrder != 0) { - return nameOrder; + } + + /** + * A utility for converting between coordinate systems in a string of text interspersed with + * newline characters. The coordinate systems are: + * <ul> + * <li>0-based character offset from the beginning of the text, where newline characters count + * as a single character and the first character in the text occupies position 0.</li> + * <li>1-based position in the 2D space of lines and columns, in which the first position in the + * text is at (1,1).</li> + * </ul> + * <p> + * This utility is based on positions occupied by characters, not text stream positions as in a + * text editor. The distinction shows up in editors where you can put the cursor just past the + * last character in a buffer; this is necessary, among other reasons, so that you can put the + * edit cursor in a new (empty) buffer. For the purposes of this utility, however, there are no + * character positions in an empty text string and there are no lines in an empty text string. + * <p> + * A newline character designates the end of a line and occupies a column position. + * <p> + * If the text ends with a character other than a newline, then the characters following the + * final newline character count as a line, even though not newline-terminated. + * <p> + * <strong>Limitations:</strong> + * <ul> + * <li>Does not handle multiple character encodings correctly.</li> + * <li>Treats tabs as occupying 1 column.</li> + * <li>Does not handle multiple-character line termination sequences correctly.</li> + * </ul> + */ + private static final class TextMap { + + // 0-based offsets of newline characters in the text, with sentinel + private final int[] nlOffsets; + + // The number of characters in the text, including newlines (which count as 1). + private final int textLength; + + // Is the final text character a newline? + final boolean finalNL; + + /** + * Constructs map permitting translation between 0-based character offsets and 1-based + * lines/columns. + */ + public TextMap(String text) { + this.textLength = text.length(); + final ArrayList<Integer> lines = new ArrayList<>(); + lines.add(0); + int offset = 0; + + while (offset < text.length()) { + final int nlIndex = text.indexOf('\n', offset); + if (nlIndex >= 0) { + offset = nlIndex + 1; + lines.add(offset); + } else { + break; + } + } + lines.add(Integer.MAX_VALUE); + + nlOffsets = new int[lines.size()]; + for (int line = 0; line < lines.size(); line++) { + nlOffsets[line] = lines.get(line); } - return Integer.compare(line, other.line); + + finalNL = textLength > 0 && (textLength == nlOffsets[nlOffsets.length - 2]); + } + + /** + * Converts 0-based character offset to 1-based number of the line containing the character. + * + * @throws IllegalArgumentException if the offset is outside the string. + */ + public int offsetToLine(int offset) throws IllegalArgumentException { + if (offset < 0 || offset >= textLength) { + throw new IllegalArgumentException("offset out of bounds"); + } + int line = 1; + while (offset >= nlOffsets[line]) { + line++; + } + return line; + } + + /** + * Converts 0-based character offset to 1-based number of the column occupied by the + * character. + * <p> + * Tabs are not expanded; they occupy 1 column. + * + * @throws IllegalArgumentException if the offset is outside the string. + */ + public int offsetToCol(int offset) throws IllegalArgumentException { + return 1 + offset - nlOffsets[offsetToLine(offset) - 1]; + } + + /** + * The number of lines in the text; if characters appear after the final newline, then they + * also count as a line, even though not newline-terminated. + */ + public int lineCount() { + if (textLength == 0) { + return 0; + } + return finalNL ? nlOffsets.length - 2 : nlOffsets.length - 1; + } + + /** + * Converts 1-based line number to the 0-based offset of the line's first character; this + * would be the offset of a newline if the line is empty. + * + * @throws IllegalArgumentException if there is no such line in the text. + */ + public int lineStartOffset(int line) throws IllegalArgumentException { + if (textLength == 0 || lineOutOfRange(line)) { + throw new IllegalArgumentException("line out of bounds"); + } + return nlOffsets[line - 1]; + } + + /** + * Gets the number of characters in a line, identified by 1-based line number; + * <em>does not</em> include the final newline, if any. + * + * @throws IllegalArgumentException if there is no such line in the text. + */ + public int lineLength(int line) throws IllegalArgumentException { + if (textLength == 0 || lineOutOfRange(line)) { + throw new IllegalArgumentException("line out of bounds"); + } + if (line == nlOffsets.length - 1 && !finalNL) { + return textLength - nlOffsets[line - 1]; + } + return (nlOffsets[line] - nlOffsets[line - 1]) - 1; + + } + + /** + * Is the line number out of range. + */ + private boolean lineOutOfRange(int line) { + return line <= 0 || line >= nlOffsets.length || (line == nlOffsets.length - 1 && finalNL); } } + }