| // Copyright 2014 The Bazel Authors. 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 net.starlark.java.syntax; | |
| import java.util.Arrays; | |
| import java.util.Objects; | |
| import javax.annotation.concurrent.Immutable; | |
| /** | |
| * FileLocations maps each source offset within a file to a Location. An offset is a (UTF-16) char | |
| * index such that {@code 0 <= offset <= size}. A Location is a (file, line, column) triple. | |
| */ | |
| @Immutable | |
| final class FileLocations { | |
| private final int[] linestart; // maps line number (line >= 1) to char offset | |
| private final String file; | |
| private final int size; // size of file in chars | |
| private FileLocations(int[] linestart, String file, int size) { | |
| this.linestart = linestart; | |
| this.file = file; | |
| this.size = size; | |
| } | |
| static FileLocations create(char[] buffer, String file) { | |
| return new FileLocations(computeLinestart(buffer), file, buffer.length); | |
| } | |
| String file() { | |
| return file; | |
| } | |
| private int getLineAt(int offset) { | |
| if (offset < 0 || offset > size) { | |
| throw new IllegalStateException("Illegal position: " + offset); | |
| } | |
| int lowBoundary = 1; | |
| int highBoundary = linestart.length - 1; | |
| while (true) { | |
| if ((highBoundary - lowBoundary) <= 1) { | |
| if (linestart[highBoundary] > offset) { | |
| return lowBoundary; | |
| } else { | |
| return highBoundary; | |
| } | |
| } | |
| int medium = lowBoundary + ((highBoundary - lowBoundary) >> 1); | |
| if (linestart[medium] > offset) { | |
| highBoundary = medium; | |
| } else { | |
| lowBoundary = medium; | |
| } | |
| } | |
| } | |
| Location getLocation(int offset) { | |
| int line = getLineAt(offset); | |
| int column = offset - linestart[line] + 1; | |
| return new Location(file, line, column); | |
| } | |
| int size() { | |
| return size; | |
| } | |
| @Override | |
| public int hashCode() { | |
| return Objects.hash(Arrays.hashCode(linestart), file, size); | |
| } | |
| @Override | |
| public boolean equals(Object other) { | |
| if (!(other instanceof FileLocations)) { | |
| return false; | |
| } | |
| FileLocations that = (FileLocations) other; | |
| return this.size == that.size | |
| && Arrays.equals(this.linestart, that.linestart) | |
| && this.file.equals(that.file); | |
| } | |
| private static int[] computeLinestart(char[] buffer) { | |
| // Compute the size. | |
| int size = 2; | |
| for (int i = 0; i < buffer.length; i++) { | |
| if (buffer[i] == '\n') { | |
| size++; | |
| } | |
| } | |
| int[] linestart = new int[size]; | |
| int index = 0; | |
| linestart[index++] = 0; // The 0th line does not exist - so we fill something in | |
| // to make sure the start pos for the 1st line ends up at | |
| // linestart[1]. Using 0 is useful for tables that are | |
| // completely empty. | |
| linestart[index++] = 0; // The first line ("line 1") starts at offset 0. | |
| // Scan the buffer and record the offset of each line start. Doing this | |
| // once upfront is faster than checking each char as it is pulled from | |
| // the buffer. | |
| for (int i = 0; i < buffer.length; i++) { | |
| if (buffer[i] == '\n') { | |
| linestart[index++] = i + 1; | |
| } | |
| } | |
| return linestart; | |
| } | |
| } |