lib/bufio: add Option C parallel vstream API (#94 fold-e4)
Adds bufio_vstream + isbuffered_v alongside the pre-vtable bufio.init / bufio.isbuffered surface, mirroring fold-e2's lib/memio and fold-e3's lib/fmt parallel-API shape. The OLD bufio.ww surface stays untouched; fold-eFinal (#50) atomically flips the package shape, drops the `_v` suffix, and retires the legacy callbacks. bufio_ctx wraps an underlying *io.stream (OLD API) — bufio_vstream src parameter type stays *io.stream until io fold-2 lands `handle = (file | int)` (drew-deferred). vt is the first field for the intrusive vstream→*bufio_ctx cast, same shape as memio/fmt vstream wrappers. Sibling task filed: #210 struct-lit slice-typed field silently drops under alloc(T{slice = val})?. Parallel to #207 for slice fields; scalar/ptr fields in the same alloc-struct-lit populate correctly. Workaround: post-alloc field-assign c.slicefield = val. Documented inline; drops out on close. Other deferrals retained inline: #206 cast wrappers (3 vtable wire-up + 2 isbuffered_v comparand), #173 nomem-widen for the io.closed → io.error boundary, #207 alloc-zero-chain for vt. bufio is not embedded in any selfhost combined.ww (test-only); no Makefile regen needed (#110-blind safe). test/wcc/778_bufio_vstream_run pins the 5-row scenario set: write+flush, read+refill, isbuffered_v discriminator, OLD/NEW boundary check, and a branched-callee #105 row. cs+ww+byte-id green on all 5 rows. make test: 211 passed (was 210).
This commit is contained in:
320
lib/bufio/vstream.ww
Normal file
320
lib/bufio/vstream.ww
Normal file
@@ -0,0 +1,320 @@
|
||||
// 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 `(*<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
|
||||
// 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;
|
||||
};
|
||||
Reference in New Issue
Block a user