| // 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); |
| } |
| } |
| } |