summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAntia Anderson <anita@ansan.io>2026-08-25 19:32:14 -0400
committerAntia Anderson <anita@ansan.io>2026-08-25 19:32:14 -0400
commitdc0927bceaa122ca9a73373a430cec9fc8001470 (patch)
treea4c327305f0d0f6f74a2135cf58d530dc8a7f2fc /src
parent4948df9851a037175e538cef9ac0585ea71a9645 (diff)
downloadmai-dc0927bceaa122ca9a73373a430cec9fc8001470.tar.gz
mai-dc0927bceaa122ca9a73373a430cec9fc8001470.zip
Start work diagnosticsparser
Signed-off-by: Antia Anderson <anita@ansan.io>
Diffstat (limited to 'src')
-rw-r--r--src/moe/anita/mai/buffer/Buffer.java1
-rw-r--r--src/moe/anita/mai/buffer/MemBuf.java12
-rw-r--r--src/moe/anita/mai/buffer/SrcBuf.java8
-rw-r--r--src/moe/anita/mai/diag/ColorMode.java7
-rw-r--r--src/moe/anita/mai/diag/ConsolePrinter.java150
-rw-r--r--src/moe/anita/mai/diag/Diagnostic.java13
-rw-r--r--src/moe/anita/mai/diag/JsonPrinter.java115
-rw-r--r--src/moe/anita/mai/diag/Printer.java8
-rw-r--r--src/moe/anita/mai/span/Line.java4
-rw-r--r--src/moe/anita/mai/span/Lines.java40
-rw-r--r--src/moe/anita/mai/span/Pos.java28
-rw-r--r--src/moe/anita/mai/span/Span.java19
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); }
}