diff options
| author | 2026-10-14 10:42:28 +0000 | |
|---|---|---|
| committer | 2026-10-14 10:42:28 +0000 | |
| commit | ea052eab91b304459ddf64281fc93fc152597412 (patch) | |
| tree | 8dcd378d6634626dd2529c2aef684890999de963 | |
| parent | todo: the spec verifier restarted from the first agent's partial work (diff) | |
| parent | fix std hover, full capture terminator, bare static calls, void main in run-o... (diff) | |
Merge four small fixes: std hovers, capture terminator, bare static calls, void main; todo updated
| -rw-r--r-- | TODO.md | 7 | ||||
| -rw-r--r-- | docs/SPEC.md | 4 | ||||
| -rw-r--r-- | src/compiler/expr.zc | 4 | ||||
| -rw-r--r-- | src/compiler/types.zc | 6 | ||||
| -rw-r--r-- | src/lib/std.zc | 5 | ||||
| -rw-r--r-- | src/lsp/server.zc | 40 | ||||
| -rw-r--r-- | src/runtime/run.zc | 5 | ||||
| -rw-r--r-- | tests/drivers/lsp_test.js | 12 | ||||
| -rw-r--r-- | tests/drivers/runtime_image_test.py | 2 | ||||
| -rw-r--r-- | tests/programs/capture_long.zc | 26 | ||||
| -rw-r--r-- | tests/programs/struct_static_call.zc | 31 | ||||
| -rw-r--r-- | tests/programs/void_main.zc | 8 | ||||
| -rwxr-xr-x | tests/test.sh | 5 |
13 files changed, 135 insertions, 20 deletions
@@ -294,7 +294,6 @@ worktree is uploaded to the owner after each update. - runtime line tables and backtraces, see "runtime source positions". - bounds checking with `unbounded`, see "bounds checking". - the cross-reference report, see "a cross-reference report". -- four small fixes: std member hovers, `capture.end`, bare sibling static calls, `void main` in run-only images. - the `defer` statement, see "`defer` statement". ### decisions @@ -310,10 +309,6 @@ sorted by how hard each is to implement; the final full suite stays last. items with an architecture note carry the design worked out so far, enough to brief someone without re-reading the code. -- [ ] (in flight) hover on standard-library members (`std.Text.itoa`, `std.array_contains`, a group name) returns nothing in the language server, though completion shows the same documentation; a struct static function in the document itself does show its hover. find where `do_hover` resolves qualified library members and fix it, with a hover check. -- [ ] (in flight) `std.capture.end` writes its terminator at `rt.outbuf[rt.outn]`; when more than 256 characters were captured, `outn` equals the capacity and the 0 lands one word past the buffer. check and fix, with a regression program. -- [ ] (in flight) a bare call to a sibling static function inside a struct's method (`lim()` for a static `lim`) reports `lim is used but never defined`; resolve it like the bare struct constants now are (`mstruct` in the declarations part). -- [ ] (in flight) a run-only image accepts `void main()` from zc0 but the self-hosted compiler rejects it against `run.zc`'s `int main();` prototype; make the two agree. - [ ] remaining zc0 gaps: string literals over 4095 bytes, `embed` of an array type inside a function, `x as T` with more than a single name (`x as int*`), zc0 silently accepting `.u` on a parameter, and integer literals (`010` is 8 in zc0, 10 in the compiler; zc0 saturates on overflow). `docs/history/43-stdlib/notes.md` still says zc0 reserves u/m/d: add a pointer forward rather than rewriting history. - [ ] (in flight) verify both compilers against `docs/SPEC.md` with a fresh agent that did not write it: expectations derived from the spec text before running anything, one program per rule under `tests/conformance/<section>/`, a runner `tests/conformance/run.sh`, each failure classified as self-hosted bug, zc0 bug, or spec error, and appendix b confirmed item by item. then add the runner to `tests/test.sh`. - [ ] fix the implementation bugs listed in `docs/SPEC.md` appendix b, once confirmed: `memdebug scrub;` rejected; enum members can't reference earlier members; a plain function redefinition silently shadows the first; a prototype after the definition hides it; `?:` with one int and one float branch gives garbage; `bool` not normalised; float global initializers rejected; a global initializer naming another global uses its address; `include std;` inside a function body; syntax-error recovery hiding later errors; unterminated `/*` and string literals; `x as int * y`; prototypes needing parameter names; `len(char*)` with typecheck off; duplicate `case` labels; local `const` in constant expressions; constant expressions without precedence (`1+2*3` is 9). fix the prose items the appendix lists too. @@ -331,6 +326,7 @@ brief someone without re-reading the code. - [ ] borrow returns tied to an argument. inference already works for a callee defined before the call: `char* skip(char* s) { ...; return s + i; }` makes `let r = skip(t); rt.free(t); r[0]` an error. the gap: when the call comes before the callee's definition, nothing is reported and the program reads freed memory. extend the deferred effect requirements (`effect_require`, `effect_finish` at unit end) so a forward-called function whose return turns out to alias a parameter produces the same error. for bodyless declarations, externs and function types, add an unobtrusive spelling, for example a `borrows s` clause after the parameter list (`char* skip(char* s) borrows s;`), checked against the body when one exists. - [ ] variadic arguments and `std.print`. design: a pack is passed as one hidden argument, a caller-built read-only growable-array handle (`fixed T[?]` view: [len, cap, items, element size] in the caller's frame, so `len(a)` and `a[i]` work unchanged and nothing is allocated). `...int a` is a typed pack; several packs per function when their element types differ (`void f(...int a, ...char b)`): each extra argument goes to the pack whose element type matches its static type, an ambiguous match is an error, packs come after the fixed parameters. `...any args` packs each value with a kind code (int, float, char, string, bstr, pointer, ...) and its type record: `any` is a small built-in record { kind, value, type }. `...*any args` makes the call comma-free: after the fixed arguments, the parser reads unary expressions until `)`, commas optional, so `std.print("x = " name ", age " age "\n");` works and binary expressions must be parenthesized. `std.print(...*any args)` prints each argument by its kind (`put_n`, the float printer, `put_s`, `put_bs`, a character for `char`, hex for pointers). - [ ] x86-64 jit phase 2: compile o3 regions to x86-64 (port `rcompile` and the region register allocation; regions now run on the region interpreter and are 2 to 4 times slower than their plain versions on this jit) and inline enhancements (lower the optimized micro-ops to x86 registers as the arm64 path does, instead of calling `xrun`). validate with `tests/drivers/jit_x64_test.sh` and `tests/bench/bench.sh`, which now has an x86-64 jit column. add the driver to `tests/test.sh` on x86-64 linux. +- [ ] `memdebug`'s `obj_base` searches only 64 words back for a block header, so under `memdebug on` an interior access more than 64 words into a block (`buf[255]` on a 256-word block) reports "bad heap pointer". find the header from the buddy allocator's size classes instead of a bounded scan, with a regression program. - [ ] build the llvm core by default when `llvm-config` is available (the makefile falls back to the interpreter otherwise), so x86 hosts get a native tier and the language server stops being interpreted; keep `ZYNOLLVM=1` and `-DZYINTERP` as the opt-outs. - [ ] stack-effect checking: every function and `asm` block declares or is inferred to have a fixed data-stack effect, and the compiler reports a block whose paths leave the stack unbalanced. the first step of a proving scaffold. - [ ] declared side effects at module boundaries (globals touched, syscalls or not), checked against the effect masks the compiler already computes. @@ -343,6 +339,7 @@ brief someone without re-reading the code. - [ ] run the full suite once everything above is done (`make test JOBS=4`; `make check` is the quick one), fix what it finds, and only then tick this item. ## done in the sorted list +- [x] four small fixes: hover on standard-library members (`hover_embedded_sym` sends std/icon symbols to the library lookup; `bs_hover` renders a member hit), `capture.end`'s terminator (the buffer has `cap + 1` words; latent, since the allocator's power-of-two slack hid it; `tests/programs/capture_long.zc`), bare sibling static calls inside a struct's methods (`mstruct` set for every non-generic struct; the struct's own constant or static beats a top-level name, a local beats both; `tests/programs/struct_static_call.zc`, spec 5.5.5), and `void main()` in run-only images (`run.zc` declares an untyped `fn main();`; spec 5.2.5). - [x] a hand-written x86-64 jit, phase 1 (`ZYJIT_X64` in `src/core/core.c`, default on x86-64 linux; `-DZYINTERP` and `make build/bin/zyi` give the interpreter). r12 memory, r13 data stack, r14 return stack, r15 table, rbx state; stack slots addressed with a virtual offset so r13 moves only at calls and exits; shares the table, coverage map, store hook and invalidation with the arm64 jit. enhancements are called through `xrun`, o3 regions run on the region interpreter (`ZYNOO3=1` runs their vm code through the jit instead). 179 of 179 programs agree with the interpreter (`tests/drivers/jit_x64_test.sh`), the self-compile is byte-identical and about 7x faster (1.5 s against 11 s), and the rejection, learn, icon, actor, lsp and hint suites pass on it. - [x] remove temporary markdown files (`TASKLIST.md`, `docs/design/handoff.md` deleted; `BOOTSTRAP-OWNERSHIP.md` moved to `docs/history/`). diff --git a/docs/SPEC.md b/docs/SPEC.md index 51ed5d1..8a6d089 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -667,7 +667,7 @@ a bare `name(params) { ... }` with no `fn` and no type is not a function declara 5.2.4 untyped functions (`fn`) infer parameter and result types by propagating constraints from calls and returns until a fixed point (README, 32-inferred-calls); the exact algorithm is not specified here. typed and untyped functions can be called interchangeably. -5.2.5 `main`. after the unit compiles without errors, the runtime calls `main` with no arguments (declared parameters read unspecified values; `main(a, b)` prints 0 0 in the reference implementation). its return value is ignored and is not the exit status. 8.1. +5.2.5 `main`. after the unit compiles without errors, the runtime calls `main` with no arguments (declared parameters read unspecified values; `main(a, b)` prints 0 0 in the reference implementation). its return value is ignored and is not the exit status; `main` may be declared `int`, `void` or untyped, in a run-only image as in `zy`. 8.1. 5.2.6 a function is defined once. redefining a name that already has a body is, per the rule, error: f: is defined twice; the reference implementation does not detect an ordinary redefinition (appendix b). a prototype must agree with the definition: error: f: doesn't match its earlier declaration. @@ -702,7 +702,7 @@ a field of a struct cannot have a tag. 5.5.4 `e.name(args)` calls method `name` with `e` as `this`, unless the struct has a field `name` (a field wins). when `e` is an integer or `word` and exactly one struct has a method of that name, that method is called (error if several: name: is a method of more than one struct). -5.5.5 a static function has no `this` and is called `Struct.name(args)`. +5.5.5 a static function has no `this` and is called `Struct.name(args)`, or by its bare name `name(args)` inside the struct's own methods and static functions. a bare name there is the struct's own constant or static function first, so it wins over a top-level function or constant of the same name; a local variable or parameter still wins over both. outside the struct only `Struct.name(args)` reaches it. 5.5.6 `Struct.MAX`, `Struct.name(...)` with a misspelled member: error: MAXX: is not a static function of this struct; did you mean 'MAX'? diff --git a/src/compiler/expr.zc b/src/compiler/expr.zc index c6d29c6..40de6ef 100644 --- a/src/compiler/expr.zc +++ b/src/compiler/expr.zc @@ -1014,7 +1014,9 @@ 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; } + // in a method of a struct a bare name is that struct's own constant or static function first, so it + // wins over a global (a top-level function or constant) of the same name + if (mstruct && !ginst) { ms = lookup(methname(mstruct, n)); if (ms >= 0 && (skind[ms] == K_CONST || (skind[ms] == K_FN && (sflag[ms] & F_STATIC)))) 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) { diff --git a/src/compiler/types.zc b/src/compiler/types.zc index c643f0b..6dda110 100644 --- a/src/compiler/types.zc +++ b/src/compiler/types.zc @@ -497,8 +497,9 @@ 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 +// the struct whose body or methods are being compiled: a bare name there is looked up as Struct.name +// (a constant, or a static function called by its bare name) before the global scope, so the struct's +// own wins over a global of the same name 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) @@ -652,6 +653,7 @@ int kw_struct() { } accept(s_semi); i = 0; + if (!gen) mstruct = n; 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; diff --git a/src/lib/std.zc b/src/lib/std.zc index 33ab1e9..772b887 100644 --- a/src/lib/std.zc +++ b/src/lib/std.zc @@ -73,7 +73,8 @@ int libinit() { // rt.put_s("hello world!"); // nothing on the terminal // std.capture.end(buf); // rt.put_s(buf); // hello world! -// the buffer holds 256 characters, and output past that is dropped. +// the buffer holds 256 characters, and output past that is dropped. it has one +// more word for the 0 that end() writes, so a full capture is still a string. // one capture at a time, and the buffer belongs to whoever holds it. struct capture { // starts a capture: output goes into a new buffer instead of the terminal. @@ -81,7 +82,7 @@ struct capture { static char* start() { int cap = 256; rt.outmode = 2; rt.outn = 0; rt.outcap = cap; - rt.outbuf = rt.new(cap); + rt.outbuf = rt.new(cap + 1); return rt.outbuf; } // stops the capture and ends the buffer with a 0. diff --git a/src/lsp/server.zc b/src/lsp/server.zc index afa8de4..a43f910 100644 --- a/src/lsp/server.zc +++ b/src/lsp/server.zc @@ -1761,6 +1761,12 @@ int hover_method(int i) { if (!found) ls = le + 1; } if (!found) return 0; + return hover_method_at(i, sg); +} +// the markdown of the method whose signature starts at byte sg of document i: the +// signature up to the body, a ; or the end of the line, then the docs above it +int hover_method_at(int i, int sg) { + int base = doff[i], size = dsize[i], k, se; // the signature: up to the body, a ; or the end of the line se = sg; while (se < size && dc(base, se) != 10 && dc(base, se) != '{' && dc(base, se) != ';') se++; @@ -1919,17 +1925,24 @@ int hover_global(int i, int kind) { r = rs_find(hoff); if (r >= 0 && run.rsrec[r * run.RS_W + 1] == run.RS_SYM && run.rsrec[r * run.RS_W + 3] >= 0 && kind != 2) { s = run.rsrec[r * run.RS_W + 3]; - if (run.skind[s] != run.K_KW && bs_find(run.sname[s])) { hover_fill(bs_load(bs_k)); return hn; } + if (run.skind[s] != run.K_KW && bs_find(run.sname[s])) { bs_hover(); return hn; } } if (kind != 2) { kt = kwtext(); if (kt) return kw_put(kt); } - if (kind == 1) { if (bs_find(hname)) { hover_fill(bs_load(bs_k)); return hn; } hover_blob(i); return hn; } + if (kind == 1) { if (bs_find(hname)) { bs_hover(); return hn; } hover_blob(i); return hn; } if (hover_scan(i)) { hover_fill(i); return hn; } if (hover_method(i)) return hn; if (kind == 0 && hover_local(i)) return hn; - if (kind != 2 && bs_find(hname)) { hover_fill(bs_load(bs_k)); return hn; } + if (kind != 2 && bs_find(hname)) { bs_hover(); return hn; } hover_blob(i); return hn; } +// is symbol s declared in an embedded library source (the text of std or icon, included +// from tinyvm:std.zc and tinyvm:icon.zc)? such a symbol is a user symbol to the compiler +// (rs_sym sets 16 for it) but its text is not in the document, so hover looks it up by +// its qualified name in the library source, as it does for the runtime. +int hover_embedded_sym(int s) { + return s >= 0 && run.sdsrc && run.sdsrc[s] && bs_name(run.sdsrc[s]) && bs_source(run.sdsrc[s]) >= 0; +} // the hover at the cursor. the compiler's record of the name decides what it // is: a local is its declaration, a parameter the header that names it, and a // symbol is looked up as that kind of symbol. a name with no record is found by @@ -1950,7 +1963,9 @@ int do_hover(int i) { else if (k == run.RS_FIELD && v >= 0) hover_named(i, v, 4); else if (k == run.RS_LOCAL) hover_local(i); else if (k == run.RS_SYM) { - if ((v & 15) == run.K_GVAR || (v & 15) == run.K_GARR) { + // a symbol from an embedded library source (std, icon) is above the user's symbols but is not in the document: its declaration is found in that source by its qualified name + if ((v & 16) && hover_embedded_sym(run.rsrec[r * run.RS_W + 3])) hover_global(i, 1); + else if ((v & 15) == run.K_GVAR || (v & 15) == run.K_GARR) { s = run.rsrec[r * run.RS_W + 3]; q = 0; o = 0 - 1; while (q < run.rsn) { if (run.rsrec[q * run.RS_W + 3] == s && lt_find(run.rsrec[q * run.RS_W]) >= 0) { o = run.rsrec[q * run.RS_W]; break; } q++; } if (o >= 0) hover_named(i, o, 3); else if (v & 16) hover_global(i, 2); else hover_global(i, 1); @@ -2261,16 +2276,29 @@ int bs_method(int dd, char* name, int dot) { } return 0 - 1; } +// did the last bs_find_in hit find a method of a struct (bs_off is its name), not a top-level declaration +int bs_meth; +// the hover of the declaration the last successful bs_find found: a method by its line, anything else +// by the scan that found it +int bs_hover() { + int dd = bs_load(bs_k), q; + if (!bs_meth) return hover_fill(dd); + q = bs_off; + while (q > 0 && dc(doff[dd], q - 1) != 10) q--; + while (q < bs_off && (dc(doff[dd], q) == 32 || dc(doff[dd], q) == 9)) q++; + hn = 0; hover_method_at(dd, q); + return hn; +} // locate a declaration inside one source, after removing its module prefix. int bs_find_in(int k, char* name) { int n = 0, dot = -1, o, dd = bs_load(k); while (name[n]) { if (name[n] == '.' && dot < 0) dot = n; n++; } if (dot > 0 && name[dot + 1]) { o = bs_method(dd, name, dot); - if (o >= 0) { bs_k = k; bs_off = o; return 1; } + if (o >= 0) { bs_k = k; bs_off = o; bs_meth = 1; return 1; } } else if (dot < 0) { set_hname(name); - if (hover_scan(dd)) { bs_k = k; bs_off = decl_off(doff[dd], hdl, hdle); return 1; } + if (hover_scan(dd)) { bs_k = k; bs_off = decl_off(doff[dd], hdl, hdle); bs_meth = 0; return 1; } } return 0; } diff --git a/src/runtime/run.zc b/src/runtime/run.zc index a8f82e2..19a5234 100644 --- a/src/runtime/run.zc +++ b/src/runtime/run.zc @@ -8,9 +8,10 @@ include rt; // // cat build/run/rt.zc src/runtime/run.zc examples/hello.zc | ./zc0 >build/run/genome.h -// the program's entry point, written by the program (see start). +// the program's entry point, written by the program (see start). the prototype is untyped so +// that main may be declared int, void or untyped, as in any other program: its value is ignored. namespace; -int main(); +fn main(); namespace run; include rt; diff --git a/tests/drivers/lsp_test.js b/tests/drivers/lsp_test.js index 0f4abe1..2ad9181 100644 --- a/tests/drivers/lsp_test.js +++ b/tests/drivers/lsp_test.js @@ -199,6 +199,18 @@ const tmsrc = 'struct point {\n\tlet x;\n\t// the sum of the parts\n\tint sum(in hv = hover(tmsrc, 7, at(tmsrc, 7, 'sum')); check(hv && hv.includes('int sum(int k)') && hv.includes('the sum of the parts'), 'a typed method shows its return type and parameters', hv); +// members of the embedded standard library hover like the document's own: the group's static +// function, the group, and a top-level function, each with the comment from std.zc +const stdsrc = 'include rt, std;\nfn main() { char[24] b; rt.put_s(std.Text.itoa(5, b)); return std.array_contains; }\n'; +hv = hover(stdsrc, 1, at(stdsrc, 1, 'itoa')); +check(hv && hv.includes('static int itoa(int n, char* dst)') && hv.includes('writes n in decimal into dst'), 'hover on a std group function shows its signature and doc from std.zc', hv); +hv = hover(stdsrc, 1, at(stdsrc, 1, 'Text')); +check(hv && hv.includes('struct Text') && hv.includes('helpers for char* strings'), 'hover on a std group shows the comment of the group', hv); +hv = hover(stdsrc, 1, at(stdsrc, 1, 'array_contains')); +check(hv && hv.includes('array_contains<T>') && hv.includes('true when an element equals value'), 'hover on a top-level std function shows its doc', hv); +hv = hover(stdsrc, 1, at(stdsrc, 1, 'put_s')); +check(hv && hv.includes('int put_s(char* s)'), 'hover on a runtime function still works beside the std ones', hv); + // externs are declarations, so they draw no "never reassigned" hint const ext = 'extern double sqrt(double);\nextern double pow(double x, double y);\nfn main() {\n\treturn 0;\n}\n'; r = run([init(1), open(A, ext)]); diff --git a/tests/drivers/runtime_image_test.py b/tests/drivers/runtime_image_test.py index d214613..6c44349 100644 --- a/tests/drivers/runtime_image_test.py +++ b/tests/drivers/runtime_image_test.py @@ -18,6 +18,8 @@ cases = [ ('enhancements', 'include rt; fn main() { rt.put_n(17 % 5); rt.put_c(32); rt.put_n(17 / 5); }', b'2 3'), ('array algorithms', Path('examples/learn/31-array-algorithms.zc').read_text(), Path('examples/learn/31-array-algorithms.out').read_bytes()), ('owned pop', 'include rt; int*.u take() { int*[?] a = rt.new(int*[?]); int* p = rt.new(int); *p = 8; a.push(p); return a.pop(); } fn main() { let p = take(); rt.put_n(rt.isobj(p)); rt.put_c(32); rt.put_n(*p); }', b'1 8'), + # main may be void: run.zc's prototype of main must not fix its type (docs/SPEC.md 5.2.5) + ('void main', Path('tests/programs/void_main.zc').read_text(), b'void main\n'), ] prefix = b''.join(Path(f).read_bytes() + b'\n' for f in ['src/runtime/rt.zc', 'src/core/core.zc', 'src/runtime/run.zc']) for name, source, expected in cases: diff --git a/tests/programs/capture_long.zc b/tests/programs/capture_long.zc new file mode 100644 index 0000000..52736a4 --- /dev/null +++ b/tests/programs/capture_long.zc @@ -0,0 +1,26 @@ +namespace; +include rt, std; + +// a capture holds 256 characters and drops the rest. end() must still put its +// 0 inside the buffer, even when the capture was full: the length is 256 and the +// last kept character is the 256th one written (index 255, 'a' + 255 % 26 = 'v'). +// a block allocated right after the buffer is checked afterwards: a 0 written one +// word past the buffer would land in its header and read as a bad or freed block. +memdebug on; // header checks make a write into a neighbouring block visible +fn capture_long() { + let buf = std.capture.start(); + int* next = rt.new(8); + int i = 0; + while (i < 300) { rt.put_c('a' + i % 26); i++; } + std.capture.end(buf); + next[0] = 7; // stops with "bad heap pointer" if its header was overwritten + rt.put_n(next[0]); rt.put_c(10); // 7 + return buf; +} +memdebug off; // the header check only searches 64 words back, so the long buffer is read unchecked +fn main() { + char* buf = capture_long(); + rt.put_n(rt.len_w(buf)); rt.put_c(10); // 256 + rt.put_c(buf[255]); rt.put_c(10); // v + return 0; +} diff --git a/tests/programs/struct_static_call.zc b/tests/programs/struct_static_call.zc new file mode 100644 index 0000000..73fc0fc --- /dev/null +++ b/tests/programs/struct_static_call.zc @@ -0,0 +1,31 @@ +namespace; +include rt; + +// a static function of a struct can be called by its bare name inside the struct's +// methods and static functions, as a struct constant can. the struct's own function +// wins over a top-level one of the same name; outside the struct only S.name reaches it. +int lim() { return 100; } +int base() { return 7; } + +struct S { + int v; + static int lim() { return 3; } + static int twice(int x) { return x * 2; } + static int both() { return lim() + twice(4); } + int get() { return lim() + this.v; } + int scaled() { return twice(this.v) + later(); } + int top() { return base(); } + static int later() { return 1000; } +} + +int main() { + S s; + s.v = 5; + rt.put_n(S.lim()); rt.put_c(32); // 3 + rt.put_n(lim()); rt.put_c(32); // 100, the global one + rt.put_n(S.both()); rt.put_c(32); // 3 + 8 + rt.put_n(s.get()); rt.put_c(32); // 3 + 5 + rt.put_n(s.scaled()); rt.put_c(32); // 10 + 1000, a static function declared later + rt.put_n(s.top()); rt.put_c(10); // 7, a global that the struct has no member of + return 0; +} diff --git a/tests/programs/void_main.zc b/tests/programs/void_main.zc new file mode 100644 index 0000000..b4fde70 --- /dev/null +++ b/tests/programs/void_main.zc @@ -0,0 +1,8 @@ +namespace; +include rt; + +// main may be declared void: the runtime ignores what main returns (docs/SPEC.md 5.2.5, 8.1.3), +// so a run-only image, whose run.zc declares int main();, accepts it as zy does. +void main() { + rt.put_s("void main"); rt.put_c(10); +} diff --git a/tests/test.sh b/tests/test.sh index ec93827..a0175c3 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -129,6 +129,7 @@ run(){ # $1 = vm command, $2 = label 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/struct_static_call.zc)" "3 100 11 8 1010 7" "$l a static function of a struct is called by its bare name in its methods, and wins over a global of the same name" 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" @@ -159,6 +160,9 @@ hello world!42 1 1 0" "$l capture.start() collects output and end() hands back the string" + chk "$($vm < tests/programs/capture_long.zc)" "7 +256 +v" "$l a full capture is still terminated inside its buffer" chk "$($vm < tests/programs/sys.zc)" "2 zy time" "$l syscalls: write, read, time" chk "$($vm < tests/programs/memleak.zc)" $'leak: line 8 3 words\n1' "$l memdebug names a live allocation" chk "$($vm < tests/programs/uaf.zc)" $'zc: line 10: warning: use after free: this storage\'s lifetime ended on line 9\nzy: use after free from line 7' "$l memdebug: use after free" @@ -354,6 +358,7 @@ s_runonly() { runonly wide tests/programs/wide.zc runonly selfpatch examples/selfpatch.zc runonly gc tests/programs/gc.zc + runonly voidmain tests/programs/void_main.zc runonly float tests/programs/float.zc runonly ftruth tests/programs/ftruth.zc runonly ftext tests/programs/ftext.zc |
