From 38a906cd9b2bc5d8856891e402728005837341c6 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Mon, 1 Jun 2026 16:26:11 +0900 Subject: [PATCH] lib/strings: add cut and rcut --- lib/strings/strings.ww | 17 ++++ lib/strings/stringstest.ww | 113 +++++++++++++++++++++++++++ selfhost/cmd/w6a/main.combined.ww | 17 ++++ selfhost/cmd/w6c/main.combined.ww | 17 ++++ selfhost/cmd/w6l/main.combined.ww | 17 ++++ selfhost/cmd/ww/main.combined.ww | 17 ++++ selfhost/cmd/wwdump/main.combined.ww | 17 ++++ selfhost/test/smoke.combined.ww | 17 ++++ 8 files changed, 232 insertions(+) diff --git a/lib/strings/strings.ww b/lib/strings/strings.ww index 209f0ef9..7f2ae56b 100644 --- a/lib/strings/strings.ww +++ b/lib/strings/strings.ww @@ -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 diff --git a/lib/strings/stringstest.ww b/lib/strings/stringstest.ww index 86cbbda0..f3e97946 100644 --- a/lib/strings/stringstest.ww +++ b/lib/strings/stringstest.ww @@ -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; }; diff --git a/selfhost/cmd/w6a/main.combined.ww b/selfhost/cmd/w6a/main.combined.ww index 6edcbb8a..60e38acc 100644 --- a/selfhost/cmd/w6a/main.combined.ww +++ b/selfhost/cmd/w6a/main.combined.ww @@ -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 diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index b02fd0bd..162cacfd 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -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 diff --git a/selfhost/cmd/w6l/main.combined.ww b/selfhost/cmd/w6l/main.combined.ww index f6dfe10b..ab14d111 100644 --- a/selfhost/cmd/w6l/main.combined.ww +++ b/selfhost/cmd/w6l/main.combined.ww @@ -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 diff --git a/selfhost/cmd/ww/main.combined.ww b/selfhost/cmd/ww/main.combined.ww index ec237884..bf9563e3 100644 --- a/selfhost/cmd/ww/main.combined.ww +++ b/selfhost/cmd/ww/main.combined.ww @@ -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 diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index f55b4e42..3fe9f2ec 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -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 diff --git a/selfhost/test/smoke.combined.ww b/selfhost/test/smoke.combined.ww index d043652b..ae1e8d90 100644 --- a/selfhost/test/smoke.combined.ww +++ b/selfhost/test/smoke.combined.ww @@ -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