aboutsummaryrefslogtreecommitdiff
path: root/src/lib/std.zc
diff options
context:
space:
mode:
authorGravatar zinc <zinc@downsta.ir> 2026-10-14 09:32:37 +0000
committerGravatar zinc <zinc@downsta.ir> 2026-10-14 09:32:37 +0000
commit35adc61f0b3d9e5748102df77655b90fa29eeb36 (patch)
treedfd45391b69c828e40ab69cc23dbb5bf5405e47d /src/lib/std.zc
parenttodo: 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.zc128
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]);