Unblocked by the package-mode ruling: same-package test files now
compose everywhere (coordinator dir-mode owns test-library and the
libbyteid dir entry owns byte identity), so the per-file standalone
constraint is gone and Go's stdlib layout applies directly. The
15-banner strings_test.ww monolith dissolves into per-ref-file
family files — dup, concat, trim, sub, utf8, iter, tokenize
(tokenize+splitn+cut, all of ref tokenize.ha), pad, replace — plus
helpers_test.ww holding exactly the one cross-family helper (streq),
Go's shared test-helper idiom. Pure moves; family-local helpers
(expect_str_token et al.) stay in their family file. Leading banners
that merely restated the filename dropped; 6 interior sub-family
boundaries remain. lib/ census 141 -> 132.
libbyteid gains the dx() dir-mode entry form decided in B2: compose
every *_test.ww under moddir exactly as pkgcombined does
(module-reset separators, byte-lex order) and byte-id the composed
root through both driver stages. The five per-file strings fx
entries retire into one dx("lib/strings") — every source line they
covered is inside the composed unit, and the completeness scan still
keys the module. NENTEXPECT 60 -> 56.
Recorded, not split: fmt_test's 13 banners are scenario groups over
the one fprint surface and ref/hare/fmt itself keeps a single
+test.ha — a print/wrappers split would fight the ref; memio stays
per the standing ruling (everything ww ports lives in ref
stream.ha).
306 lines
9.4 KiB
Plaintext
306 lines
9.4 KiB
Plaintext
// 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;
|
|
};
|
|
};
|
|
|
|
// ---- 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 => { 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")));
|
|
};
|
|
|