diff --git a/Makefile b/Makefile index 7e7256df..a0243fe3 100644 --- a/Makefile +++ b/Makefile @@ -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 $@ $< diff --git a/lib/bufio/bufio.ww b/lib/bufio/bufio.ww index 57740061..06979c4d 100644 --- a/lib/bufio/bufio.ww +++ b/lib/bufio/bufio.ww @@ -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.` 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); }; diff --git a/lib/bufio/bufiotest.ww b/lib/bufio/bufiotest.ww index 0076e760..90356b30 100644 --- a/lib/bufio/bufiotest.ww +++ b/lib/bufio/bufiotest.ww @@ -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(); diff --git a/lib/bufio/vstream.ww b/lib/bufio/vstream.ww deleted file mode 100644 index ba5f2616..00000000 --- a/lib/bufio/vstream.ww +++ /dev/null @@ -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.` 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; }; - }; -}; diff --git a/lib/fmt/fmt.ww b/lib/fmt/fmt.ww index 764158f1..cd5c3994 100644 --- a/lib/fmt/fmt.ww +++ b/lib/fmt/fmt.ww @@ -1,28 +1,35 @@ -// fmt — formatting writers. Mirrors Hare's lib/fmt subset. +// fmt — formatting writers. Mirrors Hare's lib/fmt subset. Project #94 +// fold-eFinal. // -// Hare's `fmt::fprint` takes `io::handle = (io::file | int)`, which -// ww doesn't yet have. So we ship two sinks side-by-side, with the -// distinction baked into the name: +// Two sinks behind one surface, the distinction baked into the name: // -// fprint / fprintln write to a [[io.stream]] — Hare's -// primary surface. Errors via `io.closed`. -// fdprint / fdprintln write to a raw fd via [[os.write]] — ww- -// specific. Errors via the raw `-errno` i64 -// convention. Used by the process-stdio -// wrappers below (print/println/errorln/ -// fatal) until lib/io grows an fd-backed -// stream; at that point both halves -// graduate "in one go" (lib/CLAUDE.md) and -// the fd-suffixed names disappear. +// fprint / fprintln / fprintf / fprintfln +// write to an [[io.stream]] (= `*io.vtable`) — +// Hare's primary surface. Errors via [[io.error]]. +// fdprint / fdprintln / fdprintf / fdprintfln +// write to a raw fd via [[os.write]], wrapped in a +// stack-resident [[fd_ctx]] vtable. NO Hare +// counterpart — a pre-handle shim. Hare routes +// fmt's fd sinks through `io::handle = (io::file +// | int)` (ref/hare/fmt/wrappers.ha:9-25); ww has +// no handle sum yet, so these stand in until io +// fold-2 (#5) lands the handle port, at which +// point each fdNNN folds into fNNN-over-handle and +// the fd-prefixed names disappear. // -// The printf-family ({n}-placeholder parser) is provided alongside — -// see the "{n}-placeholder parser" section below. Same sink split: -// `fprintf` / `fprintfln` on streams; `fdprintf` / `fdprintfln` on -// raw fds; the process-stdio wrappers route through fd 1 / fd 2. +// The process-stdio wrappers (print/println/errorln/printf/printfln/ +// errorfln/fatal/fatalf) route through the fd family on fd 1 / fd 2. // // Call sites take Hare's variadic shape: `fmt.println(42, "hi", true)` -// gathers the args into a `[]formattable` slice; wrappers forward -// via `args...`. +// gathers the args into a `[]formattable` slice; wrappers forward via +// `args...`. +// +// 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.` cast at each fd_ctx vtable store is the +// Hare-faithful minimum-touch route; the #206 cast-drop is gated on +// #214. The fd sinks construct nomem and widen to io.error explicitly +// rather than using `os.trywrite(...)?` for the no-handle interim. package fmt; @@ -80,209 +87,74 @@ fn i64dec(v: i64) str = { // arm when the first in-tree caller needs it. export type formattable = (i64 | str | bool | rune | f64); -// ---- fd sinks -------------------------------------------------------- +// fd_ctx — vt at offset 0 for the intrusive io.stream→*fd_ctx cast. +// Single tagged field (vt) means the multi-tagged-field struct-lit +// drop in #207 doesn't bite; every fd wrapper stack-allocates fd_ctx +// inside its own frame and dispatches without escaping `&c.vt`. +export type fd_ctx = struct { + vt: io.vtable, + fd: i32, +}; -// fdprint — write the formatted form of each `args` element to `fd`, -// separated by spaces. Returns total bytes written or the first -// negative [[os.write]] result (Linux's `-errno`). Hare's separator- -// by-space matches. -// -// Renamed from `fprint` once lib/fmt grew an io.stream sink (`fprint` -// now points at that). This entry stays under `fd`-prefix until lib/io -// can express the full Hare `io::handle = (file | int)` union, at -// which point both halves graduate in one go. -export fn fdprint(fd: i32, args: formattable...) i64 = { - let total: i64 = 0; - let i: i32 = 0; - for (i < args.len) { - if (i > 0) { - let r: i64 = os.write(fd, " ".ptr, 1u64); - if (r < 0) { return r; }; - total += r; - }; - match (args[i]) { - case let n: i64 => { - let s: str = i64dec(n); - let r: i64 = os.write(fd, s.ptr, s.len: u64); - if (r < 0) { return r; }; - total += r; - }; - case let s: str => { - let r: i64 = os.write(fd, s.ptr, s.len: u64); - if (r < 0) { return r; }; - total += r; - }; - case let b: bool => { - let s: str = "false"; - if (b) { s = "true"; }; - let r: i64 = os.write(fd, s.ptr, s.len: u64); - if (r < 0) { return r; }; - total += r; - }; - case let r: rune => { - let buf: [4]u8; - buf[0] = r: u8; - let n: i64 = os.write(fd, &buf[0], 1u64); - if (n < 0) { return n; }; - total += n; - }; - case let v: f64 => { - // strconv.f64tos's static-buffer view is consumed - // immediately by os.write; no intervening strconv - // call against the same buffer between bind and write. - let s: str = strconv.f64tos(v); - let r: i64 = os.write(fd, s.ptr, s.len: u64); - if (r < 0) { return r; }; - total += r; - }; - }; - i += 1; +// fdsinkread — eof-only sink; the fd half of fmt is write-only. The +// unused-`buf` parameter matches io.reader's signature. +fn fdsinkread(s: io.stream, buf: []u8) (size | io.eof | io.error) = { + let e: io.eof; + return e; +}; + +// fdsinkwrite — recover fd via intrusive cast, dispatch one os.write. +// -errno collapses to a nomem-widened io.error. Construction-then-widen +// (vs `os.trywrite(...)?`) for the no-handle interim. +fn fdsinkwrite(s: io.stream, 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 total; + return r: size; }; -// fdprintln — fdprint plus a trailing newline. -export fn fdprintln(fd: i32, args: formattable...) i64 = { - let n: i64 = fdprint(fd, args...); - if (n < 0) { return n; }; - let m: i64 = os.write(fd, "\n".ptr, 1u64); - if (m < 0) { return m; }; - return n + m; -}; +// ---- internal stream formatters -------------------------------------- -// ---- stream sinks ---------------------------------------------------- - -// putbytes — internal helper that wraps (`*u8`, `i32`) into a `[]u8` -// slice and feeds it to [[io.write]]. Not exported: callers compose -// the same `(ptr, len)` triple as their underlying source (str view, -// strconv buffer, stack rune buffer), and the slice is invariant -// in shape across the formattable arms. -fn putbytes(s: *io.stream, p: *u8, n: i32) (i32 | io.closed) = { +// putbytes — io.write(s, [ptr..ptr+n)). Internal helper; the inline +// `let v` slice synthesis composes the (ptr, len) triple each +// formattable arm carries. +fn putbytes(s: io.stream, p: *u8, n: i32) (size | io.error) = { let v: []u8; v.ptr = p; v.len = n; return io.write(s, v); }; -// fprint — write the formatted form of each `args` element to `s`, -// separated by spaces. Returns total bytes written, or `io.closed` -// if the sink rejects mid-write. Mirrors Hare's `fmt::fprint` shape -// for an `io::handle` sink, modulo ww's i32-sized byte counters and -// the narrower `io.closed`-only error set on lib/io's stream vtable. -// -// A short write (sink accepts fewer bytes than asked) is reported by -// the returned count, not as an error — matches [[io.write]]'s contract -// per [[memio.fixedwrite]]. Callers that need write-all semantics layer -// it on top, the same way they do over raw [[io.write]]. -export fn fprint(s: *io.stream, args: formattable...) (i32 | io.closed) = { - let total: i32 = 0; - let i: i32 = 0; - for (i < args.len) { - if (i > 0) { - let r: (i32 | io.closed) = putbytes(s, " ".ptr, 1); - match (r) { - case let n: i32 => { total += n; }; - case io.closed => { let c: io.closed; return c; }; - }; - }; - match (args[i]) { - case let n: i64 => { - let view: str = i64dec(n); - let r: (i32 | io.closed) = putbytes(s, view.ptr, view.len); - match (r) { - case let m: i32 => { total += m; }; - case io.closed => { let c: io.closed; return c; }; - }; - }; - case let v: str => { - let r: (i32 | io.closed) = putbytes(s, v.ptr, v.len); - match (r) { - case let m: i32 => { total += m; }; - case io.closed => { let c: io.closed; return c; }; - }; - }; - case let b: bool => { - let v: str = "false"; - if (b) { v = "true"; }; - let r: (i32 | io.closed) = putbytes(s, v.ptr, v.len); - match (r) { - case let m: i32 => { total += m; }; - case io.closed => { let c: io.closed; return c; }; - }; - }; - case let r: rune => { - let buf: [4]u8; - buf[0] = r: u8; - let rs: (i32 | io.closed) = io.write(s, buf[0:1]); - match (rs) { - case let m: i32 => { total += m; }; - case io.closed => { let c: io.closed; return c; }; - }; - }; - case let v: f64 => { - // strconv.f64tos returns a static-buffer view — - // emit to the sink before yielding control to the - // next arg iteration so no sibling f64/i64 strconv - // call clobbers the buffer mid-flight. - let view: str = strconv.f64tos(v); - let rs: (i32 | io.closed) = putbytes(s, view.ptr, view.len); - match (rs) { - case let m: i32 => { total += m; }; - case io.closed => { let c: io.closed; return c; }; - }; - }; - }; - i += 1; +// writeone — emit one formattable through io.write. +fn writeone(s: io.stream, a: formattable) (size | io.error) = { + match (a) { + case let n: i64 => { + let v: str = i64dec(n); + return putbytes(s, v.ptr, v.len); }; - return total; -}; - -// fprintln — fprint plus a trailing newline. Mirrors Hare's -// `fmt::fprintln(io::handle, args...)`. -export fn fprintln(s: *io.stream, args: formattable...) (i32 | io.closed) = { - let total: i32 = 0; - let r1: (i32 | io.closed) = fprint(s, args...); - match (r1) { - case let n: i32 => { total = n; }; - case io.closed => { let c: io.closed; return c; }; + case let v: str => return putbytes(s, v.ptr, v.len); + case let b: bool => { + let v: str = "false"; + if (b) { v = "true"; }; + return putbytes(s, v.ptr, v.len); }; - let r2: (i32 | io.closed) = putbytes(s, "\n".ptr, 1); - match (r2) { - case let m: i32 => { total += m; }; - case io.closed => { let c: io.closed; return c; }; + case let r: rune => { + let buf: [4]u8; + buf[0] = r: u8; + return putbytes(s, &buf[0], 1); }; - return total; -}; - -// ---- process-stdio wrappers ----------------------------------------- - -// print / println — fdprint / fdprintln on stdout. Direct counterparts -// of Hare's fmt::print / fmt::println. -export fn print(args: formattable...) i64 = { - return fdprint(1, args...); -}; - -export fn println(args: formattable...) i64 = { - return fdprintln(1, args...); -}; - -// errorln — fdprintln on stderr. Hare's `fmt::error` (without -ln) is -// skipped here: the bare `error` name collides with strconv's -// `type error = !(invalid | overflow)` under the driver's flat -// concatenation namespace. Callers wanting the no-newline form use -// `fdprint(2, args...)` directly. -export fn errorln(args: formattable...) i64 = { - return fdprintln(2, args...); -}; - -// fatal — errorln then exit(255). `never` return marks the bottom -// type so flow-control checks treat callers as terminated. The -// fdprintln result is dropped as an expression statement (Hare's -// `_ = fprintln(...)` wouldn't add safety here — process exit -// follows immediately). -export fn fatal(args: formattable...) never = { - fdprintln(2, args...); - os.exit(255); + case let v: f64 => { + // strconv.f64tos static-buffer view consumed before next + // strconv call. + let v2: str = strconv.f64tos(v); + return putbytes(s, v2.ptr, v2.len); + }; + }; + return 0: size; }; // ---- {n}-placeholder parser ----------------------------------------- @@ -307,9 +179,8 @@ export fn fatal(args: formattable...) never = { // . precision // // Not v1: '%' parametric form ({0%1}); float modifiers (e/f/g/F…); -// void / null arms (not in formattable). `asprintf` is deferred until -// lib/os exports alloc (task #14). Invalid format aborts via os.exit -// — Hare uses `abort()`; same effect. +// void / null arms (not in formattable). Invalid format aborts via +// os.exit — Hare uses `abort()`; same effect. // neg, alignment — mirror iter.ha:19 / iter.ha:26. export type neg = enum i32 { NONE = 0, SPACE = 1, PLUS = 2 }; @@ -439,7 +310,7 @@ fn signof(neg_flag: bool, m: *mods) u8 = { }; // rawleni64 — bytes the raw render of `v` under `m` would emit. Used -// for width-alignment without rendering twice. +// for width-alignment without rendering twice. Mirror print.ha. fn rawleni64(v: i64, m: *mods) i32 = { let neg_flag: bool = v < 0; let u: u64 = v: u64; @@ -459,113 +330,11 @@ fn rawlenstr(s: str, m: *mods) i32 = { return s.len; }; -// formatraw — write the bare value (no width padding) to `s`. -// Mirrors print.ha:76 format_raw, narrowed to the formattable arms -// fmt.formattable carries today. -fn formatraw(out: *io.stream, arg: formattable, m: *mods) (i32 | io.closed) = { - 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: i32 = 0; - if (sb != 0u8) { - let buf: [1]u8; - buf[0] = sb; - let r: (i32 | io.closed) = putbytes(out, &buf[0], 1); - match (r) { - case let n: i32 => { total += n; }; - case io.closed => { let c: io.closed; return c; }; - }; - }; - 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: (i32 | io.closed) = putbytes(out, &buf[0], 1); - match (r) { - case let n: i32 => { total += n; }; - case io.closed => { let c: io.closed; return c; }; - }; - pi += 1; - }; - let view: str = strconv.u64tos(u, m.base); - let r: (i32 | io.closed) = putbytes(out, view.ptr, view.len); - match (r) { - case let n: i32 => { total += n; }; - case io.closed => { let c: io.closed; return c; }; - }; - return total; - }; - case let v: str => { - let n: i32 = rawlenstr(v, m); - return putbytes(out, v.ptr, n); - }; - case let b: bool => { - let v: str = "false"; - if (b) { v = "true"; }; - return putbytes(out, v.ptr, v.len); - }; - case let r: rune => { - let buf: [4]u8; - buf[0] = r: u8; - return putbytes(out, &buf[0], 1); - }; - case let v: f64 => { - // strconv.f64tos already prepends '-' for negative values; - // peel it back off so [[signof]] can fold neg/plus/space - // mods uniformly with the i64 arm. The view bytes are then - // emitted directly — strconv's static buf is not held past - // the putbytes call (no sibling strconv call lands between). - // - // Mods scope: width / alignment / pad / sign honored via - // the same raw-then-pad machinery as i64. `prec` ignored — - // strconv.f64tos is shortest-G with no precision knob; grows a - // precision path when mods gains ffmt / fflags (the parametric - // ftosf, strconv task #64). `base` ignored (Hare does too — - // base is int-only). NaN/±Inf now render as "nan"/"infinity" - // (Ryū graduation, fold-5); the uniform sign-peel below would - // turn a SHOW_POS nan into "+nan" (a minor fmt-layer edge, not - // strconv's — strconv emits a bare "nan"). - 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: i32 = 0; - if (sb != 0u8) { - let buf: [1]u8; - buf[0] = sb; - let r: (i32 | io.closed) = putbytes(out, &buf[0], 1); - match (r) { - case let n: i32 => { total += n; }; - case io.closed => { let c: io.closed; return c; }; - }; - }; - let r: (i32 | io.closed) = putbytes(out, view.ptr, view.len); - match (r) { - case let n: i32 => { total += n; }; - case io.closed => { let c: io.closed; return c; }; - }; - return total; - }; - }; - let z: io.closed; return z; // unreachable — match is exhaustive -}; - -// rawlenf64 — bytes the raw render of `v` under `m` would emit. -// Calls strconv.f64tos to count digits + decimal point; peels off -// the natural '-' so signof's neg/plus/space byte is counted exactly -// once, the same accounting [[rawleni64]] does. +// rawlenf64 — 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. drew NaN/Inf +// safe (strconv emits no leading '-' on nan/inf, so the peel is a no-op +// on those views). Mirror print.ha. fn rawlenf64(v: f64, m: *mods) i32 = { let view: str = strconv.f64tos(v); let body: i32 = view.len; @@ -579,9 +348,7 @@ fn rawlenf64(v: f64, m: *mods) i32 = { return signlen + body; }; -// rawlen — bytes formatraw would emit for `arg` under `m`. Used by -// formatone's width-alignment path (Hare's print.ha:53 calls -// format_raw with io::empty for the same purpose). +// rawlen — dispatch the rawlen sum over formattable. Mirror print.ha:53. fn rawlen(arg: formattable, m: *mods) i32 = { match (arg) { case let v: i64 => return rawleni64(v, m); @@ -593,9 +360,106 @@ fn rawlen(arg: formattable, m: *mods) i32 = { return 0; // unreachable — match is exhaustive }; +// formatraw — write the bare value (no width padding) to `s`. Mirror +// print.ha:76 format_raw. `prec` ignored on f64 (strconv.f64tos is +// shortest-G with no precision knob); base ignored on f64 (Hare too — +// base is int-only). drew NaN/Inf signoff: nan/infinity strings emitted +// unchanged. +fn formatraw(s: io.stream, 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) = putbytes(s, &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) = putbytes(s, &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) = putbytes(s, 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 = rawlenstr(v, m); + return putbytes(s, v.ptr, n); + }; + case let b: bool => { + let v: str = "false"; + if (b) { v = "true"; }; + return putbytes(s, v.ptr, v.len); + }; + case let r: rune => { + let buf: [4]u8; + buf[0] = r: u8; + return putbytes(s, &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. + 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) = putbytes(s, &buf[0], 1); + match (r) { + case let n: size => { total += n; }; + case let e: io.error => return e; + }; + }; + let r: (size | io.error) = putbytes(s, 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 +}; + // formatone — render `arg` to `s` with `m`'s width / alignment / pad -// applied. Mirrors print.ha:45 format. -fn formatone(out: *io.stream, arg: formattable, m: *mods) (i32 | io.closed) = { +// applied. Mirror print.ha:45 format. The tail-pad loop drives on a +// counter because putbytes 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 when the sink runs out. +fn formatone(s: io.stream, arg: formattable, m: *mods) (size | io.error) = { let start: i32 = 0; if (m.width > 0 && m.alignment != alignment.LEFT) { let raw: i32 = rawlen(arg, m); @@ -604,90 +468,117 @@ fn formatone(out: *io.stream, arg: formattable, m: *mods) (i32 | io.closed) = { if (m.alignment == alignment.CENTER) { start = (pad + 1) / 2; } else { start = pad; }; }; - let total: i32 = 0; + let total: size = 0; let i: i32 = 0; let padb: [1]u8; padb[0] = m.pad: u8; for (i < start) { - let r: (i32 | io.closed) = putbytes(out, &padb[0], 1); + let r: (size | io.error) = putbytes(s, &padb[0], 1); match (r) { - case let n: i32 => { total += n; }; - case io.closed => { let c: io.closed; return c; }; + case let n: size => { total += n; }; + case let e: io.error => return e; }; i += 1; }; - let r1: (i32 | io.closed) = formatraw(out, arg, m); + let r1: (size | io.error) = formatraw(s, arg, m); match (r1) { - case let n: i32 => { total += n; }; - case io.closed => { let c: io.closed; return c; }; + case let n: size => { total += n; }; + case let e: io.error => return e; }; - // Tail-pad: drive with a counter (mirrors the start-pad shape - // above). ww's memio.fixed reports a full buffer as a 0-byte - // partial write, not io.closed (unlike Hare's errors::overflow - // + `?` shape at print.ha:69); looping on `total < m.width` - // would spin on bsprintf when the sink runs out of room. let need: i32 = 0; - if (m.width > total) { need = m.width - total; }; + let twidth: size = m.width: size; + if (twidth > total) { need = (twidth - total): i32; }; let j: i32 = 0; for (j < need) { - let r: (i32 | io.closed) = putbytes(out, &padb[0], 1); + let r: (size | io.error) = putbytes(s, &padb[0], 1); match (r) { - case let n: i32 => { total += n; }; - case io.closed => { let c: io.closed; return c; }; + case let n: size => { total += n; }; + case let e: io.error => return e; }; j += 1; }; return total; }; -// formatfield — dispatch on the `field` slot directly. A -// `field→formattable` widen helper called from fprintf's for-loop -// would trip task #18 (silent miscompile of 24B return-by-value in -// for-loop context); inline-per-arm sidesteps it and is independently -// the cleaner shape. -fn formatfield(out: *io.stream, f: field, m: *mods) (i32 | io.closed) = { +// formatfield — field→formattable inline-per-arm dispatch through +// formatone. A field→formattable widen helper called from fprintf's +// for-loop would trip #18 (silent miscompile of 24B return-by-value in +// for-loop context); inline-per-arm sidesteps it. `*mods` arm aborts +// (parametric '%' form not implemented). Mirror print.ha:648. +fn formatfield(s: io.stream, f: field, m: *mods) (size | io.error) = { match (f) { - case let v: i64 => { + case let v: i64 => { let a: formattable = v; - return formatone(out, a, m); + return formatone(s, a, m); }; - case let v: str => { + case let v: str => { let a: formattable = v; - return formatone(out, a, m); + return formatone(s, a, m); }; case let b: bool => { let a: formattable = b; - return formatone(out, a, m); + return formatone(s, a, m); }; case let r: rune => { let a: formattable = r; - return formatone(out, a, m); + return formatone(s, a, m); }; case let v: f64 => { let a: formattable = v; - return formatone(out, a, m); + return formatone(s, a, m); }; - case let p: *mods => { fmtabort(); let c: io.closed; return c; }; + case let p: *mods => { fmtabort(); let z: size = 0; return z; }; }; }; +// ---- stream sinks ---------------------------------------------------- + +// fprint — write the formatted form of each `args` element to `s`, +// separated by spaces. Returns total bytes written or the first +// io.error. Mirrors ref/hare/fmt/print.ha (fprint) + wrappers.ha. +// +// A short write (sink accepts fewer bytes than asked) is reported by +// the returned count, not as an error — matches [[io.write]]'s contract +// per [[memio.fixed]]. Callers that need write-all semantics layer it +// on top, the same way they do over raw [[io.write]]. +export fn fprint(s: io.stream, 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) = putbytes(s, " ".ptr, 1); + match (r) { + case let n: size => { total += n; }; + case let e: io.error => return e; + }; + }; + let r: (size | io.error) = writeone(s, args[i]); + match (r) { + case let n: size => { total += n; }; + case let e: io.error => return e; + }; + i += 1; + }; + return total; +}; + // fprintf — Hare's primary printf-family surface. Mirrors print.ha:26. -// Returns total bytes written or io.closed on the first sink failure. -export fn fprintf(s: *io.stream, fmt: str, args: field...) (i32 | io.closed) = { - let total: i32 = 0; +// Returns total bytes written or io.error on the first sink failure. +export fn fprintf(s: io.stream, 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) { // '{' + if (c == 123u8) { // '{' i += 1; if (i >= fmt.len) { fmtabort(); }; - if (fmt[i] == 123u8) { // '{{' literal - let r: (i32 | io.closed) = putbytes(s, fmt.ptr + i: u64, 1); + if (fmt[i] == 123u8) { // '{{' literal + let r: (size | io.error) = putbytes(s, fmt.ptr + i: u64, 1); match (r) { - case let n: i32 => { total += n; }; - case io.closed => { let cl: io.closed; return cl; }; + case let n: size => { total += n; }; + case let e: io.error => return e; }; i += 1; } else { @@ -709,26 +600,26 @@ export fn fprintf(s: *io.stream, fmt: str, args: field...) (i32 | io.closed) = { if (i >= fmt.len || fmt[i] != 125u8) { fmtabort(); }; i += 1; if (idx >= args.len) { fmtabort(); }; - let r: (i32 | io.closed) = formatfield(s, args[idx], &m); + let r: (size | io.error) = formatfield(s, args[idx], &m); match (r) { - case let n: i32 => { total += n; }; - case io.closed => { let cl: io.closed; return cl; }; + case let n: size => { total += n; }; + case let e: io.error => return e; }; }; - } else if (c == 125u8) { // '}' + } else if (c == 125u8) { // '}' i += 1; if (i >= fmt.len || fmt[i] != 125u8) { fmtabort(); }; - let r: (i32 | io.closed) = putbytes(s, fmt.ptr + i: u64, 1); + let r: (size | io.error) = putbytes(s, fmt.ptr + i: u64, 1); match (r) { - case let n: i32 => { total += n; }; - case io.closed => { let cl: io.closed; return cl; }; + case let n: size => { total += n; }; + case let e: io.error => return e; }; i += 1; } else { - let r: (i32 | io.closed) = putbytes(s, fmt.ptr + i: u64, 1); + let r: (size | io.error) = putbytes(s, fmt.ptr + i: u64, 1); match (r) { - case let n: i32 => { total += n; }; - case io.closed => { let cl: io.closed; return cl; }; + case let n: size => { total += n; }; + case let e: io.error => return e; }; i += 1; }; @@ -737,155 +628,76 @@ export fn fprintf(s: *io.stream, fmt: str, args: field...) (i32 | io.closed) = { return total; }; -// fprintfln — fprintf plus a trailing newline. Mirrors wrappers.ha:69. -export fn fprintfln(s: *io.stream, fmt: str, args: field...) (i32 | io.closed) = { - let total: i32 = 0; - let r1: (i32 | io.closed) = fprintf(s, fmt, args...); - match (r1) { - case let n: i32 => { total = n; }; - case io.closed => { let c: io.closed; return c; }; +// fprintln — fprint plus a trailing newline. Mirrors wrappers.ha. +export fn fprintln(s: io.stream, args: formattable...) (size | io.error) = { + let total: size = 0; + match (fprint(s, args...)) { + case let n: size => { total = n; }; + case let e: io.error => return e; }; - let r2: (i32 | io.closed) = putbytes(s, "\n".ptr, 1); - match (r2) { - case let m: i32 => { total += m; }; - case io.closed => { let c: io.closed; return c; }; + match (putbytes(s, "\n".ptr, 1)) { + case let n: size => { total += n; }; + case let e: io.error => return e; }; return total; }; -// ---- fd-sink printf twins ------------------------------------------- -// -// Hare's wrappers.ha:10 routes printf through os::stdout (an -// io::handle). ww has no fd-backed stream yet, so the fd path here -// wraps the fd in a stack-resident io.stream and dispatches through -// fprintf. -errno from [[os.write]] collapses to io.closed inside the -// wrapper; the public i64 return signals success-bytes vs -1 failure. -// Symmetric with the fdprint / fdprintln pair above. - -fn fdsinkread(s: *io.stream, buf: []u8) (i32 | io.eof | io.closed) = { - let e: io.eof; return e; -}; -fn fdsinkwrite(s: *io.stream, buf: []u8) (i32 | io.closed) = { - let fdp: *i32 = s.ctx: *i32; - let r: i64 = os.write(*fdp, buf.ptr, buf.len: u64); - if (r < 0) { let c: io.closed; return c; }; - return r: i32; -}; -fn fdsinkclose(s: *io.stream) (void | io.closed) = { return; }; - -fn fdwrap(fd: *i32, s: *io.stream) void = { - s.ctx = fd: *void; - s.read = fdsinkread; - s.write = fdsinkwrite; - s.close = fdsinkclose; -}; - -// fdprintf / fdprintfln — fprintf / fprintfln over a raw fd. Failure -// (any underlying os.write returning -errno) collapses to -1; success -// returns total bytes written. See header comment. -export fn fdprintf(fd: i32, fmt: str, args: field...) i64 = { - let f: i32 = fd; - let s: io.stream; - fdwrap(&f, &s); - let r: (i32 | io.closed) = fprintf(&s, fmt, args...); - match (r) { - case let n: i32 => return n: i64; - case io.closed => return -1i64; +// fprintfln — fprintf plus a trailing newline. Mirrors wrappers.ha:69. +export fn fprintfln(s: io.stream, fmt: str, args: field...) (size | io.error) = { + let total: size = 0; + match (fprintf(s, fmt, args...)) { + case let n: size => { total = n; }; + case let e: io.error => return e; }; -}; - -export fn fdprintfln(fd: i32, fmt: str, args: field...) i64 = { - let f: i32 = fd; - let s: io.stream; - fdwrap(&f, &s); - let r: (i32 | io.closed) = fprintfln(&s, fmt, args...); - match (r) { - case let n: i32 => return n: i64; - case io.closed => return -1i64; + match (putbytes(s, "\n".ptr, 1)) { + case let n: size => { total += n; }; + case let e: io.error => return e; }; + return total; }; -// ---- process-stdio printf wrappers ---------------------------------- -// -// printf / printfln — wrappers.ha:10 / :15 on os.stdout (fd 1). -// errorfln — wrappers.ha:24 on stderr. Hare ships `errorf` too, but -// the bare `error` name collides with strconv.error under the -// driver's flat-scope concat (same divergence the existing [[errorln]] -// notes at line 234); ship the -ln form only and document. -// fatalf — wrappers.ha:54 errorfln-then-exit(255). - -export fn printf(fmt: str, args: field...) i64 = { - return fdprintf(1, fmt, args...); -}; - -export fn printfln(fmt: str, args: field...) i64 = { - return fdprintfln(1, fmt, args...); -}; - -export fn errorfln(fmt: str, args: field...) i64 = { - return fdprintfln(2, fmt, args...); -}; - -export fn fatalf(fmt: str, args: field...) never = { - fdprintfln(2, fmt, args...); - os.exit(255); -}; - -// bsprintf — render into a caller-supplied buffer through memio.fixed; -// return the str view of the bytes actually written. Mirrors -// wrappers.ha:42. io.closed propagates as-is (Hare's `nomem` arm). On -// short writes the returned str is just the prefix that fit — Hare's -// memio.fixed contract is the same. -export fn bsprintf(buf: []u8, fmt: str, args: field...) (str | io.closed) = { - let m: memio.state; - let s: io.stream; - memio.fixed(&m, &s, buf); - let r: (i32 | io.closed) = fprintf(&s, fmt, args...); - match (r) { - case let n: i32 => { return memio.string(&m); }; - case io.closed => { let c: io.closed; return c; }; +// bsprintf — render into `buf` through memio.fixed; return the str view +// of bytes actually written. Mirrors wrappers.ha:42. memio.fixed returns +// the `stream` BY VALUE — the stream lives in this frame; `&st.vt` is +// the io.stream and `&st` the accessor handle. Hare returns +// `(const str | nomem)`; ww collapses to (str | io.error) so the +// fprintf path's io.error arm stays uniform. Short writes surface as a +// prefix (memio.fixed contract). +export fn bsprintf(buf: []u8, fmt: str, args: field...) (str | io.error) = { + let st: memio.stream = memio.fixed(buf); + let s: io.stream = &st.vt; + match (fprintf(s, fmt, args...)) { + case let n: size => { return memio.string(&st); }; + case let e: io.error => return e; }; }; // asprintf — render `fmt`/`args` into a heap-allocated str through -// memio.dynamic, then shrink to a tight allocation so the caller's -// free(r.ptr, r.len) matches the underlying mmap length. Mirrors -// wrappers.ha:29 modulo: -// -// - Bare `str` return. Hare returns `(str | nomem)`; ww has no -// `nomem` variant — os.alloc faults on OOM per lib/os.ww's -// contract. -// - The `io.closed` arm of fprintf is statically unreachable -// here (memio.dynamicwrite never returns io.closed, memio.ww:163), -// but the type checker still requires the match; both arms have -// empty bodies. Same effect as Hare's `case size => void`. -// - Shrink-to-fit copy. memio.dynamic's `cap` doubles past `pos` -// during growth (memio.ww:193); the close path frees the -// cap-sized mapping (memio.ww:179). Returning memio.string(&m) -// directly would either leak the cap-vs-len slack (skip close) -// or dangle the returned view (close first). Copying into a -// fresh `view.len`-sized alloc lets the caller free with -// `r.len`, matching strings.dup's tight-alloc contract. -// -// Caller frees with `os.free(r.ptr, r.len: u64)` when `r.len > 0`; -// skip the free when `r.len == 0` — same empty-output shape as -// strings.dup (no allocation took place). +// memio.dynamic, shrink-copy to a tight allocation, close the dynamic +// backing. Mirrors wrappers.ha:29 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. Shrink-to-fit: memio.dynamic's cap doubles past pos during +// growth; io.close frees the cap-sized mapping. Returning +// memio.string 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 asprintf(fmt: str, args: field...) str = { - let m: memio.state; - let s: io.stream; - memio.dynamic(&m, &s); - let wres: (i32 | io.closed) = fprintf(&s, fmt, args...); - match (wres) { - case let n: i32 => {}; - case io.closed => {}; - }; - let view: str = memio.string(&m); let out: str; out.ptr = nil; out.len = 0; + let st: memio.stream = memio.dynamic(); + let s: io.stream = &st.vt; + let wres: (size | io.error) = fprintf(s, fmt, args...); + match (wres) { + case let n: size => {}; + case let e: io.error => {}; + }; + let view: str = memio.string(&st); if (view.len == 0) { - let cres: (void | io.closed) = io.close(&s); - match (cres) { case void => {}; case io.closed => {}; }; + let cres: (void | io.error) = io.close(s); + match (cres) { case void => {}; case let e: io.error => {}; }; return out; }; let tight: []u8 = alloc([], view.len: u64)!; @@ -894,8 +706,119 @@ export fn asprintf(fmt: str, args: field...) str = { tight[i] = view.ptr[i]; i += 1; }; - let cres: (void | io.closed) = io.close(&s); - match (cres) { case void => {}; case io.closed => {}; }; + let cres: (void | io.error) = io.close(s); + match (cres) { case void => {}; case let e: io.error => {}; }; tight.len = view.len; return strings.frombytes(tight); }; + +// ---- fd sinks (pre-handle shim, NO Hare counterpart; #5) ------------- + +// fdprint — fdprint over a stack-resident fd_ctx io.stream. The &c.vt +// io.stream never escapes this frame. +export fn fdprint(fd: i32, args: formattable...) (size | io.error) = { + let c: fd_ctx; + c.fd = fd; + c.vt.reader = (&fdsinkread): *io.reader; + c.vt.writer = (&fdsinkwrite): *io.writer; + let s: io.stream = &c.vt; + return fprint(s, args...); +}; + +// fdprintln — fdprint + trailing newline. +export fn fdprintln(fd: i32, args: formattable...) (size | io.error) = { + let c: fd_ctx; + c.fd = fd; + c.vt.reader = (&fdsinkread): *io.reader; + c.vt.writer = (&fdsinkwrite): *io.writer; + let s: io.stream = &c.vt; + let total: size = 0; + match (fprint(s, args...)) { + case let n: size => { total = n; }; + case let e: io.error => return e; + }; + match (putbytes(s, "\n".ptr, 1)) { + case let n: size => { total += n; }; + case let e: io.error => return e; + }; + return total; +}; + +// fdprintf — fdprintf over a fd_ctx io.stream. +export fn fdprintf(fd: i32, fmt: str, args: field...) (size | io.error) = { + let c: fd_ctx; + c.fd = fd; + c.vt.reader = (&fdsinkread): *io.reader; + c.vt.writer = (&fdsinkwrite): *io.writer; + let s: io.stream = &c.vt; + return fprintf(s, fmt, args...); +}; + +// fdprintfln — fdprintf + trailing newline. Mirrors wrappers.ha:69. +export fn fdprintfln(fd: i32, fmt: str, args: field...) (size | io.error) = { + let c: fd_ctx; + c.fd = fd; + c.vt.reader = (&fdsinkread): *io.reader; + c.vt.writer = (&fdsinkwrite): *io.writer; + let s: io.stream = &c.vt; + let total: size = 0; + match (fprintf(s, fmt, args...)) { + case let n: size => { total = n; }; + case let e: io.error => return e; + }; + match (putbytes(s, "\n".ptr, 1)) { + case let n: size => { total += n; }; + case let e: io.error => return e; + }; + return total; +}; + +// ---- process-stdio wrappers ----------------------------------------- +// +// Hare routes these through os::stdout / os::stderr (io::handle); ww +// has no fd-backed handle yet, so they route through the fd shim on +// fd 1 / fd 2 (io fold-2, #5, collapses the fd shim). errorf / asprint / +// bsprint omitted: the bare `error` name collides with strconv.error +// under the driver's flat-scope concat; ship the -ln forms only. + +// print / println — wrappers.ha:78/:84 on fd 1. +export fn print(args: formattable...) (size | io.error) = { + return fdprint(1, args...); +}; + +export fn println(args: formattable...) (size | io.error) = { + return fdprintln(1, args...); +}; + +// errorln — wrappers.ha:96 on fd 2. +export fn errorln(args: formattable...) (size | io.error) = { + return fdprintln(2, args...); +}; + +// fatal — errorln then exit(255). `never` return marks the bottom type +// so flow-control checks treat callers as terminated. Mirrors +// wrappers.ha:63. +export fn fatal(args: formattable...) never = { + fdprintln(2, args...); + os.exit(255); +}; + +// printf / printfln — wrappers.ha:10/:15 on fd 1. +export fn printf(fmt: str, args: field...) (size | io.error) = { + return fdprintf(1, fmt, args...); +}; + +export fn printfln(fmt: str, args: field...) (size | io.error) = { + return fdprintfln(1, fmt, args...); +}; + +// errorfln — wrappers.ha:24 on fd 2. +export fn errorfln(fmt: str, args: field...) (size | io.error) = { + return fdprintfln(2, fmt, args...); +}; + +// fatalf — errorfln then exit(255). Mirrors wrappers.ha:54. +export fn fatalf(fmt: str, args: field...) never = { + fdprintfln(2, fmt, args...); + os.exit(255); +}; diff --git a/lib/fmt/fmttest.ww b/lib/fmt/fmttest.ww index d07a1a04..0a03037b 100644 --- a/lib/fmt/fmttest.ww +++ b/lib/fmt/fmttest.ww @@ -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(); }; }; diff --git a/lib/fmt/vstream.ww b/lib/fmt/vstream.ww deleted file mode 100644 index edfc67f3..00000000 --- a/lib/fmt/vstream.ww +++ /dev/null @@ -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 -// `(* | void)` field-init / let-binding. Explicit -// `(&fn_name): *io.` cast at each store site is the Hare- -// faithful minimum-touch route — same workaround memio/vstream.ww -// 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); -}; diff --git a/lib/io/io.ww b/lib/io/io.ww index 12455358..aac312ad 100644 --- a/lib/io/io.ww +++ b/lib/io/io.ww @@ -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); -}; diff --git a/lib/io/stream.ww b/lib/io/stream.ww index 06186002..5d63e0c7 100644 --- a/lib/io/stream.ww +++ b/lib/io/stream.ww @@ -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); diff --git a/lib/io/types.ww b/lib/io/types.ww index b79fbe35..7de7becc 100644 --- a/lib/io/types.ww +++ b/lib/io/types.ww @@ -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); diff --git a/lib/log/log.ww b/lib/log/log.ww index 1ce90489..f2bf6ba3 100644 --- a/lib/log/log.ww +++ b/lib/log/log.ww @@ -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.` 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); diff --git a/lib/log/logtest.ww b/lib/log/logtest.ww index b4b5b2e4..a0ad3caf 100644 --- a/lib/log/logtest.ww +++ b/lib/log/logtest.ww @@ -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); diff --git a/lib/log/vstream.ww b/lib/log/vstream.ww deleted file mode 100644 index 98cece92..00000000 --- a/lib/log/vstream.ww +++ /dev/null @@ -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 `(* | void)` field-init / let-binding. Explicit -// `(&fn_name): *io.` cast at each store site is the Hare- -// faithful minimum-touch route — same workaround memio/vstream.ww -// + fmt/vstream.ww + bufio/vstream.ww use. 2 cast tokens here at -// the stderrsink_ctx_g vt wire-up (reader + writer; no closer — -// the process owns fd 2, same divergence as OLD stderrclose at -// log.ww:162). Both casts drop out wholesale once #206 closes. -// -// Chained-N_DOT-on-module-static field-assign per OLD log.ww:134-140 -// (`_default.logger.println = stdprintln;`) — proven-working spine. -// stderrsink_ctx_g.vt.X writes pattern through same shape; the -// tagged-union assign with widening cast composes via #199 α -// (concrete *fn → NAMED variant of (*reader | void) parent). -// -// Newline emission: vfprint / vfprintf are bare (no trailing \n); -// Hare's ref/hare/fmt/wrappers.ha:60-68 appends the newline at the -// fprintln site (after fprint). ww mirrors: stdprintln_v / stdprintfln_v -// call fmt.vfprint / fmt.vfprintf then io.st_write a one-byte slice -// `[10u8]` for the LF. Same shape OLD log.stdprintln uses via -// fmt.fprintln (log.ww:169). -// -// Sibling tasks parked here (filed, NOT fixed): -// -// - eFinal (#50): atomic flip + delete OLD log surface + drop the -// `_v` suffix wholesale (graduates to Hare's bare names). -// -// - #206 (bare &fn → (*alias|void)): 2 cast sites at ensureinit_v; -// drop out wholesale on close. -// -// - #173 (TRY-on-tagged-return both-stages broken): stderrwrite_v -// constructs nomem and widens to io.error explicitly rather than -// using `os.trywrite(...)?`; same shape memio.vstream.ww + -// fmt.vstream.ww + bufio.vstream.ww adopt. -// -// - #207 (struct-lit multi-tagged-field copy drops past first): -// not bitten here — module-static stderrsink_ctx_g is zero-init -// at link time per #129; vt fields wired via post-decl chained -// assigns rather than struct-lit, sidestepping #207 wholesale. -// The pattern is the module-static analogue of memio.vstream.ww's -// alloc-zero-chain. -// -// - #209 (wwstage formattable match-arm bail when fmt imported): -// log imports fmt transitively (via the formattable / field -// types in vlogger's vtable + fmt.vfprint / vfprintf calls in -// the std callbacks). All probe rows in test/wcc/779 run -// STAGE_CS-only; eFinal closes the wwstage half when #209 lands. - -package log; - -import fmt; -import io; -import os; - -// stderrsink_ctx — module-static state for the default vlogger's -// stderr sink. vt FIRST field for the intrusive vstream→*stderrsink_ctx -// cast in stderrwrite_v. Mirrors fmt.fd_ctx (fmt/vstream.ww:66) + -// memio.fixed_ctx (memio/vstream.ww:48); only scalars/ptrs beyond -// vt per ken's mandate. -type stderrsink_ctx = struct { - vt: io.vtable, - fd: i32, -}; - -// Zero-init at link time per #129 A.2/A.3 SSoT; ensureinit_v wires -// vt.reader / vt.writer + fd lazily on first dispatch. Same shape -// OLD log.ww uses for the pre-vtable stderrsink (log.ww:104). -let stderrsink_ctx_g: stderrsink_ctx; - -// vlogger — V-side counterpart to OLD logger (log.ww:85). Same shape -// modulo the `_v` field-suffix (collapses on #50). Dispatchers route -// through io.vstream rather than *io.stream. -// -// Mirrors ref/hare/log/logger.ha:9. -export type vlogger = struct { - println_v: fn(l: *vlogger, args: fmt.formattable...) void, - printfln_v: fn(l: *vlogger, format: str, fields: fmt.field...) void, -}; - -// vstdlogger — concrete vlogger forwarding to an io.vstream sink. -// First-field embed: `&sl.logger` yields a `*vlogger` (intrusive -// outer→inner cast); the vtable callback recovers the outer via -// `*vstdlogger` cast. -// -// Mirrors ref/hare/log/logger.ha:18 (stdlogger). -export type vstdlogger = struct { - logger: vlogger, - sink: io.vstream, -}; - -// _silent_v / _default_v — backing storage for the silent_v / -// default_v *vlogger globals. Zero-init at link time; ensureinit_v -// fills the println_v / printfln_v slots. -let _silent_v: vlogger; -let _default_v: vstdlogger; - -// silent_v / default_v / global_v — Hare-style *vlogger globals. -// Zero-init at link time; lazily wired by ensureinit_v on the first -// call to any exported fn. Same divergence as OLD log.ww:116-118 -// (header rationale for the lazy-init route applies wholesale: ww -// cstage emit_lets only constexprs primitive / str-literal / -// array-literal lets — struct- and pointer-literal init aren't -// reachable). #50 / eFinal graduates if static-init lands. -export let silent_v: *vlogger; -export let default_v: *vlogger; -export let global_v: *vlogger; - -// initdone_v — guards ensureinit_v so the one-shot wiring runs once. -// Mirrors OLD initdone (log.ww:122) + lib/temp's rnginit pattern. -let initdone_v: i32 = 0; - -fn ensureinit_v() void = { - if (initdone_v != 0) { return; }; - stderrsink_ctx_g.fd = 2; - stderrsink_ctx_g.vt.reader = (&stderrread_v): *io.reader; - stderrsink_ctx_g.vt.writer = (&stderrwrite_v): *io.writer; - - _silent_v.println_v = silentprintln_v; - _silent_v.printfln_v = silentprintfln_v; - - _default_v.logger.println_v = stdprintln_v; - _default_v.logger.printfln_v = stdprintfln_v; - _default_v.sink = &stderrsink_ctx_g.vt; - - silent_v = &_silent_v; - default_v = &_default_v.logger; - global_v = default_v; - initdone_v = 1; -}; - -// stderrread_v / stderrwrite_v — vtable callbacks for the private -// stderr sink. Read is eof-only — lib/log writes; never reads. -// Mirror of OLD stderrread / stderrwrite (log.ww:148-160) modulo the -// (size | io.eof | io.error) / (size | io.error) return surface; -// -errno collapses to a nomem-widened io.error per the -// fmt.fdsinkwrite_v precedent (fmt/vstream.ww:88-92). -fn stderrread_v(s: io.vstream, buf: []u8) (size | io.eof | io.error) = { - let e: io.eof; - return e; -}; - -fn stderrwrite_v(s: io.vstream, buf: []u8) (size | io.error) = { - let c: *stderrsink_ctx = s: *stderrsink_ctx; - let r: i64 = os.write(c.fd, buf.ptr, buf.len: u64); - if (r < 0) { - let nm: nomem; - let e: io.error = nm; - return e; - }; - return r: size; -}; - -// stdprintln_v / stdprintfln_v — vtable callbacks for vstdlogger. -// Forwards to fmt.vfprint / vfprintf on the sink + appends the -// trailing newline via io.st_write. Hare's fmt.fprintln emits the -// newline inside one wrapper (ref/hare/fmt/wrappers.ha:60-68); we -// inline that split at the log site because fmt.vfprint is the bare -// (no-newline) primitive. The (size | io.error) results are dropped -// — lib/log's surface is `void` (matches Hare + OLD stdprintln at -// log.ww:167). -fn stdprintln_v(l: *vlogger, args: fmt.formattable...) void = { - let sl: *vstdlogger = l: *vstdlogger; - fmt.vfprint(sl.sink, args...); - let nl: [1]u8; - nl[0] = 10u8; - let v: []u8 = nl[0:1]; - io.st_write(sl.sink, v); -}; - -fn stdprintfln_v(l: *vlogger, format: str, fields: fmt.field...) void = { - let sl: *vstdlogger = l: *vstdlogger; - fmt.vfprintf(sl.sink, format, fields...); - let nl: [1]u8; - nl[0] = 10u8; - let v: []u8 = nl[0:1]; - io.st_write(sl.sink, v); -}; - -// silentprintln_v / silentprintfln_v — vtable callbacks for the -// silent vlogger. Discard every record without dispatching through -// fmt; keeps silent truly silent if fmt ever gets stateful. Mirror -// of OLD silentprintln / silentprintfln (log.ww:183-187). -fn silentprintln_v(l: *vlogger, args: fmt.formattable...) void = { }; -fn silentprintfln_v(l: *vlogger, format: str, fields: fmt.field...) void = { }; - -// ---- public API (10 _v variants) ----------------------------------------- - -// new_v — build a vstdlogger over `sink`, returned BY VALUE (fold-eFinal -// PREP). Hare returns the stdlogger by value (ref/hare/log/logger.ha:20); -// the OLD out-parameter shape existed only because wide-struct sret was -// unimplemented — it now round-trips (test/wcc/925), so the constructor -// builds in a local and `return r;` (proven field-by-field sret form). -// vstdlogger is 24B (two fn-ptrs + sink), returned via the SysV memory -// class; the caller owns the returned logger (stack ownership, no-GC) -// and passes `&sl.logger` to the dispatch fns. -export fn new_v(sink: io.vstream) vstdlogger = { - ensureinit_v(); - let r: vstdlogger; - r.logger.println_v = stdprintln_v; - r.logger.printfln_v = stdprintfln_v; - r.sink = sink; - return r; -}; - -// lprintln_v — dispatch `args` through `log`. Mirrors OLD lprintln -// (log.ww:202) + ref/hare/log/funcs.ha:8. -export fn lprintln_v(log: *vlogger, args: fmt.formattable...) void = { - ensureinit_v(); - log.println_v(log, args...); -}; - -// println_v — dispatch through the [[global_v]] vlogger. Mirrors -// OLD println (log.ww:208) + ref/hare/log/funcs.ha:18. -export fn println_v(args: fmt.formattable...) void = { - ensureinit_v(); - lprintln_v(global_v, args...); -}; - -// lfatal_v — lprintln_v to `log` then exit(255). `never` return -// marks the bottom type so flow-control checks treat callers as -// terminated. Mirrors OLD lfatal (log.ww:216) + -// ref/hare/log/funcs.ha:28. -export fn lfatal_v(log: *vlogger, args: fmt.formattable...) never = { - lprintln_v(log, args...); - os.exit(255); -}; - -// fatal_v — lprintln_v to [[global_v]] then exit(255). Mirrors OLD -// fatal (log.ww:223) + ref/hare/log/funcs.ha:45. -export fn fatal_v(args: fmt.formattable...) never = { - println_v(args...); - os.exit(255); -}; - -// setlogger_v — install `log` as the [[global_v]] vlogger. Mirrors -// OLD setlogger (log.ww:230) + ref/hare/log/global.ha:20. -export fn setlogger_v(log: *vlogger) void = { - ensureinit_v(); - global_v = log; -}; - -// lprintfln_v — dispatch a format-string record through `log`. -// Mirrors OLD lprintfln (log.ww:237) + ref/hare/log/funcs.ha:13. -export fn lprintfln_v(log: *vlogger, format: str, fields: fmt.field...) void = { - ensureinit_v(); - log.printfln_v(log, format, fields...); -}; - -// printfln_v — dispatch through the [[global_v]] vlogger. Mirrors -// OLD printfln (log.ww:244) + ref/hare/log/funcs.ha:23. -export fn printfln_v(format: str, fields: fmt.field...) void = { - ensureinit_v(); - lprintfln_v(global_v, format, fields...); -}; - -// lfatalf_v — lprintfln_v to `log` then exit(255). Mirrors OLD -// lfatalf (log.ww:251) + ref/hare/log/funcs.ha:35. -export fn lfatalf_v(log: *vlogger, format: str, fields: fmt.field...) never = { - lprintfln_v(log, format, fields...); - os.exit(255); -}; - -// fatalf_v — lprintfln_v to [[global_v]] then exit(255). Mirrors -// OLD fatalf (log.ww:258) + ref/hare/log/funcs.ha:52. -export fn fatalf_v(format: str, fields: fmt.field...) never = { - printfln_v(format, fields...); - os.exit(255); -}; diff --git a/lib/memio/memio.ww b/lib/memio/memio.ww index 88690a8f..eb265137 100644 --- a/lib/memio/memio.ww +++ b/lib/memio/memio.ww @@ -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.` 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; }; diff --git a/lib/memio/memiotest.ww b/lib/memio/memiotest.ww index eb1fdbd5..2cae7f28 100644 --- a/lib/memio/memiotest.ww +++ b/lib/memio/memiotest.ww @@ -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 = { diff --git a/lib/memio/vstream.ww b/lib/memio/vstream.ww deleted file mode 100644 index 13e0f24c..00000000 --- a/lib/memio/vstream.ww +++ /dev/null @@ -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.` 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; -}; diff --git a/lib/shlex/shlex.ww b/lib/shlex/shlex.ww index 21dcca30..96529469 100644 --- a/lib/shlex/shlex.ww +++ b/lib/shlex/shlex.ww @@ -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 '"'; // '\' 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; }; diff --git a/lib/shlex/shlextest.ww b/lib/shlex/shlextest.ww index bddf7be9..4c4f0d55 100644 --- a/lib/shlex/shlextest.ww +++ b/lib/shlex/shlextest.ww @@ -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 -------------------------- diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index d1c80acd..f2e3e0ef 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -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.` 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); }; diff --git a/selfhost/cmd/wcc/cgen.ww b/selfhost/cmd/wcc/cgen.ww index ba4af657..917609a3 100644 --- a/selfhost/cmd/wcc/cgen.ww +++ b/selfhost/cmd/wcc/cgen.ww @@ -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); }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index c35baf6f..9de4130c 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -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.` 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); }; diff --git a/test/wcc/700_e2e.c b/test/wcc/700_e2e.c index e5f65862..a1d4b19c 100644 --- a/test/wcc/700_e2e.c +++ b/test/wcc/700_e2e.c @@ -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. diff --git a/test/wcc/768_io_types_run.c b/test/wcc/768_io_types_run.c index ccce42a9..a06181ab 100644 --- a/test/wcc/768_io_types_run.c +++ b/test/wcc/768_io_types_run.c @@ -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" diff --git a/test/wcc/775_io_vtable_run.c b/test/wcc/775_io_vtable_run.c index a36d5335..df52e866 100644 --- a/test/wcc/775_io_vtable_run.c +++ b/test/wcc/775_io_vtable_run.c @@ -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.` 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" diff --git a/test/wcc/776_memio_vstream_run.c b/test/wcc/776_memio_vstream_run.c index 6756be9e..1ae03e83 100644 --- a/test/wcc/776_memio_vstream_run.c +++ b/test/wcc/776_memio_vstream_run.c @@ -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 #include @@ -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" diff --git a/test/wcc/777_fmt_vstream_run.c b/test/wcc/777_fmt_vstream_run.c index f4a2a02f..185dc028 100644 --- a/test/wcc/777_fmt_vstream_run.c +++ b/test/wcc/777_fmt_vstream_run.c @@ -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 @@ -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" diff --git a/test/wcc/778_bufio_vstream_run.c b/test/wcc/778_bufio_vstream_run.c index a079cd4d..811b6d13 100644 --- a/test/wcc/778_bufio_vstream_run.c +++ b/test/wcc/778_bufio_vstream_run.c @@ -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 #include @@ -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; } diff --git a/test/wcc/779_log_vstream_run.c b/test/wcc/779_log_vstream_run.c index b934f669..9b54c550 100644 --- a/test/wcc/779_log_vstream_run.c +++ b/test/wcc/779_log_vstream_run.c @@ -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 @@ -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" diff --git a/test/wcc/780_fmt_vstream_mods_run.c b/test/wcc/780_fmt_vstream_mods_run.c index bde4f692..c4cdbe55 100644 --- a/test/wcc/780_fmt_vstream_mods_run.c +++ b/test/wcc/780_fmt_vstream_mods_run.c @@ -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 #include @@ -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" diff --git a/test/wcc/781_fmt_vstream_compositions_run.c b/test/wcc/781_fmt_vstream_compositions_run.c index 3c014d73..622e8527 100644 --- a/test/wcc/781_fmt_vstream_compositions_run.c +++ b/test/wcc/781_fmt_vstream_compositions_run.c @@ -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 #include @@ -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" diff --git a/test/wcc/900_stdlib.c b/test/wcc/900_stdlib.c index 3e5693c2..b87fadf7 100644 --- a/test/wcc/900_stdlib.c +++ b/test/wcc/900_stdlib.c @@ -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 +