namespace std; include rt, run; // small helpers for strings, files, the clock and byte buffers. not part of the // genome as declarations; its source is embedded. include it with: include std; // syscall numbers (SYS_OPEN and the rest) live in the runtime. // 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 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 ---- //: 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). 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 char*.u argstr(int op, int i) { int n = rt.syscall(op, i, 0, 0); char* s; if (n < 0) return 0; s = rt.new(n + 1); rt.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 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; } // 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(rt.SYS_ARGC, rt.SYS_ARG); envp = argsvec(rt.SYS_ENVC, rt.SYS_ENV); argc = len(argv); envc = len(envp); return 0; } //: ---- capturing output ---- // 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(); // put_s("hello world!"); // nothing on the terminal // capture.end(buf); // put_s(buf); // hello world // 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 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 static char* end(char* buffer) { rt.outbuf[rt.outn] = 0; rt.outmode = 0; rt.outbuf = 0; rt.outn = 0; rt.outcap = 0; return buffer; } } //: ---- character 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) { 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++; } 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; } // 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 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, 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; } // v, kept between lo and hi. // v: the value // 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 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; int t; while (b) { t = a % b; a = b; b = t; } return a; } // 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--; } return r; } // 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; 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 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; 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, -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 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 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 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. // 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 = rt.syscall(rt.SYS_OPEN, path, 1, 0), n; if (fd < 0) return -1; 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. character-string helpers above are unchanged. // a packed byte string has type bstr; len_s reads its stored byte length. struct Bytes { // 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, 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 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(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 (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: 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(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(bstr path, bstr s) { int fd = rt.bopen(path, 1), n; if (fd < 0) return -1; n = rt.bwrite(fd, s, rt.len_s(s)); rt.syscall(rt.SYS_CLOSE, fd, 0, 0); return n; } } //: ---- arenas ---- // a region that is freed 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); } 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 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 // before the reset are no longer valid. // a: the arena // returns: 0 static int reset(word a) { return rt.arena_reset(a); } } //: ---- growable byte buffers ---- // a byte buffer stores 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 word .u bnew(int n) { int words; word p; if (n < 0) n = 0; words = (n + 7) >> 3; p = rt.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 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. // 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; 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(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(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(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(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(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(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(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(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(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);