From 1c53304d03414890af0da7432d1aea87e754a14a Mon Sep 17 00:00:00 2001 From: Moon Flower Date: Wed, 14 Oct 2026 08:29:46 +0330 Subject: feat: grow memory limits, add icon/actor libraries, overhaul LSP diagnostics Compiler and VM - make every memory map limit dynamic and adjustable at both build time and runtime, including compiler tables, stacks, and every execution region - reserve and grow tables, operand and return stacks, frame segments, and image-building output without relocating live code or globals - rename i/o and string helpers consistently (puts -> put_s, putn -> put_n, scat -> cat_s) across compiler, runtime, examples, tests, and docs - recast and consume values assigned to globals from inside functions - allow fixed and const together, add typed closure returns (fn:t(...)), multi-include syntax (include rt, run, core;), and recurse type inference - never infer string literals as word, anywhere, as argument, variable, or return type - draft docs/BORROW.md: a borrow checker less intrusive than Rust's but more intelligent LSP and editor integration - require explicit includes and give every toolchain area its own namespace (rt, lsp, run, core) - hover for built-in types, inferred types, values known at hover time, array and pointer lifetime/size, library names in include statements, and inferred lifetimes with explanations - honor /* */ comments as documentation for variables and arguments, and stop misattributing section headers or trailing comments as variable hints - stream diagnostics and cancel stale requests on change - jump to definition and references, including cross-file - quick fixes with automatic library import, and namespace completion Hints and diagnostics - render hints as blue squigglies by default, with hints on/small/off per file - suppress magic-number hints via a preceding /* */ comment, and say so - warn on unused arguments; allow multiple _ discards in parameters, pattern matching, and tagged enums, warning if a _ is actually used - rewrite hint and comment prose in professional compiler-doc register Libraries - add icon.zc: run-time self-modification, find/replace syntax, type and block edits, and step-through inspection of locals and globals - add actor.zc: async actors with Actor, implicit this, spawn/recv/send, injected into the compiler machinery at include time rather than built in Tooling and docs - reorganize sources, tests, and designs into subdirectories - unify windows entry points in tinyvm.bat and add a matching tinyvm.sh - add --help across the toolchain - polish the learning guide, stdlib docs, and design notes Tests - add accept, reject, and regression coverage across the compiler, LSP, - runtime, icon, actors, threads, formatting, and the toolchain contracts, plus fuzzers and property tests --- src/lib/std.zc | 327 +++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 238 insertions(+), 89 deletions(-) (limited to 'src/lib/std.zc') diff --git a/src/lib/std.zc b/src/lib/std.zc index 375f403..f232422 100644 --- a/src/lib/std.zc +++ b/src/lib/std.zc @@ -1,35 +1,40 @@ +namespace std; +include rt, run; + // small helpers for strings, files, the clock and byte buffers. not part of the -// genome; include it with: include "src/lib/std.zc"; +// genome as declarations; its source is embedded. include it with: include std; // syscall numbers (SYS_OPEN and the rest) live in the runtime. -// a string here is a word string, one character per word and a 0 at the end, -// which is char*. the Bytes group takes packed byte strings (b"..."), whose -// type is a plain word. +// a character string has type char*, one character per word and a 0 at the end. +// the Bytes group takes packed byte strings (b"..."), whose +// type is bstr. // the helpers are grouped in structs, and each is a static function, so it is -// called as Group.name(args): Text.itoa(n, buf), Host.now(), Buf.get(b, 0). +// called as std.Group.name(args): std.Text.itoa(n, buf), std.Host.now(), +// std.Buf.get(b, 0). // the constructors arena(n) and bnew(n) and the argv helpers are plain // functions, because an owned result has to carry its .u tag. -// ---- the command line and the environment ---- +//: ---- the command line and the environment ---- + +//: argc and argv, envc and envp, as passed to main by c. a zc string is +//: one character per word, so an array of them is char*[?]. libinit() fills +//: these and the runtime calls it before main, if the program has one (an +//: image built by build_image, or a check-mode document, does not). -// argc and argv, envc and envp, the way c hands them to main. a zc string is -// one character per word, so an array of them is []char*. libinit() fills -// these and the runtime calls it before main, if the program has one (an -// image built by build_image, or a check-mode document, does not). int argc; -[]char* argv; +char*[?] argv; int envc; -[]char* envp; +char*[?] envp; // one entry of a vector syscall, as a fresh string, or 0 past the end. // op: the syscall that fetches an entry (SYS_ARG or SYS_ENV) // i: which entry // returns: the entry, owned by the caller; 0 when there is no entry i -unchecked char*.u argstr(int op, int i) { - int n = syscall(op, i, 0, 0); +char*.u argstr(int op, int i) { + int n = rt.syscall(op, i, 0, 0); char* s; if (n < 0) return 0; - s = new(n + 1); - syscall(op, i, s, n + 1); + s = rt.new(n + 1); + rt.syscall(op, i, s, n + 1); return s; } @@ -37,9 +42,9 @@ unchecked char*.u argstr(int op, int i) { // cntop: the syscall that counts the entries (SYS_ARGC or SYS_ENVC) // stop: the syscall that fetches one (SYS_ARG or SYS_ENV) // returns: a new array of the strings, owned by the caller -unchecked []char*.u argsvec(int cntop, int stop) { - int n = syscall(cntop, 0, 0, 0), i = 0; - []char* a = new([]char*, 0); +char*[?].u argsvec(int cntop, int stop) { + int n = rt.syscall(cntop, 0, 0, 0), i = 0; + char*[?] a = rt.new(char*[?], 0); while (i < n) { push(a, argstr(stop, i)); i++; } return a; } @@ -48,43 +53,44 @@ unchecked []char*.u argsvec(int cntop, int stop) { // it stays a plain function: the compiler looks it up by name. // returns: 0 int libinit() { - argv = argsvec(SYS_ARGC, SYS_ARG); - envp = argsvec(SYS_ENVC, SYS_ENV); + argv = argsvec(rt.SYS_ARGC, rt.SYS_ARG); + envp = argsvec(rt.SYS_ENVC, rt.SYS_ENV); argc = len(argv); envc = len(envp); return 0; } -// ---- capturing output ---- +//: ---- capturing output ---- // capture.start() redirects everything the program writes into a string and -// hands it back. writes go to the buffer instead of the terminal until end() +// returns it. writes go to the buffer instead of the terminal until end() // stops it, so the buffer is a string once end() has run: // let buf = capture.start(); // put_s("hello world!"); // nothing on the terminal -// buf.end(); +// capture.end(buf); // put_s(buf); // hello world // one capture at a time, and the buffer belongs to whoever holds it. + struct capture { - // starts a capture of the output. - // returns: the buffer, a word on purpose: the method end() is looked up on - // any receiver the compiler cannot type, and a typed one has none - static word start() { + // redirects program output into a collector-managed character buffer. + // returns the buffer; call end(buffer) before reading its terminator. + // returns: the buffer used for this capture + static char* start() { int cap = 256; - outmode = 2; outn = 0; outcap = cap; - outbuf = new(cap); - return outbuf; + rt.outmode = 2; rt.outn = 0; rt.outcap = cap; + rt.outbuf = rt.new(cap); + return rt.outbuf; } - - // stops the capture and terminates the buffer. - // returns: the string it collected (this is the buffer) - word end() { - outbuf[outn] = 0; - outmode = 0; outbuf = 0; outn = 0; outcap = 0; - return this; + // stops output capture, terminates the buffer, and returns the collected text. + // buffer: the buffer returned by start + // returns: the same buffer, now terminated + static char* end(char* buffer) { + rt.outbuf[rt.outn] = 0; + rt.outmode = 0; rt.outbuf = 0; rt.outn = 0; rt.outcap = 0; + return buffer; } } -// ---- word strings ---- +//: ---- character strings ---- struct Text { // the number written in decimal at the start of s. a leading '-' makes it @@ -106,7 +112,7 @@ struct Text { int b[24], i = 0, j = 0, neg = 0; if (!n) { dst[0] = '0'; dst[1] = 0; return 1; } if (n < 0) { neg = 1; n = 0 - n; } - if (n < 0) { copy_w(dst, "-9223372036854775808", 21); return 20; } + if (n < 0) { rt.copy_w(dst, "-9223372036854775808", 21); return 20; } while (n) { b[i] = '0' + n % 10; i++; n = n / 10; } if (neg) { dst[0] = '-'; j = 1; } while (i) { i--; dst[j] = b[i]; j++; } @@ -138,6 +144,19 @@ struct Text { return 1; } + // does s end with suffix? an empty suffix always matches. + // s: the string to test + // suffix: the ending to look for + // returns: 1 when it matches, 0 otherwise + static int ends(char* s, char* suffix) { + int n = 0, m = 0, i = 0; + while (s[n]) n++; + while (suffix[m]) m++; + if (m > n) return 0; + while (i < m) { if (s[n - m + i] != suffix[i]) return 0; i++; } + return 1; + } + // how many characters come before the 0. // s: the string // returns: the length, not counting the 0 @@ -227,23 +246,32 @@ struct Text { } } -// ---- numbers ---- +//: ---- numbers ---- + // plain integer helpers. none of them can fail: an out of range result wraps // the same way the arithmetic does. + struct Num { - // the absolute value of n. + // the absolute value of n, using wrapping word arithmetic. the minimum + // word remains negative because its positive value cannot fit in a word. + // n: the value + // returns: its absolute value, or the minimum word static int abs(int n) { if (n < 0) return 0 - n; return n; } // the smaller of a and b. + // a, b: the values to compare + // returns: the smaller value (a when they are equal) static int min(int a, int b) { if (a < b) return a; return b; } // the larger of a and b. + // a, b: the values to compare + // returns: the larger value (a when they are equal) static int max(int a, int b) { if (a > b) return a; return b; @@ -251,14 +279,17 @@ struct Num { // v, kept between lo and hi. // v: the value - // lo, hi: the bounds, lo first + // lo, hi: the bounds, lo first; lo must not exceed hi + // returns: v clamped to the bounds static int clamp(int v, int lo, int hi) { if (v < lo) return lo; if (v > hi) return hi; return v; } - // the greatest common divisor of two numbers, always 0 or more. + // the greatest common divisor of a and b, computed with word arithmetic. + // a, b: the values + // returns: their greatest common divisor static int gcd(int a, int b) { if (a < 0) a = 0 - a; if (b < 0) b = 0 - b; @@ -267,7 +298,10 @@ struct Num { return a; } - // b raised to the power e, for e of 0 or more. e below 0 gives 1. + // b raised to the power e. e below 0 gives 1; the result wraps as a word. + // b: the base + // e: the exponent + // returns: b to the power e, or 1 when e is negative static int pow(int b, int e) { int r = 1; while (e > 0) { r = r * b; e--; } @@ -275,6 +309,8 @@ struct Num { } // the whole square root of n, rounded down. n below 0 gives 0. + // n: the value + // returns: the greatest integer whose square is at most n, or 0 for n <= 0 static int isqrt(int n) { int lo = 0, hi = 3037000499, mid; if (n <= 0) return 0; @@ -285,7 +321,9 @@ struct Num { return lo; } - // -1, 0 or 1 by the sign of n. + // -1, 0 or 1 according to the sign of n. + // n: the value + // returns: -1 when n is negative, 0 when it is zero, and 1 when it is positive static int sign(int n) { if (n < 0) return 0 - 1; if (n > 0) return 1; @@ -293,9 +331,9 @@ struct Num { } } -// ---- the host ---- +//: ---- the host ---- -// the argument block for Host.raw: the number, then its six arguments. +//: the argument block for Host.raw: the number, then its six arguments. int rawblk[7]; struct Host { @@ -309,115 +347,160 @@ struct Host { // is wanted. // nr: the syscall number (x86-64 and arm64 numbers differ, so check the host) // a..f: the six arguments, unused ones 0 - // returns: the kernel's result, or -errno when the call fails + // returns: the kernel's result, -errno on failure, or -1 on unsupported hosts static int raw(int nr, int a, int b, int c, int d, int e, int f) { rawblk[0] = nr; rawblk[1] = a; rawblk[2] = b; rawblk[3] = c; rawblk[4] = d; rawblk[5] = e; rawblk[6] = f; - return syscall(SYS_RAW, rawblk, 0, 0); + return rt.syscall(rt.SYS_RAW, rawblk, 0, 0); } // the value of an environment variable. // name: the variable's name - // dst: where the value goes, a word string + // dst: where the value goes, a zero-terminated character string // n: how many words dst has // returns: the length of the value, -1 if the variable is not set - static int env(char* name, char* dst, int n) { return syscall(SYS_GETENV, name, dst, n); } + static int env(char* name, char* dst, int n) { return rt.syscall(rt.SYS_GETENV, name, dst, n); } // the clock. // returns: the time in seconds since 1970 - static int now() { return syscall(SYS_TIME, 0, 0, 0); } + static int now() { return rt.syscall(rt.SYS_TIME, 0, 0, 0); } // writes a string to a file, replacing what was there. // path: the file's name // s: the text to write // returns: how many bytes were written, -1 if the file could not be opened static int writefile(char* path, char* s) { - int fd = syscall(SYS_OPEN, path, 1, 0), n; + int fd = rt.syscall(rt.SYS_OPEN, path, 1, 0), n; if (fd < 0) return -1; - n = syscall(SYS_WRITE, fd, s, len_w(s)); - syscall(SYS_CLOSE, fd, 0, 0); + n = rt.syscall(rt.SYS_WRITE, fd, s, rt.len_w(s)); + rt.syscall(rt.SYS_CLOSE, fd, 0, 0); return n; } } -// ---- byte strings ---- -// the same helpers for packed byte strings. word strings above are unchanged. -// a byte string is a word whose first word is its length (see len_s). +//: ---- byte strings ---- + +// the same helpers for packed byte strings. character-string helpers above are unchanged. +// a packed byte string has type bstr; len_s reads its stored byte length. struct Bytes { - // like Text.atoi, for a byte string. + // reads a decimal number at the start of s; a leading '-' makes it + // negative, and the first non-digit ends the number. // s: the byte string - // returns: the number - static int atoi(word s) { return s_to_n(s); } + // returns: the number, or 0 when s starts with no digit + static int atoi(bstr s) { return rt.s_to_n(s); } // like Text.itoa, but the text is made fresh. // n: the number // returns: a new byte string - static word itoa(int n) { return n_to_s(n); } + static bstr itoa(int n) { return rt.n_to_s(n); } // like Text.starts, for byte strings. // s: the byte string to test // pfx: the beginning to look for // returns: 1 if s begins with pfx, 0 if not - static int starts(word s, word pfx) { + static int starts(bstr s, bstr pfx) { + int i = 0, n = rt.len_s(s), m = rt.len_s(pfx); + if (m > n) return 0; + while (i < m) { if (rt.at_s(s, i) != rt.at_s(pfx, i)) return 0; i++; } + return 1; + } + + // the number of bytes in a packed byte string. + // s: the byte string + // returns: its stored length + static int len(bstr s) { return rt.len_s(s); } + + // are two packed byte strings equal, byte for byte. + // a, b: the byte strings to compare + // returns: 1 if equal, 0 otherwise + static int eq(bstr a, bstr b) { int i = 0; - if (len_s(pfx) > len_s(s)) return 0; - while (i < len_s(pfx)) { if (at_s(s, i) != at_s(pfx, i)) return 0; i++; } + if (rt.len_s(a) != rt.len_s(b)) return 0; + while (i < rt.len_s(a)) { if (rt.at_s(a, i) != rt.at_s(b, i)) return 0; i++; } + return 1; + } + + // where pat first appears in s. + // s: the byte string to search + // pat: the byte string to find; an empty pat is found at 0 + // returns: its byte index, or -1 when absent + static int find(bstr s, bstr pat) { + int i = 0, j, n = rt.len_s(s), m = rt.len_s(pat); + if (!m) return 0; + while (i + m <= n) { + j = 0; + while (j < m && rt.at_s(s, i + j) == rt.at_s(pat, j)) j++; + if (j == m) return i; + i++; + } + return 0 - 1; + } + + // does s end with suffix? an empty suffix always matches. + // s: the byte string to test + // suffix: the ending to look for + // returns: 1 when it matches, 0 otherwise + static int ends(bstr s, bstr suffix) { + int n = rt.len_s(s), m = rt.len_s(suffix), i = 0; + if (m > n) return 0; + while (i < m) { if (rt.at_s(s, n - m + i) != rt.at_s(suffix, i)) return 0; i++; } return 1; } // like Host.env, for byte strings. // name: the variable's name, a byte string - // dst: where the value goes, a word buffer - // n: how many bytes dst can take + // dst: the packed byte string whose payload receives the value + // n: the maximum number of bytes to write // returns: the length of the value, -1 if the variable is not set - static int env(word name, word dst, int n) { return bgetenv(name, dst, n); } + static int env(bstr name, bstr dst, int n) { return rt.bgetenv(name, dst, n); } // like Host.writefile, for a byte string. // path: the file's name, a byte string // s: the bytes to write // returns: how many bytes were written, -1 if the file could not be opened - static int writefile(word path, word s) { - int fd = bopen(path, 1), n; + static int writefile(bstr path, bstr s) { + int fd = rt.bopen(path, 1), n; if (fd < 0) return -1; - n = bwrite(fd, s, len_s(s)); - syscall(SYS_CLOSE, fd, 0, 0); + n = rt.bwrite(fd, s, rt.len_s(s)); + rt.syscall(rt.SYS_CLOSE, fd, 0, 0); return n; } } -// ---- arenas ---- +//: ---- arenas ---- // a region that is freed all at once. -// n: its size in words +// n: its capacity in words; values below 1 allocate one word // returns: the arena, owned by the caller -int .u arena(int n) { return arena_new(n); } +int .u arena(int n) { return rt.arena_new(n); } struct Arena { - // takes n words from an arena. + // takes n zeroed words from an arena. // a: the arena // n: how many words - // returns: the address of the first, 0 if the arena is full - static int alloc(word a, int n) { return arena_alloc(a, n); } + // returns: the first word, or 0 when n words do not fit + static int alloc(word a, int n) { return rt.arena_alloc(a, n); } - // gives every block of an arena back at once. + // gives every block of an arena back at once. pointers returned by alloc + // before the reset are no longer valid. // a: the arena // returns: 0 - static int reset(word a) { return arena_reset(a); } + static int reset(word a) { return rt.arena_reset(a); } } -// ---- growable byte buffers ---- +//: ---- growable byte buffers ---- -// a byte buffer that knows its length and its capacity: two words, then the +// a byte buffer stores its length and capacity in two words, followed by the // bytes, packed. -// n: the capacity in bytes -// returns: the buffer, owned by the caller, empty +// n: the capacity in bytes; a negative value is treated as 0 +// returns: an empty buffer with that capacity, owned by the caller word .u bnew(int n) { int words; word p; if (n < 0) n = 0; words = (n + 7) >> 3; - p = new(2 + words); + p = rt.new(2 + words); p[0] = 0; p[1] = n; return u*p; @@ -438,9 +521,9 @@ struct Buf { // b: the buffer // i: the position // returns: the byte, 0 when i is outside the buffer - static int get(word b, int i) { if (i < 0 || i >= b[0]) return 0; return ld8(addr_b(b + 2) + i); } + static int get(word b, int i) { if (i < 0 || i >= b[0]) return 0; return rt.ld8(rt.addr_b(b + 2) + i); } - // sets one byte of a buffer. setting the byte just past the end makes the + // sets one byte of a buffer. setting the byte at the current length makes the // buffer one longer. // b: the buffer // i: the position, below the capacity @@ -448,8 +531,74 @@ struct Buf { // returns: the byte stored, 0 when i is outside the capacity static int set(word b, int i, int v) { if (i < 0 || i >= b[1]) return 0; - st8(addr_b(b + 2) + i, v & 255); + rt.st8(rt.addr_b(b + 2) + i, v & 255); if (i >= b[0]) b[0] = i + 1; return v & 255; } } + +//: array algorithms borrow their input and visit elements in index order. +//: map and filter return independent handles. reference elements are shared +//: with the collector, so freeing the input does not invalidate the result. + +// the first index equal to value, or -1 when no element matches. +int array_find(fixed T[?].m source, T value) { + int length = len(source); + for (int i = 0; i < length; i++) if (source[i] == value) return i; + return -1; +} +// true when an element equals value. +int array_contains(fixed T[?].m source, T value) { return array_find(source, value) >= 0; } +// the first index whose predicate is true, or -1 when none matches. +int array_find_if(fixed T[?].m source, int(T).m predicate) { + int length = len(source); + for (int i = 0; i < length; i++) if (predicate(source[i])) return i; + return -1; +} +// true when any element satisfies predicate; false for an empty array. +int array_any(fixed T[?].m source, int(T).m predicate) { return array_find_if(source, predicate) >= 0; } +// true when every element satisfies predicate, including an empty array. +int array_all(fixed T[?].m source, int(T).m predicate) { + int length = len(source); + for (int i = 0; i < length; i++) if (!predicate(source[i])) return 0; + return 1; +} +// the number of elements satisfying predicate. +int array_count(fixed T[?].m source, int(T).m predicate) { + int length = len(source); + int count = 0; + for (int i = 0; i < length; i++) if (predicate(source[i])) count++; + return count; +} +// apply transform once to each element and return the transformed array. +// the result element type follows the callback's return type. +U[?].u array_map(fixed T[?].m source, U(T).m transform) { + int length = len(source); + U[?] result = rt.new(U[?]); + for (int i = 0; i < length; i++) push(result, run.share_value(transform(source[i]))); + return result; +} +// copy elements satisfying predicate into a new array, preserving order. +T[?].u array_filter(fixed T[?].m source, int(T).m predicate) { + int length = len(source); + T[?] result = rt.new(T[?]); + for (int i = 0; i < length; i++) if (predicate(source[i])) push(result, run.share_value(source[i])); + return result; +} +// fold elements from left to right, starting with initial; an empty array +// returns initial without calling combine. +A array_reduce(fixed T[?].m source, A initial, A(A,T).m combine) { + int length = len(source); + for (int i = 0; i < length; i++) initial = combine(initial, source[i]); + return initial; +} + +run.method("find", array_find); +run.method("contains", array_contains); +run.method("find_if", array_find_if); +run.method("any", array_any); +run.method("all", array_all); +run.method("count", array_count); +run.method("map", array_map); +run.method("filter", array_filter); +run.method("reduce", array_reduce); -- cgit v1.2.3