// strings — operations over the immutable str type ({ *u8, len }). // Mirrors Hare's strings::; `len` and `is-empty` aren't functions // (callers use `s.len` and `s.len == 0` directly). use os; // compare — bytewise three-way comparison: negative if ab. Matches Hare's strings::compare. ASCII-order, not // locale-aware. Callers that just need equality use `compare(a, b) == 0`. export fn compare(a: str, b: str) i32 = { 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]: i32) - (b[i]: i32); }; i += 1; }; return a.len - b.len; }; export fn hasprefix(s: str, p: str) bool = { if (p.len > s.len) { return false; }; let i: i32 = 0; for (i < p.len) { if (s[i] != p[i]) { return false; }; i += 1; }; return true; }; export fn hassuffix(s: str, suf: str) 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; }; // byteindex — first index of byte `c` in `s`. Hare-shaped optional: // (i32 | void). void variant indicates "not found". export fn byteindex(s: str, c: u8) (i32 | void) = { let i: i32 = 0; for (i < s.len) { if (s[i] == c) { return i; }; i += 1; }; return; }; // rbyteindex — last index of byte `c` in `s`. Mirrors Hare's // strings::rbyteindex. export fn rbyteindex(s: str, c: u8) (i32 | void) = { let i: i32 = s.len - 1; for (i >= 0) { if (s[i] == c) { return i; }; i -= 1; }; return; }; // index — first index of `sub` in `s`. Naive scan; fine for short // patterns and small strings, which dominate config and CLI parsing. // Empty `sub` matches at 0. export fn index(s: str, sub: str) (i32 | void) = { 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; }; export fn contains(s: str, sub: str) bool = { let r: (i32 | void) = index(s, sub); match (r) { case let i: i32 => return true; case void => return false; }; return false; }; // concat — joins two strings into a fresh str. Caller owns the // returned str's storage; release via `os.free(r.ptr, r.len)`. Mirrors // Hare's strings::concat shape. export fn concat(a: str, b: str) str = { let total: i32 = a.len + b.len; let buf: *u8 = os.alloc(total: u64): *u8; let i: i32 = 0; for (i < a.len) { buf[i] = a[i]; i += 1; }; let j: i32 = 0; for (j < b.len) { buf[a.len + j] = b[j]; j += 1; }; let r: str; r.ptr = buf; r.len = total; return r; }; // dup — duplicate a string into a fresh allocation. Caller owns the // returned str's storage; release via `os.free(r.ptr, r.len)`. Mirrors // Hare's strings::dup shape — Hare returns `(str | nomem)`, ww doesn't // have nomem (os.alloc aborts on OOM), so we return plain `str`. // // Empty input yields a `{nil, 0}` str — Hare returns the static empty // string; same observable result. export fn dup(s: str) str = { let r: str; r.ptr = nil; r.len = 0; if (s.len == 0) { return r; }; let buf: *u8 = os.alloc(s.len: u64): *u8; let i: i32 = 0; for (i < s.len) { buf[i] = s[i]; i += 1; }; r.ptr = buf; r.len = s.len; return r; };