diff options
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | src/compiler/compiler.zc | 124 | ||||
| -rw-r--r-- | tests/drivers/hint_fixes_test.js | 4 | ||||
| -rw-r--r-- | tests/errors/static_const_typo.zc | 5 | ||||
| -rw-r--r-- | tests/errors/static_typo.zc | 5 | ||||
| -rw-r--r-- | tests/langerrors/struct_const_runtime.zc | 6 | ||||
| -rw-r--r-- | tests/programs/struct_const.zc | 28 | ||||
| -rwxr-xr-x | tests/test.sh | 1 | ||||
| -rw-r--r-- | tests/typeerrors/field_typo.zc | 5 |
9 files changed, 173 insertions, 7 deletions
@@ -311,6 +311,8 @@ p.dist(q); underneath, a method is an ordinary function called `point.dist` whose first parameter is `point* this`, compiled once the struct is complete, so a method can call the ones declared after it. a field with the same name wins over a method. a method of a generic struct is a generic function over the struct's parameters, compiled for each set of arguments it is called with (the receiver says which), so `this.items[i]` and `sizeof(K)` inside it are exact. +a `static` function (`static fn name(...)` or `static int name(...)`) has no `this` and is called as `Struct.name(args)`, or `std.Text.itoa(n, buf)` for a library group. a struct can also hold untyped constants: `struct Text { const MAX = 24, MIN = 1; int len; }` (a type before the name, as in `const int MAX = 24;`, is read and dropped). the initializers are constant expressions, a constant takes no space in the struct, it is reached as `Text.MAX` (`std.Text.MAX` from a library), and a method or static function of the struct can use the bare name `MAX`. a misspelled field, static function or constant gets the same `; did you mean 'name'?` suggestion as an undefined function, and the language server offers it as a quick fix. + `fn(a, b) { ... }` in an expression is a closure. the value is a heap block: word 0 is the body's entry, and its captured cell pointers start at word 3. the vm only runs code in the code area, so the block is never executed. a call through a value checks whether the callee is at or above `HEAP_LO`. a plain function is called as it is. a closure is called with the block plus 3 as its environment and jumps to the entry in word 0. captured locals and parameters share cells with the enclosing function and sibling closures, so mutations stay visible after the closure is made and after its creator returns. captured owned objects remain alive with those cells. the block is an owned object; a fresh closure can be passed directly to a proved borrower, and returning one transfers it to the caller. `examples/learn/25-closures.zc` and `examples/learn/26-closure-state.zc` cover these. `fn:type(args)` declares a closure's result and parameter types. an owned closure can be returned from a factory with `.u`; its captured cells remain alive for the returned value. [the typed closure example](examples/learn/30-typed-closure.zc) covers this form. diff --git a/src/compiler/compiler.zc b/src/compiler/compiler.zc index 86e00c5..8335ab9 100644 --- a/src/compiler/compiler.zc +++ b/src/compiler/compiler.zc @@ -3981,6 +3981,44 @@ int dtype() { } return 0; } +// the struct whose body or methods are being compiled, when it declares constants: a bare +// name there is looked up as Struct.name before the global scope +char* mstruct; +// does a constant declaration start in a struct body? const NAME = e; const T NAME = e; +// (a field of a const type has no initializer, so the = tells them apart) +int struct_const_start() { + if (!is_id(s_const)) return 0; + peekat(0); if (pk_tk != T_ID) return 0; + peekat(1); + if (pk_tk == T_OP && pk_tv == s_eq) return 1; + if (pk_tk == T_ID) { peekat(2); return pk_tk == T_OP && pk_tv == s_eq; } + if (pk_tk == T_OP && pk_tv == s_star) { peekat(2); if (pk_tk != T_ID) return 0; peekat(3); return pk_tk == T_OP && pk_tv == s_eq; } + return 0; +} +// const NAME = e, NAME = e; inside struct sn: a compile-time constant named Struct.NAME that takes no +// space in the layout. the optional type is read and dropped, as in gconst. +int struct_const(char* sn) { + int off, s; char* cn; + next(); + if (tk == T_ID) { peekat(0); if (pk_tk == T_ID || (pk_tk == T_OP && pk_tv == s_star)) ptype(); } + mstruct = sn; + while (1) { + off = toff; cn = name(); + s = lookup(methname(sn, cn)); + if (s >= ubase && s >= 0) serrn(cn, "is defined twice in this struct"); + expect(s_eq); + s = addsym(methname(sn, cn), K_CONST, cexpr()); + rs_put(off, RS_SYM, rs_sym(s), s); + if (!is_op(s_comma) && !is_op(s_semi)) { // a call or a variable's value follows what cprim could read + serr("a struct constant must be a constant expression"); + while (!is_op(s_semi) && !is_op(s_rb) && tk != T_EOF) next(); + break; + } + if (!accept(s_comma)) break; + } + accept(s_semi); + return 0; +} // struct name { type field, ... } or struct name<A, B> { ... } // the struct keyword. fields go in a scratch buffer first (name, type pairs), // then are laid out: each field takes tsz(type) words, so offsets are words. @@ -3989,7 +4027,7 @@ int dtype() { // and call its siblings. a generic struct keeps its fields unresolved and // can't have methods; each instance is laid out later by tresolve. int kw_struct() { - char* n; word t; int s, nf = 0; word buf = chk_resize(0, 0, 64); int bufcap = 64, i; word fs; int off = 0, gen = 0, ft, nm = 0, mcap = STRUCT_METHODS; char** mn = chk_resize(0, 0, mcap); word mq = chk_resize(0, 0, mcap), mst = chk_resize(0, 0, mcap), mty = chk_resize(0, 0, mcap), mrt = chk_resize(0, 0, mcap), mrg = chk_resize(0, 0, mcap); int bo, k, stat = 0; char* fname = 0; + char* n; word t; int s, nf = 0; word buf = chk_resize(0, 0, 64); int bufcap = 64, i; word fs; int off = 0, gen = 0, ft, nm = 0, mcap = STRUCT_METHODS; char** mn = chk_resize(0, 0, mcap); word mq = chk_resize(0, 0, mcap), mst = chk_resize(0, 0, mcap), mty = chk_resize(0, 0, mcap), mrt = chk_resize(0, 0, mcap), mrg = chk_resize(0, 0, mcap); int bo, k, stat = 0; char* fname = 0; char* msv = mstruct; next(); n = module_symbol(name()); if (is_op(s_lt)) { // generic @@ -4011,6 +4049,7 @@ int kw_struct() { while (!is_op(s_rb)) { if (tk == T_EOF) { die("missing }"); break; } stat = 0; + if (struct_const_start()) { struct_const(n); continue; } if (is_id(s_unchecked)) { next(); stat = 2; } if (is_id(s_static)) { // static fn name(params) { body }, or static T name(params) { body }: a function of the struct, called as Struct.name(args), with no this next(); @@ -4099,6 +4138,7 @@ int kw_struct() { i = 0; while (i < nm) { fn_method_compile = 1; methdef(n, mn[i], mq[i], mst[i], mty[i], mrt[i], mrg[i]); fn_method_compile = 0; i++; } mdg = 0; + mstruct = msv; return s; } // ---- methods ---- @@ -6800,6 +6840,7 @@ int primary() { return C_PLAIN; } s = source_lookup(n); + if (mstruct && !ginst) { ms = lookup(methname(mstruct, n)); if (ms >= 0 && skind[ms] == K_CONST) s = ms; } if (s < 0 && rt.eq_w(n, "new")) s = source_lookup(s_new); if (s >= 0) rs_put(toff, RS_SYM, rs_sym(s), s); if (s >= 0 && skind[s] == K_KW) { @@ -6812,8 +6853,8 @@ int primary() { c = toff + toklen; next(); if (probe_off >= 0 && cmode && !dry && !ginst && incdepth == 0 && probe_off >= c && ((tk == T_ID && probe_off <= toff + toklen) || (tk != T_ID && probe_off <= toff))) { probe_static = n; probe_has = 3; chkabort = 1; } - k = toff; mn = name(); - ms = lookup(methname(n, mn)); + k = toff; sv = tpack(); mn = name(); + ms = lookup(methname(sname[s], mn)); if (ms >= 0) rs_put(k, RS_SYM, rs_sym(ms), ms); if (ms >= 0 && skind[ms] == K_CONST) { // Enum.MEMBER xvk = 1; xvv = sval[ms]; @@ -6829,7 +6870,11 @@ int primary() { if (!is_op(s_lp)) die("a static function is called with ( after its name"); return call(ms); } - serrn(mn, "is not a static function of this struct"); + if (!panic && !dry) { + c = tpack(); tunpack(sv); // the error sits on the member name, where the quick fix replaces it + errat(); rt.put_s(mn); rt.put_s(": is not a static function of this struct"); suggest_member(sname[s], 0, mn, 1); errend(); + tunpack(c); + } lit(0); xty = tword; return C_PLAIN; @@ -6949,7 +6994,7 @@ int incdec(int post, int dlt) { word field(int c) { int readonly = treadonly(xty); if_value = 0; - int n, fieldoff; word st; + int n, fieldoff, fieldpos, sv; word st; word borrowed = xaf, base_mask = effect_mask(c); af_use(xaf, tpack(), c); xaf = 0; xvk = 0; int i, b = xbor, k = 0, scanned = 0, ms, de, pmatch = 0; de = toff + toklen; next(); @@ -6958,7 +7003,7 @@ word field(int c) { if (tk == T_ID) { if (probe_off <= toff + toklen) pmatch = 1; } else if (probe_off <= toff) pmatch = 1; } - n = name(); fieldoff = nameoff; + fieldpos = tpack(); n = name(); fieldoff = nameoff; st = xty; if (st[0] == TY_ARR || st[0] == TY_DYN) { if (pmatch) probe_member(st); @@ -7023,7 +7068,11 @@ word field(int c) { } if (scanned) i = fldi; else i = tfindfield(st, n); if (i < 0) { - if (!panic && !dry) { errat(); tprint(st); rt.put_s(" has no field "); rt.put_s(n); errend(); } + if (!panic && !dry) { + sv = tpack(); tunpack(fieldpos); // the error sits on the field name, where the quick fix replaces it + errat(); tprint(st); rt.put_s(" has no field "); rt.put_s(n); suggest_member(st[2], st, n, 0); errend(); + tunpack(sv); + } xty = tword; lv = 1; lvk = LV_OTHER; xbor = -1; return C_PLAIN; } rs_put(fieldoff, RS_FIELD, field_decl(st, n), st[4][i * 3 + 1]); @@ -7651,6 +7700,7 @@ word cprim() { if (accept(s_lp)) { v = cexpr(); expect(s_rp); return v; } if (tk == T_ID) { s = lookup(tv); + if (mstruct && !ginst) { mc = lookup(methname(mstruct, tv)); if (mc >= 0 && skind[mc] == K_CONST) s = mc; } if (s >= 0 && (skind[s] == K_CONST || skind[s] == K_GARR || (skind[s] == K_FN && (sflag[s] & F_DEF)) || skind[s] == K_GVAR)) { next(); return sval[s]; @@ -9572,6 +9622,66 @@ int similar(int n) { if (bd > lim) return 0; return best; } +// the field or method of struct sn closest to n (statics is 0), or the static function or constant +// (statics is 1), when it is close enough to be a likely typo. the limits are those of similar. +char* similar_member(char* sn, word st, char* n, int statics) { + int j, bd = 99, dist, k, ln = rt.len_w(sn), lim = rt.len_w(n) / 3, nf; char* best = 0; char* tail; + if (rt.len_w(n) < 3) return 0; + if (lim < 1) lim = 1; + if (lim > 3) lim = 3; + if (!statics && st && st[0] == TY_STRUCT) { + nf = st[3]; k = 0; + while (k < nf) { + tail = st[4][k * 3]; dist = editdist(n, tail); + if (dist == 2 && swapped(n, tail)) dist = 1; + if (dist > 0 && dist < bd) { bd = dist; best = tail; } + k++; + } + } + j = 0; + while (j < nsym) { + k = skind[j]; + if (((k == K_FN && ((sflag[j] & F_STATIC) != 0) == statics) || (statics && k == K_CONST)) && rt.len_w(sname[j]) > ln + 1 && sname[j][ln] == '.') { + tail = sname[j] + ln + 1; + if (has_prefix(sname[j], sn, ln) && !has_dot(tail)) { + dist = editdist(n, tail); + if (dist == 2 && swapped(n, tail)) dist = 1; + if (dist > 0 && dist < bd) { bd = dist; best = tail; } + } + } + j++; + } + if (bd > lim) return 0; + return best; +} +// are a and b the same but for two neighbouring chars swapped (itao for itoa)? a swap counts as one typo. +int swapped(char* a, char* b) { + int i = 0, la = rt.len_w(a); + if (la != rt.len_w(b)) return 0; + while (a[i] && a[i] == b[i]) i++; + if (!a[i] || !a[i + 1] || a[i] != b[i + 1] || a[i + 1] != b[i]) return 0; + i = i + 2; + while (a[i] && a[i] == b[i]) i++; + return !a[i]; +} +// does s start with the first n chars of p? +int has_prefix(char* s, char* p, int n) { + int i = 0; + while (i < n && s[i] == p[i]) i++; + return i == n; +} +// does the string contain a dot? +int has_dot(char* s) { + int i = 0; + while (s[i]) { if (s[i] == '.') return 1; i++; } + return 0; +} +// ends an error message with "; did you mean 'x'?" when a member of struct sn is close to n +int suggest_member(char* sn, word st, char* n, int statics) { + char* best = similar_member(sn, st, n, statics); + if (best) { rt.put_s("; did you mean '"); rt.put_s(best); rt.put_s("'?"); } + return 0; +} // 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; diff --git a/tests/drivers/hint_fixes_test.js b/tests/drivers/hint_fixes_test.js index 20d3dd1..a388911 100644 --- a/tests/drivers/hint_fixes_test.js +++ b/tests/drivers/hint_fixes_test.js @@ -76,5 +76,9 @@ fixes('a misspelled local function is corrected', 'int count_items() { return 1; 'int count_items() { return 1; }\nfn main() {\n\treturn count_items();\n}\n', 'change to `count_items`'); fixes('a qualified name from a file library adds its include', 'include rt;\nfn main() {\n\treturn actor.vector();\n}\n', 'actor', 0, 'include rt;\ninclude actor;\nfn main() {\n\treturn actor.vector();\n}\n', 'include actor'); +fixes('a misspelled field is corrected', 'include rt;\nstruct point { int length, width; }\nfn main() {\n\tpoint* p = rt.new(point);\n\treturn p.lenght;\n}\n', 'lenght', 0, + 'include rt;\nstruct point { int length, width; }\nfn main() {\n\tpoint* p = rt.new(point);\n\treturn p.length;\n}\n', 'change to `length`'); +fixes('a misspelled static function of a library group is corrected', 'include rt;\ninclude std;\nfn main() {\n\treturn std.Text.itao(5, 0);\n}\n', 'itao', 0, + 'include rt;\ninclude std;\nfn main() {\n\treturn std.Text.itoa(5, 0);\n}\n', 'change to `itoa`'); console.log(ok + ' hint fix checks passed, ' + bad + ' failed'); process.exit(bad ? 1 : 0); diff --git a/tests/errors/static_const_typo.zc b/tests/errors/static_const_typo.zc new file mode 100644 index 0000000..c7f2b7b --- /dev/null +++ b/tests/errors/static_const_typo.zc @@ -0,0 +1,5 @@ +namespace; + +// expect: MAXX: is not a static function of this struct; did you mean 'MAX'? +struct Text { const MAX = 24; int len; }; +fn main() { return Text.MAXX; } diff --git a/tests/errors/static_typo.zc b/tests/errors/static_typo.zc new file mode 100644 index 0000000..97f6513 --- /dev/null +++ b/tests/errors/static_typo.zc @@ -0,0 +1,5 @@ +namespace; + +// expect: itao: is not a static function of this struct; did you mean 'itoa'? +struct Text { int len; static int itoa(int n) { return n; } }; +fn main() { return Text.itao(1); } diff --git a/tests/langerrors/struct_const_runtime.zc b/tests/langerrors/struct_const_runtime.zc new file mode 100644 index 0000000..b55626e --- /dev/null +++ b/tests/langerrors/struct_const_runtime.zc @@ -0,0 +1,6 @@ +namespace; + +// expect: a struct constant must be a constant expression +int f(int x) { return x; } +struct Text { const MAX = f(3); int len; } +fn main() { return 0; } diff --git a/tests/programs/struct_const.zc b/tests/programs/struct_const.zc new file mode 100644 index 0000000..59af8fd --- /dev/null +++ b/tests/programs/struct_const.zc @@ -0,0 +1,28 @@ +namespace; +include rt; + +struct Text { + const MAX = 24, MIN = 1; + const int WIDE = MAX * 2; + int len, tag; + static int top() { return MAX; } + static int span() { return MAX - MIN; } + int room() { return MAX - this.len; } + int total() { int[MIN + 2] a; a[2] = WIDE; return a[2] + this.tag; } +} + +const MAX = 99; + +int main() { + Text t; + t.len = 5; t.tag = 7; + rt.put_n(Text.MAX); rt.put_c(32); + rt.put_n(Text.MIN + Text.WIDE); rt.put_c(32); + rt.put_n(Text.top()); rt.put_c(32); + rt.put_n(Text.span()); rt.put_c(32); + rt.put_n(t.room()); rt.put_c(32); + rt.put_n(t.total()); rt.put_c(32); + rt.put_n(t.len + t.tag); rt.put_c(32); + rt.put_n(MAX); rt.put_c(10); + return 0; +} diff --git a/tests/test.sh b/tests/test.sh index 46625a6..7ae7b9d 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -123,6 +123,7 @@ run(){ # $1 = vm command, $2 = label chk "$($vm < tests/programs/generics.zc | md5sum)" "$(md5sum < tests/programs/generics.expected)" "$l generic functions are compiled per type arguments" chk "$($vm < tests/programs/genmeth.zc | md5sum)" "$(md5sum < tests/programs/genmeth.expected)" "$l methods of generic structs are compiled per type arguments" chk "$($vm < tests/programs/static.zc)" "14 5 9" "$l static functions of a struct are called as Struct.name(args)" + chk "$($vm < tests/programs/struct_const.zc)" "24 49 24 23 19 55 12 99" "$l constants declared in a struct are reached as Struct.NAME, and by bare name in its methods" chk "$($vm < tests/programs/methodown.zc)" "7 9" "$l struct methods and functions return owned objects (.u)" chk "$($vm < tests/programs/typedmethods.zc)" "14 5 13" "$l struct methods declare a return type and typed parameters" chk "$($vm < tests/programs/enumscope.zc)" "0 5 6 blue" "$l enum members are reached as Enum.MEMBER, also in a case" diff --git a/tests/typeerrors/field_typo.zc b/tests/typeerrors/field_typo.zc new file mode 100644 index 0000000..aa64209 --- /dev/null +++ b/tests/typeerrors/field_typo.zc @@ -0,0 +1,5 @@ +namespace; + +// expect: point has no field lenght; did you mean 'length'? +struct point { int length, width; }; +fn main() { point* p; return p.lenght; } |
