diff options
| author | 2026-10-14 08:30:01 +0000 | |
|---|---|---|
| committer | 2026-10-14 08:30:01 +0000 | |
| commit | d274751e89f75cc0f0e25c6e5e87ef91b64597ee (patch) | |
| tree | ef76f2cfe9d2a09f3fe5d27278cf775649bc162f /src/compiler | |
| parent | lsp: quick fixes for hints and bare library names; comment cleanup (diff) | |
zc: untyped local const, c precedence for && and ||, import fixes for every module
const name = value; inside a function declares a local constant that takes
the initializer's type. the let quick fix now writes a bare const.
the self-hosted compiler bound || tighter than &&, unlike zc0 and the
readme. no existing source mixed them without parentheses, so only new
code was affected.
missing-name diagnostics and their quick fixes now search every module:
rt, core, run, loaded files, and std, icon and actor scanned from source,
including static groups (std.Text.itoa). typos suggest module exports
(putc -> rt.put_c) and get a change-to fix.
Diffstat (limited to 'src/compiler')
| -rw-r--r-- | src/compiler/compiler.zc | 185 |
1 files changed, 166 insertions, 19 deletions
diff --git a/src/compiler/compiler.zc b/src/compiler/compiler.zc index 40d83cf..9c24af9 100644 --- a/src/compiler/compiler.zc +++ b/src/compiler/compiler.zc @@ -2128,7 +2128,8 @@ int add_binop(char* nm, int prec, char* code) { int p = intern(nm), i = binfind(p), a, n; if (i < 0) { reserve_binary(nbin + 1); i = nbin; nbin++; } bname[i] = p; bprec[i] = prec; - if (prec < 0) { bprec[i] = 0 - prec; blen[i] = prec; bseq[i] = 0; addkop(p); return i; } + // && (-1) and || (-2) short-circuit: they bind below |, and && above ||, as in c + if (prec < 0) { bprec[i] = 3 + prec; blen[i] = prec; bseq[i] = 0; addkop(p); return i; } bseq[i] = seqasm(code); blen[i] = seqtop - bseq[i]; addkop(p); // and its compound assignment, x op= y @@ -6861,6 +6862,11 @@ int primary() { } if (s >= 0 && skind[s] == K_GARR) { lit(sval[s]); if (stype[s]) xty = stype[s]; return C_PLAIN; } if (s >= 0 && skind[s] == K_ENH) return enhcall(s); + if (s < 0 && is_op(s_dot)) { + // Text.itoa with no std: a library's static function, reported as one missing name + peekat(0); + if (pk_tk == T_ID && export_owner(methname(n, pk_tv))) { next(); n = methname(n, tv); next(); } + } if (s < 0) s = fwdfn_at(n, callpos); if (is_op(s_lt) && (sflag[s] & F_GEN)) { // f<int, char*>(...) next(); ngbind = 0; @@ -7696,6 +7702,7 @@ int stmt_inner() { s = lookup(tv); if (s >= 0 && skind[s] == K_KW && (sflag[s] & F_STMT)) { kwcall(s, 0); return 0; } } + if (untyped_const()) { next(); localdecl(2); return 0; } if (tstart()) { localdecl(1); return 0; } if (tk == T_OP) { if (tv == s_semi) { next(); return 0; } @@ -7776,8 +7783,16 @@ int lt_note(int o, int t) { return 0; } // compile a local declaration, typed or let-style. +// does `const name =` start here? a const with no type takes the initializer's type. +int untyped_const() { + if (tk != T_ID || tv != s_const) return 0; + peekat(0); if (pk_tk != T_ID || istypename(pk_tv)) return 0; + peekat(1); return pk_tk == T_OP && (pk_tv == s_eq || pk_tv == s_semi || pk_tv == s_comma); +} +// typed is 1 after a type, 0 after let, and 2 after an untyped const. int localdecl(int typed) { - int n, i, c, k, t = 0, tg = 0, pp, kc, kp = ifkp, gty = 0, gok = !typed && ifon && !dry, o, vr; word iq, it; + int n, i, c, k, t = 0, tg = 0, pp, kc, kp = ifkp, gty = 0, gok = !typed && ifon && !dry, o, vr, ro = typed == 2; word iq, it; + if (ro) typed = 0; if (typed) { dtype(); t = dty; tg = dtag; if (t == tvoid) serr("a variable can't be void"); } if ((o3on || o3one) && !typed) serr("o3 variables need a type"); while (1) { @@ -7823,9 +7838,11 @@ int localdecl(int typed) { else ltype[i] = xty; } assigncheck(LV_LOCAL, i, c); + if (ro) { k = ltype[i]; if (!k) { k = tword; if (isnum(xty)) k = xty; } ltype[i] = tconst(k); } if (gok) { k = xty; if (isagg(k)) k = ltype[i]; if (!ifpure(k) || (gty && gty != k)) gok = 0; else gty = k; } } - } else if (gok && !larr[i]) gok = 0; // no initializer: nothing says what it is + } else if (ro) serr("a const needs an initializer"); + else if (gok && !larr[i]) gok = 0; // no initializer: nothing says what it is if (larr[i] && ltype[i][0] == TY_ARR && ltype[i][3] < 0) serr("T[] needs an initializer to determine its length"); if (larr[i]) { k = 1; if (ltype[i][0] == TY_ARR) k = ltype[i][3]; lafact[i] = af_new(k, 1, pp); laaddr[i] = lafact[i]; if (lafact[i]) lafact[i][5] = i; } af_declaration(o, lafact[i]); os_at(o, i); @@ -9508,10 +9525,27 @@ word editdist(word a, word b) { } return row[lb]; } +// the part of a symbol a bare name is compared with: put_c for rt.put_c, the whole name otherwise +char* similar_tail(char* name) { + int k = 0; char* tail = name; + while (name[k] && name[k] != '.') k++; + if (name[k] && module_named(name, k)) tail = name + k + 1; + return tail; +} +// is name[0..k) a module the compiler knows? +int module_named(char* name, int k) { + char* prefix = chk_resize(0, 0, k + 1); int known; + rt.copy_w(prefix, name, k); prefix[k] = 0; + known = module_known(prefix); + rt.free(prefix); + return known; +} // the defined function, global or constant whose name is closest to n, if it -// is close enough to be a likely typo; 0 when none is +// is close enough to be a likely typo; 0 when none is. a module's export is +// compared without its prefix, so putc finds rt.put_c int similar(int n) { int j = 0, best = 0, bd = 99, dist, k, lim = rt.len_w(n) / 3; + if (rt.len_w(n) < 3) return 0; // one edit turns g into gc: too short to guess from if (lim < 1) lim = 1; if (lim > 3) lim = 3; while (j < nsym) { @@ -9519,7 +9553,7 @@ int similar(int n) { if (k == K_FN || k == K_GVAR || k == K_GARR || k == K_CONST) { if (sflag[j] & F_DEF) { if (sname[j] != n) { - dist = editdist(n, sname[j]); + dist = editdist(n, similar_tail(sname[j])); if (dist < bd) { bd = dist; best = sname[j]; } } } @@ -9529,24 +9563,137 @@ int similar(int n) { if (bd > lim) return 0; return best; } -// end a compile unit from token from onward, and report what it defined. -// the module that exports a bare name, when one does: put_c is rt's. the compiler's -// own modules come first, then the libraries this unit has loaded. 0 when none does. -char* export_owner(char* name) { - int k = 0; word p = included_modules; +// a copy of s[a..e) as a string +char* lib_copy(char* s, int a, int e) { + char* b = chk_resize(0, 0, e - a + 1); rt.copy_w(b, s + a, e - a); b[e - a] = 0; + return b; +} +// group.name, as a fresh string: the lists below outlive the name table of a check +char* lib_join(char* group, char* name) { + int g = rt.len_w(group), n = rt.len_w(name); char* b = chk_resize(0, 0, g + n + 2); + rt.copy_w(b, group, g); b[g] = '.'; rt.copy_w(b + g + 1, name, n); b[g + n + 1] = 0; + return b; +} +// the declarations of a library, read from its source text, as a list of [next, tail, path]: +// a top-level name's path is the name itself, a static method's is Group.name. the scan +// only looks at shapes: a name before ( or a declarator's = ; , at the top, or inside a +// struct's braces before (. qualified calls (run.statement_hook(...)) are not declarations. +word lib_scan(char* s) { + int i = 0, depth = 0, paren = 0, init = 0, la = -1, le = 0, dotted = 0, after_dot = 0, words = 0, skip = 0, gpend = 0, gdepth = -1, c, a, k; + char* group = 0; char* path; word list = 0, entry, back = 0; + while (s[i]) { + c = s[i]; + if (c == '/' && s[i + 1] == '/') { while (s[i] && s[i] != 10) i++; continue; } + if (c == '/' && s[i + 1] == '*') { i = i + 2; while (s[i] && !(s[i] == '*' && s[i + 1] == '/')) i++; if (s[i]) i = i + 2; continue; } + if (c == 34 || c == 39) { i++; while (s[i] && s[i] != c) { if (s[i] == 92 && s[i + 1]) i++; i++; } if (s[i]) i++; la = -1; continue; } + if (c == 32 || c == 9 || c == 10 || c == 13) { i++; continue; } + if (isdig(c)) { while (isidc(s[i])) i++; la = -1; continue; } + if (isal(c)) { + a = i; while (isidc(s[i])) i++; + if (gpend) { group = lib_copy(s, a, i); gpend = 0; entry = chk_resize(0, 0, 3); entry[0] = list; entry[1] = group; entry[2] = group; list = entry; } + if (depth == 0 && paren == 0 && !init) { + if (words == 0 && ((i - a == 9 && rt.eq_w(lib_copy(s, a, i), "namespace")) || (i - a == 7 && rt.eq_w(lib_copy(s, a, i), "include")))) skip = 1; + if (i - a == 6 && rt.eq_w(lib_copy(s, a, i), "struct")) { gpend = 1; group = 0; } + words++; + } + la = a; le = i; dotted = after_dot; after_dot = 0; + continue; + } + after_dot = c == '.'; + if (c == '<' && s[i + 1] != '<' && s[i + 1] != '=' && paren == 0 && !init && (depth == 0 || (depth == 1 && group))) { + // generic parameters: int find<T>(...) is still find + k = 1; i++; while (s[i] && k) { if (s[i] == '<') k++; if (s[i] == '>') k--; i++; } + continue; + } + if (c == '(' && paren == 0 && !init && la >= 0 && !dotted && !skip) { + if (depth == 0 || (depth == 1 && group && gdepth == 0)) { + path = lib_copy(s, la, le); + entry = chk_resize(0, 0, 3); entry[0] = list; entry[1] = path; entry[2] = path; + if (depth == 1) entry[2] = lib_join(group, path); + list = entry; + } + } + if (((c == '=' && s[i + 1] != '=') || c == ';' || c == ',' || c == '[') && depth == 0 && paren == 0 && !init && la >= 0 && !dotted && !skip && words > 1) { + entry = chk_resize(0, 0, 3); entry[0] = list; entry[1] = lib_copy(s, la, le); entry[2] = entry[1]; list = entry; + } + if (c == '=' && s[i + 1] != '=' && depth == 0 && paren == 0) init = 1; + if (c == '(' || c == '[') paren++; + if ((c == ')' || c == ']') && paren > 0) paren--; + if (c == '{') { if (depth == 0 && paren == 0 && group) gdepth = 0; else if (depth == 0) group = 0; depth++; } + if (c == '}' && depth > 0) { depth--; if (depth == 0) { group = 0; gdepth = -1; words = 0; skip = 0; init = 0; } } + if (c == ';' && depth == 0 && paren == 0) { init = 0; words = 0; skip = 0; group = 0; } + if (c == ',' && depth == 0 && paren == 0) init = 0; + la = -1; i++; + } + // first declared first: Text.itoa comes before Bytes.itoa + while (list) { entry = list[0]; list[0] = back; back = list; list = entry; } + return back; +} +// the libraries under src/lib, scanned once: std and icon are embedded, actor is read like its include +word std_names, icon_names, actor_names; int names_read; +word lib_names(char* module) { + char* save = incres; + if (!names_read) { + names_read = 1; + std_names = lib_scan(std_text()); icon_names = lib_scan(icon_text()); + char* text = inc_open("src/lib/actor.zc"); + if (text) actor_names = lib_scan(text); + rt.free(text); + incres = save; + } + if (rt.eq_w(module, "std")) return std_names; + if (rt.eq_w(module, "icon")) return icon_names; + return actor_names; +} +// where name is in a library's list: Text.itoa is itself, a bare itoa is a top-level +// declaration first, then a static method of some group. 0 when it is not there. +char* lib_path(word list, char* name) { + word p = list; int k = 0; + while (p) { if (rt.eq_w(p[2], name)) return p[2]; p = p[0]; } while (name[k]) { if (name[k] == '.') return 0; k++; } - if (export_in("rt", name)) return "rt"; - if (export_in("core", name)) return "core"; - if (export_in("run", name)) return "run"; - while (p) { if (export_in(p[1], name)) return p[1]; p = p[0]; } - return 0; + p = list; + while (p) { if (rt.eq_w(p[1], name)) return p[2]; p = p[0]; } + return 0; +} +// the module that exports a bare name, and the path after its prefix: put_c is rt's put_c, +// itoa is std's Text.itoa. every owner, best first, is a list of [next, module, path]: the +// compiler's own modules, then the modules this unit loaded, then the libraries, which are +// read from their source whether this unit loaded them or not. +word export_list, export_tail; char* export_path; +int export_add(char* module, char* path) { + word p = export_list; + if (!path) return 0; + while (p) { if (rt.eq_w(p[1], module)) return 0; p = p[0]; } + p = chk_resize(0, 0, 3); p[1] = module; p[2] = path; + if (export_tail) export_tail[0] = p; else export_list = p; + export_tail = p; + return 0; +} +word export_owners(char* name) { + word p = included_modules; + export_list = 0; export_tail = 0; + export_add("rt", export_in("rt", name)); + export_add("core", export_in("core", name)); + export_add("run", export_in("run", name)); + while (p) { export_add(p[1], export_in(p[1], name)); p = p[0]; } + export_add("std", lib_path(lib_names("std"), name)); + export_add("icon", lib_path(lib_names("icon"), name)); + export_add("actor", lib_path(lib_names("actor"), name)); + return export_list; +} +char* export_owner(char* name) { + word p = export_owners(name); + if (!p) return 0; + export_path = p[2]; return p[1]; } -// does module define name: a function, a global, a constant or a type of it. -int export_in(char* module, char* name) { +// name, when module defines it: a function, a global, a constant or a type of it. 0 otherwise. +char* export_in(char* module, char* name) { int s = lookup_raw(methname(module, name)); if (s < 0) return 0; - return skind[s] != K_DOC && (skind[s] != K_FN || (sflag[s] & F_DEF)); + if (skind[s] != K_DOC && (skind[s] != K_FN || (sflag[s] & F_DEF))) return name; + return 0; } +// end a compile unit from token from onward, and report what it defined. int unit_end(int from) { int i = from, best; char* owner; effect_finish(from); @@ -9564,7 +9711,7 @@ int unit_end(int from) { if (owner) { rt.put_s("; did you mean to "); if (!module_imported(owner)) { rt.put_s("include "); rt.put_s(owner); rt.put_s(" and "); } - rt.put_s("use "); rt.put_s(owner); rt.put_c('.'); rt.put_s(sname[i]); rt.put_s("?"); + rt.put_s("use "); rt.put_s(owner); rt.put_c('.'); rt.put_s(export_path); rt.put_s("?"); } else if (best) { rt.put_s("; did you mean '"); rt.put_s(best); rt.put_s("'?"); } errend(); |
