aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorgingerBill <bill@gingerbill.org>2021-07-08 23:15:07 +0100
committergingerBill <bill@gingerbill.org>2021-07-08 23:15:07 +0100
commit35230b1a11940117ee218066ef5cd11243a23456 (patch)
tree8e3cc3ec2b5efd40352897bd5d5bbcccb60d1fbf /src
parent7acbf8b7b9cfc1f3e63d330e4dac13b28f1310f2 (diff)
Add "Suggestion: Did you mean?" for selector expression typos
Diffstat (limited to 'src')
-rw-r--r--src/array.cpp13
-rw-r--r--src/check_builtin.cpp9
-rw-r--r--src/check_expr.cpp55
-rw-r--r--src/common.cpp91
-rw-r--r--src/main.cpp12
-rw-r--r--src/string.cpp38
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
@@ -90,6 +90,19 @@ Slice<T> slice_from_array(Array<T> const &a) {
return {a.data, a.count};
}
template <typename T>
+Slice<T> slice_array(Array<T> const &array, isize lo, isize hi) {
+ GB_ASSERT(0 <= lo && lo <= hi && hi <= array.count);
+ Slice<T> out = {};
+ isize len = hi-lo;
+ if (len > 0) {
+ out.data = array.data+lo;
+ out.count = len;
+ }
+ return out;
+}
+
+
+template <typename T>
Slice<T> slice_clone(gbAllocator const &allocator, Slice<T> const &a) {
T *data = cast(T *)gb_alloc_copy_align(allocator, a.data, a.count*gb_size_of(T), gb_align_of(T));
return {data, a.count};
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<Entity *> 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<FileInfo> *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<DistanceAndTarget> 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<DistanceAndTarget> 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<String> 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;
-}