From 7c5463c7d9d39be0b1f6555aa964bab128b7d33f Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Tue, 19 May 2026 05:08:57 +0900 Subject: [PATCH] lib/strings+test: graduate contains to Hare (str|rune)... variadic (#9) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit contains(haystack, needle: (str|rune)) -> contains(haystack: str, needles: (str|rune)...) bool per ref/hare/strings/contains.ha:9. Body: for-loop over needles.len; inner match (needles[i]) dispatches byteindex(haystack, s|r); early return true on hit; fall-through return false. Hare's match-yield + if (matched) return true collapses naturally given byteindex's (i32|void) shape (Hare uses bytes::contains -> bool). 0-arg returns false per Hare spec. Unblocked by: #8 (5ed6293 varargseq per cgcall), #12 (cbf1042 sum-tag forward), #15 (5609d04 frame-strategy), #16 (d9b0c90 callee_variadic_ param N_DOT). All four landed this session; #9 exercises every unblocker. The cross-module same-leaf collision with bytes.contains (non-variadic) is closed by #16's fnparamslookupmod re-routing — existing 750 sentinel row bytes_strings_contains continues to pass. contains_cases adds 6 variadic rows (signalled 1600+i): 0-arg, 1-arg str, 1-arg rune, 3-arg mixed (middle hits), 3-arg all-miss, multibyte mixed (heart-é via 0xE9u32: rune per single-byte lexrune precedent at hasprefix_cases:115). Module-header divergence note for contains removed; lib/bytes whitespace-default note retained for future bytes graduation. make test 126/126; ww2==ww3==ww4 byte-id holds via 995_self_rebuild. --- lib/strings/strings.ww | 31 ++++++++++++++++++---------- lib/strings/stringstest.ww | 14 +++++++++++++ selfhost/cmd/w6c/main.combined.ww | 31 ++++++++++++++++++---------- selfhost/cmd/wwdump/main.combined.ww | 31 ++++++++++++++++++---------- selfhost/test/smoke.combined.ww | 31 ++++++++++++++++++---------- 5 files changed, 94 insertions(+), 44 deletions(-) diff --git a/lib/strings/strings.ww b/lib/strings/strings.ww index e66adeaa..1919c2db 100644 --- a/lib/strings/strings.ww +++ b/lib/strings/strings.ww @@ -7,10 +7,6 @@ // Hare strips ASCII whitespace via `bytes::ltrim(input, // whitespace...)`; that needs `lib/bytes` variadic graduation // (future commit). -// - `contains` is non-variadic. Hare's is -// `contains(haystack, needles: (str | rune)...)` -// (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 @@ -293,13 +289,26 @@ export fn rindex(haystack: str, needle: (str | rune)) (i32 | void) = { return; }; -// contains — true iff `needle` occurs in `haystack`. -// ref/hare/strings/contains.ha:9 (subset: Hare's variadic form -// `(needles: (str | rune)...)` blocks on task #16). -export fn contains(haystack: str, needle: (str | rune)) bool = { - match (byteindex(haystack, needle)) { - case let i: i32 => return true; - case void => return false; +// contains — true iff any of `needles` occurs in `haystack`. +// ref/hare/strings/contains.ha:9. +export fn contains(haystack: str, needles: (str | rune)...) bool = { + let i: i32 = 0; + for (i < needles.len) { + match (needles[i]) { + case let s: str => { + match (byteindex(haystack, s)) { + case let bo: i32 => return true; + case void => void; + }; + }; + case let r: rune => { + match (byteindex(haystack, r)) { + case let bo: i32 => return true; + case void => void; + }; + }; + }; + i += 1; }; return false; }; diff --git a/lib/strings/stringstest.ww b/lib/strings/stringstest.ww index bf1e2d1d..8faf6ef7 100644 --- a/lib/strings/stringstest.ww +++ b/lib/strings/stringstest.ww @@ -139,6 +139,20 @@ fn streq(a: str, b: str) bool = { if ( strings.contains("hello world", "foobar")) { fail(); }; if (!strings.contains("こんにちは", 0x306Bu32: rune)) { fail(); }; // 'に' if (!strings.contains("こんにちは", "ちは")) { fail(); }; + + // Variadic rows. ref/hare/strings/contains.ha:27. + signalled = 1600; + if ( strings.contains("hello")) { fail(); }; + signalled = 1601; + if (!strings.contains("hello world", "world")) { fail(); }; + signalled = 1602; + if (!strings.contains("hello", 'l')) { fail(); }; + signalled = 1603; + if (!strings.contains("hello world", "foo", "world", 'x')) { fail(); }; + signalled = 1604; + if ( strings.contains("hello", "foo", 'z', "bar")) { fail(); }; + signalled = 1605; + if (!strings.contains("héllo", "x", 0xE9u32: rune)) { fail(); }; }; // ---- byteindex -------------------------------------------------------- diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 0a859be8..231cda4a 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -1449,10 +1449,6 @@ export fn position(d: *decoder) i32 = { // Hare strips ASCII whitespace via `bytes::ltrim(input, // whitespace...)`; that needs `lib/bytes` variadic graduation // (future commit). -// - `contains` is non-variadic. Hare's is -// `contains(haystack, needles: (str | rune)...)` -// (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 @@ -1735,13 +1731,26 @@ export fn rindex(haystack: str, needle: (str | rune)) (i32 | void) = { return; }; -// contains — true iff `needle` occurs in `haystack`. -// ref/hare/strings/contains.ha:9 (subset: Hare's variadic form -// `(needles: (str | rune)...)` blocks on task #16). -export fn contains(haystack: str, needle: (str | rune)) bool = { - match (byteindex(haystack, needle)) { - case let i: i32 => return true; - case void => return false; +// contains — true iff any of `needles` occurs in `haystack`. +// ref/hare/strings/contains.ha:9. +export fn contains(haystack: str, needles: (str | rune)...) bool = { + let i: i32 = 0; + for (i < needles.len) { + match (needles[i]) { + case let s: str => { + match (byteindex(haystack, s)) { + case let bo: i32 => return true; + case void => void; + }; + }; + case let r: rune => { + match (byteindex(haystack, r)) { + case let bo: i32 => return true; + case void => void; + }; + }; + }; + i += 1; }; return false; }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 018cd310..043db4a3 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -1449,10 +1449,6 @@ export fn position(d: *decoder) i32 = { // Hare strips ASCII whitespace via `bytes::ltrim(input, // whitespace...)`; that needs `lib/bytes` variadic graduation // (future commit). -// - `contains` is non-variadic. Hare's is -// `contains(haystack, needles: (str | rune)...)` -// (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 @@ -1735,13 +1731,26 @@ export fn rindex(haystack: str, needle: (str | rune)) (i32 | void) = { return; }; -// contains — true iff `needle` occurs in `haystack`. -// ref/hare/strings/contains.ha:9 (subset: Hare's variadic form -// `(needles: (str | rune)...)` blocks on task #16). -export fn contains(haystack: str, needle: (str | rune)) bool = { - match (byteindex(haystack, needle)) { - case let i: i32 => return true; - case void => return false; +// contains — true iff any of `needles` occurs in `haystack`. +// ref/hare/strings/contains.ha:9. +export fn contains(haystack: str, needles: (str | rune)...) bool = { + let i: i32 = 0; + for (i < needles.len) { + match (needles[i]) { + case let s: str => { + match (byteindex(haystack, s)) { + case let bo: i32 => return true; + case void => void; + }; + }; + case let r: rune => { + match (byteindex(haystack, r)) { + case let bo: i32 => return true; + case void => void; + }; + }; + }; + i += 1; }; return false; }; diff --git a/selfhost/test/smoke.combined.ww b/selfhost/test/smoke.combined.ww index ed442b72..536f0fb5 100644 --- a/selfhost/test/smoke.combined.ww +++ b/selfhost/test/smoke.combined.ww @@ -1340,10 +1340,6 @@ export fn position(d: *decoder) i32 = { // Hare strips ASCII whitespace via `bytes::ltrim(input, // whitespace...)`; that needs `lib/bytes` variadic graduation // (future commit). -// - `contains` is non-variadic. Hare's is -// `contains(haystack, needles: (str | rune)...)` -// (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 @@ -1626,13 +1622,26 @@ export fn rindex(haystack: str, needle: (str | rune)) (i32 | void) = { return; }; -// contains — true iff `needle` occurs in `haystack`. -// ref/hare/strings/contains.ha:9 (subset: Hare's variadic form -// `(needles: (str | rune)...)` blocks on task #16). -export fn contains(haystack: str, needle: (str | rune)) bool = { - match (byteindex(haystack, needle)) { - case let i: i32 => return true; - case void => return false; +// contains — true iff any of `needles` occurs in `haystack`. +// ref/hare/strings/contains.ha:9. +export fn contains(haystack: str, needles: (str | rune)...) bool = { + let i: i32 = 0; + for (i < needles.len) { + match (needles[i]) { + case let s: str => { + match (byteindex(haystack, s)) { + case let bo: i32 => return true; + case void => void; + }; + }; + case let r: rune => { + match (byteindex(haystack, r)) { + case let bo: i32 => return true; + case void => void; + }; + }; + }; + i += 1; }; return false; };