lib/strings+test: re-port index str-arm to dual-iterator rune walk
Old shape ran byteindex then rewound to count runes — two passes, different algorithm from Hare. New `indexstring` mirrors ref/hare/strings/index.ha:59-81: one outer iterator over the haystack, an inner iterator re-seated from it for each candidate match, both walking rune-by-rune. Returns the rune-index of the first match, or void. Rest-iterator copy is field-wise rather than `let rest_iter = s_iter;` because the local-to-local copy of the 3-field iterator struct diverges between stages today (#41 — 993_ww_ww and 995_self_rebuild byte-diverge when written the natural way). WHY-comment cites #41 with the precise failing tests. Tests pin the rune-vs-byte distinction at i=2 and i=4 with 3-byte kana, plus self-match, empty-needle, empty-haystack, and a no-match multibyte row from ref/hare/strings/index.ha:119.
This commit is contained in:
@@ -2106,29 +2106,59 @@ export fn rbyteindex(haystack: str, needle: (str | rune)) (i32 | void) = {
|
||||
return bytes.rindex(toutf8(haystack), n);
|
||||
};
|
||||
|
||||
// index — rune-wise offset of `needle`'s first occurrence in
|
||||
// `haystack`, or void if absent. ref/hare/strings/index.ha:10. The
|
||||
// str-arm reuses `byteindex` for the anchor byte offset and then
|
||||
// walks `iter` forward to convert byte→rune index; the rune-arm
|
||||
// mirrors Hare's `index_rune` (ref/hare/strings/index.ha:31).
|
||||
export fn index(haystack: str, needle: (str | rune)) (i32 | void) = {
|
||||
match (needle) {
|
||||
case let s: str => {
|
||||
match (byteindex(haystack, s)) {
|
||||
case void => return;
|
||||
case let bo: i32 => {
|
||||
let it: iterator = iter(haystack);
|
||||
let i: i32 = 0;
|
||||
for (position(&it) < bo) {
|
||||
match (next(&it)) {
|
||||
case let r: rune => i += 1;
|
||||
case utf8.done => break;
|
||||
};
|
||||
// indexstring — str-arm of [[index]]. Dual-rune-iterator walk: at each
|
||||
// candidate rune index `i`, compare `haystack` from that position
|
||||
// against `needle` rune-by-rune until needle is exhausted (match) or
|
||||
// a mismatch / haystack-exhaustion breaks the inner loop. Mirrors
|
||||
// ref/hare/strings/index.ha:59 (#10). Hare copies `rest_iter = s_iter`
|
||||
// directly via struct assignment; ww re-seats `rest_iter` field-wise
|
||||
// because the let-init struct-copy form diverges between cstage and
|
||||
// wwstage on this iterator type (993_ww_ww + 995_self_rebuild fail,
|
||||
// filed as #41) and rule #10 (CLAUDE.md) forbids stage asymmetry.
|
||||
fn indexstring(haystack: str, needle: str) (i32 | void) = {
|
||||
let s_iter: iterator = iter(haystack);
|
||||
let i: i32 = 0;
|
||||
for (true) {
|
||||
let rest_iter: iterator;
|
||||
rest_iter.src = s_iter.src;
|
||||
rest_iter.offs = s_iter.offs;
|
||||
rest_iter.reverse = s_iter.reverse;
|
||||
let needle_iter: iterator = iter(needle);
|
||||
let matched: bool = false;
|
||||
for (true) {
|
||||
let rest_done: bool = false;
|
||||
let rest_r: rune;
|
||||
match (next(&rest_iter)) {
|
||||
case let r: rune => rest_r = r;
|
||||
case utf8.done => rest_done = true;
|
||||
};
|
||||
return i;
|
||||
let needle_done: bool = false;
|
||||
let needle_r: rune;
|
||||
match (next(&needle_iter)) {
|
||||
case let r: rune => needle_r = r;
|
||||
case utf8.done => needle_done = true;
|
||||
};
|
||||
if (rest_done && !needle_done) { break; };
|
||||
if (needle_done) { matched = true; break; };
|
||||
if (rest_r != needle_r) { break; };
|
||||
};
|
||||
if (matched) { return i; };
|
||||
match (next(&s_iter)) {
|
||||
case let r: rune => i += 1;
|
||||
case utf8.done => return;
|
||||
};
|
||||
};
|
||||
return;
|
||||
};
|
||||
|
||||
// index — rune-wise offset of `needle`'s first occurrence in
|
||||
// `haystack`, or void if absent. ref/hare/strings/index.ha:10. The
|
||||
// str-arm delegates to [[indexstring]] (dual-iterator rune-by-rune
|
||||
// walk per Hare's `index_string`, #10); the rune-arm mirrors Hare's
|
||||
// `index_rune` (ref/hare/strings/index.ha:31).
|
||||
export fn index(haystack: str, needle: (str | rune)) (i32 | void) = {
|
||||
match (needle) {
|
||||
case let s: str => return indexstring(haystack, s);
|
||||
case let r: rune => {
|
||||
let it: iterator = iter(haystack);
|
||||
let i: i32 = 0;
|
||||
|
||||
@@ -2106,29 +2106,59 @@ export fn rbyteindex(haystack: str, needle: (str | rune)) (i32 | void) = {
|
||||
return bytes.rindex(toutf8(haystack), n);
|
||||
};
|
||||
|
||||
// index — rune-wise offset of `needle`'s first occurrence in
|
||||
// `haystack`, or void if absent. ref/hare/strings/index.ha:10. The
|
||||
// str-arm reuses `byteindex` for the anchor byte offset and then
|
||||
// walks `iter` forward to convert byte→rune index; the rune-arm
|
||||
// mirrors Hare's `index_rune` (ref/hare/strings/index.ha:31).
|
||||
export fn index(haystack: str, needle: (str | rune)) (i32 | void) = {
|
||||
match (needle) {
|
||||
case let s: str => {
|
||||
match (byteindex(haystack, s)) {
|
||||
case void => return;
|
||||
case let bo: i32 => {
|
||||
let it: iterator = iter(haystack);
|
||||
let i: i32 = 0;
|
||||
for (position(&it) < bo) {
|
||||
match (next(&it)) {
|
||||
case let r: rune => i += 1;
|
||||
case utf8.done => break;
|
||||
};
|
||||
// indexstring — str-arm of [[index]]. Dual-rune-iterator walk: at each
|
||||
// candidate rune index `i`, compare `haystack` from that position
|
||||
// against `needle` rune-by-rune until needle is exhausted (match) or
|
||||
// a mismatch / haystack-exhaustion breaks the inner loop. Mirrors
|
||||
// ref/hare/strings/index.ha:59 (#10). Hare copies `rest_iter = s_iter`
|
||||
// directly via struct assignment; ww re-seats `rest_iter` field-wise
|
||||
// because the let-init struct-copy form diverges between cstage and
|
||||
// wwstage on this iterator type (993_ww_ww + 995_self_rebuild fail,
|
||||
// filed as #41) and rule #10 (CLAUDE.md) forbids stage asymmetry.
|
||||
fn indexstring(haystack: str, needle: str) (i32 | void) = {
|
||||
let s_iter: iterator = iter(haystack);
|
||||
let i: i32 = 0;
|
||||
for (true) {
|
||||
let rest_iter: iterator;
|
||||
rest_iter.src = s_iter.src;
|
||||
rest_iter.offs = s_iter.offs;
|
||||
rest_iter.reverse = s_iter.reverse;
|
||||
let needle_iter: iterator = iter(needle);
|
||||
let matched: bool = false;
|
||||
for (true) {
|
||||
let rest_done: bool = false;
|
||||
let rest_r: rune;
|
||||
match (next(&rest_iter)) {
|
||||
case let r: rune => rest_r = r;
|
||||
case utf8.done => rest_done = true;
|
||||
};
|
||||
return i;
|
||||
let needle_done: bool = false;
|
||||
let needle_r: rune;
|
||||
match (next(&needle_iter)) {
|
||||
case let r: rune => needle_r = r;
|
||||
case utf8.done => needle_done = true;
|
||||
};
|
||||
if (rest_done && !needle_done) { break; };
|
||||
if (needle_done) { matched = true; break; };
|
||||
if (rest_r != needle_r) { break; };
|
||||
};
|
||||
if (matched) { return i; };
|
||||
match (next(&s_iter)) {
|
||||
case let r: rune => i += 1;
|
||||
case utf8.done => return;
|
||||
};
|
||||
};
|
||||
return;
|
||||
};
|
||||
|
||||
// index — rune-wise offset of `needle`'s first occurrence in
|
||||
// `haystack`, or void if absent. ref/hare/strings/index.ha:10. The
|
||||
// str-arm delegates to [[indexstring]] (dual-iterator rune-by-rune
|
||||
// walk per Hare's `index_string`, #10); the rune-arm mirrors Hare's
|
||||
// `index_rune` (ref/hare/strings/index.ha:31).
|
||||
export fn index(haystack: str, needle: (str | rune)) (i32 | void) = {
|
||||
match (needle) {
|
||||
case let s: str => return indexstring(haystack, s);
|
||||
case let r: rune => {
|
||||
let it: iterator = iter(haystack);
|
||||
let i: i32 = 0;
|
||||
|
||||
Reference in New Issue
Block a user