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:
@@ -311,7 +311,7 @@ export fn tryparse(out: *command, argv: []str, help: []help) (void | error) = {
|
||||
} else {
|
||||
// PARAM: glued value, or next argv slot.
|
||||
if (bi + 1 < arg.len) {
|
||||
let v: str = strings.sub(arg, bi + 1, arg.len);
|
||||
let v: str = strings.bytesub(arg, bi + 1, arg.len);
|
||||
appendoption(&opts, r, v);
|
||||
advanced = true;
|
||||
} else {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -586,6 +586,65 @@ fn streq(a: str, b: str) bool = {
|
||||
if (strings.compare("ABC", "abc") >= 0) { fail(); };
|
||||
};
|
||||
|
||||
// ---- sub / bytesub ----------------------------------------------------
|
||||
// ref/hare/strings/sub.ha:44 (@test fn sub), :79 (@test fn bytesub). Hare's
|
||||
// 2-arg `sub(s, start)` rows are omitted: ww has no default-parameter
|
||||
// syntax (filed as #37). The `is utf8::invalid` row for bytesub is
|
||||
// deferred to the validating port (#7).
|
||||
|
||||
@test fn sub_cases() void = {
|
||||
signalled = 1790;
|
||||
if (!streq(strings.sub("a string", 0, 8), "a string")) { fail(); };
|
||||
signalled = 1791;
|
||||
if (!streq(strings.sub("a string", 0, 1), "a")) { fail(); };
|
||||
signalled = 1792;
|
||||
if (!streq(strings.sub("a string", 0, 3), "a s")) { fail(); };
|
||||
signalled = 1793;
|
||||
if (!streq(strings.sub("a string", 2, 8), "string")) { fail(); };
|
||||
// start == end yields an empty borrowed view.
|
||||
signalled = 1794;
|
||||
if (!streq(strings.sub("a string", 4, 4), "")) { fail(); };
|
||||
if (strings.sub("a string", 4, 4).len != 0) { fail(); };
|
||||
// Hare vector — rune indices 1..3 over "こんにちは" select bytes
|
||||
// 3..9 ("んに"), not bytes 1..3.
|
||||
signalled = 1795;
|
||||
if (!streq(strings.sub("こんにちは", 1, 3), "んに")) { fail(); };
|
||||
// 2-byte rune at rune index 1 in "héllo" — byte offsets 1..3.
|
||||
signalled = 1796;
|
||||
if (!streq(strings.sub("héllo", 1, 2), "é")) { fail(); };
|
||||
// start == 0, end == rune-len of full string.
|
||||
signalled = 1797;
|
||||
if (!streq(strings.sub("héllo", 0, 5), "héllo")) { fail(); };
|
||||
};
|
||||
|
||||
@test fn bytesub_cases() void = {
|
||||
signalled = 1800;
|
||||
if (!streq(strings.bytesub("a string", 0, 8), "a string")) { fail(); };
|
||||
signalled = 1801;
|
||||
if (!streq(strings.bytesub("a string", 0, 1), "a")) { fail(); };
|
||||
signalled = 1802;
|
||||
if (!streq(strings.bytesub("a string", 0, 3), "a s")) { fail(); };
|
||||
signalled = 1803;
|
||||
if (!streq(strings.bytesub("a string", 2, 8), "string")) { fail(); };
|
||||
signalled = 1804;
|
||||
if (!streq(strings.bytesub("a string", 4, 4), "")) { fail(); };
|
||||
// Hare vector — byte indices 3..9 over "こんにちは" select "んに".
|
||||
signalled = 1805;
|
||||
if (!streq(strings.bytesub("こんにちは", 3, 9), "んに")) { fail(); };
|
||||
// Rune/byte axis disagree on identical args (#3): sub(s,0,3) walks 3
|
||||
// runes and yields 9 bytes; bytesub(s,0,3) yields the first 3 bytes
|
||||
// — one 3-byte codepoint.
|
||||
signalled = 1806;
|
||||
if (!streq(strings.sub("こんにちは", 0, 3), "こんに")) { fail(); };
|
||||
if (!streq(strings.bytesub("こんにちは", 0, 3), "こ")) { fail(); };
|
||||
// Borrowed view: ptr aliases input.
|
||||
signalled = 1807;
|
||||
let s: str = "hello";
|
||||
let r: str = strings.bytesub(s, 1, 4);
|
||||
if (r.ptr != s.ptr + 1u64) { fail(); };
|
||||
if (r.len != 3) { fail(); };
|
||||
};
|
||||
|
||||
// ---- toutf8 / fromutf8_unsafe roundtrip -------------------------------
|
||||
// ref/hare/strings/utf8.ha:31.
|
||||
|
||||
@@ -1332,6 +1391,8 @@ export fn main() i32 = {
|
||||
signalled = 12; rtrim_cases();
|
||||
signalled = 13; trim_cases();
|
||||
signalled = 14; compare_cases();
|
||||
signalled = 40; sub_cases();
|
||||
signalled = 41; bytesub_cases();
|
||||
signalled = 15; utf8_roundtrip_cases();
|
||||
signalled = 16; iter_empty_cases();
|
||||
signalled = 17; iter_ascii_cases();
|
||||
|
||||
@@ -2008,20 +2008,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;
|
||||
};
|
||||
|
||||
|
||||
@@ -2008,20 +2008,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;
|
||||
};
|
||||
|
||||
|
||||
@@ -1899,20 +1899,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