725 lines
23 KiB
Plaintext
725 lines
23 KiB
Plaintext
// fmt — formatting writers. Mirrors Hare's lib/fmt subset. Project #94
|
|
// fold-eFinal; io fold-2 (#5) graduated the sink to [[io.handle]].
|
|
//
|
|
// fprint / fprintln / fprintf / fprintfln
|
|
// write to an [[io.handle]] (= `(io.file |
|
|
// io.stream)`) — Hare's primary surface
|
|
// (ref/hare/fmt/print.ha:13). Errors via
|
|
// [[io.error]]. A file handle (raw fd) and a stream
|
|
// (`*io.vtable`) both flow in; [[io.write]]
|
|
// dispatches per arm.
|
|
//
|
|
// The process-stdio wrappers (print/println/errorln/printf/printfln/
|
|
// errorfln/fatal/fatalf) route through the fprint family over a file
|
|
// handle built from os.STD{OUT,ERR}_FILENO. The #5 handle convergence
|
|
// retired the prior `fd_ctx` shim — the fake-stream vtable around
|
|
// os.write that stood in before io.write took a handle.
|
|
//
|
|
// Call sites take Hare's variadic shape: `fmt.println(42, "hi", true)`
|
|
// gathers the args into a `[]formattable` slice; wrappers forward via
|
|
// `args...`.
|
|
|
|
package fmt;
|
|
|
|
import io;
|
|
import memio;
|
|
import os;
|
|
import strings;
|
|
import strconv;
|
|
|
|
// i64dec_buf — scratch buffer for [[i64dec]] below. Module-level
|
|
// because Hare's `strconv::i64tos` is a static-buffer view and we
|
|
// match that shape here. 21 bytes is enough for `-9223372036854775808`
|
|
// (20 digits + sign).
|
|
let i64dec_buf: [21]u8;
|
|
|
|
// i64dec — render `v` as a base-10 ASCII string into [[i64dec_buf]],
|
|
// returning a borrowed view. Preserved as a base-10 specialisation
|
|
// of strconv.i64tos for the pre-printf-family print path; the
|
|
// printf-family path below dispatches through strconv directly.
|
|
fn i64dec(v: i64) str = {
|
|
let neg: bool = false;
|
|
let n: i64 = v;
|
|
if (n < 0) { neg = true; n = -n; };
|
|
let tmp: [20]u8;
|
|
let i: i32 = 0;
|
|
if (n == 0) { tmp[0] = '0'; i = 1; };
|
|
for (n > 0) {
|
|
let d: i64 = n % 10i64;
|
|
tmp[i] = (d + 48i64): u8;
|
|
n = n / 10i64;
|
|
i += 1;
|
|
};
|
|
let out: i32 = 0;
|
|
if (neg) { i64dec_buf[out] = '-'; out += 1; };
|
|
for (i > 0) {
|
|
i -= 1;
|
|
i64dec_buf[out] = tmp[i];
|
|
out += 1;
|
|
};
|
|
let r: str;
|
|
r.ptr = &i64dec_buf[0];
|
|
r.len = out;
|
|
return r;
|
|
};
|
|
|
|
// 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 — 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);
|
|
|
|
// ---- internal stream formatters --------------------------------------
|
|
|
|
// putbytes — io.write(s, [ptr..ptr+n)). Internal helper; the inline
|
|
// `let v` slice synthesis composes the (ptr, len) triple each
|
|
// formattable arm carries.
|
|
fn putbytes(s: io.handle, p: *u8, n: i32) (size | io.error) = {
|
|
let v: []u8;
|
|
v.ptr = p;
|
|
v.len = n;
|
|
return io.write(s, v);
|
|
};
|
|
|
|
// writeone — emit one formattable through io.write.
|
|
fn writeone(s: io.handle, a: formattable) (size | io.error) = {
|
|
match (a) {
|
|
case let n: i64 => {
|
|
let v: str = i64dec(n);
|
|
return putbytes(s, v.ptr, v.len);
|
|
};
|
|
case let v: str => return putbytes(s, v.ptr, v.len);
|
|
case let b: bool => {
|
|
let v: str = "false";
|
|
if (b) { v = "true"; };
|
|
return putbytes(s, v.ptr, v.len);
|
|
};
|
|
case let r: rune => {
|
|
let buf: [4]u8;
|
|
buf[0] = r: u8;
|
|
return putbytes(s, &buf[0], 1);
|
|
};
|
|
case let v: f64 => {
|
|
// strconv.f64tos static-buffer view consumed before next
|
|
// strconv call.
|
|
let v2: str = strconv.f64tos(v);
|
|
return putbytes(s, v2.ptr, v2.len);
|
|
};
|
|
};
|
|
return 0: size;
|
|
};
|
|
|
|
// ---- {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). 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 < '0' || c > '9') {
|
|
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 == '}') { return; };
|
|
*pos += 1;
|
|
if (c == '-') { m.alignment = alignment.LEFT; }
|
|
else if (c == '=') { m.alignment = alignment.CENTER; }
|
|
else if (c == '_') {
|
|
if (*pos >= s.len) { fmtabort(); };
|
|
m.pad = s[*pos]: rune;
|
|
*pos += 1;
|
|
}
|
|
else if (c == ' ') { m.neg = neg.SPACE; }
|
|
else if (c == '+') { m.neg = neg.PLUS; }
|
|
else if (c == 'x') { m.base = strconv.base.HEX_LOWER; }
|
|
else if (c == 'X') { m.base = strconv.base.HEX_UPPER; }
|
|
else if (c == 'o') { m.base = strconv.base.OCT; }
|
|
else if (c == 'b') { m.base = strconv.base.BIN; }
|
|
else if (c == '.') {
|
|
m.prec = scandigits(s, pos);
|
|
}
|
|
else if (c >= '1' && c <= '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. Mirror print.ha.
|
|
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;
|
|
};
|
|
|
|
// rawlenf64 — bytes the raw render of `v` under `m` would emit; the
|
|
// strconv.f64tos static-buffer view is consumed by reading view.len +
|
|
// view.ptr[0] before the sign byte is folded into signof. drew NaN/Inf
|
|
// safe (strconv emits no leading '-' on nan/inf, so the peel is a no-op
|
|
// on those views). Mirror print.ha.
|
|
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] == '-') {
|
|
had_neg = true;
|
|
body -= 1;
|
|
};
|
|
let signlen: i32 = 0;
|
|
if (signof(had_neg, m) != 0u8) { signlen = 1; };
|
|
return signlen + body;
|
|
};
|
|
|
|
// rawlen — dispatch the rawlen sum over formattable. Mirror print.ha:53.
|
|
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;
|
|
case let v: f64 => return rawlenf64(v, m);
|
|
};
|
|
return 0; // unreachable — match is exhaustive
|
|
};
|
|
|
|
// formatraw — write the bare value (no width padding) to `s`. Mirror
|
|
// print.ha:76 format_raw. `prec` ignored on f64 (strconv.f64tos is
|
|
// shortest-G with no precision knob); base ignored on f64 (Hare too —
|
|
// base is int-only). drew NaN/Inf signoff: nan/infinity strings emitted
|
|
// unchanged.
|
|
fn formatraw(s: io.handle, arg: formattable, m: *mods) (size | io.error) = {
|
|
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: size = 0;
|
|
if (sb != 0u8) {
|
|
let buf: [1]u8;
|
|
buf[0] = sb;
|
|
let r: (size | io.error) = putbytes(s, &buf[0], 1);
|
|
match (r) {
|
|
case let n: size => { total += n; };
|
|
case let e: io.error => return e;
|
|
};
|
|
};
|
|
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] = '0';
|
|
let r: (size | io.error) = putbytes(s, &buf[0], 1);
|
|
match (r) {
|
|
case let n: size => { total += n; };
|
|
case let e: io.error => return e;
|
|
};
|
|
pi += 1;
|
|
};
|
|
let view: str = strconv.u64tos(u, m.base);
|
|
let r: (size | io.error) = putbytes(s, view.ptr, view.len);
|
|
match (r) {
|
|
case let n: size => { total += n; };
|
|
case let e: io.error => return e;
|
|
};
|
|
return total;
|
|
};
|
|
case let v: str => {
|
|
let n: i32 = rawlenstr(v, m);
|
|
return putbytes(s, v.ptr, n);
|
|
};
|
|
case let b: bool => {
|
|
let v: str = "false";
|
|
if (b) { v = "true"; };
|
|
return putbytes(s, v.ptr, v.len);
|
|
};
|
|
case let r: rune => {
|
|
let buf: [4]u8;
|
|
buf[0] = r: u8;
|
|
return putbytes(s, &buf[0], 1);
|
|
};
|
|
case let v: f64 => {
|
|
// strconv.f64tos prepends '-' for negative values; peel here so
|
|
// signof folds neg/plus/space mods uniformly with the i64 arm.
|
|
// drew NaN/Inf signoff: nan/infinity views have no leading '-',
|
|
// so this peel is a no-op for them.
|
|
let view: str = strconv.f64tos(v);
|
|
let neg_flag: bool = false;
|
|
if (view.len > 0 && view.ptr[0] == '-') {
|
|
neg_flag = true;
|
|
view.ptr = view.ptr + 1u64;
|
|
view.len -= 1;
|
|
};
|
|
let sb: u8 = signof(neg_flag, m);
|
|
let total: size = 0;
|
|
if (sb != 0u8) {
|
|
let buf: [1]u8;
|
|
buf[0] = sb;
|
|
let r: (size | io.error) = putbytes(s, &buf[0], 1);
|
|
match (r) {
|
|
case let n: size => { total += n; };
|
|
case let e: io.error => return e;
|
|
};
|
|
};
|
|
let r: (size | io.error) = putbytes(s, view.ptr, view.len);
|
|
match (r) {
|
|
case let n: size => { total += n; };
|
|
case let e: io.error => return e;
|
|
};
|
|
return total;
|
|
};
|
|
};
|
|
let z: size = 0; return z; // unreachable — match is exhaustive
|
|
};
|
|
|
|
// formatone — render `arg` to `s` with `m`'s width / alignment / pad
|
|
// applied. Mirror print.ha:45 format. The tail-pad loop drives on a
|
|
// counter because putbytes over memio.fixed reports a full buffer as a
|
|
// 0-byte partial write, not io.error — looping on `total < m.width`
|
|
// would spin on bsprintf when the sink runs out.
|
|
fn formatone(s: io.handle, arg: formattable, m: *mods) (size | io.error) = {
|
|
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: size = 0;
|
|
let i: i32 = 0;
|
|
let padb: [1]u8;
|
|
padb[0] = m.pad: u8;
|
|
for (i < start) {
|
|
let r: (size | io.error) = putbytes(s, &padb[0], 1);
|
|
match (r) {
|
|
case let n: size => { total += n; };
|
|
case let e: io.error => return e;
|
|
};
|
|
i += 1;
|
|
};
|
|
let r1: (size | io.error) = formatraw(s, arg, m);
|
|
match (r1) {
|
|
case let n: size => { total += n; };
|
|
case let e: io.error => return e;
|
|
};
|
|
let need: i32 = 0;
|
|
let twidth: size = m.width: size;
|
|
if (twidth > total) { need = (twidth - total): i32; };
|
|
let j: i32 = 0;
|
|
for (j < need) {
|
|
let r: (size | io.error) = putbytes(s, &padb[0], 1);
|
|
match (r) {
|
|
case let n: size => { total += n; };
|
|
case let e: io.error => return e;
|
|
};
|
|
j += 1;
|
|
};
|
|
return total;
|
|
};
|
|
|
|
// formatfield — field→formattable inline-per-arm dispatch through
|
|
// formatone. A field→formattable widen helper called from fprintf's
|
|
// for-loop would trip #18 (silent miscompile of 24B return-by-value in
|
|
// for-loop context); inline-per-arm sidesteps it. `*mods` arm aborts
|
|
// (parametric '%' form not implemented). Mirror print.ha:648.
|
|
fn formatfield(s: io.handle, f: field, m: *mods) (size | io.error) = {
|
|
match (f) {
|
|
case let v: i64 => {
|
|
let a: formattable = v;
|
|
return formatone(s, a, m);
|
|
};
|
|
case let v: str => {
|
|
let a: formattable = v;
|
|
return formatone(s, a, m);
|
|
};
|
|
case let b: bool => {
|
|
let a: formattable = b;
|
|
return formatone(s, a, m);
|
|
};
|
|
case let r: rune => {
|
|
let a: formattable = r;
|
|
return formatone(s, a, m);
|
|
};
|
|
case let v: f64 => {
|
|
let a: formattable = v;
|
|
return formatone(s, a, m);
|
|
};
|
|
case let p: *mods => { fmtabort(); let z: size = 0; return z; };
|
|
};
|
|
};
|
|
|
|
// ---- stream sinks ----------------------------------------------------
|
|
|
|
// fprint — write the formatted form of each `args` element to `s`,
|
|
// separated by spaces. Returns total bytes written or the first
|
|
// io.error. Mirrors ref/hare/fmt/print.ha (fprint) + wrappers.ha.
|
|
//
|
|
// A short write (sink accepts fewer bytes than asked) is reported by
|
|
// the returned count, not as an error — matches [[io.write]]'s contract
|
|
// per [[memio.fixed]]. Callers that need write-all semantics layer it
|
|
// on top, the same way they do over raw [[io.write]].
|
|
export fn fprint(s: io.handle, args: formattable...) (size | io.error) = {
|
|
let total: size = 0;
|
|
let i: i32 = 0;
|
|
for (i < args.len) {
|
|
if (i > 0) {
|
|
let r: (size | io.error) = putbytes(s, " ".ptr, 1);
|
|
match (r) {
|
|
case let n: size => { total += n; };
|
|
case let e: io.error => return e;
|
|
};
|
|
};
|
|
let r: (size | io.error) = writeone(s, args[i]);
|
|
match (r) {
|
|
case let n: size => { total += n; };
|
|
case let e: io.error => return e;
|
|
};
|
|
i += 1;
|
|
};
|
|
return total;
|
|
};
|
|
|
|
// fprintf — Hare's primary printf-family surface. Mirrors print.ha:26.
|
|
// Returns total bytes written or io.error on the first sink failure.
|
|
export fn fprintf(s: io.handle, fmt: str, args: field...) (size | io.error) = {
|
|
let total: size = 0;
|
|
let i: i32 = 0;
|
|
let nextimpl: i32 = 0;
|
|
let checkunused: bool = true;
|
|
for (i < fmt.len) {
|
|
let c: u8 = fmt[i];
|
|
if (c == '{') {
|
|
i += 1;
|
|
if (i >= fmt.len) { fmtabort(); };
|
|
if (fmt[i] == '{') { // '{{' literal
|
|
let r: (size | io.error) = putbytes(s, fmt.ptr + i: u64, 1);
|
|
match (r) {
|
|
case let n: size => { total += n; };
|
|
case let e: io.error => return e;
|
|
};
|
|
i += 1;
|
|
} else {
|
|
let idx: i32 = 0;
|
|
let d: u8 = fmt[i];
|
|
if (d >= '0' && d <= '9') {
|
|
checkunused = false;
|
|
idx = scandigits(fmt, &i);
|
|
} else {
|
|
idx = nextimpl;
|
|
nextimpl += 1;
|
|
};
|
|
let m: mods;
|
|
modsinit(&m);
|
|
if (i < fmt.len && fmt[i] == ':') {
|
|
i += 1;
|
|
scanmods(fmt, &i, &m);
|
|
};
|
|
if (i >= fmt.len || fmt[i] != '}') { fmtabort(); };
|
|
i += 1;
|
|
if (idx >= args.len) { fmtabort(); };
|
|
let r: (size | io.error) = formatfield(s, args[idx], &m);
|
|
match (r) {
|
|
case let n: size => { total += n; };
|
|
case let e: io.error => return e;
|
|
};
|
|
};
|
|
} else if (c == '}') {
|
|
i += 1;
|
|
if (i >= fmt.len || fmt[i] != '}') { fmtabort(); };
|
|
let r: (size | io.error) = putbytes(s, fmt.ptr + i: u64, 1);
|
|
match (r) {
|
|
case let n: size => { total += n; };
|
|
case let e: io.error => return e;
|
|
};
|
|
i += 1;
|
|
} else {
|
|
let r: (size | io.error) = putbytes(s, fmt.ptr + i: u64, 1);
|
|
match (r) {
|
|
case let n: size => { total += n; };
|
|
case let e: io.error => return e;
|
|
};
|
|
i += 1;
|
|
};
|
|
};
|
|
if (checkunused && nextimpl != args.len) { fmtabort(); };
|
|
return total;
|
|
};
|
|
|
|
// fprintln — fprint plus a trailing newline. Mirrors wrappers.ha.
|
|
export fn fprintln(s: io.handle, args: formattable...) (size | io.error) = {
|
|
let total: size = 0;
|
|
match (fprint(s, args...)) {
|
|
case let n: size => { total = n; };
|
|
case let e: io.error => return e;
|
|
};
|
|
match (putbytes(s, "\n".ptr, 1)) {
|
|
case let n: size => { total += n; };
|
|
case let e: io.error => return e;
|
|
};
|
|
return total;
|
|
};
|
|
|
|
// fprintfln — fprintf plus a trailing newline. Mirrors wrappers.ha:69.
|
|
export fn fprintfln(s: io.handle, fmt: str, args: field...) (size | io.error) = {
|
|
let total: size = 0;
|
|
match (fprintf(s, fmt, args...)) {
|
|
case let n: size => { total = n; };
|
|
case let e: io.error => return e;
|
|
};
|
|
match (putbytes(s, "\n".ptr, 1)) {
|
|
case let n: size => { total += n; };
|
|
case let e: io.error => return e;
|
|
};
|
|
return total;
|
|
};
|
|
|
|
// bsprintf — render into `buf` through memio.fixed; return the str view
|
|
// of bytes actually written. Mirrors wrappers.ha:42. memio.fixed returns
|
|
// the `stream` BY VALUE — the stream lives in this frame; `&st.vt` is
|
|
// the io.stream and `&st` the accessor handle. Hare returns
|
|
// `(const str | nomem)`; ww collapses to (str | io.error) so the
|
|
// fprintf path's io.error arm stays uniform. Short writes surface as a
|
|
// prefix (memio.fixed contract).
|
|
export fn bsprintf(buf: []u8, fmt: str, args: field...) (str | io.error) = {
|
|
let st: memio.stream = memio.fixed(buf);
|
|
let s: io.stream = &st.vt;
|
|
match (fprintf(s, fmt, args...)) {
|
|
case let n: size => { return memio.string(&st); };
|
|
case let e: io.error => return e;
|
|
};
|
|
};
|
|
|
|
// asprintf — render `fmt`/`args` into a heap-allocated str through
|
|
// memio.dynamic, shrink-copy to a tight allocation, close the dynamic
|
|
// backing. Mirrors wrappers.ha:29 modulo: bare `str` return (Hare
|
|
// returns `(str | nomem)`; ww has no `nomem` variant on the public
|
|
// surface — os.alloc faults on OOM per lib/os.ww). Caller frees with
|
|
// `os.free(r.ptr, r.len: u64)` when r.len > 0; r.len == 0 is a no-op
|
|
// free. Shrink-to-fit: memio.dynamic's cap doubles past pos during
|
|
// growth; io.close frees the cap-sized mapping. Returning
|
|
// memio.string directly would leak the cap-vs-len slack (skip close) or
|
|
// dangle the view (close first); the copy lets the caller free with
|
|
// r.len.
|
|
export fn asprintf(fmt: str, args: field...) str = {
|
|
let out: str;
|
|
out.ptr = nil;
|
|
out.len = 0;
|
|
let st: memio.stream = memio.dynamic();
|
|
let s: io.stream = &st.vt;
|
|
let wres: (size | io.error) = fprintf(s, fmt, args...);
|
|
match (wres) {
|
|
case let n: size => {};
|
|
case let e: io.error => {};
|
|
};
|
|
let view: str = memio.string(&st);
|
|
if (view.len == 0) {
|
|
let cres: (void | io.error) = io.close(s);
|
|
match (cres) { case void => {}; case let e: io.error => {}; };
|
|
return out;
|
|
};
|
|
let tight: []u8 = alloc([], view.len: u64)!;
|
|
let i: i32 = 0;
|
|
for (i < view.len) {
|
|
tight[i] = view.ptr[i];
|
|
i += 1;
|
|
};
|
|
let cres: (void | io.error) = io.close(s);
|
|
match (cres) { case void => {}; case let e: io.error => {}; };
|
|
tight.len = view.len;
|
|
return strings.frombytes(tight);
|
|
};
|
|
|
|
// ---- process-stdio wrappers -----------------------------------------
|
|
//
|
|
// Mirror ref/hare/fmt/wrappers.ha. Hare routes these through
|
|
// os::stdout / os::stderr (io::handle); ww's os plays the sys role and
|
|
// can't import io (import floor), so it exports the std fd NUMBERS
|
|
// (os.STD{OUT,ERR}_FILENO) and the io.file binding is cast at the call
|
|
// site — `os.STDOUT_FILENO: io.file` widens into the fprint handle param
|
|
// (io fold-2, #5). errorf / asprint / bsprint omitted: the bare `error`
|
|
// name collides with strconv.error under the driver's flat-scope concat;
|
|
// ship the -ln forms only.
|
|
|
|
// print / println — wrappers.ha:78/:84 on stdout.
|
|
export fn print(args: formattable...) (size | io.error) = {
|
|
return fprint(os.STDOUT_FILENO: io.file, args...);
|
|
};
|
|
|
|
export fn println(args: formattable...) (size | io.error) = {
|
|
return fprintln(os.STDOUT_FILENO: io.file, args...);
|
|
};
|
|
|
|
// errorln — wrappers.ha:96 on stderr.
|
|
export fn errorln(args: formattable...) (size | io.error) = {
|
|
return fprintln(os.STDERR_FILENO: io.file, args...);
|
|
};
|
|
|
|
// fatal — errorln then exit(255). `never` return marks the bottom type
|
|
// so flow-control checks treat callers as terminated. Mirrors
|
|
// wrappers.ha:63.
|
|
export fn fatal(args: formattable...) never = {
|
|
fprintln(os.STDERR_FILENO: io.file, args...);
|
|
os.exit(255);
|
|
};
|
|
|
|
// printf / printfln — wrappers.ha:10/:15 on stdout.
|
|
export fn printf(fmt: str, args: field...) (size | io.error) = {
|
|
return fprintf(os.STDOUT_FILENO: io.file, fmt, args...);
|
|
};
|
|
|
|
export fn printfln(fmt: str, args: field...) (size | io.error) = {
|
|
return fprintfln(os.STDOUT_FILENO: io.file, fmt, args...);
|
|
};
|
|
|
|
// errorfln — wrappers.ha:24 on stderr.
|
|
export fn errorfln(fmt: str, args: field...) (size | io.error) = {
|
|
return fprintfln(os.STDERR_FILENO: io.file, fmt, args...);
|
|
};
|
|
|
|
// fatalf — errorfln then exit(255). Mirrors wrappers.ha:54.
|
|
export fn fatalf(fmt: str, args: field...) never = {
|
|
fprintfln(os.STDERR_FILENO: io.file, fmt, args...);
|
|
os.exit(255);
|
|
};
|