lib/fmt+test: add {n}-placeholder printf family
Hare-shaped {} / {0} / {n:mods} parser + printf wrappers. APIs:
fprintf, fprintfln, fdprintf, fdprintfln, printf, printfln, errorfln,
fatalf, bsprintf. Parser handles indexed/positional placeholders,
alignment (- / default / =), pad-width, zero-pad (_05), radix (x X o
b), precision (.N for int pad / str trunc), sign markers (+, space),
and {{ / }} escape.
Internals: scandigits + scanmods drive a field-by-field dispatch into
formatfield, which inlines the field→formattable widen per-arm to
sidestep task #18 (24B return-by-value miscompile in for-loop
context). Render through formatraw + formatone over io.stream sinks.
formatone tail-pad uses a separate counter rather than mirroring
Hare's `?`-propagating loop: ww's memio.fixed returns partial-write
0 instead of errors::overflow, so the Hare shape would spin forever
on a full fixed buffer.
Deferred per drew's vet: asprintf/errorf (needs os.alloc, #16),
parametric width/precision dispatch (#16-family), float arm (#17),
log.printfln family wiring (#15).
Tests: 26 scenarios covering every placeholder shape, both arms of
fprintf's variadic dispatch (incl. bool/rune to pin #18 regression),
bsprintf overflow + width-against-full-buffer, closed-stream.
This commit is contained in:
@@ -193,6 +193,258 @@ fn closedstream(s: *io.stream) void = {
|
||||
};
|
||||
};
|
||||
|
||||
// ---- 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();
|
||||
@@ -202,5 +454,23 @@ export fn main() i32 = {
|
||||
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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user