lib/strings: add cut and rcut

This commit is contained in:
2026-06-01 16:26:11 +09:00
parent 2b893b9353
commit 38a906cd9b
8 changed files with 232 additions and 0 deletions

View File

@@ -720,6 +720,23 @@ export fn remaining_tokens(s: *tokenizer) str = {
return frombytes(bytes.remaining_tokens(b));
};
// cut — split `in` along the first instance of `delim`, returning the
// portions before and after it. When `delim` is absent the whole input
// is the first half and the second is empty. Both halves are borrowed
// from `in`; caller ensures `delim` is non-empty.
// ref/hare/strings/tokenize.ha:288.
export fn cut(in: str, delim: str) (str, str) = {
let (a, b) = bytes.cut(toutf8(in), toutf8(delim));
return (frombytes(a), frombytes(b));
};
// rcut — like [[cut]] but split along the LAST instance of `delim`.
// ref/hare/strings/tokenize.ha:302.
export fn rcut(in: str, delim: str) (str, str) = {
let (a, b) = bytes.rcut(toutf8(in), toutf8(delim));
return (frombytes(a), frombytes(b));
};
// rt_ensure is the runtime slice-growth helper invoked by the
// `append(s, v)` builtin. Direct bind for the same reason as
// lib/shlex.shlex (appendstr, 16B): the builtin's expansion stores

View File

@@ -1685,6 +1685,117 @@ fn expect_str(toks: []str, i: i32, want: str) void = {
};
};
// ---- cut / rcut -------------------------------------------------------
// ref/hare/strings/tokenize.ha:316 (@test fn cut). str wrappers over
// bytes.cut/rcut. The `let (a, b) = cut(...)` destructure drives the
// over-cap tuple-return (sret) path end-to-end (a 2nd #10 witness).
@test fn cut_cases() void = {
// both halves present.
signalled = 1830;
let (a0, b0) = strings.cut("hello=world", "=");
if (!streq(a0, "hello")) { fail(); };
if (!streq(b0, "world")) { fail(); };
// only first instance is cut; rest stays in the second half.
signalled = 1831;
let (a1, b1) = strings.cut("hello=world=foobar", "=");
if (!streq(a1, "hello")) { fail(); };
if (!streq(b1, "world=foobar")) { fail(); };
// delim absent -> (whole input, "").
signalled = 1832;
let (a2, b2) = strings.cut("hello world", "=");
if (!streq(a2, "hello world")) { fail(); };
if (!streq(b2, "")) { fail(); };
// delim at start -> empty before.
signalled = 1833;
let (a3, b3) = strings.cut("=world", "=");
if (!streq(a3, "")) { fail(); };
if (!streq(b3, "world")) { fail(); };
// delim at end -> empty after.
signalled = 1834;
let (a4, b4) = strings.cut("hello=", "=");
if (!streq(a4, "hello")) { fail(); };
if (!streq(b4, "")) { fail(); };
// empty input -> ("", "").
signalled = 1835;
let (a5, b5) = strings.cut("", "=");
if (!streq(a5, "")) { fail(); };
if (!streq(b5, "")) { fail(); };
// multi-byte delim present.
signalled = 1836;
let (a6, b6) = strings.cut("aXYbXYc", "XY");
if (!streq(a6, "a")) { fail(); };
if (!streq(b6, "bXYc")) { fail(); };
// multi-byte delim absent.
signalled = 1837;
let (a7, b7) = strings.cut("abc", "XY");
if (!streq(a7, "abc")) { fail(); };
if (!streq(b7, "")) { fail(); };
// borrowed, not copied: first half aliases the input bytes.
signalled = 1838;
let in: str = "hello=world";
let (a8, b8) = strings.cut(in, "=");
if (a8.ptr != in.ptr) { fail(); };
};
@test fn rcut_cases() void = {
// rcut splits along the LAST instance.
signalled = 1840;
let (a0, b0) = strings.rcut("hello=world=foobar", "=");
if (!streq(a0, "hello=world")) { fail(); };
if (!streq(b0, "foobar")) { fail(); };
// single instance == cut.
signalled = 1841;
let (a1, b1) = strings.rcut("hello=world", "=");
if (!streq(a1, "hello")) { fail(); };
if (!streq(b1, "world")) { fail(); };
// delim absent -> (whole input, "").
signalled = 1842;
let (a2, b2) = strings.rcut("hello world", "=");
if (!streq(a2, "hello world")) { fail(); };
if (!streq(b2, "")) { fail(); };
// delim at end -> empty after.
signalled = 1843;
let (a3, b3) = strings.rcut("hello=", "=");
if (!streq(a3, "hello")) { fail(); };
if (!streq(b3, "")) { fail(); };
// delim at start -> empty before.
signalled = 1844;
let (a4, b4) = strings.rcut("=world", "=");
if (!streq(a4, "")) { fail(); };
if (!streq(b4, "world")) { fail(); };
// empty input -> ("", "").
signalled = 1845;
let (a5, b5) = strings.rcut("", "=");
if (!streq(a5, "")) { fail(); };
if (!streq(b5, "")) { fail(); };
// multi-byte delim, two instances -> cut at LAST.
signalled = 1846;
let (a6, b6) = strings.rcut("XYaXYb", "XY");
if (!streq(a6, "XYa")) { fail(); };
if (!streq(b6, "b")) { fail(); };
// multi-byte delim absent.
signalled = 1847;
let (a7, b7) = strings.rcut("abc", "XY");
if (!streq(a7, "abc")) { fail(); };
if (!streq(b7, "")) { fail(); };
};
export fn main() i32 = {
signalled = 1; dup_cases();
signalled = 42; dupall_cases();
@@ -1729,5 +1840,7 @@ export fn main() i32 = {
signalled = 38; lpad_cases();
signalled = 39; rpad_cases();
signalled = 44; replace_cases();
signalled = 45; cut_cases();
signalled = 46; rcut_cases();
return 0;
};

View File

@@ -2588,6 +2588,23 @@ export fn remaining_tokens(s: *tokenizer) str = {
return frombytes(bytes.remaining_tokens(b));
};
// cut — split `in` along the first instance of `delim`, returning the
// portions before and after it. When `delim` is absent the whole input
// is the first half and the second is empty. Both halves are borrowed
// from `in`; caller ensures `delim` is non-empty.
// ref/hare/strings/tokenize.ha:288.
export fn cut(in: str, delim: str) (str, str) = {
let (a, b) = bytes.cut(toutf8(in), toutf8(delim));
return (frombytes(a), frombytes(b));
};
// rcut — like [[cut]] but split along the LAST instance of `delim`.
// ref/hare/strings/tokenize.ha:302.
export fn rcut(in: str, delim: str) (str, str) = {
let (a, b) = bytes.rcut(toutf8(in), toutf8(delim));
return (frombytes(a), frombytes(b));
};
// rt_ensure is the runtime slice-growth helper invoked by the
// `append(s, v)` builtin. Direct bind for the same reason as
// lib/shlex.shlex (appendstr, 16B): the builtin's expansion stores

View File

@@ -2588,6 +2588,23 @@ export fn remaining_tokens(s: *tokenizer) str = {
return frombytes(bytes.remaining_tokens(b));
};
// cut — split `in` along the first instance of `delim`, returning the
// portions before and after it. When `delim` is absent the whole input
// is the first half and the second is empty. Both halves are borrowed
// from `in`; caller ensures `delim` is non-empty.
// ref/hare/strings/tokenize.ha:288.
export fn cut(in: str, delim: str) (str, str) = {
let (a, b) = bytes.cut(toutf8(in), toutf8(delim));
return (frombytes(a), frombytes(b));
};
// rcut — like [[cut]] but split along the LAST instance of `delim`.
// ref/hare/strings/tokenize.ha:302.
export fn rcut(in: str, delim: str) (str, str) = {
let (a, b) = bytes.rcut(toutf8(in), toutf8(delim));
return (frombytes(a), frombytes(b));
};
// rt_ensure is the runtime slice-growth helper invoked by the
// `append(s, v)` builtin. Direct bind for the same reason as
// lib/shlex.shlex (appendstr, 16B): the builtin's expansion stores

View File

@@ -2697,6 +2697,23 @@ export fn remaining_tokens(s: *tokenizer) str = {
return frombytes(bytes.remaining_tokens(b));
};
// cut — split `in` along the first instance of `delim`, returning the
// portions before and after it. When `delim` is absent the whole input
// is the first half and the second is empty. Both halves are borrowed
// from `in`; caller ensures `delim` is non-empty.
// ref/hare/strings/tokenize.ha:288.
export fn cut(in: str, delim: str) (str, str) = {
let (a, b) = bytes.cut(toutf8(in), toutf8(delim));
return (frombytes(a), frombytes(b));
};
// rcut — like [[cut]] but split along the LAST instance of `delim`.
// ref/hare/strings/tokenize.ha:302.
export fn rcut(in: str, delim: str) (str, str) = {
let (a, b) = bytes.rcut(toutf8(in), toutf8(delim));
return (frombytes(a), frombytes(b));
};
// rt_ensure is the runtime slice-growth helper invoked by the
// `append(s, v)` builtin. Direct bind for the same reason as
// lib/shlex.shlex (appendstr, 16B): the builtin's expansion stores

View File

@@ -2588,6 +2588,23 @@ export fn remaining_tokens(s: *tokenizer) str = {
return frombytes(bytes.remaining_tokens(b));
};
// cut — split `in` along the first instance of `delim`, returning the
// portions before and after it. When `delim` is absent the whole input
// is the first half and the second is empty. Both halves are borrowed
// from `in`; caller ensures `delim` is non-empty.
// ref/hare/strings/tokenize.ha:288.
export fn cut(in: str, delim: str) (str, str) = {
let (a, b) = bytes.cut(toutf8(in), toutf8(delim));
return (frombytes(a), frombytes(b));
};
// rcut — like [[cut]] but split along the LAST instance of `delim`.
// ref/hare/strings/tokenize.ha:302.
export fn rcut(in: str, delim: str) (str, str) = {
let (a, b) = bytes.rcut(toutf8(in), toutf8(delim));
return (frombytes(a), frombytes(b));
};
// rt_ensure is the runtime slice-growth helper invoked by the
// `append(s, v)` builtin. Direct bind for the same reason as
// lib/shlex.shlex (appendstr, 16B): the builtin's expansion stores

View File

@@ -4105,6 +4105,23 @@ export fn remaining_tokens(s: *tokenizer) str = {
return frombytes(bytes.remaining_tokens(b));
};
// cut — split `in` along the first instance of `delim`, returning the
// portions before and after it. When `delim` is absent the whole input
// is the first half and the second is empty. Both halves are borrowed
// from `in`; caller ensures `delim` is non-empty.
// ref/hare/strings/tokenize.ha:288.
export fn cut(in: str, delim: str) (str, str) = {
let (a, b) = bytes.cut(toutf8(in), toutf8(delim));
return (frombytes(a), frombytes(b));
};
// rcut — like [[cut]] but split along the LAST instance of `delim`.
// ref/hare/strings/tokenize.ha:302.
export fn rcut(in: str, delim: str) (str, str) = {
let (a, b) = bytes.rcut(toutf8(in), toutf8(delim));
return (frombytes(a), frombytes(b));
};
// rt_ensure is the runtime slice-growth helper invoked by the
// `append(s, v)` builtin. Direct bind for the same reason as
// lib/shlex.shlex (appendstr, 16B): the builtin's expansion stores

View File

@@ -4105,6 +4105,23 @@ export fn remaining_tokens(s: *tokenizer) str = {
return frombytes(bytes.remaining_tokens(b));
};
// cut — split `in` along the first instance of `delim`, returning the
// portions before and after it. When `delim` is absent the whole input
// is the first half and the second is empty. Both halves are borrowed
// from `in`; caller ensures `delim` is non-empty.
// ref/hare/strings/tokenize.ha:288.
export fn cut(in: str, delim: str) (str, str) = {
let (a, b) = bytes.cut(toutf8(in), toutf8(delim));
return (frombytes(a), frombytes(b));
};
// rcut — like [[cut]] but split along the LAST instance of `delim`.
// ref/hare/strings/tokenize.ha:302.
export fn rcut(in: str, delim: str) (str, str) = {
let (a, b) = bytes.rcut(toutf8(in), toutf8(delim));
return (frombytes(a), frombytes(b));
};
// rt_ensure is the runtime slice-growth helper invoked by the
// `append(s, v)` builtin. Direct bind for the same reason as
// lib/shlex.shlex (appendstr, 16B): the builtin's expansion stores