diff options
| author | 2026-10-14 09:39:22 +0000 | |
|---|---|---|
| committer | 2026-10-14 09:39:22 +0000 | |
| commit | 6bb3d79cbd853601623095b497a1807489c3ef6e (patch) | |
| tree | 188bc9ceb6d6db1670427f4355529eb8d46d1494 | |
| parent | todo: record the reverted unchecked trial and what it showed (diff) | |
add c-style typedef and a built-in string alias
typedef T name; binds name to the type record of T, at the top level or
in a function (scoped to its block). string is a built-in alias for char*.
A type name straight before , or ) in a parameter list is the parameter's
name, so fn f(string) still works. The language server shows typedef T
name; on hover and highlights typedef as a keyword.
| -rw-r--r-- | README.md | 4 | ||||
| -rw-r--r-- | editors/vscode/syntaxes/zc.tmLanguage.json | 2 | ||||
| -rw-r--r-- | src/compiler/compiler.zc | 107 | ||||
| -rw-r--r-- | src/lsp/server.zc | 50 | ||||
| -rw-r--r-- | tests/drivers/lsp_test.js | 18 | ||||
| -rw-r--r-- | tests/langerrors/typedef_generic_bare.zc | 6 | ||||
| -rw-r--r-- | tests/langerrors/typedef_local_redefine.zc | 8 | ||||
| -rw-r--r-- | tests/langerrors/typedef_redefine.zc | 6 | ||||
| -rw-r--r-- | tests/langerrors/typedef_scope.zc | 8 | ||||
| -rw-r--r-- | tests/langerrors/typedef_string_redefine.zc | 5 | ||||
| -rw-r--r-- | tests/langerrors/typedef_tag.zc | 5 | ||||
| -rw-r--r-- | tests/langerrors/typedef_unknown.zc | 5 | ||||
| -rw-r--r-- | tests/langerrors/typedef_variable_clash.zc | 8 | ||||
| -rw-r--r-- | tests/langerrors/typedef_void_variable.zc | 5 | ||||
| -rw-r--r-- | tests/programs/typedef.zc | 89 | ||||
| -rwxr-xr-x | tests/test.sh | 1 | ||||
| -rw-r--r-- | tests/typeerrors/typedef_mismatch.zc | 6 | ||||
| -rw-r--r-- | tests/typeerrors/typedef_pointer.zc | 7 |
18 files changed, 326 insertions, 14 deletions
@@ -169,6 +169,8 @@ fn main() { declarations are C-style, type first: `int a = 1, b;`, `node* p = rt.new(node);`, `char[4] buf;`, `point origin;`. `let` declares without a type: it infers `char*` from a word-string literal and `bstr` from a packed byte-string literal, infers other pointers and function types from the initializer, uses a pointer alias for an in-place array or struct, and keeps numbers as plain words. `let a[10];` is an untyped array. `const k = 5;` inside a function is a local constant with no type: it takes the initializer's type, needs an initializer, and can't be assigned. statements are declarations, `if`/`else`, `while`, `do`/`while`, `for`, `switch`, `break`, `continue`, `return`, `{ }` blocks, `asm { }` and expressions. `do` body `while (` cond `);` runs the body once before the test, and `continue` jumps to the test. `enum color { RED, GREEN = 5, BLUE };` makes global constants, and `color` is a type of its own in typed code (`typecheck on;`): a member takes the enum, a plain int does not become a `color`, and a `color` still becomes an `int`. untyped code treats the members as plain ints. each member is also reached as `color.BLUE`, in an expression or a `case` label. a member can carry fields too: `enum shape { circle { int r; }, rect { int w; int h; }, dot };` makes the constructors `shape.circle(3)` and `shape.rect(4, 5)`, which return an owned `shape*`, and `shape.dot()`. the tag is the word at offset 0 of the object. a `switch` over a `shape*` matches a member with `case circle(r):`, which binds its fields in that arm only. a switch on a tagged enum that leaves out a member and has no `default` gets a warning on its line (not an error). `(T)e` is a cast with the same meaning as `e as T`: the bits stay, the type changes. it has to be a simple type the parser can see in a few tokens, `int` or `*node`, not a nested array. `include "path";` reads a file and compiles it at that point, signatures first, so a library can live in its own file. each file has its own namespace and imports; dependencies included by a file do not import into the caller. `u`, `m` and `d` are ordinary names. a tag is `.u`, `.m` or `.d`, and `u*x` with no space before the star is still a recast. `switch (e) { case 1, 2: ... default: ... }` takes constant labels, and its cases don't fall through: each ends where the next begins, and `break` leaves early. operators follow C precedence, including `c ? a : b`, plus `p.x` and `p->x` (the same thing: `.` goes through a pointer on its own), `e as T` to change a type without changing the bits, and the casts below. comparisons and `&& ||` give 0 or 1. `/`, `%` and `<<` compile to native enhancement opcodes. character literals, `0x` hex and the usual escapes are there. +`typedef T name;` gives a type another name, as in c, at the top level or inside a function (where it lasts to the end of its block, like a local): `typedef node* link;`, `typedef int[4] quad;`, `typedef int(int, int) binop;`, `typedef list<int> ints;`. `type name = T;` says the same thing. a typedef is only a new spelling: `link` and `node*` are the very same type record, so they mix freely in declarations, parameters, results, casts, `sizeof`, struct fields, generic arguments and `typecheck on;` code, and they print as the target type. `T` can be any type the parser accepts, but not a tag (`.u`, `.m` and `.d` belong to a declaration). a typedef can't name a type that isn't declared yet, so a struct comes before the typedefs of its pointers; using a name that is already defined is an error, and a typedef of `void` is `void` (a variable of it is the usual error). `string` is built in as a typedef of `char*`, so `string s = "hi";` and `string f(string a)` work with no declaration; a function, variable or parameter of the same name shadows it. + functions are declared like C, return type first: `int f(int x)`, `node*.u make()`, `.u grab()` (a tag on its own), `void log(char* s)`. `fn f(a, b)` starts without explicit parameter or return types; constraints from calls and returned values propagate through callers and callees until inference reaches a fixed point, including recursive calls anchored by typed callees. conflicting argument types or an unknown reassignment leave the affected type unresolved instead of choosing a type. the learning example [32](examples/learn/32-inferred-calls.zc) shows this propagation and an owned raw-list return. a generic function lists its type parameters after its name, `T first<T>(*list<T> l)`, the same place a call gives explicit ones, `first<int>(l)`. the return type can use them before they're declared: while it's read, an unknown name is taken as a type parameter, and the list after the name has to confirm it. function types are written the same way, `int(int, *char)`, and `fn(int)` for one that returns a word. the earlier `fn f(...) -> T` spelling is gone. array types put the element type first. `T[n]` has its size in the type and lives in place. `T[]` also lives in place; its length is inferred from an initializer such as `int[] a = {1, 2, 3};` and cannot grow. `T[?]` can also be initialized in a function with a list, such as `int[?] a = {1, 2};`. it is a growable array: a small handle object that owns its items, made with `rt.new(T[?])` or `rt.new(T[?], n)`, indexed with a bounds check, and changed with `push(a, x)`, `pop(a)` and `resize(a, n)`. `len(a)` works on both (for `T[n]` it's the constant) and `cap(a)` gives the room before the next grow. `fixed T[?]` is a `T[?]` that can't change size: `rt.new(fixed T[?], n)` needs the length, and `push`, `pop` and `resize` on it are compile errors. a `fixed T[?]` can't be passed where a `T[?]` is expected, the other way round is fine, and a constant index outside a `T[n]` is caught at compile time. the items belong to the array, so freeing it frees them, and objects stored into a `T*[?]` are its children like any container's. @@ -294,7 +296,7 @@ the kinds are `word`, `int`, `char`, `bool`, `void`, pointers `T*`, arrays `T[n] ### pointer types -a pointer type places its star after the type, like c: `int*`, `point*`, `vec<T>*`, `vec<point*>*`, `point**`. a tag follows the stars (`vec<T>*.u`, `int*.d x`). there is no prefix form: `*int` as a type is an error, and a star in front of an expression is only ever a dereference. a pointer to an array or to a function type is spelled with a `type` alias (`type row = int[3];` then `row*`), because `int*[3]` reads as an array of pointers. `tprint`, and so error messages and reflection, print the same form. +a pointer type places its star after the type, like c: `int*`, `point*`, `vec<T>*`, `vec<point*>*`, `point**`. a tag follows the stars (`vec<T>*.u`, `int*.d x`). there is no prefix form: `*int` as a type is an error, and a star in front of an expression is only ever a dereference. a pointer to an array or to a function type is spelled with a `type` or `typedef` alias (`type row = int[3];` or `typedef int[3] row;`, then `row*`), because `int*[3]` reads as an array of pointers. `tprint`, and so error messages and reflection, print the same form. ### methods and closures diff --git a/editors/vscode/syntaxes/zc.tmLanguage.json b/editors/vscode/syntaxes/zc.tmLanguage.json index 36f1c63..6f76e03 100644 --- a/editors/vscode/syntaxes/zc.tmLanguage.json +++ b/editors/vscode/syntaxes/zc.tmLanguage.json @@ -87,7 +87,7 @@ }, { "name": "keyword.declaration.zc", - "match": "\\b(fn|let|struct|type|enum|const|enhance|micro|vm)\\b" + "match": "\\b(fn|let|struct|type|typedef|enum|const|enhance|micro|vm)\\b" }, { "name": "keyword.other.zc", diff --git a/src/compiler/compiler.zc b/src/compiler/compiler.zc index 86e00c5..7619b64 100644 --- a/src/compiler/compiler.zc +++ b/src/compiler/compiler.zc @@ -285,8 +285,12 @@ int fldst, fldi; // the one struct a bare address's field name resolved to char*[8] tpn; int ntp; // type parameters in scope // reading a return type: unknown names are type parameters declared after the function's name int tvauto; +// the typedefs of the blocks being compiled: three words each (the symbol, the block depth and the +// compile context it was made in), newest last. a block's end unlinks them from their names. +word tdstk; int ntd, tdcap; word readonly_types; int dty, dtag; // result of dtype() +int s_typedef; int s_lt, s_gt, s_shr, s_le, s_ge, s_eqeq, s_ne, s_plus, s_struct, s_type, s_typecheck, s_docs, s_small, s_std, s_icon, s_on, s_off, s_new, s_new_t; int s_o3, s_lfa, s_outside, s_new_raw, s_new_oz, s_oob; int s_ld8, s_st8, s_ld16, s_st16, s_ld32, s_st32; @@ -1102,6 +1106,7 @@ int source_lookup(char* name) { // drop the symbols above index to, which ends a scope. int popsyms(int to) { while (nsym > to) { nsym--; sname[nsym][-1] = snext[nsym]; } + while (ntd && tdstk[ntd * 3 - 3] >= to) ntd--; // local typedefs made above to went with them return 0; } // look a name up from a plain string. @@ -2952,6 +2957,7 @@ int block_close(int save) { while (i < save) { if (lhome[i] >= fdepth) { lstate[i] = S_DEAD; lhome[i] = fdepth - 1; } i++; } local_drop(save); fdrop(save); + td_end(fdepth); return 0; } @@ -4348,6 +4354,89 @@ int kw_type() { expect(s_semi); return 0; } +// does a parameter start with a type? a type name straight before , or ) is the parameter's +// own name (fn f(string)), not a type, since a typed parameter always has a name after it. +int paramstart() { + if (tk == T_OP) return tv == s_lk; + if (tk != T_ID) return 0; + if (tv == s_fn) return 1; + if (!istypename(tv)) return 0; + peek(); + return !(pk_tk == T_OP && (pk_tv == s_comma || pk_tv == s_rp)); +} +// does a typedef start here? typedef followed by a name is one, so typedef can still be +// a variable or a function's name. +int typedef_ahead() { + if (tk != T_ID || tv != s_typedef) return 0; + peekat(0); + return pk_tk == T_ID; +} +// unlink the symbol s from its name, leaving the rest of the name's chain as it was. +int td_unlink(int s) { + char* nm = sname[s]; int i = nm[-1] - 1; + if (i == s) { nm[-1] = snext[s]; return 0; } + while (i >= 0 && snext[i] - 1 != s) i = snext[i] - 1; + if (i >= 0) snext[i] = snext[s]; + return 0; +} +// end the scope of the local typedefs made at block depth depth or deeper in this +// compile context (and of any made in a context nested inside it). +int td_end(int depth) { + int k; + while (ntd) { + k = ntd * 3 - 3; + if (tdstk[k + 2] < ctxsp || (tdstk[k + 2] == ctxsp && tdstk[k + 1] < depth)) break; + td_unlink(tdstk[k]); + ntd--; + } + return 0; +} +// is the name q (as a symbol is named) already a typedef of the block being compiled? +int td_here(char* q) { + int k = ntd; + while (k) { + k--; + if (tdstk[k * 3 + 2] != ctxsp || tdstk[k * 3 + 1] != fdepth) break; + if (sname[tdstk[k * 3]] == q) return 1; + } + return 0; +} +// typedef T name; +// the C spelling of type name = T;. name becomes another way to write T: the very same +// type record, so the two are interchangeable everywhere. at the top level the name is +// global; in a function it lasts to the end of its block, like a local. a name that is +// already in use is an error, except the same type under the same name again. a tag +// (.u, .m, .d) belongs to a declaration, not to a type, so a typedef has none. +int kw_typedef() { + char* n; char* q; word t; int s, at, clash = 0, local = infn; + next(); + t = ptype(); + if (panic) return 0; + if (is_op(s_dot)) { die("a typedef names a type, which has no tag: .u, .m and .d go on a declaration"); return 0; } + at = toff; n = name(); + if (panic) return 0; + q = module_symbol(n); + if (local) { + s = findloc(n); + if ((s >= 0 && lhome[s] >= fdepth) || td_here(q)) clash = 1; + } else { + s = lookup_raw(q); + if (s < 0 && module_name) { s = lookup_raw(n); if (s >= 0 && skind[s] != K_TYPE && skind[s] != K_KW) s = 0 - 1; } + if (s >= 0 && skind[s] == K_TYPE && sval[s] == t) return expect(s_semi); // the same typedef again + if (s >= 0) clash = 1; + } + if (clash) serrn(n, "already defined"); + else { + s = addsym(n, K_TYPE, t); + rs_put(at, RS_SYM, rs_sym(s), s); + if (local) { + if (ntd == tdcap) { if (tdcap) tdcap = tdcap * 2; else tdcap = 16; tdstk = chk_resize(tdstk, ntd * 3, tdcap * 3); } + tdstk[ntd * 3] = s; tdstk[ntd * 3 + 1] = fdepth; tdstk[ntd * 3 + 2] = ctxsp; ntd++; + } + } + expect(s_semi); + return 0; +} // (T) or (T*) ahead of the current '(', within the lookahead we have // the parser sees ( and has to decide between a parenthesized expression and a // cast. a cast is ( type stars ) so peek ahead through the stars to the ). @@ -5516,6 +5605,7 @@ int types_init() { addsym(tword[2], K_TYPE, tword); addsym(tint[2], K_TYPE, tint); addsym(tchar[2], K_TYPE, tchar); addsym(tbool[2], K_TYPE, tbool); addsym(tvoid[2], K_TYPE, tvoid); addsym(tfloat[2], K_TYPE, tfloat); addsym(intern("f64"), K_TYPE, tfloat); + addsym(intern("string"), K_TYPE, tptr(tchar)); // string is char*, as if by typedef char* string; // f32: single precision. a variable holds the rounded value as a double; an element // of an array or pointer of f32 is packed two to a word, like the sized integers f32p = tnew(TY_FLOAT, 1, intern("f32"), 0); @@ -5530,7 +5620,7 @@ int types_init() { s_le = intern("<="); s_ge = intern(">="); s_eqeq = intern("=="); s_ne = intern("!="); s_plus = intern("+"); - s_struct = intern("struct"); s_type = intern("type"); s_typecheck = intern("typecheck"); s_docs = intern("docs"); s_std = intern("std"); s_icon = intern("icon"); s_small = intern("small"); + s_struct = intern("struct"); s_type = intern("type"); s_typedef = intern("typedef"); s_typecheck = intern("typecheck"); s_docs = intern("docs"); s_std = intern("std"); s_icon = intern("icon"); s_small = intern("small"); s_on = intern("on"); s_off = intern("off"); s_new = intern("rt.new"); @@ -6445,7 +6535,7 @@ int closure() { while (1) { pty[np] = tword; if (expected && expected[0] == TY_FN && np < expected[3]) pty[np] = expected[7 + np]; - if (!is_op(s_dot) && ((tk == T_OP && tv == s_lk) || (tk == T_ID && (tv == s_fn || istypename(tv))))) { + if (!is_op(s_dot) && paramstart()) { pty[np] = ptype(); if (pty[np][0] == TY_ARR) pty[np] = decay(pty[np]); } tag = 0; @@ -7611,7 +7701,7 @@ word stmtstart() { int s; if (tk == T_OP) return tv == s_semi || tv == s_lb || tstart(); if (tk != T_ID || findloc(tv) >= 0) return 0; - if (tstart()) return 1; + if (tstart() || typedef_ahead()) return 1; s = lookup(tv); return s >= 0 && skind[s] == K_KW && !(sflag[s] & F_EXPR); } @@ -7712,6 +7802,7 @@ int stmt_inner() { 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 (typedef_ahead() && findloc(tv) < 0) return kw_typedef(); if (tstart()) { localdecl(1); return 0; } if (tk == T_OP) { if (tv == s_semi) { next(); return 0; } @@ -8596,7 +8687,7 @@ int prescan(int kind, int obj) { if (is_op(s_lb)) depth++; else if (is_op(s_rb)) { depth--; pend = 0; po3 = 0; } else if (depth <= 0) { - if (is_id(s_type)) { while (tk != T_EOF && !is_op(s_semi)) next(); if (is_op(s_semi)) next(); pend = 0; po3 = 0; continue; } + if (is_id(s_type) || typedef_ahead()) { while (tk != T_EOF && !is_op(s_semi)) next(); if (is_op(s_semi)) next(); pend = 0; po3 = 0; continue; } if (tk == T_ID && rt.eq_w(tv, "namespace")) { kw_namespace(); continue; } if (is_op(s_semi)) { pend = 0; po3 = 0; } else if (is_op(s_dot)) { peek(); if (pk_tk == T_ID && pk_tv == s_u) pend = 1; } @@ -8745,6 +8836,7 @@ int epilogue() { infn = 0; local_drop(lbase); fdrop(lbase); + td_end(0); return 0; } // the statements of a function between its braces, then the end-of-function @@ -8800,7 +8892,7 @@ int fndef(int chk, int rty, int ret, int n) { t = 0; pty[np] = tword; pk = 0; - if (!is_op(s_dot) && ((tk == T_OP && tv == s_lk) || (tk == T_ID && (tv == s_fn || istypename(tv))))) { + if (!is_op(s_dot) && paramstart()) { pty[np] = ptype(); typed = 1; pk = 1; if (pty[np][0] == TY_ARR) pty[np] = decay(pty[np]); if (isagg(pty[np])) { @@ -9065,7 +9157,7 @@ int topsync() { while (tk != T_EOF) { if (is_op(s_lb)) depth++; else if (is_op(s_rb)) { depth--; if (depth < 0) depth = 0; } - else if (!depth && tk == T_ID && (tv == s_fn || tv == s_var || tv == s_const || tv == s_struct || tv == s_type + else if (!depth && tk == T_ID && (tv == s_fn || tv == s_var || tv == s_const || tv == s_struct || tv == s_type || tv == s_typedef || tv == s_enhance || tv == s_typecheck || tv == s_docs || tv == s_unchecked || tv == s_immediate || tv == s_o3 || tv == s_memdebug || tv == s_enum || tv == s_include)) break; next(); @@ -9175,6 +9267,7 @@ int topitem1() { if (is_id(s_var)) return gdecl(0); if (is_id(s_struct)) { kw_struct(); return 0; } if (is_id(s_type)) return kw_type(); + if (typedef_ahead()) return kw_typedef(); if (is_id(s_typecheck)) return kw_typecheck(); if (is_id(s_docs)) return kw_docs(); if (is_id(s_memdebug)) return kw_memdebug(); @@ -9978,7 +10071,7 @@ int zc_init() { poolend = R_POOLEND; poolblocks = 0; namecount = 0; rt.fill_w(namehash, 0, hashcap); nsym = 0; nfixu = 0; fixfree = 0; nbin = 0; nun = 0; nasg = 0; nkop = 0; seqtop = 0; - lsp = 0; ctxsp = 0; snaptop = 0; nloc = 0; lbase = 0; npm = 0; npe = 0; rt.fill_w(lkeys, 0, lkeycap); lkeyn = 0; + lsp = 0; ctxsp = 0; ntd = 0; snaptop = 0; nloc = 0; lbase = 0; npm = 0; npe = 0; rt.fill_w(lkeys, 0, lkeycap); lkeyn = 0; s_lp = intern("("); s_rp = intern(")"); s_lb = intern("{"); s_rb = intern("}"); s_lk = intern("["); s_rk = intern("]"); s_comma = intern(","); s_semi = intern(";"); s_eq = intern("="); s_arrow = intern("->"); s_inc = intern("++"); s_dec = intern("--"); diff --git a/src/lsp/server.zc b/src/lsp/server.zc index 1608c4c..3262e39 100644 --- a/src/lsp/server.zc +++ b/src/lsp/server.zc @@ -390,7 +390,7 @@ int isopc(int c) { int iskeyword(int n) { return n == run.s_fn || n == run.s_var || n == run.s_const || n == run.s_else || n == run.s_as || n == run.s_unchecked || n == run.s_immediate || n == run.s_case || n == run.s_default || n == run.s_do || n == run.s_while || n == run.s_enum || n == run.s_include || n == run.s_memdebug - || n == run.s_enhance || n == run.s_micro || n == run.s_vm || n == run.s_struct || n == run.s_type || n == run.s_typecheck || n == run.s_static; + || n == run.s_enhance || n == run.s_micro || n == run.s_vm || n == run.s_struct || n == run.s_type || n == run.s_typedef || n == run.s_typecheck || n == run.s_static; } // the record of the name at byte offset o (see rs_note in the compiler), or -1. // the records are sorted by offset, so this is a binary search. @@ -934,7 +934,7 @@ int reply_error(int code) { return 0; } -int hs_name = -1, hs_first = -1, hs_const; +int hs_name = -1, hs_first = -1, hs_const, hs_typedef, hs_tname; int bs_k, bs_off; // the embedded source and name offset found by bs_find // ---- hover ---- @@ -999,8 +999,8 @@ word hover_decl(int base, int ls, int le) { // it (hcs is -1 when none) which runs to the line at hdl. a declaration needs // no comment, so the signature still shows without one. int hover_scan(int i) { - hs_name = -1; hs_first = -1; hs_const = 0; - if (hover_source_decl(i) || hover_source_function(i)) return 1; + hs_name = -1; hs_first = -1; hs_const = 0; hs_typedef = 0; + if (hover_typedef(i) || hover_source_decl(i) || hover_source_function(i)) return 1; int base = doff[i], size = dsize[i], ls = 0, le, cs = 0 - 1, c0; while (ls < size) { le = ls; @@ -1182,6 +1182,12 @@ int hover_docs(int i, int head, int name, int length, int tail) { int hover_fill(int i) { int base = doff[i], o = hdl, c, cs, ls, typed = 0, k, run.start, end; hn = 0; + if (hs_typedef) { // typedef T name; as written, from its first word to its ; + hov_text("```zc\n"); c = hdl; + while (c < hdle) { hov_put(dc(base, c)); c++; } + hov_text("\n```"); hover_docs(i, hdl, hdl, 0 - 1, hdle); + return hn; + } if (hs_name >= 0) { hov_text("```zc\n"); c = hdl; while (c < hs_first) { hov_put(dc(base, c)); c++; } @@ -1624,9 +1630,11 @@ int hover_type_span(int i) { } if (best < 0) return 0; t = run.ht_type[best]; + // a typedef name is shown as its typedef, then the type it stands for + if (width == rt.len_w(hname) && run.ht_start[best] == hoff && hover_typedef(i)) { hover_fill(i); hover_type_info(t); return 1; } hov_text("```zc\n"); hov_type(t); hov_text("\n```"); hover_type_info(t); k = kwtext(); - if (k && (hname_is("word") || hname_is("int") || hname_is("char") || hname_is("bool") || hname_is("void") || hname_is("float") || hname_is("f32") || hname_is("f64") || hname_is("byte") || hname_is("u8") || hname_is("i8") || hname_is("u16") || hname_is("i16") || hname_is("u32") || hname_is("i32"))) { + if (k && (hname_is("word") || hname_is("int") || hname_is("char") || hname_is("bool") || hname_is("void") || hname_is("float") || hname_is("f32") || hname_is("f64") || hname_is("byte") || hname_is("string") || hname_is("u8") || hname_is("i8") || hname_is("u16") || hname_is("i16") || hname_is("u32") || hname_is("i32"))) { while (*k && *k != '~') k++; if (*k) { hov_text("\n\n"); hov_text(k + 1); } } @@ -1686,6 +1694,8 @@ char* kwtext() { if (hname_is("return")) return "return e; or return;~leaves the function with a value, or leaves a void function. returning an owned local transfers it to the caller automatically."; if (hname_is("enum")) return "enum name { A, B = 5 };~global constants. under typecheck on they are a type of their own, and a member can carry fields to make a constructor."; if (hname_is("struct")) return "struct name { fields };~a record type. struct name<T> { } makes it generic, one copy for each set of type arguments."; + if (hname_is("typedef")) return "typedef T name;~an alias for a type, the same as type name = T; but in C order. the name is another way to write T, not a new type. inside a function it lasts to the end of its block."; + if (hname_is("string")) return "string~a built-in alias for char*, a word string, as if typedef char* string; were written."; if (hname_is("type")) return "type name = T;~an alias for a type, such as type row = int[3]; for a pointer to an array."; if (hname_is("include")) return "include \"path\"; / include std;~compiles a file or the embedded standard library at that point, its signatures first."; if (hname_is("embed")) return "embed \"path\" as [type] name;~declares name with the bytes of the file at path, read when the unit is compiled. with no type, or bstr, name is a byte string; char* holds one character per word; a number type reads the bytes little-endian; T[] and T[n] lay them out as array elements."; @@ -2160,6 +2170,7 @@ int bs_name(char* path) { } // the offset of the name a declaration line declares: the word before its first delimiter int decl_off(int base, int ls, int le) { + if (hs_typedef) return hs_tname; if (hs_name >= 0) return hs_name; int dl = ls, e, b; while (dl < le && !hdelim(dc(base, dl))) dl++; @@ -2441,6 +2452,35 @@ int hover_source_function(int i) { } return 0; } +// the statement typedef T name; that declares hname, found by tokens, at the top level or in a block +// (the last such one before the cursor, else the first). hdl and hdle are the typedef word and the end +// of the ;, and hs_tname is where the name is. +int hover_typedef(int i) { + int t = 0, w, depth, last, found = -1, first = -1, fend, fname, hend, hname_at; + fm_lex(i); + while (t < fm_nt) { + if (fm_k[t] == 7) break; + if (!fm_is(i, t, "typedef")) { t++; continue; } + w = t + 1; depth = 0; last = -1; + while (w < fm_nt && fm_k[w] != 7) { + if (fm_k[w] == 6 && fm_o[w] == 2 && !depth) break; + if (fm_k[w] == 6 && (fm_o[w] == 5 || fm_o[w] == 6)) break; + if (fm_k[w] == 6 && fm_o[w] == 3) depth++; + else if (fm_k[w] == 6 && fm_o[w] == 4) depth--; + else if (fm_k[w] == 1 && !depth) last = w; + w++; + } + if (w < fm_nt && fm_k[w] == 6 && fm_o[w] == 2 && last > t && hs_match(i, last)) { + if (first < 0) { first = t; fend = fm_e[w]; fname = fm_s[last]; } + if (fm_s[t] <= hcursor) { found = t; hend = fm_e[w]; hname_at = fm_s[last]; } + } + t = w + 1; + } + if (first < 0) return 0; + if (found < 0) { found = first; hend = fend; hname_at = fname; } + hdl = fm_s[found]; hdle = hend; hs_tname = hname_at; hs_typedef = 1; hs_name = -1; hs_first = -1; hs_const = 0; hcs = -1; + return 1; +} int hover_source_decl(int i) { int t = 0, head = -1, first = -1, candidate = -1, depth = 0, brackets = 0, init = 0, constant = 0, matched = 0, tail, prev = -1; fm_lex(i); diff --git a/tests/drivers/lsp_test.js b/tests/drivers/lsp_test.js index 18d8bf7..0f4abe1 100644 --- a/tests/drivers/lsp_test.js +++ b/tests/drivers/lsp_test.js @@ -250,5 +250,23 @@ check(hcv && hcv.includes('parameter'), 'hover on a use of a parameter named lik hcv = hover(scp, 4, at(scp, 4, 'len')); check(hcv && hcv.includes('the number of items'), 'hover on the built-in still shows its text where no local shadows it', hcv); +// typedefs: the name shows its declaration, at the declaration, at a use in a declaration, and in a block +const tdsrc = 'struct node { int v; node* next; };\n// another name for node*\ntypedef node* link;\ntypedef int(int, int) binop;\nlink head(link n) { return n; }\nint use(string s) {\n\ttypedef int cell;\n\tcell c = 1;\n\tbinop f = 0;\n\treturn c;\n}\n'; +hv = hover(tdsrc, 2, at(tdsrc, 2, 'link')); +check(hv && hv.includes('typedef node* link;') && hv.includes('another name for node*'), 'hover on the name in a typedef shows the typedef and its doc', hv); +hv = hover(tdsrc, 4, at(tdsrc, 4, 'link')); +check(hv && hv.includes('typedef node* link;') && hv.includes('a pointer to `node`'), 'hover on a typedef name used as a type shows the typedef and the type', hv); +hv = hover(tdsrc, 4, at(tdsrc, 4, 'link', 1)); +check(hv && hv.includes('typedef node* link;'), 'hover on a typedef name used as a parameter type shows the typedef', hv); +hv = hover(tdsrc, 3, at(tdsrc, 3, 'binop')); +check(hv && hv.includes('typedef int(int, int) binop;'), 'hover on a function type typedef shows the whole typedef', hv); +hv = hover(tdsrc, 7, at(tdsrc, 7, 'cell')); +check(hv && hv.includes('typedef int cell;'), 'hover on a typedef made in a block shows the typedef', hv); +hv = hover(tdsrc, 5, at(tdsrc, 5, 'string')); +check(hv && hv.includes('char*') && hv.includes('built-in alias'), 'hover on string shows char* and what string is', hv); +hv = hover(tdsrc, 2, 3); +check(hv && hv.includes('typedef T name;') && hv.includes('type name = T;'), 'hover on the typedef keyword explains it', hv); +check(tokens(tdsrc).includes('typedef:keyword'), 'typedef is highlighted as a keyword', tokens(tdsrc)); + console.log(ok + ' lsp checks passed, ' + bad + ' failed'); process.exit(bad ? 1 : 0); diff --git a/tests/langerrors/typedef_generic_bare.zc b/tests/langerrors/typedef_generic_bare.zc new file mode 100644 index 0000000..5f3e906 --- /dev/null +++ b/tests/langerrors/typedef_generic_bare.zc @@ -0,0 +1,6 @@ +namespace; + +// expect: expected '<', found 'plain' +struct box<T> { T item; }; +typedef box plain; +fn main() { return 0; } diff --git a/tests/langerrors/typedef_local_redefine.zc b/tests/langerrors/typedef_local_redefine.zc new file mode 100644 index 0000000..e51c32f --- /dev/null +++ b/tests/langerrors/typedef_local_redefine.zc @@ -0,0 +1,8 @@ +namespace; + +// expect: cell: already defined +fn main() { + typedef int cell; + typedef char* cell; + return 0; +} diff --git a/tests/langerrors/typedef_redefine.zc b/tests/langerrors/typedef_redefine.zc new file mode 100644 index 0000000..05085ec --- /dev/null +++ b/tests/langerrors/typedef_redefine.zc @@ -0,0 +1,6 @@ +namespace; + +// expect: num: already defined +typedef int num; +typedef char* num; +fn main() { return 0; } diff --git a/tests/langerrors/typedef_scope.zc b/tests/langerrors/typedef_scope.zc new file mode 100644 index 0000000..833e30e --- /dev/null +++ b/tests/langerrors/typedef_scope.zc @@ -0,0 +1,8 @@ +namespace; + +// expect: expected ';', found 'x' +fn main() { + { typedef int cell; cell a = 1; } + cell x = 2; + return 0; +} diff --git a/tests/langerrors/typedef_string_redefine.zc b/tests/langerrors/typedef_string_redefine.zc new file mode 100644 index 0000000..226a94e --- /dev/null +++ b/tests/langerrors/typedef_string_redefine.zc @@ -0,0 +1,5 @@ +namespace; + +// expect: string: already defined +typedef int string; +fn main() { return 0; } diff --git a/tests/langerrors/typedef_tag.zc b/tests/langerrors/typedef_tag.zc new file mode 100644 index 0000000..70ab491 --- /dev/null +++ b/tests/langerrors/typedef_tag.zc @@ -0,0 +1,5 @@ +namespace; + +// expect: a typedef names a type, which has no tag +typedef char*.u owned; +fn main() { return 0; } diff --git a/tests/langerrors/typedef_unknown.zc b/tests/langerrors/typedef_unknown.zc new file mode 100644 index 0000000..8076e86 --- /dev/null +++ b/tests/langerrors/typedef_unknown.zc @@ -0,0 +1,5 @@ +namespace; + +// expect: missing_t: isn't a type +typedef missing_t thing; +fn main() { return 0; } diff --git a/tests/langerrors/typedef_variable_clash.zc b/tests/langerrors/typedef_variable_clash.zc new file mode 100644 index 0000000..2c843f2 --- /dev/null +++ b/tests/langerrors/typedef_variable_clash.zc @@ -0,0 +1,8 @@ +namespace; + +// expect: count: already defined +fn main() { + int count = 1; + typedef int count; + return count; +} diff --git a/tests/langerrors/typedef_void_variable.zc b/tests/langerrors/typedef_void_variable.zc new file mode 100644 index 0000000..c5c7ced --- /dev/null +++ b/tests/langerrors/typedef_void_variable.zc @@ -0,0 +1,5 @@ +namespace; + +// expect: a variable can't be void +typedef void nothing; +fn main() { nothing x; return 0; } diff --git a/tests/programs/typedef.zc b/tests/programs/typedef.zc new file mode 100644 index 0000000..307ef75 --- /dev/null +++ b/tests/programs/typedef.zc @@ -0,0 +1,89 @@ +namespace; +include rt; +typecheck on; + +// typedef T name; makes name another way to write T: the same type, so the two mix freely +struct node { int v; node* next; }; +struct pair<A, B> { A a; B b; }; + +typedef int count; +typedef char* label; +typedef node* link; +typedef link* links; +typedef int[4] quad; +typedef int(int, int) binop; +typedef pair<int, int> ipair; +typedef int[?] ints; +typedef count counter; + +struct box { count n; link head; ipair p; }; + +count twice(count x) { return x + x; } +count add(int a, int b) { return a + b; } +count apply(binop f, count a, count b) { return f(a, b); } +count sum(link n) { count s = 0; while (n) { s += n.v; n = n.next; } return s; } +label hello() { return "hello"; } +string greet(string s) { return s; } +// string is only a type where a type is expected: an untyped parameter can still be called string +fn twice_name(string) { return string + string; } +count first_of(links ls) { return ls[0].v; } +T pick<T>(T x) { typedef T item; item y = x; return y; } +count scoped() { + count total = 0; + { typedef int cell; cell c = 3; total += c; } + { typedef char* cell; cell c = "ab"; total += rt.len_w(c); } + return total; +} +count scoped_again() { typedef quad cell; cell c; c[2] = 7; return c[2] + sizeof(cell); } + +// a link is a node*: every use below mixes the two +count same_ptr(node* p, link q) { return p == q; } +count casts(link p) { + word w = p as word; + link back = w as link; + node* n = (node*)back; + return back.v + ((link)n == p); +} +count boxed(link p) { + box bx; bx.n = 6; bx.head = 0; bx.p.a = 1; bx.p.b = 2; + pair<link, count> pl; pl.a = 0; pl.b = 5; + return bx.n + p.v + bx.p.a + bx.p.b + pl.b + !bx.head + !pl.a; +} +count by_ref(links ls) { return first_of(ls); } + +fn main() { + link a = rt.new(node), b = rt.new(node); + a.v = 3; b.v = 4; a.next = b; + rt.put_n(sum(a)); rt.put_c(' '); + rt.put_n(same_ptr(a, a)); rt.put_c(' '); + rt.put_n(twice(21)); rt.put_c(' '); + rt.put_n(apply(add, 2, 3)); rt.put_c(' '); + binop op = add; + rt.put_n(op(10, 20)); rt.put_c(10); + + quad q; q[3] = 9; + rt.put_n(q[3] + sizeof(quad)); rt.put_c(' '); + rt.put_n(sizeof(count) + sizeof(link) + sizeof(ipair)); rt.put_c(' '); + rt.put_n(boxed(a)); rt.put_c(' '); + ints xs = {1, 2, 3}; + push(xs, 4); + rt.put_n(len(xs)); rt.put_c(10); + + rt.put_n(casts(a)); rt.put_c(' '); + counter k = (count)3; + rt.put_n(k); rt.put_c(' '); + links ls = &a; + rt.put_n(by_ref(ls)); rt.put_c(' '); + rt.put_n(pick<count>(11) + pick(1)); rt.put_c(10); + + rt.put_n(scoped()); rt.put_c(' '); + rt.put_n(scoped_again()); rt.put_c(10); + + string s = "hi"; + label l = s; + char* c = l; + rt.put_s(s); rt.put_s(l); rt.put_s(c); rt.put_s(hello()); rt.put_s(greet("ok")); rt.put_c(10); + rt.put_n(twice_name(4)); rt.put_c(10); + rt.free(d*a); + return 0; +} diff --git a/tests/test.sh b/tests/test.sh index 46625a6..df6417f 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -106,6 +106,7 @@ run(){ # $1 = vm command, $2 = label chk "$($vm < tests/programs/meta.zc | tr '\n' '/')" "1024 19/unless ok/rrr/15 42/7/42/7/144/" "$l parse words, quote, eval, load, operator tables" chk "$($vm < tests/programs/misc.zc | tr '\n' '/')" "45/54321/ok/" "$l compile-time eval, patched mnemonics, unchecked" chk "$($vm < tests/programs/types.zc | tr '\n' '/')" "12 5 2 15 56 42 7 14 3/rect rect* list<int>* list<T>*.u(int) T(list<T>*) fn(word.d)/a:point@0 b:point@2 tag:int@4 /point 1 1/" "$l types, generics, reflection" + chk "$($vm < tests/programs/typedef.zc | tr '\n' '/')" "7 1 42 5 30/13 4 19 4/4 3 3 12/5 11/hihihihellook/8/" "$l typedef, global and local, in every place a type goes, and string" chk "$($vm < tests/programs/typeok.zc)" "14" "$l a program the typechecker accepts" chk "$($vm < tests/programs/typeoff.zc)" "6" "$l the typechecker is off by default" chk "$($vm < tests/programs/enhance.zc)" "100 0 343 3,2 5 -2 0" "$l virtual enhancements" diff --git a/tests/typeerrors/typedef_mismatch.zc b/tests/typeerrors/typedef_mismatch.zc new file mode 100644 index 0000000..26453dd --- /dev/null +++ b/tests/typeerrors/typedef_mismatch.zc @@ -0,0 +1,6 @@ +namespace; + +// expect: type error: initializer: expected int, got char* +typecheck on; +typedef int count; +fn main() { count c = "text"; return 0; } diff --git a/tests/typeerrors/typedef_pointer.zc b/tests/typeerrors/typedef_pointer.zc new file mode 100644 index 0000000..5dbbb17 --- /dev/null +++ b/tests/typeerrors/typedef_pointer.zc @@ -0,0 +1,7 @@ +namespace; + +// expect: type error: initializer: expected node*, got int* +typecheck on; +struct node { int v; }; +typedef node* link; +fn main() { int n = 0; link p = &n; return 0; } |
