From c305b04cb52361c2ee7a808b209e59a3c1ca240a Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Tue, 19 May 2026 02:32:17 +0900 Subject: [PATCH] lib/strings+test: graduate trim/ltrim/rtrim to Hare rune... variadic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- lib/strings/strings.ww | 105 ++++++++++---------- lib/strings/stringstest.ww | 138 +++++++++++++++++++++++---- selfhost/cmd/w6c/main.combined.ww | 105 ++++++++++---------- selfhost/cmd/wwdump/main.combined.ww | 105 ++++++++++---------- selfhost/test/smoke.combined.ww | 105 ++++++++++---------- 5 files changed, 344 insertions(+), 214 deletions(-) diff --git a/lib/strings/strings.ww b/lib/strings/strings.ww index c8d3cb67..72dcf611 100644 --- a/lib/strings/strings.ww +++ b/lib/strings/strings.ww @@ -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 diff --git a/lib/strings/stringstest.ww b/lib/strings/stringstest.ww index 5a95db54..f0bb553a 100644 --- a/lib/strings/stringstest.ww +++ b/lib/strings/stringstest.ww @@ -250,32 +250,134 @@ fn streq(a: str, b: str) bool = { if (!streq(strings.trimsuffix("hello", "hello"), "")) { fail(); }; }; -// ---- ltrim / rtrim / trim (single-rune subset) ------------------------ -// ref/hare/strings/trim.ha:75-97. Vectors restricted to single-rune -// patterns (Hare's `rune...` blocks on task #16). +// ---- ltrim / rtrim / trim --------------------------------------------- +// ref/hare/strings/trim.ha:75-97. Bytes.ltrim whitespace-default rows +// (`ltrim(" hi") == "hi"`, etc.) deferred — they need lib/bytes +// variadic graduation. @test fn ltrim_cases() void = { - if (!streq(strings.ltrim("", 'x'), "")) { fail(); }; - if (!streq(strings.ltrim("aaabc", 'a'), "bc")) { fail(); }; - if (!streq(strings.ltrim("xyz", 'a'), "xyz")) { fail(); }; // no match - if (!streq(strings.ltrim("aaaa", 'a'), "")) { fail(); }; // all stripped - // 4-byte rune pattern — '𝚊' = U+1D68A. - if (!streq(strings.ltrim("𝚊𝚊hi", 0x1D68Au32: rune), "hi")) { fail(); }; + let runes: [9]rune; + runes[0] = 'x'; + runes[1] = 'a'; + runes[2] = 0x1D68Au32: rune; + runes[3] = '('; + runes[4] = ')'; + runes[5] = 'a'; + runes[6] = 'b'; + runes[7] = 'c'; + runes[8] = 'd'; + + let inputs: [8]str; + let argo: [8]i32; + let argn: [8]i32; + let want: [8]str; + inputs[0]=""; argo[0]=0; argn[0]=1; want[0]=""; + inputs[1]="aaabc"; argo[1]=1; argn[1]=1; want[1]="bc"; + inputs[2]="xyz"; argo[2]=1; argn[2]=1; want[2]="xyz"; + inputs[3]="aaaa"; argo[3]=1; argn[3]=1; want[3]=""; + inputs[4]="𝚊𝚊hi"; argo[4]=2; argn[4]=1; want[4]="hi"; + inputs[5]="((()(())))())"; argo[5]=3; argn[5]=2; want[5]=""; + inputs[6]="abacadabra"; argo[6]=5; argn[6]=4; want[6]="ra"; + inputs[7]="hello"; argo[7]=0; argn[7]=0; want[7]="hello"; + + let i: i32 = 0; + for (i < 8) { + signalled = 1100 + i; + let argv: []rune; + argv.ptr = &runes[argo[i]]; + argv.len = argn[i]; + argv.cap = argn[i]; + let got: str = strings.ltrim(inputs[i], argv...); + if (!streq(got, want[i])) { fail(); }; + i += 1; + }; }; @test fn rtrim_cases() void = { - if (!streq(strings.rtrim("", 'x'), "")) { fail(); }; - if (!streq(strings.rtrim("bcaaa", 'a'), "bc")) { fail(); }; - if (!streq(strings.rtrim("xyz", 'a'), "xyz")) { fail(); }; - if (!streq(strings.rtrim("aaaa", 'a'), "")) { fail(); }; - if (!streq(strings.rtrim("hi𝚊𝚊", 0x1D68Au32: rune), "hi")) { fail(); }; + let runes: [19]rune; + runes[0] = 'x'; + runes[1] = 'a'; + runes[2] = 0x1D68Au32: rune; + runes[3] = 'w'; + runes[4] = 'd'; + runes[5] = 'o'; + runes[6] = 'r'; + runes[7] = ' '; + runes[8] = 's'; + runes[9] = 'i'; + runes[10] = 'l'; + runes[11] = 'z'; + runes[12] = 't'; + runes[13] = 'm'; + runes[14] = 'n'; + runes[15] = 'o'; + runes[16] = 'e'; + runes[17] = 'a'; + runes[18] = 'd'; + + let inputs: [8]str; + let argo: [8]i32; + let argn: [8]i32; + let want: [8]str; + inputs[0]=""; argo[0]=0; argn[0]=1; want[0]=""; + inputs[1]="bcaaa"; argo[1]=1; argn[1]=1; want[1]="bc"; + inputs[2]="xyz"; argo[2]=1; argn[2]=1; want[2]="xyz"; + inputs[3]="aaaa"; argo[3]=1; argn[3]=1; want[3]=""; + inputs[4]="hi𝚊𝚊"; argo[4]=2; argn[4]=1; want[4]="hi"; + inputs[5]="yellowwooddoor"; + argo[5]=3; argn[5]=4; want[5]="yell"; + inputs[6]="Sentimentalized sensationalism sensationalized sentimentalisms"; + argo[6]=7; argn[6]=12; want[6]="S"; + inputs[7]="hello"; argo[7]=0; argn[7]=0; want[7]="hello"; + + let i: i32 = 0; + for (i < 8) { + signalled = 1200 + i; + let argv: []rune; + argv.ptr = &runes[argo[i]]; + argv.len = argn[i]; + argv.cap = argn[i]; + let got: str = strings.rtrim(inputs[i], argv...); + if (!streq(got, want[i])) { fail(); }; + i += 1; + }; }; @test fn trim_cases() void = { - if (!streq(strings.trim("", 'x'), "")) { fail(); }; - if (!streq(strings.trim("aaabcaaa", 'a'), "bc")) { fail(); }; - if (!streq(strings.trim("xyz", 'a'), "xyz")) { fail(); }; - if (!streq(strings.trim("aaaa", 'a'), "")) { fail(); }; + let runes: [8]rune; + runes[0] = 'x'; + runes[1] = 'a'; + runes[2] = 'm'; + runes[3] = 'i'; + runes[4] = 'p'; + runes[5] = 's'; + runes[6] = '['; + runes[7] = ']'; + + let inputs: [7]str; + let argo: [7]i32; + let argn: [7]i32; + let want: [7]str; + inputs[0]=""; argo[0]=0; argn[0]=1; want[0]=""; + inputs[1]="aaabcaaa"; argo[1]=1; argn[1]=1; want[1]="bc"; + inputs[2]="xyz"; argo[2]=1; argn[2]=1; want[2]="xyz"; + inputs[3]="aaaa"; argo[3]=1; argn[3]=1; want[3]=""; + inputs[4]="mississippi"; argo[4]=2; argn[4]=4; want[4]=""; + inputs[5]="[[][[[]]][][].[[]][]]][]]]"; + argo[5]=6; argn[5]=2; want[5]="."; + inputs[6]="hello"; argo[6]=0; argn[6]=0; want[6]="hello"; + + let i: i32 = 0; + for (i < 7) { + signalled = 1300 + i; + let argv: []rune; + argv.ptr = &runes[argo[i]]; + argv.len = argn[i]; + argv.cap = argn[i]; + let got: str = strings.trim(inputs[i], argv...); + if (!streq(got, want[i])) { fail(); }; + i += 1; + }; }; // ---- compare ---------------------------------------------------------- diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 35f0e66b..f4d8fd21 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -1445,14 +1445,10 @@ export fn position(d: *decoder) i32 = { // // 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)...` @@ -1685,56 +1681,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 diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 316c5139..9281e328 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -1445,14 +1445,10 @@ export fn position(d: *decoder) i32 = { // // 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)...` @@ -1685,56 +1681,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 diff --git a/selfhost/test/smoke.combined.ww b/selfhost/test/smoke.combined.ww index 8868c60a..efa16c24 100644 --- a/selfhost/test/smoke.combined.ww +++ b/selfhost/test/smoke.combined.ww @@ -1336,14 +1336,10 @@ export fn position(d: *decoder) i32 = { // // 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)...` @@ -1576,56 +1572,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