Files
ww/selfhost/test/smoke.combined.ww

6591 lines
235 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// time — clocks, instants, durations. Mirrors Hare's lib/time
// (ref/hare/time/duration.ha, instant.ha, arithm.ha,
// +linux/functions.ha). Calendar / date / strftime / timezone /
// sleep live in separate Hare modules and graduate when callers /
// supporting stdlib arrive.
//
// `duration` is a NAMED alias of i64 (lib/math/random precedent
// at lib/math/random/random.ww:8); ww treats NAMED as a newtype,
// so cross-i64 arithmetic inside this module needs explicit casts.
// Hare's structural alias semantics let those casts vanish, but
// our type checker is strict.
package time;
@symbol("rt_syscall") fn syscall2(num: i64, a: i64, b: i64) i64;
@symbol("rt_abort") fn abort(msg: str) void;
def SYS_CLOCK_GETTIME: i64 = 228;
// ref/hare/time/duration.ha:6. 290y representable range.
export type duration = i64;
// ref/hare/time/duration.ha:9-18. Plan-9 naming (lowercase)
// diverges from Hare's uppercase per project rule 4.
export def nanosecond: duration = 1i64;
export def microsecond: duration = 1000i64;
export def millisecond: duration = 1000000i64;
export def second: duration = 1000000000i64;
// ref/hare/time/instant.ha:9. (sec, nsec) pair — NOT POSIX struct
// timespec (which uses u32 nsec). Layout matches Linux's struct
// timespec on 64-bit (i64+i64) so we can pass &instant directly
// to clock_gettime.
export type instant = struct {
sec: i64,
nsec: i64,
};
// ref/hare/time/+linux/functions.ha:84. First cut exposes only
// realtime and monotonic; Hare's process_cpu / thread_cpu / boot /
// realtime_alarm / boot_alarm / tai graduate when a caller needs
// them (CLAUDE.md rule 9 — Hare-fidelity, no premature surface).
export type clock = enum i32 {
realtime = 0,
monotonic = 1,
};
// ref/hare/time/+linux/functions.ha:138. Hare's now() also aborts
// on impossible errnos. (instant | oserror) is deliberately not
// the return shape — EINVAL / EFAULT are programmer errors (bad
// clock id, bad ptr), and a 1-word-payload sum return walks into
// task #9's cgen-divergence trap.
export fn now(c: clock) instant = {
let i: instant;
let rc = syscall2(SYS_CLOCK_GETTIME, (c as i32): i64, (&i): i64);
if (rc != 0i64) { abort("time.now: clock_gettime failed"); };
return i;
};
// ref/hare/time/arithm.ha:9. Adds duration to instant. The
// negative-duration branch normalises nsec into [0, second).
export fn add(i: instant, x: duration) instant = {
let r: instant;
let xi: i64 = x: i64;
let sec: i64 = second: i64;
let nsec: i64 = nanosecond: i64;
if (xi == 0i64) {
r.sec = i.sec;
r.nsec = i.nsec;
return r;
};
if (xi > 0i64) {
r.sec = i.sec + (i.nsec + xi) / sec;
r.nsec = (i.nsec + xi) % sec;
return r;
};
r.sec = i.sec + (i.nsec + xi - sec + nsec) / sec;
r.nsec = (i.nsec + (xi % sec) + sec) % sec;
return r;
};
// ref/hare/time/arithm.ha:26. Returns duration from a to b.
// Sign convention: b - a.
export fn diff(a: instant, b: instant) duration = {
let sec: i64 = second: i64;
let v: i64 = ((b.sec - a.sec) * sec) + (b.nsec - a.nsec);
return v: duration;
};
// ref/hare/time/arithm.ha:32. -1 if a < b, 0 if equal, +1 if a > b.
export fn compare(a: instant, b: instant) i8 = {
if (a.sec < b.sec) { return -1i8; };
if (a.sec > b.sec) { return 1i8; };
if (a.nsec < b.nsec) { return -1i8; };
if (a.nsec > b.nsec) { return 1i8; };
return 0i8;
};
// os — process and filesystem facade. The body of each call lands
// either in libwwrt.a (rt_syscall trampoline) or libc bindings,
// depending on how the program was linked.
package os;
import time;
@symbol("rt_syscall") fn syscall0(num: nr) i64;
@symbol("rt_syscall") fn syscall1(num: nr, a: i64) i64;
@symbol("rt_syscall") fn syscall2(num: nr, a: i64, b: i64) i64;
@symbol("rt_syscall") fn syscall3(num: nr, a: i64, b: i64, c: i64) i64;
@symbol("rt_syscall") fn syscall4(num: nr, a: i64, b: i64, c: i64, d: i64) i64;
@symbol("rt_free") export fn free(p: *void, n: u64) void;
@symbol("rt_abort") fn abort(msg: str) void;
// Hare-style runtime check. Caller passes a message that's printed
// to stderr before exit(1).
export fn assert(cond: bool, msg: str) void = {
if (!cond) { abort(msg); };
};
// Linux amd64 syscall numbers. Internal to this module — passed as
// the first arg of syscall0..4 via libwwrt's rt_syscall trampoline.
// `nr` is the type so the call sites can't accidentally pass an
// arbitrary i64 (`syscall1(0i64, ...)` no longer typechecks).
type nr = enum i64 {
READ = 0,
WRITE = 1,
OPEN = 2,
CLOSE = 3,
LSEEK = 8,
ACCESS = 21,
DUP2 = 33,
GETPID = 39,
FORK = 57,
EXECVE = 59,
EXIT = 60,
WAIT4 = 61,
MKDIR = 83,
RMDIR = 84,
UNLINK = 87,
GETCWD = 79,
GETDENTS64 = 217,
NEWFSTATAT = 262,
};
// open(2) flags. Linux values, matching <fcntl.h>. Hare names them
// `fs::flag::RDONLY` etc; we use the same leaf names so callers say
// `os.flag.RDONLY` and `os.flag.WRONLY | os.flag.CREATE`.
export type flag = enum i32 {
RDONLY = 0,
WRONLY = 1,
RDWR = 2,
CREATE = 64, // 0x40
EXCL = 128, // 0x80 — pair with CREATE to fail on existing path
TRUNC = 512, // 0x200
};
// lseek(2) whence. Hare names it `io::whence`.
export type whence = enum i32 {
SET = 0,
CUR = 1,
END = 2,
};
export fn exit(code: i32) void = {
syscall1(nr.EXIT, code: i64);
};
// PATH_MAX / pathbuf / kpath — port of Hare's ref/hare/sys/+linux/
// syscalls.ha:25,27,29-55. Hare's `path` accepts a sum `(str |
// []u8 | *const u8)`; ww's lib/os public surface narrows to `str`
// (the Hare-faithful surface at ref/hare/os/os.ha:37,47,50 etc).
// Internally, [[kpath]] copies the `str` bytes into a single
// module-level [[pathbuf]] scratch slot and NUL-terminates so the
// raw Linux syscalls (which require C strings) see a valid
// terminator. Same precedent as Hare's static `pathbuf`.
//
// Non-reentrant: one buffer, every [[stat]] / [[open]] / etc.
// rewrites it. Same caveat as strconv's `*tos` family (overwritten
// on next call). Caller must NOT hold a kpath-returned pointer
// across another lib/os path call. Graduates when ww grows a
// thread story.
//
// `nil`-as-overflow over `(*u8 | oserror)`: wwstage over-allocates
// 1-word-payload tagged returns to 24B (cstage emits 16B).
// Task #9; revert at task #10 when fixed. Repro at
// .ai/probe_tagged_return_pointer_payload.ww.
export def PATH_MAX: i32 = 4096;
let pathbuf: [4096]u8;
// ref/hare/sys/+linux/types.ha:886-888. ww folds `sys` into `os`, so the
// std fd NUMBERS live here (the sys role). Typed i32, NOT io.file as in
// Hare's os::stdout_file (ref/hare/os/+linux/stdfd.ha:28): Hare's `os`
// imports `io`, but ww's `os` is the import floor and must never import
// io (lib/CLAUDE.md) — so the io.file/io.handle binding can't live here.
// Consumers (lib/fmt's stdio wrappers) cast i32→io.file at the use site,
// where the handle layer is already in scope.
export def STDIN_FILENO: i32 = 0;
export def STDOUT_FILENO: i32 = 1;
export def STDERR_FILENO: i32 = 2;
fn kpath(p: str) *u8 = {
if (p.len + 1 >= PATH_MAX) { return nil: *u8; }; // ENAMETOOLONG
let i: i32 = 0;
for (i < p.len) { pathbuf[i] = p[i]; i += 1; };
pathbuf[p.len] = 0u8;
return &pathbuf[0];
};
// Raw, non-fallible primitives. These return Linux's int conventions
// (negative = -errno, non-negative = bytes/fd/etc). Callers wanting a
// Hare-style fallible API use the wrappers below.
export fn write(fd: i32, buf: *u8, n: u64) i64 = {
return syscall3(nr.WRITE, fd: i64, buf: i64, n: i64);
};
export fn read(fd: i32, buf: *u8, n: u64) i64 = {
return syscall3(nr.READ, fd: i64, buf: i64, n: i64);
};
export fn close(fd: i32) i32 = {
return syscall1(nr.CLOSE, fd: i64): i32;
};
// dup2(2): make `newfd` refer to the same description as `oldfd`,
// closing `newfd` first if open. Returns `newfd` on success or a
// negative errno. Used by w6c_ww to redirect stdout into an output
// file without changing the cgen emit path.
export fn dup2(oldfd: i32, newfd: i32) i32 = {
return syscall2(nr.DUP2, oldfd: i64, newfd: i64): i32;
};
// Fallible wrappers. The error variant is `oserror` (an i64 carrying
// -errno). The sum type makes success/failure explicit and lets
// callers `?` the result up the stack.
export fn tryread(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
let r: i64 = read(fd, buf, n);
if (r < 0) { return r: oserror; };
return r;
};
export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
let r: i64 = write(fd, buf, n);
if (r < 0) { return r: oserror; };
return r;
};
// open — Linux open(2). Returns -errno on failure, fd otherwise.
// Higher-level callers prefer `tryopen`. Mirrors Hare's os::open
// (ref/hare/os/os.ha:117); kpath lands the bytes in pathbuf.
// Returns -ENAMETOOLONG (-36) if the path overflows PATH_MAX.
export fn open(path: str, flags: flag, mode: i32) i32 = {
let p: *u8 = kpath(path);
if (p == nil: *u8) { return -36i32; }; // ENAMETOOLONG
return syscall3(nr.OPEN, p: i64, (flags as i32): i64, mode: i64): i32;
};
export fn tryopen(path: str, flags: flag, mode: i32) (i32 | oserror) = {
let fd: i32 = open(path, flags, mode);
if (fd < 0) { return fd: i64: oserror; };
return fd;
};
// lseek — set/inspect the fd's position. Returns the new offset or
// a negative errno. We use this for fstat-free file-size discovery
// (open ⇒ lseek to end ⇒ lseek back).
export fn lseek(fd: i32, off: i64, w: whence) i64 = {
return syscall3(nr.LSEEK, fd: i64, off, (w as i32): i64);
};
// oserror — the underlying errno from a failed syscall, as a
// negative i64 (Linux's int convention; e.g. -2 = ENOENT). The
// `!`-flagged alias makes ?-propagation pick this variant as the
// error half of any (T | oserror) shape. Hare's analogue is
// errors::errno carried inside io::error.
export type oserror = !i64;
// errno — the raw Linux errno as a positive code (ref/hare/sys/+linux/
// errno.ha:5, `errno = !int`). ww folds Hare's `sys` role into os
// (lib/CLAUDE.md), so the sys::errno machinery lands here. Spelled i32
// rather than int: Linux errnos are kernel ints (32-bit), keeping os's
// kernel-facing surface uniformly i32. Distinct from [[oserror]] (!i64,
// the syscall's *negative* raw return) — the two model different
// things, so they are not unified; the negative→positive normalization
// lives at the oserror→errors.error boundary in those callers.
export type errno = !i32;
// Mapped errno values, ref/hare/sys/+linux/errno.ha:559-682. Positive,
// matching Hare's defs (the kernel returns -N; the wrap-to-positive is
// the caller's concern). Subset: exactly the errnos [[errors.errno]]
// maps to a named condition; grow as callers surface more.
export def ENOENT: errno = 2;
export def EINTR: errno = 4;
export def EAGAIN: errno = 11;
export def EACCES: errno = 13;
export def EBUSY: errno = 16;
export def EEXIST: errno = 17;
export def EINVAL: errno = 22;
export def EOVERFLOW: errno = 75;
export def ENETUNREACH: errno = 101;
export def ETIMEDOUT: errno = 110;
export def ECONNREFUSED: errno = 111;
export def ECANCELED: errno = 125;
// strerror — human-readable text for an [[errno]] (Hare's
// sys::strerror, ref/hare/sys/+linux/errno.ha:18). FAITHFUL MINIMAL
// SUBSET: the mapped errnos above plus a generic fallback; grow the
// switch as callers surface more (lib/CLAUDE.md documented-subset, not
// a workaround). Messages verbatim from the reference. Hare's
// unknown_errno formats the numeric value; that is deferred.
export fn strerror(err: errno) str = {
switch (err) {
case ENOENT: return "No such file or directory";
case EINTR: return "Interrupted system call";
case EAGAIN: return "Resource temporarily unavailable";
case EACCES: return "Permission denied";
case EBUSY: return "Device or resource busy";
case EEXIST: return "File exists";
case EINVAL: return "Invalid argument";
case EOVERFLOW: return "Value too large for defined data type";
case ENETUNREACH: return "Network is unreachable";
case ETIMEDOUT: return "Connection timed out";
case ECONNREFUSED: return "Connection refused";
case ECANCELED: return "Operation canceled";
};
return "Unknown error";
};
// filesize — byte length of an open fd via lseek-to-end-and-back.
export fn filesize(fd: i32) (i64 | oserror) = {
let end: i64 = lseek(fd, 0i64, whence.END);
if (end < 0) { return end: oserror; };
let r: i64 = lseek(fd, 0i64, whence.SET);
if (r < 0) { return r: oserror; };
return end;
};
// readall — keep reading until `n` bytes have arrived or the fd
// closes early. Hare name (io::readall); the buffer is caller-
// supplied, matching the Plan 9 subset convention.
export fn readall(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
let got: u64 = 0u64;
for (got < n) {
let r: i64 = read(fd, buf + got, n - got);
if (r < 0) { return r: oserror; };
if (r == 0) { return got: i64; }; // short read: caller decides
got += r: u64;
};
return got: i64;
};
// writeall — keep writing until `n` bytes have been accepted or the
// fd refuses progress. Hare name (io::writeall).
export fn writeall(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
let sent: u64 = 0u64;
for (sent < n) {
let r: i64 = write(fd, buf + sent, n - sent);
if (r < 0) { return r: oserror; };
if (r == 0) { return sent: i64; };
sent += r: u64;
};
return sent: i64;
};
// ---- process and filesystem helpers used by the `ww` driver ----------
// access(2): returns 0 if the file is reachable, negative errno
// otherwise. mode is the bitset described in <unistd.h> (F_OK=0).
// Mirrors Hare's os::access (ref/hare/os/+linux/fs.ha:access).
// Returns -ENAMETOOLONG (-36) if the path overflows PATH_MAX.
export fn access(path: str, mode: i32) i32 = {
let p: *u8 = kpath(path);
if (p == nil: *u8) { return -36i32; };
return syscall2(nr.ACCESS, p: i64, mode: i64): i32;
};
// remove — unlink(2). Mirrors Hare's os::remove
// (ref/hare/os/os.ha:12).
export fn remove(path: str) i32 = {
let p: *u8 = kpath(path);
if (p == nil: *u8) { return -36i32; };
return syscall1(nr.UNLINK, p: i64): i32;
};
// mkdir — mkdir(2). Mode is the unix permission bitset (e.g. 0o700).
// Returns 0 on success, negative errno otherwise. Mirrors Hare's
// os::mkdir (ref/hare/os/os.ha:50).
export fn mkdir(path: str, mode: i32) i32 = {
let p: *u8 = kpath(path);
if (p == nil: *u8) { return -36i32; };
return syscall2(nr.MKDIR, p: i64, mode: i64): i32;
};
// rmdir — rmdir(2). Mirrors Hare's os::rmdir
// (ref/hare/os/os.ha:58).
export fn rmdir(path: str) i32 = {
let p: *u8 = kpath(path);
if (p == nil: *u8) { return -36i32; };
return syscall1(nr.RMDIR, p: i64): i32;
};
// mkdirs — recursive mkdir. Creates `path` and any non-existent
// parent directories with the given mode. EEXIST is silently
// accepted (matches Hare's `errors::exists` skip in os::mkdirs);
// any other syscall failure surfaces as `oserror`.
//
// Mirrors Hare's os::mkdirs (ref/hare/os/os.ha:54). The in-place
// '/' → NUL splice walks the kpath-loaded [[pathbuf]] directly
// instead of recursing through [[mkdir]] — re-entering kpath would
// clobber the buffer mid-walk (single static slot, see kpath's
// non-reentrancy note above).
export fn mkdirs(path: str, mode: i32) (void | oserror) = {
let cp: *u8 = kpath(path);
if (cp == nil: *u8) { return -36i64: oserror; };
let n: i32 = path.len;
if (n == 0) { return; };
// Walk forward; at each '/' boundary, NUL-terminate the prefix,
// raw MKDIR syscall on pathbuf, restore the slash, continue.
// Skip index 0 so a leading '/' on absolute paths doesn't
// trigger an empty mkdir.
let i: i32 = 1;
for (i < n) {
if (pathbuf[i] == 47u8) { // '/'
pathbuf[i] = 0u8;
let r: i32 = syscall2(nr.MKDIR,
(&pathbuf[0]): i64, mode: i64): i32;
pathbuf[i] = 47u8;
if (r < 0) {
if (r != -17) { return r: i64: oserror; };
};
};
i += 1;
};
let r: i32 = syscall2(nr.MKDIR,
(&pathbuf[0]): i64, mode: i64): i32;
if (r < 0) {
if (r != -17) { return r: i64: oserror; };
};
return;
};
// getpid(2). Used by the driver to mint unique scratch paths.
export fn getpid() i32 = {
return syscall0(nr.GETPID): i32;
};
// fork(2): 0 in the child, child pid in the parent, negative errno
// on failure.
export fn fork() i32 = {
return syscall0(nr.FORK): i32;
};
// execve(2): on success, does not return. Mirrors Hare's
// os::exec::exec path arg (str). argv/envp stay `**u8` — the
// kernel takes a NUL-pointer-terminated table of NUL-terminated
// C strings, a different shape from a path.
export fn execve(path: str, argv: **u8, envp: **u8) i32 = {
let p: *u8 = kpath(path);
if (p == nil: *u8) { return -36i32; };
return syscall3(nr.EXECVE, p: i64, argv: i64, envp: i64): i32;
};
// wait4(2): wait for `pid` (or any child if -1), store status in
// `*status`, return the pid that ended (or negative errno).
export fn wait4(pid: i32, status: *i32, options: i32, rusage: *void) i32 = {
return syscall4(nr.WAIT4, pid: i64, status: i64,
options: i64, rusage: i64): i32;
};
// getcwd(2) — Linux flavour. Writes the NUL-terminated cwd into `buf`
// and returns the number of bytes written (including the NUL), or a
// negative errno. The driver uses it to expand `.` to the cwd's
// basename for `ww build` / `ww test`.
export fn getcwd(buf: *u8, n: u64) i64 = {
return syscall2(nr.GETCWD, buf: i64, n: i64);
};
// getdents64(2) — Linux directory enumeration. The fd must be opened
// with O_RDONLY on a directory. `buf` receives a packed sequence of
// linux_dirent64 records:
//
// struct linux_dirent64 {
// u64 d_ino; // 0..7
// i64 d_off; // 8..15
// u16 d_reclen; // 16..17 — total bytes for this record
// u8 d_type; // 18 — DT_REG/DT_DIR/...
// u8 d_name[]; // 19.. — NUL-terminated name + padding
// };
//
// Returns bytes written into `buf` (advance by d_reclen to walk),
// 0 at end-of-directory, or a negative errno.
export fn getdents64(fd: i32, buf: *u8, n: u64) i64 = {
return syscall3(nr.GETDENTS64, fd: i64, buf: i64, n: i64);
};
// ---- environment ------------------------------------------------------
// rt_envp — runtime-side getter. rt/start.s captures envp into a DATAW
// slot before calling main; this binding lifts the captured pointer
// into ww. Same FFI shape as rt_syscall / rt_malloc / rt_abort: a TEXT
// symbol the linker resolves. The returned `**u8` is a NUL-terminated
// table of `*u8` entries, each pointing at a NUL-terminated
// "NAME=VALUE" byte sequence.
//
// We don't expose `rtenvp` directly; [[getenv]] is the only consumer.
@symbol("rt_envp") fn rtenvp() **u8;
// getenv — POSIX getenv. Returns a borrowed `str` view over the value
// bytes of the named environment variable, or void if the name is not
// present. The view is valid for the process lifetime — the bytes
// live in the kernel-supplied envp table at process entry. A future
// `setenv` (separate task) that grows the table behind the scenes
// would invalidate prior views; v1 has no setenv, so callers can
// hold the view indefinitely.
//
// Mirrors Hare's os::tryenv shape (returns void rather than panicking
// on missing). Hare also ships os::getenv (`(str | void)`) and
// os::mustenv (panic-on-missing); ww collapses to the single
// `(str | void)` form for now — consumers wanting "must" semantics
// abort at the call site.
//
// Algorithm: walk the NUL-pointer-terminated `environ` table doing a
// "name=" prefix match against each entry, byte-wise. NUL inside
// `name` would never match a real env var (env var names cannot
// contain '\0'), so we don't filter — POSIX puts that responsibility
// on the caller.
export fn getenv(name: str) (str | void) = {
let envp: **u8 = rtenvp();
let i: i32 = 0;
for (true) {
let entry: *u8 = envp[i];
if (entry == nil: *u8) { return; };
let j: i32 = 0;
let matched: bool = true;
for (j < name.len) {
if (entry[j] == 0u8) { matched = false; break; };
if (entry[j] != name[j]) { matched = false; break; };
j += 1;
};
if (matched) {
if (entry[name.len] == 61u8) { // '='
let val: *u8 = entry + ((name.len + 1): u64);
let n: i32 = 0;
for (val[n] != 0u8) { n += 1; };
let r: str;
r.ptr = val;
r.len = n;
return r;
};
};
i += 1;
};
return;
};
// ---- stat / lstat / fstat / exists -----------------------------------
//
// Ports of Hare's stat family (ref/hare/fs/fs.ha:172,196 +
// ref/hare/sys/+linux/stat.ha:24-58). The Hare surface returns
// `filestat` by value; ww's cgreturn ABI tops out at 24B today (see
// STATUS task #21) and filestat is 80B, so [[stat]] / [[lstat]] /
// [[fstat]] take an out-parameter and return `(void | oserror)`.
// Re-evaluate the by-value shape when full sret lands.
//
// `filestat`, `mode`, and `stat_mask` live in lib/os because ww has
// no lib/fs yet; Hare puts them in `fs::`. These types graduate to
// lib/fs when that module ships — callers should expect a future
// re-export.
//
// Underlying syscall is SYS_newfstatat (262), which unifies
// stat/lstat/fstat through the `dirfd + flags` triple:
// stat = newfstatat(AT_FDCWD, path, 0)
// lstat = newfstatat(AT_FDCWD, path, AT_SYMLINK_NOFOLLOW)
// fstat = newfstatat(fd, "", AT_EMPTY_PATH)
// Avoiding SYS_statx — its 256B variable layout would buy btime,
// but Hare's filestat doesn't expose btime either, so we stay on
// the simpler 144B kernel struct.
// fstatat(2) flag values. Linux constants from <linux/fcntl.h>.
// Names mirror Hare's ref/hare/sys/+linux/types.ha:45-51 (capital-
// AT_ prefix, top-level `def`s).
export def AT_FDCWD: i32 = -100;
export def AT_SYMLINK_NOFOLLOW: i32 = 256; // 0x100
export def AT_EMPTY_PATH: i32 = 4096; // 0x1000
// mode — file-mode bits. Mirrors Hare's fs::mode (ref/hare/fs/
// types.ha:63). Permission bits are the standard Unix octal subset;
// type bits live in the S_IFMT = 0o170000 region. Type-bit test:
//
// let t: u32 = (fi.mode as u32) & 61440u32; // 0o170000 mask
// if (t == os.mode.DIR as u32) { /* directory */ };
//
// Numeric values are octal in Hare's source; ww has no octal
// literals so they're written as decimal with the octal in a
// trailing comment.
export type mode = enum u32 {
// permission bits
USER_RWX = 448u32, // 0o700
USER_RW = 384u32, // 0o600
USER_RX = 320u32, // 0o500
USER_R = 256u32, // 0o400
USER_W = 128u32, // 0o200
USER_X = 64u32, // 0o100
GROUP_RWX = 56u32, // 0o070
GROUP_RW = 48u32, // 0o060
GROUP_RX = 40u32, // 0o050
GROUP_R = 32u32, // 0o040
GROUP_W = 16u32, // 0o020
GROUP_X = 8u32, // 0o010
OTHER_RWX = 7u32, // 0o007
OTHER_RW = 6u32, // 0o006
OTHER_RX = 5u32, // 0o005
OTHER_R = 4u32, // 0o004
OTHER_W = 2u32, // 0o002
OTHER_X = 1u32, // 0o001
SETUID = 2048u32, // 0o4000
SETGID = 1024u32, // 0o2000
STICKY = 512u32, // 0o1000
// file-type bits (S_IFMT mask = 0o170000 = 61440)
UNKNOWN = 0u32,
FIFO = 4096u32, // 0o010000
CHR = 8192u32, // 0o020000
DIR = 16384u32, // 0o040000
BLK = 24576u32, // 0o060000
REG = 32768u32, // 0o100000
LINK = 40960u32, // 0o120000
SOCK = 49152u32, // 0o140000
};
// stat_mask — which filestat fields the call populated. Mirrors
// Hare's fs::stat_mask (ref/hare/fs/types.ha:129). newfstatat fills
// every field, so [[stat]] / [[lstat]] / [[fstat]] always set all
// seven bits OR-folded (see [[fillfilestat]]); per-bit testing is
// the documented sparse-backend pattern (cf. Hare's fs::fs network
// backends that only populate mtime+size).
export type stat_mask = enum u32 {
UID = 1u32,
GID = 2u32,
SIZE = 4u32,
INODE = 8u32,
ATIME = 16u32,
MTIME = 32u32,
CTIME = 64u32,
};
// filestat — Hare's fs::filestat (ref/hare/fs/types.ha:141). 80
// bytes. Times are time.instant (ref/hare/time/instant.ha:9) — the
// canonical Hare shape. See module-header note re: graduation to
// lib/fs.
export type filestat = struct {
mask: stat_mask, // 0 (4)
mode: mode, // 4 (4)
uid: u32, // 8 (4)
gid: u32, // 12 (4)
sz: u64, // 16 (8)
inode: u64, // 24 (8)
atime: time.instant, // 32 (16)
mtime: time.instant, // 48 (16)
ctime: time.instant, // 64 (16) — ends at 80
};
// kstat — x86_64 kernel `struct stat` layout. Mirrors
// arch/x86/include/uapi/asm/stat.h (`__kernel_ulong_t`-keyed
// fields). 144 bytes. Module-internal; SYS_newfstatat writes into
// this buffer and the public stat fns then copy the bits into the
// Hare-shaped [[filestat]].
type kstat = struct {
dev: u64, // 0
ino: u64, // 8
nlink: u64, // 16
mode: u32, // 24
uid: u32, // 28
gid: u32, // 32
pad0: u32, // 36
rdev: u64, // 40
sz: i64, // 48
blksize: i64, // 56
blocks: i64, // 64
atime_sec: i64, // 72
atime_nsec: i64, // 80
mtime_sec: i64, // 88
mtime_nsec: i64, // 96
ctime_sec: i64, // 104
ctime_nsec: i64, // 112
unused0: i64, // 120
unused1: i64, // 128
unused2: i64, // 136 — ends at 144
};
// emptypath — single-NUL byte used as the `pathname` arg to
// newfstatat with AT_EMPTY_PATH. The kernel requires a non-NULL
// pointer to a zero-length C string, NOT a null pointer. Bytes are
// read-only from the kernel's view; ww has no module-level const so
// this is a writable `let`.
let emptypath: [1]u8 = [0u8];
// fillfilestat — copy a 144B kstat into the 80B Hare-shaped
// filestat. Internal helper used by all three public entry points.
// Mirrors Hare's st_to_filestat (ref/hare/os/+linux/dirfdfs.ha:259):
// newfstatat populates every field, so the mask is the OR-fold of
// all seven Hare stat_mask bits.
fn fillfilestat(out: *filestat, k: *kstat) void = {
out.mask = stat_mask.UID | stat_mask.GID | stat_mask.SIZE
| stat_mask.INODE | stat_mask.ATIME | stat_mask.MTIME
| stat_mask.CTIME;
out.mode = k.mode: mode;
out.uid = k.uid;
out.gid = k.gid;
out.sz = k.sz: u64;
out.inode = k.ino;
out.atime.sec = k.atime_sec;
out.atime.nsec = k.atime_nsec;
out.mtime.sec = k.mtime_sec;
out.mtime.nsec = k.mtime_nsec;
out.ctime.sec = k.ctime_sec;
out.ctime.nsec = k.ctime_nsec;
};
// stat — fill *out with metadata for `path`. Follows symlinks.
// Returns ENAMETOOLONG (-36) as `oserror` if the path overflows
// PATH_MAX.
//
// Mirrors Hare's sys::stat (ref/hare/sys/+linux/stat.ha:51) modulo
// the out-param shape forced by the cgreturn 24B cap. Note: Hare's
// higher-level fs::stat (ref/hare/fs/fs.ha:172) instead has lstat
// semantics — we follow sys::stat's POSIX-stat behavior here.
export fn stat(out: *filestat, path: str) (void | oserror) = {
let cp: *u8 = kpath(path);
if (cp == nil: *u8) { return -36i64: oserror; };
let k: kstat;
let r: i64 = syscall4(nr.NEWFSTATAT,
AT_FDCWD: i64, cp: i64, (&k): i64, 0i64);
if (r < 0) { return r: oserror; };
fillfilestat(out, &k);
};
// lstat — like [[stat]] but does NOT follow a terminal symlink.
// Mirrors Hare's sys::lstat (ref/hare/sys/+linux/stat.ha:57).
export fn lstat(out: *filestat, path: str) (void | oserror) = {
let cp: *u8 = kpath(path);
if (cp == nil: *u8) { return -36i64: oserror; };
let k: kstat;
let r: i64 = syscall4(nr.NEWFSTATAT,
AT_FDCWD: i64, cp: i64, (&k): i64,
AT_SYMLINK_NOFOLLOW: i64);
if (r < 0) { return r: oserror; };
fillfilestat(out, &k);
};
// fstat — like [[stat]] but addresses the file by fd. Uses
// newfstatat(fd, "", AT_EMPTY_PATH); the kernel resolves the fd
// directly. Mirrors Hare's sys::fstat (ref/hare/sys/+linux/stat.ha:54).
export fn fstat(out: *filestat, fd: i32) (void | oserror) = {
let k: kstat;
let r: i64 = syscall4(nr.NEWFSTATAT,
fd: i64, (&emptypath[0]): i64, (&k): i64,
AT_EMPTY_PATH: i64);
if (r < 0) { return r: oserror; };
fillfilestat(out, &k);
};
// exists — true if `path` resolves to anything (regular file,
// directory, symlink, ...). Stat-shaped (Hare's `fs::exists`,
// ref/hare/fs/fs.ha:196) — no separate syscall. Symlinks are
// followed; a dangling symlink is `false`. ENAMETOOLONG is
// swallowed as `false` — Hare's os::exists doc says "true if a
// node exists at the given path, or false if not."
//
// Race warning: prefer "open and handle the error" over "exists
// then open" in real code (Hare's docstring carries the same
// note). The race is unavoidable in this shape.
//
// Goes through SYS_newfstatat directly rather than match'ing on
// [[stat]]'s `(void | oserror)` return. Functionally identical;
// the direct shape sidesteps a cstage/wwstage cgen disagreement
// on the slot size of `(void | oserror)` (cstage 16B, wwstage 24B
// — same class as STATUS #22, surfaced first time a match on this
// shape combined with an 80B local-struct local frame). Use the
// match shape once #22 lands.
export fn exists(path: str) bool = {
let cp: *u8 = kpath(path);
if (cp == nil: *u8) { return false; };
let k: kstat;
let r: i64 = syscall4(nr.NEWFSTATAT,
AT_FDCWD: i64, cp: i64, (&k): i64, 0i64);
return r >= 0i64;
};
// strconv — arbitrary-precision decimal engine for float↔string
// conversion. Mirrors ref/hare/strconv/decimal.ha (Hare in turn ports
// Go's lib/strconv/decimal.go). Pure integer arithmetic; no f32/f64
// references (#121 residual-guard SAFE).
//
// Spelling divergences from Hare (mechanical, ww-side parser shape):
// - Hare `let a = X, b = Y;` → two single `let` statements
// (ww parser doesn't accept comma-separated bindings).
// - Hare `tbl[lo..]` open-ended slice → direct indexing
// `tbl[lo + i]` at point-of-use (equivalent algorithm; no
// allocation, no aliasing). ww `[lo:hi]` uses `:`; `..` form
// is not parsed.
// - Hare `0z`/`1z` size literals → ww has no `z` suffix; pre-bind
// `let SZ_ZERO: size = (0u64: size);` etc. at function entry
// ("hoisted size casts as local consts" — ww `T: type` casts
// embedded inside expressions confuse the parser).
// - Hare `~0u64` typed-suffix literal → ww parser rejects `~` on
// typed-suffix; route via a named zero local + `~zero`.
// - Hare `for (cond; afterthought)` 2-clause → ww 3-clause
// `for (init; cond; post)` (when continue is used; the post
// must run each iteration) or inline-the-afterthought in body
// (when no continue exists in the loop).
// - Hare `fn foo() T = if (cond) {...} else expr;` expression body
// → ww requires a `{}` block body throughout.
// - Hare bare `assert(cond)` builtin → `os.assert(cond, msg)`;
// wwstage cgen has no `assert` intercept (deferred fold).
// - In-file instances of the above hoist pattern: `i_sz` (line 93)
// hoists a per-iteration size cast out of a for-loop comparison
// (bullet 3 sub-case — the size-cast hoist applied inside a loop
// body, not just at function entry); `lowbit_lit` (line 242)
// decomposes Hare's `(nd > 0 && d.digits[nd - 1] & 1 != 0)` into
// a stepwise boolean local to dodge ww parser precedence on mixed
// `&` / `&&` / `!=` within a single expression.
//
// CGEN class closures consumed (post-prereqs):
// - #131 (4acab6e) — `len(d.digits)` compile-time-folds cs==ww
// - #134 (36bf603) — `d.digits[nd] >= 5u8` picks JAE (unsigned)
// - #133 (3986818) — `d.digits[i] += 1u8` load-op-store BOTH
// stages
// - #135 (ade6840) — `(*d).digits[i]` read+write N_DOT-base addr
//
// Drew CGEN-SAFE invariants:
// - #129: module-level decls here are integer-literal defs only.
// - #128: digits is fundamental [800]u8, zero-init only.
// - #121: zero float ops.
// - Drew watch-item `*d = decimal{...};` reset (line 110 in Hare):
// pointer-deref reset to composite-literal probed cs==ww
// byte-id safe.
package strconv;
import os;
// ref/hare/strconv/decimal.ha:5.
def maxshift: u8 = 60u8;
// ref/hare/strconv/decimal.ha:6.
def decimal_point_range: u16 = 2047u16;
// ref/hare/strconv/decimal.ha:8-26. Field layout 1:1. The 800-digit
// bound covers subnormal doubles (min exp -1074, max mantissa 4e16
// → at most 767 digits; 800 leaves headroom).
export type decimal = struct {
digits: [800]u8,
nd: size,
dp: i32,
negative: bool,
truncated: bool,
};
// ref/hare/strconv/decimal.ha:29-33. Strip trailing zeros.
fn trim(d: *decimal) void = {
let SZ_ZERO: size = (0u64: size);
let SZ_ONE: size = (1u64: size);
for (d.nd > SZ_ZERO && d.digits[d.nd - SZ_ONE] == 0u8) {
d.nd -= SZ_ONE;
};
};
// ref/hare/strconv/decimal.ha:35-55. Compute the digit-count
// increase for a left-shift `shift` (consults left_shift_table +
// pow5_table from stof_data.ww, bb6f840). Uses `continue` so the
// loop stays in 3-clause form for byte-id-correct post-increment.
fn leftshift_newdigits(d: *decimal, shift: u32) u32 = {
shift &= 63u32;
let x_a: u32 = (left_shift_table[shift]: u32);
let x_b: u32 = (left_shift_table[shift + 1u32]: u32);
let nn: u32 = x_a >> 11u32;
let pow5_a: u32 = 0x7FFu32 & x_a;
let pow5_b: u32 = 0x7FFu32 & x_b;
let n: u32 = pow5_b - pow5_a;
for (let i: u32 = 0u32; i < n; i += 1u32) {
let i_sz: size = (i: size);
if (i_sz >= d.nd) {
return nn - 1u32;
} else if (d.digits[i] == pow5_table[pow5_a + i]) {
continue;
} else if (d.digits[i] < pow5_table[pow5_a + i]) {
return nn - 1u32;
} else {
return nn;
};
};
return nn;
};
// ref/hare/strconv/decimal.ha:57-91. Shift `d` left by k bits.
fn leftshift(d: *decimal, k: u32) void = {
let SZ_ONE: size = (1u64: size);
let SZ_BOUND: size = (len(d.digits): size);
let kU64: u64 = (k: u64);
let MAXSHIFT_U32: u32 = (maxshift: u32);
os.assert(k <= MAXSHIFT_U32, "strconv.leftshift: k > maxshift");
if (d.nd == (0u64: size)) { return; };
let nn: u32 = leftshift_newdigits(d, k);
let r: int = (d.nd: int) - 1;
let w: size = (r: size) + (nn: size);
let n: u64 = 0u64;
for (r >= 0) {
n += (d.digits[r]: u64) << kU64;
let quo: u64 = n / 10u64;
let rem: u64 = n - 10u64 * quo;
if (w < SZ_BOUND) {
d.digits[w] = (rem: u8);
} else if (rem != 0u64) {
d.truncated = true;
};
n = quo;
r -= 1;
w -= SZ_ONE;
};
for (n > 0u64) {
let quo: u64 = n / 10u64;
let rem: u64 = n - 10u64 * quo;
if (w < SZ_BOUND) {
d.digits[w] = (rem: u8);
} else if (rem != 0u64) {
d.truncated = true;
};
n = quo;
w -= SZ_ONE;
};
d.nd += (nn: size);
if (d.nd > SZ_BOUND) {
d.nd = SZ_BOUND;
};
d.dp += (nn: i32);
trim(d);
};
// ref/hare/strconv/decimal.ha:93-134. Shift `d` right by k bits.
// Two outer Hare 2-clause loops (`for (cond; r += 1)`) are inlined
// as `for (cond) { ... r += SZ_ONE; }` since neither uses continue.
fn rightshift(d: *decimal, k: u32) void = {
let SZ_ZERO: size = (0u64: size);
let SZ_ONE: size = (1u64: size);
let SZ_BOUND: size = (len(d.digits): size);
let kU64: u64 = (k: u64);
let r: size = SZ_ZERO;
let w: size = SZ_ZERO;
let n: u64 = 0u64;
for ((n >> kU64) == 0u64) {
if (r >= d.nd) {
if (n == 0u64) {
d.nd = SZ_ZERO;
return;
};
for ((n >> kU64) == 0u64) {
n *= 10u64;
r += SZ_ONE;
};
break;
};
n = n * 10u64 + (d.digits[r]: u64);
r += SZ_ONE;
};
d.dp -= (r: i32) - 1;
if (d.dp < -(decimal_point_range: i32)) {
// Drew-watch-item: pointer-deref reset to composite
// literal — probed cs==ww byte-id safe in pre-flight.
*d = decimal { ... };
return;
};
let mask: u64 = (1u64 << kU64) - 1u64;
for (r < d.nd) {
let dig: u64 = n >> kU64;
n &= mask;
d.digits[w] = (dig: u8);
w += SZ_ONE;
n = n * 10u64 + (d.digits[r]: u64);
r += SZ_ONE;
};
for (n > 0u64) {
let dig: u64 = n >> kU64;
n &= mask;
if (w < SZ_BOUND) {
d.digits[w] = (dig: u8);
w += SZ_ONE;
} else if (dig > 0u64) {
d.truncated = true;
};
n *= 10u64;
};
d.nd = w;
trim(d);
};
// ref/hare/strconv/decimal.ha:138-153. Shift right (k < 0) or left
// (k > 0). Hardware shifts cap at 60 bits without losing top
// digits, so break large shifts into maxshift-sized chunks.
fn decimal_shift(d: *decimal, k: int) void = {
let MAXSHIFT_INT: int = (maxshift: int);
let MAXSHIFT_U32: u32 = (maxshift: u32);
if (d.nd == (0u64: size)) { return; };
if (k > 0) {
for (k > MAXSHIFT_INT) {
leftshift(d, MAXSHIFT_U32);
k -= MAXSHIFT_INT;
};
leftshift(d, (k: u32));
} else if (k < 0) {
for (k < -MAXSHIFT_INT) {
rightshift(d, MAXSHIFT_U32);
k += MAXSHIFT_INT;
};
rightshift(d, ((-k): u32));
};
};
// ref/hare/strconv/decimal.ha:155-160. Banker's rounding decision:
// at the exact half (digit==5, no more digits) round to even (the
// preceding digit's low bit decides); past-half rounds up; below-
// half rounds down. Hare's expression-bodied `if` re-shaped as a
// block per ww parser.
fn should_round_up(d: *decimal, nd: uint) bool = {
let nd_sz: size = (nd: size);
let SZ_ONE: size = (1u64: size);
let U_ONE: uint = (1u32: uint);
let U_ZERO: uint = (0u32: uint);
if (nd_sz < d.nd) {
if (d.digits[nd] == 5u8 && (nd_sz + SZ_ONE) == d.nd) {
let lowbit_lit: bool = false;
if (nd > U_ZERO) {
if ((d.digits[nd - U_ONE] & 1u8) != 0u8) {
lowbit_lit = true;
};
};
return d.truncated || lowbit_lit;
} else {
return d.digits[nd] >= 5u8;
};
};
return false;
};
// ref/hare/strconv/decimal.ha:162-166. Round to `nd` digits.
fn round(d: *decimal, nd: uint) void = {
if ((nd: size) >= d.nd) { return; };
if (should_round_up(d, nd)) {
roundup(d, nd);
} else {
rounddown(d, nd);
};
};
// ref/hare/strconv/decimal.ha:168-172. Truncate to `nd` digits.
fn rounddown(d: *decimal, nd: uint) void = {
if ((nd: size) >= d.nd) { return; };
d.nd = (nd: size);
trim(d);
};
// ref/hare/strconv/decimal.ha:174-186. Round up to `nd` digits;
// propagate carry. If all 9s, the result is a single 1 with the
// decimal point advanced.
fn roundup(d: *decimal, nd: uint) void = {
let SZ_ONE: size = (1u64: size);
if ((nd: size) >= d.nd) { return; };
for (let i: int = (nd: int) - 1; i >= 0; i -= 1) {
if (d.digits[i] < 9u8) {
d.digits[i] += 1u8;
d.nd = (i: size) + SZ_ONE;
return;
};
};
d.digits[0] = 1u8;
d.nd = SZ_ONE;
d.dp += 1;
};
// ref/hare/strconv/decimal.ha:188-202. Read `d` as the integer
// rounded to `d.dp` digits. Returns 0 if `d.dp <= 0`; returns
// ~0u64 if `d.dp > 18` (exceeds u64 range). Hare's two 2-clause
// loops (`for (cond; i += 1)`) are inlined per the spelling
// divergence at file top.
fn decimal_round(d: *decimal) u64 = {
let SZ_ZERO: size = (0u64: size);
let SZ_ONE: size = (1u64: size);
if (d.nd == SZ_ZERO || d.dp < 0) { return 0u64; };
if (d.dp > 18) {
// Hare's `~0u64` doesn't parse on a typed-suffix literal
// in ww; route via a named zero.
let zero: u64 = 0u64;
return ~zero;
};
let dp_sz: size = ((d.dp: uint): size);
let i: size = SZ_ZERO;
let n: u64 = 0u64;
for (i < dp_sz && i < d.nd) {
n = n * 10u64 + (d.digits[i]: u64);
i += SZ_ONE;
};
for (i < dp_sz) {
n *= 10u64;
i += SZ_ONE;
};
if (should_round_up(d, (d.dp: uint))) {
n += 1u64;
};
return n;
};
// floats — f64 classification, sign, bit-reinterpret core, and the f64
// decompose half (subnormal-normalize + frexp). Ported from
// ref/hare/math/floats.ha (fold-1: classify/sign/bits; fold-2a:
// issubnormalf64/normalizef64/frexpf64; strconv-foundation fold-1a:
// F32 bit-layout + f32bits/f32frombits + floatinfo struct type;
// fold-1b: NAN_BITS/INF_BITS sentinels; γ-cleanup: f64info/f32info
// instances re-folded once #149 lowered &math.f64info). frexpf64's
// zero guard `n == 0f64` rides the #103
// fix (no-decimal f64 literal now materialized into XMM) and its
// (f64, i64) tuple return rides the #105 fix (tuple f64-word read).
// The ldexp/modfrac/nextafter family stays deferred (need f64 DIVIDE).
package math;
// Returns the binary representation of the given f64.
// ref/hare/math/floats.ha:5. Parens around &n are load-bearing: ww's `:`
// cast binds tighter than unary `&`, so Hare's `*(&n: *u64)` would parse
// as `*(&(n: *u64))`; `(&n): *u64` reinterprets the address as intended.
export fn f64bits(n: f64) u64 = {
return *((&n): *u64);
};
// Returns the binary representation of the given f32.
// ref/hare/math/floats.ha:8
export fn f32bits(n: f32) u32 = {
return *((&n): *u32);
};
// Returns f64 with the given binary representation.
// ref/hare/math/floats.ha:11
export fn f64frombits(n: u64) f64 = {
return *((&n): *f64);
};
// Returns f32 with the given binary representation.
// ref/hare/math/floats.ha:14
export fn f32frombits(n: u32) f32 = {
return *((&n): *f32);
};
// ref/hare/math/floats.ha:17,20,23 declare these as untyped int. ww has
// no untyped def (every def carries a type) and routes shift/bitwise
// through unify_arith, which rejects mixed operand types (cmd/wcc/
// check.c:769). The bit-structure consts are used only as u64 shift
// amounts and mask widths, so they are typed u64 here — the closest
// stand-in for Hare's untyped-int adapt at those use sites.
// The number of bits in the significand of the binary representation of f64.
export def F64_MANTISSA_BITS: u64 = 52;
// The number of bits in the exponent of the binary representation of f64.
export def F64_EXPONENT_BITS: u64 = 11;
// The bias of the exponent of the binary representation of f64. Subtract this
// from the exponent in the binary representation to get the actual exponent.
export def F64_EXPONENT_BIAS: u64 = 1023;
// Mask with each bit of an f64's mantissa set.
// ref/hare/math/floats.ha:37
export def F64_MANTISSA_MASK: u64 = (1 << F64_MANTISSA_BITS) - 1;
// Mask with each bit of an f64's exponent set.
// ref/hare/math/floats.ha:40
export def F64_EXPONENT_MASK: u64 = (1 << F64_EXPONENT_BITS) - 1;
// The mask that gets an f64's sign.
// ref/hare/math/floats.ha:75
def F64_SIGN_MASK: u64 = 1u64 << 63;
// Mask that clears an f64's exponent field, keeping sign + mantissa.
// ref/hare/math/floats.ha:77. Hare hardcodes the 0x800FFFFFFFFFFFFF binary
// literal because its lexer can't const-fold the expression; ww's #88
// def-const-fold can, so the readable form is kept. floats.ha:79's NOTE
// expression has an `0u64 &` upstream typo (it would yield 0); the value it
// documents is exactly ~(F64_EXPONENT_MASK << F64_MANTISSA_BITS).
def F64_EXP_REMOVAL_MASK: u64 = ~(F64_EXPONENT_MASK << F64_MANTISSA_BITS);
// The f64 bit pattern whose exponent field evaluates to zero (0.5 scale).
// ref/hare/math/floats.ha:84
def F64_EXP_ZERO: u64 = (F64_EXPONENT_BIAS - 1) << F64_MANTISSA_BITS;
// F32 bit-structure constants. ref/hare/math/floats.ha:27,30,33 declare
// these as untyped int; ww has no untyped def, so they ride u32 (matching
// the u32 bit container, the same way the F64 family rides u64 — see
// the note above F64_MANTISSA_BITS).
// The number of bits in the significand of the binary representation of f32.
// ref/hare/math/floats.ha:27
export def F32_MANTISSA_BITS: u32 = 23u32;
// The number of bits in the exponent of the binary representation of f32.
// ref/hare/math/floats.ha:30
export def F32_EXPONENT_BITS: u32 = 8u32;
// The bias of the exponent of the binary representation of f32. Subtract this
// from the exponent in the binary representation to get the actual exponent.
// ref/hare/math/floats.ha:33
export def F32_EXPONENT_BIAS: u32 = 127u32;
// Mask with each bit of an f32's mantissa set.
// ref/hare/math/floats.ha:43
export def F32_MANTISSA_MASK: u32 = (1u32 << F32_MANTISSA_BITS) - 1u32;
// Mask with each bit of an f32's exponent set.
// ref/hare/math/floats.ha:46
export def F32_EXPONENT_MASK: u32 = (1u32 << F32_EXPONENT_BITS) - 1u32;
// The mask that gets an f32's sign.
// ref/hare/math/floats.ha:87
def F32_SIGN_MASK: u32 = 1u32 << 31;
// Mask that clears an f32's exponent field, keeping sign + mantissa.
// ref/hare/math/floats.ha:92. Hare hardcodes the binary literal (its
// lexer can't const-fold the expression); ww's #88 def-const-fold can,
// so the readable form is kept (same call as F64_EXP_REMOVAL_MASK).
def F32_EXP_REMOVAL_MASK: u32 = ~(F32_EXPONENT_MASK << F32_MANTISSA_BITS);
// The f32 bit pattern whose exponent field evaluates to zero (0.5 scale).
// ref/hare/math/floats.ha:95
def F32_EXP_ZERO: u32 = (F32_EXPONENT_BIAS - 1u32) << F32_MANTISSA_BITS;
// floatinfo — IEEE-754 shape parameters for a binary float type, passed
// to width-generic helpers in strconv (eisel_lemire, floatbits, hex_to_bits,
// mkfloat). ref/hare/math/floats.ha:101. Hare's `int` maps to ww's `int`
// (machine word, 8B; project_int_machine_word_derived_limits), so the
// expbias field stays `int` — that keeps the fold-4 stof port byte-for-byte
// against ref/hare/strconv/stof.ha:248,288 (`let e: int = 0` arithmetic
// against `f.expbias` of the same type, no cast at use site).
export type floatinfo = struct {
// Bits in significand.
mantbits: u64,
// Bits in exponent.
expbits: u64,
// Bias of exponent.
expbias: int,
// Mask for mantissa.
mantmask: u64,
// Mask for exponent.
expmask: u64,
};
// floatinfo instances for the f64 / f32 types, consumed by the
// width-generic strconv helpers via &math.f64info (cross-module
// address-of, lowered since #149). ref/hare/math/floats.ha:117,126.
// Hare spells the masks (1 << 52) - 1 / (1 << 23) - 1; the #129 A.2
// struct-composite static-init path folds only bare-literal field
// initializers, not const-fold expressions, so the value-identical hex
// literals are used here (0xFFFFFFFFFFFFF == (1<<52)-1, 0x7FFFFF ==
// (1<<23)-1 — same hex-literal style as the NAN_BITS/INF_BITS sentinels
// below). expbias rides `int` (the field type) with no suffix.
export def f64info: floatinfo = floatinfo {
mantbits = 52u64,
expbits = 11u64,
expbias = 1023,
mantmask = 0xFFFFFFFFFFFFFu64,
expmask = 0x7FFu64,
};
export def f32info: floatinfo = floatinfo {
mantbits = 23u64,
expbits = 8u64,
expbias = 127,
mantmask = 0x7FFFFFu64,
expmask = 0xFFu64,
};
// IEEE-754 quiet-NaN and positive-Infinity f64 bit sentinels.
// ref/hare/math/floats.ha:137,141. Hare exports `def NAN = 0.0/0.0;` and
// `def INF = 1.0/0.0;` (untyped float def-fold); ww's cgen doesn't lower
// `def: f64 = expr;` (the symbol comes out undefined at link time — see
// #129). Callers materialize the f64 sentinel via f64frombits(NAN_BITS)
// / f64frombits(INF_BITS); same bit-exact value, one extra reinterpret.
// 0x7FF8000000000000 is the IEEE-754 binary64 quiet-NaN (sign=0, exp=
// all-ones, mantissa MSB=1, rest=0); 0x7FF0000000000000 is +Infinity
// (sign=0, exp=all-ones, mantissa=0). Re-fold to `def NAN: f64 = ...`
// when #129 closes (γ-cleanup pattern per amalloc-drop precedent).
export def NAN_BITS: u64 = 0x7FF8000000000000u64;
export def INF_BITS: u64 = 0x7FF0000000000000u64;
// Returns true if the given floating-point number is NaN.
// ref/hare/math/floats.ha:144 (Hare's expression body inlined into a
// block: ww has no expression-bodied fn form, only brace blocks).
export fn isnan(n: f64) bool = {
return n != n;
};
// Returns true if the given floating-point number is infinite.
// ref/hare/math/floats.ha:147
export fn isinf(n: f64) bool = {
const bits = f64bits(n);
const mant = bits & F64_MANTISSA_MASK;
const exp = bits >> F64_MANTISSA_BITS & F64_EXPONENT_MASK;
return exp == F64_EXPONENT_MASK && mant == 0;
};
// Returns true if the given f64 is subnormal.
// ref/hare/math/floats.ha:179
export fn issubnormalf64(n: f64) bool = {
const bits = f64bits(n);
const mant = bits & F64_MANTISSA_MASK;
const exp = bits >> F64_MANTISSA_BITS & F64_EXPONENT_MASK;
return exp == 0 && mant != 0;
};
// Returns the absolute value of f64 n.
// ref/hare/math/floats.ha:195
export fn absf64(n: f64) f64 = {
if (isnan(n)) {
return n;
};
return f64frombits(f64bits(n) & ~F64_SIGN_MASK);
};
// Returns 1 if x is positive and -1 if x is negative. Note that zero is also
// signed.
// ref/hare/math/floats.ha:212
export fn signf64(x: f64) i64 = {
if (f64bits(x) & F64_SIGN_MASK == 0) {
return 1i64;
} else {
return -1i64;
};
};
// Returns whether or not x is positive.
// ref/hare/math/floats.ha:231
export fn ispositivef64(x: f64) bool = {
return signf64(x) == 1i64;
};
// Returns whether or not x is negative.
// ref/hare/math/floats.ha:237
export fn isnegativef64(x: f64) bool = {
return signf64(x) == -1i64;
};
// Returns x, but with the sign of y.
// ref/hare/math/floats.ha:243
export fn copysignf64(x: f64, y: f64) f64 = {
return f64frombits((f64bits(x) & ~F64_SIGN_MASK) |
(f64bits(y) & F64_SIGN_MASK));
};
// Takes a potentially subnormal f64 n and returns a normal f64 normal_float
// and an exponent exp such that n == normal_float * 2^{exp}.
// ref/hare/math/floats.ha:256
export fn normalizef64(n: f64) (f64, i64) = {
if (issubnormalf64(n)) {
const factor = 1i64 << (F64_MANTISSA_BITS: i64);
const normal_float = (n * (factor: f64));
return (normal_float, -(F64_MANTISSA_BITS: i64));
};
return (n, 0);
};
// Breaks a f64 down into its mantissa and exponent. The mantissa will be
// between 0.5 and 1.
// ref/hare/math/floats.ha:278
export fn frexpf64(n: f64) (f64, i64) = {
if (isnan(n) || isinf(n) || n == 0f64) {
return (n, 0);
};
const normalized = normalizef64(n);
const normal_float = normalized.0;
const normalization_exp = normalized.1;
const bits = f64bits(normal_float);
const raw_exp: u64 = (bits >> F64_MANTISSA_BITS) & F64_EXPONENT_MASK;
const exp: i64 = normalization_exp +
(raw_exp: i64) - (F64_EXPONENT_BIAS: i64) + 1;
const mantissa: f64 =
f64frombits((bits & F64_EXP_REMOVAL_MASK) | F64_EXP_ZERO);
return (mantissa, exp);
};
// math — numeric helpers. Subset of Hare's math::; only the absolute-
// value pair for the signed integer types we currently care about. The
// return type is unsigned so that abs(I32_MIN) doesn't overflow.
package math;
export fn absi32(n: i32) u32 = {
if (n < 0) { return (-n): u32; };
return n: u32;
};
export fn absi64(n: i64) u64 = {
if (n < 0) { return (-n): u64; };
return n: u64;
};
// strconv — float→string via Ryū (shortest round-trippable decimal).
// Mirrors ref/hare/strconv/ftos_ryu.ha (the algorithm core) +
// ref/hare/strconv/ftos.ha:432 (the f64tos driver). Ryū: Ulf Adams,
// https://doi.org/10.1145/3192366.3192369 — Hare translated it from the
// reference C (https://github.com/ulfjack/ryu); ww follows Hare.
//
// SCOPE — the f64tos + f32tos shortest-representation subset (Hare's
// ffmt::G, prec=void, fflags::NONE). f32tos (ftos.ha:448) + its f32 Ryū
// sub-path (f32todecf32 + mulpow5inv/pow5_divpow2 + mulshift32 + the *32
// helpers, reusing the shared u64-core + the f64 SPLIT2 tables — the f32
// path has no separate tables, matching ftos_ryu.ha) ship here in fold-5b
// (task #67): the gating #143 f32-arg-push cgen fix landed (aff7725, MOVSS
// both stages), so f32tos's math.f32bits(n) call — passing an f32 arg — is
// now byte-id-clean. One deferral remains:
// - the parametric fftosf/ffmt/fflags/ftosf surface → task #64 (needs
// io::handle/memio + a `(size|io::error)?` per appendrune (#158);
// for G/void/NONE the ffmt/fflags/precision/multiprecision-fallback
// machinery is provably dead code — `ok` is always true → init_dec/
// compute_round/round unreachable — which bootstrap-coverage rejects).
// The lib note blesses "a documented subset". This file ships ZERO float
// literals — Ryū is all bit/integer arithmetic on f64bits(n) — so the
// wwdump TK_FLOAT embedding concern is moot.
//
// Decomposition divergences (the #163-166 tuple/struct-ABI cluster —
// ww's partial tuple support miscompiles the shapes Hare uses; the
// WORKING shapes, struct-RETURN + scalar-PARAMS, are this algorithm's
// own idiom: ftos_ryu.ha:12 already uses `struct r128` not a tuple for
// u128mul, and fold-4/stof.ww decomposed likewise):
// - `mulshiftall64`'s tuple param `mul:(u64,u64)` → two scalar params
// `mul0,mul1` (#163: tuple-as-param reads garbage); its 3-tuple
// return `(u64,u64,u64)` → 24B struct `ryuv` (#164: 3-tuple return
// reads 0; struct-RETURN is byte-id-clean — r128 precedent). NO
// struct-as-PARAM anywhere (#165: 16B struct-param diverges cs≠ww).
// - `f64computeinvpow5`/`f64computepow5` keep their 2-tuple `(u64,u64)`
// return (call-return 2-tuple + `.0`/`.1` is byte-id-clean — the
// math/floats.ww frexpf64 precedent).
// - dead `mulshift64` (tuple-param, never called) + dead
// `F32/F64_DECIMAL_DIGITS` dropped.
//
// Spelling divergences (mechanical, ww parser/cgen; cite ftos_ryu.ha):
// - scalar-PARAM mutation (`m<<=1`, `value*=…`) → copy-to-local
// (stof.ww hex_to_bits precedent).
// - `&&=` → `x = x && y`. `ibool=if(b)1 else 0` expr-body → block.
// comma `let a=…, b=…` → split. `if/else` expr-yield → pre-bound
// local + block. `assert()` → `os.assert(cond,msg)`.
// - 2D row-bind `mul=TBL[base]` → direct double-index `TBL[base][0/1]`
// (#155 / #156, stof.ww eisel_lemire precedent).
// - ibool's u8 result + the u8 BITCOUNT defs cast explicitly to u32/u64
// at each use (Hare promotes; ww is strict — int-machine-word note).
// - a `(N: uint)` cast embedded inside an array subscript `[ ]` is
// rejected by the ww parser → hoist to a named local before the
// index (decimal.ww "hoist size casts" note); see init_dec_mant_exp
// + encode_e_dec.
package strconv;
import math;
import os;
// ref/hare/strconv/ftos_ryu.ha:33. (hi:lo) >> s, low 64 bits. Hare's
// "TODO: use 128-bit integers" — ww has no u128; pure-u64 decomposition.
// (u128mul + the r128 struct live in stof.ww, fold-4's first consumer;
// reused in-package here.)
fn u128rshift(lo: u64, hi: u64, s: u32) u64 = {
os.assert(s <= 64u32, "strconv.u128rshift: s > 64");
return (hi << (64u64 - (s: u64))) | (lo >> (s: u64));
};
// ref/hare/strconv/ftos_ryu.ha:39. Largest p with 5^p | value.
fn pow5fac(v: u64) u32 = {
let value: u64 = v;
let m_inv_5: u64 = 14757395258967641293u64; // 5 * m_inv_5 == 1 (mod 2^64)
let n_div_5: u64 = 3689348814741910323u64;
let count: u32 = 0u32;
for (true) {
os.assert(value != 0u64, "strconv.pow5fac: value == 0");
value *= m_inv_5;
if (value > n_div_5) { break; };
count += 1u32;
};
return count;
};
// ref/hare/strconv/ftos_ryu.ha:64.
fn ibool(b: bool) u8 = {
if (b) { return 1u8; };
return 0u8;
};
// ref/hare/strconv/ftos_ryu.ha:66-67.
fn pow5multiple(v: u64, p: u32) bool = { return pow5fac(v) >= p; };
// ref/hare/strconv/ftos_ryu.ha:69.
fn pow2multiple(v: u64, p: u32) bool = {
os.assert(v > 0u64, "strconv.pow2multiple: v == 0");
os.assert(p < 64u32, "strconv.pow2multiple: p >= 64");
return (v & ((1u64 << (p: u64)) - 1u64)) == 0u64;
};
// ref/hare/strconv/ftos_ryu.ha:89. The (v+, v-rounded, v-) triple.
// Decomposed: tuple param → mul0/mul1 scalars (#163); 3-tuple return →
// this struct (#164). The `mm_shift==1` `if/else`-yield → pre-bound
// `v_minus` + block.
type ryuv = struct { vp: u64, vr: u64, vm: u64 };
fn mulshiftall64(m: u64, mul0: u64, mul1: u64, j: i32, mm_shift: u32) ryuv = {
let mm: u64 = m << 1u64;
let r0: r128 = u128mul(mm, mul0);
let r1: r128 = u128mul(mm, mul1);
let lo: u64 = r0.lo;
let tmp: u64 = r0.hi;
let mid: u64 = tmp + r1.lo;
let hi: u64 = r1.hi + (ibool(mid < tmp): u64);
let lo2: u64 = lo + mul0;
let mid2: u64 = mid + mul1 + (ibool(lo2 < lo): u64);
let hi2: u64 = hi + (ibool(mid2 < mid): u64);
let v_plus: u64 = u128rshift(mid2, hi2, ((j - 64 - 1): u32));
let v_minus: u64 = 0u64;
if (mm_shift == 1u32) {
let lo3: u64 = lo - mul0;
let mid3: u64 = mid - mul1 - (ibool(lo3 > lo): u64);
let hi3: u64 = hi - (ibool(mid3 > mid): u64);
v_minus = u128rshift(mid3, hi3, ((j - 64 - 1): u32));
} else {
let lo3: u64 = lo + lo;
let mid3: u64 = mid + mid + (ibool(lo3 < lo): u64);
let hi3: u64 = hi + hi + (ibool(mid3 < mid): u64);
let lo4: u64 = lo3 - mul0;
let mid4: u64 = mid3 - mul1 - (ibool(lo4 > lo3): u64);
let hi4: u64 = hi3 - (ibool(mid4 > mid3): u64);
v_minus = u128rshift(mid4, hi4, ((j - 64): u32));
};
let v_rounded: u64 = u128rshift(mid, hi, ((j - 64 - 1): u32));
return ryuv { vp = v_plus, vr = v_rounded, vm = v_minus };
};
// ref/hare/strconv/ftos_ryu.ha:140.
fn log2pow5(e: u32) u32 = {
os.assert(e <= 3528u32, "strconv.log2pow5: e > 3528");
return (e * 1217359u32) >> 19u32;
};
// ref/hare/strconv/ftos_ryu.ha:145-147.
fn ceil_log2pow5(e: u32) u32 = { return log2pow5(e) + 1u32; };
fn pow5bits(e: u32) u32 = { return ceil_log2pow5(e); };
// ref/hare/strconv/ftos_ryu.ha:149.
fn log10pow2(e: u32) u32 = {
os.assert(e <= 1650u32, "strconv.log10pow2: e > 1650");
return (e * 78913u32) >> 18u32;
};
// ref/hare/strconv/ftos_ryu.ha:154.
fn log10pow5(e: u32) u32 = {
os.assert(e <= 2620u32, "strconv.log10pow5: e > 2620");
return (e * 732923u32) >> 20u32;
};
// ref/hare/strconv/ftos_ryu.ha:224. Returns the (low, high) split of the
// inverse power of five. 2-tuple kept (works); row-bind → double-index.
fn f64computeinvpow5(i: u32) (u64, u64) = {
let base: u32 = (i + (POW5_TABLE_SZ: u32) - 1u32) / (POW5_TABLE_SZ: u32);
let base2: u32 = base * (POW5_TABLE_SZ: u32);
let off: u32 = base2 - i;
if (off == 0u32) {
return (F64_POW5_INV_SPLIT2[base][0], F64_POW5_INV_SPLIT2[base][1]);
};
let m: u64 = POW5_TABLE[off];
let r1: r128 = u128mul(m, F64_POW5_INV_SPLIT2[base][1]);
let r0: r128 = u128mul(m, F64_POW5_INV_SPLIT2[base][0] - 1u64);
let high1: u64 = r1.hi;
let low1: u64 = r1.lo;
let high0: u64 = r0.hi;
let low0: u64 = r0.lo;
let sum: u64 = high0 + low1;
if (sum < high0) {
high1 += 1u64;
};
let delta: u32 = pow5bits(base2) - pow5bits(i);
let res0: u64 = u128rshift(low0, sum, delta) + 1u64 +
(((POW5_INV_OFFSETS[i / 16u32] >> ((i % 16u32) << 1u32)) & 3u32): u64);
let res1: u64 = u128rshift(sum, high1, delta);
return (res0, res1);
};
// ref/hare/strconv/ftos_ryu.ha:246.
fn f64computepow5(i: u32) (u64, u64) = {
let base: u32 = i / (POW5_TABLE_SZ: u32);
let base2: u32 = base * (POW5_TABLE_SZ: u32);
let off: u32 = i - base2;
if (off == 0u32) {
return (F64_POW5_SPLIT2[base][0], F64_POW5_SPLIT2[base][1]);
};
let m: u64 = POW5_TABLE[off];
let r1: r128 = u128mul(m, F64_POW5_SPLIT2[base][1]);
let r0: r128 = u128mul(m, F64_POW5_SPLIT2[base][0]);
let high1: u64 = r1.hi;
let low1: u64 = r1.lo;
let high0: u64 = r0.hi;
let low0: u64 = r0.lo;
let sum: u64 = high0 + low1;
if (sum < high0) {
high1 += 1u64;
};
let delta: u32 = pow5bits(i) - pow5bits(base2);
let res0: u64 = u128rshift(low0, sum, delta) +
(((POW5_OFFSETS[i / 16u32] >> ((i % 16u32) << 1u32)) & 3u32): u64);
let res1: u64 = u128rshift(sum, high1, delta);
return (res0, res1);
};
// ref/hare/strconv/ftos_ryu.ha:267. Shortest decimal of an f64:
// value == mantissa * 10^exponent. `exponent` rides i64 not Hare's i32
// (ftos_ryu.ha:269): a 16B struct-return with a NARROW (i32) second
// field unpacks MOVL in wwstage vs MOVQ in cstage (store-width cs≠ww
// byte-id split, #169); an 8B i64 field unpacks MOVQ in both. The value
// always fits i32 (cast at the init_dec_mant_exp call site).
type decf64 = struct { mantissa: u64, exponent: i64 };
// ref/hare/strconv/ftos_ryu.ha:272. `mantissa`/`exponent` are the raw
// IEEE-754 fields of an f64.
fn f64todecf64(mantissa: u64, exponent: u32) decf64 = {
let e2: i32 = (math.F64_EXPONENT_BIAS + math.F64_MANTISSA_BITS + 2u64): i32;
let m2: u64 = 0u64;
if (exponent == 0u32) {
e2 = 1i32 - e2;
m2 = mantissa;
} else {
e2 = (exponent: i32) - e2;
m2 = (1u64 << math.F64_MANTISSA_BITS) | mantissa;
};
let accept_bounds: bool = (m2 & 1u64) == 0u64;
let mv: u64 = 4u64 * m2;
let mm_shift: u32 = ibool(mantissa != 0u64 || exponent <= 1u32): u32;
let vp: u64 = 0u64;
let vr: u64 = 0u64;
let vm: u64 = 0u64;
let e10: i32 = 0i32;
let vm_trailing_zeros: bool = false;
let vr_trailing_zeros: bool = false;
if (e2 >= 0i32) {
let q: u32 = log10pow2(e2: u32) - (ibool(e2 > 3i32): u32);
e10 = q: i32;
let k: u32 = (F64_POW5_INV_BITCOUNT: u32) + pow5bits(q) - 1u32;
let i: i32 = -e2 + ((q + k): i32);
let pow5 = f64computeinvpow5(q);
let res: ryuv = mulshiftall64(m2, pow5.0, pow5.1, i, mm_shift);
vp = res.vp; vr = res.vr; vm = res.vm;
if (q <= 21u32) {
if ((mv - 5u64 * (mv / 5u64)) == 0u64) {
vr_trailing_zeros = pow5multiple(mv, q);
} else if (accept_bounds) {
vm_trailing_zeros = pow5multiple(mv - 1u64 - (mm_shift: u64), q);
} else {
vp -= (ibool(pow5multiple(mv + 2u64, q)): u64);
};
};
} else {
let q: u32 = log10pow5((-e2): u32) - (ibool(-e2 > 1i32): u32);
e10 = e2 + (q: i32);
let i: i32 = -e2 - (q: i32);
let k: i32 = (pow5bits(i: u32): i32) - (F64_POW5_BITCOUNT: i32);
let j: i32 = (q: i32) - k;
let pow5 = f64computepow5(i: u32);
let res: ryuv = mulshiftall64(m2, pow5.0, pow5.1, j, mm_shift);
vp = res.vp; vr = res.vr; vm = res.vm;
if (q <= 1u32) {
vr_trailing_zeros = true;
if (accept_bounds) {
vm_trailing_zeros = mm_shift == 1u32;
} else {
vp -= 1u64;
};
} else if (q < 63u32) {
vr_trailing_zeros = pow2multiple(mv, q);
};
};
let removed: i32 = 0i32;
let last_removed_digit: u8 = 0u8;
let output: u64 = 0u64;
if (vm_trailing_zeros || vr_trailing_zeros) {
for (true) {
let vpby10: u64 = vp / 10u64;
let vmby10: u64 = vm / 10u64;
if (vpby10 <= vmby10) { break; };
let vmmod10: u32 = (vm: u32) - 10u32 * (vmby10: u32);
let vrby10: u64 = vr / 10u64;
let vrmod10: u32 = (vr: u32) - 10u32 * (vrby10: u32);
vm_trailing_zeros = vm_trailing_zeros && (vmmod10 == 0u32);
vr_trailing_zeros = vr_trailing_zeros && (last_removed_digit == 0u8);
last_removed_digit = (vrmod10: u8);
vr = vrby10; vp = vpby10; vm = vmby10;
removed += 1i32;
};
if (vm_trailing_zeros) {
for (true) {
let vmby10: u64 = vm / 10u64;
let vmmod10: u32 = (vm: u32) - 10u32 * (vmby10: u32);
if (vmmod10 != 0u32) { break; };
let vpby10: u64 = vp / 10u64;
let vrby10: u64 = vr / 10u64;
let vrmod10: u32 = (vr: u32) - 10u32 * (vrby10: u32);
vr_trailing_zeros = vr_trailing_zeros && (last_removed_digit == 0u8);
last_removed_digit = (vrmod10: u8);
vr = vrby10; vp = vpby10; vm = vmby10;
removed += 1i32;
};
};
if (vr_trailing_zeros && last_removed_digit == 5u8 && (vr & 1u64) == 0u64) {
last_removed_digit = 4u8; // round to even
};
let cond1: bool = (vr == vm) && ((!accept_bounds) || (!vm_trailing_zeros));
let cond2: bool = last_removed_digit >= 5u8;
output = vr + (ibool(cond1 || cond2): u64);
} else {
let round_up: bool = false;
let vpby100: u64 = vp / 100u64;
let vmby100: u64 = vm / 100u64;
if (vpby100 > vmby100) {
let vrby100: u64 = vr / 100u64;
let vrmod100: u32 = (vr: u32) - 100u32 * (vrby100: u32);
round_up = vrmod100 >= 50u32;
vr = vrby100; vp = vpby100; vm = vmby100;
removed += 2i32;
};
for (true) {
let vmby10: u64 = vm / 10u64;
let vpby10: u64 = vp / 10u64;
if (vpby10 <= vmby10) { break; };
let vrby10: u64 = vr / 10u64;
let vrmod10: u32 = (vr: u32) - 10u32 * (vrby10: u32);
round_up = vrmod10 >= 5u32;
vr = vrby10; vp = vpby10; vm = vmby10;
removed += 1i32;
};
output = vr + (ibool(vr == vm || round_up): u64);
};
let exp: i32 = e10 + removed;
return decf64 { exponent = (exp: i64), mantissa = output };
};
// ==== f32 Ryū sub-path (ftos_ryu.ha). The *32 helpers below mirror their
// u64 siblings at 32-bit width; they reuse the SHARED f64computeinvpow5/
// f64computepow5 (and thus the f64 SPLIT2 tables) per ftos_ryu.ha — there
// is no separate f32 table. Same scalar-PARAM-mutation → copy-to-local,
// comma-split, assert → os.assert, expr-yield → block divergences as the
// f64 path above. ====
// ref/hare/strconv/ftos_ryu.ha:52. Largest p with 5^p | value (32-bit).
fn pow5fac32(v: u32) u32 = {
let value: u32 = v;
let count: u32 = 0u32;
for (true) {
os.assert(value != 0u32, "strconv.pow5fac32: value == 0");
let q: u32 = value / 5u32;
let r: u32 = value % 5u32;
if (r != 0u32) { break; };
value = q;
count += 1u32;
};
return count;
};
// ref/hare/strconv/ftos_ryu.ha:67.
fn pow5multiple32(v: u32, p: u32) bool = { return pow5fac32(v) >= p; };
// ref/hare/strconv/ftos_ryu.ha:75.
fn pow2multiple32(v: u32, p: u32) bool = {
os.assert(v > 0u32, "strconv.pow2multiple32: v == 0");
os.assert(p < 32u32, "strconv.pow2multiple32: p >= 32");
return (v & ((1u32 << p) - 1u32)) == 0u32;
};
// ref/hare/strconv/ftos_ryu.ha:121. `m * a_lo` etc. carry an explicit
// (m: u64) cast (Hare promotes the u32 operand; ww is strict). The bound
// assert inlines U32_MAX's value: ww's types.U32_MAX is package-private
// (lib/types/types.ww — no `export`), so Hare's `types::U32_MAX` can't be
// referenced cross-package.
fn mulshift32(m: u32, a: u64, s: u32) u32 = {
os.assert(s > 32u32, "strconv.mulshift32: s <= 32");
let a_lo: u64 = (a: u32): u64;
let a_hi: u64 = a >> 32u64;
let b0: u64 = (m: u64) * a_lo;
let b1: u64 = (m: u64) * a_hi;
let sum: u64 = (b0 >> 32u64) + b1;
let ss: u64 = sum >> ((s: u64) - 32u64);
os.assert(ss <= 4294967295u64, "strconv.mulshift32: ss > U32_MAX");
return ss: u32;
};
// ref/hare/strconv/ftos_ryu.ha:130.
fn mulpow5inv_divpow2(m: u32, q: u32, j: i32) u32 = {
let pow5 = f64computeinvpow5(q);
return mulshift32(m, pow5.1 + 1u64, (j: u32));
};
// ref/hare/strconv/ftos_ryu.ha:135.
fn mulpow5_divpow2(m: u32, i: u32, j: i32) u32 = {
let pow5 = f64computepow5(i);
return mulshift32(m, pow5.1, (j: u32));
};
// ref/hare/strconv/ftos_ryu.ha:387. `exponent` rides i64 not Hare's i32,
// for the same reason decf64 does: widening the field to a full second
// eightbyte SIDESTEPS the #169 narrow-i32-field struct-return unpack (a
// narrow i32 there unpacks MOVL wwstage vs MOVQ cstage). The value always
// fits i32 (cast at the init_dec_mant_exp call site). `mantissa` stays u32
// (Hare's width); the {u32, pad, i64} layout's first eightbyte holds
// mantissa@0 + 4B pad and reads cleanly — byte-id CONFIRMED by the 990-997
// gate (0-diff cs vs ww), not relied on as an ABI guarantee.
type decf32 = struct { mantissa: u32, exponent: i64 };
// ref/hare/strconv/ftos_ryu.ha:392. Shortest decimal of an f32:
// value == mantissa * 10^exponent. `mantissa`/`exponent` are the raw
// IEEE-754 fields of an f32.
fn f32todecf32(mantissa: u32, exponent: u32) decf32 = {
let e2: i32 = (math.F32_EXPONENT_BIAS + math.F32_MANTISSA_BITS + 2u32): i32;
let m2: u32 = 0u32;
if (exponent == 0u32) {
e2 = 1i32 - e2;
m2 = mantissa;
} else {
e2 = (exponent: i32) - e2;
m2 = (1u32 << math.F32_MANTISSA_BITS) | mantissa;
};
let accept_bounds: bool = (m2 & 1u32) == 0u32;
let mv: u32 = 4u32 * m2;
let mp: u32 = mv + 2u32;
let mm_shift: u32 = ibool(mantissa != 0u32 || exponent <= 1u32): u32;
let mm: u32 = mv - 1u32 - mm_shift;
let vr: u32 = 0u32;
let vp: u32 = 0u32;
let vm: u32 = 0u32;
let e10: i32 = 0i32;
let vm_trailing_zeroes: bool = false;
let vr_trailing_zeroes: bool = false;
let last_removed_digit: u8 = 0u8;
if (e2 >= 0i32) {
let q: u32 = log10pow2(e2: u32);
e10 = q: i32;
let k: u32 = (F32_POW5_INV_BITCOUNT: u32) + pow5bits(q) - 1u32;
let i: i32 = -e2 + ((q + k): i32);
vr = mulpow5inv_divpow2(mv, q, i);
vp = mulpow5inv_divpow2(mp, q, i);
vm = mulpow5inv_divpow2(mm, q, i);
if (q != 0u32 && (vp - 1u32) / 10u32 <= vm / 10u32) {
let l: u32 = (F32_POW5_INV_BITCOUNT: u32) + pow5bits(q - 1u32) - 1u32;
last_removed_digit = (mulpow5inv_divpow2(mv, q - 1u32,
-e2 + ((q + l): i32) - 1i32) % 10u32): u8;
};
if (q <= 9u32) {
if (mv % 5u32 == 0u32) {
vr_trailing_zeroes = pow5multiple32(mv, q);
} else if (accept_bounds) {
vm_trailing_zeroes = pow5multiple32(mm, q);
} else {
vp -= (ibool(pow5multiple32(mp, q)): u32);
};
};
} else {
let q: u32 = log10pow5((-e2): u32);
e10 = (q: i32) + e2;
let i: u32 = (-e2 - (q: i32)): u32;
let k: u32 = pow5bits(i) - (F32_POW5_BITCOUNT: u32);
let j: i32 = (q: i32) - (k: i32);
vr = mulpow5_divpow2(mv, i, j);
vp = mulpow5_divpow2(mp, i, j);
vm = mulpow5_divpow2(mm, i, j);
if (q != 0u32 && (vp - 1u32) / 10u32 <= vm / 10u32) {
j = (q: i32) - 1i32 - ((pow5bits(i + 1u32): i32) - (F32_POW5_BITCOUNT: i32));
last_removed_digit = (mulpow5_divpow2(mv, (i + 1u32), j) % 10u32): u8;
};
if (q <= 1u32) {
vr_trailing_zeroes = true;
if (accept_bounds) {
vm_trailing_zeroes = mm_shift == 1u32;
} else {
vp -= 1u32;
};
} else if (q < 31u32) {
vr_trailing_zeroes = pow2multiple32(mv, q - 1u32);
};
};
let removed: i32 = 0i32;
let output: u32 = 0u32;
if (vm_trailing_zeroes || vr_trailing_zeroes) {
for ((vp / 10u32) > (vm / 10u32)) {
vm_trailing_zeroes = vm_trailing_zeroes && ((vm - (vm / 10u32) * 10u32) == 0u32);
vr_trailing_zeroes = vr_trailing_zeroes && (last_removed_digit == 0u8);
last_removed_digit = (vr % 10u32): u8;
vr /= 10u32;
vp /= 10u32;
vm /= 10u32;
removed += 1i32;
};
if (vm_trailing_zeroes) {
for ((vm % 10u32) == 0u32) {
vr_trailing_zeroes = vr_trailing_zeroes && (last_removed_digit == 0u8);
last_removed_digit = (vr % 10u32): u8;
vr /= 10u32;
vp /= 10u32;
vm /= 10u32;
removed += 1i32;
};
};
if (vr_trailing_zeroes && last_removed_digit == 5u8 && vr % 2u32 == 0u32) {
last_removed_digit = 4u8; // round to even
};
let cond1: bool = (vr == vm) && ((!accept_bounds) || (!vm_trailing_zeroes));
let cond2: bool = last_removed_digit >= 5u8;
output = vr + (ibool(cond1 || cond2): u32);
} else {
for ((vp / 10u32) > (vm / 10u32)) {
last_removed_digit = (vr % 10u32): u8;
vr /= 10u32;
vp /= 10u32;
vm /= 10u32;
removed += 1i32;
};
output = vr + (ibool(vr == vm || last_removed_digit >= 5u8): u32);
};
let exp: i32 = e10 + removed;
return decf32 { mantissa = output, exponent = (exp: i64) };
};
// ==== G-format encode layer (ftos.ha) — only the ffmt::G / prec=void /
// fflags::NONE-REACHABLE logic. The SHOW_POINT/precision/E-vs-uppercase
// arms (ftos.ha:88-105, 127-145, 170-213's zeros/caps) are UNREACHABLE
// for G/void/NONE (ffpoint(NONE)=false, prec is never uint, f is always
// G) and are NOT ported — porting them stubbed would be untested dead
// code. The parametric ftosf/ffmt/fflags surface is deferred (task #64;
// needs a parametric consumer + io::handle + #158). ====
// ref/hare/strconv/ftos.ha:49. Decimal digit-count of n (n <= 1e17).
fn declen(n: u64) uint = {
os.assert(n <= 100000000000000000u64, "strconv.declen: n > 1e17");
if (n >= 100000000000000000u64) { return (18u32: uint); };
if (n >= 10000000000000000u64) { return (17u32: uint); };
if (n >= 1000000000000000u64) { return (16u32: uint); };
if (n >= 100000000000000u64) { return (15u32: uint); };
if (n >= 10000000000000u64) { return (14u32: uint); };
if (n >= 1000000000000u64) { return (13u32: uint); };
if (n >= 100000000000u64) { return (12u32: uint); };
if (n >= 10000000000u64) { return (11u32: uint); };
if (n >= 1000000000u64) { return (10u32: uint); };
if (n >= 100000000u64) { return (9u32: uint); };
if (n >= 10000000u64) { return (8u32: uint); };
if (n >= 1000000u64) { return (7u32: uint); };
if (n >= 100000u64) { return (6u32: uint); };
if (n >= 10000u64) { return (5u32: uint); };
if (n >= 1000u64) { return (4u32: uint); };
if (n >= 100u64) { return (3u32: uint); };
if (n >= 10u64) { return (2u32: uint); };
return (1u32: uint);
};
// ref/hare/strconv/ftos.ha:217. Lay the Ryū shortest (mantissa,exponent)
// into the decimal `d`. `mantissa` is mutated in Hare → local `mant`.
fn init_dec_mant_exp(d: *decimal, mantissa: u64, exponent: i32) void = {
// Hoisted uint casts: ww parser rejects a `(N: uint)` cast embedded
// inside an array subscript (decimal.ww "hoist size casts" note).
let U_ZERO: uint = (0u32: uint);
let U_ONE: uint = (1u32: uint);
let mant: u64 = mantissa;
let dl: uint = declen(mant);
let i: uint = U_ZERO;
for (i < dl) {
d.digits[dl - i - U_ONE] = (mant % 10u64): u8;
mant /= 10u64;
i += U_ONE;
};
d.nd = (dl: size);
d.dp = (dl: i32) + exponent;
};
// ref/hare/strconv/ftos.ha:71. writestr → buffer-cursor adaptation (the
// *tos static-buffer convention replaces Hare's io::handle sink).
fn putstr(buf: []u8, out: i32, s: str) i32 = {
let o: i32 = out;
let k: i32 = 0i32;
for (k < s.len) {
buf[o] = s[k];
o += 1i32;
k += 1i32;
};
return o;
};
// ref/hare/strconv/ftos.ha:109. Fixed-point render (G/void/NONE-reachable
// logic only). Writes into `buf` at cursor `out`, returns the new cursor.
fn encode_f_dec(d: *decimal, buf: []u8, out: i32) i32 = {
let o: i32 = out;
let lo: i32 = 0i32;
if (d.dp <= 0i32) { lo = d.dp - 1i32; };
let hi: i32 = d.dp;
if ((d.nd: i32) > d.dp) { hi = (d.nd: i32); };
if (hi > (d.nd: i32) && d.dp <= 0i32) {
hi = (d.nd: i32);
} else if (hi > d.dp && d.dp > 0i32) {
hi = d.dp;
if ((d.nd: i32) > d.dp) { hi = (d.nd: i32); };
};
let i: i32 = lo;
for (i < hi) {
if (i == d.dp) {
buf[o] = '.';
o += 1i32;
};
if (0i32 <= i && i < (d.nd: i32)) {
buf[o] = (d.digits[i] + 48u8): u8;
} else {
buf[o] = '0';
};
o += 1i32;
i += 1i32;
};
return o;
};
// ref/hare/strconv/ftos.ha:160. Scientific render (G/void/NONE-reachable
// logic only): no precision zeros, lowercase 'e', no '+'/two-digit pad.
fn encode_e_dec(d: *decimal, buf: []u8, out: i32) i32 = {
let o: i32 = out;
os.assert(d.nd > (0u64: size), "strconv.encode_e_dec: nd == 0");
buf[o] = (d.digits[0] + 48u8): u8;
o += 1i32;
if ((d.nd: i32) > 1i32) {
buf[o] = '.';
o += 1i32;
};
let i: size = (1u64: size);
for (i < d.nd) {
buf[o] = (d.digits[i] + 48u8): u8;
o += 1i32;
i += (1u64: size);
};
buf[o] = 'e';
o += 1i32;
let e: i32 = d.dp - 1i32;
if (e < 0i32) {
e = -e;
buf[o] = '-';
o += 1i32;
};
// Hoisted uint casts (ww parser rejects `(N: uint)` inside `[ ]`).
let U_ONE: uint = (1u32: uint);
let U_TWO: uint = (2u32: uint);
let U_THREE: uint = (3u32: uint);
let ebuf: [3]u8 = [0u8, 0u8, 0u8]; // exponents are at most 3 digits
let l: uint = declen(e: u64);
let k: uint = (0u32: uint);
for (k < l) {
ebuf[U_TWO - k] = (e % 10i32): u8;
e /= 10i32;
k += U_ONE;
};
let m: uint = U_THREE - l;
for (m < U_THREE) {
buf[o] = (ebuf[m] + 48u8): u8;
o += 1i32;
m += U_ONE;
};
return o;
};
// ref/hare/strconv/ftos.ha:432. f64 → shortest base-10 str. Returns a
// view into a static buffer overwritten on the next call (the *tos
// convention; see strings.dup to retain). Equivalent to Hare's ftosf
// with format G + precision void. The fftosf G/void/NONE path is inlined
// (the parametric surface is deferred — task #64).
//
// Max output is 24 (ftos.ha:434): sign + digit + '.' + 16 digits + 'e' +
// exp-sign + 3 exp-digits. Sized 32 not 24: a no-rhs [24]u8 module buffer
// emits 4 DATAW in wwstage vs 2 in cstage (#43, the size-16/24 emitletdataw
// split); 32 emits 2 in both (byte-id). The extra 8 bytes are unused.
let f64tos_buf: [32]u8;
export fn f64tos(n: f64) str = {
let bits: u64 = math.f64bits(n);
let mantissa: u64 = bits & math.F64_MANTISSA_MASK;
let exponent: u32 = ((bits >> math.F64_MANTISSA_BITS) & math.F64_EXPONENT_MASK): u32;
let sign: bool = (bits >> (math.F64_EXPONENT_BITS + math.F64_MANTISSA_BITS)) > 0u64;
let special: bool = exponent == (math.F64_EXPONENT_MASK: u32);
let o: i32 = 0i32;
let r: str;
r.ptr = &f64tos_buf[0];
// NaN carries no sign prefix (ftos.ha:331-333, before sign handling).
if (special && mantissa != 0u64) {
o = putstr(f64tos_buf[0:32], o, "nan");
r.len = o;
return r;
};
if (sign) {
f64tos_buf[o] = '-';
o += 1i32;
};
if (special) {
o = putstr(f64tos_buf[0:32], o, "infinity");
r.len = o;
return r;
};
if (exponent == 0u32 && mantissa == 0u64) {
f64tos_buf[o] = '0'; // encode_zero, G/void/NONE
o += 1i32;
r.len = o;
return r;
};
let d = decimal { ... };
// Reads of d.nd / d.dp ride a *decimal pointer: wwstage resolves a
// scalar-field read of a LOCAL struct (`d.nd`) to a bogus global
// symbol (`nd(SB)`), but a pointer-deref field read (`pd.nd`) lowers
// correctly in both stages (the stof.ww/decimal.ww *decimal precedent)
// — #170. The init/trim/encode calls already took &d; route via pd.
let pd: *decimal = &d;
let dd: decf64 = f64todecf64(mantissa, exponent);
init_dec_mant_exp(pd, dd.mantissa, (dd.exponent: i32));
// ok = !ffpoint(NONE) || ... is always true → no multiprecision
// fallback (ftos.ha:365). f == G → trim (ftos.ha:386).
trim(pd);
if (pd.nd == (0u64: size)) {
f64tos_buf[o] = '0'; // rounded to zero
o += 1i32;
} else if (pd.dp < -1i32 || (pd.dp - (pd.nd: i32)) > 2i32) {
o = encode_e_dec(pd, f64tos_buf[0:32], o);
} else {
o = encode_f_dec(pd, f64tos_buf[0:32], o);
};
r.len = o;
return r;
};
// ref/hare/strconv/ftos.ha:448. f32 → shortest base-10 str. Same static-
// buffer convention + G/void/NONE-inlined path as f64tos. f32bits(n)
// passes an f32 arg → MOVSS both stages post-#143 (aff7725); this is the
// piece fold-5b was gated on.
//
// Hare sizes this [14]u8 (ftos.ha:451: 1 + 1 + 1 + 7 + 1 + 1 + 2). Sized
// 32 to reuse f64tos's proven byte-id-clean band: a no-rhs [N]u8 module
// buffer at the size-16/24 band emits divergent DATAW counts cs≠ww (#43);
// 32 emits 2 DATAW in both. The unused tail bytes are harmless.
let f32tos_buf: [32]u8;
export fn f32tos(n: f32) str = {
let bits: u32 = math.f32bits(n);
let mantissa: u32 = bits & math.F32_MANTISSA_MASK;
let exponent: u32 = (bits >> math.F32_MANTISSA_BITS) & math.F32_EXPONENT_MASK;
let sign: bool = (bits >> (math.F32_EXPONENT_BITS + math.F32_MANTISSA_BITS)) > 0u32;
let special: bool = exponent == math.F32_EXPONENT_MASK;
let o: i32 = 0i32;
let r: str;
r.ptr = &f32tos_buf[0];
// NaN carries no sign prefix (ftos.ha:331-333, before sign handling).
if (special && mantissa != 0u32) {
o = putstr(f32tos_buf[0:32], o, "nan");
r.len = o;
return r;
};
if (sign) {
f32tos_buf[o] = '-';
o += 1i32;
};
if (special) {
o = putstr(f32tos_buf[0:32], o, "infinity");
r.len = o;
return r;
};
if (exponent == 0u32 && mantissa == 0u32) {
f32tos_buf[o] = '0'; // encode_zero, G/void/NONE
o += 1i32;
r.len = o;
return r;
};
let d = decimal { ... };
// *decimal pointer for the field reads (the #170 dodge; see f64tos).
let pd: *decimal = &d;
let dd: decf32 = f32todecf32(mantissa, exponent);
init_dec_mant_exp(pd, (dd.mantissa: u64), (dd.exponent: i32));
trim(pd);
if (pd.nd == (0u64: size)) {
f32tos_buf[o] = '0'; // rounded to zero
o += 1i32;
} else if (pd.dp < -1i32 || (pd.dp - (pd.nd: i32)) > 2i32) {
o = encode_e_dec(pd, f32tos_buf[0:32], o);
} else {
o = encode_f_dec(pd, f32tos_buf[0:32], o);
};
r.len = o;
return r;
};
// strconv — Ryū float→string lookup tables + bit-count constants.
// Mirrors ref/hare/strconv/ftos_ryu.ha:159-222 byte-exact. Pure data
// fold (strconv #106 fold-5): no logic, consumed by ftos.ww's
// f64computeinvpow5 / f64computepow5 (the Ryū power-of-five cores).
//
// File-organisation divergence: Hare keeps these tables INLINE in
// ftos_ryu.ha. ww splits data from logic into ftos_data.ww (mirroring
// the stof.ww / stof_data.ww split) — same `package strconv`, so the
// tables stay visible to ftos.ww with no qualification.
//
// Spelling divergences (same as stof_data.ww, candidate #130 + rule-12):
// - Hare `const TBL = [...]` → ww module-level `let` (ww has no
// module-`const` keyword; the values are never written).
// - every literal carries its element-width suffix (`u64`/`u32`):
// cstage rejects bare integer literals in `[N]uXX` init while
// wwstage accepts them; the suffixed form is the only shape both
// stages agree on.
// - the [N][2]u64 tables stay faithful 2D (rule-12, not flattened);
// the 2D module-level static-init + double-index read landed in
// #156 (cbeffea), proven by stof_data.ww's powers_of_ten[596][2]u64.
package strconv;
// ref/hare/strconv/ftos_ryu.ha:159-160. Bit-counts of the split
// power-of-five tables. Defined u8 (faithful); ftos.ww casts to u32/i32
// at each use site (Hare promotes a u8 def inside mixed-width arithmetic;
// ww is strict — explicit cast, project_int_machine_word_derived_limits).
def F64_POW5_INV_BITCOUNT: u8 = 125u8;
def F64_POW5_BITCOUNT: u8 = 125u8;
// ref/hare/strconv/ftos_ryu.ha:162-163. The f32 split-table bit-counts,
// derived from the f64 siblings (Hare: F64_..._BITCOUNT - 64). Consumed by
// f32todecf32 (ftos.ww), landed in fold-5b (task #67) — the f32 path reuses
// the f64 SPLIT2 tables (via f64computeinvpow5/f64computepow5), so no
// separate F32 tables exist (matches ftos_ryu.ha). u8 like the f64 defs;
// ftos.ww casts to u32/i32 at each use.
def F32_POW5_INV_BITCOUNT: u8 = F64_POW5_INV_BITCOUNT - 64u8;
def F32_POW5_BITCOUNT: u8 = F64_POW5_BITCOUNT - 64u8;
// ref/hare/strconv/ftos_ryu.ha:165-181.
let F64_POW5_INV_SPLIT2: [15][2]u64 = [
[1u64, 2305843009213693952u64],
[5955668970331000884u64, 1784059615882449851u64],
[8982663654677661702u64, 1380349269358112757u64],
[7286864317269821294u64, 2135987035920910082u64],
[7005857020398200553u64, 1652639921975621497u64],
[17965325103354776697u64, 1278668206209430417u64],
[8928596168509315048u64, 1978643211784836272u64],
[10075671573058298858u64, 1530901034580419511u64],
[597001226353042382u64, 1184477304306571148u64],
[1527430471115325346u64, 1832889850782397517u64],
[12533209867169019542u64, 1418129833677084982u64],
[5577825024675947042u64, 2194449627517475473u64],
[11006974540203867551u64, 1697873161311732311u64],
[10313493231639821582u64, 1313665730009899186u64],
[12701016819766672773u64, 2032799256770390445u64],
];
// ref/hare/strconv/ftos_ryu.ha:183-188.
let POW5_INV_OFFSETS: [19]u32 = [
0x54544554u32, 0x04055545u32, 0x10041000u32, 0x00400414u32, 0x40010000u32, 0x41155555u32,
0x00000454u32, 0x00010044u32, 0x40000000u32, 0x44000041u32, 0x50454450u32, 0x55550054u32,
0x51655554u32, 0x40004000u32, 0x01000001u32, 0x00010500u32, 0x51515411u32, 0x05555554u32,
0x00000000u32,
];
// ref/hare/strconv/ftos_ryu.ha:190-204.
let F64_POW5_SPLIT2: [13][2]u64 = [
[0u64, 1152921504606846976u64],
[0u64, 1490116119384765625u64],
[1032610780636961552u64, 1925929944387235853u64],
[7910200175544436838u64, 1244603055572228341u64],
[16941905809032713930u64, 1608611746708759036u64],
[13024893955298202172u64, 2079081953128979843u64],
[6607496772837067824u64, 1343575221513417750u64],
[17332926989895652603u64, 1736530273035216783u64],
[13037379183483547984u64, 2244412773384604712u64],
[1605989338741628675u64, 1450417759929778918u64],
[9630225068416591280u64, 1874621017369538693u64],
[665883850346957067u64, 1211445438634777304u64],
[14931890668723713708u64, 1565756531257009982u64],
];
// ref/hare/strconv/ftos_ryu.ha:206-211.
let POW5_OFFSETS: [21]u32 = [
0x00000000u32, 0x00000000u32, 0x00000000u32, 0x00000000u32, 0x40000000u32, 0x59695995u32,
0x55545555u32, 0x56555515u32, 0x41150504u32, 0x40555410u32, 0x44555145u32, 0x44504540u32,
0x45555550u32, 0x40004000u32, 0x96440440u32, 0x55565565u32, 0x54454045u32, 0x40154151u32,
0x55559155u32, 0x51405555u32, 0x00000105u32,
];
// ref/hare/strconv/ftos_ryu.ha:213. Divisor/index stride in
// f64computeinvpow5 / f64computepow5 (ftos.ww). Kept as a def for those
// arithmetic uses; POW5_TABLE's dimension below must be a literal (cstage
// rejects a def-named array length — "array length must be an integer
// literal"; wwstage accepts it but emits an empty DATAW — divergence
// #167, so the literal `26` is the only shape both stages agree on;
// matches decimal.ww's `[800]u8` array-dimension-literal precedent).
def POW5_TABLE_SZ: u8 = 26u8;
// ref/hare/strconv/ftos_ryu.ha:215-222. 5^0 .. 5^25 (the 5^26 entry is
// commented out in Hare too — it lives implicitly in the SPLIT2 tables).
let POW5_TABLE: [26]u64 = [
1u64, 5u64, 25u64, 125u64, 625u64, 3125u64, 15625u64, 78125u64,
390625u64, 1953125u64, 9765625u64, 48828125u64, 244140625u64,
1220703125u64, 6103515625u64, 30517578125u64, 152587890625u64,
762939453125u64, 3814697265625u64, 19073486328125u64, 95367431640625u64,
476837158203125u64, 2384185791015625u64, 11920928955078125u64,
59604644775390625u64, 298023223876953125u64,
];
// types — integer limits. Mirrors Hare's types::limits (I8_MAX, …)
// platform-fixed for amd64. Numeric helpers live in lib/math, matching
// Hare's split between types::limits and math::.
package types;
def I8_MAX: i8 = 127;
def I16_MAX: i16 = 32767;
def I32_MAX: i32 = 2147483647;
def I64_MAX: i64 = 9223372036854775807;
def I8_MIN: i8 = -128;
def I16_MIN: i16 = -32768;
def I32_MIN: i32 = -2147483648;
def I64_MIN: i64 = -9223372036854775808;
def U8_MAX: u8 = 255;
def U16_MAX: u16 = 65535;
def U32_MAX: u32 = 4294967295;
def U64_MAX: u64 = 18446744073709551615;
def U8_MIN: u8 = 0;
def U16_MIN: u16 = 0;
def U32_MIN: u32 = 0;
def U64_MIN: u64 = 0;
// int/uint are machine-word (Go-style, type.c:58); limits derived from
// size(int) per #114 + user ruling; cf Go math.MaxInt; diverges from
// Hare's per-arch literal (arch+x86_64.ha) because ww's int is 64-bit.
def INT_MAX: int = (1 << (size(int)*8 - 1)) - 1;
def INT_MIN: int = -1 << (size(int)*8 - 1);
def UINT_MIN: uint = 0;
def UINT_MAX: uint = ~(0: uint);
// size is 8B on amd64; no cast needed (size ∈ unsigned class per #113).
def SIZE_MIN: size = U64_MIN;
def SIZE_MAX: size = U64_MAX;
// uintptr not in the unsigned class, so the cast is required (Hare's form).
def UINTPTR_MIN: uintptr = U64_MIN: uintptr;
def UINTPTR_MAX: uintptr = U64_MAX: uintptr;
def RUNE_MIN: rune = '\0';
// bytes — slice operations over []u8. Mirrors Hare's bytes module
// (ref/hare/bytes/) for the in-tree subset: search/equality/prefix
// helpers used by lib/encoding, lib/bufio, lib/memio.
//
// Documented divergences from Hare:
// - index_slice / rindex_slice use naive O(n·m); Hare specialises
// 2/3/4-byte needles and falls back to two_way (Crochemore-Perrin)
// for longer (ref/hare/bytes/index.ha:61, ref/hare/bytes/two_way.ha).
// Correctness equivalent.
// - peek_token dispatches index/rindex by branching on `reverse`
// rather than a function-pointer `ifunc` (ref/hare/bytes/tokenize.ha:97).
// ww has no fn pointers in scope yet — same pattern as lib/strings
// `move`. Outwardly identical.
// - tokenize / rtokenize zero the `delim` field on the constructed
// tokenizer when `in` is empty, rather than mutating the variadic
// param before the struct write (ref/hare/bytes/tokenize.ha:26-28).
// Semantically identical; the variadic param is borrowed and
// captured-by-value into the struct, so mutating either side
// yields the same observable state.
package bytes;
import os;
import types;
// done — iteration sentinel returned by next_token / peek_token at
// end-of-input. ref/hare/bytes/tokenize.ha uses the built-in `done`
// token; ww spells it per-package the same way lib/encoding/utf8 does
// (utf8.ww:36). Plain `void` (not `!void`): continuation signal.
export type done = void;
// tokenizer — cursor over an input slice. Layout mirrors
// ref/hare/bytes/tokenize.ha:6-10. `p` is the cached peek-position;
// I64_MAX (forward) / I64_MIN (reverse) are the unprimed sentinels.
// p < 0 also identifies a reverse-direction iterator.
export type tokenizer = struct {
in: []u8,
delim: []u8,
p: i64,
};
// equal — true iff `a` and `b` have the same length and contents.
// ref/hare/bytes/equal.ha:9.
export fn equal(a: []u8, b: []u8) bool = {
if (a.len != b.len) { return false; };
let i: i32 = 0;
for (i < a.len) {
if (a[i] != b[i]) { return false; };
i += 1;
};
return true;
};
// index — first offset of `needle` in `s`. u8 needle scans for the
// byte; []u8 needle scans for the substring. void if absent.
// ref/hare/bytes/index.ha:6.
export fn index(s: []u8, needle: (u8 | []u8)) (i32 | void) = {
match (needle) {
case let c: u8 => {
let i: i32 = 0;
for (i < s.len) {
if (s[i] == c) { return i; };
i += 1;
};
return;
};
case let sub: []u8 => {
if (sub.len == 0) { return 0; };
if (sub.len > s.len) { return; };
let last: i32 = s.len - sub.len;
let i: i32 = 0;
for (i <= last) {
let j: i32 = 0;
let ok: bool = true;
for (j < sub.len) {
if (s[i + j] != sub[j]) { ok = false; j = sub.len; }
else { j += 1; };
};
if (ok) { return i; };
i += 1;
};
return;
};
};
return;
};
// rindex — last offset of `needle` in `s`. Empty []u8 needle returns
// s.len (ref/hare/bytes/index.ha:103 — Hare's loop yields r-0 at i=0).
// ref/hare/bytes/index.ha:86.
export fn rindex(s: []u8, needle: (u8 | []u8)) (i32 | void) = {
match (needle) {
case let c: u8 => {
let i: i32 = s.len - 1;
for (i >= 0) {
if (s[i] == c) { return i; };
i -= 1;
};
return;
};
case let sub: []u8 => {
if (sub.len == 0) { return s.len; };
if (sub.len > s.len) { return; };
let i: i32 = s.len - sub.len;
for (i >= 0) {
let j: i32 = 0;
let ok: bool = true;
for (j < sub.len) {
if (s[i + j] != sub[j]) { ok = false; j = sub.len; }
else { j += 1; };
};
if (ok) { return i; };
i -= 1;
};
return;
};
};
return;
};
// contains — true iff any of `needles` (byte or sub-slice) appears in `s`.
// ref/hare/bytes/contains.ha:6.
export fn contains(s: []u8, needles: (u8 | []u8)...) bool = {
let i: i32 = 0;
for (i < needles.len) {
match (needles[i]) {
case let b: u8 => {
match (index(s, b)) {
case let bo: i32 => return true;
case void => void;
};
};
case let n: []u8 => {
match (index(s, n)) {
case let bo: i32 => return true;
case void => void;
};
};
};
i += 1;
};
return false;
};
// ltrim — borrowed view of `in` with leading bytes in `trim` stripped.
// `trim` must be non-empty. ref/hare/bytes/trim.ha:7.
export fn ltrim(in: []u8, trim: u8...) []u8 = {
os.assert(trim.len > 0, "bytes.ltrim called with empty trim set");
let i: i32 = 0;
for (i < in.len && contains(trim, in[i])) { i += 1; };
let r: []u8;
r.ptr = in.ptr + (i: u64);
r.len = in.len - i;
r.cap = r.len;
return r;
};
// rtrim — borrowed view of `in` with trailing bytes in `trim` stripped.
// `trim` must be non-empty. ref/hare/bytes/trim.ha:17. Hare's loop uses
// `size` underflow at i==0 to terminate; ww indices are signed i32, so
// the equivalent termination is spelled `i >= 0` explicitly.
export fn rtrim(in: []u8, trim: u8...) []u8 = {
os.assert(trim.len > 0, "bytes.rtrim called with empty trim set");
let i: i32 = in.len - 1;
for (i >= 0 && contains(trim, in[i])) { i -= 1; };
let r: []u8;
r.ptr = in.ptr;
r.len = i + 1;
r.cap = r.len;
return r;
};
// trim — borrowed view of `in` with both ends in `trim` stripped.
// ref/hare/bytes/trim.ha:27.
export fn trim(in: []u8, trim: u8...) []u8 = {
return ltrim(rtrim(in, trim...), trim...);
};
// hasprefix — true iff `s` starts with `pre`.
// ref/hare/bytes/contains.ha:21.
export fn hasprefix(s: []u8, pre: []u8) bool = {
if (pre.len > s.len) { return false; };
let i: i32 = 0;
for (i < pre.len) {
if (s[i] != pre[i]) { return false; };
i += 1;
};
return true;
};
// hassuffix — true iff `s` ends with `suf`.
// ref/hare/bytes/contains.ha:35.
export fn hassuffix(s: []u8, suf: []u8) bool = {
if (suf.len > s.len) { return false; };
let off: i32 = s.len - suf.len;
let i: i32 = 0;
for (i < suf.len) {
if (s[off + i] != suf[i]) { return false; };
i += 1;
};
return true;
};
// reverse — in-place reverse of `s`. ref/hare/bytes/reverse.ha:5.
export fn reverse(s: []u8) void = {
let i: i32 = 0;
let j: i32 = s.len - 1;
for (i < j) {
let t: u8 = s[i];
s[i] = s[j];
s[j] = t;
i += 1;
j -= 1;
};
};
// zero — set every byte of `s` to 0. ref/hare/bytes/zero.ha:5.
export fn zero(s: []u8) void = {
let i: i32 = 0;
for (i < s.len) {
s[i] = 0u8;
i += 1;
};
};
// tokenize — iterator yielding tokens from `in` separated by any byte
// in `delim`. Leading / trailing / adjacent delims yield empty tokens.
// `delim` is borrowed; caller keeps it valid for the tokenizer's
// lifetime. ref/hare/bytes/tokenize.ha:22.
export fn tokenize(in: []u8, delim: u8...) tokenizer = {
os.assert(delim.len > 0, "bytes.tokenize called with empty slice");
os.assert((in.len: i64) < types.I64_MAX,
"bytes.tokenize: input length exceeds I64_MAX");
let t: tokenizer;
t.in = in;
t.delim = delim;
if (in.len == 0) {
t.delim.len = 0;
t.delim.cap = 0;
};
t.p = types.I64_MAX;
return t;
};
// rtokenize — reverse-direction tokenize. First next_token yields the
// last token, last next_token yields the first. ref/hare/bytes/tokenize.ha:40.
export fn rtokenize(in: []u8, delim: u8...) tokenizer = {
os.assert(delim.len > 0, "bytes.rtokenize called with empty slice");
os.assert((in.len: i64) < types.I64_MAX,
"bytes.rtokenize: input length exceeds I64_MAX");
let t: tokenizer;
t.in = in;
t.delim = delim;
if (in.len == 0) {
t.delim.len = 0;
t.delim.cap = 0;
};
t.p = types.I64_MIN;
return t;
};
// peek_token — next token without advancing the cursor. Returns done
// once `s.delim` has been zeroed by a prior past-end next_token.
// ref/hare/bytes/tokenize.ha:91.
export fn peek_token(s: *tokenizer) ([]u8 | done) = {
if (s.delim.len == 0) {
let d: done; return d;
};
let reverse: bool = s.p < 0i64;
let known: bool = false;
if (reverse) {
if (s.p != types.I64_MIN) { known = true; };
} else {
if (s.p != types.I64_MAX) { known = true; };
};
if (!known) {
let i: i64 = types.I64_MAX;
if (reverse) { i = types.I64_MIN; };
let dlen: i64 = 0i64;
let slen: i64 = s.in.len: i64;
let k: i32 = 0;
for (k < s.delim.len) {
let d: u8 = s.delim[k];
let ix_found: bool = false;
let ix_val: i32 = 0;
if (reverse) {
match (rindex(s.in, d)) {
case let v: i32 => { ix_found = true; ix_val = v; };
case void => void;
};
} else {
match (index(s.in, d)) {
case let v: i32 => { ix_found = true; ix_val = v; };
case void => void;
};
};
if (ix_found) {
if (!reverse) {
if ((ix_val: i64) < i) { i = ix_val: i64; dlen = 1i64; };
} else {
if ((ix_val: i64) > i) { i = ix_val: i64; dlen = 1i64; };
};
} else {
if (!reverse) {
if (slen < i) { i = slen; };
} else {
if (0i64 > i) { i = 0i64; };
};
};
k += 1;
};
if (reverse) {
if (i == slen) {
s.p = -(slen + 1i64);
} else {
s.p = i + dlen - slen - 1i64;
};
} else {
s.p = i;
};
};
let r: []u8;
if (reverse) {
let start: i32 = (s.in.len: i64 + s.p + 1i64): i32;
r.ptr = s.in.ptr + (start: u64);
r.len = s.in.len - start;
r.cap = r.len;
} else {
let end: i32 = s.p: i32;
r.ptr = s.in.ptr;
r.len = end;
r.cap = end;
};
return r;
};
// next_token — current token, then advance past it and the delim.
// Once the input is exhausted, returns done and zeros `s.delim` so
// subsequent peeks short-circuit. ref/hare/bytes/tokenize.ha:59.
export fn next_token(s: *tokenizer) ([]u8 | done) = {
let b: []u8;
match (peek_token(s)) {
case let v: []u8 => { b = v; };
case done => { let d: done; return d; };
};
let slen: i64 = s.in.len: i64;
let reverse: bool = s.p < 0i64;
if (reverse) {
if (slen + s.p + 1i64 == 0i64) {
s.delim.len = 0;
s.delim.cap = 0;
s.in.len = 0;
s.in.cap = 0;
} else {
let end: i32 = (slen + s.p + 1i64 - 1i64): i32;
s.in.len = end;
s.in.cap = end;
};
s.p = types.I64_MIN;
} else {
if (s.p == slen) {
s.delim.len = 0;
s.delim.cap = 0;
s.in.len = 0;
s.in.cap = 0;
} else {
let adv: u64 = (s.p: u64) + 1u64;
let adv_i32: i32 = (s.p: i32) + 1;
s.in.ptr = s.in.ptr + adv;
s.in.len = s.in.len - adv_i32;
s.in.cap = s.in.cap - adv_i32;
};
s.p = types.I64_MAX;
};
return b;
};
// remaining_tokens — the unconsumed portion of `s.in`. Read-only view.
// ref/hare/bytes/tokenize.ha:145.
export fn remaining_tokens(s: *tokenizer) []u8 = {
return s.in;
};
// rt_ensure is the runtime slice-growth helper invoked by the
// `append(s, v)` builtin. We bind it directly because the builtin's
// expansion stores only 8 bytes of the new element (cgen emits a
// single MOVQ), losing the .len/.cap fields of a []u8 element (24B).
// Mirrors the same workaround in lib/shlex.shlex (appendstr, 16B) and
// lib/getopt.getopt (appendoption, 24B); collapses in one go when the
// append builtin learns to store the full element width.
@symbol("rt_ensure") fn rtensure(s: *void, membsz: u64) void;
// appendslice — grow `*slice` by one and store `item` (24B). Mirror
// of [[shlex.appendstr]] / [[getopt.appendoption]]. Bypasses the
// `append` builtin's first-8B-only-store gap for a slice-element.
fn appendslice(slice: *[][]u8, item: []u8) void = {
let newlen: i32 = slice.len + 1;
slice.len = newlen;
rtensure(slice: *void, 24u64);
let dst: *[]u8 = &slice.ptr[newlen - 1];
dst.ptr = item.ptr;
dst.len = item.len;
dst.cap = item.cap;
};
// splitn — split `in` on any byte in `delim`, returning up to `n`
// tokens via forward iteration. The trailing slot (when more than
// `n - 1` tokens exist) holds the unconsumed remainder.
//
// The caller frees the returned slice via
// `os.free(r.ptr: *void, (r.cap: u64) * 24u64)`. Element bytes are
// borrowed from `in`.
//
// Hare's `([][]u8 | nomem)` collapses to `[][]u8` here: ww os.alloc
// has no recoverable failure path. Same precedent as
// shlex.split / getopt.tryparse.
//
// ref/hare/bytes/tokenize.ha:156.
export fn splitn(in: []u8, delim: []u8, n: i32) [][]u8 = {
os.assert(delim.len > 0,
"bytes.splitn must not be called with an empty delimiter");
let toks: [][]u8;
toks.ptr = nil: *[]u8;
toks.len = 0;
toks.cap = 0;
let tok: tokenizer = tokenize(in, delim...);
let i: i32 = 0;
for (i < n - 1) {
match (next_token(&tok)) {
case let s: []u8 => { appendslice(&toks, s); };
case done => { return toks; };
};
i += 1;
};
match (peek_token(&tok)) {
case done => void;
case let pk: []u8 => {
let r: []u8 = remaining_tokens(&tok);
appendslice(&toks, r);
};
};
return toks;
};
// rsplitn — reverse-direction counterpart to [[splitn]]: tokens are
// collected from the end of `in`. The trailing slot holds the
// unconsumed prefix (everything before the n-th-from-last delim hit).
//
// When the input has fewer than n tokens, the `done` short-circuit
// returns toks UN-reversed (in last-token-first order). Mirrors Hare
// at ref/hare/bytes/tokenize.ha:196-199 where the in-place reverse
// step is gated behind the n-1 loop running to completion. Only the
// "loop ran to completion AND peek saw a remainder" path applies the
// reverse; both early-exit paths skip it.
//
// ref/hare/bytes/tokenize.ha:186.
export fn rsplitn(in: []u8, delim: []u8, n: i32) [][]u8 = {
os.assert(delim.len > 0,
"bytes.rsplitn called with empty delimiter");
let toks: [][]u8;
toks.ptr = nil: *[]u8;
toks.len = 0;
toks.cap = 0;
let tok: tokenizer = rtokenize(in, delim...);
let i: i32 = 0;
for (i < n - 1) {
match (next_token(&tok)) {
case let s: []u8 => { appendslice(&toks, s); };
case done => { return toks; };
};
i += 1;
};
match (peek_token(&tok)) {
case done => void;
case let pk: []u8 => {
let r: []u8 = remaining_tokens(&tok);
appendslice(&toks, r);
};
};
// In-place reverse so callers see argv-order, matching Hare
// (ref/hare/bytes/tokenize.ha:207). Element copy is field-wise
// through `*[]u8` because `toks[i] = toks[j]` (full 24B slice
// store) lands in the multi-word-store gap noted at
// cmd/w6c/cgen.c:6515-6523.
let a: i32 = 0;
let b: i32 = toks.len - 1;
for (a < b) {
let pa: *[]u8 = &toks.ptr[a];
let pb: *[]u8 = &toks.ptr[b];
let tp: *u8 = pa.ptr;
let tl: i32 = pa.len;
let tc: i32 = pa.cap;
pa.ptr = pb.ptr;
pa.len = pb.len;
pa.cap = pb.cap;
pb.ptr = tp;
pb.len = tl;
pb.cap = tc;
a += 1;
b -= 1;
};
return toks;
};
// split — full split of `in` on `delim` (no token cap). Mirrors
// `splitn(in, delim, types::SIZE_MAX)`. ww uses `types.I32_MAX`
// because the index type is i32 (lib/CLAUDE.md).
//
// ref/hare/bytes/tokenize.ha:225.
export fn split(in: []u8, delim: []u8) [][]u8 = {
return splitn(in, delim, types.I32_MAX);
};
// cut — split `in` along the first instance of `delim`, returning the
// portion before and the portion after the delimiter as a borrowed
// tuple. When `delim` is absent, the whole input is the first half and
// the second is empty. ref/hare/bytes/tokenize.ha:392.
//
// Delim is spelled (u8 | []u8) to match index/rindex (bytes.ww:57/91);
// the tagged union is an unordered set, so this is the same type as
// Hare's ([]u8 | u8), not a divergence.
export fn cut(in: []u8, delim: (u8 | []u8)) ([]u8, []u8) = {
let ln: i32 = match (delim) {
case let c: u8 => yield 1i32;
case let sub: []u8 => {
os.assert(sub.len > 0,
"bytes.cut called with empty delimiter");
yield sub.len;
};
};
match (index(in, delim)) {
case let i: i32 => {
let lo: i32 = i + ln;
return (in[0:i], in[lo:in.len]);
};
case void => {
let empty: []u8;
empty.ptr = nil; empty.len = 0; empty.cap = 0;
return (in, empty);
};
};
};
// rcut — like [[cut]] but splits along the last instance of `delim`.
// ref/hare/bytes/tokenize.ha:413.
export fn rcut(in: []u8, delim: (u8 | []u8)) ([]u8, []u8) = {
let ln: i32 = match (delim) {
case let c: u8 => yield 1i32;
case let sub: []u8 => {
os.assert(sub.len > 0,
"bytes.rcut called with empty delimiter");
yield sub.len;
};
};
match (rindex(in, delim)) {
case let i: i32 => {
let lo: i32 = i + ln;
return (in[0:i], in[lo:in.len]);
};
case void => {
let empty: []u8;
empty.ptr = nil; empty.len = 0; empty.cap = 0;
return (in, empty);
};
};
};
// encoding/utf8 — UTF-8 encode/decode. Hare port; see
// ref/hare/encoding/utf8/{types,rune,encode,decode,decodetable}.ha.
//
// The decoder is Hoehrmann's branchless DFA, originally published
// at <https://bjoern.hoehrmann.de/utf-8/decoder/dfa/>. Hare's
// ref/hare/encoding/utf8/decodetable.ha:4 restructures Hoehrmann's
// flat table to 2D `[8][256]i8`; we flatten back to 1D `[2048]i8`
// because ww cgen does not yet ship 2D arrays (task #20).
//
// Surface deviation from ref/hare/encoding/utf8:
//
// - `encoderune` takes a caller-supplied `out: []u8` and returns
// the byte count. Hare returns a slice into a `static let buf`;
// the caller-buffer form skips the static-buffer/slice-return pair.
//
// Deferred (no in-tree caller, follow-up tasks): `appendrune`,
// `strencode`, `strdecode`. Hare's string-iteration surface
// (`strings::iterator`/`strings::next` — ref/hare/strings/iter.ha)
// lives under lib/strings, not here.
// ref/hare/encoding/utf8/types.ha:6 — incomplete trailing sequence.
// Plain `void` (not `!void`): a truncated tail is a control-flow
// signal, not an error caller can ignore.
package utf8;
export type more = void;
// ref/hare/encoding/utf8/types.ha:9 — invalid UTF-8 sequence.
export type invalid = !void;
// ref/hare/encoding/utf8/types.ha:12 — fixed message; `invalid` carries
// no payload, so the rendering is constant.
export fn strerror(err: invalid) str = {
return "Invalid UTF-8";
};
// `done` is not a built-in singleton in ww (Hare ships it as part of
// the type system). Plain `void` (not `!void`): end-of-input is a
// continuation signal, not an error. lib/io spells its EOF the same
// way (lib/io/io.ww:8-11).
export type done = void;
// ref/hare/encoding/utf8/decodetable.ha:4 — Hoehrmann's UTF-8 DFA,
// flat 1D `[2048]i8`. Layout: dfa[state*256 + byte] gives the next
// state (>0), the accept transition (0 — emit rune), or invalid (-1).
// Values match ref/hare/encoding/utf8/decodetable.ha verbatim.
let dfa: [2048]i8 = [
// state 0 — initial byte: ASCII accepts (0), continuation/illegal
// byte rejects (-1), legal multibyte start emits a state.
0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8,
0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8,
0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8,
0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8,
0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8,
0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8,
0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8,
0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8,
1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8,
3i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 4i8, 2i8, 2i8,
5i8, 6i8, 6i8, 6i8, 7i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
// state 1 — expecting one continuation byte (0x80..0xBF).
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8,
0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8,
0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8,
0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
// state 2 — expecting one continuation byte (full 0x80..0xBF range).
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8,
1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8,
1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8,
1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
// state 3 — first byte was 0xE0; continuation byte must be 0xA0..0xBF
// (rejects overlong 3-byte encodings).
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8,
1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
// state 4 — first byte was 0xED; continuation byte must be 0x80..0x9F
// (rejects UTF-16 surrogate codepoints U+D800..U+DFFF).
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8,
1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
// state 5 — first byte was 0xF0; continuation byte must be 0x90..0xBF
// (rejects overlong 4-byte encodings).
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8,
2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8,
2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
// state 6 — middle continuation byte of a 4-byte sequence (0x80..0xBF).
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8,
2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8,
2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8,
2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
// state 7 — first byte was 0xF4; continuation byte must be 0x80..0x8F
// (rejects codepoints above U+10FFFF).
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
];
// ref/hare/encoding/utf8/decode.ha:17 — payload-bit masks. Hare's
// [2][8]u8 flattened to 1D [16]u8; row 0 (offsets 0..7) is the
// continuation-byte mask (always 0x3F), row 1 (offsets 8..15) is the
// initial-byte payload mask indexed by the transition class.
let masks: [16]u8 = [
0x3fu8, 0x3fu8, 0x3fu8, 0x3fu8, 0x3fu8, 0x3fu8, 0x3fu8, 0x3fu8,
0x7fu8, 0x1fu8, 0x0fu8, 0x0fu8, 0x0fu8, 0x07u8, 0x07u8, 0x07u8,
];
// ref/hare/encoding/utf8/decode.ha:6 — incremental decoder state.
export type decoder = struct {
offs: i32,
src: []u8,
};
// ref/hare/encoding/utf8/decode.ha:12.
export fn decode(src: []u8) decoder = {
let d: decoder;
d.src = src;
d.offs = 0;
return d;
};
// ref/hare/encoding/utf8/decode.ha:27. Returns the next rune from a
// decoder, `done` at end-of-input, `more` on truncated trailing
// sequence, `invalid` on malformed input (overlong, surrogate,
// out-of-range, bad continuation).
//
// Algorithm is verbatim Hoehrmann (see file header). One structural
// rewrite: Hare encodes the "initial vs continuation byte" decision
// as the branchless `(state - 1): uint >> 31`, which assumes a 32-bit
// uint. ww's uint is 64-bit (cmd/wcc/type.c:58), so the shift answer
// would be 0x1_ffff_ffff rather than 1. We spell the same predicate
// with an explicit conditional.
export fn next(d: *decoder) (rune | done | more | invalid) = {
if (d.offs == d.src.len) {
let dn: done; return dn;
};
let nx: i32 = 0;
let state: i32 = 0;
let r: u32 = 0u32;
for (d.offs < d.src.len) {
let b: u8 = d.src[d.offs];
let bi: i32 = b: i32;
let row: i32 = state * 256 + bi;
let cell: i8 = dfa[row];
nx = cell: i32;
let mi: i32 = 0;
if (state == 0) { mi = 1; };
let m: u8 = masks[mi * 8 + (nx & 7)];
r = (r << 6u32) | ((b & m): u32);
if (nx <= 0) {
d.offs += 1;
if (nx == 0) { return r: rune; };
let e: invalid; return e;
};
state = nx;
d.offs += 1;
};
let mr: more; return mr;
};
// ref/hare/encoding/utf8/decode.ha:207. Strict whole-input check.
// The hot path: tight DFA loop, no rune assembly. Bails the moment
// the table returns -1 so malformed inputs don't pay for the rest
// of the buffer.
export fn validate(src: []u8) (void | invalid) = {
let state: i32 = 0;
let i: i32 = 0;
for (i < src.len) {
if (state < 0) { break; };
let bi: i32 = src[i]: i32;
let cell: i8 = dfa[state * 256 + bi];
state = cell: i32;
i += 1;
};
if (state == 0) { return; };
let e: invalid; return e;
};
// ref/hare/encoding/utf8/rune.ha:5. Encoded byte length of `r` as
// UTF-8. Callers in ww use this to size the buffer they hand to
// [[encoderune]]; values >0x10FFFF or negative are not legal Unicode
// codepoints and Hare aborts on them in `encoderune` itself, so we
// keep `runesz` infallible (matches Hare).
export fn runesz(r: rune) i32 = {
let ch: u32 = r: u32;
if (ch < 128u32) { return 1; };
if (ch < 2048u32) { return 2; };
if (ch < 65536u32) { return 3; };
return 4;
};
// ref/hare/encoding/utf8/rune.ha:15. Expected byte length of the
// codepoint that starts with `c`, or `invalid` if `c` cannot start
// a legal UTF-8 sequence. Constants written in decimal because ww
// doesn't accept Hare's `0b1000_0000` binary syntax: 0x80=128,
// 0xC2=194, 0xE0=224, 0xF0=240, 0xF8=248.
export fn utf8sz(c: u8) (i32 | invalid) = {
if (c < 128u8) { return 1; };
if (c < 194u8) { let e: invalid; return e; };
if (c >= 248u8) { let e: invalid; return e; };
if (c < 224u8) { return 2; };
if (c < 240u8) { return 3; };
return 4;
};
// ref/hare/encoding/utf8/encode.ha:7. Encode `r` into `out` (caller-
// supplied; must hold at least [[runesz]](r) bytes) and return the
// byte count. ABORT if `r` is a UTF-16 surrogate or above U+10FFFF —
// same precondition Hare asserts at ref/hare/encoding/utf8/encode.ha:9.
//
// Surface deviation: Hare returns `[]u8` (slice into a static buf).
// ww uses the caller-buffer form; caller can reuse a [4]u8 stack
// scratch across encodes.
export fn encoderune(out: []u8, r: rune) i32 = {
let ch: u32 = r: u32;
if (ch >= 0xD800u32) {
if (ch <= 0xDFFFu32) {
abort("utf8.encoderune: surrogate codepoint");
};
};
if (ch > 0x10FFFFu32) {
abort("utf8.encoderune: codepoint > U+10FFFF");
};
let n: i32 = 0;
let first: u8 = 0u8;
if (ch < 0x80u32) {
first = 0u8; n = 1;
} else if (ch < 0x800u32) {
first = 0xC0u8; n = 2;
} else if (ch < 0x10000u32) {
first = 0xE0u8; n = 3;
} else {
first = 0xF0u8; n = 4;
};
let v: u32 = ch;
let i: i32 = n - 1;
for (i > 0) {
out[i] = ((v: u8) & 0x3Fu8) | 0x80u8;
v = v >> 6u32;
i -= 1;
};
out[0] = (v: u8) | first;
return n;
};
// ref/hare/encoding/utf8/decode.ha:52. Walks back from `d.offs` to a
// byte that could start a codepoint (state-0 dfa cell != -1), re-decodes
// forward from there, and confirms the forward decode lands back at the
// original offset. Returns `done` at start-of-input; `invalid` if no
// initial byte appears within 4 steps (no legal UTF-8 codepoint exceeds
// 4 bytes), if the forward decode returns `more`/`invalid`, or if it
// lands at a different offset than expected. Returns `more` when the
// walk reaches byte 0 without finding any initial byte.
//
// Hare's `for (d.offs < len(d.src); d.offs -= 1)` relies on size_t
// wrap-around to exit when offs underflows past 0; ww's offs is i32,
// so we spell the same exit as `d.offs >= 0`. Hare's `defer d.offs = t`
// is inlined in each match arm — ww has no defer.
export fn prev(d: *decoder) (rune | done | more | invalid) = {
if (d.offs == 0) {
let dn: done; return dn;
};
let n: i32 = d.offs;
d.offs -= 1;
for (d.offs >= 0) {
let b: u8 = d.src[d.offs];
let bi: i32 = b: i32;
let cell: i8 = dfa[bi];
if (cell: i32 != -1) {
let t: i32 = d.offs;
match (next(d)) {
case let r: rune => {
let landed: i32 = d.offs;
d.offs = t;
if (landed != n) {
let e: invalid; return e;
};
return r;
};
case let dn: done => {
d.offs = t;
let e: invalid; return e;
};
case let m: more => {
d.offs = t;
let e: invalid; return e;
};
case let e: invalid => {
d.offs = t;
let e2: invalid; return e2;
};
};
};
if (n - d.offs == 4) {
let e: invalid; return e;
};
d.offs -= 1;
};
let mr: more; return mr;
};
// ref/hare/encoding/utf8/decode.ha:74. Borrowed view of the bytes from
// the decoder's current position to the end of its source.
export fn remaining(d: *decoder) []u8 = {
let r: []u8;
r.ptr = d.src.ptr + (d.offs: u64);
r.len = d.src.len - d.offs;
r.cap = d.src.len - d.offs;
return r;
};
// ref/hare/encoding/utf8/decode.ha:80. Borrowed view of the bytes
// between two decoders' positions. Precondition (Hare asserts both):
// the decoders share the same source, and `begin.offs <= end.offs`.
export fn slice(begin: *decoder, end: *decoder) []u8 = {
if (begin.src.ptr != end.src.ptr) {
abort("utf8.slice: decoders from different sources");
};
if (begin.offs > end.offs) {
abort("utf8.slice: begin past end");
};
let r: []u8;
r.ptr = begin.src.ptr + (begin.offs: u64);
r.len = end.offs - begin.offs;
r.cap = end.offs - begin.offs;
return r;
};
// ref/hare/encoding/utf8/decode.ha:203. Byte position of the decoder
// in its source.
export fn position(d: *decoder) i32 = {
return d.offs;
};
// rt — runtime primitives exposed to ww programs.
// Mirrors Hare's rt:: module placement (ref/hare/rt/).
package rt;
// malloc — mmap-backed page allocator. Untyped: `malloc(n)` returns a
// `*void`; callers cast to the target type. Diverges from Hare: Hare
// exposes `alloc` / `free` as typed language builtins that the
// compiler lowers to rt::malloc/rt::free; ww has no such builtins,
// so the rt-symbol surface is exposed directly. Stdlib callers that
// need a typed allocation pattern wrap this with a cast plus a stored
// capacity (see [[strings.dup]], [[memio.dynamic]]).
//
// OOM: rt_malloc is a bare mmap(MAP_ANON|MAP_PRIVATE) wrapper with no
// error path. The raw Linux mmap syscall returns a negative errno cast
// to `*void` on failure (e.g. `(void*)-12` for ENOMEM); the
// `MAP_FAILED` (`(void*)-1`) value is a libc-wrapper convention that
// rt_malloc doesn't apply. Neither `== nil` nor `== (void*)-1` catches
// it; any deref of such a return faults. Today the stdlib does not
// check; OOM faults on first dereference. A typed fallible variant is
// a future task (task #39). ref/hare/rt/malloc.ha:27.
@symbol("rt_malloc") export fn malloc(n: u64) *void;
// strings — operations over str ({ptr,len}). Hare port; see
// ref/hare/strings/.
//
// Documented divergences from Hare:
//
// - `byteindex` / `rbyteindex` rune arms encode via
// `utf8.encoderune`; the legacy impls scanned for `r: u8` (an
// undocumented ASCII-only restriction that silently dropped
// to the wrong byte for U+80..U+7FF and higher).
// - `dup(s: str) str` — Hare returns `(str | nomem)`. ww's
// `os.alloc` aborts on OOM (no `nomem` type), so we return plain
// `str`. Empty input returns `{nil, 0}`; Hare returns the static
// empty string — same observable result.
// - `iterator` is flattened (`offs`, `src`, `reverse` fields).
// Hare uses anonymous-embedded `utf8::decoder`
// (ref/hare/strings/iter.ha:6-9); ww has no anonymous-embed
// syntax, so `next`/`prev`/`slice` copy `offs`/`src` into a
// local `utf8.decoder` for the call (and `next`/`prev` write
// `offs` back).
// - Hare's private `move()` helper dispatches on a `forward: bool`
// using a function-pointer `let fun = if (forward) &utf8::next
// else &utf8::prev`. ww has no fn-pointers in scope yet, so the
// dispatch is a branch on `forward` selecting the call site.
package strings;
import bytes;
import encoding.utf8;
import os;
import rt;
import types;
// toutf8 — borrowed []u8 view of `s`. ref/hare/strings/utf8.ha:29.
// `cap` equals `len`; the slice does not own a separate allocation.
export fn toutf8(s: str) []u8 = {
let r: []u8;
r.ptr = s.ptr;
r.len = s.len;
r.cap = s.len;
return r;
};
// frombytes — borrowed str view of `in`. Pure reinterpret per
// CLAUDE.md rule 9 carve-out; ref/hare/strings/utf8.ha:10.
export fn frombytes(in: []u8) str = {
let r: str;
r.ptr = in.ptr;
r.len = in.len;
return r;
};
// compare — three-way bytewise codepoint-order comparison. Return is
// a sign (neg/zero/pos), not an index, so it tracks Hare's `int`
// rather than the str-index i32 (#8). ref/hare/strings/compare.ha:12.
export fn compare(a: str, b: str) int = {
let n: i32 = a.len;
if (b.len < n) { n = b.len; };
let i: i32 = 0;
for (i < n) {
if (a[i] != b[i]) { return (a[i]: int) - (b[i]: int); };
i += 1;
};
return (a.len: int) - (b.len: int);
};
// dup — allocate a fresh copy of `s`. Caller releases with
// `os.free(r.ptr, r.len: u64)`. ref/hare/strings/dup.ha:7.
export fn dup(s: str) str = {
let r: str;
r.ptr = nil;
r.len = 0;
if (s.len == 0) { return r; };
let buf: []u8 = alloc([], s.len: u64)!;
let i: i32 = 0;
for (i < s.len) { buf[i] = s[i]; i += 1; };
buf.len = s.len;
return frombytes(buf);
};
// dupall — fresh `[]str` whose elements are independent copies of
// `s`'s elements. Caller releases via [[freeall]].
// ref/hare/strings/dup.ha:26 (#6).
//
// Hare gates the per-element dup behind `?` and rolls back via
// `defer if (!ok) freeall(newsl)`. ww has no `defer if`; more
// importantly, ww's [[dup]] is still unchecked (returns plain `str`,
// aborts via os.alloc on OOM — see top-of-file divergence note),
// so the only nomem propagation point is the initial slice alloc.
// With no inner failure path, the rollback is structurally a no-op
// and is omitted; it returns once dup graduates to `(str | nomem)`
// (#46). The pre-allocated slice has `cap == s.len`, so appendstr's
// rt_ensure call never reaches the grow branch.
//
// Empty input bypasses the alloc: rt_malloc(0) is an mmap of 0 bytes
// which returns -EINVAL, and the alloc-slice `?` shortcut routes
// that through nomem — Hare's heap allocator hands back a sentinel
// instead (#47). Return `{nil, 0, 0}` directly so callers get the
// Hare-observable shape (len==0, freeall is a no-op via cap==0).
export fn dupall(s: []str) ([]str | nomem) = {
if (s.len == 0) {
let r: []str;
r.ptr = nil: *str;
r.len = 0;
r.cap = 0;
return r;
};
let newsl: []str = alloc([], s.len)?;
let i: i32 = 0;
for (i < s.len) {
appendstr(&newsl, dup(s[i]));
i += 1;
};
return newsl;
};
// freeall — release each element + the slice header. The natural
// disposer for any `[]str` of dup'd elements (e.g. shlex.split).
// ref/hare/strings/dup.ha:38.
//
// Empty elements (`{nil, 0}` from a zero-length dup) are skipped:
// os.free on a nil pointer at len 0 tickles the rt_free guard. The
// slice header itself is freed at `cap * size(str)` — the literal
// would drift under #1's str-layout bump, so route through the
// typ.ww SSoT. A never-grown slice (cap == 0) skips the header free.
export fn freeall(s: []str) void = {
let i: i32 = 0;
for (i < s.len) {
if (s[i].len > 0) {
os.free(s[i].ptr: *void, s[i].len: u64);
};
i += 1;
};
if (s.cap > 0) {
os.free(s.ptr: *void, (s.cap: u64) * size(str): u64);
};
};
// concat — fresh allocation containing each element of `strs` in
// order. Caller releases with `os.free(r.ptr, r.len: u64)`.
// ref/hare/strings/concat.ha:5. Hare's `nomem` return is dropped:
// `os.alloc` aborts on OOM.
export fn concat(strs: str...) str = {
let total: i32 = 0;
let i: i32 = 0;
for (i < strs.len) { total += strs[i].len; i += 1; };
let r: str;
r.ptr = nil;
r.len = 0;
if (total == 0) { return r; };
let buf: []u8 = alloc([], total: u64)!;
let off: i32 = 0;
i = 0;
for (i < strs.len) {
let j: i32 = 0;
for (j < strs[i].len) {
buf[off + j] = strs[i][j];
j += 1;
};
off += strs[i].len;
i += 1;
};
buf.len = total;
return frombytes(buf);
};
// join — fresh allocation with `delim` placed between each element of
// `strs`. Caller releases with `os.free(r.ptr, r.len: u64)`.
// ref/hare/strings/concat.ha:46. Hare's `nomem` return is dropped:
// `os.alloc` aborts on OOM.
export fn join(delim: str, strs: str...) str = {
let total: i32 = 0;
let i: i32 = 0;
for (i < strs.len) {
total += strs[i].len;
if (i + 1 < strs.len) { total += delim.len; };
i += 1;
};
let r: str;
r.ptr = nil;
r.len = 0;
if (total == 0) { return r; };
let buf: []u8 = alloc([], total: u64)!;
let off: i32 = 0;
i = 0;
for (i < strs.len) {
let j: i32 = 0;
for (j < strs[i].len) {
buf[off + j] = strs[i][j];
j += 1;
};
off += strs[i].len;
if (i + 1 < strs.len) {
j = 0;
for (j < delim.len) {
buf[off + j] = delim[j];
j += 1;
};
off += delim.len;
};
i += 1;
};
buf.len = total;
return frombytes(buf);
};
// utf8bytelenbounded — walk `it` forward `end` runes and return the
// resulting byte offset. ref/hare/strings/sub.ha:10. Aborts on
// short input per Hare's contract for the rune-wise [[sub]].
fn utf8bytelenbounded(it: *iterator, end: i32) i32 = {
let i: i32 = 0;
for (i < end) {
match (next(it)) {
case let r: rune => void;
case utf8.done => abort("strings.sub: index exceeds string length");
};
i += 1;
};
return it.offs;
};
// sub — borrowed substring [start, end) where start/end are rune
// indices. ref/hare/strings/sub.ha:30. Hare's 2-arg `sub(s, start)`
// defaulting end=END is omitted: ww has no default-parameter syntax
// (filed as #37). Byte-indexed counterpart: [[bytesub]].
export fn sub(s: str, start: i32, end: i32) str = {
os.assert(start <= end, "strings.sub: start is higher than end");
let it: iterator = iter(s);
let starti: i32 = utf8bytelenbounded(&it, start);
let endi: i32 = utf8bytelenbounded(&it, end - start);
let r: str;
r.ptr = s.ptr + (starti: u64);
r.len = endi - starti;
return r;
};
// bytesub — borrowed substring [start, end) where start/end are byte
// offsets. ref/hare/strings/sub.ha:59 (#7). Returns `utf8.invalid` if
// either endpoint lands on a continuation byte (would split a
// codepoint); the equivalent Hare predicate is `s[i] & 0xc0 == 0x80`
// at ref/hare/strings/sub.ha:72-73.
export fn bytesub(s: str, start: i32, end: i32) (str | utf8.invalid) = {
os.assert(start <= end, "strings.bytesub: start is higher than end");
os.assert(end <= s.len, "strings.bytesub: end exceeds string length");
if (start < s.len) {
if ((s[start] & 0xC0u8) == 0x80u8) {
let e: utf8.invalid; return e;
};
};
if (end < s.len) {
if ((s[end] & 0xC0u8) == 0x80u8) {
let e: utf8.invalid; return e;
};
};
let r: str;
r.ptr = s.ptr + (start: u64);
r.len = end - start;
return r;
};
// runebytes — encode `r` into caller's `scratch` (must hold 4 bytes)
// and return the borrowed slice trimmed to the encoded length. Hare
// inlines the same shape at ref/hare/strings/index.ha:132.
fn runebytes(scratch: []u8, r: rune) []u8 = {
let n: i32 = utf8.encoderune(scratch, r);
let s: []u8;
s.ptr = scratch.ptr;
s.len = n;
s.cap = n;
return s;
};
// hasprefix — true iff `in` begins with `prefix`.
// ref/hare/strings/suffix.ha:8.
export fn hasprefix(in: str, prefix: (str | rune)) bool = {
let scratch: [4]u8;
let p: []u8 = match (prefix) {
case let s: str => yield toutf8(s);
case let r: rune => yield runebytes(scratch[0:4], r);
};
return bytes.hasprefix(toutf8(in), p);
};
// hassuffix — true iff `in` ends with `suff`.
// ref/hare/strings/suffix.ha:26.
export fn hassuffix(in: str, suff: (str | rune)) bool = {
let scratch: [4]u8;
let s: []u8 = match (suff) {
case let v: str => yield toutf8(v);
case let r: rune => yield runebytes(scratch[0:4], r);
};
return bytes.hassuffix(toutf8(in), s);
};
// byteindex — byte-wise offset of `needle` in `haystack`, or void if
// absent. ref/hare/strings/index.ha:127. Rune arm encodes via
// utf8.encoderune (Hare passes the encoded slice straight to
// bytes::index).
export fn byteindex(haystack: str, needle: (str | rune)) (i32 | void) = {
let scratch: [4]u8;
let n: []u8 = match (needle) {
case let s: str => yield toutf8(s);
case let r: rune => yield runebytes(scratch[0:4], r);
};
return bytes.index(toutf8(haystack), n);
};
// rbyteindex — byte-wise offset of the last `needle` in `haystack`.
// ref/hare/strings/index.ha:138.
export fn rbyteindex(haystack: str, needle: (str | rune)) (i32 | void) = {
let scratch: [4]u8;
let n: []u8 = match (needle) {
case let s: str => yield toutf8(s);
case let r: rune => yield runebytes(scratch[0:4], r);
};
return bytes.rindex(toutf8(haystack), n);
};
// indexstring — str-arm of [[index]]. Dual-rune-iterator walk: at each
// candidate rune index `i`, compare `haystack` from that position
// against `needle` rune-by-rune until needle is exhausted (match) or
// a mismatch / haystack-exhaustion breaks the inner loop. Mirrors
// ref/hare/strings/index.ha:59 (#10). Hare copies `rest_iter = s_iter`
// directly via struct assignment; ww re-seats `rest_iter` field-wise
// because the let-init struct-copy form diverges between cstage and
// wwstage on this iterator type (993_ww_ww + 995_self_rebuild fail,
// filed as #41) and rule #10 (CLAUDE.md) forbids stage asymmetry.
fn indexstring(haystack: str, needle: str) (i32 | void) = {
let s_iter: iterator = iter(haystack);
let i: i32 = 0;
for (true) {
let rest_iter: iterator;
rest_iter.src = s_iter.src;
rest_iter.offs = s_iter.offs;
rest_iter.reverse = s_iter.reverse;
let needle_iter: iterator = iter(needle);
let matched: bool = false;
for (true) {
let rest_done: bool = false;
let rest_r: rune;
match (next(&rest_iter)) {
case let r: rune => rest_r = r;
case utf8.done => rest_done = true;
};
let needle_done: bool = false;
let needle_r: rune;
match (next(&needle_iter)) {
case let r: rune => needle_r = r;
case utf8.done => needle_done = true;
};
if (rest_done && !needle_done) { break; };
if (needle_done) { matched = true; break; };
if (rest_r != needle_r) { break; };
};
if (matched) { return i; };
match (next(&s_iter)) {
case let r: rune => i += 1;
case utf8.done => return;
};
};
return;
};
// index — rune-wise offset of `needle`'s first occurrence in
// `haystack`, or void if absent. ref/hare/strings/index.ha:10. The
// str-arm delegates to [[indexstring]] (dual-iterator rune-by-rune
// walk per Hare's `index_string`, #10); the rune-arm mirrors Hare's
// `index_rune` (ref/hare/strings/index.ha:31).
export fn index(haystack: str, needle: (str | rune)) (i32 | void) = {
match (needle) {
case let s: str => return indexstring(haystack, s);
case let r: rune => {
let it: iterator = iter(haystack);
let i: i32 = 0;
for (true) {
match (next(&it)) {
case let n: rune => {
if (n == r) { return i; };
i += 1;
};
case utf8.done => return;
};
};
};
};
return;
};
// rindex — rune-wise offset of `needle`'s last occurrence in
// `haystack`, or void if absent. ref/hare/strings/index.ha:22. The
// str-arm reuses `rbyteindex`; the rune-arm walks forward tracking
// the most recent matching rune index (Hare's `rindex_rune` with
// `riter` returns a byte-offset value for multibyte strings, which
// disagrees with the rune-wise docstring; we keep the docstring's
// contract).
export fn rindex(haystack: str, needle: (str | rune)) (i32 | void) = {
match (needle) {
case let s: str => {
match (rbyteindex(haystack, s)) {
case void => return;
case let bo: i32 => {
let it: iterator = iter(haystack);
let i: i32 = 0;
for (position(&it) < bo) {
match (next(&it)) {
case let r: rune => i += 1;
case utf8.done => break;
};
};
return i;
};
};
};
case let r: rune => {
let it: iterator = iter(haystack);
let i: i32 = 0;
let last: i32 = -1;
for (true) {
match (next(&it)) {
case let n: rune => {
if (n == r) { last = i; };
i += 1;
};
case utf8.done => break;
};
};
if (last < 0) { return; };
return last;
};
};
return;
};
// contains — true iff any of `needles` occurs in `haystack`.
// ref/hare/strings/contains.ha:9.
export fn contains(haystack: str, needles: (str | rune)...) bool = {
let i: i32 = 0;
for (i < needles.len) {
match (needles[i]) {
case let s: str => {
match (byteindex(haystack, s)) {
case let bo: i32 => return true;
case void => void;
};
};
case let r: rune => {
match (byteindex(haystack, r)) {
case let bo: i32 => return true;
case void => void;
};
};
};
i += 1;
};
return false;
};
// trimprefix — `s` with `prefix` stripped from the front, or `s`
// unchanged if it doesn't start with `prefix`. Borrowed view.
// ref/hare/strings/trim.ha:60.
export fn trimprefix(input: str, prefix: str) str = {
if (!hasprefix(input, prefix)) { return input; };
let r: str;
r.ptr = input.ptr + (prefix.len: u64);
r.len = input.len - prefix.len;
return r;
};
// trimsuffix — symmetric. ref/hare/strings/trim.ha:69.
export fn trimsuffix(input: str, suffix: str) str = {
if (!hassuffix(input, suffix)) { return input; };
let r: str;
r.ptr = input.ptr;
r.len = input.len - suffix.len;
return r;
};
// whitespace — ASCII whitespace set used by the 0-arg ltrim/rtrim/trim
// branches (#9). ref/hare/strings/trim.ha:6.
let whitespace: [4]u8 = [0x20u8, 0x0Au8, 0x09u8, 0x0Du8];
// ltrim — strip leading runes that occur in `trim`. Borrowed view.
// 0-arg strips ASCII whitespace via [[bytes.ltrim]] (#9).
// ref/hare/strings/trim.ha:11. The spread expression is inlined
// because `let ws: []u8 = whitespace[0:4]` produces a slice whose
// ptr doesn't track the module-level array storage (filed as #40);
// `b.flush = flushdefault[0:1]` in lib/bufio is the same shape via
// the working field-assign path.
export fn ltrim(input: str, trim: rune...) str = {
if (trim.len == 0) {
return frombytes(bytes.ltrim(toutf8(input), whitespace[0:4]...));
};
let it: iterator = iter(input);
for (true) {
match (next(&it)) {
case let r: rune => {
let j: i32 = 0;
let found: bool = false;
for (j < trim.len) {
if (r == trim[j]) { found = true; j = trim.len; }
else { j += 1; };
};
if (!found) {
match (prev(&it)) {
case let r2: rune => void;
case utf8.done => void;
};
break;
};
};
case utf8.done => break;
};
};
return iterstr(&it);
};
// rtrim — strip trailing runes that occur in `trim`. Borrowed view.
// 0-arg strips ASCII whitespace via [[bytes.rtrim]] (#9). Spread is
// inlined to dodge #40 — see [[ltrim]].
// ref/hare/strings/trim.ha:32.
export fn rtrim(input: str, trim: rune...) str = {
if (trim.len == 0) {
return frombytes(bytes.rtrim(toutf8(input), whitespace[0:4]...));
};
let it: iterator = riter(input);
for (true) {
match (next(&it)) {
case let r: rune => {
let j: i32 = 0;
let found: bool = false;
for (j < trim.len) {
if (r == trim[j]) { found = true; j = trim.len; }
else { j += 1; };
};
if (!found) {
match (prev(&it)) {
case let r2: rune => void;
case utf8.done => void;
};
break;
};
};
case utf8.done => break;
};
};
return iterstr(&it);
};
// trim — strip from both ends. ref/hare/strings/trim.ha:54.
export fn trim(input: str, trim: rune...) str = {
return ltrim(rtrim(input, trim...), trim...);
};
// iterator — UTF-8 rune cursor over a `str`. Layout flattens Hare's
// anonymous-embedded `utf8::decoder` (ref/hare/strings/iter.ha:6-9) to
// explicit fields. `reverse` selects walk direction: forward iterators
// (`iter`) advance through utf8.next; reverse iterators (`riter`) advance
// through utf8.prev. May be copied to save state.
export type iterator = struct {
offs: i32,
src: []u8,
reverse: bool,
};
// iter — initialize a forward iterator at the start of `src`.
// ref/hare/strings/iter.ha:24.
export fn iter(src: str) iterator = {
let r: iterator;
r.src = toutf8(src);
r.offs = 0;
r.reverse = false;
return r;
};
// riter — initialize a reverse iterator at the end of `src`. `next`
// on a reverse iterator walks back through the string.
// ref/hare/strings/iter.ha:32.
export fn riter(src: str) iterator = {
let r: iterator;
r.src = toutf8(src);
r.offs = src.len;
r.reverse = true;
return r;
};
// move — private dispatch shared by next/prev. `forward` selects
// utf8.next vs utf8.prev. Aborts on more/invalid per Hare's
// ref/hare/strings/iter.ha:51-58 ("Invalid UTF-8 string (this should
// not happen)"). Hare picks the utf8 function via a fn-pointer; ww
// branches on `forward` at each call site instead.
fn move(forward: bool, it: *iterator) (rune | utf8.done) = {
let d: utf8.decoder;
d.src = it.src;
d.offs = it.offs;
if (forward) {
match (utf8.next(&d)) {
case let r: rune => { it.offs = d.offs; return r; };
case let dn: utf8.done => return dn;
case let m: utf8.more => abort("strings.move: invalid UTF-8");
case let e: utf8.invalid => abort("strings.move: invalid UTF-8");
};
} else {
match (utf8.prev(&d)) {
case let r: rune => { it.offs = d.offs; return r; };
case let dn: utf8.done => return dn;
case let m: utf8.more => abort("strings.move: invalid UTF-8");
case let e: utf8.invalid => abort("strings.move: invalid UTF-8");
};
};
};
// next — advance the iterator one rune. Forward iterators step
// through utf8.next; reverse iterators (riter) step backward through
// utf8.prev. Returns utf8.done at end-of-walk. ref/hare/strings/iter.ha:45.
export fn next(it: *iterator) (rune | utf8.done) = {
return move(!it.reverse, it);
};
// prev — step back one rune. Dual to next: on a forward iterator
// this walks utf8.prev; on a reverse iterator (riter) it walks
// utf8.next. ref/hare/strings/iter.ha:49.
export fn prev(it: *iterator) (rune | utf8.done) = {
return move(it.reverse, it);
};
// iterstr — borrowed view of the bytes remaining in the iterator's
// walk direction. Forward iter: bytes from offs to end; reverse iter:
// bytes from start to offs. ref/hare/strings/iter.ha:63.
export fn iterstr(it: *iterator) str = {
let r: []u8;
if (it.reverse) {
r = it.src[0:it.offs];
} else {
r = it.src[it.offs:it.src.len];
};
return frombytes(r);
};
// slice — borrowed substring between two iterator positions.
// ref/hare/strings/iter.ha:75. Hare passes `*iterator` directly where
// `*utf8::decoder` is expected via anonymous-embed coercion; ww has
// no anonymous embed, so we reconstruct a local utf8.decoder for each
// endpoint and forward — same pattern as `move` above.
export fn slice(begin: *iterator, end: *iterator) str = {
let b: utf8.decoder;
b.src = begin.src;
b.offs = begin.offs;
let e: utf8.decoder;
e.src = end.src;
e.offs = end.offs;
return frombytes(utf8.slice(&b, &e));
};
// position — byte-wise offset of the iterator in its source.
// ref/hare/strings/iter.ha:82.
export fn position(it: *iterator) i32 = {
return it.offs;
};
// tokenizer — re-export of bytes.tokenizer. ref/hare/strings/tokenize.ha:7.
// First cross-module type alias in tree; needs #22's transitive
// alias-chain unwrap (cstage type_chase_named + wwstage
// structlookupchain) to walk struct fields through the chain.
export type tokenizer = bytes.tokenizer;
// tokenize — yield substrings of `s` split on any byte in `delim`.
// Leading / trailing / adjacent delims yield empty tokens. `s` and
// `delim` are borrowed; caller keeps them live for the tokenizer's
// lifetime. ref/hare/strings/tokenize.ha:32. ASCII-only delim
// asserted per Hare lines 35-37: a multibyte rune in delim would
// split on a single continuation byte and yield invalid UTF-8.
export fn tokenize(s: str, delim: str) tokenizer = {
let d: []u8 = toutf8(delim);
let i: i32 = 0;
for (i < d.len) {
os.assert((d[i] & 0x80u8) == 0u8,
"strings.tokenize cannot tokenize on non-ASCII delimiters");
i += 1;
};
return bytes.tokenize(toutf8(s), d...);
};
// rtokenize — reverse-direction counterpart to [[tokenize]]. First
// next_token yields the last token, last yields the first.
// ref/hare/strings/tokenize.ha:44.
export fn rtokenize(s: str, delim: str) tokenizer = {
let d: []u8 = toutf8(delim);
let i: i32 = 0;
for (i < d.len) {
os.assert((d[i] & 0x80u8) == 0u8,
"strings.rtokenize cannot tokenize on non-ASCII delimiters");
i += 1;
};
return bytes.rtokenize(toutf8(s), d...);
};
// next_token — current token, advancing the cursor.
// ref/hare/strings/tokenize.ha:62.
export fn next_token(s: *tokenizer) (str | bytes.done) = {
let b: *bytes.tokenizer = s: *bytes.tokenizer;
match (bytes.next_token(b)) {
case let v: []u8 => return frombytes(v);
case bytes.done => { let d: bytes.done; return d; };
};
};
// peek_token — current token without advancing.
// ref/hare/strings/tokenize.ha:71.
export fn peek_token(s: *tokenizer) (str | bytes.done) = {
let b: *bytes.tokenizer = s: *bytes.tokenizer;
match (bytes.peek_token(b)) {
case let v: []u8 => return frombytes(v);
case bytes.done => { let d: bytes.done; return d; };
};
};
// remaining_tokens — unconsumed portion of the input ahead of the
// cursor. ref/hare/strings/tokenize.ha:79.
export fn remaining_tokens(s: *tokenizer) str = {
let b: *bytes.tokenizer = s: *bytes.tokenizer;
return frombytes(bytes.remaining_tokens(b));
};
// cut — split `in` along the first instance of `delim`, returning the
// portions before and after it. When `delim` is absent the whole input
// is the first half and the second is empty. Both halves are borrowed
// from `in`; caller ensures `delim` is non-empty.
// ref/hare/strings/tokenize.ha:288.
export fn cut(in: str, delim: str) (str, str) = {
let (a, b) = bytes.cut(toutf8(in), toutf8(delim));
return (frombytes(a), frombytes(b));
};
// rcut — like [[cut]] but split along the LAST instance of `delim`.
// ref/hare/strings/tokenize.ha:302.
export fn rcut(in: str, delim: str) (str, str) = {
let (a, b) = bytes.rcut(toutf8(in), toutf8(delim));
return (frombytes(a), frombytes(b));
};
// rt_ensure is the runtime slice-growth helper invoked by the
// `append(s, v)` builtin. Direct bind for the same reason as
// lib/shlex.shlex (appendstr, 16B): the builtin's expansion stores
// only 8B of the new element, losing the `.len` half of a `str`.
@symbol("rt_ensure") fn rtensure(s: *void, membsz: u64) void;
// appendstr — grow `*slice` by one and store `item` (16B). Mirror of
// lib/shlex.shlex appendstr. Collapses when the append builtin learns
// to store the full element width.
fn appendstr(slice: *[]str, item: str) void = {
let newlen: i32 = slice.len + 1;
slice.len = newlen;
rtensure(slice: *void, size(str): u64);
let dst: *str = &slice.ptr[newlen - 1];
dst.ptr = item.ptr;
dst.len = item.len;
};
// splitn — split `in` on any byte in `delim`, returning up to `n`
// tokens via forward iteration. The trailing slot (when more than
// `n - 1` tokens exist) holds the unconsumed remainder. Strings
// within the result are borrowed from `in`.
//
// The caller frees the returned slice via
// `os.free(r.ptr: *void, (r.cap: u64) * size(str): u64)`.
//
// Hare's `([]str | nomem)` collapses to `[]str` here: ww os.alloc
// has no recoverable failure path. Same precedent as
// shlex.split / bytes.splitn.
//
// ref/hare/strings/tokenize.ha:172.
export fn splitn(in: str, delim: str, n: i32) []str = {
let toks: []str;
toks.ptr = nil: *str;
toks.len = 0;
toks.cap = 0;
let tok: tokenizer = tokenize(in, delim);
let i: i32 = 0;
for (i < n - 1) {
match (next_token(&tok)) {
case let s: str => { appendstr(&toks, s); };
case bytes.done => { return toks; };
};
i += 1;
};
match (peek_token(&tok)) {
case bytes.done => void;
case let pk: str => {
let r: str = remaining_tokens(&tok);
appendstr(&toks, r);
};
};
return toks;
};
// rsplitn — reverse-direction counterpart to [[splitn]]: tokens are
// collected from the end of `in`. The trailing slot holds the
// unconsumed prefix (everything before the n-th-from-last delim hit).
//
// When the input has fewer than n tokens, the `done` short-circuit
// returns toks UN-reversed (in last-token-first order). Mirrors Hare
// at ref/hare/strings/tokenize.ha:219-224 where the in-place reverse
// step is gated behind the n-1 loop running to completion.
//
// ref/hare/strings/tokenize.ha:200.
export fn rsplitn(in: str, delim: str, n: i32) []str = {
let toks: []str;
toks.ptr = nil: *str;
toks.len = 0;
toks.cap = 0;
let tok: tokenizer = rtokenize(in, delim);
let i: i32 = 0;
for (i < n - 1) {
match (next_token(&tok)) {
case let s: str => { appendstr(&toks, s); };
case bytes.done => { return toks; };
};
i += 1;
};
match (peek_token(&tok)) {
case bytes.done => void;
case let pk: str => {
let r: str = remaining_tokens(&tok);
appendstr(&toks, r);
};
};
// In-place reverse so callers see argv-order, matching Hare
// (ref/hare/strings/tokenize.ha:220). Element copy is field-wise
// through `*str` because `toks[i] = toks[j]` (full 16B str store)
// lands in the multi-word-store gap noted at cmd/w6c/cgen.c:6515.
let a: i32 = 0;
let b: i32 = toks.len - 1;
for (a < b) {
let pa: *str = &toks.ptr[a];
let pb: *str = &toks.ptr[b];
let tp: *u8 = pa.ptr;
let tl: i32 = pa.len;
pa.ptr = pb.ptr;
pa.len = pb.len;
pb.ptr = tp;
pb.len = tl;
a += 1;
b -= 1;
};
return toks;
};
// split — full split of `in` on `delim` (no token cap). Mirrors
// `splitn(in, delim, types::SIZE_MAX)`. ww uses `types.I32_MAX`
// because the index type is i32 (lib/CLAUDE.md).
//
// ref/hare/strings/tokenize.ha:242.
export fn split(in: str, delim: str) []str = {
return splitn(in, delim, types.I32_MAX);
};
// lpad — left-pad `s` with `p` rune until the result reaches `maxlen`
// bytes. Length comparison is BYTES, mirroring Hare's `len(s) >= maxlen`
// at ref/hare/strings/pad.ha:9. A multibyte `p` whose encoded width
// doesn't divide `maxlen - s.len` evenly leaves a trailing pad byte
// pair sliced mid-codepoint at byte `maxlen-1`, exactly as Hare's
// `res[..maxlen]` does (ref/hare/strings/pad.ha:20). When
// `(maxlen - s.len) * pad.len >= maxlen` (multibyte pad overflows the
// budget), `s` is entirely sliced off — same as Hare. Caller releases
// with `os.free(r.ptr, r.len: u64)`. Hare's `nomem` return is dropped:
// `os.alloc` aborts on OOM. Buf size == r.len keeps the free-contract
// shape of [[dup]] / [[concat]] / [[join]]; Hare's `alloc([], maxlen)!`
// over-allocs via append then slices, but Hare's slice-free recovers
// the true capacity from the heap allocator (rt/ensure.ha:24), which
// ww's munmap-based `os.free` cannot do.
export fn lpad(s: str, p: rune, maxlen: i32) str = {
if (s.len >= maxlen) { return dup(s); };
let scratch: [4]u8;
let pad: []u8 = runebytes(scratch[0:4], p);
let buf: []u8 = alloc([], maxlen: u64)!;
let padwrite: i32 = (maxlen - s.len) * pad.len;
if (padwrite > maxlen) { padwrite = maxlen; };
let off: i32 = 0;
for (off < padwrite) {
buf[off] = pad.ptr[off % pad.len];
off += 1;
};
let k: i32 = 0;
let srem: i32 = maxlen - off;
if (srem > s.len) { srem = s.len; };
for (k < srem) {
buf[off + k] = s[k];
k += 1;
};
buf.len = maxlen;
return frombytes(buf);
};
// replace — fresh allocation of `s` with every non-overlapping
// occurrence of `needle` replaced by `target`. Caller releases with
// `os.free(r.ptr, r.len: u64)`. ref/hare/strings/replace.ha:8 (#4).
//
// Hare delegates to [[multireplace]] with a single pair; ww has no
// `(str, str)` variadic shape today (#39), so this is a standalone
// two-pass implementation: pass 1 counts matches to size the result,
// pass 2 copies chunks and `target` into a single fresh buffer.
// Single nomem path (the `alloc([], total)?`) preserves Hare's
// signature without a per-write `append(...)?` (ww's append builtin
// aborts on OOM, #11). Empty `needle` would hasprefix-match every
// position with a zero stride — same infinite loop Hare exhibits at
// ref/hare/strings/replace.ha:31; not gated.
export fn replace(s: str, needle: str, target: str) (str | nomem) = {
let sb: []u8 = toutf8(s);
let nb: []u8 = toutf8(needle);
let tb: []u8 = toutf8(target);
let count: i32 = 0;
let i: i32 = 0;
for (i < sb.len) {
if (bytes.hasprefix(sb[i:sb.len], nb)) {
count += 1;
i += nb.len;
} else {
i += 1;
};
};
let total: i32 = sb.len + count * (tb.len - nb.len);
if (total == 0) {
let r: str;
r.ptr = nil;
r.len = 0;
return r;
};
let res: []u8 = alloc([], total)?;
let off: i32 = 0;
i = 0;
for (i < sb.len) {
if (bytes.hasprefix(sb[i:sb.len], nb)) {
let j: i32 = 0;
for (j < tb.len) {
res.ptr[off + j] = tb.ptr[j];
j += 1;
};
off += tb.len;
i += nb.len;
} else {
res.ptr[off] = sb.ptr[i];
off += 1;
i += 1;
};
};
res.len = total;
return frombytes(res);
};
// rpad — right-pad `s` with `p` rune until the result reaches `maxlen`
// bytes. Symmetric with [[lpad]]. ref/hare/strings/pad.ha:39.
export fn rpad(s: str, p: rune, maxlen: i32) str = {
if (s.len >= maxlen) { return dup(s); };
let scratch: [4]u8;
let pad: []u8 = runebytes(scratch[0:4], p);
let buf: []u8 = alloc([], maxlen: u64)!;
let k: i32 = 0;
for (k < s.len) {
buf[k] = s[k];
k += 1;
};
let padwrite: i32 = maxlen - s.len;
let i: i32 = 0;
for (i < padwrite) {
buf[s.len + i] = pad.ptr[i % pad.len];
i += 1;
};
buf.len = maxlen;
return frombytes(buf);
};
// ascii — rune-class predicates and case folding for the ASCII range.
// Matches Hare's ascii::isdigit family (rune-taking signature). Runes
// outside 0..127 always answer `false`. The lexer hot path uses these
// inline; they are expected to inline to a couple of compares.
package ascii;
import strings;
export fn isdigit(c: rune) bool = {
if (c < '0') { return false; };
if (c > '9') { return false; };
return true;
};
export fn isupper(c: rune) bool = {
if (c < 'A') { return false; };
if (c > 'Z') { return false; };
return true;
};
export fn islower(c: rune) bool = {
if (c < 'a') { return false; };
if (c > 'z') { return false; };
return true;
};
export fn isalpha(c: rune) bool = {
if (isupper(c)) { return true; };
return islower(c);
};
export fn isalnum(c: rune) bool = {
if (isalpha(c)) { return true; };
return isdigit(c);
};
// isspace — the C/Hare set: space, tab, NL, VT, FF, CR.
export fn isspace(c: rune) bool = {
if (c == ' ') { return true; };
if (c == '\t') { return true; };
if (c == '\n') { return true; };
if (c == '\v') { return true; };
if (c == '\f') { return true; };
if (c == '\r') { return true; };
return false;
};
export fn isxdigit(c: rune) bool = {
if (isdigit(c)) { return true; };
if (c >= 'A') {
if (c <= 'F') { return true; };
};
if (c >= 'a') {
if (c <= 'f') { return true; };
};
return false;
};
// valid — `c` is in the 0..127 ASCII range.
export fn valid(c: rune) bool = {
if (c < 0) { return false; };
if (c > 127) { return false; };
return true;
};
// validstr — every byte in `s` is ASCII (0..127).
export fn validstr(s: str) bool = {
let i: i32 = 0;
for (i < s.len) {
// High-bit test rather than `> 127u8`; both cgens lower
// the bitwise form identically. The `> u8` form picks
// JA vs JG depending on signed/unsigned dispatch.
if ((s[i] & 128u8) != 0u8) { return false; };
i += 1;
};
return true;
};
// iscntrl — control chars: 0..31 and 127.
export fn iscntrl(c: rune) bool = {
if (c >= 0) { if (c <= 31) { return true; }; };
if (c == 127) { return true; };
return false;
};
// isblank — space and tab.
export fn isblank(c: rune) bool = {
if (c == ' ') { return true; };
if (c == '\t') { return true; };
return false;
};
// isprint — printable: space through '~'.
export fn isprint(c: rune) bool = {
if (c < ' ') { return false; };
if (c > '~') { return false; };
return true;
};
// isgraph — printable, non-space.
export fn isgraph(c: rune) bool = {
if (c < '!') { return false; };
if (c > '~') { return false; };
return true;
};
// ispunct — printable, non-alnum, non-space.
export fn ispunct(c: rune) bool = {
if (!isgraph(c)) { return false; };
if (isalnum(c)) { return false; };
return true;
};
// tolower / toupper — fold ASCII case. Non-letters pass through.
export fn tolower(c: rune) rune = {
if (isupper(c)) { return c + 32; };
return c;
};
export fn toupper(c: rune) rune = {
if (islower(c)) { return c - 32; };
return c;
};
// strcasecmp — three-way ASCII case-insensitive compare.
export fn strcasecmp(a: str, b: str) i32 = {
let n: i32 = a.len;
if (b.len < n) { n = b.len; };
let i: i32 = 0;
for (i < n) {
let ca: rune = tolower(a[i]: rune);
let cb: rune = tolower(b[i]: rune);
if (ca != cb) { return (ca - cb): i32; };
i += 1;
};
return a.len - b.len;
};
// strlower — ASCII-lowercased copy of s, newly allocated.
// ref/hare/ascii/string.ha:11.
export fn strlower(s: str) (str | nomem) = {
// empty bypass: ww alloc([],0) routes through nomem; Hare allocs 0
// and zero-loops (ref/hare/ascii/string.ha:12).
if (s.len == 0) {
let r: str;
r.ptr = nil;
r.len = 0;
return r;
};
let buf: []u8 = alloc([], s.len: u64)?;
return strlower_buf(s, buf);
};
// strlower_buf — ASCII-lowercase s into buf (overwrites). nomem if buf
// too small. ref/hare/ascii/string.ha:21.
// Byte-wise fold: ASCII case-fold only touches bytes <0x80; UTF-8
// multibyte bytes are >=0x80 and pass through unchanged, so byte-wise
// equals Hare's rune fold and is length-preserving.
// ww uses an explicit `buf.cap < s.len` check + `let nm: nomem` value
// because it has no static-append builtin; Hare reaches the same
// nomem-on-too-small via `static append(buf, ...)?` (string.ha:25).
export fn strlower_buf(s: str, buf: []u8) (str | nomem) = {
if (buf.cap < s.len) {
let nm: nomem;
return nm;
};
let i: i32 = 0;
for (i < s.len) {
buf.ptr[i] = tolower(s[i]: rune): u8;
i += 1;
};
buf.len = s.len;
return strings.frombytes(buf);
};
// strupper — ASCII-uppercased copy of s, newly allocated.
// ref/hare/ascii/string.ha:33.
export fn strupper(s: str) (str | nomem) = {
if (s.len == 0) {
let r: str;
r.ptr = nil;
r.len = 0;
return r;
};
let buf: []u8 = alloc([], s.len: u64)?;
return strupper_buf(s, buf);
};
// strupper_buf — see strlower_buf. ref/hare/ascii/string.ha:43.
export fn strupper_buf(s: str, buf: []u8) (str | nomem) = {
if (buf.cap < s.len) {
let nm: nomem;
return nm;
};
let i: i32 = 0;
for (i < s.len) {
buf.ptr[i] = toupper(s[i]: rune): u8;
i += 1;
};
buf.len = s.len;
return strings.frombytes(buf);
};
// strconv — string-to-float. Mirrors ref/hare/strconv/stof.ha
// (Hare in turn adapts Go): Eisel-Lemire fast path [1] with the
// Simple-Decimal-Conversion slow path [2] (decimal.ww) as fallback.
// [1]: https://nigeltao.github.io/blog/2020/eisel-lemire.html
// [2]: https://nigeltao.github.io/blog/2020/parse-number-f64-simple.html
//
// The Eisel-Lemire fast path (`eisel_lemire` + the `powers_of_ten`
// table in stof_data.ww + the three call sites: floatbits's d.nd<=19
// block, stof64/stof32's !truncated block) is a pure speed
// optimisation — it returns the same correctly-rounded value the
// decimal slow path (decimal_parse → floatbits) computes, or void to
// defer. Its prereqs landed: the 2D `[596][2]u64` static-init +
// double-index read (#156) and the tagged float-variant return-pack
// (#157, which the public `(f64|invalid|overflow)` return needs).
//
// Spelling divergences from Hare (mechanical, ww parser/cgen shape):
// - str scan index rides `i32` (ww `str.len: i32` + `invalid = !i32`
// payload), not Hare's `size`/`len(s)`. lib CLAUDE.md str-index note.
// - char literals kept faithful (`buf[i] == '.'`, `c - '0'`); probed
// byte-id + value-correct both stages.
// - Hare `?` error-propagation → nested statement-`match` with all-
// return arms + a `case void => void` continuation. ww's `?`
// lowering and a bound `match`-expression with mixed yield/return
// arms both diverge cs≠ww (the latter wwstage-checker-rejected);
// strconv.ww's stoi32 set the explicit-match precedent.
// - Hare `for (cond; afterthought)` 2-clause + `continue` → ww
// 2-clause `for (cond)` with the afterthought inlined at body end
// AND before each `continue` (ww has no empty-init 3-clause
// `for (; c; p)`; #138 post-skip is dodged since 2-clause has no
// post). decimal.ww set the inline-afterthought precedent.
// - Hare `if`/`switch`-expression yield → explicit if-statements +
// pre-bound scalar locals (ww has no expression-bodied if).
// - Hare fn-pointer-in-tuple + `switch yield` selecting the digit
// predicate in fast_parse → a `base==HEX` bool + an `isdigitbase`
// helper that branches to ascii.isdigit/isxdigit (no fn-ptr, no
// tuple, no switch).
// - struct-param field MUTATION (hex_to_bits mutates its by-value
// `p`) → copy p's fields to scalar locals at entry; ww miscompiles
// + diverges on writing a by-value struct param's fields (filed).
// - default arg dropped: Hare `b: base = base::DEC` → callers pass
// base explicitly (no lib fn ships a default arg; strconv.ww
// stoi64 precedent). The base param is normalised through a local
// `bb` (param reassignment avoided).
// - `math::NAN`/`math::INF` (f32) absent in ww math → materialised
// via f32frombits of the IEEE-754 f32 bit patterns (same honest
// construction as math/floats.ww's NAN_BITS/INF_BITS).
// - narrowing int→i32 assignments carry explicit casts (ww `int` is
// an 8B machine word; project_int_machine_word_derived_limits).
// - `r128`/`u128mul` live here (fold-4 is first consumer); fold-5
// ftos (Ryū) shares them in-package.
package strconv;
import ascii;
import math;
import os;
import strings;
// ref/hare/strconv/ftos_ryu.ha:12. 64×64→128 result halves.
type r128 = struct {
hi: u64,
lo: u64,
};
// ref/hare/strconv/ftos_ryu.ha:18. 64×64→128 via 32-bit decomposition
// (Hare's own "TODO: use 128-bit integers when implemented" — ww has
// no u128; the decomposition is the portable shape both stages agree
// on). Comma let-bindings split per decimal.ww divergence.
fn u128mul(a: u64, b: u64) r128 = {
let a0: u64 = (a: u32): u64;
let a1: u64 = a >> 32u64;
let b0: u64 = (b: u32): u64;
let b1: u64 = b >> 32u64;
let p00: u64 = a0 * b0;
let p01: u64 = a0 * b1;
let p10: u64 = a1 * b0;
let p11: u64 = a1 * b1;
let p00_lo: u64 = (p00: u32): u64;
let p00_hi: u64 = p00 >> 32u64;
let mid1: u64 = p10 + p00_hi;
let mid1_lo: u64 = (mid1: u32): u64;
let mid1_hi: u64 = mid1 >> 32u64;
let mid2: u64 = p01 + mid1_lo;
let mid2_lo: u64 = (mid2: u32): u64;
let mid2_hi: u64 = mid2 >> 32u64;
let r_hi: u64 = p11 + mid1_hi + mid2_hi;
let r_lo: u64 = (mid2_lo << 32u64) | p00_lo;
return r128 { hi = r_hi, lo = r_lo };
};
// ref/hare/strconv/stof.ha:14.
fn todig(c: u8) u8 = {
if ('0' <= c && c <= '9') { return c - '0'; };
if ('a' <= c && c <= 'f') { return c - 'a' + 10u8; };
if ('A' <= c && c <= 'F') { return c - 'A' + 10u8; };
abort("strconv.todig: unreachable");
return 0u8; // unreachable; rt_abort is void-typed (path-cov)
};
@symbol("rt_abort") fn abort(msg: str) void;
// ref/hare/strconv/stof.ha:25.
type fast_parsed_float = struct {
mantissa: u64,
exponent: i32,
negative: bool,
truncated: bool,
};
// Digit-class predicate selector for fast_parse — replaces Hare's
// fn-pointer-in-tuple (`&ascii::isdigit` / `&ascii::isxdigit`).
fn isdigitbase(c: rune, ishex: bool) bool = {
if (ishex) { return ascii.isxdigit(c); };
return ascii.isdigit(c);
};
// ref/hare/strconv/stof.ha:32.
fn fast_parse(s: str, b: base) (fast_parsed_float | invalid) = {
let buf: []u8 = strings.toutf8(s);
let i: i32 = 0;
let neg: bool = false;
let trunc: bool = false;
if (buf[i] == '-') {
neg = true;
i += 1;
} else if (buf[i] == '+') {
i += 1;
};
let ishex: bool = (b == base.HEX);
let expchr: rune = 'e';
let max_ndmant: int = 19;
if (ishex) {
expchr = 'p';
max_ndmant = 16;
};
let bnum: u64 = (b: i32): u64;
let sawdot: bool = false;
let sawdigits: bool = false;
let nd: int = 0;
let ndmant: int = 0;
let dp: int = 0;
let mant: u64 = 0u64;
let exp: i32 = 0i32;
for (i < s.len) {
if (buf[i] == '.') {
if (sawdot) { return i: invalid; };
sawdot = true;
dp = nd;
} else if (isdigitbase(buf[i]: rune, ishex)) {
sawdigits = true;
if (buf[i] == '0' && nd == 0) {
dp -= 1;
i += 1;
continue;
};
nd += 1;
if (ndmant < max_ndmant) {
mant = mant * bnum + (todig(buf[i]): u64);
ndmant += 1;
} else if (buf[i] != '0') {
trunc = true;
};
} else {
break;
};
i += 1;
};
if (!sawdigits) { return i: invalid; };
if (!sawdot) {
dp = nd;
};
if (b == base.HEX) {
dp *= 4;
ndmant *= 4;
};
if (i < s.len && ascii.tolower(buf[i]: rune) == expchr) {
i += 1;
if (i >= s.len) { return i: invalid; };
let expsign: int = 1;
if (buf[i] == '+') {
i += 1;
} else if (buf[i] == '-') {
expsign = -1;
i += 1;
};
if (i >= s.len || !ascii.isdigit(buf[i]: rune)) {
return i: invalid;
};
let e: int = 0;
for (i < s.len && ascii.isdigit(buf[i]: rune)) {
if (e < 10000) {
e = e * 10 + ((buf[i] - '0'): int);
};
i += 1;
};
dp += e * expsign;
} else if (b == base.HEX) {
return i: invalid; // hex floats must have an exponent
};
if (i != s.len) { return i: invalid; };
if (mant != 0u64) {
exp = (dp - ndmant): i32;
};
return fast_parsed_float {
mantissa = mant,
exponent = exp,
negative = neg,
truncated = trunc,
};
};
// ref/hare/strconv/stof.ha:115. Fills the slow-path decimal `d`.
fn decimal_parse(d: *decimal, s: str) (void | invalid) = {
let i: i32 = 0;
let buf: []u8 = strings.toutf8(s);
d.negative = false;
d.truncated = false;
if (buf[0] == '+') {
i += 1;
} else if (buf[0] == '-') {
d.negative = true;
i += 1;
};
let sawdot: bool = false;
let sawdigits: bool = false;
for (i < s.len) {
if (buf[i] == '.') {
if (sawdot) { return i: invalid; };
sawdot = true;
d.dp = (d.nd: i32);
} else if (ascii.isdigit(buf[i]: rune)) {
sawdigits = true;
if (buf[i] == '0' && d.nd == (0u64: size)) {
d.dp -= 1;
i += 1;
continue;
};
if (d.nd < (len(d.digits): size)) {
d.digits[d.nd] = buf[i] - '0';
d.nd += (1u64: size);
} else if (buf[i] != '0') {
d.truncated = true;
};
} else {
break;
};
i += 1;
};
if (!sawdigits) { return i: invalid; };
if (!sawdot) {
d.dp = (d.nd: i32);
};
if (i < s.len && (buf[i] == 'e' || buf[i] == 'E')) {
i += 1;
if (i >= s.len) { return i: invalid; };
let expsign: int = 1;
if (buf[i] == '+') {
i += 1;
} else if (buf[i] == '-') {
expsign = -1;
i += 1;
};
if (i >= s.len || !ascii.isdigit(buf[i]: rune)) {
return i: invalid;
};
let e: int = 0;
for (i < s.len && ascii.isdigit(buf[i]: rune)) {
if (e < 10000) {
e = e * 10 + ((buf[i] - '0'): int);
};
i += 1;
};
d.dp += (e * expsign): i32;
};
if (i != s.len) { return i: invalid; };
return;
};
// ref/hare/strconv/stof.ha:173. Count of leading zero bits in n>0.
fn leading_zeroes(n: u64) uint = {
os.assert(n > 0u64, "strconv.leading_zeroes: n == 0");
let b: u64 = 0u64;
if ((n & 0xFFFFFFFF00000000u64) > 0u64) {
n >>= 32u64;
b |= 32u64;
};
if ((n & 0xFFFF0000u64) > 0u64) {
n >>= 16u64;
b |= 16u64;
};
if ((n & 0xFF00u64) > 0u64) {
n >>= 8u64;
b |= 8u64;
};
if ((n & 0xF0u64) > 0u64) {
n >>= 4u64;
b |= 4u64;
};
if ((n & 0xCu64) > 0u64) {
n >>= 2u64;
b |= 2u64;
};
if ((n & 0x2u64) > 0u64) {
n >>= 1u64;
b |= 1u64;
};
return ((63u64 - b): uint);
};
// ref/hare/strconv/stof.ha:203. Eisel-Lemire fast path: a correctly-
// rounded f64/f32 from (mantissa, exp10) when the 128-bit product is
// unambiguous, else void → caller falls to the decimal slow path.
// Divergences at-site: `mantissa <<= clz` (scalar-param mutate) → local
// `mnt`; whole-struct local reassign `x = merged` copies only the first
// word in cgen → per-field `x.hi = …; x.lo = …` (#155); `po10 =
// powers_of_ten[i]` row-bind → direct double-index (#155, A2); bitwise-
// vs-compare fully parenthesised; comma let-bindings split.
fn eisel_lemire(
mantissa: u64,
exp10: i32,
neg: bool,
f: *math.floatinfo,
) (u64 | void) = {
if (mantissa == 0u64 || exp10 > 288 || exp10 < -307) {
return;
};
let idx: i32 = exp10 + 307;
let clz: uint = leading_zeroes(mantissa);
let mnt: u64 = mantissa << (clz: u64);
let shift: u64 = 64u64 - f.mantbits - 3u64;
let mask: u64 = (1u64 << shift) - 1u64;
// log(10)/log(2) ≈ 217706 / 65536; x / 65536 = x >> 16.
let exp: int = (217706 * (exp10: int)) >> 16;
let e2: u64 = ((exp + f.expbias + 64): u64) - (clz: u64);
let x: r128 = u128mul(mnt, powers_of_ten[idx][1]);
if ((x.hi & mask) == mask && (x.lo + mnt) < mnt) {
let y: r128 = u128mul(mnt, powers_of_ten[idx][0]);
let merged: r128 = r128 { hi = x.hi, lo = x.lo + y.hi };
if (merged.lo < x.lo) {
// local-struct-field compound-assign drops the load in
// wwstage (sets =1, not +=1) — explicit form, byte-id.
merged.hi = merged.hi + 1u64;
};
if ((merged.hi & mask) == mask && (merged.lo + 1u64) == 0u64 &&
(y.lo + mnt) < mnt) {
return;
};
x.hi = merged.hi;
x.lo = merged.lo;
};
let msb: u64 = x.hi >> 63u64;
let mant: u64 = x.hi >> (msb + shift);
e2 -= 1u64 ^ msb;
if (x.lo == 0u64 && (x.hi & mask) == 0u64 && (mant & 3u64) == 1u64) {
return;
};
mant += mant & 1u64;
mant >>= 1u64;
if ((mant >> (f.mantbits + 1u64)) > 0u64) {
mant >>= 1u64;
e2 += 1u64;
};
if (e2 <= 0u64 || e2 >= (1u64 << f.expbits) - 1u64) {
return;
};
return mkfloat(mant, (e2: uint), neg, f);
};
// ref/hare/strconv/stof.ha:247. Slow-path: decimal `d` → IEEE bits.
fn floatbits(d: *decimal, f: *math.floatinfo) (u64 | overflow) = {
let e: int = 0;
let m: u64 = 0u64;
let powtab: [19]i8 = [
0i8, 3i8, 6i8, 9i8, 13i8, 16i8, 19i8, 23i8, 26i8, 29i8,
33i8, 36i8, 39i8, 43i8, 46i8, 49i8, 53i8, 56i8, 59i8,
];
if (d.nd == (0u64: size) || d.dp < -326) {
if (d.negative) {
return mkfloat(0u64, (0u32: uint), d.negative, f);
};
return 0u64;
} else if (d.dp > 310) {
return overflow{};
};
if (d.nd <= (19u64: size)) {
let dmant: u64 = 0u64;
let i: size = (0u64: size);
for (i < d.nd) {
dmant = 10u64 * dmant + (d.digits[i]: u64);
i += (1u64: size);
};
let exp10: i32 = d.dp - (d.nd: i32);
match (eisel_lemire(dmant, exp10, d.negative, f)) {
case let r: u64 => { return r; };
case void => void;
};
};
for (d.dp > 0) {
let n: int = 0;
if ((d.dp: uint) >= (len(powtab): uint)) {
n = (maxshift: int);
} else {
n = (powtab[d.dp]: int);
};
decimal_shift(d, -n);
e += n;
};
for (d.dp <= 0) {
let n: int = 0;
if (d.dp == 0) {
if (d.digits[0] >= 5u8) { break; };
if (d.digits[0] < 2u8) { n = 2; } else { n = 1; };
} else if ((-d.dp) >= (len(powtab): i32)) {
n = (maxshift: int);
} else {
n = (powtab[-d.dp]: int);
};
decimal_shift(d, n);
e -= n;
};
e -= 1;
if (e <= -f.expbias + 1) {
let nn: int = -f.expbias - e + 1;
decimal_shift(d, -nn);
e += nn;
};
if (e + f.expbias >= ((1u64 << f.expbits): int) - 1) {
return overflow{};
};
decimal_shift(d, (f.mantbits: int) + 1);
m = decimal_round(d);
if (m == (2u64 << f.mantbits)) {
m >>= 1u64;
e += 1;
if (e + f.expbias >= ((1u64 << f.expbits): int) - 1) {
return overflow{};
};
};
if ((m & (1u64 << f.mantbits)) == 0u64) {
e = -f.expbias;
};
return mkfloat(m, ((e + f.expbias): uint), d.negative, f);
};
// ref/hare/strconv/stof.ha:311. Assemble sign|exp|mantissa.
fn mkfloat(m: u64, e: uint, negative: bool, f: *math.floatinfo) u64 = {
let n: u64 = m & ((1u64 << f.mantbits) - 1u64);
n |= ((e: u64) & ((1u64 << f.expbits) - 1u64)) << f.mantbits;
if (negative) {
n |= 1u64 << (f.mantbits + f.expbits);
};
return n;
};
// ref/hare/strconv/stof.ha:320. Exact f64 powers of ten 1e0..1e22 (all
// exactly representable; see stof64exact).
let f64pow10: [23]f64 = [
1.0e0, 1.0e1, 1.0e2, 1.0e3, 1.0e4, 1.0e5, 1.0e6, 1.0e7, 1.0e8, 1.0e9,
1.0e10, 1.0e11, 1.0e12, 1.0e13, 1.0e14, 1.0e15, 1.0e16, 1.0e17, 1.0e18,
1.0e19, 1.0e20, 1.0e21, 1.0e22,
];
// ref/hare/strconv/stof.ha:326.
fn stof64exact(mant: u64, exp: i32, neg: bool) (f64 | void) = {
if (mant >> math.F64_MANTISSA_BITS != 0u64) { return; };
let n: f64 = (mant: i64): f64;
if (neg) {
n = -n;
};
if (exp == 0i32) {
return n;
};
if (-22i32 <= exp && exp <= 22i32) {
if (exp >= 0i32) {
// f64 compound-assign mis-lowers in cgen — explicit
// form (strconv.ww f64tos precedent).
n = n * f64pow10[exp];
} else {
n = n / f64pow10[-exp];
};
} else {
return;
};
return n;
};
// ref/hare/strconv/stof.ha:345. Exact f32 powers of ten 1e0..1e10.
let f32pow10: [11]f32 = [
1.0e0f32, 1.0e1f32, 1.0e2f32, 1.0e3f32, 1.0e4f32, 1.0e5f32, 1.0e6f32,
1.0e7f32, 1.0e8f32, 1.0e9f32, 1.0e10f32,
];
// ref/hare/strconv/stof.ha:349.
fn stof32exact(mant: u64, exp: i32, neg: bool) (f32 | void) = {
if (mant >> (math.F32_MANTISSA_BITS: u64) != 0u64) { return; };
let n: f32 = (mant: i32): f32;
if (neg) {
n = -n;
};
if (exp == 0i32) {
return n;
};
if (-10i32 <= exp && exp <= 10i32) {
if (exp >= 0i32) {
// f32 compound-assign mis-lowers in cgen — explicit form.
n = n * f32pow10[exp];
} else {
n = n / (f64pow10[-exp]: f32);
};
} else {
return;
};
return n;
};
// ref/hare/strconv/stof.ha:369. Adapted from Go's atofHex. The by-value
// `p` is mutated in Hare; ww copies its fields to scalar locals (struct
// param field-write miscompiles + diverges — filed).
fn hex_to_bits(p: fast_parsed_float, info: *math.floatinfo) (u64 | overflow) = {
let pmant: u64 = p.mantissa;
let pexp: i32 = p.exponent;
let pneg: bool = p.negative;
let ptrunc: bool = p.truncated;
let max_exp: int = ((1u64 << info.expbits): int) - info.expbias - 2;
let min_exp: int = -info.expbias + 1;
pexp += (info.mantbits: i32);
// Shift left until a leading 1 bit followed by mantbits + 2 rounding.
for (pmant != 0u64 && pmant >> (info.mantbits + 2u64) == 0u64) {
pmant <<= 1u64;
pexp -= 1;
};
if (ptrunc) {
pmant |= 1u64;
};
// Too many bits: shift right (sticky-or the dropped bit).
for (pmant >> (3u64 + info.mantbits) != 0u64) {
pmant = (pmant >> 1u64) | (pmant & 1u64);
pexp += 1;
};
// Denormalise if the exponent is small.
for (pmant > 1u64 && pexp < (min_exp: i32) - 2) {
pmant = (pmant >> 1u64) | (pmant & 1u64);
pexp += 1;
};
// Round to even.
let round: u64 = pmant & 3u64;
pmant >>= 2u64;
round |= pmant & 1u64;
pexp += 2;
if (round == 3u64) {
pmant += 1u64;
if (pmant == 1u64 << (1u64 + info.mantbits)) {
pmant >>= 1u64;
pexp += 1;
};
};
// Denormal or zero.
if (pmant >> info.mantbits == 0u64) {
pexp = (-info.expbias): i32;
};
if (pexp > (max_exp: i32)) {
return overflow{};
};
let bits: u64 = pmant & info.mantmask;
bits |= (((pexp + (info.expbias: i32)): u64) & info.expmask) << info.mantbits;
if (pneg) {
bits |= 1u64 << (info.mantbits + info.expbits);
};
return bits;
};
// ref/hare/strconv/stof.ha:425. "nan"/"infinity"/±"infinity",
// case-insensitive. ww math has no f32 NAN/INF consts → f32frombits of
// the IEEE-754 f32 bit patterns (qNaN 0x7FC00000, ±Inf 0x7F800000 /
// 0xFF800000).
fn special(s: str) (f32 | void) = {
if (ascii.strcasecmp(s, "nan") == 0) {
return math.f32frombits(0x7FC00000u32);
} else if (ascii.strcasecmp(s, "infinity") == 0) {
return math.f32frombits(0x7F800000u32);
} else if (ascii.strcasecmp(s, "+infinity") == 0) {
return math.f32frombits(0x7F800000u32);
} else if (ascii.strcasecmp(s, "-infinity") == 0) {
return math.f32frombits(0xFF800000u32);
};
return;
};
// ref/hare/strconv/stof.ha:445. Parse `s` as f64 (base DEC or HEX). See
// the module note: the EL fast path is HELD; the decimal fallback gives
// correct results meanwhile.
export fn stof64(s: str, b: base) (f64 | invalid | overflow) = {
let bb: base = b;
if (bb == base.DEFAULT) {
bb = base.DEC;
} else if (bb == base.HEX_LOWER) {
bb = base.HEX;
};
os.assert(bb == base.DEC || bb == base.HEX,
"strconv.stof64: base must be DEC or HEX");
if (s.len == 0) {
return 0: invalid;
};
match (special(s)) {
case let f: f32 => { return (f: f64); };
case void => void;
};
match (fast_parse(s, bb)) {
case let p: fast_parsed_float => {
if (bb == base.HEX) {
match (hex_to_bits(p, &math.f64info)) {
case let bits: u64 => { return math.f64frombits(bits); };
case let eo: overflow => { return eo; };
};
} else if (!p.truncated) {
match (stof64exact(p.mantissa, p.exponent, p.negative)) {
case let n: f64 => { return n; };
case void => void;
};
match (eisel_lemire(p.mantissa, p.exponent, p.negative,
&math.f64info)) {
case let n: u64 => { return math.f64frombits(n); };
case void => void;
};
};
let d = decimal { ... };
match (decimal_parse(&d, s)) {
case let ei: invalid => { return ei; };
case void => void;
};
match (floatbits(&d, &math.f64info)) {
case let n: u64 => { return math.f64frombits(n); };
case let eo: overflow => { return eo; };
};
};
case let ei: invalid => { return ei; };
};
return 0: invalid; // unreachable (path-cov)
};
// ref/hare/strconv/stof.ha:491. Parse `s` as f32 (base DEC or HEX).
export fn stof32(s: str, b: base) (f32 | invalid | overflow) = {
let bb: base = b;
if (bb == base.DEFAULT) {
bb = base.DEC;
} else if (bb == base.HEX_LOWER) {
bb = base.HEX;
};
os.assert(bb == base.DEC || bb == base.HEX,
"strconv.stof32: base must be DEC or HEX");
if (s.len == 0) {
return 0: invalid;
};
match (special(s)) {
case let f: f32 => { return f; };
case void => void;
};
match (fast_parse(s, bb)) {
case let p: fast_parsed_float => {
if (bb == base.HEX) {
match (hex_to_bits(p, &math.f32info)) {
case let bits: u64 => {
return math.f32frombits(bits: u32);
};
case let eo: overflow => { return eo; };
};
} else if (!p.truncated) {
match (stof32exact(p.mantissa, p.exponent, p.negative)) {
case let n: f32 => { return n; };
case void => void;
};
match (eisel_lemire(p.mantissa, p.exponent, p.negative,
&math.f32info)) {
case let n: u64 => { return math.f32frombits(n: u32); };
case void => void;
};
};
let d = decimal { ... };
match (decimal_parse(&d, s)) {
case let ei: invalid => { return ei; };
case void => void;
};
match (floatbits(&d, &math.f32info)) {
case let n: u64 => { return math.f32frombits(n: u32); };
case let eo: overflow => { return eo; };
};
};
case let ei: invalid => { return ei; };
};
return 0: invalid; // unreachable (path-cov)
};
// strconv — stof/ftos lookup tables. Mirrors ref/hare/strconv/stof_data.ha
// byte-exact. Pure-data fold (strconv #106 fold-2, was fold-3 before drew
// re-sequenced 2026-05-26): no logic, exercised transitively when fold-3's
// `leftshift_newdigits` lands (ref/hare/strconv/decimal.ha:35).
//
// ww uses module-level `let` for compile-time array data (ref/hare/strconv
// `const` has no ww keyword equivalent; lib/encoding/utf8/utf8.ww:48 sets
// the precedent with [2048]i8 dfa). Literal suffixes (`u16`, `u8`) are
// required because cstage rejects bare integer literals in `[N]u8`/`[N]u16`
// init while wwstage accepts them; the suffixed form is the only shape
// both stages agree on (candidate #130).
//
// `powers_of_ten: [596][2]u64` (ref/hare/strconv/stof_data.ha:73) is the
// Eisel-Lemire fast-path table (consumed by stof.ww's eisel_lemire); it
// lands here in fold-4 alongside its consumer, indexed `[exp10 + 307]`
// for exp10 in [-307, 288]. Faithful 2D `[596][2]u64` (the {hi,lo} pair
// IS the 128-bit truncated power-of-ten; rule-12, not flattened) — the
// 2D module-level static-init + double-index read it needs landed in
// #156 (cbeffea). See the table at the foot of this file.
package strconv;
// ref/hare/strconv/stof_data.ha:4. Powers-of-five decimal-expansion
// metadata for `leftshift_newdigits` (decimal.ha:35). The top 5 bits
// of each entry are the new-digit-count `nn`; the low 11 bits index
// `pow5_table` for the digits themselves.
let left_shift_table: [65]u16 = [
0x0000u16, 0x0800u16, 0x0801u16, 0x0803u16, 0x1006u16, 0x1009u16, 0x100Du16, 0x1812u16, 0x1817u16,
0x181Du16, 0x2024u16, 0x202Bu16, 0x2033u16, 0x203Cu16, 0x2846u16, 0x2850u16, 0x285Bu16, 0x3067u16,
0x3073u16, 0x3080u16, 0x388Eu16, 0x389Cu16, 0x38ABu16, 0x38BBu16, 0x40CCu16, 0x40DDu16, 0x40EFu16,
0x4902u16, 0x4915u16, 0x4929u16, 0x513Eu16, 0x5153u16, 0x5169u16, 0x5180u16, 0x5998u16, 0x59B0u16,
0x59C9u16, 0x61E3u16, 0x61FDu16, 0x6218u16, 0x6A34u16, 0x6A50u16, 0x6A6Du16, 0x6A8Bu16, 0x72AAu16,
0x72C9u16, 0x72E9u16, 0x7B0Au16, 0x7B2Bu16, 0x7B4Du16, 0x8370u16, 0x8393u16, 0x83B7u16, 0x83DCu16,
0x8C02u16, 0x8C28u16, 0x8C4Fu16, 0x9477u16, 0x949Fu16, 0x94C8u16, 0x9CF2u16, 0x051Cu16, 0x051Cu16,
0x051Cu16, 0x051Cu16,
];
// ref/hare/strconv/stof_data.ha:15. Decimal digits of 5^k for k=1..60,
// concatenated. Indexed via `left_shift_table` (above); each shift k
// reads its `pow5_b - pow5_a` digits starting at `pow5_a`.
let pow5_table: [0x051C]u8 = [
5u8, 2u8, 5u8, 1u8, 2u8, 5u8, 6u8, 2u8, 5u8, 3u8, 1u8, 2u8, 5u8, 1u8, 5u8, 6u8, 2u8, 5u8, 7u8, 8u8, 1u8, 2u8, 5u8, 3u8,
9u8, 0u8, 6u8, 2u8, 5u8, 1u8, 9u8, 5u8, 3u8, 1u8, 2u8, 5u8, 9u8, 7u8, 6u8, 5u8, 6u8, 2u8, 5u8, 4u8, 8u8, 8u8, 2u8, 8u8,
1u8, 2u8, 5u8, 2u8, 4u8, 4u8, 1u8, 4u8, 0u8, 6u8, 2u8, 5u8, 1u8, 2u8, 2u8, 0u8, 7u8, 0u8, 3u8, 1u8, 2u8, 5u8, 6u8, 1u8,
0u8, 3u8, 5u8, 1u8, 5u8, 6u8, 2u8, 5u8, 3u8, 0u8, 5u8, 1u8, 7u8, 5u8, 7u8, 8u8, 1u8, 2u8, 5u8, 1u8, 5u8, 2u8, 5u8, 8u8,
7u8, 8u8, 9u8, 0u8, 6u8, 2u8, 5u8, 7u8, 6u8, 2u8, 9u8, 3u8, 9u8, 4u8, 5u8, 3u8, 1u8, 2u8, 5u8, 3u8, 8u8, 1u8, 4u8, 6u8,
9u8, 7u8, 2u8, 6u8, 5u8, 6u8, 2u8, 5u8, 1u8, 9u8, 0u8, 7u8, 3u8, 4u8, 8u8, 6u8, 3u8, 2u8, 8u8, 1u8, 2u8, 5u8, 9u8, 5u8,
3u8, 6u8, 7u8, 4u8, 3u8, 1u8, 6u8, 4u8, 0u8, 6u8, 2u8, 5u8, 4u8, 7u8, 6u8, 8u8, 3u8, 7u8, 1u8, 5u8, 8u8, 2u8, 0u8, 3u8,
1u8, 2u8, 5u8, 2u8, 3u8, 8u8, 4u8, 1u8, 8u8, 5u8, 7u8, 9u8, 1u8, 0u8, 1u8, 5u8, 6u8, 2u8, 5u8, 1u8, 1u8, 9u8, 2u8, 0u8,
9u8, 2u8, 8u8, 9u8, 5u8, 5u8, 0u8, 7u8, 8u8, 1u8, 2u8, 5u8, 5u8, 9u8, 6u8, 0u8, 4u8, 6u8, 4u8, 4u8, 7u8, 7u8, 5u8, 3u8,
9u8, 0u8, 6u8, 2u8, 5u8, 2u8, 9u8, 8u8, 0u8, 2u8, 3u8, 2u8, 2u8, 3u8, 8u8, 7u8, 6u8, 9u8, 5u8, 3u8, 1u8, 2u8, 5u8, 1u8,
4u8, 9u8, 0u8, 1u8, 1u8, 6u8, 1u8, 1u8, 9u8, 3u8, 8u8, 4u8, 7u8, 6u8, 5u8, 6u8, 2u8, 5u8, 7u8, 4u8, 5u8, 0u8, 5u8, 8u8,
0u8, 5u8, 9u8, 6u8, 9u8, 2u8, 3u8, 8u8, 2u8, 8u8, 1u8, 2u8, 5u8, 3u8, 7u8, 2u8, 5u8, 2u8, 9u8, 0u8, 2u8, 9u8, 8u8, 4u8,
6u8, 1u8, 9u8, 1u8, 4u8, 0u8, 6u8, 2u8, 5u8, 1u8, 8u8, 6u8, 2u8, 6u8, 4u8, 5u8, 1u8, 4u8, 9u8, 2u8, 3u8, 0u8, 9u8, 5u8,
7u8, 0u8, 3u8, 1u8, 2u8, 5u8, 9u8, 3u8, 1u8, 3u8, 2u8, 2u8, 5u8, 7u8, 4u8, 6u8, 1u8, 5u8, 4u8, 7u8, 8u8, 5u8, 1u8, 5u8,
6u8, 2u8, 5u8, 4u8, 6u8, 5u8, 6u8, 6u8, 1u8, 2u8, 8u8, 7u8, 3u8, 0u8, 7u8, 7u8, 3u8, 9u8, 2u8, 5u8, 7u8, 8u8, 1u8, 2u8,
5u8, 2u8, 3u8, 2u8, 8u8, 3u8, 0u8, 6u8, 4u8, 3u8, 6u8, 5u8, 3u8, 8u8, 6u8, 9u8, 6u8, 2u8, 8u8, 9u8, 0u8, 6u8, 2u8, 5u8,
1u8, 1u8, 6u8, 4u8, 1u8, 5u8, 3u8, 2u8, 1u8, 8u8, 2u8, 6u8, 9u8, 3u8, 4u8, 8u8, 1u8, 4u8, 4u8, 5u8, 3u8, 1u8, 2u8, 5u8,
5u8, 8u8, 2u8, 0u8, 7u8, 6u8, 6u8, 0u8, 9u8, 1u8, 3u8, 4u8, 6u8, 7u8, 4u8, 0u8, 7u8, 2u8, 2u8, 6u8, 5u8, 6u8, 2u8, 5u8,
2u8, 9u8, 1u8, 0u8, 3u8, 8u8, 3u8, 0u8, 4u8, 5u8, 6u8, 7u8, 3u8, 3u8, 7u8, 0u8, 3u8, 6u8, 1u8, 3u8, 2u8, 8u8, 1u8, 2u8,
5u8, 1u8, 4u8, 5u8, 5u8, 1u8, 9u8, 1u8, 5u8, 2u8, 2u8, 8u8, 3u8, 6u8, 6u8, 8u8, 5u8, 1u8, 8u8, 0u8, 6u8, 6u8, 4u8, 0u8,
6u8, 2u8, 5u8, 7u8, 2u8, 7u8, 5u8, 9u8, 5u8, 7u8, 6u8, 1u8, 4u8, 1u8, 8u8, 3u8, 4u8, 2u8, 5u8, 9u8, 0u8, 3u8, 3u8, 2u8,
0u8, 3u8, 1u8, 2u8, 5u8, 3u8, 6u8, 3u8, 7u8, 9u8, 7u8, 8u8, 8u8, 0u8, 7u8, 0u8, 9u8, 1u8, 7u8, 1u8, 2u8, 9u8, 5u8, 1u8,
6u8, 6u8, 0u8, 1u8, 5u8, 6u8, 2u8, 5u8, 1u8, 8u8, 1u8, 8u8, 9u8, 8u8, 9u8, 4u8, 0u8, 3u8, 5u8, 4u8, 5u8, 8u8, 5u8, 6u8,
4u8, 7u8, 5u8, 8u8, 3u8, 0u8, 0u8, 7u8, 8u8, 1u8, 2u8, 5u8, 9u8, 0u8, 9u8, 4u8, 9u8, 4u8, 7u8, 0u8, 1u8, 7u8, 7u8, 2u8,
9u8, 2u8, 8u8, 2u8, 3u8, 7u8, 9u8, 1u8, 5u8, 0u8, 3u8, 9u8, 0u8, 6u8, 2u8, 5u8, 4u8, 5u8, 4u8, 7u8, 4u8, 7u8, 3u8, 5u8,
0u8, 8u8, 8u8, 6u8, 4u8, 6u8, 4u8, 1u8, 1u8, 8u8, 9u8, 5u8, 7u8, 5u8, 1u8, 9u8, 5u8, 3u8, 1u8, 2u8, 5u8, 2u8, 2u8, 7u8,
3u8, 7u8, 3u8, 6u8, 7u8, 5u8, 4u8, 4u8, 3u8, 2u8, 3u8, 2u8, 0u8, 5u8, 9u8, 4u8, 7u8, 8u8, 7u8, 5u8, 9u8, 7u8, 6u8, 5u8,
6u8, 2u8, 5u8, 1u8, 1u8, 3u8, 6u8, 8u8, 6u8, 8u8, 3u8, 7u8, 7u8, 2u8, 1u8, 6u8, 1u8, 6u8, 0u8, 2u8, 9u8, 7u8, 3u8, 9u8,
3u8, 7u8, 9u8, 8u8, 8u8, 2u8, 8u8, 1u8, 2u8, 5u8, 5u8, 6u8, 8u8, 4u8, 3u8, 4u8, 1u8, 8u8, 8u8, 6u8, 0u8, 8u8, 0u8, 8u8,
0u8, 1u8, 4u8, 8u8, 6u8, 9u8, 6u8, 8u8, 9u8, 9u8, 4u8, 1u8, 4u8, 0u8, 6u8, 2u8, 5u8, 2u8, 8u8, 4u8, 2u8, 1u8, 7u8, 0u8,
9u8, 4u8, 3u8, 0u8, 4u8, 0u8, 4u8, 0u8, 0u8, 7u8, 4u8, 3u8, 4u8, 8u8, 4u8, 4u8, 9u8, 7u8, 0u8, 7u8, 0u8, 3u8, 1u8, 2u8,
5u8, 1u8, 4u8, 2u8, 1u8, 0u8, 8u8, 5u8, 4u8, 7u8, 1u8, 5u8, 2u8, 0u8, 2u8, 0u8, 0u8, 3u8, 7u8, 1u8, 7u8, 4u8, 2u8, 2u8,
4u8, 8u8, 5u8, 3u8, 5u8, 1u8, 5u8, 6u8, 2u8, 5u8, 7u8, 1u8, 0u8, 5u8, 4u8, 2u8, 7u8, 3u8, 5u8, 7u8, 6u8, 0u8, 1u8, 0u8,
0u8, 1u8, 8u8, 5u8, 8u8, 7u8, 1u8, 1u8, 2u8, 4u8, 2u8, 6u8, 7u8, 5u8, 7u8, 8u8, 1u8, 2u8, 5u8, 3u8, 5u8, 5u8, 2u8, 7u8,
1u8, 3u8, 6u8, 7u8, 8u8, 8u8, 0u8, 0u8, 5u8, 0u8, 0u8, 9u8, 2u8, 9u8, 3u8, 5u8, 5u8, 6u8, 2u8, 1u8, 3u8, 3u8, 7u8, 8u8,
9u8, 0u8, 6u8, 2u8, 5u8, 1u8, 7u8, 7u8, 6u8, 3u8, 5u8, 6u8, 8u8, 3u8, 9u8, 4u8, 0u8, 0u8, 2u8, 5u8, 0u8, 4u8, 6u8, 4u8,
6u8, 7u8, 7u8, 8u8, 1u8, 0u8, 6u8, 6u8, 8u8, 9u8, 4u8, 5u8, 3u8, 1u8, 2u8, 5u8, 8u8, 8u8, 8u8, 1u8, 7u8, 8u8, 4u8, 1u8,
9u8, 7u8, 0u8, 0u8, 1u8, 2u8, 5u8, 2u8, 3u8, 2u8, 3u8, 3u8, 8u8, 9u8, 0u8, 5u8, 3u8, 3u8, 4u8, 4u8, 7u8, 2u8, 6u8, 5u8,
6u8, 2u8, 5u8, 4u8, 4u8, 4u8, 0u8, 8u8, 9u8, 2u8, 0u8, 9u8, 8u8, 5u8, 0u8, 0u8, 6u8, 2u8, 6u8, 1u8, 6u8, 1u8, 6u8, 9u8,
4u8, 5u8, 2u8, 6u8, 6u8, 7u8, 2u8, 3u8, 6u8, 3u8, 2u8, 8u8, 1u8, 2u8, 5u8, 2u8, 2u8, 2u8, 0u8, 4u8, 4u8, 6u8, 0u8, 4u8,
9u8, 2u8, 5u8, 0u8, 3u8, 1u8, 3u8, 0u8, 8u8, 0u8, 8u8, 4u8, 7u8, 2u8, 6u8, 3u8, 3u8, 3u8, 6u8, 1u8, 8u8, 1u8, 6u8, 4u8,
0u8, 6u8, 2u8, 5u8, 1u8, 1u8, 1u8, 0u8, 2u8, 2u8, 3u8, 0u8, 2u8, 4u8, 6u8, 2u8, 5u8, 1u8, 5u8, 6u8, 5u8, 4u8, 0u8, 4u8,
2u8, 3u8, 6u8, 3u8, 1u8, 6u8, 6u8, 8u8, 0u8, 9u8, 0u8, 8u8, 2u8, 0u8, 3u8, 1u8, 2u8, 5u8, 5u8, 5u8, 5u8, 1u8, 1u8, 1u8,
5u8, 1u8, 2u8, 3u8, 1u8, 2u8, 5u8, 7u8, 8u8, 2u8, 7u8, 0u8, 2u8, 1u8, 1u8, 8u8, 1u8, 5u8, 8u8, 3u8, 4u8, 0u8, 4u8, 5u8,
4u8, 1u8, 0u8, 1u8, 5u8, 6u8, 2u8, 5u8, 2u8, 7u8, 7u8, 5u8, 5u8, 5u8, 7u8, 5u8, 6u8, 1u8, 5u8, 6u8, 2u8, 8u8, 9u8, 1u8,
3u8, 5u8, 1u8, 0u8, 5u8, 9u8, 0u8, 7u8, 9u8, 1u8, 7u8, 0u8, 2u8, 2u8, 7u8, 0u8, 5u8, 0u8, 7u8, 8u8, 1u8, 2u8, 5u8, 1u8,
3u8, 8u8, 7u8, 7u8, 7u8, 8u8, 7u8, 8u8, 0u8, 7u8, 8u8, 1u8, 4u8, 4u8, 5u8, 6u8, 7u8, 5u8, 5u8, 2u8, 9u8, 5u8, 3u8, 9u8,
5u8, 8u8, 5u8, 1u8, 1u8, 3u8, 5u8, 2u8, 5u8, 3u8, 9u8, 0u8, 6u8, 2u8, 5u8, 6u8, 9u8, 3u8, 8u8, 8u8, 9u8, 3u8, 9u8, 0u8,
3u8, 9u8, 0u8, 7u8, 2u8, 2u8, 8u8, 3u8, 7u8, 7u8, 6u8, 4u8, 7u8, 6u8, 9u8, 7u8, 9u8, 2u8, 5u8, 5u8, 6u8, 7u8, 6u8, 2u8,
6u8, 9u8, 5u8, 3u8, 1u8, 2u8, 5u8, 3u8, 4u8, 6u8, 9u8, 4u8, 4u8, 6u8, 9u8, 5u8, 1u8, 9u8, 5u8, 3u8, 6u8, 1u8, 4u8, 1u8,
8u8, 8u8, 8u8, 2u8, 3u8, 8u8, 4u8, 8u8, 9u8, 6u8, 2u8, 7u8, 8u8, 3u8, 8u8, 1u8, 3u8, 4u8, 7u8, 6u8, 5u8, 6u8, 2u8, 5u8,
1u8, 7u8, 3u8, 4u8, 7u8, 2u8, 3u8, 4u8, 7u8, 5u8, 9u8, 7u8, 6u8, 8u8, 0u8, 7u8, 0u8, 9u8, 4u8, 4u8, 1u8, 1u8, 9u8, 2u8,
4u8, 4u8, 8u8, 1u8, 3u8, 9u8, 1u8, 9u8, 0u8, 6u8, 7u8, 3u8, 8u8, 2u8, 8u8, 1u8, 2u8, 5u8, 8u8, 6u8, 7u8, 3u8, 6u8, 1u8,
7u8, 3u8, 7u8, 9u8, 8u8, 8u8, 4u8, 0u8, 3u8, 5u8, 4u8, 7u8, 2u8, 0u8, 5u8, 9u8, 6u8, 2u8, 2u8, 4u8, 0u8, 6u8, 9u8, 5u8,
9u8, 5u8, 3u8, 3u8, 6u8, 9u8, 1u8, 4u8, 0u8, 6u8, 2u8, 5u8,
];
// ref/hare/strconv/stof_data.ha:73. Eisel-Lemire 128-bit power-of-ten
// table (see header note). 596 rows, {hi, lo} u64 pair per row.
let powers_of_ten: [596][2]u64 = [
[0xA5D3B6D479F8E056u64, 0x8FD0C16206306BABu64],
[0x8F48A4899877186Cu64, 0xB3C4F1BA87BC8696u64],
[0x331ACDABFE94DE87u64, 0xE0B62E2929ABA83Cu64],
[0x9FF0C08B7F1D0B14u64, 0x8C71DCD9BA0B4925u64],
[0x07ECF0AE5EE44DD9u64, 0xAF8E5410288E1B6Fu64],
[0xC9E82CD9F69D6150u64, 0xDB71E91432B1A24Au64],
[0xBE311C083A225CD2u64, 0x892731AC9FAF056Eu64],
[0x6DBD630A48AAF406u64, 0xAB70FE17C79AC6CAu64],
[0x092CBBCCDAD5B108u64, 0xD64D3D9DB981787Du64],
[0x25BBF56008C58EA5u64, 0x85F0468293F0EB4Eu64],
[0xAF2AF2B80AF6F24Eu64, 0xA76C582338ED2621u64],
[0x1AF5AF660DB4AEE1u64, 0xD1476E2C07286FAAu64],
[0x50D98D9FC890ED4Du64, 0x82CCA4DB847945CAu64],
[0xE50FF107BAB528A0u64, 0xA37FCE126597973Cu64],
[0x1E53ED49A96272C8u64, 0xCC5FC196FEFD7D0Cu64],
[0x25E8E89C13BB0F7Au64, 0xFF77B1FCBEBCDC4Fu64],
[0x77B191618C54E9ACu64, 0x9FAACF3DF73609B1u64],
[0xD59DF5B9EF6A2417u64, 0xC795830D75038C1Du64],
[0x4B0573286B44AD1Du64, 0xF97AE3D0D2446F25u64],
[0x4EE367F9430AEC32u64, 0x9BECCE62836AC577u64],
[0x229C41F793CDA73Fu64, 0xC2E801FB244576D5u64],
[0x6B43527578C1110Fu64, 0xF3A20279ED56D48Au64],
[0x830A13896B78AAA9u64, 0x9845418C345644D6u64],
[0x23CC986BC656D553u64, 0xBE5691EF416BD60Cu64],
[0x2CBFBE86B7EC8AA8u64, 0xEDEC366B11C6CB8Fu64],
[0x7BF7D71432F3D6A9u64, 0x94B3A202EB1C3F39u64],
[0xDAF5CCD93FB0CC53u64, 0xB9E08A83A5E34F07u64],
[0xD1B3400F8F9CFF68u64, 0xE858AD248F5C22C9u64],
[0x23100809B9C21FA1u64, 0x91376C36D99995BEu64],
[0xABD40A0C2832A78Au64, 0xB58547448FFFFB2Du64],
[0x16C90C8F323F516Cu64, 0xE2E69915B3FFF9F9u64],
[0xAE3DA7D97F6792E3u64, 0x8DD01FAD907FFC3Bu64],
[0x99CD11CFDF41779Cu64, 0xB1442798F49FFB4Au64],
[0x40405643D711D583u64, 0xDD95317F31C7FA1Du64],
[0x482835EA666B2572u64, 0x8A7D3EEF7F1CFC52u64],
[0xDA3243650005EECFu64, 0xAD1C8EAB5EE43B66u64],
[0x90BED43E40076A82u64, 0xD863B256369D4A40u64],
[0x5A7744A6E804A291u64, 0x873E4F75E2224E68u64],
[0x711515D0A205CB36u64, 0xA90DE3535AAAE202u64],
[0x0D5A5B44CA873E03u64, 0xD3515C2831559A83u64],
[0xE858790AFE9486C2u64, 0x8412D9991ED58091u64],
[0x626E974DBE39A872u64, 0xA5178FFF668AE0B6u64],
[0xFB0A3D212DC8128Fu64, 0xCE5D73FF402D98E3u64],
[0x7CE66634BC9D0B99u64, 0x80FA687F881C7F8Eu64],
[0x1C1FFFC1EBC44E80u64, 0xA139029F6A239F72u64],
[0xA327FFB266B56220u64, 0xC987434744AC874Eu64],
[0x4BF1FF9F0062BAA8u64, 0xFBE9141915D7A922u64],
[0x6F773FC3603DB4A9u64, 0x9D71AC8FADA6C9B5u64],
[0xCB550FB4384D21D3u64, 0xC4CE17B399107C22u64],
[0x7E2A53A146606A48u64, 0xF6019DA07F549B2Bu64],
[0x2EDA7444CBFC426Du64, 0x99C102844F94E0FBu64],
[0xFA911155FEFB5308u64, 0xC0314325637A1939u64],
[0x793555AB7EBA27CAu64, 0xF03D93EEBC589F88u64],
[0x4BC1558B2F3458DEu64, 0x96267C7535B763B5u64],
[0x9EB1AAEDFB016F16u64, 0xBBB01B9283253CA2u64],
[0x465E15A979C1CADCu64, 0xEA9C227723EE8BCBu64],
[0x0BFACD89EC191EC9u64, 0x92A1958A7675175Fu64],
[0xCEF980EC671F667Bu64, 0xB749FAED14125D36u64],
[0x82B7E12780E7401Au64, 0xE51C79A85916F484u64],
[0xD1B2ECB8B0908810u64, 0x8F31CC0937AE58D2u64],
[0x861FA7E6DCB4AA15u64, 0xB2FE3F0B8599EF07u64],
[0x67A791E093E1D49Au64, 0xDFBDCECE67006AC9u64],
[0xE0C8BB2C5C6D24E0u64, 0x8BD6A141006042BDu64],
[0x58FAE9F773886E18u64, 0xAECC49914078536Du64],
[0xAF39A475506A899Eu64, 0xDA7F5BF590966848u64],
[0x6D8406C952429603u64, 0x888F99797A5E012Du64],
[0xC8E5087BA6D33B83u64, 0xAAB37FD7D8F58178u64],
[0xFB1E4A9A90880A64u64, 0xD5605FCDCF32E1D6u64],
[0x5CF2EEA09A55067Fu64, 0x855C3BE0A17FCD26u64],
[0xF42FAA48C0EA481Eu64, 0xA6B34AD8C9DFC06Fu64],
[0xF13B94DAF124DA26u64, 0xD0601D8EFC57B08Bu64],
[0x76C53D08D6B70858u64, 0x823C12795DB6CE57u64],
[0x54768C4B0C64CA6Eu64, 0xA2CB1717B52481EDu64],
[0xA9942F5DCF7DFD09u64, 0xCB7DDCDDA26DA268u64],
[0xD3F93B35435D7C4Cu64, 0xFE5D54150B090B02u64],
[0xC47BC5014A1A6DAFu64, 0x9EFA548D26E5A6E1u64],
[0x359AB6419CA1091Bu64, 0xC6B8E9B0709F109Au64],
[0xC30163D203C94B62u64, 0xF867241C8CC6D4C0u64],
[0x79E0DE63425DCF1Du64, 0x9B407691D7FC44F8u64],
[0x985915FC12F542E4u64, 0xC21094364DFB5636u64],
[0x3E6F5B7B17B2939Du64, 0xF294B943E17A2BC4u64],
[0xA705992CEECF9C42u64, 0x979CF3CA6CEC5B5Au64],
[0x50C6FF782A838353u64, 0xBD8430BD08277231u64],
[0xA4F8BF5635246428u64, 0xECE53CEC4A314EBDu64],
[0x871B7795E136BE99u64, 0x940F4613AE5ED136u64],
[0x28E2557B59846E3Fu64, 0xB913179899F68584u64],
[0x331AEADA2FE589CFu64, 0xE757DD7EC07426E5u64],
[0x3FF0D2C85DEF7621u64, 0x9096EA6F3848984Fu64],
[0x0FED077A756B53A9u64, 0xB4BCA50B065ABE63u64],
[0xD3E8495912C62894u64, 0xE1EBCE4DC7F16DFBu64],
[0x64712DD7ABBBD95Cu64, 0x8D3360F09CF6E4BDu64],
[0xBD8D794D96AACFB3u64, 0xB080392CC4349DECu64],
[0xECF0D7A0FC5583A0u64, 0xDCA04777F541C567u64],
[0xF41686C49DB57244u64, 0x89E42CAAF9491B60u64],
[0x311C2875C522CED5u64, 0xAC5D37D5B79B6239u64],
[0x7D633293366B828Bu64, 0xD77485CB25823AC7u64],
[0xAE5DFF9C02033197u64, 0x86A8D39EF77164BCu64],
[0xD9F57F830283FDFCu64, 0xA8530886B54DBDEBu64],
[0xD072DF63C324FD7Bu64, 0xD267CAA862A12D66u64],
[0x4247CB9E59F71E6Du64, 0x8380DEA93DA4BC60u64],
[0x52D9BE85F074E608u64, 0xA46116538D0DEB78u64],
[0x67902E276C921F8Bu64, 0xCD795BE870516656u64],
[0x00BA1CD8A3DB53B6u64, 0x806BD9714632DFF6u64],
[0x80E8A40ECCD228A4u64, 0xA086CFCD97BF97F3u64],
[0x6122CD128006B2CDu64, 0xC8A883C0FDAF7DF0u64],
[0x796B805720085F81u64, 0xFAD2A4B13D1B5D6Cu64],
[0xCBE3303674053BB0u64, 0x9CC3A6EEC6311A63u64],
[0xBEDBFC4411068A9Cu64, 0xC3F490AA77BD60FCu64],
[0xEE92FB5515482D44u64, 0xF4F1B4D515ACB93Bu64],
[0x751BDD152D4D1C4Au64, 0x991711052D8BF3C5u64],
[0xD262D45A78A0635Du64, 0xBF5CD54678EEF0B6u64],
[0x86FB897116C87C34u64, 0xEF340A98172AACE4u64],
[0xD45D35E6AE3D4DA0u64, 0x9580869F0E7AAC0Eu64],
[0x8974836059CCA109u64, 0xBAE0A846D2195712u64],
[0x2BD1A438703FC94Bu64, 0xE998D258869FACD7u64],
[0x7B6306A34627DDCFu64, 0x91FF83775423CC06u64],
[0x1A3BC84C17B1D542u64, 0xB67F6455292CBF08u64],
[0x20CABA5F1D9E4A93u64, 0xE41F3D6A7377EECAu64],
[0x547EB47B7282EE9Cu64, 0x8E938662882AF53Eu64],
[0xE99E619A4F23AA43u64, 0xB23867FB2A35B28Du64],
[0x6405FA00E2EC94D4u64, 0xDEC681F9F4C31F31u64],
[0xDE83BC408DD3DD04u64, 0x8B3C113C38F9F37Eu64],
[0x9624AB50B148D445u64, 0xAE0B158B4738705Eu64],
[0x3BADD624DD9B0957u64, 0xD98DDAEE19068C76u64],
[0xE54CA5D70A80E5D6u64, 0x87F8A8D4CFA417C9u64],
[0x5E9FCF4CCD211F4Cu64, 0xA9F6D30A038D1DBCu64],
[0x7647C3200069671Fu64, 0xD47487CC8470652Bu64],
[0x29ECD9F40041E073u64, 0x84C8D4DFD2C63F3Bu64],
[0xF468107100525890u64, 0xA5FB0A17C777CF09u64],
[0x7182148D4066EEB4u64, 0xCF79CC9DB955C2CCu64],
[0xC6F14CD848405530u64, 0x81AC1FE293D599BFu64],
[0xB8ADA00E5A506A7Cu64, 0xA21727DB38CB002Fu64],
[0xA6D90811F0E4851Cu64, 0xCA9CF1D206FDC03Bu64],
[0x908F4A166D1DA663u64, 0xFD442E4688BD304Au64],
[0x9A598E4E043287FEu64, 0x9E4A9CEC15763E2Eu64],
[0x40EFF1E1853F29FDu64, 0xC5DD44271AD3CDBAu64],
[0xD12BEE59E68EF47Cu64, 0xF7549530E188C128u64],
[0x82BB74F8301958CEu64, 0x9A94DD3E8CF578B9u64],
[0xE36A52363C1FAF01u64, 0xC13A148E3032D6E7u64],
[0xDC44E6C3CB279AC1u64, 0xF18899B1BC3F8CA1u64],
[0x29AB103A5EF8C0B9u64, 0x96F5600F15A7B7E5u64],
[0x7415D448F6B6F0E7u64, 0xBCB2B812DB11A5DEu64],
[0x111B495B3464AD21u64, 0xEBDF661791D60F56u64],
[0xCAB10DD900BEEC34u64, 0x936B9FCEBB25C995u64],
[0x3D5D514F40EEA742u64, 0xB84687C269EF3BFBu64],
[0x0CB4A5A3112A5112u64, 0xE65829B3046B0AFAu64],
[0x47F0E785EABA72ABu64, 0x8FF71A0FE2C2E6DCu64],
[0x59ED216765690F56u64, 0xB3F4E093DB73A093u64],
[0x306869C13EC3532Cu64, 0xE0F218B8D25088B8u64],
[0x1E414218C73A13FBu64, 0x8C974F7383725573u64],
[0xE5D1929EF90898FAu64, 0xAFBD2350644EEACFu64],
[0xDF45F746B74ABF39u64, 0xDBAC6C247D62A583u64],
[0x6B8BBA8C328EB783u64, 0x894BC396CE5DA772u64],
[0x066EA92F3F326564u64, 0xAB9EB47C81F5114Fu64],
[0xC80A537B0EFEFEBDu64, 0xD686619BA27255A2u64],
[0xBD06742CE95F5F36u64, 0x8613FD0145877585u64],
[0x2C48113823B73704u64, 0xA798FC4196E952E7u64],
[0xF75A15862CA504C5u64, 0xD17F3B51FCA3A7A0u64],
[0x9A984D73DBE722FBu64, 0x82EF85133DE648C4u64],
[0xC13E60D0D2E0EBBAu64, 0xA3AB66580D5FDAF5u64],
[0x318DF905079926A8u64, 0xCC963FEE10B7D1B3u64],
[0xFDF17746497F7052u64, 0xFFBBCFE994E5C61Fu64],
[0xFEB6EA8BEDEFA633u64, 0x9FD561F1FD0F9BD3u64],
[0xFE64A52EE96B8FC0u64, 0xC7CABA6E7C5382C8u64],
[0x3DFDCE7AA3C673B0u64, 0xF9BD690A1B68637Bu64],
[0x06BEA10CA65C084Eu64, 0x9C1661A651213E2Du64],
[0x486E494FCFF30A62u64, 0xC31BFA0FE5698DB8u64],
[0x5A89DBA3C3EFCCFAu64, 0xF3E2F893DEC3F126u64],
[0xF89629465A75E01Cu64, 0x986DDB5C6B3A76B7u64],
[0xF6BBB397F1135823u64, 0xBE89523386091465u64],
[0x746AA07DED582E2Cu64, 0xEE2BA6C0678B597Fu64],
[0xA8C2A44EB4571CDCu64, 0x94DB483840B717EFu64],
[0x92F34D62616CE413u64, 0xBA121A4650E4DDEBu64],
[0x77B020BAF9C81D17u64, 0xE896A0D7E51E1566u64],
[0x0ACE1474DC1D122Eu64, 0x915E2486EF32CD60u64],
[0x0D819992132456BAu64, 0xB5B5ADA8AAFF80B8u64],
[0x10E1FFF697ED6C69u64, 0xE3231912D5BF60E6u64],
[0xCA8D3FFA1EF463C1u64, 0x8DF5EFABC5979C8Fu64],
[0xBD308FF8A6B17CB2u64, 0xB1736B96B6FD83B3u64],
[0xAC7CB3F6D05DDBDEu64, 0xDDD0467C64BCE4A0u64],
[0x6BCDF07A423AA96Bu64, 0x8AA22C0DBEF60EE4u64],
[0x86C16C98D2C953C6u64, 0xAD4AB7112EB3929Du64],
[0xE871C7BF077BA8B7u64, 0xD89D64D57A607744u64],
[0x11471CD764AD4972u64, 0x87625F056C7C4A8Bu64],
[0xD598E40D3DD89BCFu64, 0xA93AF6C6C79B5D2Du64],
[0x4AFF1D108D4EC2C3u64, 0xD389B47879823479u64],
[0xCEDF722A585139BAu64, 0x843610CB4BF160CBu64],
[0xC2974EB4EE658828u64, 0xA54394FE1EEDB8FEu64],
[0x733D226229FEEA32u64, 0xCE947A3DA6A9273Eu64],
[0x0806357D5A3F525Fu64, 0x811CCC668829B887u64],
[0xCA07C2DCB0CF26F7u64, 0xA163FF802A3426A8u64],
[0xFC89B393DD02F0B5u64, 0xC9BCFF6034C13052u64],
[0xBBAC2078D443ACE2u64, 0xFC2C3F3841F17C67u64],
[0xD54B944B84AA4C0Du64, 0x9D9BA7832936EDC0u64],
[0x0A9E795E65D4DF11u64, 0xC5029163F384A931u64],
[0x4D4617B5FF4A16D5u64, 0xF64335BCF065D37Du64],
[0x504BCED1BF8E4E45u64, 0x99EA0196163FA42Eu64],
[0xE45EC2862F71E1D6u64, 0xC06481FB9BCF8D39u64],
[0x5D767327BB4E5A4Cu64, 0xF07DA27A82C37088u64],
[0x3A6A07F8D510F86Fu64, 0x964E858C91BA2655u64],
[0x890489F70A55368Bu64, 0xBBE226EFB628AFEAu64],
[0x2B45AC74CCEA842Eu64, 0xEADAB0ABA3B2DBE5u64],
[0x3B0B8BC90012929Du64, 0x92C8AE6B464FC96Fu64],
[0x09CE6EBB40173744u64, 0xB77ADA0617E3BBCBu64],
[0xCC420A6A101D0515u64, 0xE55990879DDCAABDu64],
[0x9FA946824A12232Du64, 0x8F57FA54C2A9EAB6u64],
[0x47939822DC96ABF9u64, 0xB32DF8E9F3546564u64],
[0x59787E2B93BC56F7u64, 0xDFF9772470297EBDu64],
[0x57EB4EDB3C55B65Au64, 0x8BFBEA76C619EF36u64],
[0xEDE622920B6B23F1u64, 0xAEFAE51477A06B03u64],
[0xE95FAB368E45ECEDu64, 0xDAB99E59958885C4u64],
[0x11DBCB0218EBB414u64, 0x88B402F7FD75539Bu64],
[0xD652BDC29F26A119u64, 0xAAE103B5FCD2A881u64],
[0x4BE76D3346F0495Fu64, 0xD59944A37C0752A2u64],
[0x6F70A4400C562DDBu64, 0x857FCAE62D8493A5u64],
[0xCB4CCD500F6BB952u64, 0xA6DFBD9FB8E5B88Eu64],
[0x7E2000A41346A7A7u64, 0xD097AD07A71F26B2u64],
[0x8ED400668C0C28C8u64, 0x825ECC24C873782Fu64],
[0x728900802F0F32FAu64, 0xA2F67F2DFA90563Bu64],
[0x4F2B40A03AD2FFB9u64, 0xCBB41EF979346BCAu64],
[0xE2F610C84987BFA8u64, 0xFEA126B7D78186BCu64],
[0x0DD9CA7D2DF4D7C9u64, 0x9F24B832E6B0F436u64],
[0x91503D1C79720DBBu64, 0xC6EDE63FA05D3143u64],
[0x75A44C6397CE912Au64, 0xF8A95FCF88747D94u64],
[0xC986AFBE3EE11ABAu64, 0x9B69DBE1B548CE7Cu64],
[0xFBE85BADCE996168u64, 0xC24452DA229B021Bu64],
[0xFAE27299423FB9C3u64, 0xF2D56790AB41C2A2u64],
[0xDCCD879FC967D41Au64, 0x97C560BA6B0919A5u64],
[0x5400E987BBC1C920u64, 0xBDB6B8E905CB600Fu64],
[0x290123E9AAB23B68u64, 0xED246723473E3813u64],
[0xF9A0B6720AAF6521u64, 0x9436C0760C86E30Bu64],
[0xF808E40E8D5B3E69u64, 0xB94470938FA89BCEu64],
[0xB60B1D1230B20E04u64, 0xE7958CB87392C2C2u64],
[0xB1C6F22B5E6F48C2u64, 0x90BD77F3483BB9B9u64],
[0x1E38AEB6360B1AF3u64, 0xB4ECD5F01A4AA828u64],
[0x25C6DA63C38DE1B0u64, 0xE2280B6C20DD5232u64],
[0x579C487E5A38AD0Eu64, 0x8D590723948A535Fu64],
[0x2D835A9DF0C6D851u64, 0xB0AF48EC79ACE837u64],
[0xF8E431456CF88E65u64, 0xDCDB1B2798182244u64],
[0x1B8E9ECB641B58FFu64, 0x8A08F0F8BF0F156Bu64],
[0xE272467E3D222F3Fu64, 0xAC8B2D36EED2DAC5u64],
[0x5B0ED81DCC6ABB0Fu64, 0xD7ADF884AA879177u64],
[0x98E947129FC2B4E9u64, 0x86CCBB52EA94BAEAu64],
[0x3F2398D747B36224u64, 0xA87FEA27A539E9A5u64],
[0x8EEC7F0D19A03AADu64, 0xD29FE4B18E88640Eu64],
[0x1953CF68300424ACu64, 0x83A3EEEEF9153E89u64],
[0x5FA8C3423C052DD7u64, 0xA48CEAAAB75A8E2Bu64],
[0x3792F412CB06794Du64, 0xCDB02555653131B6u64],
[0xE2BBD88BBEE40BD0u64, 0x808E17555F3EBF11u64],
[0x5B6ACEAEAE9D0EC4u64, 0xA0B19D2AB70E6ED6u64],
[0xF245825A5A445275u64, 0xC8DE047564D20A8Bu64],
[0xEED6E2F0F0D56712u64, 0xFB158592BE068D2Eu64],
[0x55464DD69685606Bu64, 0x9CED737BB6C4183Du64],
[0xAA97E14C3C26B886u64, 0xC428D05AA4751E4Cu64],
[0xD53DD99F4B3066A8u64, 0xF53304714D9265DFu64],
[0xE546A8038EFE4029u64, 0x993FE2C6D07B7FABu64],
[0xDE98520472BDD033u64, 0xBF8FDB78849A5F96u64],
[0x963E66858F6D4440u64, 0xEF73D256A5C0F77Cu64],
[0xDDE7001379A44AA8u64, 0x95A8637627989AADu64],
[0x5560C018580D5D52u64, 0xBB127C53B17EC159u64],
[0xAAB8F01E6E10B4A6u64, 0xE9D71B689DDE71AFu64],
[0xCAB3961304CA70E8u64, 0x9226712162AB070Du64],
[0x3D607B97C5FD0D22u64, 0xB6B00D69BB55C8D1u64],
[0x8CB89A7DB77C506Au64, 0xE45C10C42A2B3B05u64],
[0x77F3608E92ADB242u64, 0x8EB98A7A9A5B04E3u64],
[0x55F038B237591ED3u64, 0xB267ED1940F1C61Cu64],
[0x6B6C46DEC52F6688u64, 0xDF01E85F912E37A3u64],
[0x2323AC4B3B3DA015u64, 0x8B61313BBABCE2C6u64],
[0xABEC975E0A0D081Au64, 0xAE397D8AA96C1B77u64],
[0x96E7BD358C904A21u64, 0xD9C7DCED53C72255u64],
[0x7E50D64177DA2E54u64, 0x881CEA14545C7575u64],
[0xDDE50BD1D5D0B9E9u64, 0xAA242499697392D2u64],
[0x955E4EC64B44E864u64, 0xD4AD2DBFC3D07787u64],
[0xBD5AF13BEF0B113Eu64, 0x84EC3C97DA624AB4u64],
[0xECB1AD8AEACDD58Eu64, 0xA6274BBDD0FADD61u64],
[0x67DE18EDA5814AF2u64, 0xCFB11EAD453994BAu64],
[0x80EACF948770CED7u64, 0x81CEB32C4B43FCF4u64],
[0xA1258379A94D028Du64, 0xA2425FF75E14FC31u64],
[0x096EE45813A04330u64, 0xCAD2F7F5359A3B3Eu64],
[0x8BCA9D6E188853FCu64, 0xFD87B5F28300CA0Du64],
[0x775EA264CF55347Du64, 0x9E74D1B791E07E48u64],
[0x95364AFE032A819Du64, 0xC612062576589DDAu64],
[0x3A83DDBD83F52204u64, 0xF79687AED3EEC551u64],
[0xC4926A9672793542u64, 0x9ABE14CD44753B52u64],
[0x75B7053C0F178293u64, 0xC16D9A0095928A27u64],
[0x5324C68B12DD6338u64, 0xF1C90080BAF72CB1u64],
[0xD3F6FC16EBCA5E03u64, 0x971DA05074DA7BEEu64],
[0x88F4BB1CA6BCF584u64, 0xBCE5086492111AEAu64],
[0x2B31E9E3D06C32E5u64, 0xEC1E4A7DB69561A5u64],
[0x3AFF322E62439FCFu64, 0x9392EE8E921D5D07u64],
[0x09BEFEB9FAD487C2u64, 0xB877AA3236A4B449u64],
[0x4C2EBE687989A9B3u64, 0xE69594BEC44DE15Bu64],
[0x0F9D37014BF60A10u64, 0x901D7CF73AB0ACD9u64],
[0x538484C19EF38C94u64, 0xB424DC35095CD80Fu64],
[0x2865A5F206B06FB9u64, 0xE12E13424BB40E13u64],
[0xF93F87B7442E45D3u64, 0x8CBCCC096F5088CBu64],
[0xF78F69A51539D748u64, 0xAFEBFF0BCB24AAFEu64],
[0xB573440E5A884D1Bu64, 0xDBE6FECEBDEDD5BEu64],
[0x31680A88F8953030u64, 0x89705F4136B4A597u64],
[0xFDC20D2B36BA7C3Du64, 0xABCC77118461CEFCu64],
[0x3D32907604691B4Cu64, 0xD6BF94D5E57A42BCu64],
[0xA63F9A49C2C1B10Fu64, 0x8637BD05AF6C69B5u64],
[0x0FCF80DC33721D53u64, 0xA7C5AC471B478423u64],
[0xD3C36113404EA4A8u64, 0xD1B71758E219652Bu64],
[0x645A1CAC083126E9u64, 0x83126E978D4FDF3Bu64],
[0x3D70A3D70A3D70A3u64, 0xA3D70A3D70A3D70Au64],
[0xCCCCCCCCCCCCCCCCu64, 0xCCCCCCCCCCCCCCCCu64],
[0x0000000000000000u64, 0x8000000000000000u64],
[0x0000000000000000u64, 0xA000000000000000u64],
[0x0000000000000000u64, 0xC800000000000000u64],
[0x0000000000000000u64, 0xFA00000000000000u64],
[0x0000000000000000u64, 0x9C40000000000000u64],
[0x0000000000000000u64, 0xC350000000000000u64],
[0x0000000000000000u64, 0xF424000000000000u64],
[0x0000000000000000u64, 0x9896800000000000u64],
[0x0000000000000000u64, 0xBEBC200000000000u64],
[0x0000000000000000u64, 0xEE6B280000000000u64],
[0x0000000000000000u64, 0x9502F90000000000u64],
[0x0000000000000000u64, 0xBA43B74000000000u64],
[0x0000000000000000u64, 0xE8D4A51000000000u64],
[0x0000000000000000u64, 0x9184E72A00000000u64],
[0x0000000000000000u64, 0xB5E620F480000000u64],
[0x0000000000000000u64, 0xE35FA931A0000000u64],
[0x0000000000000000u64, 0x8E1BC9BF04000000u64],
[0x0000000000000000u64, 0xB1A2BC2EC5000000u64],
[0x0000000000000000u64, 0xDE0B6B3A76400000u64],
[0x0000000000000000u64, 0x8AC7230489E80000u64],
[0x0000000000000000u64, 0xAD78EBC5AC620000u64],
[0x0000000000000000u64, 0xD8D726B7177A8000u64],
[0x0000000000000000u64, 0x878678326EAC9000u64],
[0x0000000000000000u64, 0xA968163F0A57B400u64],
[0x0000000000000000u64, 0xD3C21BCECCEDA100u64],
[0x0000000000000000u64, 0x84595161401484A0u64],
[0x0000000000000000u64, 0xA56FA5B99019A5C8u64],
[0x0000000000000000u64, 0xCECB8F27F4200F3Au64],
[0x4000000000000000u64, 0x813F3978F8940984u64],
[0x5000000000000000u64, 0xA18F07D736B90BE5u64],
[0xA400000000000000u64, 0xC9F2C9CD04674EDEu64],
[0x4D00000000000000u64, 0xFC6F7C4045812296u64],
[0xF020000000000000u64, 0x9DC5ADA82B70B59Du64],
[0x6C28000000000000u64, 0xC5371912364CE305u64],
[0xC732000000000000u64, 0xF684DF56C3E01BC6u64],
[0x3C7F400000000000u64, 0x9A130B963A6C115Cu64],
[0x4B9F100000000000u64, 0xC097CE7BC90715B3u64],
[0x1E86D40000000000u64, 0xF0BDC21ABB48DB20u64],
[0x1314448000000000u64, 0x96769950B50D88F4u64],
[0x17D955A000000000u64, 0xBC143FA4E250EB31u64],
[0x5DCFAB0800000000u64, 0xEB194F8E1AE525FDu64],
[0x5AA1CAE500000000u64, 0x92EFD1B8D0CF37BEu64],
[0xF14A3D9E40000000u64, 0xB7ABC627050305ADu64],
[0x6D9CCD05D0000000u64, 0xE596B7B0C643C719u64],
[0xE4820023A2000000u64, 0x8F7E32CE7BEA5C6Fu64],
[0xDDA2802C8A800000u64, 0xB35DBF821AE4F38Bu64],
[0xD50B2037AD200000u64, 0xE0352F62A19E306Eu64],
[0x4526F422CC340000u64, 0x8C213D9DA502DE45u64],
[0x9670B12B7F410000u64, 0xAF298D050E4395D6u64],
[0x3C0CDD765F114000u64, 0xDAF3F04651D47B4Cu64],
[0xA5880A69FB6AC800u64, 0x88D8762BF324CD0Fu64],
[0x8EEA0D047A457A00u64, 0xAB0E93B6EFEE0053u64],
[0x72A4904598D6D880u64, 0xD5D238A4ABE98068u64],
[0x47A6DA2B7F864750u64, 0x85A36366EB71F041u64],
[0x999090B65F67D924u64, 0xA70C3C40A64E6C51u64],
[0xFFF4B4E3F741CF6Du64, 0xD0CF4B50CFE20765u64],
[0xBFF8F10E7A8921A4u64, 0x82818F1281ED449Fu64],
[0xAFF72D52192B6A0Du64, 0xA321F2D7226895C7u64],
[0x9BF4F8A69F764490u64, 0xCBEA6F8CEB02BB39u64],
[0x02F236D04753D5B4u64, 0xFEE50B7025C36A08u64],
[0x01D762422C946590u64, 0x9F4F2726179A2245u64],
[0x424D3AD2B7B97EF5u64, 0xC722F0EF9D80AAD6u64],
[0xD2E0898765A7DEB2u64, 0xF8EBAD2B84E0D58Bu64],
[0x63CC55F49F88EB2Fu64, 0x9B934C3B330C8577u64],
[0x3CBF6B71C76B25FBu64, 0xC2781F49FFCFA6D5u64],
[0x8BEF464E3945EF7Au64, 0xF316271C7FC3908Au64],
[0x97758BF0E3CBB5ACu64, 0x97EDD871CFDA3A56u64],
[0x3D52EEED1CBEA317u64, 0xBDE94E8E43D0C8ECu64],
[0x4CA7AAA863EE4BDDu64, 0xED63A231D4C4FB27u64],
[0x8FE8CAA93E74EF6Au64, 0x945E455F24FB1CF8u64],
[0xB3E2FD538E122B44u64, 0xB975D6B6EE39E436u64],
[0x60DBBCA87196B616u64, 0xE7D34C64A9C85D44u64],
[0xBC8955E946FE31CDu64, 0x90E40FBEEA1D3A4Au64],
[0x6BABAB6398BDBE41u64, 0xB51D13AEA4A488DDu64],
[0xC696963C7EED2DD1u64, 0xE264589A4DCDAB14u64],
[0xFC1E1DE5CF543CA2u64, 0x8D7EB76070A08AECu64],
[0x3B25A55F43294BCBu64, 0xB0DE65388CC8ADA8u64],
[0x49EF0EB713F39EBEu64, 0xDD15FE86AFFAD912u64],
[0x6E3569326C784337u64, 0x8A2DBF142DFCC7ABu64],
[0x49C2C37F07965404u64, 0xACB92ED9397BF996u64],
[0xDC33745EC97BE906u64, 0xD7E77A8F87DAF7FBu64],
[0x69A028BB3DED71A3u64, 0x86F0AC99B4E8DAFDu64],
[0xC40832EA0D68CE0Cu64, 0xA8ACD7C0222311BCu64],
[0xF50A3FA490C30190u64, 0xD2D80DB02AABD62Bu64],
[0x792667C6DA79E0FAu64, 0x83C7088E1AAB65DBu64],
[0x577001B891185938u64, 0xA4B8CAB1A1563F52u64],
[0xED4C0226B55E6F86u64, 0xCDE6FD5E09ABCF26u64],
[0x544F8158315B05B4u64, 0x80B05E5AC60B6178u64],
[0x696361AE3DB1C721u64, 0xA0DC75F1778E39D6u64],
[0x03BC3A19CD1E38E9u64, 0xC913936DD571C84Cu64],
[0x04AB48A04065C723u64, 0xFB5878494ACE3A5Fu64],
[0x62EB0D64283F9C76u64, 0x9D174B2DCEC0E47Bu64],
[0x3BA5D0BD324F8394u64, 0xC45D1DF942711D9Au64],
[0xCA8F44EC7EE36479u64, 0xF5746577930D6500u64],
[0x7E998B13CF4E1ECBu64, 0x9968BF6ABBE85F20u64],
[0x9E3FEDD8C321A67Eu64, 0xBFC2EF456AE276E8u64],
[0xC5CFE94EF3EA101Eu64, 0xEFB3AB16C59B14A2u64],
[0xBBA1F1D158724A12u64, 0x95D04AEE3B80ECE5u64],
[0x2A8A6E45AE8EDC97u64, 0xBB445DA9CA61281Fu64],
[0xF52D09D71A3293BDu64, 0xEA1575143CF97226u64],
[0x593C2626705F9C56u64, 0x924D692CA61BE758u64],
[0x6F8B2FB00C77836Cu64, 0xB6E0C377CFA2E12Eu64],
[0x0B6DFB9C0F956447u64, 0xE498F455C38B997Au64],
[0x4724BD4189BD5EACu64, 0x8EDF98B59A373FECu64],
[0x58EDEC91EC2CB657u64, 0xB2977EE300C50FE7u64],
[0x2F2967B66737E3EDu64, 0xDF3D5E9BC0F653E1u64],
[0xBD79E0D20082EE74u64, 0x8B865B215899F46Cu64],
[0xECD8590680A3AA11u64, 0xAE67F1E9AEC07187u64],
[0xE80E6F4820CC9495u64, 0xDA01EE641A708DE9u64],
[0x3109058D147FDCDDu64, 0x884134FE908658B2u64],
[0xBD4B46F0599FD415u64, 0xAA51823E34A7EEDEu64],
[0x6C9E18AC7007C91Au64, 0xD4E5E2CDC1D1EA96u64],
[0x03E2CF6BC604DDB0u64, 0x850FADC09923329Eu64],
[0x84DB8346B786151Cu64, 0xA6539930BF6BFF45u64],
[0xE612641865679A63u64, 0xCFE87F7CEF46FF16u64],
[0x4FCB7E8F3F60C07Eu64, 0x81F14FAE158C5F6Eu64],
[0xE3BE5E330F38F09Du64, 0xA26DA3999AEF7749u64],
[0x5CADF5BFD3072CC5u64, 0xCB090C8001AB551Cu64],
[0x73D9732FC7C8F7F6u64, 0xFDCB4FA002162A63u64],
[0x2867E7FDDCDD9AFAu64, 0x9E9F11C4014DDA7Eu64],
[0xB281E1FD541501B8u64, 0xC646D63501A1511Du64],
[0x1F225A7CA91A4226u64, 0xF7D88BC24209A565u64],
[0x3375788DE9B06958u64, 0x9AE757596946075Fu64],
[0x0052D6B1641C83AEu64, 0xC1A12D2FC3978937u64],
[0xC0678C5DBD23A49Au64, 0xF209787BB47D6B84u64],
[0xF840B7BA963646E0u64, 0x9745EB4D50CE6332u64],
[0xB650E5A93BC3D898u64, 0xBD176620A501FBFFu64],
[0xA3E51F138AB4CEBEu64, 0xEC5D3FA8CE427AFFu64],
[0xC66F336C36B10137u64, 0x93BA47C980E98CDFu64],
[0xB80B0047445D4184u64, 0xB8A8D9BBE123F017u64],
[0xA60DC059157491E5u64, 0xE6D3102AD96CEC1Du64],
[0x87C89837AD68DB2Fu64, 0x9043EA1AC7E41392u64],
[0x29BABE4598C311FBu64, 0xB454E4A179DD1877u64],
[0xF4296DD6FEF3D67Au64, 0xE16A1DC9D8545E94u64],
[0x1899E4A65F58660Cu64, 0x8CE2529E2734BB1Du64],
[0x5EC05DCFF72E7F8Fu64, 0xB01AE745B101E9E4u64],
[0x76707543F4FA1F73u64, 0xDC21A1171D42645Du64],
[0x6A06494A791C53A8u64, 0x899504AE72497EBAu64],
[0x0487DB9D17636892u64, 0xABFA45DA0EDBDE69u64],
[0x45A9D2845D3C42B6u64, 0xD6F8D7509292D603u64],
[0x0B8A2392BA45A9B2u64, 0x865B86925B9BC5C2u64],
[0x8E6CAC7768D7141Eu64, 0xA7F26836F282B732u64],
[0x3207D795430CD926u64, 0xD1EF0244AF2364FFu64],
[0x7F44E6BD49E807B8u64, 0x8335616AED761F1Fu64],
[0x5F16206C9C6209A6u64, 0xA402B9C5A8D3A6E7u64],
[0x36DBA887C37A8C0Fu64, 0xCD036837130890A1u64],
[0xC2494954DA2C9789u64, 0x802221226BE55A64u64],
[0xF2DB9BAA10B7BD6Cu64, 0xA02AA96B06DEB0FDu64],
[0x6F92829494E5ACC7u64, 0xC83553C5C8965D3Du64],
[0xCB772339BA1F17F9u64, 0xFA42A8B73ABBF48Cu64],
[0xFF2A760414536EFBu64, 0x9C69A97284B578D7u64],
[0xFEF5138519684ABAu64, 0xC38413CF25E2D70Du64],
[0x7EB258665FC25D69u64, 0xF46518C2EF5B8CD1u64],
[0xEF2F773FFBD97A61u64, 0x98BF2F79D5993802u64],
[0xAAFB550FFACFD8FAu64, 0xBEEEFB584AFF8603u64],
[0x95BA2A53F983CF38u64, 0xEEAABA2E5DBF6784u64],
[0xDD945A747BF26183u64, 0x952AB45CFA97A0B2u64],
[0x94F971119AEEF9E4u64, 0xBA756174393D88DFu64],
[0x7A37CD5601AAB85Du64, 0xE912B9D1478CEB17u64],
[0xAC62E055C10AB33Au64, 0x91ABB422CCB812EEu64],
[0x577B986B314D6009u64, 0xB616A12B7FE617AAu64],
[0xED5A7E85FDA0B80Bu64, 0xE39C49765FDF9D94u64],
[0x14588F13BE847307u64, 0x8E41ADE9FBEBC27Du64],
[0x596EB2D8AE258FC8u64, 0xB1D219647AE6B31Cu64],
[0x6FCA5F8ED9AEF3BBu64, 0xDE469FBD99A05FE3u64],
[0x25DE7BB9480D5854u64, 0x8AEC23D680043BEEu64],
[0xAF561AA79A10AE6Au64, 0xADA72CCC20054AE9u64],
[0x1B2BA1518094DA04u64, 0xD910F7FF28069DA4u64],
[0x90FB44D2F05D0842u64, 0x87AA9AFF79042286u64],
[0x353A1607AC744A53u64, 0xA99541BF57452B28u64],
[0x42889B8997915CE8u64, 0xD3FA922F2D1675F2u64],
[0x69956135FEBADA11u64, 0x847C9B5D7C2E09B7u64],
[0x43FAB9837E699095u64, 0xA59BC234DB398C25u64],
[0x94F967E45E03F4BBu64, 0xCF02B2C21207EF2Eu64],
[0x1D1BE0EEBAC278F5u64, 0x8161AFB94B44F57Du64],
[0x6462D92A69731732u64, 0xA1BA1BA79E1632DCu64],
[0x7D7B8F7503CFDCFEu64, 0xCA28A291859BBF93u64],
[0x5CDA735244C3D43Eu64, 0xFCB2CB35E702AF78u64],
[0x3A0888136AFA64A7u64, 0x9DEFBF01B061ADABu64],
[0x088AAA1845B8FDD0u64, 0xC56BAEC21C7A1916u64],
[0x8AAD549E57273D45u64, 0xF6C69A72A3989F5Bu64],
[0x36AC54E2F678864Bu64, 0x9A3C2087A63F6399u64],
[0x84576A1BB416A7DDu64, 0xC0CB28A98FCF3C7Fu64],
[0x656D44A2A11C51D5u64, 0xF0FDF2D3F3C30B9Fu64],
[0x9F644AE5A4B1B325u64, 0x969EB7C47859E743u64],
[0x873D5D9F0DDE1FEEu64, 0xBC4665B596706114u64],
[0xA90CB506D155A7EAu64, 0xEB57FF22FC0C7959u64],
[0x09A7F12442D588F2u64, 0x9316FF75DD87CBD8u64],
[0x0C11ED6D538AEB2Fu64, 0xB7DCBF5354E9BECEu64],
[0x8F1668C8A86DA5FAu64, 0xE5D3EF282A242E81u64],
[0xF96E017D694487BCu64, 0x8FA475791A569D10u64],
[0x37C981DCC395A9ACu64, 0xB38D92D760EC4455u64],
[0x85BBE253F47B1417u64, 0xE070F78D3927556Au64],
[0x93956D7478CCEC8Eu64, 0x8C469AB843B89562u64],
[0x387AC8D1970027B2u64, 0xAF58416654A6BABBu64],
[0x06997B05FCC0319Eu64, 0xDB2E51BFE9D0696Au64],
[0x441FECE3BDF81F03u64, 0x88FCF317F22241E2u64],
[0xD527E81CAD7626C3u64, 0xAB3C2FDDEEAAD25Au64],
[0x8A71E223D8D3B074u64, 0xD60B3BD56A5586F1u64],
[0xF6872D5667844E49u64, 0x85C7056562757456u64],
[0xB428F8AC016561DBu64, 0xA738C6BEBB12D16Cu64],
[0xE13336D701BEBA52u64, 0xD106F86E69D785C7u64],
[0xECC0024661173473u64, 0x82A45B450226B39Cu64],
[0x27F002D7F95D0190u64, 0xA34D721642B06084u64],
[0x31EC038DF7B441F4u64, 0xCC20CE9BD35C78A5u64],
[0x7E67047175A15271u64, 0xFF290242C83396CEu64],
[0x0F0062C6E984D386u64, 0x9F79A169BD203E41u64],
[0x52C07B78A3E60868u64, 0xC75809C42C684DD1u64],
[0xA7709A56CCDF8A82u64, 0xF92E0C3537826145u64],
[0x88A66076400BB691u64, 0x9BBCC7A142B17CCBu64],
[0x6ACFF893D00EA435u64, 0xC2ABF989935DDBFEu64],
[0x0583F6B8C4124D43u64, 0xF356F7EBF83552FEu64],
[0xC3727A337A8B704Au64, 0x98165AF37B2153DEu64],
[0x744F18C0592E4C5Cu64, 0xBE1BF1B059E9A8D6u64],
[0x1162DEF06F79DF73u64, 0xEDA2EE1C7064130Cu64],
[0x8ADDCB5645AC2BA8u64, 0x9485D4D1C63E8BE7u64],
[0x6D953E2BD7173692u64, 0xB9A74A0637CE2EE1u64],
[0xC8FA8DB6CCDD0437u64, 0xE8111C87C5C1BA99u64],
[0x1D9C9892400A22A2u64, 0x910AB1D4DB9914A0u64],
[0x2503BEB6D00CAB4Bu64, 0xB54D5E4A127F59C8u64],
[0x2E44AE64840FD61Du64, 0xE2A0B5DC971F303Au64],
[0x5CEAECFED289E5D2u64, 0x8DA471A9DE737E24u64],
[0x7425A83E872C5F47u64, 0xB10D8E1456105DADu64],
[0xD12F124E28F77719u64, 0xDD50F1996B947518u64],
[0x82BD6B70D99AAA6Fu64, 0x8A5296FFE33CC92Fu64],
[0x636CC64D1001550Bu64, 0xACE73CBFDC0BFB7Bu64],
[0x3C47F7E05401AA4Eu64, 0xD8210BEFD30EFA5Au64],
[0x65ACFAEC34810A71u64, 0x8714A775E3E95C78u64],
[0x7F1839A741A14D0Du64, 0xA8D9D1535CE3B396u64],
[0x1EDE48111209A050u64, 0xD31045A8341CA07Cu64],
[0x934AED0AAB460432u64, 0x83EA2B892091E44Du64],
[0xF81DA84D5617853Fu64, 0xA4E4B66B68B65D60u64],
[0x36251260AB9D668Eu64, 0xCE1DE40642E3F4B9u64],
[0xC1D72B7C6B426019u64, 0x80D2AE83E9CE78F3u64],
[0xB24CF65B8612F81Fu64, 0xA1075A24E4421730u64],
[0xDEE033F26797B627u64, 0xC94930AE1D529CFCu64],
[0x169840EF017DA3B1u64, 0xFB9B7CD9A4A7443Cu64],
[0x8E1F289560EE864Eu64, 0x9D412E0806E88AA5u64],
[0xF1A6F2BAB92A27E2u64, 0xC491798A08A2AD4Eu64],
[0xAE10AF696774B1DBu64, 0xF5B5D7EC8ACB58A2u64],
[0xACCA6DA1E0A8EF29u64, 0x9991A6F3D6BF1765u64],
[0x17FD090A58D32AF3u64, 0xBFF610B0CC6EDD3Fu64],
[0xDDFC4B4CEF07F5B0u64, 0xEFF394DCFF8A948Eu64],
[0x4ABDAF101564F98Eu64, 0x95F83D0A1FB69CD9u64],
[0x9D6D1AD41ABE37F1u64, 0xBB764C4CA7A4440Fu64],
[0x84C86189216DC5EDu64, 0xEA53DF5FD18D5513u64],
[0x32FD3CF5B4E49BB4u64, 0x92746B9BE2F8552Cu64],
[0x3FBC8C33221DC2A1u64, 0xB7118682DBB66A77u64],
[0x0FABAF3FEAA5334Au64, 0xE4D5E82392A40515u64],
[0x29CB4D87F2A7400Eu64, 0x8F05B1163BA6832Du64],
[0x743E20E9EF511012u64, 0xB2C71D5BCA9023F8u64],
[0x914DA9246B255416u64, 0xDF78E4B2BD342CF6u64],
[0x1AD089B6C2F7548Eu64, 0x8BAB8EEFB6409C1Au64],
[0xA184AC2473B529B1u64, 0xAE9672ABA3D0C320u64],
[0xC9E5D72D90A2741Eu64, 0xDA3C0F568CC4F3E8u64],
[0x7E2FA67C7A658892u64, 0x8865899617FB1871u64],
[0xDDBB901B98FEEAB7u64, 0xAA7EEBFB9DF9DE8Du64],
[0x552A74227F3EA565u64, 0xD51EA6FA85785631u64],
[0xD53A88958F87275Fu64, 0x8533285C936B35DEu64],
[0x8A892ABAF368F137u64, 0xA67FF273B8460356u64],
[0x2D2B7569B0432D85u64, 0xD01FEF10A657842Cu64],
[0x9C3B29620E29FC73u64, 0x8213F56A67F6B29Bu64],
[0x8349F3BA91B47B8Fu64, 0xA298F2C501F45F42u64],
[0x241C70A936219A73u64, 0xCB3F2F7642717713u64],
[0xED238CD383AA0110u64, 0xFE0EFB53D30DD4D7u64],
[0xF4363804324A40AAu64, 0x9EC95D1463E8A506u64],
[0xB143C6053EDCD0D5u64, 0xC67BB4597CE2CE48u64],
[0xDD94B7868E94050Au64, 0xF81AA16FDC1B81DAu64],
[0xCA7CF2B4191C8326u64, 0x9B10A4E5E9913128u64],
[0xFD1C2F611F63A3F0u64, 0xC1D4CE1F63F57D72u64],
[0xBC633B39673C8CECu64, 0xF24A01A73CF2DCCFu64],
[0xD5BE0503E085D813u64, 0x976E41088617CA01u64],
[0x4B2D8644D8A74E18u64, 0xBD49D14AA79DBC82u64],
[0xDDF8E7D60ED1219Eu64, 0xEC9C459D51852BA2u64],
[0xCABB90E5C942B503u64, 0x93E1AB8252F33B45u64],
[0x3D6A751F3B936243u64, 0xB8DA1662E7B00A17u64],
[0x0CC512670A783AD4u64, 0xE7109BFBA19C0C9Du64],
[0x27FB2B80668B24C5u64, 0x906A617D450187E2u64],
[0xB1F9F660802DEDF6u64, 0xB484F9DC9641E9DAu64],
[0x5E7873F8A0396973u64, 0xE1A63853BBD26451u64],
[0xDB0B487B6423E1E8u64, 0x8D07E33455637EB2u64],
[0x91CE1A9A3D2CDA62u64, 0xB049DC016ABC5E5Fu64],
[0x7641A140CC7810FBu64, 0xDC5C5301C56B75F7u64],
[0xA9E904C87FCB0A9Du64, 0x89B9B3E11B6329BAu64],
[0x546345FA9FBDCD44u64, 0xAC2820D9623BF429u64],
[0xA97C177947AD4095u64, 0xD732290FBACAF133u64],
[0x49ED8EABCCCC485Du64, 0x867F59A9D4BED6C0u64],
[0x5C68F256BFFF5A74u64, 0xA81F301449EE8C70u64],
[0x73832EEC6FFF3111u64, 0xD226FC195C6A2F8Cu64],
];
// strconv — number↔string conversions.
//
// Mirrors Hare's strconv:: surface. The *tos functions return a
// `const str` view into a module-level buffer that is overwritten on
// the next call to the same function; callers must copy the bytes if
// they need to outlive the next invocation. See [[strings.dup]] to
// duplicate. Matches Hare's strconv::*tos semantics.
package strconv;
import ascii;
import bytes;
import os;
import strings;
// invalid — input wasn't a valid number in the requested format.
// Payload is the byte index of the first offending position.
// Mirrors Hare's strconv::invalid = !size.
export type invalid = !i32;
// overflow — input was valid but doesn't fit the target type.
// Mirrors Hare's strconv::overflow = !void.
export type overflow = !void;
// error — any error from a strconv call. Mirrors Hare's strconv::error.
export type error = !(invalid | overflow);
// base — numeric base for parsing/formatting. Mirrors Hare's
// `strconv::base` (Hare uses `enum uint`; we pick `enum i32` since
// the underlying parse/format loops index with i32).
//
// HEX is an alias for HEX_UPPER; HEX_LOWER is a pseudo-base that
// produces lowercase a-f digits.
export type base = enum i32 {
DEFAULT = 0,
BIN = 2,
OCT = 8,
DEC = 10,
HEX_UPPER = 16,
HEX = 16,
HEX_LOWER = 17,
};
fn basenum(b: base) i64 = {
if (b == base.BIN) { return 2; };
if (b == base.OCT) { return 8; };
if (b == base.HEX) { return 16; };
if (b == base.HEX_UPPER) { return 16; };
if (b == base.HEX_LOWER) { return 16; };
return 10; // DEC and DEFAULT
};
// lut_upper / lut_lower — digit→glyph tables. Verbatim port of the
// `static const lut_upper`/`lut_lower` rune arrays in
// ref/hare/strconv/utos.ha:14-20. Module-level `let` (ww has no module
// `const`; never written) following the ftos_data.ww table convention.
// Declared [16]rune (faithful to Hare's inferred rune element type);
// u64tos casts the indexed glyph to u8 at the store, as Hare does
// (utos.ha:35).
let lut_upper: [16]rune = [
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
];
let lut_lower: [16]rune = [
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
];
// u64tos_buf — overwritten on each u64tos call (Hare's `static let buf`,
// utos.ha:12). 64 = the widest u64 rendering (binary). `[0...]` kept for
// fidelity; the initial value is irrelevant (only the freshly-written
// prefix is ever read) but the fill form is exercised (probed: emits
// byte-identically cross-stage).
let u64tos_buf: [64]u8 = [0...]; // 64 binary digits
// u64tos — convert u to a base-b numeric string. Returns a view into
// `u64tos_buf`, overwritten on the next call; copy via strings.dup to
// outlive it. Verbatim port of ref/hare/strconv/utos.ha:10-42.
// Divergences:
// - Hare's `static assert(types::U64_MAX == ...)` dropped (ww has no
// static assert; the bound lives in lib/types/types.ww:20).
// - Hare selects the LUT via an if-EXPRESSION and reassigns `b` to
// HEX_UPPER / DEC inline (utos.ha:21-26). ww has no if-expression
// (standing divergence, stof.ww:31), so the glyph case is a `lower`
// bool branch and the divisor is basenum(b) — the file's existing
// normalize helper, which maps DEFAULT→10 and HEX_LOWER→16 exactly
// as Hare's reassignment does.
// - Hare's `types::string { data = &buf, ... }` + `*(&s: *str)`
// reinterpret (utos.ha:28,41) → strings.frombytes (CLAUDE.md rule 9
// carve-out: ww's lib/types has no `string` struct; frombytes is the
// honest ww idiom, cf. ascii/strings).
export fn u64tos(u: u64, b: base) str = {
let nb: u64 = basenum(b): u64;
let lower: bool = (b == base.HEX_LOWER);
let length: i32 = 0;
let n: u64 = u;
if (n == 0u64) {
u64tos_buf[length] = lut_upper[0]: u8;
length += 1;
};
for (n > 0u64) {
let d: i64 = (n % nb): i64;
if (lower) { u64tos_buf[length] = lut_lower[d]: u8; }
else { u64tos_buf[length] = lut_upper[d]: u8; };
length += 1;
n = n / nb;
};
bytes.reverse(u64tos_buf[0:length]);
return strings.frombytes(u64tos_buf[0:length]);
};
// i64tos_buf — independent from u64tos_buf so i64tos's own u64tos call
// (the magnitude) doesn't clobber the in-flight result. 65 = 64 digits
// plus the leading '-'. Hare's `static let buf: [65]u8` (itos.ha:18).
let i64tos_buf: [65]u8 = [0...]; // 64 binary digits plus '-'
// i64tos — convert i to a base-b numeric string. Returns a view into
// `i64tos_buf`. Verbatim port of ref/hare/strconv/itos.ha:10-32.
// Divergences:
// - `static assert` dropped (see u64tos); the DEFAULT→DEC normalize
// rides basenum(b) inside the u64tos call (itos.ha:12-14).
// - Hare's slice-assign `buf[1..len(u)+1] = u[..]` + the bounds assert
// (itos.ha:26-28) → explicit copy loop (existing-file convention;
// the [65] buffer holds the 64-digit max + sign exactly, so the
// bound is structural).
// - `*(&s: *str)` → strings.frombytes (see u64tos).
export fn i64tos(i: i64, b: base) str = {
if (i >= 0) { return u64tos(i: u64, b); };
i64tos_buf[0] = 45u8; // '-'
// `(-i): u64`: for I64_MIN, -i wraps (two's complement) back to the
// I64_MIN bit pattern; reinterpreting to u64 yields the true
// magnitude 9223372036854775808. ref/hare/strconv/itos.ha:25. Probed
// on both stages (NEG then i64→u64 reinterpret byte-identical);
// closes the i64tos-on-I64_MIN bug noted at cgen.ww #144.
let u: str = u64tos((-i): u64, b);
let k: i32 = 0;
for (k < u.len) {
i64tos_buf[k + 1] = u[k];
k += 1;
};
return strings.frombytes(i64tos_buf[0 : u.len + 1]);
};
export fn i32tos(v: i32, b: base) str = { return i64tos(v: i64, b); };
export fn i16tos(v: i16, b: base) str = { return i64tos(v: i64, b); };
export fn i8tos(v: i8, b: base) str = { return i64tos(v: i64, b); };
// itos — int (ww machine-word, 8B → i64-width) → string.
// ref/hare/strconv/itos.ha:52.
export fn itos(i: int, b: base) str = { return i64tos(i: i64, b); };
export fn u32tos(v: u32, b: base) str = { return u64tos(v: u64, b); };
export fn u16tos(v: u16, b: base) str = { return u64tos(v: u64, b); };
export fn u8tos(v: u8, b: base) str = { return u64tos(v: u64, b); };
// utos — uint (8B → u64-width) → string. ref/hare/strconv/utos.ha:62.
export fn utos(u: uint, b: base) str = { return u64tos(u: u64, b); };
// ztos — size (8B → u64-width) → string. ref/hare/strconv/utos.ha:67.
export fn ztos(u: size, b: base) str = { return u64tos(u: u64, b); };
// uptrtos — uintptr → string. ref/hare/strconv/utos.ha:72 (param `uptr`
// cast `uptr: u64`).
export fn uptrtos(uptr: uintptr, b: base) str = { return u64tos(uptr: u64, b); };
// rune_to_integer — digit value of r (0-9 → 0-9; a-z/A-Z → 10-35),
// or void if r is not alphanumeric. Verbatim port of
// ref/hare/strconv/stou.ha:8-15 (ww yields the void variant with a
// bare `return;`, per lib/bytes/bytes.ww:65).
fn rune_to_integer(r: rune) (u64 | void) = {
if (ascii.isdigit(r)) {
return (r: u32 - '0'): u64;
} else if (ascii.isalpha(r) && ascii.islower(r)) {
return (r: u32 - 'a'): u64 + 10;
} else if (ascii.isalpha(r) && ascii.isupper(r)) {
return (r: u32 - 'A'): u64 + 10;
};
return;
};
// parseint — shared sign+digit+overflow core for stoi64/stou64.
// Verbatim port of ref/hare/strconv/stou.ha:17-65. Divergences:
// - param `base` → `b` (ww: avoid the type/value name collision; the
// file already names the enum arg `b`).
// - Hare's DEFAULT→DEC / HEX_LOWER→HEX base reassignment + the
// base-validity assert collapse into basenum(b), which already maps
// every base to its numeric value {2,8,10,16} (default 10). HEX_LOWER
// thus parses case-insensitively, matching Hare's normalize-then-parse.
// - str is byte-indexable, so Hare's `buf = strings::toutf8(s)` is
// elided (existing file convention, cf. the old stoi64/stou64).
// - n *= base / n += digit spelled as plain assignment (sibling-fn
// convention).
fn parseint(s: str, b: base) ((bool, u64) | invalid | overflow) = {
let nb: u64 = basenum(b): u64;
if (s.len == 0) {
return 0: invalid;
};
let i: i32 = 0;
let sign: bool = s[i] == '-';
if (sign || s[i] == '+') {
i += 1;
};
// Require at least one digit.
if (i == s.len) {
return i: invalid;
};
let n: u64 = 0u64;
// Hare's `for (i < len(buf); i += 1)` (stou.ha:43) → condition-only
// for + tail increment (ww has no 2-clause for; sort.ww:25). Early
// returns exit before the increment, so it's never skipped.
for (i < s.len) {
let digit: u64 = match (rune_to_integer(s[i]: rune)) {
case void => return i: invalid;
case let d: u64 => yield d;
};
if (digit >= nb) {
return i: invalid;
};
let old: u64 = n;
n = n * nb;
n = n + digit;
if (n < old) {
return overflow{};
};
i += 1;
};
return (sign, n);
};
// stoi64 — parse signed base-b number. Verbatim port of
// ref/hare/strconv/stoi.ha:9-17. types.I64_MAX is inlined (the const is
// package-private — see the mulshift32 note in ftos.ww).
export fn stoi64(s: str, b: base) (i64 | invalid | overflow) = {
let (sign, u) = parseint(s, b)?;
// Two's complement: I64_MIN = -I64_MAX - 1. Hare's two if-expressions
// (stoi.ha:12,16) are lowered to statement-if — ww has no
// if-expression (standing divergence, see the note in stof.ww:31).
let max: u64 = 9223372036854775807u64;
if (sign) {
max = max + 1u64;
};
if (u > max) {
return overflow{};
};
let r: i64 = u: i64;
if (sign) {
r = -r;
};
return r;
};
// stou64 — parse unsigned base-b number. Verbatim port of
// ref/hare/strconv/stou.ha:70-76.
export fn stou64(s: str, b: base) (u64 | invalid | overflow) = {
let (sign, u) = parseint(s, b)?;
if (sign) {
return overflow{};
};
return u;
};
export fn stoi32(s: str, b: base) (i32 | invalid | overflow) = {
let r = stoi64(s, b);
match (r) {
case let v: i64 => {
if (v > 2147483647i64) { return overflow{}; };
if (v < -2147483648i64) { return overflow{}; };
return v: i32;
};
case let e: invalid => return e;
case let e: overflow => return e;
};
return 0: invalid; // unreachable; appeases the path-cov checker
};
export fn stoi16(s: str, b: base) (i16 | invalid | overflow) = {
let r = stoi64(s, b);
match (r) {
case let v: i64 => {
if (v > 32767i64) { return overflow{}; };
if (v < -32768i64) { return overflow{}; };
return v: i16;
};
case let e: invalid => return e;
case let e: overflow => return e;
};
return 0: invalid;
};
export fn stoi8(s: str, b: base) (i8 | invalid | overflow) = {
let r = stoi64(s, b);
match (r) {
case let v: i64 => {
if (v > 127i64) { return overflow{}; };
if (v < -128i64) { return overflow{}; };
return v: i8;
};
case let e: invalid => return e;
case let e: overflow => return e;
};
return 0: invalid;
};
// stoi — parse signed base-b number into an int. Mirrors Hare's
// strconv::stoi (ref/hare/strconv/stoi.ha:53), which clamps to
// types::INT_MIN/INT_MAX via stoiminmax. ww's int is a machine word
// (8B → i64-width, so INT_MIN/INT_MAX == I64_MIN/I64_MAX per
// lib/types/types.ww:30-31), so stoi64's result always fits and the
// clamp is a no-op — omitted, not inlined (the bound consts are
// package-private; see the inline note at mulshift32 in ftos.ww).
export fn stoi(s: str, b: base) (int | invalid | overflow) = {
let r = stoi64(s, b);
match (r) {
case let v: i64 => return v: int;
case let e: invalid => return e;
case let e: overflow => return e;
};
return 0: invalid;
};
export fn stou32(s: str, b: base) (u32 | invalid | overflow) = {
let r = stou64(s, b);
match (r) {
case let v: u64 => {
if (v > 4294967295u64) { return overflow{}; };
return v: u32;
};
case let e: invalid => return e;
case let e: overflow => return e;
};
return 0: invalid;
};
export fn stou16(s: str, b: base) (u16 | invalid | overflow) = {
let r = stou64(s, b);
match (r) {
case let v: u64 => {
if (v > 65535u64) { return overflow{}; };
return v: u16;
};
case let e: invalid => return e;
case let e: overflow => return e;
};
return 0: invalid;
};
export fn stou8(s: str, b: base) (u8 | invalid | overflow) = {
let r = stou64(s, b);
match (r) {
case let v: u64 => {
if (v > 255u64) { return overflow{}; };
return v: u8;
};
case let e: invalid => return e;
case let e: overflow => return e;
};
return 0: invalid;
};
// stou — parse unsigned base-b number into a uint. Mirrors Hare's
// strconv::stou (ref/hare/strconv/stou.ha:107), which clamps to
// types::UINT_MAX via stoumax. ww's uint is a machine word (8B →
// u64-width, so UINT_MAX == U64_MAX per lib/types/types.ww:33), so
// stou64's result always fits and the clamp is a no-op.
export fn stou(s: str, b: base) (uint | invalid | overflow) = {
let r = stou64(s, b);
match (r) {
case let v: u64 => return v: uint;
case let e: invalid => return e;
case let e: overflow => return e;
};
return 0: invalid;
};
// stoz — parse unsigned base-b number into a size. Mirrors Hare's
// strconv::stoz (ref/hare/strconv/stou.ha:113). ww's size is u64-width
// (SIZE_MAX == U64_MAX per lib/types/types.ww:37), so the clamp is a no-op.
export fn stoz(s: str, b: base) (size | invalid | overflow) = {
let r = stou64(s, b);
match (r) {
case let v: u64 => return v: size;
case let e: invalid => return e;
case let e: overflow => return e;
};
return 0: invalid;
};
// f64tos — graduated to the Ryū shortest-round-trippable implementation
// in ftos.ww (strconv #106 fold-5). The old lossy fixed-point version
// (6 fractional digits, "huge" fallback ≥9e18, no NaN/Inf) was deleted
// here per the lib-note graduation rule ("replace in one go, don't keep
// both"); ftos.ww's f64tos is the live one. f32tos follows in fold-5b
// (task #67, gated on the #143 f32-arg-push cgen fix).
// strerror — convert an strconv error to a user-readable string.
// Returns owned str; release via os.free. Mirrors Hare's
// strconv::strerror.
export fn strerror(e: error) str = {
match (e) {
case let v: invalid => return strings.dup("input is not a valid number");
case let v: overflow => return strings.dup("input number doesn't fit target type");
};
return strings.dup("");
};
// selfhost/test/smoke.ww — end-to-end smoke for the selfhost path.
//
// Exercises the patterns the real ww-side compiler port will use:
// - bump arena allocator (mem.ww shape)
// - error idiom (T | str)
// - struct of fn pointers + ctx pointer (the io.stream-style
// polymorphism we use instead of interfaces)
// - byte-level scanning that mirrors the hot path inside lex.ww
// - strconv round-trip via the real stdlib
//
// `main` returns 42 when every check passes, 1..N on failure
// indicating which probe broke. The 990_selfhost test asserts 42.
//
// Note: only stack-local mutable state. Top-level `let` mutation
// requires a writable .data segment in w6l, which is a separate
// task; until then we exercise polymorphism via ctx pointers, which
// is what the real port wants anyway.
package test;
import os;
import strconv;
import ascii;
// --- bump arena ---------------------------------------------------------
type arena = struct {
buf: *u8,
off: u64,
cap: u64,
};
// In-place init. Returning a 24-byte struct by value isn't yet
// supported in w6c (SysV requires a hidden return-slot pointer for
// structs >16 bytes), so we initialize through a pointer like the
// real compiler does today.
fn arena_init(a: *arena, buf: *u8, cap: u64) void = {
a.buf = buf;
a.off = 0u64;
a.cap = cap;
};
fn arena_alloc(a: *arena, n: u64) *u8 = {
if (n > a.cap - a.off) { return nil; };
let p: *u8 = a.buf + a.off;
a.off += n;
return p;
};
// --- (i32 | str) error idiom -------------------------------------------
fn checked_div(num: i32, den: i32) (i32 | str) = {
if (den == 0) { return "div by zero"; };
return num / den;
};
// --- struct-of-fn-pointer polymorphism ---------------------------------
//
// A trivial "writer" abstraction: a function pointer plus a context.
// This mirrors how io.stream / Plan 9 Bio work. The ctx pointer lets
// the implementation own its own state without a global.
type counter = struct {
n: i32,
};
type writer = struct {
ctx: *void,
emit: fn(ctx: *void, b: u8) void,
};
fn count_emit(ctx: *void, b: u8) void = {
let c: *counter = ctx: *counter;
c.n += 1;
};
// --- size/align/offset typed-builtin fixtures (#42) --------------------
type point = struct {
x: i32,
y: i32,
};
// Mixed-alignment struct: i8 lays at 0, then i64 needs to skip to
// offset 8 (the i64's natural align). Probe asserts both ends.
type mixalign = struct {
tag: i8,
val: i64,
};
// --- byte scanner like lex.ww's hot path -------------------------------
fn count_digits(s: str) i32 = {
let i: i32 = 0;
let n: i32 = 0;
for (i < s.len) {
let c: u8 = s[i];
if (c >= 48u8) {
if (c <= 57u8) { n += 1; };
};
i += 1;
};
return n;
};
// --- entry --------------------------------------------------------------
export fn main() i32 = {
// Probe 1 — arena hands out distinct pointers, refuses oversize.
let buf: [256]u8;
let a: arena;
arena_init(&a, buf.ptr, 256u64);
let p1: *u8 = arena_alloc(&a, 32u64);
let p2: *u8 = arena_alloc(&a, 32u64);
if (p1 == nil) { return 1; };
if (p2 == nil) { return 2; };
if (p1 == p2) { return 3; };
let p3: *u8 = arena_alloc(&a, 1024u64);
if (p3 != nil) { return 4; };
// Probe 2 — error union both ways.
let r_ok: (i32 | str) = checked_div(84, 2);
let r_bad: (i32 | str) = checked_div(1, 0);
let acc: i32 = 0;
match (r_ok) {
case let v: i32 => acc = v;
case let e: str => return 5;
};
if (acc != 42) { return 6; };
match (r_bad) {
case let v: i32 => return 7;
case let e: str => acc = e.len: i32;
};
if (acc != 11) { return 8; }; // len("div by zero") == 11
// Probe 3 — struct-of-fn-pointer dispatch via ctx pointer.
let c: counter = counter { n = 0 };
let w: writer = writer { ctx = (&c): *void, emit = count_emit };
w.emit(w.ctx, 65u8);
w.emit(w.ctx, 66u8);
w.emit(w.ctx, 67u8);
if (c.n != 3) { return 9; };
// Probe 4 — byte scan over a literal.
let dn: i32 = count_digits("ww123abc");
if (dn != 3) { return 10; };
// Probe 5 — strconv round-trip via the real stdlib.
let s: str = strconv.i64tos(4242i64, strconv.base.DEC);
if (s.len != 4) { return 11; };
if (s.ptr[0] != 52u8) { return 12; }; // '4'
if (s.ptr[3] != 50u8) { return 13; }; // '2'
// Probe 6 — ascii classifications (rune-taking, Hare-shaped).
if (!ascii.isdigit(53)) { return 14; }; // '5'
if (ascii.isdigit(65)) { return 15; }; // 'A' is not a digit
if (!ascii.isalpha(122)) { return 16; }; // 'z'
if (!ascii.isxdigit(70)) { return 17; }; // 'F'
if (ascii.isxdigit(71)) { return 18; }; // 'G' is not hex
if (ascii.tolower(65) != 97) { return 19; }; // 'A' -> 'a'
if (ascii.toupper(122) != 90) { return 20; }; // 'z' -> 'Z'
// Probe 7 — file open/read via the new os APIs. /proc/self/cmdline
// always exists on Linux, no write side, and is non-empty.
let path: str = "/proc/self/cmdline";
// Use raw os.open here (returns i32 with -errno) for the same
// reason as os.read below: probe 6 in 990_selfhost compiles
// smoke.ww standalone (no `use` expansion), so cross-module type
// references like `os.oserror` and `os.flag` don't resolve at
// that step. RDONLY is 0; passing the literal keeps the call
// site standalone-compilable to byte-identical asm on both
// compilers.
let fd: i32 = os.open(path, 0, 0i32);
if (fd < 0) { return 21; };
let rbuf: [128]u8;
// Use raw os.read here (single syscall, plain i64) instead of
// os.readall: the 990 cgen-match probe compiles smoke.ww
// standalone without `use os;` expansion, so cross-module type
// references like `os.oserror` can't be resolved.
let n: i64 = os.read(fd, rbuf.ptr, 128u64);
os.close(fd);
if (n <= 0i64) { return 22; };
// Probe 8 — size(T) / align(T) / offset(e.f) typed-builtin folds
// (#42). Each call folds to an N_INTLIT at check time; cgen
// materialises the literal as a plain `MOVQ $N, AX`. Mirrors
// cstage cmd/wcc/check.c:907-960 byte-for-byte on this corpus.
if (size(str) != 24) { return 23; }; // str IS []u8: {ptr,len,cap} 24B (#1/Phase 3)
if (size(i64) != 8) { return 24; };
if (size(i32) != 4) { return 25; };
if (align(i64) != 8) { return 26; };
if (align(i32) != 4) { return 27; };
// Initialize struct locals explicitly so the cgen path doesn't
// drift from cstage on bare `let X: T;` zero-init (pre-existing
// wwstage divergence outside #42).
let pt: point = point { x = 0, y = 0 };
if (offset(pt.x) != 0) { return 28; };
if (offset(pt.y) != 4) { return 29; };
let mx: mixalign = mixalign { tag = 0i8, val = 0i64 };
if (offset(mx.tag) != 0) { return 30; };
if (offset(mx.val) != 8) { return 31; }; // align-padded to 8
// Width breadth: smallest prim, ptr, slice, struct (8B + padded),
// covering astsize's TPTR/TSLICE/TNAME-resolve-to-struct arms.
if (size(i8) != 1) { return 32; };
if (align(i8) != 1) { return 33; };
if (size(*i32) != 8) { return 34; };
if (size([]i32) != 24) { return 35; };
if (size(point) != 8) { return 36; };
if (size(mixalign) != 16) { return 37; };
return 42;
};