1 /*
2 * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package jdk.incubator.code.extern.impl;
27
28 import java.util.BitSet;
29
30 /**
31 * A class that defines source code positions as simple character
32 * offsets from the beginning of the file. The first character
33 * is at position 0.
34 * <p>
35 * Support is also provided for (line,column) coordinates, but tab
36 * expansion is optional and no Unicode escape translation is considered.
37 * The first character is at location (1,1).
38 *
39 * <p><b>This is NOT part of any supported API.
40 * If you write code that depends on this, you do so at your own risk.
41 * This code and its internal interfaces are subject to change or
42 * deletion without notice.</b>
43 */
44 public final class Position {
45 static final int NOPOS = -1;
46
47 static final int FIRSTPOS = 0;
48 static final int FIRSTLINE = 1;
49 static final int FIRSTCOLUMN = 1;
50
51 static final int LINESHIFT = 10;
52 static final int MAXCOLUMN = (1 << LINESHIFT) - 1;
53 static final int MAXLINE = (1 << (Integer.SIZE - LINESHIFT)) - 1;
54
55 static final int MAXPOS = Integer.MAX_VALUE;
56
57 /**
58 * This is class is not supposed to be instantiated.
59 */
60 private Position() {
61 }
62
63 /**
64 * A two-way map between line/column numbers and positions,
65 * derived from a scan done at creation time. Tab expansion is
66 * optionally supported via a character map. Text content
67 * is not retained.
68 * <p>
69 * Notes: The first character position FIRSTPOS is at
70 * (FIRSTLINE,FIRSTCOLUMN). No account is taken of Unicode escapes.
71 *
72 * @param src Source characters
73 * @param max Number of characters to read
74 * @param expandTabs If true, expand tabs when calculating columns
75 */
76 static LineMap makeLineMap(char[] src, int max, boolean expandTabs) {
77 LineMapImpl lineMap = expandTabs ?
78 new LineTabMapImpl(max) : new LineMapImpl();
79 lineMap.build(src, max);
80 return lineMap;
81 }
82
83 /**
84 * Encode line and column numbers in an integer as:
85 * {@code line-number << LINESHIFT + column-number }.
86 * {@link Position#NOPOS} represents an undefined position.
87 *
88 * @param line number of line (first is 1)
89 * @param col number of character on line (first is 1)
90 * @return an encoded position or {@link Position#NOPOS}
91 * if the line or column number is too big to
92 * represent in the encoded format
93 * @throws IllegalArgumentException if line or col is less than 1
94 */
95 static int encodePosition(int line, int col) {
96 if (line < 1)
97 throw new IllegalArgumentException("line must be greater than 0");
98 if (col < 1)
99 throw new IllegalArgumentException("column must be greater than 0");
100
101 if (line > MAXLINE || col > MAXCOLUMN) {
102 return NOPOS;
103 }
104 return (line << LINESHIFT) + col;
105 }
106
107 public interface LineMap {
108 /**
109 * Finds the start position of a line.
110 *
111 * @param line line number (beginning at 1)
112 * @return position of first character in line
113 * @throws IndexOutOfBoundsException if {@code lineNumber < 1}
114 * if {@code lineNumber > no. of lines}
115 */
116 long getStartPosition(long line);
117
118 /**
119 * Finds the position corresponding to a (line,column).
120 *
121 * @param line line number (beginning at 1)
122 * @param column tab-expanded column number (beginning 1)
123 * @return position of character
124 * @throws IndexOutOfBoundsException if {@code line < 1}
125 * if {@code line > no. of lines}
126 */
127 long getPosition(long line, long column);
128
129 /**
130 * Finds the line containing a position; a line termination
131 * character is on the line it terminates.
132 *
133 * @param pos character offset of the position
134 * @return the line number of pos (first line is 1)
135 */
136 long getLineNumber(long pos);
137
138 /**
139 * Finds the column for a character position.
140 * Tab characters preceding the position on the same line
141 * will be expanded when calculating the column number.
142 *
143 * @param pos character offset of the position
144 * @return the tab-expanded column number of pos (first column is 1)
145 */
146 long getColumnNumber(long pos);
147
148 /**
149 * Find the start position of a line.
150 *
151 * @param line number of line (first is 1)
152 * @return position of first character in line
153 * @throws ArrayIndexOutOfBoundsException if {@code lineNumber < 1}
154 * if {@code lineNumber > no. of lines}
155 */
156 int getStartPosition(int line);
157
158 /**
159 * Find the position corresponding to a (line,column).
160 *
161 * @param line number of line (first is 1)
162 * @param column number of character on line (first is 1)
163 * @return position of character
164 * @throws ArrayIndexOutOfBoundsException if {@code line < 1}
165 * if {@code line > no. of lines}
166 */
167 int getPosition(int line, int column);
168
169 /**
170 * Find the line containing a position; a line termination
171 * character is on the line it terminates.
172 *
173 * @param pos character offset of the position
174 * @return the line number on which pos occurs (first line is 1)
175 */
176 int getLineNumber(int pos);
177
178 /**
179 * Find the column for a character position.
180 * Note: this method does not handle tab expansion.
181 * If tab expansion is needed, use a LineTabMap instead.
182 *
183 * @param pos character offset of the position
184 * @return the column number at which pos occurs
185 */
186 int getColumnNumber(int pos);
187 }
188
189 static class LineMapImpl implements LineMap {
190 int[] startPosition; // start position of each line
191
192 LineMapImpl() {
193 }
194
195 void build(char[] src, int max) {
196 int c = 0;
197 int i = 0;
198 int[] linebuf = new int[max];
199 while (i < max) {
200 linebuf[c++] = i;
201 do {
202 char ch = src[i];
203 if (ch == '\r' || ch == '\n') {
204 if (ch == '\r' && (i + 1) < max && src[i + 1] == '\n')
205 i += 2;
206 else
207 ++i;
208 break;
209 } else if (ch == '\t')
210 setTabPosition(i);
211 } while (++i < max);
212 }
213 this.startPosition = new int[c];
214 System.arraycopy(linebuf, 0, startPosition, 0, c);
215 }
216
217 @Override
218 public int getStartPosition(int line) {
219 return startPosition[line - FIRSTLINE];
220 }
221
222 @Override
223 public long getStartPosition(long line) {
224 return getStartPosition(longToInt(line));
225 }
226
227 @Override
228 public int getPosition(int line, int column) {
229 return startPosition[line - FIRSTLINE] + column - FIRSTCOLUMN;
230 }
231
232 @Override
233 public long getPosition(long line, long column) {
234 return getPosition(longToInt(line), longToInt(column));
235 }
236
237 // Cache of last line number lookup
238 private int lastPosition = Position.FIRSTPOS;
239 private int lastLine = Position.FIRSTLINE;
240
241 @Override
242 public int getLineNumber(int pos) {
243 if (pos == lastPosition) {
244 return lastLine;
245 }
246 lastPosition = pos;
247
248 int low = 0;
249 int high = startPosition.length - 1;
250 while (low <= high) {
251 int mid = (low + high) >> 1;
252 int midVal = startPosition[mid];
253
254 if (midVal < pos)
255 low = mid + 1;
256 else if (midVal > pos)
257 high = mid - 1;
258 else {
259 lastLine = mid + 1; // pos is at beginning of this line
260 return lastLine;
261 }
262 }
263 lastLine = low;
264 return lastLine; // pos is on this line
265 }
266
267 @Override
268 public long getLineNumber(long pos) {
269 return getLineNumber(longToInt(pos));
270 }
271
272 @Override
273 public int getColumnNumber(int pos) {
274 return pos - startPosition[getLineNumber(pos) - FIRSTLINE] + FIRSTCOLUMN;
275 }
276
277 @Override
278 public long getColumnNumber(long pos) {
279 return getColumnNumber(longToInt(pos));
280 }
281
282 static int longToInt(long longValue) {
283 int intValue = (int) longValue;
284 if (intValue != longValue)
285 throw new IndexOutOfBoundsException();
286 return intValue;
287 }
288
289 void setTabPosition(int offset) {
290 }
291 }
292
293 /**
294 * A LineMap that handles tab expansion correctly. The cost is
295 * an additional bit per character in the source array.
296 */
297 static class LineTabMapImpl extends LineMapImpl {
298 private final BitSet tabMap; // bits set for tab positions.
299
300 LineTabMapImpl(int max) {
301 super();
302 tabMap = new BitSet(max);
303 }
304
305 @Override
306 void setTabPosition(int offset) {
307 tabMap.set(offset);
308 }
309
310 @Override
311 public int getColumnNumber(int pos) {
312 int lineStart = startPosition[getLineNumber(pos) - FIRSTLINE];
313 int column = 0;
314 for (int bp = lineStart; bp < pos; bp++) {
315 if (tabMap.get(bp))
316 column = tabulate(column);
317 else
318 column++;
319 }
320 return column + FIRSTCOLUMN;
321 }
322
323 @Override
324 public int getPosition(int line, int column) {
325 int pos = startPosition[line - FIRSTLINE];
326 column -= FIRSTCOLUMN;
327 int col = 0;
328 while (col < column) {
329 pos++;
330 if (tabMap.get(pos))
331 col = tabulate(col);
332 else
333 col++;
334 }
335 return pos;
336 }
337 }
338
339 /**
340 * Tabulator column increment.
341 */
342 static final int TabInc = 8;
343
344 /**
345 * Bump column to the next tab.
346 */
347 static int tabulate(int column) {
348 return (column / TabInc * TabInc) + TabInc;
349 }
350 }