diff --git a/src/build_settings.cpp b/src/build_settings.cpp index dc11a5fd2..0495cf3b5 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -844,6 +844,8 @@ struct BuildContext { bool show_unused; bool show_unused_with_location; bool show_more_timings; + bool show_defineables; + String export_defineables_file; bool show_system_calls; bool keep_temp_files; bool ignore_unknown_attributes; diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 3aee804df..203ae11ea 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -1756,9 +1756,21 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o operand->mode = Addressing_Constant; operand->value = exact_value_bool(is_defined); + // If the arg is a selector expression we don't add it, `-define` only allows identifiers. + if (arg->kind == Ast_Ident) { + Defineable defineable = {}; + defineable.docs = nullptr; + defineable.name = arg->Ident.token.string; + defineable.default_value = exact_value_bool(false); + defineable.pos = arg->Ident.token.pos; + + MUTEX_GUARD(&c->info->defineables_mutex); + array_add(&c->info->defineables, defineable); + } + } else if (name == "config") { if (ce->args.count != 2) { - error(call, "'#config' expects 2 argument, got %td", ce->args.count); + error(call, "'#config' expects 2 arguments, got %td", ce->args.count); return false; } Ast *arg = unparen_expr(ce->args[0]); @@ -1793,6 +1805,20 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o operand->value = found->Constant.value; } } + + Defineable defineable = {}; + defineable.docs = nullptr; + defineable.name = name; + defineable.default_value = def.value; + defineable.pos = arg->Ident.token.pos; + + if (c->decl) { + defineable.docs = c->decl->docs; + } + + MUTEX_GUARD(&c->info->defineables_mutex); + array_add(&c->info->defineables, defineable); + } else { error(call, "Unknown directive call: #%.*s", LIT(name)); } diff --git a/src/checker.cpp b/src/checker.cpp index 49726c090..4945b3810 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1302,6 +1302,7 @@ gb_internal void init_checker_info(CheckerInfo *i) { array_init(&i->init_procedures, a, 0, 0); array_init(&i->fini_procedures, a, 0, 0); array_init(&i->required_foreign_imports_through_force, a, 0, 0); + array_init(&i->defineables, a); map_init(&i->objc_msgSend_types); string_map_init(&i->load_file_cache); @@ -1331,6 +1332,7 @@ gb_internal void destroy_checker_info(CheckerInfo *i) { string_map_destroy(&i->packages); array_free(&i->variable_init_order); array_free(&i->required_foreign_imports_through_force); + array_free(&i->defineables); mpsc_destroy(&i->entity_queue); mpsc_destroy(&i->definition_queue); diff --git a/src/checker.hpp b/src/checker.hpp index 492a64fb6..781737140 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -382,6 +382,17 @@ struct GenTypesData { RecursiveMutex mutex; }; +struct Defineable { + String name; + ExactValue default_value; + TokenPos pos; + CommentGroup *docs; + + // These strings are only computed from previous fields when defineables are being shown or exported. + String default_value_str; + String pos_str; +}; + // CheckerInfo stores all the symbol information for a type-checked program struct CheckerInfo { Checker *checker; @@ -408,6 +419,9 @@ struct CheckerInfo { Array entities; Array required_foreign_imports_through_force; + BlockingMutex defineables_mutex; + Array defineables; + // Below are accessed within procedures RwMutex global_untyped_mutex; diff --git a/src/main.cpp b/src/main.cpp index f4cd40fe9..368b9aa6d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -288,6 +288,9 @@ enum BuildFlagKind { BuildFlag_NoThreadedChecker, BuildFlag_ShowDebugMessages, + BuildFlag_ShowDefineables, + BuildFlag_ExportDefineables, + BuildFlag_Vet, BuildFlag_VetShadowing, BuildFlag_VetUnused, @@ -483,6 +486,9 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_NoThreadedChecker, str_lit("no-threaded-checker"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_ShowDebugMessages, str_lit("show-debug-messages"), BuildFlagParam_None, Command_all); + add_flag(&build_flags, BuildFlag_ShowDefineables, str_lit("show-defineables"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_ExportDefineables, str_lit("export-defineables"), BuildFlagParam_String, Command__does_check); + add_flag(&build_flags, BuildFlag_Vet, str_lit("vet"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_VetUnused, str_lit("vet-unused"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_VetUnusedVariables, str_lit("vet-unused-variables"), BuildFlagParam_None, Command__does_check); @@ -814,6 +820,24 @@ gb_internal bool parse_build_flags(Array args) { break; } + case BuildFlag_ShowDefineables: { + GB_ASSERT(value.kind == ExactValue_Invalid); + build_context.show_defineables = true; + break; + } + case BuildFlag_ExportDefineables: { + GB_ASSERT(value.kind == ExactValue_String); + + String export_path = string_trim_whitespace(value.value_string); + if (is_build_flag_path_valid(export_path)) { + build_context.export_defineables_file = path_to_full_path(heap_allocator(), export_path); + } else { + gb_printf_err("Invalid -export-defineables path, got %.*s\n", LIT(export_path)); + bad_flags = true; + } + + break; + } case BuildFlag_ShowSystemCalls: { GB_ASSERT(value.kind == ExactValue_Invalid); build_context.show_system_calls = true; @@ -1553,6 +1577,115 @@ gb_internal void timings_export_all(Timings *t, Checker *c, bool timings_are_fin gb_printf("Done.\n"); } +gb_internal void check_defines(BuildContext *bc, Checker *c) { + for (auto const &entry : bc->defined_values) { + String name = make_string_c(entry.key); + ExactValue value = entry.value; + GB_ASSERT(value.kind != ExactValue_Invalid); + + bool found = false; + for_array(i, c->info.defineables) { + Defineable *def = &c->info.defineables[i]; + if (def->name == name) { + found = true; + break; + } + } + + if (!found) { + ERROR_BLOCK(); + warning(nullptr, "given -define:%.*s is unused in the project", LIT(name)); + error_line("\tSuggestion: use the -show-defineables flag for an overview of the possible defines\n"); + } + } +} + +gb_internal void temp_alloc_defineable_strings(Checker *c) { + for_array(i, c->info.defineables) { + Defineable *def = &c->info.defineables[i]; + def->default_value_str = make_string_c(write_exact_value_to_string(gb_string_make(temporary_allocator(), ""), def->default_value)); + def->pos_str = make_string_c(token_pos_to_string(def->pos)); + } +} + +gb_internal GB_COMPARE_PROC(defineables_cmp) { + Defineable *x = (Defineable *)a; + Defineable *y = (Defineable *)b; + + int cmp = 0; + + String x_file = get_file_path_string(x->pos.file_id); + String y_file = get_file_path_string(y->pos.file_id); + cmp = string_compare(x_file, y_file); + if (cmp) { + return cmp; + } + + return i32_cmp(x->pos.offset, y->pos.offset); +} + +gb_internal void sort_defineables(Checker *c) { + gb_sort_array(c->info.defineables.data, c->info.defineables.count, defineables_cmp); +} + +gb_internal void export_defineables(Checker *c, String path) { + gbFile f = {}; + gbFileError err = gb_file_open_mode(&f, gbFileMode_Write, (char *)path.text); + if (err != gbFileError_None) { + gb_printf_err("Failed to export defineables to: %.*s\n", LIT(path)); + gb_exit(1); + return; + } else { + gb_printf("Exporting defineables to '%.*s'...\n", LIT(path)); + } + defer (gb_file_close(&f)); + + gbString docs = gb_string_make(heap_allocator(), ""); + defer (gb_string_free(docs)); + + gb_fprintf(&f, "Defineable,Default Value,Docs,Location\n"); + for_array(i, c->info.defineables) { + Defineable *def = &c->info.defineables[i]; + + gb_string_clear(docs); + if (def->docs) { + docs = gb_string_appendc(docs, "\""); + for (Token const &token : def->docs->list) { + for (isize i = 0; i < token.string.len; i++) { + u8 c = token.string.text[i]; + if (c == '"') { + docs = gb_string_appendc(docs, "\"\""); + } else { + docs = gb_string_append_length(docs, &c, 1); + } + } + } + docs = gb_string_appendc(docs, "\""); + } + + gb_fprintf(&f,"%.*s,%.*s,%s,%.*s\n", LIT(def->name), LIT(def->default_value_str), docs, LIT(def->pos_str)); + } +} + +gb_internal void show_defineables(Checker *c) { + for_array(i, c->info.defineables) { + Defineable *def = &c->info.defineables[i]; + if (has_ansi_terminal_colours()) { + gb_printf("\x1b[0;90m"); + } + printf("%.*s\n", LIT(def->pos_str)); + if (def->docs) { + for (Token const &token : def->docs->list) { + gb_printf("%.*s\n", LIT(token.string)); + } + } + if (has_ansi_terminal_colours()) { + gb_printf("\x1b[0m"); + } + gb_printf("%.*s :: %.*s\n\n", LIT(def->name), LIT(def->default_value_str)); + } +} + gb_internal void show_timings(Checker *c, Timings *t) { Parser *p = c->parser; isize lines = p->total_line_count; @@ -1955,6 +2088,15 @@ gb_internal void print_show_help(String const arg0, String const &command) { print_usage_line(2, "Usage in code:"); print_usage_line(3, "#config(SPAM, default_value)"); print_usage_line(0, ""); + + print_usage_line(1, "-show-defineables"); + print_usage_line(2, "Shows an overview of all the #config/#defined usages in the project."); + print_usage_line(0, ""); + + print_usage_line(1, "-export-defineables:"); + print_usage_line(2, "Exports an overview of all the #config/#defined usages in CSV format to the given file path."); + print_usage_line(2, "Example: -export-defineables:defineables.csv"); + print_usage_line(0, ""); } if (build) { @@ -2953,6 +3095,7 @@ int main(int arg_count, char const **arg_ptr) { defer (destroy_checker(checker)); check_parsed_files(checker); + check_defines(&build_context, checker); if (any_errors()) { print_all_errors(); return 1; @@ -2961,6 +3104,19 @@ int main(int arg_count, char const **arg_ptr) { print_all_errors(); } + if (build_context.show_defineables || build_context.export_defineables_file != "") { + TEMPORARY_ALLOCATOR_GUARD(); + temp_alloc_defineable_strings(checker); + sort_defineables(checker); + + if (build_context.show_defineables) { + show_defineables(checker); + } + + if (build_context.export_defineables_file != "") { + export_defineables(checker, build_context.export_defineables_file); + } + } if (build_context.command_kind == Command_strip_semicolon) { return strip_semicolons(parser); diff --git a/tests/core/math/big/build.bat b/tests/core/math/big/build.bat index 54b715a4f..ad199d775 100644 --- a/tests/core/math/big/build.bat +++ b/tests/core/math/big/build.bat @@ -5,7 +5,7 @@ set TEST_ARGS=-fast-tests set TEST_ARGS=-no-random set TEST_ARGS= set OUT_NAME=math_big_test_library.dll -set COMMON=-build-mode:shared -show-timings -no-bounds-check -define:MATH_BIG_EXE=false -vet -strict-style -define:ODIN_TEST_FANCY=false +set COMMON=-build-mode:shared -show-timings -no-bounds-check -define:MATH_BIG_EXE=false -vet -strict-style echo --- echo Running core:math/big tests echo ---