diff --git a/Makefile b/Makefile index b0fba687..fa5f2a96 100644 --- a/Makefile +++ b/Makefile @@ -486,6 +486,8 @@ LIBRARY_TESTS = lib/errors/errno_test.ww lib/ascii/ascii_test.ww \ lib/ww/syntax/sym_test.ww \ lib/strconv/test/ftos_test.ww lib/strconv/test/stof_test.ww \ lib/strconv/test/int_test.ww lib/strings/strings_test.ww \ + lib/strings/suffix_test.ww lib/strings/contains_test.ww \ + lib/strings/index_test.ww lib/strings/compare_test.ww \ lib/bytes/bytes_test.ww lib/encoding/utf8/utf8_test.ww \ lib/bufio/bufio_test.ww lib/math/random/random_test.ww \ lib/math/checked/checked_test.ww lib/fmt/fmt_test.ww \ diff --git a/lib/strings/compare_test.ww b/lib/strings/compare_test.ww new file mode 100644 index 00000000..bd635d56 --- /dev/null +++ b/lib/strings/compare_test.ww @@ -0,0 +1,18 @@ +// comparetest — exercises strings.compare. A failing row aborts +// via the assert/abort builtin (task #5 @test conversion). +// Vectors mirror ref/hare/strings/compare.ha. + +package strings_test; + +import strings; + +// ref/hare/strings/compare.ha:16. + +@test fn compare_cases() void = { + assert(!(strings.compare("ABC", "ABC") != 0)); + assert(!(strings.compare("ABC", "AB") <= 0)); + assert(!(strings.compare("AB", "ABC") >= 0)); + assert(!(strings.compare("BCD", "ABC") <= 0)); + assert(!(strings.compare("ABC", "abc") >= 0)); + assert(!(strings.compare("ABC", "こんにちは") >= 0)); +}; diff --git a/lib/strings/contains_test.ww b/lib/strings/contains_test.ww new file mode 100644 index 00000000..0e257d8a --- /dev/null +++ b/lib/strings/contains_test.ww @@ -0,0 +1,28 @@ +// containstest — exercises strings.contains. A failing row aborts +// via the assert/abort builtin (task #5 @test conversion). +// Vectors mirror ref/hare/strings/contains.ha. + +package strings_test; + +import strings; + +// ref/hare/strings/contains.ha:27. + +@test fn contains_cases() void = { + assert(!(!strings.contains("hello world", "hello"))); + assert(!(!strings.contains("hello world", 'h'))); + assert(!( strings.contains("hello world", 'x'))); + assert(!(!strings.contains("hello world", "world"))); + assert(!(!strings.contains("hello world", ""))); // empty hits at 0 + assert(!( strings.contains("hello world", "foobar"))); + assert(!(!strings.contains("こんにちは", 0x306Bu32: rune))); // 'に' + assert(!(!strings.contains("こんにちは", "ちは"))); + + // Variadic rows. ref/hare/strings/contains.ha:27. + assert(!( strings.contains("hello"))); + assert(!(!strings.contains("hello world", "world"))); + assert(!(!strings.contains("hello", 'l'))); + assert(!(!strings.contains("hello world", "foo", "world", 'x'))); + assert(!( strings.contains("hello", "foo", 'z', "bar"))); + assert(!(!strings.contains("héllo", "x", 0xE9u32: rune))); +}; diff --git a/lib/strings/index_test.ww b/lib/strings/index_test.ww new file mode 100644 index 00000000..655880a4 --- /dev/null +++ b/lib/strings/index_test.ww @@ -0,0 +1,240 @@ +// 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; + }; +}; diff --git a/lib/strings/strings_test.ww b/lib/strings/strings_test.ww index e8a46740..e043d98a 100644 --- a/lib/strings/strings_test.ww +++ b/lib/strings/strings_test.ww @@ -5,8 +5,8 @@ // abort reports the file, not the row (drew-t2-conversion-spec sec.5; // Hare-equivalent, which reports file:line not loop index). // -// Vectors mirror ref/hare/strings/{dup,concat,trim,contains,index, -// suffix,compare}.ha where ww can express them. +// Vectors mirror ref/hare/strings/{dup,concat,trim,sub,utf8,iter, +// tokenize,pad,replace}.ha where ww can express them. // `_test` suffix: Go external-test-package idiom (sanctioned Go-over-Hare // departure for lib/ tests) — self-import is hard-rejected, so the test @@ -237,294 +237,6 @@ fn streq(a: str, b: str) bool = { }; }; -// ---- hasprefix -------------------------------------------------------- -// ref/hare/strings/suffix.ha:18. - -@test fn hasprefix_cases() void = { - assert(!(!strings.hasprefix("hello world", "hello"))); - assert(!(!strings.hasprefix("hello world", 'h'))); - assert(!( strings.hasprefix("hello world", "world"))); - assert(!( strings.hasprefix("hello world", 'q'))); - assert(!(!strings.hasprefix("hello", "hello"))); // equal-len - assert(!(!strings.hasprefix("anything", ""))); // empty prefix - assert(!( strings.hasprefix("", "x"))); - // multibyte rune prefix — '\'é\'' literal blocked by single-byte - // lexrune (lib/ww/lex/lex.ww:659); pass codepoint directly. - assert(!(!strings.hasprefix("éclat", 0xE9u32: rune))); - assert(!(!strings.hasprefix("🦀rust", 0x1F980u32: rune))); -}; - -// ---- hassuffix -------------------------------------------------------- -// ref/hare/strings/suffix.ha:36. - -@test fn hassuffix_cases() void = { - assert(!(!strings.hassuffix("hello world", "world"))); - assert(!(!strings.hassuffix("hello world", 'd'))); - assert(!( strings.hassuffix("hello world", "hello"))); - assert(!( strings.hassuffix("hello world", 'h'))); - assert(!(!strings.hassuffix("café", 0xE9u32: rune))); // multibyte -}; - -// ---- contains --------------------------------------------------------- -// ref/hare/strings/contains.ha:27. - -@test fn contains_cases() void = { - assert(!(!strings.contains("hello world", "hello"))); - assert(!(!strings.contains("hello world", 'h'))); - assert(!( strings.contains("hello world", 'x'))); - assert(!(!strings.contains("hello world", "world"))); - assert(!(!strings.contains("hello world", ""))); // empty hits at 0 - assert(!( strings.contains("hello world", "foobar"))); - assert(!(!strings.contains("こんにちは", 0x306Bu32: rune))); // 'に' - assert(!(!strings.contains("こんにちは", "ちは"))); - - // Variadic rows. ref/hare/strings/contains.ha:27. - assert(!( strings.contains("hello"))); - assert(!(!strings.contains("hello world", "world"))); - assert(!(!strings.contains("hello", 'l'))); - assert(!(!strings.contains("hello world", "foo", "world", 'x'))); - assert(!( strings.contains("hello", "foo", 'z', "bar"))); - assert(!(!strings.contains("héllo", "x", 0xE9u32: rune))); -}; - -// ---- 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 => { 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; - }; -}; - -// ---- rbyteindex ------------------------------------------------------- - -@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; - }; -}; - -// ---- 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. - 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(); }; - }; -}; - -// ---- rindex ----------------------------------------------------------- -// 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; - }; -}; - // ---- trimprefix / trimsuffix ------------------------------------------ // ref/hare/strings/trim.ha:99-107. @@ -687,18 +399,6 @@ fn streq(a: str, b: str) bool = { }; }; -// ---- compare ---------------------------------------------------------- -// ref/hare/strings/compare.ha:16. - -@test fn compare_cases() void = { - assert(!(strings.compare("ABC", "ABC") != 0)); - assert(!(strings.compare("ABC", "AB") <= 0)); - assert(!(strings.compare("AB", "ABC") >= 0)); - assert(!(strings.compare("BCD", "ABC") <= 0)); - assert(!(strings.compare("ABC", "abc") >= 0)); - assert(!(strings.compare("ABC", "こんにちは") >= 0)); -}; - // ---- 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 diff --git a/lib/strings/suffix_test.ww b/lib/strings/suffix_test.ww new file mode 100644 index 00000000..7f966918 --- /dev/null +++ b/lib/strings/suffix_test.ww @@ -0,0 +1,33 @@ +// suffixtest — exercises strings.hasprefix/hassuffix. A failing row +// aborts via the assert/abort builtin (task #5 @test conversion). +// Vectors mirror ref/hare/strings/suffix.ha. + +package strings_test; + +import strings; + +// ref/hare/strings/suffix.ha:18. + +@test fn hasprefix_cases() void = { + assert(!(!strings.hasprefix("hello world", "hello"))); + assert(!(!strings.hasprefix("hello world", 'h'))); + assert(!( strings.hasprefix("hello world", "world"))); + assert(!( strings.hasprefix("hello world", 'q'))); + assert(!(!strings.hasprefix("hello", "hello"))); // equal-len + assert(!(!strings.hasprefix("anything", ""))); // empty prefix + assert(!( strings.hasprefix("", "x"))); + // multibyte rune prefix — '\'é\'' literal blocked by single-byte + // lexrune (lib/ww/lex/lex.ww:659); pass codepoint directly. + assert(!(!strings.hasprefix("éclat", 0xE9u32: rune))); + assert(!(!strings.hasprefix("🦀rust", 0x1F980u32: rune))); +}; + +// ref/hare/strings/suffix.ha:36. + +@test fn hassuffix_cases() void = { + assert(!(!strings.hassuffix("hello world", "world"))); + assert(!(!strings.hassuffix("hello world", 'd'))); + assert(!( strings.hassuffix("hello world", "hello"))); + assert(!( strings.hassuffix("hello world", 'h'))); + assert(!(!strings.hassuffix("café", 0xE9u32: rune))); // multibyte +}; diff --git a/test/byteid/libbyteid_test.ww b/test/byteid/libbyteid_test.ww index 6ae73ab9..47f9ea00 100644 --- a/test/byteid/libbyteid_test.ww +++ b/test/byteid/libbyteid_test.ww @@ -43,7 +43,7 @@ import time; def MID: i32 = 0; def MDIVERGE: i32 = 1; def MWWREJECT: i32 = 2; -def NENTEXPECT: i32 = 47; +def NENTEXPECT: i32 = 51; type ent = struct { fixture: str, // repo-relative .ww; "" -> probe entry @@ -83,7 +83,7 @@ fn pri(p: str, inc: str, sentinel: str, moddir: str) ent = { return e; }; -// The 46-unit roster. Graduation history lives in git (the retired C +// The 51-unit roster. Graduation history lives in git (the retired C // carrier's table comments); cites are kept only where a non-ID pin // would need them. fn corpus() []ent = { @@ -110,6 +110,10 @@ fn corpus() []ent = { append(es, fx("lib/strconv/test/stof_test.ww")); append(es, fx("lib/strconv/test/int_test.ww")); append(es, fx("lib/strings/strings_test.ww")); + append(es, fx("lib/strings/suffix_test.ww")); + append(es, fx("lib/strings/contains_test.ww")); + append(es, fx("lib/strings/index_test.ww")); + append(es, fx("lib/strings/compare_test.ww")); append(es, fx("lib/temp/temp_test.ww")); append(es, fx("lib/time/arithm_test.ww")); append(es, fx("lib/time/duration_test.ww"));