diff options
| author | 2026-10-12 08:46:23 +0330 | |
|---|---|---|
| committer | 2026-10-12 08:46:23 +0330 | |
| commit | ecc350ca5c2eb50be8309c5f14f8bf0d87db9a02 (patch) | |
| tree | 70e0af76a1b13affff7c6cc8aac7b3e6ef960478 /src/lib | |
a
Diffstat (limited to 'src/lib')
| -rw-r--r-- | src/lib/std.zc | 455 |
1 files changed, 455 insertions, 0 deletions
diff --git a/src/lib/std.zc b/src/lib/std.zc new file mode 100644 index 0000000..375f403 --- /dev/null +++ b/src/lib/std.zc @@ -0,0 +1,455 @@ +// small helpers for strings, files, the clock and byte buffers. not part of the +// genome; include it with: include "src/lib/std.zc"; +// 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. +// 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). +// 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 ---- + +// 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; +int envc; +[]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* s; + if (n < 0) return 0; + s = new(n + 1); + syscall(op, i, s, n + 1); + return s; +} + +// every entry of a vector syscall, in order. +// 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); + while (i < n) { push(a, argstr(stop, i)); i++; } + return a; +} + +// reads the command line and the environment into argc, argv, envc and envp. +// 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); + argc = len(argv); envc = len(envp); + return 0; +} + +// ---- 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() +// 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(); +// 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() { + int cap = 256; + outmode = 2; outn = 0; outcap = cap; + outbuf = new(cap); + return 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; + } +} + +// ---- word strings ---- + +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. + // s: the string to read + // returns: the number, 0 when s starts with no digit + static int atoi(char* s) { + int n = 0, i = 0, sign = 1; + if (s[0] == '-') { sign = 0 - 1; i = 1; } + while (s[i] >= '0' && s[i] <= '9') { n = n * 10 + s[i] - '0'; i++; } + return n * sign; + } + + // writes n in decimal into dst, with a 0 after it. + // n: the number + // dst: where the text goes; 24 words is always enough + // 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; + 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; } + 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++; } + dst[j] = 0; + return j; + } + + // joins two strings. + // dst: where the result goes, with room for both and a 0; not a or b + // a: the first string + // b: the second string + // returns: the length of the result + static int cat(char* dst, char* a, char* b) { + int i = 0, j = 0; + while (a[i]) { dst[j] = a[i]; i++; j++; } + i = 0; + while (b[i]) { dst[j] = b[i]; i++; j++; } + dst[j] = 0; + return j; + } + + // does s begin with pfx? + // s: the string to test + // pfx: the beginning to look for + // returns: 1 if it does (always, for an empty pfx), 0 if not + static int starts(char* s, char* pfx) { + int i = 0; + while (pfx[i]) { if (s[i] != pfx[i]) return 0; i++; } + return 1; + } + + // how many characters come before the 0. + // s: the string + // returns: the length, not counting the 0 + static int len(char* s) { + int n = 0; + while (s[n]) n++; + return n; + } + + // are the two strings the same, character for character? + // a, b: the strings to compare + // returns: 1 if they are equal, 0 if not + static int eq(char* a, char* b) { + int i = 0; + while (a[i] && a[i] == b[i]) i++; + return a[i] == b[i]; + } + + // copies s into dst, with a 0 after it. + // dst: where the copy goes, with room for s and the 0 + // s: the string to copy + // returns: the length of the copy + static int copy(char* dst, char* s) { + int i = 0; + while (s[i]) { dst[i] = s[i]; i++; } + dst[i] = 0; + return i; + } + + // where pat first appears in s. + // s: the string to search + // pat: the string to look for; an empty pat is found at 0 + // returns: the index of the first match, or -1 when there is none + static int find(char* s, char* pat) { + int i = 0, j; + if (!pat[0]) return 0; + while (s[i]) { + j = 0; + while (pat[j] && s[i + j] == pat[j]) j++; + if (!pat[j]) return i; + i++; + } + return 0 - 1; + } + + // changes the ASCII letters of s to lower case, in place. + // s: the string to change + // returns: the length of s + static int lower(char* s) { + int i = 0; + while (s[i]) { + if (s[i] >= 'A' && s[i] <= 'Z') s[i] = s[i] + 32; + i++; + } + return i; + } + + // changes the ASCII letters of s to upper case, in place. + // s: the string to change + // returns: the length of s + static int upper(char* s) { + int i = 0; + while (s[i]) { + if (s[i] >= 'a' && s[i] <= 'z') s[i] = s[i] - 32; + i++; + } + return i; + } + + // removes the spaces, tabs and line breaks from both ends of s, in place. + // s: the string to change + // returns: the length of what is left + static int trim(char* s) { + int n = 0, a = 0, b, i = 0, c; + while (s[n]) n++; + while (a < n && (s[a] == ' ' || s[a] == '\t' || s[a] == '\n' || s[a] == '\r')) a++; + b = n; + while (b > a) { + c = s[b - 1]; + if (!(c == ' ' || c == '\t' || c == '\n' || c == '\r')) break; + b--; + } + n = b - a; + while (i < n) { s[i] = s[i + a]; i++; } + s[n] = 0; + return n; + } +} + +// ---- 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. + static int abs(int n) { + if (n < 0) return 0 - n; + return n; + } + + // the smaller of a and b. + static int min(int a, int b) { + if (a < b) return a; + return b; + } + + // the larger of a and b. + static int max(int a, int b) { + if (a > b) return a; + return b; + } + + // v, kept between lo and hi. + // v: the value + // lo, hi: the bounds, lo first + 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. + static int gcd(int a, int b) { + if (a < 0) a = 0 - a; + if (b < 0) b = 0 - b; + int t; + while (b) { t = a % b; a = b; b = t; } + return a; + } + + // b raised to the power e, for e of 0 or more. e below 0 gives 1. + static int pow(int b, int e) { + int r = 1; + while (e > 0) { r = r * b; e--; } + return r; + } + + // the whole square root of n, rounded down. n below 0 gives 0. + static int isqrt(int n) { + int lo = 0, hi = 3037000499, mid; + if (n <= 0) return 0; + while (lo < hi) { + mid = lo + (hi - lo + 1) / 2; + if (mid <= n / mid) lo = mid; else hi = mid - 1; + } + return lo; + } + + // -1, 0 or 1 by the sign of n. + static int sign(int n) { + if (n < 0) return 0 - 1; + if (n > 0) return 1; + return 0; + } +} + +// ---- the host ---- + +// the argument block for Host.raw: the number, then its six arguments. +int rawblk[7]; + +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. + // 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 + 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); + } + + // the value of an environment variable. + // name: the variable's name + // dst: where the value goes, a word 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); } + + // the clock. + // returns: the time in seconds since 1970 + static int now() { return syscall(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; + if (fd < 0) return -1; + n = syscall(SYS_WRITE, fd, s, len_w(s)); + syscall(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). + +struct Bytes { + // like Text.atoi, for a byte string. + // s: the byte string + // returns: the number + static int atoi(word s) { return 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); } + + // 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) { + 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++; } + 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 + // 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); } + + // 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; + if (fd < 0) return -1; + n = bwrite(fd, s, len_s(s)); + syscall(SYS_CLOSE, fd, 0, 0); + return n; + } +} + +// ---- arenas ---- + +// a region that is freed all at once. +// n: its size in words +// returns: the arena, owned by the caller +int .u arena(int n) { return arena_new(n); } + +struct Arena { + // takes n 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); } + + // gives every block of an arena back at once. + // a: the arena + // returns: 0 + static int reset(word a) { return arena_reset(a); } +} + +// ---- growable byte buffers ---- + +// a byte buffer that knows its length and its capacity: two words, then the +// bytes, packed. +// n: the capacity in bytes +// returns: the buffer, owned by the caller, empty +word .u bnew(int n) { + int words; + word p; + if (n < 0) n = 0; + words = (n + 7) >> 3; + p = new(2 + words); + p[0] = 0; + p[1] = n; + return u*p; +} + +struct Buf { + // how many bytes a buffer holds. + // b: the buffer + // returns: its length + static int length(word b) { return b[0]; } + + // how many bytes a buffer can hold. + // b: the buffer + // 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 + static int get(word b, int i) { if (i < 0 || i >= b[0]) return 0; return ld8(addr_b(b + 2) + i); } + + // sets one byte of a buffer. setting the byte just past the end makes the + // buffer one longer. + // b: the buffer + // i: the position, below the capacity + // v: the value; only its low 8 bits are kept + // 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); + if (i >= b[0]) b[0] = i + 1; + return v & 255; + } +} |
