From 35230b1a11940117ee218066ef5cd11243a23456 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 8 Jul 2021 23:15:07 +0100 Subject: [PATCH] Add "Suggestion: Did you mean?" for selector expression typos --- src/array.cpp | 13 +++++++ src/check_builtin.cpp | 9 +++++ src/check_expr.cpp | 55 +++++++++++++++++++++++++- src/common.cpp | 91 +++++++++++++++++++++++++++++++++++++++++-- src/main.cpp | 12 +++--- src/string.cpp | 38 ------------------ 6 files changed, 168 insertions(+), 50 deletions(-) diff --git a/src/array.cpp b/src/array.cpp index db51e2bfb..a7c9204b0 100644 --- a/src/array.cpp +++ b/src/array.cpp @@ -89,6 +89,19 @@ template Slice slice_from_array(Array const &a) { return {a.data, a.count}; } +template +Slice slice_array(Array const &array, isize lo, isize hi) { + GB_ASSERT(0 <= lo && lo <= hi && hi <= array.count); + Slice out = {}; + isize len = hi-lo; + if (len > 0) { + out.data = array.data+lo; + out.count = len; + } + return out; +} + + template Slice slice_clone(gbAllocator const &allocator, Slice const &a) { T *data = cast(T *)gb_alloc_copy_align(allocator, a.data, a.count*gb_size_of(T), gb_align_of(T)); diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index ea48c7ade..d9dd2be03 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -580,6 +580,11 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 error(ce->args[0], "'%s' has no field named '%.*s'", type_str, LIT(arg->token.string)); gb_string_free(type_str); + + Type *bt = base_type(type); + if (bt->kind == Type_Struct) { + check_did_you_mean_type(arg->token.string, bt->Struct.fields); + } return false; } if (sel.indirect) { @@ -3082,6 +3087,10 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 error(ce->args[0], "'%s' has no field named '%.*s'", type_str, LIT(field_name)); gb_string_free(type_str); + + if (bt->kind == Type_Struct) { + check_did_you_mean_type(field_name, bt->Struct.fields); + } return false; } if (sel.indirect) { diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 20e54ab3d..9b2d17206 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -3576,6 +3576,39 @@ ExactValue get_constant_field(CheckerContext *c, Operand const *operand, Selecti if (success_) *success_ = true; return empty_exact_value; } +void check_did_you_mean_print(DidYouMeanAnswers *d) { + auto results = did_you_mean_results(d); + if (results.count != 0) { + error_line("\tSuggestion: Did you mean?\n"); + for_array(i, results) { + String const &target = results[i].target; + error_line("\t\t%.*s\n", LIT(target)); + } + } +} + +void check_did_you_mean_type(String const &name, Array const &fields) { + DidYouMeanAnswers d = did_you_mean_make(heap_allocator(), fields.count, name); + defer (did_you_mean_destroy(&d)); + + for_array(i, fields) { + did_you_mean_append(&d, fields[i]->token.string); + } + check_did_you_mean_print(&d); +} + +void check_did_you_mean_scope(String const &name, Scope *scope) { + DidYouMeanAnswers d = did_you_mean_make(heap_allocator(), scope->elements.entries.count, name); + defer (did_you_mean_destroy(&d)); + + for_array(i, scope->elements.entries) { + Entity *e = scope->elements.entries[i].value; + did_you_mean_append(&d, e->token.string); + } + check_did_you_mean_print(&d); +} + + Entity *check_selector(CheckerContext *c, Operand *operand, Ast *node, Type *type_hint) { ast_node(se, SelectorExpr, node); @@ -3641,6 +3674,8 @@ Entity *check_selector(CheckerContext *c, Operand *operand, Ast *node, Type *typ error(op_expr, "'%.*s' is not declared by '%.*s'", LIT(entity_name), LIT(import_name)); operand->mode = Addressing_Invalid; operand->expr = node; + + check_did_you_mean_scope(entity_name, import_scope); return nullptr; } @@ -3818,6 +3853,17 @@ Entity *check_selector(CheckerContext *c, Operand *operand, Ast *node, Type *typ gbString type_str = type_to_string(operand->type); gbString sel_str = expr_to_string(selector); error(op_expr, "'%s' of type '%s' has no field '%s'", op_str, type_str, sel_str); + + if (operand->type != nullptr && selector->kind == Ast_Ident) { + String const &name = selector->Ident.token.string; + Type *bt = base_type(operand->type); + if (bt->kind == Type_Struct) { + check_did_you_mean_type(name, bt->Struct.fields); + } else if (bt->kind == Type_Enum) { + check_did_you_mean_type(name, bt->Enum.fields); + } + } + gb_string_free(sel_str); gb_string_free(type_str); gb_string_free(op_str); @@ -6180,9 +6226,14 @@ ExprKind check_implicit_selector_expr(CheckerContext *c, Operand *o, Ast *node, String name = ise->selector->Ident.token.string; if (is_type_enum(th)) { + Type *bt = base_type(th); + GB_ASSERT(bt->kind == Type_Enum); + gbString typ = type_to_string(th); - error(node, "Undeclared name %.*s for type '%s'", LIT(name), typ); - gb_string_free(typ); + defer (gb_string_free(typ)); + error(node, "Undeclared name '%.*s' for type '%s'", LIT(name), typ); + + check_did_you_mean_type(name, bt->Enum.fields); } else { gbString typ = type_to_string(th); gbString str = expr_to_string(node); diff --git a/src/common.cpp b/src/common.cpp index a70f9c629..9dfd694f4 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -325,13 +325,13 @@ gb_global u64 const unsigned_integer_maxs[] = { bool add_overflow_u64(u64 x, u64 y, u64 *result) { - *result = x + y; - return *result < x || *result < y; + *result = x + y; + return *result < x || *result < y; } bool sub_overflow_u64(u64 x, u64 y, u64 *result) { - *result = x - y; - return *result > x; + *result = x - y; + return *result > x; } void mul_overflow_u64(u64 x, u64 y, u64 *lo, u64 *hi) { @@ -1174,3 +1174,86 @@ ReadDirectoryError read_directory(String path, Array *fi) { #else #error Implement read_directory #endif + + + + +isize levenstein_distance_case_insensitive(String const &a, String const &b) { + isize w = a.len+1; + isize h = b.len+1; + isize *matrix = gb_alloc_array(temporary_allocator(), isize, w*h); + for (isize i = 0; i <= a.len; i++) { + matrix[i*w + 0] = i; + } + for (isize i = 0; i <= b.len; i++) { + matrix[0*w + i] = i; + } + + for (isize i = 1; i <= a.len; i++) { + char a_c = gb_char_to_lower(cast(char)a.text[i-1]); + for (isize j = 1; j <= b.len; j++) { + char b_c = gb_char_to_lower(cast(char)b.text[j-1]); + if (a_c == b_c) { + matrix[i*w + j] = matrix[(i-1)*w + j-1]; + } else { + isize remove = matrix[(i-1)*w + j] + 1; + isize insert = matrix[i*w + j-1] + 1; + isize substitute = matrix[(i-1)*w + j-1] + 1; + isize minimum = remove; + if (insert < minimum) { + minimum = insert; + } + if (substitute < minimum) { + minimum = substitute; + } + matrix[i*w + j] = minimum; + } + } + } + + return matrix[a.len*w + b.len]; +} + + +struct DistanceAndTarget { + isize distance; + String target; +}; + +struct DidYouMeanAnswers { + Array distances; + String key; +}; + +enum {MAX_SMALLEST_DID_YOU_MEAN_DISTANCE = 3}; + +DidYouMeanAnswers did_you_mean_make(gbAllocator allocator, isize cap, String const &key) { + DidYouMeanAnswers d = {}; + array_init(&d.distances, allocator, 0, cap); + d.key = key; + return d; +} +void did_you_mean_destroy(DidYouMeanAnswers *d) { + array_free(&d->distances); +} +void did_you_mean_append(DidYouMeanAnswers *d, String const &target) { + if (target.len == 0 || target == "_") { + return; + } + DistanceAndTarget dat = {}; + dat.target = target; + dat.distance = levenstein_distance_case_insensitive(d->key, target); + array_add(&d->distances, dat); +} +Slice did_you_mean_results(DidYouMeanAnswers *d) { + gb_sort_array(d->distances.data, d->distances.count, gb_isize_cmp(gb_offset_of(DistanceAndTarget, distance))); + isize count = 0; + for (isize i = 0; i < d->distances.count; i++) { + isize distance = d->distances[i].distance; + if (distance > MAX_SMALLEST_DID_YOU_MEAN_DISTANCE) { + break; + } + count += 1; + } + return slice_array(d->distances, 0, count); +} diff --git a/src/main.cpp b/src/main.cpp index f2a397965..5222a0321 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1106,24 +1106,24 @@ bool parse_build_flags(Array args) { } if (!found) { - struct DistanceAndTarget { + struct DistanceAndTargetIndex { isize distance; isize target_index; }; - DistanceAndTarget distances[gb_count_of(named_targets)] = {}; + + DistanceAndTargetIndex distances[gb_count_of(named_targets)] = {}; for (isize i = 0; i < gb_count_of(named_targets); i++) { distances[i].target_index = i; distances[i].distance = levenstein_distance_case_insensitive(str, named_targets[i].name); } - gb_sort_array(distances, gb_count_of(distances), gb_isize_cmp(gb_offset_of(DistanceAndTarget, distance))); + gb_sort_array(distances, gb_count_of(distances), gb_isize_cmp(gb_offset_of(DistanceAndTargetIndex, distance))); gb_printf_err("Unknown target '%.*s'\n", LIT(str)); - enum {MAX_SMALLEST_DISTANCE = 3}; - if (distances[0].distance <= MAX_SMALLEST_DISTANCE) { + if (distances[0].distance <= MAX_SMALLEST_DID_YOU_MEAN_DISTANCE) { gb_printf_err("Did you mean:\n"); for (isize i = 0; i < gb_count_of(named_targets); i++) { - if (distances[i].distance > MAX_SMALLEST_DISTANCE) { + if (distances[i].distance > MAX_SMALLEST_DID_YOU_MEAN_DISTANCE) { break; } gb_printf_err("\t%.*s\n", LIT(named_targets[distances[i].target_index].name)); diff --git a/src/string.cpp b/src/string.cpp index 4226f0751..9cb5933e0 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -779,41 +779,3 @@ i32 unquote_string(gbAllocator a, String *s_, u8 quote=0, bool has_carriage_retu return 2; } - -isize levenstein_distance_case_insensitive(String const &a, String const &b) { - isize w = a.len+1; - isize h = b.len+1; - isize *matrix = gb_alloc_array(heap_allocator(), isize, w*h); - for (isize i = 0; i <= a.len; i++) { - matrix[i*w + 0] = i; - } - for (isize i = 0; i <= b.len; i++) { - matrix[0*w + i] = i; - } - - for (isize i = 1; i <= a.len; i++) { - char a_c = gb_char_to_lower(cast(char)a.text[i-1]); - for (isize j = 1; j <= b.len; j++) { - char b_c = gb_char_to_lower(cast(char)b.text[j-1]); - if (a_c == b_c) { - matrix[i*w + j] = matrix[(i-1)*w + j-1]; - } else { - isize remove = matrix[(i-1)*w + j] + 1; - isize insert = matrix[i*w + j-1] + 1; - isize substitute = matrix[(i-1)*w + j-1] + 1; - isize minimum = remove; - if (insert < minimum) { - minimum = insert; - } - if (substitute < minimum) { - minimum = substitute; - } - matrix[i*w + j] = minimum; - } - } - } - - isize res = matrix[a.len*w + b.len]; - gb_free(heap_allocator(), matrix); - return res; -}