/* * Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Business Objects nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * SourcePosition.java * Creation date (Sep 4, 2002). * By: Bo Ilic */ package org.openquark.cal.compiler; import java.util.Comparator; /** * Identifies a position within a CAL source stream and (optionally) the source stream itself. Useful for error reporting. * This class is intended to be an immutable value class. * Creation date (Sep 4, 2002). * @author Bo Ilic */ public final class SourcePosition { /** * The source line of the source position. */ private final int line; /** * The source column of the source position. */ private final int column; /** * The name of the source. */ private final String sourceName; /** * Constructs an instance of this class. * @param line the source line of the source position. * @param column the source column of the source position. * @param sourceName the name of the source. */ SourcePosition (int line, int column, String sourceName) { if (line == -1 && column == -1) { //SourcePosition objects constructed from unpositioned Antlr errors have (line, column) == (-1, -1) line = 0; column = 0; } else if (line < 0 || column < 0) { throw new IllegalArgumentException(); } this.line = line; this.column = column; this.sourceName = sourceName; } /** * Constructs an instance of this class. * @param line the source line of the source position. * @param column the source column of the source position. * @param moduleName the name of the source, which is a module. */ SourcePosition(int line, int column, ModuleName moduleName) { this(line, column, moduleName.toSourceText()); } /** * Constructs an instance of this class, with null as the source name. * @param line the source line of the source position. * @param column the source column of the source position. */ SourcePosition(int line, int column) { this(line, column, (String)null); } /** @return line number within the stream where the source is located. The first line is line 1. Uninitialized is 0.*/ public int getLine() { return line; } /** @return column number within the stream where the source is located. The first column is column 1. Uninitialized is 0.*/ public int getColumn() { return column; } /** * Get the name of the source identified by this source position. * @return the name of the source, or null if none. */ public String getSourceName() { return sourceName; } /** * Computes the zero-based index into the sourceText that this SourcePosition specifies. * This method is expensive to call since it traverses sourceText, counting newlines. * @param sourceText source text for which this SourcePosition object identifies a position. * (this changes when we calculate offsets via previous source positions) * @return int zero-based index into the sourceText that this SourcePosition specifies. * @throws IllegalStateException if this SourcePosition has an uninitialized line or column * @throws IllegalArgumentException if the sourceText is not consistent with this SourcePosition * e.g. doesn't have enough lines. */ public int getPosition(String sourceText) { return getPosition(sourceText, 1); } /** * Computes the zero-based index into the sourceText that this SourcePosition specifies. * This method is expensive to call since it traverses sourceText, counting newlines. * @param sourceText source text for which this SourcePosition object identifies a position. * @param startColumn indicates the column that the sourceText begins with * (may not be 1 for the optimized case where we're only scanning from a * previous known SourcePosition rather than from the start of a block of text) * @return int zero-based index into the sourceText that this SourcePosition specifies. * @throws IllegalStateException if this SourcePosition has an uninitialized line or column * @throws IllegalArgumentException if the sourceText is not consistent with this SourcePosition * e.g. doesn't have enough lines. */ private int getPosition(String sourceText, int startColumn) { if (line == 0 || column == 0) { throw new IllegalStateException(); } int currentNewlinePos = -1; for (int currentLine = 1; currentLine < line; ++currentLine) { currentNewlinePos = sourceText.indexOf('\n', currentNewlinePos + 1); if (currentNewlinePos == -1) { //the sourceText doesn't have enough newlines in it to be consistent with the line number of this SourcePosition String msg = "Invalid position: " + toString() + "\nstartColumn: " + startColumn + "\nsourceText length:" + sourceText.length() + "\nsourceText:\n" + sourceText; throw new IllegalArgumentException(msg); } } // This will track the position within the string buffer int pos = currentNewlinePos; int curColumn = 0; if(pos == -1) { // Same line as previous position. // So the first char of sourceText has position startColumn pos = 0; curColumn = startColumn; } while(pos < sourceText.length() && curColumn < column) { curColumn += columnWidth(curColumn, sourceText.charAt(pos)); pos++; } //make sure that we can actually index into sourceText at the position returned. //(if curColumn < column, then we left the loop above because the pos was too large). if (curColumn < column) { //the sourceText is not long enough String msg = "Invalid position: " + toString() + "\nstartColumn: " + startColumn + "\nsourceText length:" + sourceText.length() + "\nsourceText:\n" + sourceText; throw new IllegalArgumentException(msg); } return pos; } /** * A more efficient version of getPosition that uses known "(line, column) == position" information * to compute a subsequent position. * @param sourceText * @param priorSourcePosition SourcePosition object that getPosition (sourceText) would return priorGetPosition. * @param priorGetPosition * @return int */ public int getPosition(String sourceText, SourcePosition priorSourcePosition, int priorGetPosition) { if (line == 0 || column == 0) { throw new IllegalStateException(); } int priorLine = priorSourcePosition.getLine(); int priorColumn = priorSourcePosition.getColumn(); int lineDiff = line - priorLine; if (lineDiff < 0) { //the known line and column must occur in the source text prior to this SourcePosition throw new IllegalArgumentException(); } SourcePosition offsetSourcePosition; if (lineDiff == 0) { int columnDiff = column - priorColumn; if (columnDiff < 0) { //the known line and column must occur in the source text prior to this SourcePosition throw new IllegalArgumentException(); } offsetSourcePosition = new SourcePosition(1, column); } else { offsetSourcePosition = new SourcePosition(lineDiff + 1, column); } String offsetSourceText = sourceText.substring(priorGetPosition); return priorGetPosition + offsetSourcePosition.getPosition(offsetSourceText, priorColumn); } /** * Calculates an exclusive end position for the offsetText assuming that it starts at this SourcePosition. * @param offsetText The text of the range to calculate an end position for * @return an exclusive end position for the rangeText assuming that it starts at startPosition */ public SourcePosition offsetPositionByText(String offsetText) { int endLine = line; int endColumn = column; for(int idx = 0; idx < offsetText.length(); idx++) { char currentChar = offsetText.charAt(idx); if(currentChar == '\n') { endLine ++; endColumn = 1; } else { endColumn += columnWidth(endColumn, currentChar); } } return new SourcePosition(endLine, endColumn, sourceName); } /** * Calculate the width of a character (ie, the number of columns that it consumes) * @param charColumnPosition 1-based column position of offsetChar * @param offsetChar Character to calculate the width of * @return The number of column positions that this character consumes */ public static int columnWidth(int charColumnPosition, char offsetChar) { // tab size used for the source code (this affects the line/column to buffer position translation) final int sourceTabSize = CALMultiplexedLexer.TAB_SIZE; int zeroBasedColumn = charColumnPosition - 1; // Tab characters advance to the next tab stop, which can take between 1 and sourceTabSize columns if(offsetChar == '\t') { int gapFromLastTabStop = zeroBasedColumn % sourceTabSize; return sourceTabSize - gapFromLastTabStop; } return 1; } /** {@inheritDoc} */ @Override public String toString() { return (sourceName != null) ? "source: " + sourceName + ": (line " + line + " column " + column + ")" : "(line " + line + " column " + column + ")"; } /** * Comparator object to order Source Positions by increasing position. * * Two positions are considered in increasing order if the first position * is on a line prior to the second position; if the positions are on the same * line, they are ordered if the first position's column is before the second. */ public static final Comparator<SourcePosition> compareByPosition = new CompareByPosition(); private static class CompareByPosition implements Comparator<SourcePosition> { /** {@inheritDoc} */ public int compare(SourcePosition sourcePos1, SourcePosition sourcePos2) { if ((sourcePos1 == null) || (sourcePos2 == null)) { throw new IllegalArgumentException(); } int line1 = sourcePos1.getLine(); int line2 = sourcePos2.getLine(); int compareLines = compareInts (line1, line2); if (compareLines != 0) { // Different lines; order by lines return compareLines; } else { // Positions on same line; order by column int column1 = sourcePos1.getColumn(); int column2 = sourcePos2.getColumn(); return compareInts (column1, column2); } } private static int compareInts(int i1, int i2) { //note: it is a cute trick to return i1 - i2, but this doesn't work //for all ints due to overflow in int arithmetic. return (i1 < i2 ? -1 : (i1 == i2 ? 0 : 1)); } } /** * Is this position after the given position. * @param position the position to compare to * @return true if this position is after the given position. */ public boolean isAfter(SourcePosition position){ if (line > position.line){ return true; } if (line < position.line){ return false; } return column > position.column; } /** * Is this position after the given position. * @param position the position to compare to * @return true if this position is after the given position. */ public boolean isBefore(SourcePosition position){ if (line < position.line){ return true; } if (line > position.line){ return false; } return column < position.column; } /** * {@inheritDoc} */ @Override public boolean equals(final Object obj) { if (obj instanceof SourcePosition) { return equals((SourcePosition)obj); } else { return false; } } /** * Returns whether the other source position equals this one. * @param other the other source position. * @return true if the source positions are equal; false otherwise. */ public boolean equals(final SourcePosition other) { return other != null && line == other.line && column == other.column && (sourceName == null ? other.sourceName == null : sourceName.equals(other.sourceName)); } /** * {@inheritDoc} */ @Override public int hashCode() { int result = 17; result = 37 * result + line; result = 37 * result + column; result = 37 * result + (sourceName == null ? 0 : sourceName.hashCode()); return result; } }