java-commons/muscript/src/main/java/io/gitlab/jfronny/muscript/error/LocationalError.java

101 lines
4.2 KiB
Java

package io.gitlab.jfronny.muscript.error;
import org.jetbrains.annotations.Nullable;
/**
* Class for storing errors with code context
* Can be generated from a LocationalException with asPrintable
*
* @param message The error message
* @param start The location of the start of this error. Both it and its components must not be null.
* @param end The location of the end of this error. May be null.
*/
public record LocationalError(String message, Location start, @Nullable Location end) {
/**
* A location in the source code
*
* @param line The code of the line this is in
* @param column The column of this character, starting with 0
* @param row The row of this line, starting with 1
*/
public record Location(String line, int column, int row) {
public static Location create(String source, int index) {
if (index < 0) return new Location("", -1, -1);
int lineStart = source.lastIndexOf('\n', index);
int lineEnd = source.indexOf('\n', index);
if (lineEnd == -1) lineEnd = source.length();
int lineIndex = lineStart > 0 ? (int) source.substring(0, lineStart).chars().filter(c -> c == '\n').count() : 0;
int column = index - lineStart - 1;
String line = lineStart == source.length() - 1 ? "" : source.substring(lineStart + 1, lineEnd);
return new Location(line, column, lineIndex + 1);
}
@Override
public String toString() {
if (column >= line.length()) return "character " + (column + 1) + " of line " + row;
else return "'" + line.charAt(column) + "' (character " + (column + 1) + ")";
}
public String prettyPrint() {
String linePrefix = String.format("%1$6d", row) + " | ";
return linePrefix + line + "\n" + " ".repeat(linePrefix.length() + column) + "^-- ";
}
}
/**
* Generates an error by getting the relevant data from what is available while lexing/parsing
*
* @param source The complete source for the code that generated this error
* @param start The index of the start of the expression that caused this error
* @param message The error message
* @return An error using the provided information
*/
public static LocationalError create(String source, int start, String message) {
return new LocationalError(message, Location.create(source, start), null);
}
/**
* Generates an error by getting the relevant data from what is available while lexing/parsing
*
* @param source The complete source for the code that generated this error
* @param start The index of the start of the expression that caused this error
* @param end The index of the end of the expression that caused this error
* @param message The error message
* @return An error using the provided information
*/
public static LocationalError create(String source, int start, int end, String message) {
if (end == start) return create(source, start, message);
if (end < start) {
int a = end;
end = start;
start = a;
}
return new LocationalError(message, Location.create(source, start), Location.create(source, end));
}
/**
* Converts this error to a human-readable error message
*
* @return A string representation
*/
@Override
public String toString() {
if (start.column < 0) return "Error at unknown location: " + message;
if (end == null) return "Error at " + start + ": " + message + "\n" + start.prettyPrint() + "Here";
if (start.row == end.row) {
String linePrefix = String.format("%1$6d", start.row) + " | ";
return "Error at " + start + ": " + message + "\n"
+ linePrefix + start.line + "\n"
+ " ".repeat(linePrefix.length() + start.column) + "^"
+ "-".repeat(end.column - start.column - 1)
+ "^-- Here";
}
return "Error at " + start + ": " + message + "\n"
+ start.prettyPrint() + "From here"
+ end.prettyPrint() + "to here";
}
}