blob: 1e20e4577ad7bbffb1c6e65a42ef51cf0213226e [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002//
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
tomlua155b532017-11-08 20:12:47 +010017import com.google.common.base.Preconditions;
Eric Fellheimer7ce73fb2015-04-07 13:18:12 +000018import 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 +010024import java.io.Serializable;
Eric Fellheimer65f42232015-06-15 15:53:34 +000025import java.nio.CharBuffer;
Eric Fellheimer7ce73fb2015-04-07 13:18:12 +000026import java.util.ArrayList;
Janak82512512015-07-13 14:35:14 +000027import java.util.Arrays;
Googlerefd45ca2016-05-14 00:30:54 +000028import java.util.Collections;
Eric Fellheimer1c4bf132015-11-03 00:28:58 +000029import java.util.HashMap;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010030import java.util.List;
Eric Fellheimer1c4bf132015-11-03 00:28:58 +000031import java.util.Map;
Janak82512512015-07-13 14:35:14 +000032import java.util.Objects;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010033import java.util.regex.Matcher;
34import java.util.regex.Pattern;
35
36/**
37 * A table to keep track of line numbers in source files. The client creates a LineNumberTable for
38 * their buffer using {@link #create}. The client can then ask for the line and column given a
39 * position using ({@link #getLineAndColumn(int)}).
40 */
Carmi Grushkoa896a6c2015-06-15 20:46:52 +000041public abstract class LineNumberTable implements Serializable {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010042
43 /**
44 * Returns the (line, column) pair for the specified offset.
45 */
46 abstract LineAndColumn getLineAndColumn(int offset);
47
48 /**
49 * Returns the (start, end) offset pair for a specified line, not including
50 * newline chars.
51 */
52 abstract Pair<Integer, Integer> getOffsetsForLine(int line);
53
54 /**
55 * Returns the path corresponding to the given offset.
56 */
Lukacs Berki48338222015-06-12 11:37:46 +000057 abstract PathFragment getPath(int offset);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010058
Lukacs Berki48338222015-06-12 11:37:46 +000059 static LineNumberTable create(char[] buffer, PathFragment path) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010060 // If #line appears within a BUILD file, we assume it has been preprocessed
61 // by gconfig2blaze. We ignore all actual newlines and compute the logical
62 // LNT based only on the presence of #line markers.
63 return StringUtilities.containsSubarray(buffer, "\n#line ".toCharArray())
64 ? new HashLine(buffer, path)
65 : new Regular(buffer, path);
66 }
67
68 /**
69 * Line number table implementation for regular source files. Records
70 * offsets of newlines.
71 */
72 @Immutable
Carmi Grushkoa896a6c2015-06-15 20:46:52 +000073 public static class Regular extends LineNumberTable {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010074
75 /**
76 * A mapping from line number (line >= 1) to character offset into the file.
77 */
78 private final int[] linestart;
Lukacs Berki48338222015-06-12 11:37:46 +000079 private final PathFragment path;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010080 private final int bufferLength;
81
Carmi Grushkoa896a6c2015-06-15 20:46:52 +000082 public Regular(char[] buffer, PathFragment path) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010083 // Compute the size.
84 int size = 2;
85 for (int i = 0; i < buffer.length; i++) {
86 if (buffer[i] == '\n') {
87 size++;
88 }
89 }
90 linestart = new int[size];
91
92 int index = 0;
93 linestart[index++] = 0; // The 0th line does not exist - so we fill something in
94 // to make sure the start pos for the 1st line ends up at
95 // linestart[1]. Using 0 is useful for tables that are
96 // completely empty.
97 linestart[index++] = 0; // The first line ("line 1") starts at offset 0.
98
99 // Scan the buffer and record the offset of each line start. Doing this
100 // once upfront is faster than checking each char as it is pulled from
101 // the buffer.
102 for (int i = 0; i < buffer.length; i++) {
103 if (buffer[i] == '\n') {
104 linestart[index++] = i + 1;
105 }
106 }
107 this.bufferLength = buffer.length;
108 this.path = path;
109 }
110
Eric Fellheimer7ce73fb2015-04-07 13:18:12 +0000111 private int getLineAt(int offset) {
Michajlo Matijkiw8f15a512015-12-07 15:51:18 +0000112 if (offset < 0) {
113 throw new IllegalStateException("Illegal position: " + offset);
114 }
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000115 int lowBoundary = 1;
116 int highBoundary = linestart.length - 1;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100117 while (true) {
118 if ((highBoundary - lowBoundary) <= 1) {
Eric Fellheimer7ce73fb2015-04-07 13:18:12 +0000119 if (linestart[highBoundary] > offset) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100120 return lowBoundary;
121 } else {
122 return highBoundary;
123 }
124 }
125 int medium = lowBoundary + ((highBoundary - lowBoundary) >> 1);
Eric Fellheimer7ce73fb2015-04-07 13:18:12 +0000126 if (linestart[medium] > offset) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100127 highBoundary = medium;
128 } else {
129 lowBoundary = medium;
130 }
131 }
132 }
133
134 @Override
135 LineAndColumn getLineAndColumn(int offset) {
136 int line = getLineAt(offset);
137 int column = offset - linestart[line] + 1;
138 return new LineAndColumn(line, column);
139 }
140
141 @Override
Lukacs Berki48338222015-06-12 11:37:46 +0000142 PathFragment getPath(int offset) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100143 return path;
144 }
145
146 @Override
147 Pair<Integer, Integer> getOffsetsForLine(int line) {
148 if (line <= 0 || line >= linestart.length) {
149 throw new IllegalArgumentException("Illegal line: " + line);
150 }
151 return Pair.of(linestart[line], line < linestart.length - 1
152 ? linestart[line + 1]
153 : bufferLength);
154 }
Janak82512512015-07-13 14:35:14 +0000155
156
157 @Override
158 public int hashCode() {
159 return Objects.hash(Arrays.hashCode(linestart), path, bufferLength);
160 }
161
162 @Override
163 public boolean equals(Object other) {
164 if (other == null || !other.getClass().equals(getClass())) {
165 return false;
166 }
167 Regular that = (Regular) other;
168 return this.bufferLength == that.bufferLength
169 && Arrays.equals(this.linestart, that.linestart)
170 && Objects.equals(this.path, that.path);
171 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100172 }
173
174 /**
175 * Line number table implementation for source files that have been
176 * preprocessed. Ignores newlines and uses only #line directives.
177 */
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100178 @Immutable
Carmi Grushkoa896a6c2015-06-15 20:46:52 +0000179 public static class HashLine extends LineNumberTable {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100180
181 /**
182 * Represents a "#line" directive
183 */
184 private static class SingleHashLine implements Serializable {
Laurent Le Brun9cc44242015-02-09 13:47:11 +0000185 private final int offset;
186 private final int line;
Lukacs Berki48338222015-06-12 11:37:46 +0000187 private final PathFragment path;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100188
Lukacs Berki48338222015-06-12 11:37:46 +0000189 SingleHashLine(int offset, int line, PathFragment path) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100190 this.offset = offset;
191 this.line = line;
192 this.path = path;
193 }
194 }
195
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000196 private static final Ordering<SingleHashLine> hashOrdering =
197 new Ordering<SingleHashLine>() {
198
Eric Fellheimer7ce73fb2015-04-07 13:18:12 +0000199 @Override
200 public int compare(SingleHashLine o1, SingleHashLine o2) {
201 return Integer.compare(o1.offset, o2.offset);
202 }
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000203 };
Eric Fellheimer7ce73fb2015-04-07 13:18:12 +0000204
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100205 private static final Pattern pattern = Pattern.compile("\n#line ([0-9]+) \"([^\"\\n]+)\"");
206
207 private final List<SingleHashLine> table;
Lukacs Berki48338222015-06-12 11:37:46 +0000208 private final PathFragment defaultPath;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100209 private final int bufferLength;
210
Carmi Grushkoa896a6c2015-06-15 20:46:52 +0000211 public HashLine(char[] buffer, PathFragment defaultPath) {
Eric Fellheimer65f42232015-06-15 15:53:34 +0000212 CharSequence bufString = CharBuffer.wrap(buffer);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100213 Matcher m = pattern.matcher(bufString);
Eric Fellheimer7ce73fb2015-04-07 13:18:12 +0000214 List<SingleHashLine> unorderedTable = new ArrayList<>();
Eric Fellheimer1c4bf132015-11-03 00:28:58 +0000215 Map<String, PathFragment> pathCache = new HashMap<>();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100216 while (m.find()) {
Eric Fellheimer1c4bf132015-11-03 00:28:58 +0000217 String pathString = m.group(2);
laurentlb3d2a68c2017-06-30 00:32:04 +0200218 PathFragment pathFragment = pathCache.computeIfAbsent(pathString, defaultPath::getRelative);
Eric Fellheimer7ce73fb2015-04-07 13:18:12 +0000219 unorderedTable.add(new SingleHashLine(
Eric Fellheimer1c4bf132015-11-03 00:28:58 +0000220 m.start(0) + 1, //offset (+1 to skip \n in pattern)
221 Integer.parseInt(m.group(1)), // line number
222 pathFragment)); // filename is an absolute path
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100223 }
Eric Fellheimer7ce73fb2015-04-07 13:18:12 +0000224 this.table = hashOrdering.immutableSortedCopy(unorderedTable);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100225 this.bufferLength = buffer.length;
Janak82512512015-07-13 14:35:14 +0000226 this.defaultPath = Preconditions.checkNotNull(defaultPath);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100227 }
228
Eric Fellheimer7ce73fb2015-04-07 13:18:12 +0000229 private SingleHashLine getHashLine(int offset) {
Michajlo Matijkiw8f15a512015-12-07 15:51:18 +0000230 if (offset < 0) {
231 throw new IllegalStateException("Illegal position: " + offset);
232 }
Googlerefd45ca2016-05-14 00:30:54 +0000233 int binarySearchIndex =
234 Collections.binarySearch(table, new SingleHashLine(offset, -1, null), hashOrdering);
Eric Fellheimer7ce73fb2015-04-07 13:18:12 +0000235 if (binarySearchIndex >= 0) {
236 // An exact match in the binary search. Return it.
237 return table.get(binarySearchIndex);
238 } else if (binarySearchIndex < -1) {
239 // See docs at Collections#binarySearch. Our index is -(insertionPoint + 1). To get to the
240 // nearest existing value in the original list, we must subtract 2.
241 return table.get(-binarySearchIndex - 2);
242 } else {
243 return null;
244 }
245 }
246
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100247 @Override
248 LineAndColumn getLineAndColumn(int offset) {
Eric Fellheimer7ce73fb2015-04-07 13:18:12 +0000249 SingleHashLine hashLine = getHashLine(offset);
250 return hashLine == null ? new LineAndColumn(-1, 1) : new LineAndColumn(hashLine.line, 1);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100251 }
252
253 @Override
Lukacs Berki48338222015-06-12 11:37:46 +0000254 PathFragment getPath(int offset) {
Eric Fellheimer7ce73fb2015-04-07 13:18:12 +0000255 SingleHashLine hashLine = getHashLine(offset);
256 return hashLine == null ? defaultPath : hashLine.path;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100257 }
258
259 /**
260 * Returns 0, 0 for an unknown line
261 */
262 @Override
263 Pair<Integer, Integer> getOffsetsForLine(int line) {
264 for (int ii = 0, len = table.size(); ii < len; ii++) {
265 if (table.get(ii).line == line) {
266 return Pair.of(table.get(ii).offset, ii < len - 1
267 ? table.get(ii + 1).offset
268 : bufferLength);
269 }
270 }
271 return Pair.of(0, 0);
272 }
Janak82512512015-07-13 14:35:14 +0000273
274 @Override
275 public int hashCode() {
276 return Objects.hash(table, defaultPath, bufferLength);
277 }
278
279 @Override
280 public boolean equals(Object other) {
281 if (other == null || !other.getClass().equals(getClass())) {
282 return false;
283 }
284 HashLine that = (HashLine) other;
285 return this.bufferLength == that.bufferLength
286 && this.defaultPath.equals(that.defaultPath)
287 && this.table.equals(that.table);
288 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100289 }
290}