// vstream — Hare-shaped vtable wrappers over [[bufio.stream]]. Project // #94 fold-e4 (Option C, parallel API for lib/bufio). // // Adds bufio_vstream + isbuffered_v alongside the pre-vtable // bufio.init / bufio.isbuffered surface in bufio.ww. The OLD surface // stays untouched here — fold-eFinal (task #50) atomically flips the // package shape: deletes the OLD constructors + callbacks, renames // `_v` suffix off (drops the `isbuffered_v` to `isbuffered` per Hare's // ref/hare/bufio/stream.ha:179), and migrates the few callers. // // Hare's ref/hare/bufio/stream.ha:69 (init) returns the stream by // value, with `vtable: nullable *io::vtable` pre-resolved per-mode // (vtable_r / vtable_w / vtable_rw module-static singletons at // stream.ha:8-25); ww doesn't ship static-storage tagged-union // singletons yet, so we heap-allocate bufio_ctx, fill the vtable // post-alloc per slot, and return &c.vt as the io.vstream. Same // intrusive shape memio/vstream.ww + fmt/vstream.ww use. // // drew-approved deferrals (Hare-shape, not collapsed here): // // - Hare uses three vtable singletons (vtable_r / vtable_w / // vtable_rw) to discriminate r-only / w-only / r+w mode; // bufio_vstream always installs all three callbacks per // bufio.ww:32-38's pre-existing divergence (zero-length rbuf or // wbuf degenerates the matching callback in-cb). The Hare-shape // mode-keyed singletons graduate with io fold-2 (handle). // // - Hare's defer-handle (`flag::MANAGED_*` ownership bits) deferred // to io fold-2 (drew). Caller owns rbuf/wbuf/src for now; // bclose_v flushes and forwards close to src but doesn't free // any buffers (mirrors bufio.ww:60-64 OLD-side ownership note). // // 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 // and fmt/vstream.ww use. 3 cast tokens at the vtable wire-up site, // plus 2 in isbuffered_v for the fn-ptr-equality comparand. All 5 // drop out wholesale once #206 closes. // // Slice-field workaround per #210 (sibling of #207, surfaced here): // struct-lit slice-typed field through `alloc(bufio_ctx{rbuf = rbuf, // ...})?` SILENTLY drops the slice (reads back as zero-length). // Scalar i32 and pointer fields in the same struct-lit populate // correctly. Workaround: post-alloc `c.rbuf = rbuf;` field-assign, // which works through the *bufio_ctx pointer. Same alloc-zero-chain // shape as memio/vstream.ww uses for #207 over vt's tagged fields. // 3 slice-field stores (rbuf / wbuf / flush) move post-alloc. // Drops out wholesale on #210 close. // // fn-ptr-equality discriminator per ref/hare/bufio/stream.ha:179-184: // isbuffered_v pattern-extracts the reader/writer slot and compares // against `(&bread_v): *io.reader` / `(&bwrite_v): *io.writer`. Hare // uses static-singleton symbol-address equality; ww routes through // the cast-then-eq form because each slot is a tagged union // (`(*reader | void)`) rather than a plain pointer. eFinal collapses // to the Hare-direct form (`s.reader == &read`) once #192 closes and // the slot becomes a plain nullable. // // io.closed → io.error conversion at the OLD-API boundary: the // underlying *io.stream's io.read/io.write return (.. | io.closed) // but the V API surface returns (.. | io.error). We construct nomem // and widen to io.error per the fdsinkwrite_v precedent // (fmt/vstream.ww:88-92) — same shape memio/vstream.ww uses for its // os.write < 0 path. nomem is the carrier (not a semantic match for // "closed") until io fold-2 lands a properly-shaped error variant. // // Sibling tasks parked here (filed, NOT fixed): // // - io fold-2 (handle port): drew-deferred. Hare's // ref/hare/bufio/stream.ha:69 takes `src: io::handle`; once // handle = (file | int) lands, bufio_vstream's src parameter // collapses accordingly. eFinal (#50) graduates. // // - #206 (bare &fn → (*alias|void)): 5 cast sites here; drop // out wholesale on close. // // - #207 (struct-lit multi-tagged-field copy drops past first): // alloc-zero-chain pattern (`vt = zero` + post-alloc // `c.vt.X = ...`) sidesteps; same shape memio/vstream.ww uses. // Only one tagged field in bufio_ctx (vt), but the local-zero // route stays consistent with memio's idiom. // // - #210 (struct-lit slice-typed field drops under alloc): // surfaced by this fold. rbuf/wbuf/flush move post-alloc; // drops out wholesale on close. // // - #209 (wwstage formattable match-arm bail when fmt imported): // bufio doesn't import fmt transitively (only io), so the // test/wcc/778 probe runs both stages. package bufio; import io; // bufio_ctx — heap-alloc'd state for bufio_vstream. `vt` at offset 0 // for the intrusive vstream→*bufio_ctx cast. Mirrors lib/bufio.stream // (bufio.ww:252) modulo the new io.vtable in place of the OLD // io.stream first-field embed. export type bufio_ctx = struct { vt: io.vtable, src: *io.stream, rbuf: []u8, rstart: i32, rend: i32, wbuf: []u8, wend: i32, flush: []u8, }; // bufio_vstream — wire a vstream over an underlying *io.stream with // caller-supplied read/write buffers. Both rbuf and wbuf may be // empty; bread_v / bwrite_v degenerate per bufio.ww:34-38 in that // case. The flush byte-set defaults to "\n" (line-buffered writes); // per-stream setflush graduates with #50 / io fold-2. // // Mirrors ref/hare/bufio/stream.ha:69 (init). export fn bufio_vstream(src: *io.stream, rbuf: []u8, wbuf: []u8) (io.vstream | nomem) = { let zero: io.vtable; let c: *bufio_ctx = alloc(bufio_ctx{ vt = zero, src = src, rstart = rbuf.len, rend = rbuf.len, wend = 0, })?; // Post-alloc slice-field stores per #210 (sibling of #207): // struct-lit `rbuf = rbuf` silently drops slice-typed fields // under alloc(T{...}). Scalar/ptr fields in the same struct-lit // store correctly. Same alloc-zero-chain shape memio/vstream.ww // uses for #207 over vt's tagged fields. 3 stores; drops out on // #210 close. c.rbuf = rbuf; c.wbuf = wbuf; c.flush = flushdefault[0:1]; c.vt.reader = (&bread_v): *io.reader; c.vt.writer = (&bwrite_v): *io.writer; c.vt.closer = (&bclose_v): *io.closer; return &c.vt; }; // isbuffered_v — true when `s` was returned by [[bufio_vstream]]. // Hare uses callback-identity (ref/hare/bufio/stream.ha:179); // matches the OLD isbuffered shape (bufio.ww:338) but over the new // `_v` fn symbols. Either reader or writer matching is sufficient // (closer alone isn't unique to bufio). export fn isbuffered_v(s: io.vstream) bool = { match (s.reader) { case let r: *io.reader => { if (r == (&bread_v): *io.reader) { return true; }; }; case void => { }; }; match (s.writer) { case let w: *io.writer => { if (w == (&bwrite_v): *io.writer) { return true; }; }; case void => { }; }; return false; }; // ---- vtable callbacks ---------------------------------------------------- // bread_v — vstream-side read. Recover ctx via the intrusive // vstream→*bufio_ctx cast. Mirrors bufio.ww:346 (OLD bread) modulo // the (size | io.eof | io.error) return surface; io.closed from the // underlying *io.stream's io.read widens to io.error via nomem // (same shape fmt/vstream.ww:88-92 + memio/vstream.ww:71-74 use, // per #173 deferral). fn bread_v(s: io.vstream, buf: []u8) (size | io.eof | io.error) = { let b: *bufio_ctx = s: *bufio_ctx; if (b.rbuf.len == 0) { let e: io.eof; return e; }; if (b.rstart >= b.rend) { b.rstart = 0; b.rend = 0; let r: (i32 | io.eof | io.closed) = io.read(b.src, b.rbuf); match (r) { case let n: i32 => { b.rend = n; }; case io.eof => { let e: io.eof; return e; }; case io.closed => { let nm: nomem; let e: io.error = nm; return e; }; }; }; let avail: i32 = b.rend - b.rstart; let n: i32 = buf.len; if (avail < n) { n = avail; }; let i: i32 = 0; for (i < n) { buf[i] = b.rbuf[b.rstart + i]; i += 1; }; b.rstart += n; return n: size; }; // bwrite_v — vstream-side write. Mirrors bufio.ww:376 (OLD bwrite) // modulo the (size | io.error) return surface; io.closed widens to // io.error per the bread_v note. Default-flush scan + per-batch copy // + post-write conditional flush — same flush-byte-set semantics as // the OLD path (ref/hare/bufio/stream.ha:236-246, labeled-break // inlined per bufio.ww:382's note). fn bwrite_v(s: io.vstream, buf: []u8) (size | io.error) = { let b: *bufio_ctx = s: *bufio_ctx; if (b.wbuf.len == 0) { let r: (i32 | io.closed) = io.write(b.src, buf); match (r) { case let n: i32 => { return n: size; }; case io.closed => { let nm: nomem; let e: io.error = nm; return e; }; }; }; let doflush: bool = false; if (b.flush.len != 0) { let i: i32 = 0; for (i < buf.len) { let j: i32 = 0; for (j < b.flush.len) { if (buf[i] == b.flush[j]) { doflush = true; i = buf.len; j = b.flush.len; }; j += 1; }; i += 1; }; }; let z: i32 = 0; for (z < buf.len) { let avail: i32 = b.wbuf.len - b.wend; if (avail == 0) { let fr: (void | io.error) = flush_v(b); match (fr) { case void => { }; case let e: io.error => return e; }; avail = b.wbuf.len; }; let n: i32 = buf.len - z; if (avail < n) { n = avail; }; let i: i32 = 0; for (i < n) { b.wbuf[b.wend + i] = buf[z + i]; i += 1; }; b.wend += n; z += n; }; if (doflush) { let fr: (void | io.error) = flush_v(b); match (fr) { case void => { }; case let e: io.error => return e; }; }; return buf.len: size; }; // bclose_v — flush pending wbuf, forward close to src. Mirrors // bufio.ww:431. Hare's close_buffered also handles the // MANAGED_HANDLE / MANAGED_RDBUF / MANAGED_WRBUF ownership bits // (ref/hare/bufio/stream.ha:188-202); ww drops them per bufio.ww:33-47 // (caller-owned today; defer-handle graduates with io fold-2, // drew-deferred). fn bclose_v(s: io.vstream) (void | io.error) = { let b: *bufio_ctx = s: *bufio_ctx; let fr: (void | io.error) = flush_v(b); match (fr) { case void => { }; case let e: io.error => return e; }; let cr: (void | io.closed) = io.close(b.src); match (cr) { case void => return void; case io.closed => { let nm: nomem; let e: io.error = nm; return e; }; }; }; // flush_v — drain pending wbuf to src. Mirror of bufio.ww:294 (OLD // flush) over the V surface; io.closed widens to io.error per the // bread_v note. Module-prefixed `_v` suffix keeps the cstage flat-TU // private-fn scope from colliding with OLD flush (same shape // memio.dynamicgrow_v at memio/vstream.ww:229). fn flush_v(b: *bufio_ctx) (void | io.error) = { if (b.wend == 0) { return; }; let off: i32 = 0; for (off < b.wend) { let r: (i32 | io.closed) = io.write(b.src, b.wbuf[off:b.wend]); match (r) { case let n: i32 => { if (n == 0) { let nm: nomem; let e: io.error = nm; return e; }; off += n; }; case io.closed => { let nm: nomem; let e: io.error = nm; return e; }; }; }; b.wend = 0; return; };