blob: b35dfb3db2e010445ae4c59d3d51f1e7f867e2cd [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
mjhalupka374ad9e2018-06-29 12:35:11 -070017import com.google.common.collect.Interner;
18import com.google.devtools.build.lib.concurrent.BlazeInterners;
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;
shahan7ac7b632018-01-16 18:21:16 -080021import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010022import com.google.devtools.build.lib.util.Pair;
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;
Janak82512512015-07-13 14:35:14 +000025import java.util.Arrays;
Janak82512512015-07-13 14:35:14 +000026import java.util.Objects;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010027
28/**
29 * A table to keep track of line numbers in source files. The client creates a LineNumberTable for
30 * their buffer using {@link #create}. The client can then ask for the line and column given a
31 * position using ({@link #getLineAndColumn(int)}).
32 */
laurentlb229ca182018-02-15 07:58:03 -080033@AutoCodec
34@Immutable
35public class LineNumberTable implements Serializable {
mjhalupka374ad9e2018-06-29 12:35:11 -070036 private static final Interner<LineNumberTable> LINE_NUMBER_TABLE_INTERNER =
37 BlazeInterners.newWeakInterner();
38
laurentlb229ca182018-02-15 07:58:03 -080039 /** A mapping from line number (line >= 1) to character offset into the file. */
40 private final int[] linestart;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010041
laurentlb229ca182018-02-15 07:58:03 -080042 private final PathFragment path;
43 private final int bufferLength;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010044
laurentlb229ca182018-02-15 07:58:03 -080045 public LineNumberTable(char[] buffer, PathFragment path) {
46 this(computeLinestart(buffer), path, buffer.length);
47 }
48
laurentlb229ca182018-02-15 07:58:03 -080049 LineNumberTable(int[] linestart, PathFragment path, int bufferLength) {
50 this.linestart = linestart;
51 this.path = path;
52 this.bufferLength = bufferLength;
53 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010054
Googler99cbc172018-11-08 07:43:00 -080055 @AutoCodec.Instantiator
56 static LineNumberTable createForSerialization(
57 int[] linestart, PathFragment path, int bufferLength) {
58 return LINE_NUMBER_TABLE_INTERNER.intern(new LineNumberTable(linestart, path, bufferLength));
59 }
60
Lukacs Berki48338222015-06-12 11:37:46 +000061 static LineNumberTable create(char[] buffer, PathFragment path) {
laurentlb229ca182018-02-15 07:58:03 -080062 return new LineNumberTable(buffer, path);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010063 }
64
laurentlb229ca182018-02-15 07:58:03 -080065 private int getLineAt(int offset) {
66 if (offset < 0) {
67 throw new IllegalStateException("Illegal position: " + offset);
shahan7ac7b632018-01-16 18:21:16 -080068 }
laurentlb229ca182018-02-15 07:58:03 -080069 int lowBoundary = 1;
70 int highBoundary = linestart.length - 1;
71 while (true) {
72 if ((highBoundary - lowBoundary) <= 1) {
73 if (linestart[highBoundary] > offset) {
74 return lowBoundary;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010075 } else {
laurentlb229ca182018-02-15 07:58:03 -080076 return highBoundary;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010077 }
78 }
laurentlb229ca182018-02-15 07:58:03 -080079 int medium = lowBoundary + ((highBoundary - lowBoundary) >> 1);
80 if (linestart[medium] > offset) {
81 highBoundary = medium;
82 } else {
83 lowBoundary = medium;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010084 }
shahan7ac7b632018-01-16 18:21:16 -080085 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010086 }
87
laurentlb229ca182018-02-15 07:58:03 -080088 LineAndColumn getLineAndColumn(int offset) {
89 int line = getLineAt(offset);
90 int column = offset - linestart[line] + 1;
91 return new LineAndColumn(line, column);
92 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010093
laurentlb229ca182018-02-15 07:58:03 -080094 PathFragment getPath(int offset) {
95 return path;
96 }
shahan7ac7b632018-01-16 18:21:16 -080097
laurentlb229ca182018-02-15 07:58:03 -080098 Pair<Integer, Integer> getOffsetsForLine(int line) {
99 if (line <= 0 || line >= linestart.length) {
100 throw new IllegalArgumentException("Illegal line: " + line);
101 }
102 return Pair.of(
103 linestart[line], line < linestart.length - 1 ? linestart[line + 1] : bufferLength);
104 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100105
laurentlb229ca182018-02-15 07:58:03 -0800106 @Override
107 public int hashCode() {
108 return Objects.hash(Arrays.hashCode(linestart), path, bufferLength);
109 }
shahan7ac7b632018-01-16 18:21:16 -0800110
laurentlb229ca182018-02-15 07:58:03 -0800111 @Override
112 public boolean equals(Object other) {
113 if (other == null || !other.getClass().equals(getClass())) {
114 return false;
115 }
116 LineNumberTable that = (LineNumberTable) other;
117 return this.bufferLength == that.bufferLength
118 && Arrays.equals(this.linestart, that.linestart)
119 && Objects.equals(this.path, that.path);
120 }
shahan7ac7b632018-01-16 18:21:16 -0800121
laurentlb229ca182018-02-15 07:58:03 -0800122 private static int[] computeLinestart(char[] buffer) {
123 // Compute the size.
124 int size = 2;
125 for (int i = 0; i < buffer.length; i++) {
126 if (buffer[i] == '\n') {
127 size++;
shahan7ac7b632018-01-16 18:21:16 -0800128 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100129 }
laurentlb229ca182018-02-15 07:58:03 -0800130 int[] linestart = new int[size];
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100131
laurentlb229ca182018-02-15 07:58:03 -0800132 int index = 0;
133 linestart[index++] = 0; // The 0th line does not exist - so we fill something in
134 // to make sure the start pos for the 1st line ends up at
135 // linestart[1]. Using 0 is useful for tables that are
136 // completely empty.
137 linestart[index++] = 0; // The first line ("line 1") starts at offset 0.
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000138
laurentlb229ca182018-02-15 07:58:03 -0800139 // Scan the buffer and record the offset of each line start. Doing this
140 // once upfront is faster than checking each char as it is pulled from
141 // the buffer.
142 for (int i = 0; i < buffer.length; i++) {
143 if (buffer[i] == '\n') {
144 linestart[index++] = i + 1;
Eric Fellheimer7ce73fb2015-04-07 13:18:12 +0000145 }
146 }
laurentlb229ca182018-02-15 07:58:03 -0800147 return linestart;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100148 }
149}