// Vectors mirror ref/hare/strings/iter.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; // 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 => { abort(); }; case utf8.done => void; }; // Repeated next after done stays done. match (strings.next(&it)) { case let r: rune => { abort(); }; 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 => { assert(!(r != 'h')); }; case utf8.done => { abort(); }; }; match (strings.next(&it)) { case let r: rune => { assert(!(r != 'i')); }; case utf8.done => { abort(); }; }; match (strings.next(&it)) { case let r: rune => { assert(!(r != '!')); }; case utf8.done => { abort(); }; }; match (strings.next(&it)) { case let r: rune => { abort(); }; 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 => { assert(!(r != expect[i])); }; case utf8.done => { abort(); }; }; i += 1; }; match (strings.next(&it)) { case let r: rune => { abort(); }; 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 => { assert(!(r != expect[i])); }; case utf8.done => { abort(); }; }; i += 1; }; match (strings.next(&it)) { case let r: rune => { abort(); }; 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 => { assert(!(r != expect[i])); }; case utf8.done => { abort(); }; }; i += 1; }; match (strings.next(&it)) { case let r: rune => { abort(); }; 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 => { assert(!(r != expect[i])); }; case utf8.done => { abort(); }; }; i += 1; }; match (strings.next(&it)) { case let r: rune => { abort(); }; case utf8.done => void; }; }; // 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 => { abort(); }; }; }; @test fn iter_prev_ascii_cases() void = { let it: strings.iterator = strings.iter("abc"); match (strings.next(&it)) { case let r: rune => { assert(!(r != 'a')); }; case utf8.done => { abort(); }; }; match (strings.prev(&it)) { case let r: rune => { assert(!(r != 'a')); }; case utf8.done => { abort(); }; }; match (strings.prev(&it)) { case utf8.done => void; case let r: rune => { abort(); }; }; }; // 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 => { abort(); }; }; 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 => { assert(!(r != expect1[i])); }; case utf8.done => { abort(); }; }; i += 1; }; assert(!(!streq(strings.iterstr(&s), "にちは"))); match (strings.prev(&s)) { case let r: rune => { assert(!(r != 0x3093u32: rune)); }; // 'ん' case utf8.done => { abort(); }; }; 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 => { assert(!(r != expect2[i])); }; case utf8.done => { abort(); }; }; i += 1; }; match (strings.next(&s)) { case utf8.done => void; case let r: rune => { abort(); }; }; // Repeated next-after-done stays done. match (strings.next(&s)) { case utf8.done => void; case let r: rune => { abort(); }; }; match (strings.prev(&s)) { case let r: rune => { assert(!(r != 0x306Fu32: rune)); }; // 'は' case utf8.done => { abort(); }; }; // 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 => { assert(!(r != expect3[i])); }; case utf8.done => { abort(); }; }; i += 1; }; match (strings.next(&s)) { case utf8.done => void; case let r: rune => { abort(); }; }; match (strings.prev(&s)) { case let r: rune => { assert(!(r != 0x306Bu32: rune)); }; // 'に' case utf8.done => { abort(); }; }; }; @test fn iter_position_cases() void = { let it: strings.iterator = strings.iter("café"); // 5 bytes: c-a-f-é(2) assert(!(strings.position(&it) != 0)); match (strings.next(&it)) { case let r: rune => void; case utf8.done => { abort(); }; }; assert(!(strings.position(&it) != 1)); match (strings.next(&it)) { case let r: rune => void; case utf8.done => { abort(); }; }; match (strings.next(&it)) { case let r: rune => void; case utf8.done => { abort(); }; }; assert(!(strings.position(&it) != 3)); match (strings.next(&it)) { case let r: rune => void; case utf8.done => { abort(); }; }; assert(!(strings.position(&it) != 5)); }; // 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("こんにちは"); assert(!(strings.slice(&s, &t).len != 0)); assert(!(strings.slice(&t, &s).len != 0)); let i: i32 = 0; for (i < 2) { match (strings.next(&s)) { case let r: rune => void; case utf8.done => { abort(); }; }; match (strings.next(&t)) { case let r: rune => void; case utf8.done => { abort(); }; }; i += 1; }; assert(!(strings.slice(&s, &t).len != 0)); assert(!(strings.slice(&t, &s).len != 0)); i = 0; for (i < 3) { match (strings.next(&t)) { case let r: rune => void; case utf8.done => { abort(); }; }; i += 1; }; assert(!(!streq(strings.slice(&s, &t), "にちは"))); i = 0; for (i < 3) { match (strings.next(&s)) { case let r: rune => void; case utf8.done => { abort(); }; }; i += 1; }; assert(!(strings.slice(&s, &t).len != 0)); assert(!(strings.slice(&t, &s).len != 0)); }; @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"); assert(!(!streq(strings.iterstr(&rit), "hello"))); match (strings.next(&rit)) { case let r: rune => void; case utf8.done => { abort(); }; }; assert(!(!streq(strings.iterstr(&rit), "hell"))); match (strings.next(&rit)) { case let r: rune => void; case utf8.done => { abort(); }; }; match (strings.next(&rit)) { case let r: rune => void; case utf8.done => { abort(); }; }; assert(!(!streq(strings.iterstr(&rit), "he"))); };