diff options
| author | 2026-10-14 09:32:37 +0000 | |
|---|---|---|
| committer | 2026-10-14 09:32:37 +0000 | |
| commit | 35adc61f0b3d9e5748102df77655b90fa29eeb36 (patch) | |
| tree | dfd45391b69c828e40ab69cc23dbb5bf5405e47d /src/lib/std.zc | |
| parent | todo: record the reverted unchecked trial and what it showed (diff) | |
document the standard library api
every public declaration in src/lib/std.zc has a comment directly above it,
so the language server can show it. comments only; no code changed. the
comments now match the bodies: Host.env and Bytes.env report the characters
copied (at most n - 1), capture drops output past 256 characters, and Buf.set
can raise the length past the current end.
adds examples/std_tour.zc, which calls one or two functions from each group,
and points the README standard-library section at it.
Diffstat (limited to 'src/lib/std.zc')
| -rw-r--r-- | src/lib/std.zc | 128 |
1 files changed, 81 insertions, 47 deletions
diff --git a/src/lib/std.zc b/src/lib/std.zc index f0bc390..33ab1e9 100644 --- a/src/lib/std.zc +++ b/src/lib/std.zc @@ -20,9 +20,14 @@ include rt, run; // 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). +// the number of command line arguments, set by libinit(). int argc; +// the command line arguments, one string each. argv[0] is the program path, +// or <eval> when the source came from stdin. char*[?] argv; +// the number of environment entries, set by libinit(). int envc; +// the environment, one NAME=value string per entry, set by libinit(). char*[?] envp; // one entry of a vector syscall, as a fresh string, or 0 past the end. @@ -64,25 +69,24 @@ int libinit() { // capture.start() redirects everything the program writes into a string and // 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(); +// let buf = std.capture.start(); // rt.put_s("hello world!"); // nothing on the terminal -// capture.end(buf); -// rt.put_s(buf); // hello world +// std.capture.end(buf); +// rt.put_s(buf); // hello world! +// the buffer holds 256 characters, and output past that is dropped. // one capture at a time, and the buffer belongs to whoever holds it. - struct capture { - // 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 + // starts a capture: output goes into a new buffer instead of the terminal. + // returns: the buffer, managed by the collector; read it after end(buffer) static char* start() { int cap = 256; rt.outmode = 2; rt.outn = 0; rt.outcap = cap; rt.outbuf = rt.new(cap); return rt.outbuf; } - // stops output capture, terminates the buffer, and returns the collected text. - // buffer: the buffer returned by start - // returns: the same buffer, now terminated + // stops the capture and ends the buffer with a 0. + // buffer: the buffer that start returned + // returns: the same buffer, now a string static char* end(char* buffer) { rt.outbuf[rt.outn] = 0; rt.outmode = 0; rt.outbuf = 0; rt.outn = 0; rt.outcap = 0; @@ -92,6 +96,8 @@ struct capture { // ---- character strings ---- +// helpers for char* strings: one character per word, ended by a 0 word. +// called as std.Text.name(args). struct Text { // the number written in decimal at the start of s. a leading '-' makes it // negative and the first character that is not a digit ends it. @@ -106,7 +112,7 @@ struct Text { // writes n in decimal into dst, with a 0 after it. // n: the number - // dst: where the text goes; 24 words is always enough + // dst: where the text goes; 21 words is always enough (20 characters and the 0) // returns: how many characters were written, not counting the 0 static int itoa(int n, char* dst) { int b[24], i = 0, j = 0, neg = 0; @@ -120,7 +126,7 @@ struct Text { return j; } - // joins two strings. + // joins two strings into dst. // dst: where the result goes, with room for both and a 0; not a or b // a: the first string // b: the second string @@ -248,9 +254,8 @@ struct Text { // ---- numbers ---- -// plain integer helpers. none of them can fail: an out of range result wraps -// the same way the arithmetic does. - +// plain integer helpers, called as std.Num.name(args). none of them can fail: +// an out of range result wraps the same way the arithmetic does. struct Num { // the absolute value of n, using wrapping word arithmetic. the minimum // word remains negative because its positive value cannot fit in a word. @@ -298,7 +303,8 @@ struct Num { return a; } - // b raised to the power e. e below 0 gives 1; the result wraps as a word. + // b raised to the power e, by one multiply per step of e, so a large e is slow. + // e below 0 gives 1, and the result wraps as a word. // b: the base // e: the exponent // returns: b to the power e, or 1 when e is negative @@ -333,18 +339,18 @@ struct Num { // ---- the host ---- -// the argument block for Host.raw: the number, then its six arguments. +// the argument block for Host.raw: word 0 is the syscall number, words 1 to 6 +// are its arguments. Host.raw fills it on each call. int rawblk[7]; +// calls into the host that runs the vm: the environment, the clock, files and +// raw system calls. called as std.Host.name(args). struct Host { // calls any linux system call on the host. this is the escape hatch below // the named helpers: the number is the kernel's, so the meaning of each - // argument depends on the call. a pointer argument is an address in the - // program's memory, and only the host reads it, so a string or buffer has - // to be passed as its address. - // it runs the call on the real machine, not inside the vm, so a program - // that uses it can do whatever the process can do. only use it where that - // is wanted. + // argument depends on the call. the words reach the kernel unchanged, so a + // pointer is a host address to it, not a place in the vm's memory. the call + // runs on the real machine, so only use it where that 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, -errno on failure, or -1 on unsupported hosts @@ -354,18 +360,20 @@ struct Host { return rt.syscall(rt.SYS_RAW, rawblk, 0, 0); } - // the value of an environment variable. + // copies the value of an environment variable into dst, as a 0-terminated string. // name: the variable's name - // 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 + // dst: where the value goes + // n: the size of dst in words, the 0 included + // returns: how many characters were copied, at most n - 1, so a longer value + // is cut short; -1 if the variable is not set or n is below 1 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 rt.syscall(rt.SYS_TIME, 0, 0, 0); } - // writes a string to a file, replacing what was there. + // writes a string to a file, replacing what was there. the file is created if + // it is missing. // path: the file's name // s: the text to write // returns: how many bytes were written, -1 if the file could not be opened @@ -380,9 +388,8 @@ struct Host { // ---- 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. - +// the same helpers as Text, for packed byte strings of type bstr (b"..." +// literals). len reads the stored byte length. called as std.Bytes.name(args). struct Bytes { // reads a decimal number at the start of s; a leading '-' makes it // negative, and the first non-digit ends the number. @@ -392,7 +399,7 @@ struct Bytes { // like Text.itoa, but the text is made fresh. // n: the number - // returns: a new byte string + // returns: a new byte string, managed by the collector; the caller does not free it static bstr itoa(int n) { return rt.n_to_s(n); } // like Text.starts, for byte strings. @@ -448,14 +455,15 @@ struct Bytes { return 1; } - // like Host.env, for byte strings. + // like Host.env, for byte strings. the value is written into the bytes of dst, + // and the length of dst is not changed, so make dst big enough first. // name: the variable's name, a byte string - // 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 + // dst: the byte string that receives the value + // n: the most bytes dst may use, the 0 after the value included + // returns: how many bytes were copied, at most n - 1; -1 if the variable is not set or n is below 1 static int env(bstr name, bstr dst, int n) { return rt.bgetenv(name, dst, n); } - // like Host.writefile, for a byte string. + // like Host.writefile, for a byte string. the file is replaced, or created. // 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 @@ -470,16 +478,17 @@ struct Bytes { // ---- arenas ---- -// a region that is freed all at once. +// a region of n words that Arena.alloc hands out and Arena.reset frees all at once. // n: its capacity in words; values below 1 allocate one word // returns: the arena, owned by the caller int .u arena(int n) { return rt.arena_new(n); } +// helpers for an arena made by std.arena. called as std.Arena.name(args). struct Arena { // takes n zeroed words from an arena. // a: the arena // n: how many words - // returns: the first word, or 0 when n words do not fit + // returns: the first word; 0 when n words do not fit, or n is negative static int alloc(word a, int n) { return rt.arena_alloc(a, n); } // gives every block of an arena back at once. pointers returned by alloc @@ -491,10 +500,10 @@ struct Arena { // ---- growable byte buffers ---- -// a byte buffer stores its length and capacity in two words, followed by the -// bytes, packed. +// makes an empty byte buffer with room for n bytes. the buffer keeps its length +// and capacity in two words, followed by the bytes, packed. // n: the capacity in bytes; a negative value is treated as 0 -// returns: an empty buffer with that capacity, owned by the caller +// returns: the buffer, owned by the caller word .u bnew(int n) { int words; word p; @@ -506,25 +515,27 @@ word .u bnew(int n) { return u*p; } +// byte buffers made by bnew. the length grows as bytes are set, up to the +// capacity, which is fixed. called as std.Buf.name(args). struct Buf { // how many bytes a buffer holds. - // b: the buffer + // b: the buffer, from bnew // returns: its length static int length(word b) { return b[0]; } // how many bytes a buffer can hold. - // b: the buffer + // b: the buffer, from bnew // returns: its capacity static int capacity(word b) { return b[1]; } // one byte of a buffer. // b: the buffer // i: the position - // returns: the byte, 0 when i is outside the buffer + // returns: the byte, 0 when i is outside the buffer (below 0, or at or past the length) 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 at the current length makes the - // buffer one longer. + // sets one byte of a buffer. a write at or past the length makes the length + // i + 1, and the bytes skipped over are left at 0. // b: the buffer // i: the position, below the capacity // v: the value; only its low 8 bits are kept @@ -542,28 +553,41 @@ struct Buf { // with the collector, so freeing the input does not invalidate the result. // the first index equal to value, or -1 when no element matches. +// source: the array, only read +// value: the element to find int array_find<T>(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. +// source: the array, only read +// value: the element to find int array_contains<T>(fixed T[?].m source, T value) { return array_find(source, value) >= 0; } // the first index whose predicate is true, or -1 when none matches. +// source: the array, only read +// predicate: called on each element; nonzero means a match int array_find_if<T>(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. +// source: the array, only read +// predicate: called on each element; nonzero means a match int array_any<T>(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. +// source: the array, only read +// predicate: called on each element; nonzero means a match +// returns: 1 or 0 int array_all<T>(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. +// source: the array, only read +// predicate: called on each element; nonzero means a match int array_count<T>(fixed T[?].m source, int(T).m predicate) { int length = len(source); int count = 0; @@ -572,6 +596,9 @@ int array_count<T>(fixed T[?].m source, int(T).m predicate) { } // apply transform once to each element and return the transformed array. // the result element type follows the callback's return type. +// source: the array, only read +// transform: called once on each element, in order +// returns: a new array of the results, owned by the caller U[?].u array_map<T,U>(fixed T[?].m source, U(T).m transform) { int length = len(source); U[?] result = rt.new(U[?]); @@ -579,6 +606,9 @@ U[?].u array_map<T,U>(fixed T[?].m source, U(T).m transform) { return result; } // copy elements satisfying predicate into a new array, preserving order. +// source: the array, only read +// predicate: called on each element; nonzero keeps it +// returns: the new array, owned by the caller T[?].u array_filter<T>(fixed T[?].m source, int(T).m predicate) { int length = len(source); T[?] result = rt.new(T[?]); @@ -587,6 +617,10 @@ T[?].u array_filter<T>(fixed T[?].m source, int(T).m predicate) { } // fold elements from left to right, starting with initial; an empty array // returns initial without calling combine. +// source: the array, only read +// initial: the starting value +// combine: called as combine(total, element) for each element, in order +// returns: the folded value A array_reduce<T,A>(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]); |
