Class A silent miscompile, surfaced by landing strings.slice in Hare's natural delegation form `fromutf8_unsafe(utf8.slice(begin, end))` (ref/hare/strings/iter.ha:75). strings.slice itself returns str, so the inner utf8.slice (cross-module N_DOT) call's cgcall return-ABI fixup hit post-#4e fnretlookup's same-module-first walk and grabbed strings.slice's own str return — emitted a spurious `MOVQ DX, BX` after the cross-module CALL even though utf8.slice returns []u8 (selfhost/cmd/wcc/cgenexpr.ww cgcall return-ABI fixup, line 3249-3261 pre-fix). Every other consumer of cgcall:3249's str-shuffle decision sat on the same bare-leaf table and was silently miscompiling on the same collision shape pre-#34. Sibling: nodeisslice + nodeisstr N_CALL arms in selfhost/cmd/wcc/cgenutil.ww were N_IDENT-only — for a cross- module N_DOT call returning a slice or str, pushargsrev fell through to the natural 1-word PUSHQ AX, dropping the `.len` (and `.cap` for slices) of the return value when consumed as a call arg. strings.slice's body passes utf8.slice's []u8 result to fromutf8_unsafe; pre-fix wwstage pushed 1 word vs cstage's 3, breaking the receiver's slice-3-pop drain. Cstage carries no sister bug: cmd/w6c/cgen.c reads return shape from the typed `n->lhs->type` (TY_FN sig) for both str-shuffle and slice-/str-arg push counts — module-aware via the typed AST, sidestepping any bare-leaf table. Mirror of #4e's cstage-no- sister-bug note. Fix: route cgcall return-ABI fixup + nodeisslice/nodeisstr N_CALL arms through fnretlookupmod with `callee.lhs.str` (N_DOT qualifier) or `c.curmod` (N_IDENT). Mirror of #28 fnparamslookupmod / #31 fnretlookupmod N_DOT re-routing. Remaining bare-leaf fnretlookup consumer sites (~8 sites across cgenexpr/cgenutil/cgenstmt/cgendecl listed in task #34a) stay on the graduated bare-leaf path — none of the present-corpus N_DOT leaf collisions have return-shape divergence at those sites. A future stdlib port introducing a return-shape-divergent same-leaf N_DOT collision will need the *mod re-routing — filed as #34a sibling-latents. Bundled three concerns per rule 11: cgcall fix, nodeisslice/ nodeisstr fix, and strings.slice retire + sentinel. (a) alone leaves strings.slice byte-id breaking on slice-arg push count. (b) alone leaves a phantom MOVQ DX, BX on the inner cross- module CALL. (c) alone fails 995_self_rebuild without (a)+(b). The three cannot land separately bisect-cleanly; the 745 sentinel pins the primary repro (cgcall str-shuffle) which sentinel-flips on a cgcall:3257 revert. 745_fnret34_modshadow pins the fix with 1 row: caller.slice returns str (same leaf as the cross-module callee, divergent return shape); caller.run calls myutf8.slice returning []u8. Asserts CALL myutf8.slice present inside caller.run TEXT + `MOVQ DX, BX` anti-check on each stage plus cs-vs-ws byte-id. strings.slice retired in lib/strings/strings.ww: the deferral block becomes the natural Hare delegation form with two local utf8.decoder reconstructions for the iterator endpoints — ww has no anonymous-embed (parallel to the existing `move` helper). iter_slice_cases mirrors ref/hare/strings/iter.ha:110-127; sidesteps the Hare `let t = s;` iterator-copy via fresh strings.iter() to stay clear of #35's sibling latents. 119/119 ok. ww2 == ww3 == ww4 byte-id holds.
602 lines
20 KiB
Plaintext
602 lines
20 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);
|
||
};
|
||
|
||
// ---- concat -----------------------------------------------------------
|
||
// ref/hare/strings/concat.ha:18 (2-arg subset).
|
||
|
||
@test fn concat_cases() void = {
|
||
let a: str = strings.concat("hello ", "world");
|
||
if (!streq(a, "hello world")) { fail(); };
|
||
defer os.free(a.ptr: *void, a.len: u64);
|
||
|
||
let e: str = strings.concat("", "");
|
||
if (!streq(e, "")) { fail(); };
|
||
// e.len == 0 — os.free guarded, skip.
|
||
|
||
let l: str = strings.concat("", "world");
|
||
if (!streq(l, "world")) { fail(); };
|
||
defer os.free(l.ptr: *void, l.len: u64);
|
||
|
||
let r: str = strings.concat("hello", "");
|
||
if (!streq(r, "hello")) { fail(); };
|
||
defer os.free(r.ptr: *void, r.len: u64);
|
||
|
||
let m: str = strings.concat("こん", "にちは");
|
||
if (!streq(m, "こんにちは")) { fail(); };
|
||
defer os.free(m.ptr: *void, m.len: u64);
|
||
};
|
||
|
||
// ---- 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(); };
|
||
};
|
||
|
||
// ---- 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;
|
||
};
|
||
};
|
||
|
||
// ---- 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 (single-rune subset) ------------------------
|
||
// ref/hare/strings/trim.ha:75-97. Vectors restricted to single-rune
|
||
// patterns (Hare's `rune...` blocks on task #16).
|
||
|
||
@test fn ltrim_cases() void = {
|
||
if (!streq(strings.ltrim("", 'x'), "")) { fail(); };
|
||
if (!streq(strings.ltrim("aaabc", 'a'), "bc")) { fail(); };
|
||
if (!streq(strings.ltrim("xyz", 'a'), "xyz")) { fail(); }; // no match
|
||
if (!streq(strings.ltrim("aaaa", 'a'), "")) { fail(); }; // all stripped
|
||
// 4-byte rune pattern — '𝚊' = U+1D68A.
|
||
if (!streq(strings.ltrim("𝚊𝚊hi", 0x1D68Au32: rune), "hi")) { fail(); };
|
||
};
|
||
|
||
@test fn rtrim_cases() void = {
|
||
if (!streq(strings.rtrim("", 'x'), "")) { fail(); };
|
||
if (!streq(strings.rtrim("bcaaa", 'a'), "bc")) { fail(); };
|
||
if (!streq(strings.rtrim("xyz", 'a'), "xyz")) { fail(); };
|
||
if (!streq(strings.rtrim("aaaa", 'a'), "")) { fail(); };
|
||
if (!streq(strings.rtrim("hi𝚊𝚊", 0x1D68Au32: rune), "hi")) { fail(); };
|
||
};
|
||
|
||
@test fn trim_cases() void = {
|
||
if (!streq(strings.trim("", 'x'), "")) { fail(); };
|
||
if (!streq(strings.trim("aaabcaaa", 'a'), "bc")) { fail(); };
|
||
if (!streq(strings.trim("xyz", 'a'), "xyz")) { fail(); };
|
||
if (!streq(strings.trim("aaaa", 'a'), "")) { fail(); };
|
||
};
|
||
|
||
// ---- 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(); };
|
||
};
|
||
|
||
// ---- 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
|
||
};
|
||
|
||
// ---- 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(); };
|
||
};
|
||
|
||
export fn main() i32 = {
|
||
signalled = 1; dup_cases();
|
||
signalled = 2; concat_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 = 9; trimprefix_cases();
|
||
signalled = 10; trimsuffix_cases();
|
||
signalled = 11; ltrim_cases();
|
||
signalled = 12; rtrim_cases();
|
||
signalled = 13; trim_cases();
|
||
signalled = 14; compare_cases();
|
||
signalled = 15; utf8_roundtrip_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();
|
||
return 0;
|
||
};
|