From 4976ebcef45983888b1369dc2fce21411e5ceec1 Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Sat, 27 Jul 2024 21:11:54 +0200 Subject: [PATCH] Permit foreach values to be optional. Update matching algorithm. --- releasenotes.md | 1 + src/compiler/sema_name_resolution.c | 47 +++++++++++++------ src/compiler/sema_stmts.c | 8 +--- test/test_suite/statements/foreach_errors.c3 | 2 +- .../test_suite/statements/foreach_r_errors.c3 | 2 +- 5 files changed, 36 insertions(+), 24 deletions(-) diff --git a/releasenotes.md b/releasenotes.md index 8261712af..db69e43e4 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -13,6 +13,7 @@ - Give some symbol name suggestions when the path is matched. - Don't generate .o files on `compile` and `compile-run` if there is no `main`. - c3c init-lib does not create the directory with the .c3l suffix #1253 +- Permit foreach values to be optional. ### Fixes - Broken WASM library code. diff --git a/src/compiler/sema_name_resolution.c b/src/compiler/sema_name_resolution.c index cb9211d81..fa2e5748b 100644 --- a/src/compiler/sema_name_resolution.c +++ b/src/compiler/sema_name_resolution.c @@ -484,21 +484,38 @@ static bool sema_resolve_no_path_symbol(SemaContext *context, NameResolve *name_ return sema_find_decl_in_global(context, &global_context.symbols, NULL, name_resolve, false); } -static int levenshtein(const char *s, int ls, const char *t, int lt) +#define MAX_TEST 256 + +int damerau_levenshtein_distance(const char *a, int a_len, const char *b, int b_len) { - if (!ls) return lt; - if (!lt) return ls; - if (s[ls - 1] == t[lt - 1]) return levenshtein(s, ls - 1, t, lt - 1); - int a = levenshtein(s, ls - 1, t, lt - 1); - int b = levenshtein(s, ls, t, lt - 1); - int c = levenshtein(s, ls - 1, t, lt ); - - if (a > b) a = b; - if (a > c) a = c; - - return a + 1; + if (!a_len) return b_len; + if (!b_len) return a_len; + if (a_len >= MAX_TEST || b_len >= MAX_TEST) return MAX_TEST; + int score[MAX_TEST][MAX_TEST]; + memset(score, 0, MAX_TEST * MAX_TEST); + for (int i = 0; i <= a_len; i++) score[i][0] = i; + for (int i = 0; i <= b_len; i++) score[0][i] = i; + for (int i = 0; i < a_len; i++) + { + for (int j = 0; j < b_len; j++) + { + int cost = a[i] == b[i] ? 0 : 1; + int del = score[i][j + 1] + 1; + int insert = score[i + 1][j] + 1; + int substitute = score[i][j] + cost; + int min = del < insert ? del : insert; + score[i + 1][j + 1] = min < substitute ? min : substitute; + if (i > 0 && j > 0 && a[i] == b[j - 1] && a[i - 1] == b[j]) + { + int comp = score[i - 1][j - 1] + 1; + if (comp < score[i + 1][j + 1]) score[i + 1][j + 1] = comp; + } + } + } + return score[a_len][b_len]; } + static void find_closest(const char *name, int name_len, Decl **decls, int *count_ref, Decl* matches[3], int *best_distance_ref) { int best_distance = *best_distance_ref; @@ -506,7 +523,7 @@ static void find_closest(const char *name, int name_len, Decl **decls, int *coun FOREACH(Decl *, decl, decls) { if (decl->visibility != VISIBLE_PUBLIC) continue; - int dist = levenshtein(name, name_len, decl->name, strlen(decl->name)); + int dist = damerau_levenshtein_distance(name, name_len, decl->name, strlen(decl->name)); if (dist < best_distance) { matches[0] = decl; @@ -527,9 +544,9 @@ static int module_closest_ident_names(Module *module, const char *name, Decl* ma matches[0] = matches[1] = matches[2] = NULL; Decl *best = NULL; - int distance = 2; int count = 0; int len = strlen(name); + int distance = MAX(1, (int)(len * 0.8)); FOREACH(CompilationUnit *, unit, module->units) { find_closest(name, len, unit->functions, &count, matches, &distance); @@ -627,7 +644,7 @@ static void sema_report_error_on_decl(SemaContext *context, NameResolve *name_re return; case 3: sema_error_at(context, span, "'%s::%s' could not be found, did you perhaps want '%s::%s', '%s::%s' or '%s::%s'?", - path_name, symbol, path_name, closest[0]->name, + path_name, symbol, path_name, closest[0]->name, path_name, closest[1]->name, path_name, closest[2]->name); return; default: diff --git a/src/compiler/sema_stmts.c b/src/compiler/sema_stmts.c index f44dedcec..cc3658b2b 100644 --- a/src/compiler/sema_stmts.c +++ b/src/compiler/sema_stmts.c @@ -1539,7 +1539,7 @@ static inline bool sema_analyse_foreach_stmt(SemaContext *context, Ast *statemen } if (!by_ref && value_by_ref) { - SEMA_ERROR(enumerator, "%s does not support 'foreach' with the value by reference.", type_quoted_error_string(enumerator->type)); + SEMA_ERROR(enumerator, "%s does not support 'foreach' by reference, but you iterate by value.", type_quoted_error_string(enumerator->type)); return false; } if (!decl_ok(len) || !decl_ok(by_val) || !decl_ok(by_ref)) return false; @@ -1566,12 +1566,6 @@ static inline bool sema_analyse_foreach_stmt(SemaContext *context, Ast *statemen } if (!sema_resolve_type_info(context, type_info, RESOLVE_TYPE_DEFAULT)) return false; - if (type_is_optional(type_info->type)) - { - SEMA_ERROR(type_info, "The variable may not be an optional."); - return false; - } - // Set up the optional index parameter Type *index_var_type = NULL; if (index) diff --git a/test/test_suite/statements/foreach_errors.c3 b/test/test_suite/statements/foreach_errors.c3 index 1b07279d9..e3a9b6988 100644 --- a/test/test_suite/statements/foreach_errors.c3 +++ b/test/test_suite/statements/foreach_errors.c3 @@ -33,7 +33,7 @@ fn void test4() fn void test5() { - foreach (int! y : z) foo(); // #error: The variable may not be an optional. + foreach (int! y : z) foo(); } fn void test6() diff --git a/test/test_suite/statements/foreach_r_errors.c3 b/test/test_suite/statements/foreach_r_errors.c3 index 490a8ba06..bd211ead3 100644 --- a/test/test_suite/statements/foreach_r_errors.c3 +++ b/test/test_suite/statements/foreach_r_errors.c3 @@ -33,7 +33,7 @@ fn void test4() fn void test5() { - foreach_r (int! y : z) foo(); // #error: The variable may not be an optional. + foreach_r (int! y : z) foo(); } fn void test6()