aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar zinc <zinc@downsta.ir> 2026-10-15 09:46:14 +0330
committerGravatar zinc <zinc@downsta.ir> 2026-10-15 09:46:14 +0330
commit903e57850e6678f85c5f26cecf6ec318564ae139 (patch)
tree19fb6944dc0fdcf4c54a1582c0cfb6573f93aa26
parentxref: baseline records the prover's writers of ft_ln (bin, kw_for), xub and x... (diff)
parentlsp: prefer the rt import fix when a library group wraps the same name (diff)
merge work/stdio: std.Io and one-level module completion
-rw-r--r--README.md4
-rw-r--r--docs/LEARN.md24
-rw-r--r--examples/learn/35-io.out3
-rw-r--r--examples/learn/35-io.zc7
-rw-r--r--examples/std_tour.zc3
-rw-r--r--src/lib/std.zc78
-rw-r--r--src/lsp/server.zc35
-rw-r--r--tests/drivers/completion_test.js13
-rw-r--r--tests/programs/stdio.expected8
-rw-r--r--tests/programs/stdio.zc18
-rwxr-xr-xtests/test.sh1
11 files changed, 184 insertions, 10 deletions
diff --git a/README.md b/README.md
index 6a56945..f6e5ce8 100644
--- a/README.md
+++ b/README.md
@@ -196,9 +196,9 @@ fn main() { rt.put_s(greeting); rt.put_n(answer + table[0]); return 0; }
a unit is read whole and scanned for function signatures before it compiles, so calls can come before definitions, tags included, and prototypes are optional. `main` runs after the whole program has compiled. use `void f()` for a function with no result and `return;` to leave it; value-returning functions use an explicit result type or the inferred `fn` form.
-the runtime api lives in the `rt` namespace and requires `include rt;`: `rt.put_c`, `rt.get_c`, `rt.eof`, `rt.put_s`, `rt.put_n`, `rt.halt`, `rt.len_w`, `rt.eq_w`, `rt.copy_w`, `rt.fill_w`, the packed-string helpers `rt.len_s`, `rt.at_s`, `rt.slice_s`, `rt.cat_s`, `rt.eq_s`, `rt.s_to_n`, `rt.n_to_s` and `rt.put_bs`, allocation and ownership helpers such as `rt.new`, `rt.new_t`, `rt.free`, `rt.gc`, `rt.memreport`, and host functions including `rt.syscall` and `rt.readfile`. their signatures and descriptions are in the runtime source comments and built-in hover table. `src/lib/std.zc` declares the `std` namespace; its static groups are called as `std.Group.name(args)`: `std.Text`, `std.Num`, `std.Host`, `std.Bytes`, `std.Arena`, and `std.Buf`. the constructors `std.arena(n)` and `std.bnew(n)` and globals such as `std.argc` and `std.argv` are plain declarations in that namespace. capture uses static functions: `std.capture.start()` begins output capture and `std.capture.end(buf)` ends it and returns the buffer.
+the runtime api lives in the `rt` namespace and requires `include rt;`: `rt.put_c`, `rt.get_c`, `rt.eof`, `rt.put_s`, `rt.put_n`, `rt.halt`, `rt.len_w`, `rt.eq_w`, `rt.copy_w`, `rt.fill_w`, the packed-string helpers `rt.len_s`, `rt.at_s`, `rt.slice_s`, `rt.cat_s`, `rt.eq_s`, `rt.s_to_n`, `rt.n_to_s` and `rt.put_bs`, allocation and ownership helpers such as `rt.new`, `rt.new_t`, `rt.free`, `rt.gc`, `rt.memreport`, and host functions including `rt.syscall` and `rt.readfile`. their signatures and descriptions are in the runtime source comments and built-in hover table. `src/lib/std.zc` declares the `std` namespace; its static groups are called as `std.Group.name(args)`: `std.Io`, `std.Text`, `std.Num`, `std.Host`, `std.Bytes`, `std.Arena`, and `std.Buf`. the constructors `std.arena(n)` and `std.bnew(n)` and globals such as `std.argc` and `std.argv` are plain declarations in that namespace. capture uses static functions: `std.capture.start()` begins output capture and `std.capture.end(buf)` ends it and returns the buffer.
-the standard library has seven static groups: `std.capture` (output capture), `std.Text` (char* strings), `std.Num` (integer helpers), `std.Host` (environment, clock, files and raw syscalls; `std.Host.open` returns a `std.File` whose drop closes it), `std.Bytes` (packed byte strings), `std.Arena` (word arenas) and `std.Buf` (byte buffers). the array algorithms `std.array_find`, `std.array_map`, `std.array_reduce` and the others are plain functions in the same namespace. every public declaration in `src/lib/std.zc` has a comment that the language server shows on hover. `examples/std_tour.zc` calls one or two functions from each group and the array algorithms; run it with `build/bin/zy < examples/std_tour.zc`.
+the standard library has eight static groups: `std.capture` (output capture), `std.Io` (the terminal: `print`, `println`, `nl`, `put_c`, `put_s`, `put_n`, `put_hex`, `put_bs`, `put_f`, `get_c`, `eof`, `read`, `getline`; the same operations as the `rt` writers, which stay), `std.Text` (char* strings), `std.Num` (integer helpers), `std.Host` (environment, clock, files and raw syscalls; `std.Host.open` returns a `std.File` whose drop closes it), `std.Bytes` (packed byte strings), `std.Arena` (word arenas) and `std.Buf` (byte buffers). the array algorithms `std.array_find`, `std.array_map`, `std.array_reduce` and the others are plain functions in the same namespace. every public declaration in `src/lib/std.zc` has a comment that the language server shows on hover. `examples/std_tour.zc` calls one or two functions from each group and the array algorithms; run it with `build/bin/zy < examples/std_tour.zc`.
`std.argc` and `std.argv` provide command-line arguments to `main`; `std.envc` and `std.envp` provide environment entries in `NAME=value` form. `std.argc` is an `int`, and `std.argv` is a `char*[?]`; the environment fields have matching types. `std.argv[0]` is the program path, or `<eval>` when source comes from stdin. `std.libinit()` fills all four before `main` when the program includes `std`. Since include dependencies do not import into the caller, runtime calls also require `include rt;`:
diff --git a/docs/LEARN.md b/docs/LEARN.md
index ed12caf..35ee914 100644
--- a/docs/LEARN.md
+++ b/docs/LEARN.md
@@ -531,6 +531,30 @@ output:
124
```
+### terminal input and output
+
+`std.Io` groups the terminal: `print(s)` and `println(s)` write a string, `nl()` a line break, and `put_c`, `put_s`, `put_n`, `put_hex`, `put_bs` and `put_f` write a character, a string, a decimal or hexadecimal integer, a packed byte string and a float. `get_c()`, `eof()`, `read(buf, n)` and `getline(buf, n)` read the program input; `getline` drops the line break and returns -1 at the end of the input. zc has no varargs or overloading, so `print` takes a string only and numbers use their own writers. the writers go through `rt.put_c`, so `std.capture` sees them, and the `rt` names (`rt.put_n` and the rest) keep working.
+
+[example source](../examples/learn/35-io.zc):
+
+```zc
+namespace;
+include std;
+fn main() {
+ std.Io.print("total: "); std.Io.put_n(6 * 7); std.Io.nl();
+ std.Io.print("ratio: "); std.Io.put_f(1.0 / 4.0); std.Io.nl();
+ std.Io.println("done");
+}
+```
+
+output:
+
+```text
+total: 42
+ratio: 0.250000
+done
+```
+
### host services
`rt.syscall(op, a, b, c)` calls the runtime interface to the vm's syscall gate. the runtime defines names such as `SYS_OPEN`, `SYS_READ`, `SYS_WRITE`, `SYS_GETENV`, and `SYS_TIME`. unsupported operations return -1. `std.Host.now()` wraps the clock request.
diff --git a/examples/learn/35-io.out b/examples/learn/35-io.out
new file mode 100644
index 0000000..9aa7f64
--- /dev/null
+++ b/examples/learn/35-io.out
@@ -0,0 +1,3 @@
+total: 42
+ratio: 0.250000
+done
diff --git a/examples/learn/35-io.zc b/examples/learn/35-io.zc
new file mode 100644
index 0000000..e370daa
--- /dev/null
+++ b/examples/learn/35-io.zc
@@ -0,0 +1,7 @@
+namespace;
+include std;
+fn main() {
+ std.Io.print("total: "); std.Io.put_n(6 * 7); std.Io.nl();
+ std.Io.print("ratio: "); std.Io.put_f(1.0 / 4.0); std.Io.nl();
+ std.Io.println("done");
+}
diff --git a/examples/std_tour.zc b/examples/std_tour.zc
index df4d02b..1ef33f3 100644
--- a/examples/std_tour.zc
+++ b/examples/std_tour.zc
@@ -9,6 +9,9 @@ int dbl(int x) { return x * 2; }
int add(int a, int b) { return a + b; }
fn main() {
+ // io: the terminal
+ std.Io.print("io "); std.Io.put_n(7); std.Io.put_c(' '); std.Io.put_f(0.5); std.Io.nl(); // io 7 0.500000
+
// text: char* strings, one character per word
let buf[32];
std.Text.itoa(0 - 42, buf); rt.put_s(buf); rt.put_c(10); // -42
diff --git a/src/lib/std.zc b/src/lib/std.zc
index 745a921..b206888 100644
--- a/src/lib/std.zc
+++ b/src/lib/std.zc
@@ -415,6 +415,84 @@ struct Host {
}
}
+// ---- standard input and output ----
+
+// the terminal: printing to the standard output and reading the standard input.
+// called as std.Io.name(args). the writers go through rt.put_c, so a capture
+// (std.capture) sees their text too. the rt names (rt.put_n, rt.put_s and the
+// rest) stay; these are the same operations, grouped with the library.
+// zc has no varargs or overloading, so print takes a string, and numbers have
+// their own writers: std.Io.print("n = "); std.Io.put_n(n); std.Io.nl();
+struct Io {
+ // writes a string.
+ // s: the text
+ // returns: 0
+ static int print(char* s) { return rt.put_s(s); }
+ // writes a string and a line break.
+ // s: the text
+ // returns: 0
+ static int println(char* s) { rt.put_s(s); return rt.put_c(10); }
+ // writes a line break.
+ // returns: 0
+ static int nl() { return rt.put_c(10); }
+ // writes one character.
+ // c: the character
+ // returns: 0
+ static int put_c(int c) { return rt.put_c(c); }
+ // writes a string, the same as print.
+ // s: the text
+ // returns: 0
+ static int put_s(char* s) { return rt.put_s(s); }
+ // writes a number in decimal, with a minus sign when it is negative.
+ // n: the number
+ // returns: 0
+ static int put_n(int n) { return rt.put_n(n); }
+ // writes a number in hexadecimal.
+ // n: the number
+ // returns: 0
+ static int put_hex(int n) { return rt.put_hex(n); }
+ // writes a packed byte string.
+ // s: the byte string
+ // returns: 0
+ static int put_bs(bstr s) { return rt.put_bs(s); }
+ // writes a float with the vm's float printer (six decimals, inf, nan).
+ // x: the value
+ // returns: 0
+ static int put_f(float x) { asm { x @ #1220 } return 0; }
+ // reads one character.
+ // returns: its byte value, -1 at the end of the input
+ static int get_c() { return rt.get_c(); }
+ // true when the input has no more characters.
+ // returns: 1 at the end of the input, 0 otherwise
+ static int eof() { return rt.eof(); }
+ // reads up to n characters, one to a word, stopping early at the end of the input.
+ // buf: where they go, room for n words
+ // n: the most to read
+ // returns: how many were read, 0 at the end of the input
+ static int read(char* buf, int n) {
+ int i = 0, c;
+ while (i < n) { c = rt.get_c(); if (c < 0 && rt.eof()) break; buf[i] = c; i++; }
+ return i;
+ }
+ // reads one line into buf as a string, without its line break. a line longer
+ // than n - 1 characters is cut short and the rest is left for the next call.
+ // buf: where the line goes, room for n words, the 0 included
+ // n: the size of buf in words
+ // returns: the line's length, or -1 at the end of the input with nothing read
+ static int getline(char* buf, int n) {
+ int i = 0, c;
+ if (n < 1) return -1;
+ while (i < n - 1) {
+ c = rt.get_c();
+ if (c < 0 && rt.eof()) { if (!i) { buf[0] = 0; return -1; } break; }
+ if (c == 10) break;
+ buf[i] = c; i++;
+ }
+ buf[i] = 0;
+ return i;
+ }
+}
+
// ---- byte strings ----
// the same helpers as Text, for packed byte strings of type bstr (b"..."
diff --git a/src/lsp/server.zc b/src/lsp/server.zc
index 52e5e9a..6f4ce67 100644
--- a/src/lsp/server.zc
+++ b/src/lsp/server.zc
@@ -2658,7 +2658,7 @@ char* module_description() {
if (hname_is("run")) return "the resident compiler module: compile and evaluate source, inspect tokens and functions, and register syntax or receiver extensions.";
if (hname_is("core")) return "the vm host interface: host gates, operand-stack addresses, byte-addressed memory access and program termination.";
if (hname_is("lsp")) return "the language server module: document analysis, diagnostics, markdown hovers, completion, navigation and formatting.";
- if (hname_is("std")) return "the embedded standard library: text and byte strings, numeric helpers, files, buffers, array algorithms and command-line arguments. no file is read and nothing imports it automatically.";
+ if (hname_is("std")) return "the embedded standard library: terminal input and output, text and byte strings, numeric helpers, files, buffers, array algorithms and command-line arguments. no file is read and nothing imports it automatically.";
if (hname_is("icon")) return "structured runtime syntax transformations: token templates, balanced captures, expression and block edits, function publication and scalar execution inspection.";
return 0;
}
@@ -2739,7 +2739,7 @@ int hover_instruction(int i) {
if (fm_k[t] == 7) break;
if (hcursor >= fm_s[t] && hcursor < fm_e[t]) {
if (prev >= 0 && fm_is(i, prev, "include") && fm_is(i, t, "std")) {
- hn = 0; return kw_put("include std;~compiles the embedded standard library here. no file is read and nothing imports it automatically. provides Text, Bytes, Host, Arena, Buf, capture, and command-line helpers.");
+ hn = 0; return kw_put("include std;~compiles the embedded standard library here. no file is read and nothing imports it automatically. provides Io, Text, Bytes, Host, Arena, Buf, capture, and command-line helpers.");
}
if (!raw || (prev >= 0 && fm_k[prev] == 6 && fm_o[prev] == 8)) return 0;
op = -1;
@@ -3180,6 +3180,16 @@ int gen_completion() {
}
ws("]}}"); return 0;
}
+// a group such as File in std.File.write, offered once as a namespace unless its own symbol (a struct) is listed.
+int cp_group(char* nm, int from, int to) {
+ int k = 0; char* g = cp_copy(nm);
+ g[to] = 0;
+ if (run.lookup(g) >= 0) return 0;
+ while (k < cp_n) { if (rt.eq_w(cp_name[k], g + from)) return 0; k++; }
+ hn = 0; hov_text("namespace "); hov_text(g);
+ cp_add(g + from, 9, 0, 2);
+ return 0;
+}
int do_completion(int i) {
int s, k, j, old, n, head, c; word member, fields; char* nm; char* owner = 0;
cp_i = i; cp_n = 0;
@@ -3190,20 +3200,30 @@ int do_completion(int i) {
n = cp_cursor - cp_start; cp_prefix = lsp_grow(0, 0, n + 1);
k = 0; while (k < n) { cp_prefix[k] = dc(doff[i], cp_start + k); k++; }
if (cp_start > 0 && dc(doff[i], cp_start - 1) == '.') {
+ // the owner is the whole dotted path before the last dot (std or std.File); its first part must be a module.
head = cp_start - 1;
- while (head > 0 && hid(dc(doff[i], head - 1))) head--;
+ while (head > 0 && (hid(dc(doff[i], head - 1)) || (dc(doff[i], head - 1) == '.' && head > 1 && hid(dc(doff[i], head - 2))))) head--;
n = cp_start - 1 - head; owner = m*lsp_grow(0, 0, n + 1);
k = 0; while (k < n) { owner[k] = dc(doff[i], head + k); k++; } owner[n] = 0;
- if (!run.module_known(owner)) owner = 0;
+ k = 0; while (owner[k] && owner[k] != '.') k++;
+ c = owner[k]; owner[k] = 0;
+ if (!run.module_known(owner)) owner = 0; else owner[k] = c;
}
// the full check supplies functions declared later; the probe supplies locals and their current lifetimes.
ensure_checked(i);
if (owner) {
- if (run.module_imported(owner)) {
+ // one level at a time: owner.name is listed, owner.group.name only as its group.
+ k = 0; while (owner[k] && owner[k] != '.') k++;
+ c = owner[k]; owner[k] = 0; j = run.module_imported(owner); owner[k] = c;
+ if (j) {
n = rt.len_w(owner); s = 0;
while (s < run.nsym) {
nm = run.sname[s]; k = 0; while (k < n && nm[k] == owner[k]) k++;
- if (k == n && nm[k] == '.' && run.skind[s] != run.K_DOC && run.lookup(nm) == s) cp_symbol(s, nm + n + 1);
+ if (k == n && nm[k] == '.' && nm[k + 1] && run.skind[s] != run.K_DOC && run.lookup(nm) == s) {
+ j = k + 1; while (nm[j] && nm[j] != '.') j++;
+ if (!nm[j]) cp_symbol(s, nm + n + 1);
+ else cp_group(nm, n + 1, j);
+ }
s++;
}
}
@@ -3466,7 +3486,8 @@ int gen_code_actions() {
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(p[2]); }
ws("{\"title\":"); wjs(hov, hn); ws(",\"kind\":\"quickfix\",");
- if (single) ws("\"isPreferred\":true,");
+ // the runtime's own name wins over a library wrapper of it (rt.put_c over std.Io.put_c).
+ if (single || (p == ca_list && rt.eq_w(p[1], "rt"))) ws("\"isPreferred\":true,");
ws("\"edit\":{\"documentChanges\":[{\"textDocument\":{\"uri\":");
wjs(duri[ca_i], dulen[ca_i]); ws(",\"version\":"); wn(dver[ca_i]); ws("},\"edits\":[");
pc_reset(ca_i);
diff --git a/tests/drivers/completion_test.js b/tests/drivers/completion_test.js
index f1ee5e6..4b767a1 100644
--- a/tests/drivers/completion_test.js
+++ b/tests/drivers/completion_test.js
@@ -104,4 +104,15 @@ assert.match(get(r.items, 'map').documentation.value, /transform.*each element/)
assert.ok(!r.items.some(c => ['push', 'pop', 'resize'].includes(c.label)), 'library algorithms do not make a fixed array growable');
r = complete('include rt; fn main() { int[?] items = {}; items.| }');
assert.ok(!r.items.some(c => ['map', 'filter'].includes(c.label)), 'library methods require an explicit import');
-console.log('ok completion: signatures, docs, fuzzy ranking, scope, array members, shadowing and consumed lifetimes');
+r = complete('include std; fn main() { std.| }');
+for (const name of ['File', 'Io', 'Text', 'Host', 'argv', 'bnew']) get(r.items, name);
+assert.ok(!r.items.some(c => c.label.includes('.')), 'module completion lists one level: ' + r.items.filter(c => c.label.includes('.')).map(c => c.label));
+r = complete('include std; fn main() { std.File.| }');
+for (const name of ['read', 'write']) get(r.items, name);
+assert.ok(!r.items.some(c => ['Io', 'File', 'main', 'int'].includes(c.label)), 'group completion lists only its members');
+r = complete('include std; fn main() { std.Io.| }');
+for (const name of ['print', 'println', 'put_n', 'put_f', 'getline']) get(r.items, name);
+assert.ok(!r.items.some(c => ['write', 'itoa', 'main', 'int'].includes(c.label)), 'std.Io. lists only Io members');
+r = complete('include std; fn main() { std.Io.pr| }');
+assert.deepStrictEqual(r.items.map(c => c.label).sort(), ['print', 'println']);
+console.log('ok completion: signatures, docs, fuzzy ranking, scope, array members, shadowing, consumed lifetimes and one level of a module path');
diff --git a/tests/programs/stdio.expected b/tests/programs/stdio.expected
new file mode 100644
index 0000000..4a7fd8a
--- /dev/null
+++ b/tests/programs/stdio.expected
@@ -0,0 +1,8 @@
+hi -42 2.500000
+line
+bytes 0xff s
+8 caught 7
+3:abc
+3:lon
+7 103
+-1 1
diff --git a/tests/programs/stdio.zc b/tests/programs/stdio.zc
new file mode 100644
index 0000000..5a79262
--- /dev/null
+++ b/tests/programs/stdio.zc
@@ -0,0 +1,18 @@
+include rt, std;
+
+// std.Io: the writers, a capture of them, and the line reader over program input.
+fn main() {
+ std.Io.print("hi "); std.Io.put_n(-42); std.Io.put_c(32); std.Io.put_f(2.5); std.Io.nl();
+ std.Io.println("line"); std.Io.put_bs(b"bytes"); std.Io.put_c(32); std.Io.put_hex(255);
+ std.Io.put_c(32); std.Io.put_s("s"); std.Io.nl();
+ let cap = std.capture.start();
+ std.Io.print("caught "); std.Io.put_n(7);
+ std.capture.end(cap);
+ std.Io.put_n(std.Text.len(cap)); std.Io.put_c(32); std.Io.println(cap);
+ char[32] buf; int n = std.Io.getline(buf, 32);
+ std.Io.put_n(n); std.Io.print(":"); std.Io.println(buf);
+ n = std.Io.getline(buf, 4); std.Io.put_n(n); std.Io.print(":"); std.Io.println(buf);
+ n = std.Io.read(buf, 32); std.Io.put_n(n); std.Io.put_c(32); std.Io.put_n(buf[0]); std.Io.nl();
+ n = std.Io.getline(buf, 32); std.Io.put_n(n); std.Io.put_c(32); std.Io.put_n(std.Io.eof()); std.Io.nl();
+ return 0;
+}
diff --git a/tests/test.sh b/tests/test.sh
index 8bf7a9b..7745a66 100755
--- a/tests/test.sh
+++ b/tests/test.sh
@@ -155,6 +155,7 @@ run(){ # $1 = vm command, $2 = label
chk "$($vm < tests/programs/use_std.zc)" "42 -7 1" "$l include and the string helpers"
chk "$($vm < tests/programs/stdbuf.zc)" "4 1 266 8" "$l stdlib arena and packed byte buffer"
chk "$($vm < tests/programs/stdmore.zc)" "$(printf '5 1 0 0 7 copy me 12 -1 0 1 0 1\n9 mixed 123 9 MIXED 123\n11 [padded line]\n42 3 9 10 12 81 9 10 -1 0\n6 1 2 -1 1 0')" "$l stdlib text, byte-string and num helpers"
+ chk "$( (cat tests/programs/stdio.zc; printf '`abc\nlonger\nxyz') | $vm)" "$(cat tests/programs/stdio.expected)" "$l std.Io writers, capture, getline and read"
chk "$(ZC_FILE=tests/data/hi.txt $vm < examples/cli.zc)" "6 h" "$l reading a file named by the environment"
chk "$(ZC_ARG_TEST=ok $vm < tests/programs/args.zc)" "1 <eval> <eval> 1 1" "$l argv and envp from the host, argv[0] is <eval> on stdin"
chk "$(ZC_ARG_TEST=ok $vm < tests/programs/capture.zc)" "0