diff options
| author | Antia Anderson <anita@ansan.io> | 2026-08-25 19:32:14 -0400 |
|---|---|---|
| committer | Antia Anderson <anita@ansan.io> | 2026-08-25 19:32:14 -0400 |
| commit | dc0927bceaa122ca9a73373a430cec9fc8001470 (patch) | |
| tree | a4c327305f0d0f6f74a2135cf58d530dc8a7f2fc /src/moe/anita | |
| parent | 4948df9851a037175e538cef9ac0585ea71a9645 (diff) | |
| download | mai-parser.tar.gz mai-parser.zip | |
Start work diagnosticsparser
Signed-off-by: Antia Anderson <anita@ansan.io>
Diffstat (limited to 'src/moe/anita')
| -rw-r--r-- | src/moe/anita/mai/buffer/Buffer.java | 1 | ||||
| -rw-r--r-- | src/moe/anita/mai/buffer/MemBuf.java | 12 | ||||
| -rw-r--r-- | src/moe/anita/mai/buffer/SrcBuf.java | 8 | ||||
| -rw-r--r-- | src/moe/anita/mai/diag/ColorMode.java | 7 | ||||
| -rw-r--r-- | src/moe/anita/mai/diag/ConsolePrinter.java | 150 | ||||
| -rw-r--r-- | src/moe/anita/mai/diag/Diagnostic.java | 13 | ||||
| -rw-r--r-- | src/moe/anita/mai/diag/JsonPrinter.java | 115 | ||||
| -rw-r--r-- | src/moe/anita/mai/diag/Printer.java | 8 | ||||
| -rw-r--r-- | src/moe/anita/mai/span/Line.java | 4 | ||||
| -rw-r--r-- | src/moe/anita/mai/span/Lines.java | 40 | ||||
| -rw-r--r-- | src/moe/anita/mai/span/Pos.java | 28 | ||||
| -rw-r--r-- | src/moe/anita/mai/span/Span.java | 19 |
12 files changed, 373 insertions, 32 deletions
diff --git a/src/moe/anita/mai/buffer/Buffer.java b/src/moe/anita/mai/buffer/Buffer.java index e119b40..1ed3cd6 100644 --- a/src/moe/anita/mai/buffer/Buffer.java +++ b/src/moe/anita/mai/buffer/Buffer.java @@ -5,6 +5,7 @@ package moe.anita.mai.buffer; */ public interface Buffer { + String name(); /** * Returns the length of the underlying source representation. * diff --git a/src/moe/anita/mai/buffer/MemBuf.java b/src/moe/anita/mai/buffer/MemBuf.java index 922e624..7fb0c46 100644 --- a/src/moe/anita/mai/buffer/MemBuf.java +++ b/src/moe/anita/mai/buffer/MemBuf.java @@ -10,13 +10,21 @@ import java.util.Objects; */ public final class MemBuf implements Buffer { + private final String name; private final byte[] data; public MemBuf(String source) { - Objects.requireNonNull(source); - data = source.getBytes(StandardCharsets.UTF_8); + this("<memory>", source); } + public MemBuf(String name, String source) { + this.name = name; + this.data = source.getBytes(StandardCharsets.UTF_8); + } + + @Override + public String name() { return name; } + @Override public int length() { return data.length; diff --git a/src/moe/anita/mai/buffer/SrcBuf.java b/src/moe/anita/mai/buffer/SrcBuf.java index f288316..f0e3296 100644 --- a/src/moe/anita/mai/buffer/SrcBuf.java +++ b/src/moe/anita/mai/buffer/SrcBuf.java @@ -16,11 +16,9 @@ public final class SrcBuf implements Buffer { this.data = Files.readString(path, StandardCharsets.UTF_8); } - /** - * Returns the source file path. - * - * @return source path - */ + @Override + public String name() { return path.getFileName().toString(); } + public Path path() { return path; } diff --git a/src/moe/anita/mai/diag/ColorMode.java b/src/moe/anita/mai/diag/ColorMode.java new file mode 100644 index 0000000..ebb31b8 --- /dev/null +++ b/src/moe/anita/mai/diag/ColorMode.java @@ -0,0 +1,7 @@ +package moe.anita.mai.diag; + +public enum ColorMode { + AUTO, + ALWAYS, + NEVER, +} diff --git a/src/moe/anita/mai/diag/ConsolePrinter.java b/src/moe/anita/mai/diag/ConsolePrinter.java new file mode 100644 index 0000000..0c8e051 --- /dev/null +++ b/src/moe/anita/mai/diag/ConsolePrinter.java @@ -0,0 +1,150 @@ +package moe.anita.mai.diag; + +import moe.anita.mai.buffer.Buffer; +import moe.anita.mai.span.Line; +import moe.anita.mai.span.Lines; +import moe.anita.mai.span.Pos; +import moe.anita.mai.span.Span; + +import java.io.PrintStream; + +public class ConsolePrinter implements Printer { + + private static final String RESET = "\033[0m"; + private static final String BOLD = "\033[1m"; + + private static final String RED = "\033[31m"; + private static final String YELLOW = "\033[33m"; + private static final String CYAN = "\033[36m"; + + private final PrintStream out; + private final boolean color; + + public ConsolePrinter(PrintStream out, ColorMode colorMode) { + this.out = out; + this.color = useColor(colorMode); + } + + @Override + public void print(Buffer source, Diagnostic diagnostic) { + Span span = diagnostic.span(); + Pos start = span.start(); + Line line = Lines.at(source, start); + + printHeader(diagnostic); + printLocation(source, start); + printSource(line, diagnostic); + } + + private void printHeader(Diagnostic diagnostic) { + printLevel(diagnostic.level()); + out.print(": "); + out.println(diagnostic.msg()); + } + + private void printLocation(Buffer source, Pos pos) { + out.print(" --> "); + + if (color) { + out.print(CYAN); + } + + out.printf("%s:%d:%d", source.name(), pos.line(), pos.column()); + + if (color) { + out.print(RESET); + } + + out.println(); + } + + private void printSource(Line line, Diagnostic diagnostic) { + int gutterWidth = digits(line.number()); + + out.printf("%" + gutterWidth + "s |%n", ""); + out.printf("%" + gutterWidth + "d | %s%n", line.number(), line.text()); + + printMarker(line, diagnostic, gutterWidth); + + out.printf("%" + gutterWidth + "s |%n", ""); + } + + private void printMarker(Line line, Diagnostic diagnostic, int gutterWidth) { + Span span = diagnostic.span(); + int startColumn = span.start().column(); + int width = markerWidth(span, line); + + out.printf("%" + gutterWidth + "s | ", ""); + out.print(" ".repeat(Math.max(0, startColumn - 1))); + + beginLevelStyle(diagnostic.level()); + + out.print("^".repeat(width)); + + if (diagnostic.label() != null && !diagnostic.label().isEmpty()) { + out.print(" "); + out.print(diagnostic.label()); + } + + endStyle(); + out.println(); + } + + private int markerWidth(Span span, Line line) { + if (span.start().line() != span.end().line()) { + return Math.max(1, line.text().codePointCount(0, line.text().length()) - span.start().column() + 2); + } + + return Math.max(1, span.end().column() - span.start().column()); + } + + private void printLevel(Diagnostic.Level level) { + beginLevelStyle(level); + out.print(levelName(level)); + endStyle(); + } + + private void beginLevelStyle(Diagnostic.Level level) { + if (!color) { + return; + } + + out.print(BOLD); + out.print(levelColor(level)); + } + + private void endStyle() { + if (color) { + out.print(RESET); + } + } + + private String levelName(Diagnostic.Level level) { + return switch (level) { + case ERROR -> "error"; + case WARNING -> "warning"; + case NOTE -> "note"; + case HELP -> "help"; + }; + } + + private String levelColor(Diagnostic.Level level) { + return switch (level) { + case ERROR -> RED; + case WARNING -> YELLOW; + case NOTE, HELP -> CYAN; + }; + } + + private int digits(int value) { + return Integer.toString(value).length(); + } + + private boolean useColor(ColorMode mode) { + return switch (mode) { + case ALWAYS -> true; + case NEVER -> false; + case AUTO -> System.console() != null; + }; + } +} diff --git a/src/moe/anita/mai/diag/Diagnostic.java b/src/moe/anita/mai/diag/Diagnostic.java new file mode 100644 index 0000000..da1d92b --- /dev/null +++ b/src/moe/anita/mai/diag/Diagnostic.java @@ -0,0 +1,13 @@ +package moe.anita.mai.diag; + +import moe.anita.mai.span.Span; + +public record Diagnostic(Level level, String msg, Span span, String label) { + + public enum Level { + ERROR, + WARNING, + NOTE, + HELP + } +} diff --git a/src/moe/anita/mai/diag/JsonPrinter.java b/src/moe/anita/mai/diag/JsonPrinter.java new file mode 100644 index 0000000..5aceab4 --- /dev/null +++ b/src/moe/anita/mai/diag/JsonPrinter.java @@ -0,0 +1,115 @@ +package moe.anita.mai.diag; + +import java.io.PrintStream; + +import moe.anita.mai.buffer.Buffer; +import moe.anita.mai.span.Pos; +import moe.anita.mai.span.Span; + +/** + * Prints diagnostics as newline-delimited JSON. + */ +public final class JsonPrinter implements Printer { + + private final PrintStream out; + + public JsonPrinter(PrintStream out) { + this.out = out; + } + + @Override + public void print(Buffer source, Diagnostic diagnostic) { + Span span = diagnostic.span(); + + out.print('{'); + + field("source", source.name()); + out.print(','); + + field("level", levelName(diagnostic.level())); + out.print(','); + + field("message", diagnostic.msg()); + + if (diagnostic.label() != null && !diagnostic.label().isEmpty()) { + out.print(','); + field("label", diagnostic.label()); + } + + out.print(','); + out.print("\"span\":"); + + printSpan(span); + + out.println('}'); + } + + private void printSpan(Span span) { + out.print('{'); + + out.print("\"start\":"); + printPos(span.start()); + + out.print(','); + + out.print("\"end\":"); + printPos(span.end()); + + out.print('}'); + } + + private void printPos(Pos pos) { + out.print('{'); + + out.printf("\"offset\":%d,", pos.offset()); + out.printf("\"line\":%d,", pos.line()); + out.printf("\"column\":%d", pos.column()); + + out.print('}'); + } + + private void field(String name, String value) { + out.print('"'); + out.print(escape(name)); + out.print("\":\""); + out.print(escape(value)); + out.print('"'); + } + + private String levelName(Diagnostic.Level level) { + return switch (level) { + case ERROR -> "error"; + case WARNING -> "warning"; + case NOTE -> "note"; + case HELP -> "help"; + }; + } + + private String escape(String value) { + var result = new StringBuilder(value.length()); + + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + + switch (c) { + case '"' -> result.append("\\\""); + case '\\' -> result.append("\\\\"); + case '\b' -> result.append("\\b"); + case '\f' -> result.append("\\f"); + case '\n' -> result.append("\\n"); + case '\r' -> result.append("\\r"); + case '\t' -> result.append("\\t"); + + default -> { + if (c < 0x20) { + result.append("\\u%04x".formatted((int) c)); + } else { + result.append(c); + } + } + } + } + + return result.toString(); + } +} diff --git a/src/moe/anita/mai/diag/Printer.java b/src/moe/anita/mai/diag/Printer.java new file mode 100644 index 0000000..21234e9 --- /dev/null +++ b/src/moe/anita/mai/diag/Printer.java @@ -0,0 +1,8 @@ +package moe.anita.mai.diag; + +import moe.anita.mai.buffer.Buffer; + +@FunctionalInterface +public interface Printer { + void print(Buffer buf, Diagnostic diagnostic); +} diff --git a/src/moe/anita/mai/span/Line.java b/src/moe/anita/mai/span/Line.java new file mode 100644 index 0000000..3afb9c2 --- /dev/null +++ b/src/moe/anita/mai/span/Line.java @@ -0,0 +1,4 @@ +package moe.anita.mai.span; + +public record Line(int number, int start, int end, String text) { +} diff --git a/src/moe/anita/mai/span/Lines.java b/src/moe/anita/mai/span/Lines.java new file mode 100644 index 0000000..e1e27d1 --- /dev/null +++ b/src/moe/anita/mai/span/Lines.java @@ -0,0 +1,40 @@ +package moe.anita.mai.span; + +import moe.anita.mai.buffer.Buffer; + +public final class Lines { + + public static Line at(Buffer buf, Pos pos) { + int start = lineStart(buf, pos.offset()); + int end = lineEnd(buf, pos.offset()); + return new Line(pos.line(), start , end, buf.slice(start, end).toString()); + } + + private static int lineStart(Buffer buf, int offset) { + int current = 0; + int previous = 0; + + while (current < offset) { + if (buf.get(current) == '\n') { + previous = current + buf.width(current); + } + current += buf.width(current); + } + return previous; + } + + public static int lineEnd(Buffer buf, int offset) { + int current = offset; + + while (current < buf.length()) { + if (buf.get(current) == '\n') { + return current; + } + current += buf.width(current); + } + + return buf.length(); + } + + private Lines() {} +} diff --git a/src/moe/anita/mai/span/Pos.java b/src/moe/anita/mai/span/Pos.java index 4b129e2..17685ab 100644 --- a/src/moe/anita/mai/span/Pos.java +++ b/src/moe/anita/mai/span/Pos.java @@ -19,28 +19,12 @@ public final class Pos { return new Pos(value); } - public static Pos zero() { - return new Pos(0); - } - - public int offset() { - return (int) ((value >>> 32) & OFFSET_MASK); - } - - public int line() { - return (int) ((value >>> 16) & U16_MASK); - } - - public int column() { - return (int) (value & U16_MASK); - } - - public boolean isZero() { - return value == 0; - } + public static Pos zero() { return new Pos(0); } + public int offset() { return (int) ((value >>> 32) & OFFSET_MASK); } + public int line() { return (int) ((value >>> 16) & U16_MASK); } + public int column() { return (int) (value & U16_MASK); } + public boolean isZero() { return value == 0; } @Override - public String toString() { - return "%d:%d".formatted(line(), column()); - } + public String toString() { return "%d:%d".formatted(line(), column()); } } diff --git a/src/moe/anita/mai/span/Span.java b/src/moe/anita/mai/span/Span.java index fe27a91..4ab40cd 100644 --- a/src/moe/anita/mai/span/Span.java +++ b/src/moe/anita/mai/span/Span.java @@ -1,5 +1,7 @@ package moe.anita.mai.span; +import java.util.Objects; + public final class Span { private final Pos start; @@ -38,8 +40,19 @@ public final class Span { return start.isZero() && end.isZero(); } - @Override - public String toString() { - return "%s-%s".formatted(start, end); + public boolean contains(int offset) { + return offset >= start.offset() && offset < end.offset(); + } + + public Span merge(Span other) { + Objects.requireNonNull(other); + + Pos mergedStart = start.offset() <= other.start.offset() ? start : other.start; + Pos mergedEnd = end.offset() >= other.end.offset() ? end : other.end; + + return new Span(mergedStart, mergedEnd); } + + @Override + public String toString() { return "%s-%s".formatted(start, end); } } |
