// vstream — Hare-shaped vtable wrappers over the logger surface. // Project #94 fold-e5 (Option C, parallel API for lib/log). Last // per-caller migration before fold-eFinal (task #50). // // Adds the ten _v variants of OLD log.ww's surface — new_v / // lprintln_v / println_v / lprintfln_v / printfln_v / lfatal_v / // fatal_v / lfatalf_v / fatalf_v / setlogger_v — alongside a vlogger // vtable + vstdlogger over an io.vstream sink. The OLD surface stays // untouched here — fold-eFinal (task #50) atomically flips the // package shape: deletes the OLD logger / stdlogger / globals + the // pre-vtable stderrsink + ensureinit, renames the `_v` suffix off // (drops to bare `new` / `println` / etc. per Hare's // ref/hare/log/{logger,funcs,global,silent}.ha), and migrates the // few callers (lib/log/logtest.ww plus any downstream). // // drew acks (3/3): // - module-static stderrsink_ctx (zero-init per #129 A.2/A.3 SSoT) // - vt FIRST field intrusive cast (matches fmt/memio/bufio fold-e // ctxs) // - all 10 OLD fns get _v variants (Option-C consistency) // // ken mandates honored: // - bootstrap byte-id mechanical pre==post: log is NOT embedded // in any selfhost/cmd/*/main.combined.ww (grep-confirmed // pre-impl), so adding vstream.ww + minimal export bumps in // lib/fmt does not perturb the embedded surfaces. 990-997 // gates stay green by virtue of log being test-only. // - module-static ctx via #129 A.2/A.3 (cs == ww symmetric) // - ctx shape simple scalars/ptrs (vt is the established intrusive // first-field idiom; the only other field is `fd: i32`. No f32 // per #165b; no nested aggregate beyond vt per #18). // // Hare reference: ref/hare/log/{logger.ha,funcs.ha,global.ha,silent.ha} // + ref/hare/log/log.ha (re-export hub). // // Cast workaround per #206: bare `&fn_name` does not type-check as // a `(* | void)` field-init / let-binding. Explicit // `(&fn_name): *io.` cast at each store site is the Hare- // faithful minimum-touch route — same workaround memio/vstream.ww // + fmt/vstream.ww + bufio/vstream.ww use. 2 cast tokens here at // the stderrsink_ctx_g vt wire-up (reader + writer; no closer — // the process owns fd 2, same divergence as OLD stderrclose at // log.ww:162). Both casts drop out wholesale once #206 closes. // // Chained-N_DOT-on-module-static field-assign per OLD log.ww:134-140 // (`_default.logger.println = stdprintln;`) — proven-working spine. // stderrsink_ctx_g.vt.X writes pattern through same shape; the // tagged-union assign with widening cast composes via #199 α // (concrete *fn → NAMED variant of (*reader | void) parent). // // Newline emission: vfprint / vfprintf are bare (no trailing \n); // Hare's ref/hare/fmt/wrappers.ha:60-68 appends the newline at the // fprintln site (after fprint). ww mirrors: stdprintln_v / stdprintfln_v // call fmt.vfprint / fmt.vfprintf then io.st_write a one-byte slice // `[10u8]` for the LF. Same shape OLD log.stdprintln uses via // fmt.fprintln (log.ww:169). // // Sibling tasks parked here (filed, NOT fixed): // // - eFinal (#50): atomic flip + delete OLD log surface + drop the // `_v` suffix wholesale (graduates to Hare's bare names). // // - #206 (bare &fn → (*alias|void)): 2 cast sites at ensureinit_v; // drop out wholesale on close. // // - #173 (TRY-on-tagged-return both-stages broken): stderrwrite_v // constructs nomem and widens to io.error explicitly rather than // using `os.trywrite(...)?`; same shape memio.vstream.ww + // fmt.vstream.ww + bufio.vstream.ww adopt. // // - #207 (struct-lit multi-tagged-field copy drops past first): // not bitten here — module-static stderrsink_ctx_g is zero-init // at link time per #129; vt fields wired via post-decl chained // assigns rather than struct-lit, sidestepping #207 wholesale. // The pattern is the module-static analogue of memio.vstream.ww's // alloc-zero-chain. // // - #209 (wwstage formattable match-arm bail when fmt imported): // log imports fmt transitively (via the formattable / field // types in vlogger's vtable + fmt.vfprint / vfprintf calls in // the std callbacks). All probe rows in test/wcc/779 run // STAGE_CS-only; eFinal closes the wwstage half when #209 lands. package log; import fmt; import io; import os; // stderrsink_ctx — module-static state for the default vlogger's // stderr sink. vt FIRST field for the intrusive vstream→*stderrsink_ctx // cast in stderrwrite_v. Mirrors fmt.fd_ctx (fmt/vstream.ww:66) + // memio.fixed_ctx (memio/vstream.ww:48); only scalars/ptrs beyond // vt per ken's mandate. type stderrsink_ctx = struct { vt: io.vtable, fd: i32, }; // Zero-init at link time per #129 A.2/A.3 SSoT; ensureinit_v wires // vt.reader / vt.writer + fd lazily on first dispatch. Same shape // OLD log.ww uses for the pre-vtable stderrsink (log.ww:104). let stderrsink_ctx_g: stderrsink_ctx; // vlogger — V-side counterpart to OLD logger (log.ww:85). Same shape // modulo the `_v` field-suffix (collapses on #50). Dispatchers route // through io.vstream rather than *io.stream. // // Mirrors ref/hare/log/logger.ha:9. export type vlogger = struct { println_v: fn(l: *vlogger, args: fmt.formattable...) void, printfln_v: fn(l: *vlogger, format: str, fields: fmt.field...) void, }; // vstdlogger — concrete vlogger forwarding to an io.vstream sink. // First-field embed: `&sl.logger` yields a `*vlogger` (intrusive // outer→inner cast); the vtable callback recovers the outer via // `*vstdlogger` cast. // // Mirrors ref/hare/log/logger.ha:18 (stdlogger). export type vstdlogger = struct { logger: vlogger, sink: io.vstream, }; // _silent_v / _default_v — backing storage for the silent_v / // default_v *vlogger globals. Zero-init at link time; ensureinit_v // fills the println_v / printfln_v slots. let _silent_v: vlogger; let _default_v: vstdlogger; // silent_v / default_v / global_v — Hare-style *vlogger globals. // Zero-init at link time; lazily wired by ensureinit_v on the first // call to any exported fn. Same divergence as OLD log.ww:116-118 // (header rationale for the lazy-init route applies wholesale: ww // cstage emit_lets only constexprs primitive / str-literal / // array-literal lets — struct- and pointer-literal init aren't // reachable). #50 / eFinal graduates if static-init lands. export let silent_v: *vlogger; export let default_v: *vlogger; export let global_v: *vlogger; // initdone_v — guards ensureinit_v so the one-shot wiring runs once. // Mirrors OLD initdone (log.ww:122) + lib/temp's rnginit pattern. let initdone_v: i32 = 0; fn ensureinit_v() void = { if (initdone_v != 0) { return; }; stderrsink_ctx_g.fd = 2; stderrsink_ctx_g.vt.reader = (&stderrread_v): *io.reader; stderrsink_ctx_g.vt.writer = (&stderrwrite_v): *io.writer; _silent_v.println_v = silentprintln_v; _silent_v.printfln_v = silentprintfln_v; _default_v.logger.println_v = stdprintln_v; _default_v.logger.printfln_v = stdprintfln_v; _default_v.sink = &stderrsink_ctx_g.vt; silent_v = &_silent_v; default_v = &_default_v.logger; global_v = default_v; initdone_v = 1; }; // stderrread_v / stderrwrite_v — vtable callbacks for the private // stderr sink. Read is eof-only — lib/log writes; never reads. // Mirror of OLD stderrread / stderrwrite (log.ww:148-160) modulo the // (size | io.eof | io.error) / (size | io.error) return surface; // -errno collapses to a nomem-widened io.error per the // fmt.fdsinkwrite_v precedent (fmt/vstream.ww:88-92). fn stderrread_v(s: io.vstream, buf: []u8) (size | io.eof | io.error) = { let e: io.eof; return e; }; fn stderrwrite_v(s: io.vstream, buf: []u8) (size | io.error) = { let c: *stderrsink_ctx = s: *stderrsink_ctx; let r: i64 = os.write(c.fd, buf.ptr, buf.len: u64); if (r < 0) { let nm: nomem; let e: io.error = nm; return e; }; return r: size; }; // stdprintln_v / stdprintfln_v — vtable callbacks for vstdlogger. // Forwards to fmt.vfprint / vfprintf on the sink + appends the // trailing newline via io.st_write. Hare's fmt.fprintln emits the // newline inside one wrapper (ref/hare/fmt/wrappers.ha:60-68); we // inline that split at the log site because fmt.vfprint is the bare // (no-newline) primitive. The (size | io.error) results are dropped // — lib/log's surface is `void` (matches Hare + OLD stdprintln at // log.ww:167). fn stdprintln_v(l: *vlogger, args: fmt.formattable...) void = { let sl: *vstdlogger = l: *vstdlogger; fmt.vfprint(sl.sink, args...); let nl: [1]u8; nl[0] = 10u8; let v: []u8 = nl[0:1]; io.st_write(sl.sink, v); }; fn stdprintfln_v(l: *vlogger, format: str, fields: fmt.field...) void = { let sl: *vstdlogger = l: *vstdlogger; fmt.vfprintf(sl.sink, format, fields...); let nl: [1]u8; nl[0] = 10u8; let v: []u8 = nl[0:1]; io.st_write(sl.sink, v); }; // silentprintln_v / silentprintfln_v — vtable callbacks for the // silent vlogger. Discard every record without dispatching through // fmt; keeps silent truly silent if fmt ever gets stateful. Mirror // of OLD silentprintln / silentprintfln (log.ww:183-187). fn silentprintln_v(l: *vlogger, args: fmt.formattable...) void = { }; fn silentprintfln_v(l: *vlogger, format: str, fields: fmt.field...) void = { }; // ---- public API (10 _v variants) ----------------------------------------- // new_v — build a vstdlogger over `sink`, returned BY VALUE (fold-eFinal // PREP). Hare returns the stdlogger by value (ref/hare/log/logger.ha:20); // the OLD out-parameter shape existed only because wide-struct sret was // unimplemented — it now round-trips (test/wcc/925), so the constructor // builds in a local and `return r;` (proven field-by-field sret form). // vstdlogger is 24B (two fn-ptrs + sink), returned via the SysV memory // class; the caller owns the returned logger (stack ownership, no-GC) // and passes `&sl.logger` to the dispatch fns. export fn new_v(sink: io.vstream) vstdlogger = { ensureinit_v(); let r: vstdlogger; r.logger.println_v = stdprintln_v; r.logger.printfln_v = stdprintfln_v; r.sink = sink; return r; }; // lprintln_v — dispatch `args` through `log`. Mirrors OLD lprintln // (log.ww:202) + ref/hare/log/funcs.ha:8. export fn lprintln_v(log: *vlogger, args: fmt.formattable...) void = { ensureinit_v(); log.println_v(log, args...); }; // println_v — dispatch through the [[global_v]] vlogger. Mirrors // OLD println (log.ww:208) + ref/hare/log/funcs.ha:18. export fn println_v(args: fmt.formattable...) void = { ensureinit_v(); lprintln_v(global_v, args...); }; // lfatal_v — lprintln_v to `log` then exit(255). `never` return // marks the bottom type so flow-control checks treat callers as // terminated. Mirrors OLD lfatal (log.ww:216) + // ref/hare/log/funcs.ha:28. export fn lfatal_v(log: *vlogger, args: fmt.formattable...) never = { lprintln_v(log, args...); os.exit(255); }; // fatal_v — lprintln_v to [[global_v]] then exit(255). Mirrors OLD // fatal (log.ww:223) + ref/hare/log/funcs.ha:45. export fn fatal_v(args: fmt.formattable...) never = { println_v(args...); os.exit(255); }; // setlogger_v — install `log` as the [[global_v]] vlogger. Mirrors // OLD setlogger (log.ww:230) + ref/hare/log/global.ha:20. export fn setlogger_v(log: *vlogger) void = { ensureinit_v(); global_v = log; }; // lprintfln_v — dispatch a format-string record through `log`. // Mirrors OLD lprintfln (log.ww:237) + ref/hare/log/funcs.ha:13. export fn lprintfln_v(log: *vlogger, format: str, fields: fmt.field...) void = { ensureinit_v(); log.printfln_v(log, format, fields...); }; // printfln_v — dispatch through the [[global_v]] vlogger. Mirrors // OLD printfln (log.ww:244) + ref/hare/log/funcs.ha:23. export fn printfln_v(format: str, fields: fmt.field...) void = { ensureinit_v(); lprintfln_v(global_v, format, fields...); }; // lfatalf_v — lprintfln_v to `log` then exit(255). Mirrors OLD // lfatalf (log.ww:251) + ref/hare/log/funcs.ha:35. export fn lfatalf_v(log: *vlogger, format: str, fields: fmt.field...) never = { lprintfln_v(log, format, fields...); os.exit(255); }; // fatalf_v — lprintfln_v to [[global_v]] then exit(255). Mirrors // OLD fatalf (log.ww:258) + ref/hare/log/funcs.ha:52. export fn fatalf_v(format: str, fields: fmt.field...) never = { printfln_v(format, fields...); os.exit(255); };