lib/bytes+test: port ltrim/rtrim/trim from Hare (u8... variadic)

This commit is contained in:
2026-05-19 16:19:12 +09:00
parent e5c1d6baa4
commit 8683202a4c
5 changed files with 240 additions and 0 deletions

View File

@@ -142,6 +142,40 @@ export fn contains(s: []u8, needles: (u8 | []u8)...) bool = {
return false;
};
// ltrim — borrowed view of `in` with leading bytes in `trim` stripped.
// `trim` must be non-empty. ref/hare/bytes/trim.ha:7.
export fn ltrim(in: []u8, trim: u8...) []u8 = {
os.assert(trim.len > 0, "bytes.ltrim called with empty trim set");
let i: i32 = 0;
for (i < in.len && contains(trim, in[i])) { i += 1; };
let r: []u8;
r.ptr = in.ptr + (i: u64);
r.len = in.len - i;
r.cap = r.len;
return r;
};
// rtrim — borrowed view of `in` with trailing bytes in `trim` stripped.
// `trim` must be non-empty. ref/hare/bytes/trim.ha:17. Hare's loop uses
// `size` underflow at i==0 to terminate; ww indices are signed i32, so
// the equivalent termination is spelled `i >= 0` explicitly.
export fn rtrim(in: []u8, trim: u8...) []u8 = {
os.assert(trim.len > 0, "bytes.rtrim called with empty trim set");
let i: i32 = in.len - 1;
for (i >= 0 && contains(trim, in[i])) { i -= 1; };
let r: []u8;
r.ptr = in.ptr;
r.len = i + 1;
r.cap = r.len;
return r;
};
// trim — borrowed view of `in` with both ends in `trim` stripped.
// ref/hare/bytes/trim.ha:27.
export fn trim(in: []u8, trim: u8...) []u8 = {
return ltrim(rtrim(in, trim...), trim...);
};
// hasprefix — true iff `s` starts with `pre`.
// ref/hare/bytes/contains.ha:21.
export fn hasprefix(s: []u8, pre: []u8) bool = {

View File

@@ -467,6 +467,107 @@ fn expect_done(t: *bytes.tokenizer) void = {
if (!bytes.equal(r2, e_12[0:2])) { fail(); };
};
// ---- ltrim / rtrim / trim ---------------------------------------------
// ref/hare/bytes/trim.ha:29 — Hare's @test fn trim pins
// `trim([0,1,2,3,5,0], 0) == [1,2,3,5]`, `trim([0,0,0], 0) == []`,
// `trim([], 0) == []`. ww spreads the matrix across ltrim/rtrim/trim.
fn beq(got: []u8, want: []u8) bool = {
if (got.len != want.len) { return false; };
let i: i32 = 0;
for (i < got.len) {
if (got[i] != want[i]) { return false; };
i += 1;
};
return true;
};
@test fn ltrim_cases() void = {
let z: [1]u8;
// [0,0,1,2] / 0 -> [1,2]
signalled = 1780;
let a: [4]u8; a[0] = 0u8; a[1] = 0u8; a[2] = 1u8; a[3] = 2u8;
let ea: [2]u8; ea[0] = 1u8; ea[1] = 2u8;
if (!beq(bytes.ltrim(a[0:4], 0u8), ea[0:2])) { fail(); };
// [1,2,3] / 0 -> [1,2,3] (no leading match)
signalled = 1781;
let b: [3]u8; b[0] = 1u8; b[1] = 2u8; b[2] = 3u8;
if (!beq(bytes.ltrim(b[0:3], 0u8), b[0:3])) { fail(); };
// [0,0,0] / 0 -> [] (full match)
signalled = 1782;
let c: [3]u8;
if (!beq(bytes.ltrim(c[0:3], 0u8), z[0:0])) { fail(); };
// [] / 0 -> [] (empty input)
signalled = 1783;
if (!beq(bytes.ltrim(z[0:0], 0u8), z[0:0])) { fail(); };
};
@test fn rtrim_cases() void = {
let z: [1]u8;
// [1,2,0,0] / 0 -> [1,2]
signalled = 1790;
let a: [4]u8; a[0] = 1u8; a[1] = 2u8; a[2] = 0u8; a[3] = 0u8;
let ea: [2]u8; ea[0] = 1u8; ea[1] = 2u8;
if (!beq(bytes.rtrim(a[0:4], 0u8), ea[0:2])) { fail(); };
// [1,2,3] / 0 -> [1,2,3] (no trailing match)
signalled = 1791;
let b: [3]u8; b[0] = 1u8; b[1] = 2u8; b[2] = 3u8;
if (!beq(bytes.rtrim(b[0:3], 0u8), b[0:3])) { fail(); };
// [0,0,0] / 0 -> [] (full match)
signalled = 1792;
let c: [3]u8;
if (!beq(bytes.rtrim(c[0:3], 0u8), z[0:0])) { fail(); };
// [] / 0 -> [] (empty input)
signalled = 1793;
if (!beq(bytes.rtrim(z[0:0], 0u8), z[0:0])) { fail(); };
};
@test fn trim_cases() void = {
let z: [1]u8;
// [0,1,2,3,5,0] / 0 -> [1,2,3,5]
signalled = 1800;
let a: [6]u8;
a[0] = 0u8; a[1] = 1u8; a[2] = 2u8;
a[3] = 3u8; a[4] = 5u8; a[5] = 0u8;
let ea: [4]u8; ea[0] = 1u8; ea[1] = 2u8; ea[2] = 3u8; ea[3] = 5u8;
if (!beq(bytes.trim(a[0:6], 0u8), ea[0:4])) { fail(); };
// [0,5,0] / 5 -> [0,5,0] (only 5 in trim set; boundary mismatch)
signalled = 1801;
let b: [3]u8; b[0] = 0u8; b[1] = 5u8; b[2] = 0u8;
if (!beq(bytes.trim(b[0:3], 5u8), b[0:3])) { fail(); };
// [0,1,42,1,0] / {0,42} -> [1,42,1] (multi-byte trim set)
signalled = 1802;
let c: [5]u8;
c[0] = 0u8; c[1] = 1u8; c[2] = 42u8; c[3] = 1u8; c[4] = 0u8;
let ec: [3]u8; ec[0] = 1u8; ec[1] = 42u8; ec[2] = 1u8;
if (!beq(bytes.trim(c[0:5], 0u8, 42u8), ec[0:3])) { fail(); };
// [0,0,0] / 0 -> [] (full match)
signalled = 1803;
let d: [3]u8;
if (!beq(bytes.trim(d[0:3], 0u8), z[0:0])) { fail(); };
// [] / 0 -> [] (empty input, Hare ref/hare/bytes/trim.ha:34)
signalled = 1804;
if (!beq(bytes.trim(z[0:0], 0u8), z[0:0])) { fail(); };
// [1,2,3,5] / 0 -> [1,2,3,5] (Hare ref/hare/bytes/trim.ha:31)
signalled = 1805;
let e: [4]u8; e[0] = 1u8; e[1] = 2u8; e[2] = 3u8; e[3] = 5u8;
if (!beq(bytes.trim(e[0:4], 0u8), e[0:4])) { fail(); };
};
// ---- splitn / rsplitn / split -----------------------------------------
// ref/hare/bytes/tokenize.ha:330 (@test fn split). Hare's table mixes
// strings; ww spells the vectors as byte arrays explicitly.
@@ -662,5 +763,8 @@ export fn main() i32 = {
signalled = 13; splitn_cases();
signalled = 14; rsplitn_cases();
signalled = 15; split_cases();
signalled = 16; ltrim_cases();
signalled = 17; rtrim_cases();
signalled = 18; trim_cases();
return 0;
};

View File

@@ -1023,6 +1023,40 @@ export fn contains(s: []u8, needles: (u8 | []u8)...) bool = {
return false;
};
// ltrim — borrowed view of `in` with leading bytes in `trim` stripped.
// `trim` must be non-empty. ref/hare/bytes/trim.ha:7.
export fn ltrim(in: []u8, trim: u8...) []u8 = {
os.assert(trim.len > 0, "bytes.ltrim called with empty trim set");
let i: i32 = 0;
for (i < in.len && contains(trim, in[i])) { i += 1; };
let r: []u8;
r.ptr = in.ptr + (i: u64);
r.len = in.len - i;
r.cap = r.len;
return r;
};
// rtrim — borrowed view of `in` with trailing bytes in `trim` stripped.
// `trim` must be non-empty. ref/hare/bytes/trim.ha:17. Hare's loop uses
// `size` underflow at i==0 to terminate; ww indices are signed i32, so
// the equivalent termination is spelled `i >= 0` explicitly.
export fn rtrim(in: []u8, trim: u8...) []u8 = {
os.assert(trim.len > 0, "bytes.rtrim called with empty trim set");
let i: i32 = in.len - 1;
for (i >= 0 && contains(trim, in[i])) { i -= 1; };
let r: []u8;
r.ptr = in.ptr;
r.len = i + 1;
r.cap = r.len;
return r;
};
// trim — borrowed view of `in` with both ends in `trim` stripped.
// ref/hare/bytes/trim.ha:27.
export fn trim(in: []u8, trim: u8...) []u8 = {
return ltrim(rtrim(in, trim...), trim...);
};
// hasprefix — true iff `s` starts with `pre`.
// ref/hare/bytes/contains.ha:21.
export fn hasprefix(s: []u8, pre: []u8) bool = {

View File

@@ -1023,6 +1023,40 @@ export fn contains(s: []u8, needles: (u8 | []u8)...) bool = {
return false;
};
// ltrim — borrowed view of `in` with leading bytes in `trim` stripped.
// `trim` must be non-empty. ref/hare/bytes/trim.ha:7.
export fn ltrim(in: []u8, trim: u8...) []u8 = {
os.assert(trim.len > 0, "bytes.ltrim called with empty trim set");
let i: i32 = 0;
for (i < in.len && contains(trim, in[i])) { i += 1; };
let r: []u8;
r.ptr = in.ptr + (i: u64);
r.len = in.len - i;
r.cap = r.len;
return r;
};
// rtrim — borrowed view of `in` with trailing bytes in `trim` stripped.
// `trim` must be non-empty. ref/hare/bytes/trim.ha:17. Hare's loop uses
// `size` underflow at i==0 to terminate; ww indices are signed i32, so
// the equivalent termination is spelled `i >= 0` explicitly.
export fn rtrim(in: []u8, trim: u8...) []u8 = {
os.assert(trim.len > 0, "bytes.rtrim called with empty trim set");
let i: i32 = in.len - 1;
for (i >= 0 && contains(trim, in[i])) { i -= 1; };
let r: []u8;
r.ptr = in.ptr;
r.len = i + 1;
r.cap = r.len;
return r;
};
// trim — borrowed view of `in` with both ends in `trim` stripped.
// ref/hare/bytes/trim.ha:27.
export fn trim(in: []u8, trim: u8...) []u8 = {
return ltrim(rtrim(in, trim...), trim...);
};
// hasprefix — true iff `s` starts with `pre`.
// ref/hare/bytes/contains.ha:21.
export fn hasprefix(s: []u8, pre: []u8) bool = {

View File

@@ -914,6 +914,40 @@ export fn contains(s: []u8, needles: (u8 | []u8)...) bool = {
return false;
};
// ltrim — borrowed view of `in` with leading bytes in `trim` stripped.
// `trim` must be non-empty. ref/hare/bytes/trim.ha:7.
export fn ltrim(in: []u8, trim: u8...) []u8 = {
os.assert(trim.len > 0, "bytes.ltrim called with empty trim set");
let i: i32 = 0;
for (i < in.len && contains(trim, in[i])) { i += 1; };
let r: []u8;
r.ptr = in.ptr + (i: u64);
r.len = in.len - i;
r.cap = r.len;
return r;
};
// rtrim — borrowed view of `in` with trailing bytes in `trim` stripped.
// `trim` must be non-empty. ref/hare/bytes/trim.ha:17. Hare's loop uses
// `size` underflow at i==0 to terminate; ww indices are signed i32, so
// the equivalent termination is spelled `i >= 0` explicitly.
export fn rtrim(in: []u8, trim: u8...) []u8 = {
os.assert(trim.len > 0, "bytes.rtrim called with empty trim set");
let i: i32 = in.len - 1;
for (i >= 0 && contains(trim, in[i])) { i -= 1; };
let r: []u8;
r.ptr = in.ptr;
r.len = i + 1;
r.cap = r.len;
return r;
};
// trim — borrowed view of `in` with both ends in `trim` stripped.
// ref/hare/bytes/trim.ha:27.
export fn trim(in: []u8, trim: u8...) []u8 = {
return ltrim(rtrim(in, trim...), trim...);
};
// hasprefix — true iff `s` starts with `pre`.
// ref/hare/bytes/contains.ha:21.
export fn hasprefix(s: []u8, pre: []u8) bool = {