From 02ada29624b8d2007e9161463cd63787823b843d Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Tue, 19 May 2026 12:22:16 +0900 Subject: [PATCH] lib/strings+test: port join from Hare str... variadic (#19) --- lib/strings/strings.ww | 41 ++++++++++++++++++++ lib/strings/stringstest.ww | 57 ++++++++++++++++++++++++++++ selfhost/cmd/w6c/main.combined.ww | 41 ++++++++++++++++++++ selfhost/cmd/wwdump/main.combined.ww | 41 ++++++++++++++++++++ selfhost/test/smoke.combined.ww | 41 ++++++++++++++++++++ 5 files changed, 221 insertions(+) diff --git a/lib/strings/strings.ww b/lib/strings/strings.ww index 1919c2db..cd0330c9 100644 --- a/lib/strings/strings.ww +++ b/lib/strings/strings.ww @@ -129,6 +129,47 @@ export fn concat(strs: str...) str = { return r; }; +// join — fresh allocation with `delim` placed between each element of +// `strs`. Caller releases with `os.free(r.ptr, r.len: u64)`. +// ref/hare/strings/concat.ha:46. Hare's `nomem` return is dropped: +// `os.alloc` aborts on OOM. +export fn join(delim: str, strs: str...) str = { + let total: i32 = 0; + let i: i32 = 0; + for (i < strs.len) { + total += strs[i].len; + if (i + 1 < strs.len) { total += delim.len; }; + i += 1; + }; + let r: str; + r.ptr = nil; + r.len = 0; + if (total == 0) { return r; }; + let buf: *u8 = os.alloc(total: u64): *u8; + let off: i32 = 0; + i = 0; + for (i < strs.len) { + let j: i32 = 0; + for (j < strs[i].len) { + buf[off + j] = strs[i][j]; + j += 1; + }; + off += strs[i].len; + if (i + 1 < strs.len) { + j = 0; + for (j < delim.len) { + buf[off + j] = delim[j]; + j += 1; + }; + off += delim.len; + }; + i += 1; + }; + r.ptr = buf; + r.len = total; + return r; +}; + // sub — borrowed `s[start..end]`. ref/hare/strings/sub.ha:30 is // rune-wise; this ww form is byte-wise (no rune iterator yet, planned // for commit 2). Clamps out-of-range silently where Hare aborts — diff --git a/lib/strings/stringstest.ww b/lib/strings/stringstest.ww index 8faf6ef7..1ef5490a 100644 --- a/lib/strings/stringstest.ww +++ b/lib/strings/stringstest.ww @@ -99,6 +99,62 @@ fn streq(a: str, b: str) bool = { }; }; +// ---- join ------------------------------------------------------------- +// ref/hare/strings/concat.ha:64. Rows mirror Hare's @test fn join +// (0-arg, 1-arg, empty-sep, 3-arg.) plus 2-arg, all-empties, long sep, +// multibyte, empty-mid (delim still inserted around the empty slot). +// Per-row `signalled` 1620+i narrows the failing row. + +@test fn join_cases() void = { + let pool: [19]str; + pool[0] = "hello"; + pool[1] = "a"; + pool[2] = "b"; + pool[3] = "a"; + pool[4] = "b"; + pool[5] = "c"; + pool[6] = "a"; + pool[7] = "b"; + pool[8] = "c"; + pool[9] = ""; + pool[10] = ""; + pool[11] = "a"; + pool[12] = "b"; + pool[13] = "c"; + pool[14] = "こん"; + pool[15] = "にちは"; + pool[16] = "a"; + pool[17] = ""; + pool[18] = "b"; + + let argo: [9]i32; + let argn: [9]i32; + let seps: [9]str; + let want: [9]str; + argo[0]=0; argn[0]=0; seps[0]="."; want[0]=""; + argo[1]=0; argn[1]=1; seps[1]="."; want[1]="hello"; + argo[2]=1; argn[2]=2; seps[2]=", "; want[2]="a, b"; + argo[3]=3; argn[3]=3; seps[3]="."; want[3]="a.b.c"; + argo[4]=6; argn[4]=3; seps[4]=""; want[4]="abc"; + argo[5]=9; argn[5]=2; seps[5]=","; want[5]=","; + argo[6]=11; argn[6]=3; seps[6]=" :: "; want[6]="a :: b :: c"; + argo[7]=14; argn[7]=2; seps[7]="・"; want[7]="こん・にちは"; + argo[8]=16; argn[8]=3; seps[8]="-"; want[8]="a--b"; + + let i: i32 = 0; + for (i < 9) { + signalled = 1620 + i; + let argv: []str; + argv.ptr = &pool[argo[i]]; + argv.len = argn[i]; + argv.cap = argn[i]; + let got: str = strings.join(seps[i], argv...); + if (!streq(got, want[i])) { fail(); }; + if (got.len > 0) { os.free(got.ptr: *void, got.len: u64); }; + i += 1; + }; +}; + // ---- hasprefix -------------------------------------------------------- // ref/hare/strings/suffix.ha:18. @@ -842,6 +898,7 @@ fn streq(a: str, b: str) bool = { export fn main() i32 = { signalled = 1; dup_cases(); signalled = 2; concat_cases(); + signalled = 30; join_cases(); signalled = 3; hasprefix_cases(); signalled = 4; hassuffix_cases(); signalled = 5; contains_cases(); diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index abc3382e..b5f7225d 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -1795,6 +1795,47 @@ export fn concat(strs: str...) str = { return r; }; +// join — fresh allocation with `delim` placed between each element of +// `strs`. Caller releases with `os.free(r.ptr, r.len: u64)`. +// ref/hare/strings/concat.ha:46. Hare's `nomem` return is dropped: +// `os.alloc` aborts on OOM. +export fn join(delim: str, strs: str...) str = { + let total: i32 = 0; + let i: i32 = 0; + for (i < strs.len) { + total += strs[i].len; + if (i + 1 < strs.len) { total += delim.len; }; + i += 1; + }; + let r: str; + r.ptr = nil; + r.len = 0; + if (total == 0) { return r; }; + let buf: *u8 = os.alloc(total: u64): *u8; + let off: i32 = 0; + i = 0; + for (i < strs.len) { + let j: i32 = 0; + for (j < strs[i].len) { + buf[off + j] = strs[i][j]; + j += 1; + }; + off += strs[i].len; + if (i + 1 < strs.len) { + j = 0; + for (j < delim.len) { + buf[off + j] = delim[j]; + j += 1; + }; + off += delim.len; + }; + i += 1; + }; + r.ptr = buf; + r.len = total; + return r; +}; + // sub — borrowed `s[start..end]`. ref/hare/strings/sub.ha:30 is // rune-wise; this ww form is byte-wise (no rune iterator yet, planned // for commit 2). Clamps out-of-range silently where Hare aborts — diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index ee68e95c..30178679 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -1795,6 +1795,47 @@ export fn concat(strs: str...) str = { return r; }; +// join — fresh allocation with `delim` placed between each element of +// `strs`. Caller releases with `os.free(r.ptr, r.len: u64)`. +// ref/hare/strings/concat.ha:46. Hare's `nomem` return is dropped: +// `os.alloc` aborts on OOM. +export fn join(delim: str, strs: str...) str = { + let total: i32 = 0; + let i: i32 = 0; + for (i < strs.len) { + total += strs[i].len; + if (i + 1 < strs.len) { total += delim.len; }; + i += 1; + }; + let r: str; + r.ptr = nil; + r.len = 0; + if (total == 0) { return r; }; + let buf: *u8 = os.alloc(total: u64): *u8; + let off: i32 = 0; + i = 0; + for (i < strs.len) { + let j: i32 = 0; + for (j < strs[i].len) { + buf[off + j] = strs[i][j]; + j += 1; + }; + off += strs[i].len; + if (i + 1 < strs.len) { + j = 0; + for (j < delim.len) { + buf[off + j] = delim[j]; + j += 1; + }; + off += delim.len; + }; + i += 1; + }; + r.ptr = buf; + r.len = total; + return r; +}; + // sub — borrowed `s[start..end]`. ref/hare/strings/sub.ha:30 is // rune-wise; this ww form is byte-wise (no rune iterator yet, planned // for commit 2). Clamps out-of-range silently where Hare aborts — diff --git a/selfhost/test/smoke.combined.ww b/selfhost/test/smoke.combined.ww index 4f02bf35..d27bcc8e 100644 --- a/selfhost/test/smoke.combined.ww +++ b/selfhost/test/smoke.combined.ww @@ -1686,6 +1686,47 @@ export fn concat(strs: str...) str = { return r; }; +// join — fresh allocation with `delim` placed between each element of +// `strs`. Caller releases with `os.free(r.ptr, r.len: u64)`. +// ref/hare/strings/concat.ha:46. Hare's `nomem` return is dropped: +// `os.alloc` aborts on OOM. +export fn join(delim: str, strs: str...) str = { + let total: i32 = 0; + let i: i32 = 0; + for (i < strs.len) { + total += strs[i].len; + if (i + 1 < strs.len) { total += delim.len; }; + i += 1; + }; + let r: str; + r.ptr = nil; + r.len = 0; + if (total == 0) { return r; }; + let buf: *u8 = os.alloc(total: u64): *u8; + let off: i32 = 0; + i = 0; + for (i < strs.len) { + let j: i32 = 0; + for (j < strs[i].len) { + buf[off + j] = strs[i][j]; + j += 1; + }; + off += strs[i].len; + if (i + 1 < strs.len) { + j = 0; + for (j < delim.len) { + buf[off + j] = delim[j]; + j += 1; + }; + off += delim.len; + }; + i += 1; + }; + r.ptr = buf; + r.len = total; + return r; +}; + // sub — borrowed `s[start..end]`. ref/hare/strings/sub.ha:30 is // rune-wise; this ww form is byte-wise (no rune iterator yet, planned // for commit 2). Clamps out-of-range silently where Hare aborts —