diff --git a/CLAUDE.md b/CLAUDE.md index 608b3e5c..3590dead 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,7 +6,7 @@ 6. ref/hare and ref/plan9front are read-only references — consult before inventing data shapes or syntax 7. No workarounds. If a bug forces a workaround, STOP and report with a precise repro. Document any retained divergence at the site with a pointer to the filed task. Never silent. 8. Comments are WHY-only. Never narrate WHAT the code does — names carry the WHAT. Comment only non-obvious WHY: a constraint, a divergence from a reference, a citation to a filed task. -9. Hare-fidelity over convenience. No ad-hoc extensions, renames, or convenience wrappers in lib/. Cite ref/hare// for every signature ported. Carve-out: the Hare `_unsafe` suffix convention is dropped wholesale (ww is C/Plan-9-lineage, an unmanaged systems language — no GC, no "safe" baseline to be unsafe relative to). bytes→str is a pure reinterpret (`strings.frombytes`, renamed from Hare's `fromutf8_unsafe`); validation is opt-in via `utf8.validate(b)?` at the IO source, never wrapped per-construction. Rationale: ref/hare/strings/utf8.ha:10,22 — the suffix flags Hare's managed-bytes-safety axis, which ww doesn't have. The honest name (`frombytes`) reserves `fromutf8` for a future true validating helper. +9. Hare-fidelity over convenience. No ad-hoc extensions, renames, or convenience wrappers in lib/. Cite ref/hare// for every signature ported. Carve-out: the Hare `_unsafe` suffix convention is dropped wholesale (ww is C/Plan-9-lineage, an unmanaged systems language — no GC, no "safe" baseline to be unsafe relative to). bytes→str is a pure reinterpret (`strings.frombytes`, renamed from Hare's `fromutf8_unsafe`); validation is opt-in via `utf8.validate(b)?` at the IO source, never wrapped per-construction. Rationale: ref/hare/strings/utf8.ha:10,22 — the suffix flags Hare's managed-bytes-safety axis, which ww doesn't have. The honest name (`frombytes`) reserves `fromutf8` for a future true validating helper. Carve-out: lib/ tests use Go's external-test-package idiom (`package _test;` + `import ;`), a sanctioned Go-over-Hare departure — ww hard-rejects self-import (full Go), and Hare's same-package `@test` colocation has no ww analogue yet (in-package white-box testing is deferred pending tasks #5/#8, not forbidden). Rule 5/9 binds lib/ *API* surface, not test-authoring style. Ref: Go `foo_test` convention; task #16. 10. Symmetric stages. cstage and wwstage MUST emit byte-identical asm for the same input. When inference power differs, align the richer side DOWN to the leaner side, not the other way. 11. Split commits when they bundle unrelated concerns. Bisect-cleanliness is the default. Multi-fix commits need a body paragraph explaining why they couldn't split. 12. Simple data, simple algorithms. Sea-of-stars style. Mirror Hare's structural choices over clever alternatives. diff --git a/Makefile b/Makefile index 8a59cd3f..54b84364 100644 --- a/Makefile +++ b/Makefile @@ -475,7 +475,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_path_run \ $(BIN)/test_letshadow_run \ $(BIN)/test_strglobeq_run \ - $(BIN)/test_decimal_run $(BIN)/test_strconv_int_run \ + $(BIN)/test_strconv_int_run \ $(BIN)/test_stof_run $(BIN)/test_ftos_run \ $(BIN)/test_memio_run $(BIN)/test_temp_run $(BIN)/test_getopt_run \ $(BIN)/test_errno_run \ @@ -2203,10 +2203,6 @@ $(BIN)/test_nkname_run: test/wcc/905_nkname_run.c $(BIN)/ww $(BIN)/w6c \ $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< -$(BIN)/test_decimal_run: test/wcc/922_decimal_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - $(BIN)/test_strconv_int_run: test/wcc/922_strconv_int_run.c $(BIN)/ww $(BIN)/w6c \ $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< diff --git a/lib/ascii/asciitest.ww b/lib/ascii/asciitest.ww index 101353c9..e127257b 100644 --- a/lib/ascii/asciitest.ww +++ b/lib/ascii/asciitest.ww @@ -4,7 +4,7 @@ // // Vectors mirror Hare's @test fn strcasecmp in ref/hare/ascii/string.ha. -package ascii; +package ascii_test; import ascii; import os; diff --git a/lib/bufio/bufiotest.ww b/lib/bufio/bufiotest.ww index 8d2714f3..c960061f 100644 --- a/lib/bufio/bufiotest.ww +++ b/lib/bufio/bufiotest.ww @@ -6,7 +6,7 @@ // surface — `let st = memio.fixed(buf); &st.vt` into the scanner/stream // init, io.read/write/close return size/io.error. -package bufio; +package bufio_test; import bufio; import bytes; diff --git a/lib/bytes/bytestest.ww b/lib/bytes/bytestest.ww index 349755cc..fc78e5e9 100644 --- a/lib/bytes/bytestest.ww +++ b/lib/bytes/bytestest.ww @@ -6,7 +6,7 @@ // Vectors mirror Hare's @test fns in ref/hare/bytes/equal.ha, // ref/hare/bytes/index.ha, ref/hare/bytes/contains.ha. -package bytes; +package bytes_test; import bytes; import os; diff --git a/lib/dirs/dirstest.ww b/lib/dirs/dirstest.ww index ce8e773c..eb7dc4be 100644 --- a/lib/dirs/dirstest.ww +++ b/lib/dirs/dirstest.ww @@ -33,7 +33,7 @@ // Don't `use io;` here — lib/os and lib/io collide on read/write/ // close (task #17). We need only os.getenv + os.exit. -package dirs; +package dirs_test; import dirs; import os; diff --git a/lib/encoding/base32/base32_test.ww b/lib/encoding/base32/base32_test.ww index 385e3a30..45ed9479 100644 --- a/lib/encoding/base32/base32_test.ww +++ b/lib/encoding/base32/base32_test.ww @@ -1,4 +1,4 @@ -package base32; +package base32_test; import base32; diff --git a/lib/encoding/base64/base64_test.ww b/lib/encoding/base64/base64_test.ww index 61fb0ebb..76131958 100644 --- a/lib/encoding/base64/base64_test.ww +++ b/lib/encoding/base64/base64_test.ww @@ -9,7 +9,7 @@ // The streaming decoder (newdecoder) is deferred (#247-sibling), so the // decode side is exercised through decodestr only. -package base64; +package base64_test; import base64; import bytes; diff --git a/lib/encoding/hex/hextest.ww b/lib/encoding/hex/hextest.ww index d8142089..b3af9fff 100644 --- a/lib/encoding/hex/hextest.ww +++ b/lib/encoding/hex/hextest.ww @@ -7,7 +7,7 @@ // The streaming decoder (newdecoder) is deferred (#247), so the decode // side is exercised through decodestr only. -package hex; +package hex_test; import bytes; import errors; diff --git a/lib/errors/errnotest.ww b/lib/errors/errnotest.ww index 8941a5ad..9c242951 100644 --- a/lib/errors/errnotest.ww +++ b/lib/errors/errnotest.ww @@ -6,7 +6,7 @@ // store gap (task #6). [[errtag]] collapses each named-void variant to // a small int so the row body is a single integer compare. -package errors; +package errors_test; import errors; import os; diff --git a/lib/fmt/fmttest.ww b/lib/fmt/fmttest.ww index 0a03037b..eeba098c 100644 --- a/lib/fmt/fmttest.ww +++ b/lib/fmt/fmttest.ww @@ -7,7 +7,7 @@ // here threads a different variadic argument-pack into fprint, and the // variadic shape can't be table-driven within a single fn body. -package fmt; +package fmt_test; import fmt; import io; diff --git a/lib/fnmatch/fnmatchtest.ww b/lib/fnmatch/fnmatchtest.ww index 5b98f299..4df0fa33 100644 --- a/lib/fnmatch/fnmatchtest.ww +++ b/lib/fnmatch/fnmatchtest.ww @@ -13,7 +13,7 @@ // reports `WEXITSTATUS = 11..N` pointing at the failing scenario. // Same convention as lib/log/logtest. -package fnmatch; +package fnmatch_test; import fnmatch; diff --git a/lib/getopt/getopttest.ww b/lib/getopt/getopttest.ww index 0c7ed5d4..ed33058c 100644 --- a/lib/getopt/getopttest.ww +++ b/lib/getopt/getopttest.ww @@ -19,7 +19,7 @@ // per task #15 — but the nested-if shape was the original workaround // and is preserved here as a regression marker. -package getopt; +package getopt_test; import getopt; import io; diff --git a/lib/hash/adler32/adler32_test.ww b/lib/hash/adler32/adler32_test.ww index 9dc2bbde..2b7497a8 100644 --- a/lib/hash/adler32/adler32_test.ww +++ b/lib/hash/adler32/adler32_test.ww @@ -1,4 +1,4 @@ -package adler32; +package adler32_test; import adler32; diff --git a/lib/hash/crc16/crc16_test.ww b/lib/hash/crc16/crc16_test.ww index 45538fcc..82c9cd08 100644 --- a/lib/hash/crc16/crc16_test.ww +++ b/lib/hash/crc16/crc16_test.ww @@ -1,4 +1,4 @@ -package crc16; +package crc16_test; import crc16; diff --git a/lib/hash/crc32/crc32_test.ww b/lib/hash/crc32/crc32_test.ww index 3695c7e9..5690d089 100644 --- a/lib/hash/crc32/crc32_test.ww +++ b/lib/hash/crc32/crc32_test.ww @@ -1,4 +1,4 @@ -package crc32; +package crc32_test; import crc32; diff --git a/lib/hash/crc64/crc64_test.ww b/lib/hash/crc64/crc64_test.ww index 406570b8..1fe45f1a 100644 --- a/lib/hash/crc64/crc64_test.ww +++ b/lib/hash/crc64/crc64_test.ww @@ -1,4 +1,4 @@ -package crc64; +package crc64_test; import crc64; diff --git a/lib/hash/siphash/siphash_test.ww b/lib/hash/siphash/siphash_test.ww index e32f4a32..bc02e5db 100644 --- a/lib/hash/siphash/siphash_test.ww +++ b/lib/hash/siphash/siphash_test.ww @@ -1,4 +1,4 @@ -package siphash; +package siphash_test; import siphash; diff --git a/lib/log/logtest.ww b/lib/log/logtest.ww index 1edcdc5f..3b5aeb83 100644 --- a/lib/log/logtest.ww +++ b/lib/log/logtest.ww @@ -9,7 +9,7 @@ // killing the test driver — left as a TODO until the project grows // a subprocess fixture. -package log; +package log_test; import fmt; import io; diff --git a/lib/math/random/random_test.ww b/lib/math/random/random_test.ww index ffeb7f11..4ced1f1f 100644 --- a/lib/math/random/random_test.ww +++ b/lib/math/random/random_test.ww @@ -1,4 +1,4 @@ -package random; +package random_test; import random; diff --git a/lib/memio/memiotest.ww b/lib/memio/memiotest.ww index 5b8073f5..e94058f5 100644 --- a/lib/memio/memiotest.ww +++ b/lib/memio/memiotest.ww @@ -7,7 +7,7 @@ // value-return surface (`let st = memio.fixed(buf); &st.vt` into the // io dispatchers; io.read/write/close return size/io.error). -package memio; +package memio_test; import bytes; import errors; diff --git a/lib/os/ostest.ww b/lib/os/ostest.ww index e3e15220..3bed7ce4 100644 --- a/lib/os/ostest.ww +++ b/lib/os/ostest.ww @@ -23,7 +23,7 @@ // concat (task #17). os alone is sufficient: getenv + exit are the // only primitives needed. -package os; +package os_test; import os; import rt; diff --git a/lib/os/stattest.ww b/lib/os/stattest.ww index 65a59b04..c0e01fcb 100644 --- a/lib/os/stattest.ww +++ b/lib/os/stattest.ww @@ -17,7 +17,7 @@ // + 10` tells the harness which row tripped. Same shape as // ostest / temptest / shlextest. -package os; +package os_test; import os; diff --git a/lib/path/pathtest.ww b/lib/path/pathtest.ww index 53565a30..262f55d6 100644 --- a/lib/path/pathtest.ww +++ b/lib/path/pathtest.ww @@ -13,7 +13,7 @@ // the table holds {segment, expected-string} pairs (stack.ha:36-42 is // the normalization spec these rows encode). -package path; +package path_test; import path; import os; diff --git a/lib/regex/regex_test.ww b/lib/regex/regex_test.ww index bd9651b3..4b617c63 100644 --- a/lib/regex/regex_test.ww +++ b/lib/regex/regex_test.ww @@ -27,7 +27,7 @@ // not `regex.inst_charset { … }`): the parser rejects a module-qualified // name in struct-literal position (#29), and the imported type // is in scope unqualified. -package regex; +package regex_test; import regex; import io; @@ -167,6 +167,8 @@ fn fail() void = { os.exit(signalled + 10); }; regex.finish(&re); }; +// rule-7 divergence: white-box probes of regex internals; mislabeled _test pending task #9 (in-pkg test harness via #5/-T dir-enum, #8 boundary). + // compile("abc") emits the 5-inst literal program: the leading // unanchored inst_skip (regex.ha:261-263), one inst_lit per rune, the // epilogue inst_match(false) (ha:475-477). compile()'s diff --git a/lib/shlex/shlextest.ww b/lib/shlex/shlextest.ww index 36fc2501..24adbe4e 100644 --- a/lib/shlex/shlextest.ww +++ b/lib/shlex/shlextest.ww @@ -20,7 +20,7 @@ // reports `WEXITSTATUS = 11..N` pointing at the failing scenario. // Same convention as fnmatchtest / logtest. -package shlex; +package shlex_test; import shlex; import io; diff --git a/lib/strconv/test/decimaltest.ww b/lib/strconv/test/decimaltest.ww deleted file mode 100644 index 5c0b7e41..00000000 --- a/lib/strconv/test/decimaltest.ww +++ /dev/null @@ -1,212 +0,0 @@ -// decimaltest — exercises lib/strconv/decimal.ww (Hare decimal.ha port). -// Run with `out/bin/ww run lib/strconv/test/decimaltest.ww`. Same -// signalled-then-fail()-with-+10 pattern as bytestest / hex / utf8. -// Non-zero exit pinpoints the failing scenario. -// -// Hare has no decimal_test.ha (decimal is exercised transitively via -// ftos_test.ha). ww-authored probes per -// feedback_test_match_hare_source ("ww-authored where Hare doesn't -// ship direct tests"). Coverage per Drew design pre-flight: -// trim (RHS-zeros), decimal_shift (k=0/k>0/k<0/|k|>maxshift), -// leftshift (small/edge), rightshift (small/break-out), round (at -// various dp), decimal_round (small + dp>18 + dp<0). -// -// Lives in lib/strconv/test/ (not lib/strconv/) so `import strconv` -// resolves to the lib/strconv DIRECTORY rather than shadowing on -// the strconv.ww FILE — pulls in decimal.ww + stof_data.ww + -// strconv.ww as the unified `package strconv` compilation. - -package strconv; - -import strconv; -import os; - -let signalled: i32 = 0; -fn fail() void = { os.exit(signalled + 10); }; - -// Seeds `d` with `vals` as the leading digits + `dp` as the decimal -// point, leaving negative/truncated false. Each test starts from a -// known state. -fn seed(d: *decimal, vals: []u8, dp: i32) void = { - let SZ_ONE: size = (1u64: size); - let i: size = (0u64: size); - for (i < (vals.len: size)) { - d.digits[i] = vals[i]; - i += SZ_ONE; - }; - d.nd = (vals.len: size); - d.dp = dp; -}; - -// ---- trim ------------------------------------------------------------- -// ref/hare/strconv/decimal.ha:29. - -@test fn trim_cases() void = { - let d: decimal; - let vals: [5]u8; - vals[0] = 1u8; vals[1] = 2u8; vals[2] = 3u8; vals[3] = 0u8; vals[4] = 0u8; - seed(&d, vals[0:5], 3); - trim(&d); - if (d.nd != (3u64: size)) { fail(); }; - if (d.dp != 3) { fail(); }; - - // all-zero case: nd shrinks to 0. - let zeros: [3]u8; - seed(&d, zeros[0:3], 2); - trim(&d); - if (d.nd != (0u64: size)) { fail(); }; -}; - -// ---- leftshift / leftshift_newdigits --------------------------------- -// ref/hare/strconv/decimal.ha:35,57. - -@test fn leftshift_small_cases() void = { - // Shift "5" by 1 → "10" → trim collapses to nd=1 with dp+=1 (Hare - // trims trailing zero of the high-shifted byte). - let d: decimal; - d.digits[0] = 5u8; - d.nd = (1u64: size); - d.dp = 1; - leftshift(&d, 1u32); - if (d.dp != 2) { fail(); }; - if (d.digits[0] != 1u8) { fail(); }; - // post-trim nd is 1 (trailing 0 stripped). - if (d.nd != (1u64: size)) { fail(); }; - - // Shift "123" by 1 → "246" (no trim — no trailing zero). - let v3: [3]u8; - v3[0] = 1u8; v3[1] = 2u8; v3[2] = 3u8; - seed(&d, v3[0:3], 3); - leftshift(&d, 1u32); - if (d.nd != (3u64: size)) { fail(); }; - if (d.dp != 3) { fail(); }; - if (d.digits[0] != 2u8) { fail(); }; - if (d.digits[1] != 4u8) { fail(); }; - if (d.digits[2] != 6u8) { fail(); }; - - // nd == 0 no-op (early-return arm of leftshift). - let z: decimal; - leftshift(&z, 1u32); - if (z.nd != (0u64: size)) { fail(); }; -}; - -// ---- rightshift ----------------------------------------------------- -// ref/hare/strconv/decimal.ha:93. - -@test fn rightshift_small_cases() void = { - // Shift "10" (dp=2) right by 1 → "5" (dp=1). - let d: decimal; - let vals: [2]u8; - vals[0] = 1u8; vals[1] = 0u8; - seed(&d, vals[0:2], 2); - rightshift(&d, 1u32); - if (d.nd != (1u64: size)) { fail(); }; - if (d.dp != 1) { fail(); }; - if (d.digits[0] != 5u8) { fail(); }; -}; - -// ---- decimal_shift -------------------------------------------------- -// ref/hare/strconv/decimal.ha:138. - -@test fn decimal_shift_cases() void = { - // k=0 no-op. - let d: decimal; - let v3: [3]u8; v3[0] = 1u8; v3[1] = 2u8; v3[2] = 3u8; - seed(&d, v3[0:3], 3); - decimal_shift(&d, 0); - if (d.nd != (3u64: size)) { fail(); }; - if (d.dp != 3) { fail(); }; - if (d.digits[0] != 1u8) { fail(); }; - - // k=2 small positive: 123 << 2 = 492 → digits 4,9,2; dp=3. - seed(&d, v3[0:3], 3); - decimal_shift(&d, 2); - if (d.dp != 3) { fail(); }; - if (d.digits[0] != 4u8) { fail(); }; - if (d.digits[1] != 9u8) { fail(); }; - if (d.digits[2] != 2u8) { fail(); }; - - // k>maxshift triggers the break-up loop. Exact digits are checked - // transitively by stof in fold-4; verify dp grew. - let v: [3]u8; v[0] = 1u8; v[1] = 0u8; v[2] = 0u8; - seed(&d, v[0:3], 3); - decimal_shift(&d, 70); - if (d.dp <= 3) { fail(); }; -}; - -// ---- should_round_up + round + decimal_round ------------------------ -// ref/hare/strconv/decimal.ha:155,162,188. - -@test fn round_cases() void = { - // rounddown — "1234" rounded to 2 digits → "12". - let d: decimal; - let v: [4]u8; v[0] = 1u8; v[1] = 2u8; v[2] = 3u8; v[3] = 4u8; - seed(&d, v[0:4], 4); - round(&d, (2u32: uint)); - if (d.nd != (2u64: size)) { fail(); }; - if (d.digits[0] != 1u8) { fail(); }; - if (d.digits[1] != 2u8) { fail(); }; - - // roundup — "1259" rounded to 2 digits → "13" (digit[2]==5 → - // should_round_up, digit[1]=2 → +1 → digit[1]=3). - let r: [4]u8; r[0] = 1u8; r[1] = 2u8; r[2] = 5u8; r[3] = 9u8; - seed(&d, r[0:4], 4); - round(&d, (2u32: uint)); - if (d.nd != (2u64: size)) { fail(); }; - if (d.digits[0] != 1u8) { fail(); }; - if (d.digits[1] != 3u8) { fail(); }; - - // roundup with all-9s carry — "999" rounded to 2 digits → "1" - // with dp incremented. - let nines: [3]u8; nines[0] = 9u8; nines[1] = 9u8; nines[2] = 9u8; - seed(&d, nines[0:3], 3); - round(&d, (2u32: uint)); - if (d.digits[0] != 1u8) { fail(); }; - if (d.nd != (1u64: size)) { fail(); }; - if (d.dp != 4) { fail(); }; -}; - -@test fn decimal_round_cases() void = { - // dp<0 → 0. - let d: decimal; - let v: [3]u8; v[0] = 1u8; v[1] = 2u8; v[2] = 3u8; - seed(&d, v[0:3], -1); - if (decimal_round(&d) != 0u64) { fail(); }; - - // nd==0 → 0. - let z: decimal; - z.dp = 5; - if (decimal_round(&z) != 0u64) { fail(); }; - - // dp>18 → ~0u64 (all-bits-set). - let big: decimal; - let one: [1]u8; one[0] = 1u8; - seed(&big, one[0:1], 19); - let zero_u: u64 = 0u64; - if (decimal_round(&big) != ~zero_u) { fail(); }; - - // "123" with dp=3 → 123. - seed(&d, v[0:3], 3); - if (decimal_round(&d) != 123u64) { fail(); }; - - // "12" with dp=4 → 1200 (multiply by 10 for each missing digit). - let two: [2]u8; two[0] = 1u8; two[1] = 2u8; - seed(&d, two[0:2], 4); - if (decimal_round(&d) != 1200u64) { fail(); }; - - // "126" with dp=2 → 13 (rounded up via should_round_up at dp=2: - // digit[2]==6 >= 5). - let r: [3]u8; r[0] = 1u8; r[1] = 2u8; r[2] = 6u8; - seed(&d, r[0:3], 2); - if (decimal_round(&d) != 13u64) { fail(); }; -}; - -export fn main() i32 = { - signalled = 1; trim_cases(); - signalled = 2; leftshift_small_cases(); - signalled = 3; rightshift_small_cases(); - signalled = 4; decimal_shift_cases(); - signalled = 5; round_cases(); - signalled = 6; decimal_round_cases(); - return 0; -}; diff --git a/lib/strconv/test/ftostest.ww b/lib/strconv/test/ftostest.ww index a89c9f86..f7369f6b 100644 --- a/lib/strconv/test/ftostest.ww +++ b/lib/strconv/test/ftostest.ww @@ -20,7 +20,7 @@ // DIRECTORY (full package), not the strconv.ww FILE — same rationale as // stoftest/decimaltest. -package strconv; +package strconv_test; import strconv; import os; @@ -43,6 +43,17 @@ fn chk(n: f64, want: str) bool = { return streq(f64tos(n), want); }; +// rt64 — bit-exact round-trip: render then parse back must reproduce the +// exact bits. The parse-back re-runs strconv's decimal slow path. +fn rt64(x: f64) bool = { + match (stof64(f64tos(x), base.DEC)) { + case let v: f64 => { return math.f64bits(v) == math.f64bits(x); }; + case let e: invalid => { return false; }; + case let e: overflow => { return false; }; + }; + return false; +}; + // ftos_test.ha:57-64 (G/void/NONE-equivalent) — fixed-point renders. @test fn f64tos_fixed() void = { if (!chk(13.37, "13.37")) { fail(); }; @@ -151,6 +162,21 @@ fn chkf32(n: f32, want: str) bool = { if (!chkf32(nan, "nan")) { fail(); }; }; +// ftos_test.ha:196-204 (tcsf64) — Hare's f64-exclusive extremes, here as a +// bit-exact ROUND-TRIP (f64bits(stof64(f64tos(x)))==f64bits(x)): the parse- +// back re-runs strconv's decimal engine end-to-end, preserving the coverage +// the retired white-box decimaltest.ww gave directly (#16). Extremes built +// via f64frombits (ww math has no F64_MIN_*/MAX_NORMAL const); exact and +// 17-digit-halfway values as literals. +@test fn f64_roundtrip() void = { + if (!rt64(9007199254740991.0)) { fail(); }; // 2^53-1 + if (!rt64(90071992547409915.0)) { fail(); }; // 17-digit halfway pair + if (!rt64(90071992547409925.0)) { fail(); }; + if (!rt64(math.f64frombits(0x0000000000000001u64))) { fail(); }; // F64_MIN_SUBNORMAL (5e-324) + if (!rt64(math.f64frombits(0x0010000000000000u64))) { fail(); }; // F64_MIN_NORMAL + if (!rt64(math.f64frombits(0x7FEFFFFFFFFFFFFFu64))) { fail(); }; // F64_MAX_NORMAL +}; + export fn main() i32 = { signalled = 1; f64tos_fixed(); signalled = 2; f64tos_scientific(); @@ -159,6 +185,7 @@ export fn main() i32 = { signalled = 5; f32tos_scientific(); signalled = 6; f32tos_extremes(); signalled = 7; f32tos_special(); + signalled = 8; f64_roundtrip(); os.exit(0); return 0; }; diff --git a/lib/strconv/test/inttest.ww b/lib/strconv/test/inttest.ww index 7bbc0133..24d5cb92 100644 --- a/lib/strconv/test/inttest.ww +++ b/lib/strconv/test/inttest.ww @@ -19,7 +19,7 @@ // literal form for the expectation side); the original Hare radix form // is noted inline. -package strconv; +package strconv_test; import strconv; import os; diff --git a/lib/strconv/test/stoftest.ww b/lib/strconv/test/stoftest.ww index 93e47670..fb579fa0 100644 --- a/lib/strconv/test/stoftest.ww +++ b/lib/strconv/test/stoftest.ww @@ -15,7 +15,7 @@ // DIRECTORY (full package: strconv.ww + decimal.ww + stof_data.ww + // stof.ww), not the strconv.ww FILE — same rationale as decimaltest. -package strconv; +package strconv_test; import strconv; import os; @@ -123,6 +123,18 @@ fn nan32(s: str) bool = { if (!chk64("-1e-324", base.DEC, nzero)) { fail(); }; if (!chk64("5e-324", base.DEC, subn)) { fail(); }; + // Decimal-engine edge probes migrated here when the white-box + // decimaltest.ww retired (#16): the external-test idiom can't reach + // strconv's unexported decimal fns, so these two black-box rows pin + // the only edges not already guaranteed transitively. C1: all-9s + // round-up carry + decimal_round dp>18 boundary. C2: nd>19 skips + // eisel-lemire, forcing the pure-decimal slow path (extra trim). + // Micro-gap (rule-7, honest): decimal_shift's k=0 early-return no-op + // is a trivial guard, not reached transitively by any slow-path input + // — consciously un-migrated, not silently dropped. + if (!chk64("9999999999999999", base.DEC, 1.0e16)) { fail(); }; + if (!chk64("100000000000000000001", base.DEC, 1.0e20)) { fail(); }; + if (inv64("") != 0) { fail(); }; if (inv64("0ZO") != 1) { fail(); }; if (inv64("1.23ezz") != 5) { fail(); }; diff --git a/lib/strings/stringstest.ww b/lib/strings/stringstest.ww index f3e97946..bb98bdb1 100644 --- a/lib/strings/stringstest.ww +++ b/lib/strings/stringstest.ww @@ -6,7 +6,11 @@ // Vectors mirror ref/hare/strings/{dup,concat,trim,contains,index, // suffix,compare}.ha where ww can express them. -package strings; +// `_test` suffix: Go external-test-package idiom (sanctioned Go-over-Hare +// departure for lib/ tests) — self-import is hard-rejected, so the test +// lives in package strings_test and pulls the real package via import. +// See CLAUDE.md rule-5/9 carve-out; task #16. +package strings_test; import strings; import encoding.utf8; diff --git a/lib/temp/temptest.ww b/lib/temp/temptest.ww index e0970994..b6ee02c9 100644 --- a/lib/temp/temptest.ww +++ b/lib/temp/temptest.ww @@ -13,7 +13,7 @@ // leave detritus under /tmp. `ls /tmp` before/after each run should // match. -package temp; +package temp_test; import os; import temp; diff --git a/lib/time/timetest.ww b/lib/time/timetest.ww index 90588f0f..638f6baa 100644 --- a/lib/time/timetest.ww +++ b/lib/time/timetest.ww @@ -3,7 +3,7 @@ // pattern as fmttest / logtest / stattest so a non-zero exit // pinpoints the offending scenario. -package time; +package time_test; import os; import time; diff --git a/test/wcc/922_decimal_run.c b/test/wcc/922_decimal_run.c deleted file mode 100644 index b47a762a..00000000 --- a/test/wcc/922_decimal_run.c +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 922_decimal_run — execute the lib/strconv/test/decimaltest fixture - * under the C-side `ww run` driver and assert exit 0. - * - * Same thin-wrapper shape as 967_bytes_run / 968_utf8_run / 979_hex_run: - * decimaltest.ww carries its own `export fn main()` that drives the - * @test fns and signals which case failed via the exit code (signalled - * + 10, so cases 1..6 → exit 11..16). - * - * The test fixture lives in lib/strconv/test/ (not lib/strconv/) so - * `import strconv` resolves to the lib/strconv DIRECTORY rather than - * shadowing on the strconv.ww FILE — same-dir resolution would pull - * in only strconv.ww and miss decimal.ww + stof_data.ww. From the - * test/ subdir, the search-path lookup falls through srcd and reaches - * the libdir's directory hit, expanding the full strconv package. - */ -#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/strconv/test/decimaltest.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, "decimal_run FAIL: %s exited %d\n", src, rc); - return 1; - } - printf("decimal_run: %s ok\n", src); - return 0; -} diff --git a/test/wcc/989_lib_byteid.c b/test/wcc/989_lib_byteid.c index 9508c46f..cc513548 100644 --- a/test/wcc/989_lib_byteid.c +++ b/test/wcc/989_lib_byteid.c @@ -85,7 +85,6 @@ static const struct ent ents[] = { { .fixture = "lib/memio/memiotest.ww", .mode = M_ID }, { .fixture = "lib/os/ostest.ww", .mode = M_ID }, { .fixture = "lib/regex/regex_test.ww", .mode = M_ID }, - { .fixture = "lib/strconv/test/decimaltest.ww", .mode = M_ID }, { .fixture = "lib/strconv/test/ftostest.ww", .mode = M_ID }, /* graduated from #59.10 DIVERGE by the #62 float-literal fold fix * (wwstage lexer now folds through strconv.stof64, matching