lib/strings+test: Hare port (prev + riter + iterstr + position)
Port four of the five c3 strings functions per ref/hare/strings/iter.ha; strings.slice deferred behind task #34 (wwstage fnretlookup same-name cross-module phantom return-ABI fixup, sub-bug of #4e). - prev ref/hare/strings/iter.ha:49 - riter ref/hare/strings/iter.ha:32 - iterstr ref/hare/strings/iter.ha:63 - position ref/hare/strings/iter.ha:82 Also adds a private move() helper (ref/hare/strings/iter.ha:51) shared by next/prev. Hare's move picks the utf8 function via a fn-pointer (`let fun = if (forward) &utf8::next else &utf8::prev`); ww has no fn-pointers in scope yet, so move branches on `forward` and calls utf8.next or utf8.prev directly at each site. strings.next is updated to dispatch via move(!it.reverse, it) — c2's implementation always called utf8.next regardless of iter.reverse, which was correct for forward iter() but would walk forward on riter()-produced iterators too. With riter landed in this commit, next() now correctly walks backward on reverse iterators per Hare's ref/hare/strings/iter.ha:45. No in-tree consumer regression: only stringstest constructs iterators today. strings.iterstr uses ww's `[lo:hi]` slice syntax instead of Hare's `[lo..hi]`; same semantics (borrowed []u8 view). strings.slice deferred — Hare's body is `fromutf8_unsafe(utf8::slice(begin, end))` (ref/hare/strings/iter.ha:76). That delegation form triggers a wwstage fnretlookup miscompile when the caller module has a function of the same name as the callee (here: both strings::slice and utf8::slice exist), causing wwstage to emit the wrong return-ABI fixup (MOVQ DX, BX, str's AX/DX→AX/BX shim) after the cross-module CALL. Cstage handles the collision correctly; wwstage routes through the same-module function's return type and the byte-id checks (993_ww_ww + 995_self_rebuild) trip. Filed as task #34 with minimal repro; will land strings.slice when the fnretlookup graduate-by-module sub-bug in #34 is fixed. Top-of-file divergence note lists slice in the deferred set; the landing site keeps a comment-only stub. The public strings c3 surface ships 4-of-5 in this commit; slice + #34 land together in a follow-up. Tests: 5 new @test fns in stringstest.ww (signalled 22-26): iter_prev_at_start_cases (prev at offs=0 → done), iter_prev_ascii_cases (round-trip on forward iter), iter_full_cases (mirror of ref/hare/strings/iter.ha:84-108 — iter "こんにちは" with mid-walk iterstr + prev + next, then s = riter(...) sret-into-existing-slot for the reverse iterator pass), iter_position_cases (position tracks offs through a multibyte walk), iter_iterstr_reverse_cases (riter iterstr is bytes BEFORE the cursor, dual to forward iter's bytes-AFTER). The Hare @test fn iter body uses `s = riter("にちは")` mid-test to swap the iterator's direction (ref/hare/strings/iter.ha:101); ww's sret-into-existing-slot path handles that fine (probed pre-port). struct-copy let-from-ident (task #32) is sidestepped because no test creates a duplicate iterator via `let dup = it;`. 117/117 ok. 995_self_rebuild stays green (ww2==ww3==ww4 byte-id). 993_ww_ww + 994_w6c_ww also green (cstage/wwstage byte-identical on every corpus input including selfhost/cmd/wwdump/main.combined.ww).
This commit is contained in:
@@ -397,6 +397,134 @@ fn streq(a: str, b: str) bool = {
|
||||
};
|
||||
};
|
||||
|
||||
// ---- 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(); };
|
||||
};
|
||||
|
||||
@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();
|
||||
@@ -419,5 +547,10 @@ export fn main() i32 = {
|
||||
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();
|
||||
return 0;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user