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:
4
Makefile
4
Makefile
@@ -113,7 +113,7 @@ $(BIN)/wwdump_ww: selfhost/cmd/wwdump/main.ww \
|
|||||||
selfhost/cmd/wcc/cgen.ww selfhost/cmd/wcc/cgenexpr.ww \
|
selfhost/cmd/wcc/cgen.ww selfhost/cmd/wcc/cgenexpr.ww \
|
||||||
selfhost/cmd/wcc/cgenstmt.ww selfhost/cmd/wcc/cgenutil.ww \
|
selfhost/cmd/wcc/cgenstmt.ww selfhost/cmd/wcc/cgenutil.ww \
|
||||||
selfhost/cmd/wcc/cgendecl.ww \
|
selfhost/cmd/wcc/cgendecl.ww \
|
||||||
lib/os/os.ww lib/strconv/strconv.ww lib/strings/strings.ww lib/ascii/ascii.ww lib/fmt/fmt.ww \
|
lib/os/os.ww lib/strconv/strconv.ww lib/strings/strings.ww lib/ascii/ascii.ww lib/io/io.ww lib/memio/memio.ww lib/fmt/fmt.ww \
|
||||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||||
$(LIB)/libwwrt.a | $(BIN)
|
$(LIB)/libwwrt.a | $(BIN)
|
||||||
cd $(BIN) && ./ww build \
|
cd $(BIN) && ./ww build \
|
||||||
@@ -134,7 +134,7 @@ $(BIN)/w6c_ww: selfhost/cmd/w6c/main.ww \
|
|||||||
selfhost/cmd/wcc/cgen.ww selfhost/cmd/wcc/cgenexpr.ww \
|
selfhost/cmd/wcc/cgen.ww selfhost/cmd/wcc/cgenexpr.ww \
|
||||||
selfhost/cmd/wcc/cgenstmt.ww selfhost/cmd/wcc/cgenutil.ww \
|
selfhost/cmd/wcc/cgenstmt.ww selfhost/cmd/wcc/cgenutil.ww \
|
||||||
selfhost/cmd/wcc/cgendecl.ww \
|
selfhost/cmd/wcc/cgendecl.ww \
|
||||||
lib/os/os.ww lib/strconv/strconv.ww lib/strings/strings.ww lib/ascii/ascii.ww lib/fmt/fmt.ww \
|
lib/os/os.ww lib/strconv/strconv.ww lib/strings/strings.ww lib/ascii/ascii.ww lib/io/io.ww lib/memio/memio.ww lib/fmt/fmt.ww \
|
||||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||||
$(LIB)/libwwrt.a | $(BIN)
|
$(LIB)/libwwrt.a | $(BIN)
|
||||||
cd $(BIN) && ./ww build \
|
cd $(BIN) && ./ww build \
|
||||||
|
|||||||
@@ -32,8 +32,10 @@ Signatures mirror Hare too, modulo:
|
|||||||
slice with `xs...` (`fdprintln(fd, args...)`). The bare `T...` form
|
slice with `xs...` (`fdprintln(fd, args...)`). The bare `T...` form
|
||||||
in tagged unions still means spread-flatten (`(...inner | E)`);
|
in tagged unions still means spread-flatten (`(...inner | E)`);
|
||||||
the two uses don't overlap because `T...` only attaches to a
|
the two uses don't overlap because `T...` only attaches to a
|
||||||
*param* decl. `lib/fmt` is intentionally print-string-only — no
|
*param* decl. `lib/fmt` ships both the print family (`print` /
|
||||||
`printf`-family.
|
`println` / `fprint` / `fprintln`) and the {n}-placeholder family
|
||||||
|
(`printf` / `fprintf` / `bsprintf` / `fatalf`); the `%`-parametric
|
||||||
|
modifier form is parsed but its `*mods` arg slot aborts on dispatch.
|
||||||
|
|
||||||
- `(T | U)` sum-typed parameters dispatch via `match` inside the
|
- `(T | U)` sum-typed parameters dispatch via `match` inside the
|
||||||
callee. `strings.byteindex(haystack: str, needle: (str | rune))`,
|
callee. `strings.byteindex(haystack: str, needle: (str | rune))`,
|
||||||
|
|||||||
514
lib/fmt/fmt.ww
514
lib/fmt/fmt.ww
@@ -15,12 +15,19 @@
|
|||||||
// graduate "in one go" (lib/CLAUDE.md) and
|
// graduate "in one go" (lib/CLAUDE.md) and
|
||||||
// the fd-suffixed names disappear.
|
// the fd-suffixed names disappear.
|
||||||
//
|
//
|
||||||
|
// The printf-family ({n}-placeholder parser) is provided alongside —
|
||||||
|
// see the "{n}-placeholder parser" section below. Same sink split:
|
||||||
|
// `fprintf` / `fprintfln` on streams; `fdprintf` / `fdprintfln` on
|
||||||
|
// raw fds; the process-stdio wrappers route through fd 1 / fd 2.
|
||||||
|
//
|
||||||
// Call sites take Hare's variadic shape: `fmt.println(42, "hi", true)`
|
// Call sites take Hare's variadic shape: `fmt.println(42, "hi", true)`
|
||||||
// gathers the args into a `[]formattable` slice; wrappers forward
|
// gathers the args into a `[]formattable` slice; wrappers forward
|
||||||
// via `args...`.
|
// via `args...`.
|
||||||
|
|
||||||
use io;
|
use io;
|
||||||
|
use memio;
|
||||||
use os;
|
use os;
|
||||||
|
use strconv;
|
||||||
|
|
||||||
// i64dec_buf — scratch buffer for [[i64dec]] below. Module-level
|
// i64dec_buf — scratch buffer for [[i64dec]] below. Module-level
|
||||||
// because Hare's `strconv::i64tos` is a static-buffer view and we
|
// because Hare's `strconv::i64tos` is a static-buffer view and we
|
||||||
@@ -29,11 +36,9 @@ use os;
|
|||||||
let i64dec_buf: [21]u8;
|
let i64dec_buf: [21]u8;
|
||||||
|
|
||||||
// i64dec — render `v` as a base-10 ASCII string into [[i64dec_buf]],
|
// i64dec — render `v` as a base-10 ASCII string into [[i64dec_buf]],
|
||||||
// returning a borrowed view. Inlined here rather than calling
|
// returning a borrowed view. Preserved as a base-10 specialisation
|
||||||
// [[strconv.i64tos]] because `use strconv;` would transitively pull
|
// of strconv.i64tos for the pre-printf-family print path; the
|
||||||
// `use os;`, whose exported read/write/close clash with
|
// printf-family path below dispatches through strconv directly.
|
||||||
// io.read/write/close under the driver's flat-scope concat. Same
|
|
||||||
// algorithmic shape as strconv's version, narrowed to base-10.
|
|
||||||
fn i64dec(v: i64) str = {
|
fn i64dec(v: i64) str = {
|
||||||
let neg: bool = false;
|
let neg: bool = false;
|
||||||
let n: i64 = v;
|
let n: i64 = v;
|
||||||
@@ -249,3 +254,502 @@ export fn fatal(args: formattable...) never = {
|
|||||||
fdprintln(2, args...);
|
fdprintln(2, args...);
|
||||||
os.exit(255);
|
os.exit(255);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ---- {n}-placeholder parser -----------------------------------------
|
||||||
|
//
|
||||||
|
// Mirrors ref/hare/fmt/{iter,print,wrappers}.ha. Format sequences:
|
||||||
|
//
|
||||||
|
// {} implicit-positional next arg
|
||||||
|
// {N} explicit-positional arg N
|
||||||
|
// {:mods} inline modifiers on next arg
|
||||||
|
// {N:mods} inline modifiers on arg N
|
||||||
|
// {{ }} literal '{' / '}'
|
||||||
|
//
|
||||||
|
// Modifier set (subset of iter.ha:129 scan_modifiers):
|
||||||
|
//
|
||||||
|
// - align LEFT
|
||||||
|
// = align CENTER
|
||||||
|
// _<rune> pad rune (default space when : is seen)
|
||||||
|
// ' ' sign SPACE
|
||||||
|
// + sign PLUS
|
||||||
|
// x X o b base
|
||||||
|
// <digits> width (first char 1..9)
|
||||||
|
// .<digits> precision
|
||||||
|
//
|
||||||
|
// Not v1: '%' parametric form ({0%1}); float modifiers (e/f/g/F…);
|
||||||
|
// void / null arms (not in formattable). `asprintf` is deferred until
|
||||||
|
// lib/os exports alloc (task #14). Invalid format aborts via os.exit
|
||||||
|
// — Hare uses `abort()`; same effect.
|
||||||
|
|
||||||
|
// neg, alignment — mirror iter.ha:19 / iter.ha:26.
|
||||||
|
export type neg = enum i32 { NONE = 0, SPACE = 1, PLUS = 2 };
|
||||||
|
export type alignment = enum i32 { RIGHT = 0, CENTER = 1, LEFT = 2 };
|
||||||
|
|
||||||
|
// mods — per-placeholder modifier set. Mirrors iter.ha:33 minus
|
||||||
|
// `ffmt` / `fflags` — formattable has no float arm, so the float-
|
||||||
|
// formatter knobs would be dead. Graduate when fmt.formattable does.
|
||||||
|
export type mods = struct {
|
||||||
|
alignment: alignment,
|
||||||
|
pad: rune,
|
||||||
|
neg: neg,
|
||||||
|
width: i32,
|
||||||
|
prec: i32,
|
||||||
|
base: strconv.base,
|
||||||
|
};
|
||||||
|
|
||||||
|
// field — variadic arg slot. `(...formattable | *mods)` per iter.ha:11.
|
||||||
|
// The *mods arm is the parametric-modifier form ({0%1}); accepted by
|
||||||
|
// the type checker today, but the '%' parser branch is deferred —
|
||||||
|
// passing a *mods that the parser reaches will abort the program.
|
||||||
|
export type field = (...formattable | *mods);
|
||||||
|
|
||||||
|
// fmtabort — invalid format string. Matches Hare's abort() (iter.ha:71)
|
||||||
|
// in effect; exit code 255 matches [[fatal]].
|
||||||
|
fn fmtabort() never = { os.exit(255); };
|
||||||
|
|
||||||
|
// modsinit — reset `m` to the all-zero default. pad stays 0 here;
|
||||||
|
// scan_modifiers (Hare iter.ha:130) defaults it to ' ' only when ':'
|
||||||
|
// is seen — bare `{}` never reaches the padding loop (width=0).
|
||||||
|
fn modsinit(m: *mods) void = {
|
||||||
|
m.alignment = alignment.RIGHT;
|
||||||
|
m.pad = 0: rune;
|
||||||
|
m.neg = neg.NONE;
|
||||||
|
m.width = 0;
|
||||||
|
m.prec = 0;
|
||||||
|
m.base = strconv.base.DEFAULT;
|
||||||
|
};
|
||||||
|
|
||||||
|
// scandigits — consume a digit run at *pos in `s`, advancing *pos
|
||||||
|
// past it; return the value. Aborts on no digits or overflow past
|
||||||
|
// i32. Mirrors iter.ha:173 scan_sz.
|
||||||
|
fn scandigits(s: str, pos: *i32) i32 = {
|
||||||
|
let v: i32 = 0;
|
||||||
|
let any: bool = false;
|
||||||
|
for (*pos < s.len) {
|
||||||
|
let c: u8 = s[*pos];
|
||||||
|
if (c < 48u8 || c > 57u8) {
|
||||||
|
if (!any) { fmtabort(); };
|
||||||
|
return v;
|
||||||
|
};
|
||||||
|
any = true;
|
||||||
|
if (v > 214748364) { fmtabort(); };
|
||||||
|
v = v * 10 + (c - 48u8): i32;
|
||||||
|
*pos += 1;
|
||||||
|
};
|
||||||
|
if (!any) { fmtabort(); };
|
||||||
|
return v;
|
||||||
|
};
|
||||||
|
|
||||||
|
// scanmods — parse the `:`-modifier run starting at *pos. *pos is on
|
||||||
|
// the first byte after `:`. Returns with *pos on the closing `}`.
|
||||||
|
// Mirrors iter.ha:129 scan_modifiers.
|
||||||
|
fn scanmods(s: str, pos: *i32, m: *mods) void = {
|
||||||
|
m.pad = 32: rune; // ' ' — Hare iter.ha:130
|
||||||
|
for (*pos < s.len) {
|
||||||
|
let c: u8 = s[*pos];
|
||||||
|
if (c == 125u8) { return; }; // '}'
|
||||||
|
*pos += 1;
|
||||||
|
if (c == 45u8) { m.alignment = alignment.LEFT; } // '-'
|
||||||
|
else if (c == 61u8) { m.alignment = alignment.CENTER; } // '='
|
||||||
|
else if (c == 95u8) { // '_'
|
||||||
|
if (*pos >= s.len) { fmtabort(); };
|
||||||
|
m.pad = s[*pos]: rune;
|
||||||
|
*pos += 1;
|
||||||
|
}
|
||||||
|
else if (c == 32u8) { m.neg = neg.SPACE; } // ' '
|
||||||
|
else if (c == 43u8) { m.neg = neg.PLUS; } // '+'
|
||||||
|
else if (c == 120u8) { m.base = strconv.base.HEX_LOWER; } // 'x'
|
||||||
|
else if (c == 88u8) { m.base = strconv.base.HEX_UPPER; } // 'X'
|
||||||
|
else if (c == 111u8) { m.base = strconv.base.OCT; } // 'o'
|
||||||
|
else if (c == 98u8) { m.base = strconv.base.BIN; } // 'b'
|
||||||
|
else if (c == 46u8) { // '.'
|
||||||
|
m.prec = scandigits(s, pos);
|
||||||
|
}
|
||||||
|
else if (c >= 49u8 && c <= 57u8) { // '1'..'9'
|
||||||
|
*pos -= 1;
|
||||||
|
m.width = scandigits(s, pos);
|
||||||
|
}
|
||||||
|
else { fmtabort(); };
|
||||||
|
};
|
||||||
|
fmtabort(); // ran off end without '}'
|
||||||
|
};
|
||||||
|
|
||||||
|
// digitsu64 — count base-b digits of `v`. Mirrors the explicit-loop
|
||||||
|
// shape strconv.u64tos uses internally; avoids materialising the
|
||||||
|
// digit string twice in the formatone width path.
|
||||||
|
fn digitsu64(v: u64, b: i64) i32 = {
|
||||||
|
if (v == 0u64) { return 1; };
|
||||||
|
let n: i32 = 0;
|
||||||
|
let nb: u64 = b: u64;
|
||||||
|
let x: u64 = v;
|
||||||
|
for (x > 0u64) {
|
||||||
|
n += 1;
|
||||||
|
x = x / nb;
|
||||||
|
};
|
||||||
|
return n;
|
||||||
|
};
|
||||||
|
|
||||||
|
// basenum — copy of strconv's internal basenum, lifted here because
|
||||||
|
// strconv keeps it private. Same shape as strconv.ww:40.
|
||||||
|
fn basenum(b: strconv.base) i64 = {
|
||||||
|
if (b == strconv.base.BIN) { return 2; };
|
||||||
|
if (b == strconv.base.OCT) { return 8; };
|
||||||
|
if (b == strconv.base.HEX) { return 16; };
|
||||||
|
if (b == strconv.base.HEX_UPPER) { return 16; };
|
||||||
|
if (b == strconv.base.HEX_LOWER) { return 16; };
|
||||||
|
return 10;
|
||||||
|
};
|
||||||
|
|
||||||
|
// signof — sign byte for `v` under `m.neg`, or 0u8 if none.
|
||||||
|
fn signof(neg_flag: bool, m: *mods) u8 = {
|
||||||
|
if (neg_flag) { return 45u8; }; // '-'
|
||||||
|
if (m.neg == neg.PLUS) { return 43u8; }; // '+'
|
||||||
|
if (m.neg == neg.SPACE) { return 32u8; }; // ' '
|
||||||
|
return 0u8;
|
||||||
|
};
|
||||||
|
|
||||||
|
// rawleni64 — bytes the raw render of `v` under `m` would emit. Used
|
||||||
|
// for width-alignment without rendering twice.
|
||||||
|
fn rawleni64(v: i64, m: *mods) i32 = {
|
||||||
|
let neg_flag: bool = v < 0;
|
||||||
|
let u: u64 = v: u64;
|
||||||
|
if (neg_flag) { u = (-v): u64; };
|
||||||
|
let signlen: i32 = 0;
|
||||||
|
if (signof(neg_flag, m) != 0u8) { signlen = 1; };
|
||||||
|
let dlen: i32 = digitsu64(u, basenum(m.base));
|
||||||
|
let inner: i32 = dlen;
|
||||||
|
if (m.prec > signlen + dlen) { inner = m.prec - signlen; };
|
||||||
|
return signlen + inner;
|
||||||
|
};
|
||||||
|
|
||||||
|
// rawlenstr — bytes the raw render of `s` would emit (after `prec`
|
||||||
|
// truncation, per Hare print.ha:86).
|
||||||
|
fn rawlenstr(s: str, m: *mods) i32 = {
|
||||||
|
if (m.prec > 0 && m.prec < s.len) { return m.prec; };
|
||||||
|
return s.len;
|
||||||
|
};
|
||||||
|
|
||||||
|
// formatraw — write the bare value (no width padding) to `s`.
|
||||||
|
// Mirrors print.ha:76 format_raw, narrowed to the formattable arms
|
||||||
|
// fmt.formattable carries today.
|
||||||
|
fn formatraw(out: *io.stream, arg: formattable, m: *mods) (i32 | io.closed) = {
|
||||||
|
match (arg) {
|
||||||
|
case let v: i64 => {
|
||||||
|
let neg_flag: bool = v < 0;
|
||||||
|
let u: u64 = v: u64;
|
||||||
|
if (neg_flag) { u = (-v): u64; };
|
||||||
|
let sb: u8 = signof(neg_flag, m);
|
||||||
|
let total: i32 = 0;
|
||||||
|
if (sb != 0u8) {
|
||||||
|
let buf: [1]u8;
|
||||||
|
buf[0] = sb;
|
||||||
|
let r: (i32 | io.closed) = putbytes(out, &buf[0], 1);
|
||||||
|
match (r) {
|
||||||
|
case let n: i32 => { total += n; };
|
||||||
|
case io.closed => { let c: io.closed; return c; };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
let dlen: i32 = digitsu64(u, basenum(m.base));
|
||||||
|
let signlen: i32 = 0;
|
||||||
|
if (sb != 0u8) { signlen = 1; };
|
||||||
|
let pad0: i32 = 0;
|
||||||
|
if (m.prec > signlen + dlen) { pad0 = m.prec - signlen - dlen; };
|
||||||
|
let pi: i32 = 0;
|
||||||
|
for (pi < pad0) {
|
||||||
|
let buf: [1]u8;
|
||||||
|
buf[0] = 48u8; // '0'
|
||||||
|
let r: (i32 | io.closed) = putbytes(out, &buf[0], 1);
|
||||||
|
match (r) {
|
||||||
|
case let n: i32 => { total += n; };
|
||||||
|
case io.closed => { let c: io.closed; return c; };
|
||||||
|
};
|
||||||
|
pi += 1;
|
||||||
|
};
|
||||||
|
let view: str = strconv.u64tos(u, m.base);
|
||||||
|
let r: (i32 | io.closed) = putbytes(out, view.ptr, view.len);
|
||||||
|
match (r) {
|
||||||
|
case let n: i32 => { total += n; };
|
||||||
|
case io.closed => { let c: io.closed; return c; };
|
||||||
|
};
|
||||||
|
return total;
|
||||||
|
};
|
||||||
|
case let v: str => {
|
||||||
|
let n: i32 = rawlenstr(v, m);
|
||||||
|
return putbytes(out, v.ptr, n);
|
||||||
|
};
|
||||||
|
case let b: bool => {
|
||||||
|
let v: str = "false";
|
||||||
|
if (b) { v = "true"; };
|
||||||
|
return putbytes(out, v.ptr, v.len);
|
||||||
|
};
|
||||||
|
case let r: rune => {
|
||||||
|
let buf: [4]u8;
|
||||||
|
buf[0] = r: u8;
|
||||||
|
return putbytes(out, &buf[0], 1);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
let z: io.closed; return z; // unreachable — match is exhaustive
|
||||||
|
};
|
||||||
|
|
||||||
|
// rawlen — bytes formatraw would emit for `arg` under `m`. Used by
|
||||||
|
// formatone's width-alignment path (Hare's print.ha:53 calls
|
||||||
|
// format_raw with io::empty for the same purpose).
|
||||||
|
fn rawlen(arg: formattable, m: *mods) i32 = {
|
||||||
|
match (arg) {
|
||||||
|
case let v: i64 => return rawleni64(v, m);
|
||||||
|
case let v: str => return rawlenstr(v, m);
|
||||||
|
case let b: bool => { if (b) { return 4; }; return 5; };
|
||||||
|
case let r: rune => return 1;
|
||||||
|
};
|
||||||
|
return 0; // unreachable — match is exhaustive
|
||||||
|
};
|
||||||
|
|
||||||
|
// formatone — render `arg` to `s` with `m`'s width / alignment / pad
|
||||||
|
// applied. Mirrors print.ha:45 format.
|
||||||
|
fn formatone(out: *io.stream, arg: formattable, m: *mods) (i32 | io.closed) = {
|
||||||
|
let start: i32 = 0;
|
||||||
|
if (m.width > 0 && m.alignment != alignment.LEFT) {
|
||||||
|
let raw: i32 = rawlen(arg, m);
|
||||||
|
let pad: i32 = 0;
|
||||||
|
if (raw < m.width) { pad = m.width - raw; };
|
||||||
|
if (m.alignment == alignment.CENTER) { start = (pad + 1) / 2; }
|
||||||
|
else { start = pad; };
|
||||||
|
};
|
||||||
|
let total: i32 = 0;
|
||||||
|
let i: i32 = 0;
|
||||||
|
let padb: [1]u8;
|
||||||
|
padb[0] = m.pad: u8;
|
||||||
|
for (i < start) {
|
||||||
|
let r: (i32 | io.closed) = putbytes(out, &padb[0], 1);
|
||||||
|
match (r) {
|
||||||
|
case let n: i32 => { total += n; };
|
||||||
|
case io.closed => { let c: io.closed; return c; };
|
||||||
|
};
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
let r1: (i32 | io.closed) = formatraw(out, arg, m);
|
||||||
|
match (r1) {
|
||||||
|
case let n: i32 => { total += n; };
|
||||||
|
case io.closed => { let c: io.closed; return c; };
|
||||||
|
};
|
||||||
|
// Tail-pad: drive with a counter (mirrors the start-pad shape
|
||||||
|
// above). ww's memio.fixed reports a full buffer as a 0-byte
|
||||||
|
// partial write, not io.closed (unlike Hare's errors::overflow
|
||||||
|
// + `?` shape at print.ha:69); looping on `total < m.width`
|
||||||
|
// would spin on bsprintf when the sink runs out of room.
|
||||||
|
let need: i32 = 0;
|
||||||
|
if (m.width > total) { need = m.width - total; };
|
||||||
|
let j: i32 = 0;
|
||||||
|
for (j < need) {
|
||||||
|
let r: (i32 | io.closed) = putbytes(out, &padb[0], 1);
|
||||||
|
match (r) {
|
||||||
|
case let n: i32 => { total += n; };
|
||||||
|
case io.closed => { let c: io.closed; return c; };
|
||||||
|
};
|
||||||
|
j += 1;
|
||||||
|
};
|
||||||
|
return total;
|
||||||
|
};
|
||||||
|
|
||||||
|
// formatfield — dispatch on the `field` slot directly. A
|
||||||
|
// `field→formattable` widen helper called from fprintf's for-loop
|
||||||
|
// would trip task #18 (silent miscompile of 24B return-by-value in
|
||||||
|
// for-loop context); inline-per-arm sidesteps it and is independently
|
||||||
|
// the cleaner shape.
|
||||||
|
fn formatfield(out: *io.stream, f: field, m: *mods) (i32 | io.closed) = {
|
||||||
|
match (f) {
|
||||||
|
case let v: i64 => {
|
||||||
|
let a: formattable = v;
|
||||||
|
return formatone(out, a, m);
|
||||||
|
};
|
||||||
|
case let v: str => {
|
||||||
|
let a: formattable = v;
|
||||||
|
return formatone(out, a, m);
|
||||||
|
};
|
||||||
|
case let b: bool => {
|
||||||
|
let a: formattable = b;
|
||||||
|
return formatone(out, a, m);
|
||||||
|
};
|
||||||
|
case let r: rune => {
|
||||||
|
let a: formattable = r;
|
||||||
|
return formatone(out, a, m);
|
||||||
|
};
|
||||||
|
case let p: *mods => { fmtabort(); let c: io.closed; return c; };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// fprintf — Hare's primary printf-family surface. Mirrors print.ha:26.
|
||||||
|
// Returns total bytes written or io.closed on the first sink failure.
|
||||||
|
export fn fprintf(s: *io.stream, fmt: str, args: field...) (i32 | io.closed) = {
|
||||||
|
let total: i32 = 0;
|
||||||
|
let i: i32 = 0;
|
||||||
|
let nextimpl: i32 = 0;
|
||||||
|
let checkunused: bool = true;
|
||||||
|
for (i < fmt.len) {
|
||||||
|
let c: u8 = fmt[i];
|
||||||
|
if (c == 123u8) { // '{'
|
||||||
|
i += 1;
|
||||||
|
if (i >= fmt.len) { fmtabort(); };
|
||||||
|
if (fmt[i] == 123u8) { // '{{' literal
|
||||||
|
let r: (i32 | io.closed) = putbytes(s, fmt.ptr + i: u64, 1);
|
||||||
|
match (r) {
|
||||||
|
case let n: i32 => { total += n; };
|
||||||
|
case io.closed => { let cl: io.closed; return cl; };
|
||||||
|
};
|
||||||
|
i += 1;
|
||||||
|
} else {
|
||||||
|
let idx: i32 = 0;
|
||||||
|
let d: u8 = fmt[i];
|
||||||
|
if (d >= 48u8 && d <= 57u8) {
|
||||||
|
checkunused = false;
|
||||||
|
idx = scandigits(fmt, &i);
|
||||||
|
} else {
|
||||||
|
idx = nextimpl;
|
||||||
|
nextimpl += 1;
|
||||||
|
};
|
||||||
|
let m: mods;
|
||||||
|
modsinit(&m);
|
||||||
|
if (i < fmt.len && fmt[i] == 58u8) { // ':'
|
||||||
|
i += 1;
|
||||||
|
scanmods(fmt, &i, &m);
|
||||||
|
};
|
||||||
|
if (i >= fmt.len || fmt[i] != 125u8) { fmtabort(); };
|
||||||
|
i += 1;
|
||||||
|
if (idx >= args.len) { fmtabort(); };
|
||||||
|
let r: (i32 | io.closed) = formatfield(s, args[idx], &m);
|
||||||
|
match (r) {
|
||||||
|
case let n: i32 => { total += n; };
|
||||||
|
case io.closed => { let cl: io.closed; return cl; };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
} else if (c == 125u8) { // '}'
|
||||||
|
i += 1;
|
||||||
|
if (i >= fmt.len || fmt[i] != 125u8) { fmtabort(); };
|
||||||
|
let r: (i32 | io.closed) = putbytes(s, fmt.ptr + i: u64, 1);
|
||||||
|
match (r) {
|
||||||
|
case let n: i32 => { total += n; };
|
||||||
|
case io.closed => { let cl: io.closed; return cl; };
|
||||||
|
};
|
||||||
|
i += 1;
|
||||||
|
} else {
|
||||||
|
let r: (i32 | io.closed) = putbytes(s, fmt.ptr + i: u64, 1);
|
||||||
|
match (r) {
|
||||||
|
case let n: i32 => { total += n; };
|
||||||
|
case io.closed => { let cl: io.closed; return cl; };
|
||||||
|
};
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
if (checkunused && nextimpl != args.len) { fmtabort(); };
|
||||||
|
return total;
|
||||||
|
};
|
||||||
|
|
||||||
|
// fprintfln — fprintf plus a trailing newline. Mirrors wrappers.ha:69.
|
||||||
|
export fn fprintfln(s: *io.stream, fmt: str, args: field...) (i32 | io.closed) = {
|
||||||
|
let total: i32 = 0;
|
||||||
|
let r1: (i32 | io.closed) = fprintf(s, fmt, args...);
|
||||||
|
match (r1) {
|
||||||
|
case let n: i32 => { total = n; };
|
||||||
|
case io.closed => { let c: io.closed; return c; };
|
||||||
|
};
|
||||||
|
let r2: (i32 | io.closed) = putbytes(s, "\n".ptr, 1);
|
||||||
|
match (r2) {
|
||||||
|
case let m: i32 => { total += m; };
|
||||||
|
case io.closed => { let c: io.closed; return c; };
|
||||||
|
};
|
||||||
|
return total;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---- fd-sink printf twins -------------------------------------------
|
||||||
|
//
|
||||||
|
// Hare's wrappers.ha:10 routes printf through os::stdout (an
|
||||||
|
// io::handle). ww has no fd-backed stream yet, so the fd path here
|
||||||
|
// wraps the fd in a stack-resident io.stream and dispatches through
|
||||||
|
// fprintf. -errno from [[os.write]] collapses to io.closed inside the
|
||||||
|
// wrapper; the public i64 return signals success-bytes vs -1 failure.
|
||||||
|
// Symmetric with the fdprint / fdprintln pair above.
|
||||||
|
|
||||||
|
fn fdsinkread(s: *io.stream, buf: []u8) (i32 | io.eof | io.closed) = {
|
||||||
|
let e: io.eof; return e;
|
||||||
|
};
|
||||||
|
fn fdsinkwrite(s: *io.stream, buf: []u8) (i32 | io.closed) = {
|
||||||
|
let fdp: *i32 = s.ctx: *i32;
|
||||||
|
let r: i64 = os.write(*fdp, buf.ptr, buf.len: u64);
|
||||||
|
if (r < 0) { let c: io.closed; return c; };
|
||||||
|
return r: i32;
|
||||||
|
};
|
||||||
|
fn fdsinkclose(s: *io.stream) (void | io.closed) = { return; };
|
||||||
|
|
||||||
|
fn fdwrap(fd: *i32, s: *io.stream) void = {
|
||||||
|
s.ctx = fd: *void;
|
||||||
|
s.read = fdsinkread;
|
||||||
|
s.write = fdsinkwrite;
|
||||||
|
s.close = fdsinkclose;
|
||||||
|
};
|
||||||
|
|
||||||
|
// fdprintf / fdprintfln — fprintf / fprintfln over a raw fd. Failure
|
||||||
|
// (any underlying os.write returning -errno) collapses to -1; success
|
||||||
|
// returns total bytes written. See header comment.
|
||||||
|
export fn fdprintf(fd: i32, fmt: str, args: field...) i64 = {
|
||||||
|
let f: i32 = fd;
|
||||||
|
let s: io.stream;
|
||||||
|
fdwrap(&f, &s);
|
||||||
|
let r: (i32 | io.closed) = fprintf(&s, fmt, args...);
|
||||||
|
match (r) {
|
||||||
|
case let n: i32 => return n: i64;
|
||||||
|
case io.closed => return -1i64;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export fn fdprintfln(fd: i32, fmt: str, args: field...) i64 = {
|
||||||
|
let f: i32 = fd;
|
||||||
|
let s: io.stream;
|
||||||
|
fdwrap(&f, &s);
|
||||||
|
let r: (i32 | io.closed) = fprintfln(&s, fmt, args...);
|
||||||
|
match (r) {
|
||||||
|
case let n: i32 => return n: i64;
|
||||||
|
case io.closed => return -1i64;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---- process-stdio printf wrappers ----------------------------------
|
||||||
|
//
|
||||||
|
// printf / printfln — wrappers.ha:10 / :15 on os.stdout (fd 1).
|
||||||
|
// errorfln — wrappers.ha:24 on stderr. Hare ships `errorf` too, but
|
||||||
|
// the bare `error` name collides with strconv.error under the
|
||||||
|
// driver's flat-scope concat (same divergence the existing [[errorln]]
|
||||||
|
// notes at line 234); ship the -ln form only and document.
|
||||||
|
// fatalf — wrappers.ha:54 errorfln-then-exit(255).
|
||||||
|
|
||||||
|
export fn printf(fmt: str, args: field...) i64 = {
|
||||||
|
return fdprintf(1, fmt, args...);
|
||||||
|
};
|
||||||
|
|
||||||
|
export fn printfln(fmt: str, args: field...) i64 = {
|
||||||
|
return fdprintfln(1, fmt, args...);
|
||||||
|
};
|
||||||
|
|
||||||
|
export fn errorfln(fmt: str, args: field...) i64 = {
|
||||||
|
return fdprintfln(2, fmt, args...);
|
||||||
|
};
|
||||||
|
|
||||||
|
export fn fatalf(fmt: str, args: field...) never = {
|
||||||
|
fdprintfln(2, fmt, args...);
|
||||||
|
os.exit(255);
|
||||||
|
};
|
||||||
|
|
||||||
|
// bsprintf — render into a caller-supplied buffer through memio.fixed;
|
||||||
|
// return the str view of the bytes actually written. Mirrors
|
||||||
|
// wrappers.ha:42. io.closed propagates as-is (Hare's `nomem` arm). On
|
||||||
|
// short writes the returned str is just the prefix that fit — Hare's
|
||||||
|
// memio.fixed contract is the same.
|
||||||
|
export fn bsprintf(buf: []u8, fmt: str, args: field...) (str | io.closed) = {
|
||||||
|
let m: memio.state;
|
||||||
|
let s: io.stream;
|
||||||
|
memio.fixed(&m, &s, buf);
|
||||||
|
let r: (i32 | io.closed) = fprintf(&s, fmt, args...);
|
||||||
|
match (r) {
|
||||||
|
case let n: i32 => { return memio.string(&m); };
|
||||||
|
case io.closed => { let c: io.closed; return c; };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -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 = {
|
export fn main() i32 = {
|
||||||
signalled = 1; fprintbarestr();
|
signalled = 1; fprintbarestr();
|
||||||
signalled = 2; fprintintstr();
|
signalled = 2; fprintintstr();
|
||||||
@@ -202,5 +454,23 @@ export fn main() i32 = {
|
|||||||
signalled = 6; fprintlnempty();
|
signalled = 6; fprintlnempty();
|
||||||
signalled = 7; fprintfixedshort();
|
signalled = 7; fprintfixedshort();
|
||||||
signalled = 8; fprintclosed();
|
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;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user