mirror of
https://github.com/aNNiMON/Own-Programming-Language-Tutorial.git
synced 2024-09-20 08:44:20 +03:00
Show location of the closest error from call stack
This commit is contained in:
parent
e304aafedd
commit
ae94a1dc8a
@ -4,14 +4,16 @@ import com.annimon.ownlang.lib.CallStack;
|
|||||||
import com.annimon.ownlang.outputsettings.ConsoleOutputSettings;
|
import com.annimon.ownlang.outputsettings.ConsoleOutputSettings;
|
||||||
import com.annimon.ownlang.outputsettings.OutputSettings;
|
import com.annimon.ownlang.outputsettings.OutputSettings;
|
||||||
import com.annimon.ownlang.stages.StagesData;
|
import com.annimon.ownlang.stages.StagesData;
|
||||||
import com.annimon.ownlang.util.ErrorsLocationFormatterStage;
|
import com.annimon.ownlang.util.*;
|
||||||
import com.annimon.ownlang.util.ExceptionConverterStage;
|
|
||||||
import com.annimon.ownlang.util.ExceptionStackTraceToStringStage;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.StringJoiner;
|
||||||
|
import static com.annimon.ownlang.util.ErrorsLocationFormatterStage.*;
|
||||||
|
|
||||||
public class Console {
|
public class Console {
|
||||||
|
|
||||||
@ -64,14 +66,29 @@ public class Console {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void handleException(StagesData stagesData, Thread thread, Exception exception) {
|
public static void handleException(StagesData stagesData, Thread thread, Exception exception) {
|
||||||
String mainError = new ExceptionConverterStage()
|
final var joiner = new StringJoiner("\n");
|
||||||
|
joiner.add(new ExceptionConverterStage()
|
||||||
.then((data, error) -> List.of(error))
|
.then((data, error) -> List.of(error))
|
||||||
.then(new ErrorsLocationFormatterStage())
|
.then(new ErrorsLocationFormatterStage())
|
||||||
.perform(stagesData, exception);
|
.perform(stagesData, exception));
|
||||||
String callStack = CallStack.getFormattedCalls();
|
final var processedPositions = stagesData.getOrDefault(TAG_POSITIONS, HashSet::new);
|
||||||
String stackTrace = new ExceptionStackTraceToStringStage()
|
if (processedPositions.isEmpty()) {
|
||||||
.perform(stagesData, exception);
|
// In case no source located errors were printed
|
||||||
error(String.join("\n", mainError, "Thread: " + thread.getName(), callStack, stackTrace));
|
// Find closest SourceLocated call stack frame
|
||||||
|
CallStack.getCalls().stream()
|
||||||
|
.limit(4)
|
||||||
|
.map(CallStack.CallInfo::range)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.findFirst()
|
||||||
|
.map(range -> new SourceLocationFormatterStage()
|
||||||
|
.perform(stagesData, range))
|
||||||
|
.ifPresent(joiner::add);
|
||||||
|
}
|
||||||
|
joiner.add("Thread: " + thread.getName());
|
||||||
|
joiner.add(CallStack.getFormattedCalls());
|
||||||
|
joiner.add(new ExceptionStackTraceToStringStage()
|
||||||
|
.perform(stagesData, exception));
|
||||||
|
error(joiner.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void handleException(Thread thread, Throwable throwable) {
|
public static void handleException(Thread thread, Throwable throwable) {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package com.annimon.ownlang.stages;
|
package com.annimon.ownlang.stages;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
public interface StagesData {
|
public interface StagesData {
|
||||||
|
|
||||||
<T> T get(String tag);
|
<T> T get(String tag);
|
||||||
@ -9,5 +11,10 @@ public interface StagesData {
|
|||||||
return value != null ? value : other;
|
return value != null ? value : other;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default <T> T getOrDefault(String tag, Supplier<? extends T> otherSuppler) {
|
||||||
|
T value = get(tag);
|
||||||
|
return value != null ? value : otherSuppler.get();
|
||||||
|
}
|
||||||
|
|
||||||
void put(String tag, Object input);
|
void put(String tag, Object input);
|
||||||
}
|
}
|
||||||
|
@ -4,53 +4,28 @@ import com.annimon.ownlang.Console;
|
|||||||
import com.annimon.ownlang.stages.Stage;
|
import com.annimon.ownlang.stages.Stage;
|
||||||
import com.annimon.ownlang.stages.StagesData;
|
import com.annimon.ownlang.stages.StagesData;
|
||||||
import com.annimon.ownlang.util.input.SourceLoaderStage;
|
import com.annimon.ownlang.util.input.SourceLoaderStage;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import static com.annimon.ownlang.util.SourceLocationFormatterStage.printPosition;
|
||||||
|
|
||||||
public class ErrorsLocationFormatterStage implements Stage<Iterable<? extends SourceLocatedError>, String> {
|
public class ErrorsLocationFormatterStage implements Stage<Iterable<? extends SourceLocatedError>, String> {
|
||||||
|
public static final String TAG_POSITIONS = "formattedPositions";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String perform(StagesData stagesData, Iterable<? extends SourceLocatedError> input) {
|
public String perform(StagesData stagesData, Iterable<? extends SourceLocatedError> input) {
|
||||||
final var sb = new StringBuilder();
|
final var sb = new StringBuilder();
|
||||||
final String source = stagesData.getOrDefault(SourceLoaderStage.TAG_SOURCE, "");
|
final var lines = stagesData.getOrDefault(SourceLoaderStage.TAG_SOURCE_LINES, new String[0]);
|
||||||
final var lines = source.split("\r?\n");
|
|
||||||
for (SourceLocatedError error : input) {
|
for (SourceLocatedError error : input) {
|
||||||
sb.append(Console.newline());
|
sb.append(Console.newline());
|
||||||
sb.append(error);
|
sb.append(error);
|
||||||
sb.append(Console.newline());
|
sb.append(Console.newline());
|
||||||
final Range range = error.getRange();
|
final Range range = error.getRange();
|
||||||
if (range != null && lines.length > 0) {
|
if (range != null && lines.length > 0) {
|
||||||
|
var positions = stagesData.getOrDefault(TAG_POSITIONS, HashSet::new);
|
||||||
|
positions.add(range);
|
||||||
|
stagesData.put(TAG_POSITIONS, positions);
|
||||||
printPosition(sb, range.normalize(), lines);
|
printPosition(sb, range.normalize(), lines);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void printPosition(StringBuilder sb, Range range, String[] lines) {
|
|
||||||
final Pos start = range.start();
|
|
||||||
final int linesCount = lines.length;;
|
|
||||||
if (range.isOnSameLine()) {
|
|
||||||
if (start.row() < linesCount) {
|
|
||||||
sb.append(lines[start.row()]);
|
|
||||||
sb.append(Console.newline());
|
|
||||||
sb.append(" ".repeat(start.col()));
|
|
||||||
sb.append("^".repeat(range.end().col() - start.col() + 1));
|
|
||||||
sb.append(Console.newline());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (start.row() < linesCount) {
|
|
||||||
String line = lines[start.row()];
|
|
||||||
sb.append(line);
|
|
||||||
sb.append(Console.newline());
|
|
||||||
sb.append(" ".repeat(start.col()));
|
|
||||||
sb.append("^".repeat(Math.max(1, line.length() - start.col())));
|
|
||||||
sb.append(Console.newline());
|
|
||||||
}
|
|
||||||
final Pos end = range.end();
|
|
||||||
if (end.row() < linesCount) {
|
|
||||||
sb.append(lines[end.row()]);
|
|
||||||
sb.append(Console.newline());
|
|
||||||
sb.append("^".repeat(end.col()));
|
|
||||||
sb.append(Console.newline());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,6 +1,10 @@
|
|||||||
package com.annimon.ownlang.util;
|
package com.annimon.ownlang.util;
|
||||||
|
|
||||||
public record SimpleError(String message) implements SourceLocatedError {
|
public record SimpleError(String message, Range range) implements SourceLocatedError {
|
||||||
|
public SimpleError(String message) {
|
||||||
|
this(message, null);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getMessage() {
|
public String getMessage() {
|
||||||
return message;
|
return message;
|
||||||
@ -10,4 +14,9 @@ public record SimpleError(String message) implements SourceLocatedError {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Range getRange() {
|
||||||
|
return range;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
package com.annimon.ownlang.util;
|
||||||
|
|
||||||
|
import com.annimon.ownlang.Console;
|
||||||
|
import com.annimon.ownlang.stages.Stage;
|
||||||
|
import com.annimon.ownlang.stages.StagesData;
|
||||||
|
import com.annimon.ownlang.util.input.SourceLoaderStage;
|
||||||
|
|
||||||
|
public class SourceLocationFormatterStage implements Stage<Range, String> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String perform(StagesData stagesData, Range input) {
|
||||||
|
final var lines = stagesData.getOrDefault(SourceLoaderStage.TAG_SOURCE_LINES, new String[0]);
|
||||||
|
final var sb = new StringBuilder();
|
||||||
|
if (input != null && lines.length > 0) {
|
||||||
|
printPosition(sb, input.normalize(), lines);
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void printPosition(StringBuilder sb, Range range, String[] lines) {
|
||||||
|
final Pos start = range.start();
|
||||||
|
final int linesCount = lines.length;;
|
||||||
|
if (range.isOnSameLine()) {
|
||||||
|
if (start.row() < linesCount) {
|
||||||
|
sb.append(lines[start.row()]);
|
||||||
|
sb.append(Console.newline());
|
||||||
|
sb.append(" ".repeat(start.col()));
|
||||||
|
sb.append("^".repeat(range.end().col() - start.col() + 1));
|
||||||
|
sb.append(Console.newline());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (start.row() < linesCount) {
|
||||||
|
String line = lines[start.row()];
|
||||||
|
sb.append(line);
|
||||||
|
sb.append(Console.newline());
|
||||||
|
sb.append(" ".repeat(start.col()));
|
||||||
|
sb.append("^".repeat(Math.max(1, line.length() - start.col())));
|
||||||
|
sb.append(Console.newline());
|
||||||
|
}
|
||||||
|
final Pos end = range.end();
|
||||||
|
if (end.row() < linesCount) {
|
||||||
|
sb.append(lines[end.row()]);
|
||||||
|
sb.append(Console.newline());
|
||||||
|
sb.append("^".repeat(end.col()));
|
||||||
|
sb.append(Console.newline());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,13 +10,16 @@ import java.nio.charset.StandardCharsets;
|
|||||||
|
|
||||||
public class SourceLoaderStage implements Stage<InputSource, String> {
|
public class SourceLoaderStage implements Stage<InputSource, String> {
|
||||||
|
|
||||||
public static final String TAG_SOURCE = "source";
|
public static final String TAG_SOURCE_LINES = "sourceLines";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String perform(StagesData stagesData, InputSource inputSource) {
|
public String perform(StagesData stagesData, InputSource inputSource) {
|
||||||
try {
|
try {
|
||||||
String result = inputSource.load();
|
String result = inputSource.load();
|
||||||
stagesData.put(TAG_SOURCE, result);
|
final var lines = (result == null || result.isEmpty())
|
||||||
|
? new String[0]
|
||||||
|
: result.split("\r?\n");
|
||||||
|
stagesData.put(TAG_SOURCE_LINES, lines);
|
||||||
return result;
|
return result;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new OwnLangRuntimeException("Unable to read input " + inputSource, e);
|
throw new OwnLangRuntimeException("Unable to read input " + inputSource, e);
|
||||||
|
Loading…
Reference in New Issue
Block a user