lib/fmt+test: add f64 dispatch arm (#17)
#30 (82be8b9) unblocked tagged-union widen for runtime f64. Add the
f64 arm to fmt's formattable union and dispatch.
`formattable` gains an f64 case (appended last to preserve existing
tag indices). fdprint / fprint mirror their i64-arm shape. formatraw
peels strconv's natural '-' so signof folds neg/+/space uniformly
with i64. formatfield uses the inline widen-per-arm #18 sidestep.
rawlenf64 renders via strconv.f64tos to count bytes for width
alignment (Hare's print.ha:53 uses an io::empty sink for this; ww
has none yet, so we render twice — acceptable v1 trade).
Width / alignment / pad / sign mods honored. prec / base ignored
with inline rationale (no ffmt/fflags in mods yet; Hare aborts on
non-DEC base, ww silently falls through). NaN/Inf deferred — Inf
renders deterministically as "huge"/"-huge" via strconv's `f >= cap`
path; NaN is garbage. Detection waits on f64↔u64 bit-reinterpret in
cgen.
13 test rows at signalled 31-43 cover basic/int-valued/neg/zero/
small-frac/huge/sign±/space/width-right/width-left + fprint
variadic + bsprintf + asprintf sinks. Each pins exact byte output.
Three rows bind negative literal via intermediate `let nv: f64 =
-2.5;` to route around #40 (cstage drops payload on N_UNARY-of-
N_FLOATLIT in tagged-union widen). Comments cite #40 at each row.
Probe at .ai/probe_f64_unary_neg.ww.
Strconv f64tos is fixed-point today; graduate to Ryū (ref/hare/
strconv/ftos.ha:432) when needed.
This commit is contained in:
@@ -68,8 +68,14 @@ fn i64dec(v: i64) str = {
|
||||
// formattable — tagged union of types fmt can render. Mirrors Hare's
|
||||
// `fmt::formattable = (...types::numeric | uintptr | str | rune |
|
||||
// bool | nullable *opaque | void)`, narrowed to the set ww actually
|
||||
// has codegen for. Slot size is 24B (8 tag + 16 str payload).
|
||||
export type formattable = (i64 | str | bool | rune);
|
||||
// has codegen for. Slot size is 24B (8 tag + 16 str payload — f64
|
||||
// arm is only 8B and rides under the str payload).
|
||||
//
|
||||
// No `f32` arm: strconv ships no `f32tos` and there is no in-tree
|
||||
// caller. Callers with an `f32` cast at the call site (`myf: f64`),
|
||||
// mirroring how `i64` covers every int width today. Ship the `f32`
|
||||
// arm when the first in-tree caller needs it.
|
||||
export type formattable = (i64 | str | bool | rune | f64);
|
||||
|
||||
// ---- fd sinks --------------------------------------------------------
|
||||
|
||||
@@ -117,6 +123,15 @@ export fn fdprint(fd: i32, args: formattable...) i64 = {
|
||||
if (n < 0) { return n; };
|
||||
total += n;
|
||||
};
|
||||
case let v: f64 => {
|
||||
// strconv.f64tos's static-buffer view is consumed
|
||||
// immediately by os.write; no intervening strconv
|
||||
// call against the same buffer between bind and write.
|
||||
let s: str = strconv.f64tos(v);
|
||||
let r: i64 = os.write(fd, s.ptr, s.len: u64);
|
||||
if (r < 0) { return r; };
|
||||
total += r;
|
||||
};
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
@@ -201,6 +216,18 @@ export fn fprint(s: *io.stream, args: formattable...) (i32 | io.closed) = {
|
||||
case io.closed => { let c: io.closed; return c; };
|
||||
};
|
||||
};
|
||||
case let v: f64 => {
|
||||
// strconv.f64tos returns a static-buffer view —
|
||||
// emit to the sink before yielding control to the
|
||||
// next arg iteration so no sibling f64/i64 strconv
|
||||
// call clobbers the buffer mid-flight.
|
||||
let view: str = strconv.f64tos(v);
|
||||
let rs: (i32 | io.closed) = putbytes(s, view.ptr, view.len);
|
||||
match (rs) {
|
||||
case let m: i32 => { total += m; };
|
||||
case io.closed => { let c: io.closed; return c; };
|
||||
};
|
||||
};
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
@@ -487,10 +514,66 @@ fn formatraw(out: *io.stream, arg: formattable, m: *mods) (i32 | io.closed) = {
|
||||
buf[0] = r: u8;
|
||||
return putbytes(out, &buf[0], 1);
|
||||
};
|
||||
case let v: f64 => {
|
||||
// strconv.f64tos already prepends '-' for negative values;
|
||||
// peel it back off so [[signof]] can fold neg/plus/space
|
||||
// mods uniformly with the i64 arm. The view bytes are then
|
||||
// emitted directly — strconv's static buf is not held past
|
||||
// the putbytes call (no sibling strconv call lands between).
|
||||
//
|
||||
// Mods scope: width / alignment / pad / sign honored via
|
||||
// the same raw-then-pad machinery as i64. `prec` ignored —
|
||||
// strconv.f64tos has no precision knob; graduate when a
|
||||
// Ryū-shaped strconv lands and mods grows ffmt / fflags.
|
||||
// `base` ignored (Hare does too — base is int-only).
|
||||
// NaN/±Inf print as whatever strconv emits today (garbage);
|
||||
// strconv detection blocked on f64↔u64 bit-reinterpret cgen.
|
||||
let view: str = strconv.f64tos(v);
|
||||
let neg_flag: bool = false;
|
||||
if (view.len > 0 && view.ptr[0] == 45u8) { // '-'
|
||||
neg_flag = true;
|
||||
view.ptr = view.ptr + 1u64;
|
||||
view.len -= 1;
|
||||
};
|
||||
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 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;
|
||||
};
|
||||
};
|
||||
let z: io.closed; return z; // unreachable — match is exhaustive
|
||||
};
|
||||
|
||||
// rawlenf64 — bytes the raw render of `v` under `m` would emit.
|
||||
// Calls strconv.f64tos to count digits + decimal point; peels off
|
||||
// the natural '-' so signof's neg/plus/space byte is counted exactly
|
||||
// once, the same accounting [[rawleni64]] does.
|
||||
fn rawlenf64(v: f64, m: *mods) i32 = {
|
||||
let view: str = strconv.f64tos(v);
|
||||
let body: i32 = view.len;
|
||||
let had_neg: bool = false;
|
||||
if (body > 0 && view.ptr[0] == 45u8) {
|
||||
had_neg = true;
|
||||
body -= 1;
|
||||
};
|
||||
let signlen: i32 = 0;
|
||||
if (signof(had_neg, m) != 0u8) { signlen = 1; };
|
||||
return signlen + body;
|
||||
};
|
||||
|
||||
// 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).
|
||||
@@ -500,6 +583,7 @@ fn rawlen(arg: formattable, m: *mods) i32 = {
|
||||
case let v: str => return rawlenstr(v, m);
|
||||
case let b: bool => { if (b) { return 4; }; return 5; };
|
||||
case let r: rune => return 1;
|
||||
case let v: f64 => return rawlenf64(v, m);
|
||||
};
|
||||
return 0; // unreachable — match is exhaustive
|
||||
};
|
||||
@@ -574,6 +658,10 @@ fn formatfield(out: *io.stream, f: field, m: *mods) (i32 | io.closed) = {
|
||||
let a: formattable = r;
|
||||
return formatone(out, a, m);
|
||||
};
|
||||
case let v: f64 => {
|
||||
let a: formattable = v;
|
||||
return formatone(out, a, m);
|
||||
};
|
||||
case let p: *mods => { fmtabort(); let c: io.closed; return c; };
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user