aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/syntax/LineNumberTable.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/syntax/LineNumberTable.java')
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/LineNumberTable.java235
1 files changed, 235 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/LineNumberTable.java b/src/main/java/com/google/devtools/build/lib/syntax/LineNumberTable.java
new file mode 100644
index 0000000000..4842a16119
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/LineNumberTable.java
@@ -0,0 +1,235 @@
+// Copyright 2014 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.syntax;
+
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.events.Location.LineAndColumn;
+import com.google.devtools.build.lib.util.Pair;
+import com.google.devtools.build.lib.util.StringUtilities;
+import com.google.devtools.build.lib.vfs.Path;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A table to keep track of line numbers in source files. The client creates a LineNumberTable for
+ * their buffer using {@link #create}. The client can then ask for the line and column given a
+ * position using ({@link #getLineAndColumn(int)}).
+ */
+abstract class LineNumberTable implements Serializable {
+
+ /**
+ * Returns the (line, column) pair for the specified offset.
+ */
+ abstract LineAndColumn getLineAndColumn(int offset);
+
+ /**
+ * Returns the (start, end) offset pair for a specified line, not including
+ * newline chars.
+ */
+ abstract Pair<Integer, Integer> getOffsetsForLine(int line);
+
+ /**
+ * Returns the path corresponding to the given offset.
+ */
+ abstract Path getPath(int offset);
+
+ static LineNumberTable create(char[] buffer, Path path) {
+ // If #line appears within a BUILD file, we assume it has been preprocessed
+ // by gconfig2blaze. We ignore all actual newlines and compute the logical
+ // LNT based only on the presence of #line markers.
+ return StringUtilities.containsSubarray(buffer, "\n#line ".toCharArray())
+ ? new HashLine(buffer, path)
+ : new Regular(buffer, path);
+ }
+
+ /**
+ * Line number table implementation for regular source files. Records
+ * offsets of newlines.
+ */
+ @Immutable
+ private static class Regular extends LineNumberTable {
+
+ /**
+ * A mapping from line number (line >= 1) to character offset into the file.
+ */
+ private final int[] linestart;
+ private final Path path;
+ private final int bufferLength;
+
+ private Regular(char[] buffer, Path path) {
+ // Compute the size.
+ int size = 2;
+ for (int i = 0; i < buffer.length; i++) {
+ if (buffer[i] == '\n') {
+ size++;
+ }
+ }
+ linestart = new int[size];
+
+ int index = 0;
+ linestart[index++] = 0; // The 0th line does not exist - so we fill something in
+ // to make sure the start pos for the 1st line ends up at
+ // linestart[1]. Using 0 is useful for tables that are
+ // completely empty.
+ linestart[index++] = 0; // The first line ("line 1") starts at offset 0.
+
+ // Scan the buffer and record the offset of each line start. Doing this
+ // once upfront is faster than checking each char as it is pulled from
+ // the buffer.
+ for (int i = 0; i < buffer.length; i++) {
+ if (buffer[i] == '\n') {
+ linestart[index++] = i + 1;
+ }
+ }
+ this.bufferLength = buffer.length;
+ this.path = path;
+ }
+
+ private int getLineAt(int pos) {
+ if (pos < 0) {
+ throw new IllegalArgumentException("Illegal position: " + pos);
+ }
+ int lowBoundary = 1, highBoundary = linestart.length - 1;
+ while (true) {
+ if ((highBoundary - lowBoundary) <= 1) {
+ if (linestart[highBoundary] > pos) {
+ return lowBoundary;
+ } else {
+ return highBoundary;
+ }
+ }
+ int medium = lowBoundary + ((highBoundary - lowBoundary) >> 1);
+ if (linestart[medium] > pos) {
+ highBoundary = medium;
+ } else {
+ lowBoundary = medium;
+ }
+ }
+ }
+
+ @Override
+ LineAndColumn getLineAndColumn(int offset) {
+ int line = getLineAt(offset);
+ int column = offset - linestart[line] + 1;
+ return new LineAndColumn(line, column);
+ }
+
+ @Override
+ Path getPath(int offset) {
+ return path;
+ }
+
+ @Override
+ Pair<Integer, Integer> getOffsetsForLine(int line) {
+ if (line <= 0 || line >= linestart.length) {
+ throw new IllegalArgumentException("Illegal line: " + line);
+ }
+ return Pair.of(linestart[line], line < linestart.length - 1
+ ? linestart[line + 1]
+ : bufferLength);
+ }
+ }
+
+ /**
+ * Line number table implementation for source files that have been
+ * preprocessed. Ignores newlines and uses only #line directives.
+ */
+ // TODO(bazel-team): Use binary search instead of linear search.
+ @Immutable
+ private static class HashLine extends LineNumberTable {
+
+ /**
+ * Represents a "#line" directive
+ */
+ private static class SingleHashLine implements Serializable {
+ final private int offset;
+ final private int line;
+ final private Path path;
+
+ SingleHashLine(int offset, int line, Path path) {
+ this.offset = offset;
+ this.line = line;
+ this.path = path;
+ }
+ }
+
+ private static final Pattern pattern = Pattern.compile("\n#line ([0-9]+) \"([^\"\\n]+)\"");
+
+ private final List<SingleHashLine> table;
+ private final Path defaultPath;
+ private final int bufferLength;
+
+ private HashLine(char[] buffer, Path defaultPath) {
+ // Not especially efficient, but that's fine: we just exec'd Python.
+ String bufString = new String(buffer);
+ Matcher m = pattern.matcher(bufString);
+ ImmutableList.Builder<SingleHashLine> tableBuilder = ImmutableList.builder();
+ while (m.find()) {
+ tableBuilder.add(new SingleHashLine(
+ m.start(0) + 1, //offset (+1 to skip \n in pattern)
+ Integer.valueOf(m.group(1)), // line number
+ defaultPath.getRelative(m.group(2)))); // filename is an absolute path
+ }
+ this.table = tableBuilder.build();
+ this.bufferLength = buffer.length;
+ this.defaultPath = defaultPath;
+ }
+
+ @Override
+ LineAndColumn getLineAndColumn(int offset) {
+ int line = -1;
+ for (int ii = 0, len = table.size(); ii < len; ii++) {
+ SingleHashLine hash = table.get(ii);
+ if (hash.offset > offset) {
+ break;
+ }
+ line = hash.line;
+ }
+ return new LineAndColumn(line, 1);
+ }
+
+ @Override
+ Path getPath(int offset) {
+ Path path = this.defaultPath;
+ for (int ii = 0, len = table.size(); ii < len; ii++) {
+ SingleHashLine hash = table.get(ii);
+ if (hash.offset > offset) {
+ break;
+ }
+ path = hash.path;
+ }
+ return path;
+ }
+
+ /**
+ * Returns 0, 0 for an unknown line
+ */
+ @Override
+ Pair<Integer, Integer> getOffsetsForLine(int line) {
+ for (int ii = 0, len = table.size(); ii < len; ii++) {
+ if (table.get(ii).line == line) {
+ return Pair.of(table.get(ii).offset, ii < len - 1
+ ? table.get(ii + 1).offset
+ : bufferLength);
+ }
+ }
+ return Pair.of(0, 0);
+ }
+ }
+}