diff options
| author | Mel <mel@rnrd.eu> | 2026-05-24 18:18:23 +0200 |
|---|---|---|
| committer | Mel <mel@rnrd.eu> | 2026-05-24 18:18:23 +0200 |
| commit | a76fd9b5af153773273121c7aec2a9cbeed1dfb7 (patch) | |
| tree | a6c619e44df4f38f60c457b9122e207b0a3c09f0 /boot/common.c | |
| parent | 8c4c219c945dfb41415e04654f488f7880e58ca2 (diff) | |
| download | catskill-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.c | 316 |
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 |
