// 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. package fmt_test; import fmt; import io; import memio; import os; 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; }; // ---- an error-returning stream for the io.error surfacing test -------- // // Replaces the OLD io.closed source. errvt is module-static; its // reader/writer return a nomem-widened io.error. let errvt: io.vtable; fn errread(s: io.stream, buf: []u8) (size | io.eof | io.error) = { let nm: nomem; let e: io.error = nm; return e; }; fn errwrite(s: io.stream, buf: []u8) (size | io.error) = { let nm: nomem; let e: io.error = nm; return e; }; fn errsource() io.stream = { errvt.reader = (&errread): *io.reader; errvt.writer = (&errwrite): *io.writer; return &errvt; }; // ---- fprint: bare str -------------------------------------------------- @test fn fprintbarestr() void = { let mem: memio.stream = memio.dynamic(); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprint(s, "hello"); match (r) { case let n: size => { assert(!(n: i32 != 5)); }; case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), "hello"))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; // ---- fprint: int + str mix, space-separated ---------------------------- @test fn fprintintstr() void = { let mem: memio.stream = memio.dynamic(); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprint(s, 42i64, "x"); match (r) { case let n: size => { assert(!(n: i32 != 4)); }; // "42 x" case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), "42 x"))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; // ---- fprint: i64::MIN / int MIN whole-magnitude (#67) ------------------ // i64dec used `n = -n` within i64, which wraps at i64::MIN (-MIN == MIN), // so the digit loop never ran and only the bare '-' was emitted. The fix // takes the magnitude through math.absi64 into u64. These rows pin the // boundary value plus a normal negative, zero, and int MIN. @test fn fprinti64min() void = { let mem: memio.stream = memio.dynamic(); let s: io.stream = &mem.vt; let m: i64 = -9223372036854775807i64 - 1i64; let r: (size | io.error) = fmt.fprint(s, m); match (r) { case let n: size => { assert(!(n: i32 != 20)); }; case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), "-9223372036854775808"))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; @test fn fprintintmin() void = { let mem: memio.stream = memio.dynamic(); let s: io.stream = &mem.vt; let m: int = -9223372036854775807 - 1; let r: (size | io.error) = fmt.fprint(s, m, -7i64, 0i64); match (r) { case let n: size => { assert(!(n: i32 != 25)); }; // "-9223372036854775808 -7 0" case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), "-9223372036854775808 -7 0"))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; // ---- fprint: bool + rune renders as "true A" -------------------------- @test fn fprintboolrune() void = { let mem: memio.stream = memio.dynamic(); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprint(s, true, 'A': rune); match (r) { case let n: size => { assert(!(n: i32 != 6)); }; // "true A" case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), "true A"))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; // ---- fprint: zero args returns 0, no bytes written -------------------- @test fn fprintempty() void = { let mem: memio.stream = memio.dynamic(); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprint(s); match (r) { case let n: size => { assert(!(n: i32 != 0)); }; case let eioe: io.error => abort(); }; assert(!(memio.string(&mem).len != 0)); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; // ---- fprintln: multi-arg, trailing '\n' -------------------------------- @test fn fprintlnmulti() void = { let mem: memio.stream = memio.dynamic(); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprintln(s, "a", 1i64, false); match (r) { case let n: size => { assert(!(n: i32 != 10)); }; // "a 1 false\n" case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), "a 1 false\n"))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; // ---- fprintln: zero args writes just the newline ---------------------- @test fn fprintlnempty() void = { let mem: memio.stream = memio.dynamic(); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprintln(s); match (r) { case let n: size => { assert(!(n: i32 != 1)); }; case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), "\n"))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; // ---- 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.error. 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.stream = memio.fixed(buf[0:3]); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprint(s, "hello"); match (r) { case let n: size => { assert(!(n: i32 != 3)); }; case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), "hel"))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; // ---- fprint over a closed stream surfaces io.error ------------------- // 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 = errsource(); let r: (size | io.error) = fmt.fprint(s, "x"); match (r) { case let n: size => abort(); case let eioe: io.error => {}; }; }; // ---- 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.stream = memio.dynamic(); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprintf(s, "hello {} {}", "world", 42i64); match (r) { case let n: size => { assert(!(n: i32 != 14)); }; case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), "hello world 42"))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; @test fn fprintf_indexed() void = { let mem: memio.stream = memio.dynamic(); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprintf(s, "{1} {0} {1}", "hello", "world"); match (r) { case let n: size => { assert(!(n: i32 != 17)); }; case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), "world hello world"))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; @test fn fprintf_literal_braces() void = { let mem: memio.stream = memio.dynamic(); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprintf(s, "{{ {} }}", 1i64); match (r) { case let n: size => { assert(!(n: i32 != 5)); }; case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), "{ 1 }"))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; @test fn fprintf_width_right() void = { let mem: memio.stream = memio.dynamic(); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprintf(s, "{:5}", 42i64); match (r) { case let n: size => { assert(!(n: i32 != 5)); }; case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), " 42"))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; @test fn fprintf_width_left() void = { let mem: memio.stream = memio.dynamic(); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprintf(s, "{:-5}", 42i64); match (r) { case let n: size => { assert(!(n: i32 != 5)); }; case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), "42 "))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; @test fn fprintf_width_center() void = { let mem: memio.stream = memio.dynamic(); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprintf(s, "{:=5}", "hi"); match (r) { case let n: size => { assert(!(n: i32 != 5)); }; case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), " hi "))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; // ---- fprint: rune args emit full UTF-8, not a truncated byte (#66) ----- // writeone/formatraw's rune arm did `buf[0] = r: u8; putbytes(...,1)`, // emitting only the low byte (invalid UTF-8 for r > 0x7F). The fix routes // through utf8.encoderune (ref/hare/fmt/print.ha:84). Edge runes: é (2B), // € (3B), 😀 (4B), A (1B). @test fn fprintrune() void = { let mem: memio.stream = memio.dynamic(); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprint(s, 233: rune, 0x20AC: rune, 0x1F600: rune, 65: rune); match (r) { case let n: size => { assert(!(n: i32 != 13)); }; // 2+1+3+1+4+1+1 case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), "é € 😀 A"))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; // rawlen's rune arm hardcoded 1, desyncing width-padding for a multibyte // rune. With runesz it counts the encoded length: € (3B) in width 5 pads // to " €" (2 spaces + 3 bytes = 5). @test fn fprintf_rune_width() void = { let mem: memio.stream = memio.dynamic(); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprintf(s, "{:5}", 0x20AC: rune); match (r) { case let n: size => { assert(!(n: i32 != 5)); }; case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), " €"))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; @test fn fprintf_pad_underscore() void = { let mem: memio.stream = memio.dynamic(); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprintf(s, "{:_05}", "hi"); match (r) { case let n: size => { assert(!(n: i32 != 5)); }; case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), "000hi"))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; @test fn fprintf_base_hex() void = { let mem: memio.stream = memio.dynamic(); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprintf(s, "{:x} {:X}", 48879i64, 61453i64); match (r) { case let n: size => { assert(!(n: i32 != 9)); }; case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), "beef F00D"))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; @test fn fprintf_base_oct_bin() void = { let mem: memio.stream = memio.dynamic(); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprintf(s, "{:o} {:b}", 493i64, 27i64); match (r) { case let n: size => { assert(!(n: i32 != 9)); }; // "755 11011" case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), "755 11011"))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; @test fn fprintf_prec_int() void = { let mem: memio.stream = memio.dynamic(); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprintf(s, "{:.5}", 42i64); match (r) { case let n: size => { assert(!(n: i32 != 5)); }; case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), "00042"))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; @test fn fprintf_prec_str_trunc() void = { let mem: memio.stream = memio.dynamic(); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprintf(s, "{:.3}", "hello"); match (r) { case let n: size => { assert(!(n: i32 != 3)); }; case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), "hel"))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; @test fn fprintf_sign_neg() void = { let mem: memio.stream = memio.dynamic(); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprintf(s, "{} {:+} {: }", -7i64, 7i64, 7i64); match (r) { case let n: size => { assert(!(n: i32 != 8)); }; // "-7 +7 7" case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), "-7 +7 7"))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; @test fn fprintfln_basic() void = { let mem: memio.stream = memio.dynamic(); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprintfln(s, "x={}", 9i64); match (r) { case let n: size => { assert(!(n: i32 != 4)); }; // "x=9\n" case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), "x=9\n"))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; @test fn fprintf_closed() void = { let s: io.stream = errsource(); let r: (size | io.error) = fmt.fprintf(s, "hello {}", 1i64); match (r) { case let n: size => abort(); case let eioe: io.error => {}; }; }; @test fn bsprintf_basic() void = { let buf: [16]u8; let r: (str | io.error) = fmt.bsprintf(buf[0:16], "{} {}", "hi", 42i64); match (r) { case let s: str => { assert(!(!streq(s, "hi 42"))); }; case let eioe: io.error => abort(); }; }; // Positive control: an EXACT-fit render must still succeed — the sink // fills to the last byte but no write lands on a full sink, so the // nomem guard (memio.ww:190) must not fire. Guards against the // truncation fix over-rejecting a render that exactly fits. @test fn bsprintf_exact() void = { let buf: [5]u8; let r: (str | io.error) = fmt.bsprintf(buf[0:5], "hello"); match (r) { case let s: str => { assert(!(!streq(s, "hello"))); }; case let eioe: io.error => abort(); }; }; // 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.stream = memio.dynamic(); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprintf(s, "{} {}", true, 'A': rune); match (r) { case let n: size => { assert(!(n: i32 != 6)); }; // "true A" case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), "true A"))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; // Verifies bsprintf's truncation contract — overflowing a fixed sink // surfaces nomem, not a prefix (memio.fixedwrite returns nomem once // full, ref/hare/fmt/wrappers.ha:50). Pairs with fprintfixedshort // above which covers the partial-write (short i32) path. @test fn bsprintf_trunc() void = { let buf: [3]u8; let r: (str | io.error) = fmt.bsprintf(buf[0:3], "{} {}", "hi", 42i64); match (r) { case let s: str => abort(); case let eioe: io.error => { assert(eioe is nomem); }; }; }; // Truncation through the width/pad path also surfaces nomem. Buffer // 3B, target " 42" (5B): the 3 lead pad spaces fill the sink, then // formatraw writes "42" into the full sink → nomem. Also pins // formatone's tail-pad counter (the loop must not spin on the full // sink before the error propagates). @test fn bsprintf_width_trunc() void = { let buf: [3]u8; let r: (str | io.error) = fmt.bsprintf(buf[0:3], "{:5}", 42i64); match (r) { case let s: str => abort(); case let eioe: io.error => { assert(eioe is nomem); }; }; }; // Empty-sink discriminator: the cleanest single-write red/green split. // The first non-empty write to a zero-length sink hits the full-sink // guard → nomem; pre-fix it returned 0 and bsprintf yielded "" as a // success. @test fn bsprintf_empty() void = { let buf: [0]u8; let r: (str | io.error) = fmt.bsprintf(buf[0:0], "x"); match (r) { case let s: str => abort(); case let eioe: io.error => { assert(eioe is nomem); }; }; }; // asprintf — heap-allocated render through memio.dynamic + shrink-to-fit. // Caller frees via os.free(r.ptr, r.len) when r.len > 0. @test fn asprintf_basic() void = { let r: str = fmt.asprintf("{} {}", "hi", 42i64); assert(!(!streq(r, "hi 42"))); os.free(r.ptr: *void, r.len: u64); }; // Output > memio.dynamic's initial 8B cap to exercise the // double-and-copy grow path; 34B target traverses 8→16→32→64. @test fn asprintf_growth() void = { let r: str = fmt.asprintf("hello {} world {} value {}", "alpha", "beta", "gamma"); assert(!(!streq(r, "hello alpha world beta value gamma"))); os.free(r.ptr: *void, r.len: u64); }; // Empty format pins the no-allocation path: memio.dynamic.grow never // fires (cap stays 0), and the asprintf shrink-to-fit path returns // {nil, 0} without calling os.alloc. Free is skipped — matches the // strings.dup empty-input contract. @test fn asprintf_empty() void = { let r: str = fmt.asprintf(""); assert(!(r.len != 0)); }; // Indexed placeholder + modifier through asprintf — confirms the full // {n:mods} parser path works through the heap-allocated sink, not just // the memio.fixed (bsprintf) and memio.dynamic-via-fprintf paths. @test fn asprintf_indexed_mods() void = { let r: str = fmt.asprintf("{1:_05}", "zzz", 42i64); assert(!(!streq(r, "00042"))); os.free(r.ptr: *void, r.len: u64); }; // ---- f64 dispatch arm ------------------------------------------------- // // Pins the f64 formattable arm landed under task #17 (unblocked by #30 // — the variant-widen-from-X0 fix). strconv.f64tos drives the render; // see lib/strconv/strconv.ww for the documented subset (fixed-point, // 6 fractional digits, trailing-zero trim, magnitudes ≥ 9e18 → "huge", // no NaN/±Inf detection). // // Mods scope under v1: width / alignment / pad / sign mods honored. // `prec` and `base` ignored — graduate when strconv grows ffmt/fflags. @test fn fprintf_f64_basic() void = { let mem: memio.stream = memio.dynamic(); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprintf(s, "{}", 1.5); match (r) { case let n: size => { assert(!(n: i32 != 3)); }; case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), "1.5"))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; @test fn fprintf_f64_int_valued() void = { let mem: memio.stream = memio.dynamic(); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprintf(s, "{}", 1.0); match (r) { case let n: size => { assert(!(n: i32 != 1)); }; case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), "1"))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; @test fn fprintf_f64_neg() void = { let mem: memio.stream = memio.dynamic(); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprintf(s, "{}", -2.5); match (r) { case let n: size => { assert(!(n: i32 != 4)); }; case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), "-2.5"))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; @test fn fprintf_f64_zero() void = { let mem: memio.stream = memio.dynamic(); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprintf(s, "{}", 0.0); match (r) { case let n: size => { assert(!(n: i32 != 1)); }; case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), "0"))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; @test fn fprintf_f64_small_frac() void = { let mem: memio.stream = memio.dynamic(); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprintf(s, "{}", 0.05); match (r) { case let n: size => { assert(!(n: i32 != 4)); }; case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), "0.05"))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; // Pins a large magnitude through the fmt dispatch — post-Ryū-graduation // (fold-5) this is scientific notation, not the old lossy "huge" fallback. // Catches any regression where mods accidentally pre-truncate the view. @test fn fprintf_f64_huge() void = { let mem: memio.stream = memio.dynamic(); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprintf(s, "{}", 9.5e18); match (r) { case let n: size => { assert(!(n: i32 != 6)); }; // "9.5e18" case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), "9.5e18"))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; // Sign-mod fold: '+' on positive, '-' still wins on negative (the // natural '-' from strconv is peeled and signof reapplies). @test fn fprintf_f64_sign_plus() void = { let mem: memio.stream = memio.dynamic(); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprintf(s, "{:+} {:+}", 1.5, -1.5); match (r) { case let n: size => { assert(!(n: i32 != 9)); }; // "+1.5 -1.5" case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), "+1.5 -1.5"))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; @test fn fprintf_f64_sign_space() void = { let mem: memio.stream = memio.dynamic(); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprintf(s, "{: }", 1.5); match (r) { case let n: size => { assert(!(n: i32 != 4)); }; case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), " 1.5"))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; // Width + alignment round-trip through rawlenf64 (sign-aware len count). @test fn fprintf_f64_width_right() void = { let mem: memio.stream = memio.dynamic(); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprintf(s, "{:8}", 1.5); match (r) { case let n: size => { assert(!(n: i32 != 8)); }; case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), " 1.5"))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; @test fn fprintf_f64_width_left() void = { let mem: memio.stream = memio.dynamic(); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprintf(s, "{:-8}", 1.5); match (r) { case let n: size => { assert(!(n: i32 != 8)); }; case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), "1.5 "))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; // Legacy fprint variadic — covers the non-printf surface arm (where // formatraw isn't reached because there's no mods parser). @test fn fprint_f64_variadic() void = { let mem: memio.stream = memio.dynamic(); let s: io.stream = &mem.vt; let r: (size | io.error) = fmt.fprint(s, 1.5, "x"); match (r) { case let n: size => { assert(!(n: i32 != 5)); }; // "1.5 x" case let eioe: io.error => abort(); }; assert(!(!streq(memio.string(&mem), "1.5 x"))); let c: (void | io.error) = io.close(s); match (c) { case void => {}; case let eioe: io.error => abort(); }; }; @test fn bsprintf_f64() void = { let buf: [16]u8; let r: (str | io.error) = fmt.bsprintf(buf[0:16], "x={}", 1.5); match (r) { case let s: str => { assert(!(!streq(s, "x=1.5"))); }; case let eioe: io.error => abort(); }; }; // Heap sink + both signs. @test fn asprintf_f64() void = { let r: str = fmt.asprintf("{} {}", 1.5, -2.5); assert(!(!streq(r, "1.5 -2.5"))); os.free(r.ptr: *void, r.len: u64); };