diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/compiler/compiler.zc | 185 | ||||
| -rw-r--r-- | src/lsp/server.zc | 75 |
2 files changed, 218 insertions, 42 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(); diff --git a/src/lsp/server.zc b/src/lsp/server.zc index 70f9efd..2587c3b 100644 --- a/src/lsp/server.zc +++ b/src/lsp/server.zc @@ -3149,10 +3149,21 @@ int do_completion(int i) { // import fixes are derived from current compiler errors and qualified exports. // each edit is tied to the document version that was checked. word ca_list; int ca_i, ca_start, ca_end, ca_insert, ca_crlf; -int ca_add(char*.d owner, int qualify) { - word p = ca_list; +// one fix: include owner, and when path is set, replace from..ca_end with owner.path. +// path belongs to the compiler's export list and lives until the next check. +int ca_add(char* owner, char* path, int from) { + word p = ca_list; int n = rt.len_w(owner); char* copy; while (p) { if (rt.eq_w(p[1], owner)) return 0; p = p[0]; } - p = rt.new(3); p[0] = ca_list; p[1] = owner; p[2] = qualify; ca_list = p; + copy = rt.new(n + 1); rt.copy_w(copy, owner, n + 1); + p = rt.new(4); p[0] = ca_list; p[1] = copy; p[2] = path; p[3] = from; ca_list = p; + return 0; +} +// the fixes for every module that exports name, best first +int ca_owners(char* name, int from) { + word p = run.export_owners(name), q = 0, next; + // ca_add prepends, so the list goes in backwards and comes out best first + while (p) { next = rt.new(2); next[0] = q; next[1] = p; q = next; p = p[0]; } + while (q) { p = q[1]; ca_add(p[1], p[2], from); next = q[0]; rt.free(q); q = next; } return 0; } int ca_import_point(int i) { @@ -3176,7 +3187,7 @@ int ca_import_point(int i) { int ca_newline() { if (ca_crlf) hov_put(13); hov_put(10); return 0; } // quick fixes for hints. each record is one edit of the document: it replaces start..end // with text built from its kind. the hint's own range and words say what to change. -const int HF_RENAME = 1, HF_CONST = 2, HF_SHIFT = 3, HF_STACK = 4; +const int HF_RENAME = 1, HF_CONST = 2, HF_SHIFT = 3, HF_STACK = 4, HF_TYPO = 5; word hf_list; // [next, kind, start, end, a, b] int hf_add(int kind, int start, int end, int a, int b) { word p = lsp_grow(0, 0, 6); @@ -3241,7 +3252,7 @@ int hf_operand(int i, int op) { } if (o == op || depth) return 0 - 1; b = o; while (b > 0 && (dc(doff[i], b - 1) == 32 || dc(doff[i], b - 1) == 9)) b--; - if (b > 0) { c = dc(doff[i], b - 1); if (c == '*' || c == '/' || c == '%' || c == '.' || c == '-' && b > 1 && dc(doff[i], b - 2) == '-') return 0 - 1; } + if (b > 0) { c = dc(doff[i], b - 1); if (c == '*' || c == '/' || c == '%' || c == '.' || (c == '-' && b > 1 && dc(doff[i], b - 2) == '-')) return 0 - 1; } return o; } // is the text at o the word w, ending there @@ -3294,6 +3305,7 @@ int hf_title(word p) { if (p[1] == HF_RENAME) { hov_text("rename `"); while (k < p[3]) { hov_put(dc(doff[ca_i], k)); k++; } hov_text("` to _"); } else if (p[1] == HF_CONST) { hov_text("make `"); k = p[3]; while (k < p[4]) { hov_put(dc(doff[ca_i], k)); k++; } hov_text("` a const"); } else if (p[1] == HF_SHIFT) { hov_text("write << "); hov_number(p[4]); } + else if (p[1] == HF_TYPO) { hov_text("change to `"); k = p[4]; while (k < p[5]) { hov_put(dgb[k]); k++; } hov_put('`'); } else { hov_text("use a stack buffer"); } return 0; } @@ -3302,9 +3314,9 @@ int hf_text(word p) { hn = 0; if (p[1] == HF_RENAME) hov_put('_'); else if (p[1] == HF_CONST) { + // let becomes a bare const, which takes the initializer's type hov_text("const "); - if (hf_word(ca_i, p[2], "let")) hov_text("int "); - else { k = p[2]; while (k < p[3]) { hov_put(dc(doff[ca_i], k)); k++; } } + if (!hf_word(ca_i, p[2], "let")) { k = p[2]; while (k < p[3]) { hov_put(dc(doff[ca_i], k)); k++; } } } else if (p[1] == HF_SHIFT) { // (operand << k): the operand's text runs from the record's start to the *, without trailing blanks @@ -3313,6 +3325,7 @@ int hf_text(word p) { while (k < c) { hov_put(dc(doff[ca_i], k)); k++; } hov_text(" << "); hov_number(p[4]); hov_put(')'); } + else if (p[1] == HF_TYPO) { k = p[4]; while (k < p[5]) { hov_put(dgb[k]); k++; } } else { hov_put('['); k = p[4]; while (k < p[5]) { hov_put(dc(doff[ca_i], k)); k++; } hov_put(']'); } return 0; } @@ -3335,7 +3348,7 @@ int gen_code_actions() { if (!first) wc(','); first = 0; imported = run.module_imported(p[1]); hn = 0; if (!imported) { hov_text("include "); hov_text(p[1]); } - if (p[2]) { if (!imported) hov_text(" and "); hov_text("use "); hov_text(p[1]); hov_put('.'); hov_text(hname); } + if (p[2]) { if (!imported) hov_text(" and "); hov_text("use "); hov_text(p[1]); hov_put('.'); hov_text(p[2]); } ws("{\"title\":"); wjs(hov, hn); ws(",\"kind\":\"quickfix\","); if (single) ws("\"isPreferred\":true,"); ws("\"edit\":{\"documentChanges\":[{\"textDocument\":{\"uri\":"); @@ -3348,13 +3361,33 @@ int gen_code_actions() { } if (p[2]) { if (!imported) wc(','); - ws("{\"range\":{\"start\":"); wpos(ca_start); ws(",\"end\":"); wpos(ca_end); ws("},\"newText\":"); - hn = 0; hov_text(p[1]); hov_put('.'); hov_text(hname); wjs(hov, hn); wc('}'); + ws("{\"range\":{\"start\":"); wpos(p[3]); ws(",\"end\":"); wpos(ca_end); ws("},\"newText\":"); + hn = 0; hov_text(p[1]); hov_put('.'); hov_text(p[2]); wjs(hov, hn); wc('}'); } ws("]}]}}"); p = p[0]; } ws("]}"); return 0; } +// "did you mean 'name'?" under the cursor: the name replaces the word. a module's export +// (rt.put_c) also gets its include when the document lacks it. +char[64] ca_owner; char[64] ca_path; +int ca_typo(int at) { + int n = dgb[at + 4], e = n - 2, b, dot, k; + if (!dg_ends(at, "'?")) return 0; + b = e; while (b > 0 && dgb[at + 5 + b - 1] != 39) b--; + if (b == 0 || e - b >= 63) return 0; + dot = b; while (dot < e && dgb[at + 5 + dot] != '.') dot++; + if (dot < e) { + k = 0; while (b + k < dot) { ca_owner[k] = dgb[at + 5 + b + k]; k++; } ca_owner[k] = 0; + if (run.module_known(ca_owner)) { + k = 0; while (dot + 1 + k < e) { ca_path[k] = dgb[at + 6 + dot + k]; k++; } ca_path[k] = 0; + ca_add(ca_owner, ca_path, ca_start); + return 0; + } + } + hf_add(HF_TYPO, ca_start, ca_end, at + 5 + b, at + 5 + e); + return 0; +} int do_code_actions(int i) { int at = 0, error = -1, s, dot, k, n, base = doff[i], start, end, cursor, limit, sl = hline, sc = hchar; ca_list = 0; hf_list = 0; ca_i = i; @@ -3375,22 +3408,18 @@ int do_code_actions(int i) { while (start > 0 && (hid(dc(base, start - 1)) || dc(base, start - 1) == '.')) start--; dot = start; while (dot < ca_end && dc(base, dot) != '.') dot++; if (dot < ca_end) { + // rt.put_c without include rt needs only the include; Text.itoa without std is std.Text.itoa char* owner = rt.new(dot - start + 1); k = start; n = 0; while (k < dot) { owner[n] = dc(base, k); n++; k++; } owner[n] = 0; - if (run.module_known(owner) && !run.module_imported(owner)) ca_add(d*owner, 0); else rt.free(owner); - } else { - s = 0; - while (s < run.nsym) { - if (run.skind[s] != run.K_DOC) { - char* symbol = run.sname[s]; dot = 0; while (symbol[dot] && symbol[dot] != '.') dot++; - if (symbol[dot] == '.' && rt.eq_w(symbol + dot + 1, hname)) { - char* owner = rt.new(dot + 1); rt.copy_w(owner, symbol, dot); owner[dot] = 0; - if (run.module_known(owner)) ca_add(d*owner, 1); else rt.free(owner); - } - } - s++; + if (run.module_known(owner) || run.module_loaded(owner)) { if (!run.module_imported(owner)) ca_add(owner, 0, 0); } + else { + char* dotted = rt.new(ca_end - start + 1); k = start; n = 0; + while (k < ca_end) { dotted[n] = dc(base, k); n++; k++; } dotted[n] = 0; + ca_owners(dotted, start); rt.free(dotted); } - } + rt.free(owner); + } else ca_owners(hname, ca_start); + if (!ca_list) ca_typo(error); send(gen_code_actions); return 0; } |
