blob: e3c74283c279e82f0d4dc61f280cf28428dbf730 [file] [log] [blame]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.google.dart.compiler.common;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.MapMaker;
import com.google.dart.compiler.Source;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Serializable;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* Contains {@link Source} and location information for AST nodes.
* <p>
* Each node in the subtree (other than the contrived nodes) carries source range(s) information
* relating back to positions in the given source (the given source itself is not remembered with
* the AST). The source range usually begins at the first character of the first token corresponding
* to the node; leading whitespace and comments are <b>not</b> included. The source range usually
* extends through the last character of the last token corresponding to the node; trailing
* whitespace and comments are <b>not</b> included. There are a handful of exceptions (including the
* various body declarations). Source ranges nest properly: the source range for a child is always
* within the source range of its parent, and the source ranges of sibling nodes never overlap.
*/
public final class SourceInfo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* The unknown {@link SourceInfo}.
*/
public static final SourceInfo UNKNOWN = new SourceInfo(null, 0, 0);
private static final Map<Source, LinesInfo> lines = new MapMaker().weakKeys().makeMap();
private final Source source;
private final int offset;
private final int length;
public SourceInfo(Source source, int offset, int length) {
Preconditions.checkArgument(offset != -1 && length >= 0 || offset == -1 && length == 0);
this.source = source;
this.offset = offset;
this.length = length;
}
/**
* @return the {@link LinesInfo}, may be empty if some {@link Exception} happens, but not
* <code>null</code>.
*/
private static LinesInfo getLinesInfo(Source source) {
LinesInfo linesInfo = lines.get(source);
if (linesInfo == null) {
linesInfo = createLinesInfo(source);
lines.put(source, linesInfo);
}
return linesInfo;
}
/**
* @return the new {@link LinesInfo}, may be empty if some {@link Exception} happens, but not
* <code>null</code>.
*/
private static LinesInfo createLinesInfo(Source source) {
BufferedReader reader = null;
try {
reader = new BufferedReader(source.getSourceReader());
int offset = 0;
List<Integer> lineOffsets = Lists.newArrayList(0);
while (true) {
int charValue = reader.read();
if (charValue == -1) {
break;
}
offset++;
char c = (char) charValue;
if (c == '\n') {
lineOffsets.add(offset);
}
}
return new LinesInfo(lineOffsets);
} catch (Throwable e) {
return new LinesInfo(ImmutableList.of(0));
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
// Ignored
}
}
}
}
/**
* @return the {@link Source}.
*/
public Source getSource() {
return source;
}
/**
* @return the 0-based character index in the {@link Source}, may <code>-1</code> if no source
* information is recorded.
*/
public int getOffset() {
return offset;
}
/**
* @return a (possibly 0) length of this node in the {@link Source}, may <code>0</code> if no
* source position information is recorded.
*/
public int getLength() {
return length;
}
/**
* @return the 0-based character index in the {@link Source}, result of {@link #getOffset()} plus
* {@link #getLength()}.
*/
public int getEnd() {
return offset + length;
}
/**
* @return a 1-based line number in the {@link Source} indicating where the source fragment
* begins. May be <code>0</code> if line not found.
*/
public int getLine() {
if (source == null) {
return 0;
}
return 1 + getLinesInfo(source).getLineOfOffset(offset);
}
/**
* @return a 1-based column number in the {@link Source} indicating where the source fragment
* begins. May be <code>0</code> if column not found.
*/
public int getColumn() {
if (source == null) {
return 0;
}
return 1 + getLinesInfo(source).getColumnOfOffset(offset);
}
/**
* Container for information about lines in some {@link Source}.
*/
private static class LinesInfo {
private final List<Integer> lineOffsets;
public LinesInfo(List<Integer> lineOffsets) {
this.lineOffsets = lineOffsets;
}
int getLineOffset(int line) {
if (line < 0) {
return 0;
}
return lineOffsets.get(line);
}
int getLineOfOffset(int offset) {
int index = Collections.binarySearch(lineOffsets, offset);
if (index >= 0) {
return index;
}
return -(2 + index);
}
int getColumnOfOffset(int offset) {
int line = getLineOfOffset(offset);
return offset - getLineOffset(line);
}
}
}