lib/strings+test: 0-arg trim strips ASCII whitespace per Hare

The 0-arg ltrim/rtrim/trim used to return input unchanged. Hare's
0-arg form strips [' ', '\n', '\t', '\r'] (ref/hare/strings/trim.ha:6).
Aligned by delegating to bytes.ltrim/bytes.rtrim with the whitespace
set spread inline at the call site — the obvious `let ws = whitespace[0:4]`
shape produces a slice whose ptr does NOT alias storage (#40).
N-arg forms (strip-specific-runes) untouched.

Test rows retargeted to Hare's canonical inputs from trim.ha:78/85
so '\r' is exercised alongside ' '/'\t'/'\n'.
This commit is contained in:
2026-05-19 22:52:23 +09:00
parent f827429215
commit 18fe1a7a31
5 changed files with 105 additions and 54 deletions

View File

@@ -3,10 +3,6 @@
//
// Documented divergences from Hare:
//
// - `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).
// - `byteindex` / `rbyteindex` rune arms encode via
// `utf8.encoderune`; the legacy impls scanned for `r: u8` (an
// undocumented ASCII-only restriction that silently dropped
@@ -402,12 +398,21 @@ export fn trimsuffix(input: str, suffix: str) str = {
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.
// 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.
// 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 input; };
if (trim.len == 0) {
return fromutf8_unsafe(bytes.ltrim(toutf8(input), whitespace[0:4]...));
};
let it: iterator = iter(input);
for (true) {
match (next(&it)) {
@@ -433,9 +438,13 @@ export fn ltrim(input: str, trim: rune...) str = {
};
// 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 input; };
if (trim.len == 0) {
return fromutf8_unsafe(bytes.rtrim(toutf8(input), whitespace[0:4]...));
};
let it: iterator = riter(input);
for (true) {
match (next(&it)) {

View File

@@ -446,9 +446,8 @@ fn streq(a: str, b: str) bool = {
};
// ---- 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.
// ref/hare/strings/trim.ha:75-97. 0-arg rows (#9) pin the ASCII
// whitespace set (' ', '\t', '\n', '\r' — ref/hare/strings/trim.ha:6).
@test fn ltrim_cases() void = {
let runes: [9]rune;
@@ -462,10 +461,12 @@ fn streq(a: str, b: str) bool = {
runes[7] = 'c';
runes[8] = 'd';
let inputs: [8]str;
let argo: [8]i32;
let argn: [8]i32;
let want: [8]str;
// 0-arg rows (8..10) strip ASCII whitespace per Hare (#9):
// only the leading side is stripped for ltrim.
let inputs: [11]str;
let argo: [11]i32;
let argn: [11]i32;
let want: [11]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";
@@ -474,9 +475,12 @@ fn streq(a: str, b: str) bool = {
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";
inputs[8]=" hello "; argo[8]=0; argn[8]=0; want[8]="hello ";
inputs[9]="\t\r\n hello"; argo[9]=0; argn[9]=0; want[9]="hello";
inputs[10]=" "; argo[10]=0; argn[10]=0; want[10]="";
let i: i32 = 0;
for (i < 8) {
for (i < 11) {
signalled = 1100 + i;
let argv: []rune;
argv.ptr = &runes[argo[i]];
@@ -510,10 +514,12 @@ fn streq(a: str, b: str) bool = {
runes[17] = 'a';
runes[18] = 'd';
let inputs: [8]str;
let argo: [8]i32;
let argn: [8]i32;
let want: [8]str;
// 0-arg rows (8..10) strip ASCII whitespace per Hare (#9):
// only the trailing side is stripped for rtrim.
let inputs: [11]str;
let argo: [11]i32;
let argn: [11]i32;
let want: [11]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";
@@ -524,9 +530,13 @@ fn streq(a: str, b: str) bool = {
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";
inputs[8]=" hello "; argo[8]=0; argn[8]=0; want[8]=" hello";
inputs[9]="hello, world\r\n\r\n";
argo[9]=0; argn[9]=0; want[9]="hello, world";
inputs[10]=" "; argo[10]=0; argn[10]=0; want[10]="";
let i: i32 = 0;
for (i < 8) {
for (i < 11) {
signalled = 1200 + i;
let argv: []rune;
argv.ptr = &runes[argo[i]];
@@ -549,10 +559,12 @@ fn streq(a: str, b: str) bool = {
runes[6] = '[';
runes[7] = ']';
let inputs: [7]str;
let argo: [7]i32;
let argn: [7]i32;
let want: [7]str;
// 0-arg rows (7..9) strip ASCII whitespace per Hare (#9) from
// both ends.
let inputs: [10]str;
let argo: [10]i32;
let argn: [10]i32;
let want: [10]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";
@@ -561,9 +573,12 @@ fn streq(a: str, b: str) bool = {
inputs[5]="[[][[[]]][][].[[]][]]][]]]";
argo[5]=6; argn[5]=2; want[5]=".";
inputs[6]="hello"; argo[6]=0; argn[6]=0; want[6]="hello";
inputs[7]=" hello "; argo[7]=0; argn[7]=0; want[7]="hello";
inputs[8]="\r\thello\n\r"; argo[8]=0; argn[8]=0; want[8]="hello";
inputs[9]=" "; argo[9]=0; argn[9]=0; want[9]="";
let i: i32 = 0;
for (i < 7) {
for (i < 10) {
signalled = 1300 + i;
let argv: []rune;
argv.ptr = &runes[argo[i]];

View File

@@ -1840,10 +1840,6 @@ export fn position(d: *decoder) i32 = {
//
// Documented divergences from Hare:
//
// - `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).
// - `byteindex` / `rbyteindex` rune arms encode via
// `utf8.encoderune`; the legacy impls scanned for `r: u8` (an
// undocumented ASCII-only restriction that silently dropped
@@ -2239,12 +2235,21 @@ export fn trimsuffix(input: str, suffix: str) str = {
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.
// 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.
// 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 input; };
if (trim.len == 0) {
return fromutf8_unsafe(bytes.ltrim(toutf8(input), whitespace[0:4]...));
};
let it: iterator = iter(input);
for (true) {
match (next(&it)) {
@@ -2270,9 +2275,13 @@ export fn ltrim(input: str, trim: rune...) str = {
};
// 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 input; };
if (trim.len == 0) {
return fromutf8_unsafe(bytes.rtrim(toutf8(input), whitespace[0:4]...));
};
let it: iterator = riter(input);
for (true) {
match (next(&it)) {

View File

@@ -1840,10 +1840,6 @@ export fn position(d: *decoder) i32 = {
//
// Documented divergences from Hare:
//
// - `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).
// - `byteindex` / `rbyteindex` rune arms encode via
// `utf8.encoderune`; the legacy impls scanned for `r: u8` (an
// undocumented ASCII-only restriction that silently dropped
@@ -2239,12 +2235,21 @@ export fn trimsuffix(input: str, suffix: str) str = {
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.
// 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.
// 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 input; };
if (trim.len == 0) {
return fromutf8_unsafe(bytes.ltrim(toutf8(input), whitespace[0:4]...));
};
let it: iterator = iter(input);
for (true) {
match (next(&it)) {
@@ -2270,9 +2275,13 @@ export fn ltrim(input: str, trim: rune...) str = {
};
// 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 input; };
if (trim.len == 0) {
return fromutf8_unsafe(bytes.rtrim(toutf8(input), whitespace[0:4]...));
};
let it: iterator = riter(input);
for (true) {
match (next(&it)) {

View File

@@ -1731,10 +1731,6 @@ export fn position(d: *decoder) i32 = {
//
// Documented divergences from Hare:
//
// - `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).
// - `byteindex` / `rbyteindex` rune arms encode via
// `utf8.encoderune`; the legacy impls scanned for `r: u8` (an
// undocumented ASCII-only restriction that silently dropped
@@ -2130,12 +2126,21 @@ export fn trimsuffix(input: str, suffix: str) str = {
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.
// 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.
// 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 input; };
if (trim.len == 0) {
return fromutf8_unsafe(bytes.ltrim(toutf8(input), whitespace[0:4]...));
};
let it: iterator = iter(input);
for (true) {
match (next(&it)) {
@@ -2161,9 +2166,13 @@ export fn ltrim(input: str, trim: rune...) str = {
};
// 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 input; };
if (trim.len == 0) {
return fromutf8_unsafe(bytes.rtrim(toutf8(input), whitespace[0:4]...));
};
let it: iterator = riter(input);
for (true) {
match (next(&it)) {