Files
ww/lib/strings/index_test.ww
Hojun-Cho 8a1dc641e3 lib: split strings suffix/contains/index/compare tests per ref/hare/strings
Pure move: the hasprefix/hassuffix, contains, byteindex/rbyteindex/
index/rindex, and compare sections of strings_test.ww become sibling
suffix_test.ww, contains_test.ww, index_test.ww, compare_test.ww,
mirroring ref/hare/strings/{suffix,contains,index,compare}.ha
ownership. Blocks are byte-identical; only the section banner lines
are deleted (the file names now carry them).

The remaining families (dup, concat, trim, sub, utf8, iter, tokenize,
pad, replace, cut) stay in strings_test.ww: they all consume the
file-local streq helper, and a helper cannot be shared across split
test files — a single-file test root does not pull same-package
siblings (undefined: helper), while dir-mode composes all package
test files into one unit (duplicate fn helper). Splitting them would
force either helper duplication or renames, both out of scope for a
pure move.

Consumers: Makefile LIBRARY_TESTS gains the four new entries beside
the strings_test.ww row; test/byteid/libbyteid_test.ww roster gains
four fx rows, NENTEXPECT 44->48 (+4).
2026-08-08 17:20:40 +09:00

241 lines
7.8 KiB
Plaintext

// indextest — exercises strings.byteindex/rbyteindex/index/rindex.
// A failing row aborts via the assert/abort builtin (task #5 @test
// conversion). Vectors mirror ref/hare/strings/index.ha.
package strings_test;
import strings;
// 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 => { assert(!(i != 0)); };
case void => { abort(); };
};
match (strings.byteindex("hello world!", "world")) {
case let i: i32 => { assert(!(i != 6)); };
case void => { abort(); };
};
match (strings.byteindex("hello world!", "orld!")) {
case let i: i32 => { assert(!(i != 7)); };
case void => { abort(); };
};
match (strings.byteindex("hello world!", "word")) {
case let i: i32 => { abort(); };
case void => void;
};
// empty needle hits at 0 (ref/hare/bytes/index.ha:63).
match (strings.byteindex("hello", "")) {
case let i: i32 => { assert(!(i != 0)); };
case void => { abort(); };
};
// empty haystack, non-empty needle — absent.
match (strings.byteindex("", "x")) {
case let i: i32 => { abort(); };
case void => void;
};
// multibyte substring in multibyte haystack.
match (strings.byteindex("こんにちは", "ちは")) {
case let i: i32 => { assert(!(i != 9)); };
case void => { abort(); };
};
};
@test fn byteindex_rune_cases() void = {
// ASCII rune (1-byte encoding).
match (strings.byteindex("hello world", 'w')) {
case let i: i32 => { assert(!(i != 6)); };
case void => { abort(); };
};
// 2-byte rune U+00E9 'é' inside "café".
match (strings.byteindex("café", 0xE9u32: rune)) {
case let i: i32 => { assert(!(i != 3)); };
case void => { abort(); };
};
// 3-byte rune U+3061 'ち' inside "こんにちは".
match (strings.byteindex("こんにちは", 0x3061u32: rune)) {
case let i: i32 => { assert(!(i != 9)); };
case void => { abort(); };
};
// 4-byte rune U+1F980 '🦀' inside "ab🦀cd".
match (strings.byteindex("ab🦀cd", 0x1F980u32: rune)) {
case let i: i32 => { assert(!(i != 2)); };
case void => { abort(); };
};
// absent.
match (strings.byteindex("こんにちは", 'q')) {
case let i: i32 => { abort(); };
case void => void;
};
};
@test fn rbyteindex_cases() void = {
// Two 'た' in "またあったね" — ref/hare/strings/index.ha:160-161.
match (strings.byteindex("またあったね", "た")) {
case let i: i32 => { assert(!(i != 3)); };
case void => { abort(); };
};
match (strings.rbyteindex("またあったね", "た")) {
case let i: i32 => { assert(!(i != 12)); };
case void => { abort(); };
};
// Rune arm, multi-byte 'に' U+306B.
match (strings.rbyteindex("こんにちは", 0x306Bu32: rune)) {
case let i: i32 => { assert(!(i != 6)); };
case void => { abort(); };
};
// Absent.
match (strings.rbyteindex("abc", 'z')) {
case let i: i32 => { abort(); };
case void => void;
};
};
// 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.
match (strings.index("hello", "ll")) {
case let i: i32 => { assert(!(i != 2)); };
case void => { abort(); };
};
// str-arm: absent needle.
match (strings.index("hello", "world")) {
case let i: i32 => { abort(); };
case void => void;
};
// Hare vectors at ref/hare/strings/index.ha:113-119.
match (strings.index("hello world!", "hello")) {
case let i: i32 => { assert(!(i != 0)); };
case void => { abort(); };
};
match (strings.index("hello world!", "world")) {
case let i: i32 => { assert(!(i != 6)); };
case void => { abort(); };
};
match (strings.index("hello world!", "orld!")) {
case let i: i32 => { assert(!(i != 7)); };
case void => { abort(); };
};
// Multibyte haystack + str needle: "ちは" at rune index 3
// in "こんにちは" (byteindex returns 9, rune-index is 3).
match (strings.index("こんにちは", "ちは")) {
case let i: i32 => { assert(!(i != 3)); };
case void => { abort(); };
};
// rune-arm.
match (strings.index("hello", 'l')) {
case let i: i32 => { assert(!(i != 2)); };
case void => { abort(); };
};
match (strings.index("hello world", 'w')) {
case let i: i32 => { assert(!(i != 6)); };
case void => { abort(); };
};
// 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).
match (strings.index("héllo", 0xE9u32: rune)) {
case let i: i32 => { assert(!(i != 1)); };
case void => { abort(); };
};
// Multibyte rune in multibyte haystack: 'ち' U+3061 at rune 3
// in "こんにちは" (byteindex returns 9, rune-index is 3).
match (strings.index("こんにちは", 0x3061u32: rune)) {
case let i: i32 => { assert(!(i != 3)); };
case void => { abort(); };
};
match (strings.index("こんにちは", 'q')) {
case let i: i32 => { abort(); };
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).
match (strings.index("またあったね", "あった")) {
case let i: i32 => { assert(!(i != 2)); };
case void => { abort(); };
};
// str-arm: tail-anchored multibyte needle. "は" is at rune 4
// (byte 12) in "こんにちは".
match (strings.index("こんにちは", "は")) {
case let i: i32 => { assert(!(i != 4)); };
case void => { abort(); };
};
// 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).
match (strings.index("hello", "")) {
case let i: i32 => { assert(!(i != 0)); };
case void => { abort(); };
};
match (strings.index("", "")) {
case let i: i32 => { assert(!(i != 0)); };
case void => { abort(); };
};
// Empty haystack, non-empty needle — absent.
match (strings.index("", "x")) {
case let i: i32 => { abort(); };
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).
match (strings.index("こんにちは", "きょうは")) {
case let i: i32 => { abort(); };
case void => void;
};
// Self-match: haystack == needle, Hare row index.ha:113.
match (strings.index("hello", "hello")) {
case let i: i32 => { assert(!(i != 0)); };
case void => { abort(); };
};
};
// ref/hare/strings/index.ha:22. Symmetric: last-occurrence rune index.
@test fn rindex_cases() void = {
// str-arm.
match (strings.rindex("hello", "lo")) {
case let i: i32 => { assert(!(i != 3)); };
case void => { abort(); };
};
// Hare vector at ref/hare/strings/index.ha:122.
match (strings.rindex("hello world!", "o")) {
case let i: i32 => { assert(!(i != 7)); };
case void => { abort(); };
};
match (strings.rindex("hello", "world")) {
case let i: i32 => { abort(); };
case void => void;
};
// Multibyte: last "た" in "またあったね" — rbyteindex returns
// 12, rune-index is 4 (ま=0 た=1 あ=2 っ=3 た=4 ね=5).
match (strings.rindex("またあったね", "た")) {
case let i: i32 => { assert(!(i != 4)); };
case void => { abort(); };
};
// rune-arm.
match (strings.rindex("hello", 'l')) {
case let i: i32 => { assert(!(i != 3)); };
case void => { abort(); };
};
match (strings.rindex("aaaaa", 'a')) {
case let i: i32 => { assert(!(i != 4)); };
case void => { abort(); };
};
// Multibyte rune: 'に' U+306B at rune 2 in "こんにちは".
match (strings.rindex("こんにちは", 0x306Bu32: rune)) {
case let i: i32 => { assert(!(i != 2)); };
case void => { abort(); };
};
match (strings.rindex("hello", 'z')) {
case let i: i32 => { abort(); };
case void => void;
};
};