Skip to content

Commit ae94a1d

Browse files
committed
Show location of the closest error from call stack
1 parent e304aaf commit ae94a1d

File tree

6 files changed

+104
-44
lines changed

6 files changed

+104
-44
lines changed

ownlang-core/src/main/java/com/annimon/ownlang/Console.java

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@
44
import com.annimon.ownlang.outputsettings.ConsoleOutputSettings;
55
import com.annimon.ownlang.outputsettings.OutputSettings;
66
import com.annimon.ownlang.stages.StagesData;
7-
import com.annimon.ownlang.util.ErrorsLocationFormatterStage;
8-
import com.annimon.ownlang.util.ExceptionConverterStage;
9-
import com.annimon.ownlang.util.ExceptionStackTraceToStringStage;
7+
import com.annimon.ownlang.util.*;
108
import java.io.ByteArrayOutputStream;
119
import java.io.File;
1210
import java.io.PrintStream;
1311
import java.nio.charset.StandardCharsets;
12+
import java.util.HashSet;
1413
import java.util.List;
14+
import java.util.Objects;
15+
import java.util.StringJoiner;
16+
import static com.annimon.ownlang.util.ErrorsLocationFormatterStage.*;
1517

1618
public class Console {
1719

@@ -64,14 +66,29 @@ public static void error(CharSequence value) {
6466
}
6567

6668
public static void handleException(StagesData stagesData, Thread thread, Exception exception) {
67-
String mainError = new ExceptionConverterStage()
69+
final var joiner = new StringJoiner("\n");
70+
joiner.add(new ExceptionConverterStage()
6871
.then((data, error) -> List.of(error))
6972
.then(new ErrorsLocationFormatterStage())
70-
.perform(stagesData, exception);
71-
String callStack = CallStack.getFormattedCalls();
72-
String stackTrace = new ExceptionStackTraceToStringStage()
73-
.perform(stagesData, exception);
74-
error(String.join("\n", mainError, "Thread: " + thread.getName(), callStack, stackTrace));
73+
.perform(stagesData, exception));
74+
final var processedPositions = stagesData.getOrDefault(TAG_POSITIONS, HashSet::new);
75+
if (processedPositions.isEmpty()) {
76+
// In case no source located errors were printed
77+
// Find closest SourceLocated call stack frame
78+
CallStack.getCalls().stream()
79+
.limit(4)
80+
.map(CallStack.CallInfo::range)
81+
.filter(Objects::nonNull)
82+
.findFirst()
83+
.map(range -> new SourceLocationFormatterStage()
84+
.perform(stagesData, range))
85+
.ifPresent(joiner::add);
86+
}
87+
joiner.add("Thread: " + thread.getName());
88+
joiner.add(CallStack.getFormattedCalls());
89+
joiner.add(new ExceptionStackTraceToStringStage()
90+
.perform(stagesData, exception));
91+
error(joiner.toString());
7592
}
7693

7794
public static void handleException(Thread thread, Throwable throwable) {

ownlang-core/src/main/java/com/annimon/ownlang/stages/StagesData.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.annimon.ownlang.stages;
22

3+
import java.util.function.Supplier;
4+
35
public interface StagesData {
46

57
<T> T get(String tag);
@@ -9,5 +11,10 @@ default <T> T getOrDefault(String tag, T other) {
911
return value != null ? value : other;
1012
}
1113

14+
default <T> T getOrDefault(String tag, Supplier<? extends T> otherSuppler) {
15+
T value = get(tag);
16+
return value != null ? value : otherSuppler.get();
17+
}
18+
1219
void put(String tag, Object input);
1320
}

ownlang-core/src/main/java/com/annimon/ownlang/util/ErrorsLocationFormatterStage.java

Lines changed: 7 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,53 +4,28 @@
44
import com.annimon.ownlang.stages.Stage;
55
import com.annimon.ownlang.stages.StagesData;
66
import com.annimon.ownlang.util.input.SourceLoaderStage;
7+
import java.util.HashSet;
8+
import static com.annimon.ownlang.util.SourceLocationFormatterStage.printPosition;
79

810
public class ErrorsLocationFormatterStage implements Stage<Iterable<? extends SourceLocatedError>, String> {
11+
public static final String TAG_POSITIONS = "formattedPositions";
912

1013
@Override
1114
public String perform(StagesData stagesData, Iterable<? extends SourceLocatedError> input) {
1215
final var sb = new StringBuilder();
13-
final String source = stagesData.getOrDefault(SourceLoaderStage.TAG_SOURCE, "");
14-
final var lines = source.split("\r?\n");
16+
final var lines = stagesData.getOrDefault(SourceLoaderStage.TAG_SOURCE_LINES, new String[0]);
1517
for (SourceLocatedError error : input) {
1618
sb.append(Console.newline());
1719
sb.append(error);
1820
sb.append(Console.newline());
1921
final Range range = error.getRange();
2022
if (range != null && lines.length > 0) {
23+
var positions = stagesData.getOrDefault(TAG_POSITIONS, HashSet::new);
24+
positions.add(range);
25+
stagesData.put(TAG_POSITIONS, positions);
2126
printPosition(sb, range.normalize(), lines);
2227
}
2328
}
2429
return sb.toString();
2530
}
26-
27-
private static void printPosition(StringBuilder sb, Range range, String[] lines) {
28-
final Pos start = range.start();
29-
final int linesCount = lines.length;;
30-
if (range.isOnSameLine()) {
31-
if (start.row() < linesCount) {
32-
sb.append(lines[start.row()]);
33-
sb.append(Console.newline());
34-
sb.append(" ".repeat(start.col()));
35-
sb.append("^".repeat(range.end().col() - start.col() + 1));
36-
sb.append(Console.newline());
37-
}
38-
} else {
39-
if (start.row() < linesCount) {
40-
String line = lines[start.row()];
41-
sb.append(line);
42-
sb.append(Console.newline());
43-
sb.append(" ".repeat(start.col()));
44-
sb.append("^".repeat(Math.max(1, line.length() - start.col())));
45-
sb.append(Console.newline());
46-
}
47-
final Pos end = range.end();
48-
if (end.row() < linesCount) {
49-
sb.append(lines[end.row()]);
50-
sb.append(Console.newline());
51-
sb.append("^".repeat(end.col()));
52-
sb.append(Console.newline());
53-
}
54-
}
55-
}
5631
}

ownlang-core/src/main/java/com/annimon/ownlang/util/SimpleError.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package com.annimon.ownlang.util;
22

3-
public record SimpleError(String message) implements SourceLocatedError {
3+
public record SimpleError(String message, Range range) implements SourceLocatedError {
4+
public SimpleError(String message) {
5+
this(message, null);
6+
}
7+
48
@Override
59
public String getMessage() {
610
return message;
@@ -10,4 +14,9 @@ public String getMessage() {
1014
public String toString() {
1115
return message;
1216
}
17+
18+
@Override
19+
public Range getRange() {
20+
return range;
21+
}
1322
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.annimon.ownlang.util;
2+
3+
import com.annimon.ownlang.Console;
4+
import com.annimon.ownlang.stages.Stage;
5+
import com.annimon.ownlang.stages.StagesData;
6+
import com.annimon.ownlang.util.input.SourceLoaderStage;
7+
8+
public class SourceLocationFormatterStage implements Stage<Range, String> {
9+
10+
@Override
11+
public String perform(StagesData stagesData, Range input) {
12+
final var lines = stagesData.getOrDefault(SourceLoaderStage.TAG_SOURCE_LINES, new String[0]);
13+
final var sb = new StringBuilder();
14+
if (input != null && lines.length > 0) {
15+
printPosition(sb, input.normalize(), lines);
16+
}
17+
return sb.toString();
18+
}
19+
20+
static void printPosition(StringBuilder sb, Range range, String[] lines) {
21+
final Pos start = range.start();
22+
final int linesCount = lines.length;;
23+
if (range.isOnSameLine()) {
24+
if (start.row() < linesCount) {
25+
sb.append(lines[start.row()]);
26+
sb.append(Console.newline());
27+
sb.append(" ".repeat(start.col()));
28+
sb.append("^".repeat(range.end().col() - start.col() + 1));
29+
sb.append(Console.newline());
30+
}
31+
} else {
32+
if (start.row() < linesCount) {
33+
String line = lines[start.row()];
34+
sb.append(line);
35+
sb.append(Console.newline());
36+
sb.append(" ".repeat(start.col()));
37+
sb.append("^".repeat(Math.max(1, line.length() - start.col())));
38+
sb.append(Console.newline());
39+
}
40+
final Pos end = range.end();
41+
if (end.row() < linesCount) {
42+
sb.append(lines[end.row()]);
43+
sb.append(Console.newline());
44+
sb.append("^".repeat(end.col()));
45+
sb.append(Console.newline());
46+
}
47+
}
48+
}
49+
}

ownlang-core/src/main/java/com/annimon/ownlang/util/input/SourceLoaderStage.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,16 @@
1010

1111
public class SourceLoaderStage implements Stage<InputSource, String> {
1212

13-
public static final String TAG_SOURCE = "source";
13+
public static final String TAG_SOURCE_LINES = "sourceLines";
1414

1515
@Override
1616
public String perform(StagesData stagesData, InputSource inputSource) {
1717
try {
1818
String result = inputSource.load();
19-
stagesData.put(TAG_SOURCE, result);
19+
final var lines = (result == null || result.isEmpty())
20+
? new String[0]
21+
: result.split("\r?\n");
22+
stagesData.put(TAG_SOURCE_LINES, lines);
2023
return result;
2124
} catch (IOException e) {
2225
throw new OwnLangRuntimeException("Unable to read input " + inputSource, e);

0 commit comments

Comments
 (0)