1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
|
/*
* Copyright 2016 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.turbine.diag;
import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Objects.requireNonNull;
import com.google.common.collect.ImmutableRangeMap;
import com.google.common.collect.Range;
/** Converts source positions to line and column information, for diagnostic formatting. */
public class LineMap {
private final String source;
private final ImmutableRangeMap<Integer, Integer> lines;
private LineMap(String source, ImmutableRangeMap<Integer, Integer> lines) {
this.source = source;
this.lines = lines;
}
public static LineMap create(String source) {
int last = 0;
int line = 1;
ImmutableRangeMap.Builder<Integer, Integer> builder = ImmutableRangeMap.builder();
for (int idx = 0; idx < source.length(); idx++) {
char ch = source.charAt(idx);
switch (ch) {
// handle CR line endings
case '\r':
// ...and CRLF
if (idx + 1 < source.length() && source.charAt(idx + 1) == '\n') {
idx++;
}
// falls through
case '\n':
builder.put(Range.closedOpen(last, idx + 1), line++);
last = idx + 1;
break;
default:
break;
}
}
// no trailing newline
if (last < source.length()) {
builder.put(Range.closedOpen(last, source.length()), line++);
}
return new LineMap(source, builder.build());
}
/** The zero-indexed column number of the given source position. */
public int column(int position) {
checkArgument(0 <= position && position < source.length(), "%s", position);
// requireNonNull is safe because `lines` covers the whole file length.
return position - requireNonNull(lines.getEntry(position)).getKey().lowerEndpoint();
}
/** The one-indexed line number of the given source position. */
public int lineNumber(int position) {
checkArgument(0 <= position && position < source.length(), "%s", position);
// requireNonNull is safe because `lines` covers the whole file length.
return requireNonNull(lines.get(position));
}
/** The one-indexed line of the given source position. */
public String line(int position) {
checkArgument(0 <= position && position < source.length(), "%s", position);
// requireNonNull is safe because `lines` covers the whole file length.
Range<Integer> range = requireNonNull(lines.getEntry(position)).getKey();
return source.substring(range.lowerEndpoint(), range.upperEndpoint());
}
}
|