blob: 653abee1f6bfda22ad4774209afe536d675b6765 [file] [log] [blame]
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001// Copyright 2014 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package com.google.devtools.build.lib.syntax;
16
Eric Fellheimer7ce73fb2015-04-07 13:18:12 +000017import com.google.common.base.Preconditions;
18import com.google.common.collect.Ordering;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010019import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
20import com.google.devtools.build.lib.events.Location.LineAndColumn;
21import com.google.devtools.build.lib.util.Pair;
22import com.google.devtools.build.lib.util.StringUtilities;
Lukacs Berki48338222015-06-12 11:37:46 +000023import com.google.devtools.build.lib.vfs.PathFragment;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010024
25import java.io.Serializable;
Eric Fellheimer7ce73fb2015-04-07 13:18:12 +000026import java.util.ArrayList;
27import java.util.Comparator;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010028import java.util.List;
29import java.util.regex.Matcher;
30import java.util.regex.Pattern;
31
32/**
33 * A table to keep track of line numbers in source files. The client creates a LineNumberTable for
34 * their buffer using {@link #create}. The client can then ask for the line and column given a
35 * position using ({@link #getLineAndColumn(int)}).
36 */
37abstract class LineNumberTable implements Serializable {
38
39 /**
40 * Returns the (line, column) pair for the specified offset.
41 */
42 abstract LineAndColumn getLineAndColumn(int offset);
43
44 /**
45 * Returns the (start, end) offset pair for a specified line, not including
46 * newline chars.
47 */
48 abstract Pair<Integer, Integer> getOffsetsForLine(int line);
49
50 /**
51 * Returns the path corresponding to the given offset.
52 */
Lukacs Berki48338222015-06-12 11:37:46 +000053 abstract PathFragment getPath(int offset);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010054
Lukacs Berki48338222015-06-12 11:37:46 +000055 static LineNumberTable create(char[] buffer, PathFragment path) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010056 // If #line appears within a BUILD file, we assume it has been preprocessed
57 // by gconfig2blaze. We ignore all actual newlines and compute the logical
58 // LNT based only on the presence of #line markers.
59 return StringUtilities.containsSubarray(buffer, "\n#line ".toCharArray())
60 ? new HashLine(buffer, path)
61 : new Regular(buffer, path);
62 }
63
64 /**
65 * Line number table implementation for regular source files. Records
66 * offsets of newlines.
67 */
68 @Immutable
69 private static class Regular extends LineNumberTable {
70
71 /**
72 * A mapping from line number (line >= 1) to character offset into the file.
73 */
74 private final int[] linestart;
Lukacs Berki48338222015-06-12 11:37:46 +000075 private final PathFragment path;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010076 private final int bufferLength;
77
Lukacs Berki48338222015-06-12 11:37:46 +000078 private Regular(char[] buffer, PathFragment path) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010079 // Compute the size.
80 int size = 2;
81 for (int i = 0; i < buffer.length; i++) {
82 if (buffer[i] == '\n') {
83 size++;
84 }
85 }
86 linestart = new int[size];
87
88 int index = 0;
89 linestart[index++] = 0; // The 0th line does not exist - so we fill something in
90 // to make sure the start pos for the 1st line ends up at
91 // linestart[1]. Using 0 is useful for tables that are
92 // completely empty.
93 linestart[index++] = 0; // The first line ("line 1") starts at offset 0.
94
95 // Scan the buffer and record the offset of each line start. Doing this
96 // once upfront is faster than checking each char as it is pulled from
97 // the buffer.
98 for (int i = 0; i < buffer.length; i++) {
99 if (buffer[i] == '\n') {
100 linestart[index++] = i + 1;
101 }
102 }
103 this.bufferLength = buffer.length;
104 this.path = path;
105 }
106
Eric Fellheimer7ce73fb2015-04-07 13:18:12 +0000107 private int getLineAt(int offset) {
108 Preconditions.checkArgument(offset >= 0, "Illegal position: ", offset);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100109 int lowBoundary = 1, highBoundary = linestart.length - 1;
110 while (true) {
111 if ((highBoundary - lowBoundary) <= 1) {
Eric Fellheimer7ce73fb2015-04-07 13:18:12 +0000112 if (linestart[highBoundary] > offset) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100113 return lowBoundary;
114 } else {
115 return highBoundary;
116 }
117 }
118 int medium = lowBoundary + ((highBoundary - lowBoundary) >> 1);
Eric Fellheimer7ce73fb2015-04-07 13:18:12 +0000119 if (linestart[medium] > offset) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100120 highBoundary = medium;
121 } else {
122 lowBoundary = medium;
123 }
124 }
125 }
126
127 @Override
128 LineAndColumn getLineAndColumn(int offset) {
129 int line = getLineAt(offset);
130 int column = offset - linestart[line] + 1;
131 return new LineAndColumn(line, column);
132 }
133
134 @Override
Lukacs Berki48338222015-06-12 11:37:46 +0000135 PathFragment getPath(int offset) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100136 return path;
137 }
138
139 @Override
140 Pair<Integer, Integer> getOffsetsForLine(int line) {
141 if (line <= 0 || line >= linestart.length) {
142 throw new IllegalArgumentException("Illegal line: " + line);
143 }
144 return Pair.of(linestart[line], line < linestart.length - 1
145 ? linestart[line + 1]
146 : bufferLength);
147 }
148 }
149
150 /**
151 * Line number table implementation for source files that have been
152 * preprocessed. Ignores newlines and uses only #line directives.
153 */
154 // TODO(bazel-team): Use binary search instead of linear search.
155 @Immutable
156 private static class HashLine extends LineNumberTable {
157
158 /**
159 * Represents a "#line" directive
160 */
161 private static class SingleHashLine implements Serializable {
Laurent Le Brun9cc44242015-02-09 13:47:11 +0000162 private final int offset;
163 private final int line;
Lukacs Berki48338222015-06-12 11:37:46 +0000164 private final PathFragment path;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100165
Lukacs Berki48338222015-06-12 11:37:46 +0000166 SingleHashLine(int offset, int line, PathFragment path) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100167 this.offset = offset;
168 this.line = line;
169 this.path = path;
170 }
171 }
172
Eric Fellheimer7ce73fb2015-04-07 13:18:12 +0000173 private static Ordering<SingleHashLine> hashOrdering = Ordering.from(
174 new Comparator<SingleHashLine>() {
175 @Override
176 public int compare(SingleHashLine o1, SingleHashLine o2) {
177 return Integer.compare(o1.offset, o2.offset);
178 }
179 });
180
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100181 private static final Pattern pattern = Pattern.compile("\n#line ([0-9]+) \"([^\"\\n]+)\"");
182
183 private final List<SingleHashLine> table;
Lukacs Berki48338222015-06-12 11:37:46 +0000184 private final PathFragment defaultPath;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100185 private final int bufferLength;
186
Lukacs Berki48338222015-06-12 11:37:46 +0000187 private HashLine(char[] buffer, PathFragment defaultPath) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100188 // Not especially efficient, but that's fine: we just exec'd Python.
189 String bufString = new String(buffer);
190 Matcher m = pattern.matcher(bufString);
Eric Fellheimer7ce73fb2015-04-07 13:18:12 +0000191 List<SingleHashLine> unorderedTable = new ArrayList<>();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100192 while (m.find()) {
Eric Fellheimer7ce73fb2015-04-07 13:18:12 +0000193 unorderedTable.add(new SingleHashLine(
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100194 m.start(0) + 1, //offset (+1 to skip \n in pattern)
195 Integer.valueOf(m.group(1)), // line number
196 defaultPath.getRelative(m.group(2)))); // filename is an absolute path
197 }
Eric Fellheimer7ce73fb2015-04-07 13:18:12 +0000198 this.table = hashOrdering.immutableSortedCopy(unorderedTable);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100199 this.bufferLength = buffer.length;
200 this.defaultPath = defaultPath;
201 }
202
Eric Fellheimer7ce73fb2015-04-07 13:18:12 +0000203 private SingleHashLine getHashLine(int offset) {
204 Preconditions.checkArgument(offset >= 0, "Illegal position: ", offset);
205 int binarySearchIndex = hashOrdering.binarySearch(
206 table, new SingleHashLine(offset, -1, null));
207 if (binarySearchIndex >= 0) {
208 // An exact match in the binary search. Return it.
209 return table.get(binarySearchIndex);
210 } else if (binarySearchIndex < -1) {
211 // See docs at Collections#binarySearch. Our index is -(insertionPoint + 1). To get to the
212 // nearest existing value in the original list, we must subtract 2.
213 return table.get(-binarySearchIndex - 2);
214 } else {
215 return null;
216 }
217 }
218
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100219 @Override
220 LineAndColumn getLineAndColumn(int offset) {
Eric Fellheimer7ce73fb2015-04-07 13:18:12 +0000221 SingleHashLine hashLine = getHashLine(offset);
222 return hashLine == null ? new LineAndColumn(-1, 1) : new LineAndColumn(hashLine.line, 1);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100223 }
224
225 @Override
Lukacs Berki48338222015-06-12 11:37:46 +0000226 PathFragment getPath(int offset) {
Eric Fellheimer7ce73fb2015-04-07 13:18:12 +0000227 SingleHashLine hashLine = getHashLine(offset);
228 return hashLine == null ? defaultPath : hashLine.path;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100229 }
230
231 /**
232 * Returns 0, 0 for an unknown line
233 */
234 @Override
235 Pair<Integer, Integer> getOffsetsForLine(int line) {
236 for (int ii = 0, len = table.size(); ii < len; ii++) {
237 if (table.get(ii).line == line) {
238 return Pair.of(table.get(ii).offset, ii < len - 1
239 ? table.get(ii + 1).offset
240 : bufferLength);
241 }
242 }
243 return Pair.of(0, 0);
244 }
245 }
246}