// 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; }; // indexbyte — first byte position of byte `c` in `s`. Mirrors // Hare's strings::byteindex when the needle is a single ASCII rune, // renamed to match bytes.indexbyte and to disambiguate from Hare's // `byteindex(haystack, needle: (str | rune))` which we don't have // the union-arg ABI for yet. export fn indexbyte(s: str, c: u8) (i32 | void) = { let i: i32 = 0; for (i < s.len) { if (s[i] == c) { return i; }; i += 1; }; return; }; // rindexbyte — last byte position of byte `c` in `s`. export fn rindexbyte(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; }; // rindex — last index of `sub` in `s`. Mirrors Hare's strings::rindex // (slice case). Empty `sub` matches at s.len. export fn rindex(s: str, sub: str) (i32 | void) = { 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; }; // sub — borrowed substring `s[start..end]`. Mirrors Hare's // strings::sub. Caller must ensure 0 <= start <= end <= s.len; out-of- // range indices are clamped silently here, where Hare aborts. export fn sub(s: str, start: i32, end: i32) str = { let lo: i32 = start; let hi: i32 = end; if (lo < 0) { lo = 0; }; if (hi > s.len) { hi = s.len; }; if (hi < lo) { hi = lo; }; let r: str; r.ptr = s.ptr + (lo: u64); r.len = hi - lo; return r; }; // trimprefix — `s` with `pre` stripped from the front, or `s` // unchanged if it doesn't start with `pre`. Returns a borrowed view. // Mirrors Hare's strings::trimprefix. export fn trimprefix(s: str, pre: str) str = { if (!hasprefix(s, pre)) { return s; }; let r: str; r.ptr = s.ptr + (pre.len: u64); r.len = s.len - pre.len; return r; }; // trimsuffix — `s` with `suf` stripped from the end, or `s` unchanged // if it doesn't end with `suf`. Returns a borrowed view. Mirrors // Hare's strings::trimsuffix. export fn trimsuffix(s: str, suf: str) str = { if (!hassuffix(s, suf)) { return s; }; let r: str; r.ptr = s.ptr; r.len = s.len - suf.len; return r; }; // ltrimbyte / rtrimbyte / trimbyte — strip occurrences of a single // byte from the left, right, or both ends. Returns a borrowed view. // Hare's strings::ltrim / rtrim / trim take a rune varargs set; ww's // subset takes a single byte (the common ASCII case). export fn ltrimbyte(s: str, c: u8) str = { let i: i32 = 0; for (i < s.len) { if (s[i] != c) { break; }; i += 1; }; let r: str; r.ptr = s.ptr + (i: u64); r.len = s.len - i; return r; }; export fn rtrimbyte(s: str, c: u8) str = { let n: i32 = s.len; for (n > 0) { if (s[n - 1] != c) { break; }; n -= 1; }; let r: str; r.ptr = s.ptr; r.len = n; return r; }; export fn trimbyte(s: str, c: u8) str = { return rtrimbyte(ltrimbyte(s, c), c); };