From d4fbd75a59bccae66ea3589e99e814c0867a6e1f Mon Sep 17 00:00:00 2001 From: Max Wash Date: Tue, 27 Jan 2026 20:47:32 +0000 Subject: [PATCH] tool: implement a diagnostic message printer --- tool/CMakeLists.txt | 1 + tool/diag/diag.h | 11 ++ tool/diag/json.c | 0 tool/diag/pretty.c | 461 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 473 insertions(+) create mode 100644 tool/diag/diag.h create mode 100644 tool/diag/json.c create mode 100644 tool/diag/pretty.c diff --git a/tool/CMakeLists.txt b/tool/CMakeLists.txt index 7c7b188..f88f9ea 100644 --- a/tool/CMakeLists.txt +++ b/tool/CMakeLists.txt @@ -1,6 +1,7 @@ file(GLOB tool_sources *.c *.h cmd/*.c cmd/*.h + diag/*.c diag/*.h cmd/internal/*.c) if (WIN32) diff --git a/tool/diag/diag.h b/tool/diag/diag.h new file mode 100644 index 0000000..595c554 --- /dev/null +++ b/tool/diag/diag.h @@ -0,0 +1,11 @@ +#ifndef _TOOL_DIAG_DIAG_H_ +#define _TOOL_DIAG_DIAG_H_ + +#include +#include + +struct mie_diag; + +extern enum mie_status mie_diag_write_pretty(struct mie_diag *diag, b_tty *stream); + +#endif diff --git a/tool/diag/json.c b/tool/diag/json.c new file mode 100644 index 0000000..e69de29 diff --git a/tool/diag/pretty.c b/tool/diag/pretty.c new file mode 100644 index 0000000..6999d4c --- /dev/null +++ b/tool/diag/pretty.c @@ -0,0 +1,461 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GET_CHAR_STOP 0 +#define GET_CHAR_CONTINUE -1 + +struct snippet_print_ctx { + b_tty *ctx_stream; + struct mie_line_source *ctx_line_source; + + size_t ctx_row, ctx_col; + + bool ctx_has_underline; + b_stringstream *ctx_underline; + const b_string *ctx_linebuf; + const b_iterator *ctx_linebuf_ptr; + + struct mie_diag_c_snippet *ctx_snippet; + + const struct mie_diag_highlight *ctx_hl; + const struct mie_diag_amendment *ctx_amendment; +}; + +#define __STREAM_COLOUR_ERROR "[red,bold]" +#define __STREAM_COLOUR_WARN "[yellow,bold]" +#define __STREAM_COLOUR_HINT "[blue,bold]" +#define __STREAM_COLOUR_LINE_NO "[cyan]" +#define __STREAM_COLOUR_ACCENT "[cyan]" +#define __STREAM_COLOUR_RESET "[reset]" + +#define STREAM_COLOUR_ERROR(stream) \ + b_tty_puts(stream, 0, __STREAM_COLOUR_ERROR); +#define STREAM_COLOUR_WARN(stream) b_tty_puts(stream, 0, __STREAM_COLOUR_WARN); +#define STREAM_COLOUR_HINT(stream) b_tty_puts(stream, 0, __STREAM_COLOUR_HINT); +#define STREAM_COLOUR_ACCENT(stream) \ + b_tty_puts(stream, 0, __STREAM_COLOUR_ACCENT); +#define STREAM_COLOUR_LINE_NO(stream) \ + b_tty_puts(stream, 0, __STREAM_COLOUR_LINE_NO); +#define STREAM_COLOUR_RESET(stream) \ + b_tty_puts(stream, 0, __STREAM_COLOUR_RESET); + +#define STREAM_COLOUR_ERROR_B(stream, str) \ + b_stream_write_string(str, __STREAM_COLOUR_ERROR, NULL); +#define STREAM_COLOUR_WARN_B(stream, str) \ + b_stream_write_string(str, __STREAM_COLOUR_WARN, NULL); +#define STREAM_COLOUR_HINT_B(stream, str) \ + b_stream_write_string(str, __STREAM_COLOUR_HINT, NULL); +#define STREAM_COLOUR_ACCENT_B(stream, str) \ + b_stream_write_string(str, __STREAM_COLOUR_ACCENT, NULL); +#define STREAM_COLOUR_RESET_B(stream, str) \ + b_stream_write_string(str, __STREAM_COLOUR_RESET, NULL); + +static void print_header(struct mie_diag *diag, b_tty *dest) +{ + unsigned int w; + b_tty_get_dimensions(dest, &w, NULL); + + const struct mie_diag_class *diag_class = diag->diag_class; + assert(diag_class); + + STREAM_COLOUR_ACCENT(dest); + + size_t nr_written = 0; + nr_written += b_tty_puts(dest, 0, "--- "); + + const char *s = diag_class->c_title; + while (*s) { + b_tty_putc(dest, 0, toupper(*s)); + s++; + nr_written++; + } + + b_tty_putc(dest, 0, ' '); + nr_written++; + + while (nr_written < w) { + b_tty_putc(dest, 0, '-'); + nr_written++; + } + + STREAM_COLOUR_RESET(dest); + + b_tty_putc(dest, 0, '\n'); +} + +static void print_location(struct mie_diag *diag, b_tty *out) +{ + size_t x; + const char *name = mie_line_source_get_path(diag->diag_src); + + b_tty_puts(out, 0, " "); + STREAM_COLOUR_ACCENT(out); + b_tty_puts(out, 0, "> "); + STREAM_COLOUR_RESET(out); + b_tty_printf( + out, 0, "%s:%lu:%lu\n", name, diag->diag_loc.c_row, + diag->diag_loc.c_col); +} + +static void print_msg( + struct mie_diag *diag, struct mie_diag_c_msg *msg, b_tty *stream) +{ + b_paragraph_format format = { + .p_left_margin = 2, + .p_right_margin = 2, + }; + + b_print_paragraph(msg->msg_content, stream, &format); +} + +static bool highlight_contains_cell( + const struct mie_diag_highlight *hl, size_t row, size_t col) +{ + if (row < hl->hl_span.s_start.c_row || row > hl->hl_span.s_end.c_row) { + return false; + } + + if (row == hl->hl_span.s_start.c_row && col < hl->hl_span.s_start.c_col) { + return false; + } + + if (row == hl->hl_span.s_end.c_row && col > hl->hl_span.s_end.c_col) { + return false; + } + + return true; +} + +static bool amendment_contains_cell( + const struct mie_diag_amendment *a, size_t row, size_t col) +{ + unsigned long limit = 0; + + switch (a->a_type) { + case MIE_DIAG_AMENDMENT_ADD: + if (a->a_add.a_loc.c_row != row) { + return false; + } + + if (col < a->a_add.a_loc.c_col) { + return false; + } + + if (col > a->a_add.a_loc.c_col + a->__x - 1) { + return false; + } + + return true; + case MIE_DIAG_AMENDMENT_REMOVE: + if (a->a_remove.a_loc.c_row != row) { + return false; + } + + if (col < a->a_remove.a_loc.c_row) { + return false; + } + + if (col > a->a_remove.a_length) { + return false; + } + + return true; + case MIE_DIAG_AMENDMENT_REPLACE: + if (a->a_replace.a_loc.c_row != row) { + return false; + } + + if (col < a->a_replace.a_loc.c_row) { + return false; + } + + limit = a->a_replace.a_loc.c_row + + b_max(ulong, a->a_replace.a_length, a->__x) - 1; + + if (col > limit) { + return false; + } + + return true; + default: + return false; + } +} + +static struct mie_diag_highlight *find_highlight( + struct mie_diag_c_snippet *snippet, size_t row, size_t col) +{ + for (size_t i = 0; i < snippet->s_nr_highlights; i++) { + struct mie_diag_highlight *hl = &snippet->s_highlights[i]; + + if (highlight_contains_cell(hl, row, col)) { + return hl; + } + } + + return NULL; +} + +static struct mie_diag_amendment *find_amendment( + struct mie_diag_c_snippet *snippet, size_t row, size_t col) +{ + for (size_t i = 0; i < snippet->s_nr_amendments; i++) { + struct mie_diag_amendment *a = &snippet->s_amendments[i]; + + if (amendment_contains_cell(a, row, col)) { + return a; + } + } + + return NULL; +} + +static void update_amendment(struct snippet_print_ctx *ctx) +{ + if (ctx->ctx_amendment + && amendment_contains_cell( + ctx->ctx_amendment, ctx->ctx_row, ctx->ctx_col)) { + return; + } + + ctx->ctx_amendment + = find_amendment(ctx->ctx_snippet, ctx->ctx_row, ctx->ctx_col); +} + +static void update_highlighting(struct snippet_print_ctx *ctx) +{ + if (ctx->ctx_hl + && highlight_contains_cell(ctx->ctx_hl, ctx->ctx_row, ctx->ctx_col)) { + return; + } + + ctx->ctx_hl = NULL; + + STREAM_COLOUR_RESET(ctx->ctx_stream); + + const struct mie_diag_highlight *new_hl + = find_highlight(ctx->ctx_snippet, ctx->ctx_row, ctx->ctx_col); + + if (!new_hl) { + return; + } + + ctx->ctx_has_underline = true; + ctx->ctx_hl = new_hl; + + switch (ctx->ctx_hl->hl_type) { + case MIE_DIAG_HIGHLIGHT_ERROR: + STREAM_COLOUR_ERROR(ctx->ctx_stream); + STREAM_COLOUR_ERROR_B(ctx->ctx_stream, ctx->ctx_underline); + break; + case MIE_DIAG_HIGHLIGHT_WARNING: + STREAM_COLOUR_WARN(ctx->ctx_stream); + STREAM_COLOUR_WARN_B(ctx->ctx_stream, ctx->ctx_underline); + break; + case MIE_DIAG_HIGHLIGHT_HINT: + STREAM_COLOUR_HINT(ctx->ctx_stream); + STREAM_COLOUR_HINT_B(ctx->ctx_stream, ctx->ctx_underline); + break; + default: + break; + } +} + +static enum mie_status read_row(struct snippet_print_ctx *ctx, size_t row) +{ + if (ctx->ctx_linebuf_ptr) { + b_iterator_unref(ctx->ctx_linebuf_ptr); + ctx->ctx_linebuf_ptr = NULL; + } + + ctx->ctx_has_underline = false; + + size_t nr_read; + enum mie_status status = mie_line_source_get_row( + ctx->ctx_line_source, row, &ctx->ctx_linebuf); + if (status != MIE_SUCCESS) { + return status; + } + + ctx->ctx_linebuf_ptr = b_iterator_cbegin(ctx->ctx_linebuf); + return MIE_SUCCESS; +} + +static int get_char_amendment(struct snippet_print_ctx *ctx) +{ + const struct mie_diag_amendment *a = ctx->ctx_amendment; + size_t i = 0; + int c = 0; + + switch (a->a_type) { + case MIE_DIAG_AMENDMENT_ADD: + i = ctx->ctx_col - a->a_add.a_loc.c_row; + return a->a_add.a_str[i]; + case MIE_DIAG_AMENDMENT_REMOVE: + b_iterator_move_next(ctx->ctx_linebuf_ptr); + return GET_CHAR_CONTINUE; + case MIE_DIAG_AMENDMENT_REPLACE: + i = ctx->ctx_col - a->a_add.a_loc.c_col; + + if (i > a->__x) { + i = a->__x; + } + + c = a->a_replace.a_str[i]; + if (i < a->a_replace.a_length) { + b_iterator_move_next(ctx->ctx_linebuf_ptr); + } + + if (c != 0) { + return c; + } + + return GET_CHAR_CONTINUE; + default: + break; + } + + return GET_CHAR_CONTINUE; +} + +static int get_char(struct snippet_print_ctx *ctx) +{ + if (ctx->ctx_amendment) { + return get_char_amendment(ctx); + } + + b_wchar c = b_iterator_get_cvalue(ctx->ctx_linebuf_ptr).v_int; + if (c == B_WCHAR_INVALID) { + return GET_CHAR_STOP; + }; + + b_iterator_move_next(ctx->ctx_linebuf_ptr); + return c; +} + +static void update_underline(struct snippet_print_ctx *ctx) +{ + if (!ctx->ctx_hl) { + b_stream_write_char(ctx->ctx_underline, ' '); + return; + } + + switch (ctx->ctx_hl->hl_type) { + case MIE_DIAG_HIGHLIGHT_ERROR: + case MIE_DIAG_HIGHLIGHT_WARNING: + case MIE_DIAG_HIGHLIGHT_HINT: + b_stream_write_char(ctx->ctx_underline, '^'); + break; + default: + b_stream_write_char(ctx->ctx_underline, ' '); + break; + } +} + +static void write_underline(struct snippet_print_ctx *ctx) +{ + if (!ctx->ctx_has_underline) { + return; + } + + STREAM_COLOUR_RESET(ctx->ctx_stream); + STREAM_COLOUR_LINE_NO(ctx->ctx_stream); + b_tty_puts(ctx->ctx_stream, 0, " : "); + b_tty_puts(ctx->ctx_stream, 0, b_stringstream_ptr(ctx->ctx_underline)); + STREAM_COLOUR_RESET(ctx->ctx_stream); + b_tty_putc(ctx->ctx_stream, 0, '\n'); +} + +static void print_snippet( + struct mie_diag *diag, struct mie_diag_c_snippet *snippet, b_tty *stream) +{ + enum mie_status status = MIE_SUCCESS; + + struct snippet_print_ctx printer = { + .ctx_stream = stream, + .ctx_snippet = snippet, + .ctx_underline = b_stringstream_create(), + .ctx_line_source = diag->diag_src, + }; + + for (unsigned long row = snippet->s_first_line; + row <= snippet->s_last_line; row++) { + STREAM_COLOUR_LINE_NO(stream); + b_tty_printf(stream, 0, " %4lu | ", row); + printer.ctx_row = row; + + status = read_row(&printer, row); + if (status != MIE_SUCCESS) { + break; + } + + for (size_t i = 0;; i++) { + printer.ctx_col = i + 1; + + update_amendment(&printer); + + int c = get_char(&printer); + + if (c == GET_CHAR_CONTINUE) { + continue; + } else if (c == GET_CHAR_STOP || c == '\n') { + break; + } + + update_highlighting(&printer); + + b_tty_putc(stream, 0, c); + + update_underline(&printer); + } + + b_tty_putc(stream, 0, '\n'); + write_underline(&printer); + } + + b_stringstream_unref(printer.ctx_underline); +} + +static void print_component( + struct mie_diag *diag, struct mie_diag_component *component, b_tty *stream) +{ + switch (component->c_type) { + case MIE_DIAG_COMPONENT_MSG: + print_msg(diag, (struct mie_diag_c_msg *)component, stream); + break; + case MIE_DIAG_COMPONENT_SNIPPET: + print_snippet(diag, (struct mie_diag_c_snippet *)component, stream); + break; + default: + abort(); + } +} + +enum mie_status mie_diag_write_pretty(struct mie_diag *diag, b_tty *stream) +{ + print_header(diag, stream); + print_location(diag, stream); + + b_queue_entry *entry = b_queue_first(&diag->diag_components); + while (entry) { + struct mie_diag_component *c + = b_unbox(struct mie_diag_component, entry, c_entry); + + b_tty_putc(stream, 0, '\n'); + print_component(diag, c, stream); + + entry = b_queue_next(entry); + } + + return MIE_SUCCESS; +}