// fmttest — exercises lib/fmt's stream sinks (fprint / fprintln). // Run with `out/bin/ww run lib/fmt/fmttest.ww`. // // Each @test writes through a memio.stream and compares the resulting // bytes against an inline `want` literal. Bodies are short enough that // the parallel-array idiom used by memiotest doesn't apply — every row // here threads a different variadic argument-pack into fprint, and the // variadic shape can't be table-driven within a single fn body. use fmt; use io; use memio; use os; // signalled — bumped before each scenario so a failing exit code // pinpoints the offending case. let signalled: i32 = 0; fn fail() void = { os.exit(signalled + 10); }; fn streq(a: str, b: str) 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; }; // ---- A write-always-closed stream for the io.closed surfacing test ---- fn closedread(s: *io.stream, buf: []u8) (i32 | io.eof | io.closed) = { let e: io.closed; return e; }; fn closedwrite(s: *io.stream, buf: []u8) (i32 | io.closed) = { let e: io.closed; return e; }; fn closedclose(s: *io.stream) (void | io.closed) = { return; }; fn closedstream(s: *io.stream) void = { s.ctx = nil; s.read = closedread; s.write = closedwrite; s.close = closedclose; }; // ---- fprint: bare str -------------------------------------------------- @test fn fprintbarestr() void = { let mem: memio.state; let s: io.stream; memio.dynamic(&mem, &s); let r: (i32 | io.closed) = fmt.fprint(&s, "hello"); match (r) { case let n: i32 => { if (n != 5) { fail(); }; }; case io.closed => fail(); }; if (!streq(memio.string(&mem), "hello")) { fail(); }; let c: (void | io.closed) = io.close(&s); match (c) { case void => {}; case io.closed => fail(); }; }; // ---- fprint: int + str mix, space-separated ---------------------------- @test fn fprintintstr() void = { let mem: memio.state; let s: io.stream; memio.dynamic(&mem, &s); let r: (i32 | io.closed) = fmt.fprint(&s, 42i64, "x"); match (r) { case let n: i32 => { if (n != 4) { fail(); }; }; // "42 x" case io.closed => fail(); }; if (!streq(memio.string(&mem), "42 x")) { fail(); }; let c: (void | io.closed) = io.close(&s); match (c) { case void => {}; case io.closed => fail(); }; }; // ---- fprint: bool + rune renders as "true A" -------------------------- @test fn fprintboolrune() void = { let mem: memio.state; let s: io.stream; memio.dynamic(&mem, &s); let r: (i32 | io.closed) = fmt.fprint(&s, true, 'A': rune); match (r) { case let n: i32 => { if (n != 6) { fail(); }; }; // "true A" case io.closed => fail(); }; if (!streq(memio.string(&mem), "true A")) { fail(); }; let c: (void | io.closed) = io.close(&s); match (c) { case void => {}; case io.closed => fail(); }; }; // ---- fprint: zero args returns 0, no bytes written -------------------- @test fn fprintempty() void = { let mem: memio.state; let s: io.stream; memio.dynamic(&mem, &s); let r: (i32 | io.closed) = fmt.fprint(&s); match (r) { case let n: i32 => { if (n != 0) { fail(); }; }; case io.closed => fail(); }; if (memio.string(&mem).len != 0) { fail(); }; let c: (void | io.closed) = io.close(&s); match (c) { case void => {}; case io.closed => fail(); }; }; // ---- fprintln: multi-arg, trailing '\n' -------------------------------- @test fn fprintlnmulti() void = { let mem: memio.state; let s: io.stream; memio.dynamic(&mem, &s); let r: (i32 | io.closed) = fmt.fprintln(&s, "a", 1i64, false); match (r) { case let n: i32 => { if (n != 10) { fail(); }; }; // "a 1 false\n" case io.closed => fail(); }; if (!streq(memio.string(&mem), "a 1 false\n")) { fail(); }; let c: (void | io.closed) = io.close(&s); match (c) { case void => {}; case io.closed => fail(); }; }; // ---- fprintln: zero args writes just the newline ---------------------- @test fn fprintlnempty() void = { let mem: memio.state; let s: io.stream; memio.dynamic(&mem, &s); let r: (i32 | io.closed) = fmt.fprintln(&s); match (r) { case let n: i32 => { if (n != 1) { fail(); }; }; case io.closed => fail(); }; if (!streq(memio.string(&mem), "\n")) { fail(); }; let c: (void | io.closed) = io.close(&s); match (c) { case void => {}; case io.closed => fail(); }; }; // ---- fprint over a fixed stream: short writes return partial count ---- // memio.fixedwrite caps each call at the remaining buffer space and // never closes the stream, so fprint sees a short i32 result, not // io.closed. Verifies the inner-loop arithmetic adds the actual byte // count rather than the requested length. @test fn fprintfixedshort() void = { let buf: [3]u8; let mem: memio.state; let s: io.stream; memio.fixed(&mem, &s, buf[0:3]); let r: (i32 | io.closed) = fmt.fprint(&s, "hello"); match (r) { case let n: i32 => { if (n != 3) { fail(); }; }; case io.closed => fail(); }; if (!streq(memio.string(&mem), "hel")) { fail(); }; let c: (void | io.closed) = io.close(&s); match (c) { case void => {}; case io.closed => fail(); }; }; // ---- fprint over a closed stream surfaces io.closed ------------------- // Exercises the early-return arm in fprint's inner match — distinct from // the short-write path above, which keeps returning i32 from a partial // accept. Single arm is enough: every formattable case routes errors // through the same putbytes / io.write wiring. @test fn fprintclosed() void = { let s: io.stream; closedstream(&s); let r: (i32 | io.closed) = fmt.fprint(&s, "x"); match (r) { case let n: i32 => fail(); case io.closed => {}; }; }; // ---- fprintf scenarios ------------------------------------------------- // Each scenario writes through memio.dynamic and compares bytes against // an inline `want`. Variadic call-site shape forces one body per shape; // see the file header. @test fn fprintf_implicit() void = { let mem: memio.state; let s: io.stream; memio.dynamic(&mem, &s); let r: (i32 | io.closed) = fmt.fprintf(&s, "hello {} {}", "world", 42i64); match (r) { case let n: i32 => { if (n != 14) { fail(); }; }; case io.closed => fail(); }; if (!streq(memio.string(&mem), "hello world 42")) { fail(); }; let c: (void | io.closed) = io.close(&s); match (c) { case void => {}; case io.closed => fail(); }; }; @test fn fprintf_indexed() void = { let mem: memio.state; let s: io.stream; memio.dynamic(&mem, &s); let r: (i32 | io.closed) = fmt.fprintf(&s, "{1} {0} {1}", "hello", "world"); match (r) { case let n: i32 => { if (n != 17) { fail(); }; }; case io.closed => fail(); }; if (!streq(memio.string(&mem), "world hello world")) { fail(); }; let c: (void | io.closed) = io.close(&s); match (c) { case void => {}; case io.closed => fail(); }; }; @test fn fprintf_literal_braces() void = { let mem: memio.state; let s: io.stream; memio.dynamic(&mem, &s); let r: (i32 | io.closed) = fmt.fprintf(&s, "{{ {} }}", 1i64); match (r) { case let n: i32 => { if (n != 5) { fail(); }; }; case io.closed => fail(); }; if (!streq(memio.string(&mem), "{ 1 }")) { fail(); }; let c: (void | io.closed) = io.close(&s); match (c) { case void => {}; case io.closed => fail(); }; }; @test fn fprintf_width_right() void = { let mem: memio.state; let s: io.stream; memio.dynamic(&mem, &s); let r: (i32 | io.closed) = fmt.fprintf(&s, "{:5}", 42i64); match (r) { case let n: i32 => { if (n != 5) { fail(); }; }; case io.closed => fail(); }; if (!streq(memio.string(&mem), " 42")) { fail(); }; let c: (void | io.closed) = io.close(&s); match (c) { case void => {}; case io.closed => fail(); }; }; @test fn fprintf_width_left() void = { let mem: memio.state; let s: io.stream; memio.dynamic(&mem, &s); let r: (i32 | io.closed) = fmt.fprintf(&s, "{:-5}", 42i64); match (r) { case let n: i32 => { if (n != 5) { fail(); }; }; case io.closed => fail(); }; if (!streq(memio.string(&mem), "42 ")) { fail(); }; let c: (void | io.closed) = io.close(&s); match (c) { case void => {}; case io.closed => fail(); }; }; @test fn fprintf_width_center() void = { let mem: memio.state; let s: io.stream; memio.dynamic(&mem, &s); let r: (i32 | io.closed) = fmt.fprintf(&s, "{:=5}", "hi"); match (r) { case let n: i32 => { if (n != 5) { fail(); }; }; case io.closed => fail(); }; if (!streq(memio.string(&mem), " hi ")) { fail(); }; let c: (void | io.closed) = io.close(&s); match (c) { case void => {}; case io.closed => fail(); }; }; @test fn fprintf_pad_underscore() void = { let mem: memio.state; let s: io.stream; memio.dynamic(&mem, &s); let r: (i32 | io.closed) = fmt.fprintf(&s, "{:_05}", "hi"); match (r) { case let n: i32 => { if (n != 5) { fail(); }; }; case io.closed => fail(); }; if (!streq(memio.string(&mem), "000hi")) { fail(); }; let c: (void | io.closed) = io.close(&s); match (c) { case void => {}; case io.closed => fail(); }; }; @test fn fprintf_base_hex() void = { let mem: memio.state; let s: io.stream; memio.dynamic(&mem, &s); let r: (i32 | io.closed) = fmt.fprintf(&s, "{:x} {:X}", 48879i64, 61453i64); match (r) { case let n: i32 => { if (n != 9) { fail(); }; }; case io.closed => fail(); }; if (!streq(memio.string(&mem), "beef F00D")) { fail(); }; let c: (void | io.closed) = io.close(&s); match (c) { case void => {}; case io.closed => fail(); }; }; @test fn fprintf_base_oct_bin() void = { let mem: memio.state; let s: io.stream; memio.dynamic(&mem, &s); let r: (i32 | io.closed) = fmt.fprintf(&s, "{:o} {:b}", 493i64, 27i64); match (r) { case let n: i32 => { if (n != 9) { fail(); }; }; // "755 11011" case io.closed => fail(); }; if (!streq(memio.string(&mem), "755 11011")) { fail(); }; let c: (void | io.closed) = io.close(&s); match (c) { case void => {}; case io.closed => fail(); }; }; @test fn fprintf_prec_int() void = { let mem: memio.state; let s: io.stream; memio.dynamic(&mem, &s); let r: (i32 | io.closed) = fmt.fprintf(&s, "{:.5}", 42i64); match (r) { case let n: i32 => { if (n != 5) { fail(); }; }; case io.closed => fail(); }; if (!streq(memio.string(&mem), "00042")) { fail(); }; let c: (void | io.closed) = io.close(&s); match (c) { case void => {}; case io.closed => fail(); }; }; @test fn fprintf_prec_str_trunc() void = { let mem: memio.state; let s: io.stream; memio.dynamic(&mem, &s); let r: (i32 | io.closed) = fmt.fprintf(&s, "{:.3}", "hello"); match (r) { case let n: i32 => { if (n != 3) { fail(); }; }; case io.closed => fail(); }; if (!streq(memio.string(&mem), "hel")) { fail(); }; let c: (void | io.closed) = io.close(&s); match (c) { case void => {}; case io.closed => fail(); }; }; @test fn fprintf_sign_neg() void = { let mem: memio.state; let s: io.stream; memio.dynamic(&mem, &s); let r: (i32 | io.closed) = fmt.fprintf(&s, "{} {:+} {: }", -7i64, 7i64, 7i64); match (r) { case let n: i32 => { if (n != 8) { fail(); }; }; // "-7 +7 7" case io.closed => fail(); }; if (!streq(memio.string(&mem), "-7 +7 7")) { fail(); }; let c: (void | io.closed) = io.close(&s); match (c) { case void => {}; case io.closed => fail(); }; }; @test fn fprintfln_basic() void = { let mem: memio.state; let s: io.stream; memio.dynamic(&mem, &s); let r: (i32 | io.closed) = fmt.fprintfln(&s, "x={}", 9i64); match (r) { case let n: i32 => { if (n != 4) { fail(); }; }; // "x=9\n" case io.closed => fail(); }; if (!streq(memio.string(&mem), "x=9\n")) { fail(); }; let c: (void | io.closed) = io.close(&s); match (c) { case void => {}; case io.closed => fail(); }; }; @test fn fprintf_closed() void = { let s: io.stream; closedstream(&s); let r: (i32 | io.closed) = fmt.fprintf(&s, "hello {}", 1i64); match (r) { case let n: i32 => fail(); case io.closed => {}; }; }; @test fn bsprintf_basic() void = { let buf: [16]u8; let r: (str | io.closed) = fmt.bsprintf(buf[0:16], "{} {}", "hi", 42i64); match (r) { case let s: str => { if (!streq(s, "hi 42")) { fail(); }; }; case io.closed => fail(); }; }; // Pins the formatfield bool / rune arms inside fprintf's for-loop — // the codegen-smell repro shape (task #18). Pre-task-#18, the str arm // is also exercised by fprintf_implicit; this adds the two remaining // tagged arms so a future task-#18 fix that re-introduces the 24B // widen-by-value cannot regress silently. @test fn fprintf_bool_rune() void = { let mem: memio.state; let s: io.stream; memio.dynamic(&mem, &s); let r: (i32 | io.closed) = fmt.fprintf(&s, "{} {}", true, 'A': rune); match (r) { case let n: i32 => { if (n != 6) { fail(); }; }; // "true A" case io.closed => fail(); }; if (!streq(memio.string(&mem), "true A")) { fail(); }; let c: (void | io.closed) = io.close(&s); match (c) { case void => {}; case io.closed => fail(); }; }; // Verifies bsprintf's documented truncation contract — short writes // return the prefix that fit (memio.fixedwrite returns 0 once full, // not io.closed). Pairs with fprintfixedshort above which covers the // underlying fprint path. @test fn bsprintf_trunc() void = { let buf: [3]u8; let r: (str | io.closed) = fmt.bsprintf(buf[0:3], "{} {}", "hi", 42i64); match (r) { case let s: str => { if (!streq(s, "hi ")) { fail(); }; }; case io.closed => fail(); }; }; // Pins formatone's tail-pad counter shape. Without the counter, the // `total < m.width` loop spins on memio.fixed's 0-byte-on-full // partial-write contract (ww divergence from Hare's errors::overflow // + `?`). Buffer 3B, target " 42" (5B); first 3 spaces fit, then // formatraw + tail-pad both write 0 — must terminate. @test fn bsprintf_width_trunc() void = { let buf: [3]u8; let r: (str | io.closed) = fmt.bsprintf(buf[0:3], "{:5}", 42i64); match (r) { case let s: str => { if (!streq(s, " ")) { fail(); }; }; case io.closed => fail(); }; }; export fn main() i32 = { signalled = 1; fprintbarestr(); signalled = 2; fprintintstr(); signalled = 3; fprintboolrune(); signalled = 4; fprintempty(); signalled = 5; fprintlnmulti(); signalled = 6; fprintlnempty(); signalled = 7; fprintfixedshort(); signalled = 8; fprintclosed(); signalled = 9; fprintf_implicit(); signalled = 10; fprintf_indexed(); signalled = 11; fprintf_literal_braces(); signalled = 12; fprintf_width_right(); signalled = 13; fprintf_width_left(); signalled = 14; fprintf_width_center(); signalled = 15; fprintf_pad_underscore(); signalled = 16; fprintf_base_hex(); signalled = 17; fprintf_base_oct_bin(); signalled = 18; fprintf_prec_int(); signalled = 19; fprintf_prec_str_trunc(); signalled = 20; fprintf_sign_neg(); signalled = 21; fprintfln_basic(); signalled = 22; fprintf_closed(); signalled = 23; bsprintf_basic(); signalled = 24; fprintf_bool_rune(); signalled = 25; bsprintf_trunc(); signalled = 26; bsprintf_width_trunc(); return 0; };