mirror of
https://github.com/aNNiMON/Own-Programming-Language-Tutorial.git
synced 2024-09-20 00:34:20 +03:00
Generalized way to show source located errors (parse and runtime errors)
This commit is contained in:
parent
1fb9c8b3c5
commit
02e9d1f6c5
@ -3,10 +3,15 @@ package com.annimon.ownlang;
|
|||||||
import com.annimon.ownlang.lib.CallStack;
|
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.util.ErrorsLocationFormatterStage;
|
||||||
|
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.List;
|
||||||
|
|
||||||
public class Console {
|
public class Console {
|
||||||
|
|
||||||
@ -58,6 +63,17 @@ public class Console {
|
|||||||
outputSettings.error(value);
|
outputSettings.error(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void handleException(StagesData stagesData, Thread thread, Exception exception) {
|
||||||
|
String mainError = new ExceptionConverterStage()
|
||||||
|
.then((data, error) -> List.of(error))
|
||||||
|
.then(new ErrorsLocationFormatterStage())
|
||||||
|
.perform(stagesData, exception);
|
||||||
|
String callStack = CallStack.getFormattedCalls();
|
||||||
|
String stackTrace = new ExceptionStackTraceToStringStage()
|
||||||
|
.perform(stagesData, exception);
|
||||||
|
error(String.join("\n", mainError, "Thread: " + thread.getName(), callStack, stackTrace));
|
||||||
|
}
|
||||||
|
|
||||||
public static void handleException(Thread thread, Throwable throwable) {
|
public static void handleException(Thread thread, Throwable throwable) {
|
||||||
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
try(final PrintStream ps = new PrintStream(baos)) {
|
try(final PrintStream ps = new PrintStream(baos)) {
|
||||||
|
@ -1,19 +1,36 @@
|
|||||||
package com.annimon.ownlang.exceptions;
|
package com.annimon.ownlang.exceptions;
|
||||||
|
|
||||||
|
import com.annimon.ownlang.util.Range;
|
||||||
|
import com.annimon.ownlang.util.SourceLocatedError;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base type for all runtime exceptions
|
* Base type for all runtime exceptions
|
||||||
*/
|
*/
|
||||||
public class OwnLangRuntimeException extends RuntimeException {
|
public class OwnLangRuntimeException extends RuntimeException implements SourceLocatedError {
|
||||||
|
|
||||||
|
private final Range range;
|
||||||
|
|
||||||
public OwnLangRuntimeException() {
|
public OwnLangRuntimeException() {
|
||||||
super();
|
super();
|
||||||
|
this.range = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public OwnLangRuntimeException(String message) {
|
public OwnLangRuntimeException(String message) {
|
||||||
|
this(message, (Range) null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public OwnLangRuntimeException(String message, Range range) {
|
||||||
super(message);
|
super(message);
|
||||||
|
this.range = range;
|
||||||
}
|
}
|
||||||
|
|
||||||
public OwnLangRuntimeException(String message, Throwable ex) {
|
public OwnLangRuntimeException(String message, Throwable ex) {
|
||||||
super(message, ex);
|
super(message, ex);
|
||||||
|
this.range = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Range getRange() {
|
||||||
|
return range;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,9 @@
|
|||||||
package com.annimon.ownlang.lib;
|
package com.annimon.ownlang.lib;
|
||||||
|
|
||||||
|
import com.annimon.ownlang.util.Range;
|
||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public final class CallStack {
|
public final class CallStack {
|
||||||
|
|
||||||
@ -13,7 +15,7 @@ public final class CallStack {
|
|||||||
calls.clear();
|
calls.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized void enter(String name, Function function, String position) {
|
public static synchronized void enter(String name, Function function, Range range) {
|
||||||
String func = function.toString();
|
String func = function.toString();
|
||||||
if (func.contains("com.annimon.ownlang.modules")) {
|
if (func.contains("com.annimon.ownlang.modules")) {
|
||||||
func = func.replaceAll(
|
func = func.replaceAll(
|
||||||
@ -22,7 +24,7 @@ public final class CallStack {
|
|||||||
if (func.contains("\n")) {
|
if (func.contains("\n")) {
|
||||||
func = func.substring(0, func.indexOf("\n")).trim();
|
func = func.substring(0, func.indexOf("\n")).trim();
|
||||||
}
|
}
|
||||||
calls.push(new CallInfo(name, func, position));
|
calls.push(new CallInfo(name, func, range));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized void exit() {
|
public static synchronized void exit() {
|
||||||
@ -33,13 +35,23 @@ public final class CallStack {
|
|||||||
return calls;
|
return calls;
|
||||||
}
|
}
|
||||||
|
|
||||||
public record CallInfo(String name, String function, String position) {
|
public static String getFormattedCalls() {
|
||||||
|
return calls.stream()
|
||||||
|
.map(CallInfo::format)
|
||||||
|
.collect(Collectors.joining("\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public record CallInfo(String name, String function, Range range) {
|
||||||
|
String format() {
|
||||||
|
return "\tat " + this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
if (position == null) {
|
if (range == null) {
|
||||||
return String.format("%s: %s", name, function);
|
return String.format("%s: %s", name, function);
|
||||||
} else {
|
} else {
|
||||||
return String.format("%s: %s %s", name, function, position);
|
return String.format("%s: %s %s", name, function, range.format());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
package com.annimon.ownlang.util;
|
||||||
|
|
||||||
|
import com.annimon.ownlang.Console;
|
||||||
|
import com.annimon.ownlang.stages.Stage;
|
||||||
|
import com.annimon.ownlang.stages.StagesData;
|
||||||
|
|
||||||
|
public class ErrorsLocationFormatterStage implements Stage<Iterable<? extends SourceLocatedError>, String> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String perform(StagesData stagesData, Iterable<? extends SourceLocatedError> input) {
|
||||||
|
final var sb = new StringBuilder();
|
||||||
|
final String source = stagesData.get(SourceLoaderStage.TAG_SOURCE);
|
||||||
|
final var lines = source.split("\r?\n");
|
||||||
|
for (SourceLocatedError error : input) {
|
||||||
|
sb.append(Console.newline());
|
||||||
|
sb.append(error);
|
||||||
|
sb.append(Console.newline());
|
||||||
|
final Range range = error.getRange();
|
||||||
|
if (range != null) {
|
||||||
|
printPosition(sb, range.normalize(), lines);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package com.annimon.ownlang.util;
|
||||||
|
|
||||||
|
import com.annimon.ownlang.Console;
|
||||||
|
import com.annimon.ownlang.stages.Stage;
|
||||||
|
import com.annimon.ownlang.stages.StagesData;
|
||||||
|
|
||||||
|
public class ErrorsStackTraceFormatterStage implements Stage<Iterable<? extends SourceLocatedError>, String> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String perform(StagesData stagesData, Iterable<? extends SourceLocatedError> input) {
|
||||||
|
final var sb = new StringBuilder();
|
||||||
|
for (SourceLocatedError error : input) {
|
||||||
|
if (!error.hasStackTrace()) continue;
|
||||||
|
for (StackTraceElement el : error.getStackTrace()) {
|
||||||
|
sb.append("\t").append(el).append(Console.newline());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package com.annimon.ownlang.util;
|
||||||
|
|
||||||
|
import com.annimon.ownlang.stages.Stage;
|
||||||
|
import com.annimon.ownlang.stages.StagesData;
|
||||||
|
|
||||||
|
public class ExceptionConverterStage implements Stage<Exception, SourceLocatedError> {
|
||||||
|
@Override
|
||||||
|
public SourceLocatedError perform(StagesData stagesData, Exception ex) {
|
||||||
|
if (ex instanceof SourceLocatedError sle) return sle;
|
||||||
|
return new SimpleError(ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package com.annimon.ownlang.util;
|
||||||
|
|
||||||
|
import com.annimon.ownlang.stages.Stage;
|
||||||
|
import com.annimon.ownlang.stages.StagesData;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
public class ExceptionStackTraceToStringStage implements Stage<Exception, String> {
|
||||||
|
@Override
|
||||||
|
public String perform(StagesData stagesData, Exception ex) {
|
||||||
|
final var baos = new ByteArrayOutputStream();
|
||||||
|
try (final PrintStream ps = new PrintStream(baos)) {
|
||||||
|
for (StackTraceElement traceElement : ex.getStackTrace()) {
|
||||||
|
ps.println("\tat " + traceElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return baos.toString(StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package com.annimon.ownlang.util;
|
||||||
|
|
||||||
|
public record SimpleError(String message) implements SourceLocatedError {
|
||||||
|
@Override
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
package com.annimon.ownlang.util;
|
||||||
|
|
||||||
|
import com.annimon.ownlang.exceptions.OwnLangRuntimeException;
|
||||||
|
import com.annimon.ownlang.stages.Stage;
|
||||||
|
import com.annimon.ownlang.stages.StagesData;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
public class SourceLoaderStage implements Stage<String, String> {
|
||||||
|
|
||||||
|
public static final String TAG_SOURCE = "source";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String perform(StagesData stagesData, String name) {
|
||||||
|
try {
|
||||||
|
String result = readSource(name);
|
||||||
|
stagesData.put(TAG_SOURCE, result);
|
||||||
|
return result;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new OwnLangRuntimeException("Unable to read input " + name, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String readSource(String name) throws IOException {
|
||||||
|
try (InputStream is = getClass().getResourceAsStream("/" + name)) {
|
||||||
|
if (is != null) {
|
||||||
|
return readStream(is);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try (InputStream is = new FileInputStream(name)) {
|
||||||
|
return readStream(is);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String readStream(InputStream is) throws IOException {
|
||||||
|
final ByteArrayOutputStream result = new ByteArrayOutputStream();
|
||||||
|
final int bufferSize = 1024;
|
||||||
|
final byte[] buffer = new byte[bufferSize];
|
||||||
|
int read;
|
||||||
|
while ((read = is.read(buffer)) != -1) {
|
||||||
|
result.write(buffer, 0, read);
|
||||||
|
}
|
||||||
|
return result.toString(StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package com.annimon.ownlang.util;
|
||||||
|
|
||||||
|
public interface SourceLocatedError extends SourceLocation {
|
||||||
|
|
||||||
|
String getMessage();
|
||||||
|
|
||||||
|
default StackTraceElement[] getStackTrace() {
|
||||||
|
return new StackTraceElement[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean hasStackTrace() {
|
||||||
|
return !stackTraceIsEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean stackTraceIsEmpty() {
|
||||||
|
final var st = getStackTrace();
|
||||||
|
return st == null || st.length == 0;
|
||||||
|
}
|
||||||
|
}
|
@ -5,9 +5,4 @@ public interface SourceLocation {
|
|||||||
default Range getRange() {
|
default Range getRange() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
default String formatRange() {
|
|
||||||
final var range = getRange();
|
|
||||||
return range == null ? "" : range.format();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,9 @@ package com.annimon.ownlang;
|
|||||||
|
|
||||||
import com.annimon.ownlang.exceptions.OwnLangParserException;
|
import com.annimon.ownlang.exceptions.OwnLangParserException;
|
||||||
import com.annimon.ownlang.exceptions.StoppedException;
|
import com.annimon.ownlang.exceptions.StoppedException;
|
||||||
import com.annimon.ownlang.parser.*;
|
import com.annimon.ownlang.parser.Beautifier;
|
||||||
|
import com.annimon.ownlang.parser.SourceLoader;
|
||||||
|
import com.annimon.ownlang.parser.Token;
|
||||||
import com.annimon.ownlang.parser.ast.Statement;
|
import com.annimon.ownlang.parser.ast.Statement;
|
||||||
import com.annimon.ownlang.parser.error.ParseErrorsFormatterStage;
|
import com.annimon.ownlang.parser.error.ParseErrorsFormatterStage;
|
||||||
import com.annimon.ownlang.parser.linters.LinterStage;
|
import com.annimon.ownlang.parser.linters.LinterStage;
|
||||||
@ -163,7 +165,7 @@ public final class Main {
|
|||||||
} catch (StoppedException ex) {
|
} catch (StoppedException ex) {
|
||||||
// skip
|
// skip
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Console.handleException(Thread.currentThread(), ex);
|
Console.handleException(stagesData, Thread.currentThread(), ex);
|
||||||
} finally {
|
} finally {
|
||||||
if (options.showTokens) {
|
if (options.showTokens) {
|
||||||
final List<Token> tokens = stagesData.get(LexerStage.TAG_TOKENS);
|
final List<Token> tokens = stagesData.get(LexerStage.TAG_TOKENS);
|
||||||
|
@ -81,7 +81,7 @@ public final class Parser {
|
|||||||
parseErrors.add(new ParseError(ex.getMessage(), ex.getRange()));
|
parseErrors.add(new ParseError(ex.getMessage(), ex.getRange()));
|
||||||
recover();
|
recover();
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
parseErrors.add(new ParseError(ex.getMessage(), getRange(), List.of(ex.getStackTrace())));
|
parseErrors.add(new ParseError(ex.getMessage(), getRange(), ex.getStackTrace()));
|
||||||
recover();
|
recover();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ public final class FunctionalExpression extends InterruptableNode
|
|||||||
values[i] = arguments.get(i).eval();
|
values[i] = arguments.get(i).eval();
|
||||||
}
|
}
|
||||||
final Function f = consumeFunction(functionExpr);
|
final Function f = consumeFunction(functionExpr);
|
||||||
CallStack.enter(functionExpr.toString(), f, formatRange());
|
CallStack.enter(functionExpr.toString(), f, range);
|
||||||
final Value result = f.execute(values);
|
final Value result = f.execute(values);
|
||||||
CallStack.exit();
|
CallStack.exit();
|
||||||
return result;
|
return result;
|
||||||
|
@ -1,16 +1,31 @@
|
|||||||
package com.annimon.ownlang.parser.error;
|
package com.annimon.ownlang.parser.error;
|
||||||
|
|
||||||
import com.annimon.ownlang.util.Range;
|
import com.annimon.ownlang.util.Range;
|
||||||
import java.util.Collections;
|
import com.annimon.ownlang.util.SourceLocatedError;
|
||||||
import java.util.List;
|
|
||||||
|
public record ParseError(
|
||||||
|
String message,
|
||||||
|
Range range,
|
||||||
|
StackTraceElement[] stackTraceElements
|
||||||
|
) implements SourceLocatedError {
|
||||||
|
|
||||||
public record ParseError(String message, Range range, List<StackTraceElement> stackTraceElements) {
|
|
||||||
public ParseError(String message, Range range) {
|
public ParseError(String message, Range range) {
|
||||||
this(message, range, Collections.emptyList());
|
this(message, range, new StackTraceElement[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasStackTrace() {
|
@Override
|
||||||
return !stackTraceElements.isEmpty();
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Range getRange() {
|
||||||
|
return range;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StackTraceElement[] getStackTrace() {
|
||||||
|
return stackTraceElements;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,63 +1,18 @@
|
|||||||
package com.annimon.ownlang.parser.error;
|
package com.annimon.ownlang.parser.error;
|
||||||
|
|
||||||
import com.annimon.ownlang.Console;
|
|
||||||
import com.annimon.ownlang.util.Pos;
|
|
||||||
import com.annimon.ownlang.util.Range;
|
|
||||||
import com.annimon.ownlang.stages.SourceLoaderStage;
|
|
||||||
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.ErrorsLocationFormatterStage;
|
||||||
|
import com.annimon.ownlang.util.ErrorsStackTraceFormatterStage;
|
||||||
|
|
||||||
public class ParseErrorsFormatterStage implements Stage<ParseErrors, String> {
|
public class ParseErrorsFormatterStage implements Stage<ParseErrors, String> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String perform(StagesData stagesData, ParseErrors input) {
|
public String perform(StagesData stagesData, ParseErrors input) {
|
||||||
final var sb = new StringBuilder();
|
String error = new ErrorsLocationFormatterStage()
|
||||||
final String source = stagesData.get(SourceLoaderStage.TAG_SOURCE);
|
.perform(stagesData, input);
|
||||||
final var lines = source.split("\r?\n");
|
String stackTrace = new ErrorsStackTraceFormatterStage()
|
||||||
for (ParseError parseError : input) {
|
.perform(stagesData, input);
|
||||||
sb.append(Console.newline());
|
return error + "\n" + stackTrace;
|
||||||
sb.append(parseError);
|
|
||||||
sb.append(Console.newline());
|
|
||||||
final Range range = parseError.range().normalize();
|
|
||||||
printPosition(sb, range, lines);
|
|
||||||
if (parseError.hasStackTrace()) {
|
|
||||||
sb.append("Stack trace:");
|
|
||||||
sb.append(Console.newline());
|
|
||||||
for (StackTraceElement el : parseError.stackTraceElements()) {
|
|
||||||
sb.append(" ").append(el).append(Console.newline());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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,21 +0,0 @@
|
|||||||
package com.annimon.ownlang.stages;
|
|
||||||
|
|
||||||
import com.annimon.ownlang.exceptions.OwnLangRuntimeException;
|
|
||||||
import com.annimon.ownlang.parser.SourceLoader;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class SourceLoaderStage implements Stage<String, String> {
|
|
||||||
|
|
||||||
public static final String TAG_SOURCE = "source";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String perform(StagesData stagesData, String input) {
|
|
||||||
try {
|
|
||||||
String result = SourceLoader.readSource(input);
|
|
||||||
stagesData.put(TAG_SOURCE, result);
|
|
||||||
return result;
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new OwnLangRuntimeException("Unable to read input " + input, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -9,6 +9,7 @@ import com.annimon.ownlang.parser.ast.Visitor;
|
|||||||
import com.annimon.ownlang.parser.optimization.OptimizationStage;
|
import com.annimon.ownlang.parser.optimization.OptimizationStage;
|
||||||
import com.annimon.ownlang.parser.visitors.AbstractVisitor;
|
import com.annimon.ownlang.parser.visitors.AbstractVisitor;
|
||||||
import com.annimon.ownlang.stages.*;
|
import com.annimon.ownlang.stages.*;
|
||||||
|
import com.annimon.ownlang.util.SourceLoaderStage;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
@ -46,7 +46,7 @@ public final class Sandbox {
|
|||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
// ownlang call stack to stdout
|
// ownlang call stack to stdout
|
||||||
System.out.format("%s in %s%n", ex.getMessage(), Thread.currentThread().getName());
|
System.out.format("%s in %s%n", ex.getMessage(), Thread.currentThread().getName());
|
||||||
CallStack.getCalls().forEach(call -> System.out.format("\tat %s%n", call));
|
System.out.println(CallStack.getFormattedCalls());
|
||||||
// java stack trace to stderr
|
// java stack trace to stderr
|
||||||
Console.handleException(Thread.currentThread(), ex);
|
Console.handleException(Thread.currentThread(), ex);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user