blob: 3304a6e550ee6c9e2e08fa0d0edb7ad49687106c [file] [log] [blame]
// 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 com.google.devtools.build.lib.events;
import com.google.common.base.Preconditions;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import java.io.Serializable;
import java.util.Objects;
/**
* A Location denotes a position within a Starlark file.
*
* <p>A location is a triple {@code (file, line column)}, where {@code file} is the apparent name of
* the file, {@code line} is the optional 1-based line number, and {@code column} is the optional
* 1-based column number measured in UTF-16 code units. If the column is zero it is not displayed.
* If the line number is also zero, it too is not displayed; in this case, the location denotes the
* file as a whole.
*/
// TODO(adonovan): invert the dependency syntax -> events dependency.
// That is, make this package depend on the Starlark syntax package,
// not the other way around.
// This Location type belongs in lib.syntax.
//
// Due to recent lib.syntax clean-ups, Location construction is
// infrequent during normal execution; Locations are constructed only
// on error.
//
// The syntax package also depends on Event, both for print events
// (which can be eliminated by defining a PrintHandler interface
// and defining an adaptor for it in lib.events) and for syntax errors,
// for which lib.syntax will need a new String+Location error type,
// which again this package will adapt to events.Event.
public abstract class Location implements Serializable, Comparable<Location> {
// TODO(adonovan): merge with Lexer.LexerLocation and make this the only implementation of
// Location, once the parser no longer eagerly creates Locations but instead
// records token offsets and the LineNumberTable in the tree.
@AutoCodec
@Immutable
static class FileLineColumn extends Location {
final String file;
final LineAndColumn linecol;
FileLineColumn(String file, LineAndColumn linecol) {
this.file = Preconditions.checkNotNull(file);
this.linecol = linecol;
}
@Override
public String file() {
return file;
}
@Override
protected LineAndColumn getLineAndColumn() {
return linecol;
}
}
/**
* Returns a Location for the given file, line and column. If {@code column} is non-zero, {@code
* line} too must be non-zero.
*/
public static Location fromFileLineColumn(String file, int line, int column) {
Preconditions.checkArgument(line != 0 || column == 0, "non-zero column but no line number");
return new FileLineColumn(file, new LineAndColumn(line, column));
}
/** Returns a Location for the file as a whole. */
public static Location fromFile(String file) {
return new FileLineColumn(file, ZERO);
}
private static final LineAndColumn ZERO = new LineAndColumn(0, 0);
/** Returns the name of the file containing this location. */
public abstract String file();
protected abstract LineAndColumn getLineAndColumn();
/** Returns the line number of this location. */
public final int line() {
return getLineAndColumn().line;
}
/** Returns the column number of this location. */
public final int column() {
return getLineAndColumn().column;
}
/**
* Formats the location as {@code "file:line:col"}. If the column is zero, it is omitted. If the
* line is also zero, it too is omitted.
*/
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append(file());
LineAndColumn linecol = getLineAndColumn();
if (linecol.line != 0) {
buf.append(':').append(linecol.line);
if (linecol.column != 0) {
buf.append(':').append(linecol.column);
}
}
return buf.toString();
}
/** A value class that describes the line and column of a location. */
// TODO(adonovan): make private when we combine with LexerLocation.
@AutoCodec
@Immutable
public static final class LineAndColumn {
public final int line;
public final int column;
public LineAndColumn(int line, int column) {
this.line = line;
this.column = column;
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof LineAndColumn)) {
return false;
}
LineAndColumn lac = (LineAndColumn) o;
return lac.line == line && lac.column == column;
}
@Override
public int hashCode() {
return line * 41 + column;
}
@Override
public String toString() {
return line + ":" + column;
}
}
/** Returns a three-valued lexicographical comparison of two Locations. */
@Override
public final int compareTo(Location that) {
int cmp = this.file().compareTo(that.file());
if (cmp != 0) {
return cmp;
}
LineAndColumn x = this.getLineAndColumn();
LineAndColumn y = that.getLineAndColumn();
return Long.compare(((long) x.line << 32) | x.column, ((long) y.line << 32) | y.column);
}
@Override
public final int hashCode() {
return Objects.hash(file(), getLineAndColumn());
}
@Override
public final boolean equals(Object that) {
return this == that
|| that instanceof Location
&& this.file().equals(((Location) that).file())
&& this.getLineAndColumn().equals(((Location) that).getLineAndColumn());
}
/** A location for built-in functions. */
@AutoCodec public static final Location BUILTIN = fromFile("<builtin>");
}