// 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; 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; }; // 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; // 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; // [[args]] allocates the []str view via the `alloc` builtin, whose malloc // lowers to rt_malloc only when the rt binding is in the bundle (mirror // lib/strings/strings.ww:30 — every alloc-using module imports rt). os is // bundled by ~every program, so without this a plain `ww build` of any // os-importing program links bare libc `malloc` (undefined). Task #17. import rt; @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; // 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 . 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 (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] == '/') { 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; // rt_argc / rt_argv — runtime-side getters for the argc/argv captured by // rt/start.s at process entry (same DATAW-slot + TEXT-getter shape as // rt_envp). [[args]] is the only consumer. @symbol("rt_argc") fn rtargc() i64; @symbol("rt_argv") fn rtargv() **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] == '=') { 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; }; // argsbuilt / argscache — build-once cache for [[args]]. drew ruling // (task #17): an explicit `built` sentinel, NOT len==0 overloading (a // real argv always has argv[0], but the sentinel keeps the contract // honest and decoupled from content). args() is loop-callable; under // ww's no-free model a per-call rebuild would leak the slice each call, // so the slice is materialised once and reused. let argsbuilt: bool = false; let argscache: []str; // args — the process arguments as a borrowed []str. args[0] is the // program name; args[1..] are the invocation arguments. Each str views // the NUL-terminated argv bytes in place (valid for the process // lifetime), so the slice must not be mutated or freed by the caller. // // DIVERGENCE (Hare-fidelity, task #26): Hare's `os::args` is a `[]str` // GLOBAL populated by an @init that walks rt's argv // (ref/hare/os/+linux/start.ha). ww has no @init mechanism, so the // faithful global is not expressible; this is the fn-shaped equivalent // (Hare NAME kept, shape diverged). Revisit if @init lands (#26). export fn args() []str = { if (argsbuilt) { return argscache; }; let argc: i32 = rtargc(): i32; let argv: **u8 = rtargv(); let r: []str = alloc([], argc: u64)!; let i: i32 = 0; for (i < argc) { let c: *u8 = argv[i]; let n: i32 = 0; for (c[n] != 0u8) { n += 1; }; let s: str; s.ptr = c; s.len = n; append(r, s); i += 1; }; argscache = r; argsbuilt = true; return r; }; // ---- 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 . // 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 → `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); 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()` → `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 = { 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) { 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 = { assert(v > 0u64, "strconv.pow2multiple: v == 0"); 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 = { 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 = { assert(e <= 1650u32, "strconv.log10pow2: e > 1650"); return (e * 78913u32) >> 18u32; }; // ref/hare/strconv/ftos_ryu.ha:154. fn log10pow5(e: u32) u32 = { 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, 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) { 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 = { assert(v > 0u32, "strconv.pow2multiple32: v == 0"); 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 = { 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); 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 = { 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; 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 = { 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 = { 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 = { assert(delim.len > 0, "bytes.tokenize called with empty slice"); 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 = { assert(delim.len > 0, "bytes.rtokenize called with empty slice"); 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; }; // 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 = { 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 => { append(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); append(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 = { 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 => { append(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); append(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 => { 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 => { 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 . 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; }; // 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 append'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) { append(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 = { 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) = { assert(start <= end, "strings.bytesub: start is higher than end"); assert(end <= s.len, "strings.bytesub: end exceeds string length"); if (start < s.len && (s[start] & 0xC0u8) == 0x80u8) { let e: utf8.invalid; return e; }; if (end < s.len && (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) { 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) { 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)); }; // 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 => { append(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); append(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 => { append(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); append(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) }; // 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 = { 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; }; 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; }; 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] = '-'; // `(-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; };