Phase A.5's tupleelemslot / fieldslotsize hardcoded 16u64 for TY_STR and 24u64 for TY_SLICE — bypassing the tinfo.size SSoT seeded by lib/ww/typ.ww:189 (the very pivot they were introduced to consult). Route those four arms through pt.size / ft.size so #1 (str→24) and #34 (slice graduation) land as a one-line bump at the seed. lib/strings/stringstest.ww carried 12 `(cap: u64) * 16u64` strides missed by #43's sweep over strings.ww + shlex.ww; convert to `* size(str): u64` so the #42 fold owns the constant. Doc comments in strings.ww (freeall + splitn) updated to the same SSoT form. No-op at today's str.size=16 / slice=24: tinfo.size already matches the literals these arms had baked in. Reviewer's pre/post asm-identity probe (struct{i64,str,i64} + (i32,str,i32) tuple + bare str) shows zero-byte diff. 131/131 + 994 + 995 + bootstrap (ww2==ww3==ww4) green. Forward-link to #1 (str→24B bump) and #64 (sizelint pre-commit gate); #65 filed for lib/bytes + lib/getopt sibling sites the reviewer surfaced. Forward of #64 will catch any future regressions of this class.
1825 lines
59 KiB
Plaintext
1825 lines
59 KiB
Plaintext
// stringstest — exercises lib/strings. Run with
|
||
// `out/bin/ww run lib/strings/stringstest.ww`.
|
||
// Same signalled-then-fail()-with-+10 shape as bytes / utf8 / hex /
|
||
// time tests: non-zero exit pinpoints the failing scenario.
|
||
//
|
||
// Vectors mirror ref/hare/strings/{dup,concat,trim,contains,index,
|
||
// suffix,compare}.ha where ww can express them.
|
||
|
||
package strings;
|
||
|
||
import strings;
|
||
import encoding.utf8;
|
||
import os;
|
||
|
||
let signalled: i32 = 0;
|
||
fn fail() void = { os.exit(signalled + 10); };
|
||
|
||
fn streq(a: str, b: str) bool = {
|
||
if (a.len != b.len) { return false; };
|
||
let i: i32 = 0;
|
||
for (i < a.len) {
|
||
if (a[i] != b[i]) { return false; };
|
||
i += 1;
|
||
};
|
||
return true;
|
||
};
|
||
|
||
// ---- dup --------------------------------------------------------------
|
||
// ref/hare/strings/dup.ha:45.
|
||
|
||
@test fn dup_cases() void = {
|
||
let e: str = strings.dup("");
|
||
if (!streq(e, "")) { fail(); };
|
||
if (e.len != 0) { fail(); };
|
||
|
||
let h: str = strings.dup("hello");
|
||
if (!streq(h, "hello")) { fail(); };
|
||
defer os.free(h.ptr: *void, h.len: u64);
|
||
|
||
// multi-byte UTF-8: dup must copy raw bytes, not aliased view.
|
||
let m: str = strings.dup("こんにちは");
|
||
if (m.len != 15) { fail(); };
|
||
if (!streq(m, "こんにちは")) { fail(); };
|
||
if (m.ptr == "こんにちは".ptr) { fail(); }; // fresh alloc
|
||
defer os.free(m.ptr: *void, m.len: u64);
|
||
};
|
||
|
||
// ---- dupall -----------------------------------------------------------
|
||
// ref/hare/strings/dup.ha:55 (#6). Per-row `signalled` bump narrows a
|
||
// failure exit code; element reads go through `&toks.ptr[i]: *str`
|
||
// per the splitn cases (16B element copy gap, cgen.c:6515).
|
||
|
||
@test fn dupall_cases() void = {
|
||
// Empty input — empty result, mirrors Hare's `payload = []`.
|
||
signalled = 1800;
|
||
let empty: []str;
|
||
empty.ptr = nil: *str;
|
||
empty.len = 0;
|
||
empty.cap = 0;
|
||
match (strings.dupall(empty)) {
|
||
case let r: []str => {
|
||
if (r.len != 0) { fail(); };
|
||
strings.freeall(r);
|
||
};
|
||
case nomem => { fail(); };
|
||
};
|
||
|
||
// Two-element ASCII — each output element is a fresh allocation
|
||
// independent of the input (ptr differs from the borrowed source).
|
||
signalled = 1801;
|
||
let in2: [2]str;
|
||
in2[0] = "hello";
|
||
in2[1] = "world";
|
||
let src2: []str;
|
||
src2.ptr = &in2[0];
|
||
src2.len = 2;
|
||
src2.cap = 2;
|
||
match (strings.dupall(src2)) {
|
||
case let r: []str => {
|
||
if (r.len != 2) { fail(); };
|
||
expect_str(r, 0, "hello");
|
||
expect_str(r, 1, "world");
|
||
let p0: *str = &r.ptr[0];
|
||
if (p0.ptr == in2[0].ptr) { fail(); };
|
||
let p1: *str = &r.ptr[1];
|
||
if (p1.ptr == in2[1].ptr) { fail(); };
|
||
strings.freeall(r);
|
||
};
|
||
case nomem => { fail(); };
|
||
};
|
||
|
||
// Singleton — `only` rune-equivalent of Hare's `["only"]`.
|
||
signalled = 1802;
|
||
let in1: [1]str;
|
||
in1[0] = "only";
|
||
let src1: []str;
|
||
src1.ptr = &in1[0];
|
||
src1.len = 1;
|
||
src1.cap = 1;
|
||
match (strings.dupall(src1)) {
|
||
case let r: []str => {
|
||
if (r.len != 1) { fail(); };
|
||
expect_str(r, 0, "only");
|
||
strings.freeall(r);
|
||
};
|
||
case nomem => { fail(); };
|
||
};
|
||
|
||
// Multibyte — UTF-8 bytes (5-byte and 15-byte) round-trip.
|
||
signalled = 1803;
|
||
let inm: [2]str;
|
||
inm[0] = "héllo";
|
||
inm[1] = "こんにちは";
|
||
let srcm: []str;
|
||
srcm.ptr = &inm[0];
|
||
srcm.len = 2;
|
||
srcm.cap = 2;
|
||
match (strings.dupall(srcm)) {
|
||
case let r: []str => {
|
||
if (r.len != 2) { fail(); };
|
||
expect_str(r, 0, "héllo");
|
||
expect_str(r, 1, "こんにちは");
|
||
let p0: *str = &r.ptr[0];
|
||
if (p0.len != 6) { fail(); }; // é is 2 bytes
|
||
if (p0.ptr == inm[0].ptr) { fail(); };
|
||
let p1: *str = &r.ptr[1];
|
||
if (p1.len != 15) { fail(); }; // each kana is 3 bytes
|
||
if (p1.ptr == inm[1].ptr) { fail(); };
|
||
strings.freeall(r);
|
||
};
|
||
case nomem => { fail(); };
|
||
};
|
||
};
|
||
|
||
// ---- concat -----------------------------------------------------------
|
||
// ref/hare/strings/concat.ha:18. Rows mirror Hare's vectors (0/1/2/3-arg,
|
||
// empty-mid, 2-empty) plus empty-first / empty-last / multibyte. The
|
||
// per-row `signalled` bump narrows a failure exit code to the offending
|
||
// row.
|
||
|
||
@test fn concat_cases() void = {
|
||
let pool: [17]str;
|
||
pool[0] = "hello";
|
||
pool[1] = "hello ";
|
||
pool[2] = "world";
|
||
pool[3] = "hello";
|
||
pool[4] = " ";
|
||
pool[5] = "world";
|
||
pool[6] = "hello";
|
||
pool[7] = "";
|
||
pool[8] = "world";
|
||
pool[9] = "";
|
||
pool[10] = "";
|
||
pool[11] = "";
|
||
pool[12] = "world";
|
||
pool[13] = "hello";
|
||
pool[14] = "";
|
||
pool[15] = "こん";
|
||
pool[16] = "にちは";
|
||
|
||
let argo: [9]i32;
|
||
let argn: [9]i32;
|
||
let want: [9]str;
|
||
let labels: [9]str;
|
||
argo[0]=0; argn[0]=0; want[0]=""; labels[0]="0-arg";
|
||
argo[1]=0; argn[1]=1; want[1]="hello"; labels[1]="1-arg";
|
||
argo[2]=1; argn[2]=2; want[2]="hello world"; labels[2]="2-arg";
|
||
argo[3]=3; argn[3]=3; want[3]="hello world"; labels[3]="3-arg";
|
||
argo[4]=6; argn[4]=3; want[4]="helloworld"; labels[4]="empty-mid";
|
||
argo[5]=9; argn[5]=2; want[5]=""; labels[5]="2-empty";
|
||
argo[6]=11; argn[6]=2; want[6]="world"; labels[6]="empty-first";
|
||
argo[7]=13; argn[7]=2; want[7]="hello"; labels[7]="empty-last";
|
||
argo[8]=15; argn[8]=2; want[8]="こんにちは"; labels[8]="multibyte";
|
||
|
||
let i: i32 = 0;
|
||
for (i < 9) {
|
||
signalled = 200 + i;
|
||
let argv: []str;
|
||
argv.ptr = &pool[argo[i]];
|
||
argv.len = argn[i];
|
||
argv.cap = argn[i];
|
||
let got: str = strings.concat(argv...);
|
||
if (!streq(got, want[i])) { fail(); };
|
||
if (got.len > 0) { os.free(got.ptr: *void, got.len: u64); };
|
||
i += 1;
|
||
};
|
||
};
|
||
|
||
// ---- join -------------------------------------------------------------
|
||
// ref/hare/strings/concat.ha:64. Rows mirror Hare's @test fn join
|
||
// (0-arg, 1-arg, empty-sep, 3-arg.) plus 2-arg, all-empties, long sep,
|
||
// multibyte, empty-mid (delim still inserted around the empty slot).
|
||
// Per-row `signalled` 1620+i narrows the failing row.
|
||
|
||
@test fn join_cases() void = {
|
||
let pool: [19]str;
|
||
pool[0] = "hello";
|
||
pool[1] = "a";
|
||
pool[2] = "b";
|
||
pool[3] = "a";
|
||
pool[4] = "b";
|
||
pool[5] = "c";
|
||
pool[6] = "a";
|
||
pool[7] = "b";
|
||
pool[8] = "c";
|
||
pool[9] = "";
|
||
pool[10] = "";
|
||
pool[11] = "a";
|
||
pool[12] = "b";
|
||
pool[13] = "c";
|
||
pool[14] = "こん";
|
||
pool[15] = "にちは";
|
||
pool[16] = "a";
|
||
pool[17] = "";
|
||
pool[18] = "b";
|
||
|
||
let argo: [9]i32;
|
||
let argn: [9]i32;
|
||
let seps: [9]str;
|
||
let want: [9]str;
|
||
argo[0]=0; argn[0]=0; seps[0]="."; want[0]="";
|
||
argo[1]=0; argn[1]=1; seps[1]="."; want[1]="hello";
|
||
argo[2]=1; argn[2]=2; seps[2]=", "; want[2]="a, b";
|
||
argo[3]=3; argn[3]=3; seps[3]="."; want[3]="a.b.c";
|
||
argo[4]=6; argn[4]=3; seps[4]=""; want[4]="abc";
|
||
argo[5]=9; argn[5]=2; seps[5]=","; want[5]=",";
|
||
argo[6]=11; argn[6]=3; seps[6]=" :: "; want[6]="a :: b :: c";
|
||
argo[7]=14; argn[7]=2; seps[7]="・"; want[7]="こん・にちは";
|
||
argo[8]=16; argn[8]=3; seps[8]="-"; want[8]="a--b";
|
||
|
||
let i: i32 = 0;
|
||
for (i < 9) {
|
||
signalled = 1620 + i;
|
||
let argv: []str;
|
||
argv.ptr = &pool[argo[i]];
|
||
argv.len = argn[i];
|
||
argv.cap = argn[i];
|
||
let got: str = strings.join(seps[i], argv...);
|
||
if (!streq(got, want[i])) { fail(); };
|
||
if (got.len > 0) { os.free(got.ptr: *void, got.len: u64); };
|
||
i += 1;
|
||
};
|
||
};
|
||
|
||
// ---- hasprefix --------------------------------------------------------
|
||
// ref/hare/strings/suffix.ha:18.
|
||
|
||
@test fn hasprefix_cases() void = {
|
||
if (!strings.hasprefix("hello world", "hello")) { fail(); };
|
||
if (!strings.hasprefix("hello world", 'h')) { fail(); };
|
||
if ( strings.hasprefix("hello world", "world")) { fail(); };
|
||
if ( strings.hasprefix("hello world", 'q')) { fail(); };
|
||
if (!strings.hasprefix("hello", "hello")) { fail(); }; // equal-len
|
||
if (!strings.hasprefix("anything", "")) { fail(); }; // empty prefix
|
||
if ( strings.hasprefix("", "x")) { fail(); };
|
||
// multibyte rune prefix — '\'é\'' literal blocked by single-byte
|
||
// lexrune (lib/ww/lex/lex.ww:659); pass codepoint directly.
|
||
if (!strings.hasprefix("éclat", 0xE9u32: rune)) { fail(); };
|
||
if (!strings.hasprefix("🦀rust", 0x1F980u32: rune)) { fail(); };
|
||
};
|
||
|
||
// ---- hassuffix --------------------------------------------------------
|
||
// ref/hare/strings/suffix.ha:36.
|
||
|
||
@test fn hassuffix_cases() void = {
|
||
if (!strings.hassuffix("hello world", "world")) { fail(); };
|
||
if (!strings.hassuffix("hello world", 'd')) { fail(); };
|
||
if ( strings.hassuffix("hello world", "hello")) { fail(); };
|
||
if ( strings.hassuffix("hello world", 'h')) { fail(); };
|
||
if (!strings.hassuffix("café", 0xE9u32: rune)) { fail(); }; // multibyte
|
||
};
|
||
|
||
// ---- contains ---------------------------------------------------------
|
||
// ref/hare/strings/contains.ha:27.
|
||
|
||
@test fn contains_cases() void = {
|
||
if (!strings.contains("hello world", "hello")) { fail(); };
|
||
if (!strings.contains("hello world", 'h')) { fail(); };
|
||
if ( strings.contains("hello world", 'x')) { fail(); };
|
||
if (!strings.contains("hello world", "world")) { fail(); };
|
||
if (!strings.contains("hello world", "")) { fail(); }; // empty hits at 0
|
||
if ( strings.contains("hello world", "foobar")) { fail(); };
|
||
if (!strings.contains("こんにちは", 0x306Bu32: rune)) { fail(); }; // 'に'
|
||
if (!strings.contains("こんにちは", "ちは")) { fail(); };
|
||
|
||
// Variadic rows. ref/hare/strings/contains.ha:27.
|
||
signalled = 1600;
|
||
if ( strings.contains("hello")) { fail(); };
|
||
signalled = 1601;
|
||
if (!strings.contains("hello world", "world")) { fail(); };
|
||
signalled = 1602;
|
||
if (!strings.contains("hello", 'l')) { fail(); };
|
||
signalled = 1603;
|
||
if (!strings.contains("hello world", "foo", "world", 'x')) { fail(); };
|
||
signalled = 1604;
|
||
if ( strings.contains("hello", "foo", 'z', "bar")) { fail(); };
|
||
signalled = 1605;
|
||
if (!strings.contains("héllo", "x", 0xE9u32: rune)) { fail(); };
|
||
};
|
||
|
||
// ---- byteindex --------------------------------------------------------
|
||
// ref/hare/strings/index.ha:147 (byteindex tests, both arms).
|
||
|
||
@test fn byteindex_str_cases() void = {
|
||
match (strings.byteindex("hello", "hello")) {
|
||
case let i: i32 => { if (i != 0) { fail(); }; };
|
||
case void => { fail(); };
|
||
};
|
||
match (strings.byteindex("hello world!", "world")) {
|
||
case let i: i32 => { if (i != 6) { fail(); }; };
|
||
case void => { fail(); };
|
||
};
|
||
match (strings.byteindex("hello world!", "orld!")) {
|
||
case let i: i32 => { if (i != 7) { fail(); }; };
|
||
case void => { fail(); };
|
||
};
|
||
match (strings.byteindex("hello world!", "word")) {
|
||
case let i: i32 => { fail(); };
|
||
case void => void;
|
||
};
|
||
// empty needle hits at 0 (ref/hare/bytes/index.ha:63).
|
||
match (strings.byteindex("hello", "")) {
|
||
case let i: i32 => { if (i != 0) { fail(); }; };
|
||
case void => { fail(); };
|
||
};
|
||
// empty haystack, non-empty needle — absent.
|
||
match (strings.byteindex("", "x")) {
|
||
case let i: i32 => { fail(); };
|
||
case void => void;
|
||
};
|
||
// multibyte substring in multibyte haystack.
|
||
match (strings.byteindex("こんにちは", "ちは")) {
|
||
case let i: i32 => { if (i != 9) { fail(); }; };
|
||
case void => { fail(); };
|
||
};
|
||
};
|
||
|
||
@test fn byteindex_rune_cases() void = {
|
||
// ASCII rune (1-byte encoding).
|
||
match (strings.byteindex("hello world", 'w')) {
|
||
case let i: i32 => { if (i != 6) { fail(); }; };
|
||
case void => { fail(); };
|
||
};
|
||
// 2-byte rune U+00E9 'é' inside "café".
|
||
match (strings.byteindex("café", 0xE9u32: rune)) {
|
||
case let i: i32 => { if (i != 3) { fail(); }; };
|
||
case void => { fail(); };
|
||
};
|
||
// 3-byte rune U+3061 'ち' inside "こんにちは".
|
||
match (strings.byteindex("こんにちは", 0x3061u32: rune)) {
|
||
case let i: i32 => { if (i != 9) { fail(); }; };
|
||
case void => { fail(); };
|
||
};
|
||
// 4-byte rune U+1F980 '🦀' inside "ab🦀cd".
|
||
match (strings.byteindex("ab🦀cd", 0x1F980u32: rune)) {
|
||
case let i: i32 => { if (i != 2) { fail(); }; };
|
||
case void => { fail(); };
|
||
};
|
||
// absent.
|
||
match (strings.byteindex("こんにちは", 'q')) {
|
||
case let i: i32 => { fail(); };
|
||
case void => void;
|
||
};
|
||
};
|
||
|
||
// ---- rbyteindex -------------------------------------------------------
|
||
|
||
@test fn rbyteindex_cases() void = {
|
||
// Two 'た' in "またあったね" — ref/hare/strings/index.ha:160-161.
|
||
match (strings.byteindex("またあったね", "た")) {
|
||
case let i: i32 => { if (i != 3) { fail(); }; };
|
||
case void => { fail(); };
|
||
};
|
||
match (strings.rbyteindex("またあったね", "た")) {
|
||
case let i: i32 => { if (i != 12) { fail(); }; };
|
||
case void => { fail(); };
|
||
};
|
||
// Rune arm, multi-byte 'に' U+306B.
|
||
match (strings.rbyteindex("こんにちは", 0x306Bu32: rune)) {
|
||
case let i: i32 => { if (i != 6) { fail(); }; };
|
||
case void => { fail(); };
|
||
};
|
||
// Absent.
|
||
match (strings.rbyteindex("abc", 'z')) {
|
||
case let i: i32 => { fail(); };
|
||
case void => void;
|
||
};
|
||
};
|
||
|
||
// ---- index ------------------------------------------------------------
|
||
// ref/hare/strings/index.ha:108. Rune-wise offset, NOT byte-wise — the
|
||
// multibyte rows pin that distinction (Hare doc at index.ha:7).
|
||
|
||
@test fn index_cases() void = {
|
||
// str-arm: ASCII haystack/needle, mid-string match.
|
||
signalled = 1400;
|
||
match (strings.index("hello", "ll")) {
|
||
case let i: i32 => { if (i != 2) { fail(); }; };
|
||
case void => { fail(); };
|
||
};
|
||
// str-arm: absent needle.
|
||
signalled = 1401;
|
||
match (strings.index("hello", "world")) {
|
||
case let i: i32 => { fail(); };
|
||
case void => void;
|
||
};
|
||
// Hare vectors at ref/hare/strings/index.ha:113-119.
|
||
signalled = 1402;
|
||
match (strings.index("hello world!", "hello")) {
|
||
case let i: i32 => { if (i != 0) { fail(); }; };
|
||
case void => { fail(); };
|
||
};
|
||
signalled = 1403;
|
||
match (strings.index("hello world!", "world")) {
|
||
case let i: i32 => { if (i != 6) { fail(); }; };
|
||
case void => { fail(); };
|
||
};
|
||
signalled = 1404;
|
||
match (strings.index("hello world!", "orld!")) {
|
||
case let i: i32 => { if (i != 7) { fail(); }; };
|
||
case void => { fail(); };
|
||
};
|
||
// Multibyte haystack + str needle: "ちは" at rune index 3
|
||
// in "こんにちは" (byteindex returns 9, rune-index is 3).
|
||
signalled = 1405;
|
||
match (strings.index("こんにちは", "ちは")) {
|
||
case let i: i32 => { if (i != 3) { fail(); }; };
|
||
case void => { fail(); };
|
||
};
|
||
// rune-arm.
|
||
signalled = 1406;
|
||
match (strings.index("hello", 'l')) {
|
||
case let i: i32 => { if (i != 2) { fail(); }; };
|
||
case void => { fail(); };
|
||
};
|
||
signalled = 1407;
|
||
match (strings.index("hello world", 'w')) {
|
||
case let i: i32 => { if (i != 6) { fail(); }; };
|
||
case void => { fail(); };
|
||
};
|
||
// Multibyte rune: 'é' U+00E9 at rune 1 in "héllo" — pins
|
||
// rune-index vs byte-index (byteindex returns 1; rune-index is
|
||
// 1 also — but the str-arm's 1405 row covers the distinction).
|
||
signalled = 1408;
|
||
match (strings.index("héllo", 0xE9u32: rune)) {
|
||
case let i: i32 => { if (i != 1) { fail(); }; };
|
||
case void => { fail(); };
|
||
};
|
||
// Multibyte rune in multibyte haystack: 'ち' U+3061 at rune 3
|
||
// in "こんにちは" (byteindex returns 9, rune-index is 3).
|
||
signalled = 1409;
|
||
match (strings.index("こんにちは", 0x3061u32: rune)) {
|
||
case let i: i32 => { if (i != 3) { fail(); }; };
|
||
case void => { fail(); };
|
||
};
|
||
signalled = 1410;
|
||
match (strings.index("こんにちは", 'q')) {
|
||
case let i: i32 => { fail(); };
|
||
case void => void;
|
||
};
|
||
// str-arm: rune-index ≠ byte-index again, mid-string match.
|
||
// "あった" starts at rune 2 (byte 6) in "またあったね"
|
||
// (each kana is 3 bytes; ref/hare/strings/index.ha:60). Pins
|
||
// the dual-iterator walk against the discarded byteindex-and-walk
|
||
// shape (#10).
|
||
signalled = 1411;
|
||
match (strings.index("またあったね", "あった")) {
|
||
case let i: i32 => { if (i != 2) { fail(); }; };
|
||
case void => { fail(); };
|
||
};
|
||
// str-arm: tail-anchored multibyte needle. "は" is at rune 4
|
||
// (byte 12) in "こんにちは".
|
||
signalled = 1412;
|
||
match (strings.index("こんにちは", "は")) {
|
||
case let i: i32 => { if (i != 4) { fail(); }; };
|
||
case void => { fail(); };
|
||
};
|
||
// Empty needle hits at rune 0 — Hare's `index_string` falls into
|
||
// the `needle_rune is done` branch on the very first inner step
|
||
// (ref/hare/strings/index.ha:70).
|
||
signalled = 1413;
|
||
match (strings.index("hello", "")) {
|
||
case let i: i32 => { if (i != 0) { fail(); }; };
|
||
case void => { fail(); };
|
||
};
|
||
signalled = 1414;
|
||
match (strings.index("", "")) {
|
||
case let i: i32 => { if (i != 0) { fail(); }; };
|
||
case void => { fail(); };
|
||
};
|
||
// Empty haystack, non-empty needle — absent.
|
||
signalled = 1415;
|
||
match (strings.index("", "x")) {
|
||
case let i: i32 => { fail(); };
|
||
case void => void;
|
||
};
|
||
// Multibyte haystack, multibyte absent needle — exercises the
|
||
// inner-loop mismatch-break across runes (#10, Hare row
|
||
// ref/hare/strings/index.ha:119).
|
||
signalled = 1416;
|
||
match (strings.index("こんにちは", "きょうは")) {
|
||
case let i: i32 => { fail(); };
|
||
case void => void;
|
||
};
|
||
// Self-match: haystack == needle, Hare row index.ha:113.
|
||
signalled = 1417;
|
||
match (strings.index("hello", "hello")) {
|
||
case let i: i32 => { if (i != 0) { fail(); }; };
|
||
case void => { fail(); };
|
||
};
|
||
};
|
||
|
||
// ---- rindex -----------------------------------------------------------
|
||
// ref/hare/strings/index.ha:22. Symmetric: last-occurrence rune index.
|
||
|
||
@test fn rindex_cases() void = {
|
||
// str-arm.
|
||
signalled = 1500;
|
||
match (strings.rindex("hello", "lo")) {
|
||
case let i: i32 => { if (i != 3) { fail(); }; };
|
||
case void => { fail(); };
|
||
};
|
||
// Hare vector at ref/hare/strings/index.ha:122.
|
||
signalled = 1501;
|
||
match (strings.rindex("hello world!", "o")) {
|
||
case let i: i32 => { if (i != 7) { fail(); }; };
|
||
case void => { fail(); };
|
||
};
|
||
signalled = 1502;
|
||
match (strings.rindex("hello", "world")) {
|
||
case let i: i32 => { fail(); };
|
||
case void => void;
|
||
};
|
||
// Multibyte: last "た" in "またあったね" — rbyteindex returns
|
||
// 12, rune-index is 4 (ま=0 た=1 あ=2 っ=3 た=4 ね=5).
|
||
signalled = 1503;
|
||
match (strings.rindex("またあったね", "た")) {
|
||
case let i: i32 => { if (i != 4) { fail(); }; };
|
||
case void => { fail(); };
|
||
};
|
||
// rune-arm.
|
||
signalled = 1504;
|
||
match (strings.rindex("hello", 'l')) {
|
||
case let i: i32 => { if (i != 3) { fail(); }; };
|
||
case void => { fail(); };
|
||
};
|
||
signalled = 1505;
|
||
match (strings.rindex("aaaaa", 'a')) {
|
||
case let i: i32 => { if (i != 4) { fail(); }; };
|
||
case void => { fail(); };
|
||
};
|
||
// Multibyte rune: 'に' U+306B at rune 2 in "こんにちは".
|
||
signalled = 1506;
|
||
match (strings.rindex("こんにちは", 0x306Bu32: rune)) {
|
||
case let i: i32 => { if (i != 2) { fail(); }; };
|
||
case void => { fail(); };
|
||
};
|
||
signalled = 1507;
|
||
match (strings.rindex("hello", 'z')) {
|
||
case let i: i32 => { fail(); };
|
||
case void => void;
|
||
};
|
||
};
|
||
|
||
// ---- trimprefix / trimsuffix ------------------------------------------
|
||
// ref/hare/strings/trim.ha:99-107.
|
||
|
||
@test fn trimprefix_cases() void = {
|
||
if (!streq(strings.trimprefix("", ""), "")) { fail(); };
|
||
if (!streq(strings.trimprefix("", "blablabla"), "")) { fail(); };
|
||
if (!streq(strings.trimprefix("hello, world", "hello"), ", world")) { fail(); };
|
||
if (!streq(strings.trimprefix("blablabla", "bla"), "blabla")) { fail(); };
|
||
// equal-length match strips to empty.
|
||
if (!streq(strings.trimprefix("hello", "hello"), "")) { fail(); };
|
||
};
|
||
|
||
@test fn trimsuffix_cases() void = {
|
||
if (!streq(strings.trimsuffix("", ""), "")) { fail(); };
|
||
if (!streq(strings.trimsuffix("", "blablabla"), "")) { fail(); };
|
||
if (!streq(strings.trimsuffix("hello, world", "world"), "hello, ")) { fail(); };
|
||
if (!streq(strings.trimsuffix("blablabla", "bla"), "blabla")) { fail(); };
|
||
if (!streq(strings.trimsuffix("hello", "hello"), "")) { fail(); };
|
||
};
|
||
|
||
// ---- ltrim / rtrim / trim ---------------------------------------------
|
||
// ref/hare/strings/trim.ha:75-97. 0-arg rows (#9) pin the ASCII
|
||
// whitespace set (' ', '\t', '\n', '\r' — ref/hare/strings/trim.ha:6).
|
||
|
||
@test fn ltrim_cases() void = {
|
||
let runes: [9]rune;
|
||
runes[0] = 'x';
|
||
runes[1] = 'a';
|
||
runes[2] = 0x1D68Au32: rune;
|
||
runes[3] = '(';
|
||
runes[4] = ')';
|
||
runes[5] = 'a';
|
||
runes[6] = 'b';
|
||
runes[7] = 'c';
|
||
runes[8] = 'd';
|
||
|
||
// 0-arg rows (8..10) strip ASCII whitespace per Hare (#9):
|
||
// only the leading side is stripped for ltrim.
|
||
let inputs: [11]str;
|
||
let argo: [11]i32;
|
||
let argn: [11]i32;
|
||
let want: [11]str;
|
||
inputs[0]=""; argo[0]=0; argn[0]=1; want[0]="";
|
||
inputs[1]="aaabc"; argo[1]=1; argn[1]=1; want[1]="bc";
|
||
inputs[2]="xyz"; argo[2]=1; argn[2]=1; want[2]="xyz";
|
||
inputs[3]="aaaa"; argo[3]=1; argn[3]=1; want[3]="";
|
||
inputs[4]="𝚊𝚊hi"; argo[4]=2; argn[4]=1; want[4]="hi";
|
||
inputs[5]="((()(())))())"; argo[5]=3; argn[5]=2; want[5]="";
|
||
inputs[6]="abacadabra"; argo[6]=5; argn[6]=4; want[6]="ra";
|
||
inputs[7]="hello"; argo[7]=0; argn[7]=0; want[7]="hello";
|
||
inputs[8]=" hello "; argo[8]=0; argn[8]=0; want[8]="hello ";
|
||
inputs[9]="\t\r\n hello"; argo[9]=0; argn[9]=0; want[9]="hello";
|
||
inputs[10]=" "; argo[10]=0; argn[10]=0; want[10]="";
|
||
|
||
let i: i32 = 0;
|
||
for (i < 11) {
|
||
signalled = 1100 + i;
|
||
let argv: []rune;
|
||
argv.ptr = &runes[argo[i]];
|
||
argv.len = argn[i];
|
||
argv.cap = argn[i];
|
||
let got: str = strings.ltrim(inputs[i], argv...);
|
||
if (!streq(got, want[i])) { fail(); };
|
||
i += 1;
|
||
};
|
||
};
|
||
|
||
@test fn rtrim_cases() void = {
|
||
let runes: [19]rune;
|
||
runes[0] = 'x';
|
||
runes[1] = 'a';
|
||
runes[2] = 0x1D68Au32: rune;
|
||
runes[3] = 'w';
|
||
runes[4] = 'd';
|
||
runes[5] = 'o';
|
||
runes[6] = 'r';
|
||
runes[7] = ' ';
|
||
runes[8] = 's';
|
||
runes[9] = 'i';
|
||
runes[10] = 'l';
|
||
runes[11] = 'z';
|
||
runes[12] = 't';
|
||
runes[13] = 'm';
|
||
runes[14] = 'n';
|
||
runes[15] = 'o';
|
||
runes[16] = 'e';
|
||
runes[17] = 'a';
|
||
runes[18] = 'd';
|
||
|
||
// 0-arg rows (8..10) strip ASCII whitespace per Hare (#9):
|
||
// only the trailing side is stripped for rtrim.
|
||
let inputs: [11]str;
|
||
let argo: [11]i32;
|
||
let argn: [11]i32;
|
||
let want: [11]str;
|
||
inputs[0]=""; argo[0]=0; argn[0]=1; want[0]="";
|
||
inputs[1]="bcaaa"; argo[1]=1; argn[1]=1; want[1]="bc";
|
||
inputs[2]="xyz"; argo[2]=1; argn[2]=1; want[2]="xyz";
|
||
inputs[3]="aaaa"; argo[3]=1; argn[3]=1; want[3]="";
|
||
inputs[4]="hi𝚊𝚊"; argo[4]=2; argn[4]=1; want[4]="hi";
|
||
inputs[5]="yellowwooddoor";
|
||
argo[5]=3; argn[5]=4; want[5]="yell";
|
||
inputs[6]="Sentimentalized sensationalism sensationalized sentimentalisms";
|
||
argo[6]=7; argn[6]=12; want[6]="S";
|
||
inputs[7]="hello"; argo[7]=0; argn[7]=0; want[7]="hello";
|
||
inputs[8]=" hello "; argo[8]=0; argn[8]=0; want[8]=" hello";
|
||
inputs[9]="hello, world\r\n\r\n";
|
||
argo[9]=0; argn[9]=0; want[9]="hello, world";
|
||
inputs[10]=" "; argo[10]=0; argn[10]=0; want[10]="";
|
||
|
||
let i: i32 = 0;
|
||
for (i < 11) {
|
||
signalled = 1200 + i;
|
||
let argv: []rune;
|
||
argv.ptr = &runes[argo[i]];
|
||
argv.len = argn[i];
|
||
argv.cap = argn[i];
|
||
let got: str = strings.rtrim(inputs[i], argv...);
|
||
if (!streq(got, want[i])) { fail(); };
|
||
i += 1;
|
||
};
|
||
};
|
||
|
||
@test fn trim_cases() void = {
|
||
let runes: [8]rune;
|
||
runes[0] = 'x';
|
||
runes[1] = 'a';
|
||
runes[2] = 'm';
|
||
runes[3] = 'i';
|
||
runes[4] = 'p';
|
||
runes[5] = 's';
|
||
runes[6] = '[';
|
||
runes[7] = ']';
|
||
|
||
// 0-arg rows (7..9) strip ASCII whitespace per Hare (#9) from
|
||
// both ends.
|
||
let inputs: [10]str;
|
||
let argo: [10]i32;
|
||
let argn: [10]i32;
|
||
let want: [10]str;
|
||
inputs[0]=""; argo[0]=0; argn[0]=1; want[0]="";
|
||
inputs[1]="aaabcaaa"; argo[1]=1; argn[1]=1; want[1]="bc";
|
||
inputs[2]="xyz"; argo[2]=1; argn[2]=1; want[2]="xyz";
|
||
inputs[3]="aaaa"; argo[3]=1; argn[3]=1; want[3]="";
|
||
inputs[4]="mississippi"; argo[4]=2; argn[4]=4; want[4]="";
|
||
inputs[5]="[[][[[]]][][].[[]][]]][]]]";
|
||
argo[5]=6; argn[5]=2; want[5]=".";
|
||
inputs[6]="hello"; argo[6]=0; argn[6]=0; want[6]="hello";
|
||
inputs[7]=" hello "; argo[7]=0; argn[7]=0; want[7]="hello";
|
||
inputs[8]="\r\thello\n\r"; argo[8]=0; argn[8]=0; want[8]="hello";
|
||
inputs[9]=" "; argo[9]=0; argn[9]=0; want[9]="";
|
||
|
||
let i: i32 = 0;
|
||
for (i < 10) {
|
||
signalled = 1300 + i;
|
||
let argv: []rune;
|
||
argv.ptr = &runes[argo[i]];
|
||
argv.len = argn[i];
|
||
argv.cap = argn[i];
|
||
let got: str = strings.trim(inputs[i], argv...);
|
||
if (!streq(got, want[i])) { fail(); };
|
||
i += 1;
|
||
};
|
||
};
|
||
|
||
// ---- compare ----------------------------------------------------------
|
||
// ref/hare/strings/compare.ha:16.
|
||
|
||
@test fn compare_cases() void = {
|
||
if (strings.compare("ABC", "ABC") != 0) { fail(); };
|
||
if (strings.compare("ABC", "AB") <= 0) { fail(); };
|
||
if (strings.compare("AB", "ABC") >= 0) { fail(); };
|
||
if (strings.compare("BCD", "ABC") <= 0) { fail(); };
|
||
if (strings.compare("ABC", "abc") >= 0) { fail(); };
|
||
if (strings.compare("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). bytesub now validates rune boundaries (#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(); };
|
||
};
|
||
|
||
// Match-shape mirrors Hare's `bytesub(...)!` at ref/hare/strings/sub.ha:
|
||
// 80-86. Inlined at each call site (rather than a helper that takes
|
||
// `(str | utf8.invalid)` by value) because the union-by-value path
|
||
// crashes — same lift-on-pass-by-value family as #48.
|
||
@test fn bytesub_cases() void = {
|
||
signalled = 1800;
|
||
match (strings.bytesub("a string", 0, 8)) {
|
||
case let s: str => { if (!streq(s, "a string")) { fail(); }; };
|
||
case let e: utf8.invalid => { fail(); };
|
||
};
|
||
signalled = 1801;
|
||
match (strings.bytesub("a string", 0, 1)) {
|
||
case let s: str => { if (!streq(s, "a")) { fail(); }; };
|
||
case let e: utf8.invalid => { fail(); };
|
||
};
|
||
signalled = 1802;
|
||
match (strings.bytesub("a string", 0, 3)) {
|
||
case let s: str => { if (!streq(s, "a s")) { fail(); }; };
|
||
case let e: utf8.invalid => { fail(); };
|
||
};
|
||
signalled = 1803;
|
||
match (strings.bytesub("a string", 2, 8)) {
|
||
case let s: str => { if (!streq(s, "string")) { fail(); }; };
|
||
case let e: utf8.invalid => { fail(); };
|
||
};
|
||
signalled = 1804;
|
||
match (strings.bytesub("a string", 4, 4)) {
|
||
case let s: str => { if (!streq(s, "")) { fail(); }; };
|
||
case let e: utf8.invalid => { fail(); };
|
||
};
|
||
// Hare vector — byte indices 3..9 over "こんにちは" select "んに".
|
||
signalled = 1805;
|
||
match (strings.bytesub("こんにちは", 3, 9)) {
|
||
case let s: str => { if (!streq(s, "んに")) { fail(); }; };
|
||
case let e: utf8.invalid => { 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(); };
|
||
match (strings.bytesub("こんにちは", 0, 3)) {
|
||
case let s: str => { if (!streq(s, "こ")) { fail(); }; };
|
||
case let e: utf8.invalid => { fail(); };
|
||
};
|
||
// Borrowed view: ptr aliases input.
|
||
signalled = 1807;
|
||
let s: str = "hello";
|
||
match (strings.bytesub(s, 1, 4)) {
|
||
case let r: str => {
|
||
if (r.ptr != s.ptr + 1u64) { fail(); };
|
||
if (r.len != 3) { fail(); };
|
||
};
|
||
case let e: utf8.invalid => { fail(); };
|
||
};
|
||
// Hare's invalid row (ref/hare/strings/sub.ha:87) — start lands on
|
||
// a continuation byte (2nd byte of "こ"), bytesub must reject (#7).
|
||
signalled = 1808;
|
||
match (strings.bytesub("こんにちは", 1, 3)) {
|
||
case let r: str => { fail(); };
|
||
case let e: utf8.invalid => void;
|
||
};
|
||
// Symmetric: end lands on a continuation byte (2nd byte of "ん").
|
||
signalled = 1809;
|
||
match (strings.bytesub("こんにちは", 0, 4)) {
|
||
case let r: str => { fail(); };
|
||
case let e: utf8.invalid => void;
|
||
};
|
||
// end == s.len bypasses the continuation check (s[end] is OOB).
|
||
signalled = 1810;
|
||
match (strings.bytesub("こんにちは", 0, 15)) {
|
||
case let s: str => { if (!streq(s, "こんにちは")) { fail(); }; };
|
||
case let e: utf8.invalid => { fail(); };
|
||
};
|
||
};
|
||
|
||
// ---- toutf8 / fromutf8_unsafe roundtrip -------------------------------
|
||
// ref/hare/strings/utf8.ha:31.
|
||
|
||
@test fn utf8_roundtrip_cases() void = {
|
||
let s: str = "hello";
|
||
let b: []u8 = strings.toutf8(s);
|
||
if (b.len != 5) { fail(); };
|
||
if (b[0] != 104u8) { fail(); }; // 'h'
|
||
let r: str = strings.fromutf8_unsafe(b);
|
||
if (!streq(r, "hello")) { fail(); };
|
||
if (r.ptr != s.ptr) { fail(); }; // borrowed, not copied
|
||
};
|
||
|
||
// ---- fromutf8 ---------------------------------------------------------
|
||
// ref/hare/strings/utf8.ha:22 + @test fn utf8 (line 31). Validating
|
||
// sibling of fromutf8_unsafe — walks via [[utf8.validate]] and returns
|
||
// `utf8.invalid` on any malformed byte sequence (#7).
|
||
|
||
@test fn fromutf8_cases() void = {
|
||
// Hare vector — "hello world" round-trips.
|
||
signalled = 1900;
|
||
let hb: [11]u8;
|
||
hb[0] = 0x68u8; hb[1] = 0x65u8; hb[2] = 0x6Cu8; hb[3] = 0x6Cu8;
|
||
hb[4] = 0x6Fu8; hb[5] = 0x20u8; hb[6] = 0x77u8; hb[7] = 0x6Fu8;
|
||
hb[8] = 0x72u8; hb[9] = 0x6Cu8; hb[10] = 0x64u8;
|
||
match (strings.fromutf8(hb[0:11])) {
|
||
case let s: str => { if (!streq(s, "hello world")) { fail(); }; };
|
||
case let e: utf8.invalid => { fail(); };
|
||
};
|
||
|
||
// Empty input — Hare's `fromutf8([])! == ""`.
|
||
signalled = 1901;
|
||
let eb: [1]u8;
|
||
match (strings.fromutf8(eb[0:0])) {
|
||
case let s: str => { if (s.len != 0) { fail(); }; };
|
||
case let e: utf8.invalid => { fail(); };
|
||
};
|
||
|
||
// ASCII-only borrowed view: ptr aliases input.
|
||
signalled = 1902;
|
||
let s: str = "abc";
|
||
let bs: []u8 = strings.toutf8(s);
|
||
match (strings.fromutf8(bs)) {
|
||
case let r: str => {
|
||
if (!streq(r, "abc")) { fail(); };
|
||
if (r.ptr != s.ptr) { fail(); };
|
||
};
|
||
case let e: utf8.invalid => { fail(); };
|
||
};
|
||
|
||
// Multibyte Japanese — full UTF-8 round-trip.
|
||
signalled = 1903;
|
||
let jb: [15]u8;
|
||
jb[0] = 0xE3u8; jb[1] = 0x81u8; jb[2] = 0x93u8; // こ
|
||
jb[3] = 0xE3u8; jb[4] = 0x82u8; jb[5] = 0x93u8; // ん
|
||
jb[6] = 0xE3u8; jb[7] = 0x81u8; jb[8] = 0xABu8; // に
|
||
jb[9] = 0xE3u8; jb[10] = 0x81u8; jb[11] = 0xA1u8; // ち
|
||
jb[12] = 0xE3u8; jb[13] = 0x81u8; jb[14] = 0xAFu8; // は
|
||
match (strings.fromutf8(jb[0:15])) {
|
||
case let s: str => { if (!streq(s, "こんにちは")) { fail(); }; };
|
||
case let e: utf8.invalid => { fail(); };
|
||
};
|
||
|
||
// Multibyte French — mixed-width "héllo".
|
||
signalled = 1904;
|
||
let fb: [6]u8;
|
||
fb[0] = 0x68u8; // h
|
||
fb[1] = 0xC3u8; fb[2] = 0xA9u8; // é
|
||
fb[3] = 0x6Cu8; fb[4] = 0x6Cu8; fb[5] = 0x6Fu8; // llo
|
||
match (strings.fromutf8(fb[0:6])) {
|
||
case let s: str => { if (!streq(s, "héllo")) { fail(); }; };
|
||
case let e: utf8.invalid => { fail(); };
|
||
};
|
||
|
||
// Lone 0x80 continuation byte — invalid.
|
||
signalled = 1905;
|
||
let c: [1]u8;
|
||
c[0] = 0x80u8;
|
||
match (strings.fromutf8(c[0:1])) {
|
||
case let r: str => { fail(); };
|
||
case let e: utf8.invalid => void;
|
||
};
|
||
|
||
// Overlong 2-byte encoding of NUL (0xC0 0x80) — invalid per
|
||
// ref/hare/encoding/utf8/decodetable.ha (state 0 rejects 0xC0).
|
||
signalled = 1906;
|
||
let ov: [2]u8;
|
||
ov[0] = 0xC0u8; ov[1] = 0x80u8;
|
||
match (strings.fromutf8(ov[0:2])) {
|
||
case let r: str => { fail(); };
|
||
case let e: utf8.invalid => void;
|
||
};
|
||
|
||
// Truncated multibyte — 0xE3 0x81 (2 of 3 bytes for U+3053) is
|
||
// rejected as invalid by [[utf8.validate]] (state != 0 at EOI).
|
||
signalled = 1907;
|
||
let tr: [2]u8;
|
||
tr[0] = 0xE3u8; tr[1] = 0x81u8;
|
||
match (strings.fromutf8(tr[0:2])) {
|
||
case let r: str => { fail(); };
|
||
case let e: utf8.invalid => void;
|
||
};
|
||
};
|
||
|
||
// ---- iter / next ------------------------------------------------------
|
||
// 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 => { fail(); };
|
||
case utf8.done => void;
|
||
};
|
||
// Repeated next after done stays done.
|
||
match (strings.next(&it)) {
|
||
case let r: rune => { fail(); };
|
||
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 => { if (r != 'h') { fail(); }; };
|
||
case utf8.done => { fail(); };
|
||
};
|
||
match (strings.next(&it)) {
|
||
case let r: rune => { if (r != 'i') { fail(); }; };
|
||
case utf8.done => { fail(); };
|
||
};
|
||
match (strings.next(&it)) {
|
||
case let r: rune => { if (r != '!') { fail(); }; };
|
||
case utf8.done => { fail(); };
|
||
};
|
||
match (strings.next(&it)) {
|
||
case let r: rune => { fail(); };
|
||
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 => { if (r != expect[i]) { fail(); }; };
|
||
case utf8.done => { fail(); };
|
||
};
|
||
i += 1;
|
||
};
|
||
match (strings.next(&it)) {
|
||
case let r: rune => { fail(); };
|
||
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 => { if (r != expect[i]) { fail(); }; };
|
||
case utf8.done => { fail(); };
|
||
};
|
||
i += 1;
|
||
};
|
||
match (strings.next(&it)) {
|
||
case let r: rune => { fail(); };
|
||
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 => { if (r != expect[i]) { fail(); }; };
|
||
case utf8.done => { fail(); };
|
||
};
|
||
i += 1;
|
||
};
|
||
match (strings.next(&it)) {
|
||
case let r: rune => { fail(); };
|
||
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 => { if (r != expect[i]) { fail(); }; };
|
||
case utf8.done => { fail(); };
|
||
};
|
||
i += 1;
|
||
};
|
||
match (strings.next(&it)) {
|
||
case let r: rune => { fail(); };
|
||
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 => { 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(); };
|
||
};
|
||
|
||
// 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("こんにちは");
|
||
if (strings.slice(&s, &t).len != 0) { fail(); };
|
||
if (strings.slice(&t, &s).len != 0) { fail(); };
|
||
let i: i32 = 0;
|
||
for (i < 2) {
|
||
match (strings.next(&s)) {
|
||
case let r: rune => void;
|
||
case utf8.done => { fail(); };
|
||
};
|
||
match (strings.next(&t)) {
|
||
case let r: rune => void;
|
||
case utf8.done => { fail(); };
|
||
};
|
||
i += 1;
|
||
};
|
||
if (strings.slice(&s, &t).len != 0) { fail(); };
|
||
if (strings.slice(&t, &s).len != 0) { fail(); };
|
||
i = 0;
|
||
for (i < 3) {
|
||
match (strings.next(&t)) {
|
||
case let r: rune => void;
|
||
case utf8.done => { fail(); };
|
||
};
|
||
i += 1;
|
||
};
|
||
if (!streq(strings.slice(&s, &t), "にちは")) { fail(); };
|
||
i = 0;
|
||
for (i < 3) {
|
||
match (strings.next(&s)) {
|
||
case let r: rune => void;
|
||
case utf8.done => { fail(); };
|
||
};
|
||
i += 1;
|
||
};
|
||
if (strings.slice(&s, &t).len != 0) { fail(); };
|
||
if (strings.slice(&t, &s).len != 0) { 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(); };
|
||
};
|
||
|
||
// ---- tokenize / rtokenize / peek_token / remaining_tokens -----------
|
||
// ref/hare/strings/tokenize.ha:110 @test fn tokenize. Row drivers
|
||
// mirror lib/bytes/bytestest expect_token / expect_done but compare
|
||
// str via streq.
|
||
|
||
fn expect_str_token(t: *strings.tokenizer, want: str) void = {
|
||
match (strings.peek_token(t)) {
|
||
case let p: str => { if (!streq(p, want)) { fail(); }; };
|
||
case bytes.done => { fail(); };
|
||
};
|
||
match (strings.next_token(t)) {
|
||
case let n: str => { if (!streq(n, want)) { fail(); }; };
|
||
case bytes.done => { fail(); };
|
||
};
|
||
};
|
||
|
||
fn expect_str_done(t: *strings.tokenizer) void = {
|
||
match (strings.peek_token(t)) {
|
||
case let p: str => { fail(); };
|
||
case bytes.done => void;
|
||
};
|
||
match (strings.next_token(t)) {
|
||
case let n: str => { fail(); };
|
||
case bytes.done => void;
|
||
};
|
||
};
|
||
|
||
@test fn tokenize_cases() void = {
|
||
// Hare vector — single-space delim. ref/hare/strings/tokenize.ha:112.
|
||
signalled = 1700;
|
||
let t: strings.tokenizer = strings.tokenize(
|
||
"Hello world! My name is Harriet.", " ");
|
||
expect_str_token(&t, "Hello");
|
||
expect_str_token(&t, "world!");
|
||
expect_str_token(&t, "My");
|
||
expect_str_token(&t, "name");
|
||
expect_str_token(&t, "is");
|
||
expect_str_token(&t, "Harriet.");
|
||
expect_str_done(&t);
|
||
|
||
// Multi-byte delim set — space + tab.
|
||
// ref/hare/strings/tokenize.ha:124.
|
||
signalled = 1701;
|
||
let t2: strings.tokenizer = strings.tokenize(
|
||
"/dev/sda1\t/ ext4 rw,relatime\t0 0", " \t");
|
||
expect_str_token(&t2, "/dev/sda1");
|
||
expect_str_token(&t2, "/");
|
||
expect_str_token(&t2, "ext4");
|
||
expect_str_token(&t2, "rw,relatime");
|
||
expect_str_token(&t2, "0");
|
||
expect_str_token(&t2, "0");
|
||
expect_str_done(&t2);
|
||
|
||
// Consecutive delimiters — empty interior tokens.
|
||
// ref/hare/strings/tokenize.ha:136.
|
||
signalled = 1702;
|
||
let t3: strings.tokenizer = strings.tokenize("hello world", " ");
|
||
expect_str_token(&t3, "hello");
|
||
expect_str_token(&t3, "");
|
||
expect_str_token(&t3, "");
|
||
expect_str_token(&t3, "");
|
||
expect_str_token(&t3, "world");
|
||
expect_str_done(&t3);
|
||
|
||
// Leading + trailing delimiters yield empty tokens.
|
||
// ref/hare/strings/tokenize.ha:147.
|
||
signalled = 1703;
|
||
let t4: strings.tokenizer = strings.tokenize(" hello world ", " ");
|
||
expect_str_token(&t4, "");
|
||
expect_str_token(&t4, "hello");
|
||
expect_str_token(&t4, "world");
|
||
expect_str_token(&t4, "");
|
||
expect_str_done(&t4);
|
||
|
||
// No delim hit — single full token.
|
||
signalled = 1704;
|
||
let t5: strings.tokenizer = strings.tokenize("abc", " ");
|
||
expect_str_token(&t5, "abc");
|
||
expect_str_done(&t5);
|
||
|
||
// Empty input — done immediately.
|
||
signalled = 1705;
|
||
let t6: strings.tokenizer = strings.tokenize("", " ");
|
||
expect_str_done(&t6);
|
||
};
|
||
|
||
@test fn rtokenize_cases() void = {
|
||
// Reverse direction — first next_token is the last token.
|
||
signalled = 1710;
|
||
let t: strings.tokenizer = strings.rtokenize(
|
||
"Hello world! My name is Harriet.", " ");
|
||
expect_str_token(&t, "Harriet.");
|
||
expect_str_token(&t, "is");
|
||
expect_str_token(&t, "name");
|
||
expect_str_token(&t, "My");
|
||
expect_str_token(&t, "world!");
|
||
expect_str_token(&t, "Hello");
|
||
expect_str_done(&t);
|
||
|
||
// Multi-byte delim set, reverse direction.
|
||
signalled = 1711;
|
||
let t2: strings.tokenizer = strings.rtokenize("a b\tc", " \t");
|
||
expect_str_token(&t2, "c");
|
||
expect_str_token(&t2, "b");
|
||
expect_str_token(&t2, "a");
|
||
expect_str_done(&t2);
|
||
|
||
// Empty input — done immediately.
|
||
signalled = 1712;
|
||
let t3: strings.tokenizer = strings.rtokenize("", " ");
|
||
expect_str_done(&t3);
|
||
};
|
||
|
||
@test fn peek_token_cases() void = {
|
||
// Two peeks without advancing return the same token.
|
||
signalled = 1720;
|
||
let t: strings.tokenizer = strings.tokenize("a b c", " ");
|
||
match (strings.peek_token(&t)) {
|
||
case let p: str => { if (!streq(p, "a")) { fail(); }; };
|
||
case bytes.done => { fail(); };
|
||
};
|
||
match (strings.peek_token(&t)) {
|
||
case let p: str => { if (!streq(p, "a")) { fail(); }; };
|
||
case bytes.done => { fail(); };
|
||
};
|
||
// Advance once — peek then returns "b".
|
||
signalled = 1721;
|
||
match (strings.next_token(&t)) {
|
||
case let n: str => { if (!streq(n, "a")) { fail(); }; };
|
||
case bytes.done => { fail(); };
|
||
};
|
||
match (strings.peek_token(&t)) {
|
||
case let p: str => { if (!streq(p, "b")) { fail(); }; };
|
||
case bytes.done => { fail(); };
|
||
};
|
||
|
||
// Empty input — peek is done.
|
||
signalled = 1722;
|
||
let t2: strings.tokenizer = strings.tokenize("", " ");
|
||
match (strings.peek_token(&t2)) {
|
||
case let p: str => { fail(); };
|
||
case bytes.done => void;
|
||
};
|
||
};
|
||
|
||
@test fn remaining_tokens_cases() void = {
|
||
// ref/hare/strings/tokenize.ha:157. After 2 next_tokens, remaining
|
||
// is "My name is Harriet.".
|
||
signalled = 1730;
|
||
let t: strings.tokenizer = strings.tokenize(
|
||
"Hello world! My name is Harriet.", " ");
|
||
match (strings.next_token(&t)) {
|
||
case let n: str => { if (!streq(n, "Hello")) { fail(); }; };
|
||
case bytes.done => { fail(); };
|
||
};
|
||
match (strings.next_token(&t)) {
|
||
case let n: str => { if (!streq(n, "world!")) { fail(); }; };
|
||
case bytes.done => { fail(); };
|
||
};
|
||
if (!streq(strings.remaining_tokens(&t), "My name is Harriet.")) {
|
||
fail();
|
||
};
|
||
|
||
// Fresh tokenizer — remaining_tokens is the whole input.
|
||
signalled = 1731;
|
||
let t2: strings.tokenizer = strings.tokenize("a b c", " ");
|
||
if (!streq(strings.remaining_tokens(&t2), "a b c")) { fail(); };
|
||
};
|
||
|
||
// ---- splitn / rsplitn / split ----------------------------------------
|
||
// ref/hare/strings/tokenize.ha:245 @test fn split. Hare's vectors
|
||
// mirrored here directly; element reads go through `&toks.ptr[i]: *str`
|
||
// rather than `toks[i]` so the 16B str element copy stays out of the
|
||
// multi-word-store gap noted at cmd/w6c/cgen.c:6515.
|
||
|
||
fn expect_str(toks: []str, i: i32, want: str) void = {
|
||
if (i >= toks.len) { fail(); };
|
||
let p: *str = &toks.ptr[i];
|
||
if (p.len != want.len) { fail(); };
|
||
let j: i32 = 0;
|
||
for (j < want.len) {
|
||
if (p.ptr[j] != want[j]) { fail(); };
|
||
j += 1;
|
||
};
|
||
};
|
||
|
||
@test fn splitn_cases() void = {
|
||
// ref/hare/strings/tokenize.ha:247 — n=4 buckets the trailing
|
||
// "is Drew" as the remainder slot.
|
||
signalled = 1740;
|
||
let t1: []str = strings.splitn("Hello, my name is Drew", " ", 4);
|
||
if (t1.len != 4) { fail(); };
|
||
expect_str(t1, 0, "Hello,");
|
||
expect_str(t1, 1, "my");
|
||
expect_str(t1, 2, "name");
|
||
expect_str(t1, 3, "is Drew");
|
||
os.free(t1.ptr: *void, (t1.cap: u64) * size(str): u64);
|
||
|
||
// ref/hare/strings/tokenize.ha:263 — n > tokens leaves a single
|
||
// slot holding the unchanged input (delim not found).
|
||
signalled = 1741;
|
||
let t2: []str = strings.splitn("one", "=", 2);
|
||
if (t2.len != 1) { fail(); };
|
||
expect_str(t2, 0, "one");
|
||
os.free(t2.ptr: *void, (t2.cap: u64) * size(str): u64);
|
||
|
||
// n == 1 — single slot holding the whole input as remainder.
|
||
signalled = 1742;
|
||
let t3: []str = strings.splitn("a b c", " ", 1);
|
||
if (t3.len != 1) { fail(); };
|
||
expect_str(t3, 0, "a b c");
|
||
os.free(t3.ptr: *void, (t3.cap: u64) * size(str): u64);
|
||
|
||
// Empty input — empty result.
|
||
signalled = 1743;
|
||
let t4: []str = strings.splitn("", " ", 5);
|
||
if (t4.len != 0) { fail(); };
|
||
if (t4.cap > 0) {
|
||
os.free(t4.ptr: *void, (t4.cap: u64) * size(str): u64);
|
||
};
|
||
|
||
// Multi-byte delim set (byte-set semantics per
|
||
// ref/hare/strings/tokenize.ha:35) — split on ',' OR ':' OR ';'.
|
||
signalled = 1744;
|
||
let t5: []str = strings.splitn("hello;world,foo:bar", ",:;", 10);
|
||
if (t5.len != 4) { fail(); };
|
||
expect_str(t5, 0, "hello");
|
||
expect_str(t5, 1, "world");
|
||
expect_str(t5, 2, "foo");
|
||
expect_str(t5, 3, "bar");
|
||
os.free(t5.ptr: *void, (t5.cap: u64) * size(str): u64);
|
||
};
|
||
|
||
@test fn rsplitn_cases() void = {
|
||
// ref/hare/strings/tokenize.ha:271 — reverse n=4 with the
|
||
// "Hello, my" prefix as the remainder slot at index 0.
|
||
signalled = 1750;
|
||
let t1: []str = strings.rsplitn("Hello, my name is Drew", " ", 4);
|
||
if (t1.len != 4) { fail(); };
|
||
expect_str(t1, 0, "Hello, my");
|
||
expect_str(t1, 1, "name");
|
||
expect_str(t1, 2, "is");
|
||
expect_str(t1, 3, "Drew");
|
||
os.free(t1.ptr: *void, (t1.cap: u64) * size(str): u64);
|
||
|
||
// n > token count — done short-circuit returns toks UN-reversed
|
||
// (last-token-first order). Mirrors bytes.rsplitn (Hare's
|
||
// ref/hare/strings/tokenize.ha:219-224 reverse step is gated
|
||
// behind the n-1 loop completion).
|
||
signalled = 1751;
|
||
let t2: []str = strings.rsplitn("a b c", " ", 10);
|
||
if (t2.len != 3) { fail(); };
|
||
expect_str(t2, 0, "c");
|
||
expect_str(t2, 1, "b");
|
||
expect_str(t2, 2, "a");
|
||
os.free(t2.ptr: *void, (t2.cap: u64) * size(str): u64);
|
||
|
||
// n == 1 — single slot holding the whole input as remainder.
|
||
signalled = 1752;
|
||
let t3: []str = strings.rsplitn("a b c", " ", 1);
|
||
if (t3.len != 1) { fail(); };
|
||
expect_str(t3, 0, "a b c");
|
||
os.free(t3.ptr: *void, (t3.cap: u64) * size(str): u64);
|
||
|
||
// delim absent — first next_token yields the entire input as the
|
||
// sole token; second iter sees done and short-circuits with the
|
||
// 1-elem toks un-reversed (single element, reverse is a no-op).
|
||
signalled = 1753;
|
||
let t4: []str = strings.rsplitn("abc", "=", 5);
|
||
if (t4.len != 1) { fail(); };
|
||
expect_str(t4, 0, "abc");
|
||
os.free(t4.ptr: *void, (t4.cap: u64) * size(str): u64);
|
||
};
|
||
|
||
@test fn split_cases() void = {
|
||
// ref/hare/strings/tokenize.ha:255 — full split, every delim hit
|
||
// is a boundary.
|
||
signalled = 1760;
|
||
let t1: []str = strings.split("Hello, my name is Drew", " ");
|
||
if (t1.len != 5) { fail(); };
|
||
expect_str(t1, 0, "Hello,");
|
||
expect_str(t1, 1, "my");
|
||
expect_str(t1, 2, "name");
|
||
expect_str(t1, 3, "is");
|
||
expect_str(t1, 4, "Drew");
|
||
os.free(t1.ptr: *void, (t1.cap: u64) * size(str): u64);
|
||
|
||
// Leading + trailing delim — empty tokens at ends.
|
||
signalled = 1761;
|
||
let t2: []str = strings.split(" a b ", " ");
|
||
if (t2.len != 4) { fail(); };
|
||
expect_str(t2, 0, "");
|
||
expect_str(t2, 1, "a");
|
||
expect_str(t2, 2, "b");
|
||
expect_str(t2, 3, "");
|
||
os.free(t2.ptr: *void, (t2.cap: u64) * size(str): u64);
|
||
|
||
// Multi-byte delim set, byte-set semantics matching Hare's
|
||
// strings::split example at ref/hare/strings/tokenize.ha:235.
|
||
signalled = 1762;
|
||
let t3: []str = strings.split("hello;world,foo:bar", ",:;");
|
||
if (t3.len != 4) { fail(); };
|
||
expect_str(t3, 0, "hello");
|
||
expect_str(t3, 1, "world");
|
||
expect_str(t3, 2, "foo");
|
||
expect_str(t3, 3, "bar");
|
||
os.free(t3.ptr: *void, (t3.cap: u64) * size(str): u64);
|
||
};
|
||
|
||
// ---- lpad / rpad -----------------------------------------------------
|
||
// ref/hare/strings/pad.ha:23 (@test fn lpad), :54 (@test fn rpad). Hare's
|
||
// row triple (s<maxlen, s==maxlen, s=="" empty) is mirrored; ww extras
|
||
// pin s>maxlen (early return path), multibyte s with byte-length
|
||
// counting, and multibyte pad rune (encoded width >1 byte). The
|
||
// multibyte-pad rows pin Hare's `[..maxlen]` slice contract — a
|
||
// multibyte trailing pad may be truncated mid-codepoint, exactly as
|
||
// Hare does.
|
||
|
||
@test fn lpad_cases() void = {
|
||
// Hare row 1: shorter s, ASCII pad.
|
||
signalled = 1770;
|
||
let r1: str = strings.lpad("2", '0', 5);
|
||
if (!streq(r1, "00002")) { fail(); };
|
||
if (r1.len != 5) { fail(); };
|
||
os.free(r1.ptr: *void, r1.len: u64);
|
||
|
||
// Hare row 2: s.len == maxlen — early dup path.
|
||
signalled = 1771;
|
||
let r2: str = strings.lpad("12345", '0', 5);
|
||
if (!streq(r2, "12345")) { fail(); };
|
||
if (r2.len != 5) { fail(); };
|
||
os.free(r2.ptr: *void, r2.len: u64);
|
||
|
||
// Hare row 3: empty s, full-width pad.
|
||
signalled = 1772;
|
||
let r3: str = strings.lpad("", '0', 5);
|
||
if (!streq(r3, "00000")) { fail(); };
|
||
if (r3.len != 5) { fail(); };
|
||
os.free(r3.ptr: *void, r3.len: u64);
|
||
|
||
// ww row 1: s.len > maxlen — early dup path returns input copy.
|
||
signalled = 1773;
|
||
let r4: str = strings.lpad("abcdef", '_', 3);
|
||
if (!streq(r4, "abcdef")) { fail(); };
|
||
if (r4.len != 6) { fail(); };
|
||
os.free(r4.ptr: *void, r4.len: u64);
|
||
|
||
// ww row 2: multibyte s — byte-length contract (Hare `len(s)`).
|
||
// "café" is 5 bytes; maxlen 7 → 2 pad bytes prepended.
|
||
signalled = 1774;
|
||
let r5: str = strings.lpad("café", '_', 7);
|
||
if (!streq(r5, "__café")) { fail(); };
|
||
if (r5.len != 7) { fail(); };
|
||
os.free(r5.ptr: *void, r5.len: u64);
|
||
|
||
// ww row 3: multibyte pad rune. 'α' U+03B1 is 2 bytes (0xCE 0xB1).
|
||
// s="x" (1 byte), maxlen=5 → npads = 5 - 1 = 4, pad_bytes=2,
|
||
// total writes = 4*2 + 1 = 9 bytes; result `[..5]` = "αα" (4 bytes)
|
||
// + 0xCE (truncated leading byte of next α) — exactly Hare's slice.
|
||
signalled = 1775;
|
||
let r6: str = strings.lpad("x", 0x03B1u32: rune, 5);
|
||
if (r6.len != 5) { fail(); };
|
||
if (r6.ptr[0] != 0xCEu8) { fail(); }; // α byte 0
|
||
if (r6.ptr[1] != 0xB1u8) { fail(); }; // α byte 1
|
||
if (r6.ptr[2] != 0xCEu8) { fail(); }; // α byte 0
|
||
if (r6.ptr[3] != 0xB1u8) { fail(); }; // α byte 1
|
||
if (r6.ptr[4] != 0xCEu8) { fail(); }; // truncated α byte 0
|
||
os.free(r6.ptr: *void, r6.len: u64);
|
||
};
|
||
|
||
@test fn rpad_cases() void = {
|
||
// Hare row 1: shorter s, ASCII pad.
|
||
signalled = 1780;
|
||
let r1: str = strings.rpad("2", '0', 5);
|
||
if (!streq(r1, "20000")) { fail(); };
|
||
if (r1.len != 5) { fail(); };
|
||
os.free(r1.ptr: *void, r1.len: u64);
|
||
|
||
// Hare row 2: s.len == maxlen — early dup path.
|
||
signalled = 1781;
|
||
let r2: str = strings.rpad("12345", '0', 5);
|
||
if (!streq(r2, "12345")) { fail(); };
|
||
if (r2.len != 5) { fail(); };
|
||
os.free(r2.ptr: *void, r2.len: u64);
|
||
|
||
// Hare row 3: empty s.
|
||
signalled = 1782;
|
||
let r3: str = strings.rpad("", '0', 5);
|
||
if (!streq(r3, "00000")) { fail(); };
|
||
if (r3.len != 5) { fail(); };
|
||
os.free(r3.ptr: *void, r3.len: u64);
|
||
|
||
// ww row 1: s.len > maxlen — early dup path.
|
||
signalled = 1783;
|
||
let r4: str = strings.rpad("abcdef", '_', 3);
|
||
if (!streq(r4, "abcdef")) { fail(); };
|
||
if (r4.len != 6) { fail(); };
|
||
os.free(r4.ptr: *void, r4.len: u64);
|
||
|
||
// ww row 2: multibyte s — byte-length contract.
|
||
signalled = 1784;
|
||
let r5: str = strings.rpad("café", '_', 7);
|
||
if (!streq(r5, "café__")) { fail(); };
|
||
if (r5.len != 7) { fail(); };
|
||
os.free(r5.ptr: *void, r5.len: u64);
|
||
|
||
// ww row 3: multibyte pad rune (2-byte 'α'). s="x" (1 byte),
|
||
// maxlen=5 → "xαα" appended is 1 + 4 = 5 bytes (no truncation).
|
||
signalled = 1785;
|
||
let r6: str = strings.rpad("x", 0x03B1u32: rune, 5);
|
||
if (r6.len != 5) { fail(); };
|
||
if (r6.ptr[0] != 'x') { fail(); };
|
||
if (r6.ptr[1] != 0xCEu8) { fail(); }; // α byte 0
|
||
if (r6.ptr[2] != 0xB1u8) { fail(); }; // α byte 1
|
||
if (r6.ptr[3] != 0xCEu8) { fail(); }; // α byte 0
|
||
if (r6.ptr[4] != 0xB1u8) { fail(); }; // α byte 1
|
||
os.free(r6.ptr: *void, r6.len: u64);
|
||
};
|
||
|
||
// ---- replace ----------------------------------------------------------
|
||
// ref/hare/strings/replace.ha:46 (#4). Hare rows 1-5 (target found
|
||
// once / multiple times / shorter / longer / multibyte) plus ww rows
|
||
// (no-match returns fresh copy, empty-replacement, result-shrinks-to-
|
||
// empty). Per-row `signalled` 1810+i narrows the failing row.
|
||
|
||
@test fn replace_cases() void = {
|
||
// Hare row 1: target found once.
|
||
signalled = 1810;
|
||
match (strings.replace("Hello world!", "world", "there")) {
|
||
case let s: str => {
|
||
if (!streq(s, "Hello there!")) { fail(); };
|
||
os.free(s.ptr: *void, s.len: u64);
|
||
};
|
||
case nomem => { fail(); };
|
||
};
|
||
|
||
// Hare row 2: needle found four times (replacement same length).
|
||
signalled = 1811;
|
||
match (strings.replace("I like dogs, dogs, birds, dogs", "dogs", "cats")) {
|
||
case let s: str => {
|
||
if (!streq(s, "I like cats, cats, birds, cats")) { fail(); };
|
||
os.free(s.ptr: *void, s.len: u64);
|
||
};
|
||
case nomem => { fail(); };
|
||
};
|
||
|
||
// Hare row 3: replacement shorter than needle, non-overlapping
|
||
// matches (Hare advances by needle.len, not 1).
|
||
signalled = 1812;
|
||
match (strings.replace("aaaaaa", "aa", "a")) {
|
||
case let s: str => {
|
||
if (!streq(s, "aaa")) { fail(); };
|
||
os.free(s.ptr: *void, s.len: u64);
|
||
};
|
||
case nomem => { fail(); };
|
||
};
|
||
|
||
// Hare row 4: replacement longer than needle.
|
||
signalled = 1813;
|
||
match (strings.replace("aaa", "a", "aa")) {
|
||
case let s: str => {
|
||
if (!streq(s, "aaaaaa")) { fail(); };
|
||
os.free(s.ptr: *void, s.len: u64);
|
||
};
|
||
case nomem => { fail(); };
|
||
};
|
||
|
||
// Hare row 5: multibyte UTF-8 (3-byte runes).
|
||
signalled = 1814;
|
||
match (strings.replace("こんにちは", "にち", "ばん")) {
|
||
case let s: str => {
|
||
if (!streq(s, "こんばんは")) { fail(); };
|
||
os.free(s.ptr: *void, s.len: u64);
|
||
};
|
||
case nomem => { fail(); };
|
||
};
|
||
|
||
// ww row 1: needle not found — fresh copy (distinct from
|
||
// borrowed input ptr).
|
||
signalled = 1815;
|
||
let src: str = "hello world";
|
||
match (strings.replace(src, "xyz", "abc")) {
|
||
case let s: str => {
|
||
if (!streq(s, "hello world")) { fail(); };
|
||
if (s.ptr == src.ptr) { fail(); };
|
||
os.free(s.ptr: *void, s.len: u64);
|
||
};
|
||
case nomem => { fail(); };
|
||
};
|
||
|
||
// ww row 2: empty replacement — needle is removed.
|
||
signalled = 1816;
|
||
match (strings.replace("foo-bar-baz", "-", "")) {
|
||
case let s: str => {
|
||
if (!streq(s, "foobarbaz")) { fail(); };
|
||
os.free(s.ptr: *void, s.len: u64);
|
||
};
|
||
case nomem => { fail(); };
|
||
};
|
||
|
||
// ww row 3: result shrinks to empty — total==0 nil-alloc path.
|
||
signalled = 1817;
|
||
match (strings.replace("aaaa", "aa", "")) {
|
||
case let s: str => {
|
||
if (!streq(s, "")) { fail(); };
|
||
if (s.len != 0) { fail(); };
|
||
};
|
||
case nomem => { fail(); };
|
||
};
|
||
|
||
// ww row 4: multibyte single-rune replace — every "に" → "X".
|
||
signalled = 1818;
|
||
match (strings.replace("こんにちは", "に", "X")) {
|
||
case let s: str => {
|
||
if (!streq(s, "こんXちは")) { fail(); };
|
||
os.free(s.ptr: *void, s.len: u64);
|
||
};
|
||
case nomem => { fail(); };
|
||
};
|
||
|
||
// ww row 5: empty input — returns empty.
|
||
signalled = 1819;
|
||
match (strings.replace("", "x", "y")) {
|
||
case let s: str => {
|
||
if (s.len != 0) { fail(); };
|
||
};
|
||
case nomem => { fail(); };
|
||
};
|
||
};
|
||
|
||
export fn main() i32 = {
|
||
signalled = 1; dup_cases();
|
||
signalled = 42; dupall_cases();
|
||
signalled = 2; concat_cases();
|
||
signalled = 30; join_cases();
|
||
signalled = 3; hasprefix_cases();
|
||
signalled = 4; hassuffix_cases();
|
||
signalled = 5; contains_cases();
|
||
signalled = 6; byteindex_str_cases();
|
||
signalled = 7; byteindex_rune_cases();
|
||
signalled = 8; rbyteindex_cases();
|
||
signalled = 28; index_cases();
|
||
signalled = 29; rindex_cases();
|
||
signalled = 9; trimprefix_cases();
|
||
signalled = 10; trimsuffix_cases();
|
||
signalled = 11; ltrim_cases();
|
||
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 = 43; fromutf8_cases();
|
||
signalled = 16; iter_empty_cases();
|
||
signalled = 17; iter_ascii_cases();
|
||
signalled = 18; iter_twobyte_cases();
|
||
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();
|
||
signalled = 27; iter_slice_cases();
|
||
signalled = 31; tokenize_cases();
|
||
signalled = 32; rtokenize_cases();
|
||
signalled = 33; peek_token_cases();
|
||
signalled = 34; remaining_tokens_cases();
|
||
signalled = 35; splitn_cases();
|
||
signalled = 36; rsplitn_cases();
|
||
signalled = 37; split_cases();
|
||
signalled = 38; lpad_cases();
|
||
signalled = 39; rpad_cases();
|
||
signalled = 44; replace_cases();
|
||
return 0;
|
||
};
|