From 9a2fc6cf4c8b4434ae45170953b77b3239120fea Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 19 Mar 2024 15:34:29 +0000 Subject: Serialize errors to make them sortable, deterministic, and generally more control --- src/error.cpp | 213 ++++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 133 insertions(+), 80 deletions(-) (limited to 'src/error.cpp') diff --git a/src/error.cpp b/src/error.cpp index e63682829..e5803e5a2 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -1,3 +1,14 @@ +enum ErrorValueKind : u32 { + ErrorValue_Error, + ErrorValue_Warning, +}; + +struct ErrorValue { + ErrorValueKind kind; + TokenPos pos; + Array msgs; +}; + struct ErrorCollector { TokenPos prev; std::atomic count; @@ -8,21 +19,54 @@ struct ErrorCollector { BlockingMutex string_mutex; RecursiveMutex block_mutex; - RecursiveMutex error_buffer_mutex; - Array error_buffer; - Array errors; + Array error_values; + ErrorValue curr_error_value; + std::atomic curr_error_value_set; }; gb_global ErrorCollector global_error_collector; +gb_internal void push_error_value(TokenPos const &pos, ErrorValueKind kind = ErrorValue_Error) { + GB_ASSERT(global_error_collector.curr_error_value_set.load() == false); + ErrorValue ev = {kind, pos}; + ev.msgs.allocator = heap_allocator(); + + global_error_collector.curr_error_value = ev; + global_error_collector.curr_error_value_set.store(true); +} + +gb_internal void pop_error_value(void) { + if (global_error_collector.curr_error_value_set.load()) { + array_add(&global_error_collector.error_values, global_error_collector.curr_error_value); + + global_error_collector.curr_error_value = {}; + global_error_collector.curr_error_value_set.store(false); + } +} + + +gb_internal void try_pop_error_value(void) { + if (!global_error_collector.in_block.load()) { + pop_error_value(); + } +} + +gb_internal ErrorValue *get_error_value(void) { + GB_ASSERT(global_error_collector.curr_error_value_set.load() == true); + return &global_error_collector.curr_error_value; +} + + + gb_internal bool any_errors(void) { return global_error_collector.count.load() != 0; } + + gb_internal void init_global_error_collector(void) { - array_init(&global_error_collector.errors, heap_allocator()); - array_init(&global_error_collector.error_buffer, heap_allocator()); + array_init(&global_error_collector.error_values, heap_allocator()); array_init(&global_file_path_strings, heap_allocator(), 1, 4096); array_init(&global_files, heap_allocator(), 1, 4096); } @@ -102,6 +146,7 @@ gb_internal AstFile *thread_safe_get_ast_file_from_id(i32 index) { gb_internal bool global_warnings_as_errors(void); gb_internal bool global_ignore_warnings(void); gb_internal bool show_error_line(void); +gb_internal bool terse_errors(void); gb_internal bool has_ansi_terminal_colours(void); gb_internal gbString get_file_line_as_string(TokenPos const &pos, i32 *offset); @@ -113,55 +158,32 @@ gb_internal void syntax_error(Token const &token, char const *fmt, ...); gb_internal void syntax_error(TokenPos pos, char const *fmt, ...); gb_internal void syntax_warning(Token const &token, char const *fmt, ...); gb_internal void compiler_error(char const *fmt, ...); +gb_internal void print_all_errors(void); -gb_internal void begin_error_block(void) { - mutex_lock(&global_error_collector.block_mutex); - global_error_collector.in_block.store(true); -} -gb_internal void end_error_block(void) { - mutex_lock(&global_error_collector.error_buffer_mutex); - isize n = global_error_collector.error_buffer.count; - if (n > 0) { - u8 *text = global_error_collector.error_buffer.data; - - bool add_extra_newline = false; +#define ERROR_OUT_PROC(name) void name(char const *fmt, va_list va) +typedef ERROR_OUT_PROC(ErrorOutProc); - if (show_error_line()) { - if (n >= 2 && !(text[n-2] == '\n' && text[n-1] == '\n')) { - add_extra_newline = true; - } - } else { - isize newline_count = 0; - for (isize i = 0; i < n; i++) { - if (text[i] == '\n') { - newline_count += 1; - } - } - if (newline_count > 1) { - add_extra_newline = true; - } - } +gb_internal ERROR_OUT_PROC(default_error_out_va) { + char buf[4096] = {}; + isize len = gb_snprintf_va(buf, gb_size_of(buf), fmt, va); + isize n = len-1; - if (add_extra_newline) { - // add an extra new line as padding when the error line is being shown - error_line("\n"); - } + String msg = {(u8 *)buf, n}; - n = global_error_collector.error_buffer.count; - text = gb_alloc_array(permanent_allocator(), u8, n+1); - gb_memmove(text, global_error_collector.error_buffer.data, n); - text[n] = 0; + ErrorValue *ev = get_error_value(); + array_add(&ev->msgs, copy_string(permanent_allocator(), msg)); +} +gb_global ErrorOutProc *error_out_va = default_error_out_va; - mutex_lock(&global_error_collector.error_out_mutex); - String s = {text, n}; - array_add(&global_error_collector.errors, s); - mutex_unlock(&global_error_collector.error_out_mutex); +gb_internal void begin_error_block(void) { + mutex_lock(&global_error_collector.block_mutex); + global_error_collector.in_block.store(true); +} - global_error_collector.error_buffer.count = 0; - } - mutex_unlock(&global_error_collector.error_buffer_mutex); +gb_internal void end_error_block(void) { + pop_error_value(); global_error_collector.in_block.store(false); mutex_unlock(&global_error_collector.block_mutex); } @@ -169,40 +191,6 @@ gb_internal void end_error_block(void) { #define ERROR_BLOCK() begin_error_block(); defer (end_error_block()) -#define ERROR_OUT_PROC(name) void name(char const *fmt, va_list va) -typedef ERROR_OUT_PROC(ErrorOutProc); - -gb_internal ERROR_OUT_PROC(default_error_out_va) { - gbFile *f = gb_file_get_standard(gbFileStandard_Error); - - char buf[4096] = {}; - isize len = gb_snprintf_va(buf, gb_size_of(buf), fmt, va); - isize n = len-1; - if (global_error_collector.in_block) { - mutex_lock(&global_error_collector.error_buffer_mutex); - - isize cap = global_error_collector.error_buffer.count + n; - array_reserve(&global_error_collector.error_buffer, cap); - u8 *data = global_error_collector.error_buffer.data + global_error_collector.error_buffer.count; - gb_memmove(data, buf, n); - global_error_collector.error_buffer.count += n; - - mutex_unlock(&global_error_collector.error_buffer_mutex); - } else { - mutex_lock(&global_error_collector.error_out_mutex); - { - u8 *text = gb_alloc_array(permanent_allocator(), u8, n+1); - gb_memmove(text, buf, n); - text[n] = 0; - array_add(&global_error_collector.errors, make_string(text, n)); - } - mutex_unlock(&global_error_collector.error_out_mutex); - - } - gb_file_write(f, buf, n); -} - -gb_global ErrorOutProc *error_out_va = default_error_out_va; gb_internal void error_out(char const *fmt, ...) { va_list va; @@ -357,9 +345,12 @@ gb_internal void error_out_coloured(char const *str, TerminalStyle style, Termin gb_internal void error_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) { global_error_collector.count.fetch_add(1); if (global_error_collector.count > MAX_ERROR_COLLECTOR_COUNT()) { + print_all_errors(); gb_exit(1); } mutex_lock(&global_error_collector.mutex); + + push_error_value(pos, ErrorValue_Error); // NOTE(bill): Duplicate error, skip it if (pos.line == 0) { error_out_coloured("Error: ", TerminalStyle_Normal, TerminalColour_Red); @@ -377,6 +368,7 @@ gb_internal void error_va(TokenPos const &pos, TokenPos end, char const *fmt, va } else { global_error_collector.count.fetch_sub(1); } + try_pop_error_value(); mutex_unlock(&global_error_collector.mutex); } @@ -387,6 +379,9 @@ gb_internal void warning_va(TokenPos const &pos, TokenPos end, char const *fmt, } global_error_collector.warning_count.fetch_add(1); mutex_lock(&global_error_collector.mutex); + + push_error_value(pos, ErrorValue_Warning); + if (!global_ignore_warnings()) { // NOTE(bill): Duplicate error, skip it if (pos.line == 0) { @@ -402,6 +397,7 @@ gb_internal void warning_va(TokenPos const &pos, TokenPos end, char const *fmt, show_error_on_line(pos, end); } } + try_pop_error_value(); mutex_unlock(&global_error_collector.mutex); } @@ -413,9 +409,13 @@ gb_internal void error_line_va(char const *fmt, va_list va) { gb_internal void error_no_newline_va(TokenPos const &pos, char const *fmt, va_list va) { global_error_collector.count.fetch_add(1); if (global_error_collector.count.load() > MAX_ERROR_COLLECTOR_COUNT()) { + print_all_errors(); gb_exit(1); } mutex_lock(&global_error_collector.mutex); + + push_error_value(pos, ErrorValue_Error); + // NOTE(bill): Duplicate error, skip it if (pos.line == 0) { error_out_coloured("Error: ", TerminalStyle_Normal, TerminalColour_Red); @@ -428,6 +428,8 @@ gb_internal void error_no_newline_va(TokenPos const &pos, char const *fmt, va_li } error_out_va(fmt, va); } + + try_pop_error_value(); mutex_unlock(&global_error_collector.mutex); } @@ -435,9 +437,13 @@ gb_internal void error_no_newline_va(TokenPos const &pos, char const *fmt, va_li gb_internal void syntax_error_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) { global_error_collector.count.fetch_add(1); if (global_error_collector.count > MAX_ERROR_COLLECTOR_COUNT()) { + print_all_errors(); gb_exit(1); } mutex_lock(&global_error_collector.mutex); + + push_error_value(pos, ErrorValue_Warning); + // NOTE(bill): Duplicate error, skip it if (global_error_collector.prev != pos) { global_error_collector.prev = pos; @@ -451,15 +457,21 @@ gb_internal void syntax_error_va(TokenPos const &pos, TokenPos end, char const * error_out_va(fmt, va); error_out("\n"); } + + try_pop_error_value(); mutex_unlock(&global_error_collector.mutex); } gb_internal void syntax_error_with_verbose_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) { global_error_collector.count.fetch_add(1); if (global_error_collector.count > MAX_ERROR_COLLECTOR_COUNT()) { + print_all_errors(); gb_exit(1); } mutex_lock(&global_error_collector.mutex); + + push_error_value(pos, ErrorValue_Warning); + // NOTE(bill): Duplicate error, skip it if (pos.line == 0) { error_out_coloured("Syntax_Error: ", TerminalStyle_Normal, TerminalColour_Red); @@ -475,6 +487,8 @@ gb_internal void syntax_error_with_verbose_va(TokenPos const &pos, TokenPos end, error_out("\n"); show_error_on_line(pos, end); } + + try_pop_error_value(); mutex_unlock(&global_error_collector.mutex); } @@ -486,6 +500,10 @@ gb_internal void syntax_warning_va(TokenPos const &pos, TokenPos end, char const } mutex_lock(&global_error_collector.mutex); global_error_collector.warning_count++; + + + push_error_value(pos, ErrorValue_Warning); + if (!global_ignore_warnings()) { // NOTE(bill): Duplicate error, skip it if (global_error_collector.prev != pos) { @@ -501,6 +519,8 @@ gb_internal void syntax_warning_va(TokenPos const &pos, TokenPos end, char const error_out("\n"); } } + + try_pop_error_value(); mutex_unlock(&global_error_collector.mutex); } @@ -568,6 +588,8 @@ gb_internal void syntax_error_with_verbose(TokenPos pos, TokenPos end, char cons gb_internal void compiler_error(char const *fmt, ...) { + print_all_errors(); + va_list va; va_start(va, fmt); @@ -577,3 +599,34 @@ gb_internal void compiler_error(char const *fmt, ...) { GB_DEBUG_TRAP(); gb_exit(1); } + + + + + +gb_internal int error_value_cmp(void const *a, void const *b) { + ErrorValue *x = cast(ErrorValue *)a; + ErrorValue *y = cast(ErrorValue *)b; + return token_pos_cmp(x->pos, y->pos); +} + +gb_internal void print_all_errors(void) { + GB_ASSERT(any_errors()); + gbFile *f = gb_file_get_standard(gbFileStandard_Error); + + array_sort(global_error_collector.error_values, error_value_cmp); + + for_array(i, global_error_collector.error_values) { + ErrorValue ev = global_error_collector.error_values[i]; + for_array(j, ev.msgs) { + String msg = ev.msgs[j]; + gb_file_write(f, msg.text, msg.len); + if (terse_errors()) { + if (string_contains_char(msg, '\n')) { + break; + } + } + } + } + +} \ No newline at end of file -- cgit v1.2.3 From 17cc7a2c5ee8cbce8aeeff0e0952f54b681e14c7 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 19 Mar 2024 15:42:59 +0000 Subject: General clean-up for error.cpp --- src/error.cpp | 53 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 22 deletions(-) (limited to 'src/error.cpp') diff --git a/src/error.cpp b/src/error.cpp index e5803e5a2..d116781fb 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -14,10 +14,9 @@ struct ErrorCollector { std::atomic count; std::atomic warning_count; std::atomic in_block; - BlockingMutex mutex; - BlockingMutex error_out_mutex; - BlockingMutex string_mutex; - RecursiveMutex block_mutex; + + RecursiveMutex mutex; + BlockingMutex path_mutex; Array error_values; ErrorValue curr_error_value; @@ -81,7 +80,7 @@ gb_internal char *token_pos_to_string(TokenPos const &pos); gb_internal bool set_file_path_string(i32 index, String const &path) { bool ok = false; GB_ASSERT(index >= 0); - mutex_lock(&global_error_collector.string_mutex); + mutex_lock(&global_error_collector.path_mutex); if (index >= global_file_path_strings.count) { array_resize(&global_file_path_strings, index+1); @@ -92,14 +91,14 @@ gb_internal bool set_file_path_string(i32 index, String const &path) { ok = true; } - mutex_unlock(&global_error_collector.string_mutex); + mutex_unlock(&global_error_collector.path_mutex); return ok; } gb_internal bool thread_safe_set_ast_file_from_id(i32 index, AstFile *file) { bool ok = false; GB_ASSERT(index >= 0); - mutex_lock(&global_error_collector.string_mutex); + mutex_lock(&global_error_collector.path_mutex); if (index >= global_files.count) { array_resize(&global_files, index+1); @@ -110,33 +109,33 @@ gb_internal bool thread_safe_set_ast_file_from_id(i32 index, AstFile *file) { ok = true; } - mutex_unlock(&global_error_collector.string_mutex); + mutex_unlock(&global_error_collector.path_mutex); return ok; } gb_internal String get_file_path_string(i32 index) { GB_ASSERT(index >= 0); - mutex_lock(&global_error_collector.string_mutex); + mutex_lock(&global_error_collector.path_mutex); String path = {}; if (index < global_file_path_strings.count) { path = global_file_path_strings[index]; } - mutex_unlock(&global_error_collector.string_mutex); + mutex_unlock(&global_error_collector.path_mutex); return path; } gb_internal AstFile *thread_safe_get_ast_file_from_id(i32 index) { GB_ASSERT(index >= 0); - mutex_lock(&global_error_collector.string_mutex); + mutex_lock(&global_error_collector.path_mutex); AstFile *file = nullptr; if (index < global_files.count) { file = global_files[index]; } - mutex_unlock(&global_error_collector.string_mutex); + mutex_unlock(&global_error_collector.path_mutex); return file; } @@ -169,23 +168,26 @@ gb_internal ERROR_OUT_PROC(default_error_out_va) { isize len = gb_snprintf_va(buf, gb_size_of(buf), fmt, va); isize n = len-1; - String msg = {(u8 *)buf, n}; + String msg = {}; + if (n) { + msg = copy_string(permanent_allocator(), {(u8 *)buf, n}); + } ErrorValue *ev = get_error_value(); - array_add(&ev->msgs, copy_string(permanent_allocator(), msg)); + array_add(&ev->msgs, msg); } gb_global ErrorOutProc *error_out_va = default_error_out_va; gb_internal void begin_error_block(void) { - mutex_lock(&global_error_collector.block_mutex); + mutex_lock(&global_error_collector.mutex); global_error_collector.in_block.store(true); } gb_internal void end_error_block(void) { pop_error_value(); global_error_collector.in_block.store(false); - mutex_unlock(&global_error_collector.block_mutex); + mutex_unlock(&global_error_collector.mutex); } #define ERROR_BLOCK() begin_error_block(); defer (end_error_block()) @@ -328,6 +330,9 @@ gb_internal bool show_error_on_line(TokenPos const &pos, TokenPos end) { return false; } +gb_internal void error_out_empty(void) { + error_out(""); +} gb_internal void error_out_pos(TokenPos pos) { terminal_set_colours(TerminalStyle_Bold, TerminalColour_White); error_out("%s ", token_pos_to_string(pos)); @@ -353,6 +358,7 @@ gb_internal void error_va(TokenPos const &pos, TokenPos end, char const *fmt, va push_error_value(pos, ErrorValue_Error); // NOTE(bill): Duplicate error, skip it if (pos.line == 0) { + error_out_empty(); error_out_coloured("Error: ", TerminalStyle_Normal, TerminalColour_Red); error_out_va(fmt, va); error_out("\n"); @@ -385,6 +391,7 @@ gb_internal void warning_va(TokenPos const &pos, TokenPos end, char const *fmt, if (!global_ignore_warnings()) { // NOTE(bill): Duplicate error, skip it if (pos.line == 0) { + error_out_empty(); error_out_coloured("Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); error_out_va(fmt, va); error_out("\n"); @@ -418,6 +425,7 @@ gb_internal void error_no_newline_va(TokenPos const &pos, char const *fmt, va_li // NOTE(bill): Duplicate error, skip it if (pos.line == 0) { + error_out_empty(); error_out_coloured("Error: ", TerminalStyle_Normal, TerminalColour_Red); error_out_va(fmt, va); } else if (global_error_collector.prev != pos) { @@ -453,6 +461,7 @@ gb_internal void syntax_error_va(TokenPos const &pos, TokenPos end, char const * error_out("\n"); // show_error_on_line(pos, end); } else if (pos.line == 0) { + error_out_empty(); error_out_coloured("Syntax Error: ", TerminalStyle_Normal, TerminalColour_Red); error_out_va(fmt, va); error_out("\n"); @@ -474,6 +483,7 @@ gb_internal void syntax_error_with_verbose_va(TokenPos const &pos, TokenPos end, // NOTE(bill): Duplicate error, skip it if (pos.line == 0) { + error_out_empty(); error_out_coloured("Syntax_Error: ", TerminalStyle_Normal, TerminalColour_Red); error_out_va(fmt, va); error_out("\n"); @@ -514,6 +524,7 @@ gb_internal void syntax_warning_va(TokenPos const &pos, TokenPos end, char const error_out("\n"); // show_error_on_line(pos, end); } else if (pos.line == 0) { + error_out_empty(); error_out_coloured("Syntax Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); error_out_va(fmt, va); error_out("\n"); @@ -618,15 +629,13 @@ gb_internal void print_all_errors(void) { for_array(i, global_error_collector.error_values) { ErrorValue ev = global_error_collector.error_values[i]; - for_array(j, ev.msgs) { + for (isize j = 0; j < ev.msgs.count; j++) { String msg = ev.msgs[j]; gb_file_write(f, msg.text, msg.len); - if (terse_errors()) { - if (string_contains_char(msg, '\n')) { - break; - } + + if (terse_errors() && string_contains_char(msg, '\n')) { + break; } } } - } \ No newline at end of file -- cgit v1.2.3 From ba428fecdb309846b9a6dc8a6a3d738f2034f2ff Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 19 Mar 2024 16:25:09 +0000 Subject: Add `-json-errors` --- src/build_settings.cpp | 8 +++- src/error.cpp | 107 +++++++++++++++++++++++++++++++++++++++++++------ src/main.cpp | 10 +++++ 3 files changed, 110 insertions(+), 15 deletions(-) (limited to 'src/error.cpp') diff --git a/src/build_settings.cpp b/src/build_settings.cpp index c4073f329..58b5c9170 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -392,6 +392,7 @@ struct BuildContext { bool warnings_as_errors; bool hide_error_line; bool terse_errors; + bool json_errors; bool has_ansi_terminal_colours; bool ignore_lazy; @@ -1270,14 +1271,17 @@ gb_internal String get_fullpath_core_collection(gbAllocator a, String path, bool } gb_internal bool show_error_line(void) { - return !build_context.hide_error_line; + return !build_context.hide_error_line && !build_context.json_errors; } gb_internal bool terse_errors(void) { return build_context.terse_errors; } +gb_internal bool json_errors(void) { + return build_context.json_errors; +} gb_internal bool has_ansi_terminal_colours(void) { - return build_context.has_ansi_terminal_colours; + return build_context.has_ansi_terminal_colours && !json_errors(); } gb_internal bool has_asm_extension(String const &path) { diff --git a/src/error.cpp b/src/error.cpp index d116781fb..509470602 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -6,6 +6,7 @@ enum ErrorValueKind : u32 { struct ErrorValue { ErrorValueKind kind; TokenPos pos; + TokenPos end; Array msgs; }; @@ -146,6 +147,7 @@ gb_internal bool global_warnings_as_errors(void); gb_internal bool global_ignore_warnings(void); gb_internal bool show_error_line(void); gb_internal bool terse_errors(void); +gb_internal bool json_errors(void); gb_internal bool has_ansi_terminal_colours(void); gb_internal gbString get_file_line_as_string(TokenPos const &pos, i32 *offset); @@ -168,13 +170,11 @@ gb_internal ERROR_OUT_PROC(default_error_out_va) { isize len = gb_snprintf_va(buf, gb_size_of(buf), fmt, va); isize n = len-1; - String msg = {}; - if (n) { - msg = copy_string(permanent_allocator(), {(u8 *)buf, n}); + if (n > 0) { + String msg = copy_string(permanent_allocator(), {(u8 *)buf, n}); + ErrorValue *ev = get_error_value(); + array_add(&ev->msgs, msg); } - - ErrorValue *ev = get_error_value(); - array_add(&ev->msgs, msg); } gb_global ErrorOutProc *error_out_va = default_error_out_va; @@ -246,6 +246,7 @@ gb_internal void terminal_reset_colours(void) { gb_internal bool show_error_on_line(TokenPos const &pos, TokenPos end) { + get_error_value()->end = end; if (!show_error_line()) { return false; } @@ -622,19 +623,99 @@ gb_internal int error_value_cmp(void const *a, void const *b) { } gb_internal void print_all_errors(void) { + auto const &escape_char = [](gbFile *f, u8 c) { + switch (c) { + case '\n': gb_file_write(f, "\\n", 2); break; + case '"': gb_file_write(f, "\\\"", 2); break; + case '\\': gb_file_write(f, "\\\\", 2); break; + case '\b': gb_file_write(f, "\\b", 2); break; + case '\f': gb_file_write(f, "\\f", 2); break; + case '\r': gb_file_write(f, "\\r", 2); break; + case '\t': gb_file_write(f, "\\t", 2); break; + default: + if ('\x00' <= c && c <= '\x1f') { + gb_fprintf(f, "\\u%04x", c); + } else { + gb_file_write(f, &c, 1); + } + break; + } + }; + GB_ASSERT(any_errors()); gbFile *f = gb_file_get_standard(gbFileStandard_Error); array_sort(global_error_collector.error_values, error_value_cmp); - for_array(i, global_error_collector.error_values) { - ErrorValue ev = global_error_collector.error_values[i]; - for (isize j = 0; j < ev.msgs.count; j++) { - String msg = ev.msgs[j]; - gb_file_write(f, msg.text, msg.len); - if (terse_errors() && string_contains_char(msg, '\n')) { - break; + if (json_errors()) { + gb_fprintf(f, "{\n"); + gb_fprintf(f, "\t\"error_count\": %td,\n", global_error_collector.error_values.count); + gb_fprintf(f, "\t\"errors\": [\n"); + for_array(i, global_error_collector.error_values) { + ErrorValue ev = global_error_collector.error_values[i]; + + gb_fprintf(f, "\t\t{\n"); + + gb_fprintf(f, "\t\t\t\"pos\": {\n"); + + if (ev.pos.file_id) { + gb_fprintf(f, "\t\t\t\t\"file\": \""); + String file = get_file_path_string(ev.pos.file_id); + for (isize k = 0; k < file.len; k++) { + escape_char(f, file.text[k]); + } + gb_fprintf(f, "\",\n"); + gb_fprintf(f, "\t\t\t\t\"line\": %d,\n", ev.pos.line); + gb_fprintf(f, "\t\t\t\t\"column\": %d,\n", ev.pos.column); + i32 end_column = gb_max(ev.end.column, ev.pos.column); + gb_fprintf(f, "\t\t\t\t\"end_column\": %d\n", end_column); + gb_fprintf(f, "\t\t\t},\n"); + } + + gb_fprintf(f, "\t\t\t\"msgs\": [\n"); + + if (ev.msgs.count > 1) { + gb_fprintf(f, "\t\t\t\t\""); + + for (isize j = 1; j < ev.msgs.count; j++) { + String msg = ev.msgs[j]; + for (isize k = 0; k < msg.len; k++) { + u8 c = msg.text[k]; + if (c == '\n') { + if (k+1 == msg.len && j+1 == ev.msgs.count) { + // don't do the last one + } else { + gb_fprintf(f, "\",\n"); + gb_fprintf(f, "\t\t\t\t\""); + } + } else { + escape_char(f, c); + } + } + } + gb_fprintf(f, "\"\n"); + } + gb_fprintf(f, "\t\t\t]\n"); + gb_fprintf(f, "\t\t}"); + if (i+1 != global_error_collector.error_values.count) { + gb_fprintf(f, ","); + } + gb_fprintf(f, "\n"); + } + + gb_fprintf(f, "\t]\n"); + gb_fprintf(f, "}\n"); + } else { + for_array(i, global_error_collector.error_values) { + ErrorValue ev = global_error_collector.error_values[i]; + for (isize j = 0; j < ev.msgs.count; j++) { + String msg = ev.msgs[j]; + gb_file_write(f, msg.text, msg.len); + + if (terse_errors() && string_contains_char(msg, '\n')) { + break; + } } } } diff --git a/src/main.cpp b/src/main.cpp index 0f28e137f..672a9318e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -292,6 +292,7 @@ enum BuildFlagKind { BuildFlag_WarningsAsErrors, BuildFlag_TerseErrors, BuildFlag_VerboseErrors, + BuildFlag_JsonErrors, BuildFlag_ErrorPosStyle, BuildFlag_MaxErrorCount, @@ -480,6 +481,7 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_WarningsAsErrors, str_lit("warnings-as-errors"), BuildFlagParam_None, Command_all); add_flag(&build_flags, BuildFlag_TerseErrors, str_lit("terse-errors"), BuildFlagParam_None, Command_all); add_flag(&build_flags, BuildFlag_VerboseErrors, str_lit("verbose-errors"), BuildFlagParam_None, Command_all); + add_flag(&build_flags, BuildFlag_JsonErrors, str_lit("json-errors"), BuildFlagParam_None, Command_all); add_flag(&build_flags, BuildFlag_ErrorPosStyle, str_lit("error-pos-style"), BuildFlagParam_String, Command_all); add_flag(&build_flags, BuildFlag_MaxErrorCount, str_lit("max-error-count"), BuildFlagParam_Integer, Command_all); @@ -1184,6 +1186,10 @@ gb_internal bool parse_build_flags(Array args) { build_context.terse_errors = false; break; + case BuildFlag_JsonErrors: + build_context.json_errors = true; + break; + case BuildFlag_ErrorPosStyle: GB_ASSERT(value.kind == ExactValue_String); @@ -1984,6 +1990,10 @@ gb_internal void print_show_help(String const arg0, String const &command) { print_usage_line(2, "Prints a terse error message without showing the code on that line and the location in that line."); print_usage_line(0, ""); + print_usage_line(1, "-json-errors"); + print_usage_line(2, "Prints the error messages as json to stderr."); + print_usage_line(0, ""); + print_usage_line(1, "-error-pos-style:"); print_usage_line(2, "Available options:"); print_usage_line(3, "-error-pos-style:unix file/path:45:3:"); -- cgit v1.2.3 From 433109ff52d2db76069273cd53b7aebf6aea9be0 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 19 Mar 2024 16:29:45 +0000 Subject: Replace `gb_exit(1)` with `exit_with_errors()` where appropriate --- src/checker.cpp | 4 ++-- src/docs_writer.cpp | 2 +- src/error.cpp | 4 ++++ src/llvm_backend.cpp | 14 +++++++------- src/main.cpp | 2 +- src/parser.cpp | 4 ++-- 6 files changed, 17 insertions(+), 13 deletions(-) (limited to 'src/error.cpp') diff --git a/src/checker.cpp b/src/checker.cpp index 836f803fc..0efe61fba 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1204,7 +1204,7 @@ gb_internal void init_universal(void) { } if (defined_values_double_declaration) { - gb_exit(1); + exit_with_errors(); } @@ -4504,7 +4504,7 @@ gb_internal void add_import_dependency_node(Checker *c, Ast *decl, PtrMapscope != nullptr); diff --git a/src/docs_writer.cpp b/src/docs_writer.cpp index 26d8027a9..824445ed5 100644 --- a/src/docs_writer.cpp +++ b/src/docs_writer.cpp @@ -1170,7 +1170,7 @@ gb_internal void odin_doc_write_to_file(OdinDocWriter *w, char const *filename) gbFileError err = gb_file_open_mode(&f, gbFileMode_Write, filename); if (err != gbFileError_None) { gb_printf_err("Failed to write .odin-doc to: %s\n", filename); - gb_exit(1); + exit_with_errors(); return; } defer (gb_file_close(&f)); diff --git a/src/error.cpp b/src/error.cpp index 509470602..8d550e969 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -613,6 +613,10 @@ gb_internal void compiler_error(char const *fmt, ...) { } +gb_internal void exit_with_errors(void) { + print_all_errors(); + gb_exit(1); +} diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index b8ee7e7fa..cc9b3ac5d 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -1350,7 +1350,7 @@ gb_internal WORKER_TASK_PROC(lb_llvm_emit_worker_proc) { if (LLVMTargetMachineEmitToFile(wd->target_machine, wd->m->mod, cast(char *)wd->filepath_obj.text, wd->code_gen_file_type, &llvm_error)) { gb_printf_err("LLVM Error: %s\n", llvm_error); - gb_exit(1); + exit_with_errors(); } debugf("Generated File: %.*s\n", LIT(wd->filepath_obj)); return 0; @@ -1919,7 +1919,7 @@ verify gb_printf_err("LLVM Error: %s\n", llvm_error); } } - gb_exit(1); + exit_with_errors(); return 1; } #endif @@ -2104,11 +2104,11 @@ gb_internal WORKER_TASK_PROC(lb_llvm_module_verification_worker_proc) { String filepath_ll = lb_filepath_ll_for_module(m); if (LLVMPrintModuleToFile(m->mod, cast(char const *)filepath_ll.text, &llvm_error)) { gb_printf_err("LLVM Error: %s\n", llvm_error); - gb_exit(1); + exit_with_errors(); return false; } } - gb_exit(1); + exit_with_errors(); return 1; } return 0; @@ -2193,7 +2193,7 @@ gb_internal bool lb_llvm_object_generation(lbGenerator *gen, bool do_threading) if (LLVMTargetMachineEmitToFile(m->target_machine, m->mod, cast(char *)filepath_obj.text, code_gen_file_type, &llvm_error)) { gb_printf_err("LLVM Error: %s\n", llvm_error); - gb_exit(1); + exit_with_errors(); return false; } debugf("Generated File: %.*s\n", LIT(filepath_obj)); @@ -2393,7 +2393,7 @@ gb_internal void lb_generate_procedure(lbModule *m, lbProcedure *p) { gb_printf_err("LLVM Error: %s\n", llvm_error); } LLVMVerifyFunction(p->value, LLVMPrintMessageAction); - gb_exit(1); + exit_with_errors(); } } @@ -2962,7 +2962,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { String filepath_ll = lb_filepath_ll_for_module(m); if (LLVMPrintModuleToFile(m->mod, cast(char const *)filepath_ll.text, &llvm_error)) { gb_printf_err("LLVM Error: %s\n", llvm_error); - gb_exit(1); + exit_with_errors(); return false; } array_add(&gen->output_temp_paths, filepath_ll); diff --git a/src/main.cpp b/src/main.cpp index 672a9318e..ab721a143 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1404,7 +1404,7 @@ gb_internal void timings_export_all(Timings *t, Checker *c, bool timings_are_fin gbFileError err = gb_file_open_mode(&f, gbFileMode_Write, fileName); if (err != gbFileError_None) { gb_printf_err("Failed to export timings to: %s\n", fileName); - gb_exit(1); + exit_with_errors(); return; } else { gb_printf("\nExporting timings to '%s'... ", fileName); diff --git a/src/parser.cpp b/src/parser.cpp index 14035d6d7..1aa40ccbf 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1484,7 +1484,7 @@ gb_internal Token expect_token(AstFile *f, TokenKind kind) { String p = token_to_string(prev); syntax_error(f->curr_token, "Expected '%.*s', got '%.*s'", LIT(c), LIT(p)); if (prev.kind == Token_EOF) { - gb_exit(1); + exit_with_errors(); } } @@ -6177,7 +6177,7 @@ gb_internal ParseFileError process_imported_file(Parser *p, ImportedFile importe if (err == ParseFile_EmptyFile) { if (fi.fullpath == p->init_fullpath) { syntax_error(pos, "Initial file is empty - %.*s\n", LIT(p->init_fullpath)); - gb_exit(1); + exit_with_errors(); } } else { switch (err) { -- cgit v1.2.3 From e4c502e79b4fba9924aac4ff029889a494d3f1ae Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 20 Mar 2024 11:06:02 +0000 Subject: Add `offset` (in bytes) field to `-json-errors` --- src/error.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src/error.cpp') diff --git a/src/error.cpp b/src/error.cpp index 8d550e969..b96719420 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -670,6 +670,7 @@ gb_internal void print_all_errors(void) { escape_char(f, file.text[k]); } gb_fprintf(f, "\",\n"); + gb_fprintf(f, "\t\t\t\t\"offset\": %d,\n", ev.pos.offset); gb_fprintf(f, "\t\t\t\t\"line\": %d,\n", ev.pos.line); gb_fprintf(f, "\t\t\t\t\"column\": %d,\n", ev.pos.column); i32 end_column = gb_max(ev.end.column, ev.pos.column); -- cgit v1.2.3 From 1951bc45a68ec930d5e10620f9e4e9c84cf74ded Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 20 Mar 2024 13:55:47 +0000 Subject: Fix #3133 by show the line of the syntax error --- src/error.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/error.cpp') diff --git a/src/error.cpp b/src/error.cpp index b96719420..2458061c2 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -460,7 +460,7 @@ gb_internal void syntax_error_va(TokenPos const &pos, TokenPos end, char const * error_out_coloured("Syntax Error: ", TerminalStyle_Normal, TerminalColour_Red); error_out_va(fmt, va); error_out("\n"); - // show_error_on_line(pos, end); + show_error_on_line(pos, end); } else if (pos.line == 0) { error_out_empty(); error_out_coloured("Syntax Error: ", TerminalStyle_Normal, TerminalColour_Red); -- cgit v1.2.3 From cab53e12b7309a28e174e07540c7b0b76780f34d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 24 Mar 2024 13:53:09 +0000 Subject: Add assert message to tell me people to report the bug --- src/error.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/error.cpp') diff --git a/src/error.cpp b/src/error.cpp index 2458061c2..e16909839 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -28,7 +28,7 @@ gb_global ErrorCollector global_error_collector; gb_internal void push_error_value(TokenPos const &pos, ErrorValueKind kind = ErrorValue_Error) { - GB_ASSERT(global_error_collector.curr_error_value_set.load() == false); + GB_ASSERT_MSG(global_error_collector.curr_error_value_set.load() == false, "Possible race condition in error handling system, please report this with an issue"); ErrorValue ev = {kind, pos}; ev.msgs.allocator = heap_allocator(); @@ -53,7 +53,7 @@ gb_internal void try_pop_error_value(void) { } gb_internal ErrorValue *get_error_value(void) { - GB_ASSERT(global_error_collector.curr_error_value_set.load() == true); + GB_ASSERT_MSG(global_error_collector.curr_error_value_set.load() == true, "Possible race condition in error handling system, please report this with an issue"); return &global_error_collector.curr_error_value; } -- cgit v1.2.3 From 53b02c5e6fa4d5b500e1bd6cfae9399d6adaf798 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 25 Mar 2024 14:46:45 +0000 Subject: Fix printing errors issue --- src/error.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'src/error.cpp') diff --git a/src/error.cpp b/src/error.cpp index e16909839..b18df79b7 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -600,7 +600,9 @@ gb_internal void syntax_error_with_verbose(TokenPos pos, TokenPos end, char cons gb_internal void compiler_error(char const *fmt, ...) { - print_all_errors(); + if (any_errors()) { + print_all_errors(); + } va_list va; @@ -614,7 +616,9 @@ gb_internal void compiler_error(char const *fmt, ...) { gb_internal void exit_with_errors(void) { - print_all_errors(); + if (any_errors()) { + print_all_errors(); + } gb_exit(1); } -- cgit v1.2.3 From 692a47f080f3de24ed05eeed90994112a4d9e0e8 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 2 Apr 2024 23:36:36 +0100 Subject: Fix printing of warnings --- src/error.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/error.cpp') diff --git a/src/error.cpp b/src/error.cpp index b18df79b7..5b12a7ab8 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -12,7 +12,7 @@ struct ErrorValue { struct ErrorCollector { TokenPos prev; - std::atomic count; + std::atomic count; // error+warning_count std::atomic warning_count; std::atomic in_block; @@ -384,6 +384,7 @@ gb_internal void warning_va(TokenPos const &pos, TokenPos end, char const *fmt, error_va(pos, end, fmt, va); return; } + global_error_collector.count.fetch_add(1); global_error_collector.warning_count.fetch_add(1); mutex_lock(&global_error_collector.mutex); -- cgit v1.2.3 From a9bfb3ac2e3a0f1e4c98596685e983aaf1e1f651 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 2 Apr 2024 23:39:14 +0100 Subject: Clarity warning and error printing --- src/error.cpp | 13 +++++++------ src/main.cpp | 7 +++++++ 2 files changed, 14 insertions(+), 6 deletions(-) (limited to 'src/error.cpp') diff --git a/src/error.cpp b/src/error.cpp index 5b12a7ab8..c92392dce 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -12,7 +12,7 @@ struct ErrorValue { struct ErrorCollector { TokenPos prev; - std::atomic count; // error+warning_count + std::atomic count; std::atomic warning_count; std::atomic in_block; @@ -62,7 +62,9 @@ gb_internal ErrorValue *get_error_value(void) { gb_internal bool any_errors(void) { return global_error_collector.count.load() != 0; } - +gb_internal bool any_warnings(void) { + return global_error_collector.warning_count.load() != 0; +} gb_internal void init_global_error_collector(void) { @@ -384,7 +386,6 @@ gb_internal void warning_va(TokenPos const &pos, TokenPos end, char const *fmt, error_va(pos, end, fmt, va); return; } - global_error_collector.count.fetch_add(1); global_error_collector.warning_count.fetch_add(1); mutex_lock(&global_error_collector.mutex); @@ -601,7 +602,7 @@ gb_internal void syntax_error_with_verbose(TokenPos pos, TokenPos end, char cons gb_internal void compiler_error(char const *fmt, ...) { - if (any_errors()) { + if (any_errors() || any_warnings()) { print_all_errors(); } @@ -617,7 +618,7 @@ gb_internal void compiler_error(char const *fmt, ...) { gb_internal void exit_with_errors(void) { - if (any_errors()) { + if (any_errors() || any_warnings()) { print_all_errors(); } gb_exit(1); @@ -651,7 +652,7 @@ gb_internal void print_all_errors(void) { } }; - GB_ASSERT(any_errors()); + GB_ASSERT(any_errors() || any_warnings()); gbFile *f = gb_file_get_standard(gbFileStandard_Error); array_sort(global_error_collector.error_values, error_value_cmp); diff --git a/src/main.cpp b/src/main.cpp index 79c3a1670..b8c21fd3b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2694,6 +2694,9 @@ int main(int arg_count, char const **arg_ptr) { print_all_errors(); return 1; } + if (any_warnings()) { + print_all_errors(); + } MAIN_TIME_SECTION("type check"); @@ -2706,6 +2709,10 @@ int main(int arg_count, char const **arg_ptr) { print_all_errors(); return 1; } + if (any_warnings()) { + print_all_errors(); + } + if (build_context.command_kind == Command_strip_semicolon) { return strip_semicolons(parser); -- cgit v1.2.3 From 16dc79fc5c98228471ed57eb0f8e853de739f6d9 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 8 Apr 2024 13:36:23 +0100 Subject: Add `"type"` field to `-json-errors` --- src/error.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'src/error.cpp') diff --git a/src/error.cpp b/src/error.cpp index c92392dce..eb167d4c3 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -667,6 +667,14 @@ gb_internal void print_all_errors(void) { gb_fprintf(f, "\t\t{\n"); + gb_fprintf(f, "\t\t\t\"type\": \""); + if (ev.kind == ErrorValue_Warning) { + gb_fprintf(f, "warning"); + } else { + gb_fprintf(f, "error"); + } + gb_fprintf(f, "\",\n"); + gb_fprintf(f, "\t\t\t\"pos\": {\n"); if (ev.pos.file_id) { -- cgit v1.2.3 From 46b9bd8c0e3987080f94ae42921b513a79708ef9 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 12 Apr 2024 13:35:14 +0100 Subject: Improve error messages for `switch` and `for` r-values with a suggestion --- src/check_stmt.cpp | 43 +++++++++++++++++++++++++++++++++++++++++++ src/error.cpp | 21 ++++++++++++++++----- src/tokenizer.cpp | 1 + 3 files changed, 60 insertions(+), 5 deletions(-) (limited to 'src/error.cpp') diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index a6def5997..f2b7f8661 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -474,16 +474,59 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O } Entity *e = entity_of_node(lhs->expr); + Entity *original_e = e; + + Ast *name = unparen_expr(lhs->expr); + while (name->kind == Ast_SelectorExpr) { + name = name->SelectorExpr.expr; + e = entity_of_node(name); + } + if (e == nullptr) { + e = original_e; + } gbString str = expr_to_string(lhs->expr); if (e != nullptr && e->flags & EntityFlag_Param) { + ERROR_BLOCK(); if (e->flags & EntityFlag_Using) { error(lhs->expr, "Cannot assign to '%s' which is from a 'using' procedure parameter", str); } else { error(lhs->expr, "Cannot assign to '%s' which is a procedure parameter", str); } + error_line("\tSuggestion: Did you mean to pass '%.*s' by pointer?\n", LIT(e->token.string)); + show_error_on_line(e->token.pos, token_pos_end(e->token)); } else { + ERROR_BLOCK(); error(lhs->expr, "Cannot assign to '%s'", str); + + if (e) if (e->flags & EntityFlag_ForValue) { + isize offset = show_error_on_line(e->token.pos, token_pos_end(e->token), "Suggestion:"); + if (offset < 0) { + if (is_type_map(e->type)) { + error_line("\tSuggestion: Did you mean? 'for key, &%.*s in ...'\n", LIT(e->token.string)); + } else { + error_line("\tSuggestion: Did you mean? 'for &%.*s in ...'\n", LIT(e->token.string)); + } + } else { + error_line("\t"); + for (isize i = 0; i < offset-1; i++) { + error_line(" "); + } + error_line("'%.*s' is immutable, declare it as '&%.*s' to make it mutable\n", LIT(e->token.string), LIT(e->token.string)); + } + + } else if (e->flags & EntityFlag_SwitchValue) { + isize offset = show_error_on_line(e->token.pos, token_pos_end(e->token), "Suggestion:"); + if (offset < 0) { + error_line("\tSuggestion: Did you mean? 'switch &%.*s in ...'\n", LIT(e->token.string)); + } else { + error_line("\t"); + for (isize i = 0; i < offset-1; i++) { + error_line(" "); + } + error_line("'%.*s' is immutable, declare it as '&%.*s' to make it mutable\n", LIT(e->token.string), LIT(e->token.string)); + } + } } gb_string_free(str); diff --git a/src/error.cpp b/src/error.cpp index eb167d4c3..8647f60b9 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -84,6 +84,7 @@ gb_internal bool set_file_path_string(i32 index, String const &path) { bool ok = false; GB_ASSERT(index >= 0); mutex_lock(&global_error_collector.path_mutex); + mutex_lock(&global_files_mutex); if (index >= global_file_path_strings.count) { array_resize(&global_file_path_strings, index+1); @@ -94,6 +95,7 @@ gb_internal bool set_file_path_string(i32 index, String const &path) { ok = true; } + mutex_unlock(&global_files_mutex); mutex_unlock(&global_error_collector.path_mutex); return ok; } @@ -102,6 +104,7 @@ gb_internal bool thread_safe_set_ast_file_from_id(i32 index, AstFile *file) { bool ok = false; GB_ASSERT(index >= 0); mutex_lock(&global_error_collector.path_mutex); + mutex_lock(&global_files_mutex); if (index >= global_files.count) { array_resize(&global_files, index+1); @@ -111,7 +114,7 @@ gb_internal bool thread_safe_set_ast_file_from_id(i32 index, AstFile *file) { global_files[index] = file; ok = true; } - + mutex_unlock(&global_files_mutex); mutex_unlock(&global_error_collector.path_mutex); return ok; } @@ -119,12 +122,14 @@ gb_internal bool thread_safe_set_ast_file_from_id(i32 index, AstFile *file) { gb_internal String get_file_path_string(i32 index) { GB_ASSERT(index >= 0); mutex_lock(&global_error_collector.path_mutex); + mutex_lock(&global_files_mutex); String path = {}; if (index < global_file_path_strings.count) { path = global_file_path_strings[index]; } + mutex_unlock(&global_files_mutex); mutex_unlock(&global_error_collector.path_mutex); return path; } @@ -132,12 +137,14 @@ gb_internal String get_file_path_string(i32 index) { gb_internal AstFile *thread_safe_get_ast_file_from_id(i32 index) { GB_ASSERT(index >= 0); mutex_lock(&global_error_collector.path_mutex); + mutex_lock(&global_files_mutex); AstFile *file = nullptr; if (index < global_files.count) { file = global_files[index]; } + mutex_unlock(&global_files_mutex); mutex_unlock(&global_error_collector.path_mutex); return file; } @@ -247,10 +254,10 @@ gb_internal void terminal_reset_colours(void) { } -gb_internal bool show_error_on_line(TokenPos const &pos, TokenPos end) { +gb_internal isize show_error_on_line(TokenPos const &pos, TokenPos end, char const *prefix=nullptr) { get_error_value()->end = end; if (!show_error_line()) { - return false; + return -1; } i32 offset = 0; @@ -270,6 +277,10 @@ gb_internal bool show_error_on_line(TokenPos const &pos, TokenPos end) { MAX_LINE_LENGTH_PADDED = MAX_LINE_LENGTH-MAX_TAB_WIDTH-ELLIPSIS_PADDING, }; + if (prefix) { + error_out("\t%s\n\n", prefix); + } + error_out("\t"); terminal_set_colours(TerminalStyle_Bold, TerminalColour_White); @@ -328,9 +339,9 @@ gb_internal bool show_error_on_line(TokenPos const &pos, TokenPos end) { terminal_reset_colours(); error_out("\n"); - return true; + return offset; } - return false; + return -1; } gb_internal void error_out_empty(void) { diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp index 3d5348074..fdff9224a 100644 --- a/src/tokenizer.cpp +++ b/src/tokenizer.cpp @@ -193,6 +193,7 @@ gb_internal void init_keyword_hash_table(void) { gb_global Array global_file_path_strings; // index is file id gb_global Array global_files; // index is file id +gb_global BlockingMutex global_files_mutex; gb_internal String get_file_path_string(i32 index); gb_internal struct AstFile *thread_safe_get_ast_file_from_id(i32 index); -- cgit v1.2.3 From 2e29687ceeb004bfa820dd8c475786d2fed78a6d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Apr 2024 10:28:14 +0100 Subject: Fix #3425 --- src/error.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'src/error.cpp') diff --git a/src/error.cpp b/src/error.cpp index 8647f60b9..2e6641e3b 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -292,10 +292,11 @@ gb_internal isize show_error_on_line(TokenPos const &pos, TokenPos end, char con if (line_len > MAX_LINE_LENGTH_PADDED) { i32 left = MAX_TAB_WIDTH; - if (offset > 0) { - line_text += offset-left; - line_len -= offset-left; - offset = left+MAX_TAB_WIDTH/2; + i32 diff = gb_max(offset-left, 0); + if (diff > 0) { + line_text += diff; + line_len -= diff; + offset = left + ELLIPSIS_PADDING/2; } if (line_len > MAX_LINE_LENGTH_PADDED) { line_len = MAX_LINE_LENGTH_PADDED; @@ -304,7 +305,7 @@ gb_internal isize show_error_on_line(TokenPos const &pos, TokenPos end, char con squiggle_extra = 1; } } - if (offset > 0) { + if (diff > 0) { error_out("... %.*s ...", cast(i32)line_len, line_text); } else { error_out("%.*s ...", cast(i32)line_len, line_text); -- cgit v1.2.3 From 8a0f9ae108a75d9ca86b8a91fca2f2423e0a58df Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 16 Apr 2024 13:15:23 +0100 Subject: Print to string buffer before printing errors --- src/error.cpp | 146 +++++++++++++++++++++++++++++++++------------------------ src/string.cpp | 37 +++++++++++++++ 2 files changed, 121 insertions(+), 62 deletions(-) (limited to 'src/error.cpp') diff --git a/src/error.cpp b/src/error.cpp index 2e6641e3b..8c9fb265b 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -7,7 +7,8 @@ struct ErrorValue { ErrorValueKind kind; TokenPos pos; TokenPos end; - Array msgs; + Array msg; + bool seen_newline; }; struct ErrorCollector { @@ -30,19 +31,21 @@ gb_global ErrorCollector global_error_collector; gb_internal void push_error_value(TokenPos const &pos, ErrorValueKind kind = ErrorValue_Error) { GB_ASSERT_MSG(global_error_collector.curr_error_value_set.load() == false, "Possible race condition in error handling system, please report this with an issue"); ErrorValue ev = {kind, pos}; - ev.msgs.allocator = heap_allocator(); + ev.msg.allocator = heap_allocator(); global_error_collector.curr_error_value = ev; global_error_collector.curr_error_value_set.store(true); } gb_internal void pop_error_value(void) { + mutex_lock(&global_error_collector.mutex); if (global_error_collector.curr_error_value_set.load()) { array_add(&global_error_collector.error_values, global_error_collector.curr_error_value); global_error_collector.curr_error_value = {}; global_error_collector.curr_error_value_set.store(false); } + mutex_unlock(&global_error_collector.mutex); } @@ -180,9 +183,18 @@ gb_internal ERROR_OUT_PROC(default_error_out_va) { isize n = len-1; if (n > 0) { - String msg = copy_string(permanent_allocator(), {(u8 *)buf, n}); ErrorValue *ev = get_error_value(); - array_add(&ev->msgs, msg); + if (terse_errors()) { + for (isize i = 0; i < n && !ev->seen_newline; i++) { + u8 c = cast(u8)buf[i]; + if (c == '\n') { + ev->seen_newline = true; + } + array_add(&ev->msg, c); + } + } else { + array_add_elems(&ev->msg, (u8 *)buf, n); + } } } @@ -645,109 +657,119 @@ gb_internal int error_value_cmp(void const *a, void const *b) { } gb_internal void print_all_errors(void) { - auto const &escape_char = [](gbFile *f, u8 c) { + auto const &escape_char = [](gbString res, u8 c) -> gbString { switch (c) { - case '\n': gb_file_write(f, "\\n", 2); break; - case '"': gb_file_write(f, "\\\"", 2); break; - case '\\': gb_file_write(f, "\\\\", 2); break; - case '\b': gb_file_write(f, "\\b", 2); break; - case '\f': gb_file_write(f, "\\f", 2); break; - case '\r': gb_file_write(f, "\\r", 2); break; - case '\t': gb_file_write(f, "\\t", 2); break; + case '\n': res = gb_string_append_length(res, "\\n", 2); break; + case '"': res = gb_string_append_length(res, "\\\"", 2); break; + case '\\': res = gb_string_append_length(res, "\\\\", 2); break; + case '\b': res = gb_string_append_length(res, "\\b", 2); break; + case '\f': res = gb_string_append_length(res, "\\f", 2); break; + case '\r': res = gb_string_append_length(res, "\\r", 2); break; + case '\t': res = gb_string_append_length(res, "\\t", 2); break; default: if ('\x00' <= c && c <= '\x1f') { - gb_fprintf(f, "\\u%04x", c); + res = gb_string_append_fmt(res, "\\u%04x", c); } else { - gb_file_write(f, &c, 1); + res = gb_string_append_length(res, &c, 1); } break; } + return res; }; GB_ASSERT(any_errors() || any_warnings()); - gbFile *f = gb_file_get_standard(gbFileStandard_Error); + array_sort(global_error_collector.error_values, error_value_cmp); + gbString res = gb_string_make(heap_allocator(), ""); + defer (gb_string_free(res)); if (json_errors()) { - gb_fprintf(f, "{\n"); - gb_fprintf(f, "\t\"error_count\": %td,\n", global_error_collector.error_values.count); - gb_fprintf(f, "\t\"errors\": [\n"); + res = gb_string_append_fmt(res, "{\n"); + res = gb_string_append_fmt(res, "\t\"error_count\": %td,\n", global_error_collector.error_values.count); + res = gb_string_append_fmt(res, "\t\"errors\": [\n"); for_array(i, global_error_collector.error_values) { ErrorValue ev = global_error_collector.error_values[i]; - gb_fprintf(f, "\t\t{\n"); + res = gb_string_append_fmt(res, "\t\t{\n"); - gb_fprintf(f, "\t\t\t\"type\": \""); + res = gb_string_append_fmt(res, "\t\t\t\"type\": \""); if (ev.kind == ErrorValue_Warning) { - gb_fprintf(f, "warning"); + res = gb_string_append_fmt(res, "warning"); } else { - gb_fprintf(f, "error"); + res = gb_string_append_fmt(res, "error"); } - gb_fprintf(f, "\",\n"); + res = gb_string_append_fmt(res, "\",\n"); - gb_fprintf(f, "\t\t\t\"pos\": {\n"); + res = gb_string_append_fmt(res, "\t\t\t\"pos\": {\n"); if (ev.pos.file_id) { - gb_fprintf(f, "\t\t\t\t\"file\": \""); + res = gb_string_append_fmt(res, "\t\t\t\t\"file\": \""); String file = get_file_path_string(ev.pos.file_id); for (isize k = 0; k < file.len; k++) { - escape_char(f, file.text[k]); + res = escape_char(res, file.text[k]); } - gb_fprintf(f, "\",\n"); - gb_fprintf(f, "\t\t\t\t\"offset\": %d,\n", ev.pos.offset); - gb_fprintf(f, "\t\t\t\t\"line\": %d,\n", ev.pos.line); - gb_fprintf(f, "\t\t\t\t\"column\": %d,\n", ev.pos.column); + res = gb_string_append_fmt(res, "\",\n"); + res = gb_string_append_fmt(res, "\t\t\t\t\"offset\": %d,\n", ev.pos.offset); + res = gb_string_append_fmt(res, "\t\t\t\t\"line\": %d,\n", ev.pos.line); + res = gb_string_append_fmt(res, "\t\t\t\t\"column\": %d,\n", ev.pos.column); i32 end_column = gb_max(ev.end.column, ev.pos.column); - gb_fprintf(f, "\t\t\t\t\"end_column\": %d\n", end_column); - gb_fprintf(f, "\t\t\t},\n"); + res = gb_string_append_fmt(res, "\t\t\t\t\"end_column\": %d\n", end_column); + res = gb_string_append_fmt(res, "\t\t\t},\n"); } - gb_fprintf(f, "\t\t\t\"msgs\": [\n"); - - if (ev.msgs.count > 1) { - gb_fprintf(f, "\t\t\t\t\""); - - for (isize j = 1; j < ev.msgs.count; j++) { - String msg = ev.msgs[j]; - for (isize k = 0; k < msg.len; k++) { - u8 c = msg.text[k]; - if (c == '\n') { - if (k+1 == msg.len && j+1 == ev.msgs.count) { - // don't do the last one - } else { - gb_fprintf(f, "\",\n"); - gb_fprintf(f, "\t\t\t\t\""); - } - } else { - escape_char(f, c); - } + res = gb_string_append_fmt(res, "\t\t\t\"msgs\": [\n"); + + auto lines = split_lines_from_array(ev.msg, heap_allocator()); + defer (array_free(&lines)); + + if (lines.count > 0) { + res = gb_string_append_fmt(res, "\t\t\t\t\""); + + for (isize j = 0; j < lines.count; j++) { + String line = lines[j]; + for (isize k = 0; k < line.len; k++) { + u8 c = line.text[k]; + res = escape_char(res, c); + } + if (j+1 < lines.count) { + res = gb_string_append_fmt(res, "\",\n"); + res = gb_string_append_fmt(res, "\t\t\t\t\""); } } - gb_fprintf(f, "\"\n"); + res = gb_string_append_fmt(res, "\"\n"); } - gb_fprintf(f, "\t\t\t]\n"); - gb_fprintf(f, "\t\t}"); + res = gb_string_append_fmt(res, "\t\t\t]\n"); + res = gb_string_append_fmt(res, "\t\t}"); if (i+1 != global_error_collector.error_values.count) { - gb_fprintf(f, ","); + res = gb_string_append_fmt(res, ","); } - gb_fprintf(f, "\n"); + res = gb_string_append_fmt(res, "\n"); } - gb_fprintf(f, "\t]\n"); - gb_fprintf(f, "}\n"); + res = gb_string_append_fmt(res, "\t]\n"); + res = gb_string_append_fmt(res, "}\n"); } else { for_array(i, global_error_collector.error_values) { ErrorValue ev = global_error_collector.error_values[i]; - for (isize j = 0; j < ev.msgs.count; j++) { - String msg = ev.msgs[j]; - gb_file_write(f, msg.text, msg.len); - if (terse_errors() && string_contains_char(msg, '\n')) { + String_Iterator it = {{ev.msg.data, ev.msg.count}, 0}; + + for (isize line_idx = 0; /**/; line_idx++) { + String line = string_split_iterator(&it, '\n'); + if (line.len == 0) { + break; + } + line = string_trim_trailing_whitespace(line); + res = gb_string_append_length(res, line.text, line.len); + res = gb_string_append_length(res, " \n", 2); + if (line_idx == 0 && terse_errors()) { break; } } } } + gbFile *f = gb_file_get_standard(gbFileStandard_Error); + gb_file_write(f, res, gb_string_length(res)); } \ No newline at end of file diff --git a/src/string.cpp b/src/string.cpp index 4adec7a90..3747f4564 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -276,6 +276,43 @@ gb_internal String string_trim_whitespace(String str) { return str; } +gb_internal String string_trim_trailing_whitespace(String str) { + while (str.len > 0) { + u8 c = str[str.len-1]; + if (rune_is_whitespace(c) || c == 0) { + str.len -= 1; + } else { + break; + } + } + return str; +} + +gb_internal String split_lines_first_line_from_array(Array const &array, gbAllocator allocator) { + String_Iterator it = {{array.data, array.count}, 0}; + + String line = string_split_iterator(&it, '\n'); + line = string_trim_trailing_whitespace(line); + return line; +} + +gb_internal Array split_lines_from_array(Array const &array, gbAllocator allocator) { + Array lines = {}; + lines.allocator = allocator; + + String_Iterator it = {{array.data, array.count}, 0}; + + for (;;) { + String line = string_split_iterator(&it, '\n'); + if (line.len == 0) { + break; + } + line = string_trim_trailing_whitespace(line); + array_add(&lines, line); + } + + return lines; +} gb_internal bool string_contains_char(String const &s, u8 c) { isize i; -- cgit v1.2.3 From 04278cd6548ccb79f01ffbf25141fdf21969d3e6 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 24 Apr 2024 17:13:53 +0100 Subject: Remove line info in message with `-json-errors` --- src/error.cpp | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) (limited to 'src/error.cpp') diff --git a/src/error.cpp b/src/error.cpp index 8c9fb265b..7fb62c966 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -391,7 +391,11 @@ gb_internal void error_va(TokenPos const &pos, TokenPos end, char const *fmt, va error_out("\n"); } else if (global_error_collector.prev != pos) { global_error_collector.prev = pos; - error_out_pos(pos); + if (json_errors()) { + error_out_empty(); + } else { + error_out_pos(pos); + } if (has_ansi_terminal_colours()) { error_out_coloured("Error: ", TerminalStyle_Normal, TerminalColour_Red); } @@ -424,7 +428,11 @@ gb_internal void warning_va(TokenPos const &pos, TokenPos end, char const *fmt, error_out("\n"); } else if (global_error_collector.prev != pos) { global_error_collector.prev = pos; - error_out_pos(pos); + if (json_errors()) { + error_out_empty(); + } else { + error_out_pos(pos); + } error_out_coloured("Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); error_out_va(fmt, va); error_out("\n"); @@ -457,7 +465,11 @@ gb_internal void error_no_newline_va(TokenPos const &pos, char const *fmt, va_li error_out_va(fmt, va); } else if (global_error_collector.prev != pos) { global_error_collector.prev = pos; - error_out_pos(pos); + if (json_errors()) { + error_out_empty(); + } else { + error_out_pos(pos); + } if (has_ansi_terminal_colours()) { error_out_coloured("Error: ", TerminalStyle_Normal, TerminalColour_Red); } @@ -482,7 +494,11 @@ gb_internal void syntax_error_va(TokenPos const &pos, TokenPos end, char const * // NOTE(bill): Duplicate error, skip it if (global_error_collector.prev != pos) { global_error_collector.prev = pos; - error_out_pos(pos); + if (json_errors()) { + error_out_empty(); + } else { + error_out_pos(pos); + } error_out_coloured("Syntax Error: ", TerminalStyle_Normal, TerminalColour_Red); error_out_va(fmt, va); error_out("\n"); @@ -516,7 +532,11 @@ gb_internal void syntax_error_with_verbose_va(TokenPos const &pos, TokenPos end, error_out("\n"); } else if (global_error_collector.prev != pos) { global_error_collector.prev = pos; - error_out_pos(pos); + if (json_errors()) { + error_out_empty(); + } else { + error_out_pos(pos); + } if (has_ansi_terminal_colours()) { error_out_coloured("Syntax_Error: ", TerminalStyle_Normal, TerminalColour_Red); } @@ -545,7 +565,11 @@ gb_internal void syntax_warning_va(TokenPos const &pos, TokenPos end, char const // NOTE(bill): Duplicate error, skip it if (global_error_collector.prev != pos) { global_error_collector.prev = pos; - error_out_pos(pos); + if (json_errors()) { + error_out_empty(); + } else { + error_out_pos(pos); + } error_out_coloured("Syntax Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); error_out_va(fmt, va); error_out("\n"); -- cgit v1.2.3 From ebfbe4d26031bde4e91ba489102d60fe6959173f Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 28 Apr 2024 06:38:32 -0400 Subject: Clear unused `global_error_collector.curr_error` This should cleanly prevent acknowledging duplicate errors on the same position as seems to be the intent based on the prior `else if` condition. --- src/error.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/error.cpp') diff --git a/src/error.cpp b/src/error.cpp index 7fb62c966..bbbb98053 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -403,6 +403,8 @@ gb_internal void error_va(TokenPos const &pos, TokenPos end, char const *fmt, va error_out("\n"); show_error_on_line(pos, end); } else { + global_error_collector.curr_error_value = {}; + global_error_collector.curr_error_value_set.store(false); global_error_collector.count.fetch_sub(1); } try_pop_error_value(); -- cgit v1.2.3 From f1c13d6bd8ad7390ba29d5d6de79596b9b53bf24 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 28 Apr 2024 14:03:11 -0400 Subject: Fix race condition in `error_va` If the error count exceeded `MAX_ERROR_COLLECTOR_COUNT`, multiple threads could print and exit simultaneously, causing a segfault. This change moves the mutex lock back before the conditional. --- src/error.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/error.cpp') diff --git a/src/error.cpp b/src/error.cpp index bbbb98053..1b091f88e 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -376,11 +376,11 @@ gb_internal void error_out_coloured(char const *str, TerminalStyle style, Termin gb_internal void error_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) { global_error_collector.count.fetch_add(1); + mutex_lock(&global_error_collector.mutex); if (global_error_collector.count > MAX_ERROR_COLLECTOR_COUNT()) { print_all_errors(); gb_exit(1); } - mutex_lock(&global_error_collector.mutex); push_error_value(pos, ErrorValue_Error); // NOTE(bill): Duplicate error, skip it -- cgit v1.2.3 From 67b786c7388db140e80653f2d6c4027e272257b2 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Wed, 1 May 2024 16:41:02 -0400 Subject: Fix more race conditions in error reporting --- src/error.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/error.cpp') diff --git a/src/error.cpp b/src/error.cpp index 1b091f88e..1ad757964 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -452,11 +452,11 @@ gb_internal void error_line_va(char const *fmt, va_list va) { gb_internal void error_no_newline_va(TokenPos const &pos, char const *fmt, va_list va) { global_error_collector.count.fetch_add(1); + mutex_lock(&global_error_collector.mutex); if (global_error_collector.count.load() > MAX_ERROR_COLLECTOR_COUNT()) { print_all_errors(); gb_exit(1); } - mutex_lock(&global_error_collector.mutex); push_error_value(pos, ErrorValue_Error); @@ -485,11 +485,11 @@ gb_internal void error_no_newline_va(TokenPos const &pos, char const *fmt, va_li gb_internal void syntax_error_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) { global_error_collector.count.fetch_add(1); + mutex_lock(&global_error_collector.mutex); if (global_error_collector.count > MAX_ERROR_COLLECTOR_COUNT()) { print_all_errors(); gb_exit(1); } - mutex_lock(&global_error_collector.mutex); push_error_value(pos, ErrorValue_Warning); @@ -518,11 +518,11 @@ gb_internal void syntax_error_va(TokenPos const &pos, TokenPos end, char const * gb_internal void syntax_error_with_verbose_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) { global_error_collector.count.fetch_add(1); + mutex_lock(&global_error_collector.mutex); if (global_error_collector.count > MAX_ERROR_COLLECTOR_COUNT()) { print_all_errors(); gb_exit(1); } - mutex_lock(&global_error_collector.mutex); push_error_value(pos, ErrorValue_Warning); -- cgit v1.2.3 From ee818304f360d67f28726f907eed2df38c1c8ea9 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sun, 5 May 2024 00:01:14 +0200 Subject: fix invalid JSON when an error does not have a position --- src/error.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/error.cpp') diff --git a/src/error.cpp b/src/error.cpp index 1b091f88e..14b0ce7b7 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -728,9 +728,8 @@ gb_internal void print_all_errors(void) { } res = gb_string_append_fmt(res, "\",\n"); - res = gb_string_append_fmt(res, "\t\t\t\"pos\": {\n"); - if (ev.pos.file_id) { + res = gb_string_append_fmt(res, "\t\t\t\"pos\": {\n"); res = gb_string_append_fmt(res, "\t\t\t\t\"file\": \""); String file = get_file_path_string(ev.pos.file_id); for (isize k = 0; k < file.len; k++) { @@ -743,6 +742,8 @@ gb_internal void print_all_errors(void) { i32 end_column = gb_max(ev.end.column, ev.pos.column); res = gb_string_append_fmt(res, "\t\t\t\t\"end_column\": %d\n", end_column); res = gb_string_append_fmt(res, "\t\t\t},\n"); + } else { + res = gb_string_append_fmt(res, "\t\t\t\"pos\": null,\n"); } res = gb_string_append_fmt(res, "\t\t\t\"msgs\": [\n"); -- cgit v1.2.3 From b23f1dd5ffd0f1d3e74dc48b7f4b13d441cea3ff Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 7 May 2024 11:19:16 +0100 Subject: Merge neighbouring error messages with the same location --- src/error.cpp | 91 +++++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 57 insertions(+), 34 deletions(-) (limited to 'src/error.cpp') diff --git a/src/error.cpp b/src/error.cpp index 1009bb7cd..08675c4be 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -12,7 +12,7 @@ struct ErrorValue { }; struct ErrorCollector { - TokenPos prev; + // TokenPos prev; // no point collecting because of the mulithreaded nature std::atomic count; std::atomic warning_count; std::atomic in_block; @@ -383,14 +383,13 @@ gb_internal void error_va(TokenPos const &pos, TokenPos end, char const *fmt, va } push_error_value(pos, ErrorValue_Error); - // NOTE(bill): Duplicate error, skip it if (pos.line == 0) { error_out_empty(); error_out_coloured("Error: ", TerminalStyle_Normal, TerminalColour_Red); error_out_va(fmt, va); error_out("\n"); - } else if (global_error_collector.prev != pos) { - global_error_collector.prev = pos; + } else { + // global_error_collector.prev = pos; if (json_errors()) { error_out_empty(); } else { @@ -402,10 +401,6 @@ gb_internal void error_va(TokenPos const &pos, TokenPos end, char const *fmt, va error_out_va(fmt, va); error_out("\n"); show_error_on_line(pos, end); - } else { - global_error_collector.curr_error_value = {}; - global_error_collector.curr_error_value_set.store(false); - global_error_collector.count.fetch_sub(1); } try_pop_error_value(); mutex_unlock(&global_error_collector.mutex); @@ -422,14 +417,13 @@ gb_internal void warning_va(TokenPos const &pos, TokenPos end, char const *fmt, push_error_value(pos, ErrorValue_Warning); if (!global_ignore_warnings()) { - // NOTE(bill): Duplicate error, skip it if (pos.line == 0) { error_out_empty(); error_out_coloured("Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); error_out_va(fmt, va); error_out("\n"); - } else if (global_error_collector.prev != pos) { - global_error_collector.prev = pos; + } else { + // global_error_collector.prev = pos; if (json_errors()) { error_out_empty(); } else { @@ -460,13 +454,12 @@ gb_internal void error_no_newline_va(TokenPos const &pos, char const *fmt, va_li push_error_value(pos, ErrorValue_Error); - // NOTE(bill): Duplicate error, skip it if (pos.line == 0) { error_out_empty(); error_out_coloured("Error: ", TerminalStyle_Normal, TerminalColour_Red); error_out_va(fmt, va); - } else if (global_error_collector.prev != pos) { - global_error_collector.prev = pos; + } else { + // global_error_collector.prev = pos; if (json_errors()) { error_out_empty(); } else { @@ -493,9 +486,13 @@ gb_internal void syntax_error_va(TokenPos const &pos, TokenPos end, char const * push_error_value(pos, ErrorValue_Warning); - // NOTE(bill): Duplicate error, skip it - if (global_error_collector.prev != pos) { - global_error_collector.prev = pos; + if (pos.line == 0) { + error_out_empty(); + error_out_coloured("Syntax Error: ", TerminalStyle_Normal, TerminalColour_Red); + error_out_va(fmt, va); + error_out("\n"); + } else { + // global_error_collector.prev = pos; if (json_errors()) { error_out_empty(); } else { @@ -505,11 +502,6 @@ gb_internal void syntax_error_va(TokenPos const &pos, TokenPos end, char const * error_out_va(fmt, va); error_out("\n"); show_error_on_line(pos, end); - } else if (pos.line == 0) { - error_out_empty(); - error_out_coloured("Syntax Error: ", TerminalStyle_Normal, TerminalColour_Red); - error_out_va(fmt, va); - error_out("\n"); } try_pop_error_value(); @@ -526,14 +518,13 @@ gb_internal void syntax_error_with_verbose_va(TokenPos const &pos, TokenPos end, push_error_value(pos, ErrorValue_Warning); - // NOTE(bill): Duplicate error, skip it if (pos.line == 0) { error_out_empty(); error_out_coloured("Syntax_Error: ", TerminalStyle_Normal, TerminalColour_Red); error_out_va(fmt, va); error_out("\n"); - } else if (global_error_collector.prev != pos) { - global_error_collector.prev = pos; + } else { + // global_error_collector.prev = pos; if (json_errors()) { error_out_empty(); } else { @@ -564,9 +555,13 @@ gb_internal void syntax_warning_va(TokenPos const &pos, TokenPos end, char const push_error_value(pos, ErrorValue_Warning); if (!global_ignore_warnings()) { - // NOTE(bill): Duplicate error, skip it - if (global_error_collector.prev != pos) { - global_error_collector.prev = pos; + if (pos.line == 0) { + error_out_empty(); + error_out_coloured("Syntax Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); + error_out_va(fmt, va); + error_out("\n"); + } else { + // global_error_collector.prev = pos; if (json_errors()) { error_out_empty(); } else { @@ -576,11 +571,6 @@ gb_internal void syntax_warning_va(TokenPos const &pos, TokenPos end, char const error_out_va(fmt, va); error_out("\n"); // show_error_on_line(pos, end); - } else if (pos.line == 0) { - error_out_empty(); - error_out_coloured("Syntax Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); - error_out_va(fmt, va); - error_out("\n"); } } @@ -705,9 +695,41 @@ gb_internal void print_all_errors(void) { GB_ASSERT(any_errors() || any_warnings()); - array_sort(global_error_collector.error_values, error_value_cmp); + + { // NOTE(bill): merge neighbouring errors + isize default_lines_to_skip = 1; + if (show_error_line()) { + // NOTE(bill): this will always be 2 extra lines + default_lines_to_skip += 2; + } + + ErrorValue *prev_ev = nullptr; + for (isize i = 0; i < global_error_collector.error_values.count; /**/) { + ErrorValue &ev = global_error_collector.error_values[i]; + + if (prev_ev && prev_ev->pos == ev.pos) { + String_Iterator it = {{ev.msg.data, ev.msg.count}, 0}; + + for (isize lines_to_skip = default_lines_to_skip; lines_to_skip > 0; lines_to_skip -= 1) { + String line = string_split_iterator(&it, '\n'); + if (line.len == 0) { + break; + } + } + + if (it.str.len-it.pos > 0) { + array_add_elems(&prev_ev->msg, it.str.text+it.pos, it.str.len-it.pos); + } + array_ordered_remove(&global_error_collector.error_values, i); + } else { + prev_ev = &ev; + i += 1; + } + } + } + gbString res = gb_string_make(heap_allocator(), ""); defer (gb_string_free(res)); @@ -715,6 +737,7 @@ gb_internal void print_all_errors(void) { res = gb_string_append_fmt(res, "{\n"); res = gb_string_append_fmt(res, "\t\"error_count\": %td,\n", global_error_collector.error_values.count); res = gb_string_append_fmt(res, "\t\"errors\": [\n"); + for_array(i, global_error_collector.error_values) { ErrorValue ev = global_error_collector.error_values[i]; -- cgit v1.2.3 From 1818df786dbd8595bb58719ce849350b354c60a7 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 7 May 2024 11:21:12 +0100 Subject: Free memory for the error messages just in case --- src/error.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src/error.cpp') diff --git a/src/error.cpp b/src/error.cpp index 08675c4be..1877a672b 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -722,6 +722,7 @@ gb_internal void print_all_errors(void) { if (it.str.len-it.pos > 0) { array_add_elems(&prev_ev->msg, it.str.text+it.pos, it.str.len-it.pos); } + array_free(&ev.msg); array_ordered_remove(&global_error_collector.error_values, i); } else { prev_ev = &ev; -- cgit v1.2.3 From 98827c867dd88b1a72d74f0a6d703f7a25d81d91 Mon Sep 17 00:00:00 2001 From: Laytan Date: Thu, 9 May 2024 19:21:39 +0200 Subject: fix duplicate suggestions and add missing newline --- src/check_expr.cpp | 2 +- src/error.cpp | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) (limited to 'src/error.cpp') diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 98aebfe4e..f0c33d9d8 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -6201,7 +6201,7 @@ gb_internal bool evaluate_where_clauses(CheckerContext *ctx, Ast *call_expr, Sco error(c, "Prefer to separate 'where' clauses with a comma rather than '&&'"); gbString x = expr_to_string(c->BinaryExpr.left); gbString y = expr_to_string(c->BinaryExpr.right); - error_line("\tSuggestion: '%s, %s'", x, y); + error_line("\tSuggestion: '%s, %s'\n", x, y); gb_string_free(y); gb_string_free(x); } diff --git a/src/error.cpp b/src/error.cpp index 1877a672b..688d1b34a 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -719,9 +719,13 @@ gb_internal void print_all_errors(void) { } } - if (it.str.len-it.pos > 0) { - array_add_elems(&prev_ev->msg, it.str.text+it.pos, it.str.len-it.pos); + // Merge additional text (suggestions for example) into the previous error. + String current = {prev_ev->msg.data, prev_ev->msg.count}; + String addition = {it.str.text+it.pos, it.str.len-it.pos}; + if (addition.len > 0 && !string_contains_string(current, addition)) { + array_add_elems(&prev_ev->msg, addition.text, addition.len); } + array_free(&ev.msg); array_ordered_remove(&global_error_collector.error_values, i); } else { -- cgit v1.2.3 From 8808e5584a4a69e401010cbfe8800e0b2a430941 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 13 May 2024 13:26:22 +0100 Subject: If only warnings exist on `print_all_errors`, next time it is called, clear the error list. This is mostly only syntax errors too --- src/error.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'src/error.cpp') diff --git a/src/error.cpp b/src/error.cpp index 688d1b34a..f5123b969 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -672,7 +672,20 @@ gb_internal int error_value_cmp(void const *a, void const *b) { return token_pos_cmp(x->pos, y->pos); } +gb_internal bool errors_already_printed = false; + gb_internal void print_all_errors(void) { + if (errors_already_printed) { + if (global_error_collector.warning_count.load() == global_error_collector.error_values.count) { + for (ErrorValue &ev : global_error_collector.error_values) { + array_free(&ev.msg); + } + array_clear(&global_error_collector.error_values); + errors_already_printed = false; + } + return; + } + auto const &escape_char = [](gbString res, u8 c) -> gbString { switch (c) { case '\n': res = gb_string_append_length(res, "\\n", 2); break; @@ -827,4 +840,6 @@ gb_internal void print_all_errors(void) { } gbFile *f = gb_file_get_standard(gbFileStandard_Error); gb_file_write(f, res, gb_string_length(res)); + + errors_already_printed = true; } \ No newline at end of file -- cgit v1.2.3 From b2dc5cc81254b4ab194dac900ab143609ba07bac Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 16 May 2024 15:32:15 +0100 Subject: Fix error reporting for enforce new switch/for syntax --- src/check_stmt.cpp | 4 ++-- src/error.cpp | 8 ++------ 2 files changed, 4 insertions(+), 8 deletions(-) (limited to 'src/error.cpp') diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index 0267bdf80..23a97696d 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -572,7 +572,7 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O error(lhs->expr, "Cannot assign to '%s'", str); if (e && e->flags & EntityFlag_ForValue) { - isize offset = show_error_on_line(e->token.pos, token_pos_end(e->token), "Suggestion:"); + isize offset = show_error_on_line(e->token.pos, token_pos_end(e->token)); if (offset < 0) { if (is_type_map(e->type)) { error_line("\tSuggestion: Did you mean? 'for key, &%.*s in ...'\n", LIT(e->token.string)); @@ -588,7 +588,7 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O } } else if (e && e->flags & EntityFlag_SwitchValue) { - isize offset = show_error_on_line(e->token.pos, token_pos_end(e->token), "Suggestion:"); + isize offset = show_error_on_line(e->token.pos, token_pos_end(e->token)); if (offset < 0) { error_line("\tSuggestion: Did you mean? 'switch &%.*s in ...'\n", LIT(e->token.string)); } else { diff --git a/src/error.cpp b/src/error.cpp index f5123b969..da444e998 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -266,7 +266,7 @@ gb_internal void terminal_reset_colours(void) { } -gb_internal isize show_error_on_line(TokenPos const &pos, TokenPos end, char const *prefix=nullptr) { +gb_internal isize show_error_on_line(TokenPos const &pos, TokenPos end) { get_error_value()->end = end; if (!show_error_line()) { return -1; @@ -289,17 +289,13 @@ gb_internal isize show_error_on_line(TokenPos const &pos, TokenPos end, char con MAX_LINE_LENGTH_PADDED = MAX_LINE_LENGTH-MAX_TAB_WIDTH-ELLIPSIS_PADDING, }; - if (prefix) { - error_out("\t%s\n\n", prefix); - } + i32 error_length = gb_max(end.offset - pos.offset, 1); error_out("\t"); terminal_set_colours(TerminalStyle_Bold, TerminalColour_White); - i32 error_length = gb_max(end.offset - pos.offset, 1); - isize squiggle_extra = 0; if (line_len > MAX_LINE_LENGTH_PADDED) { -- cgit v1.2.3 From 155516b897450ec265cfb53b6e32db90f15544f5 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 6 Jun 2024 13:02:08 +0100 Subject: Fix `-ignore-warnings` --- src/error.cpp | 63 +++++++++++++++++++++++++++++++---------------------------- 1 file changed, 33 insertions(+), 30 deletions(-) (limited to 'src/error.cpp') diff --git a/src/error.cpp b/src/error.cpp index da444e998..2556a8de4 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -407,29 +407,31 @@ gb_internal void warning_va(TokenPos const &pos, TokenPos end, char const *fmt, error_va(pos, end, fmt, va); return; } + if (global_ignore_warnings()) { + return; + } + global_error_collector.warning_count.fetch_add(1); mutex_lock(&global_error_collector.mutex); push_error_value(pos, ErrorValue_Warning); - if (!global_ignore_warnings()) { - if (pos.line == 0) { + if (pos.line == 0) { + error_out_empty(); + error_out_coloured("Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); + error_out_va(fmt, va); + error_out("\n"); + } else { + // global_error_collector.prev = pos; + if (json_errors()) { error_out_empty(); - error_out_coloured("Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); - error_out_va(fmt, va); - error_out("\n"); } else { - // global_error_collector.prev = pos; - if (json_errors()) { - error_out_empty(); - } else { - error_out_pos(pos); - } - error_out_coloured("Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); - error_out_va(fmt, va); - error_out("\n"); - show_error_on_line(pos, end); + error_out_pos(pos); } + error_out_coloured("Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); + error_out_va(fmt, va); + error_out("\n"); + show_error_on_line(pos, end); } try_pop_error_value(); mutex_unlock(&global_error_collector.mutex); @@ -544,30 +546,31 @@ gb_internal void syntax_warning_va(TokenPos const &pos, TokenPos end, char const syntax_error_va(pos, end, fmt, va); return; } + if (global_ignore_warnings()) { + return; + } mutex_lock(&global_error_collector.mutex); global_error_collector.warning_count++; push_error_value(pos, ErrorValue_Warning); - if (!global_ignore_warnings()) { - if (pos.line == 0) { + if (pos.line == 0) { + error_out_empty(); + error_out_coloured("Syntax Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); + error_out_va(fmt, va); + error_out("\n"); + } else { + // global_error_collector.prev = pos; + if (json_errors()) { error_out_empty(); - error_out_coloured("Syntax Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); - error_out_va(fmt, va); - error_out("\n"); } else { - // global_error_collector.prev = pos; - if (json_errors()) { - error_out_empty(); - } else { - error_out_pos(pos); - } - error_out_coloured("Syntax Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); - error_out_va(fmt, va); - error_out("\n"); - // show_error_on_line(pos, end); + error_out_pos(pos); } + error_out_coloured("Syntax Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); + error_out_va(fmt, va); + error_out("\n"); + // show_error_on_line(pos, end); } try_pop_error_value(); -- cgit v1.2.3 From e627fcb0e66421d52526d844c01065ba0e043c5e Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 7 Jun 2024 16:41:26 +0200 Subject: fix not printing `Error:` when terminal has no color support --- src/error.cpp | 6 ++---- tests/issues/run.sh | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) (limited to 'src/error.cpp') diff --git a/src/error.cpp b/src/error.cpp index 2556a8de4..eed69b779 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -390,8 +390,6 @@ gb_internal void error_va(TokenPos const &pos, TokenPos end, char const *fmt, va error_out_empty(); } else { error_out_pos(pos); - } - if (has_ansi_terminal_colours()) { error_out_coloured("Error: ", TerminalStyle_Normal, TerminalColour_Red); } error_out_va(fmt, va); @@ -427,8 +425,8 @@ gb_internal void warning_va(TokenPos const &pos, TokenPos end, char const *fmt, error_out_empty(); } else { error_out_pos(pos); + error_out_coloured("Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); } - error_out_coloured("Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); error_out_va(fmt, va); error_out("\n"); show_error_on_line(pos, end); @@ -841,4 +839,4 @@ gb_internal void print_all_errors(void) { gb_file_write(f, res, gb_string_length(res)); errors_already_printed = true; -} \ No newline at end of file +} diff --git a/tests/issues/run.sh b/tests/issues/run.sh index 20259ac63..8b4c1e7f2 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -16,7 +16,7 @@ $ODIN test ../test_issue_2466.odin $COMMON $ODIN test ../test_issue_2615.odin $COMMON $ODIN test ../test_issue_2637.odin $COMMON $ODIN test ../test_issue_2666.odin $COMMON -if [[ $($ODIN build ../test_issue_2395.odin $COMMON 2>&1 >/dev/null | grep -c "must have at least 2 variants") -eq 2 ]] ; then +if [[ $($ODIN build ../test_issue_2395.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 2 ]] ; then echo "SUCCESSFUL 1/1" else echo "SUCCESSFUL 0/1" -- cgit v1.2.3 From 8702bf00d5f5b22142e88258c288cfec2423089c Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 9 Jun 2024 22:47:22 -0400 Subject: Remove `_` in `Syntax_Error` verbose message --- src/error.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/error.cpp') diff --git a/src/error.cpp b/src/error.cpp index eed69b779..03d96219b 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -516,7 +516,7 @@ gb_internal void syntax_error_with_verbose_va(TokenPos const &pos, TokenPos end, if (pos.line == 0) { error_out_empty(); - error_out_coloured("Syntax_Error: ", TerminalStyle_Normal, TerminalColour_Red); + error_out_coloured("Syntax Error: ", TerminalStyle_Normal, TerminalColour_Red); error_out_va(fmt, va); error_out("\n"); } else { @@ -527,7 +527,7 @@ gb_internal void syntax_error_with_verbose_va(TokenPos const &pos, TokenPos end, error_out_pos(pos); } if (has_ansi_terminal_colours()) { - error_out_coloured("Syntax_Error: ", TerminalStyle_Normal, TerminalColour_Red); + error_out_coloured("Syntax Error: ", TerminalStyle_Normal, TerminalColour_Red); } error_out_va(fmt, va); error_out("\n"); -- cgit v1.2.3 From 657c51636080e9b3c9c2215d05b08f3cccf68e70 Mon Sep 17 00:00:00 2001 From: Thomas Voss Date: Wed, 12 Jun 2024 20:58:28 +0200 Subject: Pad ‘^~~~^’-style diagnostic ranges properly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/error.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'src/error.cpp') diff --git a/src/error.cpp b/src/error.cpp index 03d96219b..5a4cb6a0c 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -323,8 +323,13 @@ gb_internal isize show_error_on_line(TokenPos const &pos, TokenPos end) { } error_out("\n\t"); - for (i32 i = 0; i < offset; i++) { - error_out(" "); + for (i32 rune_width, off = 0; off < offset; off += rune_width) { + i32 rune; + rune_width = cast(i32)utf8proc_iterate((u8 const *)line_text + off, line_len - off, &rune); + int w = utf8proc_charwidth(rune); + if (w > 0) { + error_out("%.*s", w, " "); + } } terminal_set_colours(TerminalStyle_Bold, TerminalColour_Green); -- cgit v1.2.3 From 9f190f3937628595ecd8f6c784b85d7be6e0254a Mon Sep 17 00:00:00 2001 From: Thomas Voss Date: Thu, 13 Jun 2024 16:42:04 +0200 Subject: Generate ranges of the correct length --- src/error.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) (limited to 'src/error.cpp') diff --git a/src/error.cpp b/src/error.cpp index 5a4cb6a0c..cdf3b806a 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -336,15 +336,27 @@ gb_internal isize show_error_on_line(TokenPos const &pos, TokenPos end) { error_out("^"); if (end.file_id == pos.file_id) { + i32 rune; + if (end.line > pos.line) { - for (i32 i = offset; i < line_len; i++) { - error_out("~"); + for (i32 rune, rune_width, off = offset; off < line_len; off += rune_width) { + rune_width = cast(i32)utf8proc_iterate((u8 const *)line_text + off, line_len - off, &rune); + int w = utf8proc_charwidth(rune); + if (w > 0) { + error_out("%.*s", w, "~~~~"); + } } } else if (end.line == pos.line && end.column > pos.column) { - for (i32 i = 1; i < error_length-1+squiggle_extra; i++) { + i32 columns = squiggle_extra; + for (i32 rune, rune_width, off = offset; off < offset + error_length - 1; off += rune_width) { + rune_width = cast(i32)utf8proc_iterate((u8 const *)line_text + off, line_len - off, &rune); + columns += utf8proc_charwidth(rune); + } + + for (i32 i = 0; i < columns - 1; i++) { error_out("~"); } - if (error_length > 1 && squiggle_extra == 0) { + if (columns > 0 && squiggle_extra == 0) { error_out("^"); } } -- cgit v1.2.3 From ca9d1f940d4118f0652d11cbcd50dc4209d54720 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 13 Jun 2024 17:23:30 +0200 Subject: Just change squiggle_extra type to i32. --- src/error.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/error.cpp') diff --git a/src/error.cpp b/src/error.cpp index cdf3b806a..a63dec43a 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -296,7 +296,7 @@ gb_internal isize show_error_on_line(TokenPos const &pos, TokenPos end) { terminal_set_colours(TerminalStyle_Bold, TerminalColour_White); - isize squiggle_extra = 0; + i32 squiggle_extra = 0; if (line_len > MAX_LINE_LENGTH_PADDED) { i32 left = MAX_TAB_WIDTH; -- cgit v1.2.3 From 9f7ac1469fc364330ada811032d295ba36f6e078 Mon Sep 17 00:00:00 2001 From: Thomas Voss Date: Thu, 13 Jun 2024 16:42:04 +0200 Subject: Generate ranges of the correct length --- src/error.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) (limited to 'src/error.cpp') diff --git a/src/error.cpp b/src/error.cpp index 5a4cb6a0c..1e24325c5 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -336,15 +336,27 @@ gb_internal isize show_error_on_line(TokenPos const &pos, TokenPos end) { error_out("^"); if (end.file_id == pos.file_id) { + i32 rune; + if (end.line > pos.line) { - for (i32 i = offset; i < line_len; i++) { - error_out("~"); + for (i32 rune, rune_width, off = offset; off < line_len; off += rune_width) { + rune_width = cast(i32)utf8proc_iterate((u8 const *)line_text + off, line_len - off, &rune); + int w = utf8proc_charwidth(rune); + if (w > 0) { + error_out("%.*s", w, "~~~~"); + } } } else if (end.line == pos.line && end.column > pos.column) { - for (i32 i = 1; i < error_length-1+squiggle_extra; i++) { + isize columns = squiggle_extra; + for (i32 rune, rune_width, off = offset; off < offset + error_length - 1; off += rune_width) { + rune_width = cast(i32)utf8proc_iterate((u8 const *)line_text + off, line_len - off, &rune); + columns += utf8proc_charwidth(rune); + } + + for (isize i = 1; i < columns; i++) { error_out("~"); } - if (error_length > 1 && squiggle_extra == 0) { + if (columns > 0 && squiggle_extra == 0) { error_out("^"); } } -- cgit v1.2.3 From ff4787070d9673a417f549f1b9452e675c96f992 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 14 Jun 2024 00:06:55 +0200 Subject: Revert "Merge pull request #3744 from Mango0x45/master" This reverts commit 45044de0b756f9ab018979abd5350533334a54ac, reversing changes made to 20c17ba6f971cf572da4fa5e9601e0df8d517112. --- src/error.cpp | 30 +++++++----------------------- 1 file changed, 7 insertions(+), 23 deletions(-) (limited to 'src/error.cpp') diff --git a/src/error.cpp b/src/error.cpp index d0089c952..03d96219b 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -296,7 +296,7 @@ gb_internal isize show_error_on_line(TokenPos const &pos, TokenPos end) { terminal_set_colours(TerminalStyle_Bold, TerminalColour_White); - i32 squiggle_extra = 0; + isize squiggle_extra = 0; if (line_len > MAX_LINE_LENGTH_PADDED) { i32 left = MAX_TAB_WIDTH; @@ -323,39 +323,23 @@ gb_internal isize show_error_on_line(TokenPos const &pos, TokenPos end) { } error_out("\n\t"); - for (i32 rune_width, off = 0; off < offset; off += rune_width) { - i32 rune; - rune_width = cast(i32)utf8proc_iterate((u8 const *)line_text + off, line_len - off, &rune); - int w = utf8proc_charwidth(rune); - if (w > 0) { - error_out("%.*s", w, " "); - } + for (i32 i = 0; i < offset; i++) { + error_out(" "); } terminal_set_colours(TerminalStyle_Bold, TerminalColour_Green); error_out("^"); if (end.file_id == pos.file_id) { - i32 rune; - if (end.line > pos.line) { - for (i32 rune, rune_width, off = offset; off < line_len; off += rune_width) { - rune_width = cast(i32)utf8proc_iterate((u8 const *)line_text + off, line_len - off, &rune); - int w = utf8proc_charwidth(rune); - if (w > 0) { - error_out("%.*s", w, "~~~~"); - } + for (i32 i = offset; i < line_len; i++) { + error_out("~"); } } else if (end.line == pos.line && end.column > pos.column) { - i32 columns = squiggle_extra; - for (i32 rune, rune_width, off = offset; off < offset + error_length - 1; off += rune_width) { - rune_width = cast(i32)utf8proc_iterate((u8 const *)line_text + off, line_len - off, &rune); - columns += utf8proc_charwidth(rune); - } - for (i32 i = 1; i < columns; i++) { + for (i32 i = 1; i < error_length-1+squiggle_extra; i++) { error_out("~"); } - if (columns > 0 && squiggle_extra == 0) { + if (error_length > 1 && squiggle_extra == 0) { error_out("^"); } } -- cgit v1.2.3 From d4e2fa037797c026a1b7803f1514a87792af8888 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Fri, 14 Jun 2024 16:50:28 -0400 Subject: Refactor `show_error_on_line` This should adequately solve any issues with Unicode alignment by sidestepping the issue entirely. With this change, we make use of the built-in ANSI facilities of the terminal to underline the text. If the terminal does not support underlining, there are still the fallback bold markers at the start and end of error locations. --- src/check_stmt.cpp | 18 +--- src/error.cpp | 296 ++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 243 insertions(+), 71 deletions(-) (limited to 'src/error.cpp') diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index f2e3b0242..c369ba098 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -582,28 +582,20 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O isize offset = show_error_on_line(e->token.pos, token_pos_end(e->token)); if (offset < 0) { if (is_type_map(e->type)) { - error_line("\tSuggestion: Did you mean? 'for key, &%.*s in ...'\n", LIT(e->token.string)); + error_line("\t\tSuggestion: Did you mean? 'for key, &%.*s in ...'\n", LIT(e->token.string)); } else { - error_line("\tSuggestion: Did you mean? 'for &%.*s in ...'\n", LIT(e->token.string)); + error_line("\t\tSuggestion: Did you mean? 'for &%.*s in ...'\n", LIT(e->token.string)); } } else { - error_line("\t"); - for (isize i = 0; i < offset-1; i++) { - error_line(" "); - } - error_line("'%.*s' is immutable, declare it as '&%.*s' to make it mutable\n", LIT(e->token.string), LIT(e->token.string)); + error_line("\t\t'%.*s' is immutable, declare it as '&%.*s' to make it mutable\n", LIT(e->token.string), LIT(e->token.string)); } } else if (e && e->flags & EntityFlag_SwitchValue) { isize offset = show_error_on_line(e->token.pos, token_pos_end(e->token)); if (offset < 0) { - error_line("\tSuggestion: Did you mean? 'switch &%.*s in ...'\n", LIT(e->token.string)); + error_line("\t\tSuggestion: Did you mean? 'switch &%.*s in ...'\n", LIT(e->token.string)); } else { - error_line("\t"); - for (isize i = 0; i < offset-1; i++) { - error_line(" "); - } - error_line("'%.*s' is immutable, declare it as '&%.*s' to make it mutable\n", LIT(e->token.string), LIT(e->token.string)); + error_line("\t\t'%.*s' is immutable, declare it as '&%.*s' to make it mutable\n", LIT(e->token.string), LIT(e->token.string)); } } } diff --git a/src/error.cpp b/src/error.cpp index 03d96219b..26b4106a0 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -237,6 +237,7 @@ enum TerminalColour { TerminalColour_Blue, TerminalColour_Purple, TerminalColour_Black, + TerminalColour_Grey, }; gb_internal void terminal_set_colours(TerminalStyle style, TerminalColour foreground) { @@ -256,6 +257,7 @@ gb_internal void terminal_set_colours(TerminalStyle style, TerminalColour foregr case TerminalColour_Blue: error_out("\x1b[%s;34m", ss); break; case TerminalColour_Purple: error_out("\x1b[%s;35m", ss); break; case TerminalColour_Black: error_out("\x1b[%s;30m", ss); break; + case TerminalColour_Grey: error_out("\x1b[%s;90m", ss); break; } } } @@ -272,85 +274,263 @@ gb_internal isize show_error_on_line(TokenPos const &pos, TokenPos end) { return -1; } - i32 offset = 0; - gbString the_line = get_file_line_as_string(pos, &offset); + i32 error_start_index_bytes = 0; + gbString the_line = get_file_line_as_string(pos, &error_start_index_bytes); defer (gb_string_free(the_line)); - if (the_line != nullptr) { - char const *line_text = the_line; - isize line_len = gb_string_length(the_line); + if (the_line == nullptr || gb_string_length(the_line) == 0) { + terminal_set_colours(TerminalStyle_Normal, TerminalColour_Grey); + error_out("\t( empty line )\n"); + terminal_reset_colours(); - // TODO(bill): This assumes ASCII + // Preserve the old return behaviour. Even if we can't guarantee the + // exact visual space offset, there are two places that check this to + // change what sort of suggestion they offer. + if (the_line == nullptr) { + return -1; + } else { + return cast(isize)error_start_index_bytes; + } + } - enum { - MAX_LINE_LENGTH = 80, - MAX_TAB_WIDTH = 8, - ELLIPSIS_PADDING = 8, // `... ...` - MAX_LINE_LENGTH_PADDED = MAX_LINE_LENGTH-MAX_TAB_WIDTH-ELLIPSIS_PADDING, - }; + // Specfically use basic ASCII arrows here, in case the terminal + // doesn't support anything fancy. This is meant to be a good fallback. + char const *mark_error_sign = "><"; + char const *open_error_sign = ">>"; + char const *close_error_sign = "<<"; + const TerminalColour marker_colour = TerminalColour_Yellow; - i32 error_length = gb_max(end.offset - pos.offset, 1); + // ANSI SGR: + // 0 = Reset. + // 58:5:2 = Underline colour, 8-bit, green. (non-standard) + // 4:3 = Wiggly underline. (non-standard) + char const *wiggly_underline_sgr = ""; + char const *disable_underline_sgr = ""; + if (has_ansi_terminal_colours()) { + wiggly_underline_sgr = "\x1b[0;58:5:2;4:3m"; + disable_underline_sgr = "\x1b[24m"; + } + + // These two will be used like an Odin slice later. + char const *line_text = the_line; + i32 line_length_bytes = cast(i32)gb_string_length(the_line); + + // NOTE(Feoramund): The numbers below are in Unicode codepoints + // (or runes), not visual glyph width. Calculating the visual width of + // a cluster of Unicode codepoints is vexing, and `utf8proc_charwidth` + // is inadequate. + // + // We're counting codepoints here so we don't slice one down the + // middle during truncation. It will still look strange if we slice + // a cluster down the middle. (i.e. a letter and a combining diacritic) + // + // Luckily, if our assumption about 1 codepoint == 1 glyph is wrong, + // we only suffer a shorter or longer line displayed in total, but all + // of our highlighting and marking will be precise. + // (Unless there's an invalid Unicode codepoint, in which case, no guarantees.) + // + // The line will be longer if a codepoint occupies more than one space + // (CJK in most cases) and shorter if a codepoint is invisible or is + // a type of joiner or combining codepoint. + // + // If we get a complete Unicode glyph counter, it would be as simple as + // replacing `utf8_decode` below to make all of this work perfectly. + + enum { + MAX_LINE_LENGTH = 80, + MAX_TAB_WIDTH = 8, + ELLIPSIS_PADDING = 8, // `... ...` + MAX_MARK_WIDTH = 4, // `><` or `>>` and `<<` + MIN_LEFT_VIEW = 8, + + // A rough estimate of how many characters we'll insert, at most: + MAX_INSERTED_WIDTH = MAX_TAB_WIDTH + ELLIPSIS_PADDING + MAX_MARK_WIDTH, + + MAX_LINE_LENGTH_PADDED = MAX_LINE_LENGTH - MAX_INSERTED_WIDTH, + }; - error_out("\t"); + // For the purposes of truncating long lines, we calculate how many + // runes the line is composed of, first. We'll take note of at which + // rune index the error starts, too. + i32 error_start_index_runes = 0; - terminal_set_colours(TerminalStyle_Bold, TerminalColour_White); + i32 line_length_runes = 0; + for (i32 i = 0; i < line_length_bytes; /**/) { + Rune rune; + if (i == error_start_index_bytes) { + error_start_index_runes = line_length_runes; + } - isize squiggle_extra = 0; + i32 bytes_read = cast(i32)utf8_decode(cast(const u8 *)line_text + i, line_length_bytes - i, &rune); + if (rune == GB_RUNE_INVALID || bytes_read <= 0) { + // Bail out; we won't even try to truncate the line later. + line_length_runes = 0; + break; + } - if (line_len > MAX_LINE_LENGTH_PADDED) { - i32 left = MAX_TAB_WIDTH; - i32 diff = gb_max(offset-left, 0); - if (diff > 0) { - line_text += diff; - line_len -= diff; - offset = left + ELLIPSIS_PADDING/2; - } - if (line_len > MAX_LINE_LENGTH_PADDED) { - line_len = MAX_LINE_LENGTH_PADDED; - if (error_length > line_len-left) { - error_length = cast(i32)line_len - left; - squiggle_extra = 1; - } - } - if (diff > 0) { - error_out("... %.*s ...", cast(i32)line_len, line_text); - } else { - error_out("%.*s ...", cast(i32)line_len, line_text); - } + line_length_runes += 1; + i += bytes_read; + } + + if (error_start_index_runes == 0 && error_start_index_bytes != 0 && line_length_runes != 0) { + // The error index in runes was not found, but we did find a valid Unicode string. + // + // This is an edge case where the error is sitting on a newline or the + // end of the line, as that is the only location we could not have checked. + error_start_index_runes = line_length_runes; + } + + error_out("\t"); + + bool show_right_ellipsis = false; + + if (line_length_runes > MAX_LINE_LENGTH_PADDED) { + // Now that we know the line is over the length limit, we have to + // compose a runic window in which to display the error. + i32 window_width = MAX_LINE_LENGTH_PADDED; + + i32 extend_right = 0; + i32 extend_left = 0; + if (error_start_index_runes + window_width > line_length_runes - 1) { + // Trade space from the right to the left. + extend_right = line_length_runes - error_start_index_runes; + extend_left = window_width - extend_right; + } else if (MIN_LEFT_VIEW - error_start_index_runes > 0) { + // Trade space from the left to the right. + extend_left = error_start_index_runes; + extend_right = window_width - extend_left; } else { - error_out("%.*s", cast(i32)line_len, line_text); + // Square in the middle somewhere. + extend_left = MIN_LEFT_VIEW; + extend_right = window_width - extend_left; } - error_out("\n\t"); - for (i32 i = 0; i < offset; i++) { - error_out(" "); + i32 window_right_runes = gb_min(error_start_index_runes + extend_right, line_length_runes); + i32 window_left_runes = gb_max(0, error_start_index_runes - extend_left); + + i32 window_right_bytes = 0; + i32 window_left_bytes = 0; + + i32 i_runes = 0; + for (i32 i = 0; i < line_length_bytes; /**/) { + if (i_runes == window_left_runes ) { window_left_bytes = i; } + if (i_runes == window_right_runes) { window_right_bytes = i; } + + // No need for error-checking. + // + // We've already validated the string at this point, otherwise + // `line_length_runes` would be 0, and we would not have + // entered this block. + i32 bytes_read = cast(i32)utf8_decode(cast(const u8 *)line_text + i, line_length_bytes - i, nullptr); + + i_runes += 1; + i += bytes_read; } - terminal_set_colours(TerminalStyle_Bold, TerminalColour_Green); + if (window_right_bytes == 0) { + // The end of the window is the end of the line. + window_right_bytes = line_length_bytes; + } - error_out("^"); - if (end.file_id == pos.file_id) { - if (end.line > pos.line) { - for (i32 i = offset; i < line_len; i++) { - error_out("~"); - } - } else if (end.line == pos.line && end.column > pos.column) { - for (i32 i = 1; i < error_length-1+squiggle_extra; i++) { - error_out("~"); - } - if (error_length > 1 && squiggle_extra == 0) { - error_out("^"); - } + GB_ASSERT_MSG(window_right_runes >= window_left_runes, "Error line truncation window has wrong rune indices. (left, right: %i, %i)", window_left_runes, window_right_runes); + GB_ASSERT_MSG(window_right_bytes >= window_left_bytes, "Error line truncation window has wrong byte indices. (left, right: %i, %i)", window_left_bytes, window_right_bytes); + + if (window_right_bytes != line_length_bytes) { + show_right_ellipsis = true; + } + + // The text will advance; all indices and lengths will become relative. + // We must keep our other iterators in sync. + // NOTE: Uncomment the rune versions if they ever get used beyond this point. + + // Close the window, going left. + line_length_bytes = window_right_bytes; + + // Adjust the slice of text. In Odin, this would be: + // `line_text = line_text[window_left_bytes:]` + line_text += window_left_bytes; + line_length_bytes -= window_left_bytes; + // line_length_runes -= window_left_runes; + GB_ASSERT_MSG(line_length_bytes >= 0, "Bounds-checking error: line_length_bytes"); + + // Part of advancing `line_text`: + error_start_index_bytes -= window_left_bytes; + // error_start_index_runes -= window_left_runes; + GB_ASSERT_MSG(error_start_index_bytes >= 0, "Bounds-checking error: error_start_index_bytes"); + + if (window_left_bytes > 0) { + error_out("... "); + } + } + + // Start printing code. + + terminal_set_colours(TerminalStyle_Normal, TerminalColour_White); + error_out("%.*s", error_start_index_bytes, line_text); + + // Odin-like: `line_text = line_text[error_start_index_bytes:]` + line_text += error_start_index_bytes; + line_length_bytes -= error_start_index_bytes; + GB_ASSERT_MSG(line_length_bytes >= 0, "Bounds-checking error: line_length_bytes"); + + if (end.file_id == pos.file_id) { + // The error has an endpoint. + terminal_set_colours(TerminalStyle_Bold, marker_colour); + error_out(open_error_sign); + + if (end.line > pos.line) { + // Error goes to next line. + error_out(wiggly_underline_sgr); + error_out("%.*s", line_length_bytes, line_text); + + error_out(disable_underline_sgr); + + // Always show the ellipsis in this case + show_right_ellipsis = true; + + } else if (end.line == pos.line && end.column > pos.column) { + // Error terminates before line end. + i32 error_length_bytes = gb_min(end.column - pos.column, line_length_bytes); + + error_out(wiggly_underline_sgr); + error_out("%.*s", error_length_bytes, line_text); + line_text += error_length_bytes; + line_length_bytes -= error_length_bytes; + GB_ASSERT_MSG(line_length_bytes >= 0, "Bounds-checking error: line_length_bytes"); + + error_out(disable_underline_sgr); + + if (!show_right_ellipsis) { + // The line hasn't been truncated; show the end marker. + terminal_set_colours(TerminalStyle_Bold, marker_colour); + error_out(close_error_sign); } + + terminal_set_colours(TerminalStyle_Normal, TerminalColour_White); + error_out("%.*s", line_length_bytes, line_text); } - terminal_reset_colours(); + } else { + // The error is at one spot; no range known. + terminal_set_colours(TerminalStyle_Bold, marker_colour); + error_out(mark_error_sign); - error_out("\n"); - return offset; + terminal_set_colours(TerminalStyle_Normal, TerminalColour_White); + error_out("%.*s", line_length_bytes, line_text); + } + + if (show_right_ellipsis) { + error_out(" ..."); } - return -1; + + // NOTE(Feoramund): Specifically print a newline, then reset colours, + // instead of the other way around. Otherwise the printing mechanism + // will collapse the newline for reasons currently beyond my ken. + error_out("\n"); + terminal_reset_colours(); + + return error_start_index_bytes; } gb_internal void error_out_empty(void) { -- cgit v1.2.3 From 8ed5cb283b5039693e96d6f47eea6213194d6cbe Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sat, 29 Jun 2024 18:17:44 -0400 Subject: Re-implement the error squiggles with visual width --- src/check_stmt.cpp | 18 +++- src/error.cpp | 289 ++++++++++++++++++++++++----------------------------- 2 files changed, 143 insertions(+), 164 deletions(-) (limited to 'src/error.cpp') diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index c369ba098..f2e3b0242 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -582,20 +582,28 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O isize offset = show_error_on_line(e->token.pos, token_pos_end(e->token)); if (offset < 0) { if (is_type_map(e->type)) { - error_line("\t\tSuggestion: Did you mean? 'for key, &%.*s in ...'\n", LIT(e->token.string)); + error_line("\tSuggestion: Did you mean? 'for key, &%.*s in ...'\n", LIT(e->token.string)); } else { - error_line("\t\tSuggestion: Did you mean? 'for &%.*s in ...'\n", LIT(e->token.string)); + error_line("\tSuggestion: Did you mean? 'for &%.*s in ...'\n", LIT(e->token.string)); } } else { - error_line("\t\t'%.*s' is immutable, declare it as '&%.*s' to make it mutable\n", LIT(e->token.string), LIT(e->token.string)); + error_line("\t"); + for (isize i = 0; i < offset-1; i++) { + error_line(" "); + } + error_line("'%.*s' is immutable, declare it as '&%.*s' to make it mutable\n", LIT(e->token.string), LIT(e->token.string)); } } else if (e && e->flags & EntityFlag_SwitchValue) { isize offset = show_error_on_line(e->token.pos, token_pos_end(e->token)); if (offset < 0) { - error_line("\t\tSuggestion: Did you mean? 'switch &%.*s in ...'\n", LIT(e->token.string)); + error_line("\tSuggestion: Did you mean? 'switch &%.*s in ...'\n", LIT(e->token.string)); } else { - error_line("\t\t'%.*s' is immutable, declare it as '&%.*s' to make it mutable\n", LIT(e->token.string), LIT(e->token.string)); + error_line("\t"); + for (isize i = 0; i < offset-1; i++) { + error_line(" "); + } + error_line("'%.*s' is immutable, declare it as '&%.*s' to make it mutable\n", LIT(e->token.string), LIT(e->token.string)); } } } diff --git a/src/error.cpp b/src/error.cpp index 26b4106a0..f95123f15 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -283,9 +283,6 @@ gb_internal isize show_error_on_line(TokenPos const &pos, TokenPos end) { error_out("\t( empty line )\n"); terminal_reset_colours(); - // Preserve the old return behaviour. Even if we can't guarantee the - // exact visual space offset, there are two places that check this to - // change what sort of suggestion they offer. if (the_line == nullptr) { return -1; } else { @@ -293,244 +290,218 @@ gb_internal isize show_error_on_line(TokenPos const &pos, TokenPos end) { } } - // Specfically use basic ASCII arrows here, in case the terminal - // doesn't support anything fancy. This is meant to be a good fallback. - char const *mark_error_sign = "><"; - char const *open_error_sign = ">>"; - char const *close_error_sign = "<<"; - const TerminalColour marker_colour = TerminalColour_Yellow; - - // ANSI SGR: - // 0 = Reset. - // 58:5:2 = Underline colour, 8-bit, green. (non-standard) - // 4:3 = Wiggly underline. (non-standard) - char const *wiggly_underline_sgr = ""; - char const *disable_underline_sgr = ""; - if (has_ansi_terminal_colours()) { - wiggly_underline_sgr = "\x1b[0;58:5:2;4:3m"; - disable_underline_sgr = "\x1b[24m"; - } - // These two will be used like an Odin slice later. char const *line_text = the_line; i32 line_length_bytes = cast(i32)gb_string_length(the_line); - // NOTE(Feoramund): The numbers below are in Unicode codepoints - // (or runes), not visual glyph width. Calculating the visual width of - // a cluster of Unicode codepoints is vexing, and `utf8proc_charwidth` - // is inadequate. - // - // We're counting codepoints here so we don't slice one down the - // middle during truncation. It will still look strange if we slice - // a cluster down the middle. (i.e. a letter and a combining diacritic) - // - // Luckily, if our assumption about 1 codepoint == 1 glyph is wrong, - // we only suffer a shorter or longer line displayed in total, but all - // of our highlighting and marking will be precise. - // (Unless there's an invalid Unicode codepoint, in which case, no guarantees.) - // - // The line will be longer if a codepoint occupies more than one space - // (CJK in most cases) and shorter if a codepoint is invisible or is - // a type of joiner or combining codepoint. - // - // If we get a complete Unicode glyph counter, it would be as simple as - // replacing `utf8_decode` below to make all of this work perfectly. + ucg_grapheme* graphemes; + i32 line_length_runes = 0; + i32 line_length_graphemes = 0; + i32 line_width = 0; + + int ucg_result = ucg_decode_grapheme_clusters( + permanent_allocator(), (const uint8_t*)line_text, line_length_bytes, + &graphemes, &line_length_runes, &line_length_graphemes, &line_width); + + if (ucg_result < 0) { + // There was a UTF-8 parsing error. + // Insert a dummy grapheme so the start of the invalid rune can be pointed at. + graphemes = (ucg_grapheme*)gb_resize(permanent_allocator(), + graphemes, + sizeof(ucg_grapheme) * (line_length_graphemes), + sizeof(ucg_grapheme) * (1 + line_length_graphemes)); + ucg_grapheme append = { + error_start_index_bytes, + line_length_runes, + 1, + }; + + graphemes[line_length_graphemes] = append; + } + + // The units below are counted in visual, monospace cells. enum { MAX_LINE_LENGTH = 80, MAX_TAB_WIDTH = 8, ELLIPSIS_PADDING = 8, // `... ...` - MAX_MARK_WIDTH = 4, // `><` or `>>` and `<<` MIN_LEFT_VIEW = 8, // A rough estimate of how many characters we'll insert, at most: - MAX_INSERTED_WIDTH = MAX_TAB_WIDTH + ELLIPSIS_PADDING + MAX_MARK_WIDTH, + MAX_INSERTED_WIDTH = MAX_TAB_WIDTH + ELLIPSIS_PADDING, MAX_LINE_LENGTH_PADDED = MAX_LINE_LENGTH - MAX_INSERTED_WIDTH, }; - // For the purposes of truncating long lines, we calculate how many - // runes the line is composed of, first. We'll take note of at which - // rune index the error starts, too. - i32 error_start_index_runes = 0; - - i32 line_length_runes = 0; - for (i32 i = 0; i < line_length_bytes; /**/) { - Rune rune; - - if (i == error_start_index_bytes) { - error_start_index_runes = line_length_runes; - } - - i32 bytes_read = cast(i32)utf8_decode(cast(const u8 *)line_text + i, line_length_bytes - i, &rune); - if (rune == GB_RUNE_INVALID || bytes_read <= 0) { - // Bail out; we won't even try to truncate the line later. - line_length_runes = 0; + i32 error_start_index_graphemes = 0; + for (i32 i = 0; i < line_length_graphemes; i += 1) { + if (graphemes[i].byte_index == error_start_index_bytes) { + error_start_index_graphemes = i; break; } - - line_length_runes += 1; - i += bytes_read; } - if (error_start_index_runes == 0 && error_start_index_bytes != 0 && line_length_runes != 0) { - // The error index in runes was not found, but we did find a valid Unicode string. + if (error_start_index_graphemes == 0 && error_start_index_bytes != 0 && line_length_graphemes != 0) { + // The error index in graphemes was not found, but we did find a valid Unicode string. // // This is an edge case where the error is sitting on a newline or the // end of the line, as that is the only location we could not have checked. - error_start_index_runes = line_length_runes; + error_start_index_graphemes = line_length_graphemes; } error_out("\t"); bool show_right_ellipsis = false; - if (line_length_runes > MAX_LINE_LENGTH_PADDED) { + i32 squiggle_padding = 0; + i32 window_open_bytes = 0; + i32 window_close_bytes = 0; + if (line_width > MAX_LINE_LENGTH_PADDED) { // Now that we know the line is over the length limit, we have to - // compose a runic window in which to display the error. - i32 window_width = MAX_LINE_LENGTH_PADDED; - - i32 extend_right = 0; - i32 extend_left = 0; - if (error_start_index_runes + window_width > line_length_runes - 1) { - // Trade space from the right to the left. - extend_right = line_length_runes - error_start_index_runes; - extend_left = window_width - extend_right; - } else if (MIN_LEFT_VIEW - error_start_index_runes > 0) { - // Trade space from the left to the right. - extend_left = error_start_index_runes; - extend_right = window_width - extend_left; - } else { - // Square in the middle somewhere. - extend_left = MIN_LEFT_VIEW; - extend_right = window_width - extend_left; + // compose a visual window in which to display the error. + i32 window_size_left = 0; + i32 window_size_right = 0; + i32 window_open_graphemes = 0; + + for (i32 i = error_start_index_graphemes - 1; i > 0; i -= 1) { + window_size_left += graphemes[i].width; + if (window_size_left >= MIN_LEFT_VIEW) { + window_open_graphemes = i; + window_open_bytes = graphemes[i].byte_index; + break; + } } - i32 window_right_runes = gb_min(error_start_index_runes + extend_right, line_length_runes); - i32 window_left_runes = gb_max(0, error_start_index_runes - extend_left); - - i32 window_right_bytes = 0; - i32 window_left_bytes = 0; - - i32 i_runes = 0; - for (i32 i = 0; i < line_length_bytes; /**/) { - if (i_runes == window_left_runes ) { window_left_bytes = i; } - if (i_runes == window_right_runes) { window_right_bytes = i; } - - // No need for error-checking. - // - // We've already validated the string at this point, otherwise - // `line_length_runes` would be 0, and we would not have - // entered this block. - i32 bytes_read = cast(i32)utf8_decode(cast(const u8 *)line_text + i, line_length_bytes - i, nullptr); - - i_runes += 1; - i += bytes_read; + for (i32 i = error_start_index_graphemes; i < line_length_graphemes; i += 1) { + window_size_right += graphemes[i].width; + if (window_size_right >= MAX_LINE_LENGTH_PADDED - MIN_LEFT_VIEW) { + window_close_bytes = graphemes[i].byte_index; + break; + } + } + if (window_close_bytes == 0) { + // The window ends at the end of the line. + window_close_bytes = line_length_bytes; } - if (window_right_bytes == 0) { - // The end of the window is the end of the line. - window_right_bytes = line_length_bytes; + if (window_size_right < MAX_LINE_LENGTH_PADDED - MIN_LEFT_VIEW) { + // Hit the end of the string early on the right side; expand backwards. + for (i32 i = window_open_graphemes - 1; i > 0; i -= 1) { + window_size_left += graphemes[i].width; + if (window_size_left + window_size_right >= MAX_LINE_LENGTH_PADDED) { + window_open_graphemes = i; + window_open_bytes = graphemes[i].byte_index; + break; + } + } } - GB_ASSERT_MSG(window_right_runes >= window_left_runes, "Error line truncation window has wrong rune indices. (left, right: %i, %i)", window_left_runes, window_right_runes); - GB_ASSERT_MSG(window_right_bytes >= window_left_bytes, "Error line truncation window has wrong byte indices. (left, right: %i, %i)", window_left_bytes, window_right_bytes); + GB_ASSERT_MSG(window_close_bytes >= window_open_bytes, "Error line truncation window has wrong byte indices. (open, close: %i, %i)", window_open_bytes, window_close_bytes); - if (window_right_bytes != line_length_bytes) { + if (window_close_bytes != line_length_bytes) { show_right_ellipsis = true; } - // The text will advance; all indices and lengths will become relative. - // We must keep our other iterators in sync. - // NOTE: Uncomment the rune versions if they ever get used beyond this point. - // Close the window, going left. - line_length_bytes = window_right_bytes; + line_length_bytes = window_close_bytes; // Adjust the slice of text. In Odin, this would be: // `line_text = line_text[window_left_bytes:]` - line_text += window_left_bytes; - line_length_bytes -= window_left_bytes; - // line_length_runes -= window_left_runes; + line_text += window_open_bytes; + line_length_bytes -= window_open_bytes; GB_ASSERT_MSG(line_length_bytes >= 0, "Bounds-checking error: line_length_bytes"); - // Part of advancing `line_text`: - error_start_index_bytes -= window_left_bytes; - // error_start_index_runes -= window_left_runes; - GB_ASSERT_MSG(error_start_index_bytes >= 0, "Bounds-checking error: error_start_index_bytes"); - - if (window_left_bytes > 0) { + if (window_open_bytes > 0) { error_out("... "); + squiggle_padding += 4; } + } else { + // No truncation needed. + window_open_bytes = 0; + window_close_bytes = line_length_bytes; + } + + for (i32 i = error_start_index_graphemes; i > 0; i -= 1) { + if (graphemes[i].byte_index == window_open_bytes) { + break; + } + squiggle_padding += graphemes[i].width; } // Start printing code. terminal_set_colours(TerminalStyle_Normal, TerminalColour_White); - error_out("%.*s", error_start_index_bytes, line_text); + error_out("%.*s", line_length_bytes, line_text); - // Odin-like: `line_text = line_text[error_start_index_bytes:]` - line_text += error_start_index_bytes; - line_length_bytes -= error_start_index_bytes; - GB_ASSERT_MSG(line_length_bytes >= 0, "Bounds-checking error: line_length_bytes"); + i32 squiggle_length = 0; + bool trailing_squiggle = false; if (end.file_id == pos.file_id) { // The error has an endpoint. - terminal_set_colours(TerminalStyle_Bold, marker_colour); - error_out(open_error_sign); if (end.line > pos.line) { // Error goes to next line. - error_out(wiggly_underline_sgr); - error_out("%.*s", line_length_bytes, line_text); - - error_out(disable_underline_sgr); - // Always show the ellipsis in this case show_right_ellipsis = true; + for (i32 i = error_start_index_graphemes; i < line_length_graphemes; i += 1) { + squiggle_length += graphemes[i].width; + trailing_squiggle = true; + } + } else if (end.line == pos.line && end.column > pos.column) { // Error terminates before line end. - i32 error_length_bytes = gb_min(end.column - pos.column, line_length_bytes); - - error_out(wiggly_underline_sgr); - error_out("%.*s", error_length_bytes, line_text); - line_text += error_length_bytes; - line_length_bytes -= error_length_bytes; - GB_ASSERT_MSG(line_length_bytes >= 0, "Bounds-checking error: line_length_bytes"); + i32 adjusted_end_index = graphemes[error_start_index_graphemes].byte_index + end.column - pos.column; - error_out(disable_underline_sgr); - - if (!show_right_ellipsis) { - // The line hasn't been truncated; show the end marker. - terminal_set_colours(TerminalStyle_Bold, marker_colour); - error_out(close_error_sign); + for (i32 i = error_start_index_graphemes; i < line_length_graphemes; i += 1) { + if (graphemes[i].byte_index >= adjusted_end_index) { + break; + } else if (graphemes[i].byte_index >= window_close_bytes) { + trailing_squiggle = true; + break; + } + squiggle_length += graphemes[i].width; } - - terminal_set_colours(TerminalStyle_Normal, TerminalColour_White); - error_out("%.*s", line_length_bytes, line_text); } - } else { // The error is at one spot; no range known. - terminal_set_colours(TerminalStyle_Bold, marker_colour); - error_out(mark_error_sign); - - terminal_set_colours(TerminalStyle_Normal, TerminalColour_White); - error_out("%.*s", line_length_bytes, line_text); + squiggle_length = 1; } if (show_right_ellipsis) { error_out(" ..."); } + error_out("\n\t"); + + for (i32 i = squiggle_padding; i > 0; i -= 1) { + error_out(" "); + } + + terminal_set_colours(TerminalStyle_Bold, TerminalColour_Green); + + if (squiggle_length > 0) { + error_out("^"); + squiggle_length -= 1; + } + for (/**/; squiggle_length > 1; squiggle_length -= 1) { + error_out("~"); + } + if (squiggle_length > 0) { + if (trailing_squiggle) { + error_out("~ ..."); + } else { + error_out("^"); + } + } + // NOTE(Feoramund): Specifically print a newline, then reset colours, // instead of the other way around. Otherwise the printing mechanism // will collapse the newline for reasons currently beyond my ken. error_out("\n"); terminal_reset_colours(); - return error_start_index_bytes; + return squiggle_padding; } gb_internal void error_out_empty(void) { -- cgit v1.2.3 From 6b4b0cea5dd0b451a283f919e36fa6c060f2f236 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 20 Aug 2024 22:12:47 +0200 Subject: Add table-driven (in)definite article to some errors. --- src/check_expr.cpp | 22 +++++++++++++++------- src/error.cpp | 29 +++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 7 deletions(-) (limited to 'src/error.cpp') diff --git a/src/check_expr.cpp b/src/check_expr.cpp index f4d5cc5a4..2540230df 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -1071,16 +1071,19 @@ gb_internal void check_assignment(CheckerContext *c, Operand *operand, Type *typ return; } + // Grab definite or indefinite article matching `context_name`, or "" if not found. + String article = error_article(context_name); + if (is_type_untyped(operand->type)) { Type *target_type = type; if (type == nullptr || is_type_any(type)) { if (type == nullptr && is_type_untyped_uninit(operand->type)) { - error(operand->expr, "Use of --- in %.*s", LIT(context_name)); + error(operand->expr, "Use of --- in %.*s%.*s", LIT(article), LIT(context_name)); operand->mode = Addressing_Invalid; return; } if (type == nullptr && is_type_untyped_nil(operand->type)) { - error(operand->expr, "Use of untyped nil in %.*s", LIT(context_name)); + error(operand->expr, "Use of untyped nil in %.*s%.*s", LIT(article), LIT(context_name)); operand->mode = Addressing_Invalid; return; } @@ -1135,9 +1138,10 @@ gb_internal void check_assignment(CheckerContext *c, Operand *operand, Type *typ // TODO(bill): is this a good enough error message? error(operand->expr, - "Cannot assign overloaded procedure group '%s' to '%s' in %.*s", + "Cannot assign overloaded procedure group '%s' to '%s' in %.*s%.*s", expr_str, op_type_str, + LIT(article), LIT(context_name)); operand->mode = Addressing_Invalid; } @@ -1163,20 +1167,23 @@ gb_internal void check_assignment(CheckerContext *c, Operand *operand, Type *typ switch (operand->mode) { case Addressing_Builtin: error(operand->expr, - "Cannot assign built-in procedure '%s' in %.*s", + "Cannot assign built-in procedure '%s' to %.*s%.*s", expr_str, + LIT(article), LIT(context_name)); break; case Addressing_Type: if (is_type_polymorphic(operand->type)) { error(operand->expr, - "Cannot assign '%s' which is a polymorphic type in %.*s", + "Cannot assign '%s' — a polymorphic type — to %.*s%.*s", op_type_str, + LIT(article), LIT(context_name)); } else { error(operand->expr, - "Cannot assign '%s' which is a type in %.*s", + "Cannot assign '%s' — a type — to %.*s%.*s", op_type_str, + LIT(article), LIT(context_name)); } break; @@ -1203,10 +1210,11 @@ gb_internal void check_assignment(CheckerContext *c, Operand *operand, Type *typ ERROR_BLOCK(); error(operand->expr, - "Cannot assign value '%s' of type '%s%s' to '%s%s' in %.*s", + "Cannot assign value '%s' of type '%s%s' to '%s%s' in %.*s%.*s", expr_str, op_type_str, op_type_extra, type_str, type_extra, + LIT(article), LIT(context_name)); check_assignment_error_suggestion(c, operand, type); diff --git a/src/error.cpp b/src/error.cpp index f95123f15..1492b00c7 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -820,6 +820,35 @@ gb_internal int error_value_cmp(void const *a, void const *b) { return token_pos_cmp(x->pos, y->pos); } +gb_global String error_article_table[][2] = { + {str_lit("a "), str_lit("bit_set literal")}, + {str_lit("a "), str_lit("constant declaration")}, + {str_lit("a "), str_lit("dynamiic array literal")}, + {str_lit("a "), str_lit("map index")}, + {str_lit("a "), str_lit("map literal")}, + {str_lit("a "), str_lit("matrix literal")}, + {str_lit("a "), str_lit("polymorphic type argument")}, + {str_lit("a "), str_lit("procedure argument")}, + {str_lit("a "), str_lit("simd vector literal")}, + {str_lit("a "), str_lit("slice literal")}, + {str_lit("a "), str_lit("structure literal")}, + {str_lit("a "), str_lit("variable declaration")}, + {str_lit("an "), str_lit("'any' literal")}, + {str_lit("an "), str_lit("array literal")}, + {str_lit("an "), str_lit("enumerated array literal")}, + +}; + +// Returns definite or indefinite article matching `context_name`, or "" if not found. +gb_internal String error_article(String context_name) { + for (int i = 0; i < gb_count_of(error_article_table); i += 1) { + if (context_name == error_article_table[i][1]) { + return error_article_table[i][0]; + } + } + return str_lit(""); +} + gb_internal bool errors_already_printed = false; gb_internal void print_all_errors(void) { -- cgit v1.2.3