// 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; } }