From de4da84ca3159ad0ebcf13729716be5f4d7bbc2a Mon Sep 17 00:00:00 2001 From: zinc Date: Thu, 15 Oct 2026 09:42:25 +0330 Subject: std: add std.Io (print, println, nl, put_*, put_f, get_c, eof, read, getline) with test, learn example and docs --- src/lib/std.zc | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lsp/server.zc | 4 +-- 2 files changed, 80 insertions(+), 2 deletions(-) (limited to 'src') 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..bc2c967 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; -- cgit v1.2.3 From 992f004bf31e8902744d86dee595305907144ff1 Mon Sep 17 00:00:00 2001 From: zinc Date: Thu, 15 Oct 2026 09:43:55 +0330 Subject: lsp: complete a module path one level at a time (std. lists groups, std.File. and std.Io. their members) --- src/lsp/server.zc | 28 ++++++++++++++++++++++++---- tests/drivers/completion_test.js | 13 ++++++++++++- 2 files changed, 36 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/lsp/server.zc b/src/lsp/server.zc index bc2c967..65137a6 100644 --- a/src/lsp/server.zc +++ b/src/lsp/server.zc @@ -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++; } } 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'); -- cgit v1.2.3 From d1ded604460290b62fd9b9cd1d48090891cabe6f Mon Sep 17 00:00:00 2001 From: zinc Date: Thu, 15 Oct 2026 09:44:53 +0330 Subject: lsp: prefer the rt import fix when a library group wraps the same name --- src/lsp/server.zc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/lsp/server.zc b/src/lsp/server.zc index 65137a6..6f4ce67 100644 --- a/src/lsp/server.zc +++ b/src/lsp/server.zc @@ -3486,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); -- cgit v1.2.3