Files
ww/lib/log/vstream.ww
Hojun-Cho 10cb835f99 lib: complete the parallel vstream surface to Hare value-return shape (#94 fold-eFinal prep)
The #94 Option-C vstream surface was left incomplete and structurally
divergent from Hare: constructors heap-allocated and returned (X | nomem)
or used out-params instead of Hare's by-value stack ownership; memio lacked
reset/buffer/borrowedread; bufio's scanner was never ported to the vtable.
This is the additive half of the eFinal collapse — OLD surface stays fully
live; the destructive FLIP (delete OLD + drop _v + repoint) is the next
commit.

Reshape all constructors to VALUE-RETURN (field-by-field sret; the heap +
nomem was an unnecessary crutch — wide slice-bearing struct return-by-value
is byte-id-proven, cf 925_sret_struct_return_run). memio fixed/dynamic/
dynamicfrom, bufio init, log new now return the struct by value; the nomem
is gone with the alloc that forced it.

memio: unify the per-flavour ctx structs onto one `stream` (vt at offset 0);
collapse fixed_string + dynamic_string into a single string() over the common
header (bare-str return is the ratified rule-9 frombytes carve-out, cited at
the site per ref/hare/memio/stream.ha:81); port reset/buffer/borrowedread as
single fns over the header.

bufio: collapse the EXISTING scanner subset (newscannerbuf/scanbyte/scanbytes/
scanline/finish + setflush/flush/unread/isbuffered) onto the vtable, with src
now io.vstream so reads go through io.st_read. The Hare scanner functions ww
never implemented (scanrune/scanstring-arbitrary-delim/readtok/readline/
auto-grow newscanner) are out of scope and deferred to #217 — eFinal is a
collapse, not a feature expansion.

Keep the explicit (&fn): *io.T casts on vtable-slot stores (cgen-neutral;
avoids the #214 (X|void) over-acceptance surface; dropping the casts is a
deferred #206 payoff gated on #214).

Self-gate: 776 (memio) 18/18 and 778 (bufio) 27/27, every row carrying a
cs.s == ww.s byte-id check — bufio/fmt/log are not compiler-embedded, so
these rows are their only byte-id coverage. 779/781 stay STAGE_CS-only
pending #209. Regen w6c+wwdump combined.ww (io+memio are the embedded
modules).
2026-05-29 21:14:39 +09:00

304 lines
12 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 `(*<alias> | void)` field-init / let-binding. Explicit
// `(&fn_name): *io.<role>` 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);
};