diff --git a/Makefile b/Makefile index 30522f94..569b190a 100644 --- a/Makefile +++ b/Makefile @@ -260,7 +260,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_shlex_run $(BIN)/test_getenv_run $(BIN)/test_dirs_run \ $(BIN)/test_stat_run $(BIN)/test_time_run \ $(BIN)/test_intdiv_signed \ - $(BIN)/test_hex_run $(BIN)/test_utf8_run \ + $(BIN)/test_hex_run $(BIN)/test_utf8_run $(BIN)/test_bytes_run \ $(BIN)/test_memio_run $(BIN)/test_temp_run $(BIN)/test_getopt_run \ $(BIN)/test_base32_run $(BIN)/test_base64_run \ $(BIN)/test_adler32_run $(BIN)/test_crc16_run \ @@ -638,6 +638,10 @@ $(BIN)/test_utf8_run: test/wcc/968_utf8_run.c $(BIN)/ww $(BIN)/w6c \ $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< +$(BIN)/test_bytes_run: test/wcc/967_bytes_run.c $(BIN)/ww $(BIN)/w6c \ + $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) + $(CC) $(CFLAGS) -o $@ $< + $(BIN)/test_memio_run: test/wcc/980_memio_run.c $(BIN)/ww $(BIN)/w6c \ $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< diff --git a/lib/bytes/bytes.ww b/lib/bytes/bytes.ww index 608ef83d..f0d56b85 100644 --- a/lib/bytes/bytes.ww +++ b/lib/bytes/bytes.ww @@ -1,18 +1,31 @@ -// bytes — slice operations over []u8. +// bytes — slice operations over []u8. Mirrors Hare's bytes module +// (ref/hare/bytes/) for the in-tree subset: search/equality/prefix +// helpers used by lib/encoding, lib/bufio, lib/memio. +// +// Documented divergences from Hare: +// - index_slice / rindex_slice use naive O(n·m); Hare specialises +// 2/3/4-byte needles and falls back to two_way (Crochemore-Perrin) +// for longer (ref/hare/bytes/index.ha:61, ref/hare/bytes/two_way.ha). +// Correctness equivalent. +// - contains takes a single needle; Hare's contains is variadic +// `(u8 | []u8)...` (ref/hare/bytes/contains.ha:5). No caller needs +// the variadic shape yet; graduate when one does. +// equal — true iff `a` and `b` have the same length and contents. +// ref/hare/bytes/equal.ha:9. export fn equal(a: []u8, b: []u8) bool = { + if (a.len != b.len) { return false; }; let i: i32 = 0; for (i < a.len) { - if (i >= b.len) { return false; }; if (a[i] != b[i]) { return false; }; i += 1; }; - return i == b.len; + return true; }; -// index — first index of `needle` in `s`. Mirrors Hare's bytes::index: -// `u8` needle scans for the byte, `[]u8` needle scans for the -// substring. Returns void if absent. +// index — first offset of `needle` in `s`. u8 needle scans for the +// byte; []u8 needle scans for the substring. void if absent. +// ref/hare/bytes/index.ha:6. export fn index(s: []u8, needle: (u8 | []u8)) (i32 | void) = { match (needle) { case let c: u8 => { @@ -44,8 +57,9 @@ export fn index(s: []u8, needle: (u8 | []u8)) (i32 | void) = { return; }; -// rindex — last index of `needle` in `s`. Mirrors Hare's bytes::rindex. -// Empty []u8 needle matches at s.len. +// rindex — last offset of `needle` in `s`. Empty []u8 needle returns +// s.len (ref/hare/bytes/index.ha:103 — Hare's loop yields r-0 at i=0). +// ref/hare/bytes/index.ha:86. export fn rindex(s: []u8, needle: (u8 | []u8)) (i32 | void) = { match (needle) { case let c: u8 => { @@ -76,18 +90,18 @@ export fn rindex(s: []u8, needle: (u8 | []u8)) (i32 | void) = { return; }; -// contains — true iff `sub` appears in `s`. Mirrors Hare's -// bytes::contains for the slice case. -export fn contains(s: []u8, sub: []u8) bool = { - let r: (i32 | void) = index(s, sub); - match (r) { +// contains — true iff `needle` (byte or sub-slice) appears in `s`. +// ref/hare/bytes/contains.ha:5 (variadic subset; see header note). +export fn contains(s: []u8, needle: (u8 | []u8)) bool = { + match (index(s, needle)) { case let i: i32 => return true; case void => return false; }; return false; }; -// hasprefix — `s` starts with `pre`. Mirrors Hare's bytes::hasprefix. +// hasprefix — true iff `s` starts with `pre`. +// ref/hare/bytes/contains.ha:21. export fn hasprefix(s: []u8, pre: []u8) bool = { if (pre.len > s.len) { return false; }; let i: i32 = 0; @@ -98,7 +112,8 @@ export fn hasprefix(s: []u8, pre: []u8) bool = { return true; }; -// hassuffix — `s` ends with `suf`. Mirrors Hare's bytes::hassuffix. +// hassuffix — true iff `s` ends with `suf`. +// ref/hare/bytes/contains.ha:35. export fn hassuffix(s: []u8, suf: []u8) bool = { if (suf.len > s.len) { return false; }; let off: i32 = s.len - suf.len; @@ -110,7 +125,7 @@ export fn hassuffix(s: []u8, suf: []u8) bool = { return true; }; -// reverse — in-place reverse of `s`. Mirrors Hare's bytes::reverse. +// reverse — in-place reverse of `s`. ref/hare/bytes/reverse.ha:5. export fn reverse(s: []u8) void = { let i: i32 = 0; let j: i32 = s.len - 1; @@ -123,7 +138,7 @@ export fn reverse(s: []u8) void = { }; }; -// zero — set every byte of `s` to 0. Mirrors Hare's bytes::zero. +// zero — set every byte of `s` to 0. ref/hare/bytes/zero.ha:5. export fn zero(s: []u8) void = { let i: i32 = 0; for (i < s.len) { diff --git a/lib/bytes/bytestest.ww b/lib/bytes/bytestest.ww new file mode 100644 index 00000000..3b81ef11 --- /dev/null +++ b/lib/bytes/bytestest.ww @@ -0,0 +1,245 @@ +// bytestest — exercises lib/bytes. Run with +// `out/bin/ww run lib/bytes/bytestest.ww`. Same signalled-then- +// fail()-with-+10 pattern as hex / utf8 / time tests: non-zero exit +// pinpoints the failing scenario. +// +// Vectors mirror Hare's @test fns in ref/hare/bytes/equal.ha, +// ref/hare/bytes/index.ha, ref/hare/bytes/contains.ha. + +use bytes; +use os; + +let signalled: i32 = 0; +fn fail() void = { os.exit(signalled + 10); }; + +// ---- 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; + if (!bytes.equal(a[0:3], b[0:3])) { fail(); }; + if ( bytes.equal(a[0:3], c[0:3])) { fail(); }; + if ( bytes.equal(a[0:3], d[0:4])) { fail(); }; + if ( bytes.equal(a[0:3], e[0:2])) { fail(); }; + if (!bytes.equal(z[0:0], z[0:0])) { fail(); }; // 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 => { if (i != 0) { fail(); }; }; + case void => { fail(); }; + }; + match (bytes.index(a[0:4], 3u8)) { + case let i: i32 => { if (i != 1) { fail(); }; }; + case void => { fail(); }; + }; + match (bytes.index(a[0:4], 7u8)) { + case let i: i32 => { if (i != 3) { fail(); }; }; + case void => { fail(); }; + }; + match (bytes.index(a[0:4], 42u8)) { + case let i: i32 => { fail(); }; + case void => void; + }; + let z: [1]u8; + match (bytes.index(z[0:0], 42u8)) { + case let i: i32 => { fail(); }; + 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 => { if (i != 1) { fail(); }; }; + case void => { fail(); }; + }; + + 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 => { if (i != 1) { fail(); }; }; + case void => { fail(); }; + }; + + // 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 => { fail(); }; + 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 => { if (i != 0) { fail(); }; }; + case void => { fail(); }; + }; + + // 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 => { fail(); }; + 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 => { if (i != 1) { fail(); }; }; + case void => { fail(); }; + }; + + // 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 => { fail(); }; + 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 => { if (i != 0) { fail(); }; }; + case void => { fail(); }; + }; + // empty haystack, non-empty needle — void + match (bytes.index(z[0:0], n3[0:3])) { + case let i: i32 => { fail(); }; + 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 => { if (i != 3) { fail(); }; }; + case void => { fail(); }; + }; +}; + +// ---- 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 => { if (i != 2) { fail(); }; }; + case void => { fail(); }; + }; + match (bytes.rindex(a[0:4], 42u8)) { + case let i: i32 => { fail(); }; + case void => void; + }; + let z: [1]u8; + match (bytes.rindex(z[0:0], 42u8)) { + case let i: i32 => { fail(); }; + 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 => { if (i != 1) { fail(); }; }; + case void => { fail(); }; + }; + let n12: [2]u8; n12[0] = 1u8; n12[1] = 2u8; + match (bytes.rindex(a[0:4], n12[0:2])) { + case let i: i32 => { if (i != 2) { fail(); }; }; + case void => { fail(); }; + }; + // absent + let n99: [2]u8; n99[0] = 9u8; n99[1] = 9u8; + match (bytes.rindex(a[0:4], n99[0:2])) { + case let i: i32 => { fail(); }; + 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; + if (!bytes.contains(a[0:4], 7u8)) { fail(); }; + if ( bytes.contains(a[0:4], 42u8)) { fail(); }; + let n: [2]u8; n[0] = 3u8; n[1] = 3u8; + if (!bytes.contains(a[0:4], n[0:2])) { fail(); }; + let m: [2]u8; m[0] = 9u8; m[1] = 9u8; + if ( bytes.contains(a[0:4], m[0:2])) { fail(); }; +}; + +// ---- hasprefix -------------------------------------------------------- +// ref/hare/bytes/contains.ha:25. + +@test fn hasprefix_cases() void = { + let z: [1]u8; + if (!bytes.hasprefix(z[0:0], z[0:0])) { fail(); }; + let one: [1]u8; one[0] = 0u8; + if (!bytes.hasprefix(one[0:1], z[0:0])) { fail(); }; + if ( bytes.hasprefix(z[0:0], one[0:1])) { fail(); }; + let a: [3]u8; a[0] = 1u8; a[1] = 2u8; a[2] = 3u8; + let p12: [2]u8; p12[0] = 1u8; p12[1] = 2u8; + if (!bytes.hasprefix(a[0:3], p12[0:2])) { fail(); }; + let p11: [2]u8; p11[0] = 1u8; p11[1] = 1u8; + if ( bytes.hasprefix(a[0:3], p11[0:2])) { fail(); }; + let pl: [4]u8; pl[0] = 1u8; pl[1] = 2u8; pl[2] = 3u8; pl[3] = 4u8; + if ( bytes.hasprefix(a[0:3], pl[0:4])) { fail(); }; +}; + +// ---- hassuffix -------------------------------------------------------- +// ref/hare/bytes/contains.ha:40. + +@test fn hassuffix_cases() void = { + let z: [1]u8; + if (!bytes.hassuffix(z[0:0], z[0:0])) { fail(); }; + let one: [1]u8; one[0] = 0u8; + if (!bytes.hassuffix(one[0:1], z[0:0])) { fail(); }; + if ( bytes.hassuffix(z[0:0], one[0:1])) { fail(); }; + let a: [3]u8; a[0] = 1u8; a[1] = 2u8; a[2] = 3u8; + let s23: [2]u8; s23[0] = 2u8; s23[1] = 3u8; + if (!bytes.hassuffix(a[0:3], s23[0:2])) { fail(); }; + let s22: [2]u8; s22[0] = 2u8; s22[1] = 2u8; + if ( bytes.hassuffix(a[0:3], s22[0:2])) { fail(); }; + 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; + if (!bytes.hassuffix(a4[0:4], s234[0:3])) { fail(); }; +}; + +export fn main() i32 = { + signalled = 1; equal_cases(); + signalled = 2; index_byte_cases(); + signalled = 3; index_slice_cases(); + signalled = 4; rindex_byte_cases(); + signalled = 5; rindex_slice_cases(); + signalled = 6; contains_cases(); + signalled = 7; hasprefix_cases(); + signalled = 8; hassuffix_cases(); + return 0; +}; diff --git a/lib/encoding/hex/hextest.ww b/lib/encoding/hex/hextest.ww index 2beafd90..2f823b58 100644 --- a/lib/encoding/hex/hextest.ww +++ b/lib/encoding/hex/hextest.ww @@ -3,6 +3,7 @@ // signalled-then-fail()-with-+10 pattern as the rest of the 9xx // stdlib tests; non-zero exit pinpoints the failing scenario. +use bytes; use hex; use os; @@ -28,16 +29,6 @@ fn streq(buf: []u8, expect: str) bool = { return true; }; -fn beq(a: []u8, b: []u8) 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; -}; - // ---- encodedsize / decodedsize ----------------------------------------- @test fn sizes() void = { @@ -109,7 +100,7 @@ fn beq(a: []u8, b: []u8) bool = { let want: [8]u8; want[0] = 0xCAu8; want[1] = 0xFEu8; want[2] = 0xBAu8; want[3] = 0xBEu8; want[4] = 0xDEu8; want[5] = 0xADu8; want[6] = 0xF0u8; want[7] = 0x0Du8; - if (!beq(dst[0:8], want[0:8])) { fail(); }; + if (!bytes.equal(dst[0:8], want[0:8])) { fail(); }; }; case let e: hex.invalid => { fail(); }; }; @@ -126,7 +117,7 @@ fn beq(a: []u8, b: []u8) bool = { let want: [8]u8; want[0] = 0xCAu8; want[1] = 0xFEu8; want[2] = 0xBAu8; want[3] = 0xBEu8; want[4] = 0xDEu8; want[5] = 0xADu8; want[6] = 0xF0u8; want[7] = 0x0Du8; - if (!beq(dst[0:8], want[0:8])) { fail(); }; + if (!bytes.equal(dst[0:8], want[0:8])) { fail(); }; }; case let e: hex.invalid => { fail(); }; }; @@ -146,7 +137,7 @@ fn beq(a: []u8, b: []u8) bool = { let want: [8]u8; want[0] = 0xCAu8; want[1] = 0xFEu8; want[2] = 0xBAu8; want[3] = 0xBEu8; want[4] = 0xDEu8; want[5] = 0xADu8; want[6] = 0xF0u8; want[7] = 0x0Du8; - if (!beq(dst[0:8], want[0:8])) { fail(); }; + if (!bytes.equal(dst[0:8], want[0:8])) { fail(); }; }; case let e: hex.invalid => { fail(); }; }; @@ -218,7 +209,7 @@ fn beq(a: []u8, b: []u8) bool = { match (r) { case let m: i32 => { if (m != 256) { fail(); }; - if (!beq(src[0:256], dec[0:256])) { fail(); }; + if (!bytes.equal(src[0:256], dec[0:256])) { fail(); }; }; case let e: hex.invalid => { fail(); }; }; diff --git a/lib/encoding/utf8/utf8test.ww b/lib/encoding/utf8/utf8test.ww index cd844f09..e6292fab 100644 --- a/lib/encoding/utf8/utf8test.ww +++ b/lib/encoding/utf8/utf8test.ww @@ -9,16 +9,6 @@ use os; let signalled: i32 = 0; fn fail() void = { os.exit(signalled + 10); }; -fn beq(a: []u8, b: []u8) 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; -}; - // ---- runesz: byte length per range ------------------------------------ // ref/hare/encoding/utf8/rune.ha:5. Boundaries: 0x7F → 1, 0x80 → 2, // 0x7FF → 2, 0x800 → 3, 0xFFFF → 3, 0x10000 → 4, 0x10FFFF → 4. diff --git a/test/wcc/967_bytes_run.c b/test/wcc/967_bytes_run.c new file mode 100644 index 00000000..28be2420 --- /dev/null +++ b/test/wcc/967_bytes_run.c @@ -0,0 +1,49 @@ +/* + * 967_bytes_run — execute the lib/bytes @test fixture under the + * C-side `ww run` driver and assert exit 0. + * + * Same thin-wrapper shape as 979_hex_run / 968_utf8_run: + * bytestest.ww carries its own `export fn main()` that drives the + * @test fns and signals which case failed via the exit code. + */ +#include +#include +#include +#include + +static int +runwait(const char *cmd) +{ + int rc = system(cmd); + if (rc == -1) return -1; + if (WIFEXITED(rc)) return WEXITSTATUS(rc); + return 1; +} + +int +main(void) +{ + const char *bin = getenv("BIN"); + if (!bin) bin = "out/bin"; + char absbin[1024]; + if (bin[0] != '/') { + char cwd[1024]; + if (getcwd(cwd, sizeof cwd) == NULL) return 1; + snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); + bin = absbin; + } + char cwd[1024]; + if (getcwd(cwd, sizeof cwd) == NULL) return 1; + + const char *src = "lib/bytes/bytestest.ww"; + char path[1024], cmd[2048]; + snprintf(path, sizeof path, "%s/%s", cwd, src); + snprintf(cmd, sizeof cmd, "%s/ww run %s", bin, path); + int rc = runwait(cmd); + if (rc != 0) { + fprintf(stderr, "bytes_run FAIL: %s exited %d\n", src, rc); + return 1; + } + printf("bytes_run: %s ok\n", src); + return 0; +}