about summary refs log tree commit diff
path: root/boot/common.c
diff options
context:
space:
mode:
authorMel <mel@rnrd.eu>2026-05-24 18:18:23 +0200
committerMel <mel@rnrd.eu>2026-05-24 18:18:23 +0200
commita76fd9b5af153773273121c7aec2a9cbeed1dfb7 (patch)
treea6c619e44df4f38f60c457b9122e207b0a3c09f0 /boot/common.c
parent8c4c219c945dfb41415e04654f488f7880e58ca2 (diff)
downloadcatskill-a76fd9b5af153773273121c7aec2a9cbeed1dfb7.tar.zst
catskill-a76fd9b5af153773273121c7aec2a9cbeed1dfb7.zip
Make diagnostics generic over passes, condensing different error handling
Signed-off-by: Mel <mel@rnrd.eu>
Diffstat (limited to 'boot/common.c')
-rw-r--r--boot/common.c316
1 files changed, 316 insertions, 0 deletions
diff --git a/boot/common.c b/boot/common.c
index 0524b3b..85b1422 100644
--- a/boot/common.c
+++ b/boot/common.c
@@ -938,6 +938,54 @@ string_buffer_append(struct String_Buffer* buffer, struct String s)
     buffer->data[buffer->length] = '\0';
 }
 
+// pairs with `%S` in the printf-like formatters below.
+#define STR(s) (int)(s).length, (s).data
+
+void
+_format_translate_format(ascii* dst, const ascii* src, uint dst_size)
+{
+    uint si = 0, di = 0;
+    while (di + 4 < dst_size) {
+        ascii c = src[si];
+        if (c == '\0') break;
+        if (c == '%' && src[si + 1] == 'S') {
+            dst[di++] = '%';
+            dst[di++] = '.';
+            dst[di++] = '*';
+            dst[di++] = 's';
+            si += 2;
+        } else {
+            dst[di++] = c;
+            si++;
+        }
+    }
+    dst[di] = '\0';
+}
+
+#define MAX_FORMATTED_STRING_SIZE 1024
+
+struct String
+string_vformat(const ascii* format, va_list args)
+{
+    ascii translated[MAX_FORMAT_STRING_SIZE];
+    _format_translate_format(translated, format, sizeof translated);
+
+    ascii buffer[MAX_FORMATTED_STRING_SIZE];
+    int written = vsnprintf(buffer, sizeof buffer, translated, args);
+    check(written >= 0, "vsnprintf failed");
+    return string_new(buffer, (uint)written);
+}
+
+struct String
+string_format(const ascii* format, ...)
+{
+    va_list args;
+    va_start(args, format);
+    struct String result = string_vformat(format, args);
+    va_end(args);
+    return result;
+}
+
 // appends a C-style string to the string buffer.
 void
 string_buffer_append_c_str(struct String_Buffer* buffer, const ascii* c_str)
@@ -954,6 +1002,17 @@ string_buffer_append_c_str(struct String_Buffer* buffer, const ascii* c_str)
     buffer->data[buffer->length] = '\0';
 }
 
+// appends a formatted string to the buffer.
+void
+string_buffer_appendf(struct String_Buffer* buffer, const ascii* format, ...)
+{
+    va_list args;
+    va_start(args, format);
+    struct String formatted = string_vformat(format, args);
+    va_end(args);
+    string_buffer_append(buffer, formatted);
+}
+
 // converts the string buffer to an immutable string.
 // allocates a new string in the string region.
 struct String
@@ -977,6 +1036,263 @@ struct Source_File
     struct String path;
 };
 
+// byte position within a file.
+typedef uint64 Pos;
+
+// an extent of text within a file,
+// defined by its start and end byte positions.
+struct Span
+{
+    Pos start;
+    Pos end;
+};
+
+struct Span
+span_new(Pos start, Pos end)
+{
+    return (struct Span){ .start = start, .end = end };
+}
+
+struct Span
+span_empty(void)
+{
+    return (struct Span){ 0, 0 };
+}
+
+struct Span
+span_width(Pos start, uint width)
+{
+    return (struct Span){ .start = start, .end = start + width };
+}
+
+// create a span which encompasses two spans.
+struct Span
+span_merge(struct Span a, struct Span b)
+{
+    return (struct Span){
+        .start = a.start < b.start ? a.start : b.start,
+        .end = a.end > b.end ? a.end : b.end,
+    };
+}
+
+// expand a span by a number of bytes.
+// negative number expands the span to the left,
+// positive number expands the span to the right.
+struct Span
+span_expand(struct Span span, integer by)
+{
+    uint start_expand = by < 0 ? -by : 0;
+    uint end_expand = by > 0 ? by : 0;
+    return (struct Span){
+        .start = span.start - start_expand,
+        .end = span.end + end_expand,
+    };
+}
+
+// check if two spans are equal.
+bool
+span_equals(struct Span a, struct Span b)
+{
+    return a.start == b.start && a.end == b.end;
+}
+
+// check if span equals = { 0, 0 }.
+bool
+span_is_empty(struct Span span)
+{
+    return span_equals(span, (struct Span){ 0, 0 });
+}
+
+uint
+span_length(struct Span span)
+{
+    uint length = span.end - span.start;
+    return length == 0 ? 1 : length;
+}
+
+// a cursor position placed within a text file.
+struct Cursor
+{
+    uint line;
+    uint column;
+};
+
+struct Cursor
+cursor_new(uint line, uint column)
+{
+    return (struct Cursor){ .line = line, .column = column };
+}
+
+struct Cursor
+cursor_empty(void)
+{
+    return cursor_new(0, 0);
+}
+
+enum Diagnostic_Severity
+{
+    DIAGNOSTIC_NOTE,
+    DIAGNOSTIC_WARNING,
+    DIAGNOSTIC_ERROR,
+};
+
+struct Diagnostic
+{
+    enum Diagnostic_Severity severity;
+    struct Span span;
+    // first line of the rendered diagnostic. may contain `\n` to introduce
+    // extra body lines, each indented under the headline.
+    struct String message;
+    // optional. rendered as `hint: <hint>` under the body when non-empty.
+    struct String hint;
+    // NOTE(mel): probably overkill for bootstrapping, but i kind of want to figure
+    // the error format out for future iterations, and want to see it in action,
+    // to have more thoughts about improving it later on.
+};
+
+const ascii*
+diagnostic_severity_label(enum Diagnostic_Severity severity)
+{
+    switch (severity) {
+    case DIAGNOSTIC_NOTE:
+        return "note.";
+    case DIAGNOSTIC_WARNING:
+        return "warning?";
+    case DIAGNOSTIC_ERROR:
+        return "error!";
+    default:
+        return "diagnostic";
+    }
+}
+
+const ascii*
+diagnostic_severity_color(enum Diagnostic_Severity severity)
+{
+    switch (severity) {
+    case DIAGNOSTIC_NOTE:
+        return ANSI_BLUE;
+    case DIAGNOSTIC_WARNING:
+        return ANSI_YELLOW;
+    case DIAGNOSTIC_ERROR:
+        return ANSI_RED;
+    default:
+        return ANSI_WHITE;
+    }
+}
+
+// line and column are 1-based.
+void
+source_position_from_span(struct String source, struct Span span, uint* out_line, uint* out_column)
+{
+    uint line = 1, column = 1;
+    uint upto = span.start;
+    if (upto > string_length(source)) upto = string_length(source);
+    for (uint i = 0; i < upto; ++i) {
+        if (string_at(source, i) == '\n') {
+            line++;
+            column = 1;
+        } else {
+            column++;
+        }
+    }
+    *out_line = line;
+    *out_column = column;
+}
+
+struct Span
+source_line_span(struct String source, struct Span span)
+{
+    Pos line_start = span.start, line_end = span.start;
+    while (line_start > 0 && string_at(source, line_start - 1) != '\n') line_start--;
+    while (line_end < string_length(source) && string_at(source, line_end) != '\n') line_end++;
+    return span_new(line_start, line_end);
+}
+
+// render a diagnostic to a stream.
+void
+diagnostic_display(struct Source_File source, struct Diagnostic* d, FILE* out)
+{
+    uint line, column;
+    source_position_from_span(source.source, d->span, &line, &column);
+
+    STRING_FORMAT_TO(source.path, out, ANSI_WHITE "%s:%lu:%lu:\n", line, column);
+
+    // first line of the message is the headline. anything after a `\n` is body.
+    uint headline_end = 0;
+    while (headline_end < d->message.length && d->message.data[headline_end] != '\n')
+        headline_end++;
+
+    fprintf(out, ANSI_BOLD "%s%s " ANSI_WHITE, diagnostic_severity_color(d->severity),
+            diagnostic_severity_label(d->severity));
+    fprintf(out, "%.*s", (int)headline_end, d->message.data);
+    if (d->severity == DIAGNOSTIC_ERROR) fprintf(out, " :(");
+    fprintf(out, "\n" ANSI_NO_BOLD);
+
+    if (headline_end < d->message.length) {
+        uint body_start = headline_end + 1;
+        fprintf(out, "%.*s\n", (int)(d->message.length - body_start), d->message.data + body_start);
+    }
+
+    if (!string_is_empty(d->hint)) STRING_FORMAT_TO(d->hint, out, "  hint: %S\n");
+
+    struct Span line_span = source_line_span(source.source, d->span);
+    struct String_View source_line =
+        string_substring(source.source, line_span.start, line_span.end);
+
+    fprintf(out, ANSI_WHITE "%lu| ", line);
+    STRING_FORMAT_TO(source_line, out, "%S\n");
+
+    uint line_number_length = ceil(log10(line + 1));
+    fprintf(out, "%s%*s", diagnostic_severity_color(d->severity),
+            (int)(column + 1 + line_number_length), " ");
+    uint width = span_length(d->span);
+    if (width == 0) width = 1;
+    for (uint w = 0; w < width; w++) fprintf(out, "^");
+    fprintf(out, "\n" ANSI_RESET);
+}
+
+void
+diagnostic_push_str(
+    Array(struct Diagnostic) * into, bool* out_had_error, enum Diagnostic_Severity severity,
+    struct Span span, struct String message)
+{
+    struct Diagnostic d = { .severity = severity, .span = span, .message = message };
+    array_push(into, &d);
+    if (out_had_error && severity == DIAGNOSTIC_ERROR) *out_had_error = true;
+}
+
+void
+diagnostic_push(
+    Array(struct Diagnostic) * into, bool* out_had_error, enum Diagnostic_Severity severity,
+    struct Span span, const ascii* format, ...)
+{
+    va_list args;
+    va_start(args, format);
+    struct String message = string_vformat(format, args);
+    va_end(args);
+    diagnostic_push_str(into, out_had_error, severity, span, message);
+}
+
+#define diagnostic_emit(unit, severity, span, ...) \
+    diagnostic_push(&(unit)->diagnostics, &(unit)->had_error, severity, span, __VA_ARGS__)
+
+#define diagnostic_emit_str(unit, severity, span, message) \
+    diagnostic_push_str(&(unit)->diagnostics, &(unit)->had_error, severity, span, message)
+
+void
+diagnostic_render(
+    struct Source_File source, FILE* out, enum Diagnostic_Severity severity, struct Span span,
+    const ascii* format, ...)
+{
+    va_list args;
+    va_start(args, format);
+    struct String message = string_vformat(format, args);
+    va_end(args);
+
+    struct Diagnostic d = { .severity = severity, .span = span, .message = message };
+    diagnostic_display(source, &d, out);
+}
+
 // single iteration of the CRC32 checksum algorithm
 // described in POSIX.
 // see: https://pubs.opengroup.org/onlinepubs/9799919799/utilities/cksum.html