diff --git a/lib/strings/strings.ww b/lib/strings/strings.ww index 3aaeed6e..c8d3cb67 100644 --- a/lib/strings/strings.ww +++ b/lib/strings/strings.ww @@ -3,17 +3,18 @@ // // Documented divergences from Hare: // -// - `concat(a, b)` is 2-arg. Hare ships `concat(strs: str...)` -// (ref/hare/strings/concat.ha:5). Blocks on task #16 (cstage -// variadic-pack drops .len of multi-field element type). Cite -// reverts on fix. // - `trim` / `ltrim` / `rtrim` take a single rune. Hare's are -// `(exclude: rune...)` (ref/hare/strings/trim.ha:54). Same -// blocker as concat. Hare's no-rune branch (strip whitespace) -// is also dropped — depends on a rune set. +// `(trim: rune...)` (ref/hare/strings/trim.ha:11,32,54). The +// port body uses `iter`/`next` + inner match against the pack + +// `prev` step-back; that shape triggers #36 (wwstage scanlocals +// misses match-arm `case let` bindings, slot offsets diverge — +// `.ai/probe_trim_36extra.{ww,diff}`). Hare's no-rune +// strip-whitespace branch additionally needs `lib/bytes` +// variadic graduation. // - `contains` is non-variadic. Hare's is // `contains(haystack, needles: (str | rune)...)` -// (ref/hare/strings/contains.ha:9). Same blocker. +// (ref/hare/strings/contains.ha:9). Gated on `(str|rune)...` +// tagged-variadic gather + runtime — task #5. // - `byteindex` / `rbyteindex` rune arms encode via // `utf8.encoderune`; the legacy impls scanned for `r: u8` (an // undocumented ASCII-only restriction that silently dropped @@ -107,17 +108,30 @@ export fn freeall(s: []str) void = { }; }; -// concat — fresh allocation containing `a` then `b`. Caller releases -// with `os.free(r.ptr, r.len: u64)`. ref/hare/strings/concat.ha:5 -// (subset: Hare's `(strs: str...)` blocks on task #16). -export fn concat(a: str, b: str) str = { - let total: i32 = a.len + b.len; - let buf: *u8 = os.alloc(total: u64): *u8; +// concat — fresh allocation containing each element of `strs` in +// order. Caller releases with `os.free(r.ptr, r.len: u64)`. +// ref/hare/strings/concat.ha:5. Hare's `nomem` return is dropped: +// `os.alloc` aborts on OOM. +export fn concat(strs: str...) str = { + let total: i32 = 0; let i: i32 = 0; - for (i < a.len) { buf[i] = a[i]; i += 1; }; - let j: i32 = 0; - for (j < b.len) { buf[a.len + j] = b[j]; j += 1; }; + for (i < strs.len) { total += strs[i].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; + i += 1; + }; r.ptr = buf; r.len = total; return r; diff --git a/lib/strings/stringstest.ww b/lib/strings/stringstest.ww index 7a8a9fa0..5a95db54 100644 --- a/lib/strings/stringstest.ww +++ b/lib/strings/stringstest.ww @@ -46,28 +46,57 @@ fn streq(a: str, b: str) bool = { }; // ---- concat ----------------------------------------------------------- -// ref/hare/strings/concat.ha:18 (2-arg subset). +// ref/hare/strings/concat.ha:18. Rows mirror Hare's vectors (0/1/2/3-arg, +// empty-mid, 2-empty) plus empty-first / empty-last / multibyte. The +// per-row `signalled` bump narrows a failure exit code to the offending +// row. @test fn concat_cases() void = { - let a: str = strings.concat("hello ", "world"); - if (!streq(a, "hello world")) { fail(); }; - defer os.free(a.ptr: *void, a.len: u64); + let pool: [17]str; + pool[0] = "hello"; + pool[1] = "hello "; + pool[2] = "world"; + pool[3] = "hello"; + pool[4] = " "; + pool[5] = "world"; + pool[6] = "hello"; + pool[7] = ""; + pool[8] = "world"; + pool[9] = ""; + pool[10] = ""; + pool[11] = ""; + pool[12] = "world"; + pool[13] = "hello"; + pool[14] = ""; + pool[15] = "こん"; + pool[16] = "にちは"; - let e: str = strings.concat("", ""); - if (!streq(e, "")) { fail(); }; - // e.len == 0 — os.free guarded, skip. + let argo: [9]i32; + let argn: [9]i32; + let want: [9]str; + let labels: [9]str; + argo[0]=0; argn[0]=0; want[0]=""; labels[0]="0-arg"; + argo[1]=0; argn[1]=1; want[1]="hello"; labels[1]="1-arg"; + argo[2]=1; argn[2]=2; want[2]="hello world"; labels[2]="2-arg"; + argo[3]=3; argn[3]=3; want[3]="hello world"; labels[3]="3-arg"; + argo[4]=6; argn[4]=3; want[4]="helloworld"; labels[4]="empty-mid"; + argo[5]=9; argn[5]=2; want[5]=""; labels[5]="2-empty"; + argo[6]=11; argn[6]=2; want[6]="world"; labels[6]="empty-first"; + argo[7]=13; argn[7]=2; want[7]="hello"; labels[7]="empty-last"; + argo[8]=15; argn[8]=2; want[8]="こんにちは"; labels[8]="multibyte"; - let l: str = strings.concat("", "world"); - if (!streq(l, "world")) { fail(); }; - defer os.free(l.ptr: *void, l.len: u64); - - let r: str = strings.concat("hello", ""); - if (!streq(r, "hello")) { fail(); }; - defer os.free(r.ptr: *void, r.len: u64); - - let m: str = strings.concat("こん", "にちは"); - if (!streq(m, "こんにちは")) { fail(); }; - defer os.free(m.ptr: *void, m.len: u64); + let i: i32 = 0; + for (i < 9) { + signalled = 200 + i; + let argv: []str; + argv.ptr = &pool[argo[i]]; + argv.len = argn[i]; + argv.cap = argn[i]; + let got: str = strings.concat(argv...); + if (!streq(got, want[i])) { fail(); }; + if (got.len > 0) { os.free(got.ptr: *void, got.len: u64); }; + i += 1; + }; }; // ---- hasprefix --------------------------------------------------------