lib: collapse the parallel vstream scaffold onto the single Hare io surface (#94 fold-eFinal)

The Option-C parallel _v vstream API was scaffolding to bring the io stack up alongside the old surface; carrying both permanently is a rule-9 divergence from ref/hare, which has exactly one io surface. Collapse onto that surface (stream = *vtable, ref/hare/io/stream.ha) and rename the _v symbols to their Hare names (io vstream->stream, fmt vfprint->fprint, bufio/memio/log surfaces, log.new). Deletes the 4 lib/*/vstream.ww scaffold files; regenerates w6c/wwdump combined.ww. cstage and wwstage stay byte-identical and combined_ww_fresh holds; all 220 tests pass.
This commit is contained in:
2026-05-30 03:24:23 +09:00
parent 80184a3acf
commit 7d39f6d623
31 changed files with 2371 additions and 4834 deletions

View File

@@ -113,7 +113,7 @@ $(BIN)/wwdump_ww: selfhost/cmd/wwdump/main.ww \
selfhost/cmd/wcc/cgen.ww selfhost/cmd/wcc/cgenexpr.ww \
selfhost/cmd/wcc/cgenstmt.ww selfhost/cmd/wcc/cgenutil.ww \
selfhost/cmd/wcc/cgendecl.ww \
lib/os/os.ww lib/rt/malloc.ww lib/time/time.ww lib/strconv/strconv.ww lib/strconv/stof_data.ww lib/strconv/decimal.ww lib/strconv/stof.ww lib/strconv/ftos_data.ww lib/strconv/ftos.ww lib/strings/strings.ww lib/bytes/bytes.ww lib/encoding/utf8/utf8.ww lib/ascii/ascii.ww lib/errors/errors.ww lib/io/io.ww lib/io/stream.ww lib/io/types.ww lib/memio/memio.ww lib/memio/vstream.ww lib/fmt/fmt.ww \
lib/os/os.ww lib/rt/malloc.ww lib/time/time.ww lib/strconv/strconv.ww lib/strconv/stof_data.ww lib/strconv/decimal.ww lib/strconv/stof.ww lib/strconv/ftos_data.ww lib/strconv/ftos.ww lib/strings/strings.ww lib/bytes/bytes.ww lib/encoding/utf8/utf8.ww lib/ascii/ascii.ww lib/errors/errors.ww lib/io/io.ww lib/io/stream.ww lib/io/types.ww lib/memio/memio.ww lib/fmt/fmt.ww \
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
$(LIB)/libwwrt.a | $(BIN)
@mkdir -p $(BIN)/wwdump_ww.d
@@ -135,7 +135,7 @@ $(BIN)/w6c_ww: selfhost/cmd/w6c/main.ww \
selfhost/cmd/wcc/cgen.ww selfhost/cmd/wcc/cgenexpr.ww \
selfhost/cmd/wcc/cgenstmt.ww selfhost/cmd/wcc/cgenutil.ww \
selfhost/cmd/wcc/cgendecl.ww \
lib/os/os.ww lib/rt/malloc.ww lib/time/time.ww lib/strconv/strconv.ww lib/strconv/stof_data.ww lib/strconv/decimal.ww lib/strconv/stof.ww lib/strconv/ftos_data.ww lib/strconv/ftos.ww lib/strings/strings.ww lib/bytes/bytes.ww lib/encoding/utf8/utf8.ww lib/ascii/ascii.ww lib/errors/errors.ww lib/io/io.ww lib/io/stream.ww lib/io/types.ww lib/memio/memio.ww lib/memio/vstream.ww lib/fmt/fmt.ww \
lib/os/os.ww lib/rt/malloc.ww lib/time/time.ww lib/strconv/strconv.ww lib/strconv/stof_data.ww lib/strconv/decimal.ww lib/strconv/stof.ww lib/strconv/ftos_data.ww lib/strconv/ftos.ww lib/strings/strings.ww lib/bytes/bytes.ww lib/encoding/utf8/utf8.ww lib/ascii/ascii.ww lib/errors/errors.ww lib/io/io.ww lib/io/stream.ww lib/io/types.ww lib/memio/memio.ww lib/fmt/fmt.ww \
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
$(LIB)/libwwrt.a | $(BIN)
@mkdir -p $(BIN)/w6c_ww.d
@@ -703,14 +703,14 @@ $(BIN)/test_io_vtable_run: test/wcc/775_io_vtable_run.c \
$(BIN)/test_memio_vstream_run: test/wcc/776_memio_vstream_run.c \
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
lib/memio/memio.ww lib/memio/vstream.ww \
lib/memio/memio.ww \
$(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
$(BIN)/test_fmt_vstream_run: test/wcc/777_fmt_vstream_run.c \
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
lib/fmt/fmt.ww lib/fmt/vstream.ww \
lib/fmt/fmt.ww \
$(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
@@ -743,30 +743,30 @@ $(BIN)/test_xmod_alias_struct_collide_run: test/wcc/784_xmod_alias_struct_collid
$(BIN)/test_fmt_vstream_mods_run: test/wcc/780_fmt_vstream_mods_run.c \
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
lib/fmt/fmt.ww lib/fmt/vstream.ww \
lib/fmt/fmt.ww \
$(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
$(BIN)/test_fmt_vstream_compositions_run: test/wcc/781_fmt_vstream_compositions_run.c \
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
lib/fmt/fmt.ww lib/fmt/vstream.ww \
lib/memio/memio.ww lib/memio/vstream.ww \
lib/fmt/fmt.ww \
lib/memio/memio.ww \
$(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
$(BIN)/test_bufio_vstream_run: test/wcc/778_bufio_vstream_run.c \
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
lib/bufio/bufio.ww lib/bufio/vstream.ww \
lib/bufio/bufio.ww \
$(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
$(BIN)/test_log_vstream_run: test/wcc/779_log_vstream_run.c \
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
lib/log/log.ww lib/log/vstream.ww \
lib/fmt/fmt.ww lib/fmt/vstream.ww \
lib/log/log.ww \
lib/fmt/fmt.ww \
$(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<

View File

@@ -1,84 +1,49 @@
// bufio — buffered I/O over [[io.stream]]. Subset of Hare's bufio::
// surface (ref/hare/bufio/{scanner,stream}.ha).
// bufio — buffered I/O over [[io.stream]] (= `*io.vtable`). Subset of
// Hare's bufio:: surface (ref/hare/bufio/{scanner,stream}.ha). Project
// #94 fold-eFinal.
//
// Surface today:
// Surface:
//
// bufio.newscanner(s: *scanner, src: *io.stream, buf: []u8) void
// bufio.finish (s: *scanner) void
// bufio.scanbyte (s: *scanner) (u8 | io.eof | io.closed)
// bufio.scantok (s: *scanner, delim: u8)
// ([]u8 | io.eof | io.closed | overflow)
// bufio.scanline (s: *scanner) (str | io.eof | io.closed | overflow)
// bufio.newscannerbuf(src: io.stream, buf: []u8) scanner
// bufio.finish (s: *scanner) void
// bufio.scanbyte (s: *scanner) (u8 | io.eof | io.error)
// bufio.scanbytes (s: *scanner, delim: u8)
// ([]u8 | io.eof | io.error | overflow)
// bufio.scanline (s: *scanner) (str | io.eof | io.error | overflow)
//
// bufio.init (b: *stream, src: *io.stream, rbuf: []u8, wbuf: []u8) void
// bufio.flush (b: *stream) (void | io.closed)
// bufio.init (src: io.stream, rbuf: []u8, wbuf: []u8) stream
// bufio.flush (b: *stream) (void | io.error)
// bufio.setflush (b: *stream, bs: []u8) void
// bufio.unread (b: *stream, buf: []u8) void
// bufio.isbuffered (s: *io.stream) bool
// bufio.isbuffered (s: io.stream) bool
//
// Divergence from Hare:
// VALUE-RETURN (Hare ref/hare/bufio/stream.ha:69 init, scanner.ha:92
// newscanner_buf): each constructor builds in a local `let r: T;`,
// field-assigns every slot, and `return r;`. The caller owns the
// returned struct (stack ownership, no-GC). For the buffered stream
// the caller passes `&b.vt` to the io dispatchers; the scanner is a
// plain read-ahead tokenizer (no embedded vtable) driven directly via
// scanbyte / scanbytes / scanline.
//
// • Hare returns the scanner / stream by value; ww cgen can't
// return structs wider than 16B by value yet, so `newscanner`
// and `init` are out-parameter shaped. Same workaround
// memio.fixed uses.
// SCOPE: this is the surface ww ships, NOT a port of Hare's full
// scanner. The features ww never implemented — auto-grow newscanner,
// multibyte-delim scanbytes / scanstring, scanrune / readbyte / readtok
// / readline / readrune / unreadrune — are a separate future feature
// fold (#217). The ported set: the fixed-buffer newscannerbuf,
// single-byte scanbytes, single-byte scanline, scanbyte, finish; and
// the buffered-stream init / setflush / flush / unread / isbuffered.
//
// • The scanner's read buffer is held flat as `(ptr, cap)` rather
// than a `[]u8` field; the scanner predates struct-held slice
// support and is wired through scanbyte/scantok/scanline. The
// new stream writer half uses slice-typed `rbuf` / `wbuf`
// fields directly — chained dot through a struct slice field
// works after task #6.
// Cast workaround per #206-payoff (ken: KEEP the explicit casts; they
// are cgen-neutral and sidestep the #214 over-acceptance surface).
// `(&fn_name): *io.<role>` at each vtable store + the isbuffered
// fn-ptr-equality comparand — the #206 cast-drop is gated on #214.
//
// • Hare's `bufio::stream` discriminates r-only / w-only / r+w
// via three different vtable singletons (vtable_r, vtable_w,
// vtable_rw); ww's [[io.stream]] vtable always carries all
// three callbacks, so stream always installs `bread` /
// `bwrite` / `bclose`. A zero-length rbuf or wbuf makes the
// matching callback degenerate (rbuf=[]: bread short-circuits
// to io.eof; wbuf=[]: bwrite passes through to src).
//
// • Hare's `init` also takes a `flag` argument carrying
// MANAGED_HANDLE / MANAGED_RDBUF / MANAGED_WRBUF ownership
// bits; stream never owns its buffers or src, so the
// `flags` field and the init-time argument are dropped
// wholesale. The Hare `flush: []u8` byte-set is preserved —
// [[init]] seeds it to `['\n']` (line buffering, matching
// Hare's `flag::NONE` default at stream.ha:75) and
// [[setflush]] swaps it.
//
// • Hare's `newscanner` allocates and grows the buffer up to
// `maxread`; we ship only the caller-supplied shape (Hare's
// `newscanner_buf`) and pick up the auto-grow variant when an
// append over a struct-held slice works.
//
// • EOF-handling defaults to Hare's `EOF_DISCARD`: bytes between
// the last delimiter and EOF are dropped and io.eof is returned
// on the call that would have read them. Hare's EOF_GREEDY mode
// isn't shipped (no caller needs it yet).
//
// Owning model: caller owns the scanner state, the stream state,
// the byte buffers, and the source stream. `finish` doesn't free
// the buffer and doesn't close src; it's a no-op today but stays
// on the surface so callers don't churn when bufio grows internal
// allocations. The stream vtable `close` callback flushes pending
// writes and forwards close to src; it does not free rbuf / wbuf.
//
// let mem: memio.state;
// let m: io.stream;
// memio.fixed(&mem, &m, raw[0:N]);
// let buf: [128]u8;
// let sc: bufio.scanner;
// bufio.newscanner(&sc, &m, buf[0:128]);
// match (bufio.scanline(&sc)) { ... };
// bufio.finish(&sc);
//
// let b: bufio.stream;
// let rbuf: [256]u8;
// let wbuf: [128]u8;
// bufio.init(&b, src, rbuf[0:256], wbuf[0:128]);
// let p: *io.stream = &b.vtable; // first-field embed
// io.write(p, msg);
// bufio.flush(&b);
// Mode discrimination (drew-deferred): Hare uses three vtable
// singletons (vtable_r / vtable_w / vtable_rw); ww's vtable always
// carries all three callbacks (a zero-length rbuf/wbuf degenerates the
// matching callback in-cb). Defer-handle (MANAGED_* ownership bits)
// also deferred — caller owns rbuf/wbuf/src; bclose flushes + forwards
// close but frees nothing. Both graduate with io fold-2 (#5).
package bufio;
@@ -101,157 +66,20 @@ let flushdefault: [1]u8 = [10u8];
// use of errors::overflow for the same condition.
export type overflow = !void;
export type scanner = struct {
src: *io.stream,
ptr: *u8,
cap: i32,
start: i32, // index where the pending region starts in ptr
avail: i32, // pending byte count; pending = ptr[start..start+avail]
};
// ---- buffered stream ------------------------------------------------------
// newscanner — wire `s` to read through `src` using `buf` as the
// read-ahead window. Hare returns the scanner by value
// (newscanner_buf); we take an out-parameter pending cgen support
// for wide-struct return.
export fn newscanner(s: *scanner, src: *io.stream, buf: []u8) void = {
s.src = src;
s.ptr = buf.ptr;
s.cap = buf.len;
s.start = 0;
s.avail = 0;
};
// finish — release scanner-owned resources. No-op today (buffer is
// caller-owned, src isn't closed); kept on the surface so callers
// won't churn when bufio later grows internal allocations.
export fn finish(s: *scanner) void = { };
// readahead — make room and read once from src into the back of the
// pending region. Returns the number of bytes newly buffered (≥0,
// can be 0 if the underlying stream made no progress), or
// io.eof/io.closed propagated from src.
fn readahead(s: *scanner) (i32 | io.eof | io.closed) = {
if (s.start + s.avail == s.cap && s.start > 0) {
// Shift pending region to the front of the buffer.
let i: i32 = 0;
for (i < s.avail) {
s.ptr[i] = s.ptr[s.start + i];
i += 1;
};
s.start = 0;
};
let off: i32 = s.start + s.avail;
let v: []u8;
v.ptr = s.ptr + (off: u64);
v.len = s.cap - off;
let r: (i32 | io.eof | io.closed) = io.read(s.src, v);
match (r) {
case let n: i32 => {
s.avail += n;
return n;
};
case io.eof => { let e: io.eof; return e; };
case io.closed => { let e: io.closed; return e; };
};
};
// scanbyte — pop one byte from the scanner, refilling from src on
// demand. Mirrors Hare's `scan_byte` (ref/hare/bufio/scanner.ha:204).
export fn scanbyte(s: *scanner) (u8 | io.eof | io.closed) = {
for (s.avail == 0) {
let r: (i32 | io.eof | io.closed) = readahead(s);
match (r) {
case let n: i32 => { };
case io.eof => { let e: io.eof; return e; };
case io.closed => { let e: io.closed; return e; };
};
};
let b: u8 = s.ptr[s.start];
s.start += 1;
s.avail -= 1;
return b;
};
// scantok — read up to (and not including) the next byte equal to
// `delim`. The delim byte is consumed from the stream but not
// returned. The returned slice borrows from the scanner's internal
// buffer and is invalidated by the next scan call.
//
// EOF without finding delim discards the trailing fragment and
// returns io.eof (Hare's EOF_DISCARD default). Buffer-full without
// delim returns overflow.
//
// Mirrors Hare's `scan_bytes` (ref/hare/bufio/scanner.ha:220),
// narrowed to a single-byte delimiter.
export fn scantok(s: *scanner, delim: u8) ([]u8 | io.eof | io.closed | overflow) = {
let i: i32 = 0;
for (true) {
for (i < s.avail) {
if (s.ptr[s.start + i] == delim) {
let v: []u8;
v.ptr = s.ptr + (s.start: u64);
v.len = i;
s.start += i + 1;
s.avail -= i + 1;
return v;
};
i += 1;
};
// No delim in pending. If the buffer is full with nowhere
// to shift, the caller's budget is too small — overflow.
if (s.start + s.avail == s.cap && s.start == 0) {
let e: overflow; return e;
};
let r: (i32 | io.eof | io.closed) = readahead(s);
match (r) {
case let n: i32 => {
// readahead may shift start to 0; the searched bytes
// move with it, so `i` (count from start) is still
// accurate. Resume scanning where we left off.
};
case io.eof => { let e: io.eof; return e; };
case io.closed => { let e: io.closed; return e; };
};
};
let e: io.eof; return e;
};
// scanline — read up to (and not including) the next '\n'. The
// newline is consumed; the returned str view borrows from the
// scanner buffer and is invalidated by the next scan call.
// Hare's `scan_line` is `scan_string(s, "\n")`; we route through
// scantok directly since lib/strings doesn't yet ship `toutf8` over
// a multi-byte delim.
export fn scanline(s: *scanner) (str | io.eof | io.closed | overflow) = {
let r: ([]u8 | io.eof | io.closed | overflow) = scantok(s, 10u8);
match (r) {
case let bs: []u8 => {
let v: str;
v.ptr = bs.ptr;
v.len = bs.len;
return v;
};
case io.eof => { let e: io.eof; return e; };
case io.closed => { let e: io.closed; return e; };
case overflow => { let e: overflow; return e; };
};
};
// stream — buffered read+write over an underlying *io.stream.
//
// `vtable` is the first field so a `*stream` is castable to an
// `*io.stream` via address-of-field (`&b.vtable`). Higher layers
// (fmt.fprint, scanner, …) only see the io.stream view; bufio
// recovers the outer stream by casting the dispatch arg back to
// `*stream` inside the bread/bwrite/bclose callbacks.
// stream — heap-free buffered read+write over an underlying io.stream.
// `vt` at offset 0 for the intrusive io.stream→*stream cast. `src` is
// an io.stream (the vtable-native underlying handle). Mirrors
// ref/hare/bufio/stream.ha:18.
//
// rbuf[rstart..rend] is pending read data; wbuf[0..wend] is pending
// write data. rbuf or wbuf may be zero-length: read-empty makes
// bread return io.eof immediately, write-empty makes bwrite a
// pass-through to src (uses src.write directly, no buffering).
// write data. rbuf or wbuf may be zero-length: read-empty makes bread
// return io.eof immediately, write-empty makes bwrite a pass-through to
// src (no buffering).
export type stream = struct {
vtable: io.stream,
src: *io.stream,
vt: io.vtable,
src: io.stream,
rbuf: []u8,
rstart: i32,
rend: i32,
@@ -260,53 +88,57 @@ export type stream = struct {
flush: []u8,
};
// init — wire `b` over `src` with caller-supplied buffers. Both
// rbuf and wbuf may be empty slices; the corresponding direction
// degenerates (see [[stream]]). The flush byte-set defaults to
// "\n" (line-buffered writes); [[setflush]] swaps it.
export fn init(b: *stream, src: *io.stream, rbuf: []u8, wbuf: []u8) void = {
b.vtable.ctx = b: *void;
b.vtable.read = bread;
b.vtable.write = bwrite;
b.vtable.close = bclose;
b.src = src;
b.rbuf = rbuf;
// init — wire a buffered stream over an underlying io.stream with
// caller-supplied read/write buffers. Both rbuf and wbuf may be empty;
// bread / bwrite degenerate (see [[stream]]). The flush byte-set
// defaults to "\n" (line-buffered writes); [[setflush]] swaps it.
//
// Returns the stream BY VALUE; the caller passes `&b.vt` to the io
// dispatchers. Mirrors ref/hare/bufio/stream.ha:69.
export fn init(src: io.stream, rbuf: []u8, wbuf: []u8) stream = {
let r: stream;
r.vt.reader = (&bread): *io.reader;
r.vt.writer = (&bwrite): *io.writer;
r.vt.closer = (&bclose): *io.closer;
r.src = src;
// rstart=rbuf.len, rend=rbuf.len: pre-read unread budget = rbuf.len
// (Hare bufio/stream.ha:101).
b.rstart = rbuf.len;
b.rend = rbuf.len;
b.wbuf = wbuf;
b.wend = 0;
b.flush = flushdefault[0:1];
r.rstart = rbuf.len;
r.rend = rbuf.len;
r.rbuf = rbuf;
r.wbuf = wbuf;
r.wend = 0;
r.flush = flushdefault[0:1];
return r;
};
// setflush — install a new flush byte-set. Any byte from `bs`
// appearing in a write payload triggers an automatic flush after
// the write copies into wbuf. Mirrors Hare's `setflush`
// (ref/hare/bufio/stream.ha:128).
// setflush — install a new flush byte-set. Any byte from `bs` appearing
// in a write payload triggers an automatic flush after the write copies
// into wbuf. Mirrors ref/hare/bufio/stream.ha:128.
export fn setflush(b: *stream, bs: []u8) void = {
b.flush = bs;
};
// flush — drain any pending wbuf data to src. Hare returns
// `(void | io::error)`; ww's io.stream write-side error channel
// is `io.closed` alone, so the return shape narrows.
export fn flush(b: *stream) (void | io.closed) = {
// flush — drain any pending wbuf data to src. Public API AND the
// internal drain called by bwrite / bclose. A 0-byte non-error write
// means the sink made no progress (memio.fixed full) — surfaced as a
// nomem-carried io.error rather than spinning, matching what io.writeall
// would do in Hare. Mirrors ref/hare/bufio/stream.ha.
export fn flush(b: *stream) (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]);
let r: (size | io.error) = io.write(b.src, b.wbuf[off:b.wend]);
match (r) {
case let n: i32 => {
if (n == 0) {
// Underlying stream made no progress. Treat as
// closed rather than spin; matches what
// io.writeall would do in Hare.
let e: io.closed; return e;
case let n: size => {
if (n == 0: size) {
let nm: nomem;
let e: io.error = nm;
return e;
};
off += n;
off += n: i32;
};
case io.closed => { let e: io.closed; return e; };
case let e: io.error => return e;
};
};
b.wend = 0;
@@ -314,10 +146,9 @@ export fn flush(b: *stream) (void | io.closed) = {
};
// unread — push `buf` back into the read buffer so the next bread
// returns it first. The bytes must fit in front of the pending
// region (rstart >= buf.len); Hare aborts on overflow, ww does the
// same via rt_abort. Mirrors Hare's `stream_unread`
// (ref/hare/bufio/stream.ha:164).
// returns it first. The bytes must fit in front of the pending region
// (rstart >= buf.len); Hare aborts on overflow, ww does the same via
// rtabort. Mirrors ref/hare/bufio/stream.ha:164.
export fn unread(b: *stream, buf: []u8) void = {
if (b.rstart < buf.len) {
rtabort("bufio.unread: more data than rbuf has room for");
@@ -330,35 +161,43 @@ export fn unread(b: *stream, buf: []u8) void = {
b.rstart -= buf.len;
};
// isbuffered — true when `s` is a [[bufio.stream]]'s embedded
// vtable. Hare's discriminator is callback identity
// (ref/hare/bufio/stream.ha:179); we match the shape directly. The
// read or write callback being bread / bwrite is sufficient (close
// alone isn't unique to bufio).
export fn isbuffered(s: *io.stream) bool = {
if (s.read == bread) { return true; };
if (s.write == bwrite) { return true; };
// isbuffered — true when `s` was returned by [[init]]. Hare's
// discriminator is callback identity (ref/hare/bufio/stream.ha:179).
// Either reader or writer matching is sufficient.
export fn isbuffered(s: io.stream) bool = {
match (s.reader) {
case let r: *io.reader => {
if (r == (&bread): *io.reader) { return true; };
};
case void => { };
};
match (s.writer) {
case let w: *io.writer => {
if (w == (&bwrite): *io.writer) { return true; };
};
case void => { };
};
return false;
};
// ---- vtable callbacks ------------------------------------------------
// ---- buffered-stream vtable callbacks ------------------------------------
fn bread(s: *io.stream, buf: []u8) (i32 | io.eof | io.closed) = {
let b: *stream = s.ctx: *stream;
// No read buffer configured: surface eof immediately rather
// than punch through to src (callers asked for a write-only
// stream, reads against it are a misuse).
if (b.rbuf.len == 0) { let e: io.eof; return e; };
// Empty pending region: refill from src. Reset rstart so
// unread has the full buffer to push back into.
// bread — buffered read. Recover stream via the intrusive cast; refill
// rbuf from src via io.read on empty pending region. Mirrors
// ref/hare/bufio/stream.ha (stream_read).
fn bread(s: io.stream, buf: []u8) (size | io.eof | io.error) = {
let b: *stream = s: *stream;
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);
let r: (size | io.eof | io.error) = io.read(b.src, b.rbuf);
match (r) {
case let n: i32 => { b.rend = n; };
case let n: size => { b.rend = n: i32; };
case io.eof => { let e: io.eof; return e; };
case io.closed => { let e: io.closed; return e; };
case let e: io.error => return e;
};
};
let avail: i32 = b.rend - b.rstart;
@@ -370,17 +209,18 @@ fn bread(s: *io.stream, buf: []u8) (i32 | io.eof | io.closed) = {
i += 1;
};
b.rstart += n;
return n;
return n: size;
};
fn bwrite(s: *io.stream, buf: []u8) (i32 | io.closed) = {
let b: *stream = s.ctx: *stream;
// No write buffer configured: pass through to src directly.
if (b.wbuf.len == 0) { return io.write(b.src, buf); };
// Scan the write payload for any byte present in b.flush — a
// hit triggers a post-copy flush. Hare uses a labeled break to
// exit both loops on first hit (stream.ha:236-246); ww has no
// labeled break, so we bump both indices past their bounds.
// bwrite — buffered write. wbuf-empty passes through to src; otherwise
// default-flush scan + per-batch copy + post-write conditional flush.
// Labeled-break inlined (ww has no labeled break). Mirrors
// ref/hare/bufio/stream.ha (stream_write).
fn bwrite(s: io.stream, buf: []u8) (size | io.error) = {
let b: *stream = s: *stream;
if (b.wbuf.len == 0) {
return io.write(b.src, buf);
};
let doflush: bool = false;
if (b.flush.len != 0) {
let i: i32 = 0;
@@ -401,10 +241,10 @@ fn bwrite(s: *io.stream, buf: []u8) (i32 | io.closed) = {
for (z < buf.len) {
let avail: i32 = b.wbuf.len - b.wend;
if (avail == 0) {
let r: (void | io.closed) = flush(b);
match (r) {
let fr: (void | io.error) = flush(b);
match (fr) {
case void => { };
case io.closed => { let e: io.closed; return e; };
case let e: io.error => return e;
};
avail = b.wbuf.len;
};
@@ -419,21 +259,162 @@ fn bwrite(s: *io.stream, buf: []u8) (i32 | io.closed) = {
z += n;
};
if (doflush) {
let r: (void | io.closed) = flush(b);
match (r) {
let fr: (void | io.error) = flush(b);
match (fr) {
case void => { };
case io.closed => { let e: io.closed; return e; };
case let e: io.error => return e;
};
};
return buf.len;
return buf.len: size;
};
fn bclose(s: *io.stream) (void | io.closed) = {
let b: *stream = s.ctx: *stream;
let r: (void | io.closed) = flush(b);
match (r) {
// bclose — flush pending wbuf, forward close to src via io.close.
// Caller-owned buffers/src are not freed (drew defer-handle deferral).
fn bclose(s: io.stream) (void | io.error) = {
let b: *stream = s: *stream;
let fr: (void | io.error) = flush(b);
match (fr) {
case void => { };
case io.closed => { let e: io.closed; return e; };
case let e: io.error => return e;
};
let cr: (void | io.error) = io.close(b.src);
match (cr) {
case void => return void;
case let e: io.error => return e;
};
};
// ---- scanner (read-ahead tokenizer over an io.stream src) ----------------
//
// Plain tokenizer — no embedded vtable; driven directly via scanbyte /
// scanbytes / scanline, not through io dispatch. Its `src` is an
// io.stream and refills go through io.read. Value-return constructor
// (Hare newscanner_buf, scanner.ha:92).
//
// ptr/cap kept flat (no `buf: []u8`); the scanner predates struct-held
// slice support.
export type scanner = struct {
src: io.stream,
ptr: *u8,
cap: i32,
start: i32, // index where the pending region starts in ptr
avail: i32, // pending byte count; pending = ptr[start..start+avail]
};
// newscannerbuf — wire a scanner to read through `src` using `buf` as
// the fixed read-ahead window. Returns the scanner BY VALUE. This is
// Hare's newscanner_buf (ref/hare/bufio/scanner.ha:92); the auto-grow
// newscanner is a separate feature (#217).
export fn newscannerbuf(src: io.stream, buf: []u8) scanner = {
let r: scanner;
r.src = src;
r.ptr = buf.ptr;
r.cap = buf.len;
r.start = 0;
r.avail = 0;
return r;
};
// finish — release scanner-owned resources. No-op (buffer is
// caller-owned, src isn't closed); kept on the surface so callers won't
// churn. Mirrors ref/hare/bufio/scanner.ha:110.
export fn finish(s: *scanner) void = { };
// readahead — make room and read once from src into the back of the
// pending region. Returns bytes newly buffered (>=0), or io.eof/io.error
// from src. The size from io.read narrows to i32 (buffer-length type).
fn readahead(s: *scanner) (i32 | io.eof | io.error) = {
if (s.start + s.avail == s.cap && s.start > 0) {
let i: i32 = 0;
for (i < s.avail) {
s.ptr[i] = s.ptr[s.start + i];
i += 1;
};
s.start = 0;
};
let off: i32 = s.start + s.avail;
let v: []u8;
v.ptr = s.ptr + (off: u64);
v.len = s.cap - off;
let r: (size | io.eof | io.error) = io.read(s.src, v);
match (r) {
case let n: size => {
s.avail += n: i32;
return n: i32;
};
case io.eof => { let e: io.eof; return e; };
case let e: io.error => return e;
};
};
// scanbyte — pop one byte, refilling from src on demand. Mirrors
// ref/hare/bufio/scanner.ha:204.
export fn scanbyte(s: *scanner) (u8 | io.eof | io.error) = {
for (s.avail == 0) {
let r: (i32 | io.eof | io.error) = readahead(s);
match (r) {
case let n: i32 => { };
case io.eof => { let e: io.eof; return e; };
case let e: io.error => return e;
};
};
let b: u8 = s.ptr[s.start];
s.start += 1;
s.avail -= 1;
return b;
};
// scanbytes — read up to (and not including) the next byte equal to
// `delim`. The delim is consumed but not returned. The returned slice
// borrows from the scanner buffer and is invalidated by the next scan.
// EOF without delim discards the trailing fragment and returns io.eof
// (Hare EOF_DISCARD default); buffer-full without delim returns
// overflow. Single-byte delim only — Hare's `(u8 | []u8)` multibyte
// form is #217. Mirrors ref/hare/bufio/scanner.ha:220 (narrowed).
export fn scanbytes(s: *scanner, delim: u8) ([]u8 | io.eof | io.error | overflow) = {
let i: i32 = 0;
for (true) {
for (i < s.avail) {
if (s.ptr[s.start + i] == delim) {
let v: []u8;
v.ptr = s.ptr + (s.start: u64);
v.len = i;
s.start += i + 1;
s.avail -= i + 1;
return v;
};
i += 1;
};
if (s.start + s.avail == s.cap && s.start == 0) {
let e: overflow; return e;
};
let r: (i32 | io.eof | io.error) = readahead(s);
match (r) {
case let n: i32 => { };
case io.eof => { let e: io.eof; return e; };
case let e: io.error => return e;
};
};
let e: io.eof; return e;
};
// scanline — read up to (and not including) the next '\n'. The newline
// is consumed; the returned str view borrows from the scanner buffer.
// Single-byte route (Hare's scan_line = scan_string(s, "\n"); the
// arbitrary multibyte-delim scan_string is #217). Mirrors
// ref/hare/bufio/scanner.ha:307.
export fn scanline(s: *scanner) (str | io.eof | io.error | overflow) = {
let r: ([]u8 | io.eof | io.error | overflow) = scanbytes(s, 10u8);
match (r) {
case let bs: []u8 => {
let v: str;
v.ptr = bs.ptr;
v.len = bs.len;
return v;
};
case io.eof => { let e: io.eof; return e; };
case let e: io.error => return e;
case overflow => { let e: overflow; return e; };
};
return io.close(b.src);
};

View File

@@ -1,11 +1,10 @@
// bufiotest — exercises lib/bufio. Run with `out/bin/ww run lib/bufio/bufiotest.ww`.
//
// The scanner @tests enumerate parallel `[N]T` arrays of inputs and
// expectations, then iterate one body across them. Parallel arrays
// (rather than `[N]struct{...}`) match the same cgen workaround
// memiotest leans on. The stream @tests are one scenario per fn
// (the ww-stdlib idiom): the "table" is the fn list in main, not
// a row array.
// expectations, then iterate one body across them. The stream @tests
// are one scenario per fn. #94 fold-eFinal: the unified value-return
// surface — `let st = memio.fixed(buf); &st.vt` into the scanner/stream
// init, io.read/write/close return size/io.error.
package bufio;
@@ -39,38 +38,34 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
return off + s.len;
};
// ---- A closed-on-read stream for the io.closed surfacing test ---------
// ---- an error-returning stream for the io.error surfacing test --------
//
// Replaces the OLD io.closed source. errvt is module-static; its
// reader/writer return a nomem-widened io.error.
let errvt: io.vtable;
fn closedread(s: *io.stream, buf: []u8) (i32 | io.eof | io.closed) = {
let e: io.closed; return e;
fn errread(s: io.stream, buf: []u8) (size | io.eof | io.error) = {
let nm: nomem; let e: io.error = nm; return e;
};
fn closedwrite(s: *io.stream, buf: []u8) (i32 | io.closed) = {
let e: io.closed; return e;
fn errwrite(s: io.stream, buf: []u8) (size | io.error) = {
let nm: nomem; let e: io.error = nm; return e;
};
fn closedclose(s: *io.stream) (void | io.closed) = { return; };
fn closedstream(s: *io.stream) void = {
s.ctx = nil;
s.read = closedread;
s.write = closedwrite;
s.close = closedclose;
fn errsource() io.stream = {
errvt.reader = (&errread): *io.reader;
errvt.writer = (&errwrite): *io.writer;
return &errvt;
};
// ---- scanbyte: drain four bytes through a 2-byte window ---------------
// Reading buf is smaller than the stream contents (2 vs 4) — the
// scanner has to refill twice. Row 4 is one past EOF, row 5 stays at
// EOF (idempotent).
@test fn scanbytecases() void = {
let raw: [4]u8;
raw[0] = 11u8; raw[1] = 22u8; raw[2] = 33u8; raw[3] = 44u8;
let mem: memio.state;
let m: io.stream;
memio.fixed(&mem, &m, raw[0:4]);
let mem: memio.stream = memio.fixed(raw[0:4]);
let m: io.stream = &mem.vt;
let buf: [2]u8;
let sc: bufio.scanner;
bufio.newscanner(&sc, &m, buf[0:2]);
let sc: bufio.scanner = bufio.newscannerbuf(m, buf[0:2]);
// (wantbyte, weof)
let want: [6]u8;
@@ -84,14 +79,14 @@ fn closedstream(s: *io.stream) void = {
let i: i32 = 0;
for (i < 6) {
let r: (u8 | io.eof | io.closed) = bufio.scanbyte(&sc);
let r: (u8 | io.eof | io.error) = bufio.scanbyte(&sc);
match (r) {
case let b: u8 => {
if (weof[i] != 0) { fail(); };
if (b != want[i]) { fail(); };
};
case io.eof => { if (weof[i] == 0) { fail(); }; };
case io.closed => fail();
case let e: io.error => fail();
};
i += 1;
};
@@ -105,17 +100,12 @@ fn closedstream(s: *io.stream) void = {
let src: [32]u8;
let n: i32 = putstr("foo\nbar\nbaz\ntrailing", src[0:32], 0);
let mem: memio.state;
let m: io.stream;
memio.fixed(&mem, &m, src[0:n]);
let mem: memio.stream = memio.fixed(src[0:n]);
let m: io.stream = &mem.vt;
let buf: [32]u8;
let sc: bufio.scanner;
bufio.newscanner(&sc, &m, buf[0:32]);
let sc: bufio.scanner = bufio.newscannerbuf(m, buf[0:32]);
// (wantlen, wantfirst, weof, wovr)
// Row 0..2: terminated lines.
// Row 3: tail "trailing" — EOF_DISCARD drops it, returns eof.
// Row 4: subsequent call stays at eof.
let wl: [5]i32;
let wf: [5]u8;
let weof: [5]i32;
@@ -128,7 +118,7 @@ fn closedstream(s: *io.stream) void = {
let i: i32 = 0;
for (i < 5) {
let r: (str | io.eof | io.closed | bufio.overflow) = bufio.scanline(&sc);
let r: (str | io.eof | io.error | bufio.overflow) = bufio.scanline(&sc);
match (r) {
case let v: str => {
if (weof[i] != 0) { fail(); };
@@ -137,7 +127,7 @@ fn closedstream(s: *io.stream) void = {
if (v.len > 0 && v[0] != wf[i]) { fail(); };
};
case io.eof => { if (weof[i] == 0) { fail(); }; };
case io.closed => fail();
case let e: io.error => fail();
case bufio.overflow => { if (wovr[i] == 0) { fail(); }; };
};
i += 1;
@@ -152,47 +142,37 @@ fn closedstream(s: *io.stream) void = {
let src: [32]u8;
let n: i32 = putstr("ABCDEFGHI\n", src[0:32], 0);
let mem: memio.state;
let m: io.stream;
memio.fixed(&mem, &m, src[0:n]);
let mem: memio.stream = memio.fixed(src[0:n]);
let m: io.stream = &mem.vt;
// Buffer too small for the 9-byte payload + delim. Caller
// owns the budget; overflow signals "raise it".
// Buffer too small for the 9-byte payload + delim.
let buf: [4]u8;
let sc: bufio.scanner;
bufio.newscanner(&sc, &m, buf[0:4]);
let sc: bufio.scanner = bufio.newscannerbuf(m, buf[0:4]);
let r: (str | io.eof | io.closed | bufio.overflow) = bufio.scanline(&sc);
let r: (str | io.eof | io.error | bufio.overflow) = bufio.scanline(&sc);
match (r) {
case let v: str => fail();
case io.eof => fail();
case io.closed => fail();
case let e: io.error => fail();
case bufio.overflow => { };
};
bufio.finish(&sc);
};
// ---- scantok: multi-delim hit, delim-at-start, EOF before delim ------
// ---- scanbytes: multi-delim hit, delim-at-start, EOF before delim ----
@test fn scantokcases() void = {
@test fn scanbytescases() void = {
let src: [16]u8;
// ",a,,b,c" — leading delim, empty token, multi-hit, trailing fragment.
let n: i32 = putstr(",a,,b,c", src[0:16], 0);
let mem: memio.state;
let m: io.stream;
memio.fixed(&mem, &m, src[0:n]);
let mem: memio.stream = memio.fixed(src[0:n]);
let m: io.stream = &mem.vt;
let buf: [8]u8;
let sc: bufio.scanner;
bufio.newscanner(&sc, &m, buf[0:8]);
let sc: bufio.scanner = bufio.newscannerbuf(m, buf[0:8]);
// (wantlen, wantfirst, weof)
// Row 0: leading "," → empty token.
// Row 1: "a" before next ",".
// Row 2: empty token between consecutive ",,".
// Row 3: "b".
// Row 4: "c" — no trailing delim → EOF_DISCARD drops + eof.
let wl: [5]i32;
let wf: [5]u8;
let weof: [5]i32;
@@ -204,7 +184,7 @@ fn closedstream(s: *io.stream) void = {
let i: i32 = 0;
for (i < 5) {
let r: ([]u8 | io.eof | io.closed | bufio.overflow) = bufio.scantok(&sc, 44u8); // ','
let r: ([]u8 | io.eof | io.error | bufio.overflow) = bufio.scanbytes(&sc, 44u8); // ','
match (r) {
case let v: []u8 => {
if (weof[i] != 0) { fail(); };
@@ -212,7 +192,7 @@ fn closedstream(s: *io.stream) void = {
if (v.len > 0 && v[0] != wf[i]) { fail(); };
};
case io.eof => { if (weof[i] == 0) { fail(); }; };
case io.closed => fail();
case let e: io.error => fail();
case bufio.overflow => fail();
};
i += 1;
@@ -225,136 +205,120 @@ fn closedstream(s: *io.stream) void = {
@test fn emptystream() void = {
let raw: [1]u8;
let mem: memio.state;
let m: io.stream;
memio.fixed(&mem, &m, raw[0:0]);
let mem: memio.stream = memio.fixed(raw[0:0]);
let m: io.stream = &mem.vt;
let buf: [8]u8;
let sc: bufio.scanner;
bufio.newscanner(&sc, &m, buf[0:8]);
let sc: bufio.scanner = bufio.newscannerbuf(m, buf[0:8]);
let r1: (u8 | io.eof | io.closed) = bufio.scanbyte(&sc);
let r1: (u8 | io.eof | io.error) = bufio.scanbyte(&sc);
match (r1) {
case let b: u8 => fail();
case io.eof => { };
case io.closed => fail();
case let e: io.error => fail();
};
let r2: (str | io.eof | io.closed | bufio.overflow) = bufio.scanline(&sc);
let r2: (str | io.eof | io.error | bufio.overflow) = bufio.scanline(&sc);
match (r2) {
case let v: str => fail();
case io.eof => { };
case io.closed => fail();
case let e: io.error => fail();
case bufio.overflow => fail();
};
let r3: ([]u8 | io.eof | io.closed | bufio.overflow) = bufio.scantok(&sc, 10u8);
let r3: ([]u8 | io.eof | io.error | bufio.overflow) = bufio.scanbytes(&sc, 10u8);
match (r3) {
case let v: []u8 => fail();
case io.eof => { };
case io.closed => fail();
case let e: io.error => fail();
case bufio.overflow => fail();
};
bufio.finish(&sc);
};
// ---- io.closed surfacing on a stream that returns closed --------------
// ---- io.error surfacing on a stream that returns error ----------------
@test fn closedsource() void = {
let m: io.stream;
closedstream(&m);
@test fn errorsource() void = {
let m: io.stream = errsource();
let buf: [8]u8;
let sc: bufio.scanner;
bufio.newscanner(&sc, &m, buf[0:8]);
let sc: bufio.scanner = bufio.newscannerbuf(m, buf[0:8]);
let r1: (u8 | io.eof | io.closed) = bufio.scanbyte(&sc);
let r1: (u8 | io.eof | io.error) = bufio.scanbyte(&sc);
match (r1) {
case let b: u8 => fail();
case io.eof => fail();
case io.closed => { };
case let e: io.error => { };
};
let r2: (str | io.eof | io.closed | bufio.overflow) = bufio.scanline(&sc);
let r2: (str | io.eof | io.error | bufio.overflow) = bufio.scanline(&sc);
match (r2) {
case let v: str => fail();
case io.eof => fail();
case io.closed => { };
case let e: io.error => { };
case bufio.overflow => fail();
};
let r3: ([]u8 | io.eof | io.closed | bufio.overflow) = bufio.scantok(&sc, 32u8);
let r3: ([]u8 | io.eof | io.error | bufio.overflow) = bufio.scanbytes(&sc, 32u8);
match (r3) {
case let v: []u8 => fail();
case io.eof => fail();
case io.closed => { };
case let e: io.error => { };
case bufio.overflow => fail();
};
bufio.finish(&sc);
};
// ---- boundary: empty line + idempotent EOF on scantok ----------------
// ---- boundary: empty line + idempotent EOF on scanbytes --------------
// scanline on a leading '\n' must return an empty (len=0) view, not
// skip the empty line and pretend it's "the first non-empty line".
// scantok after EOF_DISCARD must keep returning io.eof on every
// subsequent call — same idempotency guaranteed by scanbyte rows 4/5
// and scanline rows 3/4, but the scantok path filled at the bottom
// of `for (true)` is its own state machine. Worth pinning.
@test fn boundarycases() void = {
// ---- empty line via scanline -----------------------------------
let s1: [16]u8;
let n1: i32 = putstr("\nfoo\n", s1[0:16], 0);
let m1mem: memio.state;
let m1: io.stream;
memio.fixed(&m1mem, &m1, s1[0:n1]);
let m1mem: memio.stream = memio.fixed(s1[0:n1]);
let m1: io.stream = &m1mem.vt;
let b1: [8]u8;
let sc1: bufio.scanner;
bufio.newscanner(&sc1, &m1, b1[0:8]);
let sc1: bufio.scanner = bufio.newscannerbuf(m1, b1[0:8]);
let r1: (str | io.eof | io.closed | bufio.overflow) = bufio.scanline(&sc1);
let r1: (str | io.eof | io.error | bufio.overflow) = bufio.scanline(&sc1);
match (r1) {
case let v: str => { if (v.len != 0) { fail(); }; };
case io.eof => fail();
case io.closed => fail();
case let e: io.error => fail();
case bufio.overflow => fail();
};
let r2: (str | io.eof | io.closed | bufio.overflow) = bufio.scanline(&sc1);
let r2: (str | io.eof | io.error | bufio.overflow) = bufio.scanline(&sc1);
match (r2) {
case let v: str => {
if (v.len != 3) { fail(); };
if (v[0] != 102u8) { fail(); }; // 'f'
};
case io.eof => fail();
case io.closed => fail();
case let e: io.error => fail();
case bufio.overflow => fail();
};
bufio.finish(&sc1);
// ---- scantok idempotent EOF -----------------------------------
// "ab" with delim ',' — no delim, EOF_DISCARD drops "ab", first
// call returns eof. The next call must too (state stays at eof).
// ---- scanbytes idempotent EOF ---------------------------------
let s2: [8]u8;
let n2: i32 = putstr("ab", s2[0:8], 0);
let m2mem: memio.state;
let m2: io.stream;
memio.fixed(&m2mem, &m2, s2[0:n2]);
let m2mem: memio.stream = memio.fixed(s2[0:n2]);
let m2: io.stream = &m2mem.vt;
let b2: [8]u8;
let sc2: bufio.scanner;
bufio.newscanner(&sc2, &m2, b2[0:8]);
let sc2: bufio.scanner = bufio.newscannerbuf(m2, b2[0:8]);
let t1: ([]u8 | io.eof | io.closed | bufio.overflow) = bufio.scantok(&sc2, 44u8);
let t1: ([]u8 | io.eof | io.error | bufio.overflow) = bufio.scanbytes(&sc2, 44u8);
match (t1) {
case let v: []u8 => fail();
case io.eof => { };
case io.closed => fail();
case let e: io.error => fail();
case bufio.overflow => fail();
};
let t2: ([]u8 | io.eof | io.closed | bufio.overflow) = bufio.scantok(&sc2, 44u8);
let t2: ([]u8 | io.eof | io.error | bufio.overflow) = bufio.scanbytes(&sc2, 44u8);
match (t2) {
case let v: []u8 => fail();
case io.eof => { };
case io.closed => fail();
case let e: io.error => fail();
case bufio.overflow => fail();
};
bufio.finish(&sc2);
@@ -362,20 +326,14 @@ fn closedstream(s: *io.stream) void = {
// ---- multi-fill: window smaller than longest token, shift path --------
// Buffer 4B, line "abcd\nxy\n". After reading "abcd", a delim search
// finds nothing, the buffer is full at start=0 → overflow. So bump
// to 5B (room for "abcd" + '\n'). Reads come in 5-byte chunks; the
// second line "xy" fits trivially and exercises the shift path.
@test fn multifill() void = {
let src: [32]u8;
let n: i32 = putstr("abcd\nxy\n", src[0:32], 0);
let mem: memio.state;
let m: io.stream;
memio.fixed(&mem, &m, src[0:n]);
let mem: memio.stream = memio.fixed(src[0:n]);
let m: io.stream = &mem.vt;
let buf: [5]u8;
let sc: bufio.scanner;
bufio.newscanner(&sc, &m, buf[0:5]);
let sc: bufio.scanner = bufio.newscannerbuf(m, buf[0:5]);
// (wantlen, wantfirst, weof)
let wl: [3]i32;
@@ -387,7 +345,7 @@ fn closedstream(s: *io.stream) void = {
let i: i32 = 0;
for (i < 3) {
let r: (str | io.eof | io.closed | bufio.overflow) = bufio.scanline(&sc);
let r: (str | io.eof | io.error | bufio.overflow) = bufio.scanline(&sc);
match (r) {
case let v: str => {
if (weof[i] != 0) { fail(); };
@@ -395,7 +353,7 @@ fn closedstream(s: *io.stream) void = {
if (v.len > 0 && v[0] != wf[i]) { fail(); };
};
case io.eof => { if (weof[i] == 0) { fail(); }; };
case io.closed => fail();
case let e: io.error => fail();
case bufio.overflow => fail();
};
i += 1;
@@ -408,30 +366,28 @@ fn closedstream(s: *io.stream) void = {
@test fn streamsmallwrite() void = {
let raw: [16]u8;
let mem: memio.state;
let m: io.stream;
memio.fixed(&mem, &m, raw[0:16]);
let mem: memio.stream = memio.fixed(raw[0:16]);
let m: io.stream = &mem.vt;
let b: bufio.stream;
let rb: [8]u8;
let wb: [8]u8;
bufio.init(&b, &m, rb[0:8], wb[0:8]);
let b: bufio.stream = bufio.init(m, rb[0:8], wb[0:8]);
// Write under the buffer limit — nothing reaches src.
let buf: [5]u8;
let z: i32 = putstr("hello", buf[0:5], 0);
let p: *io.stream = &b.vtable;
let r: (i32 | io.closed) = io.write(p, buf[0:5]);
let p: io.stream = &b.vt;
let r: (size | io.error) = io.write(p, buf[0:5]);
match (r) {
case let n: i32 => { if (n != 5) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != 5) { fail(); }; };
case let e: io.error => fail();
};
if (mem.pos != 0) { fail(); };
let fr: (void | io.closed) = bufio.flush(&b);
let fr: (void | io.error) = bufio.flush(&b);
match (fr) {
case void => { };
case io.closed => fail();
case let e: io.error => fail();
};
if (mem.pos != 5) { fail(); };
if (raw[0] != 104u8) { fail(); };
@@ -442,32 +398,29 @@ fn closedstream(s: *io.stream) void = {
@test fn streamautoflush() void = {
let raw: [32]u8;
let mem: memio.state;
let m: io.stream;
memio.fixed(&mem, &m, raw[0:32]);
let mem: memio.stream = memio.fixed(raw[0:32]);
let m: io.stream = &mem.vt;
let b: bufio.stream;
let rb: [4]u8;
let wb: [4]u8;
bufio.init(&b, &m, rb[0:4], wb[0:4]);
let b: bufio.stream = bufio.init(m, rb[0:4], wb[0:4]);
// 10B payload through a 4B wbuf: bwrite must flush twice mid-write,
// leaving the final 2B pending until an explicit flush.
// 10B payload through a 4B wbuf: bwrite must flush twice mid-write.
let buf: [10]u8;
let z: i32 = putstr("abcdefghij", buf[0:10], 0);
let p: *io.stream = &b.vtable;
let r: (i32 | io.closed) = io.write(p, buf[0:10]);
let p: io.stream = &b.vt;
let r: (size | io.error) = io.write(p, buf[0:10]);
match (r) {
case let n: i32 => { if (n != 10) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != 10) { fail(); }; };
case let e: io.error => fail();
};
// Two full flushes happened (8B); 2B still pending.
if (mem.pos != 8) { fail(); };
let fr: (void | io.closed) = bufio.flush(&b);
let fr: (void | io.error) = bufio.flush(&b);
match (fr) {
case void => { };
case io.closed => fail();
case let e: io.error => fail();
};
if (mem.pos != 10) { fail(); };
if (raw[0] != 97u8) { fail(); }; // 'a'
@@ -476,40 +429,34 @@ fn closedstream(s: *io.stream) void = {
// ---- default flush byte-set: '\n' in payload triggers flush ---------
// init seeds b.flush to ['\n']; bwrite must flush after copying when
// the payload contains any byte from b.flush. No setflush call —
// this exercises the default. Mirrors Hare's flag::NONE default
// (ref/hare/bufio/stream.ha:75 + 100).
@test fn streamlineflush() void = {
let raw: [32]u8;
let mem: memio.state;
let m: io.stream;
memio.fixed(&mem, &m, raw[0:32]);
let mem: memio.stream = memio.fixed(raw[0:32]);
let m: io.stream = &mem.vt;
let b: bufio.stream;
let rb: [4]u8;
let wb: [16]u8;
bufio.init(&b, &m, rb[0:4], wb[0:16]);
let b: bufio.stream = bufio.init(m, rb[0:4], wb[0:16]);
let p: *io.stream = &b.vtable;
let p: io.stream = &b.vt;
// First write: no '\n', stays buffered.
let h: [5]u8;
let zh: i32 = putstr("hello", h[0:5], 0);
let r1: (i32 | io.closed) = io.write(p, h[0:5]);
let r1: (size | io.error) = io.write(p, h[0:5]);
match (r1) {
case let n: i32 => { if (n != 5) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != 5) { fail(); }; };
case let e: io.error => fail();
};
if (mem.pos != 0) { fail(); };
// Second write: '\n' present, triggers flush of everything (6B).
let nl: [1]u8;
nl[0] = 10u8;
let r2: (i32 | io.closed) = io.write(p, nl[0:1]);
let r2: (size | io.error) = io.write(p, nl[0:1]);
match (r2) {
case let n: i32 => { if (n != 1) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != 1) { fail(); }; };
case let e: io.error => fail();
};
if (mem.pos != 6) { fail(); };
if (raw[0] != 104u8) { fail(); };
@@ -518,48 +465,42 @@ fn closedstream(s: *io.stream) void = {
// ---- explicit-flush-per-write: caller drives the drain ---------------
// The previous ww-only FLUSH_ON_WRITE flag is gone; the Hare way
// to drain after every write is a manual flush call. Setflush to
// an empty byte-set first so the default '\n' detector doesn't
// shadow what we're testing.
@test fn streamflushonwrite() void = {
let raw: [32]u8;
let mem: memio.state;
let m: io.stream;
memio.fixed(&mem, &m, raw[0:32]);
let mem: memio.stream = memio.fixed(raw[0:32]);
let m: io.stream = &mem.vt;
let b: bufio.stream;
let rb: [4]u8;
let wb: [16]u8;
bufio.init(&b, &m, rb[0:4], wb[0:16]);
let b: bufio.stream = bufio.init(m, rb[0:4], wb[0:16]);
let nilbs: [1]u8;
bufio.setflush(&b, nilbs[0:0]);
let p: *io.stream = &b.vtable;
let p: io.stream = &b.vt;
let h: [2]u8;
h[0] = 65u8; h[1] = 66u8; // "AB"
let r1: (i32 | io.closed) = io.write(p, h[0:2]);
let r1: (size | io.error) = io.write(p, h[0:2]);
match (r1) {
case let n: i32 => { if (n != 2) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != 2) { fail(); }; };
case let e: io.error => fail();
};
let f1: (void | io.closed) = bufio.flush(&b);
let f1: (void | io.error) = bufio.flush(&b);
match (f1) {
case void => { };
case io.closed => fail();
case let e: io.error => fail();
};
if (mem.pos != 2) { fail(); };
if (raw[0] != 65u8) { fail(); };
let r2: (i32 | io.closed) = io.write(p, h[0:1]);
let r2: (size | io.error) = io.write(p, h[0:1]);
match (r2) {
case let n: i32 => { if (n != 1) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != 1) { fail(); }; };
case let e: io.error => fail();
};
let f2: (void | io.closed) = bufio.flush(&b);
let f2: (void | io.error) = bufio.flush(&b);
match (f2) {
case void => { };
case io.closed => fail();
case let e: io.error => fail();
};
if (mem.pos != 3) { fail(); };
if (raw[2] != 65u8) { fail(); };
@@ -569,17 +510,15 @@ fn closedstream(s: *io.stream) void = {
@test fn streamisbuffered() void = {
let raw: [8]u8;
let mem: memio.state;
let m: io.stream;
memio.fixed(&mem, &m, raw[0:8]);
let mem: memio.stream = memio.fixed(raw[0:8]);
let m: io.stream = &mem.vt;
let b: bufio.stream;
let rb: [4]u8;
let wb: [4]u8;
bufio.init(&b, &m, rb[0:4], wb[0:4]);
let b: bufio.stream = bufio.init(m, rb[0:4], wb[0:4]);
if (!bufio.isbuffered(&b.vtable)) { fail(); };
if (bufio.isbuffered(&m)) { fail(); };
if (!bufio.isbuffered(&b.vt)) { fail(); };
if (bufio.isbuffered(m)) { fail(); };
};
// ---- bread + unread: pushed-back bytes come out first ----------------
@@ -588,25 +527,22 @@ fn closedstream(s: *io.stream) void = {
let raw: [8]u8;
let n: i32 = putstr("ABCDEFGH", raw[0:8], 0);
let mem: memio.state;
let m: io.stream;
memio.fixed(&mem, &m, raw[0:n]);
let mem: memio.stream = memio.fixed(raw[0:n]);
let m: io.stream = &mem.vt;
let b: bufio.stream;
let rb: [8]u8;
let wb: [4]u8;
bufio.init(&b, &m, rb[0:8], wb[0:4]);
let b: bufio.stream = bufio.init(m, rb[0:8], wb[0:4]);
let p: *io.stream = &b.vtable;
let p: io.stream = &b.vt;
// Pull 3 bytes through bread — fills rbuf from src (8B), serves
// 3 of them; rstart=3 after.
// Pull 3 bytes through bread — fills rbuf from src (8B), serves 3.
let out: [4]u8;
let r1: (i32 | io.eof | io.closed) = io.read(p, out[0:3]);
let r1: (size | io.eof | io.error) = io.read(p, out[0:3]);
match (r1) {
case let z: i32 => { if (z != 3) { fail(); }; };
case let z: size => { if (z: i32 != 3) { fail(); }; };
case io.eof => fail();
case io.closed => fail();
case let e: io.error => fail();
};
if (out[0] != 65u8) { fail(); }; // 'A'
if (out[2] != 67u8) { fail(); }; // 'C'
@@ -616,21 +552,21 @@ fn closedstream(s: *io.stream) void = {
push[0] = 88u8; push[1] = 89u8; // "XY"
bufio.unread(&b, push[0:2]);
let r2: (i32 | io.eof | io.closed) = io.read(p, out[0:2]);
let r2: (size | io.eof | io.error) = io.read(p, out[0:2]);
match (r2) {
case let z: i32 => { if (z != 2) { fail(); }; };
case let z: size => { if (z: i32 != 2) { fail(); }; };
case io.eof => fail();
case io.closed => fail();
case let e: io.error => fail();
};
if (out[0] != 88u8) { fail(); }; // 'X'
if (out[1] != 89u8) { fail(); }; // 'Y'
// Subsequent read returns the original tail.
let r3: (i32 | io.eof | io.closed) = io.read(p, out[0:4]);
let r3: (size | io.eof | io.error) = io.read(p, out[0:4]);
match (r3) {
case let z: i32 => { if (z != 4) { fail(); }; };
case let z: size => { if (z: i32 != 4) { fail(); }; };
case io.eof => fail();
case io.closed => fail();
case let e: io.error => fail();
};
if (out[0] != 68u8) { fail(); }; // 'D'
if (out[3] != 71u8) { fail(); }; // 'G'
@@ -638,34 +574,26 @@ fn closedstream(s: *io.stream) void = {
// ---- scanner over a stream-wrapped src: pre-read unread + scanline --
// Push three bytes into a freshly-init'd stream (rstart=rend=rbuf.len
// post-init, per Hare bufio/stream.ha:101 — the unread budget before
// the first read is the full rbuf), then scan a line through the
// stream. The scanner pulls "XYZ" from rbuf first, refills "hello\n"
// from src, and scanline returns "XYZhello".
@test fn streamscannerunread() void = {
let raw: [16]u8;
let n: i32 = putstr("hello\n", raw[0:16], 0);
let mem: memio.state;
let m: io.stream;
memio.fixed(&mem, &m, raw[0:n]);
let mem: memio.stream = memio.fixed(raw[0:n]);
let m: io.stream = &mem.vt;
let b: bufio.stream;
let rb: [16]u8;
let wb: [4]u8;
bufio.init(&b, &m, rb[0:16], wb[0:4]);
let b: bufio.stream = bufio.init(m, rb[0:16], wb[0:4]);
let push: [3]u8;
push[0] = 88u8; push[1] = 89u8; push[2] = 90u8; // "XYZ"
bufio.unread(&b, push[0:3]);
let p: *io.stream = &b.vtable;
let p: io.stream = &b.vt;
let sbuf: [32]u8;
let sc: bufio.scanner;
bufio.newscanner(&sc, p, sbuf[0:32]);
let sc: bufio.scanner = bufio.newscannerbuf(p, sbuf[0:32]);
let lr: (str | io.eof | io.closed | bufio.overflow) = bufio.scanline(&sc);
let lr: (str | io.eof | io.error | bufio.overflow) = bufio.scanline(&sc);
match (lr) {
case let v: str => {
if (v.len != 8) { fail(); }; // "XYZhello"
@@ -673,7 +601,7 @@ fn closedstream(s: *io.stream) void = {
if (v[7] != 111u8) { fail(); }; // 'o'
};
case io.eof => fail();
case io.closed => fail();
case let e: io.error => fail();
case bufio.overflow => fail();
};
bufio.finish(&sc);
@@ -683,55 +611,48 @@ fn closedstream(s: *io.stream) void = {
@test fn streamflushempty() void = {
let raw: [8]u8;
let mem: memio.state;
let m: io.stream;
memio.fixed(&mem, &m, raw[0:8]);
let mem: memio.stream = memio.fixed(raw[0:8]);
let m: io.stream = &mem.vt;
let b: bufio.stream;
let rb: [4]u8;
let wb: [4]u8;
bufio.init(&b, &m, rb[0:4], wb[0:4]);
let b: bufio.stream = bufio.init(m, rb[0:4], wb[0:4]);
let r: (void | io.closed) = bufio.flush(&b);
let r: (void | io.error) = bufio.flush(&b);
match (r) {
case void => { };
case io.closed => fail();
case let e: io.error => fail();
};
if (mem.pos != 0) { fail(); };
};
// ---- bclose drains pending wbuf then forwards close to src ----------
// io.close on the stream vtable must run the flush path (Hare
// stream.ha:188-202). Buffer two bytes, route io.close through the
// stream, and confirm both reach src.
@test fn streamcloseflushes() void = {
let raw: [8]u8;
let mem: memio.state;
let m: io.stream;
memio.fixed(&mem, &m, raw[0:8]);
let mem: memio.stream = memio.fixed(raw[0:8]);
let m: io.stream = &mem.vt;
let b: bufio.stream;
let rb: [4]u8;
let wb: [4]u8;
bufio.init(&b, &m, rb[0:4], wb[0:4]);
let b: bufio.stream = bufio.init(m, rb[0:4], wb[0:4]);
let nilbs: [1]u8;
bufio.setflush(&b, nilbs[0:0]); // suppress the default '\n' detector
let p: *io.stream = &b.vtable;
let p: io.stream = &b.vt;
let h: [2]u8;
h[0] = 80u8; h[1] = 81u8; // "PQ"
let r: (i32 | io.closed) = io.write(p, h[0:2]);
let r: (size | io.error) = io.write(p, h[0:2]);
match (r) {
case let n: i32 => { if (n != 2) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != 2) { fail(); }; };
case let e: io.error => fail();
};
if (mem.pos != 0) { fail(); }; // still buffered
let cr: (void | io.closed) = io.close(p);
let cr: (void | io.error) = io.close(p);
match (cr) {
case void => { };
case io.closed => fail();
case let e: io.error => fail();
};
if (mem.pos != 2) { fail(); };
if (raw[0] != 80u8) { fail(); };
@@ -740,34 +661,27 @@ fn closedstream(s: *io.stream) void = {
// ---- setflush with a custom non-empty byte-set -----------------------
// setflush installs a fresh byte-set (not just clears it). Use ' '
// (space) as the trigger; "ab cd" must flush at the space, leaving
// "cd" pending.
@test fn streamsetflushcustom() void = {
let raw: [16]u8;
let mem: memio.state;
let m: io.stream;
memio.fixed(&mem, &m, raw[0:16]);
let mem: memio.stream = memio.fixed(raw[0:16]);
let m: io.stream = &mem.vt;
let b: bufio.stream;
let rb: [4]u8;
let wb: [16]u8;
bufio.init(&b, &m, rb[0:4], wb[0:16]);
let b: bufio.stream = bufio.init(m, rb[0:4], wb[0:16]);
let bs: [1]u8;
bs[0] = 32u8; // ' '
bufio.setflush(&b, bs[0:1]);
let p: *io.stream = &b.vtable;
let p: io.stream = &b.vt;
let buf: [5]u8;
let z: i32 = putstr("ab cd", buf[0:5], 0);
let r: (i32 | io.closed) = io.write(p, buf[0:5]);
let r: (size | io.error) = io.write(p, buf[0:5]);
match (r) {
case let n: i32 => { if (n != 5) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != 5) { fail(); }; };
case let e: io.error => fail();
};
// Space at index 2 triggers post-copy flush of all 5 bytes; the
// scan-then-copy-then-flush ordering writes everything, so mem.pos
// hits 5 in one shot.
// Space at index 2 triggers post-copy flush of all 5 bytes.
if (mem.pos != 5) { fail(); };
if (raw[2] != 32u8) { fail(); };
if (raw[4] != 100u8) { fail(); }; // 'd'
@@ -777,9 +691,9 @@ export fn main() i32 = {
signalled = 1; scanbytecases();
signalled = 2; scanlinecases();
signalled = 3; scanlineoverflow();
signalled = 4; scantokcases();
signalled = 4; scanbytescases();
signalled = 5; emptystream();
signalled = 6; closedsource();
signalled = 6; errorsource();
signalled = 7; boundarycases();
signalled = 8; multifill();
signalled = 9; streamsmallwrite();

View File

@@ -1,429 +0,0 @@
// vstream — Hare-shaped vtable port of lib/bufio. Project #94
// fold-eFinal(PREP).
//
// Collapses the OLD bufio surface (bufio.ww's pre-vtable `stream` +
// `scanner` over a legacy `*io.stream` src) onto the io.vtable surface,
// as parallel `_v`-suffixed additions. The OLD surface stays untouched
// here — fold-eFinal (FLIP) deletes it, renames the `_v` suffix off
// (bufio_ctx→stream, bufio_vstream→init, scanner_v→scanner, scanbytes_v
// →scanbytes, isbuffered_v→isbuffered, …) per Hare's
// ref/hare/bufio/{stream,scanner}.ha, and migrates the few callers.
//
// SCOPE (#94 fold-eFinal): this is a COLLAPSE of the surface ww ALREADY
// ships, NOT a port of Hare's full scanner. The features OLD bufio
// never implemented — auto-grow newscanner, multibyte-delim scanbytes /
// scanstring, scanrune / readbyte / readtok / readline / readrune /
// unreadrune — are a separate future feature fold (#217) and are OUT of
// eFinal. The ported set mirrors OLD exactly: the fixed-buffer
// newscanner_buf, single-byte scanbytes (OLD scantok), single-byte
// scanline, scanbyte, finish; and the buffered-stream init / setflush /
// flush / unread / isbuffered.
//
// VALUE-RETURN (drew, gating): Hare's bufio constructors return BY
// VALUE (ref/hare/bufio/stream.ha:69 init, scanner.ha:92
// newscanner_buf). The OLD out-param shape existed only because
// wide-struct sret was unimplemented — it now round-trips
// (test/wcc/925 + 776/778 byte-id), so each constructor builds in a
// local `let r: T;`, field-assigns every slot, and `return r;`. The
// caller owns the returned struct (stack ownership, no-GC). For the
// buffered stream the caller passes `&b.vt` to the io.st_* dispatchers;
// the scanner is a plain read-ahead tokenizer (no embedded vtable —
// matching OLD), driven directly via scanbyte_v / scanbytes_v / …
//
// VTABLE-NATIVE SRC (the eFinal point): the buffered stream's `src` and
// the scanner's `src` are now `io.vstream`, and every underlying read /
// write / close goes through io.st_read / io.st_write / io.st_close
// (the vtable dispatchers) — NOT the legacy io.read / io.write /
// io.close over `*io.stream`. The io.st_* surface returns io.error
// directly, so the OLD `io.closed → nomem-widen → io.error` dance
// (per the #173 deferral) is GONE: the error simply forwards.
//
// Cast workaround per #206-payoff (ken: KEEP the explicit casts; they
// are cgen-neutral and sidestep the #214 over-acceptance surface).
// `(&fn_name): *io.<role>` at each vtable store + the isbuffered_v
// fn-ptr-equality comparand — same workaround memio/vstream.ww +
// test/wcc/775 use.
//
// #207 (struct-lit multi-tagged-field copy drops past first) and #210
// (slice-field struct-lit drop under alloc): not reachable from the
// value-return form — every struct is built field-by-field in a local
// then sret-returned, no `alloc(T{...})` struct-lit. Both filings stay
// open for the struct-lit path; this fold doesn't take it.
//
// #209 (wwstage formattable match-arm bail when fmt imported): bufio
// does NOT import fmt (only io), so test/wcc/778 runs both stages +
// cs.s == ww.s byte-id on every row — bufio's only byte-id coverage
// (it is not compiler-embedded).
//
// Mode discrimination (drew-deferred): Hare uses three vtable
// singletons (vtable_r / vtable_w / vtable_rw); bufio_vstream installs
// all three callbacks per bufio.ww:32-38's divergence (zero-length
// rbuf/wbuf degenerates the matching callback in-cb). Defer-handle
// (MANAGED_* ownership bits) also deferred — caller owns rbuf/wbuf/src;
// bclose_v flushes + forwards close but frees nothing. Both graduate
// with io fold-2 (#5).
package bufio;
import io;
// bufio_ctx — heap-free state for bufio_vstream. `vt` at offset 0 for
// the intrusive vstream→*bufio_ctx cast. `src` is an io.vstream (the
// vtable-native underlying handle), not the OLD `*io.stream`. Mirrors
// lib/bufio.stream (bufio.ww:252) modulo io.vtable + io.vstream src.
export type bufio_ctx = struct {
vt: io.vtable,
src: io.vstream,
rbuf: []u8,
rstart: i32,
rend: i32,
wbuf: []u8,
wend: i32,
flush: []u8,
};
// bufio_vstream — wire a buffered stream over an underlying io.vstream
// with caller-supplied read/write buffers. Both rbuf and wbuf may be
// empty; bread_v / bwrite_v degenerate per bufio.ww:34-38. The flush
// byte-set defaults to "\n" (line-buffered writes); setflush_v swaps it.
//
// Returns the ctx BY VALUE (sret — field-by-field build then
// `return r;`); the caller passes `&b.vt` to the io.st_* dispatchers.
//
// Mirrors ref/hare/bufio/stream.ha:69 (init).
export fn bufio_vstream(src: io.vstream, rbuf: []u8, wbuf: []u8) bufio_ctx = {
let r: bufio_ctx;
r.vt.reader = (&bread_v): *io.reader;
r.vt.writer = (&bwrite_v): *io.writer;
r.vt.closer = (&bclose_v): *io.closer;
r.src = src;
r.rbuf = rbuf;
r.rstart = rbuf.len;
r.rend = rbuf.len;
r.wbuf = wbuf;
r.wend = 0;
r.flush = flushdefault[0:1];
return r;
};
// setflush_v — install a new flush byte-set. Any byte from `bs`
// appearing in a write payload triggers an automatic flush after the
// write copies into wbuf. Mirrors OLD setflush (bufio.ww:287) +
// ref/hare/bufio/stream.ha:128.
export fn setflush_v(b: *bufio_ctx, bs: []u8) void = {
b.flush = bs;
};
// flush_v — drain any pending wbuf data to src. Public API AND the
// internal drain called by bwrite_v / bclose_v — OLD bufio.flush is
// itself dual-purpose (bufio.ww:294, called from bwrite/bclose), so a
// single fn serves both; no separate private callback to rename. The
// io.closed → nomem-widen of the OLD V-side is gone: io.st_write
// returns io.error directly. A 0-byte non-error write means the sink
// made no progress (memio.fixed full) — surfaced as a nomem-carried
// io.error rather than spinning, matching what io.writeall would do in
// Hare. Mirrors OLD flush (bufio.ww:294) + ref/hare/bufio/stream.ha.
export fn flush_v(b: *bufio_ctx) (void | io.error) = {
if (b.wend == 0) { return; };
let off: i32 = 0;
for (off < b.wend) {
let r: (size | io.error) = io.st_write(b.src, b.wbuf[off:b.wend]);
match (r) {
case let n: size => {
if (n == 0: size) {
let nm: nomem;
let e: io.error = nm;
return e;
};
off += n: i32;
};
case let e: io.error => return e;
};
};
b.wend = 0;
return;
};
// unread_v — push `buf` back into the read buffer so the next bread_v
// returns it first. The bytes must fit in front of the pending region
// (rstart >= buf.len); Hare aborts on overflow, ww does the same via
// rtabort (declared in bufio.ww, same package). Mirrors OLD unread
// (bufio.ww:321) + ref/hare/bufio/stream.ha:164.
export fn unread_v(b: *bufio_ctx, buf: []u8) void = {
if (b.rstart < buf.len) {
rtabort("bufio.unread_v: more data than rbuf has room for");
};
let i: i32 = 0;
for (i < buf.len) {
b.rbuf[b.rstart - buf.len + i] = buf[i];
i += 1;
};
b.rstart -= buf.len;
};
// 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) over the new `_v` symbols.
// Either reader or writer matching is sufficient.
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;
};
// ---- buffered-stream vtable callbacks ------------------------------------
// bread_v — vstream-side buffered read. Recover ctx via the intrusive
// cast; refill rbuf from src via io.st_read on empty pending region.
// Mirrors bufio.ww:346 (OLD bread) modulo the io.vstream src +
// io.st_read dispatch (io.error forwards directly — no nomem-widen).
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: (size | io.eof | io.error) = io.st_read(b.src, b.rbuf);
match (r) {
case let n: size => { b.rend = n: i32; };
case io.eof => { let e: io.eof; return e; };
case let e: io.error => 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 buffered write. wbuf-empty passes through to
// src; otherwise default-flush scan + per-batch copy + post-write
// conditional flush. Mirrors bufio.ww:376 (OLD bwrite) modulo the
// io.vstream src + io.st_write dispatch. Labeled-break inlined per
// bufio.ww:382 (ww has no labeled break).
fn bwrite_v(s: io.vstream, buf: []u8) (size | io.error) = {
let b: *bufio_ctx = s: *bufio_ctx;
if (b.wbuf.len == 0) {
return io.st_write(b.src, buf);
};
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 via io.st_close.
// Mirrors bufio.ww:431. Caller-owned buffers/src are not freed (drew
// defer-handle deferral, bufio.ww:33-47).
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.error) = io.st_close(b.src);
match (cr) {
case void => return void;
case let e: io.error => return e;
};
};
// ---- scanner (read-ahead tokenizer over an io.vstream src) ---------------
//
// Plain tokenizer — no embedded vtable (matching OLD bufio.scanner,
// bufio.ww:104); the scanner is driven directly via scanbyte_v /
// scanbytes_v / scanline_v, not through io.st_*. Its `src` is an
// io.vstream and refills go through io.st_read. Value-return
// constructor (Hare newscanner_buf, scanner.ha:92).
//
// ptr/cap kept flat (no `buf: []u8`) per the OLD scanner shape
// (bufio.ww:104-110); the scanner predates struct-held slice support.
export type scanner_v = struct {
src: io.vstream,
ptr: *u8,
cap: i32,
start: i32, // index where the pending region starts in ptr
avail: i32, // pending byte count; pending = ptr[start..start+avail]
};
// newscannerbuf_v — wire a scanner to read through `src` using `buf` as
// the fixed read-ahead window. Returns the scanner BY VALUE. This is
// Hare's newscanner_buf (ref/hare/bufio/scanner.ha:92); the auto-grow
// newscanner is a separate feature (#217, OUT of eFinal). Mirrors OLD
// newscanner (bufio.ww:116), which is itself the fixed-buffer form.
export fn newscannerbuf_v(src: io.vstream, buf: []u8) scanner_v = {
let r: scanner_v;
r.src = src;
r.ptr = buf.ptr;
r.cap = buf.len;
r.start = 0;
r.avail = 0;
return r;
};
// finish_v — release scanner-owned resources. No-op (buffer is
// caller-owned, src isn't closed); kept on the surface so callers won't
// churn. Mirrors OLD finish (bufio.ww:127) + scanner.ha:110.
export fn finish_v(s: *scanner_v) void = { };
// readahead_v — make room and read once from src into the back of the
// pending region. Returns bytes newly buffered (>=0), or io.eof/io.error
// from src. Mirrors OLD readahead (bufio.ww:133) modulo io.st_read +
// io.error (no nomem-widen). Returns i32 (OLD shape); the size from
// io.st_read narrows to i32 (buffer-length type).
fn readahead_v(s: *scanner_v) (i32 | io.eof | io.error) = {
if (s.start + s.avail == s.cap && s.start > 0) {
let i: i32 = 0;
for (i < s.avail) {
s.ptr[i] = s.ptr[s.start + i];
i += 1;
};
s.start = 0;
};
let off: i32 = s.start + s.avail;
let v: []u8;
v.ptr = s.ptr + (off: u64);
v.len = s.cap - off;
let r: (size | io.eof | io.error) = io.st_read(s.src, v);
match (r) {
case let n: size => {
s.avail += n: i32;
return n: i32;
};
case io.eof => { let e: io.eof; return e; };
case let e: io.error => return e;
};
};
// scanbyte_v — pop one byte, refilling from src on demand. Mirrors OLD
// scanbyte (bufio.ww:160) + ref/hare/bufio/scanner.ha:204.
export fn scanbyte_v(s: *scanner_v) (u8 | io.eof | io.error) = {
for (s.avail == 0) {
let r: (i32 | io.eof | io.error) = readahead_v(s);
match (r) {
case let n: i32 => { };
case io.eof => { let e: io.eof; return e; };
case let e: io.error => return e;
};
};
let b: u8 = s.ptr[s.start];
s.start += 1;
s.avail -= 1;
return b;
};
// scanbytes_v — read up to (and not including) the next byte equal to
// `delim`. The delim is consumed but not returned. The returned slice
// borrows from the scanner buffer and is invalidated by the next scan.
// EOF without delim discards the trailing fragment and returns io.eof
// (Hare EOF_DISCARD default); buffer-full without delim returns
// overflow. Single-byte delim only — Hare's `(u8 | []u8)` multibyte
// form is #217 (OUT). drew renames OLD scantok → scanbytes. Mirrors OLD
// scantok (bufio.ww:186) + ref/hare/bufio/scanner.ha:220 (narrowed).
export fn scanbytes_v(s: *scanner_v, delim: u8) ([]u8 | io.eof | io.error | overflow) = {
let i: i32 = 0;
for (true) {
for (i < s.avail) {
if (s.ptr[s.start + i] == delim) {
let v: []u8;
v.ptr = s.ptr + (s.start: u64);
v.len = i;
s.start += i + 1;
s.avail -= i + 1;
return v;
};
i += 1;
};
if (s.start + s.avail == s.cap && s.start == 0) {
let e: overflow; return e;
};
let r: (i32 | io.eof | io.error) = readahead_v(s);
match (r) {
case let n: i32 => { };
case io.eof => { let e: io.eof; return e; };
case let e: io.error => return e;
};
};
let e: io.eof; return e;
};
// scanline_v — read up to (and not including) the next '\n'. The newline
// is consumed; the returned str view borrows from the scanner buffer.
// Single-byte route (Hare's scan_line = scan_string(s, "\n"); the
// arbitrary multibyte-delim scan_string is #217, OUT). Mirrors OLD
// scanline (bufio.ww:225) + ref/hare/bufio/scanner.ha:307.
export fn scanline_v(s: *scanner_v) (str | io.eof | io.error | overflow) = {
let r: ([]u8 | io.eof | io.error | overflow) = scanbytes_v(s, 10u8);
match (r) {
case let bs: []u8 => {
let v: str;
v.ptr = bs.ptr;
v.len = bs.len;
return v;
};
case io.eof => { let e: io.eof; return e; };
case let e: io.error => return e;
case overflow => { let e: overflow; return e; };
};
};

File diff suppressed because it is too large Load Diff

View File

@@ -30,168 +30,161 @@ fn streq(a: str, b: str) bool = {
return true;
};
// ---- A write-always-closed stream for the io.closed surfacing test ----
// ---- an error-returning stream for the io.error surfacing test --------
//
// Replaces the OLD io.closed source. errvt is module-static; its
// reader/writer return a nomem-widened io.error.
let errvt: io.vtable;
fn closedread(s: *io.stream, buf: []u8) (i32 | io.eof | io.closed) = {
let e: io.closed; return e;
fn errread(s: io.stream, buf: []u8) (size | io.eof | io.error) = {
let nm: nomem; let e: io.error = nm; return e;
};
fn closedwrite(s: *io.stream, buf: []u8) (i32 | io.closed) = {
let e: io.closed; return e;
fn errwrite(s: io.stream, buf: []u8) (size | io.error) = {
let nm: nomem; let e: io.error = nm; return e;
};
fn closedclose(s: *io.stream) (void | io.closed) = { return; };
fn closedstream(s: *io.stream) void = {
s.ctx = nil;
s.read = closedread;
s.write = closedwrite;
s.close = closedclose;
fn errsource() io.stream = {
errvt.reader = (&errread): *io.reader;
errvt.writer = (&errwrite): *io.writer;
return &errvt;
};
// ---- fprint: bare str --------------------------------------------------
@test fn fprintbarestr() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
let r: (i32 | io.closed) = fmt.fprint(&s, "hello");
let r: (size | io.error) = fmt.fprint(s, "hello");
match (r) {
case let n: i32 => { if (n != 5) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != 5) { fail(); }; };
case let eioe: io.error => fail();
};
if (!streq(memio.string(&mem), "hello")) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let eioe: io.error => fail(); };
};
// ---- fprint: int + str mix, space-separated ----------------------------
@test fn fprintintstr() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
let r: (i32 | io.closed) = fmt.fprint(&s, 42i64, "x");
let r: (size | io.error) = fmt.fprint(s, 42i64, "x");
match (r) {
case let n: i32 => { if (n != 4) { fail(); }; }; // "42 x"
case io.closed => fail();
case let n: size => { if (n: i32 != 4) { fail(); }; }; // "42 x"
case let eioe: io.error => fail();
};
if (!streq(memio.string(&mem), "42 x")) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let eioe: io.error => fail(); };
};
// ---- fprint: bool + rune renders as "true A" --------------------------
@test fn fprintboolrune() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
let r: (i32 | io.closed) = fmt.fprint(&s, true, 'A': rune);
let r: (size | io.error) = fmt.fprint(s, true, 'A': rune);
match (r) {
case let n: i32 => { if (n != 6) { fail(); }; }; // "true A"
case io.closed => fail();
case let n: size => { if (n: i32 != 6) { fail(); }; }; // "true A"
case let eioe: io.error => fail();
};
if (!streq(memio.string(&mem), "true A")) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let eioe: io.error => fail(); };
};
// ---- fprint: zero args returns 0, no bytes written --------------------
@test fn fprintempty() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
let r: (i32 | io.closed) = fmt.fprint(&s);
let r: (size | io.error) = fmt.fprint(s);
match (r) {
case let n: i32 => { if (n != 0) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != 0) { fail(); }; };
case let eioe: io.error => fail();
};
if (memio.string(&mem).len != 0) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let eioe: io.error => fail(); };
};
// ---- fprintln: multi-arg, trailing '\n' --------------------------------
@test fn fprintlnmulti() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
let r: (i32 | io.closed) = fmt.fprintln(&s, "a", 1i64, false);
let r: (size | io.error) = fmt.fprintln(s, "a", 1i64, false);
match (r) {
case let n: i32 => { if (n != 10) { fail(); }; }; // "a 1 false\n"
case io.closed => fail();
case let n: size => { if (n: i32 != 10) { fail(); }; }; // "a 1 false\n"
case let eioe: io.error => fail();
};
if (!streq(memio.string(&mem), "a 1 false\n")) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let eioe: io.error => fail(); };
};
// ---- fprintln: zero args writes just the newline ----------------------
@test fn fprintlnempty() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
let r: (i32 | io.closed) = fmt.fprintln(&s);
let r: (size | io.error) = fmt.fprintln(s);
match (r) {
case let n: i32 => { if (n != 1) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != 1) { fail(); }; };
case let eioe: io.error => fail();
};
if (!streq(memio.string(&mem), "\n")) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let eioe: io.error => fail(); };
};
// ---- fprint over a fixed stream: short writes return partial count ----
// memio.fixedwrite caps each call at the remaining buffer space and
// never closes the stream, so fprint sees a short i32 result, not
// io.closed. Verifies the inner-loop arithmetic adds the actual byte
// io.error. Verifies the inner-loop arithmetic adds the actual byte
// count rather than the requested length.
@test fn fprintfixedshort() void = {
let buf: [3]u8;
let mem: memio.state;
let s: io.stream;
memio.fixed(&mem, &s, buf[0:3]);
let mem: memio.stream = memio.fixed(buf[0:3]);
let s: io.stream = &mem.vt;
let r: (i32 | io.closed) = fmt.fprint(&s, "hello");
let r: (size | io.error) = fmt.fprint(s, "hello");
match (r) {
case let n: i32 => { if (n != 3) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != 3) { fail(); }; };
case let eioe: io.error => fail();
};
if (!streq(memio.string(&mem), "hel")) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let eioe: io.error => fail(); };
};
// ---- fprint over a closed stream surfaces io.closed -------------------
// ---- fprint over a closed stream surfaces io.error -------------------
// Exercises the early-return arm in fprint's inner match — distinct from
// the short-write path above, which keeps returning i32 from a partial
// accept. Single arm is enough: every formattable case routes errors
// through the same putbytes / io.write wiring.
@test fn fprintclosed() void = {
let s: io.stream;
closedstream(&s);
let s: io.stream = errsource();
let r: (i32 | io.closed) = fmt.fprint(&s, "x");
let r: (size | io.error) = fmt.fprint(s, "x");
match (r) {
case let n: i32 => fail();
case io.closed => {};
case let n: size => fail();
case let eioe: io.error => {};
};
};
@@ -201,203 +194,189 @@ fn closedstream(s: *io.stream) void = {
// see the file header.
@test fn fprintf_implicit() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let r: (i32 | io.closed) = fmt.fprintf(&s, "hello {} {}", "world", 42i64);
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
let r: (size | io.error) = fmt.fprintf(s, "hello {} {}", "world", 42i64);
match (r) {
case let n: i32 => { if (n != 14) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != 14) { fail(); }; };
case let eioe: io.error => fail();
};
if (!streq(memio.string(&mem), "hello world 42")) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let eioe: io.error => fail(); };
};
@test fn fprintf_indexed() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let r: (i32 | io.closed) = fmt.fprintf(&s, "{1} {0} {1}", "hello", "world");
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
let r: (size | io.error) = fmt.fprintf(s, "{1} {0} {1}", "hello", "world");
match (r) {
case let n: i32 => { if (n != 17) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != 17) { fail(); }; };
case let eioe: io.error => fail();
};
if (!streq(memio.string(&mem), "world hello world")) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let eioe: io.error => fail(); };
};
@test fn fprintf_literal_braces() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let r: (i32 | io.closed) = fmt.fprintf(&s, "{{ {} }}", 1i64);
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
let r: (size | io.error) = fmt.fprintf(s, "{{ {} }}", 1i64);
match (r) {
case let n: i32 => { if (n != 5) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != 5) { fail(); }; };
case let eioe: io.error => fail();
};
if (!streq(memio.string(&mem), "{ 1 }")) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let eioe: io.error => fail(); };
};
@test fn fprintf_width_right() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let r: (i32 | io.closed) = fmt.fprintf(&s, "{:5}", 42i64);
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
let r: (size | io.error) = fmt.fprintf(s, "{:5}", 42i64);
match (r) {
case let n: i32 => { if (n != 5) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != 5) { fail(); }; };
case let eioe: io.error => fail();
};
if (!streq(memio.string(&mem), " 42")) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let eioe: io.error => fail(); };
};
@test fn fprintf_width_left() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let r: (i32 | io.closed) = fmt.fprintf(&s, "{:-5}", 42i64);
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
let r: (size | io.error) = fmt.fprintf(s, "{:-5}", 42i64);
match (r) {
case let n: i32 => { if (n != 5) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != 5) { fail(); }; };
case let eioe: io.error => fail();
};
if (!streq(memio.string(&mem), "42 ")) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let eioe: io.error => fail(); };
};
@test fn fprintf_width_center() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let r: (i32 | io.closed) = fmt.fprintf(&s, "{:=5}", "hi");
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
let r: (size | io.error) = fmt.fprintf(s, "{:=5}", "hi");
match (r) {
case let n: i32 => { if (n != 5) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != 5) { fail(); }; };
case let eioe: io.error => fail();
};
if (!streq(memio.string(&mem), " hi ")) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let eioe: io.error => fail(); };
};
@test fn fprintf_pad_underscore() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let r: (i32 | io.closed) = fmt.fprintf(&s, "{:_05}", "hi");
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
let r: (size | io.error) = fmt.fprintf(s, "{:_05}", "hi");
match (r) {
case let n: i32 => { if (n != 5) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != 5) { fail(); }; };
case let eioe: io.error => fail();
};
if (!streq(memio.string(&mem), "000hi")) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let eioe: io.error => fail(); };
};
@test fn fprintf_base_hex() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let r: (i32 | io.closed) = fmt.fprintf(&s, "{:x} {:X}", 48879i64, 61453i64);
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
let r: (size | io.error) = fmt.fprintf(s, "{:x} {:X}", 48879i64, 61453i64);
match (r) {
case let n: i32 => { if (n != 9) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != 9) { fail(); }; };
case let eioe: io.error => fail();
};
if (!streq(memio.string(&mem), "beef F00D")) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let eioe: io.error => fail(); };
};
@test fn fprintf_base_oct_bin() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let r: (i32 | io.closed) = fmt.fprintf(&s, "{:o} {:b}", 493i64, 27i64);
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
let r: (size | io.error) = fmt.fprintf(s, "{:o} {:b}", 493i64, 27i64);
match (r) {
case let n: i32 => { if (n != 9) { fail(); }; }; // "755 11011"
case io.closed => fail();
case let n: size => { if (n: i32 != 9) { fail(); }; }; // "755 11011"
case let eioe: io.error => fail();
};
if (!streq(memio.string(&mem), "755 11011")) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let eioe: io.error => fail(); };
};
@test fn fprintf_prec_int() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let r: (i32 | io.closed) = fmt.fprintf(&s, "{:.5}", 42i64);
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
let r: (size | io.error) = fmt.fprintf(s, "{:.5}", 42i64);
match (r) {
case let n: i32 => { if (n != 5) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != 5) { fail(); }; };
case let eioe: io.error => fail();
};
if (!streq(memio.string(&mem), "00042")) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let eioe: io.error => fail(); };
};
@test fn fprintf_prec_str_trunc() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let r: (i32 | io.closed) = fmt.fprintf(&s, "{:.3}", "hello");
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
let r: (size | io.error) = fmt.fprintf(s, "{:.3}", "hello");
match (r) {
case let n: i32 => { if (n != 3) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != 3) { fail(); }; };
case let eioe: io.error => fail();
};
if (!streq(memio.string(&mem), "hel")) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let eioe: io.error => fail(); };
};
@test fn fprintf_sign_neg() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let r: (i32 | io.closed) = fmt.fprintf(&s, "{} {:+} {: }", -7i64, 7i64, 7i64);
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
let r: (size | io.error) = fmt.fprintf(s, "{} {:+} {: }", -7i64, 7i64, 7i64);
match (r) {
case let n: i32 => { if (n != 8) { fail(); }; }; // "-7 +7 7"
case io.closed => fail();
case let n: size => { if (n: i32 != 8) { fail(); }; }; // "-7 +7 7"
case let eioe: io.error => fail();
};
if (!streq(memio.string(&mem), "-7 +7 7")) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let eioe: io.error => fail(); };
};
@test fn fprintfln_basic() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let r: (i32 | io.closed) = fmt.fprintfln(&s, "x={}", 9i64);
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
let r: (size | io.error) = fmt.fprintfln(s, "x={}", 9i64);
match (r) {
case let n: i32 => { if (n != 4) { fail(); }; }; // "x=9\n"
case io.closed => fail();
case let n: size => { if (n: i32 != 4) { fail(); }; }; // "x=9\n"
case let eioe: io.error => fail();
};
if (!streq(memio.string(&mem), "x=9\n")) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let eioe: io.error => fail(); };
};
@test fn fprintf_closed() void = {
let s: io.stream;
closedstream(&s);
let r: (i32 | io.closed) = fmt.fprintf(&s, "hello {}", 1i64);
let s: io.stream = errsource();
let r: (size | io.error) = fmt.fprintf(s, "hello {}", 1i64);
match (r) {
case let n: i32 => fail();
case io.closed => {};
case let n: size => fail();
case let eioe: io.error => {};
};
};
@test fn bsprintf_basic() void = {
let buf: [16]u8;
let r: (str | io.closed) = fmt.bsprintf(buf[0:16], "{} {}", "hi", 42i64);
let r: (str | io.error) = fmt.bsprintf(buf[0:16], "{} {}", "hi", 42i64);
match (r) {
case let s: str => { if (!streq(s, "hi 42")) { fail(); }; };
case io.closed => fail();
case let eioe: io.error => fail();
};
};
@@ -407,29 +386,28 @@ fn closedstream(s: *io.stream) void = {
// tagged arms so a future task-#18 fix that re-introduces the 24B
// widen-by-value cannot regress silently.
@test fn fprintf_bool_rune() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let r: (i32 | io.closed) = fmt.fprintf(&s, "{} {}", true, 'A': rune);
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
let r: (size | io.error) = fmt.fprintf(s, "{} {}", true, 'A': rune);
match (r) {
case let n: i32 => { if (n != 6) { fail(); }; }; // "true A"
case io.closed => fail();
case let n: size => { if (n: i32 != 6) { fail(); }; }; // "true A"
case let eioe: io.error => fail();
};
if (!streq(memio.string(&mem), "true A")) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let eioe: io.error => fail(); };
};
// Verifies bsprintf's documented truncation contract — short writes
// return the prefix that fit (memio.fixedwrite returns 0 once full,
// not io.closed). Pairs with fprintfixedshort above which covers the
// not io.error). Pairs with fprintfixedshort above which covers the
// underlying fprint path.
@test fn bsprintf_trunc() void = {
let buf: [3]u8;
let r: (str | io.closed) = fmt.bsprintf(buf[0:3], "{} {}", "hi", 42i64);
let r: (str | io.error) = fmt.bsprintf(buf[0:3], "{} {}", "hi", 42i64);
match (r) {
case let s: str => { if (!streq(s, "hi ")) { fail(); }; };
case io.closed => fail();
case let eioe: io.error => fail();
};
};
@@ -440,10 +418,10 @@ fn closedstream(s: *io.stream) void = {
// formatraw + tail-pad both write 0 — must terminate.
@test fn bsprintf_width_trunc() void = {
let buf: [3]u8;
let r: (str | io.closed) = fmt.bsprintf(buf[0:3], "{:5}", 42i64);
let r: (str | io.error) = fmt.bsprintf(buf[0:3], "{:5}", 42i64);
match (r) {
case let s: str => { if (!streq(s, " ")) { fail(); }; };
case io.closed => fail();
case let eioe: io.error => fail();
};
};
@@ -494,173 +472,162 @@ fn closedstream(s: *io.stream) void = {
// `prec` and `base` ignored — graduate when strconv grows ffmt/fflags.
@test fn fprintf_f64_basic() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let r: (i32 | io.closed) = fmt.fprintf(&s, "{}", 1.5);
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
let r: (size | io.error) = fmt.fprintf(s, "{}", 1.5);
match (r) {
case let n: i32 => { if (n != 3) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != 3) { fail(); }; };
case let eioe: io.error => fail();
};
if (!streq(memio.string(&mem), "1.5")) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let eioe: io.error => fail(); };
};
@test fn fprintf_f64_int_valued() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let r: (i32 | io.closed) = fmt.fprintf(&s, "{}", 1.0);
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
let r: (size | io.error) = fmt.fprintf(s, "{}", 1.0);
match (r) {
case let n: i32 => { if (n != 1) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != 1) { fail(); }; };
case let eioe: io.error => fail();
};
if (!streq(memio.string(&mem), "1")) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let eioe: io.error => fail(); };
};
@test fn fprintf_f64_neg() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let r: (i32 | io.closed) = fmt.fprintf(&s, "{}", -2.5);
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
let r: (size | io.error) = fmt.fprintf(s, "{}", -2.5);
match (r) {
case let n: i32 => { if (n != 4) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != 4) { fail(); }; };
case let eioe: io.error => fail();
};
if (!streq(memio.string(&mem), "-2.5")) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let eioe: io.error => fail(); };
};
@test fn fprintf_f64_zero() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let r: (i32 | io.closed) = fmt.fprintf(&s, "{}", 0.0);
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
let r: (size | io.error) = fmt.fprintf(s, "{}", 0.0);
match (r) {
case let n: i32 => { if (n != 1) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != 1) { fail(); }; };
case let eioe: io.error => fail();
};
if (!streq(memio.string(&mem), "0")) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let eioe: io.error => fail(); };
};
@test fn fprintf_f64_small_frac() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let r: (i32 | io.closed) = fmt.fprintf(&s, "{}", 0.05);
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
let r: (size | io.error) = fmt.fprintf(s, "{}", 0.05);
match (r) {
case let n: i32 => { if (n != 4) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != 4) { fail(); }; };
case let eioe: io.error => fail();
};
if (!streq(memio.string(&mem), "0.05")) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let eioe: io.error => fail(); };
};
// Pins a large magnitude through the fmt dispatch — post-Ryū-graduation
// (fold-5) this is scientific notation, not the old lossy "huge" fallback.
// Catches any regression where mods accidentally pre-truncate the view.
@test fn fprintf_f64_huge() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let r: (i32 | io.closed) = fmt.fprintf(&s, "{}", 9.5e18);
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
let r: (size | io.error) = fmt.fprintf(s, "{}", 9.5e18);
match (r) {
case let n: i32 => { if (n != 6) { fail(); }; }; // "9.5e18"
case io.closed => fail();
case let n: size => { if (n: i32 != 6) { fail(); }; }; // "9.5e18"
case let eioe: io.error => fail();
};
if (!streq(memio.string(&mem), "9.5e18")) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let eioe: io.error => fail(); };
};
// Sign-mod fold: '+' on positive, '-' still wins on negative (the
// natural '-' from strconv is peeled and signof reapplies).
@test fn fprintf_f64_sign_plus() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let r: (i32 | io.closed) = fmt.fprintf(&s, "{:+} {:+}", 1.5, -1.5);
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
let r: (size | io.error) = fmt.fprintf(s, "{:+} {:+}", 1.5, -1.5);
match (r) {
case let n: i32 => { if (n != 9) { fail(); }; }; // "+1.5 -1.5"
case io.closed => fail();
case let n: size => { if (n: i32 != 9) { fail(); }; }; // "+1.5 -1.5"
case let eioe: io.error => fail();
};
if (!streq(memio.string(&mem), "+1.5 -1.5")) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let eioe: io.error => fail(); };
};
@test fn fprintf_f64_sign_space() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let r: (i32 | io.closed) = fmt.fprintf(&s, "{: }", 1.5);
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
let r: (size | io.error) = fmt.fprintf(s, "{: }", 1.5);
match (r) {
case let n: i32 => { if (n != 4) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != 4) { fail(); }; };
case let eioe: io.error => fail();
};
if (!streq(memio.string(&mem), " 1.5")) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let eioe: io.error => fail(); };
};
// Width + alignment round-trip through rawlenf64 (sign-aware len count).
@test fn fprintf_f64_width_right() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let r: (i32 | io.closed) = fmt.fprintf(&s, "{:8}", 1.5);
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
let r: (size | io.error) = fmt.fprintf(s, "{:8}", 1.5);
match (r) {
case let n: i32 => { if (n != 8) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != 8) { fail(); }; };
case let eioe: io.error => fail();
};
if (!streq(memio.string(&mem), " 1.5")) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let eioe: io.error => fail(); };
};
@test fn fprintf_f64_width_left() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let r: (i32 | io.closed) = fmt.fprintf(&s, "{:-8}", 1.5);
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
let r: (size | io.error) = fmt.fprintf(s, "{:-8}", 1.5);
match (r) {
case let n: i32 => { if (n != 8) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != 8) { fail(); }; };
case let eioe: io.error => fail();
};
if (!streq(memio.string(&mem), "1.5 ")) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let eioe: io.error => fail(); };
};
// Legacy fprint variadic — covers the non-printf surface arm (where
// formatraw isn't reached because there's no mods parser).
@test fn fprint_f64_variadic() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let r: (i32 | io.closed) = fmt.fprint(&s, 1.5, "x");
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
let r: (size | io.error) = fmt.fprint(s, 1.5, "x");
match (r) {
case let n: i32 => { if (n != 5) { fail(); }; }; // "1.5 x"
case io.closed => fail();
case let n: size => { if (n: i32 != 5) { fail(); }; }; // "1.5 x"
case let eioe: io.error => fail();
};
if (!streq(memio.string(&mem), "1.5 x")) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let eioe: io.error => fail(); };
};
@test fn bsprintf_f64() void = {
let buf: [16]u8;
let r: (str | io.closed) = fmt.bsprintf(buf[0:16], "x={}", 1.5);
let r: (str | io.error) = fmt.bsprintf(buf[0:16], "x={}", 1.5);
match (r) {
case let s: str => { if (!streq(s, "x=1.5")) { fail(); }; };
case io.closed => fail();
case let eioe: io.error => fail();
};
};

View File

@@ -1,667 +0,0 @@
// vstream — Hare-shaped vtable wrappers over an fd sink. Project #94
// fold-e3 (Option C, parallel API for lib/fmt).
//
// Adds four wrappers that take a raw fd and dispatch through an
// [[io.vstream]] (= `*io.vtable`, the Hare-shape from
// lib/io/stream.ww) alongside the pre-vtable fdprint / fdprintln /
// fdprintf / fdprintfln in fmt.ww. The OLD surface stays untouched
// here — fold-eFinal (task #50) atomically flips the package shape:
// deletes the OLD wrappers + callbacks, renames `_v` suffix off, and
// migrates the few callers.
//
// Hare routes fmt's fd sinks through `io::handle = (io::file | int)`
// (ref/hare/fmt/wrappers.ha:9-25); ww doesn't have the handle sum yet
// so the pure-vstream sink — drew-deferred per fold-d/fold-e3 —
// stands in until io fold-2 lands the handle port. fd_ctx is the
// minimum carrier: one tagged field (vt) + one i32 (fd). Single-
// tagged-field shape lets direct struct-lit init through (sibling
// task #207 only bites multi-tagged-field copies); ken's escape-risk
// discipline holds — every wrapper stack-allocates fd_ctx inside its
// own frame, gets `vs = &c.vt` from the same frame, and dispatches
// without returning &c outside it.
//
// 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
// uses (2 cast tokens here at the vtable wire-up site; both drop out
// wholesale once #206 closes).
//
// Dispatch chain (per wrapper):
// fd{print,println,printf,printfln}_v
// → vfprint / vfprintf (internal vstream-side formatter)
// → io.st_write(vs, buf)
// → vs.writer (= fdsinkwrite_v after the cast)
// → os.write(c.fd, ...)
//
// The internal vfprint/vfprintf duplicate the per-arg and {n}-
// placeholder loops from fmt.ww (fprint at line 177, fprintf at 676)
// because the OLD versions take `*io.stream` (the legacy struct) and
// fmt.ww must stay UNCHANGED this fold. Shared bits — `i64dec`,
// `modsinit`, `scandigits`, `scanmods`, `formattable`, `field`,
// `mods`, `fmtabort`, `signof`, `digitsu64`, `basenum`, the modifier
// enums (`neg`, `alignment`) — are reused directly from fmt.ww (same
// package). The v* modifier-formatting helpers (vrawleni64 / vrawlenstr
// / vrawlenf64 / vrawlen / vformatraw / vformatone) mirror the OLD
// rawlen* / formatraw / formatone verbatim (compute body identical;
// vputbytes + (size | io.error) routing instead of putbytes +
// (i32 | io.closed)) — #94 fold-e6 ports them V-side so V's printf
// honours modifiers (fold-e3 dropped them after parse). eFinal (#50)
// collapses both surfaces.
//
// Sibling tasks parked here (filed, NOT fixed):
//
// - io fold-2 (handle port): drew-deferred. Hare's
// ref/hare/fmt/wrappers.ha:9-25 routes through io::handle;
// fdNNN_v collapses into bare fNNN_v once handle = (file | int)
// lands and the V API graduates over the OLD per-fd surface.
// - #206 (bare &fn → (*alias|void)): 2 cast sites here; drops
// out wholesale on close.
// - #173 (TRY-on-tagged-return both-stages broken): fdsinkwrite_v
// constructs nomem and widens to io.error explicitly rather
// than using `os.trywrite(...)?`; same shape memio.vstream.ww
// adopted at line 71-74.
package fmt;
import io;
import memio;
import os;
import strconv;
import strings;
// fd_ctx — vt at offset 0 for the intrusive vstream→*fd_ctx cast.
// Mirrors lib/memio.fixed_ctx (memio/vstream.ww:48); single tagged
// field (vt) means the multi-tagged-field drop in #207 doesn't bite.
export type fd_ctx = struct {
vt: io.vtable,
fd: i32,
};
// fdsinkread_v — eof-only sink; the fd half of fmt is write-only.
// Mirrors OLD fdsinkread (fmt.ww:765). The unused-`buf` parameter
// matches io.reader's signature.
fn fdsinkread_v(s: io.vstream, buf: []u8) (size | io.eof | io.error) = {
let e: io.eof;
return e;
};
// fdsinkwrite_v — recover fd via intrusive cast, dispatch one
// os.write. -errno collapses to a nomem-widened io.error: the OLD
// fdsinkwrite (fmt.ww:768) flattens this to io.closed; we keep the
// Hare-shape error here because the V surface returns io.error
// directly. Construction-then-widen (vs `os.trywrite(...)?`)
// sidesteps #173 same as memio.vstream.ww (line 71-74 note).
fn fdsinkwrite_v(s: io.vstream, buf: []u8) (size | io.error) = {
let c: *fd_ctx = s: *fd_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;
};
// ---- internal vstream-side formatters -------------------------------
// vputbytes — io.st_write(vs, [ptr..ptr+n)). Internal helper; the
// inline `let v` slice synthesis mirrors fmt.putbytes (fmt.ww:160)
// for the legacy *io.stream sink.
fn vputbytes(vs: io.vstream, p: *u8, n: i32) (size | io.error) = {
let v: []u8;
v.ptr = p;
v.len = n;
return io.st_write(vs, v);
};
// vwriteone — emit one formattable through io.st_write. Mirror of
// the per-arm dispatch in fmt.fdprint (fmt.ww:103-138) and fmt.fprint
// (fmt.ww:188-234); shape matches OLD modulo the (size | io.error)
// return instead of i64 / (i32 | io.closed).
fn vwriteone(vs: io.vstream, a: formattable) (size | io.error) = {
match (a) {
case let n: i64 => {
let s: str = i64dec(n);
return vputbytes(vs, s.ptr, s.len);
};
case let s: str => return vputbytes(vs, s.ptr, s.len);
case let b: bool => {
let s: str = "false";
if (b) { s = "true"; };
return vputbytes(vs, s.ptr, s.len);
};
case let r: rune => {
let buf: [4]u8;
buf[0] = r: u8;
return vputbytes(vs, &buf[0], 1);
};
case let v: f64 => {
// strconv.f64tos static-buffer view consumed before next
// strconv call — same shape as fmt.fdprint (fmt.ww:130).
let s: str = strconv.f64tos(v);
return vputbytes(vs, s.ptr, s.len);
};
};
return 0: size;
};
// ---- V-side modifier formatting (port of OLD rawlen*/formatraw/
// formatone from fmt.ww:443-641). Project #94 fold-e6.
//
// V-side mirror of the OLD modifier-formatting machinery so the V
// printf path honours width / alignment / pad / sign / base / prec
// instead of dropping mods after parse. Drew-signoff: NaN/Inf safe
// — strconv.f64tos / f32tos render "nan"/"infinity" with no leading
// '-', so the sign-peel in vrawlenf64 / vformatraw f64 arm is a
// no-op on those views (sibling of OLD rawlenf64's same path). Ken-
// signoff: cs==ww mechanical — both stages compile the new V-side
// identically; eFinal (#50) graduates the V-side over the OLD.
//
// Helpers mirror fmt.ww verbatim (compute-only, no I/O) rather than
// reusing OLD by call — fold-eFinal (#50) atomically deletes the OLD
// surface AND collapses these v* names back, so the duplication is
// transient. Mirror sites:
//
// vrawleni64 fmt.ww:443 rawleni64
// vrawlenstr fmt.ww:457 rawlenstr
// vrawlenf64 fmt.ww:569 rawlenf64
// vrawlen fmt.ww:585 rawlen
// vformatraw fmt.ww:465 formatraw
// vformatone fmt.ww:598 formatone
// vrawleni64 — bytes the raw render of `v` under `m` would emit; the
// signof / digitsu64 / basenum helpers are read from fmt.ww (same
// package). Mirror fmt.ww:443.
fn vrawleni64(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;
};
// vrawlenstr — bytes the raw render of `s` would emit (after `prec`
// truncation). Mirror fmt.ww:457.
fn vrawlenstr(s: str, m: *mods) i32 = {
if (m.prec > 0 && m.prec < s.len) { return m.prec; };
return s.len;
};
// vrawlenf64 — 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. Mirror
// fmt.ww:569; drew NaN/Inf safe (strconv emits no leading '-' on
// nan/inf, so the peel is a no-op on those views).
fn vrawlenf64(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] == 45u8) {
had_neg = true;
body -= 1;
};
let signlen: i32 = 0;
if (signof(had_neg, m) != 0u8) { signlen = 1; };
return signlen + body;
};
// vrawlen — dispatch the rawlen sum over formattable. Mirror fmt.ww:585.
fn vrawlen(arg: formattable, m: *mods) i32 = {
match (arg) {
case let v: i64 => return vrawleni64(v, m);
case let v: str => return vrawlenstr(v, m);
case let b: bool => { if (b) { return 4; }; return 5; };
case let r: rune => return 1;
case let v: f64 => return vrawlenf64(v, m);
};
return 0; // unreachable — match is exhaustive
};
// vformatraw — write the bare value (no width padding) to `vs`. Mirror
// fmt.ww:465 formatraw, vputbytes-routed and (size | io.error)-typed.
// `prec` ignored on f64 (strconv.f64tos is shortest-G with no precision
// knob); base ignored on f64 (Hare too — base is int-only). Drew-
// signoff: NaN/Inf strings emitted unchanged via vputbytes.
fn vformatraw(vs: io.vstream, 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) = vputbytes(vs, &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] = 48u8; // '0'
let r: (size | io.error) = vputbytes(vs, &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) = vputbytes(vs, 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 = vrawlenstr(v, m);
return vputbytes(vs, v.ptr, n);
};
case let b: bool => {
let v: str = "false";
if (b) { v = "true"; };
return vputbytes(vs, v.ptr, v.len);
};
case let r: rune => {
let buf: [4]u8;
buf[0] = r: u8;
return vputbytes(vs, &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
// (sign-mod on a nan emits e.g. "+nan" — a fmt-layer edge,
// not strconv's; same OLD behaviour at fmt.ww:520-562).
let view: str = strconv.f64tos(v);
let neg_flag: bool = false;
if (view.len > 0 && view.ptr[0] == 45u8) { // '-'
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) = vputbytes(vs, &buf[0], 1);
match (r) {
case let n: size => { total += n; };
case let e: io.error => return e;
};
};
let r: (size | io.error) = vputbytes(vs, 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
};
// vformatone — render `arg` to `vs` with `m`'s width / alignment / pad
// applied. Mirror fmt.ww:598 formatone; the tail-pad loop drives on
// a counter (mirrors OLD) because vputbytes 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_v when the sink runs out.
fn vformatone(vs: io.vstream, arg: formattable, m: *mods) (size | io.error) = {
let start: i32 = 0;
if (m.width > 0 && m.alignment != alignment.LEFT) {
let raw: i32 = vrawlen(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) = vputbytes(vs, &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) = vformatraw(vs, 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) = vputbytes(vs, &padb[0], 1);
match (r) {
case let n: size => { total += n; };
case let e: io.error => return e;
};
j += 1;
};
return total;
};
// vformatfield — field→formattable inline-per-arm dispatch through
// vformatone. The for-loop call site in vfprintf would trip task #18
// (silent miscompile of 24B return-by-value in for-loop context) if
// widened via a helper; inline-per-arm sidesteps it — same shape OLD
// formatfield uses at fmt.ww:648. `*mods` arm aborts (parametric '%'
// form not implemented; OLD fprintf aborts identically). #94 fold-e6
// widens the signature with `*mods` so the V path honours modifiers
// (fold-e3 dropped them after parse).
fn vformatfield(vs: io.vstream, f: field, m: *mods) (size | io.error) = {
match (f) {
case let v: i64 => {
let a: formattable = v;
return vformatone(vs, a, m);
};
case let v: str => {
let a: formattable = v;
return vformatone(vs, a, m);
};
case let b: bool => {
let a: formattable = b;
return vformatone(vs, a, m);
};
case let r: rune => {
let a: formattable = r;
return vformatone(vs, a, m);
};
case let v: f64 => {
let a: formattable = v;
return vformatone(vs, a, m);
};
case let p: *mods => { fmtabort(); let z: size = 0; return z; };
};
};
// vfprint — vstream-side fprint. Mirror of fmt.fprint (fmt.ww:177).
// Per-arg loop with a space separator; returns total bytes written
// or the first io.error. Exported so lib/log's V API (fold-e5) can
// dispatch through it; eFinal (#50) collapses fprint over the unified
// surface.
export fn vfprint(vs: io.vstream, 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) = vputbytes(vs, " ".ptr, 1);
match (r) {
case let n: size => { total += n; };
case let e: io.error => return e;
};
};
let r: (size | io.error) = vwriteone(vs, args[i]);
match (r) {
case let n: size => { total += n; };
case let e: io.error => return e;
};
i += 1;
};
return total;
};
// vfprintf — vstream-side fprintf. Mirror of fmt.fprintf (fmt.ww:676).
// {n}-placeholder parser routed through vformatfield + vputbytes
// instead of fmt.formatfield + fmt.putbytes; the placeholder language
// (incl. `{N:mods}` modifier subset) is identical — scandigits /
// scanmods / modsinit reused directly from fmt.ww. Exported (same
// rationale as vfprint above) so lib/log's V API dispatches through it.
export fn vfprintf(vs: io.vstream, 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 == 123u8) { // '{'
i += 1;
if (i >= fmt.len) { fmtabort(); };
if (fmt[i] == 123u8) { // '{{' literal
let r: (size | io.error) = vputbytes(vs, 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 >= 48u8 && d <= 57u8) {
checkunused = false;
idx = scandigits(fmt, &i);
} else {
idx = nextimpl;
nextimpl += 1;
};
let m: mods;
modsinit(&m);
if (i < fmt.len && fmt[i] == 58u8) { // ':'
i += 1;
scanmods(fmt, &i, &m);
};
if (i >= fmt.len || fmt[i] != 125u8) { fmtabort(); };
i += 1;
if (idx >= args.len) { fmtabort(); };
let r: (size | io.error) = vformatfield(vs, args[idx], &m);
match (r) {
case let n: size => { total += n; };
case let e: io.error => return e;
};
};
} else if (c == 125u8) { // '}'
i += 1;
if (i >= fmt.len || fmt[i] != 125u8) { fmtabort(); };
let r: (size | io.error) = vputbytes(vs, 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) = vputbytes(vs, 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;
};
// ---- public fd-sink wrappers (V API) ---------------------------------
// fdprint_v — fdprint over io.vstream. Stack-allocates fd_ctx; the
// &c.vt vstream pointer never escapes this frame (ken's escape-risk
// discipline). Mirrors fmt.fdprint (fmt.ww:94).
export fn fdprint_v(fd: i32, args: formattable...) (size | io.error) = {
let c: fd_ctx;
c.fd = fd;
c.vt.reader = (&fdsinkread_v): *io.reader;
c.vt.writer = (&fdsinkwrite_v): *io.writer;
let vs: io.vstream = &c.vt;
return vfprint(vs, args...);
};
// fdprintln_v — fdprint_v + trailing newline. Mirrors fmt.fdprintln
// (fmt.ww:145).
export fn fdprintln_v(fd: i32, args: formattable...) (size | io.error) = {
let c: fd_ctx;
c.fd = fd;
c.vt.reader = (&fdsinkread_v): *io.reader;
c.vt.writer = (&fdsinkwrite_v): *io.writer;
let vs: io.vstream = &c.vt;
let total: size = 0;
match (vfprint(vs, args...)) {
case let n: size => { total = n; };
case let e: io.error => return e;
};
match (vputbytes(vs, "\n".ptr, 1)) {
case let n: size => { total += n; };
case let e: io.error => return e;
};
return total;
};
// fdprintf_v — fdprintf over io.vstream. Mirrors fmt.fdprintf
// (fmt.ww:786).
export fn fdprintf_v(fd: i32, fmt: str, args: field...) (size | io.error) = {
let c: fd_ctx;
c.fd = fd;
c.vt.reader = (&fdsinkread_v): *io.reader;
c.vt.writer = (&fdsinkwrite_v): *io.writer;
let vs: io.vstream = &c.vt;
return vfprintf(vs, fmt, args...);
};
// fdprintfln_v — fdprintf_v + trailing newline. Mirrors
// fmt.fdprintfln (fmt.ww:797) and Hare's wrappers.ha:69.
export fn fdprintfln_v(fd: i32, fmt: str, args: field...) (size | io.error) = {
let c: fd_ctx;
c.fd = fd;
c.vt.reader = (&fdsinkread_v): *io.reader;
c.vt.writer = (&fdsinkwrite_v): *io.writer;
let vs: io.vstream = &c.vt;
let total: size = 0;
match (vfprintf(vs, fmt, args...)) {
case let n: size => { total = n; };
case let e: io.error => return e;
};
match (vputbytes(vs, "\n".ptr, 1)) {
case let n: size => { total += n; };
case let e: io.error => return e;
};
return total;
};
// ---- V-side compositions over the vfprint / vfprintf primitives.
// Project #94 fold-e7. drew-approved bundle (memio.string_v is the
// collapsed single accessor over the common `stream` header — direct
// enabler, not churn — feedback_refactor_routing_same_class_drops);
// ken cs==ww mechanical
// (additive only). eFinal (#50) collapses both surfaces and renames
// `v` suffix off.
//
// Mirror sites:
// vfprintln fmt.ww:240 fprintln ref/hare/fmt/wrappers.ha:48
// vfprintfln fmt.ww:740 fprintfln ref/hare/fmt/wrappers.ha:69
// vbsprintf fmt.ww:839 bsprintf ref/hare/fmt/wrappers.ha:42
// vasprintf fmt.ww:873 asprintf ref/hare/fmt/wrappers.ha:29
// vfprintln — vfprint + trailing newline. Mirror fmt.fprintln
// (fmt.ww:240); vputbytes + (size | io.error) routing.
export fn vfprintln(vs: io.vstream, args: formattable...) (size | io.error) = {
let total: size = 0;
match (vfprint(vs, args...)) {
case let n: size => { total = n; };
case let e: io.error => return e;
};
match (vputbytes(vs, "\n".ptr, 1)) {
case let n: size => { total += n; };
case let e: io.error => return e;
};
return total;
};
// vfprintfln — vfprintf + trailing newline. Mirror fmt.fprintfln
// (fmt.ww:740).
export fn vfprintfln(vs: io.vstream, fmt: str, args: field...) (size | io.error) = {
let total: size = 0;
match (vfprintf(vs, fmt, args...)) {
case let n: size => { total = n; };
case let e: io.error => return e;
};
match (vputbytes(vs, "\n".ptr, 1)) {
case let n: size => { total += n; };
case let e: io.error => return e;
};
return total;
};
// vbsprintf — render into `buf` through memio.fixed_vstream; return
// the str view of bytes actually written. Mirror fmt.bsprintf
// (fmt.ww:839). memio.fixed_vstream now returns the `stream` BY VALUE
// (fold-eFinal PREP, ref/hare/memio/stream.ha:46) — no alloc, no
// `nomem` arm. The stream lives in this frame; `&st.vt` is the
// io.vstream and `&st` the accessor handle. Hare wrappers.ha:42 returns
// `(const str | nomem)`; ww collapses to (str | io.error) so the
// vfprintf path's io.error arm stays uniform. Short writes surface as a
// prefix (memio.fixed contract, vstream.ww fixedwrite note).
export fn vbsprintf(buf: []u8, fmt: str, args: field...) (str | io.error) = {
let st: memio.stream = memio.fixed_vstream(buf);
let vs: io.vstream = &st.vt;
match (vfprintf(vs, fmt, args...)) {
case let n: size => { return memio.string_v(&st); };
case let e: io.error => return e;
};
};
// vasprintf — render `fmt`/`args` into a heap-allocated str through
// memio.dynamic_vstream, shrink-copy to a tight allocation, close the
// dynamic backing. Mirror fmt.asprintf (fmt.ww:873) 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 (same shape strings.dup uses at strings.ww:70).
// Shrink-to-fit rationale: memio.dynamic_vstream's cap doubles past
// pos during growth (memio dynamicgrow_v); io.st_close frees the
// cap-sized mapping. Returning memio.string_v 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 vasprintf(fmt: str, args: field...) str = {
let out: str;
out.ptr = nil;
out.len = 0;
// memio.dynamic_vstream returns the `stream` BY VALUE (fold-eFinal
// PREP, ref/hare/memio/stream.ha:58) — no alloc, no `nomem` arm.
// The stream lives in this frame across the vfprintf + close; the
// heap-grown backing (st.ptr) is freed by io.st_close.
let st: memio.stream = memio.dynamic_vstream();
let vs: io.vstream = &st.vt;
let wres: (size | io.error) = vfprintf(vs, fmt, args...);
match (wres) {
case let n: size => {};
case let e: io.error => {};
};
let view: str = memio.string_v(&st);
if (view.len == 0) {
let cres: (void | io.error) = io.st_close(vs);
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.st_close(vs);
match (cres) { case void => {}; case let e: io.error => {}; };
tight.len = view.len;
return strings.frombytes(tight);
};

View File

@@ -1,44 +1,26 @@
// io — stream interface (Plan 9 Bio / Hare io::stream shape).
//
// No closures, no methods. A `stream` is a struct of function
// pointers plus a `ctx: *void`. The error channel is the return
// value of read/write/close — Hare-shaped tagged unions instead of
// errno-style integer sentinels.
// No closures, no methods. A `stream` is a pointer to a `vtable` of
// function pointers (lib/io/stream.ww); read/write/close dispatch
// through it. The error channel is the return value — Hare-shaped
// tagged unions instead of errno-style integer sentinels.
//
// This file owns the eof / underread variant tags; lib/io/stream.ww
// owns the `vtable` + `stream` + read/write/close dispatchers, and
// lib/io/types.ww owns the error union, mode/whence enums, and the
// reader/writer/closer fn-type aliases. #94 fold-eFinal collapsed the
// pre-vtable `stream` struct + `closed` tag into the single vtable
// surface; the dispatchers are read/write/close (over `stream`),
// final over `handle` at io fold-2 (#5).
package io;
// eof — read past the end of the stream. Hare uses the `done`
// singleton for EOF; ww doesn't have `done` yet so we ship a
// named-void variant tag.
package io;
// named-void variant tag. Lifts to `done` with #93.
export type eof = void;
// closed — operation attempted on a stream that has already been
// closed. ww-specific: Hare's io collapses this into the wider
// errors union, but our stream vtable has no handle-ownership
// semantics, so a distinct tag is honest. Named void.
export type closed = void;
// underread — an I/O handle hit eof partway through a fixed-size
// read. Payload is the byte count actually delivered. Mirrors
// Hare's `io::underread = !size`; ww uses i32 because the
// underlying buffer-length type is i32 today.
export type underread = !i32;
export type stream = struct {
ctx: *void,
read: fn(s: *stream, buf: []u8) (i32 | eof | closed),
write: fn(s: *stream, buf: []u8) (i32 | closed),
close: fn(s: *stream) (void | closed),
};
export fn read(s: *stream, buf: []u8) (i32 | eof | closed) = {
return s.read(s, buf);
};
export fn write(s: *stream, buf: []u8) (i32 | closed) = {
return s.write(s, buf);
};
export fn close(s: *stream) (void | closed) = {
return s.close(s);
};

View File

@@ -1,8 +1,7 @@
// stream — Hare-shaped vtable port. Project #94 fold-e1.
// stream — Hare-shaped vtable surface. Project #94 fold-eFinal.
//
// Adds the additive vtable surface alongside lib/io/io.ww's pre-vtable
// stream struct + read/write/close wrappers (which fold-e2-eN migrate
// to vtable-backed implementations and then retire). Three exports:
// The single io stream surface (the fold-eFinal collapse retired the
// pre-vtable `stream` struct + `closed` tag). Three exports:
//
// vtable a struct of optional fn-pointer slots — reader/writer/
// closer per ref/hare/io/stream.ha:36-42. Hare spells the
@@ -10,35 +9,25 @@
// spelling (#192), so each slot is a `(*T | void)` tagged
// union (Plan-9-lean per the Nullable kept ruling; opt-in
// at optionality boundaries only).
// vstream `*vtable` alias matching Hare's `stream = *vtable`
// (ref/hare/io/stream.ha:33). Named `vstream` rather than
// `stream` because lib/io/io.ww still owns the legacy
// `stream` struct name until fold-eN collapses both.
// st_read /
// st_write /
// st_close the three dispatchers per ref/hare/io/stream.ha:44-68.
// stream `*vtable` alias matching Hare's `stream = *vtable`
// (ref/hare/io/stream.ha:33).
// read /
// write /
// close the three dispatchers per ref/hare/io/stream.ha:44-68.
// Each `match`es the matching vtable slot, returns
// `errors.unsupported` (widened twice) when the slot is
// void, otherwise calls through the fn pointer.
// void, otherwise calls through the fn pointer. Hare keeps
// these private (`st_read` …) behind a `handle`-typed
// public `read`/`write`/`close`; ww has no `handle` yet
// (io fold-2, #5), so the dispatchers ARE the public
// surface and grow the `handle` match when #5 lands.
//
// Fold-e1 cgen/checker dependencies (all closed at HEAD): #191
// (N_DOT on aliased-ptr receiver — `s.reader` resolves through the
// `vstream = *vtable` alias), #198 (`is` on cross-module variant
// tag), #199 α (concrete→tagged widen via NAMED-variant nominal
// match, used by `let e: error = u;`), #200 (`as` payload-extract),
// #201 (return-of-tagged-call forwards instead of rewinds), #205
// (tagged→tagged subset accepts NAMED-variant nominal match, used
// by `return e;` where ret type contains `error` as a direct
// variant). Two direct widens compose for the void-arm `return`:
// concrete `errors.unsupported` → tagged `error` via #199 α, then
// tagged `error` → tagged `(size | eof | error)` via #205.
//
// Deferrals (drew-signed): `seeker` lands with fold-e2 once `off`
// + `whence` plug into the signature; Hare's `?`-propagating
// `st_close` body (`c(s)?;`) collapses to a direct `return (*c)(s);`
// here because #173 (TRY-on-tagged-return both-stages broken) is
// still open — the dispatcher's surface stays Hare-shaped, only the
// internal try-prop is omitted.
// Deferrals (drew-signed): `seeker` lands with io fold-2 (#5) once `off`
// + `whence` plug into the signature. Hare's `?`-propagating `close`
// body (`c(s)?;`) collapses to a direct `return (*c)(s);` here because
// the dispatcher's surface stays Hare-shaped; only the internal
// try-prop is omitted (#173 history, now closed — kept direct for the
// void-arm fall-through shape).
package io;
@@ -50,16 +39,15 @@ export type vtable = struct {
closer: (*closer | void),
};
// ref/hare/io/stream.ha:33. `vstream` not `stream` while io.ww still
// owns the legacy `stream` struct name.
export type vstream = *vtable;
// ref/hare/io/stream.ha:33.
export type stream = *vtable;
// ref/hare/io/stream.ha:44-50. The void-arm `return e;` chains two
// direct widens: `errors.unsupported → error` via #199 α, then
// `error → (size | eof | error)` via #205. The call-arm spells the
// fn-ptr call explicitly per #193 (ww requires `(*r)(args)` rather
// than implicit `r(args)`).
export fn st_read(s: vstream, buf: []u8) (size | eof | error) = {
export fn read(s: stream, buf: []u8) (size | eof | error) = {
match (s.reader) {
case void => {
let u: errors.unsupported;
@@ -71,7 +59,7 @@ export fn st_read(s: vstream, buf: []u8) (size | eof | error) = {
};
// ref/hare/io/stream.ha:53-59.
export fn st_write(s: vstream, buf: []u8) (size | error) = {
export fn write(s: stream, buf: []u8) (size | error) = {
match (s.writer) {
case void => {
let u: errors.unsupported;
@@ -84,10 +72,9 @@ export fn st_write(s: vstream, buf: []u8) (size | error) = {
// ref/hare/io/stream.ha:62-68. Hare propagates the close error via
// `c(s)?;` then falls through to the void arm; ww collapses to a
// direct return because #173 (TRY-on-tagged-return) is still open.
// The surface (void | error) matches; only the internal try-prop
// is omitted.
export fn st_close(s: vstream) (void | error) = {
// direct return for the void-arm fall-through shape. The surface
// (void | error) matches.
export fn close(s: stream) (void | error) = {
match (s.closer) {
case void => return void;
case let c: *closer => return (*c)(s);

View File

@@ -1,19 +1,17 @@
// types — error union, mode/whence enums, reader/writer/closer
// fn-type aliases. Project #94 fold-1 step (d) (drew Fold D); the
// fn-aliases re-targeted from *stream → vstream as part of fold-e1
// (vtable port in stream.ww).
// fn-type aliases. Project #94 fold-eFinal; the fn-aliases target
// `stream` (= `*vtable`, the single io surface).
//
// Hare splits the io module across stream.ha + types.ha and ww does
// the same: lib/io/io.ww owns the (pre-vtable) `stream`, `eof`, and
// `underread` shapes; lib/io/stream.ww owns the Hare vtable port
// (`vtable`, `vstream`, `st_read`/`st_write`/`st_close` dispatch);
// this file owns the surrounding port. The three files share
// `package io;` so cross-file refs resolve via the dir-enum concat
// order (io.ww < stream.ww < types.ww — `vstream` lands before the
// reader/writer/closer aliases below).
// the same: lib/io/io.ww owns the `eof` and `underread` tags;
// lib/io/stream.ww owns the vtable surface (`vtable`, `stream`,
// `read`/`write`/`close` dispatch); this file owns the surrounding
// port. The three files share `package io;` so cross-file refs
// resolve via the dir-enum concat order (io.ww < stream.ww < types.ww
// — `stream` lands before the reader/writer/closer aliases below).
//
// Deferrals (drew-signed): `handle`, `seeker`, `copier`, `strerror`
// land with fold-e2+ (need the `handle` sum). Hare's `EOF = done`
// land with io fold-2 (#5; need the `handle` sum). Hare's `EOF = done`
// stays as `io.eof` until the `done` singleton ships (#93).
package io;
@@ -43,20 +41,20 @@ export type whence = enum i32 {
// ww enumerates the tags explicitly (ken's #204-block — spread of an
// errors-side tagged into this union triggers a wrapper-vs-flatten
// layout mismatch; #199b layout-extension is the deferred fix). The
// st_read/st_write dispatchers in stream.ww return
// `errors.unsupported` when the matching vtable slot is unset, so the
// variant lands here directly.
// read/write dispatchers in stream.ww return `errors.unsupported`
// when the matching vtable slot is unset, so the variant lands here
// directly.
export type error = !(errors.unsupported | underread | nomem);
// ref/hare/io/types.ha:46. `eof` (not Hare's `done`) per the io.ww:8
// rationale; lifts to `done` with #93. `vstream` forward-refs the
// ref/hare/io/types.ha:46. `eof` (not Hare's `done`) per the io.ww
// rationale; lifts to `done` with #93. `stream` forward-refs the
// `*vtable` alias in stream.ww — the dispatcher receives the vtable
// pointer directly (Hare's stream.ha:33 + types.ha:46 collapse).
export type reader = fn(s: vstream, buf: []u8) (size | eof | error);
export type reader = fn(s: stream, buf: []u8) (size | eof | error);
// ref/hare/io/types.ha:51. No `const` qualifier — ww has none, and
// lib/io/io.ww:30 + lib/sort/sort.ww:18 drop it the same way.
export type writer = fn(s: vstream, buf: []u8) (size | error);
// lib/sort/sort.ww:18 drops it the same way.
export type writer = fn(s: stream, buf: []u8) (size | error);
// ref/hare/io/types.ha:55.
export type closer = fn(s: vstream) (void | error);
export type closer = fn(s: stream) (void | error);

View File

@@ -1,11 +1,12 @@
// log — process logger over a [[io.stream]] sink. Subset of Hare's
// lib/log (ref/hare/log/{logger,funcs,global,silent}.ha).
// log — process logger over an [[io.stream]] (= `*io.vtable`) sink.
// Subset of Hare's lib/log (ref/hare/log/{logger,funcs,global,silent}.ha).
// Project #94 fold-eFinal.
//
// Surface today:
// Surface:
//
// log.logger — vtable with `println` + `printfln` slots
// log.stdlogger — first-field embed of logger + a `*io.stream` sink
// log.new (sl: *stdlogger, sink: *io.stream) void
// log.stdlogger — first-field embed of logger + an io.stream sink
// log.new (sink: io.stream) stdlogger
// log.silent *logger — a logger that discards every record
// log.default *logger — a stdlogger writing to stderr
// log.global *logger — the dispatch target for [[println]] / [[fatal]]
@@ -19,57 +20,43 @@
// log.lfatalf (log: *logger, format: str, fields: fmt.field...) never
// log.setlogger (log: *logger) void
//
// VALUE-RETURN (Hare ref/hare/log/logger.ha:20): [[new]] builds a
// stdlogger in a local and `return r;` (proven field-by-field sret).
// stdlogger 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.
//
// Divergences from Hare:
//
// • Hare returns the stdlogger from `new` by value; ww cgen can't
// return wide structs, so [[new]] is out-parameter shaped. Same
// workaround as memio.fixed / bufio.init.
// • silent / default / _default initialised via a lazy [[ensureinit]]
// (lib/temp's `rnginit==0` pattern) rather than module-scope struct-
// literal lets: cstage `emit_lets` only constexprs primitive /
// str-literal / array-literal lets. The public *logger globals are
// zero initially and become non-nil after the first lib/log call;
// callers that read globals directly must call some lib/log fn first
// so init runs. Graduates if static-init lands.
//
// • Hare initialises silent / default / _default via module-scope
// struct literal lets. cstage `emit_lets` (cmd/w6c/cgen.c:6176)
// only constexprs primitive / str-literal / array-literal lets —
// struct- and pointer-literal init aren't reachable. ww drops to
// a lazy [[ensureinit]] (lib/temp's `rnginit==0` pattern) called
// from every exported fn. The public *logger globals are zero
// initially and become non-nil after the first lib/log call;
// callers that read globals directly (rather than going through
// [[println]] / [[lprintln]] / [[setlogger]] / etc.) must call
// some lib/log fn first so init runs.
// • Param name divergence: the format-string parameter is `format`
// (Hare's is `fmt`). Permanent — #19 refuses any let/param shadowing
// an imported module name; `use fmt; fn x(fmt: T)` is structurally
// ambiguous under ww's `.`-for-both rule.
//
// • Param name divergence from Hare: the format-string parameter is
// `format` (Hare's is `fmt`). Permanent — #19 refuses any
// let/param that shadows an imported module name, regardless of
// body usage. ww chose `.` for both module-access and field-
// access, so `use fmt; fn x(fmt: T)` is structurally ambiguous;
// the resolver refuses it at decl.
// Sink today is an [[io.stream]] only — lib/io has no fd-backed handle
// yet (Hare's `io::handle = file | int`, io fold-2 #5). The default
// logger writes to stderr via a private fd_ctx whose write callback
// forwards to [[os.write]] on fd 2.
//
// Sink today is [[io.stream]] only — lib/io has no fd-backed stream
// yet (Hare's `io::handle = file | int` collapses to one variant).
// The default logger writes to stderr via a private io.stream whose
// write callback forwards to [[os.write]] on fd 2.
// Cast workaround per #206-payoff (ken: KEEP the explicit casts; they
// are cgen-neutral and sidestep the #214 over-acceptance surface). The
// `(&fn_name): *io.<role>` cast at the stderrsink_ctx_g vt wire-up; the
// #206 cast-drop is gated on #214. stderrwrite constructs nomem and
// widens to io.error explicitly rather than `os.trywrite(...)?` for the
// no-handle interim.
//
// Compiler note: wwstage cgen's variadic dispatch is name-based
// (`fnparamslookup(callee.str)`), which would conflate the
// `logger.println` vtable field with the top-level [[println]] fn
// if both stages compiled this module. cstage uses type-driven
// dispatch via the call-node's lhs type and handles fn-pointer
// variadic calls correctly. Today lib/log is consumed only by tests
// routed through `ww run` (cstage), so this is a latent issue
// covered by #11 (wwstage checkfile pass).
//
// let buf: [128]u8;
// let mem: memio.state;
// let s: io.stream;
// memio.fixed(&mem, &s, buf[0:128]);
//
// let sl: log.stdlogger;
// log.new(&sl, &s);
// let sl: log.stdlogger = log.new(&s.vt);
// log.lprintln(&sl.logger, "hello", 42i64);
// // mem now holds "hello 42\n"
//
// log.setlogger(&sl.logger);
// log.println("through global");
//
// log.setlogger(log.silent);
// log.println("dropped");
@@ -87,53 +74,55 @@ export type logger = struct {
printfln: fn(l: *logger, format: str, fields: fmt.field...) void,
};
// stdlogger — concrete logger forwarding to a `*io.stream` sink.
// First-field embed: `&sl.logger` yields a `*logger` (same shape as
// bufio.stream over its embedded io.stream vtable). The vtable
// callback recovers the outer stdlogger by casting the dispatch arg
// back to `*stdlogger`.
// stdlogger — concrete logger forwarding to an io.stream sink.
// First-field embed: `&sl.logger` yields a `*logger` (intrusive
// outer→inner cast); the vtable callback recovers the outer via
// `*stdlogger` cast. Mirrors ref/hare/log/logger.ha:18.
export type stdlogger = struct {
logger: logger,
sink: *io.stream,
sink: io.stream,
};
// stderrsink — private io.stream wired through fd 2 via [[os.write]].
// Used as [[default]]'s sink. Independent of lib/io's future fd-backed
// stream — when that lands, this collapses to a thin wrapper and
// graduates in one go.
let stderrsink: io.stream;
// stderrsink_ctx — module-static state for the default logger's stderr
// sink. vt FIRST field for the intrusive io.stream→*stderrsink_ctx cast
// in stderrwrite. Mirrors fmt.fd_ctx; only scalars/ptrs beyond vt.
type stderrsink_ctx = struct {
vt: io.vtable,
fd: i32,
};
// _silent — backing storage for the [[silent]] global.
let _silent: logger;
// Zero-init at link time per #129 A.2/A.3 SSoT; ensureinit wires
// vt.reader / vt.writer + fd lazily on first dispatch.
let stderrsink_ctx_g: stderrsink_ctx;
// _default — backing storage for the [[default]] global. Its sink
// points at [[stderrsink]] after [[ensureinit]] runs.
// _silent / _default — backing storage for the silent / default
// *logger globals. Zero-init at link time; ensureinit fills the
// println / printfln slots.
let _silent: logger;
let _default: stdlogger;
// silent / default / global — Hare-style *logger globals. Zero-init
// at link time; lazily wired by [[ensureinit]] on the first call to
// any exported fn. See the file header for the divergence rationale.
// silent / default / global — Hare-style *logger globals. Zero-init at
// link time; lazily wired by [[ensureinit]] on the first call to any
// exported fn. See the file header for the divergence rationale.
export let silent: *logger;
export let default: *logger;
export let global: *logger;
// initdone — guards [[ensureinit]] so the one-shot wiring runs once.
// Mirrors lib/temp's `rnginit` pattern.
let initdone: i32 = 0;
fn ensureinit() void = {
if (initdone != 0) { return; };
stderrsink.ctx = nil;
stderrsink.read = stderrread;
stderrsink.write = stderrwrite;
stderrsink.close = stderrclose;
stderrsink_ctx_g.fd = 2;
stderrsink_ctx_g.vt.reader = (&stderrread): *io.reader;
stderrsink_ctx_g.vt.writer = (&stderrwrite): *io.writer;
_silent.println = silentprintln;
_silent.printfln = silentprintfln;
_default.logger.println = stdprintln;
_default.logger.printfln = stdprintfln;
_default.sink = &stderrsink;
_default.sink = &stderrsink_ctx_g.vt;
silent = &_silent;
default = &_default.logger;
@@ -141,120 +130,127 @@ fn ensureinit() void = {
initdone = 1;
};
// stderrread / stderrwrite / stderrclose — io.stream callbacks for
// the private stderr sink. Read is a no-op returning io.eof; close
// is a no-op (the process owns fd 2). Write forwards to write(2)
// and translates a negative-errno into io.closed.
fn stderrread(s: *io.stream, buf: []u8) (i32 | io.eof | io.closed) = {
// stderrread / stderrwrite — vtable callbacks for the private stderr
// sink. Read is eof-only — lib/log writes; never reads. -errno collapses
// to a nomem-widened io.error.
fn stderrread(s: io.stream, buf: []u8) (size | io.eof | io.error) = {
let e: io.eof;
return e;
};
fn stderrwrite(s: *io.stream, buf: []u8) (i32 | io.closed) = {
let r: i64 = os.write(2, buf.ptr, buf.len: u64);
fn stderrwrite(s: io.stream, 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 e: io.closed;
let nm: nomem;
let e: io.error = nm;
return e;
};
return r: i32;
return r: size;
};
fn stderrclose(s: *io.stream) (void | io.closed) = { return; };
// stdprintln — vtable callback for stdlogger. Forwards to
// [[fmt.fprintln]] on the sink; the (i32 | io.closed) result is
// dropped — lib/log's surface is `void` (matches Hare).
// stdprintln / stdprintfln — vtable callbacks for stdlogger. Forwards
// to fmt.fprint / fprintf on the sink + appends the trailing newline
// via io.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.fprint is the bare (no-newline) primitive. The
// (size | io.error) results are dropped — lib/log's surface is `void`.
fn stdprintln(l: *logger, args: fmt.formattable...) void = {
let sl: *stdlogger = l: *stdlogger;
fmt.fprintln(sl.sink, args...);
fmt.fprint(sl.sink, args...);
let nl: [1]u8;
nl[0] = 10u8;
let v: []u8 = nl[0:1];
io.write(sl.sink, v);
};
// stdprintfln — vtable callback for stdlogger's format-string slot.
// Mirrors ref/hare/log/logger.ha:38 log_printfln. `format` (not
// Hare's `fmt`) per the param-name divergence noted in the header.
fn stdprintfln(l: *logger, format: str, fields: fmt.field...) void = {
let sl: *stdlogger = l: *stdlogger;
fmt.fprintfln(sl.sink, format, fields...);
fmt.fprintf(sl.sink, format, fields...);
let nl: [1]u8;
nl[0] = 10u8;
let v: []u8 = nl[0:1];
io.write(sl.sink, v);
};
// silentprintln — vtable callback for the silent logger. Discards
// every record without dispatching through fmt; keeps silent truly
// silent if fmt ever gets stateful.
// silentprintln / silentprintfln — vtable callbacks for the silent
// logger. Discard every record without dispatching through fmt; keeps
// silent truly silent if fmt ever gets stateful. Mirror
// ref/hare/log/silent.ha:15.
fn silentprintln(l: *logger, args: fmt.formattable...) void = { };
// silentprintfln — format-string sibling of [[silentprintln]].
// Mirrors ref/hare/log/silent.ha:15.
fn silentprintfln(l: *logger, format: str, fields: fmt.field...) void = { };
// new — wire `sl` as a stdlogger over `sink`. Hare returns by value
// (ref/hare/log/logger.ha:20); ww cgen can't return wide structs,
// so we take an out-parameter pointer (same shape as memio.fixed,
// bufio.init).
export fn new(sl: *stdlogger, sink: *io.stream) void = {
// ---- public API ----------------------------------------------------------
// new — build a stdlogger over `sink`, returned BY VALUE. Mirrors
// ref/hare/log/logger.ha:20.
export fn new(sink: io.stream) stdlogger = {
ensureinit();
sl.logger.println = stdprintln;
sl.logger.printfln = stdprintfln;
sl.sink = sink;
let r: stdlogger;
r.logger.println = stdprintln;
r.logger.printfln = stdprintfln;
r.sink = sink;
return r;
};
// lprintln — dispatch `args` through `log`. Hare's lib/log/funcs.ha
// counterpart at line 8.
// lprintln — dispatch `args` through `log`. Mirrors ref/hare/log/funcs.ha:8.
export fn lprintln(log: *logger, args: fmt.formattable...) void = {
ensureinit();
log.println(log, args...);
};
// println — dispatch through the [[global]] logger.
// println — dispatch through the [[global]] logger. Mirrors
// ref/hare/log/funcs.ha:18.
export fn println(args: fmt.formattable...) void = {
ensureinit();
lprintln(global, args...);
};
// lfatal — lprintln to `log` then exit(255). `never` return marks
// the bottom type so flow-control checks treat callers as terminated.
// Hare's lib/log/funcs.ha counterpart at line 28.
// lfatal — lprintln to `log` then exit(255). `never` return marks the
// bottom type so flow-control checks treat callers as terminated.
// Mirrors ref/hare/log/funcs.ha:28.
export fn lfatal(log: *logger, args: fmt.formattable...) never = {
lprintln(log, args...);
os.exit(255);
};
// fatal — lprintln to [[global]] then exit(255). Hare's lib/log/
// funcs.ha counterpart at line 45.
// fatal — lprintln to [[global]] then exit(255). Mirrors
// ref/hare/log/funcs.ha:45.
export fn fatal(args: fmt.formattable...) never = {
println(args...);
os.exit(255);
};
// setlogger — install `log` as the [[global]] logger. Hare's
// lib/log/global.ha counterpart at line 20.
// setlogger — install `log` as the [[global]] logger. Mirrors
// ref/hare/log/global.ha:20.
export fn setlogger(log: *logger) void = {
ensureinit();
global = log;
};
// lprintfln — dispatch a format-string record through `log`. Hare's
// lib/log/funcs.ha counterpart at line 13.
// lprintfln — dispatch a format-string record through `log`. Mirrors
// ref/hare/log/funcs.ha:13.
export fn lprintfln(log: *logger, format: str, fields: fmt.field...) void = {
ensureinit();
log.printfln(log, format, fields...);
};
// printfln — dispatch through the [[global]] logger. Hare's
// lib/log/funcs.ha counterpart at line 23.
// printfln — dispatch through the [[global]] logger. Mirrors
// ref/hare/log/funcs.ha:23.
export fn printfln(format: str, fields: fmt.field...) void = {
ensureinit();
lprintfln(global, format, fields...);
};
// lfatalf — lprintfln to `log` then exit(255). Hare's
// lib/log/funcs.ha counterpart at line 35.
// lfatalf — lprintfln to `log` then exit(255). Mirrors
// ref/hare/log/funcs.ha:35.
export fn lfatalf(log: *logger, format: str, fields: fmt.field...) never = {
lprintfln(log, format, fields...);
os.exit(255);
};
// fatalf — lprintfln to [[global]] then exit(255). Hare's
// lib/log/funcs.ha counterpart at line 52.
// fatalf — lprintfln to [[global]] then exit(255). Mirrors
// ref/hare/log/funcs.ha:52.
export fn fatalf(format: str, fields: fmt.field...) never = {
printfln(format, fields...);
os.exit(255);

View File

@@ -44,11 +44,9 @@ fn streq(a: str, b: str) bool = {
// scenario must run before [[setloggerswap]].
@test fn defaultwiredtoglobal() void = {
let buf: [4]u8;
let mem: memio.state;
let s: io.stream;
memio.fixed(&mem, &s, buf[0:4]);
let sl: log.stdlogger;
log.new(&sl, &s); // triggers ensureinit
let mem: memio.stream = memio.fixed(buf[0:4]);
let s: io.stream = &mem.vt;
let sl: log.stdlogger = log.new(s); // triggers ensureinit
if (log.silent == nil) { fail(); };
if (log.default == nil) { fail(); };
@@ -60,12 +58,10 @@ fn streq(a: str, b: str) bool = {
@test fn lprintlnbasic() void = {
let buf: [32]u8;
let mem: memio.state;
let s: io.stream;
memio.fixed(&mem, &s, buf[0:32]);
let mem: memio.stream = memio.fixed(buf[0:32]);
let s: io.stream = &mem.vt;
let sl: log.stdlogger;
log.new(&sl, &s);
let sl: log.stdlogger = log.new(s);
log.lprintln(&sl.logger, "hello", 42i64);
@@ -76,12 +72,10 @@ fn streq(a: str, b: str) bool = {
@test fn lprintlnsingle() void = {
let buf: [16]u8;
let mem: memio.state;
let s: io.stream;
memio.fixed(&mem, &s, buf[0:16]);
let mem: memio.stream = memio.fixed(buf[0:16]);
let s: io.stream = &mem.vt;
let sl: log.stdlogger;
log.new(&sl, &s);
let sl: log.stdlogger = log.new(s);
log.lprintln(&sl.logger, "only");
@@ -92,12 +86,10 @@ fn streq(a: str, b: str) bool = {
@test fn lprintlnempty() void = {
let buf: [4]u8;
let mem: memio.state;
let s: io.stream;
memio.fixed(&mem, &s, buf[0:4]);
let mem: memio.stream = memio.fixed(buf[0:4]);
let s: io.stream = &mem.vt;
let sl: log.stdlogger;
log.new(&sl, &s);
let sl: log.stdlogger = log.new(s);
log.lprintln(&sl.logger);
@@ -114,12 +106,10 @@ fn streq(a: str, b: str) bool = {
// empty (silent has no path to it anyway).
@test fn silentwritesnothing() void = {
let buf: [16]u8;
let mem: memio.state;
let s: io.stream;
memio.fixed(&mem, &s, buf[0:16]);
let mem: memio.stream = memio.fixed(buf[0:16]);
let s: io.stream = &mem.vt;
let sl: log.stdlogger;
log.new(&sl, &s); // triggers log.ensureinit; populates log.silent
let sl: log.stdlogger = log.new(s); // triggers log.ensureinit; populates log.silent
if (log.silent == nil) { fail(); };
@@ -136,18 +126,14 @@ fn streq(a: str, b: str) bool = {
// the swap semantics.
@test fn setloggerswap() void = {
let buf1: [32]u8;
let mem1: memio.state;
let s1: io.stream;
memio.fixed(&mem1, &s1, buf1[0:32]);
let sl1: log.stdlogger;
log.new(&sl1, &s1);
let mem1: memio.stream = memio.fixed(buf1[0:32]);
let s1: io.stream = &mem1.vt;
let sl1: log.stdlogger = log.new(s1);
let buf2: [32]u8;
let mem2: memio.state;
let s2: io.stream;
memio.fixed(&mem2, &s2, buf2[0:32]);
let sl2: log.stdlogger;
log.new(&sl2, &s2);
let mem2: memio.stream = memio.fixed(buf2[0:32]);
let s2: io.stream = &mem2.vt;
let sl2: log.stdlogger = log.new(s2);
log.setlogger(&sl1.logger);
log.println("first");
@@ -171,12 +157,10 @@ fn streq(a: str, b: str) bool = {
@test fn lprintflnbasic() void = {
let buf: [32]u8;
let mem: memio.state;
let s: io.stream;
memio.fixed(&mem, &s, buf[0:32]);
let mem: memio.stream = memio.fixed(buf[0:32]);
let s: io.stream = &mem.vt;
let sl: log.stdlogger;
log.new(&sl, &s);
let sl: log.stdlogger = log.new(s);
log.lprintfln(&sl.logger, "x={} y={}", 42i64, "hi");
@@ -189,18 +173,14 @@ fn streq(a: str, b: str) bool = {
// install sl1 as global, printfln writes there; mem2 stays empty.
@test fn printflnglobal() void = {
let buf1: [32]u8;
let mem1: memio.state;
let s1: io.stream;
memio.fixed(&mem1, &s1, buf1[0:32]);
let sl1: log.stdlogger;
log.new(&sl1, &s1);
let mem1: memio.stream = memio.fixed(buf1[0:32]);
let s1: io.stream = &mem1.vt;
let sl1: log.stdlogger = log.new(s1);
let buf2: [32]u8;
let mem2: memio.state;
let s2: io.stream;
memio.fixed(&mem2, &s2, buf2[0:32]);
let sl2: log.stdlogger;
log.new(&sl2, &s2);
let mem2: memio.stream = memio.fixed(buf2[0:32]);
let s2: io.stream = &mem2.vt;
let sl2: log.stdlogger = log.new(s2);
log.setlogger(&sl1.logger);
log.printfln("v={}", 7i64);
@@ -215,12 +195,10 @@ fn streq(a: str, b: str) bool = {
// bare-args path.
@test fn silentignoresprintfln() void = {
let buf: [16]u8;
let mem: memio.state;
let s: io.stream;
memio.fixed(&mem, &s, buf[0:16]);
let mem: memio.stream = memio.fixed(buf[0:16]);
let s: io.stream = &mem.vt;
let sl: log.stdlogger;
log.new(&sl, &s); // triggers log.ensureinit; populates log.silent
let sl: log.stdlogger = log.new(s); // triggers log.ensureinit; populates log.silent
if (log.silent == nil) { fail(); };
@@ -236,12 +214,10 @@ fn streq(a: str, b: str) bool = {
// resolve correctly.
@test fn lprintflnindexed() void = {
let buf: [16]u8;
let mem: memio.state;
let s: io.stream;
memio.fixed(&mem, &s, buf[0:16]);
let mem: memio.stream = memio.fixed(buf[0:16]);
let s: io.stream = &mem.vt;
let sl: log.stdlogger;
log.new(&sl, &s);
let sl: log.stdlogger = log.new(s);
log.lprintfln(&sl.logger, "{1} {0}", "a", "b");
@@ -254,12 +230,10 @@ fn streq(a: str, b: str) bool = {
// intact (parser proper covered in fmttest).
@test fn lprintflnmods() void = {
let buf: [16]u8;
let mem: memio.state;
let s: io.stream;
memio.fixed(&mem, &s, buf[0:16]);
let mem: memio.stream = memio.fixed(buf[0:16]);
let s: io.stream = &mem.vt;
let sl: log.stdlogger;
log.new(&sl, &s);
let sl: log.stdlogger = log.new(s);
log.lprintfln(&sl.logger, "{:5}", 42i64);

View File

@@ -1,303 +0,0 @@
// 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);
};

View File

@@ -1,30 +1,51 @@
// memio — in-memory io stream.
// memio — in-memory io stream. Project #94 fold-eFinal.
//
// Hare's memio:: surface, drop underscores. Two flavours behind a
// single [[io.stream]]:
// single [[io.stream]] (= `*io.vtable`):
//
// fixed caller owns the buffer, writes stop when full.
// dynamic memio owns the buffer, writes grow it; close frees.
//
// Call shape divergence from Hare: the caller supplies both the
// memio `state` and the `io.stream` slot, by pointer. ww cgen does
// not yet implement &x.field or 32B-struct return-by-value, so the
// Hare `let s = memio::fixed(buf)` shape isn't reachable; collapse
// to a single returned struct when those land (lib/CLAUDE.md
// "graduate in one go").
// Hare's memio::fixed/dynamic/dynamic_from return a `stream` whose
// FIRST field IS the `io::stream` (= `*vtable`). ww mirrors that
// intrusively: `stream`'s first field is `vt: io.vtable` (the vtable
// embedded INLINE) so a stack `stream` is castable to
// `io.stream = *vtable` via `&s.vt` — and the callbacks recover the
// outer `stream` by casting the dispatch arg back to `*stream`. Same
// intrusive shape as lib/bufio + lib/log over their embedded vtables.
//
// let mem: memio.state;
// let s: io.stream;
// memio.fixed(&mem, &s, buf);
// io.write(&s, bytes);
// let s: memio.stream = memio.fixed(buf);
// io.write(&s.vt, bytes);
// let view: str = memio.string(&s);
//
// Subset of Hare's surface: io.stream's variants are {eof, closed},
// so memio drops Hare's NONBLOCK flag (would need an `again` variant
// in lib/io). string()'s utf8-validating constructor is omitted per
// CLAUDE.md rule 9 carve-out. Hare's seek / copy callbacks are
// likewise absent: lib/io's stream vtable has only read/write/close
// slots, so memio can't wire a seeker or copier even if we wanted
// to. All three come back when their dependencies do.
// Constructors return the `stream` BY VALUE (Hare shape,
// ref/hare/memio/stream.ha:46,58,64): build the struct in a local,
// field-assign every slot, and `return r;` (the proven sret round-trip
// pinned by test/wcc/925 + 776). NO heap, NO `nomem`: the alloc that
// forced an earlier heap return is gone, so the constructor cannot
// fail. Caller owns the returned `stream` (stack ownership, no-GC) and
// passes `&s.vt` to the io dispatchers — exactly Hare's `&s` into
// io::write.
//
// Subset of Hare's surface: io's variants are {eof, error}, so memio
// drops Hare's NONBLOCK flag (would need an `again` variant in lib/io).
// string()'s utf8-validating constructor is omitted per CLAUDE.md
// rule 9 carve-out — see [[string]]. Hare's seek / copy callbacks are
// likewise absent: io's stream vtable has only read/write/close slots,
// so memio can't wire a seeker or copier yet (io fold-2, #5).
//
// Cast workaround per #206-payoff (ken: KEEP the explicit casts; they
// are cgen-neutral and sidestep the #214 over-acceptance surface). The
// `(&fn_name): *io.<role>` cast at each store site is the Hare-faithful
// minimum-touch route — the #206 cast-drop is a separate deferred
// payoff gated on #214.
//
// ptr/len/cap kept flat (no `buf: []u8`) per a historical cgen note:
// chained-dot writes through a state pointer into a slice subfield
// miscompile silently (#195 family); the flat shape sidesteps it.
// dynamicfrom uses the slice's `cap` (NOT `len`) to track the
// allocated-capacity-to-free on close — passing a half-filled append
// slice with len < cap and using only `buf.len` would under-free.
package memio;
@@ -32,106 +53,79 @@ import io;
import os;
import rt;
// state — memio's per-stream bookkeeping. The caller owns the slot
// and passes its address into a constructor. `ptr/len/cap` are the
// slice fields kept flat to dodge a chained-dot write through the
// state pointer (cgen doesn't store into `m.buf.ptr` reliably).
export type state = struct {
// stream — Hare's memio::stream (ref/hare/memio/stream.ha:18). `vt` at
// offset 0 for the intrusive stream→io.stream cast (`&s.vt`) and the
// callbacks' reverse `s: *stream` cast. Unified across fixed/dynamic
// (Hare keeps a single `stream` over per-mode vtable singletons; ww
// wires the per-mode callbacks post-construction instead). ptr/len/cap
// flat per the header note.
export type stream = struct {
vt: io.vtable,
ptr: *u8,
len: i32,
cap: i32,
pos: i32,
};
// fixed — wire `s` over a caller-supplied buffer. Writes never grow;
// they return 0 once `pos` reaches the end of the buffer.
export fn fixed(m: *state, s: *io.stream, buf: []u8) void = {
m.ptr = buf.ptr;
m.len = buf.len;
m.cap = buf.len;
m.pos = 0;
s.ctx = m: *void;
s.read = readfn;
s.write = fixedwrite;
s.close = closenoop;
// fixed — wire a stream over a caller-supplied buffer. Writes never
// grow; they return 0 once `pos` reaches the end of the buffer (Hare
// returns `nomem` here; ww surfaces 0 — graduating to Hare's `nomem`
// return needs the widen-from-bare-nomem path that #173-family work
// gates).
//
// Mirrors ref/hare/memio/stream.ha:46.
export fn fixed(buf: []u8) stream = {
let r: stream;
r.vt.reader = (&readfn): *io.reader;
r.vt.writer = (&fixedwrite): *io.writer;
r.ptr = buf.ptr;
r.len = buf.len;
r.cap = buf.len;
r.pos = 0;
return r;
};
// dynamic — wire `s` with no initial buffer. Writes grow the backing
// allocation; [[io.close]] frees it.
export fn dynamic(m: *state, s: *io.stream) void = {
m.ptr = nil;
m.len = 0;
m.cap = 0;
m.pos = 0;
s.ctx = m: *void;
s.read = readfn;
s.write = dynamicwrite;
s.close = dynamicclose;
// dynamic — wire a stream with no initial buffer. Writes grow the
// backing allocation; [[io.close]] frees it.
//
// Mirrors ref/hare/memio/stream.ha:58.
export fn dynamic() stream = {
let r: stream;
r.vt.reader = (&readfn): *io.reader;
r.vt.writer = (&dynamicwrite): *io.writer;
r.vt.closer = (&dynamicclose): *io.closer;
r.ptr = nil;
r.len = 0;
r.cap = 0;
r.pos = 0;
return r;
};
// dynamicfrom — like [[dynamic]] but seeded with an existing slice.
// Ownership of the slice transfers to the stream; [[io.close]] frees
// it. The slice must come from the runtime allocator: close calls
// [[os.free]] with `m.cap` bytes, which is taken from `buf.cap` (the
// slice's allocated capacity), not its logical length. Passing a
// half-filled append slice (len < cap) and using only `buf.len` here
// would under-free on close.
export fn dynamicfrom(m: *state, s: *io.stream, buf: []u8) void = {
m.ptr = buf.ptr;
m.len = buf.len;
m.cap = buf.cap;
m.pos = 0;
s.ctx = m: *void;
s.read = readfn;
s.write = dynamicwrite;
s.close = dynamicclose;
};
// buffer — borrowed view of bytes written so far (buf[..pos]).
// Seek to the end before calling if the full buffer is wanted.
export fn buffer(m: *state) []u8 = {
let r: []u8;
r.ptr = m.ptr;
r.len = m.pos;
// Ownership transfers; close frees `cap` bytes from the slice's
// allocated capacity (NOT logical length).
//
// Mirrors ref/hare/memio/stream.ha:64.
export fn dynamicfrom(buf: []u8) stream = {
let r: stream;
r.vt.reader = (&readfn): *io.reader;
r.vt.writer = (&dynamicwrite): *io.writer;
r.vt.closer = (&dynamicclose): *io.closer;
r.ptr = buf.ptr;
r.len = buf.len;
r.cap = buf.cap;
r.pos = 0;
return r;
};
// string — bytes written so far, as a str view. Hare returns
// (str | utf8::invalid); ww doesn't ship utf8 validation yet, so
// this returns the unchecked view.
export fn string(m: *state) str = {
let r: str;
r.ptr = m.ptr;
r.len = m.pos;
return r;
};
// ---- vtable callbacks ----------------------------------------------------
// reset — rewind the cursor and truncate the logical content to 0.
// Backing storage is preserved; subsequent writes (dynamic) re-fill
// from the start without reallocation.
export fn reset(m: *state) void = {
m.pos = 0;
m.len = 0;
};
// borrowedread — return an `amt`-byte view starting at `pos` without
// copying, advancing the cursor. eof if fewer bytes are available.
export fn borrowedread(m: *state, amt: i32) ([]u8 | io.eof) = {
if (m.len - m.pos < amt) {
let e: io.eof;
return e;
};
let r: []u8;
r.ptr = m.ptr + (m.pos: u64);
r.len = amt;
m.pos += amt;
return r;
};
// ---- vtable callbacks ------------------------------------------------
fn readfn(s: *io.stream, buf: []u8) (i32 | io.eof | io.closed) = {
let m: *state = s.ctx: *state;
// readfn — recover the stream from the io.stream's `*vtable` via the
// intrusive offset-0 cast. Single fn over the common header (Hare's
// single `read` at ref/hare/memio/stream.ha:103); fixed and dynamic
// share it because the read path is buffer-flavour-agnostic.
fn readfn(s: io.stream, buf: []u8) (size | io.eof | io.error) = {
let m: *stream = s: *stream;
if (m.pos >= m.len) {
let e: io.eof;
return e;
@@ -145,12 +139,12 @@ fn readfn(s: *io.stream, buf: []u8) (i32 | io.eof | io.closed) = {
i += 1;
};
m.pos += n;
return n;
return n: size;
};
fn fixedwrite(s: *io.stream, buf: []u8) (i32 | io.closed) = {
let m: *state = s.ctx: *state;
if (m.pos >= m.len) { return 0; };
fn fixedwrite(s: io.stream, buf: []u8) (size | io.error) = {
let m: *stream = s: *stream;
if (m.pos >= m.len) { return 0: size; };
let space: i32 = m.len - m.pos;
let n: i32 = buf.len;
if (space < n) { n = space; };
@@ -160,11 +154,11 @@ fn fixedwrite(s: *io.stream, buf: []u8) (i32 | io.closed) = {
i += 1;
};
m.pos += n;
return n;
return n: size;
};
fn dynamicwrite(s: *io.stream, buf: []u8) (i32 | io.closed) = {
let m: *state = s.ctx: *state;
fn dynamicwrite(s: io.stream, buf: []u8) (size | io.error) = {
let m: *stream = s: *stream;
let need: i32 = m.pos + buf.len;
if (need > m.cap) { dynamicgrow(m, need); };
let i: i32 = 0;
@@ -174,11 +168,11 @@ fn dynamicwrite(s: *io.stream, buf: []u8) (i32 | io.closed) = {
};
m.pos += buf.len;
if (m.pos > m.len) { m.len = m.pos; };
return buf.len;
return buf.len: size;
};
fn dynamicclose(s: *io.stream) (void | io.closed) = {
let m: *state = s.ctx: *state;
fn dynamicclose(s: io.stream) (void | io.error) = {
let m: *stream = s: *stream;
if (m.cap > 0) { os.free(m.ptr: *void, m.cap: u64); };
m.ptr = nil;
m.len = 0;
@@ -187,30 +181,77 @@ fn dynamicclose(s: *io.stream) (void | io.closed) = {
return;
};
fn closenoop(s: *io.stream) (void | io.closed) = {
return;
};
// Double-and-copy growth. Initial bump from 0 lands at 8 to amortise
// small write bursts without a tail of reallocs.
//
// `dynamicgrow`, not Hare's bare `grow`: cstage bundles all imported
// modules into a flat TU and resolves private fns by unqualified name,
// so historically two `fn grow` decls (here + the now-deleted bump
// arena's `grow`) would have collided. Module-prefixed name kept the
// symmetry with `dynamicwrite`/`dynamicclose`; retained pending task
// #9 (module-aware private-fn scoping in cstage).
fn dynamicgrow(m: *state, need: i32) void = {
let newcap: i32 = m.cap;
// Double-and-copy growth with floor at 8. `dynamicgrow`, not Hare's
// bare `grow`: cstage bundles all imported modules into a flat TU and
// resolves private fns by unqualified name (task #9), so the
// module-prefixed name keeps the symmetry with dynamicwrite/dynamicclose.
fn dynamicgrow(d: *stream, need: i32) void = {
let newcap: i32 = d.cap;
if (newcap < 8) { newcap = 8; };
for (newcap < need) { newcap *= 2; };
let nbuf: *u8 = rt.malloc(newcap: u64): *u8;
let i: i32 = 0;
for (i < m.len) {
nbuf[i] = m.ptr[i];
for (i < d.len) {
nbuf[i] = d.ptr[i];
i += 1;
};
if (m.cap > 0) { os.free(m.ptr: *void, m.cap: u64); };
m.ptr = nbuf;
m.cap = newcap;
if (d.cap > 0) { os.free(d.ptr: *void, d.cap: u64); };
d.ptr = nbuf;
d.cap = newcap;
};
// ---- accessors over the common `stream` header -----------------------
//
// Single fn each: Hare's string/reset/buffer/borrowedread all take
// `*stream` and read the flat header.
// string — bytes written so far, as a str view (buf[0..pos]).
//
// Mirrors ref/hare/memio/stream.ha:81 string(in: *stream). Hare returns
// (str | utf8::invalid) — the validating constructor. ww returns a bare
// `str` per the CLAUDE.md rule-9 frombytes carve-out: utf8.validate at
// the IO source is opt-in, never wrapped per-construction; the honest
// name reserves a future validating helper.
export fn string(s: *stream) str = {
let r: str;
r.ptr = s.ptr;
r.len = s.pos;
return r;
};
// buffer — borrowed []u8 view of bytes written so far (buf[0..pos]).
//
// Mirrors ref/hare/memio/stream.ha:74 buffer(in: *stream).
export fn buffer(s: *stream) []u8 = {
let r: []u8;
r.ptr = s.ptr;
r.len = s.pos;
return r;
};
// reset — rewind the cursor and truncate the logical content to 0.
// Backing storage is preserved; subsequent writes (dynamic) re-fill
// from the start without reallocation.
//
// Mirrors ref/hare/memio/stream.ha:87 reset(in: *stream).
export fn reset(s: *stream) void = {
s.pos = 0;
s.len = 0;
};
// borrowedread — return an `amt`-byte view starting at `pos` without
// copying, advancing the cursor. eof if fewer bytes are available.
//
// Mirrors ref/hare/memio/stream.ha:94 borrowedread(st: *stream, amt).
// `amt: i32` (not Hare's `size`) per the i32-index convention.
export fn borrowedread(s: *stream, amt: i32) ([]u8 | io.eof) = {
if (s.len - s.pos < amt) {
let e: io.eof;
return e;
};
let r: []u8;
r.ptr = s.ptr + (s.pos: u64);
r.len = amt;
s.pos += amt;
return r;
};

View File

@@ -3,7 +3,9 @@
// Every @test enumerates parallel `[N]T` arrays of inputs and
// expectations, then iterates one body across them. Parallel arrays
// (rather than `[N]struct{...}`) sidestep the cstage cgen's chained
// `arr[i].field` store gap (task #6).
// `arr[i].field` store gap (task #6). #94 fold-eFinal: the unified
// value-return surface (`let st = memio.fixed(buf); &st.vt` into the
// io dispatchers; io.read/write/close return size/io.error).
package memio;
@@ -47,9 +49,8 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
req[3]=1; wantn[3]=0; wantf[3]=0u8; wantl[3]=0u8; weof[3]=1;
req[4]=0; wantn[4]=0; wantf[4]=0u8; wantl[4]=0u8; weof[4]=1;
let mem: memio.state;
let s: io.stream;
memio.fixed(&mem, &s, arr[0:8]);
let st: memio.stream = memio.fixed(arr[0:8]);
let s: io.stream = &st.vt;
let out: [8]u8;
let i: i32 = 0;
@@ -58,32 +59,32 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
let k: i32 = 0;
for (k < 8) { out[k] = 0u8; k += 1; };
let r: (i32 | io.eof | io.closed) = io.read(&s, out[0:req[i]]);
let r: (size | io.eof | io.error) = io.read(s, out[0:req[i]]);
match (r) {
case let n: i32 => {
case let nz: size => {
let n: i32 = nz: i32;
if (weof[i] != 0) { fail(); };
if (n != wantn[i]) { fail(); };
if (n > 0 && out[0] != wantf[i]) { fail(); };
if (n > 0 && out[n - 1] != wantl[i]) { fail(); };
};
case io.eof => { if (weof[i] == 0) { fail(); }; };
case io.closed => fail();
case let e: io.error => fail();
};
i += 1;
};
// closenoop on a fixed stream returns void.
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
// fixed stream has no closer → io.close returns void.
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let e: io.error => fail(); };
};
// ---- fixedwritecases: full-fit / exact-fill / partial / overflow -------------
@test fn fixedwritecases() void = {
let dst: [16]u8;
let mem: memio.state;
let s: io.stream;
memio.fixed(&mem, &s, dst[0:16]);
let st: memio.stream = memio.fixed(dst[0:16]);
let s: io.stream = &st.vt;
// One flat source; rows index into it via (off, len). Sequence:
// "hello " (6), "world!!" (7), "XXXXXXX" (7), "Y" (1).
@@ -104,10 +105,10 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
for (i < 4) {
let lo: i32 = off[i];
let hi: i32 = lo + ln[i];
let r: (i32 | io.closed) = io.write(&s, src[lo:hi]);
let r: (size | io.error) = io.write(s, src[lo:hi]);
match (r) {
case let n: i32 => { if (n != wantn[i]) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != wantn[i]) { fail(); }; };
case let e: io.error => fail();
};
i += 1;
};
@@ -117,27 +118,23 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
if (!bytes.equal(dst[0:16], want[0:16])) { fail(); };
// 0-byte write is always 0 with no side effects.
let r0: (i32 | io.closed) = io.write(&s, src[0:0]);
let r0: (size | io.error) = io.write(s, src[0:0]);
match (r0) {
case let n: i32 => { if (n != 0) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n != 0: size) { fail(); }; };
case let e: io.error => fail();
};
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let e: io.error => fail(); };
};
// ---- dynamicgrowcases: every cap doubling exercised ---------------------
// Drive grow 0 → 8 → 16 → 32 by writing sized chunks. Verify
// accumulated `pos` after each step. Suffix `cases` mirrors
// `fixedwritecases` / `borrowedreadcases`; bare `dynamicgrow`
// would collide with memio.ww's private `dynamicgrow` in the same
// package.
// accumulated `pos` after each step.
@test fn dynamicgrowcases() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let st: memio.stream = memio.dynamic();
let s: io.stream = &st.vt;
let src: [32]u8;
let _: i32 = putstr("abcdefghijklmnopqrstuvwxyz012345", src[0:32], 0);
@@ -157,20 +154,20 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
for (i < 5) {
let lo: i32 = off;
let hi: i32 = lo + chunk[i];
let r: (i32 | io.closed) = io.write(&s, src[lo:hi]);
let r: (size | io.error) = io.write(s, src[lo:hi]);
match (r) {
case let n: i32 => { if (n != chunk[i]) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != chunk[i]) { fail(); }; };
case let e: io.error => fail();
};
if (memio.buffer(&mem).len != total[i]) { fail(); };
if (memio.buffer(&st).len != total[i]) { fail(); };
off += chunk[i];
i += 1;
};
if (!bytes.equal(memio.buffer(&mem), src[0:26])) { fail(); };
if (!bytes.equal(memio.buffer(&st), src[0:26])) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let e: io.error => fail(); };
};
// ---- dynamicreset: write / reset / write cycles -------------------------
@@ -178,9 +175,8 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
// op=0 writes `ln` bytes from a rolling source; op=1 resets and ignores
// ln. After each row the accumulated len must equal `want`.
@test fn dynamicreset() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let st: memio.stream = memio.dynamic();
let s: io.stream = &st.vt;
let src: [16]u8;
src[0]=10u8; src[1]=20u8; src[2]=30u8; src[3]=40u8;
@@ -200,26 +196,26 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
let i: i32 = 0;
for (i < 6) {
if (op[i] == 1) {
memio.reset(&mem);
memio.reset(&st);
off = 0;
} else {
let lo: i32 = off;
let hi: i32 = lo + ln[i];
let r: (i32 | io.closed) = io.write(&s, src[lo:hi]);
let r: (size | io.error) = io.write(s, src[lo:hi]);
match (r) {
case let n: i32 => { if (n != ln[i]) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != ln[i]) { fail(); }; };
case let e: io.error => fail();
};
off += ln[i];
};
if (memio.buffer(&mem).len != want[i]) { fail(); };
if (memio.buffer(&st).len != want[i]) { fail(); };
i += 1;
};
if (!bytes.equal(memio.buffer(&mem), src[0:8])) { fail(); };
if (!bytes.equal(memio.buffer(&st), src[0:8])) { fail(); };
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let e: io.error => fail(); };
};
// ---- borrowedread: under / exact / over / 0-byte ------------------------
@@ -228,9 +224,7 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
let arr: [6]u8;
arr[0]=0u8; arr[1]=1u8; arr[2]=2u8; arr[3]=3u8; arr[4]=4u8; arr[5]=5u8;
let mem: memio.state;
let s: io.stream;
memio.fixed(&mem, &s, arr[0:6]);
let st: memio.stream = memio.fixed(arr[0:6]);
// (amt, wantfirst, wantlast, weof)
let amt: [4]i32;
@@ -244,7 +238,7 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
let i: i32 = 0;
for (i < 4) {
let r: ([]u8 | io.eof) = memio.borrowedread(&mem, amt[i]);
let r: ([]u8 | io.eof) = memio.borrowedread(&st, amt[i]);
match (r) {
case let v: []u8 => {
if (weof[i] != 0) { fail(); };
@@ -261,11 +255,10 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
// ---- stringview: len + endpoints track pos across appends ---------------
@test fn stringview() void = {
let mem: memio.state;
let s: io.stream;
memio.dynamic(&mem, &s);
let st: memio.stream = memio.dynamic();
let s: io.stream = &st.vt;
if (memio.string(&mem).len != 0) { fail(); };
if (memio.string(&st).len != 0) { fail(); };
let src: [32]u8;
let _: i32 = putstr("hello world!!", src[0:32], 0);
@@ -284,17 +277,17 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
for (i < 3) {
let lo: i32 = off[i];
let hi: i32 = lo + ln[i];
let r: (i32 | io.closed) = io.write(&s, src[lo:hi]);
match (r) { case let n: i32 => {}; case io.closed => fail(); };
let v: str = memio.string(&mem);
let r: (size | io.error) = io.write(s, src[lo:hi]);
match (r) { case let n: size => {}; case let e: io.error => fail(); };
let v: str = memio.string(&st);
if (v.len != want[i]) { fail(); };
if (v[0] != wf[i]) { fail(); };
if (v[v.len - 1] != wl[i]) { fail(); };
i += 1;
};
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let e: io.error => fail(); };
};
// ---- dynamicfromseed: ownership-transferred seed, read then write -------
@@ -302,15 +295,13 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
@test fn dynamicfromseed() void = {
// Build a heap-allocated seed via append (rt_ensure path), then
// hand ownership to memio. dynamicclose then frees `seed.cap`
// bytes — the regression that motivated the `m.cap = buf.cap`
// fix in lib/memio/memio.ww.
// bytes — the regression that motivated the `cap = buf.cap` fix.
let seed: []u8;
seed.ptr = nil; seed.len = 0; seed.cap = 0;
append(seed, 100u8, 101u8, 102u8, 103u8);
let mem: memio.state;
let s: io.stream;
memio.dynamicfrom(&mem, &s, seed);
let st: memio.stream = memio.dynamicfrom(seed);
let s: io.stream = &st.vt;
// (readn, wantbyte, weof)
let rn: [5]i32;
@@ -325,15 +316,16 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
let out: [4]u8;
let i: i32 = 0;
for (i < 5) {
let r: (i32 | io.eof | io.closed) = io.read(&s, out[0:rn[i]]);
let r: (size | io.eof | io.error) = io.read(s, out[0:rn[i]]);
match (r) {
case let n: i32 => {
case let nz: size => {
let n: i32 = nz: i32;
if (weof[i] != 0) { fail(); };
if (n != rn[i]) { fail(); };
if (out[0] != want[i]) { fail(); };
};
case io.eof => { if (weof[i] == 0) { fail(); }; };
case io.closed => fail();
case let e: io.error => fail();
};
i += 1;
};
@@ -342,14 +334,14 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
// must free the (post-grow) cap, not the seed cap.
let extra: [4]u8;
extra[0]=200u8; extra[1]=201u8; extra[2]=202u8; extra[3]=203u8;
let w: (i32 | io.closed) = io.write(&s, extra[0:4]);
let w: (size | io.error) = io.write(s, extra[0:4]);
match (w) {
case let n: i32 => { if (n != 4) { fail(); }; };
case io.closed => fail();
case let n: size => { if (n: i32 != 4) { fail(); }; };
case let e: io.error => fail();
};
let c: (void | io.closed) = io.close(&s);
match (c) { case void => {}; case io.closed => fail(); };
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let e: io.error => fail(); };
};
export fn main() i32 = {

View File

@@ -1,257 +0,0 @@
// vstream — Hare-shaped vtable wrappers over memio. Project #94
// fold-eFinal(PREP).
//
// Adds three constructors that return a memio `stream` BY VALUE (the
// Hare shape, ref/hare/memio/stream.ha:46,58,64) alongside the
// pre-vtable memio.fixed / memio.dynamic / memio.dynamicfrom in
// memio.ww. The OLD surface stays untouched here — fold-eFinal (FLIP)
// atomically flips the package shape: deletes the OLD constructors +
// `state` + callbacks, renames the `_vstream`/`_v` suffix off (so
// `fixed_vstream → fixed`, `stream` stays, `string_v → string`), and
// migrates the few callers (lib/fmt/vstream.ww + the 776 probe).
//
// Hare's memio::fixed/dynamic/dynamic_from return a `stream` whose
// FIRST field IS the `io::stream` (= `*vtable`). ww mirrors that
// intrusively: `stream`'s first field is `vt: io.vtable` (the vtable
// embedded INLINE) so a stack `stream` is castable to
// `vstream = *vtable` via `&s.vt` — and the callbacks recover the
// outer `stream` by casting the dispatch arg back to `*stream`. Same
// intrusive shape as lib/bufio + lib/log over their embedded vtables.
//
// VALUE-RETURN (drew, gating): the constructor builds the struct in a
// local `let r: stream;`, field-assigns every slot (including the
// tagged vt sub-fields), and `return r;` — the proven sret round-trip
// shape pinned by test/wcc/925 ("ident_return_rhs" row) and extended
// to tagged-union-field structs by the 776 byte-id rows here. NO heap,
// NO `nomem`: the alloc that forced the OLD `(io.vstream | nomem)`
// return is gone, so the constructor cannot fail. Caller owns the
// returned `stream` (stack ownership, no-GC) and passes `&s.vt` to the
// io.st_* dispatchers — exactly Hare's `&s` into io::write.
//
// Cast workaround per #206-payoff (ken: KEEP the explicit casts; they
// are cgen-neutral and sidestep the #214 over-acceptance surface). The
// `(&fn_name): *io.<role>` cast at each store site is the Hare-faithful
// minimum-touch route — same workaround test/wcc/775_io_vtable_run.c
// uses for the bare-vtable init.
//
// ptr/len/cap kept flat (no `buf: []u8`) per the memio.state note
// (memio.ww:39): chained-dot writes through a state pointer into a
// slice subfield miscompile silently (#195 family); the flat shape
// sidesteps it. dynamicfrom_vstream uses the slice's `cap` (NOT `len`)
// to track the allocated-capacity-to-free on close — mirrors the OLD
// memio.dynamicfrom note (memio.ww:73).
package memio;
import io;
import os;
import rt;
// stream — Hare's memio::stream (ref/hare/memio/stream.ha:18). `vt` at
// offset 0 for the intrusive stream→vstream cast (`&s.vt`) and the
// callbacks' reverse `s: *stream` cast. Unified across fixed/dynamic
// (Hare keeps a single `stream` over per-mode vtable singletons; ww
// wires the per-mode callbacks post-construction instead). ptr/len/cap
// flat per the memio.state note above.
export type stream = struct {
vt: io.vtable,
ptr: *u8,
len: i32,
cap: i32,
pos: i32,
};
// fixed_vstream — wire a stream over a caller-supplied buffer. Writes
// never grow; they return 0 once `pos` reaches the end of the buffer
// (Hare returns `nomem` here; ww surfaces 0 to mirror the OLD
// memio.fixedwrite divergence — graduating to Hare's `nomem` return
// needs the widen-from-bare-nomem path that #173 blocks).
//
// Mirrors ref/hare/memio/stream.ha:46.
export fn fixed_vstream(buf: []u8) stream = {
let r: stream;
r.vt.reader = (&read_v): *io.reader;
r.vt.writer = (&fixedwrite_v): *io.writer;
r.ptr = buf.ptr;
r.len = buf.len;
r.cap = buf.len;
r.pos = 0;
return r;
};
// dynamic_vstream — wire a stream with no initial buffer. Writes grow
// the backing allocation; [[io.st_close]] frees it.
//
// Mirrors ref/hare/memio/stream.ha:58.
export fn dynamic_vstream() stream = {
let r: stream;
r.vt.reader = (&read_v): *io.reader;
r.vt.writer = (&dynamicwrite_v): *io.writer;
r.vt.closer = (&dynamicclose_v): *io.closer;
r.ptr = nil;
r.len = 0;
r.cap = 0;
r.pos = 0;
return r;
};
// dynamicfrom_vstream — like [[dynamic_vstream]] but seeded with an
// existing slice. Ownership transfers; close frees `cap` bytes from
// the slice's allocated capacity (NOT logical length) — passing a
// half-filled append slice with len < cap and using only `buf.len`
// would under-free on close.
//
// Mirrors ref/hare/memio/stream.ha:64.
export fn dynamicfrom_vstream(buf: []u8) stream = {
let r: stream;
r.vt.reader = (&read_v): *io.reader;
r.vt.writer = (&dynamicwrite_v): *io.writer;
r.vt.closer = (&dynamicclose_v): *io.closer;
r.ptr = buf.ptr;
r.len = buf.len;
r.cap = buf.cap;
r.pos = 0;
return r;
};
// ---- vtable callbacks ----------------------------------------------------
// read_v — recover the stream from the vstream's `*vtable` via the
// intrusive offset-0 cast. Single fn over the common header (Hare's
// single `read` at ref/hare/memio/stream.ha:103); fixed and dynamic
// share it because the read path is buffer-flavour-agnostic.
fn read_v(s: io.vstream, buf: []u8) (size | io.eof | io.error) = {
let m: *stream = s: *stream;
if (m.pos >= m.len) {
let e: io.eof;
return e;
};
let avail: i32 = m.len - m.pos;
let n: i32 = buf.len;
if (avail < n) { n = avail; };
let i: i32 = 0;
for (i < n) {
buf[i] = m.ptr[m.pos + i];
i += 1;
};
m.pos += n;
return n: size;
};
fn fixedwrite_v(s: io.vstream, buf: []u8) (size | io.error) = {
let m: *stream = s: *stream;
if (m.pos >= m.len) { return 0: size; };
let space: i32 = m.len - m.pos;
let n: i32 = buf.len;
if (space < n) { n = space; };
let i: i32 = 0;
for (i < n) {
m.ptr[m.pos + i] = buf[i];
i += 1;
};
m.pos += n;
return n: size;
};
fn dynamicwrite_v(s: io.vstream, buf: []u8) (size | io.error) = {
let m: *stream = s: *stream;
let need: i32 = m.pos + buf.len;
if (need > m.cap) { dynamicgrow_v(m, need); };
let i: i32 = 0;
for (i < buf.len) {
m.ptr[m.pos + i] = buf[i];
i += 1;
};
m.pos += buf.len;
if (m.pos > m.len) { m.len = m.pos; };
return buf.len: size;
};
fn dynamicclose_v(s: io.vstream) (void | io.error) = {
let m: *stream = s: *stream;
if (m.cap > 0) { os.free(m.ptr: *void, m.cap: u64); };
m.ptr = nil;
m.len = 0;
m.cap = 0;
m.pos = 0;
return;
};
// Mirror of memio.ww:203 dynamicgrow. Double-and-copy with floor at
// 8. Module-prefixed `_v` suffix vs the OLD memio.dynamicgrow keeps
// the cstage flat-TU private-fn scope from colliding (memio.ww:200
// note + task #9).
fn dynamicgrow_v(d: *stream, need: i32) void = {
let newcap: i32 = d.cap;
if (newcap < 8) { newcap = 8; };
for (newcap < need) { newcap *= 2; };
let nbuf: *u8 = rt.malloc(newcap: u64): *u8;
let i: i32 = 0;
for (i < d.len) {
nbuf[i] = d.ptr[i];
i += 1;
};
if (d.cap > 0) { os.free(d.ptr: *void, d.cap: u64); };
d.ptr = nbuf;
d.cap = newcap;
};
// ---- accessors over the common `stream` header -----------------------
//
// Single fn each (drew string-collapse): Hare's string/reset/buffer/
// borrowedread all take `*stream` and read the flat header, so the
// fixed/dynamic split the OLD V-side carried (fixed_string /
// dynamic_string) collapses to one. FLIP drops the `_v` suffix
// (string_v → string, …) and deletes the OLD `*state` twins in
// memio.ww.
// string_v — bytes written so far, as a str view (buf[0..pos]).
//
// Mirrors ref/hare/memio/stream.ha:81 string(in: *stream). Hare returns
// (str | utf8::invalid) — the validating constructor. ww returns a bare
// `str` per the CLAUDE.md rule-9 frombytes carve-out: utf8.validate at
// the IO source is opt-in, never wrapped per-construction; the honest
// name reserves a future validating helper.
export fn string_v(s: *stream) str = {
let r: str;
r.ptr = s.ptr;
r.len = s.pos;
return r;
};
// buffer_v — borrowed []u8 view of bytes written so far (buf[0..pos]).
//
// Mirrors ref/hare/memio/stream.ha:74 buffer(in: *stream).
export fn buffer_v(s: *stream) []u8 = {
let r: []u8;
r.ptr = s.ptr;
r.len = s.pos;
return r;
};
// reset_v — rewind the cursor and truncate the logical content to 0.
// Backing storage is preserved; subsequent writes (dynamic) re-fill
// from the start without reallocation.
//
// Mirrors ref/hare/memio/stream.ha:87 reset(in: *stream).
export fn reset_v(s: *stream) void = {
s.pos = 0;
s.len = 0;
};
// borrowedread_v — return an `amt`-byte view starting at `pos` without
// copying, advancing the cursor. eof if fewer bytes are available.
//
// Mirrors ref/hare/memio/stream.ha:94 borrowedread(st: *stream, amt).
// `amt: i32` (not Hare's `size`) per the i32-index convention.
export fn borrowedread_v(s: *stream, amt: i32) ([]u8 | io.eof) = {
if (s.len - s.pos < amt) {
let e: io.eof;
return e;
};
let r: []u8;
r.ptr = s.ptr + (s.pos: u64);
r.len = amt;
s.pos += amt;
return r;
};

View File

@@ -7,7 +7,7 @@
// shlex.syntaxerr — !void; bad shell syntax
// shlex.strerror(syntaxerr) — "Invalid shell syntax"
// shlex.split(in: str) — tokenize; returns ([]str | syntaxerr)
// shlex.quote(*io.stream, s) — write `s` shell-quoted to a sink
// shlex.quote(io.stream, s) — write `s` shell-quoted to a sink
// shlex.quotestr(s: str) str — quote into a fresh str
//
// Owning model: split() returns a fresh `[]str` of strings.dup'd
@@ -39,19 +39,16 @@
// / scan_backslash work on bytes; an escaped multibyte sequence
// round-trips as the byte sequence it appears in the input.
//
// - memio glue uses ww's caller-supplies-state shape:
// let mst: memio.state; let s: io.stream;
// memio.dynamic(&mst, &s);
// ... io.write(&s, ...) ...
// let view: str = memio.string(&mst); // borrowed
// instead of Hare's `let s = memio::dynamic()` return-by-value.
// Locked in by lib/memio's commit until cgen ships >24B return-
// by-value (graduates "in one go" per lib/CLAUDE.md).
// - memio glue uses the value-return shape (#94 fold-eFinal):
// let st: memio.stream = memio.dynamic();
// ... io.write(&st.vt, ...) ...
// let view: str = memio.string(&st); // borrowed
// mirroring Hare's `let s = memio::dynamic()` return-by-value.
// `&st.vt` is the [[io.stream]] (= `*io.vtable`) the dispatchers
// take.
//
// - quote() takes `*io.stream` (not Hare's `io::handle`) — that's
// already the ww-divergent shape lib/io ships. Returns
// `(i32 | io.closed)` instead of Hare's wider io::error: lib/io's
// stream vtable carries only `closed` on the write arm.
// - quote() takes `io.stream` (not Hare's `io::handle`) — that's
// the ww-divergent shape lib/io ships until io fold-2 (#5).
//
// - quote() emits raw bytes directly into the sink; without
// [[memio.appendrune]] / [[memio.concat]] in lib/memio yet, the
@@ -86,11 +83,9 @@
// Caller layout — quote into a fixed buffer:
//
// let buf: [128]u8;
// let mst: memio.state;
// let s: io.stream;
// memio.fixed(&mst, &s, buf[0:128]);
// shlex.quote(&s, "hello world"); // writes 'hello world'
// let view: str = memio.string(&mst);
// let st: memio.stream = memio.fixed(buf[0:128]);
// shlex.quote(&st.vt, "hello world"); // writes 'hello world'
// let view: str = memio.string(&st);
package shlex;
@@ -167,11 +162,11 @@ fn freepartial(slice: []str) void = {
// ship an appendbyte/appendrune in the ww port yet, so this is the
// uniform write-one-byte primitive across the module.
//
// The dynamic memio sink never returns `io.closed`; the [[split]] +
// [[quotestr]] callers ignore the error arm because their stream is
// always memio.dynamic. [[quote]] (caller-supplied sink) propagates
// it through its `(i32 | io.closed)` return.
fn writebyte(s: *io.stream, b: u8) (i32 | io.closed) = {
// The dynamic memio sink never errors; the [[split]] + [[quotestr]]
// callers ignore the error arm because their stream is always
// memio.dynamic. [[quote]] (caller-supplied sink) propagates it through
// its `(size | io.error)` return.
fn writebyte(s: io.stream, b: u8) (size | io.error) = {
let buf: [1]u8;
buf[0] = b;
return io.write(s, buf[0:1]);
@@ -182,7 +177,7 @@ fn writebyte(s: *io.stream, b: u8) (i32 | io.closed) = {
// newline silently. A trailing bare backslash (`*pos == in.len`) is
// `syntaxerr`. Otherwise the next byte is appended verbatim. Mirrors
// Hare's scan_backslash (split.ha:86).
fn scan_backslash(out: *io.stream, in: str, pos: *i32) (void | syntaxerr) = {
fn scan_backslash(out: io.stream, in: str, pos: *i32) (void | syntaxerr) = {
if (*pos >= in.len) { let e: syntaxerr; return e; };
let r: u8 = in[*pos];
*pos += 1;
@@ -194,7 +189,7 @@ fn scan_backslash(out: *io.stream, in: str, pos: *i32) (void | syntaxerr) = {
// scan_double — consume a `"..."` group at `*pos`. Closes on '"';
// '\<c>' inside processed by [[scan_backslash]]; unterminated → err.
// Mirrors Hare's scan_double (split.ha:110).
fn scan_double(out: *io.stream, in: str, pos: *i32) (void | syntaxerr) = {
fn scan_double(out: io.stream, in: str, pos: *i32) (void | syntaxerr) = {
let loop: bool = true;
for (loop) {
if (*pos >= in.len) { let e: syntaxerr; return e; };
@@ -217,7 +212,7 @@ fn scan_double(out: *io.stream, in: str, pos: *i32) (void | syntaxerr) = {
// scan_single — consume a `'...'` group at `*pos`. Closes on "'";
// no escapes inside (POSIX: single-quoted text is fully literal);
// unterminated → err. Mirrors Hare's scan_single (split.ha:135).
fn scan_single(out: *io.stream, in: str, pos: *i32) (void | syntaxerr) = {
fn scan_single(out: io.stream, in: str, pos: *i32) (void | syntaxerr) = {
let loop: bool = true;
for (loop) {
if (*pos >= in.len) { let e: syntaxerr; return e; };
@@ -242,9 +237,8 @@ fn scan_single(out: *io.stream, in: str, pos: *i32) (void | syntaxerr) = {
// signal for "an empty quote group was the only content of this
// token", which still pushes a literal "" (e.g. `cmd ''`).
export fn split(in: str) ([]str | syntaxerr) = {
let mst: memio.state;
let snk: io.stream;
memio.dynamic(&mst, &snk);
let st: memio.stream = memio.dynamic();
let snk: io.stream = &st.vt;
let slice: []str;
slice.ptr = nil: *str;
@@ -275,56 +269,56 @@ export fn split(in: str) ([]str | syntaxerr) = {
};
};
if (!first) {
let view: str = memio.string(&mst);
let view: str = memio.string(&st);
let owned: str = dupstr(view);
appendstr(&slice, owned);
memio.reset(&mst);
memio.reset(&st);
};
dirty = false;
} else { if (r == 92u8) { // '\\'
let er = scan_backslash(&snk, in, &pos);
let er = scan_backslash(snk, in, &pos);
match (er) {
case let e: syntaxerr => {
let _c = io.close(&snk);
let _c = io.close(snk);
freepartial(slice);
return e;
};
case void => {};
};
} else { if (r == 34u8) { // '"'
let er = scan_double(&snk, in, &pos);
let er = scan_double(snk, in, &pos);
match (er) {
case let e: syntaxerr => {
let _c = io.close(&snk);
let _c = io.close(snk);
freepartial(slice);
return e;
};
case void => {};
};
} else { if (r == 39u8) { // "'"
let er = scan_single(&snk, in, &pos);
let er = scan_single(snk, in, &pos);
match (er) {
case let e: syntaxerr => {
let _c = io.close(&snk);
let _c = io.close(snk);
freepartial(slice);
return e;
};
case void => {};
};
} else {
let _w = writebyte(&snk, r);
let _w = writebyte(snk, r);
}; }; }; };
if (first) { first = false; };
};
if (dirty) {
let view: str = memio.string(&mst);
let view: str = memio.string(&st);
let owned: str = dupstr(view);
appendstr(&slice, owned);
};
let _c = io.close(&snk);
let _c = io.close(snk);
return slice;
};
@@ -355,10 +349,8 @@ fn issafe(s: str) bool = {
};
// quote — emit `s` shell-quoted to `sink`. Returns total bytes written,
// or `io.closed` if the sink rejects mid-write. Mirrors Hare's
// shlex::quote (escape.ha:25), modulo the narrower `io.closed`-only
// error set on lib/io's stream vtable (Hare's `io::error` would carry
// underread/etc., which lib/io doesn't yet model).
// or `io.error` if the sink rejects mid-write. Mirrors Hare's
// shlex::quote (escape.ha:25).
//
// Strategy:
// - empty `s` → `''`
@@ -366,14 +358,14 @@ fn issafe(s: str) bool = {
// - otherwise → wrap in single quotes; an embedded `'`
// becomes `'"'"'` (close, double-quoted
// literal `'`, reopen).
export fn quote(sink: *io.stream, s: str) (i32 | io.closed) = {
export fn quote(sink: io.stream, s: str) (size | io.error) = {
if (s.len == 0) {
let buf: [2]u8;
buf[0] = 39u8; buf[1] = 39u8;
let r = io.write(sink, buf[0:2]);
match (r) {
case let n: i32 => return n;
case let c: io.closed => return c;
case let n: size => return n;
case let e: io.error => return e;
};
};
if (issafe(s)) {
@@ -382,16 +374,16 @@ export fn quote(sink: *io.stream, s: str) (i32 | io.closed) = {
raw.len = s.len;
let r = io.write(sink, raw);
match (r) {
case let n: i32 => return n;
case let c: io.closed => return c;
case let n: size => return n;
case let e: io.error => return e;
};
};
let total: i32 = 0;
let total: size = 0;
let r1 = writebyte(sink, 39u8); // "'"
match (r1) {
case let n: i32 => { total += n; };
case let c: io.closed => return c;
case let n: size => { total += n; };
case let e: io.error => return e;
};
let i: i32 = 0;
for (i < s.len) {
@@ -402,22 +394,22 @@ export fn quote(sink: *io.stream, s: str) (i32 | io.closed) = {
pat[3] = 34u8; pat[4] = 39u8;
let rr = io.write(sink, pat[0:5]);
match (rr) {
case let n: i32 => { total += n; };
case let c2: io.closed => return c2;
case let n: size => { total += n; };
case let e2: io.error => return e2;
};
} else {
let rr = writebyte(sink, c);
match (rr) {
case let n: i32 => { total += n; };
case let c2: io.closed => return c2;
case let n: size => { total += n; };
case let e2: io.error => return e2;
};
};
i += 1;
};
let r2 = writebyte(sink, 39u8); // "'"
match (r2) {
case let n: i32 => { total += n; };
case let c: io.closed => return c;
case let n: size => { total += n; };
case let e: io.error => return e;
};
return total;
};
@@ -427,19 +419,18 @@ export fn quote(sink: *io.stream, s: str) (i32 | io.closed) = {
// `os.free(r.ptr, r.len: u64)`. Mirrors Hare's shlex::quotestr
// (escape.ha:50).
//
// memio.dynamic's writes never return io.closed, so the `match` on
// `quote(...)` discards the error arm — it can't reach this caller's
// sink. quote()'s `(i32 | io.closed)` shape is preserved for the
// caller-supplied-sink path.
// memio.dynamic's writes never error, so the `match` on `quote(...)`
// discards the error arm — it can't reach this caller's sink. quote()'s
// `(size | io.error)` shape is preserved for the caller-supplied-sink
// path.
export fn quotestr(s: str) str = {
let mst: memio.state;
let snk: io.stream;
memio.dynamic(&mst, &snk);
let st: memio.stream = memio.dynamic();
let snk: io.stream = &st.vt;
let _r = quote(&snk, s);
let view: str = memio.string(&mst);
let _r = quote(snk, s);
let view: str = memio.string(&st);
let owned: str = dupstr(view);
let _c = io.close(&snk);
let _c = io.close(snk);
return owned;
};

View File

@@ -108,22 +108,21 @@ fn checkempty(in: str) void = {
};
fn checkquote(in: str, expected: str) void = {
let mst: memio.state;
let snk: io.stream;
memio.dynamic(&mst, &snk);
let st: memio.stream = memio.dynamic();
let snk: io.stream = &st.vt;
let r = shlex.quote(&snk, in);
let n: i32 = 0;
let r = shlex.quote(snk, in);
let n: size = 0;
match (r) {
case let v: i32 => { n = v; };
case io.closed => { fail(); };
case let v: size => { n = v; };
case let _e: io.error => { fail(); };
};
if (n != expected.len) { fail(); };
if (n != expected.len: size) { fail(); };
let view: str = memio.string(&mst);
let view: str = memio.string(&st);
if (!streq(view, expected)) { fail(); };
let _c = io.close(&snk);
let _c: (void | io.error) = io.close(snk);
};
// ---- split: Hare's @test fn split() table --------------------------

View File

@@ -14054,54 +14054,35 @@ export fn checkfile(c: *checker, file: *node) void = {
// io — stream interface (Plan 9 Bio / Hare io::stream shape).
//
// No closures, no methods. A `stream` is a struct of function
// pointers plus a `ctx: *void`. The error channel is the return
// value of read/write/close — Hare-shaped tagged unions instead of
// errno-style integer sentinels.
// No closures, no methods. A `stream` is a pointer to a `vtable` of
// function pointers (lib/io/stream.ww); read/write/close dispatch
// through it. The error channel is the return value — Hare-shaped
// tagged unions instead of errno-style integer sentinels.
//
// This file owns the eof / underread variant tags; lib/io/stream.ww
// owns the `vtable` + `stream` + read/write/close dispatchers, and
// lib/io/types.ww owns the error union, mode/whence enums, and the
// reader/writer/closer fn-type aliases. #94 fold-eFinal collapsed the
// pre-vtable `stream` struct + `closed` tag into the single vtable
// surface; the dispatchers are read/write/close (over `stream`),
// final over `handle` at io fold-2 (#5).
package io;
// eof — read past the end of the stream. Hare uses the `done`
// singleton for EOF; ww doesn't have `done` yet so we ship a
// named-void variant tag.
package io;
// named-void variant tag. Lifts to `done` with #93.
export type eof = void;
// closed — operation attempted on a stream that has already been
// closed. ww-specific: Hare's io collapses this into the wider
// errors union, but our stream vtable has no handle-ownership
// semantics, so a distinct tag is honest. Named void.
export type closed = void;
// underread — an I/O handle hit eof partway through a fixed-size
// read. Payload is the byte count actually delivered. Mirrors
// Hare's `io::underread = !size`; ww uses i32 because the
// underlying buffer-length type is i32 today.
export type underread = !i32;
export type stream = struct {
ctx: *void,
read: fn(s: *stream, buf: []u8) (i32 | eof | closed),
write: fn(s: *stream, buf: []u8) (i32 | closed),
close: fn(s: *stream) (void | closed),
};
export fn read(s: *stream, buf: []u8) (i32 | eof | closed) = {
return s.read(s, buf);
};
export fn write(s: *stream, buf: []u8) (i32 | closed) = {
return s.write(s, buf);
};
export fn close(s: *stream) (void | closed) = {
return s.close(s);
};
// stream — Hare-shaped vtable port. Project #94 fold-e1.
// stream — Hare-shaped vtable surface. Project #94 fold-eFinal.
//
// Adds the additive vtable surface alongside lib/io/io.ww's pre-vtable
// stream struct + read/write/close wrappers (which fold-e2-eN migrate
// to vtable-backed implementations and then retire). Three exports:
// The single io stream surface (the fold-eFinal collapse retired the
// pre-vtable `stream` struct + `closed` tag). Three exports:
//
// vtable a struct of optional fn-pointer slots — reader/writer/
// closer per ref/hare/io/stream.ha:36-42. Hare spells the
@@ -14109,35 +14090,25 @@ export fn close(s: *stream) (void | closed) = {
// spelling (#192), so each slot is a `(*T | void)` tagged
// union (Plan-9-lean per the Nullable kept ruling; opt-in
// at optionality boundaries only).
// vstream `*vtable` alias matching Hare's `stream = *vtable`
// (ref/hare/io/stream.ha:33). Named `vstream` rather than
// `stream` because lib/io/io.ww still owns the legacy
// `stream` struct name until fold-eN collapses both.
// st_read /
// st_write /
// st_close the three dispatchers per ref/hare/io/stream.ha:44-68.
// stream `*vtable` alias matching Hare's `stream = *vtable`
// (ref/hare/io/stream.ha:33).
// read /
// write /
// close the three dispatchers per ref/hare/io/stream.ha:44-68.
// Each `match`es the matching vtable slot, returns
// `errors.unsupported` (widened twice) when the slot is
// void, otherwise calls through the fn pointer.
// void, otherwise calls through the fn pointer. Hare keeps
// these private (`st_read` …) behind a `handle`-typed
// public `read`/`write`/`close`; ww has no `handle` yet
// (io fold-2, #5), so the dispatchers ARE the public
// surface and grow the `handle` match when #5 lands.
//
// Fold-e1 cgen/checker dependencies (all closed at HEAD): #191
// (N_DOT on aliased-ptr receiver — `s.reader` resolves through the
// `vstream = *vtable` alias), #198 (`is` on cross-module variant
// tag), #199 α (concrete→tagged widen via NAMED-variant nominal
// match, used by `let e: error = u;`), #200 (`as` payload-extract),
// #201 (return-of-tagged-call forwards instead of rewinds), #205
// (tagged→tagged subset accepts NAMED-variant nominal match, used
// by `return e;` where ret type contains `error` as a direct
// variant). Two direct widens compose for the void-arm `return`:
// concrete `errors.unsupported` → tagged `error` via #199 α, then
// tagged `error` → tagged `(size | eof | error)` via #205.
//
// Deferrals (drew-signed): `seeker` lands with fold-e2 once `off`
// + `whence` plug into the signature; Hare's `?`-propagating
// `st_close` body (`c(s)?;`) collapses to a direct `return (*c)(s);`
// here because #173 (TRY-on-tagged-return both-stages broken) is
// still open — the dispatcher's surface stays Hare-shaped, only the
// internal try-prop is omitted.
// Deferrals (drew-signed): `seeker` lands with io fold-2 (#5) once `off`
// + `whence` plug into the signature. Hare's `?`-propagating `close`
// body (`c(s)?;`) collapses to a direct `return (*c)(s);` here because
// the dispatcher's surface stays Hare-shaped; only the internal
// try-prop is omitted (#173 history, now closed — kept direct for the
// void-arm fall-through shape).
package io;
@@ -14149,16 +14120,15 @@ export type vtable = struct {
closer: (*closer | void),
};
// ref/hare/io/stream.ha:33. `vstream` not `stream` while io.ww still
// owns the legacy `stream` struct name.
export type vstream = *vtable;
// ref/hare/io/stream.ha:33.
export type stream = *vtable;
// ref/hare/io/stream.ha:44-50. The void-arm `return e;` chains two
// direct widens: `errors.unsupported → error` via #199 α, then
// `error → (size | eof | error)` via #205. The call-arm spells the
// fn-ptr call explicitly per #193 (ww requires `(*r)(args)` rather
// than implicit `r(args)`).
export fn st_read(s: vstream, buf: []u8) (size | eof | error) = {
export fn read(s: stream, buf: []u8) (size | eof | error) = {
match (s.reader) {
case void => {
let u: errors.unsupported;
@@ -14170,7 +14140,7 @@ export fn st_read(s: vstream, buf: []u8) (size | eof | error) = {
};
// ref/hare/io/stream.ha:53-59.
export fn st_write(s: vstream, buf: []u8) (size | error) = {
export fn write(s: stream, buf: []u8) (size | error) = {
match (s.writer) {
case void => {
let u: errors.unsupported;
@@ -14183,10 +14153,9 @@ export fn st_write(s: vstream, buf: []u8) (size | error) = {
// ref/hare/io/stream.ha:62-68. Hare propagates the close error via
// `c(s)?;` then falls through to the void arm; ww collapses to a
// direct return because #173 (TRY-on-tagged-return) is still open.
// The surface (void | error) matches; only the internal try-prop
// is omitted.
export fn st_close(s: vstream) (void | error) = {
// direct return for the void-arm fall-through shape. The surface
// (void | error) matches.
export fn close(s: stream) (void | error) = {
match (s.closer) {
case void => return void;
case let c: *closer => return (*c)(s);
@@ -14222,21 +14191,19 @@ export type exists = !void;
export type unsupported = !void;
// types — error union, mode/whence enums, reader/writer/closer
// fn-type aliases. Project #94 fold-1 step (d) (drew Fold D); the
// fn-aliases re-targeted from *stream → vstream as part of fold-e1
// (vtable port in stream.ww).
// fn-type aliases. Project #94 fold-eFinal; the fn-aliases target
// `stream` (= `*vtable`, the single io surface).
//
// Hare splits the io module across stream.ha + types.ha and ww does
// the same: lib/io/io.ww owns the (pre-vtable) `stream`, `eof`, and
// `underread` shapes; lib/io/stream.ww owns the Hare vtable port
// (`vtable`, `vstream`, `st_read`/`st_write`/`st_close` dispatch);
// this file owns the surrounding port. The three files share
// `package io;` so cross-file refs resolve via the dir-enum concat
// order (io.ww < stream.ww < types.ww — `vstream` lands before the
// reader/writer/closer aliases below).
// the same: lib/io/io.ww owns the `eof` and `underread` tags;
// lib/io/stream.ww owns the vtable surface (`vtable`, `stream`,
// `read`/`write`/`close` dispatch); this file owns the surrounding
// port. The three files share `package io;` so cross-file refs
// resolve via the dir-enum concat order (io.ww < stream.ww < types.ww
// — `stream` lands before the reader/writer/closer aliases below).
//
// Deferrals (drew-signed): `handle`, `seeker`, `copier`, `strerror`
// land with fold-e2+ (need the `handle` sum). Hare's `EOF = done`
// land with io fold-2 (#5; need the `handle` sum). Hare's `EOF = done`
// stays as `io.eof` until the `done` singleton ships (#93).
package io;
@@ -14266,283 +14233,72 @@ export type whence = enum i32 {
// ww enumerates the tags explicitly (ken's #204-block — spread of an
// errors-side tagged into this union triggers a wrapper-vs-flatten
// layout mismatch; #199b layout-extension is the deferred fix). The
// st_read/st_write dispatchers in stream.ww return
// `errors.unsupported` when the matching vtable slot is unset, so the
// variant lands here directly.
// read/write dispatchers in stream.ww return `errors.unsupported`
// when the matching vtable slot is unset, so the variant lands here
// directly.
export type error = !(errors.unsupported | underread | nomem);
// ref/hare/io/types.ha:46. `eof` (not Hare's `done`) per the io.ww:8
// rationale; lifts to `done` with #93. `vstream` forward-refs the
// ref/hare/io/types.ha:46. `eof` (not Hare's `done`) per the io.ww
// rationale; lifts to `done` with #93. `stream` forward-refs the
// `*vtable` alias in stream.ww — the dispatcher receives the vtable
// pointer directly (Hare's stream.ha:33 + types.ha:46 collapse).
export type reader = fn(s: vstream, buf: []u8) (size | eof | error);
export type reader = fn(s: stream, buf: []u8) (size | eof | error);
// ref/hare/io/types.ha:51. No `const` qualifier — ww has none, and
// lib/io/io.ww:30 + lib/sort/sort.ww:18 drop it the same way.
export type writer = fn(s: vstream, buf: []u8) (size | error);
// lib/sort/sort.ww:18 drops it the same way.
export type writer = fn(s: stream, buf: []u8) (size | error);
// ref/hare/io/types.ha:55.
export type closer = fn(s: vstream) (void | error);
export type closer = fn(s: stream) (void | error);
// memio — in-memory io stream.
// memio — in-memory io stream. Project #94 fold-eFinal.
//
// Hare's memio:: surface, drop underscores. Two flavours behind a
// single [[io.stream]]:
// single [[io.stream]] (= `*io.vtable`):
//
// fixed caller owns the buffer, writes stop when full.
// dynamic memio owns the buffer, writes grow it; close frees.
//
// Call shape divergence from Hare: the caller supplies both the
// memio `state` and the `io.stream` slot, by pointer. ww cgen does
// not yet implement &x.field or 32B-struct return-by-value, so the
// Hare `let s = memio::fixed(buf)` shape isn't reachable; collapse
// to a single returned struct when those land (lib/CLAUDE.md
// "graduate in one go").
//
// let mem: memio.state;
// let s: io.stream;
// memio.fixed(&mem, &s, buf);
// io.write(&s, bytes);
//
// Subset of Hare's surface: io.stream's variants are {eof, closed},
// so memio drops Hare's NONBLOCK flag (would need an `again` variant
// in lib/io). string()'s utf8-validating constructor is omitted per
// CLAUDE.md rule 9 carve-out. Hare's seek / copy callbacks are
// likewise absent: lib/io's stream vtable has only read/write/close
// slots, so memio can't wire a seeker or copier even if we wanted
// to. All three come back when their dependencies do.
package memio;
import io;
import os;
import rt;
// state — memio's per-stream bookkeeping. The caller owns the slot
// and passes its address into a constructor. `ptr/len/cap` are the
// slice fields kept flat to dodge a chained-dot write through the
// state pointer (cgen doesn't store into `m.buf.ptr` reliably).
export type state = struct {
ptr: *u8,
len: i32,
cap: i32,
pos: i32,
};
// fixed — wire `s` over a caller-supplied buffer. Writes never grow;
// they return 0 once `pos` reaches the end of the buffer.
export fn fixed(m: *state, s: *io.stream, buf: []u8) void = {
m.ptr = buf.ptr;
m.len = buf.len;
m.cap = buf.len;
m.pos = 0;
s.ctx = m: *void;
s.read = readfn;
s.write = fixedwrite;
s.close = closenoop;
};
// dynamic — wire `s` with no initial buffer. Writes grow the backing
// allocation; [[io.close]] frees it.
export fn dynamic(m: *state, s: *io.stream) void = {
m.ptr = nil;
m.len = 0;
m.cap = 0;
m.pos = 0;
s.ctx = m: *void;
s.read = readfn;
s.write = dynamicwrite;
s.close = dynamicclose;
};
// dynamicfrom — like [[dynamic]] but seeded with an existing slice.
// Ownership of the slice transfers to the stream; [[io.close]] frees
// it. The slice must come from the runtime allocator: close calls
// [[os.free]] with `m.cap` bytes, which is taken from `buf.cap` (the
// slice's allocated capacity), not its logical length. Passing a
// half-filled append slice (len < cap) and using only `buf.len` here
// would under-free on close.
export fn dynamicfrom(m: *state, s: *io.stream, buf: []u8) void = {
m.ptr = buf.ptr;
m.len = buf.len;
m.cap = buf.cap;
m.pos = 0;
s.ctx = m: *void;
s.read = readfn;
s.write = dynamicwrite;
s.close = dynamicclose;
};
// buffer — borrowed view of bytes written so far (buf[..pos]).
// Seek to the end before calling if the full buffer is wanted.
export fn buffer(m: *state) []u8 = {
let r: []u8;
r.ptr = m.ptr;
r.len = m.pos;
return r;
};
// string — bytes written so far, as a str view. Hare returns
// (str | utf8::invalid); ww doesn't ship utf8 validation yet, so
// this returns the unchecked view.
export fn string(m: *state) str = {
let r: str;
r.ptr = m.ptr;
r.len = m.pos;
return r;
};
// reset — rewind the cursor and truncate the logical content to 0.
// Backing storage is preserved; subsequent writes (dynamic) re-fill
// from the start without reallocation.
export fn reset(m: *state) void = {
m.pos = 0;
m.len = 0;
};
// borrowedread — return an `amt`-byte view starting at `pos` without
// copying, advancing the cursor. eof if fewer bytes are available.
export fn borrowedread(m: *state, amt: i32) ([]u8 | io.eof) = {
if (m.len - m.pos < amt) {
let e: io.eof;
return e;
};
let r: []u8;
r.ptr = m.ptr + (m.pos: u64);
r.len = amt;
m.pos += amt;
return r;
};
// ---- vtable callbacks ------------------------------------------------
fn readfn(s: *io.stream, buf: []u8) (i32 | io.eof | io.closed) = {
let m: *state = s.ctx: *state;
if (m.pos >= m.len) {
let e: io.eof;
return e;
};
let avail: i32 = m.len - m.pos;
let n: i32 = buf.len;
if (avail < n) { n = avail; };
let i: i32 = 0;
for (i < n) {
buf[i] = m.ptr[m.pos + i];
i += 1;
};
m.pos += n;
return n;
};
fn fixedwrite(s: *io.stream, buf: []u8) (i32 | io.closed) = {
let m: *state = s.ctx: *state;
if (m.pos >= m.len) { return 0; };
let space: i32 = m.len - m.pos;
let n: i32 = buf.len;
if (space < n) { n = space; };
let i: i32 = 0;
for (i < n) {
m.ptr[m.pos + i] = buf[i];
i += 1;
};
m.pos += n;
return n;
};
fn dynamicwrite(s: *io.stream, buf: []u8) (i32 | io.closed) = {
let m: *state = s.ctx: *state;
let need: i32 = m.pos + buf.len;
if (need > m.cap) { dynamicgrow(m, need); };
let i: i32 = 0;
for (i < buf.len) {
m.ptr[m.pos + i] = buf[i];
i += 1;
};
m.pos += buf.len;
if (m.pos > m.len) { m.len = m.pos; };
return buf.len;
};
fn dynamicclose(s: *io.stream) (void | io.closed) = {
let m: *state = s.ctx: *state;
if (m.cap > 0) { os.free(m.ptr: *void, m.cap: u64); };
m.ptr = nil;
m.len = 0;
m.cap = 0;
m.pos = 0;
return;
};
fn closenoop(s: *io.stream) (void | io.closed) = {
return;
};
// Double-and-copy growth. Initial bump from 0 lands at 8 to amortise
// small write bursts without a tail of reallocs.
//
// `dynamicgrow`, not Hare's bare `grow`: cstage bundles all imported
// modules into a flat TU and resolves private fns by unqualified name,
// so historically two `fn grow` decls (here + the now-deleted bump
// arena's `grow`) would have collided. Module-prefixed name kept the
// symmetry with `dynamicwrite`/`dynamicclose`; retained pending task
// #9 (module-aware private-fn scoping in cstage).
fn dynamicgrow(m: *state, need: i32) void = {
let newcap: i32 = m.cap;
if (newcap < 8) { newcap = 8; };
for (newcap < need) { newcap *= 2; };
let nbuf: *u8 = rt.malloc(newcap: u64): *u8;
let i: i32 = 0;
for (i < m.len) {
nbuf[i] = m.ptr[i];
i += 1;
};
if (m.cap > 0) { os.free(m.ptr: *void, m.cap: u64); };
m.ptr = nbuf;
m.cap = newcap;
};
// vstream — Hare-shaped vtable wrappers over memio. Project #94
// fold-eFinal(PREP).
//
// Adds three constructors that return a memio `stream` BY VALUE (the
// Hare shape, ref/hare/memio/stream.ha:46,58,64) alongside the
// pre-vtable memio.fixed / memio.dynamic / memio.dynamicfrom in
// memio.ww. The OLD surface stays untouched here — fold-eFinal (FLIP)
// atomically flips the package shape: deletes the OLD constructors +
// `state` + callbacks, renames the `_vstream`/`_v` suffix off (so
// `fixed_vstream → fixed`, `stream` stays, `string_v → string`), and
// migrates the few callers (lib/fmt/vstream.ww + the 776 probe).
//
// Hare's memio::fixed/dynamic/dynamic_from return a `stream` whose
// FIRST field IS the `io::stream` (= `*vtable`). ww mirrors that
// intrusively: `stream`'s first field is `vt: io.vtable` (the vtable
// embedded INLINE) so a stack `stream` is castable to
// `vstream = *vtable` via `&s.vt` — and the callbacks recover the
// `io.stream = *vtable` via `&s.vt` — and the callbacks recover the
// outer `stream` by casting the dispatch arg back to `*stream`. Same
// intrusive shape as lib/bufio + lib/log over their embedded vtables.
//
// VALUE-RETURN (drew, gating): the constructor builds the struct in a
// local `let r: stream;`, field-assigns every slot (including the
// tagged vt sub-fields), and `return r;` — the proven sret round-trip
// shape pinned by test/wcc/925 ("ident_return_rhs" row) and extended
// to tagged-union-field structs by the 776 byte-id rows here. NO heap,
// NO `nomem`: the alloc that forced the OLD `(io.vstream | nomem)`
// return is gone, so the constructor cannot fail. Caller owns the
// returned `stream` (stack ownership, no-GC) and passes `&s.vt` to the
// io.st_* dispatchers — exactly Hare's `&s` into io::write.
// let s: memio.stream = memio.fixed(buf);
// io.write(&s.vt, bytes);
// let view: str = memio.string(&s);
//
// Constructors return the `stream` BY VALUE (Hare shape,
// ref/hare/memio/stream.ha:46,58,64): build the struct in a local,
// field-assign every slot, and `return r;` (the proven sret round-trip
// pinned by test/wcc/925 + 776). NO heap, NO `nomem`: the alloc that
// forced an earlier heap return is gone, so the constructor cannot
// fail. Caller owns the returned `stream` (stack ownership, no-GC) and
// passes `&s.vt` to the io dispatchers — exactly Hare's `&s` into
// io::write.
//
// Subset of Hare's surface: io's variants are {eof, error}, so memio
// drops Hare's NONBLOCK flag (would need an `again` variant in lib/io).
// string()'s utf8-validating constructor is omitted per CLAUDE.md
// rule 9 carve-out — see [[string]]. Hare's seek / copy callbacks are
// likewise absent: io's stream vtable has only read/write/close slots,
// so memio can't wire a seeker or copier yet (io fold-2, #5).
//
// Cast workaround per #206-payoff (ken: KEEP the explicit casts; they
// are cgen-neutral and sidestep the #214 over-acceptance surface). The
// `(&fn_name): *io.<role>` cast at each store site is the Hare-faithful
// minimum-touch route — same workaround test/wcc/775_io_vtable_run.c
// uses for the bare-vtable init.
// minimum-touch route — the #206 cast-drop is a separate deferred
// payoff gated on #214.
//
// ptr/len/cap kept flat (no `buf: []u8`) per the memio.state note
// (memio.ww:39): chained-dot writes through a state pointer into a
// slice subfield miscompile silently (#195 family); the flat shape
// sidesteps it. dynamicfrom_vstream uses the slice's `cap` (NOT `len`)
// to track the allocated-capacity-to-free on close — mirrors the OLD
// memio.dynamicfrom note (memio.ww:73).
// ptr/len/cap kept flat (no `buf: []u8`) per a historical cgen note:
// chained-dot writes through a state pointer into a slice subfield
// miscompile silently (#195 family); the flat shape sidesteps it.
// dynamicfrom uses the slice's `cap` (NOT `len`) to track the
// allocated-capacity-to-free on close — passing a half-filled append
// slice with len < cap and using only `buf.len` would under-free.
package memio;
@@ -14551,11 +14307,11 @@ import os;
import rt;
// stream — Hare's memio::stream (ref/hare/memio/stream.ha:18). `vt` at
// offset 0 for the intrusive stream→vstream cast (`&s.vt`) and the
// offset 0 for the intrusive stream→io.stream cast (`&s.vt`) and the
// callbacks' reverse `s: *stream` cast. Unified across fixed/dynamic
// (Hare keeps a single `stream` over per-mode vtable singletons; ww
// wires the per-mode callbacks post-construction instead). ptr/len/cap
// flat per the memio.state note above.
// flat per the header note.
export type stream = struct {
vt: io.vtable,
ptr: *u8,
@@ -14564,17 +14320,17 @@ export type stream = struct {
pos: i32,
};
// fixed_vstream — wire a stream over a caller-supplied buffer. Writes
// never grow; they return 0 once `pos` reaches the end of the buffer
// (Hare returns `nomem` here; ww surfaces 0 to mirror the OLD
// memio.fixedwrite divergence — graduating to Hare's `nomem` return
// needs the widen-from-bare-nomem path that #173 blocks).
// fixed — wire a stream over a caller-supplied buffer. Writes never
// grow; they return 0 once `pos` reaches the end of the buffer (Hare
// returns `nomem` here; ww surfaces 0 — graduating to Hare's `nomem`
// return needs the widen-from-bare-nomem path that #173-family work
// gates).
//
// Mirrors ref/hare/memio/stream.ha:46.
export fn fixed_vstream(buf: []u8) stream = {
export fn fixed(buf: []u8) stream = {
let r: stream;
r.vt.reader = (&read_v): *io.reader;
r.vt.writer = (&fixedwrite_v): *io.writer;
r.vt.reader = (&readfn): *io.reader;
r.vt.writer = (&fixedwrite): *io.writer;
r.ptr = buf.ptr;
r.len = buf.len;
r.cap = buf.len;
@@ -14582,15 +14338,15 @@ export fn fixed_vstream(buf: []u8) stream = {
return r;
};
// dynamic_vstream — wire a stream with no initial buffer. Writes grow
// the backing allocation; [[io.st_close]] frees it.
// dynamic — wire a stream with no initial buffer. Writes grow the
// backing allocation; [[io.close]] frees it.
//
// Mirrors ref/hare/memio/stream.ha:58.
export fn dynamic_vstream() stream = {
export fn dynamic() stream = {
let r: stream;
r.vt.reader = (&read_v): *io.reader;
r.vt.writer = (&dynamicwrite_v): *io.writer;
r.vt.closer = (&dynamicclose_v): *io.closer;
r.vt.reader = (&readfn): *io.reader;
r.vt.writer = (&dynamicwrite): *io.writer;
r.vt.closer = (&dynamicclose): *io.closer;
r.ptr = nil;
r.len = 0;
r.cap = 0;
@@ -14598,18 +14354,16 @@ export fn dynamic_vstream() stream = {
return r;
};
// dynamicfrom_vstream — like [[dynamic_vstream]] but seeded with an
// existing slice. Ownership transfers; close frees `cap` bytes from
// the slice's allocated capacity (NOT logical length) — passing a
// half-filled append slice with len < cap and using only `buf.len`
// would under-free on close.
// dynamicfrom — like [[dynamic]] but seeded with an existing slice.
// Ownership transfers; close frees `cap` bytes from the slice's
// allocated capacity (NOT logical length).
//
// Mirrors ref/hare/memio/stream.ha:64.
export fn dynamicfrom_vstream(buf: []u8) stream = {
export fn dynamicfrom(buf: []u8) stream = {
let r: stream;
r.vt.reader = (&read_v): *io.reader;
r.vt.writer = (&dynamicwrite_v): *io.writer;
r.vt.closer = (&dynamicclose_v): *io.closer;
r.vt.reader = (&readfn): *io.reader;
r.vt.writer = (&dynamicwrite): *io.writer;
r.vt.closer = (&dynamicclose): *io.closer;
r.ptr = buf.ptr;
r.len = buf.len;
r.cap = buf.cap;
@@ -14619,11 +14373,11 @@ export fn dynamicfrom_vstream(buf: []u8) stream = {
// ---- vtable callbacks ----------------------------------------------------
// read_v — recover the stream from the vstream's `*vtable` via the
// readfn — recover the stream from the io.stream's `*vtable` via the
// intrusive offset-0 cast. Single fn over the common header (Hare's
// single `read` at ref/hare/memio/stream.ha:103); fixed and dynamic
// share it because the read path is buffer-flavour-agnostic.
fn read_v(s: io.vstream, buf: []u8) (size | io.eof | io.error) = {
fn readfn(s: io.stream, buf: []u8) (size | io.eof | io.error) = {
let m: *stream = s: *stream;
if (m.pos >= m.len) {
let e: io.eof;
@@ -14641,7 +14395,7 @@ fn read_v(s: io.vstream, buf: []u8) (size | io.eof | io.error) = {
return n: size;
};
fn fixedwrite_v(s: io.vstream, buf: []u8) (size | io.error) = {
fn fixedwrite(s: io.stream, buf: []u8) (size | io.error) = {
let m: *stream = s: *stream;
if (m.pos >= m.len) { return 0: size; };
let space: i32 = m.len - m.pos;
@@ -14656,10 +14410,10 @@ fn fixedwrite_v(s: io.vstream, buf: []u8) (size | io.error) = {
return n: size;
};
fn dynamicwrite_v(s: io.vstream, buf: []u8) (size | io.error) = {
fn dynamicwrite(s: io.stream, buf: []u8) (size | io.error) = {
let m: *stream = s: *stream;
let need: i32 = m.pos + buf.len;
if (need > m.cap) { dynamicgrow_v(m, need); };
if (need > m.cap) { dynamicgrow(m, need); };
let i: i32 = 0;
for (i < buf.len) {
m.ptr[m.pos + i] = buf[i];
@@ -14670,7 +14424,7 @@ fn dynamicwrite_v(s: io.vstream, buf: []u8) (size | io.error) = {
return buf.len: size;
};
fn dynamicclose_v(s: io.vstream) (void | io.error) = {
fn dynamicclose(s: io.stream) (void | io.error) = {
let m: *stream = s: *stream;
if (m.cap > 0) { os.free(m.ptr: *void, m.cap: u64); };
m.ptr = nil;
@@ -14680,11 +14434,11 @@ fn dynamicclose_v(s: io.vstream) (void | io.error) = {
return;
};
// Mirror of memio.ww:203 dynamicgrow. Double-and-copy with floor at
// 8. Module-prefixed `_v` suffix vs the OLD memio.dynamicgrow keeps
// the cstage flat-TU private-fn scope from colliding (memio.ww:200
// note + task #9).
fn dynamicgrow_v(d: *stream, need: i32) void = {
// Double-and-copy growth with floor at 8. `dynamicgrow`, not Hare's
// bare `grow`: cstage bundles all imported modules into a flat TU and
// resolves private fns by unqualified name (task #9), so the
// module-prefixed name keeps the symmetry with dynamicwrite/dynamicclose.
fn dynamicgrow(d: *stream, need: i32) void = {
let newcap: i32 = d.cap;
if (newcap < 8) { newcap = 8; };
for (newcap < need) { newcap *= 2; };
@@ -14701,53 +14455,49 @@ fn dynamicgrow_v(d: *stream, need: i32) void = {
// ---- accessors over the common `stream` header -----------------------
//
// Single fn each (drew string-collapse): Hare's string/reset/buffer/
// borrowedread all take `*stream` and read the flat header, so the
// fixed/dynamic split the OLD V-side carried (fixed_string /
// dynamic_string) collapses to one. FLIP drops the `_v` suffix
// (string_v → string, …) and deletes the OLD `*state` twins in
// memio.ww.
// Single fn each: Hare's string/reset/buffer/borrowedread all take
// `*stream` and read the flat header.
// string_v — bytes written so far, as a str view (buf[0..pos]).
// string — bytes written so far, as a str view (buf[0..pos]).
//
// Mirrors ref/hare/memio/stream.ha:81 string(in: *stream). Hare returns
// (str | utf8::invalid) — the validating constructor. ww returns a bare
// `str` per the CLAUDE.md rule-9 frombytes carve-out: utf8.validate at
// the IO source is opt-in, never wrapped per-construction; the honest
// name reserves a future validating helper.
export fn string_v(s: *stream) str = {
export fn string(s: *stream) str = {
let r: str;
r.ptr = s.ptr;
r.len = s.pos;
return r;
};
// buffer_v — borrowed []u8 view of bytes written so far (buf[0..pos]).
// buffer — borrowed []u8 view of bytes written so far (buf[0..pos]).
//
// Mirrors ref/hare/memio/stream.ha:74 buffer(in: *stream).
export fn buffer_v(s: *stream) []u8 = {
export fn buffer(s: *stream) []u8 = {
let r: []u8;
r.ptr = s.ptr;
r.len = s.pos;
return r;
};
// reset_v — rewind the cursor and truncate the logical content to 0.
// reset — rewind the cursor and truncate the logical content to 0.
// Backing storage is preserved; subsequent writes (dynamic) re-fill
// from the start without reallocation.
//
// Mirrors ref/hare/memio/stream.ha:87 reset(in: *stream).
export fn reset_v(s: *stream) void = {
export fn reset(s: *stream) void = {
s.pos = 0;
s.len = 0;
};
// borrowedread_v — return an `amt`-byte view starting at `pos` without
// borrowedread — return an `amt`-byte view starting at `pos` without
// copying, advancing the cursor. eof if fewer bytes are available.
//
// Mirrors ref/hare/memio/stream.ha:94 borrowedread(st: *stream, amt).
// `amt: i32` (not Hare's `size`) per the i32-index convention.
export fn borrowedread_v(s: *stream, amt: i32) ([]u8 | io.eof) = {
export fn borrowedread(s: *stream, amt: i32) ([]u8 | io.eof) = {
if (s.len - s.pos < amt) {
let e: io.eof;
return e;
@@ -28984,14 +28734,13 @@ fn localfind(c: *cgen, name: str) i32 = {
// Re-init per fn would abandon the buffer (no [[io.close]] path → no
// [[os.free]]) and re-grow from 0 via the 8→…→65536 ladder for every
// function. Same idiom as lib/log/log.ww:124 `ensureinit`.
let cgoutstate: memio.state;
let cgoutstream: io.stream;
let cgoutstream: memio.stream;
let cgoutmode: i32 = 0;
let cgoutinit: i32 = 0;
fn cgout_enable() void = {
if (cgoutinit == 0) {
memio.dynamic(&cgoutstate, &cgoutstream);
cgoutstream = memio.dynamic();
cgoutinit = 1;
};
cgoutmode = 1;
@@ -29000,9 +28749,9 @@ fn cgout_enable() void = {
fn cgout_disable() void = { cgoutmode = 0; };
fn cgout_flush() void = {
if (cgoutstate.pos > 0) {
os.write(1, cgoutstate.ptr, cgoutstate.pos: u64);
memio.reset(&cgoutstate);
if (cgoutstream.pos > 0) {
os.write(1, cgoutstream.ptr, cgoutstream.pos: u64);
memio.reset(&cgoutstream);
};
};
@@ -29011,9 +28760,10 @@ fn emitbytes(p: *u8, n: u64) void = {
let buf: []u8;
buf.ptr = p;
buf.len = n: i32;
// memio.dynamicwrite never returns io.closed (memio.ww:166);
// bare-discard mirrors lib/log/log.ww:169 fmt.fprintln.
io.write(&cgoutstream, buf);
// io.write over the embedded vtable (&cgoutstream.vt = io.stream);
// memio.dynamicwrite never errors. Bare-discard mirrors
// lib/log/log.ww stdprintln. #94 fold-eFinal.
io.write(&cgoutstream.vt, buf);
} else {
os.write(1, p, n);
};

View File

@@ -713,14 +713,13 @@ fn localfind(c: *cgen, name: str) i32 = {
// Re-init per fn would abandon the buffer (no [[io.close]] path → no
// [[os.free]]) and re-grow from 0 via the 8→…→65536 ladder for every
// function. Same idiom as lib/log/log.ww:124 `ensureinit`.
let cgoutstate: memio.state;
let cgoutstream: io.stream;
let cgoutstream: memio.stream;
let cgoutmode: i32 = 0;
let cgoutinit: i32 = 0;
fn cgout_enable() void = {
if (cgoutinit == 0) {
memio.dynamic(&cgoutstate, &cgoutstream);
cgoutstream = memio.dynamic();
cgoutinit = 1;
};
cgoutmode = 1;
@@ -729,9 +728,9 @@ fn cgout_enable() void = {
fn cgout_disable() void = { cgoutmode = 0; };
fn cgout_flush() void = {
if (cgoutstate.pos > 0) {
os.write(1, cgoutstate.ptr, cgoutstate.pos: u64);
memio.reset(&cgoutstate);
if (cgoutstream.pos > 0) {
os.write(1, cgoutstream.ptr, cgoutstream.pos: u64);
memio.reset(&cgoutstream);
};
};
@@ -740,9 +739,10 @@ fn emitbytes(p: *u8, n: u64) void = {
let buf: []u8;
buf.ptr = p;
buf.len = n: i32;
// memio.dynamicwrite never returns io.closed (memio.ww:166);
// bare-discard mirrors lib/log/log.ww:169 fmt.fprintln.
io.write(&cgoutstream, buf);
// io.write over the embedded vtable (&cgoutstream.vt = io.stream);
// memio.dynamicwrite never errors. Bare-discard mirrors
// lib/log/log.ww stdprintln. #94 fold-eFinal.
io.write(&cgoutstream.vt, buf);
} else {
os.write(1, p, n);
};

View File

@@ -14054,54 +14054,35 @@ export fn checkfile(c: *checker, file: *node) void = {
// io — stream interface (Plan 9 Bio / Hare io::stream shape).
//
// No closures, no methods. A `stream` is a struct of function
// pointers plus a `ctx: *void`. The error channel is the return
// value of read/write/close — Hare-shaped tagged unions instead of
// errno-style integer sentinels.
// No closures, no methods. A `stream` is a pointer to a `vtable` of
// function pointers (lib/io/stream.ww); read/write/close dispatch
// through it. The error channel is the return value — Hare-shaped
// tagged unions instead of errno-style integer sentinels.
//
// This file owns the eof / underread variant tags; lib/io/stream.ww
// owns the `vtable` + `stream` + read/write/close dispatchers, and
// lib/io/types.ww owns the error union, mode/whence enums, and the
// reader/writer/closer fn-type aliases. #94 fold-eFinal collapsed the
// pre-vtable `stream` struct + `closed` tag into the single vtable
// surface; the dispatchers are read/write/close (over `stream`),
// final over `handle` at io fold-2 (#5).
package io;
// eof — read past the end of the stream. Hare uses the `done`
// singleton for EOF; ww doesn't have `done` yet so we ship a
// named-void variant tag.
package io;
// named-void variant tag. Lifts to `done` with #93.
export type eof = void;
// closed — operation attempted on a stream that has already been
// closed. ww-specific: Hare's io collapses this into the wider
// errors union, but our stream vtable has no handle-ownership
// semantics, so a distinct tag is honest. Named void.
export type closed = void;
// underread — an I/O handle hit eof partway through a fixed-size
// read. Payload is the byte count actually delivered. Mirrors
// Hare's `io::underread = !size`; ww uses i32 because the
// underlying buffer-length type is i32 today.
export type underread = !i32;
export type stream = struct {
ctx: *void,
read: fn(s: *stream, buf: []u8) (i32 | eof | closed),
write: fn(s: *stream, buf: []u8) (i32 | closed),
close: fn(s: *stream) (void | closed),
};
export fn read(s: *stream, buf: []u8) (i32 | eof | closed) = {
return s.read(s, buf);
};
export fn write(s: *stream, buf: []u8) (i32 | closed) = {
return s.write(s, buf);
};
export fn close(s: *stream) (void | closed) = {
return s.close(s);
};
// stream — Hare-shaped vtable port. Project #94 fold-e1.
// stream — Hare-shaped vtable surface. Project #94 fold-eFinal.
//
// Adds the additive vtable surface alongside lib/io/io.ww's pre-vtable
// stream struct + read/write/close wrappers (which fold-e2-eN migrate
// to vtable-backed implementations and then retire). Three exports:
// The single io stream surface (the fold-eFinal collapse retired the
// pre-vtable `stream` struct + `closed` tag). Three exports:
//
// vtable a struct of optional fn-pointer slots — reader/writer/
// closer per ref/hare/io/stream.ha:36-42. Hare spells the
@@ -14109,35 +14090,25 @@ export fn close(s: *stream) (void | closed) = {
// spelling (#192), so each slot is a `(*T | void)` tagged
// union (Plan-9-lean per the Nullable kept ruling; opt-in
// at optionality boundaries only).
// vstream `*vtable` alias matching Hare's `stream = *vtable`
// (ref/hare/io/stream.ha:33). Named `vstream` rather than
// `stream` because lib/io/io.ww still owns the legacy
// `stream` struct name until fold-eN collapses both.
// st_read /
// st_write /
// st_close the three dispatchers per ref/hare/io/stream.ha:44-68.
// stream `*vtable` alias matching Hare's `stream = *vtable`
// (ref/hare/io/stream.ha:33).
// read /
// write /
// close the three dispatchers per ref/hare/io/stream.ha:44-68.
// Each `match`es the matching vtable slot, returns
// `errors.unsupported` (widened twice) when the slot is
// void, otherwise calls through the fn pointer.
// void, otherwise calls through the fn pointer. Hare keeps
// these private (`st_read` …) behind a `handle`-typed
// public `read`/`write`/`close`; ww has no `handle` yet
// (io fold-2, #5), so the dispatchers ARE the public
// surface and grow the `handle` match when #5 lands.
//
// Fold-e1 cgen/checker dependencies (all closed at HEAD): #191
// (N_DOT on aliased-ptr receiver — `s.reader` resolves through the
// `vstream = *vtable` alias), #198 (`is` on cross-module variant
// tag), #199 α (concrete→tagged widen via NAMED-variant nominal
// match, used by `let e: error = u;`), #200 (`as` payload-extract),
// #201 (return-of-tagged-call forwards instead of rewinds), #205
// (tagged→tagged subset accepts NAMED-variant nominal match, used
// by `return e;` where ret type contains `error` as a direct
// variant). Two direct widens compose for the void-arm `return`:
// concrete `errors.unsupported` → tagged `error` via #199 α, then
// tagged `error` → tagged `(size | eof | error)` via #205.
//
// Deferrals (drew-signed): `seeker` lands with fold-e2 once `off`
// + `whence` plug into the signature; Hare's `?`-propagating
// `st_close` body (`c(s)?;`) collapses to a direct `return (*c)(s);`
// here because #173 (TRY-on-tagged-return both-stages broken) is
// still open — the dispatcher's surface stays Hare-shaped, only the
// internal try-prop is omitted.
// Deferrals (drew-signed): `seeker` lands with io fold-2 (#5) once `off`
// + `whence` plug into the signature. Hare's `?`-propagating `close`
// body (`c(s)?;`) collapses to a direct `return (*c)(s);` here because
// the dispatcher's surface stays Hare-shaped; only the internal
// try-prop is omitted (#173 history, now closed — kept direct for the
// void-arm fall-through shape).
package io;
@@ -14149,16 +14120,15 @@ export type vtable = struct {
closer: (*closer | void),
};
// ref/hare/io/stream.ha:33. `vstream` not `stream` while io.ww still
// owns the legacy `stream` struct name.
export type vstream = *vtable;
// ref/hare/io/stream.ha:33.
export type stream = *vtable;
// ref/hare/io/stream.ha:44-50. The void-arm `return e;` chains two
// direct widens: `errors.unsupported → error` via #199 α, then
// `error → (size | eof | error)` via #205. The call-arm spells the
// fn-ptr call explicitly per #193 (ww requires `(*r)(args)` rather
// than implicit `r(args)`).
export fn st_read(s: vstream, buf: []u8) (size | eof | error) = {
export fn read(s: stream, buf: []u8) (size | eof | error) = {
match (s.reader) {
case void => {
let u: errors.unsupported;
@@ -14170,7 +14140,7 @@ export fn st_read(s: vstream, buf: []u8) (size | eof | error) = {
};
// ref/hare/io/stream.ha:53-59.
export fn st_write(s: vstream, buf: []u8) (size | error) = {
export fn write(s: stream, buf: []u8) (size | error) = {
match (s.writer) {
case void => {
let u: errors.unsupported;
@@ -14183,10 +14153,9 @@ export fn st_write(s: vstream, buf: []u8) (size | error) = {
// ref/hare/io/stream.ha:62-68. Hare propagates the close error via
// `c(s)?;` then falls through to the void arm; ww collapses to a
// direct return because #173 (TRY-on-tagged-return) is still open.
// The surface (void | error) matches; only the internal try-prop
// is omitted.
export fn st_close(s: vstream) (void | error) = {
// direct return for the void-arm fall-through shape. The surface
// (void | error) matches.
export fn close(s: stream) (void | error) = {
match (s.closer) {
case void => return void;
case let c: *closer => return (*c)(s);
@@ -14222,21 +14191,19 @@ export type exists = !void;
export type unsupported = !void;
// types — error union, mode/whence enums, reader/writer/closer
// fn-type aliases. Project #94 fold-1 step (d) (drew Fold D); the
// fn-aliases re-targeted from *stream → vstream as part of fold-e1
// (vtable port in stream.ww).
// fn-type aliases. Project #94 fold-eFinal; the fn-aliases target
// `stream` (= `*vtable`, the single io surface).
//
// Hare splits the io module across stream.ha + types.ha and ww does
// the same: lib/io/io.ww owns the (pre-vtable) `stream`, `eof`, and
// `underread` shapes; lib/io/stream.ww owns the Hare vtable port
// (`vtable`, `vstream`, `st_read`/`st_write`/`st_close` dispatch);
// this file owns the surrounding port. The three files share
// `package io;` so cross-file refs resolve via the dir-enum concat
// order (io.ww < stream.ww < types.ww — `vstream` lands before the
// reader/writer/closer aliases below).
// the same: lib/io/io.ww owns the `eof` and `underread` tags;
// lib/io/stream.ww owns the vtable surface (`vtable`, `stream`,
// `read`/`write`/`close` dispatch); this file owns the surrounding
// port. The three files share `package io;` so cross-file refs
// resolve via the dir-enum concat order (io.ww < stream.ww < types.ww
// — `stream` lands before the reader/writer/closer aliases below).
//
// Deferrals (drew-signed): `handle`, `seeker`, `copier`, `strerror`
// land with fold-e2+ (need the `handle` sum). Hare's `EOF = done`
// land with io fold-2 (#5; need the `handle` sum). Hare's `EOF = done`
// stays as `io.eof` until the `done` singleton ships (#93).
package io;
@@ -14266,283 +14233,72 @@ export type whence = enum i32 {
// ww enumerates the tags explicitly (ken's #204-block — spread of an
// errors-side tagged into this union triggers a wrapper-vs-flatten
// layout mismatch; #199b layout-extension is the deferred fix). The
// st_read/st_write dispatchers in stream.ww return
// `errors.unsupported` when the matching vtable slot is unset, so the
// variant lands here directly.
// read/write dispatchers in stream.ww return `errors.unsupported`
// when the matching vtable slot is unset, so the variant lands here
// directly.
export type error = !(errors.unsupported | underread | nomem);
// ref/hare/io/types.ha:46. `eof` (not Hare's `done`) per the io.ww:8
// rationale; lifts to `done` with #93. `vstream` forward-refs the
// ref/hare/io/types.ha:46. `eof` (not Hare's `done`) per the io.ww
// rationale; lifts to `done` with #93. `stream` forward-refs the
// `*vtable` alias in stream.ww — the dispatcher receives the vtable
// pointer directly (Hare's stream.ha:33 + types.ha:46 collapse).
export type reader = fn(s: vstream, buf: []u8) (size | eof | error);
export type reader = fn(s: stream, buf: []u8) (size | eof | error);
// ref/hare/io/types.ha:51. No `const` qualifier — ww has none, and
// lib/io/io.ww:30 + lib/sort/sort.ww:18 drop it the same way.
export type writer = fn(s: vstream, buf: []u8) (size | error);
// lib/sort/sort.ww:18 drops it the same way.
export type writer = fn(s: stream, buf: []u8) (size | error);
// ref/hare/io/types.ha:55.
export type closer = fn(s: vstream) (void | error);
export type closer = fn(s: stream) (void | error);
// memio — in-memory io stream.
// memio — in-memory io stream. Project #94 fold-eFinal.
//
// Hare's memio:: surface, drop underscores. Two flavours behind a
// single [[io.stream]]:
// single [[io.stream]] (= `*io.vtable`):
//
// fixed caller owns the buffer, writes stop when full.
// dynamic memio owns the buffer, writes grow it; close frees.
//
// Call shape divergence from Hare: the caller supplies both the
// memio `state` and the `io.stream` slot, by pointer. ww cgen does
// not yet implement &x.field or 32B-struct return-by-value, so the
// Hare `let s = memio::fixed(buf)` shape isn't reachable; collapse
// to a single returned struct when those land (lib/CLAUDE.md
// "graduate in one go").
//
// let mem: memio.state;
// let s: io.stream;
// memio.fixed(&mem, &s, buf);
// io.write(&s, bytes);
//
// Subset of Hare's surface: io.stream's variants are {eof, closed},
// so memio drops Hare's NONBLOCK flag (would need an `again` variant
// in lib/io). string()'s utf8-validating constructor is omitted per
// CLAUDE.md rule 9 carve-out. Hare's seek / copy callbacks are
// likewise absent: lib/io's stream vtable has only read/write/close
// slots, so memio can't wire a seeker or copier even if we wanted
// to. All three come back when their dependencies do.
package memio;
import io;
import os;
import rt;
// state — memio's per-stream bookkeeping. The caller owns the slot
// and passes its address into a constructor. `ptr/len/cap` are the
// slice fields kept flat to dodge a chained-dot write through the
// state pointer (cgen doesn't store into `m.buf.ptr` reliably).
export type state = struct {
ptr: *u8,
len: i32,
cap: i32,
pos: i32,
};
// fixed — wire `s` over a caller-supplied buffer. Writes never grow;
// they return 0 once `pos` reaches the end of the buffer.
export fn fixed(m: *state, s: *io.stream, buf: []u8) void = {
m.ptr = buf.ptr;
m.len = buf.len;
m.cap = buf.len;
m.pos = 0;
s.ctx = m: *void;
s.read = readfn;
s.write = fixedwrite;
s.close = closenoop;
};
// dynamic — wire `s` with no initial buffer. Writes grow the backing
// allocation; [[io.close]] frees it.
export fn dynamic(m: *state, s: *io.stream) void = {
m.ptr = nil;
m.len = 0;
m.cap = 0;
m.pos = 0;
s.ctx = m: *void;
s.read = readfn;
s.write = dynamicwrite;
s.close = dynamicclose;
};
// dynamicfrom — like [[dynamic]] but seeded with an existing slice.
// Ownership of the slice transfers to the stream; [[io.close]] frees
// it. The slice must come from the runtime allocator: close calls
// [[os.free]] with `m.cap` bytes, which is taken from `buf.cap` (the
// slice's allocated capacity), not its logical length. Passing a
// half-filled append slice (len < cap) and using only `buf.len` here
// would under-free on close.
export fn dynamicfrom(m: *state, s: *io.stream, buf: []u8) void = {
m.ptr = buf.ptr;
m.len = buf.len;
m.cap = buf.cap;
m.pos = 0;
s.ctx = m: *void;
s.read = readfn;
s.write = dynamicwrite;
s.close = dynamicclose;
};
// buffer — borrowed view of bytes written so far (buf[..pos]).
// Seek to the end before calling if the full buffer is wanted.
export fn buffer(m: *state) []u8 = {
let r: []u8;
r.ptr = m.ptr;
r.len = m.pos;
return r;
};
// string — bytes written so far, as a str view. Hare returns
// (str | utf8::invalid); ww doesn't ship utf8 validation yet, so
// this returns the unchecked view.
export fn string(m: *state) str = {
let r: str;
r.ptr = m.ptr;
r.len = m.pos;
return r;
};
// reset — rewind the cursor and truncate the logical content to 0.
// Backing storage is preserved; subsequent writes (dynamic) re-fill
// from the start without reallocation.
export fn reset(m: *state) void = {
m.pos = 0;
m.len = 0;
};
// borrowedread — return an `amt`-byte view starting at `pos` without
// copying, advancing the cursor. eof if fewer bytes are available.
export fn borrowedread(m: *state, amt: i32) ([]u8 | io.eof) = {
if (m.len - m.pos < amt) {
let e: io.eof;
return e;
};
let r: []u8;
r.ptr = m.ptr + (m.pos: u64);
r.len = amt;
m.pos += amt;
return r;
};
// ---- vtable callbacks ------------------------------------------------
fn readfn(s: *io.stream, buf: []u8) (i32 | io.eof | io.closed) = {
let m: *state = s.ctx: *state;
if (m.pos >= m.len) {
let e: io.eof;
return e;
};
let avail: i32 = m.len - m.pos;
let n: i32 = buf.len;
if (avail < n) { n = avail; };
let i: i32 = 0;
for (i < n) {
buf[i] = m.ptr[m.pos + i];
i += 1;
};
m.pos += n;
return n;
};
fn fixedwrite(s: *io.stream, buf: []u8) (i32 | io.closed) = {
let m: *state = s.ctx: *state;
if (m.pos >= m.len) { return 0; };
let space: i32 = m.len - m.pos;
let n: i32 = buf.len;
if (space < n) { n = space; };
let i: i32 = 0;
for (i < n) {
m.ptr[m.pos + i] = buf[i];
i += 1;
};
m.pos += n;
return n;
};
fn dynamicwrite(s: *io.stream, buf: []u8) (i32 | io.closed) = {
let m: *state = s.ctx: *state;
let need: i32 = m.pos + buf.len;
if (need > m.cap) { dynamicgrow(m, need); };
let i: i32 = 0;
for (i < buf.len) {
m.ptr[m.pos + i] = buf[i];
i += 1;
};
m.pos += buf.len;
if (m.pos > m.len) { m.len = m.pos; };
return buf.len;
};
fn dynamicclose(s: *io.stream) (void | io.closed) = {
let m: *state = s.ctx: *state;
if (m.cap > 0) { os.free(m.ptr: *void, m.cap: u64); };
m.ptr = nil;
m.len = 0;
m.cap = 0;
m.pos = 0;
return;
};
fn closenoop(s: *io.stream) (void | io.closed) = {
return;
};
// Double-and-copy growth. Initial bump from 0 lands at 8 to amortise
// small write bursts without a tail of reallocs.
//
// `dynamicgrow`, not Hare's bare `grow`: cstage bundles all imported
// modules into a flat TU and resolves private fns by unqualified name,
// so historically two `fn grow` decls (here + the now-deleted bump
// arena's `grow`) would have collided. Module-prefixed name kept the
// symmetry with `dynamicwrite`/`dynamicclose`; retained pending task
// #9 (module-aware private-fn scoping in cstage).
fn dynamicgrow(m: *state, need: i32) void = {
let newcap: i32 = m.cap;
if (newcap < 8) { newcap = 8; };
for (newcap < need) { newcap *= 2; };
let nbuf: *u8 = rt.malloc(newcap: u64): *u8;
let i: i32 = 0;
for (i < m.len) {
nbuf[i] = m.ptr[i];
i += 1;
};
if (m.cap > 0) { os.free(m.ptr: *void, m.cap: u64); };
m.ptr = nbuf;
m.cap = newcap;
};
// vstream — Hare-shaped vtable wrappers over memio. Project #94
// fold-eFinal(PREP).
//
// Adds three constructors that return a memio `stream` BY VALUE (the
// Hare shape, ref/hare/memio/stream.ha:46,58,64) alongside the
// pre-vtable memio.fixed / memio.dynamic / memio.dynamicfrom in
// memio.ww. The OLD surface stays untouched here — fold-eFinal (FLIP)
// atomically flips the package shape: deletes the OLD constructors +
// `state` + callbacks, renames the `_vstream`/`_v` suffix off (so
// `fixed_vstream → fixed`, `stream` stays, `string_v → string`), and
// migrates the few callers (lib/fmt/vstream.ww + the 776 probe).
//
// Hare's memio::fixed/dynamic/dynamic_from return a `stream` whose
// FIRST field IS the `io::stream` (= `*vtable`). ww mirrors that
// intrusively: `stream`'s first field is `vt: io.vtable` (the vtable
// embedded INLINE) so a stack `stream` is castable to
// `vstream = *vtable` via `&s.vt` — and the callbacks recover the
// `io.stream = *vtable` via `&s.vt` — and the callbacks recover the
// outer `stream` by casting the dispatch arg back to `*stream`. Same
// intrusive shape as lib/bufio + lib/log over their embedded vtables.
//
// VALUE-RETURN (drew, gating): the constructor builds the struct in a
// local `let r: stream;`, field-assigns every slot (including the
// tagged vt sub-fields), and `return r;` — the proven sret round-trip
// shape pinned by test/wcc/925 ("ident_return_rhs" row) and extended
// to tagged-union-field structs by the 776 byte-id rows here. NO heap,
// NO `nomem`: the alloc that forced the OLD `(io.vstream | nomem)`
// return is gone, so the constructor cannot fail. Caller owns the
// returned `stream` (stack ownership, no-GC) and passes `&s.vt` to the
// io.st_* dispatchers — exactly Hare's `&s` into io::write.
// let s: memio.stream = memio.fixed(buf);
// io.write(&s.vt, bytes);
// let view: str = memio.string(&s);
//
// Constructors return the `stream` BY VALUE (Hare shape,
// ref/hare/memio/stream.ha:46,58,64): build the struct in a local,
// field-assign every slot, and `return r;` (the proven sret round-trip
// pinned by test/wcc/925 + 776). NO heap, NO `nomem`: the alloc that
// forced an earlier heap return is gone, so the constructor cannot
// fail. Caller owns the returned `stream` (stack ownership, no-GC) and
// passes `&s.vt` to the io dispatchers — exactly Hare's `&s` into
// io::write.
//
// Subset of Hare's surface: io's variants are {eof, error}, so memio
// drops Hare's NONBLOCK flag (would need an `again` variant in lib/io).
// string()'s utf8-validating constructor is omitted per CLAUDE.md
// rule 9 carve-out — see [[string]]. Hare's seek / copy callbacks are
// likewise absent: io's stream vtable has only read/write/close slots,
// so memio can't wire a seeker or copier yet (io fold-2, #5).
//
// Cast workaround per #206-payoff (ken: KEEP the explicit casts; they
// are cgen-neutral and sidestep the #214 over-acceptance surface). The
// `(&fn_name): *io.<role>` cast at each store site is the Hare-faithful
// minimum-touch route — same workaround test/wcc/775_io_vtable_run.c
// uses for the bare-vtable init.
// minimum-touch route — the #206 cast-drop is a separate deferred
// payoff gated on #214.
//
// ptr/len/cap kept flat (no `buf: []u8`) per the memio.state note
// (memio.ww:39): chained-dot writes through a state pointer into a
// slice subfield miscompile silently (#195 family); the flat shape
// sidesteps it. dynamicfrom_vstream uses the slice's `cap` (NOT `len`)
// to track the allocated-capacity-to-free on close — mirrors the OLD
// memio.dynamicfrom note (memio.ww:73).
// ptr/len/cap kept flat (no `buf: []u8`) per a historical cgen note:
// chained-dot writes through a state pointer into a slice subfield
// miscompile silently (#195 family); the flat shape sidesteps it.
// dynamicfrom uses the slice's `cap` (NOT `len`) to track the
// allocated-capacity-to-free on close — passing a half-filled append
// slice with len < cap and using only `buf.len` would under-free.
package memio;
@@ -14551,11 +14307,11 @@ import os;
import rt;
// stream — Hare's memio::stream (ref/hare/memio/stream.ha:18). `vt` at
// offset 0 for the intrusive stream→vstream cast (`&s.vt`) and the
// offset 0 for the intrusive stream→io.stream cast (`&s.vt`) and the
// callbacks' reverse `s: *stream` cast. Unified across fixed/dynamic
// (Hare keeps a single `stream` over per-mode vtable singletons; ww
// wires the per-mode callbacks post-construction instead). ptr/len/cap
// flat per the memio.state note above.
// flat per the header note.
export type stream = struct {
vt: io.vtable,
ptr: *u8,
@@ -14564,17 +14320,17 @@ export type stream = struct {
pos: i32,
};
// fixed_vstream — wire a stream over a caller-supplied buffer. Writes
// never grow; they return 0 once `pos` reaches the end of the buffer
// (Hare returns `nomem` here; ww surfaces 0 to mirror the OLD
// memio.fixedwrite divergence — graduating to Hare's `nomem` return
// needs the widen-from-bare-nomem path that #173 blocks).
// fixed — wire a stream over a caller-supplied buffer. Writes never
// grow; they return 0 once `pos` reaches the end of the buffer (Hare
// returns `nomem` here; ww surfaces 0 — graduating to Hare's `nomem`
// return needs the widen-from-bare-nomem path that #173-family work
// gates).
//
// Mirrors ref/hare/memio/stream.ha:46.
export fn fixed_vstream(buf: []u8) stream = {
export fn fixed(buf: []u8) stream = {
let r: stream;
r.vt.reader = (&read_v): *io.reader;
r.vt.writer = (&fixedwrite_v): *io.writer;
r.vt.reader = (&readfn): *io.reader;
r.vt.writer = (&fixedwrite): *io.writer;
r.ptr = buf.ptr;
r.len = buf.len;
r.cap = buf.len;
@@ -14582,15 +14338,15 @@ export fn fixed_vstream(buf: []u8) stream = {
return r;
};
// dynamic_vstream — wire a stream with no initial buffer. Writes grow
// the backing allocation; [[io.st_close]] frees it.
// dynamic — wire a stream with no initial buffer. Writes grow the
// backing allocation; [[io.close]] frees it.
//
// Mirrors ref/hare/memio/stream.ha:58.
export fn dynamic_vstream() stream = {
export fn dynamic() stream = {
let r: stream;
r.vt.reader = (&read_v): *io.reader;
r.vt.writer = (&dynamicwrite_v): *io.writer;
r.vt.closer = (&dynamicclose_v): *io.closer;
r.vt.reader = (&readfn): *io.reader;
r.vt.writer = (&dynamicwrite): *io.writer;
r.vt.closer = (&dynamicclose): *io.closer;
r.ptr = nil;
r.len = 0;
r.cap = 0;
@@ -14598,18 +14354,16 @@ export fn dynamic_vstream() stream = {
return r;
};
// dynamicfrom_vstream — like [[dynamic_vstream]] but seeded with an
// existing slice. Ownership transfers; close frees `cap` bytes from
// the slice's allocated capacity (NOT logical length) — passing a
// half-filled append slice with len < cap and using only `buf.len`
// would under-free on close.
// dynamicfrom — like [[dynamic]] but seeded with an existing slice.
// Ownership transfers; close frees `cap` bytes from the slice's
// allocated capacity (NOT logical length).
//
// Mirrors ref/hare/memio/stream.ha:64.
export fn dynamicfrom_vstream(buf: []u8) stream = {
export fn dynamicfrom(buf: []u8) stream = {
let r: stream;
r.vt.reader = (&read_v): *io.reader;
r.vt.writer = (&dynamicwrite_v): *io.writer;
r.vt.closer = (&dynamicclose_v): *io.closer;
r.vt.reader = (&readfn): *io.reader;
r.vt.writer = (&dynamicwrite): *io.writer;
r.vt.closer = (&dynamicclose): *io.closer;
r.ptr = buf.ptr;
r.len = buf.len;
r.cap = buf.cap;
@@ -14619,11 +14373,11 @@ export fn dynamicfrom_vstream(buf: []u8) stream = {
// ---- vtable callbacks ----------------------------------------------------
// read_v — recover the stream from the vstream's `*vtable` via the
// readfn — recover the stream from the io.stream's `*vtable` via the
// intrusive offset-0 cast. Single fn over the common header (Hare's
// single `read` at ref/hare/memio/stream.ha:103); fixed and dynamic
// share it because the read path is buffer-flavour-agnostic.
fn read_v(s: io.vstream, buf: []u8) (size | io.eof | io.error) = {
fn readfn(s: io.stream, buf: []u8) (size | io.eof | io.error) = {
let m: *stream = s: *stream;
if (m.pos >= m.len) {
let e: io.eof;
@@ -14641,7 +14395,7 @@ fn read_v(s: io.vstream, buf: []u8) (size | io.eof | io.error) = {
return n: size;
};
fn fixedwrite_v(s: io.vstream, buf: []u8) (size | io.error) = {
fn fixedwrite(s: io.stream, buf: []u8) (size | io.error) = {
let m: *stream = s: *stream;
if (m.pos >= m.len) { return 0: size; };
let space: i32 = m.len - m.pos;
@@ -14656,10 +14410,10 @@ fn fixedwrite_v(s: io.vstream, buf: []u8) (size | io.error) = {
return n: size;
};
fn dynamicwrite_v(s: io.vstream, buf: []u8) (size | io.error) = {
fn dynamicwrite(s: io.stream, buf: []u8) (size | io.error) = {
let m: *stream = s: *stream;
let need: i32 = m.pos + buf.len;
if (need > m.cap) { dynamicgrow_v(m, need); };
if (need > m.cap) { dynamicgrow(m, need); };
let i: i32 = 0;
for (i < buf.len) {
m.ptr[m.pos + i] = buf[i];
@@ -14670,7 +14424,7 @@ fn dynamicwrite_v(s: io.vstream, buf: []u8) (size | io.error) = {
return buf.len: size;
};
fn dynamicclose_v(s: io.vstream) (void | io.error) = {
fn dynamicclose(s: io.stream) (void | io.error) = {
let m: *stream = s: *stream;
if (m.cap > 0) { os.free(m.ptr: *void, m.cap: u64); };
m.ptr = nil;
@@ -14680,11 +14434,11 @@ fn dynamicclose_v(s: io.vstream) (void | io.error) = {
return;
};
// Mirror of memio.ww:203 dynamicgrow. Double-and-copy with floor at
// 8. Module-prefixed `_v` suffix vs the OLD memio.dynamicgrow keeps
// the cstage flat-TU private-fn scope from colliding (memio.ww:200
// note + task #9).
fn dynamicgrow_v(d: *stream, need: i32) void = {
// Double-and-copy growth with floor at 8. `dynamicgrow`, not Hare's
// bare `grow`: cstage bundles all imported modules into a flat TU and
// resolves private fns by unqualified name (task #9), so the
// module-prefixed name keeps the symmetry with dynamicwrite/dynamicclose.
fn dynamicgrow(d: *stream, need: i32) void = {
let newcap: i32 = d.cap;
if (newcap < 8) { newcap = 8; };
for (newcap < need) { newcap *= 2; };
@@ -14701,53 +14455,49 @@ fn dynamicgrow_v(d: *stream, need: i32) void = {
// ---- accessors over the common `stream` header -----------------------
//
// Single fn each (drew string-collapse): Hare's string/reset/buffer/
// borrowedread all take `*stream` and read the flat header, so the
// fixed/dynamic split the OLD V-side carried (fixed_string /
// dynamic_string) collapses to one. FLIP drops the `_v` suffix
// (string_v → string, …) and deletes the OLD `*state` twins in
// memio.ww.
// Single fn each: Hare's string/reset/buffer/borrowedread all take
// `*stream` and read the flat header.
// string_v — bytes written so far, as a str view (buf[0..pos]).
// string — bytes written so far, as a str view (buf[0..pos]).
//
// Mirrors ref/hare/memio/stream.ha:81 string(in: *stream). Hare returns
// (str | utf8::invalid) — the validating constructor. ww returns a bare
// `str` per the CLAUDE.md rule-9 frombytes carve-out: utf8.validate at
// the IO source is opt-in, never wrapped per-construction; the honest
// name reserves a future validating helper.
export fn string_v(s: *stream) str = {
export fn string(s: *stream) str = {
let r: str;
r.ptr = s.ptr;
r.len = s.pos;
return r;
};
// buffer_v — borrowed []u8 view of bytes written so far (buf[0..pos]).
// buffer — borrowed []u8 view of bytes written so far (buf[0..pos]).
//
// Mirrors ref/hare/memio/stream.ha:74 buffer(in: *stream).
export fn buffer_v(s: *stream) []u8 = {
export fn buffer(s: *stream) []u8 = {
let r: []u8;
r.ptr = s.ptr;
r.len = s.pos;
return r;
};
// reset_v — rewind the cursor and truncate the logical content to 0.
// reset — rewind the cursor and truncate the logical content to 0.
// Backing storage is preserved; subsequent writes (dynamic) re-fill
// from the start without reallocation.
//
// Mirrors ref/hare/memio/stream.ha:87 reset(in: *stream).
export fn reset_v(s: *stream) void = {
export fn reset(s: *stream) void = {
s.pos = 0;
s.len = 0;
};
// borrowedread_v — return an `amt`-byte view starting at `pos` without
// borrowedread — return an `amt`-byte view starting at `pos` without
// copying, advancing the cursor. eof if fewer bytes are available.
//
// Mirrors ref/hare/memio/stream.ha:94 borrowedread(st: *stream, amt).
// `amt: i32` (not Hare's `size`) per the i32-index convention.
export fn borrowedread_v(s: *stream, amt: i32) ([]u8 | io.eof) = {
export fn borrowedread(s: *stream, amt: i32) ([]u8 | io.eof) = {
if (s.len - s.pos < amt) {
let e: io.eof;
return e;
@@ -28984,14 +28734,13 @@ fn localfind(c: *cgen, name: str) i32 = {
// Re-init per fn would abandon the buffer (no [[io.close]] path → no
// [[os.free]]) and re-grow from 0 via the 8→…→65536 ladder for every
// function. Same idiom as lib/log/log.ww:124 `ensureinit`.
let cgoutstate: memio.state;
let cgoutstream: io.stream;
let cgoutstream: memio.stream;
let cgoutmode: i32 = 0;
let cgoutinit: i32 = 0;
fn cgout_enable() void = {
if (cgoutinit == 0) {
memio.dynamic(&cgoutstate, &cgoutstream);
cgoutstream = memio.dynamic();
cgoutinit = 1;
};
cgoutmode = 1;
@@ -29000,9 +28749,9 @@ fn cgout_enable() void = {
fn cgout_disable() void = { cgoutmode = 0; };
fn cgout_flush() void = {
if (cgoutstate.pos > 0) {
os.write(1, cgoutstate.ptr, cgoutstate.pos: u64);
memio.reset(&cgoutstate);
if (cgoutstream.pos > 0) {
os.write(1, cgoutstream.ptr, cgoutstream.pos: u64);
memio.reset(&cgoutstream);
};
};
@@ -29011,9 +28760,10 @@ fn emitbytes(p: *u8, n: u64) void = {
let buf: []u8;
buf.ptr = p;
buf.len = n: i32;
// memio.dynamicwrite never returns io.closed (memio.ww:166);
// bare-discard mirrors lib/log/log.ww:169 fmt.fprintln.
io.write(&cgoutstream, buf);
// io.write over the embedded vtable (&cgoutstream.vt = io.stream);
// memio.dynamicwrite never errors. Bare-discard mirrors
// lib/log/log.ww stdprintln. #94 fold-eFinal.
io.write(&cgoutstream.vt, buf);
} else {
os.write(1, p, n);
};

View File

@@ -1372,32 +1372,30 @@ static const struct row rows[] = {
" raw[0] = 102u8; raw[1] = 111u8; raw[2] = 111u8; raw[3] = 10u8;\n"
" raw[4] = 98u8; raw[5] = 97u8; raw[6] = 114u8; raw[7] = 10u8;\n"
" raw[8] = 98u8; raw[9] = 97u8; raw[10] = 122u8;\n"
" let mem: memio.state;\n"
" let m: io.stream;\n"
" memio.fixed(&mem, &m, raw[0:11]);\n"
" let mst: memio.stream = memio.fixed(raw[0:11]);\n"
" let m: io.stream = &mst.vt;\n"
" let buf: [16]u8;\n"
" let sc: bufio.scanner;\n"
" bufio.newscanner(&sc, &m, buf[0:16]);\n"
" let sc: bufio.scanner = bufio.newscannerbuf(m, buf[0:16]);\n"
" let acc: i32 = 0;\n"
" let l1: (str | io.eof | io.closed | bufio.overflow) = bufio.scanline(&sc);\n"
" let l1: (str | io.eof | io.error | bufio.overflow) = bufio.scanline(&sc);\n"
" match (l1) {\n"
" case let s: str => acc += s.len;\n"
" case io.eof => acc += -100;\n"
" case io.closed => acc += -1000;\n"
" case let _e: io.error => acc += -1000;\n"
" case bufio.overflow => acc += -10000;\n"
" };\n"
" let l2: (str | io.eof | io.closed | bufio.overflow) = bufio.scanline(&sc);\n"
" let l2: (str | io.eof | io.error | bufio.overflow) = bufio.scanline(&sc);\n"
" match (l2) {\n"
" case let s: str => acc += s.len;\n"
" case io.eof => acc += -100;\n"
" case io.closed => acc += -1000;\n"
" case let _e: io.error => acc += -1000;\n"
" case bufio.overflow => acc += -10000;\n"
" };\n"
" let l3: (str | io.eof | io.closed | bufio.overflow) = bufio.scanline(&sc);\n"
" let l3: (str | io.eof | io.error | bufio.overflow) = bufio.scanline(&sc);\n"
" match (l3) {\n"
" case let s: str => acc += -100;\n"
" case io.eof => acc += 7;\n"
" case io.closed => acc += -1000;\n"
" case let _e: io.error => acc += -1000;\n"
" case bufio.overflow => acc += -10000;\n"
" };\n"
" return acc;\n"
@@ -1715,11 +1713,16 @@ static const struct row rows[] = {
* mixed-type args at the call site. End-to-end exercises the
* lib/fmt graduation: the wrapper-chain `println → fdprintln →
* fdprint` is itself variadic-forwarding, so this validates both
* gather (at main) and `args...` forward (inside lib/fmt). The
* exit code is bytes printed (`hello 7\n` = 8). */
* gather (at main) and `args...` forward (inside lib/fmt). println
* returns Hare's `(size | io.error)` (#94 fold-eFinal); the size
* arm carries bytes printed (`hello 7\n` = 8). */
{ "import fmt;\n"
"import io;\n"
"fn main() i32 = {\n"
" return fmt.println(\"hello\", 7i64): i32;\n"
" match (fmt.println(\"hello\", 7i64)) {\n"
" case let n: size => return n: i32;\n"
" case let _e: io.error => return -1;\n"
" };\n"
"};", 8 },
/* short-circuit `&&`: RHS skipped when LHS is false. Without
* short-circuit the `p.x` deref on a nil pointer segfaults.

View File

@@ -2,8 +2,8 @@
* 768_io_types_run — project #94 fold-1 step (d) (drew Fold D) sentinel.
* Pins lib/io/types.ww (ref/hare/io/types.ha) — `mode` / `whence`
* enums, `error = !(errors.unsupported | underread | nomem)` union,
* and the `reader` / `writer` / `closer` fn-type aliases. Fold-e1
* re-targeted the aliases from *stream → `vstream` (= *vtable in
* and the `reader` / `writer` / `closer` fn-type aliases. The
* fold-eFinal FLIP targets the aliases at `stream` (= *vtable in
* lib/io/stream.ww); the rows track. Hare splits the io module the
* same way: stream.ha + types.ha share the `io` module.
*
@@ -104,16 +104,16 @@ static const struct row rows[] = {
STAGE_CS | STAGE_WW },
/* reader/writer/closer alias rows. Fold-e1 re-targeted these aliases
* from *stream → vstream (= *vtable); the test fixtures track. The
* callee receivers are typed io.vstream and the call sites build a
* callee receivers are typed io.stream and the call sites build a
* stub vtable to take its address from. The vtable values are inert
* — only the dispatch type-shape is exercised. */
{ "reader_alias_param",
"package main;\n"
"import io;\n"
"fn dummy_read(s: io.vstream, buf: []u8) (size | io.eof | io.error) = {\n"
"fn dummy_read(s: io.stream, buf: []u8) (size | io.eof | io.error) = {\n"
" return 0: size;\n"
"};\n"
"fn use_reader(r: io.reader, s: io.vstream, buf: []u8) i32 = {\n"
"fn use_reader(r: io.reader, s: io.stream, buf: []u8) i32 = {\n"
" let v = r(s, buf);\n"
" if (v is size) { return 11; };\n"
" return -1;\n"
@@ -129,10 +129,10 @@ static const struct row rows[] = {
{ "writer_alias_param",
"package main;\n"
"import io;\n"
"fn dummy_write(s: io.vstream, buf: []u8) (size | io.error) = {\n"
"fn dummy_write(s: io.stream, buf: []u8) (size | io.error) = {\n"
" return buf.len: size;\n"
"};\n"
"fn use_writer(w: io.writer, s: io.vstream, buf: []u8) i32 = {\n"
"fn use_writer(w: io.writer, s: io.stream, buf: []u8) i32 = {\n"
" let v = w(s, buf);\n"
" if (v is size) { return 22; };\n"
" return -1;\n"
@@ -148,10 +148,10 @@ static const struct row rows[] = {
{ "closer_alias_param",
"package main;\n"
"import io;\n"
"fn dummy_close(s: io.vstream) (void | io.error) = {\n"
"fn dummy_close(s: io.stream) (void | io.error) = {\n"
" return void;\n"
"};\n"
"fn use_closer(c: io.closer, s: io.vstream) i32 = {\n"
"fn use_closer(c: io.closer, s: io.stream) i32 = {\n"
" let _v = c(s);\n"
" return 33;\n"
"};\n"

View File

@@ -1,10 +1,12 @@
/*
* 775_io_vtable_run — project #94 fold-e1 sentinel. Pins the additive
* lib/io/stream.ww vtable port: `vtable` (the three (*T|void) slots),
* `vstream` (= *vtable), and the `st_read`/`st_write`/`st_close`
* dispatchers (ref/hare/io/stream.ha:33-68). Coexists with lib/io/io.ww's
* legacy `stream` struct + read/write/close wrappers (fold-e2+ migrates
* them to vtable-backed implementations and retires the legacy surface).
* 775_io_vtable_run — project #94 fold-eFinal sentinel. Pins the
* single-surface lib/io/stream.ww vtable port: `vtable` (the three
* (*T|void) slots), `stream` (= *vtable), and the read/write/close
* dispatchers (ref/hare/io/stream.ha:33-68). The fold-eFinal FLIP
* retired the pre-vtable `stream` struct + `closed` tag, leaving this
* as the sole io surface. (Comment prose below predates the FLIP and
* still says `vstream`/`st_read`/`st_write`/`st_close` for the same
* `stream`/`read`/`write`/`close`.)
*
* Each row imports io, allocates a vtable, optionally points one slot at
* a user fn (via the `(&fn): *io.<role>` cast — see SIBLINGS below),
@@ -48,7 +50,7 @@
* | callee row; catches a constant-fold
* | mistake in the dispatch path.
*
* SIBLINGS (filed inline, NOT fixed here — fold-e1 is purely additive):
* DEFERRALS / RELATED (NOT addressed by these rows):
*
* - #199b layout-extension (wrapped-slot tag-remap): when widening a
* NAMED wrapper-typed value (e.g. `error`) into a tagged parent
@@ -109,16 +111,16 @@ static const struct row rows[] = {
{ "reader_set_happy",
"package main;\n"
"import io;\n"
"fn myread(s: io.vstream, buf: []u8) (size | io.eof | io.error) = {\n"
"fn myread(s: io.stream, buf: []u8) (size | io.eof | io.error) = {\n"
" return 7: size;\n"
"};\n"
"export fn main() i32 = {\n"
" let vt: io.vtable;\n"
" vt.reader = (&myread): *io.reader;\n"
" let v: io.vstream = &vt;\n"
" let v: io.stream = &vt;\n"
" let buf: [4]u8;\n"
" let bs: []u8 = buf[0:4];\n"
" let r = io.st_read(v, bs);\n"
" let r = io.read(v, bs);\n"
" if (r is size) {\n"
" let n = r as size;\n"
" if (n == 7: size) { return 70; };\n"
@@ -133,10 +135,10 @@ static const struct row rows[] = {
"import io;\n"
"export fn main() i32 = {\n"
" let vt: io.vtable;\n"
" let v: io.vstream = &vt;\n"
" let v: io.stream = &vt;\n"
" let buf: [4]u8;\n"
" let bs: []u8 = buf[0:4];\n"
" let _r = io.st_read(v, bs);\n"
" let _r = io.read(v, bs);\n"
" return 11;\n"
"};\n",
11,
@@ -144,16 +146,16 @@ static const struct row rows[] = {
{ "writer_set_happy",
"package main;\n"
"import io;\n"
"fn mywrite(s: io.vstream, buf: []u8) (size | io.error) = {\n"
"fn mywrite(s: io.stream, buf: []u8) (size | io.error) = {\n"
" return buf.len: size;\n"
"};\n"
"export fn main() i32 = {\n"
" let vt: io.vtable;\n"
" vt.writer = (&mywrite): *io.writer;\n"
" let v: io.vstream = &vt;\n"
" let v: io.stream = &vt;\n"
" let buf: [5]u8;\n"
" let bs: []u8 = buf[0:5];\n"
" let r = io.st_write(v, bs);\n"
" let r = io.write(v, bs);\n"
" if (r is size) {\n"
" let n = r as size;\n"
" if (n == 5: size) { return 80; };\n"
@@ -168,10 +170,10 @@ static const struct row rows[] = {
"import io;\n"
"export fn main() i32 = {\n"
" let vt: io.vtable;\n"
" let v: io.vstream = &vt;\n"
" let v: io.stream = &vt;\n"
" let buf: [3]u8;\n"
" let bs: []u8 = buf[0:3];\n"
" let _r = io.st_write(v, bs);\n"
" let _r = io.write(v, bs);\n"
" return 22;\n"
"};\n",
22,
@@ -181,8 +183,8 @@ static const struct row rows[] = {
"import io;\n"
"export fn main() i32 = {\n"
" let vt: io.vtable;\n"
" let v: io.vstream = &vt;\n"
" let r = io.st_close(v);\n"
" let v: io.stream = &vt;\n"
" let r = io.close(v);\n"
" if (r is void) { return 50; };\n"
" return 99;\n"
"};\n",
@@ -191,14 +193,14 @@ static const struct row rows[] = {
{ "closer_set",
"package main;\n"
"import io;\n"
"fn myclose(s: io.vstream) (void | io.error) = {\n"
"fn myclose(s: io.stream) (void | io.error) = {\n"
" return void;\n"
"};\n"
"export fn main() i32 = {\n"
" let vt: io.vtable;\n"
" vt.closer = (&myclose): *io.closer;\n"
" let v: io.vstream = &vt;\n"
" let r = io.st_close(v);\n"
" let v: io.stream = &vt;\n"
" let r = io.close(v);\n"
" if (r is void) { return 60; };\n"
" return 99;\n"
"};\n",
@@ -207,10 +209,10 @@ static const struct row rows[] = {
{ "branched_readers",
"package main;\n"
"import io;\n"
"fn r1(s: io.vstream, buf: []u8) (size | io.eof | io.error) = {\n"
"fn r1(s: io.stream, buf: []u8) (size | io.eof | io.error) = {\n"
" return 1: size;\n"
"};\n"
"fn r2(s: io.vstream, buf: []u8) (size | io.eof | io.error) = {\n"
"fn r2(s: io.stream, buf: []u8) (size | io.eof | io.error) = {\n"
" return 2: size;\n"
"};\n"
"export fn main() i32 = {\n"
@@ -220,8 +222,8 @@ static const struct row rows[] = {
" vt2.reader = (&r2): *io.reader;\n"
" let buf: [4]u8;\n"
" let bs: []u8 = buf[0:4];\n"
" let a = io.st_read(&vt1, bs);\n"
" let b = io.st_read(&vt2, bs);\n"
" let a = io.read(&vt1, bs);\n"
" let b = io.read(&vt2, bs);\n"
" let asz: size = 0;\n"
" let bsz: size = 0;\n"
" if (a is size) { asz = a as size; };\n"

View File

@@ -1,25 +1,24 @@
/*
* 776_memio_vstream_run — project #94 fold-eFinal(PREP) sentinel. Pins
* the additive lib/memio/vstream.ww parallel API: a single unified
* 776_memio_vstream_run — project #94 fold-eFinal sentinel. Pins
* the single-surface lib/memio: a unified
* `stream` struct with `vt: io.vtable` as the first field for the
* intrusive cast, three constructors (fixed_vstream / dynamic_vstream /
* dynamicfrom_vstream) that return the `stream` BY VALUE (sret —
* intrusive cast, three constructors (fixed / dynamic /
* dynamicfrom) that return the `stream` BY VALUE (sret —
* field-by-field build then `return r;`, ref/hare/memio/stream.ha:46,
* 58,64), the unified read_v / fixedwrite_v / dynamicwrite_v /
* dynamicclose_v callbacks that recover the outer stream via a
* 58,64), the unified read / fixedwrite / dynamicwrite /
* dynamicclose callbacks that recover the outer stream via a
* vstream→*stream pointer cast, and the collapsed single accessors
* (string_v / buffer_v / reset_v / borrowedread_v) over the common
* header. Coexists with the pre-vtable memio.fixed/dynamic/dynamicfrom
* + `state` surface in memio.ww (fold-eFinal FLIP retires the latter
* and drops the `_vstream`/`_v` suffix wholesale).
* (string / buffer / reset / borrowedread) over the common
* header. The fold-eFinal FLIP collapsed the dual memio surface into
* this one Hare-shaped surface (bare names, no suffix).
*
* Each row imports memio + io, calls one or more constructors, drives
* the io.st_read/st_write/st_close dispatchers, and asserts an exit
* the io.read/io.write/io.close dispatchers, and asserts an exit
* constant. Both stages run; cs.s == ww.s byte-id on every row.
*
* row | what it pins
* --------------------------+--------------------------------------
* fixed_read_5 | fixed_vstream + io.st_read of 5 bytes
* fixed_read_5 | fixed + io.read of 5 bytes
* | over an 8-byte source. Asserts the
* | return is `size` and equal to 5, and
* | that out[0]/out[4] mirror the buffer.
@@ -27,14 +26,14 @@
* | stream field-by-field → return r (sret);
* | caller takes &st.vt; dispatcher recovers
* | via s: *stream).
* dynamic_write_grow | dynamic_vstream + io.st_write of 3
* dynamic_write_grow | dynamic + io.write of 3
* | bytes. Asserts the write reports 3
* | and that the dynamicgrow_v path
* | and that the dynamicgrow path
* | allocated the backing buffer (initial
* | cap=0 forces growth on the first
* | write). Closes via io.st_close to
* | exercise dynamicclose_v's free.
* dynamicfrom_alt_rw | dynamicfrom_vstream seeded with a
* | write). Closes via io.close to
* | exercise dynamicclose's free.
* dynamicfrom_alt_rw | dynamicfrom seeded with a
* | 4-byte slice. Alternates st_read
* | (4 bytes back) → st_write (2 bytes,
* | grows past cap=4 → realloc). Asserts
@@ -42,7 +41,7 @@
* | Single ctx flavour, two dispatcher
* | paths through the same vtable.
* branched_fixed | branched callee per #105: two
* | fixed_vstream instances with
* | fixed instances with
* | different buffer sizes (3 and 5),
* | runtime-selected by the exit code
* | nudge. st_read on each returns the
@@ -62,25 +61,24 @@
* the order no longer matters and all 4 rows now run STAGE_CS|STAGE_WW
* byte-id. The earlier `import os;`-FIRST workaround is retired here.
*
* SIBLINGS (filed inline, NOT fixed here — fold-e2 is purely
* additive over fold-e1's frozen io.* surface):
* DEFERRALS / RELATED (NOT addressed by this row):
*
* - #173 (TRY-on-tagged-return both-stages broken) blocks
* fixed_write_v from returning Hare's `nomem` when full;
* vstream.ww surfaces 0 instead (memio.ww:155 OLD divergence).
* fixedwrite from returning Hare's `nomem` when full;
* memio.ww surfaces 0 instead (documented divergence).
* The probe doesn't pin Hare-equivalent nomem behaviour;
* fold-eFinal can graduate once #173 closes.
* it can graduate once #173 closes.
*
* - #195 (cgassign N_DOT TK_ASSIGN on aliased-ptr receiver):
* not bitten — the value-return constructor builds the stream in
* a local `let r: stream;` and field-assigns each slot (including
* r.vt.reader) through a plain pointer-to-local, not an aliased-ptr
* receiver. The flat ptr/len/cap fields (memio.ww:39) stay flat.
* receiver. The flat ptr/len/cap fields stay flat.
*
* GATE POLARITY: must stay GREEN. A red here means vstream.ww
* regressed, the alloc-struct-? path miscompiled, the intrusive
* vstream→*ctx cast miscomputed offsets, or io.vtable's first-field
* embed lost its offset-0 invariant.
* GATE POLARITY: must stay GREEN. A red here means lib/memio
* regressed, the sret value-return path miscompiled, the intrusive
* io.stream→*stream cast miscomputed offsets, or io.vtable's
* first-field embed lost its offset-0 invariant.
*/
#include <stdio.h>
#include <stdlib.h>
@@ -118,11 +116,11 @@ static const struct row rows[] = {
" let buf: [8]u8;\n"
" buf[0] = 65u8; buf[1] = 66u8; buf[2] = 67u8; buf[3] = 68u8;\n"
" buf[4] = 69u8; buf[5] = 70u8; buf[6] = 71u8; buf[7] = 72u8;\n"
" let st: memio.stream = memio.fixed_vstream(buf[0:8]);\n"
" let s: io.vstream = &st.vt;\n"
" let st: memio.stream = memio.fixed(buf[0:8]);\n"
" let s: io.stream = &st.vt;\n"
" let out: [5]u8;\n"
" let n: size = 0;\n"
" let rd = io.st_read(s, out[0:5]);\n"
" let rd = io.read(s, out[0:5]);\n"
" if (rd is size) { n = rd as size; };\n"
" if (n == 5: size && out[0] == 65u8 && out[4] == 69u8) { return 42; };\n"
" return 99;\n"
@@ -135,14 +133,14 @@ static const struct row rows[] = {
"import io;\n"
"import os;\n"
"export fn main() i32 = {\n"
" let st: memio.stream = memio.dynamic_vstream();\n"
" let s: io.vstream = &st.vt;\n"
" let st: memio.stream = memio.dynamic();\n"
" let s: io.stream = &st.vt;\n"
" let src: [3]u8;\n"
" src[0] = 88u8; src[1] = 89u8; src[2] = 90u8;\n"
" let wr = io.st_write(s, src[0:3]);\n"
" let wr = io.write(s, src[0:3]);\n"
" let n: size = 0;\n"
" if (wr is size) { n = wr as size; };\n"
" let cl = io.st_close(s);\n"
" let cl = io.close(s);\n"
" if (cl is void && n == 3: size) { return 43; };\n"
" return 99;\n"
"};\n",
@@ -156,15 +154,15 @@ static const struct row rows[] = {
"export fn main() i32 = {\n"
" let seed: [4]u8;\n"
" seed[0] = 1u8; seed[1] = 2u8; seed[2] = 3u8; seed[3] = 4u8;\n"
" let st: memio.stream = memio.dynamicfrom_vstream(seed[0:4]);\n"
" let s: io.vstream = &st.vt;\n"
" let st: memio.stream = memio.dynamicfrom(seed[0:4]);\n"
" let s: io.stream = &st.vt;\n"
" let out: [4]u8;\n"
" let rd = io.st_read(s, out[0:4]);\n"
" let rd = io.read(s, out[0:4]);\n"
" let rn: size = 0;\n"
" if (rd is size) { rn = rd as size; };\n"
" let extra: [2]u8;\n"
" extra[0] = 99u8; extra[1] = 100u8;\n"
" let wr = io.st_write(s, extra[0:2]);\n"
" let wr = io.write(s, extra[0:2]);\n"
" let wn: size = 0;\n"
" if (wr is size) { wn = wr as size; };\n"
" if (rn == 4: size && wn == 2: size && out[0] == 1u8 && out[3] == 4u8) { return 44; };\n"
@@ -182,14 +180,14 @@ static const struct row rows[] = {
" a[0] = 10u8; a[1] = 11u8; a[2] = 12u8;\n"
" let b: [5]u8;\n"
" b[0] = 20u8; b[1] = 21u8; b[2] = 22u8; b[3] = 23u8; b[4] = 24u8;\n"
" let sta: memio.stream = memio.fixed_vstream(a[0:3]);\n"
" let stb: memio.stream = memio.fixed_vstream(b[0:5]);\n"
" let sa: io.vstream = &sta.vt;\n"
" let sb: io.vstream = &stb.vt;\n"
" let sta: memio.stream = memio.fixed(a[0:3]);\n"
" let stb: memio.stream = memio.fixed(b[0:5]);\n"
" let sa: io.stream = &sta.vt;\n"
" let sb: io.stream = &stb.vt;\n"
" let outa: [3]u8;\n"
" let outb: [5]u8;\n"
" let rda = io.st_read(sa, outa[0:3]);\n"
" let rdb = io.st_read(sb, outb[0:5]);\n"
" let rda = io.read(sa, outa[0:3]);\n"
" let rdb = io.read(sb, outb[0:5]);\n"
" let na: size = 0;\n"
" let nb: size = 0;\n"
" if (rda is size) { na = rda as size; };\n"
@@ -200,10 +198,10 @@ static const struct row rows[] = {
45,
STAGE_CS | STAGE_WW, 1 },
/* accessors_string_buffer_reset — fold-eFinal PREP: the collapsed
* single accessors over the common `stream` header (string_v /
* buffer_v / reset_v, ref/hare/memio/stream.ha:81,74,87). Writes 3
* single accessors over the common `stream` header (string /
* buffer / reset, ref/hare/memio/stream.ha:81,74,87). Writes 3
* bytes through the value-returned fixed stream, then asserts
* string_v (str view), buffer_v ([]u8 view), and reset_v (rewind to
* string (str view), buffer ([]u8 view), and reset (rewind to
* 0) all read the flat header correctly. Pins the value-return
* field integrity (st.vt + st.ptr/len/cap/pos survive sret) AND the
* accessor collapse. cs.s == ww.s byte-id. */
@@ -214,23 +212,23 @@ static const struct row rows[] = {
"import os;\n"
"export fn main() i32 = {\n"
" let buf: [16]u8;\n"
" let st: memio.stream = memio.fixed_vstream(buf[0:16]);\n"
" let s: io.vstream = &st.vt;\n"
" let st: memio.stream = memio.fixed(buf[0:16]);\n"
" let s: io.stream = &st.vt;\n"
" let payload: [3]u8;\n"
" payload[0] = 65u8; payload[1] = 66u8; payload[2] = 67u8;\n"
" let wr = io.st_write(s, payload[0:3]);\n"
" let wr = io.write(s, payload[0:3]);\n"
" if (!(wr is size)) { return 90; };\n"
" let v: str = memio.string_v(&st);\n"
" let v: str = memio.string(&st);\n"
" if (v.len != 3 || v[0] != 65u8 || v[2] != 67u8) { return 91; };\n"
" let bv: []u8 = memio.buffer_v(&st);\n"
" let bv: []u8 = memio.buffer(&st);\n"
" if (bv.len != 3 || bv[1] != 66u8) { return 92; };\n"
" memio.reset_v(&st);\n"
" if (memio.string_v(&st).len != 0) { return 93; };\n"
" memio.reset(&st);\n"
" if (memio.string(&st).len != 0) { return 93; };\n"
" return 46;\n"
"};\n",
46,
STAGE_CS | STAGE_WW, 1 },
/* borrowedread_view_eof — the collapsed borrowedread_v
/* borrowedread_view_eof — the collapsed borrowedread
* (ref/hare/memio/stream.ha:94): returns an amt-byte borrowed view
* advancing the cursor, eof when fewer remain. Reads 3 of 4 seeded
* bytes (view len 3), then a 2-byte borrowedread over the remaining
@@ -243,13 +241,13 @@ static const struct row rows[] = {
"export fn main() i32 = {\n"
" let seed: [4]u8;\n"
" seed[0] = 1u8; seed[1] = 2u8; seed[2] = 3u8; seed[3] = 4u8;\n"
" let st: memio.stream = memio.dynamicfrom_vstream(seed[0:4]);\n"
" let br = memio.borrowedread_v(&st, 3);\n"
" let st: memio.stream = memio.dynamicfrom(seed[0:4]);\n"
" let br = memio.borrowedread(&st, 3);\n"
" match (br) {\n"
" case let b: []u8 => { if (b.len != 3 || b[0] != 1u8 || b[2] != 3u8) { return 91; }; };\n"
" case io.eof => { return 92; };\n"
" };\n"
" let br2 = memio.borrowedread_v(&st, 2);\n"
" let br2 = memio.borrowedread(&st, 2);\n"
" match (br2) {\n"
" case let b: []u8 => { return 93; };\n"
" case io.eof => { return 47; };\n"

View File

@@ -1,14 +1,13 @@
/*
* 777_fmt_vstream_run — project #94 fold-e3 sentinel. Pins the
* additive lib/fmt/vstream.ww Option C parallel API: stack-resident
* fd_ctx with `vt: io.vtable` as the first field for the intrusive
* vstream cast, four wrappers (fdprint_v / fdprintln_v / fdprintf_v /
* fdprintfln_v) that take a raw fd, allocate fd_ctx on their own
* frame (never escapes — ken's dispatcher-CONTAINED discipline), and
* dispatch through io.st_write via the wired fdsinkwrite_v callback.
* Coexists with the pre-vtable fdprint / fdprintln / fdprintf /
* fdprintfln surface in fmt.ww (fold-eFinal, task #50, retires the
* latter once io fold-2 ports `handle = (file | int)`).
* 777_fmt_vstream_run — project #94 fold-eFinal sentinel. Pins the
* single-surface lib/fmt fd shim: stack-resident fd_ctx with
* `vt: io.vtable` as the first field for the intrusive io.stream
* cast, four wrappers (fdprint / fdprintln / fdprintf / fdprintfln)
* that take a raw fd, allocate fd_ctx on their own frame (never
* escapes — ken's dispatcher-CONTAINED discipline), and dispatch
* through io.write via the wired fdsinkwrite callback. The fd shim
* has no Hare counterpart; it folds into fNNN-over-handle once io
* fold-2 (#5) ports `handle = (file | int)`.
*
* Each row imports fmt + os + io, opens a per-process /tmp output
* file, calls one V wrapper to write through the fd, closes, re-
@@ -34,25 +33,25 @@
*
* row | what it pins
* --------------------------+--------------------------------------
* fdprintf_v_int | fdprintf_v with `"v={}\n"`-style
* fdprintf_v_int | fdprintf with `"v={}\n"`-style
* | format + one i64 arg. Asserts the
* | full intrusive shape (stack fd_ctx →
* | &c.vt → io.st_write → fdsinkwrite_v
* | &c.vt → io.write → fdsinkwrite
* | → os.write) plus the {n}-placeholder
* | parser routed through vformatfield.
* fdprintln_v_multi | fdprintln_v with three positional
* fdprintln_v_multi | fdprintln with three positional
* | args (i64 + str + bool). Asserts the
* | space-separator + trailing newline
* | shape mirrors OLD fdprintln (fmt.ww:
* | 145), routed through io.st_write.
* fdprint_v_raw | fdprint_v with one str arg, no
* | 145), routed through io.write.
* fdprint_v_raw | fdprint with one str arg, no
* | format mods (zero-format raw). Pins
* | the per-arg loop without the
* | placeholder parser (vfprint code
* | path, fmt/vstream.ww).
* branched_fdprintf_v | branched callee per #105: two
* | placeholder parser (fprint code
* | path, fmt.ww).
* branched_fdprintf | branched callee per #105: two
* | distinct format strings runtime-
* | selected from a single fdprintf_v
* | selected from a single fdprintf
* | call site. Catches a constant-fold
* | mistake in the format-string
* | dispatch (mirror of 776's
@@ -63,24 +62,24 @@
* (wwstage checker ordering-sensitivity on os.tryread/trywrite/
* tryopen's bare `return r;` over a tagged return type).
*
* SIBLINGS (filed inline, NOT fixed here — fold-e3 is purely
* additive over fold-e1/e2's frozen io.* + memio.* surface):
* DEFERRALS / RELATED (NOT addressed by these rows):
*
* - io fold-2 (handle port): drew-deferred per fold-d/fold-e3
* precedent. Hare's ref/hare/fmt/wrappers.ha:9-25 routes through
* io::handle; fdNNN_v collapses into bare fNNN_v once handle =
* (file | int) lands. eFinal (#50) graduates.
* - io fold-2 (handle port): drew-deferred. Hare's
* ref/hare/fmt/wrappers.ha:9-25 routes through io::handle; fdNNN
* collapses into bare fNNN once handle = (file | int) lands.
* #5 graduates.
*
* - #206 (bare &fn → (*alias|void)): 2 cast sites per wrapper
* vtable wire-up; drop out wholesale on close.
* vtable wire-up; explicit casts KEPT (ken/#214 — cast-drop
* deferred, gated on #214).
*
* - #173 (TRY-on-tagged-return both-stages broken): fdsinkwrite_v
* - #173 (TRY-on-tagged-return both-stages broken): fdsinkwrite
* constructs nomem and widens to io.error explicitly rather
* than using `os.trywrite(...)?`; same memio.vstream.ww shape.
* than using `os.trywrite(...)?`; same shape memio.ww adopts.
*
* GATE POLARITY: must stay GREEN. A red here means vstream.ww
* regressed, the stack fd_ctx → vstream cast miscomputed offsets,
* the {n}-placeholder dispatch through vformatfield regressed, or
* GATE POLARITY: must stay GREEN. A red here means lib/fmt
* regressed, the stack fd_ctx → io.stream cast miscomputed offsets,
* the {n}-placeholder dispatch through formatfield regressed, or
* io.vtable's first-field embed lost its offset-0 invariant.
*/
#include <stdio.h>
@@ -119,7 +118,7 @@ static const struct row rows[] = {
" let path: str = \"/tmp/wwfmtv_777_a\";\n"
" let fd: i32 = os.open(path, os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC, 0o644: i32);\n"
" if (fd < 0) { return 90; };\n"
" let wr = fmt.fdprintf_v(fd, \"v={}\\n\", 42i64);\n"
" let wr = fmt.fdprintf(fd, \"v={}\\n\", 42i64);\n"
" let nw: size = 0;\n"
" match (wr) {\n"
" case let n: size => { nw = n; };\n"
@@ -147,7 +146,7 @@ static const struct row rows[] = {
" let path: str = \"/tmp/wwfmtv_777_b\";\n"
" let fd: i32 = os.open(path, os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC, 0o644: i32);\n"
" if (fd < 0) { return 90; };\n"
" let wr = fmt.fdprintln_v(fd, 7i64, \"hi\", true);\n"
" let wr = fmt.fdprintln(fd, 7i64, \"hi\", true);\n"
" let nw: size = 0;\n"
" match (wr) {\n"
" case let n: size => { nw = n; };\n"
@@ -175,7 +174,7 @@ static const struct row rows[] = {
" let path: str = \"/tmp/wwfmtv_777_c\";\n"
" let fd: i32 = os.open(path, os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC, 0o644: i32);\n"
" if (fd < 0) { return 90; };\n"
" let wr = fmt.fdprint_v(fd, \"raw\");\n"
" let wr = fmt.fdprint(fd, \"raw\");\n"
" let nw: size = 0;\n"
" match (wr) {\n"
" case let n: size => { nw = n; };\n"
@@ -194,7 +193,7 @@ static const struct row rows[] = {
"};\n",
44,
STAGE_CS, 0 },
{ "branched_fdprintf_v",
{ "branched_fdprintf",
"package main;\n"
"import os;\n"
"import fmt;\n"
@@ -208,7 +207,7 @@ static const struct row rows[] = {
" let f2: str = \"B{}\";\n"
" let fmtstr: str = f1;\n"
" if (sel == 0) { fmtstr = f2; };\n"
" let wr = fmt.fdprintf_v(fd, fmtstr, 9i64);\n"
" let wr = fmt.fdprintf(fd, fmtstr, 9i64);\n"
" let nw: size = 0;\n"
" match (wr) {\n"
" case let n: size => { nw = n; };\n"

View File

@@ -1,22 +1,18 @@
/*
* 778_bufio_vstream_run — project #94 fold-eFinal(PREP) sentinel. Pins
* the lib/bufio/vstream.ww parallel API — a COLLAPSE of the OLD bufio
* surface onto the io.vtable, NOT a port of Hare's missing scanner
* 778_bufio_vstream_run — project #94 fold-eFinal sentinel. Pins
* the single-surface lib/bufio over io.stream — the unified surface
* the eFinal FLIP collapsed to, NOT a port of Hare's missing scanner
* features (those are #217, OUT of eFinal). Covers:
* - buffered stream: `bufio_ctx` (vt@offset0 for the intrusive cast),
* `bufio_vstream` (returns the ctx BY VALUE; sret field-by-field)
* over an underlying **io.vstream** src — every underlying read/
* write/close goes through io.st_read/io.st_write/io.st_close (the
* - buffered stream: `stream` (vt@offset0 for the intrusive cast),
* `init` (returns the stream BY VALUE; sret field-by-field)
* over an underlying **io.stream** src — every underlying read/
* write/close goes through io.read/io.write/io.close (the
* vtable dispatchers), io.error forwards directly (no nomem-widen).
* Plus setflush_v / flush_v (public + internal drain) / unread_v /
* isbuffered_v (fn-ptr-equality discriminator).
* - scanner: `scanner_v` (value-return newscannerbuf_v over an
* io.vstream src) + scanbyte_v / scanbytes_v (single-byte delim) /
* scanline_v / finish_v.
* Coexists with the pre-vtable bufio.init / scanner / isbuffered surface
* in bufio.ww (fold-eFinal FLIP retires the latter + drops the `_v`
* suffix: bufio_ctx→stream, bufio_vstream→init, scanner_v→scanner,
* scanbytes_v→scanbytes, newscannerbuf_v→newscannerbuf, …).
* Plus setflush / flush (public + internal drain) / unread /
* isbuffered (fn-ptr-equality discriminator).
* - scanner: `scanner` (value-return newscannerbuf over an
* io.stream src) + scanbyte / scanbytes (single-byte delim) /
* scanline / finish.
*
* Each row imports os + bufio + memio + io (NOT fmt — bufio doesn't
* transitively pull fmt, so #209's formattable-match bail doesn't
@@ -25,89 +21,79 @@
*
* row | what it pins
* --------------------------+--------------------------------------
* stream_write_flush | bufio_vstream init + io.st_write
* | through bwrite_v + bufio.flush_v via
* | io.st_close. Asserts the data
* stream_write_flush | bufio.init + io.write
* | through bwrite + bufio.flush via
* | io.close. Asserts the data
* | reaches the underlying memio buffer
* | after close drains wbuf. Full chain:
* | bufio_ctx built field-by-field →
* | stream built field-by-field →
* | return r (sret) → caller takes &b.vt →
* | dispatcher recovers via *bufio_ctx
* | flush_v → io.write(b.src, ...).
* stream_read_unread | bufio_vstream read path: io.st_read
* | dispatcher recovers via *stream
* | flush → io.write(b.src, ...).
* stream_read_unread | init read path: io.read
* | refills rbuf from src, serves first
* | N bytes. Pins bread_v's read path
* | through the intrusive cast. unread_v
* | N bytes. Pins bread's read path
* | through the intrusive cast. unread
* | not exposed yet (no bufio.unread
* | equivalent on the V API surface
* | until eFinal); this row exercises
* | the base read path instead.
* isbuffered_v_discriminate | isbuffered_v on a fresh bufio_vstream
* | returns true; isbuffered_v on a
* | plain memio.fixed_vstream (no bufio
* isbuffered_v_discriminate | isbuffered on a fresh init
* | returns true; isbuffered on a
* | plain memio.fixed (no bufio
* | wrap) returns false. Pins the
* | fn-ptr-equality discriminator
* | (ref/hare/bufio/stream.ha:179) over
* | the new `_v` callback symbols.
* isbuffered_v_boundary | NEW isbuffered_v on bufio_vstream
* | returns true AND OLD bufio.isbuffered
* | on a bytewise-cast view of the same
* | vstream returns false. Boundary
* | check: the OLD discriminator reads
* | (s.read == bread) but the bytes at
* | &bufio_ctx.vt are vt.reader (tagged
* | union) — the cast reinterprets vt's
* | tag+ptr layout as the OLD stream's
* | ctx/read/write/close 4-fnptr layout,
* | and the OLD `bread` symbol address
* | is distinct from the NEW `bread_v`,
* | so the OLD discriminator returns
* | false. Proves the OLD + NEW worlds
* | use independent fn symbols.
* branched_bufio_vstream | branched callee per #105: two
* | the bread/bwrite callback symbols.
* isbuffered_v_boundary | isbuffered discriminates by vtable
* | identity: true on the bufio.init
* | wrapper stream, false on the
* | underlying (non-bufio) memio stream.
* | Pins the fn-ptr-equality discriminator
* | (ref/hare/bufio/stream.ha:179) over
* | the single-surface io.stream.
* branched_bufio_wrap | branched callee per #105: two
* | distinct underlying *io.stream's
* | runtime-selected; bufio_vstream
* | wraps the picked one. st_read
* | runtime-selected; bufio.init
* | wraps the picked one. io.read
* | returns the matching source's
* | content. Catches a constant-fold
* | mistake in the dispatch path
* | (mirror of 775's branched_readers
* | for the bufio-wrap side).
*
* SIBLINGS (filed inline, NOT fixed here — fold-e4 is purely
* additive over fold-e1/e2/e3's frozen io.* + memio.* + fmt.*
* surface):
* DEFERRALS / RELATED (NOT addressed by this row):
*
* - 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.
* handle = (file | int) lands, init's src parameter
* collapses accordingly. #5 graduates.
*
* - #206 (bare &fn → (*alias|void)): 5 cast sites in vstream.ww
* (3 vtable wire-up + 2 in isbuffered_v); drop out wholesale
* on close.
* - #206 (bare &fn → (*alias|void)): 5 cast sites in bufio.ww
* (3 vtable wire-up + 2 in isbuffered); explicit casts KEPT
* (ken/#214 — cast-drop deferred, gated on #214).
*
* - #173 (TRY-on-tagged-return both-stages broken): flush_v
* - #173 (TRY-on-tagged-return both-stages broken): flush
* constructs nomem and widens to io.error explicitly rather
* than using `io.write(...)?`; same memio.vstream.ww +
* fmt.vstream.ww shape.
* than using `io.write(...)?`; same shape memio.ww + fmt.ww
* adopt.
*
* - #207 (struct-lit multi-tagged-field copy drops past first) and
* #210 (slice-field struct-lit drop): not reachable from the
* value-return form — bufio_ctx is built field-by-field in a
* value-return form — stream is built field-by-field in a
* local then sret-returned, no `alloc(T{...})` struct-lit.
*
* BOOTSTRAP-EMBED CHECK: bufio is NOT embedded in any selfhost
* combined.ww (grep confirmed). Adding lib/bufio/vstream.ww does
* NOT require a Makefile regen (#110); only lib/bufio/ test paths
* see the new module. 990-997 byte-id gates stay green by virtue
* combined.ww (grep confirmed), so the fold-eFinal bufio collapse
* does NOT require a Makefile regen (#110); only lib/bufio/ test
* paths see the surface. 990-997 byte-id gates stay green by virtue
* of bufio being test-only.
*
* GATE POLARITY: must stay GREEN. A red here means vstream.ww
* regressed, the alloc-struct-? path miscompiled, the intrusive
* vstream→*bufio_ctx cast miscomputed offsets, the io.vtable
* GATE POLARITY: must stay GREEN. A red here means lib/bufio
* regressed, the sret value-return path miscompiled, the intrusive
* io.stream→*stream cast miscomputed offsets, the io.vtable
* first-field embed lost its offset-0 invariant, or the fn-ptr-
* equality discriminator in isbuffered_v stopped resolving.
* equality discriminator in isbuffered stopped resolving.
*/
#include <stdio.h>
#include <stdlib.h>
@@ -144,20 +130,20 @@ static const struct row rows[] = {
"import io;\n"
"export fn main() i32 = {\n"
" let raw: [16]u8;\n"
" let mst: memio.stream = memio.fixed_vstream(raw[0:16]);\n"
" let msrc: io.vstream = &mst.vt;\n"
" let mst: memio.stream = memio.fixed(raw[0:16]);\n"
" let msrc: io.stream = &mst.vt;\n"
" let rbuf: [8]u8;\n"
" let wbuf: [8]u8;\n"
" let b: bufio.bufio_ctx = bufio.bufio_vstream(msrc, rbuf[0:8], wbuf[0:8]);\n"
" let vs: io.vstream = &b.vt;\n"
" let b: bufio.stream = bufio.init(msrc, rbuf[0:8], wbuf[0:8]);\n"
" let vs: io.stream = &b.vt;\n"
" let payload: [5]u8;\n"
" payload[0] = 104u8; payload[1] = 105u8;\n"
" payload[2] = 33u8; payload[3] = 98u8; payload[4] = 121u8;\n"
" let wr = io.st_write(vs, payload[0:5]);\n"
" let wr = io.write(vs, payload[0:5]);\n"
" let nw: size = 0;\n"
" if (wr is size) { nw = wr as size; };\n"
" if (mst.pos != 0) { return 91; };\n"
" let cl = io.st_close(vs);\n"
" let cl = io.close(vs);\n"
" if (cl is io.error) { return 92; };\n"
" if (mst.pos != 5) { return 93; };\n"
" if (nw != 5: size) { return 94; };\n"
@@ -176,19 +162,19 @@ static const struct row rows[] = {
" let raw: [8]u8;\n"
" raw[0] = 65u8; raw[1] = 66u8; raw[2] = 67u8; raw[3] = 68u8;\n"
" raw[4] = 69u8; raw[5] = 70u8; raw[6] = 71u8; raw[7] = 72u8;\n"
" let mst: memio.stream = memio.fixed_vstream(raw[0:8]);\n"
" let msrc: io.vstream = &mst.vt;\n"
" let mst: memio.stream = memio.fixed(raw[0:8]);\n"
" let msrc: io.stream = &mst.vt;\n"
" let rbuf: [8]u8;\n"
" let wbuf: [4]u8;\n"
" let b: bufio.bufio_ctx = bufio.bufio_vstream(msrc, rbuf[0:8], wbuf[0:4]);\n"
" let vs: io.vstream = &b.vt;\n"
" let b: bufio.stream = bufio.init(msrc, rbuf[0:8], wbuf[0:4]);\n"
" let vs: io.stream = &b.vt;\n"
" let out: [4]u8;\n"
" let rd1 = io.st_read(vs, out[0:3]);\n"
" let rd1 = io.read(vs, out[0:3]);\n"
" let n1: size = 0;\n"
" if (rd1 is size) { n1 = rd1 as size; };\n"
" if (n1 != 3: size) { return 91; };\n"
" if (out[0] != 65u8 || out[2] != 67u8) { return 92; };\n"
" let rd2 = io.st_read(vs, out[0:4]);\n"
" let rd2 = io.read(vs, out[0:4]);\n"
" let n2: size = 0;\n"
" if (rd2 is size) { n2 = rd2 as size; };\n"
" if (n2 != 4: size) { return 93; };\n"
@@ -205,17 +191,17 @@ static const struct row rows[] = {
"import io;\n"
"export fn main() i32 = {\n"
" let raw: [8]u8;\n"
" let mst: memio.stream = memio.fixed_vstream(raw[0:8]);\n"
" let msrc: io.vstream = &mst.vt;\n"
" let mst: memio.stream = memio.fixed(raw[0:8]);\n"
" let msrc: io.stream = &mst.vt;\n"
" let rbuf: [4]u8;\n"
" let wbuf: [4]u8;\n"
" let b: bufio.bufio_ctx = bufio.bufio_vstream(msrc, rbuf[0:4], wbuf[0:4]);\n"
" let vsbuf: io.vstream = &b.vt;\n"
" let b: bufio.stream = bufio.init(msrc, rbuf[0:4], wbuf[0:4]);\n"
" let vsbuf: io.stream = &b.vt;\n"
" let buf2: [4]u8;\n"
" let st2: memio.stream = memio.fixed_vstream(buf2[0:4]);\n"
" let vsplain: io.vstream = &st2.vt;\n"
" if (!bufio.isbuffered_v(vsbuf)) { return 91; };\n"
" if (bufio.isbuffered_v(vsplain)) { return 92; };\n"
" let st2: memio.stream = memio.fixed(buf2[0:4]);\n"
" let vsplain: io.stream = &st2.vt;\n"
" if (!bufio.isbuffered(vsbuf)) { return 91; };\n"
" if (bufio.isbuffered(vsplain)) { return 92; };\n"
" return 48;\n"
"};\n",
48,
@@ -228,30 +214,19 @@ static const struct row rows[] = {
"import io;\n"
"export fn main() i32 = {\n"
" let raw: [8]u8;\n"
" let mst: memio.stream = memio.fixed_vstream(raw[0:8]);\n"
" let msrc: io.vstream = &mst.vt;\n"
" let mst: memio.stream = memio.fixed(raw[0:8]);\n"
" let msrc: io.stream = &mst.vt;\n"
" let rbuf: [4]u8;\n"
" let wbuf: [4]u8;\n"
" let bc: bufio.bufio_ctx = bufio.bufio_vstream(msrc, rbuf[0:4], wbuf[0:4]);\n"
" let vs: io.vstream = &bc.vt;\n"
" if (!bufio.isbuffered_v(vs)) { return 91; };\n"
" let raw2: u64 = vs: u64;\n"
" let asold: *io.stream = raw2: *io.stream;\n"
" if (bufio.isbuffered(asold)) { return 92; };\n"
" let b: bufio.stream;\n"
" let rb: [4]u8;\n"
" let wb: [4]u8;\n"
" let raw3: [8]u8;\n"
" let mem3: memio.state;\n"
" let m3: io.stream;\n"
" memio.fixed(&mem3, &m3, raw3[0:8]);\n"
" bufio.init(&b, &m3, rb[0:4], wb[0:4]);\n"
" if (!bufio.isbuffered(&b.vtable)) { return 93; };\n"
" let bc: bufio.stream = bufio.init(msrc, rbuf[0:4], wbuf[0:4]);\n"
" let vs: io.stream = &bc.vt;\n"
" if (!bufio.isbuffered(vs)) { return 91; };\n"
" if (bufio.isbuffered(msrc)) { return 92; };\n"
" return 49;\n"
"};\n",
49,
STAGE_CS | STAGE_WW, 1 },
{ "branched_bufio_vstream",
{ "branched_bufio_wrap",
"package main;\n"
"import os;\n"
"import bufio;\n"
@@ -262,17 +237,17 @@ static const struct row rows[] = {
" raA[0] = 10u8; raA[1] = 11u8; raA[2] = 12u8; raA[3] = 13u8;\n"
" let raB: [4]u8;\n"
" raB[0] = 20u8; raB[1] = 21u8; raB[2] = 22u8; raB[3] = 23u8;\n"
" let mstA: memio.stream = memio.fixed_vstream(raA[0:4]);\n"
" let mstB: memio.stream = memio.fixed_vstream(raB[0:4]);\n"
" let mstA: memio.stream = memio.fixed(raA[0:4]);\n"
" let mstB: memio.stream = memio.fixed(raB[0:4]);\n"
" let sel: i32 = 1;\n"
" let src: io.vstream = &mstA.vt;\n"
" let src: io.stream = &mstA.vt;\n"
" if (sel == 0) { src = &mstB.vt; };\n"
" let rbuf: [4]u8;\n"
" let wbuf: [4]u8;\n"
" let bc: bufio.bufio_ctx = bufio.bufio_vstream(src, rbuf[0:4], wbuf[0:4]);\n"
" let vs: io.vstream = &bc.vt;\n"
" let bc: bufio.stream = bufio.init(src, rbuf[0:4], wbuf[0:4]);\n"
" let vs: io.stream = &bc.vt;\n"
" let out: [4]u8;\n"
" let rd = io.st_read(vs, out[0:4]);\n"
" let rd = io.read(vs, out[0:4]);\n"
" let n: size = 0;\n"
" if (rd is size) { n = rd as size; };\n"
" if (n != 4: size) { return 91; };\n"
@@ -282,9 +257,9 @@ static const struct row rows[] = {
51,
STAGE_CS | STAGE_WW, 1 },
/* scanner_lines — fold-eFinal PREP: the value-return scanner over an
* io.vstream src. newscannerbuf_v (Hare newscanner_buf) → scanline_v
* io.stream src. newscannerbuf (Hare newscanner_buf) → scanline
* twice ("ab", "c") → trailing "def" with no newline is EOF_DISCARD
* → io.eof → finish_v. Pins the scanner reads through io.st_read
* → io.eof → finish. Pins the scanner reads through io.read
* (vtable dispatch) on the underlying memio stream. cs.s==ww.s. */
{ "scanner_lines",
"package main;\n"
@@ -297,36 +272,36 @@ static const struct row rows[] = {
" raw[0]=97u8; raw[1]=98u8; raw[2]=10u8;\n"
" raw[3]=99u8; raw[4]=10u8;\n"
" raw[5]=100u8; raw[6]=101u8; raw[7]=102u8;\n"
" let mst: memio.stream = memio.dynamicfrom_vstream(raw[0:8]);\n"
" let vs: io.vstream = &mst.vt;\n"
" let mst: memio.stream = memio.dynamicfrom(raw[0:8]);\n"
" let vs: io.stream = &mst.vt;\n"
" let win: [16]u8;\n"
" let sc: bufio.scanner_v = bufio.newscannerbuf_v(vs, win[0:16]);\n"
" let sc: bufio.scanner = bufio.newscannerbuf(vs, win[0:16]);\n"
" let ok: i32 = 0;\n"
" match (bufio.scanline_v(&sc)) {\n"
" match (bufio.scanline(&sc)) {\n"
" case let s: str => { if (s.len == 2 && s[0] == 97u8 && s[1] == 98u8) { ok += 1; }; };\n"
" case io.eof => { return 81; };\n"
" case let e: io.error => { return 82; };\n"
" case bufio.overflow => { return 83; };\n"
" };\n"
" match (bufio.scanline_v(&sc)) {\n"
" match (bufio.scanline(&sc)) {\n"
" case let s: str => { if (s.len == 1 && s[0] == 99u8) { ok += 1; }; };\n"
" case io.eof => { return 84; };\n"
" case let e: io.error => { return 85; };\n"
" case bufio.overflow => { return 86; };\n"
" };\n"
" match (bufio.scanline_v(&sc)) {\n"
" match (bufio.scanline(&sc)) {\n"
" case let s: str => { return 87; };\n"
" case io.eof => { ok += 1; };\n"
" case let e: io.error => { return 88; };\n"
" case bufio.overflow => { return 89; };\n"
" };\n"
" bufio.finish_v(&sc);\n"
" bufio.finish(&sc);\n"
" if (ok != 3) { return 90; };\n"
" return 52;\n"
"};\n",
52,
STAGE_CS | STAGE_WW, 1 },
/* scanner_byte_bytes — scanbyte_v pops 'A'; scanbytes_v(',') then
/* scanner_byte_bytes — scanbyte pops 'A'; scanbytes(',') then
* tokenizes "B" and "CD" from "AB,CD,". Single-byte delim (the OLD
* scantok behaviour; multibyte is #217). cs.s==ww.s. */
{ "scanner_byte_bytes",
@@ -339,24 +314,24 @@ static const struct row rows[] = {
" let raw: [6]u8;\n"
" raw[0]=65u8; raw[1]=66u8; raw[2]=44u8;\n"
" raw[3]=67u8; raw[4]=68u8; raw[5]=44u8;\n"
" let mst: memio.stream = memio.dynamicfrom_vstream(raw[0:6]);\n"
" let vs: io.vstream = &mst.vt;\n"
" let mst: memio.stream = memio.dynamicfrom(raw[0:6]);\n"
" let vs: io.stream = &mst.vt;\n"
" let win: [8]u8;\n"
" let sc: bufio.scanner_v = bufio.newscannerbuf_v(vs, win[0:8]);\n"
" let sc: bufio.scanner = bufio.newscannerbuf(vs, win[0:8]);\n"
" let fb: u8 = 0u8;\n"
" match (bufio.scanbyte_v(&sc)) {\n"
" match (bufio.scanbyte(&sc)) {\n"
" case let b: u8 => { fb = b; };\n"
" case io.eof => { return 81; };\n"
" case let e: io.error => { return 82; };\n"
" };\n"
" if (fb != 65u8) { return 83; };\n"
" match (bufio.scanbytes_v(&sc, 44u8)) {\n"
" match (bufio.scanbytes(&sc, 44u8)) {\n"
" case let bs: []u8 => { if (bs.len != 1 || bs[0] != 66u8) { return 84; }; };\n"
" case io.eof => { return 85; };\n"
" case let e: io.error => { return 86; };\n"
" case bufio.overflow => { return 87; };\n"
" };\n"
" match (bufio.scanbytes_v(&sc, 44u8)) {\n"
" match (bufio.scanbytes(&sc, 44u8)) {\n"
" case let bs: []u8 => { if (bs.len != 2 || bs[0] != 67u8 || bs[1] != 68u8) { return 88; }; };\n"
" case io.eof => { return 89; };\n"
" case let e: io.error => { return 90; };\n"
@@ -366,9 +341,9 @@ static const struct row rows[] = {
"};\n",
53,
STAGE_CS | STAGE_WW, 1 },
/* stream_setflush — setflush_v swaps the auto-flush byte-set to ';';
/* stream_setflush — setflush swaps the auto-flush byte-set to ';';
* writing "ab;" auto-flushes (contains ';') so the sink advances to
* 3 with no explicit flush. Pins setflush_v + the flush-scan path.
* 3 with no explicit flush. Pins setflush + the flush-scan path.
* cs.s==ww.s. */
{ "stream_setflush",
"package main;\n"
@@ -378,18 +353,18 @@ static const struct row rows[] = {
"import io;\n"
"export fn main() i32 = {\n"
" let sinkbuf: [16]u8;\n"
" let sink: memio.stream = memio.fixed_vstream(sinkbuf[0:16]);\n"
" let sinkvs: io.vstream = &sink.vt;\n"
" let sink: memio.stream = memio.fixed(sinkbuf[0:16]);\n"
" let sinkvs: io.stream = &sink.vt;\n"
" let rb: [4]u8;\n"
" let wb: [8]u8;\n"
" let b: bufio.bufio_ctx = bufio.bufio_vstream(sinkvs, rb[0:4], wb[0:8]);\n"
" let bvs: io.vstream = &b.vt;\n"
" let b: bufio.stream = bufio.init(sinkvs, rb[0:4], wb[0:8]);\n"
" let bvs: io.stream = &b.vt;\n"
" let semi: [1]u8;\n"
" semi[0] = 59u8;\n"
" bufio.setflush_v(&b, semi[0:1]);\n"
" bufio.setflush(&b, semi[0:1]);\n"
" let payload: [3]u8;\n"
" payload[0]=97u8; payload[1]=98u8; payload[2]=59u8;\n"
" let wr = io.st_write(bvs, payload[0:3]);\n"
" let wr = io.write(bvs, payload[0:3]);\n"
" if (!(wr is size)) { return 81; };\n"
" if (sink.pos != 3) { return 82; };\n"
" if (sinkbuf[0] != 97u8 || sinkbuf[2] != 59u8) { return 83; };\n"
@@ -397,9 +372,9 @@ static const struct row rows[] = {
"};\n",
54,
STAGE_CS | STAGE_WW, 1 },
/* stream_unread — read "XYZ" then unread_v("XY") pushes the two
/* stream_unread — read "XYZ" then unread("XY") pushes the two
* bytes back in front of the read buffer; the next read returns them
* first. Pins unread_v's in-place shift through *bufio_ctx.
* first. Pins unread's in-place shift through *stream.
* cs.s==ww.s. */
{ "stream_unread",
"package main;\n"
@@ -410,22 +385,22 @@ static const struct row rows[] = {
"export fn main() i32 = {\n"
" let raw: [4]u8;\n"
" raw[0]=88u8; raw[1]=89u8; raw[2]=90u8;\n"
" let mst: memio.stream = memio.dynamicfrom_vstream(raw[0:3]);\n"
" let vs: io.vstream = &mst.vt;\n"
" let mst: memio.stream = memio.dynamicfrom(raw[0:3]);\n"
" let vs: io.stream = &mst.vt;\n"
" let rbuf: [4]u8;\n"
" let wbuf: [4]u8;\n"
" let b: bufio.bufio_ctx = bufio.bufio_vstream(vs, rbuf[0:4], wbuf[0:4]);\n"
" let bvs: io.vstream = &b.vt;\n"
" let b: bufio.stream = bufio.init(vs, rbuf[0:4], wbuf[0:4]);\n"
" let bvs: io.stream = &b.vt;\n"
" let out: [4]u8;\n"
" let rd = io.st_read(bvs, out[0:3]);\n"
" let rd = io.read(bvs, out[0:3]);\n"
" let n: size = 0;\n"
" if (rd is size) { n = rd as size; };\n"
" if (n != 3: size || out[0] != 88u8 || out[2] != 90u8) { return 81; };\n"
" let push: [2]u8;\n"
" push[0]=88u8; push[1]=89u8;\n"
" bufio.unread_v(&b, push[0:2]);\n"
" bufio.unread(&b, push[0:2]);\n"
" let out2: [2]u8;\n"
" let rd2 = io.st_read(bvs, out2[0:2]);\n"
" let rd2 = io.read(bvs, out2[0:2]);\n"
" let n2: size = 0;\n"
" if (rd2 is size) { n2 = rd2 as size; };\n"
" if (n2 != 2: size || out2[0] != 88u8 || out2[1] != 89u8) { return 82; };\n"
@@ -564,7 +539,7 @@ main(void)
int got = run_row(cdrv, cwd, &rows[i], seq++);
if (got != rows[i].want_exit) {
fprintf(stderr,
"bufio_vstream_run[cs][%s]: exit=%d want=%d\n",
"init_run[cs][%s]: exit=%d want=%d\n",
rows[i].label, got, rows[i].want_exit);
fail++;
}
@@ -574,7 +549,7 @@ main(void)
int got = run_row(wdrv, cwd, &rows[i], seq++);
if (got != rows[i].want_exit) {
fprintf(stderr,
"bufio_vstream_run[ww][%s]: exit=%d want=%d\n",
"init_run[ww][%s]: exit=%d want=%d\n",
rows[i].label, got, rows[i].want_exit);
fail++;
}
@@ -582,7 +557,7 @@ main(void)
total++;
if (asm_byte_identical(cdrv, wdrv, cwd, &rows[i], seq++) != 0) {
fprintf(stderr,
"bufio_vstream_run[byte-id][%s]: cstage vs wwstage asm differs\n",
"init_run[byte-id][%s]: cstage vs wwstage asm differs\n",
rows[i].label);
fail++;
}
@@ -591,13 +566,13 @@ main(void)
}
if (!wwpresent)
fprintf(stderr, "bufio_vstream_run: skip wwstage (no %s)\n", wdrv);
fprintf(stderr, "init_run: skip wwstage (no %s)\n", wdrv);
if (fail) {
fprintf(stderr, "bufio_vstream_run: %d/%d fixtures failed\n",
fprintf(stderr, "init_run: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("bufio_vstream_run: %d/%d ok\n", total, total);
printf("init_run: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,16 +1,13 @@
/*
* 779_log_vstream_run — project #94 fold-e5 sentinel. Pins the
* additive lib/log/vstream.ww Option C parallel API: module-static
* stderrsink_ctx_g with `vt: io.vtable` as the first field for the
* intrusive vstream cast, the 10 _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), a vlogger vtable +
* vstdlogger over io.vstream sink, and lazy ensureinit_v wiring the
* stderr default + silent_v / default_v / global_v *vlogger globals.
* Coexists with the pre-vtable log.logger / log.stdlogger / log.new /
* log.println / etc. surface in log.ww (fold-eFinal, task #50, retires
* the latter and drops the `_v` suffix wholesale to match Hare's bare
* names).
* 779_log_vstream_run — project #94 fold-eFinal sentinel. Pins the
* single-surface lib/log: module-static stderrsink_ctx_g with
* `vt: io.vtable` as the first field for the intrusive cast, the 10
* exported variants of log.ww's surface (new / lprintln / println /
* lprintfln / printfln / lfatal / fatal / lfatalf / fatalf /
* setlogger), a logger vtable + stdlogger over an io.stream sink, and
* lazy ensureinit wiring the stderr default + silent / default /
* global *logger globals. The fold-eFinal FLIP collapsed the dual
* surface into this one Hare-shaped surface (bare names, no suffix).
*
* Each row imports os + log + memio + io. log itself imports fmt,
* so the formattable / field types flow through transitively.
@@ -18,8 +15,8 @@
* Cstage-only per row (no STAGE_WW, no byte_id) — pre-existing
* wwstage bug #209 (sibling of #190): the wwstage checker bails on
* match-arm-over-formattable when fmt is imported transitively, which
* lib/log does for the vlogger vtable signatures + the fmt.vfprint /
* vfprintf dispatch in stdprintln_v / stdprintfln_v. The bug bites the
* lib/log does for the logger vtable signatures + the fmt.fprint /
* fprintf dispatch in stdprintln / stdprintfln. The bug bites the
* OLD log surface identically — `log.println("hi")` from a probe
* trips the same trace. Existing 970 logtest runs the @test fixture
* under cstage `ww run` only, so the OLD surface is fine via cstage.
@@ -30,70 +27,63 @@
*
* row | what it pins
* --------------------------+--------------------------------------
* println_v_default_stderr | log.println_v through the [[default_v]]
* println_v_default_stderr | log.println through the [[default]]
* | global, routed to fd 2 via the
* | module-static stderrsink_ctx_g. Asserts
* | ensureinit_v wires + dispatches without
* | ensureinit wires + dispatches without
* | crashing; stderr capture is left to the
* | shell runner. Pins the full chain:
* | println_v → lprintln_v(global_v) →
* | (*vlogger).println_v → stdprintln_v
* | fmt.vfprint + io.st_write("\n").
* | println → lprintln(global) →
* | (*logger).println → stdprintln →
* | fmt.fprint + io.write("\n").
* printfln_v_default_stderr | Same chain as row 1 but through the
* | format-string slot (printfln_v
* | lprintfln_v(global_v) → stdprintfln_v
* | → fmt.vfprintf + io.st_write("\n")).
* | format-string slot (printfln →
* | lprintfln(global) → stdprintfln
* | → fmt.fprintf + io.write("\n")).
* | Pins the {n}-placeholder dispatch
* | through vformatfield reaches the
* | default sink.
* lprintln_v_custom_sink | memio.fixed_vstream-backed vstdlogger;
* | log.new_v wires the vtable + sink,
* | log.lprintln_v dispatches "hi" + 7i64
* | through stdprintln_v. Asserts the
* lprintln_v_custom_sink | memio.fixed-backed stdlogger;
* | log.new wires the vtable + sink,
* | log.lprintln dispatches "hi" + 7i64
* | through stdprintln. Asserts the
* | exact bytes ("hi 7\n") land in the
* | caller's buffer (fixed_vstream borrows
* | caller's buffer (fixed borrows
* | the slice, so the bytes are visible
* | back through buf[]).
* branched_lprintln_v | branched callee per #105: two distinct
* | vstdlogger sinks runtime-selected via
* | *vlogger pointer. lprintln_v dispatches
* branched_lprintln | branched callee per #105: two distinct
* | stdlogger sinks runtime-selected via
* | *logger pointer. lprintln dispatches
* | through the picked sink only; the
* | other stays empty. Catches a constant-
* | fold mistake in the fn-ptr dispatch
* | (mirror of 778's branched_bufio_vstream).
* | (mirror of 778's branched_bufio_wrap).
*
* SIBLINGS (filed inline, NOT fixed here — fold-e5 is purely
* additive over fold-e1/e2/e3/e4's frozen io.* + memio.* + fmt.* +
* bufio.* surface plus the minimal `export` bump on fmt.vfprint /
* fmt.vfprintf):
* DEFERRALS / RELATED (NOT addressed by this row):
*
* - eFinal (#50): atomic flip + delete OLD log surface + drop the
* `_v` suffix wholesale.
* - #206 (bare &fn → (*alias|void)): 2 cast sites at ensureinit
* in lib/log/log.ww; the explicit casts are KEPT (ken/#214 —
* cast-drop is a separate deferred payoff gated on #214).
*
* - #206 (bare &fn → (*alias|void)): 2 cast sites at ensureinit_v
* in lib/log/vstream.ww; drop out wholesale on close.
*
* - #173 (TRY-on-tagged-return both-stages broken): stderrwrite_v
* - #173 (TRY-on-tagged-return both-stages broken): stderrwrite
* 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.
* using `os.trywrite(...)?`; same shape memio.ww + fmt.ww +
* bufio.ww adopt.
*
* - #209 (wwstage formattable match-arm bail): every row is
* STAGE_CS-only; byte-id deferred until #209 lands.
*
* BOOTSTRAP-EMBED CHECK: log is NOT embedded in any selfhost
* combined.ww (grep `package log\|import log` returned empty pre-
* impl). Adding lib/log/vstream.ww + the two `export` bumps on
* lib/fmt/vstream.ww does NOT require a Makefile regen (#110); only
* lib/log/ + lib/fmt/ test paths see the new module surface. 990-997
* byte-id gates stay green by virtue of log being test-only and the
* fmt vstream.ww changes being non-embedded.
* combined.ww (grep `package log\|import log` returns empty), so the
* fold-eFinal log collapse does not require a Makefile regen (#110);
* only lib/log/ + lib/fmt/ test paths see the surface. 990-997
* byte-id gates stay green by virtue of log being test-only.
*
* GATE POLARITY: must stay GREEN. A red here means vstream.ww
* GATE POLARITY: must stay GREEN. A red here means lib/log
* regressed, the module-static stderrsink_ctx_g zero-init or chained
* vt-field assigns regressed, the intrusive vstream→*stderrsink_ctx
* cast miscomputed offsets, ensureinit_v's one-shot guard broke, or
* the fn-ptr dispatch through vlogger.println_v / printfln_v stopped
* vt-field assigns regressed, the intrusive io.stream→*stderrsink_ctx
* cast miscomputed offsets, ensureinit's one-shot guard broke, or
* the fn-ptr dispatch through logger.println / printfln stopped
* resolving.
*/
#include <stdio.h>
@@ -128,7 +118,7 @@ static const struct row rows[] = {
"import os;\n"
"import log;\n"
"export fn main() i32 = {\n"
" log.println_v(\"hello\");\n"
" log.println(\"hello\");\n"
" return 42;\n"
"};\n",
42,
@@ -138,7 +128,7 @@ static const struct row rows[] = {
"import os;\n"
"import log;\n"
"export fn main() i32 = {\n"
" log.printfln_v(\"v={}\", 7i64);\n"
" log.printfln(\"v={}\", 7i64);\n"
" return 43;\n"
"};\n",
43,
@@ -151,10 +141,10 @@ static const struct row rows[] = {
"import io;\n"
"export fn main() i32 = {\n"
" let buf: [16]u8;\n"
" let st: memio.stream = memio.fixed_vstream(buf[0:16]);\n"
" let vs: io.vstream = &st.vt;\n"
" let sl: log.vstdlogger = log.new_v(vs);\n"
" log.lprintln_v(&sl.logger, \"hi\", 7i64);\n"
" let st: memio.stream = memio.fixed(buf[0:16]);\n"
" let vs: io.stream = &st.vt;\n"
" let sl: log.stdlogger = log.new(vs);\n"
" log.lprintln(&sl.logger, \"hi\", 7i64);\n"
" if (buf[0] != 104u8) { return 91; };\n"
" if (buf[1] != 105u8) { return 92; };\n"
" if (buf[2] != 32u8) { return 93; };\n"
@@ -164,7 +154,7 @@ static const struct row rows[] = {
"};\n",
44,
STAGE_CS, 0 },
{ "branched_lprintln_v",
{ "branched_lprintln",
"package main;\n"
"import os;\n"
"import log;\n"
@@ -173,16 +163,16 @@ static const struct row rows[] = {
"export fn main() i32 = {\n"
" let bufA: [16]u8;\n"
" let bufB: [16]u8;\n"
" let stA: memio.stream = memio.fixed_vstream(bufA[0:16]);\n"
" let vsA: io.vstream = &stA.vt;\n"
" let stB: memio.stream = memio.fixed_vstream(bufB[0:16]);\n"
" let vsB: io.vstream = &stB.vt;\n"
" let slA: log.vstdlogger = log.new_v(vsA);\n"
" let slB: log.vstdlogger = log.new_v(vsB);\n"
" let stA: memio.stream = memio.fixed(bufA[0:16]);\n"
" let vsA: io.stream = &stA.vt;\n"
" let stB: memio.stream = memio.fixed(bufB[0:16]);\n"
" let vsB: io.stream = &stB.vt;\n"
" let slA: log.stdlogger = log.new(vsA);\n"
" let slB: log.stdlogger = log.new(vsB);\n"
" let sel: i32 = 1;\n"
" let chosen: *log.vlogger = &slA.logger;\n"
" let chosen: *log.logger = &slA.logger;\n"
" if (sel == 0) { chosen = &slB.logger; };\n"
" log.lprintln_v(chosen, \"pick\");\n"
" log.lprintln(chosen, \"pick\");\n"
" if (bufA[0] != 112u8) { return 91; };\n"
" if (bufA[1] != 105u8) { return 92; };\n"
" if (bufA[2] != 99u8) { return 93; };\n"

View File

@@ -1,18 +1,14 @@
/*
* 780_fmt_vstream_mods_run — project #94 fold-e6 sentinel. Pins the
* V-side modifier-formatting machinery (vrawleni64 / vrawlenstr /
* vrawlenf64 / vrawlen / vformatraw / vformatone) in lib/fmt/
* vstream.ww. Before fold-e6, the V printf path parsed mods via
* scanmods but dropped them after parse: vformatfield routed straight
* to vwriteone (no width / alignment / pad / sign / base / prec
* honoured). fold-e6 widens vformatfield to take `*mods` and routes
* through vformatone — same dispatch shape as OLD formatfield at
* fmt.ww:648. Coexists with the OLD modifier path at fmt.ww:443-641
* (fmt.ww UNCHANGED this fold); fold-eFinal (task #50) collapses both
* surfaces.
* 780_fmt_vstream_mods_run — project #94 fold-eFinal sentinel. Pins the
* modifier-formatting machinery (rawleni64 / rawlenstr / rawlenf64 /
* rawlen / formatraw / formatone) in lib/fmt/fmt.ww: formatfield takes
* `*mods` and routes through formatone so the {:mods} placeholder
* honours width / alignment / pad / sign / base / prec. The fold-eFinal
* FLIP collapsed the dual fmt surface into this one (the former `v*`
* modifier helpers merged into the bare names).
*
* Each row imports fmt + os + io, opens a per-process /tmp output
* file, calls fdprintf_v with a {:mods} format string + one arg, and
* file, calls fdprintf with a {:mods} format string + one arg, and
* asserts the exact byte content read back.
*
* Cstage-only per row — pre-existing wwstage bug #209 (sibling of
@@ -48,28 +44,22 @@
* (wwstage checker ordering-sensitivity on os.tryread/trywrite/
* tryopen's bare `return r;` over a tagged return type).
*
* SIBLINGS (filed inline, NOT fixed here — fold-e6 is purely additive
* over fold-e1/e2/e3/e4/e5's frozen io.* + memio.* + fmt.* + bufio.*
* + log.* surface):
* DEFERRALS / RELATED (NOT addressed by these rows):
*
* - #209 (wwstage formattable match-arm bail): blocks STAGE_WW
* here, same as 777_fmt_vstream_run. Cstage-only until close.
* - eFinal (#50): collapses V + OLD surfaces; the `v*` names rename
* off and the OLD rawlen* / formatraw / formatone delete. The v*
* duplication of the rawlen-family today is transient — eFinal
* dedupes.
*
* BOOTSTRAP-EMBED CHECK: fmt is NOT embedded in any selfhost
* combined.ww (grep confirmed pre-impl). Adding modifier formatting
* to lib/fmt/vstream.ww does NOT require a Makefile regen (#110);
* only test paths see the new machinery. 990-997 byte-id gates stay
* green by virtue of fmt being test-only on the bootstrap surface.
* combined.ww (grep confirmed), so the fold-eFinal fmt collapse does
* NOT require a Makefile regen (#110); only test paths see the
* machinery. 990-997 byte-id gates stay green by virtue of fmt being
* test-only on the bootstrap surface.
*
* GATE POLARITY: must stay GREEN. A red here means vformatone /
* vformatraw / vrawlen miscomputed bytes, vformatfield regressed its
* *mods route, scanmods/modsinit (shared from fmt.ww) drifted, or
* the strconv.u64tos base path miscompiled under fdprintf_v's
* intrusive vstream→*fd_ctx dispatch.
* GATE POLARITY: must stay GREEN. A red here means formatone /
* formatraw / rawlen miscomputed bytes, formatfield regressed its
* *mods route, scanmods/modsinit drifted, or the strconv.u64tos base
* path miscompiled under fdprintf's intrusive io.stream→*fd_ctx
* dispatch.
*/
#include <stdio.h>
#include <stdlib.h>
@@ -107,7 +97,7 @@ static const struct row rows[] = {
" let path: str = \"/tmp/wwfmtv_780_a\";\n"
" let fd: i32 = os.open(path, os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC, 0o644: i32);\n"
" if (fd < 0) { return 90; };\n"
" let wr = fmt.fdprintf_v(fd, \"{:5}\", 42i64);\n"
" let wr = fmt.fdprintf(fd, \"{:5}\", 42i64);\n"
" let nw: size = 0;\n"
" match (wr) {\n"
" case let n: size => { nw = n; };\n"
@@ -135,7 +125,7 @@ static const struct row rows[] = {
" let path: str = \"/tmp/wwfmtv_780_b\";\n"
" let fd: i32 = os.open(path, os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC, 0o644: i32);\n"
" if (fd < 0) { return 90; };\n"
" let wr = fmt.fdprintf_v(fd, \"{:.3}\", \"hello\");\n"
" let wr = fmt.fdprintf(fd, \"{:.3}\", \"hello\");\n"
" let nw: size = 0;\n"
" match (wr) {\n"
" case let n: size => { nw = n; };\n"
@@ -163,7 +153,7 @@ static const struct row rows[] = {
" let path: str = \"/tmp/wwfmtv_780_c\";\n"
" let fd: i32 = os.open(path, os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC, 0o644: i32);\n"
" if (fd < 0) { return 90; };\n"
" let wr = fmt.fdprintf_v(fd, \"{:x}\", 255i64);\n"
" let wr = fmt.fdprintf(fd, \"{:x}\", 255i64);\n"
" let nw: size = 0;\n"
" match (wr) {\n"
" case let n: size => { nw = n; };\n"
@@ -191,7 +181,7 @@ static const struct row rows[] = {
" let path: str = \"/tmp/wwfmtv_780_d\";\n"
" let fd: i32 = os.open(path, os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC, 0o644: i32);\n"
" if (fd < 0) { return 90; };\n"
" let wr = fmt.fdprintf_v(fd, \"{:+}\", 7i64);\n"
" let wr = fmt.fdprintf(fd, \"{:+}\", 7i64);\n"
" let nw: size = 0;\n"
" match (wr) {\n"
" case let n: size => { nw = n; };\n"
@@ -219,7 +209,7 @@ static const struct row rows[] = {
" let path: str = \"/tmp/wwfmtv_780_e\";\n"
" let fd: i32 = os.open(path, os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC, 0o644: i32);\n"
" if (fd < 0) { return 90; };\n"
" let wr = fmt.fdprintf_v(fd, \"{:_05}\", 42i64);\n"
" let wr = fmt.fdprintf(fd, \"{:_05}\", 42i64);\n"
" let nw: size = 0;\n"
" match (wr) {\n"
" case let n: size => { nw = n; };\n"

View File

@@ -1,17 +1,15 @@
/*
* 781_fmt_vstream_compositions_run — project #94 fold-e7 sentinel.
* Pins the V-side composition wrappers in lib/fmt/vstream.ww —
* vfprintln / vfprintfln / vbsprintf / vasprintf — plus the two
* memio enablers fold-e7 bundles alongside (memio.fixed_string /
* memio.dynamic_string in lib/memio/vstream.ww). Coexists with the
* OLD fmt.fprintln / fprintfln / bsprintf / asprintf surface in
* fmt.ww:240,740,839,873 (fmt.ww UNCHANGED this fold; eFinal
* task #50 collapses both surfaces and renames the `v` suffix off).
* 781_fmt_vstream_compositions_run — project #94 fold-eFinal sentinel.
* Pins the composition wrappers in lib/fmt/fmt.ww —
* fprintln / fprintfln / bsprintf / asprintf — plus the memio
* accessor (memio.string in lib/memio/memio.ww) they read back
* through. The fold-eFinal FLIP collapsed the dual fmt + memio
* surfaces into one (the former `v`-suffixed wrappers and the
* fixed_string / dynamic_string accessors merged into the bare names).
*
* The two memio helpers are direct enablers (vbsprintf reads its
* written prefix via fixed_string; vasprintf reads its grown buffer
* via dynamic_string), drew-approved per
* feedback_refactor_routing_same_class_drops (prereqs, not churn).
* memio.string is the direct enabler: bsprintf reads its written
* prefix via memio.string; asprintf reads its grown buffer via the
* same accessor.
*
* Each row imports fmt + os + io, drives one of the four V wrappers,
* and asserts the exact bytes / size emitted. Per-row unique exit
@@ -19,40 +17,39 @@
*
* row | what it pins
* --------------------------+--------------------------------------
* fdprintln_v_run_basic | fdprintln_v wired vstream sink
* | (inline vfprint + vputbytes "\n").
* fdprintln_v_run_basic | fdprintln wired vstream sink
* | (inline fprint + vputbytes "\n").
* | One i64 arg + str arg + bool arg;
* | asserts space-separator plus trailing
* | newline byte content.
* fdprintfln_v_run_fmt | fdprintfln_v wired vstream sink with
* fdprintfln_v_run_fmt | fdprintfln wired vstream sink with
* | a {n}-placeholder format string.
* | Asserts placeholder substitution +
* | trailing newline shape (mirror of
* | fmt.fprintfln, fmt.ww:740).
* vfprintln_basic | fmt.vfprintln dispatched DIRECTLY
* | through memio.fixed_vstream + read
* | back via memio.fixed_string. Pins
* | the new V-side composition symbol
* | itself (fdprintln_v open-codes the
* | same vfprint+vputbytes chain rather
* | than calling vfprintln, so without
* | this row the new symbol ships
* | uncovered).
* vfprintfln_basic | fmt.vfprintfln direct dispatch via
* | memio.fixed_vstream + fixed_string,
* fprintln_basic | fmt.fprintln dispatched DIRECTLY
* | through memio.fixed + read
* | back via memio.string. Pins
* | the composition symbol itself
* | (fdprintln open-codes the same
* | fprint+putbytes chain rather than
* | calling fprintln, so without this
* | row the symbol ships uncovered).
* fprintfln_basic | fmt.fprintfln direct dispatch via
* | memio.fixed + memio.string,
* | same direct-symbol rationale as
* | vfprintln_basic.
* bsprintf_v_basic | vbsprintf into a caller-supplied
* | fprintln_basic.
* bsprintf_v_basic | bsprintf into a caller-supplied
* | [16]u8 buffer through
* | memio.fixed_vstream;
* | memio.fixed_string returns the prefix
* | memio.fixed;
* | memio.string returns the prefix
* | view. Asserts both the bytes in the
* | caller's buffer AND the returned str
* | view (ptr == buf + 0, len == 5).
* asprintf_v_basic | vasprintf into a heap-grown str
* | through memio.dynamic_vstream;
* | memio.dynamic_string + shrink-copy +
* | io.st_close. Asserts the owned str
* asprintf_v_basic | asprintf into a heap-grown str
* | through memio.dynamic;
* | memio.string + shrink-copy +
* | io.close. Asserts the owned str
* | bytes, then frees via os.free.
*
* Cstage-only per row (no STAGE_WW, no byte_id) — pre-existing
@@ -68,32 +65,26 @@
* document (wwstage checker ordering-sensitivity on os.tryread /
* trywrite / tryopen's bare `return r;` over a tagged return type).
*
* SIBLINGS (filed inline, NOT fixed here — fold-e7 is purely
* additive over fold-e1/e2/e3/e4/e5/e6's frozen io.* + memio.* +
* fmt.* + bufio.* + log.* surface):
* DEFERRALS / RELATED (NOT addressed by these rows):
*
* - #209 (wwstage formattable match-arm bail): blocks STAGE_WW
* here, same as 777/780. Cstage-only until close.
* - #173 (TRY-on-tagged-return both-stages broken): vbsprintf
* - #173 (TRY-on-tagged-return both-stages broken): bsprintf
* widens nomem into io.error inline rather than `memio.
* fixed_vstream(buf)?` for the same reason memio.vstream.ww
* does at line 87-99.
* - eFinal (#50): collapses V + OLD surfaces; the `v` names
* rename off and the OLD fprintln / fprintfln / bsprintf /
* asprintf delete.
* fixed(buf)?` for the same reason memio.ww does.
*
* BOOTSTRAP-EMBED CHECK: fmt is NOT embedded in any selfhost
* combined.ww (grep verified pre-impl). memio.vstream.ww IS embedded
* in w6c + wwdump combined.ww — the fold-e7 memio helpers ride along
* via the regen committed in this same commit (per #110 SSoT). 990/
* 994/995 byte-id gates + combined_ww_fresh stay green by virtue of
* the regen.
* combined.ww (grep verified). memio IS embedded in w6c + wwdump
* combined.ww — the fold-eFinal memio collapse rides along via the
* regen committed in this same commit (per #110 SSoT). 990/994/995
* byte-id gates + combined_ww_fresh stay green by virtue of the
* regen.
*
* GATE POLARITY: must stay GREEN. A red here means vfprintln /
* vfprintfln dropped the newline byte, vbsprintf returned a stale
* memio.fixed_string view (intrusive cast broke), vasprintf failed
* to shrink-copy / close cleanly, or the underlying vfprint /
* vfprintf primitives regressed.
* GATE POLARITY: must stay GREEN. A red here means fprintln /
* fprintfln dropped the newline byte, bsprintf returned a stale
* memio.string view (intrusive cast broke), asprintf failed
* to shrink-copy / close cleanly, or the underlying fprint /
* fprintf primitives regressed.
*/
#include <stdio.h>
#include <stdlib.h>
@@ -131,7 +122,7 @@ static const struct row rows[] = {
" let path: str = \"/tmp/wwfmtv_781_a\";\n"
" let fd: i32 = os.open(path, os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC, 0o644: i32);\n"
" if (fd < 0) { return 90; };\n"
" let wr = fmt.fdprintln_v(fd, 1i64, \"hi\", true);\n"
" let wr = fmt.fdprintln(fd, 1i64, \"hi\", true);\n"
" let nw: size = 0;\n"
" match (wr) {\n"
" case let n: size => { nw = n; };\n"
@@ -163,7 +154,7 @@ static const struct row rows[] = {
" let path: str = \"/tmp/wwfmtv_781_b\";\n"
" let fd: i32 = os.open(path, os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC, 0o644: i32);\n"
" if (fd < 0) { return 90; };\n"
" let wr = fmt.fdprintfln_v(fd, \"x={}\", 7i64);\n"
" let wr = fmt.fdprintfln(fd, \"x={}\", 7i64);\n"
" let nw: size = 0;\n"
" match (wr) {\n"
" case let n: size => { nw = n; };\n"
@@ -182,7 +173,7 @@ static const struct row rows[] = {
"};\n",
61,
STAGE_CS, 0 },
{ "vfprintln_basic",
{ "fprintln_basic",
"package main;\n"
"import os;\n"
"import fmt;\n"
@@ -190,16 +181,16 @@ static const struct row rows[] = {
"import memio;\n"
"export fn main() i32 = {\n"
" let buf: [16]u8;\n"
" let st: memio.stream = memio.fixed_vstream(buf[0:16]);\n"
" let vs: io.vstream = &st.vt;\n"
" let wr = fmt.vfprintln(vs, 1i64, \"hi\", true);\n"
" let st: memio.stream = memio.fixed(buf[0:16]);\n"
" let vs: io.stream = &st.vt;\n"
" let wr = fmt.fprintln(vs, 1i64, \"hi\", true);\n"
" let nw: size = 0;\n"
" match (wr) {\n"
" case let n: size => { nw = n; };\n"
" case io.error => { return 91; };\n"
" };\n"
" if (nw != 10: size) { return 92; };\n"
" let view: str = memio.string_v(&st);\n"
" let view: str = memio.string(&st);\n"
" if (view.len != 10) { return 93; };\n"
" if (view[0] != 49u8) { return 94; };\n"
" if (view[1] != 32u8) { return 95; };\n"
@@ -209,7 +200,7 @@ static const struct row rows[] = {
"};\n",
64,
STAGE_CS, 0 },
{ "vfprintfln_basic",
{ "fprintfln_basic",
"package main;\n"
"import os;\n"
"import fmt;\n"
@@ -217,16 +208,16 @@ static const struct row rows[] = {
"import memio;\n"
"export fn main() i32 = {\n"
" let buf: [16]u8;\n"
" let st: memio.stream = memio.fixed_vstream(buf[0:16]);\n"
" let vs: io.vstream = &st.vt;\n"
" let wr = fmt.vfprintfln(vs, \"x={}\", 7i64);\n"
" let st: memio.stream = memio.fixed(buf[0:16]);\n"
" let vs: io.stream = &st.vt;\n"
" let wr = fmt.fprintfln(vs, \"x={}\", 7i64);\n"
" let nw: size = 0;\n"
" match (wr) {\n"
" case let n: size => { nw = n; };\n"
" case io.error => { return 91; };\n"
" };\n"
" if (nw != 4: size) { return 92; };\n"
" let view: str = memio.string_v(&st);\n"
" let view: str = memio.string(&st);\n"
" if (view.len != 4) { return 93; };\n"
" if (view[0] != 120u8 || view[1] != 61u8) { return 94; };\n"
" if (view[2] != 55u8 || view[3] != 10u8) { return 95; };\n"
@@ -241,7 +232,7 @@ static const struct row rows[] = {
"import io;\n"
"export fn main() i32 = {\n"
" let buf: [16]u8;\n"
" let r = fmt.vbsprintf(buf[0:16], \"v={}\", 42i64);\n"
" let r = fmt.bsprintf(buf[0:16], \"v={}\", 42i64);\n"
" let view: str = \"\";\n"
" match (r) {\n"
" case let s: str => { view = s; };\n"
@@ -261,7 +252,7 @@ static const struct row rows[] = {
"import fmt;\n"
"import io;\n"
"export fn main() i32 = {\n"
" let view: str = fmt.vasprintf(\"hello={}\", 9i64);\n"
" let view: str = fmt.asprintf(\"hello={}\", 9i64);\n"
" if (view.len != 7) { return 90; };\n"
" if (view[0] != 104u8 || view[1] != 101u8 || view[2] != 108u8) { return 91; };\n"
" if (view[3] != 108u8 || view[4] != 111u8) { return 92; };\n"

View File

@@ -34,7 +34,7 @@ static const char *modules[] = {
* lib/os/os.ww, and lib/strings/strings.ww moved off this list:
* each has cross-module type refs that only resolve once the
* driver concatenates `use`d modules. bufio / fmt graduated to
* io.stream-based sinks (*io.stream, io.closed, io.eof); lib/os
* io.stream-based sinks (io.stream = *io.vtable, io.eof, io.error); lib/os
* carries time.instant in filestat post-Commit B;
* lib/strings.iterator + lib/strings.next reference utf8.decoder
* / utf8.done; lib/bytes.tokenize references os.assert +