From c459f50e67996cb33f13bbfef31a1e5dda96c8ca Mon Sep 17 00:00:00 2001 From: Max Wash Date: Thu, 8 May 2025 10:48:23 +0100 Subject: [PATCH] meta: add ivy-diag library ivy-diag is used for generating and emitting diagnostic messages during compilation. --- CMakeLists.txt | 1 + diag/CMakeLists.txt | 15 ++ diag/ctx.c | 116 ++++++++++ diag/ctx.h | 26 +++ diag/diag.c | 96 ++++++++ diag/diag.h | 51 +++++ diag/include/ivy/diag.h | 166 ++++++++++++++ diag/stream.c | 134 +++++++++++ diag/stream.h | 18 ++ diag/write.c | 26 +++ diag/write.h | 15 ++ diag/write/json.c | 0 diag/write/pretty.c | 487 ++++++++++++++++++++++++++++++++++++++++ 13 files changed, 1151 insertions(+) create mode 100644 diag/CMakeLists.txt create mode 100644 diag/ctx.c create mode 100644 diag/ctx.h create mode 100644 diag/diag.c create mode 100644 diag/diag.h create mode 100644 diag/include/ivy/diag.h create mode 100644 diag/stream.c create mode 100644 diag/stream.h create mode 100644 diag/write.c create mode 100644 diag/write.h create mode 100644 diag/write/json.c create mode 100644 diag/write/pretty.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 9449b21..7fa358f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,7 @@ endif () find_package(Bluelib REQUIRED) add_subdirectory(common) +add_subdirectory(diag) add_subdirectory(lang) add_subdirectory(asm) add_subdirectory(mie) diff --git a/diag/CMakeLists.txt b/diag/CMakeLists.txt new file mode 100644 index 0000000..1815925 --- /dev/null +++ b/diag/CMakeLists.txt @@ -0,0 +1,15 @@ +file(GLOB_RECURSE diag_sources *.c *.h include/ivy/*.h) + +if (WIN32) + set(rc_file ${CMAKE_CURRENT_SOURCE_DIR}/../res/win32/diag.rc) +endif () + +if (IVY_STATIC) + add_library(ivy-diag STATIC ${diag_sources} ${rc_file}) +else () + add_library(ivy-diag SHARED ${diag_sources} ${rc_file}) +endif () + +target_include_directories(ivy-diag PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include/) +target_compile_definitions(ivy-diag PRIVATE IVY_EXPORT=1 IVY_STATIC=${IVY_STATIC}) +target_link_libraries(ivy-diag ivy-common Bluelib::Core Bluelib::Object Bluelib::Term) diff --git a/diag/ctx.c b/diag/ctx.c new file mode 100644 index 0000000..651e61b --- /dev/null +++ b/diag/ctx.c @@ -0,0 +1,116 @@ +#include "ctx.h" + +#include "diag.h" +#include "write.h" + +#include +#include +#include + +enum ivy_status ivy_diag_ctx_create(struct ivy_diag_ctx **out) +{ + struct ivy_diag_ctx *ctx = malloc(sizeof *ctx); + if (!ctx) { + return IVY_ERR_NO_MEMORY; + } + + memset(ctx, 0x0, sizeof *ctx); + + *out = ctx; + return IVY_OK; +} + +void ivy_diag_ctx_destroy(struct ivy_diag_ctx *ctx) +{ +} + +enum ivy_status ivy_diag_set_line_source( + struct ivy_diag_ctx *ctx, struct ivy_line_source *src) +{ + ctx->ctx_line_source = src; + return IVY_OK; +} + +enum ivy_status ivy_diag_ctx_set_class_definitions( + struct ivy_diag_ctx *ctx, const struct ivy_diag_class *classes, + size_t nr_classes) +{ + ctx->ctx_classes = classes; + ctx->ctx_nr_classes = nr_classes; + return IVY_OK; +} + +enum ivy_status ivy_diag_ctx_set_msg_definitions( + struct ivy_diag_ctx *ctx, const struct ivy_diag_msg *msgs, size_t nr_msgs) +{ + ctx->ctx_msg = msgs; + ctx->ctx_nr_msg = nr_msgs; + return IVY_OK; +} + +void ivy_diag_ctx_write( + struct ivy_diag_ctx *ctx, enum ivy_diag_format format, + struct ivy_diag_stream *stream) +{ + b_queue_iterator it; + b_queue_foreach (&it, &ctx->ctx_diags) { + struct ivy_diag *diag + = b_unbox(struct ivy_diag, it.entry, diag_entry); + diag_write(ctx, diag, format, stream); + } +} + +struct ivy_diag *ivy_diag_ctx_create_diag( + struct ivy_diag_ctx *ctx, unsigned long diag_class) +{ + struct ivy_diag *out = malloc(sizeof *out); + if (!out) { + return NULL; + } + + memset(out, 0x0, sizeof *out); + out->diag_class = diag_class; + out->diag_parent = ctx; + + b_queue_push_back(&ctx->ctx_diags, &out->diag_entry); + + return out; +} + +const struct ivy_diag_class *diag_ctx_get_class( + struct ivy_diag_ctx *ctx, unsigned long class_id) +{ + if (!ctx->ctx_classes) { + return NULL; + } + + if (class_id >= ctx->ctx_nr_classes) { + return NULL; + } + + const struct ivy_diag_class *out = &ctx->ctx_classes[class_id]; + if (out->c_type == IVY_DIAG_NONE || !out->c_title) { + return NULL; + } + + return out; +} + +const struct ivy_diag_msg *diag_ctx_get_msg( + struct ivy_diag_ctx *ctx, unsigned long msg_id) +{ + if (!ctx->ctx_msg) { + return NULL; + } + + if (msg_id >= ctx->ctx_nr_msg) { + return NULL; + } + + const struct ivy_diag_msg *out = &ctx->ctx_msg[msg_id]; + if (!out->msg_content) { + return NULL; + } + + return out; +} diff --git a/diag/ctx.h b/diag/ctx.h new file mode 100644 index 0000000..499ca39 --- /dev/null +++ b/diag/ctx.h @@ -0,0 +1,26 @@ +#ifndef _DIAG_CTX_H_ +#define _DIAG_CTX_H_ + +#include +#include + +struct ivy_line_source; + +struct ivy_diag_ctx { + struct ivy_line_source *ctx_line_source; + + const struct ivy_diag_class *ctx_classes; + size_t ctx_nr_classes; + + const struct ivy_diag_msg *ctx_msg; + size_t ctx_nr_msg; + + b_queue ctx_diags; +}; + +extern const struct ivy_diag_class *diag_ctx_get_class( + struct ivy_diag_ctx *ctx, unsigned long class_id); +extern const struct ivy_diag_msg *diag_ctx_get_msg( + struct ivy_diag_ctx *ctx, unsigned long msg_id); + +#endif diff --git a/diag/diag.c b/diag/diag.c new file mode 100644 index 0000000..24a1339 --- /dev/null +++ b/diag/diag.c @@ -0,0 +1,96 @@ +#include "diag.h" + +#include "ctx.h" + +#include +#include +#include +#include +#include + +struct diag_c_msg *diag_msg_create(const struct ivy_diag_msg *content) +{ + struct diag_c_msg *out = malloc(sizeof *out); + if (!out) { + return NULL; + } + + memset(out, 0x0, sizeof *out); + + out->msg_base.c_type = DIAG_COMPONENT_MSG; + out->msg_content = b_strdup(content->msg_content); + + return out; +} + +struct diag_c_snippet *diag_snippet_create( + unsigned long first_line, unsigned long last_line, + const struct ivy_diag_amendment *amendments, size_t nr_amendments, + const struct ivy_diag_highlight *highlights, size_t nr_highlights) +{ + struct diag_c_snippet *out = malloc(sizeof *out); + if (!out) { + return NULL; + } + + memset(out, 0x0, sizeof *out); + + out->s_base.c_type = DIAG_COMPONENT_SNIPPET; + out->s_first_line = first_line; + out->s_last_line = last_line; + + out->s_nr_amendments = nr_amendments; + out->s_amendments = calloc(nr_amendments, sizeof *amendments); + if (!out->s_amendments) { + free(out); + return NULL; + } + memcpy(out->s_amendments, amendments, nr_amendments * sizeof *amendments); + + out->s_nr_highlights = nr_highlights; + out->s_highlights = calloc(nr_highlights, sizeof *highlights); + if (!out->s_highlights) { + free(out->s_amendments); + free(out); + return NULL; + } + memcpy(out->s_highlights, highlights, nr_highlights * sizeof *highlights); + + return out; +} + +void ivy_diag_set_location( + struct ivy_diag *diag, unsigned long row, unsigned long col) +{ + diag->diag_row = row; + diag->diag_col = col; +} + +void ivy_diag_push_msg(struct ivy_diag *diag, unsigned long msg, ...) +{ + const struct ivy_diag_msg *msg_info + = diag_ctx_get_msg(diag->diag_parent, msg); + assert(msg_info); + + struct diag_c_msg *c_msg = diag_msg_create(msg_info); + if (!c_msg) { + return; + } + + b_queue_push_back(&diag->diag_components, &c_msg->msg_base.c_entry); +} + +void ivy_diag_push_snippet( + struct ivy_diag *diag, unsigned long first_line, unsigned long last_line, + const struct ivy_diag_amendment *amendments, size_t nr_amendments, + const struct ivy_diag_highlight *highlights, size_t nr_highlights) +{ + struct diag_c_snippet *c_snippet = diag_snippet_create( + first_line, last_line, amendments, nr_amendments, highlights, + nr_highlights); + if (!c_snippet) { + return; + } + + b_queue_push_back(&diag->diag_components, &c_snippet->s_base.c_entry); +} diff --git a/diag/diag.h b/diag/diag.h new file mode 100644 index 0000000..02d87b9 --- /dev/null +++ b/diag/diag.h @@ -0,0 +1,51 @@ +#ifndef _DIAG_DIAG_H_ +#define _DIAG_DIAG_H_ + +#include +#include + +struct ivy_diag_msg; + +enum diag_component_type { + DIAG_COMPONENT_NONE = 0, + DIAG_COMPONENT_MSG, + DIAG_COMPONENT_SNIPPET, +}; + +struct diag_component { + enum diag_component_type c_type; + b_queue_entry c_entry; +}; + +struct diag_c_msg { + struct diag_component msg_base; + char *msg_content; +}; + +struct diag_c_snippet { + struct diag_component s_base; + unsigned long s_first_line, s_last_line; + + struct ivy_diag_amendment *s_amendments; + size_t s_nr_amendments; + + struct ivy_diag_highlight *s_highlights; + size_t s_nr_highlights; +}; + +struct ivy_diag { + struct ivy_diag_ctx *diag_parent; + unsigned long diag_class; + + unsigned long diag_row, diag_col; + b_queue_entry diag_entry; + b_queue diag_components; +}; + +extern struct diag_c_msg *diag_msg_create(const struct ivy_diag_msg *content); +extern struct diag_c_snippet *diag_snippet_create( + unsigned long first_line, unsigned long last_line, + const struct ivy_diag_amendment *amendmends, size_t nr_amendments, + const struct ivy_diag_highlight *highlights, size_t nr_highlights); + +#endif diff --git a/diag/include/ivy/diag.h b/diag/include/ivy/diag.h new file mode 100644 index 0000000..4ab880b --- /dev/null +++ b/diag/include/ivy/diag.h @@ -0,0 +1,166 @@ +#ifndef IVY_DIAG_H_ +#define IVY_DIAG_H_ + +#include +#include +#include + +#define IVY_DIAG_HL(type, row0, col0, row1, col1) \ + { \ + .h_type = IVY_DIAG_HIGHLIGHT_##type, .h_start_row = row0, \ + .h_start_col = col0, .h_end_row = row1, .h_end_col = col1 \ + } + +#define IVY_DIAG_ADD(row, col, str) \ + { \ + .a_type = IVY_DIAG_AMENDMENT_ADD, .__x = strlen(str), \ + .a_add = { \ + .a_row = (row), \ + .a_col = (col), \ + .a_str = (str), \ + }, \ + } +#define IVY_DIAG_REMOVE(row, col, len) \ + { \ + .a_type = IVY_DIAG_AMENDMENT_REMOVE, \ + .a_remove = { \ + .a_row = (row), \ + .a_col = (col), \ + .a_length = (len), \ + }, \ + } +#define IVY_DIAG_REPLACE(row, col, len, str) \ + { \ + .a_type = IVY_DIAG_AMENDMENT_REPLACE, .__x = strlen(str), \ + .a_replace = { \ + .a_row = (row), \ + .a_col = (col), \ + .a_length = (len), \ + .a_str = (str), \ + }, \ + } + +struct ivy_line_source; +struct ivy_diag_ctx; +struct ivy_diag; + +struct b_tty; + +enum ivy_diag_stream_flags { + IVY_DIAG_STREAM_F_NONE = 0x00u, + IVY_DIAG_STREAM_F_COLOUR = 0x01u, +}; + +enum ivy_diag_stream_type { + IVY_DIAG_STREAM_NONE = 0, + IVY_DIAG_STREAM_FILE, + IVY_DIAG_STREAM_TTY, +}; + +struct ivy_diag_stream { + enum ivy_diag_stream_type s_type; + enum ivy_diag_stream_flags s_flags; + size_t s_row, s_col; + int s_esc; + + union { + struct b_tty *s_tty; + FILE *s_fp; + }; +}; + +enum ivy_diag_type { + IVY_DIAG_NONE = 0, + IVY_DIAG_ERROR, + IVY_DIAG_WARNING, + IVY_DIAG_HINT, +}; + +enum ivy_diag_highlight_type { + IVY_DIAG_HIGHLIGHT_NONE = 0, + IVY_DIAG_HIGHLIGHT_ERROR, + IVY_DIAG_HIGHLIGHT_WARNING, + IVY_DIAG_HIGHLIGHT_HINT, +}; + +struct ivy_diag_highlight { + enum ivy_diag_highlight_type h_type; + unsigned long h_start_row, h_start_col; + unsigned long h_end_row, h_end_col; +}; + +enum ivy_diag_amendment_type { + IVY_DIAG_AMENDMENT_NONE = 0, + IVY_DIAG_AMENDMENT_ADD, + IVY_DIAG_AMENDMENT_REMOVE, + IVY_DIAG_AMENDMENT_REPLACE, +}; + +enum ivy_diag_format { + IVY_DIAG_FORMAT_NONE = 0, + IVY_DIAG_FORMAT_PRETTY, + IVY_DIAG_FORMAT_JSON, +}; + +struct ivy_diag_amendment { + enum ivy_diag_amendment_type a_type; + unsigned long __x; + + union { + struct { + unsigned long a_row, a_col; + char *a_str; + } a_add; + + struct { + unsigned long a_row, a_col; + unsigned long a_length; + } a_remove; + + struct { + unsigned long a_row, a_col; + unsigned long a_length; + char *a_str; + } a_replace; + }; +}; + +struct ivy_diag_class { + enum ivy_diag_type c_type; + const char *c_title; +}; + +struct ivy_diag_msg { + const char *msg_content; +}; + +extern void ivy_diag_stream_init_file(struct ivy_diag_stream *stream, FILE *fp); +extern void ivy_diag_stream_init_tty( + struct ivy_diag_stream *stream, struct b_tty *tty); + +extern enum ivy_status ivy_diag_ctx_create(struct ivy_diag_ctx **out); +extern void ivy_diag_ctx_destroy(struct ivy_diag_ctx *ctx); + +extern enum ivy_status ivy_diag_set_line_source( + struct ivy_diag_ctx *ctx, struct ivy_line_source *src); +extern enum ivy_status ivy_diag_ctx_set_class_definitions( + struct ivy_diag_ctx *ctx, const struct ivy_diag_class *classes, + size_t nr_classes); +extern enum ivy_status ivy_diag_ctx_set_msg_definitions( + struct ivy_diag_ctx *ctx, const struct ivy_diag_msg *msgs, size_t nr_msgs); + +extern void ivy_diag_ctx_write( + struct ivy_diag_ctx *ctx, enum ivy_diag_format format, + struct ivy_diag_stream *stream); +extern struct ivy_diag *ivy_diag_ctx_create_diag( + struct ivy_diag_ctx *ctx, unsigned long diag_class); + +extern void ivy_diag_set_location( + struct ivy_diag *diag, unsigned long row, unsigned long col); +extern void ivy_diag_push_msg(struct ivy_diag *diag, unsigned long msg, ...); +extern void ivy_diag_push_snippet( + struct ivy_diag *diag, unsigned long first_line, unsigned long last_line, + const struct ivy_diag_amendment *amendmends, size_t nr_amendments, + const struct ivy_diag_highlight *highlights, size_t nr_highlights); + +#endif diff --git a/diag/stream.c b/diag/stream.c new file mode 100644 index 0000000..186aa33 --- /dev/null +++ b/diag/stream.c @@ -0,0 +1,134 @@ +#include "stream.h" + +#include +#include + +void ivy_diag_stream_init_file(struct ivy_diag_stream *stream, FILE *fp) +{ + memset(stream, 0x0, sizeof *stream); + + stream->s_type = IVY_DIAG_STREAM_FILE; + stream->s_flags = IVY_DIAG_STREAM_F_NONE; + stream->s_fp = fp; + stream->s_row = 1; + stream->s_col = 1; +} + +void ivy_diag_stream_init_tty(struct ivy_diag_stream *stream, struct b_tty *tty) +{ + memset(stream, 0x0, sizeof *stream); + + stream->s_type = IVY_DIAG_STREAM_TTY; + stream->s_flags = IVY_DIAG_STREAM_F_COLOUR; + stream->s_tty = tty; + stream->s_row = 1; + stream->s_col = 1; +} + +enum ivy_status diag_stream_get_dimensions( + struct ivy_diag_stream *stream, size_t *out_rows, size_t *out_cols) +{ + switch (stream->s_type) { + case IVY_DIAG_STREAM_FILE: + if (out_rows) { + *out_rows = (size_t)-1; + } + + if (out_cols) { + *out_cols = (size_t)-1; + } + + break; + case IVY_DIAG_STREAM_TTY: { + unsigned int w, h; + b_tty_get_dimensions(stream->s_tty, &w, &h); + if (out_rows) { + *out_rows = h; + } + + if (out_cols) { + *out_cols = w; + } + break; + } + default: + return IVY_ERR_BAD_STATE; + } + + return IVY_OK; +} + +enum ivy_status diag_stream_putc(struct ivy_diag_stream *stream, char c) +{ + enum ivy_status status = IVY_OK; + + switch (stream->s_type) { + case IVY_DIAG_STREAM_FILE: + fputc(c, stream->s_fp); + + status = (ferror(stream->s_fp)) ? IVY_ERR_IO_FAILURE : IVY_OK; + break; + case IVY_DIAG_STREAM_TTY: + b_tty_putc(stream->s_tty, 0, c); + status = IVY_OK; + break; + default: + status = IVY_ERR_BAD_STATE; + break; + } + + if (stream->s_esc) { + if (c == '[' || c == ']') { + stream->s_esc = 0; + } + + return status; + } + + if (!stream->s_esc && c == '[') { + stream->s_esc = 1; + return status; + } + + switch (c) { + case '\r': + stream->s_col = 1; + break; + case '\n': + stream->s_col = 1; + stream->s_row++; + break; + default: + stream->s_col++; + break; + } + + return status; +} + +enum ivy_status diag_stream_puts(struct ivy_diag_stream *stream, const char *s) +{ + enum ivy_status status = IVY_OK; + + while (*s && status == IVY_OK) { + status = diag_stream_putc(stream, *s); + s++; + } + + return status; +} + +enum ivy_status diag_stream_printf( + struct ivy_diag_stream *stream, const char *format, ...) +{ + enum ivy_status status = IVY_OK; + va_list arg; + va_start(arg, format); + + char buf[256]; + vsnprintf(buf, sizeof buf, format, arg); + + va_end(arg); + + return diag_stream_puts(stream, buf); +} diff --git a/diag/stream.h b/diag/stream.h new file mode 100644 index 0000000..0f7b419 --- /dev/null +++ b/diag/stream.h @@ -0,0 +1,18 @@ +#ifndef _DIAG_STREAM_H_ +#define _DIAG_STREAM_H_ + +#include + +#define DIAG_STREAM_COL(sp) ((sp)->s_col) +#define DIAG_STREAM_ROW(sp) ((sp)->s_row) +#define DIAG_STREAM_FLAG_SET(sp, f) (((sp)->s_flags & (f)) != 0) + +extern enum ivy_status diag_stream_get_dimensions( + struct ivy_diag_stream *stream, size_t *out_rows, size_t *out_cols); +extern enum ivy_status diag_stream_putc(struct ivy_diag_stream *stream, char c); +extern enum ivy_status diag_stream_puts( + struct ivy_diag_stream *stream, const char *s); +extern enum ivy_status diag_stream_printf( + struct ivy_diag_stream *stream, const char *format, ...); + +#endif diff --git a/diag/write.c b/diag/write.c new file mode 100644 index 0000000..2246bcc --- /dev/null +++ b/diag/write.c @@ -0,0 +1,26 @@ +#include "write.h" + +#include + +extern struct diag_writer pretty_writer; + +static const struct diag_writer *writers[] = { + [IVY_DIAG_FORMAT_PRETTY] = &pretty_writer, +}; +static const size_t nr_writers = sizeof writers / sizeof writers[0]; + +enum ivy_status diag_write( + struct ivy_diag_ctx *ctx, struct ivy_diag *diag, + enum ivy_diag_format format, struct ivy_diag_stream *stream) +{ + if (format < 0 || format >= nr_writers) { + return IVY_ERR_INVALID_VALUE; + } + + const struct diag_writer *writer = writers[format]; + if (!writer) { + return IVY_ERR_INVALID_VALUE; + } + + return writer->write(ctx, diag, stream); +} diff --git a/diag/write.h b/diag/write.h new file mode 100644 index 0000000..9869b2d --- /dev/null +++ b/diag/write.h @@ -0,0 +1,15 @@ +#ifndef _DIAG_WRITE_H_ +#define _DIAG_WRITE_H_ + +#include + +struct diag_writer { + enum ivy_status (*write)( + struct ivy_diag_ctx *, struct ivy_diag *, struct ivy_diag_stream *); +}; + +extern enum ivy_status diag_write( + struct ivy_diag_ctx *ctx, struct ivy_diag *diag, + enum ivy_diag_format format, struct ivy_diag_stream *stream); + +#endif diff --git a/diag/write/json.c b/diag/write/json.c new file mode 100644 index 0000000..e69de29 diff --git a/diag/write/pretty.c b/diag/write/pretty.c new file mode 100644 index 0000000..534cb03 --- /dev/null +++ b/diag/write/pretty.c @@ -0,0 +1,487 @@ +#include "../ctx.h" +#include "../diag.h" +#include "../stream.h" +#include "../write.h" + +#include +#include +#include +#include +#include +#include + +#define GET_CHAR_STOP 0 +#define GET_CHAR_CONTINUE -1 + +struct snippet_print_ctx { + struct ivy_diag_stream *ctx_stream; + struct ivy_line_source *ctx_line_source; + + size_t ctx_row, ctx_col; + + char ctx_underline_buf[256]; + char ctx_line_buf[256]; + size_t ctx_line_buf_ptr; + + bool ctx_has_underline; + b_stringstream ctx_underline; + + struct diag_c_snippet *ctx_snippet; + + const struct ivy_diag_highlight *ctx_hl; + const struct ivy_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_ACCENT "[cyan]" +#define __STREAM_COLOUR_RESET "[reset]" + +#define STREAM_COLOUR_ERROR(stream) \ + if (DIAG_STREAM_FLAG_SET(stream, IVY_DIAG_STREAM_F_COLOUR)) { \ + diag_stream_puts(stream, __STREAM_COLOUR_ERROR); \ + } +#define STREAM_COLOUR_WARN(stream) \ + if (DIAG_STREAM_FLAG_SET(stream, IVY_DIAG_STREAM_F_COLOUR)) { \ + diag_stream_puts(stream, __STREAM_COLOUR_WARN); \ + } +#define STREAM_COLOUR_HINT(stream) \ + if (DIAG_STREAM_FLAG_SET(stream, IVY_DIAG_STREAM_F_COLOUR)) { \ + diag_stream_puts(stream, __STREAM_COLOUR_HINT); \ + } +#define STREAM_COLOUR_ACCENT(stream) \ + if (DIAG_STREAM_FLAG_SET(stream, IVY_DIAG_STREAM_F_COLOUR)) { \ + diag_stream_puts(stream, __STREAM_COLOUR_ACCENT); \ + } +#define STREAM_COLOUR_RESET(stream) \ + if (DIAG_STREAM_FLAG_SET(stream, IVY_DIAG_STREAM_F_COLOUR)) { \ + diag_stream_puts(stream, __STREAM_COLOUR_RESET); \ + } + +#define STREAM_COLOUR_ERROR_B(stream, str) \ + if (DIAG_STREAM_FLAG_SET(stream, IVY_DIAG_STREAM_F_COLOUR)) { \ + b_stringstream_add(str, __STREAM_COLOUR_ERROR); \ + } +#define STREAM_COLOUR_WARN_B(stream, str) \ + if (DIAG_STREAM_FLAG_SET(stream, IVY_DIAG_STREAM_F_COLOUR)) { \ + b_stringstream_add(str, __STREAM_COLOUR_WARN); \ + } +#define STREAM_COLOUR_HINT_B(stream, str) \ + if (DIAG_STREAM_FLAG_SET(stream, IVY_DIAG_STREAM_F_COLOUR)) { \ + b_stringstream_add(str, __STREAM_COLOUR_HINT); \ + } +#define STREAM_COLOUR_ACCENT_B(stream, str) \ + if (DIAG_STREAM_FLAG_SET(stream, IVY_DIAG_STREAM_F_COLOUR)) { \ + b_stringstream_add(str, __STREAM_COLOUR_ACCENT); \ + } +#define STREAM_COLOUR_RESET_B(stream, str) \ + if (DIAG_STREAM_FLAG_SET(stream, IVY_DIAG_STREAM_F_COLOUR)) { \ + b_stringstream_add(str, __STREAM_COLOUR_RESET); \ + } + +static void print_header( + struct ivy_diag_ctx *ctx, struct ivy_diag *diag, + struct ivy_diag_stream *stream) +{ + size_t w; + diag_stream_get_dimensions(stream, NULL, &w); + + const struct ivy_diag_class *diag_class + = diag_ctx_get_class(ctx, diag->diag_class); + assert(diag_class); + + STREAM_COLOUR_ACCENT(stream); + + diag_stream_puts(stream, "--- "); + + const char *s = diag_class->c_title; + while (*s) { + diag_stream_putc(stream, toupper(*s)); + s++; + } + + diag_stream_putc(stream, ' '); + + while (DIAG_STREAM_COL(stream) <= w) { + diag_stream_putc(stream, '-'); + } + + STREAM_COLOUR_RESET(stream); + + diag_stream_putc(stream, '\n'); +} + +static void print_location( + struct ivy_diag_ctx *ctx, struct ivy_diag *diag, + struct ivy_diag_stream *stream) +{ + char name[512]; + size_t x; + enum ivy_status status = ivy_line_source_get_name( + ctx->ctx_line_source, name, sizeof name, &x); + if (status != IVY_OK) { + return; + } + + diag_stream_puts(stream, " "); + STREAM_COLOUR_ACCENT(stream); + diag_stream_puts(stream, "> "); + STREAM_COLOUR_RESET(stream); + diag_stream_printf( + stream, "%s:%lu:%lu\n", name, diag->diag_row, diag->diag_col); +} + +static void print_msg( + struct ivy_diag_ctx *ctx, struct ivy_diag *diag, struct diag_c_msg *msg, + struct ivy_diag_stream *stream) +{ + diag_stream_puts(stream, " "); + diag_stream_puts(stream, msg->msg_content); + diag_stream_putc(stream, '\n'); +} + +static bool highlight_contains_cell( + const struct ivy_diag_highlight *hl, size_t row, size_t col) +{ + if (row < hl->h_start_row || row > hl->h_end_row) { + return false; + } + + if (row == hl->h_start_row && col < hl->h_start_col) { + return false; + } + + if (row == hl->h_end_row && col > hl->h_end_col) { + return false; + } + + return true; +} + +static bool amendment_contains_cell( + const struct ivy_diag_amendment *a, size_t row, size_t col) +{ + unsigned long limit = 0; + + switch (a->a_type) { + case IVY_DIAG_AMENDMENT_ADD: + if (a->a_add.a_row != row) { + return false; + } + + if (col < a->a_add.a_col) { + return false; + } + + if (col > a->a_add.a_col + a->__x - 1) { + return false; + } + + return true; + case IVY_DIAG_AMENDMENT_REMOVE: + if (a->a_remove.a_row != row) { + return false; + } + + if (col < a->a_remove.a_col) { + return false; + } + + if (col > a->a_remove.a_length) { + return false; + } + + return true; + case IVY_DIAG_AMENDMENT_REPLACE: + if (a->a_replace.a_row != row) { + return false; + } + + if (col < a->a_replace.a_col) { + return false; + } + + limit = a->a_replace.a_col + + b_max(ulong, a->a_replace.a_length, a->__x) - 1; + + if (col > limit) { + return false; + } + + return true; + default: + return false; + } +} + +static struct ivy_diag_highlight *find_highlight( + struct diag_c_snippet *snippet, size_t row, size_t col) +{ + b_queue_iterator it; + for (size_t i = 0; i < snippet->s_nr_highlights; i++) { + struct ivy_diag_highlight *hl = &snippet->s_highlights[i]; + + if (highlight_contains_cell(hl, row, col)) { + return hl; + } + } + + return NULL; +} + +static struct ivy_diag_amendment *find_amendment( + struct diag_c_snippet *snippet, size_t row, size_t col) +{ + b_queue_iterator it; + for (size_t i = 0; i < snippet->s_nr_amendments; i++) { + struct ivy_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 ivy_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->h_type) { + case IVY_DIAG_HIGHLIGHT_ERROR: + STREAM_COLOUR_ERROR(ctx->ctx_stream); + STREAM_COLOUR_ERROR_B(ctx->ctx_stream, &ctx->ctx_underline); + break; + case IVY_DIAG_HIGHLIGHT_WARNING: + STREAM_COLOUR_WARN(ctx->ctx_stream); + STREAM_COLOUR_WARN_B(ctx->ctx_stream, &ctx->ctx_underline); + break; + case IVY_DIAG_HIGHLIGHT_HINT: + STREAM_COLOUR_HINT(ctx->ctx_stream); + STREAM_COLOUR_HINT_B(ctx->ctx_stream, &ctx->ctx_underline); + break; + default: + break; + } +} + +static enum ivy_status read_row(struct snippet_print_ctx *ctx, size_t row) +{ + ctx->ctx_line_buf[0] = 0; + ctx->ctx_underline_buf[0] = 0; + ctx->ctx_line_buf_ptr = 0; + ctx->ctx_has_underline = false; + + b_stringstream_begin( + &ctx->ctx_underline, ctx->ctx_underline_buf, + sizeof ctx->ctx_underline_buf); + + size_t nr_read; + return ivy_line_source_get_row( + ctx->ctx_line_source, row, ctx->ctx_line_buf, + sizeof ctx->ctx_line_buf, &nr_read); +} + +static int get_char_amendment(struct snippet_print_ctx *ctx) +{ + const struct ivy_diag_amendment *a = ctx->ctx_amendment; + size_t i = 0; + int c = 0; + + switch (a->a_type) { + case IVY_DIAG_AMENDMENT_ADD: + i = ctx->ctx_col - a->a_add.a_col; + return a->a_add.a_str[i]; + case IVY_DIAG_AMENDMENT_REMOVE: + ctx->ctx_line_buf_ptr++; + return GET_CHAR_CONTINUE; + case IVY_DIAG_AMENDMENT_REPLACE: + i = ctx->ctx_col - a->a_add.a_col; + + if (i > a->__x) { + i = a->__x; + } + + c = a->a_replace.a_str[i]; + if (i < a->a_replace.a_length) { + ctx->ctx_line_buf_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); + } + + int c = ctx->ctx_line_buf[ctx->ctx_line_buf_ptr]; + if (c == 0) { + return GET_CHAR_STOP; + }; + + ctx->ctx_line_buf_ptr++; + return c; +} + +static void update_underline(struct snippet_print_ctx *ctx) +{ + if (!ctx->ctx_hl) { + b_stringstream_add(&ctx->ctx_underline, " "); + return; + } + + switch (ctx->ctx_hl->h_type) { + case IVY_DIAG_HIGHLIGHT_ERROR: + case IVY_DIAG_HIGHLIGHT_WARNING: + b_stringstream_add(&ctx->ctx_underline, "^"); + break; + case IVY_DIAG_HIGHLIGHT_HINT: + b_stringstream_add(&ctx->ctx_underline, "-"); + break; + default: + b_stringstream_add(&ctx->ctx_underline, " "); + break; + } +} + +static void write_underline(struct snippet_print_ctx *ctx) +{ + if (!ctx->ctx_has_underline) { + return; + } + + STREAM_COLOUR_ACCENT(ctx->ctx_stream); + diag_stream_puts(ctx->ctx_stream, " | "); + diag_stream_puts(ctx->ctx_stream, ctx->ctx_underline_buf); + STREAM_COLOUR_RESET(ctx->ctx_stream); + diag_stream_putc(ctx->ctx_stream, '\n'); +} + +static void print_snippet( + struct ivy_diag_ctx *ctx, struct ivy_diag *diag, + struct diag_c_snippet *snippet, struct ivy_diag_stream *stream) +{ + enum ivy_status status = IVY_OK; + + struct snippet_print_ctx printer = { + .ctx_stream = stream, + .ctx_snippet = snippet, + .ctx_line_source = ctx->ctx_line_source, + }; + + for (unsigned long row = snippet->s_first_line; + row <= snippet->s_last_line; row++) { + STREAM_COLOUR_ACCENT(stream); + diag_stream_printf(stream, " %4lu | ", row); + printer.ctx_row = row; + + status = read_row(&printer, row); + if (status != IVY_OK) { + 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) { + break; + } + + update_highlighting(&printer); + + diag_stream_putc(stream, c); + + update_underline(&printer); + } + + diag_stream_putc(stream, '\n'); + write_underline(&printer); + } +} + +static void print_component( + struct ivy_diag_ctx *ctx, struct ivy_diag *diag, + struct diag_component *component, struct ivy_diag_stream *stream) +{ + switch (component->c_type) { + case DIAG_COMPONENT_MSG: + print_msg(ctx, diag, (struct diag_c_msg *)component, stream); + break; + case DIAG_COMPONENT_SNIPPET: + print_snippet(ctx, diag, (struct diag_c_snippet *)component, stream); + break; + default: + abort(); + } +} + +static enum ivy_status write( + struct ivy_diag_ctx *ctx, struct ivy_diag *diag, + struct ivy_diag_stream *stream) +{ + print_header(ctx, diag, stream); + print_location(ctx, diag, stream); + + b_queue_iterator it; + b_queue_foreach (&it, &diag->diag_components) { + struct diag_component *c + = b_unbox(struct diag_component, it.entry, c_entry); + + diag_stream_putc(stream, '\n'); + print_component(ctx, diag, c, stream); + } + + return IVY_OK; +} + +struct diag_writer pretty_writer = { + .write = write, +};