aboutsummaryrefslogtreecommitdiff
path: root/src/lib/std.zc
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/std.zc')
-rw-r--r--src/lib/std.zc327
1 files changed, 238 insertions, 89 deletions
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<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.
+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.
+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.
+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.
+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.
+int array_count<T>(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<T,U>(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<T>(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<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]);
+ 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);