mirror of
https://github.com/Ed94/Odin.git
synced 2026-08-06 15:48:51 +00:00
Add "Suggestion: Did you mean?" for selector expression typos
This commit is contained in:
@@ -89,6 +89,19 @@ template <typename T>
|
|||||||
Slice<T> slice_from_array(Array<T> const &a) {
|
Slice<T> slice_from_array(Array<T> const &a) {
|
||||||
return {a.data, a.count};
|
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>
|
template <typename T>
|
||||||
Slice<T> slice_clone(gbAllocator const &allocator, Slice<T> const &a) {
|
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));
|
T *data = cast(T *)gb_alloc_copy_align(allocator, a.data, a.count*gb_size_of(T), gb_align_of(T));
|
||||||
|
|||||||
@@ -580,6 +580,11 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
|
|||||||
error(ce->args[0],
|
error(ce->args[0],
|
||||||
"'%s' has no field named '%.*s'", type_str, LIT(arg->token.string));
|
"'%s' has no field named '%.*s'", type_str, LIT(arg->token.string));
|
||||||
gb_string_free(type_str);
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
if (sel.indirect) {
|
if (sel.indirect) {
|
||||||
@@ -3082,6 +3087,10 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
|
|||||||
error(ce->args[0],
|
error(ce->args[0],
|
||||||
"'%s' has no field named '%.*s'", type_str, LIT(field_name));
|
"'%s' has no field named '%.*s'", type_str, LIT(field_name));
|
||||||
gb_string_free(type_str);
|
gb_string_free(type_str);
|
||||||
|
|
||||||
|
if (bt->kind == Type_Struct) {
|
||||||
|
check_did_you_mean_type(field_name, bt->Struct.fields);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (sel.indirect) {
|
if (sel.indirect) {
|
||||||
|
|||||||
+53
-2
@@ -3576,6 +3576,39 @@ ExactValue get_constant_field(CheckerContext *c, Operand const *operand, Selecti
|
|||||||
if (success_) *success_ = true;
|
if (success_) *success_ = true;
|
||||||
return empty_exact_value;
|
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) {
|
Entity *check_selector(CheckerContext *c, Operand *operand, Ast *node, Type *type_hint) {
|
||||||
ast_node(se, SelectorExpr, node);
|
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));
|
error(op_expr, "'%.*s' is not declared by '%.*s'", LIT(entity_name), LIT(import_name));
|
||||||
operand->mode = Addressing_Invalid;
|
operand->mode = Addressing_Invalid;
|
||||||
operand->expr = node;
|
operand->expr = node;
|
||||||
|
|
||||||
|
check_did_you_mean_scope(entity_name, import_scope);
|
||||||
return nullptr;
|
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 type_str = type_to_string(operand->type);
|
||||||
gbString sel_str = expr_to_string(selector);
|
gbString sel_str = expr_to_string(selector);
|
||||||
error(op_expr, "'%s' of type '%s' has no field '%s'", op_str, type_str, sel_str);
|
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(sel_str);
|
||||||
gb_string_free(type_str);
|
gb_string_free(type_str);
|
||||||
gb_string_free(op_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;
|
String name = ise->selector->Ident.token.string;
|
||||||
|
|
||||||
if (is_type_enum(th)) {
|
if (is_type_enum(th)) {
|
||||||
|
Type *bt = base_type(th);
|
||||||
|
GB_ASSERT(bt->kind == Type_Enum);
|
||||||
|
|
||||||
gbString typ = type_to_string(th);
|
gbString typ = type_to_string(th);
|
||||||
error(node, "Undeclared name %.*s for type '%s'", LIT(name), typ);
|
defer (gb_string_free(typ));
|
||||||
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 {
|
} else {
|
||||||
gbString typ = type_to_string(th);
|
gbString typ = type_to_string(th);
|
||||||
gbString str = expr_to_string(node);
|
gbString str = expr_to_string(node);
|
||||||
|
|||||||
+87
-4
@@ -325,13 +325,13 @@ gb_global u64 const unsigned_integer_maxs[] = {
|
|||||||
|
|
||||||
|
|
||||||
bool add_overflow_u64(u64 x, u64 y, u64 *result) {
|
bool add_overflow_u64(u64 x, u64 y, u64 *result) {
|
||||||
*result = x + y;
|
*result = x + y;
|
||||||
return *result < x || *result < y;
|
return *result < x || *result < y;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool sub_overflow_u64(u64 x, u64 y, u64 *result) {
|
bool sub_overflow_u64(u64 x, u64 y, u64 *result) {
|
||||||
*result = x - y;
|
*result = x - y;
|
||||||
return *result > x;
|
return *result > x;
|
||||||
}
|
}
|
||||||
|
|
||||||
void mul_overflow_u64(u64 x, u64 y, u64 *lo, u64 *hi) {
|
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
|
#else
|
||||||
#error Implement read_directory
|
#error Implement read_directory
|
||||||
#endif
|
#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);
|
||||||
|
}
|
||||||
|
|||||||
+6
-6
@@ -1106,24 +1106,24 @@ bool parse_build_flags(Array<String> args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!found) {
|
if (!found) {
|
||||||
struct DistanceAndTarget {
|
struct DistanceAndTargetIndex {
|
||||||
isize distance;
|
isize distance;
|
||||||
isize target_index;
|
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++) {
|
for (isize i = 0; i < gb_count_of(named_targets); i++) {
|
||||||
distances[i].target_index = i;
|
distances[i].target_index = i;
|
||||||
distances[i].distance = levenstein_distance_case_insensitive(str, named_targets[i].name);
|
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));
|
gb_printf_err("Unknown target '%.*s'\n", LIT(str));
|
||||||
|
|
||||||
enum {MAX_SMALLEST_DISTANCE = 3};
|
if (distances[0].distance <= MAX_SMALLEST_DID_YOU_MEAN_DISTANCE) {
|
||||||
if (distances[0].distance <= MAX_SMALLEST_DISTANCE) {
|
|
||||||
gb_printf_err("Did you mean:\n");
|
gb_printf_err("Did you mean:\n");
|
||||||
for (isize i = 0; i < gb_count_of(named_targets); i++) {
|
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;
|
break;
|
||||||
}
|
}
|
||||||
gb_printf_err("\t%.*s\n", LIT(named_targets[distances[i].target_index].name));
|
gb_printf_err("\t%.*s\n", LIT(named_targets[distances[i].target_index].name));
|
||||||
|
|||||||
@@ -779,41 +779,3 @@ i32 unquote_string(gbAllocator a, String *s_, u8 quote=0, bool has_carriage_retu
|
|||||||
return 2;
|
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;
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user