lib/strings+test: graduate trim/ltrim/rtrim to Hare rune... variadic

ltrim(input, exclude: rune) -> ltrim(input, trim: rune...) per
ref/hare/strings/trim.ha:11. rtrim mirrors via riter/prev per :32.
trim thin wrapper ltrim(rtrim(input, trim...), trim...) per :54.
Unblocked by #15 (5609d04) — multi-rune iter+match-prev composition
in non-leaf callees now produces byte-identical asm + correct runtime
on both stages.

Subset: 0-arg call (len(trim)==0) returns input unchanged. lib/bytes
whitespace-default future commit will graduate strings.trim's
whitespace... fast path.

ltrim_cases / rtrim_cases / trim_cases table-driven via parallel runes
+ inputs + argo/argn + want arrays + loop. Hare ref/hare/strings/trim.ha:75
vectors covered: bracket-pair, "mississippi" multi-rune, "yellowwooddoor"
multi-rune, Sentimentalized long form, plus 0-arg cases and 4-byte rune
"abacadabra"/"ahi" coverage. Bisect via signalled = 1100/1200/1300 + i.

make test 122/122; ww2==ww3==ww4 byte-id holds via 995_self_rebuild.
This commit is contained in:
2026-05-19 02:32:17 +09:00
parent 5609d0456f
commit c305b04cb5
5 changed files with 344 additions and 214 deletions

View File

@@ -3,14 +3,10 @@
//
// Documented divergences from Hare:
//
// - `trim` / `ltrim` / `rtrim` take a single rune. Hare's are
// `(trim: rune...)` (ref/hare/strings/trim.ha:11,32,54). The
// port body uses `iter`/`next` + inner match against the pack +
// `prev` step-back; that shape triggers #36 (wwstage scanlocals
// misses match-arm `case let` bindings, slot offsets diverge —
// `.ai/probe_trim_36extra.{ww,diff}`). Hare's no-rune
// strip-whitespace branch additionally needs `lib/bytes`
// variadic graduation.
// - `trim` / `ltrim` / `rtrim` 0-arg returns the input unchanged.
// Hare strips ASCII whitespace via `bytes::ltrim(input,
// whitespace...)`; that needs `lib/bytes` variadic graduation
// (future commit).
// - `contains` is non-variadic. Hare's is
// `contains(haystack, needles: (str | rune)...)`
// (ref/hare/strings/contains.ha:9). Gated on `(str|rune)...`
@@ -243,56 +239,67 @@ export fn trimsuffix(input: str, suffix: str) str = {
return r;
};
// ltrim — strip occurrences of `exclude` (encoded as UTF-8) from the
// front. Borrowed view. ref/hare/strings/trim.ha:11 (subset: single
// rune; Hare's `(trim: rune...)` blocks on task #16). The no-rune
// strip-whitespace branch is omitted for the same reason.
export fn ltrim(input: str, exclude: rune) str = {
let scratch: [4]u8;
let pat: []u8 = runebytes(scratch[0:4], exclude);
let i: i32 = 0;
for (i + pat.len <= input.len) {
let j: i32 = 0;
let ok: bool = true;
for (j < pat.len) {
if (input[i + j] != pat[j]) { ok = false; j = pat.len; }
else { j += 1; };
// ltrim — strip leading runes that occur in `trim`. Borrowed view.
// Empty `trim` returns input unchanged (Hare's no-rune branch strips
// ASCII whitespace via `bytes::ltrim`; needs lib/bytes variadic
// graduation). ref/hare/strings/trim.ha:11.
export fn ltrim(input: str, trim: rune...) str = {
if (trim.len == 0) { return input; };
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;
};
if (!ok) { break; };
i += pat.len;
};
let r: str;
r.ptr = input.ptr + (i: u64);
r.len = input.len - i;
return r;
return iterstr(&it);
};
// rtrim — strip occurrences of `exclude` from the end. Borrowed view.
// ref/hare/strings/trim.ha:32 (same subset note).
export fn rtrim(input: str, exclude: rune) str = {
let scratch: [4]u8;
let pat: []u8 = runebytes(scratch[0:4], exclude);
let n: i32 = input.len;
for (n >= pat.len) {
let off: i32 = n - pat.len;
let j: i32 = 0;
let ok: bool = true;
for (j < pat.len) {
if (input[off + j] != pat[j]) { ok = false; j = pat.len; }
else { j += 1; };
// rtrim — strip trailing runes that occur in `trim`. Borrowed view.
// ref/hare/strings/trim.ha:32.
export fn rtrim(input: str, trim: rune...) str = {
if (trim.len == 0) { return input; };
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;
};
if (!ok) { break; };
n -= pat.len;
};
let r: str;
r.ptr = input.ptr;
r.len = n;
return r;
return iterstr(&it);
};
// trim — strip from both ends. ref/hare/strings/trim.ha:54.
export fn trim(input: str, exclude: rune) str = {
return ltrim(rtrim(input, exclude), exclude);
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