lib/strings+test: split sub byte-wise vs rune-wise per Hare
The byte-indexed silent-clamp sub from ww was Hare's bytesub wearing the wrong name. Renamed accordingly; added the real rune-indexed sub per ref/hare/strings/sub.ha:30-42, with utf8bytelenbounded helper per :10. Both forms assert on start>end; bytesub also asserts end<=len(s). lib/getopt/getopt.ww:314 migrated to bytesub — its bi index is a byte offset over the arg's bytes. Tests cover ASCII parity, multi-byte UTF-8 (こんにちは / héllo) where rune index ≠ byte index, and a row contrasting identical args to make the distinction explicit. OOB-abort coverage deferred until the assert_aborts harness lands (#38). Selfhost combined.ww snapshots regenerated — they're bootstrap-stage inputs and would otherwise compile the old byte-wise sub. Two-arg default form omitted (#37, ww has no default parameter values).
This commit is contained in:
@@ -171,20 +171,46 @@ export fn join(delim: str, strs: str...) str = {
|
||||
return r;
|
||||
};
|
||||
|
||||
// sub — borrowed `s[start..end]`. ref/hare/strings/sub.ha:30 is
|
||||
// rune-wise; this ww form is byte-wise (no rune iterator yet, planned
|
||||
// for commit 2). Clamps out-of-range silently where Hare aborts —
|
||||
// retained for the existing getopt caller; will graduate when the
|
||||
// rune-wise form lands.
|
||||
// utf8bytelenbounded — walk `it` forward `end` runes and return the
|
||||
// resulting byte offset. ref/hare/strings/sub.ha:10. Aborts on
|
||||
// short input per Hare's contract for the rune-wise [[sub]].
|
||||
fn utf8bytelenbounded(it: *iterator, end: i32) i32 = {
|
||||
let i: i32 = 0;
|
||||
for (i < end) {
|
||||
match (next(it)) {
|
||||
case let r: rune => void;
|
||||
case utf8.done => abort("strings.sub: index exceeds string length");
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
return it.offs;
|
||||
};
|
||||
|
||||
// sub — borrowed substring [start, end) where start/end are rune
|
||||
// indices. ref/hare/strings/sub.ha:30. Hare's 2-arg `sub(s, start)`
|
||||
// defaulting end=END is omitted: ww has no default-parameter syntax
|
||||
// (filed as #37). Byte-indexed counterpart: [[bytesub]].
|
||||
export fn sub(s: str, start: i32, end: i32) str = {
|
||||
let lo: i32 = start;
|
||||
let hi: i32 = end;
|
||||
if (lo < 0) { lo = 0; };
|
||||
if (hi > s.len) { hi = s.len; };
|
||||
if (hi < lo) { hi = lo; };
|
||||
os.assert(start <= end, "strings.sub: start is higher than end");
|
||||
let it: iterator = iter(s);
|
||||
let starti: i32 = utf8bytelenbounded(&it, start);
|
||||
let endi: i32 = utf8bytelenbounded(&it, end - start);
|
||||
let r: str;
|
||||
r.ptr = s.ptr + (lo: u64);
|
||||
r.len = hi - lo;
|
||||
r.ptr = s.ptr + (starti: u64);
|
||||
r.len = endi - starti;
|
||||
return r;
|
||||
};
|
||||
|
||||
// bytesub — borrowed substring [start, end) where start/end are byte
|
||||
// offsets. ref/hare/strings/sub.ha:59. Hare's validating return
|
||||
// `(str | utf8::invalid)` is dropped (filed as #7); a multibyte-split
|
||||
// argument will yield a sub-string whose bytes don't decode cleanly.
|
||||
export fn bytesub(s: str, start: i32, end: i32) str = {
|
||||
os.assert(start <= end, "strings.bytesub: start is higher than end");
|
||||
os.assert(end <= s.len, "strings.bytesub: end exceeds string length");
|
||||
let r: str;
|
||||
r.ptr = s.ptr + (start: u64);
|
||||
r.len = end - start;
|
||||
return r;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user