// bytestest — exercises lib/bytes. Run with // `out/bin/ww run lib/bytes/bytestest.ww`. A failing row aborts via the // assert/abort builtin (task #5 @test conversion). // // Vectors mirror Hare's @test fns in ref/hare/bytes/equal.ha, // ref/hare/bytes/index.ha, ref/hare/bytes/contains.ha. package bytes_test; import bytes; import os; // ---- equal ------------------------------------------------------------ // ref/hare/bytes/equal.ha:21. @test fn equal_cases() void = { let a: [3]u8; a[0] = 1u8; a[1] = 2u8; a[2] = 3u8; let b: [3]u8; b[0] = 1u8; b[1] = 2u8; b[2] = 3u8; let c: [3]u8; c[0] = 1u8; c[1] = 4u8; c[2] = 5u8; let d: [4]u8; d[0] = 1u8; d[1] = 2u8; d[2] = 3u8; d[3] = 4u8; let e: [2]u8; e[0] = 1u8; e[1] = 2u8; let z: [1]u8; assert(!(!bytes.equal(a[0:3], b[0:3]))); assert(!( bytes.equal(a[0:3], c[0:3]))); assert(!( bytes.equal(a[0:3], d[0:4]))); assert(!( bytes.equal(a[0:3], e[0:2]))); assert(!(!bytes.equal(z[0:0], z[0:0]))); // empty-empty }; // ---- index(u8) -------------------------------------------------------- // ref/hare/bytes/index.ha:112. @test fn index_byte_cases() void = { let a: [4]u8; a[0] = 1u8; a[1] = 3u8; a[2] = 3u8; a[3] = 7u8; match (bytes.index(a[0:4], 1u8)) { case let i: i32 => { assert(!(i != 0)); }; case void => abort(); }; match (bytes.index(a[0:4], 3u8)) { case let i: i32 => { assert(!(i != 1)); }; case void => abort(); }; match (bytes.index(a[0:4], 7u8)) { case let i: i32 => { assert(!(i != 3)); }; case void => abort(); }; match (bytes.index(a[0:4], 42u8)) { case let i: i32 => abort(); case void => void; }; let z: [1]u8; match (bytes.index(z[0:0], 42u8)) { case let i: i32 => abort(); case void => void; }; }; // ---- index([]u8) ------------------------------------------------------ // ref/hare/bytes/index.ha:128-139. Vector strings copied verbatim where // representable as ASCII byte sequences. @test fn index_slice_cases() void = { let h1: [4]u8; h1[0] = 1u8; h1[1] = 42u8; h1[2] = 24u8; h1[3] = 0u8; let n1: [2]u8; n1[0] = 42u8; n1[1] = 24u8; match (bytes.index(h1[0:3], n1[0:2])) { case let i: i32 => { assert(!(i != 1)); }; case void => abort(); }; let h2: [4]u8; h2[0] = 1u8; h2[1] = 3u8; h2[2] = 3u8; h2[3] = 7u8; let n2: [2]u8; n2[0] = 3u8; n2[1] = 3u8; match (bytes.index(h2[0:4], n2[0:2])) { case let i: i32 => { assert(!(i != 1)); }; case void => abort(); }; // needle longer than haystack — void let h3: [3]u8; h3[0] = 1u8; h3[1] = 2u8; h3[2] = 3u8; let n3: [4]u8; n3[0] = 1u8; n3[1] = 2u8; n3[2] = 3u8; n3[3] = 4u8; match (bytes.index(h3[0:3], n3[0:4])) { case let i: i32 => abort(); case void => void; }; // len(haystack) == len(needle), match — offset 0 let h4: [2]u8; h4[0] = 42u8; h4[1] = 20u8; let n4: [2]u8; n4[0] = 42u8; n4[1] = 20u8; match (bytes.index(h4[0:2], n4[0:2])) { case let i: i32 => { assert(!(i != 0)); }; case void => abort(); }; // len(haystack) == len(needle), no match — void let h5: [4]u8; h5[0] = 1u8; h5[1] = 1u8; h5[2] = 1u8; h5[3] = 2u8; let n5: [4]u8; n5[0] = 1u8; n5[1] = 1u8; n5[2] = 1u8; n5[3] = 3u8; match (bytes.index(h5[0:4], n5[0:4])) { case let i: i32 => abort(); case void => void; }; // Partial-prefix recovery — needle [1,1,2] aligns at i=1 after the // false-start at i=0 ([1,1,1] mismatches at byte 2). Pins the // naive scanner's restart discipline. let h6: [4]u8; h6[0] = 1u8; h6[1] = 1u8; h6[2] = 1u8; h6[3] = 2u8; let n6s: [3]u8; n6s[0] = 1u8; n6s[1] = 1u8; n6s[2] = 2u8; match (bytes.index(h6[0:4], n6s[0:3])) { case let i: i32 => { assert(!(i != 1)); }; case void => abort(); }; // Same shape, longer haystack with no match anywhere. let h7: [5]u8; h7[0] = 1u8; h7[1] = 1u8; h7[2] = 1u8; h7[3] = 3u8; h7[4] = 2u8; let n7: [4]u8; n7[0] = 1u8; n7[1] = 1u8; n7[2] = 1u8; n7[3] = 2u8; match (bytes.index(h7[0:5], n7[0:4])) { case let i: i32 => abort(); case void => void; }; // empty needle — Hare returns 0 (ref/hare/bytes/index.ha:63). let z: [1]u8; let zn: [1]u8; match (bytes.index(h2[0:4], zn[0:0])) { case let i: i32 => { assert(!(i != 0)); }; case void => abort(); }; // empty haystack, non-empty needle — void match (bytes.index(z[0:0], n3[0:3])) { case let i: i32 => abort(); case void => void; }; // single-byte slice needle — should semantically equal u8 arm let n6: [1]u8; n6[0] = 7u8; match (bytes.index(h2[0:4], n6[0:1])) { case let i: i32 => { assert(!(i != 3)); }; case void => abort(); }; }; // ---- rindex(u8) ------------------------------------------------------- // ref/hare/bytes/index.ha:118. @test fn rindex_byte_cases() void = { let a: [4]u8; a[0] = 1u8; a[1] = 3u8; a[2] = 3u8; a[3] = 7u8; match (bytes.rindex(a[0:4], 3u8)) { case let i: i32 => { assert(!(i != 2)); }; case void => abort(); }; match (bytes.rindex(a[0:4], 42u8)) { case let i: i32 => abort(); case void => void; }; let z: [1]u8; match (bytes.rindex(z[0:0], 42u8)) { case let i: i32 => abort(); case void => void; }; }; // ---- rindex([]u8) ----------------------------------------------------- // ref/hare/bytes/index.ha:123-125. Distinguishes from index when the // needle appears more than once. @test fn rindex_slice_cases() void = { let a: [4]u8; a[0] = 1u8; a[1] = 1u8; a[2] = 1u8; a[3] = 2u8; let n11: [2]u8; n11[0] = 1u8; n11[1] = 1u8; match (bytes.rindex(a[0:4], n11[0:2])) { case let i: i32 => { assert(!(i != 1)); }; case void => abort(); }; let n12: [2]u8; n12[0] = 1u8; n12[1] = 2u8; match (bytes.rindex(a[0:4], n12[0:2])) { case let i: i32 => { assert(!(i != 2)); }; case void => abort(); }; // absent let n99: [2]u8; n99[0] = 9u8; n99[1] = 9u8; match (bytes.rindex(a[0:4], n99[0:2])) { case let i: i32 => abort(); case void => void; }; }; // ---- contains --------------------------------------------------------- @test fn contains_cases() void = { let a: [4]u8; a[0] = 1u8; a[1] = 3u8; a[2] = 3u8; a[3] = 7u8; assert(!(!bytes.contains(a[0:4], 7u8))); assert(!( bytes.contains(a[0:4], 42u8))); let n: [2]u8; n[0] = 3u8; n[1] = 3u8; assert(!(!bytes.contains(a[0:4], n[0:2]))); let m: [2]u8; m[0] = 9u8; m[1] = 9u8; assert(!( bytes.contains(a[0:4], m[0:2]))); // Variadic rows. ref/hare/bytes/contains.ha:6. assert(!( bytes.contains(a[0:4]))); assert(!(!bytes.contains(a[0:4], n[0:2]))); assert(!(!bytes.contains(a[0:4], 7u8))); assert(!(!bytes.contains(a[0:4], m[0:2], n[0:2], 42u8))); assert(!( bytes.contains(a[0:4], m[0:2], 42u8, m[0:2]))); }; // ---- hasprefix -------------------------------------------------------- // ref/hare/bytes/contains.ha:25. @test fn hasprefix_cases() void = { let z: [1]u8; assert(!(!bytes.hasprefix(z[0:0], z[0:0]))); let one: [1]u8; one[0] = 0u8; assert(!(!bytes.hasprefix(one[0:1], z[0:0]))); assert(!( bytes.hasprefix(z[0:0], one[0:1]))); let a: [3]u8; a[0] = 1u8; a[1] = 2u8; a[2] = 3u8; let p12: [2]u8; p12[0] = 1u8; p12[1] = 2u8; assert(!(!bytes.hasprefix(a[0:3], p12[0:2]))); let p11: [2]u8; p11[0] = 1u8; p11[1] = 1u8; assert(!( bytes.hasprefix(a[0:3], p11[0:2]))); let pl: [4]u8; pl[0] = 1u8; pl[1] = 2u8; pl[2] = 3u8; pl[3] = 4u8; assert(!( bytes.hasprefix(a[0:3], pl[0:4]))); }; // ---- hassuffix -------------------------------------------------------- // ref/hare/bytes/contains.ha:40. @test fn hassuffix_cases() void = { let z: [1]u8; assert(!(!bytes.hassuffix(z[0:0], z[0:0]))); let one: [1]u8; one[0] = 0u8; assert(!(!bytes.hassuffix(one[0:1], z[0:0]))); assert(!( bytes.hassuffix(z[0:0], one[0:1]))); let a: [3]u8; a[0] = 1u8; a[1] = 2u8; a[2] = 3u8; let s23: [2]u8; s23[0] = 2u8; s23[1] = 3u8; assert(!(!bytes.hassuffix(a[0:3], s23[0:2]))); let s22: [2]u8; s22[0] = 2u8; s22[1] = 2u8; assert(!( bytes.hassuffix(a[0:3], s22[0:2]))); let a4: [4]u8; a4[0] = 1u8; a4[1] = 2u8; a4[2] = 3u8; a4[3] = 4u8; let s234: [3]u8; s234[0] = 2u8; s234[1] = 3u8; s234[2] = 4u8; assert(!(!bytes.hassuffix(a4[0:4], s234[0:3]))); }; // ---- tokenize / rtokenize / peek_token / remaining_tokens ----------- // ref/hare/bytes/tokenize.ha:258. Hare's @test fn tokenize / rtokenize // drives the iterator through an expected-token sequence and asserts // `equal(p, n)` (peek == next), `equal(n, want)` (next == expected). // expect_token — table row driver. Advances `t` once, asserts the // returned token matches `want`. peek invariant: peek must equal next. fn expect_token(t: *bytes.tokenizer, want: []u8) void = { match (bytes.peek_token(t)) { case let p: []u8 => { assert(!(!bytes.equal(p, want))); }; case bytes.done => abort(); }; match (bytes.next_token(t)) { case let n: []u8 => { assert(!(!bytes.equal(n, want))); }; case bytes.done => abort(); }; }; // expect_done — table-row driver. peek and next must both be done. fn expect_done(t: *bytes.tokenizer) void = { match (bytes.peek_token(t)) { case let p: []u8 => abort(); case bytes.done => void; }; match (bytes.next_token(t)) { case let n: []u8 => abort(); case bytes.done => void; }; }; @test fn tokenize_cases() void = { let z: [1]u8; // simple — [1,2,0,3,4] / [0] -> [1,2],[3,4] let a: [5]u8; a[0] = 1u8; a[1] = 2u8; a[2] = 0u8; a[3] = 3u8; a[4] = 4u8; let e_12: [2]u8; e_12[0] = 1u8; e_12[1] = 2u8; let e_34: [2]u8; e_34[0] = 3u8; e_34[1] = 4u8; let t: bytes.tokenizer = bytes.tokenize(a[0:5], 0u8); expect_token(&t, e_12[0:2]); expect_token(&t, e_34[0:2]); expect_done(&t); // multiple delimiters — [1,2,0,3,4,42,5,6] / [0,42] -> [1,2],[3,4],[5,6] let b: [8]u8; b[0] = 1u8; b[1] = 2u8; b[2] = 0u8; b[3] = 3u8; b[4] = 4u8; b[5] = 42u8; b[6] = 5u8; b[7] = 6u8; let e_56: [2]u8; e_56[0] = 5u8; e_56[1] = 6u8; let t2: bytes.tokenizer = bytes.tokenize(b[0:8], 0u8, 42u8); expect_token(&t2, e_12[0:2]); expect_token(&t2, e_34[0:2]); expect_token(&t2, e_56[0:2]); expect_done(&t2); // empty interior tokens — [1,2,0,0,0,3,4] / [0] -> [1,2],[],[],[3,4] let c: [7]u8; c[0] = 1u8; c[1] = 2u8; c[2] = 0u8; c[3] = 0u8; c[4] = 0u8; c[5] = 3u8; c[6] = 4u8; let t3: bytes.tokenizer = bytes.tokenize(c[0:7], 0u8); expect_token(&t3, e_12[0:2]); expect_token(&t3, z[0:0]); expect_token(&t3, z[0:0]); expect_token(&t3, e_34[0:2]); expect_done(&t3); // leading + trailing empty — [0,1,2,3,0] / [0] -> [],[1,2,3],[] let d: [5]u8; d[0] = 0u8; d[1] = 1u8; d[2] = 2u8; d[3] = 3u8; d[4] = 0u8; let e_123: [3]u8; e_123[0] = 1u8; e_123[1] = 2u8; e_123[2] = 3u8; let t4: bytes.tokenizer = bytes.tokenize(d[0:5], 0u8); expect_token(&t4, z[0:0]); expect_token(&t4, e_123[0:3]); expect_token(&t4, z[0:0]); expect_done(&t4); // no delim hit — [1,2,3] / [0] -> [1,2,3] let f: [3]u8; f[0] = 1u8; f[1] = 2u8; f[2] = 3u8; let t5: bytes.tokenizer = bytes.tokenize(f[0:3], 0u8); expect_token(&t5, e_123[0:3]); expect_done(&t5); // empty input — [] / [0] -> done immediately let t6: bytes.tokenizer = bytes.tokenize(z[0:0], 0u8); expect_done(&t6); }; @test fn rtokenize_cases() void = { let z: [1]u8; // simple — [1,2,0,3,4] / [0] -> [3,4],[1,2] let a: [5]u8; a[0] = 1u8; a[1] = 2u8; a[2] = 0u8; a[3] = 3u8; a[4] = 4u8; let e_12: [2]u8; e_12[0] = 1u8; e_12[1] = 2u8; let e_34: [2]u8; e_34[0] = 3u8; e_34[1] = 4u8; let t: bytes.tokenizer = bytes.rtokenize(a[0:5], 0u8); expect_token(&t, e_34[0:2]); expect_token(&t, e_12[0:2]); expect_done(&t); // multiple delimiters — [1,2,0,3,4,42,5,6] / [0,42] -> [5,6],[3,4],[1,2] let b: [8]u8; b[0] = 1u8; b[1] = 2u8; b[2] = 0u8; b[3] = 3u8; b[4] = 4u8; b[5] = 42u8; b[6] = 5u8; b[7] = 6u8; let e_56: [2]u8; e_56[0] = 5u8; e_56[1] = 6u8; let t2: bytes.tokenizer = bytes.rtokenize(b[0:8], 0u8, 42u8); expect_token(&t2, e_56[0:2]); expect_token(&t2, e_34[0:2]); expect_token(&t2, e_12[0:2]); expect_done(&t2); // empty interior tokens — [1,2,0,0,0,3,4] / [0] -> [3,4],[],[],[1,2] let c: [7]u8; c[0] = 1u8; c[1] = 2u8; c[2] = 0u8; c[3] = 0u8; c[4] = 0u8; c[5] = 3u8; c[6] = 4u8; let t3: bytes.tokenizer = bytes.rtokenize(c[0:7], 0u8); expect_token(&t3, e_34[0:2]); expect_token(&t3, z[0:0]); expect_token(&t3, z[0:0]); expect_token(&t3, e_12[0:2]); expect_done(&t3); // leading + trailing empty — [0,1,2,3,0] / [0] -> [],[1,2,3],[] let d: [5]u8; d[0] = 0u8; d[1] = 1u8; d[2] = 2u8; d[3] = 3u8; d[4] = 0u8; let e_123: [3]u8; e_123[0] = 1u8; e_123[1] = 2u8; e_123[2] = 3u8; let t4: bytes.tokenizer = bytes.rtokenize(d[0:5], 0u8); expect_token(&t4, z[0:0]); expect_token(&t4, e_123[0:3]); expect_token(&t4, z[0:0]); expect_done(&t4); // no delim hit — [1,2,3] / [0] -> [1,2,3] let f: [3]u8; f[0] = 1u8; f[1] = 2u8; f[2] = 3u8; let t5: bytes.tokenizer = bytes.rtokenize(f[0:3], 0u8); expect_token(&t5, e_123[0:3]); expect_done(&t5); // empty input — [] / [0] -> done immediately let t6: bytes.tokenizer = bytes.rtokenize(z[0:0], 0u8); expect_done(&t6); }; @test fn peek_token_cases() void = { // Peeking twice without advancing returns the same token. let a: [5]u8; a[0] = 1u8; a[1] = 2u8; a[2] = 0u8; a[3] = 3u8; a[4] = 4u8; let e_12: [2]u8; e_12[0] = 1u8; e_12[1] = 2u8; let t: bytes.tokenizer = bytes.tokenize(a[0:5], 0u8); match (bytes.peek_token(&t)) { case let p: []u8 => { assert(!(!bytes.equal(p, e_12[0:2]))); }; case bytes.done => abort(); }; match (bytes.peek_token(&t)) { case let p: []u8 => { assert(!(!bytes.equal(p, e_12[0:2]))); }; case bytes.done => abort(); }; // Then advance once — peek-after-next is the second token. let e_34: [2]u8; e_34[0] = 3u8; e_34[1] = 4u8; match (bytes.next_token(&t)) { case let n: []u8 => { assert(!(!bytes.equal(n, e_12[0:2]))); }; case bytes.done => abort(); }; match (bytes.peek_token(&t)) { case let p: []u8 => { assert(!(!bytes.equal(p, e_34[0:2]))); }; case bytes.done => abort(); }; // Reverse peek symmetry — first peek is last token. let t2: bytes.tokenizer = bytes.rtokenize(a[0:5], 0u8); match (bytes.peek_token(&t2)) { case let p: []u8 => { assert(!(!bytes.equal(p, e_34[0:2]))); }; case bytes.done => abort(); }; match (bytes.peek_token(&t2)) { case let p: []u8 => { assert(!(!bytes.equal(p, e_34[0:2]))); }; case bytes.done => abort(); }; }; @test fn remaining_tokens_cases() void = { // After one next_token, remaining is bytes past the consumed delim. let a: [5]u8; a[0] = 1u8; a[1] = 2u8; a[2] = 0u8; a[3] = 3u8; a[4] = 4u8; let e_34: [2]u8; e_34[0] = 3u8; e_34[1] = 4u8; let t: bytes.tokenizer = bytes.tokenize(a[0:5], 0u8); match (bytes.next_token(&t)) { case let n: []u8 => void; case bytes.done => abort(); }; let r: []u8 = bytes.remaining_tokens(&t); assert(!(!bytes.equal(r, e_34[0:2]))); // Reverse — after one next_token, remaining is bytes before the // consumed delim. ref/hare/bytes/tokenize.ha:323-327 pins [1,2]. let e_12: [2]u8; e_12[0] = 1u8; e_12[1] = 2u8; let t2: bytes.tokenizer = bytes.rtokenize(a[0:5], 0u8); match (bytes.next_token(&t2)) { case let n: []u8 => void; case bytes.done => abort(); }; let r2: []u8 = bytes.remaining_tokens(&t2); assert(!(!bytes.equal(r2, e_12[0:2]))); }; // ---- ltrim / rtrim / trim --------------------------------------------- // ref/hare/bytes/trim.ha:29 — Hare's @test fn trim pins // `trim([0,1,2,3,5,0], 0) == [1,2,3,5]`, `trim([0,0,0], 0) == []`, // `trim([], 0) == []`. ww spreads the matrix across ltrim/rtrim/trim. fn beq(got: []u8, want: []u8) bool = { if (got.len != want.len) { return false; }; let i: i32 = 0; for (i < got.len) { if (got[i] != want[i]) { return false; }; i += 1; }; return true; }; @test fn ltrim_cases() void = { let z: [1]u8; // [0,0,1,2] / 0 -> [1,2] let a: [4]u8; a[0] = 0u8; a[1] = 0u8; a[2] = 1u8; a[3] = 2u8; let ea: [2]u8; ea[0] = 1u8; ea[1] = 2u8; assert(!(!beq(bytes.ltrim(a[0:4], 0u8), ea[0:2]))); // [1,2,3] / 0 -> [1,2,3] (no leading match) let b: [3]u8; b[0] = 1u8; b[1] = 2u8; b[2] = 3u8; assert(!(!beq(bytes.ltrim(b[0:3], 0u8), b[0:3]))); // [0,0,0] / 0 -> [] (full match) — the bare `let c: [3]u8;` zero-init // row that surfaced #16 (a dirtied slot read non-zero, ltrim trimmed // nothing). Now reads {0,0,0} and trims to empty. let c: [3]u8; assert(!(!beq(bytes.ltrim(c[0:3], 0u8), z[0:0]))); // [] / 0 -> [] (empty input) assert(!(!beq(bytes.ltrim(z[0:0], 0u8), z[0:0]))); }; @test fn rtrim_cases() void = { let z: [1]u8; // [1,2,0,0] / 0 -> [1,2] let a: [4]u8; a[0] = 1u8; a[1] = 2u8; a[2] = 0u8; a[3] = 0u8; let ea: [2]u8; ea[0] = 1u8; ea[1] = 2u8; assert(!(!beq(bytes.rtrim(a[0:4], 0u8), ea[0:2]))); // [1,2,3] / 0 -> [1,2,3] (no trailing match) let b: [3]u8; b[0] = 1u8; b[1] = 2u8; b[2] = 3u8; assert(!(!beq(bytes.rtrim(b[0:3], 0u8), b[0:3]))); // [0,0,0] / 0 -> [] (full match) let c: [3]u8; assert(!(!beq(bytes.rtrim(c[0:3], 0u8), z[0:0]))); // [] / 0 -> [] (empty input) assert(!(!beq(bytes.rtrim(z[0:0], 0u8), z[0:0]))); }; @test fn trim_cases() void = { let z: [1]u8; // [0,1,2,3,5,0] / 0 -> [1,2,3,5] let a: [6]u8; a[0] = 0u8; a[1] = 1u8; a[2] = 2u8; a[3] = 3u8; a[4] = 5u8; a[5] = 0u8; let ea: [4]u8; ea[0] = 1u8; ea[1] = 2u8; ea[2] = 3u8; ea[3] = 5u8; assert(!(!beq(bytes.trim(a[0:6], 0u8), ea[0:4]))); // [0,5,0] / 5 -> [0,5,0] (only 5 in trim set; boundary mismatch) let b: [3]u8; b[0] = 0u8; b[1] = 5u8; b[2] = 0u8; assert(!(!beq(bytes.trim(b[0:3], 5u8), b[0:3]))); // [0,1,42,1,0] / {0,42} -> [1,42,1] (multi-byte trim set) let c: [5]u8; c[0] = 0u8; c[1] = 1u8; c[2] = 42u8; c[3] = 1u8; c[4] = 0u8; let ec: [3]u8; ec[0] = 1u8; ec[1] = 42u8; ec[2] = 1u8; assert(!(!beq(bytes.trim(c[0:5], 0u8, 42u8), ec[0:3]))); // [0,0,0] / 0 -> [] (full match) let d: [3]u8; assert(!(!beq(bytes.trim(d[0:3], 0u8), z[0:0]))); // [] / 0 -> [] (empty input, Hare ref/hare/bytes/trim.ha:34) assert(!(!beq(bytes.trim(z[0:0], 0u8), z[0:0]))); // [1,2,3,5] / 0 -> [1,2,3,5] (Hare ref/hare/bytes/trim.ha:31) let e: [4]u8; e[0] = 1u8; e[1] = 2u8; e[2] = 3u8; e[3] = 5u8; assert(!(!beq(bytes.trim(e[0:4], 0u8), e[0:4]))); }; // ---- splitn / rsplitn / split ----------------------------------------- // ref/hare/bytes/tokenize.ha:330 (@test fn split). Hare's table mixes // strings; ww spells the vectors as byte arrays explicitly. // // Element reads go through `&toks.ptr[i]: *[]u8` rather than `toks[i]` // — the full 24B slice-element copy lands in the multi-word-store gap // noted at cmd/w6c/cgen.c:6515-6523, so a value-load drops .len/.cap. // Pointer-then-fields lifts 8B at a time, which the cgen routes correctly. fn expect_tok(toks: [][]u8, i: i32, want: []u8) void = { assert(!(i >= toks.len)); let p: *[]u8 = &toks.ptr[i]; assert(!(p.len != want.len)); let j: i32 = 0; for (j < want.len) { assert(!(p.ptr[j] != want[j])); j += 1; }; }; @test fn splitn_cases() void = { // "Hello, my name is Drew" ─ space-delimited, n=4 yields the 4-th // token as the unconsumed remainder. Hare pins this exact shape // at ref/hare/bytes/tokenize.ha:340. let a: [22]u8; a[0] = 72u8; a[1] = 101u8; a[2] = 108u8; a[3] = 108u8; a[4] = 111u8; a[5] = 44u8; a[6] = 32u8; a[7] = 109u8; a[8] = 121u8; a[9] = 32u8; a[10] = 110u8; a[11] = 97u8; a[12] = 109u8; a[13] = 101u8; a[14] = 32u8; a[15] = 105u8; a[16] = 115u8; a[17] = 32u8; a[18] = 68u8; a[19] = 114u8; a[20] = 101u8; a[21] = 119u8; let sp: [1]u8; sp[0] = 32u8; let t1: [][]u8 = bytes.splitn(a[0:22], sp[0:1], 4); assert(!(t1.len != 4)); expect_tok(t1, 0, a[0:6]); expect_tok(t1, 1, a[7:9]); expect_tok(t1, 2, a[10:14]); expect_tok(t1, 3, a[15:22]); os.free(t1.ptr: *void, (t1.cap: u64) * 24u64); // n > token count — final slot is "" if input ends in delim, // otherwise the last token. Here three tokens, n=10 → 3 entries. let b: [5]u8; b[0] = 1u8; b[1] = 0u8; b[2] = 2u8; b[3] = 0u8; b[4] = 3u8; let zd: [1]u8; zd[0] = 0u8; let t2: [][]u8 = bytes.splitn(b[0:5], zd[0:1], 10); assert(!(t2.len != 3)); expect_tok(t2, 0, b[0:1]); expect_tok(t2, 1, b[2:3]); expect_tok(t2, 2, b[4:5]); os.free(t2.ptr: *void, (t2.cap: u64) * 24u64); // n == 1 — single slot holding the whole input as remainder. let t3: [][]u8 = bytes.splitn(b[0:5], zd[0:1], 1); assert(!(t3.len != 1)); expect_tok(t3, 0, b[0:5]); os.free(t3.ptr: *void, (t3.cap: u64) * 24u64); // delim absent — single slot holding input unchanged. let c: [3]u8; c[0] = 1u8; c[1] = 2u8; c[2] = 3u8; let t4: [][]u8 = bytes.splitn(c[0:3], zd[0:1], 5); assert(!(t4.len != 1)); expect_tok(t4, 0, c[0:3]); os.free(t4.ptr: *void, (t4.cap: u64) * 24u64); // empty input — empty result. let z: [1]u8; let t5: [][]u8 = bytes.splitn(z[0:0], zd[0:1], 5); assert(!(t5.len != 0)); if (t5.cap > 0) { os.free(t5.ptr: *void, (t5.cap: u64) * 24u64); }; // Multi-byte delimiter set — both 0 and 42 split. let d: [8]u8; d[0] = 1u8; d[1] = 2u8; d[2] = 0u8; d[3] = 3u8; d[4] = 4u8; d[5] = 42u8; d[6] = 5u8; d[7] = 6u8; let dd: [2]u8; dd[0] = 0u8; dd[1] = 42u8; let t6: [][]u8 = bytes.splitn(d[0:8], dd[0:2], 100); assert(!(t6.len != 3)); expect_tok(t6, 0, d[0:2]); expect_tok(t6, 1, d[3:5]); expect_tok(t6, 2, d[6:8]); os.free(t6.ptr: *void, (t6.cap: u64) * 24u64); }; @test fn rsplitn_cases() void = { // "Hello, my name is Drew" ─ rsplitn n=4 buckets the first three // tokens from the *end*; the remainder ("Hello, my") is index 0. // Hare pins this at ref/hare/bytes/tokenize.ha:379. let a: [22]u8; a[0] = 72u8; a[1] = 101u8; a[2] = 108u8; a[3] = 108u8; a[4] = 111u8; a[5] = 44u8; a[6] = 32u8; a[7] = 109u8; a[8] = 121u8; a[9] = 32u8; a[10] = 110u8; a[11] = 97u8; a[12] = 109u8; a[13] = 101u8; a[14] = 32u8; a[15] = 105u8; a[16] = 115u8; a[17] = 32u8; a[18] = 68u8; a[19] = 114u8; a[20] = 101u8; a[21] = 119u8; let sp: [1]u8; sp[0] = 32u8; let t1: [][]u8 = bytes.rsplitn(a[0:22], sp[0:1], 4); assert(!(t1.len != 4)); expect_tok(t1, 0, a[0:9]); expect_tok(t1, 1, a[10:14]); expect_tok(t1, 2, a[15:17]); expect_tok(t1, 3, a[18:22]); os.free(t1.ptr: *void, (t1.cap: u64) * 24u64); // n > token count — done short-circuit returns the toks in // reverse-iteration order (last token first). Mirrors Hare's // behavior at ref/hare/bytes/tokenize.ha:196-199 where the // reverse-step is gated behind the n-1 loop completion. let b: [5]u8; b[0] = 1u8; b[1] = 0u8; b[2] = 2u8; b[3] = 0u8; b[4] = 3u8; let zd: [1]u8; zd[0] = 0u8; let t2: [][]u8 = bytes.rsplitn(b[0:5], zd[0:1], 10); assert(!(t2.len != 3)); expect_tok(t2, 0, b[4:5]); expect_tok(t2, 1, b[2:3]); expect_tok(t2, 2, b[0:1]); os.free(t2.ptr: *void, (t2.cap: u64) * 24u64); // n == 1 — single slot holding the whole input as remainder. let t3: [][]u8 = bytes.rsplitn(b[0:5], zd[0:1], 1); assert(!(t3.len != 1)); expect_tok(t3, 0, b[0:5]); os.free(t3.ptr: *void, (t3.cap: u64) * 24u64); // delim absent — done short-circuits immediately at iter 0; // toks ends up holding only the eventual remainder if n=1 // (the loop never runs and peek picks up the full input). // With n>1 the loop's first next_token sees done (no delim // match anywhere) and returns toks={} per Hare's early-exit. let c: [3]u8; c[0] = 1u8; c[1] = 2u8; c[2] = 3u8; let t4: [][]u8 = bytes.rsplitn(c[0:3], zd[0:1], 5); assert(!(t4.len != 1)); expect_tok(t4, 0, c[0:3]); os.free(t4.ptr: *void, (t4.cap: u64) * 24u64); }; @test fn split_cases() void = { // Full split — every delim hit is a boundary; no per-call cap. // Hare pins five tokens for the canonical input at // ref/hare/bytes/tokenize.ha:347. let a: [22]u8; a[0] = 72u8; a[1] = 101u8; a[2] = 108u8; a[3] = 108u8; a[4] = 111u8; a[5] = 44u8; a[6] = 32u8; a[7] = 109u8; a[8] = 121u8; a[9] = 32u8; a[10] = 110u8; a[11] = 97u8; a[12] = 109u8; a[13] = 101u8; a[14] = 32u8; a[15] = 105u8; a[16] = 115u8; a[17] = 32u8; a[18] = 68u8; a[19] = 114u8; a[20] = 101u8; a[21] = 119u8; let sp: [1]u8; sp[0] = 32u8; let t1: [][]u8 = bytes.split(a[0:22], sp[0:1]); assert(!(t1.len != 5)); expect_tok(t1, 0, a[0:6]); expect_tok(t1, 1, a[7:9]); expect_tok(t1, 2, a[10:14]); expect_tok(t1, 3, a[15:17]); expect_tok(t1, 4, a[18:22]); os.free(t1.ptr: *void, (t1.cap: u64) * 24u64); // Leading + trailing empty tokens — five entries, three of which // are []. Splits across all positions exactly the way Hare does. let b: [5]u8; b[0] = 0u8; b[1] = 1u8; b[2] = 2u8; b[3] = 3u8; b[4] = 0u8; let zd: [1]u8; zd[0] = 0u8; let z: [1]u8; let t2: [][]u8 = bytes.split(b[0:5], zd[0:1]); assert(!(t2.len != 3)); expect_tok(t2, 0, z[0:0]); expect_tok(t2, 1, b[1:4]); expect_tok(t2, 2, z[0:0]); os.free(t2.ptr: *void, (t2.cap: u64) * 24u64); }; // ---- cut / rcut ------------------------------------------------------- // ref/hare/bytes/tokenize.ha:429 (@test fn cut). Borrowed views; each // half asserted via equal(). Needle order is ww's (u8 | []u8). Rows use // the destructure form `let (before, after) = cut(...)` to drive the #10 // over-cap tuple-return (sret) end-to-end. @test fn cut_cases() void = { let z: [1]u8; let abc: [3]u8; abc[0] = 97u8; abc[1] = 98u8; abc[2] = 99u8; let ea: [1]u8; ea[0] = 97u8; let ec: [1]u8; ec[0] = 99u8; // ['a','b','c'] along byte 'b' -> ['a'],['c'] let (b0, a0) = bytes.cut(abc[0:3], 98u8); assert(!(!bytes.equal(b0, ea[0:1]))); assert(!(!bytes.equal(a0, ec[0:1]))); // same, single-byte []u8 delim -> identical let nb: [1]u8; nb[0] = 98u8; let (b1, a1) = bytes.cut(abc[0:3], nb[0:1]); assert(!(!bytes.equal(b1, ea[0:1]))); assert(!(!bytes.equal(a1, ec[0:1]))); // ['b','c'] along 'b' -> [],['c'] (empty before) let bc: [2]u8; bc[0] = 98u8; bc[1] = 99u8; let (b2, a2) = bytes.cut(bc[0:2], 98u8); assert(!(!bytes.equal(b2, z[0:0]))); assert(!(!bytes.equal(a2, ec[0:1]))); // ['a','b'] along 'b' -> ['a'],[] (empty after) let ab: [2]u8; ab[0] = 97u8; ab[1] = 98u8; let (b3, a3) = bytes.cut(ab[0:2], 98u8); assert(!(!bytes.equal(b3, ea[0:1]))); assert(!(!bytes.equal(a3, z[0:0]))); // delim absent -> (whole input, []) let (b4, a4) = bytes.cut(abc[0:3], 120u8); assert(!(!bytes.equal(b4, abc[0:3]))); assert(!(!bytes.equal(a4, z[0:0]))); // empty input -> ([],[]) let (b5, a5) = bytes.cut(z[0:0], 120u8); assert(!(!bytes.equal(b5, z[0:0]))); assert(!(!bytes.equal(a5, z[0:0]))); // repeated delim -> FIRST instance: ['a'],['c','b','a'] let abcba: [5]u8; abcba[0] = 97u8; abcba[1] = 98u8; abcba[2] = 99u8; abcba[3] = 98u8; abcba[4] = 97u8; let ecba: [3]u8; ecba[0] = 99u8; ecba[1] = 98u8; ecba[2] = 97u8; let (b6, a6) = bytes.cut(abcba[0:5], 98u8); assert(!(!bytes.equal(b6, ea[0:1]))); assert(!(!bytes.equal(a6, ecba[0:3]))); // 2-byte []u8 delim present — "abc" -> ['a','b'],['c'] let h: [5]u8; h[0] = 97u8; h[1] = 98u8; h[2] = 88u8; h[3] = 89u8; h[4] = 99u8; let xy: [2]u8; xy[0] = 88u8; xy[1] = 89u8; let eab: [2]u8; eab[0] = 97u8; eab[1] = 98u8; let (b7, a7) = bytes.cut(h[0:5], xy[0:2]); assert(!(!bytes.equal(b7, eab[0:2]))); assert(!(!bytes.equal(a7, ec[0:1]))); // 2-byte []u8 delim absent -> (whole, []) let zz: [2]u8; zz[0] = 9u8; zz[1] = 9u8; let (b8, a8) = bytes.cut(h[0:5], zz[0:2]); assert(!(!bytes.equal(b8, h[0:5]))); assert(!(!bytes.equal(a8, z[0:0]))); }; @test fn rcut_cases() void = { let z: [1]u8; let ea: [1]u8; ea[0] = 97u8; // ['a','b','c'] along 'b' -> ['a'],['c'] (single instance == cut) let abc: [3]u8; abc[0] = 97u8; abc[1] = 98u8; abc[2] = 99u8; let ec: [1]u8; ec[0] = 99u8; let (b0, a0) = bytes.rcut(abc[0:3], 98u8); assert(!(!bytes.equal(b0, ea[0:1]))); assert(!(!bytes.equal(a0, ec[0:1]))); // repeated delim -> LAST instance: ['a','b','c'],['a'] let abcba: [5]u8; abcba[0] = 97u8; abcba[1] = 98u8; abcba[2] = 99u8; abcba[3] = 98u8; abcba[4] = 97u8; let eabc: [3]u8; eabc[0] = 97u8; eabc[1] = 98u8; eabc[2] = 99u8; let (b1, a1) = bytes.rcut(abcba[0:5], 98u8); assert(!(!bytes.equal(b1, eabc[0:3]))); assert(!(!bytes.equal(a1, ea[0:1]))); // delim absent -> (whole input, []) let (b2, a2) = bytes.rcut(abc[0:3], 120u8); assert(!(!bytes.equal(b2, abc[0:3]))); assert(!(!bytes.equal(a2, z[0:0]))); // 2-byte []u8 delim present, two instances -> cut at LAST. // "ab" -> ['X','Y','a'],['b'] let h: [6]u8; h[0] = 88u8; h[1] = 89u8; h[2] = 97u8; h[3] = 88u8; h[4] = 89u8; h[5] = 98u8; let xy: [2]u8; xy[0] = 88u8; xy[1] = 89u8; let exya: [3]u8; exya[0] = 88u8; exya[1] = 89u8; exya[2] = 97u8; let eb: [1]u8; eb[0] = 98u8; let (b3, a3) = bytes.rcut(h[0:6], xy[0:2]); assert(!(!bytes.equal(b3, exya[0:3]))); assert(!(!bytes.equal(a3, eb[0:1]))); // 2-byte []u8 delim absent -> (whole, []) let zz: [2]u8; zz[0] = 9u8; zz[1] = 9u8; let (b4, a4) = bytes.rcut(h[0:6], zz[0:2]); assert(!(!bytes.equal(b4, h[0:6]))); assert(!(!bytes.equal(a4, z[0:0]))); };