// Vectors mirror ref/hare/strings/sub.ha where ww can express them. // External strings_test package per the Go foo_test idiom (rule-5/9 // carve-out, task #16); the shared streq helper lives in // helpers_test.ww — same-package test files compose in dir-mode // (the package, not the file, is the unit of testing). package strings_test; import strings; import encoding.utf8; import test; // ref/hare/strings/sub.ha:44 (@test fn sub), :79 (@test fn bytesub). Hare's // 2-arg `sub(s, start)` rows are omitted: ww has no default-parameter // syntax (filed as #37). bytesub now validates rune boundaries (#7). @test fn sub_cases() void = { assert(!(!streq(strings.sub("a string", 0, 8), "a string"))); assert(!(!streq(strings.sub("a string", 0, 1), "a"))); assert(!(!streq(strings.sub("a string", 0, 3), "a s"))); assert(!(!streq(strings.sub("a string", 2, 8), "string"))); // start == end yields an empty borrowed view. assert(!(!streq(strings.sub("a string", 4, 4), ""))); assert(!(strings.sub("a string", 4, 4).len != 0)); // Hare vector — rune indices 1..3 over "こんにちは" select bytes // 3..9 ("んに"), not bytes 1..3. assert(!(!streq(strings.sub("こんにちは", 1, 3), "んに"))); // 2-byte rune at rune index 1 in "héllo" — byte offsets 1..3. assert(!(!streq(strings.sub("héllo", 1, 2), "é"))); // start == 0, end == rune-len of full string. assert(!(!streq(strings.sub("héllo", 0, 5), "héllo"))); }; @test fn sub_negative_aborts() void = { test.expectabort(); strings.sub("abc", -1, 0); }; // Match-shape mirrors Hare's `bytesub(...)!` at ref/hare/strings/sub.ha: // 80-86. Inlined at each call site (rather than a helper that takes // `(str | utf8.invalid)` by value) because the union-by-value path // crashes — same lift-on-pass-by-value family as #48. @test fn bytesub_cases() void = { match (strings.bytesub("a string", 0, 8)) { case let s: str => { assert(!(!streq(s, "a string"))); }; case let e: utf8.invalid => { abort(); }; }; match (strings.bytesub("a string", 0, 1)) { case let s: str => { assert(!(!streq(s, "a"))); }; case let e: utf8.invalid => { abort(); }; }; match (strings.bytesub("a string", 0, 3)) { case let s: str => { assert(!(!streq(s, "a s"))); }; case let e: utf8.invalid => { abort(); }; }; match (strings.bytesub("a string", 2, 8)) { case let s: str => { assert(!(!streq(s, "string"))); }; case let e: utf8.invalid => { abort(); }; }; match (strings.bytesub("a string", 4, 4)) { case let s: str => { assert(!(!streq(s, ""))); }; case let e: utf8.invalid => { abort(); }; }; // Hare vector — byte indices 3..9 over "こんにちは" select "んに". match (strings.bytesub("こんにちは", 3, 9)) { case let s: str => { assert(!(!streq(s, "んに"))); }; case let e: utf8.invalid => { abort(); }; }; // Rune/byte axis disagree on identical args (#3): sub(s,0,3) walks 3 // runes and yields 9 bytes; bytesub(s,0,3) yields the first 3 bytes // — one 3-byte codepoint. assert(!(!streq(strings.sub("こんにちは", 0, 3), "こんに"))); match (strings.bytesub("こんにちは", 0, 3)) { case let s: str => { assert(!(!streq(s, "こ"))); }; case let e: utf8.invalid => { abort(); }; }; // Borrowed view: ptr aliases input. let s: str = "hello"; match (strings.bytesub(s, 1, 4)) { case let r: str => { assert(!(r.ptr != s.ptr + 1u64)); assert(!(r.len != 3)); }; case let e: utf8.invalid => { abort(); }; }; // Hare's invalid row (ref/hare/strings/sub.ha:87) — start lands on // a continuation byte (2nd byte of "こ"), bytesub must reject (#7). match (strings.bytesub("こんにちは", 1, 3)) { case let r: str => { abort(); }; case let e: utf8.invalid => void; }; // Symmetric: end lands on a continuation byte (2nd byte of "ん"). match (strings.bytesub("こんにちは", 0, 4)) { case let r: str => { abort(); }; case let e: utf8.invalid => void; }; // end == s.len bypasses the continuation check (s[end] is OOB). match (strings.bytesub("こんにちは", 0, 15)) { case let s: str => { assert(!(!streq(s, "こんにちは"))); }; case let e: utf8.invalid => { abort(); }; }; }; @test fn bytesub_negative_aborts() void = { test.expectabort(); strings.bytesub("abc", -1, 0); };