Files
ww/lib/strings/stringstest.ww
Hojun-Cho 7c5463c7d9 lib/strings+test: graduate contains to Hare (str|rune)... variadic (#9)
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.
2026-05-19 05:08:57 +09:00

874 lines
28 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// stringstest — exercises lib/strings. Run with
// `out/bin/ww run lib/strings/stringstest.ww`.
// Same signalled-then-fail()-with-+10 shape as bytes / utf8 / hex /
// time tests: non-zero exit pinpoints the failing scenario.
//
// Vectors mirror ref/hare/strings/{dup,concat,trim,contains,index,
// suffix,compare}.ha where ww can express them.
package strings;
import strings;
import encoding.utf8;
import os;
let signalled: i32 = 0;
fn fail() void = { os.exit(signalled + 10); };
fn streq(a: str, b: str) bool = {
if (a.len != b.len) { return false; };
let i: i32 = 0;
for (i < a.len) {
if (a[i] != b[i]) { return false; };
i += 1;
};
return true;
};
// ---- dup --------------------------------------------------------------
// ref/hare/strings/dup.ha:45.
@test fn dup_cases() void = {
let e: str = strings.dup("");
if (!streq(e, "")) { fail(); };
if (e.len != 0) { fail(); };
let h: str = strings.dup("hello");
if (!streq(h, "hello")) { fail(); };
defer os.free(h.ptr: *void, h.len: u64);
// multi-byte UTF-8: dup must copy raw bytes, not aliased view.
let m: str = strings.dup("こんにちは");
if (m.len != 15) { fail(); };
if (!streq(m, "こんにちは")) { fail(); };
if (m.ptr == "こんにちは".ptr) { fail(); }; // fresh alloc
defer os.free(m.ptr: *void, m.len: u64);
};
// ---- concat -----------------------------------------------------------
// 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 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 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 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 --------------------------------------------------------
// ref/hare/strings/suffix.ha:18.
@test fn hasprefix_cases() void = {
if (!strings.hasprefix("hello world", "hello")) { fail(); };
if (!strings.hasprefix("hello world", 'h')) { fail(); };
if ( strings.hasprefix("hello world", "world")) { fail(); };
if ( strings.hasprefix("hello world", 'q')) { fail(); };
if (!strings.hasprefix("hello", "hello")) { fail(); }; // equal-len
if (!strings.hasprefix("anything", "")) { fail(); }; // empty prefix
if ( strings.hasprefix("", "x")) { fail(); };
// multibyte rune prefix — '\'é\'' literal blocked by single-byte
// lexrune (lib/ww/lex/lex.ww:659); pass codepoint directly.
if (!strings.hasprefix("éclat", 0xE9u32: rune)) { fail(); };
if (!strings.hasprefix("🦀rust", 0x1F980u32: rune)) { fail(); };
};
// ---- hassuffix --------------------------------------------------------
// ref/hare/strings/suffix.ha:36.
@test fn hassuffix_cases() void = {
if (!strings.hassuffix("hello world", "world")) { fail(); };
if (!strings.hassuffix("hello world", 'd')) { fail(); };
if ( strings.hassuffix("hello world", "hello")) { fail(); };
if ( strings.hassuffix("hello world", 'h')) { fail(); };
if (!strings.hassuffix("café", 0xE9u32: rune)) { fail(); }; // multibyte
};
// ---- contains ---------------------------------------------------------
// ref/hare/strings/contains.ha:27.
@test fn contains_cases() void = {
if (!strings.contains("hello world", "hello")) { fail(); };
if (!strings.contains("hello world", 'h')) { fail(); };
if ( strings.contains("hello world", 'x')) { fail(); };
if (!strings.contains("hello world", "world")) { fail(); };
if (!strings.contains("hello world", "")) { fail(); }; // empty hits at 0
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 --------------------------------------------------------
// ref/hare/strings/index.ha:147 (byteindex tests, both arms).
@test fn byteindex_str_cases() void = {
match (strings.byteindex("hello", "hello")) {
case let i: i32 => { if (i != 0) { fail(); }; };
case void => { fail(); };
};
match (strings.byteindex("hello world!", "world")) {
case let i: i32 => { if (i != 6) { fail(); }; };
case void => { fail(); };
};
match (strings.byteindex("hello world!", "orld!")) {
case let i: i32 => { if (i != 7) { fail(); }; };
case void => { fail(); };
};
match (strings.byteindex("hello world!", "word")) {
case let i: i32 => { fail(); };
case void => void;
};
// empty needle hits at 0 (ref/hare/bytes/index.ha:63).
match (strings.byteindex("hello", "")) {
case let i: i32 => { if (i != 0) { fail(); }; };
case void => { fail(); };
};
// empty haystack, non-empty needle — absent.
match (strings.byteindex("", "x")) {
case let i: i32 => { fail(); };
case void => void;
};
// multibyte substring in multibyte haystack.
match (strings.byteindex("こんにちは", "ちは")) {
case let i: i32 => { if (i != 9) { fail(); }; };
case void => { fail(); };
};
};
@test fn byteindex_rune_cases() void = {
// ASCII rune (1-byte encoding).
match (strings.byteindex("hello world", 'w')) {
case let i: i32 => { if (i != 6) { fail(); }; };
case void => { fail(); };
};
// 2-byte rune U+00E9 'é' inside "café".
match (strings.byteindex("café", 0xE9u32: rune)) {
case let i: i32 => { if (i != 3) { fail(); }; };
case void => { fail(); };
};
// 3-byte rune U+3061 'ち' inside "こんにちは".
match (strings.byteindex("こんにちは", 0x3061u32: rune)) {
case let i: i32 => { if (i != 9) { fail(); }; };
case void => { fail(); };
};
// 4-byte rune U+1F980 '🦀' inside "ab🦀cd".
match (strings.byteindex("ab🦀cd", 0x1F980u32: rune)) {
case let i: i32 => { if (i != 2) { fail(); }; };
case void => { fail(); };
};
// absent.
match (strings.byteindex("こんにちは", 'q')) {
case let i: i32 => { fail(); };
case void => void;
};
};
// ---- rbyteindex -------------------------------------------------------
@test fn rbyteindex_cases() void = {
// Two 'た' in "またあったね" — ref/hare/strings/index.ha:160-161.
match (strings.byteindex("またあったね", "た")) {
case let i: i32 => { if (i != 3) { fail(); }; };
case void => { fail(); };
};
match (strings.rbyteindex("またあったね", "た")) {
case let i: i32 => { if (i != 12) { fail(); }; };
case void => { fail(); };
};
// Rune arm, multi-byte 'に' U+306B.
match (strings.rbyteindex("こんにちは", 0x306Bu32: rune)) {
case let i: i32 => { if (i != 6) { fail(); }; };
case void => { fail(); };
};
// Absent.
match (strings.rbyteindex("abc", 'z')) {
case let i: i32 => { fail(); };
case void => void;
};
};
// ---- index ------------------------------------------------------------
// ref/hare/strings/index.ha:108. Rune-wise offset, NOT byte-wise — the
// multibyte rows pin that distinction (Hare doc at index.ha:7).
@test fn index_cases() void = {
// str-arm: ASCII haystack/needle, mid-string match.
signalled = 1400;
match (strings.index("hello", "ll")) {
case let i: i32 => { if (i != 2) { fail(); }; };
case void => { fail(); };
};
// str-arm: absent needle.
signalled = 1401;
match (strings.index("hello", "world")) {
case let i: i32 => { fail(); };
case void => void;
};
// Hare vectors at ref/hare/strings/index.ha:113-119.
signalled = 1402;
match (strings.index("hello world!", "hello")) {
case let i: i32 => { if (i != 0) { fail(); }; };
case void => { fail(); };
};
signalled = 1403;
match (strings.index("hello world!", "world")) {
case let i: i32 => { if (i != 6) { fail(); }; };
case void => { fail(); };
};
signalled = 1404;
match (strings.index("hello world!", "orld!")) {
case let i: i32 => { if (i != 7) { fail(); }; };
case void => { fail(); };
};
// Multibyte haystack + str needle: "ちは" at rune index 3
// in "こんにちは" (byteindex returns 9, rune-index is 3).
signalled = 1405;
match (strings.index("こんにちは", "ちは")) {
case let i: i32 => { if (i != 3) { fail(); }; };
case void => { fail(); };
};
// rune-arm.
signalled = 1406;
match (strings.index("hello", 'l')) {
case let i: i32 => { if (i != 2) { fail(); }; };
case void => { fail(); };
};
signalled = 1407;
match (strings.index("hello world", 'w')) {
case let i: i32 => { if (i != 6) { fail(); }; };
case void => { fail(); };
};
// Multibyte rune: 'é' U+00E9 at rune 1 in "héllo" — pins
// rune-index vs byte-index (byteindex returns 1; rune-index is
// 1 also — but the str-arm's 1405 row covers the distinction).
signalled = 1408;
match (strings.index("héllo", 0xE9u32: rune)) {
case let i: i32 => { if (i != 1) { fail(); }; };
case void => { fail(); };
};
// Multibyte rune in multibyte haystack: 'ち' U+3061 at rune 3
// in "こんにちは" (byteindex returns 9, rune-index is 3).
signalled = 1409;
match (strings.index("こんにちは", 0x3061u32: rune)) {
case let i: i32 => { if (i != 3) { fail(); }; };
case void => { fail(); };
};
signalled = 1410;
match (strings.index("こんにちは", 'q')) {
case let i: i32 => { fail(); };
case void => void;
};
};
// ---- rindex -----------------------------------------------------------
// ref/hare/strings/index.ha:22. Symmetric: last-occurrence rune index.
@test fn rindex_cases() void = {
// str-arm.
signalled = 1500;
match (strings.rindex("hello", "lo")) {
case let i: i32 => { if (i != 3) { fail(); }; };
case void => { fail(); };
};
// Hare vector at ref/hare/strings/index.ha:122.
signalled = 1501;
match (strings.rindex("hello world!", "o")) {
case let i: i32 => { if (i != 7) { fail(); }; };
case void => { fail(); };
};
signalled = 1502;
match (strings.rindex("hello", "world")) {
case let i: i32 => { fail(); };
case void => void;
};
// Multibyte: last "た" in "またあったね" — rbyteindex returns
// 12, rune-index is 4 (ま=0 た=1 あ=2 っ=3 た=4 ね=5).
signalled = 1503;
match (strings.rindex("またあったね", "た")) {
case let i: i32 => { if (i != 4) { fail(); }; };
case void => { fail(); };
};
// rune-arm.
signalled = 1504;
match (strings.rindex("hello", 'l')) {
case let i: i32 => { if (i != 3) { fail(); }; };
case void => { fail(); };
};
signalled = 1505;
match (strings.rindex("aaaaa", 'a')) {
case let i: i32 => { if (i != 4) { fail(); }; };
case void => { fail(); };
};
// Multibyte rune: 'に' U+306B at rune 2 in "こんにちは".
signalled = 1506;
match (strings.rindex("こんにちは", 0x306Bu32: rune)) {
case let i: i32 => { if (i != 2) { fail(); }; };
case void => { fail(); };
};
signalled = 1507;
match (strings.rindex("hello", 'z')) {
case let i: i32 => { fail(); };
case void => void;
};
};
// ---- trimprefix / trimsuffix ------------------------------------------
// ref/hare/strings/trim.ha:99-107.
@test fn trimprefix_cases() void = {
if (!streq(strings.trimprefix("", ""), "")) { fail(); };
if (!streq(strings.trimprefix("", "blablabla"), "")) { fail(); };
if (!streq(strings.trimprefix("hello, world", "hello"), ", world")) { fail(); };
if (!streq(strings.trimprefix("blablabla", "bla"), "blabla")) { fail(); };
// equal-length match strips to empty.
if (!streq(strings.trimprefix("hello", "hello"), "")) { fail(); };
};
@test fn trimsuffix_cases() void = {
if (!streq(strings.trimsuffix("", ""), "")) { fail(); };
if (!streq(strings.trimsuffix("", "blablabla"), "")) { fail(); };
if (!streq(strings.trimsuffix("hello, world", "world"), "hello, ")) { fail(); };
if (!streq(strings.trimsuffix("blablabla", "bla"), "blabla")) { fail(); };
if (!streq(strings.trimsuffix("hello", "hello"), "")) { fail(); };
};
// ---- ltrim / rtrim / trim ---------------------------------------------
// ref/hare/strings/trim.ha:75-97. Bytes.ltrim whitespace-default rows
// (`ltrim(" hi") == "hi"`, etc.) deferred — they need lib/bytes
// variadic graduation.
@test fn ltrim_cases() void = {
let runes: [9]rune;
runes[0] = 'x';
runes[1] = 'a';
runes[2] = 0x1D68Au32: rune;
runes[3] = '(';
runes[4] = ')';
runes[5] = 'a';
runes[6] = 'b';
runes[7] = 'c';
runes[8] = 'd';
let inputs: [8]str;
let argo: [8]i32;
let argn: [8]i32;
let want: [8]str;
inputs[0]=""; argo[0]=0; argn[0]=1; want[0]="";
inputs[1]="aaabc"; argo[1]=1; argn[1]=1; want[1]="bc";
inputs[2]="xyz"; argo[2]=1; argn[2]=1; want[2]="xyz";
inputs[3]="aaaa"; argo[3]=1; argn[3]=1; want[3]="";
inputs[4]="𝚊𝚊hi"; argo[4]=2; argn[4]=1; want[4]="hi";
inputs[5]="((()(())))())"; argo[5]=3; argn[5]=2; want[5]="";
inputs[6]="abacadabra"; argo[6]=5; argn[6]=4; want[6]="ra";
inputs[7]="hello"; argo[7]=0; argn[7]=0; want[7]="hello";
let i: i32 = 0;
for (i < 8) {
signalled = 1100 + i;
let argv: []rune;
argv.ptr = &runes[argo[i]];
argv.len = argn[i];
argv.cap = argn[i];
let got: str = strings.ltrim(inputs[i], argv...);
if (!streq(got, want[i])) { fail(); };
i += 1;
};
};
@test fn rtrim_cases() void = {
let runes: [19]rune;
runes[0] = 'x';
runes[1] = 'a';
runes[2] = 0x1D68Au32: rune;
runes[3] = 'w';
runes[4] = 'd';
runes[5] = 'o';
runes[6] = 'r';
runes[7] = ' ';
runes[8] = 's';
runes[9] = 'i';
runes[10] = 'l';
runes[11] = 'z';
runes[12] = 't';
runes[13] = 'm';
runes[14] = 'n';
runes[15] = 'o';
runes[16] = 'e';
runes[17] = 'a';
runes[18] = 'd';
let inputs: [8]str;
let argo: [8]i32;
let argn: [8]i32;
let want: [8]str;
inputs[0]=""; argo[0]=0; argn[0]=1; want[0]="";
inputs[1]="bcaaa"; argo[1]=1; argn[1]=1; want[1]="bc";
inputs[2]="xyz"; argo[2]=1; argn[2]=1; want[2]="xyz";
inputs[3]="aaaa"; argo[3]=1; argn[3]=1; want[3]="";
inputs[4]="hi𝚊𝚊"; argo[4]=2; argn[4]=1; want[4]="hi";
inputs[5]="yellowwooddoor";
argo[5]=3; argn[5]=4; want[5]="yell";
inputs[6]="Sentimentalized sensationalism sensationalized sentimentalisms";
argo[6]=7; argn[6]=12; want[6]="S";
inputs[7]="hello"; argo[7]=0; argn[7]=0; want[7]="hello";
let i: i32 = 0;
for (i < 8) {
signalled = 1200 + i;
let argv: []rune;
argv.ptr = &runes[argo[i]];
argv.len = argn[i];
argv.cap = argn[i];
let got: str = strings.rtrim(inputs[i], argv...);
if (!streq(got, want[i])) { fail(); };
i += 1;
};
};
@test fn trim_cases() void = {
let runes: [8]rune;
runes[0] = 'x';
runes[1] = 'a';
runes[2] = 'm';
runes[3] = 'i';
runes[4] = 'p';
runes[5] = 's';
runes[6] = '[';
runes[7] = ']';
let inputs: [7]str;
let argo: [7]i32;
let argn: [7]i32;
let want: [7]str;
inputs[0]=""; argo[0]=0; argn[0]=1; want[0]="";
inputs[1]="aaabcaaa"; argo[1]=1; argn[1]=1; want[1]="bc";
inputs[2]="xyz"; argo[2]=1; argn[2]=1; want[2]="xyz";
inputs[3]="aaaa"; argo[3]=1; argn[3]=1; want[3]="";
inputs[4]="mississippi"; argo[4]=2; argn[4]=4; want[4]="";
inputs[5]="[[][[[]]][][].[[]][]]][]]]";
argo[5]=6; argn[5]=2; want[5]=".";
inputs[6]="hello"; argo[6]=0; argn[6]=0; want[6]="hello";
let i: i32 = 0;
for (i < 7) {
signalled = 1300 + i;
let argv: []rune;
argv.ptr = &runes[argo[i]];
argv.len = argn[i];
argv.cap = argn[i];
let got: str = strings.trim(inputs[i], argv...);
if (!streq(got, want[i])) { fail(); };
i += 1;
};
};
// ---- compare ----------------------------------------------------------
// ref/hare/strings/compare.ha:16.
@test fn compare_cases() void = {
if (strings.compare("ABC", "ABC") != 0) { fail(); };
if (strings.compare("ABC", "AB") <= 0) { fail(); };
if (strings.compare("AB", "ABC") >= 0) { fail(); };
if (strings.compare("BCD", "ABC") <= 0) { fail(); };
if (strings.compare("ABC", "abc") >= 0) { fail(); };
};
// ---- toutf8 / fromutf8_unsafe roundtrip -------------------------------
// ref/hare/strings/utf8.ha:31.
@test fn utf8_roundtrip_cases() void = {
let s: str = "hello";
let b: []u8 = strings.toutf8(s);
if (b.len != 5) { fail(); };
if (b[0] != 104u8) { fail(); }; // 'h'
let r: str = strings.fromutf8_unsafe(b);
if (!streq(r, "hello")) { fail(); };
if (r.ptr != s.ptr) { fail(); }; // borrowed, not copied
};
// ---- iter / next ------------------------------------------------------
// ref/hare/strings/iter.ha:84-108. Hare's @test fn iter() uses prev +
// riter heavily; both are deferred (no `utf8.prev`). Rebuild forward-
// only here: empty / ASCII / 2-byte / 3-byte / 4-byte / done@EOI /
// mixed-width.
@test fn iter_empty_cases() void = {
let it: strings.iterator = strings.iter("");
match (strings.next(&it)) {
case let r: rune => { fail(); };
case utf8.done => void;
};
// Repeated next after done stays done.
match (strings.next(&it)) {
case let r: rune => { fail(); };
case utf8.done => void;
};
};
@test fn iter_ascii_cases() void = {
let it: strings.iterator = strings.iter("hi!");
match (strings.next(&it)) {
case let r: rune => { if (r != 'h') { fail(); }; };
case utf8.done => { fail(); };
};
match (strings.next(&it)) {
case let r: rune => { if (r != 'i') { fail(); }; };
case utf8.done => { fail(); };
};
match (strings.next(&it)) {
case let r: rune => { if (r != '!') { fail(); }; };
case utf8.done => { fail(); };
};
match (strings.next(&it)) {
case let r: rune => { fail(); };
case utf8.done => void;
};
};
@test fn iter_twobyte_cases() void = {
let it: strings.iterator = strings.iter("café");
let i: i32 = 0;
let expect: [4]rune;
expect[0] = 'c'; expect[1] = 'a'; expect[2] = 'f';
expect[3] = 0xE9u32: rune; // 'é' U+00E9
for (i < 4) {
match (strings.next(&it)) {
case let r: rune => { if (r != expect[i]) { fail(); }; };
case utf8.done => { fail(); };
};
i += 1;
};
match (strings.next(&it)) {
case let r: rune => { fail(); };
case utf8.done => void;
};
};
@test fn iter_threebyte_cases() void = {
let it: strings.iterator = strings.iter("こんにちは");
let i: i32 = 0;
let expect: [5]rune;
expect[0] = 0x3053u32: rune; // 'こ'
expect[1] = 0x3093u32: rune; // 'ん'
expect[2] = 0x306Bu32: rune; // 'に'
expect[3] = 0x3061u32: rune; // 'ち'
expect[4] = 0x306Fu32: rune; // 'は'
for (i < 5) {
match (strings.next(&it)) {
case let r: rune => { if (r != expect[i]) { fail(); }; };
case utf8.done => { fail(); };
};
i += 1;
};
match (strings.next(&it)) {
case let r: rune => { fail(); };
case utf8.done => void;
};
};
@test fn iter_fourbyte_cases() void = {
let it: strings.iterator = strings.iter("🦀rust");
let i: i32 = 0;
let expect: [5]rune;
expect[0] = 0x1F980u32: rune; // '🦀'
expect[1] = 'r'; expect[2] = 'u'; expect[3] = 's'; expect[4] = 't';
for (i < 5) {
match (strings.next(&it)) {
case let r: rune => { if (r != expect[i]) { fail(); }; };
case utf8.done => { fail(); };
};
i += 1;
};
match (strings.next(&it)) {
case let r: rune => { fail(); };
case utf8.done => void;
};
};
@test fn iter_mixed_cases() void = {
// "Hello, 世界! 🌍" — 1+1+1+1+1+1+1+3+3+1+1+4 = 12 runes,
// widths 1/3/4 mixed.
let it: strings.iterator = strings.iter("Hello, 世界! 🌍");
let i: i32 = 0;
let expect: [12]rune;
expect[0] = 'H'; expect[1] = 'e'; expect[2] = 'l'; expect[3] = 'l';
expect[4] = 'o'; expect[5] = ','; expect[6] = ' ';
expect[7] = 0x4E16u32: rune; // '世'
expect[8] = 0x754Cu32: rune; // '界'
expect[9] = '!'; expect[10] = ' ';
expect[11] = 0x1F30Du32: rune; // '🌍'
for (i < 12) {
match (strings.next(&it)) {
case let r: rune => { if (r != expect[i]) { fail(); }; };
case utf8.done => { fail(); };
};
i += 1;
};
match (strings.next(&it)) {
case let r: rune => { fail(); };
case utf8.done => void;
};
};
// ---- prev / riter / iterstr / slice / position -----------------------
// ref/hare/strings/iter.ha:84-127. The Hare @test fn iter body uses
// `s = riter(...)` mid-test to swap the iterator's direction; ww's
// sret-into-existing-slot path handles that fine (probed pre-port).
@test fn iter_prev_at_start_cases() void = {
let it: strings.iterator = strings.iter("hi");
match (strings.prev(&it)) {
case utf8.done => void;
case let r: rune => { fail(); };
};
};
@test fn iter_prev_ascii_cases() void = {
let it: strings.iterator = strings.iter("abc");
match (strings.next(&it)) {
case let r: rune => { if (r != 'a') { fail(); }; };
case utf8.done => { fail(); };
};
match (strings.prev(&it)) {
case let r: rune => { if (r != 'a') { fail(); }; };
case utf8.done => { fail(); };
};
match (strings.prev(&it)) {
case utf8.done => void;
case let r: rune => { fail(); };
};
};
// Mirror of ref/hare/strings/iter.ha:84-108 — `iter("こんにちは")`,
// step+back+iterstr+riter-reassign sequence.
@test fn iter_full_cases() void = {
let s: strings.iterator = strings.iter("こんにちは");
match (strings.prev(&s)) {
case utf8.done => void;
case let r: rune => { fail(); };
};
let expect1: [2]rune;
expect1[0] = 0x3053u32: rune; // 'こ'
expect1[1] = 0x3093u32: rune; // 'ん'
let i: i32 = 0;
for (i < 2) {
match (strings.next(&s)) {
case let r: rune => { if (r != expect1[i]) { fail(); }; };
case utf8.done => { fail(); };
};
i += 1;
};
if (!streq(strings.iterstr(&s), "にちは")) { fail(); };
match (strings.prev(&s)) {
case let r: rune => { if (r != 0x3093u32: rune) { fail(); }; }; // 'ん'
case utf8.done => { fail(); };
};
let expect2: [4]rune;
expect2[0] = 0x3093u32: rune; // 'ん'
expect2[1] = 0x306Bu32: rune; // 'に'
expect2[2] = 0x3061u32: rune; // 'ち'
expect2[3] = 0x306Fu32: rune; // 'は'
i = 0;
for (i < 4) {
match (strings.next(&s)) {
case let r: rune => { if (r != expect2[i]) { fail(); }; };
case utf8.done => { fail(); };
};
i += 1;
};
match (strings.next(&s)) {
case utf8.done => void;
case let r: rune => { fail(); };
};
// Repeated next-after-done stays done.
match (strings.next(&s)) {
case utf8.done => void;
case let r: rune => { fail(); };
};
match (strings.prev(&s)) {
case let r: rune => { if (r != 0x306Fu32: rune) { fail(); }; }; // 'は'
case utf8.done => { fail(); };
};
// Swap to a reverse iterator. sret-into-existing-slot.
s = strings.riter("にちは");
let expect3: [3]rune;
expect3[0] = 0x306Fu32: rune; // 'は'
expect3[1] = 0x3061u32: rune; // 'ち'
expect3[2] = 0x306Bu32: rune; // 'に'
i = 0;
for (i < 3) {
match (strings.next(&s)) {
case let r: rune => { if (r != expect3[i]) { fail(); }; };
case utf8.done => { fail(); };
};
i += 1;
};
match (strings.next(&s)) {
case utf8.done => void;
case let r: rune => { fail(); };
};
match (strings.prev(&s)) {
case let r: rune => { if (r != 0x306Bu32: rune) { fail(); }; }; // 'に'
case utf8.done => { fail(); };
};
};
@test fn iter_position_cases() void = {
let it: strings.iterator = strings.iter("café"); // 5 bytes: c-a-f-é(2)
if (strings.position(&it) != 0) { fail(); };
match (strings.next(&it)) { case let r: rune => void; case utf8.done => { fail(); }; };
if (strings.position(&it) != 1) { fail(); };
match (strings.next(&it)) { case let r: rune => void; case utf8.done => { fail(); }; };
match (strings.next(&it)) { case let r: rune => void; case utf8.done => { fail(); }; };
if (strings.position(&it) != 3) { fail(); };
match (strings.next(&it)) { case let r: rune => void; case utf8.done => { fail(); }; };
if (strings.position(&it) != 5) { fail(); };
};
// ref/hare/strings/iter.ha:110 @test fn slice. Hare uses `let t = s;`
// to copy the iterator; ww re-initialises t from the same source to
// stay in scope of #32 (local struct ident rhs already fixed) without
// reaching for #35's sibling latents.
@test fn iter_slice_cases() void = {
let s: strings.iterator = strings.iter("こんにちは");
let t: strings.iterator = strings.iter("こんにちは");
if (strings.slice(&s, &t).len != 0) { fail(); };
if (strings.slice(&t, &s).len != 0) { fail(); };
let i: i32 = 0;
for (i < 2) {
match (strings.next(&s)) {
case let r: rune => void;
case utf8.done => { fail(); };
};
match (strings.next(&t)) {
case let r: rune => void;
case utf8.done => { fail(); };
};
i += 1;
};
if (strings.slice(&s, &t).len != 0) { fail(); };
if (strings.slice(&t, &s).len != 0) { fail(); };
i = 0;
for (i < 3) {
match (strings.next(&t)) {
case let r: rune => void;
case utf8.done => { fail(); };
};
i += 1;
};
if (!streq(strings.slice(&s, &t), "にちは")) { fail(); };
i = 0;
for (i < 3) {
match (strings.next(&s)) {
case let r: rune => void;
case utf8.done => { fail(); };
};
i += 1;
};
if (strings.slice(&s, &t).len != 0) { fail(); };
if (strings.slice(&t, &s).len != 0) { fail(); };
};
@test fn iter_iterstr_reverse_cases() void = {
// Reverse iter: iterstr is `src[0:offs]` — bytes BEFORE the cursor
// (the still-to-be-walked region in reverse direction).
let rit: strings.iterator = strings.riter("hello");
if (!streq(strings.iterstr(&rit), "hello")) { fail(); };
match (strings.next(&rit)) { case let r: rune => void; case utf8.done => { fail(); }; };
if (!streq(strings.iterstr(&rit), "hell")) { fail(); };
match (strings.next(&rit)) { case let r: rune => void; case utf8.done => { fail(); }; };
match (strings.next(&rit)) { case let r: rune => void; case utf8.done => { fail(); }; };
if (!streq(strings.iterstr(&rit), "he")) { fail(); };
};
export fn main() i32 = {
signalled = 1; dup_cases();
signalled = 2; concat_cases();
signalled = 3; hasprefix_cases();
signalled = 4; hassuffix_cases();
signalled = 5; contains_cases();
signalled = 6; byteindex_str_cases();
signalled = 7; byteindex_rune_cases();
signalled = 8; rbyteindex_cases();
signalled = 28; index_cases();
signalled = 29; rindex_cases();
signalled = 9; trimprefix_cases();
signalled = 10; trimsuffix_cases();
signalled = 11; ltrim_cases();
signalled = 12; rtrim_cases();
signalled = 13; trim_cases();
signalled = 14; compare_cases();
signalled = 15; utf8_roundtrip_cases();
signalled = 16; iter_empty_cases();
signalled = 17; iter_ascii_cases();
signalled = 18; iter_twobyte_cases();
signalled = 19; iter_threebyte_cases();
signalled = 20; iter_fourbyte_cases();
signalled = 21; iter_mixed_cases();
signalled = 22; iter_prev_at_start_cases();
signalled = 23; iter_prev_ascii_cases();
signalled = 24; iter_full_cases();
signalled = 25; iter_position_cases();
signalled = 26; iter_iterstr_reverse_cases();
signalled = 27; iter_slice_cases();
return 0;
};