lib: collapse the parallel vstream scaffold onto the single Hare io surface (#94 fold-eFinal)
The Option-C parallel _v vstream API was scaffolding to bring the io stack up alongside the old surface; carrying both permanently is a rule-9 divergence from ref/hare, which has exactly one io surface. Collapse onto that surface (stream = *vtable, ref/hare/io/stream.ha) and rename the _v symbols to their Hare names (io vstream->stream, fmt vfprint->fprint, bufio/memio/log surfaces, log.new). Deletes the 4 lib/*/vstream.ww scaffold files; regenerates w6c/wwdump combined.ww. cstage and wwstage stay byte-identical and combined_ww_fresh holds; all 220 tests pass.
This commit is contained in:
20
Makefile
20
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 $@ $<
|
||||
|
||||
|
||||
@@ -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.newscannerbuf(src: io.stream, buf: []u8) scanner
|
||||
// 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.scanbyte (s: *scanner) (u8 | io.eof | io.error)
|
||||
// bufio.scanbytes (s: *scanner, delim: u8)
|
||||
// ([]u8 | io.eof | io.error | overflow)
|
||||
// bufio.scanline (s: *scanner) (str | io.eof | io.error | overflow)
|
||||
//
|
||||
// bufio.init (b: *stream, src: *io.stream, rbuf: []u8, wbuf: []u8) void
|
||||
// bufio.flush (b: *stream) (void | io.closed)
|
||||
// bufio.init (src: io.stream, rbuf: []u8, wbuf: []u8) stream
|
||||
// bufio.flush (b: *stream) (void | io.error)
|
||||
// bufio.setflush (b: *stream, bs: []u8) void
|
||||
// bufio.unread (b: *stream, buf: []u8) void
|
||||
// bufio.isbuffered (s: *io.stream) bool
|
||||
// bufio.isbuffered (s: io.stream) bool
|
||||
//
|
||||
// Divergence from Hare:
|
||||
// VALUE-RETURN (Hare ref/hare/bufio/stream.ha:69 init, scanner.ha:92
|
||||
// newscanner_buf): each constructor builds in a local `let r: T;`,
|
||||
// field-assigns every slot, and `return r;`. The caller owns the
|
||||
// returned struct (stack ownership, no-GC). For the buffered stream
|
||||
// the caller passes `&b.vt` to the io dispatchers; the scanner is a
|
||||
// plain read-ahead tokenizer (no embedded vtable) driven directly via
|
||||
// scanbyte / scanbytes / scanline.
|
||||
//
|
||||
// • Hare returns the scanner / stream by value; ww cgen can't
|
||||
// return structs wider than 16B by value yet, so `newscanner`
|
||||
// and `init` are out-parameter shaped. Same workaround
|
||||
// memio.fixed uses.
|
||||
// SCOPE: this is the surface ww ships, NOT a port of Hare's full
|
||||
// scanner. The features ww never implemented — auto-grow newscanner,
|
||||
// multibyte-delim scanbytes / scanstring, scanrune / readbyte / readtok
|
||||
// / readline / readrune / unreadrune — are a separate future feature
|
||||
// fold (#217). The ported set: the fixed-buffer newscannerbuf,
|
||||
// single-byte scanbytes, single-byte scanline, scanbyte, finish; and
|
||||
// the buffered-stream init / setflush / flush / unread / isbuffered.
|
||||
//
|
||||
// • The scanner's read buffer is held flat as `(ptr, cap)` rather
|
||||
// than a `[]u8` field; the scanner predates struct-held slice
|
||||
// support and is wired through scanbyte/scantok/scanline. The
|
||||
// new stream writer half uses slice-typed `rbuf` / `wbuf`
|
||||
// fields directly — chained dot through a struct slice field
|
||||
// works after task #6.
|
||||
// Cast workaround per #206-payoff (ken: KEEP the explicit casts; they
|
||||
// are cgen-neutral and sidestep the #214 over-acceptance surface).
|
||||
// `(&fn_name): *io.<role>` at each vtable store + the isbuffered
|
||||
// fn-ptr-equality comparand — the #206 cast-drop is gated on #214.
|
||||
//
|
||||
// • Hare's `bufio::stream` discriminates r-only / w-only / r+w
|
||||
// via three different vtable singletons (vtable_r, vtable_w,
|
||||
// vtable_rw); ww's [[io.stream]] vtable always carries all
|
||||
// three callbacks, so stream always installs `bread` /
|
||||
// `bwrite` / `bclose`. A zero-length rbuf or wbuf makes the
|
||||
// matching callback degenerate (rbuf=[]: bread short-circuits
|
||||
// to io.eof; wbuf=[]: bwrite passes through to src).
|
||||
//
|
||||
// • Hare's `init` also takes a `flag` argument carrying
|
||||
// MANAGED_HANDLE / MANAGED_RDBUF / MANAGED_WRBUF ownership
|
||||
// bits; stream never owns its buffers or src, so the
|
||||
// `flags` field and the init-time argument are dropped
|
||||
// wholesale. The Hare `flush: []u8` byte-set is preserved —
|
||||
// [[init]] seeds it to `['\n']` (line buffering, matching
|
||||
// Hare's `flag::NONE` default at stream.ha:75) and
|
||||
// [[setflush]] swaps it.
|
||||
//
|
||||
// • Hare's `newscanner` allocates and grows the buffer up to
|
||||
// `maxread`; we ship only the caller-supplied shape (Hare's
|
||||
// `newscanner_buf`) and pick up the auto-grow variant when an
|
||||
// append over a struct-held slice works.
|
||||
//
|
||||
// • EOF-handling defaults to Hare's `EOF_DISCARD`: bytes between
|
||||
// the last delimiter and EOF are dropped and io.eof is returned
|
||||
// on the call that would have read them. Hare's EOF_GREEDY mode
|
||||
// isn't shipped (no caller needs it yet).
|
||||
//
|
||||
// Owning model: caller owns the scanner state, the stream state,
|
||||
// the byte buffers, and the source stream. `finish` doesn't free
|
||||
// the buffer and doesn't close src; it's a no-op today but stays
|
||||
// on the surface so callers don't churn when bufio grows internal
|
||||
// allocations. The stream vtable `close` callback flushes pending
|
||||
// writes and forwards close to src; it does not free rbuf / wbuf.
|
||||
//
|
||||
// let mem: memio.state;
|
||||
// let m: io.stream;
|
||||
// memio.fixed(&mem, &m, raw[0:N]);
|
||||
// let buf: [128]u8;
|
||||
// let sc: bufio.scanner;
|
||||
// bufio.newscanner(&sc, &m, buf[0:128]);
|
||||
// match (bufio.scanline(&sc)) { ... };
|
||||
// bufio.finish(&sc);
|
||||
//
|
||||
// let b: bufio.stream;
|
||||
// let rbuf: [256]u8;
|
||||
// let wbuf: [128]u8;
|
||||
// bufio.init(&b, src, rbuf[0:256], wbuf[0:128]);
|
||||
// let p: *io.stream = &b.vtable; // first-field embed
|
||||
// io.write(p, msg);
|
||||
// bufio.flush(&b);
|
||||
// Mode discrimination (drew-deferred): Hare uses three vtable
|
||||
// singletons (vtable_r / vtable_w / vtable_rw); ww's vtable always
|
||||
// carries all three callbacks (a zero-length rbuf/wbuf degenerates the
|
||||
// matching callback in-cb). Defer-handle (MANAGED_* ownership bits)
|
||||
// also deferred — caller owns rbuf/wbuf/src; bclose flushes + forwards
|
||||
// close but frees nothing. Both graduate with io fold-2 (#5).
|
||||
|
||||
package bufio;
|
||||
|
||||
@@ -101,157 +66,20 @@ let flushdefault: [1]u8 = [10u8];
|
||||
// use of errors::overflow for the same condition.
|
||||
export type overflow = !void;
|
||||
|
||||
export type scanner = struct {
|
||||
src: *io.stream,
|
||||
ptr: *u8,
|
||||
cap: i32,
|
||||
start: i32, // index where the pending region starts in ptr
|
||||
avail: i32, // pending byte count; pending = ptr[start..start+avail]
|
||||
};
|
||||
// ---- buffered stream ------------------------------------------------------
|
||||
|
||||
// newscanner — wire `s` to read through `src` using `buf` as the
|
||||
// read-ahead window. Hare returns the scanner by value
|
||||
// (newscanner_buf); we take an out-parameter pending cgen support
|
||||
// for wide-struct return.
|
||||
export fn newscanner(s: *scanner, src: *io.stream, buf: []u8) void = {
|
||||
s.src = src;
|
||||
s.ptr = buf.ptr;
|
||||
s.cap = buf.len;
|
||||
s.start = 0;
|
||||
s.avail = 0;
|
||||
};
|
||||
|
||||
// finish — release scanner-owned resources. No-op today (buffer is
|
||||
// caller-owned, src isn't closed); kept on the surface so callers
|
||||
// won't churn when bufio later grows internal allocations.
|
||||
export fn finish(s: *scanner) void = { };
|
||||
|
||||
// readahead — make room and read once from src into the back of the
|
||||
// pending region. Returns the number of bytes newly buffered (≥0,
|
||||
// can be 0 if the underlying stream made no progress), or
|
||||
// io.eof/io.closed propagated from src.
|
||||
fn readahead(s: *scanner) (i32 | io.eof | io.closed) = {
|
||||
if (s.start + s.avail == s.cap && s.start > 0) {
|
||||
// Shift pending region to the front of the buffer.
|
||||
let i: i32 = 0;
|
||||
for (i < s.avail) {
|
||||
s.ptr[i] = s.ptr[s.start + i];
|
||||
i += 1;
|
||||
};
|
||||
s.start = 0;
|
||||
};
|
||||
let off: i32 = s.start + s.avail;
|
||||
let v: []u8;
|
||||
v.ptr = s.ptr + (off: u64);
|
||||
v.len = s.cap - off;
|
||||
let r: (i32 | io.eof | io.closed) = io.read(s.src, v);
|
||||
match (r) {
|
||||
case let n: i32 => {
|
||||
s.avail += n;
|
||||
return n;
|
||||
};
|
||||
case io.eof => { let e: io.eof; return e; };
|
||||
case io.closed => { let e: io.closed; return e; };
|
||||
};
|
||||
};
|
||||
|
||||
// scanbyte — pop one byte from the scanner, refilling from src on
|
||||
// demand. Mirrors Hare's `scan_byte` (ref/hare/bufio/scanner.ha:204).
|
||||
export fn scanbyte(s: *scanner) (u8 | io.eof | io.closed) = {
|
||||
for (s.avail == 0) {
|
||||
let r: (i32 | io.eof | io.closed) = readahead(s);
|
||||
match (r) {
|
||||
case let n: i32 => { };
|
||||
case io.eof => { let e: io.eof; return e; };
|
||||
case io.closed => { let e: io.closed; return e; };
|
||||
};
|
||||
};
|
||||
let b: u8 = s.ptr[s.start];
|
||||
s.start += 1;
|
||||
s.avail -= 1;
|
||||
return b;
|
||||
};
|
||||
|
||||
// scantok — read up to (and not including) the next byte equal to
|
||||
// `delim`. The delim byte is consumed from the stream but not
|
||||
// returned. The returned slice borrows from the scanner's internal
|
||||
// buffer and is invalidated by the next scan call.
|
||||
//
|
||||
// EOF without finding delim discards the trailing fragment and
|
||||
// returns io.eof (Hare's EOF_DISCARD default). Buffer-full without
|
||||
// delim returns overflow.
|
||||
//
|
||||
// Mirrors Hare's `scan_bytes` (ref/hare/bufio/scanner.ha:220),
|
||||
// narrowed to a single-byte delimiter.
|
||||
export fn scantok(s: *scanner, delim: u8) ([]u8 | io.eof | io.closed | overflow) = {
|
||||
let i: i32 = 0;
|
||||
for (true) {
|
||||
for (i < s.avail) {
|
||||
if (s.ptr[s.start + i] == delim) {
|
||||
let v: []u8;
|
||||
v.ptr = s.ptr + (s.start: u64);
|
||||
v.len = i;
|
||||
s.start += i + 1;
|
||||
s.avail -= i + 1;
|
||||
return v;
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
// No delim in pending. If the buffer is full with nowhere
|
||||
// to shift, the caller's budget is too small — overflow.
|
||||
if (s.start + s.avail == s.cap && s.start == 0) {
|
||||
let e: overflow; return e;
|
||||
};
|
||||
let r: (i32 | io.eof | io.closed) = readahead(s);
|
||||
match (r) {
|
||||
case let n: i32 => {
|
||||
// readahead may shift start to 0; the searched bytes
|
||||
// move with it, so `i` (count from start) is still
|
||||
// accurate. Resume scanning where we left off.
|
||||
};
|
||||
case io.eof => { let e: io.eof; return e; };
|
||||
case io.closed => { let e: io.closed; return e; };
|
||||
};
|
||||
};
|
||||
let e: io.eof; return e;
|
||||
};
|
||||
|
||||
// scanline — read up to (and not including) the next '\n'. The
|
||||
// newline is consumed; the returned str view borrows from the
|
||||
// scanner buffer and is invalidated by the next scan call.
|
||||
// Hare's `scan_line` is `scan_string(s, "\n")`; we route through
|
||||
// scantok directly since lib/strings doesn't yet ship `toutf8` over
|
||||
// a multi-byte delim.
|
||||
export fn scanline(s: *scanner) (str | io.eof | io.closed | overflow) = {
|
||||
let r: ([]u8 | io.eof | io.closed | overflow) = scantok(s, 10u8);
|
||||
match (r) {
|
||||
case let bs: []u8 => {
|
||||
let v: str;
|
||||
v.ptr = bs.ptr;
|
||||
v.len = bs.len;
|
||||
return v;
|
||||
};
|
||||
case io.eof => { let e: io.eof; return e; };
|
||||
case io.closed => { let e: io.closed; return e; };
|
||||
case overflow => { let e: overflow; return e; };
|
||||
};
|
||||
};
|
||||
|
||||
// stream — buffered read+write over an underlying *io.stream.
|
||||
//
|
||||
// `vtable` is the first field so a `*stream` is castable to an
|
||||
// `*io.stream` via address-of-field (`&b.vtable`). Higher layers
|
||||
// (fmt.fprint, scanner, …) only see the io.stream view; bufio
|
||||
// recovers the outer stream by casting the dispatch arg back to
|
||||
// `*stream` inside the bread/bwrite/bclose callbacks.
|
||||
// stream — heap-free buffered read+write over an underlying io.stream.
|
||||
// `vt` at offset 0 for the intrusive io.stream→*stream cast. `src` is
|
||||
// an io.stream (the vtable-native underlying handle). Mirrors
|
||||
// ref/hare/bufio/stream.ha:18.
|
||||
//
|
||||
// rbuf[rstart..rend] is pending read data; wbuf[0..wend] is pending
|
||||
// write data. rbuf or wbuf may be zero-length: read-empty makes
|
||||
// bread return io.eof immediately, write-empty makes bwrite a
|
||||
// pass-through to src (uses src.write directly, no buffering).
|
||||
// write data. rbuf or wbuf may be zero-length: read-empty makes bread
|
||||
// return io.eof immediately, write-empty makes bwrite a pass-through to
|
||||
// src (no buffering).
|
||||
export type stream = struct {
|
||||
vtable: io.stream,
|
||||
src: *io.stream,
|
||||
vt: io.vtable,
|
||||
src: io.stream,
|
||||
rbuf: []u8,
|
||||
rstart: i32,
|
||||
rend: i32,
|
||||
@@ -260,53 +88,57 @@ export type stream = struct {
|
||||
flush: []u8,
|
||||
};
|
||||
|
||||
// init — wire `b` over `src` with caller-supplied buffers. Both
|
||||
// rbuf and wbuf may be empty slices; the corresponding direction
|
||||
// degenerates (see [[stream]]). The flush byte-set defaults to
|
||||
// "\n" (line-buffered writes); [[setflush]] swaps it.
|
||||
export fn init(b: *stream, src: *io.stream, rbuf: []u8, wbuf: []u8) void = {
|
||||
b.vtable.ctx = b: *void;
|
||||
b.vtable.read = bread;
|
||||
b.vtable.write = bwrite;
|
||||
b.vtable.close = bclose;
|
||||
b.src = src;
|
||||
b.rbuf = rbuf;
|
||||
// init — wire a buffered stream over an underlying io.stream with
|
||||
// caller-supplied read/write buffers. Both rbuf and wbuf may be empty;
|
||||
// bread / bwrite degenerate (see [[stream]]). The flush byte-set
|
||||
// defaults to "\n" (line-buffered writes); [[setflush]] swaps it.
|
||||
//
|
||||
// Returns the stream BY VALUE; the caller passes `&b.vt` to the io
|
||||
// dispatchers. Mirrors ref/hare/bufio/stream.ha:69.
|
||||
export fn init(src: io.stream, rbuf: []u8, wbuf: []u8) stream = {
|
||||
let r: stream;
|
||||
r.vt.reader = (&bread): *io.reader;
|
||||
r.vt.writer = (&bwrite): *io.writer;
|
||||
r.vt.closer = (&bclose): *io.closer;
|
||||
r.src = src;
|
||||
// rstart=rbuf.len, rend=rbuf.len: pre-read unread budget = rbuf.len
|
||||
// (Hare bufio/stream.ha:101).
|
||||
b.rstart = rbuf.len;
|
||||
b.rend = rbuf.len;
|
||||
b.wbuf = wbuf;
|
||||
b.wend = 0;
|
||||
b.flush = flushdefault[0:1];
|
||||
r.rstart = rbuf.len;
|
||||
r.rend = rbuf.len;
|
||||
r.rbuf = rbuf;
|
||||
r.wbuf = wbuf;
|
||||
r.wend = 0;
|
||||
r.flush = flushdefault[0:1];
|
||||
return r;
|
||||
};
|
||||
|
||||
// setflush — install a new flush byte-set. Any byte from `bs`
|
||||
// appearing in a write payload triggers an automatic flush after
|
||||
// the write copies into wbuf. Mirrors Hare's `setflush`
|
||||
// (ref/hare/bufio/stream.ha:128).
|
||||
// setflush — install a new flush byte-set. Any byte from `bs` appearing
|
||||
// in a write payload triggers an automatic flush after the write copies
|
||||
// into wbuf. Mirrors ref/hare/bufio/stream.ha:128.
|
||||
export fn setflush(b: *stream, bs: []u8) void = {
|
||||
b.flush = bs;
|
||||
};
|
||||
|
||||
// flush — drain any pending wbuf data to src. Hare returns
|
||||
// `(void | io::error)`; ww's io.stream write-side error channel
|
||||
// is `io.closed` alone, so the return shape narrows.
|
||||
export fn flush(b: *stream) (void | io.closed) = {
|
||||
// flush — drain any pending wbuf data to src. Public API AND the
|
||||
// internal drain called by bwrite / bclose. A 0-byte non-error write
|
||||
// means the sink made no progress (memio.fixed full) — surfaced as a
|
||||
// nomem-carried io.error rather than spinning, matching what io.writeall
|
||||
// would do in Hare. Mirrors ref/hare/bufio/stream.ha.
|
||||
export fn flush(b: *stream) (void | io.error) = {
|
||||
if (b.wend == 0) { return; };
|
||||
let off: i32 = 0;
|
||||
for (off < b.wend) {
|
||||
let r: (i32 | io.closed) = io.write(b.src, b.wbuf[off:b.wend]);
|
||||
let r: (size | io.error) = io.write(b.src, b.wbuf[off:b.wend]);
|
||||
match (r) {
|
||||
case let n: i32 => {
|
||||
if (n == 0) {
|
||||
// Underlying stream made no progress. Treat as
|
||||
// closed rather than spin; matches what
|
||||
// io.writeall would do in Hare.
|
||||
let e: io.closed; return e;
|
||||
case let n: size => {
|
||||
if (n == 0: size) {
|
||||
let nm: nomem;
|
||||
let e: io.error = nm;
|
||||
return e;
|
||||
};
|
||||
off += n;
|
||||
off += n: i32;
|
||||
};
|
||||
case io.closed => { let e: io.closed; return e; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
};
|
||||
b.wend = 0;
|
||||
@@ -314,10 +146,9 @@ export fn flush(b: *stream) (void | io.closed) = {
|
||||
};
|
||||
|
||||
// unread — push `buf` back into the read buffer so the next bread
|
||||
// returns it first. The bytes must fit in front of the pending
|
||||
// region (rstart >= buf.len); Hare aborts on overflow, ww does the
|
||||
// same via rt_abort. Mirrors Hare's `stream_unread`
|
||||
// (ref/hare/bufio/stream.ha:164).
|
||||
// returns it first. The bytes must fit in front of the pending region
|
||||
// (rstart >= buf.len); Hare aborts on overflow, ww does the same via
|
||||
// rtabort. Mirrors ref/hare/bufio/stream.ha:164.
|
||||
export fn unread(b: *stream, buf: []u8) void = {
|
||||
if (b.rstart < buf.len) {
|
||||
rtabort("bufio.unread: more data than rbuf has room for");
|
||||
@@ -330,35 +161,43 @@ export fn unread(b: *stream, buf: []u8) void = {
|
||||
b.rstart -= buf.len;
|
||||
};
|
||||
|
||||
// isbuffered — true when `s` is a [[bufio.stream]]'s embedded
|
||||
// vtable. Hare's discriminator is callback identity
|
||||
// (ref/hare/bufio/stream.ha:179); we match the shape directly. The
|
||||
// read or write callback being bread / bwrite is sufficient (close
|
||||
// alone isn't unique to bufio).
|
||||
export fn isbuffered(s: *io.stream) bool = {
|
||||
if (s.read == bread) { return true; };
|
||||
if (s.write == bwrite) { return true; };
|
||||
// isbuffered — true when `s` was returned by [[init]]. Hare's
|
||||
// discriminator is callback identity (ref/hare/bufio/stream.ha:179).
|
||||
// Either reader or writer matching is sufficient.
|
||||
export fn isbuffered(s: io.stream) bool = {
|
||||
match (s.reader) {
|
||||
case let r: *io.reader => {
|
||||
if (r == (&bread): *io.reader) { return true; };
|
||||
};
|
||||
case void => { };
|
||||
};
|
||||
match (s.writer) {
|
||||
case let w: *io.writer => {
|
||||
if (w == (&bwrite): *io.writer) { return true; };
|
||||
};
|
||||
case void => { };
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// ---- vtable callbacks ------------------------------------------------
|
||||
// ---- buffered-stream vtable callbacks ------------------------------------
|
||||
|
||||
fn bread(s: *io.stream, buf: []u8) (i32 | io.eof | io.closed) = {
|
||||
let b: *stream = s.ctx: *stream;
|
||||
// No read buffer configured: surface eof immediately rather
|
||||
// than punch through to src (callers asked for a write-only
|
||||
// stream, reads against it are a misuse).
|
||||
if (b.rbuf.len == 0) { let e: io.eof; return e; };
|
||||
// Empty pending region: refill from src. Reset rstart so
|
||||
// unread has the full buffer to push back into.
|
||||
// bread — buffered read. Recover stream via the intrusive cast; refill
|
||||
// rbuf from src via io.read on empty pending region. Mirrors
|
||||
// ref/hare/bufio/stream.ha (stream_read).
|
||||
fn bread(s: io.stream, buf: []u8) (size | io.eof | io.error) = {
|
||||
let b: *stream = s: *stream;
|
||||
if (b.rbuf.len == 0) {
|
||||
let e: io.eof; return e;
|
||||
};
|
||||
if (b.rstart >= b.rend) {
|
||||
b.rstart = 0;
|
||||
b.rend = 0;
|
||||
let r: (i32 | io.eof | io.closed) = io.read(b.src, b.rbuf);
|
||||
let r: (size | io.eof | io.error) = io.read(b.src, b.rbuf);
|
||||
match (r) {
|
||||
case let n: i32 => { b.rend = n; };
|
||||
case let n: size => { b.rend = n: i32; };
|
||||
case io.eof => { let e: io.eof; return e; };
|
||||
case io.closed => { let e: io.closed; return e; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
};
|
||||
let avail: i32 = b.rend - b.rstart;
|
||||
@@ -370,17 +209,18 @@ fn bread(s: *io.stream, buf: []u8) (i32 | io.eof | io.closed) = {
|
||||
i += 1;
|
||||
};
|
||||
b.rstart += n;
|
||||
return n;
|
||||
return n: size;
|
||||
};
|
||||
|
||||
fn bwrite(s: *io.stream, buf: []u8) (i32 | io.closed) = {
|
||||
let b: *stream = s.ctx: *stream;
|
||||
// No write buffer configured: pass through to src directly.
|
||||
if (b.wbuf.len == 0) { return io.write(b.src, buf); };
|
||||
// Scan the write payload for any byte present in b.flush — a
|
||||
// hit triggers a post-copy flush. Hare uses a labeled break to
|
||||
// exit both loops on first hit (stream.ha:236-246); ww has no
|
||||
// labeled break, so we bump both indices past their bounds.
|
||||
// bwrite — buffered write. wbuf-empty passes through to src; otherwise
|
||||
// default-flush scan + per-batch copy + post-write conditional flush.
|
||||
// Labeled-break inlined (ww has no labeled break). Mirrors
|
||||
// ref/hare/bufio/stream.ha (stream_write).
|
||||
fn bwrite(s: io.stream, buf: []u8) (size | io.error) = {
|
||||
let b: *stream = s: *stream;
|
||||
if (b.wbuf.len == 0) {
|
||||
return io.write(b.src, buf);
|
||||
};
|
||||
let doflush: bool = false;
|
||||
if (b.flush.len != 0) {
|
||||
let i: i32 = 0;
|
||||
@@ -401,10 +241,10 @@ fn bwrite(s: *io.stream, buf: []u8) (i32 | io.closed) = {
|
||||
for (z < buf.len) {
|
||||
let avail: i32 = b.wbuf.len - b.wend;
|
||||
if (avail == 0) {
|
||||
let r: (void | io.closed) = flush(b);
|
||||
match (r) {
|
||||
let fr: (void | io.error) = flush(b);
|
||||
match (fr) {
|
||||
case void => { };
|
||||
case io.closed => { let e: io.closed; return e; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
avail = b.wbuf.len;
|
||||
};
|
||||
@@ -419,21 +259,162 @@ fn bwrite(s: *io.stream, buf: []u8) (i32 | io.closed) = {
|
||||
z += n;
|
||||
};
|
||||
if (doflush) {
|
||||
let r: (void | io.closed) = flush(b);
|
||||
match (r) {
|
||||
let fr: (void | io.error) = flush(b);
|
||||
match (fr) {
|
||||
case void => { };
|
||||
case io.closed => { let e: io.closed; return e; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
};
|
||||
return buf.len;
|
||||
return buf.len: size;
|
||||
};
|
||||
|
||||
fn bclose(s: *io.stream) (void | io.closed) = {
|
||||
let b: *stream = s.ctx: *stream;
|
||||
let r: (void | io.closed) = flush(b);
|
||||
match (r) {
|
||||
// bclose — flush pending wbuf, forward close to src via io.close.
|
||||
// Caller-owned buffers/src are not freed (drew defer-handle deferral).
|
||||
fn bclose(s: io.stream) (void | io.error) = {
|
||||
let b: *stream = s: *stream;
|
||||
let fr: (void | io.error) = flush(b);
|
||||
match (fr) {
|
||||
case void => { };
|
||||
case io.closed => { let e: io.closed; return e; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
let cr: (void | io.error) = io.close(b.src);
|
||||
match (cr) {
|
||||
case void => return void;
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
};
|
||||
|
||||
// ---- scanner (read-ahead tokenizer over an io.stream src) ----------------
|
||||
//
|
||||
// Plain tokenizer — no embedded vtable; driven directly via scanbyte /
|
||||
// scanbytes / scanline, not through io dispatch. Its `src` is an
|
||||
// io.stream and refills go through io.read. Value-return constructor
|
||||
// (Hare newscanner_buf, scanner.ha:92).
|
||||
//
|
||||
// ptr/cap kept flat (no `buf: []u8`); the scanner predates struct-held
|
||||
// slice support.
|
||||
|
||||
export type scanner = struct {
|
||||
src: io.stream,
|
||||
ptr: *u8,
|
||||
cap: i32,
|
||||
start: i32, // index where the pending region starts in ptr
|
||||
avail: i32, // pending byte count; pending = ptr[start..start+avail]
|
||||
};
|
||||
|
||||
// newscannerbuf — wire a scanner to read through `src` using `buf` as
|
||||
// the fixed read-ahead window. Returns the scanner BY VALUE. This is
|
||||
// Hare's newscanner_buf (ref/hare/bufio/scanner.ha:92); the auto-grow
|
||||
// newscanner is a separate feature (#217).
|
||||
export fn newscannerbuf(src: io.stream, buf: []u8) scanner = {
|
||||
let r: scanner;
|
||||
r.src = src;
|
||||
r.ptr = buf.ptr;
|
||||
r.cap = buf.len;
|
||||
r.start = 0;
|
||||
r.avail = 0;
|
||||
return r;
|
||||
};
|
||||
|
||||
// finish — release scanner-owned resources. No-op (buffer is
|
||||
// caller-owned, src isn't closed); kept on the surface so callers won't
|
||||
// churn. Mirrors ref/hare/bufio/scanner.ha:110.
|
||||
export fn finish(s: *scanner) void = { };
|
||||
|
||||
// readahead — make room and read once from src into the back of the
|
||||
// pending region. Returns bytes newly buffered (>=0), or io.eof/io.error
|
||||
// from src. The size from io.read narrows to i32 (buffer-length type).
|
||||
fn readahead(s: *scanner) (i32 | io.eof | io.error) = {
|
||||
if (s.start + s.avail == s.cap && s.start > 0) {
|
||||
let i: i32 = 0;
|
||||
for (i < s.avail) {
|
||||
s.ptr[i] = s.ptr[s.start + i];
|
||||
i += 1;
|
||||
};
|
||||
s.start = 0;
|
||||
};
|
||||
let off: i32 = s.start + s.avail;
|
||||
let v: []u8;
|
||||
v.ptr = s.ptr + (off: u64);
|
||||
v.len = s.cap - off;
|
||||
let r: (size | io.eof | io.error) = io.read(s.src, v);
|
||||
match (r) {
|
||||
case let n: size => {
|
||||
s.avail += n: i32;
|
||||
return n: i32;
|
||||
};
|
||||
case io.eof => { let e: io.eof; return e; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
};
|
||||
|
||||
// scanbyte — pop one byte, refilling from src on demand. Mirrors
|
||||
// ref/hare/bufio/scanner.ha:204.
|
||||
export fn scanbyte(s: *scanner) (u8 | io.eof | io.error) = {
|
||||
for (s.avail == 0) {
|
||||
let r: (i32 | io.eof | io.error) = readahead(s);
|
||||
match (r) {
|
||||
case let n: i32 => { };
|
||||
case io.eof => { let e: io.eof; return e; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
};
|
||||
let b: u8 = s.ptr[s.start];
|
||||
s.start += 1;
|
||||
s.avail -= 1;
|
||||
return b;
|
||||
};
|
||||
|
||||
// scanbytes — read up to (and not including) the next byte equal to
|
||||
// `delim`. The delim is consumed but not returned. The returned slice
|
||||
// borrows from the scanner buffer and is invalidated by the next scan.
|
||||
// EOF without delim discards the trailing fragment and returns io.eof
|
||||
// (Hare EOF_DISCARD default); buffer-full without delim returns
|
||||
// overflow. Single-byte delim only — Hare's `(u8 | []u8)` multibyte
|
||||
// form is #217. Mirrors ref/hare/bufio/scanner.ha:220 (narrowed).
|
||||
export fn scanbytes(s: *scanner, delim: u8) ([]u8 | io.eof | io.error | overflow) = {
|
||||
let i: i32 = 0;
|
||||
for (true) {
|
||||
for (i < s.avail) {
|
||||
if (s.ptr[s.start + i] == delim) {
|
||||
let v: []u8;
|
||||
v.ptr = s.ptr + (s.start: u64);
|
||||
v.len = i;
|
||||
s.start += i + 1;
|
||||
s.avail -= i + 1;
|
||||
return v;
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
if (s.start + s.avail == s.cap && s.start == 0) {
|
||||
let e: overflow; return e;
|
||||
};
|
||||
let r: (i32 | io.eof | io.error) = readahead(s);
|
||||
match (r) {
|
||||
case let n: i32 => { };
|
||||
case io.eof => { let e: io.eof; return e; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
};
|
||||
let e: io.eof; return e;
|
||||
};
|
||||
|
||||
// scanline — read up to (and not including) the next '\n'. The newline
|
||||
// is consumed; the returned str view borrows from the scanner buffer.
|
||||
// Single-byte route (Hare's scan_line = scan_string(s, "\n"); the
|
||||
// arbitrary multibyte-delim scan_string is #217). Mirrors
|
||||
// ref/hare/bufio/scanner.ha:307.
|
||||
export fn scanline(s: *scanner) (str | io.eof | io.error | overflow) = {
|
||||
let r: ([]u8 | io.eof | io.error | overflow) = scanbytes(s, 10u8);
|
||||
match (r) {
|
||||
case let bs: []u8 => {
|
||||
let v: str;
|
||||
v.ptr = bs.ptr;
|
||||
v.len = bs.len;
|
||||
return v;
|
||||
};
|
||||
case io.eof => { let e: io.eof; return e; };
|
||||
case let e: io.error => return e;
|
||||
case overflow => { let e: overflow; return e; };
|
||||
};
|
||||
return io.close(b.src);
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -1,429 +0,0 @@
|
||||
// vstream — Hare-shaped vtable port of lib/bufio. Project #94
|
||||
// fold-eFinal(PREP).
|
||||
//
|
||||
// Collapses the OLD bufio surface (bufio.ww's pre-vtable `stream` +
|
||||
// `scanner` over a legacy `*io.stream` src) onto the io.vtable surface,
|
||||
// as parallel `_v`-suffixed additions. The OLD surface stays untouched
|
||||
// here — fold-eFinal (FLIP) deletes it, renames the `_v` suffix off
|
||||
// (bufio_ctx→stream, bufio_vstream→init, scanner_v→scanner, scanbytes_v
|
||||
// →scanbytes, isbuffered_v→isbuffered, …) per Hare's
|
||||
// ref/hare/bufio/{stream,scanner}.ha, and migrates the few callers.
|
||||
//
|
||||
// SCOPE (#94 fold-eFinal): this is a COLLAPSE of the surface ww ALREADY
|
||||
// ships, NOT a port of Hare's full scanner. The features OLD bufio
|
||||
// never implemented — auto-grow newscanner, multibyte-delim scanbytes /
|
||||
// scanstring, scanrune / readbyte / readtok / readline / readrune /
|
||||
// unreadrune — are a separate future feature fold (#217) and are OUT of
|
||||
// eFinal. The ported set mirrors OLD exactly: the fixed-buffer
|
||||
// newscanner_buf, single-byte scanbytes (OLD scantok), single-byte
|
||||
// scanline, scanbyte, finish; and the buffered-stream init / setflush /
|
||||
// flush / unread / isbuffered.
|
||||
//
|
||||
// VALUE-RETURN (drew, gating): Hare's bufio constructors return BY
|
||||
// VALUE (ref/hare/bufio/stream.ha:69 init, scanner.ha:92
|
||||
// newscanner_buf). The OLD out-param shape existed only because
|
||||
// wide-struct sret was unimplemented — it now round-trips
|
||||
// (test/wcc/925 + 776/778 byte-id), so each constructor builds in a
|
||||
// local `let r: T;`, field-assigns every slot, and `return r;`. The
|
||||
// caller owns the returned struct (stack ownership, no-GC). For the
|
||||
// buffered stream the caller passes `&b.vt` to the io.st_* dispatchers;
|
||||
// the scanner is a plain read-ahead tokenizer (no embedded vtable —
|
||||
// matching OLD), driven directly via scanbyte_v / scanbytes_v / …
|
||||
//
|
||||
// VTABLE-NATIVE SRC (the eFinal point): the buffered stream's `src` and
|
||||
// the scanner's `src` are now `io.vstream`, and every underlying read /
|
||||
// write / close goes through io.st_read / io.st_write / io.st_close
|
||||
// (the vtable dispatchers) — NOT the legacy io.read / io.write /
|
||||
// io.close over `*io.stream`. The io.st_* surface returns io.error
|
||||
// directly, so the OLD `io.closed → nomem-widen → io.error` dance
|
||||
// (per the #173 deferral) is GONE: the error simply forwards.
|
||||
//
|
||||
// Cast workaround per #206-payoff (ken: KEEP the explicit casts; they
|
||||
// are cgen-neutral and sidestep the #214 over-acceptance surface).
|
||||
// `(&fn_name): *io.<role>` at each vtable store + the isbuffered_v
|
||||
// fn-ptr-equality comparand — same workaround memio/vstream.ww +
|
||||
// test/wcc/775 use.
|
||||
//
|
||||
// #207 (struct-lit multi-tagged-field copy drops past first) and #210
|
||||
// (slice-field struct-lit drop under alloc): not reachable from the
|
||||
// value-return form — every struct is built field-by-field in a local
|
||||
// then sret-returned, no `alloc(T{...})` struct-lit. Both filings stay
|
||||
// open for the struct-lit path; this fold doesn't take it.
|
||||
//
|
||||
// #209 (wwstage formattable match-arm bail when fmt imported): bufio
|
||||
// does NOT import fmt (only io), so test/wcc/778 runs both stages +
|
||||
// cs.s == ww.s byte-id on every row — bufio's only byte-id coverage
|
||||
// (it is not compiler-embedded).
|
||||
//
|
||||
// Mode discrimination (drew-deferred): Hare uses three vtable
|
||||
// singletons (vtable_r / vtable_w / vtable_rw); bufio_vstream installs
|
||||
// all three callbacks per bufio.ww:32-38's divergence (zero-length
|
||||
// rbuf/wbuf degenerates the matching callback in-cb). Defer-handle
|
||||
// (MANAGED_* ownership bits) also deferred — caller owns rbuf/wbuf/src;
|
||||
// bclose_v flushes + forwards close but frees nothing. Both graduate
|
||||
// with io fold-2 (#5).
|
||||
|
||||
package bufio;
|
||||
|
||||
import io;
|
||||
|
||||
// bufio_ctx — heap-free state for bufio_vstream. `vt` at offset 0 for
|
||||
// the intrusive vstream→*bufio_ctx cast. `src` is an io.vstream (the
|
||||
// vtable-native underlying handle), not the OLD `*io.stream`. Mirrors
|
||||
// lib/bufio.stream (bufio.ww:252) modulo io.vtable + io.vstream src.
|
||||
export type bufio_ctx = struct {
|
||||
vt: io.vtable,
|
||||
src: io.vstream,
|
||||
rbuf: []u8,
|
||||
rstart: i32,
|
||||
rend: i32,
|
||||
wbuf: []u8,
|
||||
wend: i32,
|
||||
flush: []u8,
|
||||
};
|
||||
|
||||
// bufio_vstream — wire a buffered stream over an underlying io.vstream
|
||||
// with caller-supplied read/write buffers. Both rbuf and wbuf may be
|
||||
// empty; bread_v / bwrite_v degenerate per bufio.ww:34-38. The flush
|
||||
// byte-set defaults to "\n" (line-buffered writes); setflush_v swaps it.
|
||||
//
|
||||
// Returns the ctx BY VALUE (sret — field-by-field build then
|
||||
// `return r;`); the caller passes `&b.vt` to the io.st_* dispatchers.
|
||||
//
|
||||
// Mirrors ref/hare/bufio/stream.ha:69 (init).
|
||||
export fn bufio_vstream(src: io.vstream, rbuf: []u8, wbuf: []u8) bufio_ctx = {
|
||||
let r: bufio_ctx;
|
||||
r.vt.reader = (&bread_v): *io.reader;
|
||||
r.vt.writer = (&bwrite_v): *io.writer;
|
||||
r.vt.closer = (&bclose_v): *io.closer;
|
||||
r.src = src;
|
||||
r.rbuf = rbuf;
|
||||
r.rstart = rbuf.len;
|
||||
r.rend = rbuf.len;
|
||||
r.wbuf = wbuf;
|
||||
r.wend = 0;
|
||||
r.flush = flushdefault[0:1];
|
||||
return r;
|
||||
};
|
||||
|
||||
// setflush_v — install a new flush byte-set. Any byte from `bs`
|
||||
// appearing in a write payload triggers an automatic flush after the
|
||||
// write copies into wbuf. Mirrors OLD setflush (bufio.ww:287) +
|
||||
// ref/hare/bufio/stream.ha:128.
|
||||
export fn setflush_v(b: *bufio_ctx, bs: []u8) void = {
|
||||
b.flush = bs;
|
||||
};
|
||||
|
||||
// flush_v — drain any pending wbuf data to src. Public API AND the
|
||||
// internal drain called by bwrite_v / bclose_v — OLD bufio.flush is
|
||||
// itself dual-purpose (bufio.ww:294, called from bwrite/bclose), so a
|
||||
// single fn serves both; no separate private callback to rename. The
|
||||
// io.closed → nomem-widen of the OLD V-side is gone: io.st_write
|
||||
// returns io.error directly. A 0-byte non-error write means the sink
|
||||
// made no progress (memio.fixed full) — surfaced as a nomem-carried
|
||||
// io.error rather than spinning, matching what io.writeall would do in
|
||||
// Hare. Mirrors OLD flush (bufio.ww:294) + ref/hare/bufio/stream.ha.
|
||||
export fn flush_v(b: *bufio_ctx) (void | io.error) = {
|
||||
if (b.wend == 0) { return; };
|
||||
let off: i32 = 0;
|
||||
for (off < b.wend) {
|
||||
let r: (size | io.error) = io.st_write(b.src, b.wbuf[off:b.wend]);
|
||||
match (r) {
|
||||
case let n: size => {
|
||||
if (n == 0: size) {
|
||||
let nm: nomem;
|
||||
let e: io.error = nm;
|
||||
return e;
|
||||
};
|
||||
off += n: i32;
|
||||
};
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
};
|
||||
b.wend = 0;
|
||||
return;
|
||||
};
|
||||
|
||||
// unread_v — push `buf` back into the read buffer so the next bread_v
|
||||
// returns it first. The bytes must fit in front of the pending region
|
||||
// (rstart >= buf.len); Hare aborts on overflow, ww does the same via
|
||||
// rtabort (declared in bufio.ww, same package). Mirrors OLD unread
|
||||
// (bufio.ww:321) + ref/hare/bufio/stream.ha:164.
|
||||
export fn unread_v(b: *bufio_ctx, buf: []u8) void = {
|
||||
if (b.rstart < buf.len) {
|
||||
rtabort("bufio.unread_v: more data than rbuf has room for");
|
||||
};
|
||||
let i: i32 = 0;
|
||||
for (i < buf.len) {
|
||||
b.rbuf[b.rstart - buf.len + i] = buf[i];
|
||||
i += 1;
|
||||
};
|
||||
b.rstart -= buf.len;
|
||||
};
|
||||
|
||||
// isbuffered_v — true when `s` was returned by [[bufio_vstream]].
|
||||
// Hare uses callback-identity (ref/hare/bufio/stream.ha:179); matches
|
||||
// the OLD isbuffered shape (bufio.ww:338) over the new `_v` symbols.
|
||||
// Either reader or writer matching is sufficient.
|
||||
export fn isbuffered_v(s: io.vstream) bool = {
|
||||
match (s.reader) {
|
||||
case let r: *io.reader => {
|
||||
if (r == (&bread_v): *io.reader) { return true; };
|
||||
};
|
||||
case void => { };
|
||||
};
|
||||
match (s.writer) {
|
||||
case let w: *io.writer => {
|
||||
if (w == (&bwrite_v): *io.writer) { return true; };
|
||||
};
|
||||
case void => { };
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// ---- buffered-stream vtable callbacks ------------------------------------
|
||||
|
||||
// bread_v — vstream-side buffered read. Recover ctx via the intrusive
|
||||
// cast; refill rbuf from src via io.st_read on empty pending region.
|
||||
// Mirrors bufio.ww:346 (OLD bread) modulo the io.vstream src +
|
||||
// io.st_read dispatch (io.error forwards directly — no nomem-widen).
|
||||
fn bread_v(s: io.vstream, buf: []u8) (size | io.eof | io.error) = {
|
||||
let b: *bufio_ctx = s: *bufio_ctx;
|
||||
if (b.rbuf.len == 0) {
|
||||
let e: io.eof; return e;
|
||||
};
|
||||
if (b.rstart >= b.rend) {
|
||||
b.rstart = 0;
|
||||
b.rend = 0;
|
||||
let r: (size | io.eof | io.error) = io.st_read(b.src, b.rbuf);
|
||||
match (r) {
|
||||
case let n: size => { b.rend = n: i32; };
|
||||
case io.eof => { let e: io.eof; return e; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
};
|
||||
let avail: i32 = b.rend - b.rstart;
|
||||
let n: i32 = buf.len;
|
||||
if (avail < n) { n = avail; };
|
||||
let i: i32 = 0;
|
||||
for (i < n) {
|
||||
buf[i] = b.rbuf[b.rstart + i];
|
||||
i += 1;
|
||||
};
|
||||
b.rstart += n;
|
||||
return n: size;
|
||||
};
|
||||
|
||||
// bwrite_v — vstream-side buffered write. wbuf-empty passes through to
|
||||
// src; otherwise default-flush scan + per-batch copy + post-write
|
||||
// conditional flush. Mirrors bufio.ww:376 (OLD bwrite) modulo the
|
||||
// io.vstream src + io.st_write dispatch. Labeled-break inlined per
|
||||
// bufio.ww:382 (ww has no labeled break).
|
||||
fn bwrite_v(s: io.vstream, buf: []u8) (size | io.error) = {
|
||||
let b: *bufio_ctx = s: *bufio_ctx;
|
||||
if (b.wbuf.len == 0) {
|
||||
return io.st_write(b.src, buf);
|
||||
};
|
||||
let doflush: bool = false;
|
||||
if (b.flush.len != 0) {
|
||||
let i: i32 = 0;
|
||||
for (i < buf.len) {
|
||||
let j: i32 = 0;
|
||||
for (j < b.flush.len) {
|
||||
if (buf[i] == b.flush[j]) {
|
||||
doflush = true;
|
||||
i = buf.len;
|
||||
j = b.flush.len;
|
||||
};
|
||||
j += 1;
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
};
|
||||
let z: i32 = 0;
|
||||
for (z < buf.len) {
|
||||
let avail: i32 = b.wbuf.len - b.wend;
|
||||
if (avail == 0) {
|
||||
let fr: (void | io.error) = flush_v(b);
|
||||
match (fr) {
|
||||
case void => { };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
avail = b.wbuf.len;
|
||||
};
|
||||
let n: i32 = buf.len - z;
|
||||
if (avail < n) { n = avail; };
|
||||
let i: i32 = 0;
|
||||
for (i < n) {
|
||||
b.wbuf[b.wend + i] = buf[z + i];
|
||||
i += 1;
|
||||
};
|
||||
b.wend += n;
|
||||
z += n;
|
||||
};
|
||||
if (doflush) {
|
||||
let fr: (void | io.error) = flush_v(b);
|
||||
match (fr) {
|
||||
case void => { };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
};
|
||||
return buf.len: size;
|
||||
};
|
||||
|
||||
// bclose_v — flush pending wbuf, forward close to src via io.st_close.
|
||||
// Mirrors bufio.ww:431. Caller-owned buffers/src are not freed (drew
|
||||
// defer-handle deferral, bufio.ww:33-47).
|
||||
fn bclose_v(s: io.vstream) (void | io.error) = {
|
||||
let b: *bufio_ctx = s: *bufio_ctx;
|
||||
let fr: (void | io.error) = flush_v(b);
|
||||
match (fr) {
|
||||
case void => { };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
let cr: (void | io.error) = io.st_close(b.src);
|
||||
match (cr) {
|
||||
case void => return void;
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
};
|
||||
|
||||
// ---- scanner (read-ahead tokenizer over an io.vstream src) ---------------
|
||||
//
|
||||
// Plain tokenizer — no embedded vtable (matching OLD bufio.scanner,
|
||||
// bufio.ww:104); the scanner is driven directly via scanbyte_v /
|
||||
// scanbytes_v / scanline_v, not through io.st_*. Its `src` is an
|
||||
// io.vstream and refills go through io.st_read. Value-return
|
||||
// constructor (Hare newscanner_buf, scanner.ha:92).
|
||||
//
|
||||
// ptr/cap kept flat (no `buf: []u8`) per the OLD scanner shape
|
||||
// (bufio.ww:104-110); the scanner predates struct-held slice support.
|
||||
|
||||
export type scanner_v = struct {
|
||||
src: io.vstream,
|
||||
ptr: *u8,
|
||||
cap: i32,
|
||||
start: i32, // index where the pending region starts in ptr
|
||||
avail: i32, // pending byte count; pending = ptr[start..start+avail]
|
||||
};
|
||||
|
||||
// newscannerbuf_v — wire a scanner to read through `src` using `buf` as
|
||||
// the fixed read-ahead window. Returns the scanner BY VALUE. This is
|
||||
// Hare's newscanner_buf (ref/hare/bufio/scanner.ha:92); the auto-grow
|
||||
// newscanner is a separate feature (#217, OUT of eFinal). Mirrors OLD
|
||||
// newscanner (bufio.ww:116), which is itself the fixed-buffer form.
|
||||
export fn newscannerbuf_v(src: io.vstream, buf: []u8) scanner_v = {
|
||||
let r: scanner_v;
|
||||
r.src = src;
|
||||
r.ptr = buf.ptr;
|
||||
r.cap = buf.len;
|
||||
r.start = 0;
|
||||
r.avail = 0;
|
||||
return r;
|
||||
};
|
||||
|
||||
// finish_v — release scanner-owned resources. No-op (buffer is
|
||||
// caller-owned, src isn't closed); kept on the surface so callers won't
|
||||
// churn. Mirrors OLD finish (bufio.ww:127) + scanner.ha:110.
|
||||
export fn finish_v(s: *scanner_v) void = { };
|
||||
|
||||
// readahead_v — make room and read once from src into the back of the
|
||||
// pending region. Returns bytes newly buffered (>=0), or io.eof/io.error
|
||||
// from src. Mirrors OLD readahead (bufio.ww:133) modulo io.st_read +
|
||||
// io.error (no nomem-widen). Returns i32 (OLD shape); the size from
|
||||
// io.st_read narrows to i32 (buffer-length type).
|
||||
fn readahead_v(s: *scanner_v) (i32 | io.eof | io.error) = {
|
||||
if (s.start + s.avail == s.cap && s.start > 0) {
|
||||
let i: i32 = 0;
|
||||
for (i < s.avail) {
|
||||
s.ptr[i] = s.ptr[s.start + i];
|
||||
i += 1;
|
||||
};
|
||||
s.start = 0;
|
||||
};
|
||||
let off: i32 = s.start + s.avail;
|
||||
let v: []u8;
|
||||
v.ptr = s.ptr + (off: u64);
|
||||
v.len = s.cap - off;
|
||||
let r: (size | io.eof | io.error) = io.st_read(s.src, v);
|
||||
match (r) {
|
||||
case let n: size => {
|
||||
s.avail += n: i32;
|
||||
return n: i32;
|
||||
};
|
||||
case io.eof => { let e: io.eof; return e; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
};
|
||||
|
||||
// scanbyte_v — pop one byte, refilling from src on demand. Mirrors OLD
|
||||
// scanbyte (bufio.ww:160) + ref/hare/bufio/scanner.ha:204.
|
||||
export fn scanbyte_v(s: *scanner_v) (u8 | io.eof | io.error) = {
|
||||
for (s.avail == 0) {
|
||||
let r: (i32 | io.eof | io.error) = readahead_v(s);
|
||||
match (r) {
|
||||
case let n: i32 => { };
|
||||
case io.eof => { let e: io.eof; return e; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
};
|
||||
let b: u8 = s.ptr[s.start];
|
||||
s.start += 1;
|
||||
s.avail -= 1;
|
||||
return b;
|
||||
};
|
||||
|
||||
// scanbytes_v — read up to (and not including) the next byte equal to
|
||||
// `delim`. The delim is consumed but not returned. The returned slice
|
||||
// borrows from the scanner buffer and is invalidated by the next scan.
|
||||
// EOF without delim discards the trailing fragment and returns io.eof
|
||||
// (Hare EOF_DISCARD default); buffer-full without delim returns
|
||||
// overflow. Single-byte delim only — Hare's `(u8 | []u8)` multibyte
|
||||
// form is #217 (OUT). drew renames OLD scantok → scanbytes. Mirrors OLD
|
||||
// scantok (bufio.ww:186) + ref/hare/bufio/scanner.ha:220 (narrowed).
|
||||
export fn scanbytes_v(s: *scanner_v, delim: u8) ([]u8 | io.eof | io.error | overflow) = {
|
||||
let i: i32 = 0;
|
||||
for (true) {
|
||||
for (i < s.avail) {
|
||||
if (s.ptr[s.start + i] == delim) {
|
||||
let v: []u8;
|
||||
v.ptr = s.ptr + (s.start: u64);
|
||||
v.len = i;
|
||||
s.start += i + 1;
|
||||
s.avail -= i + 1;
|
||||
return v;
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
if (s.start + s.avail == s.cap && s.start == 0) {
|
||||
let e: overflow; return e;
|
||||
};
|
||||
let r: (i32 | io.eof | io.error) = readahead_v(s);
|
||||
match (r) {
|
||||
case let n: i32 => { };
|
||||
case io.eof => { let e: io.eof; return e; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
};
|
||||
let e: io.eof; return e;
|
||||
};
|
||||
|
||||
// scanline_v — read up to (and not including) the next '\n'. The newline
|
||||
// is consumed; the returned str view borrows from the scanner buffer.
|
||||
// Single-byte route (Hare's scan_line = scan_string(s, "\n"); the
|
||||
// arbitrary multibyte-delim scan_string is #217, OUT). Mirrors OLD
|
||||
// scanline (bufio.ww:225) + ref/hare/bufio/scanner.ha:307.
|
||||
export fn scanline_v(s: *scanner_v) (str | io.eof | io.error | overflow) = {
|
||||
let r: ([]u8 | io.eof | io.error | overflow) = scanbytes_v(s, 10u8);
|
||||
match (r) {
|
||||
case let bs: []u8 => {
|
||||
let v: str;
|
||||
v.ptr = bs.ptr;
|
||||
v.len = bs.len;
|
||||
return v;
|
||||
};
|
||||
case io.eof => { let e: io.eof; return e; };
|
||||
case let e: io.error => return e;
|
||||
case overflow => { let e: overflow; return e; };
|
||||
};
|
||||
};
|
||||
905
lib/fmt/fmt.ww
905
lib/fmt/fmt.ww
File diff suppressed because it is too large
Load Diff
@@ -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();
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,667 +0,0 @@
|
||||
// vstream — Hare-shaped vtable wrappers over an fd sink. Project #94
|
||||
// fold-e3 (Option C, parallel API for lib/fmt).
|
||||
//
|
||||
// Adds four wrappers that take a raw fd and dispatch through an
|
||||
// [[io.vstream]] (= `*io.vtable`, the Hare-shape from
|
||||
// lib/io/stream.ww) alongside the pre-vtable fdprint / fdprintln /
|
||||
// fdprintf / fdprintfln in fmt.ww. The OLD surface stays untouched
|
||||
// here — fold-eFinal (task #50) atomically flips the package shape:
|
||||
// deletes the OLD wrappers + callbacks, renames `_v` suffix off, and
|
||||
// migrates the few callers.
|
||||
//
|
||||
// Hare routes fmt's fd sinks through `io::handle = (io::file | int)`
|
||||
// (ref/hare/fmt/wrappers.ha:9-25); ww doesn't have the handle sum yet
|
||||
// so the pure-vstream sink — drew-deferred per fold-d/fold-e3 —
|
||||
// stands in until io fold-2 lands the handle port. fd_ctx is the
|
||||
// minimum carrier: one tagged field (vt) + one i32 (fd). Single-
|
||||
// tagged-field shape lets direct struct-lit init through (sibling
|
||||
// task #207 only bites multi-tagged-field copies); ken's escape-risk
|
||||
// discipline holds — every wrapper stack-allocates fd_ctx inside its
|
||||
// own frame, gets `vs = &c.vt` from the same frame, and dispatches
|
||||
// without returning &c outside it.
|
||||
//
|
||||
// Cast workaround per #206: bare `&fn_name` does not type-check as a
|
||||
// `(*<alias> | void)` field-init / let-binding. Explicit
|
||||
// `(&fn_name): *io.<role>` cast at each store site is the Hare-
|
||||
// faithful minimum-touch route — same workaround memio/vstream.ww
|
||||
// uses (2 cast tokens here at the vtable wire-up site; both drop out
|
||||
// wholesale once #206 closes).
|
||||
//
|
||||
// Dispatch chain (per wrapper):
|
||||
// fd{print,println,printf,printfln}_v
|
||||
// → vfprint / vfprintf (internal vstream-side formatter)
|
||||
// → io.st_write(vs, buf)
|
||||
// → vs.writer (= fdsinkwrite_v after the cast)
|
||||
// → os.write(c.fd, ...)
|
||||
//
|
||||
// The internal vfprint/vfprintf duplicate the per-arg and {n}-
|
||||
// placeholder loops from fmt.ww (fprint at line 177, fprintf at 676)
|
||||
// because the OLD versions take `*io.stream` (the legacy struct) and
|
||||
// fmt.ww must stay UNCHANGED this fold. Shared bits — `i64dec`,
|
||||
// `modsinit`, `scandigits`, `scanmods`, `formattable`, `field`,
|
||||
// `mods`, `fmtabort`, `signof`, `digitsu64`, `basenum`, the modifier
|
||||
// enums (`neg`, `alignment`) — are reused directly from fmt.ww (same
|
||||
// package). The v* modifier-formatting helpers (vrawleni64 / vrawlenstr
|
||||
// / vrawlenf64 / vrawlen / vformatraw / vformatone) mirror the OLD
|
||||
// rawlen* / formatraw / formatone verbatim (compute body identical;
|
||||
// vputbytes + (size | io.error) routing instead of putbytes +
|
||||
// (i32 | io.closed)) — #94 fold-e6 ports them V-side so V's printf
|
||||
// honours modifiers (fold-e3 dropped them after parse). eFinal (#50)
|
||||
// collapses both surfaces.
|
||||
//
|
||||
// Sibling tasks parked here (filed, NOT fixed):
|
||||
//
|
||||
// - io fold-2 (handle port): drew-deferred. Hare's
|
||||
// ref/hare/fmt/wrappers.ha:9-25 routes through io::handle;
|
||||
// fdNNN_v collapses into bare fNNN_v once handle = (file | int)
|
||||
// lands and the V API graduates over the OLD per-fd surface.
|
||||
// - #206 (bare &fn → (*alias|void)): 2 cast sites here; drops
|
||||
// out wholesale on close.
|
||||
// - #173 (TRY-on-tagged-return both-stages broken): fdsinkwrite_v
|
||||
// constructs nomem and widens to io.error explicitly rather
|
||||
// than using `os.trywrite(...)?`; same shape memio.vstream.ww
|
||||
// adopted at line 71-74.
|
||||
|
||||
package fmt;
|
||||
|
||||
import io;
|
||||
import memio;
|
||||
import os;
|
||||
import strconv;
|
||||
import strings;
|
||||
|
||||
// fd_ctx — vt at offset 0 for the intrusive vstream→*fd_ctx cast.
|
||||
// Mirrors lib/memio.fixed_ctx (memio/vstream.ww:48); single tagged
|
||||
// field (vt) means the multi-tagged-field drop in #207 doesn't bite.
|
||||
export type fd_ctx = struct {
|
||||
vt: io.vtable,
|
||||
fd: i32,
|
||||
};
|
||||
|
||||
// fdsinkread_v — eof-only sink; the fd half of fmt is write-only.
|
||||
// Mirrors OLD fdsinkread (fmt.ww:765). The unused-`buf` parameter
|
||||
// matches io.reader's signature.
|
||||
fn fdsinkread_v(s: io.vstream, buf: []u8) (size | io.eof | io.error) = {
|
||||
let e: io.eof;
|
||||
return e;
|
||||
};
|
||||
|
||||
// fdsinkwrite_v — recover fd via intrusive cast, dispatch one
|
||||
// os.write. -errno collapses to a nomem-widened io.error: the OLD
|
||||
// fdsinkwrite (fmt.ww:768) flattens this to io.closed; we keep the
|
||||
// Hare-shape error here because the V surface returns io.error
|
||||
// directly. Construction-then-widen (vs `os.trywrite(...)?`)
|
||||
// sidesteps #173 same as memio.vstream.ww (line 71-74 note).
|
||||
fn fdsinkwrite_v(s: io.vstream, buf: []u8) (size | io.error) = {
|
||||
let c: *fd_ctx = s: *fd_ctx;
|
||||
let r: i64 = os.write(c.fd, buf.ptr, buf.len: u64);
|
||||
if (r < 0) {
|
||||
let nm: nomem;
|
||||
let e: io.error = nm;
|
||||
return e;
|
||||
};
|
||||
return r: size;
|
||||
};
|
||||
|
||||
// ---- internal vstream-side formatters -------------------------------
|
||||
|
||||
// vputbytes — io.st_write(vs, [ptr..ptr+n)). Internal helper; the
|
||||
// inline `let v` slice synthesis mirrors fmt.putbytes (fmt.ww:160)
|
||||
// for the legacy *io.stream sink.
|
||||
fn vputbytes(vs: io.vstream, p: *u8, n: i32) (size | io.error) = {
|
||||
let v: []u8;
|
||||
v.ptr = p;
|
||||
v.len = n;
|
||||
return io.st_write(vs, v);
|
||||
};
|
||||
|
||||
// vwriteone — emit one formattable through io.st_write. Mirror of
|
||||
// the per-arm dispatch in fmt.fdprint (fmt.ww:103-138) and fmt.fprint
|
||||
// (fmt.ww:188-234); shape matches OLD modulo the (size | io.error)
|
||||
// return instead of i64 / (i32 | io.closed).
|
||||
fn vwriteone(vs: io.vstream, a: formattable) (size | io.error) = {
|
||||
match (a) {
|
||||
case let n: i64 => {
|
||||
let s: str = i64dec(n);
|
||||
return vputbytes(vs, s.ptr, s.len);
|
||||
};
|
||||
case let s: str => return vputbytes(vs, s.ptr, s.len);
|
||||
case let b: bool => {
|
||||
let s: str = "false";
|
||||
if (b) { s = "true"; };
|
||||
return vputbytes(vs, s.ptr, s.len);
|
||||
};
|
||||
case let r: rune => {
|
||||
let buf: [4]u8;
|
||||
buf[0] = r: u8;
|
||||
return vputbytes(vs, &buf[0], 1);
|
||||
};
|
||||
case let v: f64 => {
|
||||
// strconv.f64tos static-buffer view consumed before next
|
||||
// strconv call — same shape as fmt.fdprint (fmt.ww:130).
|
||||
let s: str = strconv.f64tos(v);
|
||||
return vputbytes(vs, s.ptr, s.len);
|
||||
};
|
||||
};
|
||||
return 0: size;
|
||||
};
|
||||
|
||||
// ---- V-side modifier formatting (port of OLD rawlen*/formatraw/
|
||||
// formatone from fmt.ww:443-641). Project #94 fold-e6.
|
||||
//
|
||||
// V-side mirror of the OLD modifier-formatting machinery so the V
|
||||
// printf path honours width / alignment / pad / sign / base / prec
|
||||
// instead of dropping mods after parse. Drew-signoff: NaN/Inf safe
|
||||
// — strconv.f64tos / f32tos render "nan"/"infinity" with no leading
|
||||
// '-', so the sign-peel in vrawlenf64 / vformatraw f64 arm is a
|
||||
// no-op on those views (sibling of OLD rawlenf64's same path). Ken-
|
||||
// signoff: cs==ww mechanical — both stages compile the new V-side
|
||||
// identically; eFinal (#50) graduates the V-side over the OLD.
|
||||
//
|
||||
// Helpers mirror fmt.ww verbatim (compute-only, no I/O) rather than
|
||||
// reusing OLD by call — fold-eFinal (#50) atomically deletes the OLD
|
||||
// surface AND collapses these v* names back, so the duplication is
|
||||
// transient. Mirror sites:
|
||||
//
|
||||
// vrawleni64 fmt.ww:443 rawleni64
|
||||
// vrawlenstr fmt.ww:457 rawlenstr
|
||||
// vrawlenf64 fmt.ww:569 rawlenf64
|
||||
// vrawlen fmt.ww:585 rawlen
|
||||
// vformatraw fmt.ww:465 formatraw
|
||||
// vformatone fmt.ww:598 formatone
|
||||
|
||||
// vrawleni64 — bytes the raw render of `v` under `m` would emit; the
|
||||
// signof / digitsu64 / basenum helpers are read from fmt.ww (same
|
||||
// package). Mirror fmt.ww:443.
|
||||
fn vrawleni64(v: i64, m: *mods) i32 = {
|
||||
let neg_flag: bool = v < 0;
|
||||
let u: u64 = v: u64;
|
||||
if (neg_flag) { u = (-v): u64; };
|
||||
let signlen: i32 = 0;
|
||||
if (signof(neg_flag, m) != 0u8) { signlen = 1; };
|
||||
let dlen: i32 = digitsu64(u, basenum(m.base));
|
||||
let inner: i32 = dlen;
|
||||
if (m.prec > signlen + dlen) { inner = m.prec - signlen; };
|
||||
return signlen + inner;
|
||||
};
|
||||
|
||||
// vrawlenstr — bytes the raw render of `s` would emit (after `prec`
|
||||
// truncation). Mirror fmt.ww:457.
|
||||
fn vrawlenstr(s: str, m: *mods) i32 = {
|
||||
if (m.prec > 0 && m.prec < s.len) { return m.prec; };
|
||||
return s.len;
|
||||
};
|
||||
|
||||
// vrawlenf64 — bytes the raw render of `v` under `m` would emit; the
|
||||
// strconv.f64tos static-buffer view is consumed by reading view.len
|
||||
// + view.ptr[0] before the sign byte is folded into signof. Mirror
|
||||
// fmt.ww:569; drew NaN/Inf safe (strconv emits no leading '-' on
|
||||
// nan/inf, so the peel is a no-op on those views).
|
||||
fn vrawlenf64(v: f64, m: *mods) i32 = {
|
||||
let view: str = strconv.f64tos(v);
|
||||
let body: i32 = view.len;
|
||||
let had_neg: bool = false;
|
||||
if (body > 0 && view.ptr[0] == 45u8) {
|
||||
had_neg = true;
|
||||
body -= 1;
|
||||
};
|
||||
let signlen: i32 = 0;
|
||||
if (signof(had_neg, m) != 0u8) { signlen = 1; };
|
||||
return signlen + body;
|
||||
};
|
||||
|
||||
// vrawlen — dispatch the rawlen sum over formattable. Mirror fmt.ww:585.
|
||||
fn vrawlen(arg: formattable, m: *mods) i32 = {
|
||||
match (arg) {
|
||||
case let v: i64 => return vrawleni64(v, m);
|
||||
case let v: str => return vrawlenstr(v, m);
|
||||
case let b: bool => { if (b) { return 4; }; return 5; };
|
||||
case let r: rune => return 1;
|
||||
case let v: f64 => return vrawlenf64(v, m);
|
||||
};
|
||||
return 0; // unreachable — match is exhaustive
|
||||
};
|
||||
|
||||
// vformatraw — write the bare value (no width padding) to `vs`. Mirror
|
||||
// fmt.ww:465 formatraw, vputbytes-routed and (size | io.error)-typed.
|
||||
// `prec` ignored on f64 (strconv.f64tos is shortest-G with no precision
|
||||
// knob); base ignored on f64 (Hare too — base is int-only). Drew-
|
||||
// signoff: NaN/Inf strings emitted unchanged via vputbytes.
|
||||
fn vformatraw(vs: io.vstream, arg: formattable, m: *mods) (size | io.error) = {
|
||||
match (arg) {
|
||||
case let v: i64 => {
|
||||
let neg_flag: bool = v < 0;
|
||||
let u: u64 = v: u64;
|
||||
if (neg_flag) { u = (-v): u64; };
|
||||
let sb: u8 = signof(neg_flag, m);
|
||||
let total: size = 0;
|
||||
if (sb != 0u8) {
|
||||
let buf: [1]u8;
|
||||
buf[0] = sb;
|
||||
let r: (size | io.error) = vputbytes(vs, &buf[0], 1);
|
||||
match (r) {
|
||||
case let n: size => { total += n; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
};
|
||||
let dlen: i32 = digitsu64(u, basenum(m.base));
|
||||
let signlen: i32 = 0;
|
||||
if (sb != 0u8) { signlen = 1; };
|
||||
let pad0: i32 = 0;
|
||||
if (m.prec > signlen + dlen) { pad0 = m.prec - signlen - dlen; };
|
||||
let pi: i32 = 0;
|
||||
for (pi < pad0) {
|
||||
let buf: [1]u8;
|
||||
buf[0] = 48u8; // '0'
|
||||
let r: (size | io.error) = vputbytes(vs, &buf[0], 1);
|
||||
match (r) {
|
||||
case let n: size => { total += n; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
pi += 1;
|
||||
};
|
||||
let view: str = strconv.u64tos(u, m.base);
|
||||
let r: (size | io.error) = vputbytes(vs, view.ptr, view.len);
|
||||
match (r) {
|
||||
case let n: size => { total += n; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
return total;
|
||||
};
|
||||
case let v: str => {
|
||||
let n: i32 = vrawlenstr(v, m);
|
||||
return vputbytes(vs, v.ptr, n);
|
||||
};
|
||||
case let b: bool => {
|
||||
let v: str = "false";
|
||||
if (b) { v = "true"; };
|
||||
return vputbytes(vs, v.ptr, v.len);
|
||||
};
|
||||
case let r: rune => {
|
||||
let buf: [4]u8;
|
||||
buf[0] = r: u8;
|
||||
return vputbytes(vs, &buf[0], 1);
|
||||
};
|
||||
case let v: f64 => {
|
||||
// strconv.f64tos prepends '-' for negative values; peel
|
||||
// here so signof folds neg/plus/space mods uniformly with
|
||||
// the i64 arm. Drew NaN/Inf signoff: nan/infinity views
|
||||
// have no leading '-', so this peel is a no-op for them
|
||||
// (sign-mod on a nan emits e.g. "+nan" — a fmt-layer edge,
|
||||
// not strconv's; same OLD behaviour at fmt.ww:520-562).
|
||||
let view: str = strconv.f64tos(v);
|
||||
let neg_flag: bool = false;
|
||||
if (view.len > 0 && view.ptr[0] == 45u8) { // '-'
|
||||
neg_flag = true;
|
||||
view.ptr = view.ptr + 1u64;
|
||||
view.len -= 1;
|
||||
};
|
||||
let sb: u8 = signof(neg_flag, m);
|
||||
let total: size = 0;
|
||||
if (sb != 0u8) {
|
||||
let buf: [1]u8;
|
||||
buf[0] = sb;
|
||||
let r: (size | io.error) = vputbytes(vs, &buf[0], 1);
|
||||
match (r) {
|
||||
case let n: size => { total += n; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
};
|
||||
let r: (size | io.error) = vputbytes(vs, view.ptr, view.len);
|
||||
match (r) {
|
||||
case let n: size => { total += n; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
return total;
|
||||
};
|
||||
};
|
||||
let z: size = 0; return z; // unreachable — match is exhaustive
|
||||
};
|
||||
|
||||
// vformatone — render `arg` to `vs` with `m`'s width / alignment / pad
|
||||
// applied. Mirror fmt.ww:598 formatone; the tail-pad loop drives on
|
||||
// a counter (mirrors OLD) because vputbytes over memio.fixed reports
|
||||
// a full buffer as a 0-byte partial write, not io.error — looping on
|
||||
// `total < m.width` would spin on bsprintf_v when the sink runs out.
|
||||
fn vformatone(vs: io.vstream, arg: formattable, m: *mods) (size | io.error) = {
|
||||
let start: i32 = 0;
|
||||
if (m.width > 0 && m.alignment != alignment.LEFT) {
|
||||
let raw: i32 = vrawlen(arg, m);
|
||||
let pad: i32 = 0;
|
||||
if (raw < m.width) { pad = m.width - raw; };
|
||||
if (m.alignment == alignment.CENTER) { start = (pad + 1) / 2; }
|
||||
else { start = pad; };
|
||||
};
|
||||
let total: size = 0;
|
||||
let i: i32 = 0;
|
||||
let padb: [1]u8;
|
||||
padb[0] = m.pad: u8;
|
||||
for (i < start) {
|
||||
let r: (size | io.error) = vputbytes(vs, &padb[0], 1);
|
||||
match (r) {
|
||||
case let n: size => { total += n; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
let r1: (size | io.error) = vformatraw(vs, arg, m);
|
||||
match (r1) {
|
||||
case let n: size => { total += n; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
let need: i32 = 0;
|
||||
let twidth: size = m.width: size;
|
||||
if (twidth > total) { need = (twidth - total): i32; };
|
||||
let j: i32 = 0;
|
||||
for (j < need) {
|
||||
let r: (size | io.error) = vputbytes(vs, &padb[0], 1);
|
||||
match (r) {
|
||||
case let n: size => { total += n; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
j += 1;
|
||||
};
|
||||
return total;
|
||||
};
|
||||
|
||||
// vformatfield — field→formattable inline-per-arm dispatch through
|
||||
// vformatone. The for-loop call site in vfprintf would trip task #18
|
||||
// (silent miscompile of 24B return-by-value in for-loop context) if
|
||||
// widened via a helper; inline-per-arm sidesteps it — same shape OLD
|
||||
// formatfield uses at fmt.ww:648. `*mods` arm aborts (parametric '%'
|
||||
// form not implemented; OLD fprintf aborts identically). #94 fold-e6
|
||||
// widens the signature with `*mods` so the V path honours modifiers
|
||||
// (fold-e3 dropped them after parse).
|
||||
fn vformatfield(vs: io.vstream, f: field, m: *mods) (size | io.error) = {
|
||||
match (f) {
|
||||
case let v: i64 => {
|
||||
let a: formattable = v;
|
||||
return vformatone(vs, a, m);
|
||||
};
|
||||
case let v: str => {
|
||||
let a: formattable = v;
|
||||
return vformatone(vs, a, m);
|
||||
};
|
||||
case let b: bool => {
|
||||
let a: formattable = b;
|
||||
return vformatone(vs, a, m);
|
||||
};
|
||||
case let r: rune => {
|
||||
let a: formattable = r;
|
||||
return vformatone(vs, a, m);
|
||||
};
|
||||
case let v: f64 => {
|
||||
let a: formattable = v;
|
||||
return vformatone(vs, a, m);
|
||||
};
|
||||
case let p: *mods => { fmtabort(); let z: size = 0; return z; };
|
||||
};
|
||||
};
|
||||
|
||||
// vfprint — vstream-side fprint. Mirror of fmt.fprint (fmt.ww:177).
|
||||
// Per-arg loop with a space separator; returns total bytes written
|
||||
// or the first io.error. Exported so lib/log's V API (fold-e5) can
|
||||
// dispatch through it; eFinal (#50) collapses fprint over the unified
|
||||
// surface.
|
||||
export fn vfprint(vs: io.vstream, args: formattable...) (size | io.error) = {
|
||||
let total: size = 0;
|
||||
let i: i32 = 0;
|
||||
for (i < args.len) {
|
||||
if (i > 0) {
|
||||
let r: (size | io.error) = vputbytes(vs, " ".ptr, 1);
|
||||
match (r) {
|
||||
case let n: size => { total += n; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
};
|
||||
let r: (size | io.error) = vwriteone(vs, args[i]);
|
||||
match (r) {
|
||||
case let n: size => { total += n; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
return total;
|
||||
};
|
||||
|
||||
// vfprintf — vstream-side fprintf. Mirror of fmt.fprintf (fmt.ww:676).
|
||||
// {n}-placeholder parser routed through vformatfield + vputbytes
|
||||
// instead of fmt.formatfield + fmt.putbytes; the placeholder language
|
||||
// (incl. `{N:mods}` modifier subset) is identical — scandigits /
|
||||
// scanmods / modsinit reused directly from fmt.ww. Exported (same
|
||||
// rationale as vfprint above) so lib/log's V API dispatches through it.
|
||||
export fn vfprintf(vs: io.vstream, fmt: str, args: field...) (size | io.error) = {
|
||||
let total: size = 0;
|
||||
let i: i32 = 0;
|
||||
let nextimpl: i32 = 0;
|
||||
let checkunused: bool = true;
|
||||
for (i < fmt.len) {
|
||||
let c: u8 = fmt[i];
|
||||
if (c == 123u8) { // '{'
|
||||
i += 1;
|
||||
if (i >= fmt.len) { fmtabort(); };
|
||||
if (fmt[i] == 123u8) { // '{{' literal
|
||||
let r: (size | io.error) = vputbytes(vs, fmt.ptr + i: u64, 1);
|
||||
match (r) {
|
||||
case let n: size => { total += n; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
i += 1;
|
||||
} else {
|
||||
let idx: i32 = 0;
|
||||
let d: u8 = fmt[i];
|
||||
if (d >= 48u8 && d <= 57u8) {
|
||||
checkunused = false;
|
||||
idx = scandigits(fmt, &i);
|
||||
} else {
|
||||
idx = nextimpl;
|
||||
nextimpl += 1;
|
||||
};
|
||||
let m: mods;
|
||||
modsinit(&m);
|
||||
if (i < fmt.len && fmt[i] == 58u8) { // ':'
|
||||
i += 1;
|
||||
scanmods(fmt, &i, &m);
|
||||
};
|
||||
if (i >= fmt.len || fmt[i] != 125u8) { fmtabort(); };
|
||||
i += 1;
|
||||
if (idx >= args.len) { fmtabort(); };
|
||||
let r: (size | io.error) = vformatfield(vs, args[idx], &m);
|
||||
match (r) {
|
||||
case let n: size => { total += n; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
};
|
||||
} else if (c == 125u8) { // '}'
|
||||
i += 1;
|
||||
if (i >= fmt.len || fmt[i] != 125u8) { fmtabort(); };
|
||||
let r: (size | io.error) = vputbytes(vs, fmt.ptr + i: u64, 1);
|
||||
match (r) {
|
||||
case let n: size => { total += n; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
i += 1;
|
||||
} else {
|
||||
let r: (size | io.error) = vputbytes(vs, fmt.ptr + i: u64, 1);
|
||||
match (r) {
|
||||
case let n: size => { total += n; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
};
|
||||
if (checkunused && nextimpl != args.len) { fmtabort(); };
|
||||
return total;
|
||||
};
|
||||
|
||||
// ---- public fd-sink wrappers (V API) ---------------------------------
|
||||
|
||||
// fdprint_v — fdprint over io.vstream. Stack-allocates fd_ctx; the
|
||||
// &c.vt vstream pointer never escapes this frame (ken's escape-risk
|
||||
// discipline). Mirrors fmt.fdprint (fmt.ww:94).
|
||||
export fn fdprint_v(fd: i32, args: formattable...) (size | io.error) = {
|
||||
let c: fd_ctx;
|
||||
c.fd = fd;
|
||||
c.vt.reader = (&fdsinkread_v): *io.reader;
|
||||
c.vt.writer = (&fdsinkwrite_v): *io.writer;
|
||||
let vs: io.vstream = &c.vt;
|
||||
return vfprint(vs, args...);
|
||||
};
|
||||
|
||||
// fdprintln_v — fdprint_v + trailing newline. Mirrors fmt.fdprintln
|
||||
// (fmt.ww:145).
|
||||
export fn fdprintln_v(fd: i32, args: formattable...) (size | io.error) = {
|
||||
let c: fd_ctx;
|
||||
c.fd = fd;
|
||||
c.vt.reader = (&fdsinkread_v): *io.reader;
|
||||
c.vt.writer = (&fdsinkwrite_v): *io.writer;
|
||||
let vs: io.vstream = &c.vt;
|
||||
let total: size = 0;
|
||||
match (vfprint(vs, args...)) {
|
||||
case let n: size => { total = n; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
match (vputbytes(vs, "\n".ptr, 1)) {
|
||||
case let n: size => { total += n; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
return total;
|
||||
};
|
||||
|
||||
// fdprintf_v — fdprintf over io.vstream. Mirrors fmt.fdprintf
|
||||
// (fmt.ww:786).
|
||||
export fn fdprintf_v(fd: i32, fmt: str, args: field...) (size | io.error) = {
|
||||
let c: fd_ctx;
|
||||
c.fd = fd;
|
||||
c.vt.reader = (&fdsinkread_v): *io.reader;
|
||||
c.vt.writer = (&fdsinkwrite_v): *io.writer;
|
||||
let vs: io.vstream = &c.vt;
|
||||
return vfprintf(vs, fmt, args...);
|
||||
};
|
||||
|
||||
// fdprintfln_v — fdprintf_v + trailing newline. Mirrors
|
||||
// fmt.fdprintfln (fmt.ww:797) and Hare's wrappers.ha:69.
|
||||
export fn fdprintfln_v(fd: i32, fmt: str, args: field...) (size | io.error) = {
|
||||
let c: fd_ctx;
|
||||
c.fd = fd;
|
||||
c.vt.reader = (&fdsinkread_v): *io.reader;
|
||||
c.vt.writer = (&fdsinkwrite_v): *io.writer;
|
||||
let vs: io.vstream = &c.vt;
|
||||
let total: size = 0;
|
||||
match (vfprintf(vs, fmt, args...)) {
|
||||
case let n: size => { total = n; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
match (vputbytes(vs, "\n".ptr, 1)) {
|
||||
case let n: size => { total += n; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
return total;
|
||||
};
|
||||
|
||||
// ---- V-side compositions over the vfprint / vfprintf primitives.
|
||||
// Project #94 fold-e7. drew-approved bundle (memio.string_v is the
|
||||
// collapsed single accessor over the common `stream` header — direct
|
||||
// enabler, not churn — feedback_refactor_routing_same_class_drops);
|
||||
// ken cs==ww mechanical
|
||||
// (additive only). eFinal (#50) collapses both surfaces and renames
|
||||
// `v` suffix off.
|
||||
//
|
||||
// Mirror sites:
|
||||
// vfprintln fmt.ww:240 fprintln ref/hare/fmt/wrappers.ha:48
|
||||
// vfprintfln fmt.ww:740 fprintfln ref/hare/fmt/wrappers.ha:69
|
||||
// vbsprintf fmt.ww:839 bsprintf ref/hare/fmt/wrappers.ha:42
|
||||
// vasprintf fmt.ww:873 asprintf ref/hare/fmt/wrappers.ha:29
|
||||
|
||||
// vfprintln — vfprint + trailing newline. Mirror fmt.fprintln
|
||||
// (fmt.ww:240); vputbytes + (size | io.error) routing.
|
||||
export fn vfprintln(vs: io.vstream, args: formattable...) (size | io.error) = {
|
||||
let total: size = 0;
|
||||
match (vfprint(vs, args...)) {
|
||||
case let n: size => { total = n; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
match (vputbytes(vs, "\n".ptr, 1)) {
|
||||
case let n: size => { total += n; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
return total;
|
||||
};
|
||||
|
||||
// vfprintfln — vfprintf + trailing newline. Mirror fmt.fprintfln
|
||||
// (fmt.ww:740).
|
||||
export fn vfprintfln(vs: io.vstream, fmt: str, args: field...) (size | io.error) = {
|
||||
let total: size = 0;
|
||||
match (vfprintf(vs, fmt, args...)) {
|
||||
case let n: size => { total = n; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
match (vputbytes(vs, "\n".ptr, 1)) {
|
||||
case let n: size => { total += n; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
return total;
|
||||
};
|
||||
|
||||
// vbsprintf — render into `buf` through memio.fixed_vstream; return
|
||||
// the str view of bytes actually written. Mirror fmt.bsprintf
|
||||
// (fmt.ww:839). memio.fixed_vstream now returns the `stream` BY VALUE
|
||||
// (fold-eFinal PREP, ref/hare/memio/stream.ha:46) — no alloc, no
|
||||
// `nomem` arm. The stream lives in this frame; `&st.vt` is the
|
||||
// io.vstream and `&st` the accessor handle. Hare wrappers.ha:42 returns
|
||||
// `(const str | nomem)`; ww collapses to (str | io.error) so the
|
||||
// vfprintf path's io.error arm stays uniform. Short writes surface as a
|
||||
// prefix (memio.fixed contract, vstream.ww fixedwrite note).
|
||||
export fn vbsprintf(buf: []u8, fmt: str, args: field...) (str | io.error) = {
|
||||
let st: memio.stream = memio.fixed_vstream(buf);
|
||||
let vs: io.vstream = &st.vt;
|
||||
match (vfprintf(vs, fmt, args...)) {
|
||||
case let n: size => { return memio.string_v(&st); };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
};
|
||||
|
||||
// vasprintf — render `fmt`/`args` into a heap-allocated str through
|
||||
// memio.dynamic_vstream, shrink-copy to a tight allocation, close the
|
||||
// dynamic backing. Mirror fmt.asprintf (fmt.ww:873) modulo: bare `str`
|
||||
// return (Hare returns `(str | nomem)`; ww has no `nomem` variant on
|
||||
// the public surface — os.alloc faults on OOM per lib/os.ww). Caller
|
||||
// frees with `os.free(r.ptr, r.len: u64)` when r.len > 0; r.len == 0
|
||||
// is a no-op free (same shape strings.dup uses at strings.ww:70).
|
||||
// Shrink-to-fit rationale: memio.dynamic_vstream's cap doubles past
|
||||
// pos during growth (memio dynamicgrow_v); io.st_close frees the
|
||||
// cap-sized mapping. Returning memio.string_v directly would
|
||||
// leak the cap-vs-len slack (skip close) or dangle the view (close
|
||||
// first); the copy lets the caller free with r.len.
|
||||
export fn vasprintf(fmt: str, args: field...) str = {
|
||||
let out: str;
|
||||
out.ptr = nil;
|
||||
out.len = 0;
|
||||
// memio.dynamic_vstream returns the `stream` BY VALUE (fold-eFinal
|
||||
// PREP, ref/hare/memio/stream.ha:58) — no alloc, no `nomem` arm.
|
||||
// The stream lives in this frame across the vfprintf + close; the
|
||||
// heap-grown backing (st.ptr) is freed by io.st_close.
|
||||
let st: memio.stream = memio.dynamic_vstream();
|
||||
let vs: io.vstream = &st.vt;
|
||||
let wres: (size | io.error) = vfprintf(vs, fmt, args...);
|
||||
match (wres) {
|
||||
case let n: size => {};
|
||||
case let e: io.error => {};
|
||||
};
|
||||
let view: str = memio.string_v(&st);
|
||||
if (view.len == 0) {
|
||||
let cres: (void | io.error) = io.st_close(vs);
|
||||
match (cres) { case void => {}; case let e: io.error => {}; };
|
||||
return out;
|
||||
};
|
||||
let tight: []u8 = alloc([], view.len: u64)!;
|
||||
let i: i32 = 0;
|
||||
for (i < view.len) {
|
||||
tight[i] = view.ptr[i];
|
||||
i += 1;
|
||||
};
|
||||
let cres: (void | io.error) = io.st_close(vs);
|
||||
match (cres) { case void => {}; case let e: io.error => {}; };
|
||||
tight.len = view.len;
|
||||
return strings.frombytes(tight);
|
||||
};
|
||||
46
lib/io/io.ww
46
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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
240
lib/log/log.ww
240
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.<role>` cast at the stderrsink_ctx_g vt wire-up; the
|
||||
// #206 cast-drop is gated on #214. stderrwrite constructs nomem and
|
||||
// widens to io.error explicitly rather than `os.trywrite(...)?` for the
|
||||
// no-handle interim.
|
||||
//
|
||||
// Compiler note: wwstage cgen's variadic dispatch is name-based
|
||||
// (`fnparamslookup(callee.str)`), which would conflate the
|
||||
// `logger.println` vtable field with the top-level [[println]] fn
|
||||
// if both stages compiled this module. cstage uses type-driven
|
||||
// dispatch via the call-node's lhs type and handles fn-pointer
|
||||
// variadic calls correctly. Today lib/log is consumed only by tests
|
||||
// routed through `ww run` (cstage), so this is a latent issue
|
||||
// covered by #11 (wwstage checkfile pass).
|
||||
//
|
||||
// let buf: [128]u8;
|
||||
// let mem: memio.state;
|
||||
// let s: io.stream;
|
||||
// memio.fixed(&mem, &s, buf[0:128]);
|
||||
//
|
||||
// let sl: log.stdlogger;
|
||||
// log.new(&sl, &s);
|
||||
// let sl: log.stdlogger = log.new(&s.vt);
|
||||
// log.lprintln(&sl.logger, "hello", 42i64);
|
||||
// // mem now holds "hello 42\n"
|
||||
//
|
||||
// log.setlogger(&sl.logger);
|
||||
// log.println("through global");
|
||||
//
|
||||
// log.setlogger(log.silent);
|
||||
// log.println("dropped");
|
||||
|
||||
@@ -87,53 +74,55 @@ export type logger = struct {
|
||||
printfln: fn(l: *logger, format: str, fields: fmt.field...) void,
|
||||
};
|
||||
|
||||
// stdlogger — concrete logger forwarding to a `*io.stream` sink.
|
||||
// First-field embed: `&sl.logger` yields a `*logger` (same shape as
|
||||
// bufio.stream over its embedded io.stream vtable). The vtable
|
||||
// callback recovers the outer stdlogger by casting the dispatch arg
|
||||
// back to `*stdlogger`.
|
||||
// stdlogger — concrete logger forwarding to an io.stream sink.
|
||||
// First-field embed: `&sl.logger` yields a `*logger` (intrusive
|
||||
// outer→inner cast); the vtable callback recovers the outer via
|
||||
// `*stdlogger` cast. Mirrors ref/hare/log/logger.ha:18.
|
||||
export type stdlogger = struct {
|
||||
logger: logger,
|
||||
sink: *io.stream,
|
||||
sink: io.stream,
|
||||
};
|
||||
|
||||
// stderrsink — private io.stream wired through fd 2 via [[os.write]].
|
||||
// Used as [[default]]'s sink. Independent of lib/io's future fd-backed
|
||||
// stream — when that lands, this collapses to a thin wrapper and
|
||||
// graduates in one go.
|
||||
let stderrsink: io.stream;
|
||||
// stderrsink_ctx — module-static state for the default logger's stderr
|
||||
// sink. vt FIRST field for the intrusive io.stream→*stderrsink_ctx cast
|
||||
// in stderrwrite. Mirrors fmt.fd_ctx; only scalars/ptrs beyond vt.
|
||||
type stderrsink_ctx = struct {
|
||||
vt: io.vtable,
|
||||
fd: i32,
|
||||
};
|
||||
|
||||
// _silent — backing storage for the [[silent]] global.
|
||||
// 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;
|
||||
|
||||
// _silent / _default — backing storage for the silent / default
|
||||
// *logger globals. Zero-init at link time; ensureinit fills the
|
||||
// println / printfln slots.
|
||||
let _silent: logger;
|
||||
|
||||
// _default — backing storage for the [[default]] global. Its sink
|
||||
// points at [[stderrsink]] after [[ensureinit]] runs.
|
||||
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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -1,303 +0,0 @@
|
||||
// vstream — Hare-shaped vtable wrappers over the logger surface.
|
||||
// Project #94 fold-e5 (Option C, parallel API for lib/log). Last
|
||||
// per-caller migration before fold-eFinal (task #50).
|
||||
//
|
||||
// Adds the ten _v variants of OLD log.ww's surface — new_v /
|
||||
// lprintln_v / println_v / lprintfln_v / printfln_v / lfatal_v /
|
||||
// fatal_v / lfatalf_v / fatalf_v / setlogger_v — alongside a vlogger
|
||||
// vtable + vstdlogger over an io.vstream sink. The OLD surface stays
|
||||
// untouched here — fold-eFinal (task #50) atomically flips the
|
||||
// package shape: deletes the OLD logger / stdlogger / globals + the
|
||||
// pre-vtable stderrsink + ensureinit, renames the `_v` suffix off
|
||||
// (drops to bare `new` / `println` / etc. per Hare's
|
||||
// ref/hare/log/{logger,funcs,global,silent}.ha), and migrates the
|
||||
// few callers (lib/log/logtest.ww plus any downstream).
|
||||
//
|
||||
// drew acks (3/3):
|
||||
// - module-static stderrsink_ctx (zero-init per #129 A.2/A.3 SSoT)
|
||||
// - vt FIRST field intrusive cast (matches fmt/memio/bufio fold-e
|
||||
// ctxs)
|
||||
// - all 10 OLD fns get _v variants (Option-C consistency)
|
||||
//
|
||||
// ken mandates honored:
|
||||
// - bootstrap byte-id mechanical pre==post: log is NOT embedded
|
||||
// in any selfhost/cmd/*/main.combined.ww (grep-confirmed
|
||||
// pre-impl), so adding vstream.ww + minimal export bumps in
|
||||
// lib/fmt does not perturb the embedded surfaces. 990-997
|
||||
// gates stay green by virtue of log being test-only.
|
||||
// - module-static ctx via #129 A.2/A.3 (cs == ww symmetric)
|
||||
// - ctx shape simple scalars/ptrs (vt is the established intrusive
|
||||
// first-field idiom; the only other field is `fd: i32`. No f32
|
||||
// per #165b; no nested aggregate beyond vt per #18).
|
||||
//
|
||||
// Hare reference: ref/hare/log/{logger.ha,funcs.ha,global.ha,silent.ha}
|
||||
// + ref/hare/log/log.ha (re-export hub).
|
||||
//
|
||||
// Cast workaround per #206: bare `&fn_name` does not type-check as
|
||||
// a `(*<alias> | void)` field-init / let-binding. Explicit
|
||||
// `(&fn_name): *io.<role>` cast at each store site is the Hare-
|
||||
// faithful minimum-touch route — same workaround memio/vstream.ww
|
||||
// + fmt/vstream.ww + bufio/vstream.ww use. 2 cast tokens here at
|
||||
// the stderrsink_ctx_g vt wire-up (reader + writer; no closer —
|
||||
// the process owns fd 2, same divergence as OLD stderrclose at
|
||||
// log.ww:162). Both casts drop out wholesale once #206 closes.
|
||||
//
|
||||
// Chained-N_DOT-on-module-static field-assign per OLD log.ww:134-140
|
||||
// (`_default.logger.println = stdprintln;`) — proven-working spine.
|
||||
// stderrsink_ctx_g.vt.X writes pattern through same shape; the
|
||||
// tagged-union assign with widening cast composes via #199 α
|
||||
// (concrete *fn → NAMED variant of (*reader | void) parent).
|
||||
//
|
||||
// Newline emission: vfprint / vfprintf are bare (no trailing \n);
|
||||
// Hare's ref/hare/fmt/wrappers.ha:60-68 appends the newline at the
|
||||
// fprintln site (after fprint). ww mirrors: stdprintln_v / stdprintfln_v
|
||||
// call fmt.vfprint / fmt.vfprintf then io.st_write a one-byte slice
|
||||
// `[10u8]` for the LF. Same shape OLD log.stdprintln uses via
|
||||
// fmt.fprintln (log.ww:169).
|
||||
//
|
||||
// Sibling tasks parked here (filed, NOT fixed):
|
||||
//
|
||||
// - eFinal (#50): atomic flip + delete OLD log surface + drop the
|
||||
// `_v` suffix wholesale (graduates to Hare's bare names).
|
||||
//
|
||||
// - #206 (bare &fn → (*alias|void)): 2 cast sites at ensureinit_v;
|
||||
// drop out wholesale on close.
|
||||
//
|
||||
// - #173 (TRY-on-tagged-return both-stages broken): stderrwrite_v
|
||||
// constructs nomem and widens to io.error explicitly rather than
|
||||
// using `os.trywrite(...)?`; same shape memio.vstream.ww +
|
||||
// fmt.vstream.ww + bufio.vstream.ww adopt.
|
||||
//
|
||||
// - #207 (struct-lit multi-tagged-field copy drops past first):
|
||||
// not bitten here — module-static stderrsink_ctx_g is zero-init
|
||||
// at link time per #129; vt fields wired via post-decl chained
|
||||
// assigns rather than struct-lit, sidestepping #207 wholesale.
|
||||
// The pattern is the module-static analogue of memio.vstream.ww's
|
||||
// alloc-zero-chain.
|
||||
//
|
||||
// - #209 (wwstage formattable match-arm bail when fmt imported):
|
||||
// log imports fmt transitively (via the formattable / field
|
||||
// types in vlogger's vtable + fmt.vfprint / vfprintf calls in
|
||||
// the std callbacks). All probe rows in test/wcc/779 run
|
||||
// STAGE_CS-only; eFinal closes the wwstage half when #209 lands.
|
||||
|
||||
package log;
|
||||
|
||||
import fmt;
|
||||
import io;
|
||||
import os;
|
||||
|
||||
// stderrsink_ctx — module-static state for the default vlogger's
|
||||
// stderr sink. vt FIRST field for the intrusive vstream→*stderrsink_ctx
|
||||
// cast in stderrwrite_v. Mirrors fmt.fd_ctx (fmt/vstream.ww:66) +
|
||||
// memio.fixed_ctx (memio/vstream.ww:48); only scalars/ptrs beyond
|
||||
// vt per ken's mandate.
|
||||
type stderrsink_ctx = struct {
|
||||
vt: io.vtable,
|
||||
fd: i32,
|
||||
};
|
||||
|
||||
// Zero-init at link time per #129 A.2/A.3 SSoT; ensureinit_v wires
|
||||
// vt.reader / vt.writer + fd lazily on first dispatch. Same shape
|
||||
// OLD log.ww uses for the pre-vtable stderrsink (log.ww:104).
|
||||
let stderrsink_ctx_g: stderrsink_ctx;
|
||||
|
||||
// vlogger — V-side counterpart to OLD logger (log.ww:85). Same shape
|
||||
// modulo the `_v` field-suffix (collapses on #50). Dispatchers route
|
||||
// through io.vstream rather than *io.stream.
|
||||
//
|
||||
// Mirrors ref/hare/log/logger.ha:9.
|
||||
export type vlogger = struct {
|
||||
println_v: fn(l: *vlogger, args: fmt.formattable...) void,
|
||||
printfln_v: fn(l: *vlogger, format: str, fields: fmt.field...) void,
|
||||
};
|
||||
|
||||
// vstdlogger — concrete vlogger forwarding to an io.vstream sink.
|
||||
// First-field embed: `&sl.logger` yields a `*vlogger` (intrusive
|
||||
// outer→inner cast); the vtable callback recovers the outer via
|
||||
// `*vstdlogger` cast.
|
||||
//
|
||||
// Mirrors ref/hare/log/logger.ha:18 (stdlogger).
|
||||
export type vstdlogger = struct {
|
||||
logger: vlogger,
|
||||
sink: io.vstream,
|
||||
};
|
||||
|
||||
// _silent_v / _default_v — backing storage for the silent_v /
|
||||
// default_v *vlogger globals. Zero-init at link time; ensureinit_v
|
||||
// fills the println_v / printfln_v slots.
|
||||
let _silent_v: vlogger;
|
||||
let _default_v: vstdlogger;
|
||||
|
||||
// silent_v / default_v / global_v — Hare-style *vlogger globals.
|
||||
// Zero-init at link time; lazily wired by ensureinit_v on the first
|
||||
// call to any exported fn. Same divergence as OLD log.ww:116-118
|
||||
// (header rationale for the lazy-init route applies wholesale: ww
|
||||
// cstage emit_lets only constexprs primitive / str-literal /
|
||||
// array-literal lets — struct- and pointer-literal init aren't
|
||||
// reachable). #50 / eFinal graduates if static-init lands.
|
||||
export let silent_v: *vlogger;
|
||||
export let default_v: *vlogger;
|
||||
export let global_v: *vlogger;
|
||||
|
||||
// initdone_v — guards ensureinit_v so the one-shot wiring runs once.
|
||||
// Mirrors OLD initdone (log.ww:122) + lib/temp's rnginit pattern.
|
||||
let initdone_v: i32 = 0;
|
||||
|
||||
fn ensureinit_v() void = {
|
||||
if (initdone_v != 0) { return; };
|
||||
stderrsink_ctx_g.fd = 2;
|
||||
stderrsink_ctx_g.vt.reader = (&stderrread_v): *io.reader;
|
||||
stderrsink_ctx_g.vt.writer = (&stderrwrite_v): *io.writer;
|
||||
|
||||
_silent_v.println_v = silentprintln_v;
|
||||
_silent_v.printfln_v = silentprintfln_v;
|
||||
|
||||
_default_v.logger.println_v = stdprintln_v;
|
||||
_default_v.logger.printfln_v = stdprintfln_v;
|
||||
_default_v.sink = &stderrsink_ctx_g.vt;
|
||||
|
||||
silent_v = &_silent_v;
|
||||
default_v = &_default_v.logger;
|
||||
global_v = default_v;
|
||||
initdone_v = 1;
|
||||
};
|
||||
|
||||
// stderrread_v / stderrwrite_v — vtable callbacks for the private
|
||||
// stderr sink. Read is eof-only — lib/log writes; never reads.
|
||||
// Mirror of OLD stderrread / stderrwrite (log.ww:148-160) modulo the
|
||||
// (size | io.eof | io.error) / (size | io.error) return surface;
|
||||
// -errno collapses to a nomem-widened io.error per the
|
||||
// fmt.fdsinkwrite_v precedent (fmt/vstream.ww:88-92).
|
||||
fn stderrread_v(s: io.vstream, buf: []u8) (size | io.eof | io.error) = {
|
||||
let e: io.eof;
|
||||
return e;
|
||||
};
|
||||
|
||||
fn stderrwrite_v(s: io.vstream, buf: []u8) (size | io.error) = {
|
||||
let c: *stderrsink_ctx = s: *stderrsink_ctx;
|
||||
let r: i64 = os.write(c.fd, buf.ptr, buf.len: u64);
|
||||
if (r < 0) {
|
||||
let nm: nomem;
|
||||
let e: io.error = nm;
|
||||
return e;
|
||||
};
|
||||
return r: size;
|
||||
};
|
||||
|
||||
// stdprintln_v / stdprintfln_v — vtable callbacks for vstdlogger.
|
||||
// Forwards to fmt.vfprint / vfprintf on the sink + appends the
|
||||
// trailing newline via io.st_write. Hare's fmt.fprintln emits the
|
||||
// newline inside one wrapper (ref/hare/fmt/wrappers.ha:60-68); we
|
||||
// inline that split at the log site because fmt.vfprint is the bare
|
||||
// (no-newline) primitive. The (size | io.error) results are dropped
|
||||
// — lib/log's surface is `void` (matches Hare + OLD stdprintln at
|
||||
// log.ww:167).
|
||||
fn stdprintln_v(l: *vlogger, args: fmt.formattable...) void = {
|
||||
let sl: *vstdlogger = l: *vstdlogger;
|
||||
fmt.vfprint(sl.sink, args...);
|
||||
let nl: [1]u8;
|
||||
nl[0] = 10u8;
|
||||
let v: []u8 = nl[0:1];
|
||||
io.st_write(sl.sink, v);
|
||||
};
|
||||
|
||||
fn stdprintfln_v(l: *vlogger, format: str, fields: fmt.field...) void = {
|
||||
let sl: *vstdlogger = l: *vstdlogger;
|
||||
fmt.vfprintf(sl.sink, format, fields...);
|
||||
let nl: [1]u8;
|
||||
nl[0] = 10u8;
|
||||
let v: []u8 = nl[0:1];
|
||||
io.st_write(sl.sink, v);
|
||||
};
|
||||
|
||||
// silentprintln_v / silentprintfln_v — vtable callbacks for the
|
||||
// silent vlogger. Discard every record without dispatching through
|
||||
// fmt; keeps silent truly silent if fmt ever gets stateful. Mirror
|
||||
// of OLD silentprintln / silentprintfln (log.ww:183-187).
|
||||
fn silentprintln_v(l: *vlogger, args: fmt.formattable...) void = { };
|
||||
fn silentprintfln_v(l: *vlogger, format: str, fields: fmt.field...) void = { };
|
||||
|
||||
// ---- public API (10 _v variants) -----------------------------------------
|
||||
|
||||
// new_v — build a vstdlogger over `sink`, returned BY VALUE (fold-eFinal
|
||||
// PREP). Hare returns the stdlogger by value (ref/hare/log/logger.ha:20);
|
||||
// the OLD out-parameter shape existed only because wide-struct sret was
|
||||
// unimplemented — it now round-trips (test/wcc/925), so the constructor
|
||||
// builds in a local and `return r;` (proven field-by-field sret form).
|
||||
// vstdlogger is 24B (two fn-ptrs + sink), returned via the SysV memory
|
||||
// class; the caller owns the returned logger (stack ownership, no-GC)
|
||||
// and passes `&sl.logger` to the dispatch fns.
|
||||
export fn new_v(sink: io.vstream) vstdlogger = {
|
||||
ensureinit_v();
|
||||
let r: vstdlogger;
|
||||
r.logger.println_v = stdprintln_v;
|
||||
r.logger.printfln_v = stdprintfln_v;
|
||||
r.sink = sink;
|
||||
return r;
|
||||
};
|
||||
|
||||
// lprintln_v — dispatch `args` through `log`. Mirrors OLD lprintln
|
||||
// (log.ww:202) + ref/hare/log/funcs.ha:8.
|
||||
export fn lprintln_v(log: *vlogger, args: fmt.formattable...) void = {
|
||||
ensureinit_v();
|
||||
log.println_v(log, args...);
|
||||
};
|
||||
|
||||
// println_v — dispatch through the [[global_v]] vlogger. Mirrors
|
||||
// OLD println (log.ww:208) + ref/hare/log/funcs.ha:18.
|
||||
export fn println_v(args: fmt.formattable...) void = {
|
||||
ensureinit_v();
|
||||
lprintln_v(global_v, args...);
|
||||
};
|
||||
|
||||
// lfatal_v — lprintln_v to `log` then exit(255). `never` return
|
||||
// marks the bottom type so flow-control checks treat callers as
|
||||
// terminated. Mirrors OLD lfatal (log.ww:216) +
|
||||
// ref/hare/log/funcs.ha:28.
|
||||
export fn lfatal_v(log: *vlogger, args: fmt.formattable...) never = {
|
||||
lprintln_v(log, args...);
|
||||
os.exit(255);
|
||||
};
|
||||
|
||||
// fatal_v — lprintln_v to [[global_v]] then exit(255). Mirrors OLD
|
||||
// fatal (log.ww:223) + ref/hare/log/funcs.ha:45.
|
||||
export fn fatal_v(args: fmt.formattable...) never = {
|
||||
println_v(args...);
|
||||
os.exit(255);
|
||||
};
|
||||
|
||||
// setlogger_v — install `log` as the [[global_v]] vlogger. Mirrors
|
||||
// OLD setlogger (log.ww:230) + ref/hare/log/global.ha:20.
|
||||
export fn setlogger_v(log: *vlogger) void = {
|
||||
ensureinit_v();
|
||||
global_v = log;
|
||||
};
|
||||
|
||||
// lprintfln_v — dispatch a format-string record through `log`.
|
||||
// Mirrors OLD lprintfln (log.ww:237) + ref/hare/log/funcs.ha:13.
|
||||
export fn lprintfln_v(log: *vlogger, format: str, fields: fmt.field...) void = {
|
||||
ensureinit_v();
|
||||
log.printfln_v(log, format, fields...);
|
||||
};
|
||||
|
||||
// printfln_v — dispatch through the [[global_v]] vlogger. Mirrors
|
||||
// OLD printfln (log.ww:244) + ref/hare/log/funcs.ha:23.
|
||||
export fn printfln_v(format: str, fields: fmt.field...) void = {
|
||||
ensureinit_v();
|
||||
lprintfln_v(global_v, format, fields...);
|
||||
};
|
||||
|
||||
// lfatalf_v — lprintfln_v to `log` then exit(255). Mirrors OLD
|
||||
// lfatalf (log.ww:251) + ref/hare/log/funcs.ha:35.
|
||||
export fn lfatalf_v(log: *vlogger, format: str, fields: fmt.field...) never = {
|
||||
lprintfln_v(log, format, fields...);
|
||||
os.exit(255);
|
||||
};
|
||||
|
||||
// fatalf_v — lprintfln_v to [[global_v]] then exit(255). Mirrors
|
||||
// OLD fatalf (log.ww:258) + ref/hare/log/funcs.ha:52.
|
||||
export fn fatalf_v(format: str, fields: fmt.field...) never = {
|
||||
printfln_v(format, fields...);
|
||||
os.exit(255);
|
||||
};
|
||||
@@ -1,30 +1,51 @@
|
||||
// memio — in-memory io stream.
|
||||
// memio — in-memory io stream. Project #94 fold-eFinal.
|
||||
//
|
||||
// Hare's memio:: surface, drop underscores. Two flavours behind a
|
||||
// single [[io.stream]]:
|
||||
// single [[io.stream]] (= `*io.vtable`):
|
||||
//
|
||||
// fixed caller owns the buffer, writes stop when full.
|
||||
// dynamic memio owns the buffer, writes grow it; close frees.
|
||||
//
|
||||
// Call shape divergence from Hare: the caller supplies both the
|
||||
// memio `state` and the `io.stream` slot, by pointer. ww cgen does
|
||||
// not yet implement &x.field or 32B-struct return-by-value, so the
|
||||
// Hare `let s = memio::fixed(buf)` shape isn't reachable; collapse
|
||||
// to a single returned struct when those land (lib/CLAUDE.md
|
||||
// "graduate in one go").
|
||||
// Hare's memio::fixed/dynamic/dynamic_from return a `stream` whose
|
||||
// FIRST field IS the `io::stream` (= `*vtable`). ww mirrors that
|
||||
// intrusively: `stream`'s first field is `vt: io.vtable` (the vtable
|
||||
// embedded INLINE) so a stack `stream` is castable to
|
||||
// `io.stream = *vtable` via `&s.vt` — and the callbacks recover the
|
||||
// outer `stream` by casting the dispatch arg back to `*stream`. Same
|
||||
// intrusive shape as lib/bufio + lib/log over their embedded vtables.
|
||||
//
|
||||
// let mem: memio.state;
|
||||
// let s: io.stream;
|
||||
// memio.fixed(&mem, &s, buf);
|
||||
// io.write(&s, bytes);
|
||||
// let s: memio.stream = memio.fixed(buf);
|
||||
// io.write(&s.vt, bytes);
|
||||
// let view: str = memio.string(&s);
|
||||
//
|
||||
// Subset of Hare's surface: io.stream's variants are {eof, closed},
|
||||
// so memio drops Hare's NONBLOCK flag (would need an `again` variant
|
||||
// in lib/io). string()'s utf8-validating constructor is omitted per
|
||||
// CLAUDE.md rule 9 carve-out. Hare's seek / copy callbacks are
|
||||
// likewise absent: lib/io's stream vtable has only read/write/close
|
||||
// slots, so memio can't wire a seeker or copier even if we wanted
|
||||
// to. All three come back when their dependencies do.
|
||||
// Constructors return the `stream` BY VALUE (Hare shape,
|
||||
// ref/hare/memio/stream.ha:46,58,64): build the struct in a local,
|
||||
// field-assign every slot, and `return r;` (the proven sret round-trip
|
||||
// pinned by test/wcc/925 + 776). NO heap, NO `nomem`: the alloc that
|
||||
// forced an earlier heap return is gone, so the constructor cannot
|
||||
// fail. Caller owns the returned `stream` (stack ownership, no-GC) and
|
||||
// passes `&s.vt` to the io dispatchers — exactly Hare's `&s` into
|
||||
// io::write.
|
||||
//
|
||||
// Subset of Hare's surface: io's variants are {eof, error}, so memio
|
||||
// drops Hare's NONBLOCK flag (would need an `again` variant in lib/io).
|
||||
// string()'s utf8-validating constructor is omitted per CLAUDE.md
|
||||
// rule 9 carve-out — see [[string]]. Hare's seek / copy callbacks are
|
||||
// likewise absent: io's stream vtable has only read/write/close slots,
|
||||
// so memio can't wire a seeker or copier yet (io fold-2, #5).
|
||||
//
|
||||
// Cast workaround per #206-payoff (ken: KEEP the explicit casts; they
|
||||
// are cgen-neutral and sidestep the #214 over-acceptance surface). The
|
||||
// `(&fn_name): *io.<role>` cast at each store site is the Hare-faithful
|
||||
// minimum-touch route — the #206 cast-drop is a separate deferred
|
||||
// payoff gated on #214.
|
||||
//
|
||||
// ptr/len/cap kept flat (no `buf: []u8`) per a historical cgen note:
|
||||
// chained-dot writes through a state pointer into a slice subfield
|
||||
// miscompile silently (#195 family); the flat shape sidesteps it.
|
||||
// dynamicfrom uses the slice's `cap` (NOT `len`) to track the
|
||||
// allocated-capacity-to-free on close — passing a half-filled append
|
||||
// slice with len < cap and using only `buf.len` would under-free.
|
||||
|
||||
package memio;
|
||||
|
||||
@@ -32,106 +53,79 @@ import io;
|
||||
import os;
|
||||
import rt;
|
||||
|
||||
// state — memio's per-stream bookkeeping. The caller owns the slot
|
||||
// and passes its address into a constructor. `ptr/len/cap` are the
|
||||
// slice fields kept flat to dodge a chained-dot write through the
|
||||
// state pointer (cgen doesn't store into `m.buf.ptr` reliably).
|
||||
export type state = struct {
|
||||
// stream — Hare's memio::stream (ref/hare/memio/stream.ha:18). `vt` at
|
||||
// offset 0 for the intrusive stream→io.stream cast (`&s.vt`) and the
|
||||
// callbacks' reverse `s: *stream` cast. Unified across fixed/dynamic
|
||||
// (Hare keeps a single `stream` over per-mode vtable singletons; ww
|
||||
// wires the per-mode callbacks post-construction instead). ptr/len/cap
|
||||
// flat per the header note.
|
||||
export type stream = struct {
|
||||
vt: io.vtable,
|
||||
ptr: *u8,
|
||||
len: i32,
|
||||
cap: i32,
|
||||
pos: i32,
|
||||
};
|
||||
|
||||
// fixed — wire `s` over a caller-supplied buffer. Writes never grow;
|
||||
// they return 0 once `pos` reaches the end of the buffer.
|
||||
export fn fixed(m: *state, s: *io.stream, buf: []u8) void = {
|
||||
m.ptr = buf.ptr;
|
||||
m.len = buf.len;
|
||||
m.cap = buf.len;
|
||||
m.pos = 0;
|
||||
s.ctx = m: *void;
|
||||
s.read = readfn;
|
||||
s.write = fixedwrite;
|
||||
s.close = closenoop;
|
||||
// fixed — wire a stream over a caller-supplied buffer. Writes never
|
||||
// grow; they return 0 once `pos` reaches the end of the buffer (Hare
|
||||
// returns `nomem` here; ww surfaces 0 — graduating to Hare's `nomem`
|
||||
// return needs the widen-from-bare-nomem path that #173-family work
|
||||
// gates).
|
||||
//
|
||||
// Mirrors ref/hare/memio/stream.ha:46.
|
||||
export fn fixed(buf: []u8) stream = {
|
||||
let r: stream;
|
||||
r.vt.reader = (&readfn): *io.reader;
|
||||
r.vt.writer = (&fixedwrite): *io.writer;
|
||||
r.ptr = buf.ptr;
|
||||
r.len = buf.len;
|
||||
r.cap = buf.len;
|
||||
r.pos = 0;
|
||||
return r;
|
||||
};
|
||||
|
||||
// dynamic — wire `s` with no initial buffer. Writes grow the backing
|
||||
// allocation; [[io.close]] frees it.
|
||||
export fn dynamic(m: *state, s: *io.stream) void = {
|
||||
m.ptr = nil;
|
||||
m.len = 0;
|
||||
m.cap = 0;
|
||||
m.pos = 0;
|
||||
s.ctx = m: *void;
|
||||
s.read = readfn;
|
||||
s.write = dynamicwrite;
|
||||
s.close = dynamicclose;
|
||||
// dynamic — wire a stream with no initial buffer. Writes grow the
|
||||
// backing allocation; [[io.close]] frees it.
|
||||
//
|
||||
// Mirrors ref/hare/memio/stream.ha:58.
|
||||
export fn dynamic() stream = {
|
||||
let r: stream;
|
||||
r.vt.reader = (&readfn): *io.reader;
|
||||
r.vt.writer = (&dynamicwrite): *io.writer;
|
||||
r.vt.closer = (&dynamicclose): *io.closer;
|
||||
r.ptr = nil;
|
||||
r.len = 0;
|
||||
r.cap = 0;
|
||||
r.pos = 0;
|
||||
return r;
|
||||
};
|
||||
|
||||
// dynamicfrom — like [[dynamic]] but seeded with an existing slice.
|
||||
// Ownership of the slice transfers to the stream; [[io.close]] frees
|
||||
// it. The slice must come from the runtime allocator: close calls
|
||||
// [[os.free]] with `m.cap` bytes, which is taken from `buf.cap` (the
|
||||
// slice's allocated capacity), not its logical length. Passing a
|
||||
// half-filled append slice (len < cap) and using only `buf.len` here
|
||||
// would under-free on close.
|
||||
export fn dynamicfrom(m: *state, s: *io.stream, buf: []u8) void = {
|
||||
m.ptr = buf.ptr;
|
||||
m.len = buf.len;
|
||||
m.cap = buf.cap;
|
||||
m.pos = 0;
|
||||
s.ctx = m: *void;
|
||||
s.read = readfn;
|
||||
s.write = dynamicwrite;
|
||||
s.close = dynamicclose;
|
||||
};
|
||||
|
||||
// buffer — borrowed view of bytes written so far (buf[..pos]).
|
||||
// Seek to the end before calling if the full buffer is wanted.
|
||||
export fn buffer(m: *state) []u8 = {
|
||||
let r: []u8;
|
||||
r.ptr = m.ptr;
|
||||
r.len = m.pos;
|
||||
// Ownership transfers; close frees `cap` bytes from the slice's
|
||||
// allocated capacity (NOT logical length).
|
||||
//
|
||||
// Mirrors ref/hare/memio/stream.ha:64.
|
||||
export fn dynamicfrom(buf: []u8) stream = {
|
||||
let r: stream;
|
||||
r.vt.reader = (&readfn): *io.reader;
|
||||
r.vt.writer = (&dynamicwrite): *io.writer;
|
||||
r.vt.closer = (&dynamicclose): *io.closer;
|
||||
r.ptr = buf.ptr;
|
||||
r.len = buf.len;
|
||||
r.cap = buf.cap;
|
||||
r.pos = 0;
|
||||
return r;
|
||||
};
|
||||
|
||||
// string — bytes written so far, as a str view. Hare returns
|
||||
// (str | utf8::invalid); ww doesn't ship utf8 validation yet, so
|
||||
// this returns the unchecked view.
|
||||
export fn string(m: *state) str = {
|
||||
let r: str;
|
||||
r.ptr = m.ptr;
|
||||
r.len = m.pos;
|
||||
return r;
|
||||
};
|
||||
// ---- vtable callbacks ----------------------------------------------------
|
||||
|
||||
// reset — rewind the cursor and truncate the logical content to 0.
|
||||
// Backing storage is preserved; subsequent writes (dynamic) re-fill
|
||||
// from the start without reallocation.
|
||||
export fn reset(m: *state) void = {
|
||||
m.pos = 0;
|
||||
m.len = 0;
|
||||
};
|
||||
|
||||
// borrowedread — return an `amt`-byte view starting at `pos` without
|
||||
// copying, advancing the cursor. eof if fewer bytes are available.
|
||||
export fn borrowedread(m: *state, amt: i32) ([]u8 | io.eof) = {
|
||||
if (m.len - m.pos < amt) {
|
||||
let e: io.eof;
|
||||
return e;
|
||||
};
|
||||
let r: []u8;
|
||||
r.ptr = m.ptr + (m.pos: u64);
|
||||
r.len = amt;
|
||||
m.pos += amt;
|
||||
return r;
|
||||
};
|
||||
|
||||
// ---- vtable callbacks ------------------------------------------------
|
||||
|
||||
fn readfn(s: *io.stream, buf: []u8) (i32 | io.eof | io.closed) = {
|
||||
let m: *state = s.ctx: *state;
|
||||
// readfn — recover the stream from the io.stream's `*vtable` via the
|
||||
// intrusive offset-0 cast. Single fn over the common header (Hare's
|
||||
// single `read` at ref/hare/memio/stream.ha:103); fixed and dynamic
|
||||
// share it because the read path is buffer-flavour-agnostic.
|
||||
fn readfn(s: io.stream, buf: []u8) (size | io.eof | io.error) = {
|
||||
let m: *stream = s: *stream;
|
||||
if (m.pos >= m.len) {
|
||||
let e: io.eof;
|
||||
return e;
|
||||
@@ -145,12 +139,12 @@ fn readfn(s: *io.stream, buf: []u8) (i32 | io.eof | io.closed) = {
|
||||
i += 1;
|
||||
};
|
||||
m.pos += n;
|
||||
return n;
|
||||
return n: size;
|
||||
};
|
||||
|
||||
fn fixedwrite(s: *io.stream, buf: []u8) (i32 | io.closed) = {
|
||||
let m: *state = s.ctx: *state;
|
||||
if (m.pos >= m.len) { return 0; };
|
||||
fn fixedwrite(s: io.stream, buf: []u8) (size | io.error) = {
|
||||
let m: *stream = s: *stream;
|
||||
if (m.pos >= m.len) { return 0: size; };
|
||||
let space: i32 = m.len - m.pos;
|
||||
let n: i32 = buf.len;
|
||||
if (space < n) { n = space; };
|
||||
@@ -160,11 +154,11 @@ fn fixedwrite(s: *io.stream, buf: []u8) (i32 | io.closed) = {
|
||||
i += 1;
|
||||
};
|
||||
m.pos += n;
|
||||
return n;
|
||||
return n: size;
|
||||
};
|
||||
|
||||
fn dynamicwrite(s: *io.stream, buf: []u8) (i32 | io.closed) = {
|
||||
let m: *state = s.ctx: *state;
|
||||
fn dynamicwrite(s: io.stream, buf: []u8) (size | io.error) = {
|
||||
let m: *stream = s: *stream;
|
||||
let need: i32 = m.pos + buf.len;
|
||||
if (need > m.cap) { dynamicgrow(m, need); };
|
||||
let i: i32 = 0;
|
||||
@@ -174,11 +168,11 @@ fn dynamicwrite(s: *io.stream, buf: []u8) (i32 | io.closed) = {
|
||||
};
|
||||
m.pos += buf.len;
|
||||
if (m.pos > m.len) { m.len = m.pos; };
|
||||
return buf.len;
|
||||
return buf.len: size;
|
||||
};
|
||||
|
||||
fn dynamicclose(s: *io.stream) (void | io.closed) = {
|
||||
let m: *state = s.ctx: *state;
|
||||
fn dynamicclose(s: io.stream) (void | io.error) = {
|
||||
let m: *stream = s: *stream;
|
||||
if (m.cap > 0) { os.free(m.ptr: *void, m.cap: u64); };
|
||||
m.ptr = nil;
|
||||
m.len = 0;
|
||||
@@ -187,30 +181,77 @@ fn dynamicclose(s: *io.stream) (void | io.closed) = {
|
||||
return;
|
||||
};
|
||||
|
||||
fn closenoop(s: *io.stream) (void | io.closed) = {
|
||||
return;
|
||||
};
|
||||
|
||||
// Double-and-copy growth. Initial bump from 0 lands at 8 to amortise
|
||||
// small write bursts without a tail of reallocs.
|
||||
//
|
||||
// `dynamicgrow`, not Hare's bare `grow`: cstage bundles all imported
|
||||
// modules into a flat TU and resolves private fns by unqualified name,
|
||||
// so historically two `fn grow` decls (here + the now-deleted bump
|
||||
// arena's `grow`) would have collided. Module-prefixed name kept the
|
||||
// symmetry with `dynamicwrite`/`dynamicclose`; retained pending task
|
||||
// #9 (module-aware private-fn scoping in cstage).
|
||||
fn dynamicgrow(m: *state, need: i32) void = {
|
||||
let newcap: i32 = m.cap;
|
||||
// Double-and-copy growth with floor at 8. `dynamicgrow`, not Hare's
|
||||
// bare `grow`: cstage bundles all imported modules into a flat TU and
|
||||
// resolves private fns by unqualified name (task #9), so the
|
||||
// module-prefixed name keeps the symmetry with dynamicwrite/dynamicclose.
|
||||
fn dynamicgrow(d: *stream, need: i32) void = {
|
||||
let newcap: i32 = d.cap;
|
||||
if (newcap < 8) { newcap = 8; };
|
||||
for (newcap < need) { newcap *= 2; };
|
||||
let nbuf: *u8 = rt.malloc(newcap: u64): *u8;
|
||||
let i: i32 = 0;
|
||||
for (i < m.len) {
|
||||
nbuf[i] = m.ptr[i];
|
||||
for (i < d.len) {
|
||||
nbuf[i] = d.ptr[i];
|
||||
i += 1;
|
||||
};
|
||||
if (m.cap > 0) { os.free(m.ptr: *void, m.cap: u64); };
|
||||
m.ptr = nbuf;
|
||||
m.cap = newcap;
|
||||
if (d.cap > 0) { os.free(d.ptr: *void, d.cap: u64); };
|
||||
d.ptr = nbuf;
|
||||
d.cap = newcap;
|
||||
};
|
||||
|
||||
// ---- accessors over the common `stream` header -----------------------
|
||||
//
|
||||
// Single fn each: Hare's string/reset/buffer/borrowedread all take
|
||||
// `*stream` and read the flat header.
|
||||
|
||||
// string — bytes written so far, as a str view (buf[0..pos]).
|
||||
//
|
||||
// Mirrors ref/hare/memio/stream.ha:81 string(in: *stream). Hare returns
|
||||
// (str | utf8::invalid) — the validating constructor. ww returns a bare
|
||||
// `str` per the CLAUDE.md rule-9 frombytes carve-out: utf8.validate at
|
||||
// the IO source is opt-in, never wrapped per-construction; the honest
|
||||
// name reserves a future validating helper.
|
||||
export fn string(s: *stream) str = {
|
||||
let r: str;
|
||||
r.ptr = s.ptr;
|
||||
r.len = s.pos;
|
||||
return r;
|
||||
};
|
||||
|
||||
// buffer — borrowed []u8 view of bytes written so far (buf[0..pos]).
|
||||
//
|
||||
// Mirrors ref/hare/memio/stream.ha:74 buffer(in: *stream).
|
||||
export fn buffer(s: *stream) []u8 = {
|
||||
let r: []u8;
|
||||
r.ptr = s.ptr;
|
||||
r.len = s.pos;
|
||||
return r;
|
||||
};
|
||||
|
||||
// reset — rewind the cursor and truncate the logical content to 0.
|
||||
// Backing storage is preserved; subsequent writes (dynamic) re-fill
|
||||
// from the start without reallocation.
|
||||
//
|
||||
// Mirrors ref/hare/memio/stream.ha:87 reset(in: *stream).
|
||||
export fn reset(s: *stream) void = {
|
||||
s.pos = 0;
|
||||
s.len = 0;
|
||||
};
|
||||
|
||||
// borrowedread — return an `amt`-byte view starting at `pos` without
|
||||
// copying, advancing the cursor. eof if fewer bytes are available.
|
||||
//
|
||||
// Mirrors ref/hare/memio/stream.ha:94 borrowedread(st: *stream, amt).
|
||||
// `amt: i32` (not Hare's `size`) per the i32-index convention.
|
||||
export fn borrowedread(s: *stream, amt: i32) ([]u8 | io.eof) = {
|
||||
if (s.len - s.pos < amt) {
|
||||
let e: io.eof;
|
||||
return e;
|
||||
};
|
||||
let r: []u8;
|
||||
r.ptr = s.ptr + (s.pos: u64);
|
||||
r.len = amt;
|
||||
s.pos += amt;
|
||||
return r;
|
||||
};
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -1,257 +0,0 @@
|
||||
// vstream — Hare-shaped vtable wrappers over memio. Project #94
|
||||
// fold-eFinal(PREP).
|
||||
//
|
||||
// Adds three constructors that return a memio `stream` BY VALUE (the
|
||||
// Hare shape, ref/hare/memio/stream.ha:46,58,64) alongside the
|
||||
// pre-vtable memio.fixed / memio.dynamic / memio.dynamicfrom in
|
||||
// memio.ww. The OLD surface stays untouched here — fold-eFinal (FLIP)
|
||||
// atomically flips the package shape: deletes the OLD constructors +
|
||||
// `state` + callbacks, renames the `_vstream`/`_v` suffix off (so
|
||||
// `fixed_vstream → fixed`, `stream` stays, `string_v → string`), and
|
||||
// migrates the few callers (lib/fmt/vstream.ww + the 776 probe).
|
||||
//
|
||||
// Hare's memio::fixed/dynamic/dynamic_from return a `stream` whose
|
||||
// FIRST field IS the `io::stream` (= `*vtable`). ww mirrors that
|
||||
// intrusively: `stream`'s first field is `vt: io.vtable` (the vtable
|
||||
// embedded INLINE) so a stack `stream` is castable to
|
||||
// `vstream = *vtable` via `&s.vt` — and the callbacks recover the
|
||||
// outer `stream` by casting the dispatch arg back to `*stream`. Same
|
||||
// intrusive shape as lib/bufio + lib/log over their embedded vtables.
|
||||
//
|
||||
// VALUE-RETURN (drew, gating): the constructor builds the struct in a
|
||||
// local `let r: stream;`, field-assigns every slot (including the
|
||||
// tagged vt sub-fields), and `return r;` — the proven sret round-trip
|
||||
// shape pinned by test/wcc/925 ("ident_return_rhs" row) and extended
|
||||
// to tagged-union-field structs by the 776 byte-id rows here. NO heap,
|
||||
// NO `nomem`: the alloc that forced the OLD `(io.vstream | nomem)`
|
||||
// return is gone, so the constructor cannot fail. Caller owns the
|
||||
// returned `stream` (stack ownership, no-GC) and passes `&s.vt` to the
|
||||
// io.st_* dispatchers — exactly Hare's `&s` into io::write.
|
||||
//
|
||||
// Cast workaround per #206-payoff (ken: KEEP the explicit casts; they
|
||||
// are cgen-neutral and sidestep the #214 over-acceptance surface). The
|
||||
// `(&fn_name): *io.<role>` cast at each store site is the Hare-faithful
|
||||
// minimum-touch route — same workaround test/wcc/775_io_vtable_run.c
|
||||
// uses for the bare-vtable init.
|
||||
//
|
||||
// ptr/len/cap kept flat (no `buf: []u8`) per the memio.state note
|
||||
// (memio.ww:39): chained-dot writes through a state pointer into a
|
||||
// slice subfield miscompile silently (#195 family); the flat shape
|
||||
// sidesteps it. dynamicfrom_vstream uses the slice's `cap` (NOT `len`)
|
||||
// to track the allocated-capacity-to-free on close — mirrors the OLD
|
||||
// memio.dynamicfrom note (memio.ww:73).
|
||||
|
||||
package memio;
|
||||
|
||||
import io;
|
||||
import os;
|
||||
import rt;
|
||||
|
||||
// stream — Hare's memio::stream (ref/hare/memio/stream.ha:18). `vt` at
|
||||
// offset 0 for the intrusive stream→vstream cast (`&s.vt`) and the
|
||||
// callbacks' reverse `s: *stream` cast. Unified across fixed/dynamic
|
||||
// (Hare keeps a single `stream` over per-mode vtable singletons; ww
|
||||
// wires the per-mode callbacks post-construction instead). ptr/len/cap
|
||||
// flat per the memio.state note above.
|
||||
export type stream = struct {
|
||||
vt: io.vtable,
|
||||
ptr: *u8,
|
||||
len: i32,
|
||||
cap: i32,
|
||||
pos: i32,
|
||||
};
|
||||
|
||||
// fixed_vstream — wire a stream over a caller-supplied buffer. Writes
|
||||
// never grow; they return 0 once `pos` reaches the end of the buffer
|
||||
// (Hare returns `nomem` here; ww surfaces 0 to mirror the OLD
|
||||
// memio.fixedwrite divergence — graduating to Hare's `nomem` return
|
||||
// needs the widen-from-bare-nomem path that #173 blocks).
|
||||
//
|
||||
// Mirrors ref/hare/memio/stream.ha:46.
|
||||
export fn fixed_vstream(buf: []u8) stream = {
|
||||
let r: stream;
|
||||
r.vt.reader = (&read_v): *io.reader;
|
||||
r.vt.writer = (&fixedwrite_v): *io.writer;
|
||||
r.ptr = buf.ptr;
|
||||
r.len = buf.len;
|
||||
r.cap = buf.len;
|
||||
r.pos = 0;
|
||||
return r;
|
||||
};
|
||||
|
||||
// dynamic_vstream — wire a stream with no initial buffer. Writes grow
|
||||
// the backing allocation; [[io.st_close]] frees it.
|
||||
//
|
||||
// Mirrors ref/hare/memio/stream.ha:58.
|
||||
export fn dynamic_vstream() stream = {
|
||||
let r: stream;
|
||||
r.vt.reader = (&read_v): *io.reader;
|
||||
r.vt.writer = (&dynamicwrite_v): *io.writer;
|
||||
r.vt.closer = (&dynamicclose_v): *io.closer;
|
||||
r.ptr = nil;
|
||||
r.len = 0;
|
||||
r.cap = 0;
|
||||
r.pos = 0;
|
||||
return r;
|
||||
};
|
||||
|
||||
// dynamicfrom_vstream — like [[dynamic_vstream]] but seeded with an
|
||||
// existing slice. Ownership transfers; close frees `cap` bytes from
|
||||
// the slice's allocated capacity (NOT logical length) — passing a
|
||||
// half-filled append slice with len < cap and using only `buf.len`
|
||||
// would under-free on close.
|
||||
//
|
||||
// Mirrors ref/hare/memio/stream.ha:64.
|
||||
export fn dynamicfrom_vstream(buf: []u8) stream = {
|
||||
let r: stream;
|
||||
r.vt.reader = (&read_v): *io.reader;
|
||||
r.vt.writer = (&dynamicwrite_v): *io.writer;
|
||||
r.vt.closer = (&dynamicclose_v): *io.closer;
|
||||
r.ptr = buf.ptr;
|
||||
r.len = buf.len;
|
||||
r.cap = buf.cap;
|
||||
r.pos = 0;
|
||||
return r;
|
||||
};
|
||||
|
||||
// ---- vtable callbacks ----------------------------------------------------
|
||||
|
||||
// read_v — recover the stream from the vstream's `*vtable` via the
|
||||
// intrusive offset-0 cast. Single fn over the common header (Hare's
|
||||
// single `read` at ref/hare/memio/stream.ha:103); fixed and dynamic
|
||||
// share it because the read path is buffer-flavour-agnostic.
|
||||
fn read_v(s: io.vstream, buf: []u8) (size | io.eof | io.error) = {
|
||||
let m: *stream = s: *stream;
|
||||
if (m.pos >= m.len) {
|
||||
let e: io.eof;
|
||||
return e;
|
||||
};
|
||||
let avail: i32 = m.len - m.pos;
|
||||
let n: i32 = buf.len;
|
||||
if (avail < n) { n = avail; };
|
||||
let i: i32 = 0;
|
||||
for (i < n) {
|
||||
buf[i] = m.ptr[m.pos + i];
|
||||
i += 1;
|
||||
};
|
||||
m.pos += n;
|
||||
return n: size;
|
||||
};
|
||||
|
||||
fn fixedwrite_v(s: io.vstream, buf: []u8) (size | io.error) = {
|
||||
let m: *stream = s: *stream;
|
||||
if (m.pos >= m.len) { return 0: size; };
|
||||
let space: i32 = m.len - m.pos;
|
||||
let n: i32 = buf.len;
|
||||
if (space < n) { n = space; };
|
||||
let i: i32 = 0;
|
||||
for (i < n) {
|
||||
m.ptr[m.pos + i] = buf[i];
|
||||
i += 1;
|
||||
};
|
||||
m.pos += n;
|
||||
return n: size;
|
||||
};
|
||||
|
||||
fn dynamicwrite_v(s: io.vstream, buf: []u8) (size | io.error) = {
|
||||
let m: *stream = s: *stream;
|
||||
let need: i32 = m.pos + buf.len;
|
||||
if (need > m.cap) { dynamicgrow_v(m, need); };
|
||||
let i: i32 = 0;
|
||||
for (i < buf.len) {
|
||||
m.ptr[m.pos + i] = buf[i];
|
||||
i += 1;
|
||||
};
|
||||
m.pos += buf.len;
|
||||
if (m.pos > m.len) { m.len = m.pos; };
|
||||
return buf.len: size;
|
||||
};
|
||||
|
||||
fn dynamicclose_v(s: io.vstream) (void | io.error) = {
|
||||
let m: *stream = s: *stream;
|
||||
if (m.cap > 0) { os.free(m.ptr: *void, m.cap: u64); };
|
||||
m.ptr = nil;
|
||||
m.len = 0;
|
||||
m.cap = 0;
|
||||
m.pos = 0;
|
||||
return;
|
||||
};
|
||||
|
||||
// Mirror of memio.ww:203 dynamicgrow. Double-and-copy with floor at
|
||||
// 8. Module-prefixed `_v` suffix vs the OLD memio.dynamicgrow keeps
|
||||
// the cstage flat-TU private-fn scope from colliding (memio.ww:200
|
||||
// note + task #9).
|
||||
fn dynamicgrow_v(d: *stream, need: i32) void = {
|
||||
let newcap: i32 = d.cap;
|
||||
if (newcap < 8) { newcap = 8; };
|
||||
for (newcap < need) { newcap *= 2; };
|
||||
let nbuf: *u8 = rt.malloc(newcap: u64): *u8;
|
||||
let i: i32 = 0;
|
||||
for (i < d.len) {
|
||||
nbuf[i] = d.ptr[i];
|
||||
i += 1;
|
||||
};
|
||||
if (d.cap > 0) { os.free(d.ptr: *void, d.cap: u64); };
|
||||
d.ptr = nbuf;
|
||||
d.cap = newcap;
|
||||
};
|
||||
|
||||
// ---- accessors over the common `stream` header -----------------------
|
||||
//
|
||||
// Single fn each (drew string-collapse): Hare's string/reset/buffer/
|
||||
// borrowedread all take `*stream` and read the flat header, so the
|
||||
// fixed/dynamic split the OLD V-side carried (fixed_string /
|
||||
// dynamic_string) collapses to one. FLIP drops the `_v` suffix
|
||||
// (string_v → string, …) and deletes the OLD `*state` twins in
|
||||
// memio.ww.
|
||||
|
||||
// string_v — bytes written so far, as a str view (buf[0..pos]).
|
||||
//
|
||||
// Mirrors ref/hare/memio/stream.ha:81 string(in: *stream). Hare returns
|
||||
// (str | utf8::invalid) — the validating constructor. ww returns a bare
|
||||
// `str` per the CLAUDE.md rule-9 frombytes carve-out: utf8.validate at
|
||||
// the IO source is opt-in, never wrapped per-construction; the honest
|
||||
// name reserves a future validating helper.
|
||||
export fn string_v(s: *stream) str = {
|
||||
let r: str;
|
||||
r.ptr = s.ptr;
|
||||
r.len = s.pos;
|
||||
return r;
|
||||
};
|
||||
|
||||
// buffer_v — borrowed []u8 view of bytes written so far (buf[0..pos]).
|
||||
//
|
||||
// Mirrors ref/hare/memio/stream.ha:74 buffer(in: *stream).
|
||||
export fn buffer_v(s: *stream) []u8 = {
|
||||
let r: []u8;
|
||||
r.ptr = s.ptr;
|
||||
r.len = s.pos;
|
||||
return r;
|
||||
};
|
||||
|
||||
// reset_v — rewind the cursor and truncate the logical content to 0.
|
||||
// Backing storage is preserved; subsequent writes (dynamic) re-fill
|
||||
// from the start without reallocation.
|
||||
//
|
||||
// Mirrors ref/hare/memio/stream.ha:87 reset(in: *stream).
|
||||
export fn reset_v(s: *stream) void = {
|
||||
s.pos = 0;
|
||||
s.len = 0;
|
||||
};
|
||||
|
||||
// borrowedread_v — return an `amt`-byte view starting at `pos` without
|
||||
// copying, advancing the cursor. eof if fewer bytes are available.
|
||||
//
|
||||
// Mirrors ref/hare/memio/stream.ha:94 borrowedread(st: *stream, amt).
|
||||
// `amt: i32` (not Hare's `size`) per the i32-index convention.
|
||||
export fn borrowedread_v(s: *stream, amt: i32) ([]u8 | io.eof) = {
|
||||
if (s.len - s.pos < amt) {
|
||||
let e: io.eof;
|
||||
return e;
|
||||
};
|
||||
let r: []u8;
|
||||
r.ptr = s.ptr + (s.pos: u64);
|
||||
r.len = amt;
|
||||
s.pos += amt;
|
||||
return r;
|
||||
};
|
||||
@@ -7,7 +7,7 @@
|
||||
// shlex.syntaxerr — !void; bad shell syntax
|
||||
// shlex.strerror(syntaxerr) — "Invalid shell syntax"
|
||||
// shlex.split(in: str) — tokenize; returns ([]str | syntaxerr)
|
||||
// shlex.quote(*io.stream, s) — write `s` shell-quoted to a sink
|
||||
// shlex.quote(io.stream, s) — write `s` shell-quoted to a sink
|
||||
// shlex.quotestr(s: str) str — quote into a fresh str
|
||||
//
|
||||
// Owning model: split() returns a fresh `[]str` of strings.dup'd
|
||||
@@ -39,19 +39,16 @@
|
||||
// / scan_backslash work on bytes; an escaped multibyte sequence
|
||||
// round-trips as the byte sequence it appears in the input.
|
||||
//
|
||||
// - memio glue uses ww's caller-supplies-state shape:
|
||||
// let mst: memio.state; let s: io.stream;
|
||||
// memio.dynamic(&mst, &s);
|
||||
// ... io.write(&s, ...) ...
|
||||
// let view: str = memio.string(&mst); // borrowed
|
||||
// instead of Hare's `let s = memio::dynamic()` return-by-value.
|
||||
// Locked in by lib/memio's commit until cgen ships >24B return-
|
||||
// by-value (graduates "in one go" per lib/CLAUDE.md).
|
||||
// - memio glue uses the value-return shape (#94 fold-eFinal):
|
||||
// let st: memio.stream = memio.dynamic();
|
||||
// ... io.write(&st.vt, ...) ...
|
||||
// let view: str = memio.string(&st); // borrowed
|
||||
// mirroring Hare's `let s = memio::dynamic()` return-by-value.
|
||||
// `&st.vt` is the [[io.stream]] (= `*io.vtable`) the dispatchers
|
||||
// take.
|
||||
//
|
||||
// - quote() takes `*io.stream` (not Hare's `io::handle`) — that's
|
||||
// already the ww-divergent shape lib/io ships. Returns
|
||||
// `(i32 | io.closed)` instead of Hare's wider io::error: lib/io's
|
||||
// stream vtable carries only `closed` on the write arm.
|
||||
// - quote() takes `io.stream` (not Hare's `io::handle`) — that's
|
||||
// the ww-divergent shape lib/io ships until io fold-2 (#5).
|
||||
//
|
||||
// - quote() emits raw bytes directly into the sink; without
|
||||
// [[memio.appendrune]] / [[memio.concat]] in lib/memio yet, the
|
||||
@@ -86,11 +83,9 @@
|
||||
// Caller layout — quote into a fixed buffer:
|
||||
//
|
||||
// let buf: [128]u8;
|
||||
// let mst: memio.state;
|
||||
// let s: io.stream;
|
||||
// memio.fixed(&mst, &s, buf[0:128]);
|
||||
// shlex.quote(&s, "hello world"); // writes 'hello world'
|
||||
// let view: str = memio.string(&mst);
|
||||
// let st: memio.stream = memio.fixed(buf[0:128]);
|
||||
// shlex.quote(&st.vt, "hello world"); // writes 'hello world'
|
||||
// let view: str = memio.string(&st);
|
||||
|
||||
package shlex;
|
||||
|
||||
@@ -167,11 +162,11 @@ fn freepartial(slice: []str) void = {
|
||||
// ship an appendbyte/appendrune in the ww port yet, so this is the
|
||||
// uniform write-one-byte primitive across the module.
|
||||
//
|
||||
// The dynamic memio sink never returns `io.closed`; the [[split]] +
|
||||
// [[quotestr]] callers ignore the error arm because their stream is
|
||||
// always memio.dynamic. [[quote]] (caller-supplied sink) propagates
|
||||
// it through its `(i32 | io.closed)` return.
|
||||
fn writebyte(s: *io.stream, b: u8) (i32 | io.closed) = {
|
||||
// The dynamic memio sink never errors; the [[split]] + [[quotestr]]
|
||||
// callers ignore the error arm because their stream is always
|
||||
// memio.dynamic. [[quote]] (caller-supplied sink) propagates it through
|
||||
// its `(size | io.error)` return.
|
||||
fn writebyte(s: io.stream, b: u8) (size | io.error) = {
|
||||
let buf: [1]u8;
|
||||
buf[0] = b;
|
||||
return io.write(s, buf[0:1]);
|
||||
@@ -182,7 +177,7 @@ fn writebyte(s: *io.stream, b: u8) (i32 | io.closed) = {
|
||||
// newline silently. A trailing bare backslash (`*pos == in.len`) is
|
||||
// `syntaxerr`. Otherwise the next byte is appended verbatim. Mirrors
|
||||
// Hare's scan_backslash (split.ha:86).
|
||||
fn scan_backslash(out: *io.stream, in: str, pos: *i32) (void | syntaxerr) = {
|
||||
fn scan_backslash(out: io.stream, in: str, pos: *i32) (void | syntaxerr) = {
|
||||
if (*pos >= in.len) { let e: syntaxerr; return e; };
|
||||
let r: u8 = in[*pos];
|
||||
*pos += 1;
|
||||
@@ -194,7 +189,7 @@ fn scan_backslash(out: *io.stream, in: str, pos: *i32) (void | syntaxerr) = {
|
||||
// scan_double — consume a `"..."` group at `*pos`. Closes on '"';
|
||||
// '\<c>' inside processed by [[scan_backslash]]; unterminated → err.
|
||||
// Mirrors Hare's scan_double (split.ha:110).
|
||||
fn scan_double(out: *io.stream, in: str, pos: *i32) (void | syntaxerr) = {
|
||||
fn scan_double(out: io.stream, in: str, pos: *i32) (void | syntaxerr) = {
|
||||
let loop: bool = true;
|
||||
for (loop) {
|
||||
if (*pos >= in.len) { let e: syntaxerr; return e; };
|
||||
@@ -217,7 +212,7 @@ fn scan_double(out: *io.stream, in: str, pos: *i32) (void | syntaxerr) = {
|
||||
// scan_single — consume a `'...'` group at `*pos`. Closes on "'";
|
||||
// no escapes inside (POSIX: single-quoted text is fully literal);
|
||||
// unterminated → err. Mirrors Hare's scan_single (split.ha:135).
|
||||
fn scan_single(out: *io.stream, in: str, pos: *i32) (void | syntaxerr) = {
|
||||
fn scan_single(out: io.stream, in: str, pos: *i32) (void | syntaxerr) = {
|
||||
let loop: bool = true;
|
||||
for (loop) {
|
||||
if (*pos >= in.len) { let e: syntaxerr; return e; };
|
||||
@@ -242,9 +237,8 @@ fn scan_single(out: *io.stream, in: str, pos: *i32) (void | syntaxerr) = {
|
||||
// signal for "an empty quote group was the only content of this
|
||||
// token", which still pushes a literal "" (e.g. `cmd ''`).
|
||||
export fn split(in: str) ([]str | syntaxerr) = {
|
||||
let mst: memio.state;
|
||||
let snk: io.stream;
|
||||
memio.dynamic(&mst, &snk);
|
||||
let st: memio.stream = memio.dynamic();
|
||||
let snk: io.stream = &st.vt;
|
||||
|
||||
let slice: []str;
|
||||
slice.ptr = nil: *str;
|
||||
@@ -275,56 +269,56 @@ export fn split(in: str) ([]str | syntaxerr) = {
|
||||
};
|
||||
};
|
||||
if (!first) {
|
||||
let view: str = memio.string(&mst);
|
||||
let view: str = memio.string(&st);
|
||||
let owned: str = dupstr(view);
|
||||
appendstr(&slice, owned);
|
||||
memio.reset(&mst);
|
||||
memio.reset(&st);
|
||||
};
|
||||
dirty = false;
|
||||
} else { if (r == 92u8) { // '\\'
|
||||
let er = scan_backslash(&snk, in, &pos);
|
||||
let er = scan_backslash(snk, in, &pos);
|
||||
match (er) {
|
||||
case let e: syntaxerr => {
|
||||
let _c = io.close(&snk);
|
||||
let _c = io.close(snk);
|
||||
freepartial(slice);
|
||||
return e;
|
||||
};
|
||||
case void => {};
|
||||
};
|
||||
} else { if (r == 34u8) { // '"'
|
||||
let er = scan_double(&snk, in, &pos);
|
||||
let er = scan_double(snk, in, &pos);
|
||||
match (er) {
|
||||
case let e: syntaxerr => {
|
||||
let _c = io.close(&snk);
|
||||
let _c = io.close(snk);
|
||||
freepartial(slice);
|
||||
return e;
|
||||
};
|
||||
case void => {};
|
||||
};
|
||||
} else { if (r == 39u8) { // "'"
|
||||
let er = scan_single(&snk, in, &pos);
|
||||
let er = scan_single(snk, in, &pos);
|
||||
match (er) {
|
||||
case let e: syntaxerr => {
|
||||
let _c = io.close(&snk);
|
||||
let _c = io.close(snk);
|
||||
freepartial(slice);
|
||||
return e;
|
||||
};
|
||||
case void => {};
|
||||
};
|
||||
} else {
|
||||
let _w = writebyte(&snk, r);
|
||||
let _w = writebyte(snk, r);
|
||||
}; }; }; };
|
||||
|
||||
if (first) { first = false; };
|
||||
};
|
||||
|
||||
if (dirty) {
|
||||
let view: str = memio.string(&mst);
|
||||
let view: str = memio.string(&st);
|
||||
let owned: str = dupstr(view);
|
||||
appendstr(&slice, owned);
|
||||
};
|
||||
|
||||
let _c = io.close(&snk);
|
||||
let _c = io.close(snk);
|
||||
return slice;
|
||||
};
|
||||
|
||||
@@ -355,10 +349,8 @@ fn issafe(s: str) bool = {
|
||||
};
|
||||
|
||||
// quote — emit `s` shell-quoted to `sink`. Returns total bytes written,
|
||||
// or `io.closed` if the sink rejects mid-write. Mirrors Hare's
|
||||
// shlex::quote (escape.ha:25), modulo the narrower `io.closed`-only
|
||||
// error set on lib/io's stream vtable (Hare's `io::error` would carry
|
||||
// underread/etc., which lib/io doesn't yet model).
|
||||
// or `io.error` if the sink rejects mid-write. Mirrors Hare's
|
||||
// shlex::quote (escape.ha:25).
|
||||
//
|
||||
// Strategy:
|
||||
// - empty `s` → `''`
|
||||
@@ -366,14 +358,14 @@ fn issafe(s: str) bool = {
|
||||
// - otherwise → wrap in single quotes; an embedded `'`
|
||||
// becomes `'"'"'` (close, double-quoted
|
||||
// literal `'`, reopen).
|
||||
export fn quote(sink: *io.stream, s: str) (i32 | io.closed) = {
|
||||
export fn quote(sink: io.stream, s: str) (size | io.error) = {
|
||||
if (s.len == 0) {
|
||||
let buf: [2]u8;
|
||||
buf[0] = 39u8; buf[1] = 39u8;
|
||||
let r = io.write(sink, buf[0:2]);
|
||||
match (r) {
|
||||
case let n: i32 => return n;
|
||||
case let c: io.closed => return c;
|
||||
case let n: size => return n;
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
};
|
||||
if (issafe(s)) {
|
||||
@@ -382,16 +374,16 @@ export fn quote(sink: *io.stream, s: str) (i32 | io.closed) = {
|
||||
raw.len = s.len;
|
||||
let r = io.write(sink, raw);
|
||||
match (r) {
|
||||
case let n: i32 => return n;
|
||||
case let c: io.closed => return c;
|
||||
case let n: size => return n;
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
};
|
||||
|
||||
let total: i32 = 0;
|
||||
let total: size = 0;
|
||||
let r1 = writebyte(sink, 39u8); // "'"
|
||||
match (r1) {
|
||||
case let n: i32 => { total += n; };
|
||||
case let c: io.closed => return c;
|
||||
case let n: size => { total += n; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
@@ -402,22 +394,22 @@ export fn quote(sink: *io.stream, s: str) (i32 | io.closed) = {
|
||||
pat[3] = 34u8; pat[4] = 39u8;
|
||||
let rr = io.write(sink, pat[0:5]);
|
||||
match (rr) {
|
||||
case let n: i32 => { total += n; };
|
||||
case let c2: io.closed => return c2;
|
||||
case let n: size => { total += n; };
|
||||
case let e2: io.error => return e2;
|
||||
};
|
||||
} else {
|
||||
let rr = writebyte(sink, c);
|
||||
match (rr) {
|
||||
case let n: i32 => { total += n; };
|
||||
case let c2: io.closed => return c2;
|
||||
case let n: size => { total += n; };
|
||||
case let e2: io.error => return e2;
|
||||
};
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
let r2 = writebyte(sink, 39u8); // "'"
|
||||
match (r2) {
|
||||
case let n: i32 => { total += n; };
|
||||
case let c: io.closed => return c;
|
||||
case let n: size => { total += n; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
return total;
|
||||
};
|
||||
@@ -427,19 +419,18 @@ export fn quote(sink: *io.stream, s: str) (i32 | io.closed) = {
|
||||
// `os.free(r.ptr, r.len: u64)`. Mirrors Hare's shlex::quotestr
|
||||
// (escape.ha:50).
|
||||
//
|
||||
// memio.dynamic's writes never return io.closed, so the `match` on
|
||||
// `quote(...)` discards the error arm — it can't reach this caller's
|
||||
// sink. quote()'s `(i32 | io.closed)` shape is preserved for the
|
||||
// caller-supplied-sink path.
|
||||
// memio.dynamic's writes never error, so the `match` on `quote(...)`
|
||||
// discards the error arm — it can't reach this caller's sink. quote()'s
|
||||
// `(size | io.error)` shape is preserved for the caller-supplied-sink
|
||||
// path.
|
||||
export fn quotestr(s: str) str = {
|
||||
let mst: memio.state;
|
||||
let snk: io.stream;
|
||||
memio.dynamic(&mst, &snk);
|
||||
let st: memio.stream = memio.dynamic();
|
||||
let snk: io.stream = &st.vt;
|
||||
|
||||
let _r = quote(&snk, s);
|
||||
let view: str = memio.string(&mst);
|
||||
let _r = quote(snk, s);
|
||||
let view: str = memio.string(&st);
|
||||
let owned: str = dupstr(view);
|
||||
|
||||
let _c = io.close(&snk);
|
||||
let _c = io.close(snk);
|
||||
return owned;
|
||||
};
|
||||
|
||||
@@ -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 --------------------------
|
||||
|
||||
@@ -14054,54 +14054,35 @@ export fn checkfile(c: *checker, file: *node) void = {
|
||||
|
||||
// io — stream interface (Plan 9 Bio / Hare io::stream shape).
|
||||
//
|
||||
// No closures, no methods. A `stream` is a struct of function
|
||||
// pointers plus a `ctx: *void`. The error channel is the return
|
||||
// value of read/write/close — Hare-shaped tagged unions instead of
|
||||
// errno-style integer sentinels.
|
||||
// No closures, no methods. A `stream` is a pointer to a `vtable` of
|
||||
// function pointers (lib/io/stream.ww); read/write/close dispatch
|
||||
// through it. The error channel is the return value — Hare-shaped
|
||||
// tagged unions instead of errno-style integer sentinels.
|
||||
//
|
||||
// This file owns the eof / underread variant tags; lib/io/stream.ww
|
||||
// owns the `vtable` + `stream` + read/write/close dispatchers, and
|
||||
// lib/io/types.ww owns the error union, mode/whence enums, and the
|
||||
// reader/writer/closer fn-type aliases. #94 fold-eFinal collapsed the
|
||||
// pre-vtable `stream` struct + `closed` tag into the single vtable
|
||||
// surface; the dispatchers are read/write/close (over `stream`),
|
||||
// final over `handle` at io fold-2 (#5).
|
||||
package io;
|
||||
|
||||
// eof — read past the end of the stream. Hare uses the `done`
|
||||
// singleton for EOF; ww doesn't have `done` yet so we ship a
|
||||
// named-void variant tag.
|
||||
package io;
|
||||
|
||||
// named-void variant tag. Lifts to `done` with #93.
|
||||
export type eof = void;
|
||||
|
||||
// closed — operation attempted on a stream that has already been
|
||||
// closed. ww-specific: Hare's io collapses this into the wider
|
||||
// errors union, but our stream vtable has no handle-ownership
|
||||
// semantics, so a distinct tag is honest. Named void.
|
||||
export type closed = void;
|
||||
|
||||
// underread — an I/O handle hit eof partway through a fixed-size
|
||||
// read. Payload is the byte count actually delivered. Mirrors
|
||||
// Hare's `io::underread = !size`; ww uses i32 because the
|
||||
// underlying buffer-length type is i32 today.
|
||||
export type underread = !i32;
|
||||
|
||||
export type stream = struct {
|
||||
ctx: *void,
|
||||
read: fn(s: *stream, buf: []u8) (i32 | eof | closed),
|
||||
write: fn(s: *stream, buf: []u8) (i32 | closed),
|
||||
close: fn(s: *stream) (void | closed),
|
||||
};
|
||||
|
||||
export fn read(s: *stream, buf: []u8) (i32 | eof | closed) = {
|
||||
return s.read(s, buf);
|
||||
};
|
||||
|
||||
export fn write(s: *stream, buf: []u8) (i32 | closed) = {
|
||||
return s.write(s, buf);
|
||||
};
|
||||
|
||||
export fn close(s: *stream) (void | closed) = {
|
||||
return s.close(s);
|
||||
};
|
||||
|
||||
// stream — Hare-shaped vtable port. Project #94 fold-e1.
|
||||
// stream — Hare-shaped vtable surface. Project #94 fold-eFinal.
|
||||
//
|
||||
// Adds the additive vtable surface alongside lib/io/io.ww's pre-vtable
|
||||
// stream struct + read/write/close wrappers (which fold-e2-eN migrate
|
||||
// to vtable-backed implementations and then retire). Three exports:
|
||||
// The single io stream surface (the fold-eFinal collapse retired the
|
||||
// pre-vtable `stream` struct + `closed` tag). Three exports:
|
||||
//
|
||||
// vtable a struct of optional fn-pointer slots — reader/writer/
|
||||
// closer per ref/hare/io/stream.ha:36-42. Hare spells the
|
||||
@@ -14109,35 +14090,25 @@ export fn close(s: *stream) (void | closed) = {
|
||||
// spelling (#192), so each slot is a `(*T | void)` tagged
|
||||
// union (Plan-9-lean per the Nullable kept ruling; opt-in
|
||||
// at optionality boundaries only).
|
||||
// vstream `*vtable` alias matching Hare's `stream = *vtable`
|
||||
// (ref/hare/io/stream.ha:33). Named `vstream` rather than
|
||||
// `stream` because lib/io/io.ww still owns the legacy
|
||||
// `stream` struct name until fold-eN collapses both.
|
||||
// st_read /
|
||||
// st_write /
|
||||
// st_close the three dispatchers per ref/hare/io/stream.ha:44-68.
|
||||
// stream `*vtable` alias matching Hare's `stream = *vtable`
|
||||
// (ref/hare/io/stream.ha:33).
|
||||
// read /
|
||||
// write /
|
||||
// close the three dispatchers per ref/hare/io/stream.ha:44-68.
|
||||
// Each `match`es the matching vtable slot, returns
|
||||
// `errors.unsupported` (widened twice) when the slot is
|
||||
// void, otherwise calls through the fn pointer.
|
||||
// void, otherwise calls through the fn pointer. Hare keeps
|
||||
// these private (`st_read` …) behind a `handle`-typed
|
||||
// public `read`/`write`/`close`; ww has no `handle` yet
|
||||
// (io fold-2, #5), so the dispatchers ARE the public
|
||||
// surface and grow the `handle` match when #5 lands.
|
||||
//
|
||||
// Fold-e1 cgen/checker dependencies (all closed at HEAD): #191
|
||||
// (N_DOT on aliased-ptr receiver — `s.reader` resolves through the
|
||||
// `vstream = *vtable` alias), #198 (`is` on cross-module variant
|
||||
// tag), #199 α (concrete→tagged widen via NAMED-variant nominal
|
||||
// match, used by `let e: error = u;`), #200 (`as` payload-extract),
|
||||
// #201 (return-of-tagged-call forwards instead of rewinds), #205
|
||||
// (tagged→tagged subset accepts NAMED-variant nominal match, used
|
||||
// by `return e;` where ret type contains `error` as a direct
|
||||
// variant). Two direct widens compose for the void-arm `return`:
|
||||
// concrete `errors.unsupported` → tagged `error` via #199 α, then
|
||||
// tagged `error` → tagged `(size | eof | error)` via #205.
|
||||
//
|
||||
// Deferrals (drew-signed): `seeker` lands with fold-e2 once `off`
|
||||
// + `whence` plug into the signature; Hare's `?`-propagating
|
||||
// `st_close` body (`c(s)?;`) collapses to a direct `return (*c)(s);`
|
||||
// here because #173 (TRY-on-tagged-return both-stages broken) is
|
||||
// still open — the dispatcher's surface stays Hare-shaped, only the
|
||||
// internal try-prop is omitted.
|
||||
// Deferrals (drew-signed): `seeker` lands with io fold-2 (#5) once `off`
|
||||
// + `whence` plug into the signature. Hare's `?`-propagating `close`
|
||||
// body (`c(s)?;`) collapses to a direct `return (*c)(s);` here because
|
||||
// the dispatcher's surface stays Hare-shaped; only the internal
|
||||
// try-prop is omitted (#173 history, now closed — kept direct for the
|
||||
// void-arm fall-through shape).
|
||||
|
||||
package io;
|
||||
|
||||
@@ -14149,16 +14120,15 @@ export type vtable = struct {
|
||||
closer: (*closer | void),
|
||||
};
|
||||
|
||||
// ref/hare/io/stream.ha:33. `vstream` not `stream` while io.ww still
|
||||
// owns the legacy `stream` struct name.
|
||||
export type vstream = *vtable;
|
||||
// ref/hare/io/stream.ha:33.
|
||||
export type stream = *vtable;
|
||||
|
||||
// ref/hare/io/stream.ha:44-50. The void-arm `return e;` chains two
|
||||
// direct widens: `errors.unsupported → error` via #199 α, then
|
||||
// `error → (size | eof | error)` via #205. The call-arm spells the
|
||||
// fn-ptr call explicitly per #193 (ww requires `(*r)(args)` rather
|
||||
// than implicit `r(args)`).
|
||||
export fn st_read(s: vstream, buf: []u8) (size | eof | error) = {
|
||||
export fn read(s: stream, buf: []u8) (size | eof | error) = {
|
||||
match (s.reader) {
|
||||
case void => {
|
||||
let u: errors.unsupported;
|
||||
@@ -14170,7 +14140,7 @@ export fn st_read(s: vstream, buf: []u8) (size | eof | error) = {
|
||||
};
|
||||
|
||||
// ref/hare/io/stream.ha:53-59.
|
||||
export fn st_write(s: vstream, buf: []u8) (size | error) = {
|
||||
export fn write(s: stream, buf: []u8) (size | error) = {
|
||||
match (s.writer) {
|
||||
case void => {
|
||||
let u: errors.unsupported;
|
||||
@@ -14183,10 +14153,9 @@ export fn st_write(s: vstream, buf: []u8) (size | error) = {
|
||||
|
||||
// ref/hare/io/stream.ha:62-68. Hare propagates the close error via
|
||||
// `c(s)?;` then falls through to the void arm; ww collapses to a
|
||||
// direct return because #173 (TRY-on-tagged-return) is still open.
|
||||
// The surface (void | error) matches; only the internal try-prop
|
||||
// is omitted.
|
||||
export fn st_close(s: vstream) (void | error) = {
|
||||
// direct return for the void-arm fall-through shape. The surface
|
||||
// (void | error) matches.
|
||||
export fn close(s: stream) (void | error) = {
|
||||
match (s.closer) {
|
||||
case void => return void;
|
||||
case let c: *closer => return (*c)(s);
|
||||
@@ -14222,21 +14191,19 @@ export type exists = !void;
|
||||
export type unsupported = !void;
|
||||
|
||||
// types — error union, mode/whence enums, reader/writer/closer
|
||||
// fn-type aliases. Project #94 fold-1 step (d) (drew Fold D); the
|
||||
// fn-aliases re-targeted from *stream → vstream as part of fold-e1
|
||||
// (vtable port in stream.ww).
|
||||
// fn-type aliases. Project #94 fold-eFinal; the fn-aliases target
|
||||
// `stream` (= `*vtable`, the single io surface).
|
||||
//
|
||||
// Hare splits the io module across stream.ha + types.ha and ww does
|
||||
// the same: lib/io/io.ww owns the (pre-vtable) `stream`, `eof`, and
|
||||
// `underread` shapes; lib/io/stream.ww owns the Hare vtable port
|
||||
// (`vtable`, `vstream`, `st_read`/`st_write`/`st_close` dispatch);
|
||||
// this file owns the surrounding port. The three files share
|
||||
// `package io;` so cross-file refs resolve via the dir-enum concat
|
||||
// order (io.ww < stream.ww < types.ww — `vstream` lands before the
|
||||
// reader/writer/closer aliases below).
|
||||
// the same: lib/io/io.ww owns the `eof` and `underread` tags;
|
||||
// lib/io/stream.ww owns the vtable surface (`vtable`, `stream`,
|
||||
// `read`/`write`/`close` dispatch); this file owns the surrounding
|
||||
// port. The three files share `package io;` so cross-file refs
|
||||
// resolve via the dir-enum concat order (io.ww < stream.ww < types.ww
|
||||
// — `stream` lands before the reader/writer/closer aliases below).
|
||||
//
|
||||
// Deferrals (drew-signed): `handle`, `seeker`, `copier`, `strerror`
|
||||
// land with fold-e2+ (need the `handle` sum). Hare's `EOF = done`
|
||||
// land with io fold-2 (#5; need the `handle` sum). Hare's `EOF = done`
|
||||
// stays as `io.eof` until the `done` singleton ships (#93).
|
||||
|
||||
package io;
|
||||
@@ -14266,283 +14233,72 @@ export type whence = enum i32 {
|
||||
// ww enumerates the tags explicitly (ken's #204-block — spread of an
|
||||
// errors-side tagged into this union triggers a wrapper-vs-flatten
|
||||
// layout mismatch; #199b layout-extension is the deferred fix). The
|
||||
// st_read/st_write dispatchers in stream.ww return
|
||||
// `errors.unsupported` when the matching vtable slot is unset, so the
|
||||
// variant lands here directly.
|
||||
// read/write dispatchers in stream.ww return `errors.unsupported`
|
||||
// when the matching vtable slot is unset, so the variant lands here
|
||||
// directly.
|
||||
export type error = !(errors.unsupported | underread | nomem);
|
||||
|
||||
// ref/hare/io/types.ha:46. `eof` (not Hare's `done`) per the io.ww:8
|
||||
// rationale; lifts to `done` with #93. `vstream` forward-refs the
|
||||
// ref/hare/io/types.ha:46. `eof` (not Hare's `done`) per the io.ww
|
||||
// rationale; lifts to `done` with #93. `stream` forward-refs the
|
||||
// `*vtable` alias in stream.ww — the dispatcher receives the vtable
|
||||
// pointer directly (Hare's stream.ha:33 + types.ha:46 collapse).
|
||||
export type reader = fn(s: vstream, buf: []u8) (size | eof | error);
|
||||
export type reader = fn(s: stream, buf: []u8) (size | eof | error);
|
||||
|
||||
// ref/hare/io/types.ha:51. No `const` qualifier — ww has none, and
|
||||
// lib/io/io.ww:30 + lib/sort/sort.ww:18 drop it the same way.
|
||||
export type writer = fn(s: vstream, buf: []u8) (size | error);
|
||||
// lib/sort/sort.ww:18 drops it the same way.
|
||||
export type writer = fn(s: stream, buf: []u8) (size | error);
|
||||
|
||||
// ref/hare/io/types.ha:55.
|
||||
export type closer = fn(s: vstream) (void | error);
|
||||
export type closer = fn(s: stream) (void | error);
|
||||
|
||||
// memio — in-memory io stream.
|
||||
// memio — in-memory io stream. Project #94 fold-eFinal.
|
||||
//
|
||||
// Hare's memio:: surface, drop underscores. Two flavours behind a
|
||||
// single [[io.stream]]:
|
||||
// single [[io.stream]] (= `*io.vtable`):
|
||||
//
|
||||
// fixed caller owns the buffer, writes stop when full.
|
||||
// dynamic memio owns the buffer, writes grow it; close frees.
|
||||
//
|
||||
// Call shape divergence from Hare: the caller supplies both the
|
||||
// memio `state` and the `io.stream` slot, by pointer. ww cgen does
|
||||
// not yet implement &x.field or 32B-struct return-by-value, so the
|
||||
// Hare `let s = memio::fixed(buf)` shape isn't reachable; collapse
|
||||
// to a single returned struct when those land (lib/CLAUDE.md
|
||||
// "graduate in one go").
|
||||
//
|
||||
// let mem: memio.state;
|
||||
// let s: io.stream;
|
||||
// memio.fixed(&mem, &s, buf);
|
||||
// io.write(&s, bytes);
|
||||
//
|
||||
// Subset of Hare's surface: io.stream's variants are {eof, closed},
|
||||
// so memio drops Hare's NONBLOCK flag (would need an `again` variant
|
||||
// in lib/io). string()'s utf8-validating constructor is omitted per
|
||||
// CLAUDE.md rule 9 carve-out. Hare's seek / copy callbacks are
|
||||
// likewise absent: lib/io's stream vtable has only read/write/close
|
||||
// slots, so memio can't wire a seeker or copier even if we wanted
|
||||
// to. All three come back when their dependencies do.
|
||||
|
||||
package memio;
|
||||
|
||||
import io;
|
||||
import os;
|
||||
import rt;
|
||||
|
||||
// state — memio's per-stream bookkeeping. The caller owns the slot
|
||||
// and passes its address into a constructor. `ptr/len/cap` are the
|
||||
// slice fields kept flat to dodge a chained-dot write through the
|
||||
// state pointer (cgen doesn't store into `m.buf.ptr` reliably).
|
||||
export type state = struct {
|
||||
ptr: *u8,
|
||||
len: i32,
|
||||
cap: i32,
|
||||
pos: i32,
|
||||
};
|
||||
|
||||
// fixed — wire `s` over a caller-supplied buffer. Writes never grow;
|
||||
// they return 0 once `pos` reaches the end of the buffer.
|
||||
export fn fixed(m: *state, s: *io.stream, buf: []u8) void = {
|
||||
m.ptr = buf.ptr;
|
||||
m.len = buf.len;
|
||||
m.cap = buf.len;
|
||||
m.pos = 0;
|
||||
s.ctx = m: *void;
|
||||
s.read = readfn;
|
||||
s.write = fixedwrite;
|
||||
s.close = closenoop;
|
||||
};
|
||||
|
||||
// dynamic — wire `s` with no initial buffer. Writes grow the backing
|
||||
// allocation; [[io.close]] frees it.
|
||||
export fn dynamic(m: *state, s: *io.stream) void = {
|
||||
m.ptr = nil;
|
||||
m.len = 0;
|
||||
m.cap = 0;
|
||||
m.pos = 0;
|
||||
s.ctx = m: *void;
|
||||
s.read = readfn;
|
||||
s.write = dynamicwrite;
|
||||
s.close = dynamicclose;
|
||||
};
|
||||
|
||||
// dynamicfrom — like [[dynamic]] but seeded with an existing slice.
|
||||
// Ownership of the slice transfers to the stream; [[io.close]] frees
|
||||
// it. The slice must come from the runtime allocator: close calls
|
||||
// [[os.free]] with `m.cap` bytes, which is taken from `buf.cap` (the
|
||||
// slice's allocated capacity), not its logical length. Passing a
|
||||
// half-filled append slice (len < cap) and using only `buf.len` here
|
||||
// would under-free on close.
|
||||
export fn dynamicfrom(m: *state, s: *io.stream, buf: []u8) void = {
|
||||
m.ptr = buf.ptr;
|
||||
m.len = buf.len;
|
||||
m.cap = buf.cap;
|
||||
m.pos = 0;
|
||||
s.ctx = m: *void;
|
||||
s.read = readfn;
|
||||
s.write = dynamicwrite;
|
||||
s.close = dynamicclose;
|
||||
};
|
||||
|
||||
// buffer — borrowed view of bytes written so far (buf[..pos]).
|
||||
// Seek to the end before calling if the full buffer is wanted.
|
||||
export fn buffer(m: *state) []u8 = {
|
||||
let r: []u8;
|
||||
r.ptr = m.ptr;
|
||||
r.len = m.pos;
|
||||
return r;
|
||||
};
|
||||
|
||||
// string — bytes written so far, as a str view. Hare returns
|
||||
// (str | utf8::invalid); ww doesn't ship utf8 validation yet, so
|
||||
// this returns the unchecked view.
|
||||
export fn string(m: *state) str = {
|
||||
let r: str;
|
||||
r.ptr = m.ptr;
|
||||
r.len = m.pos;
|
||||
return r;
|
||||
};
|
||||
|
||||
// reset — rewind the cursor and truncate the logical content to 0.
|
||||
// Backing storage is preserved; subsequent writes (dynamic) re-fill
|
||||
// from the start without reallocation.
|
||||
export fn reset(m: *state) void = {
|
||||
m.pos = 0;
|
||||
m.len = 0;
|
||||
};
|
||||
|
||||
// borrowedread — return an `amt`-byte view starting at `pos` without
|
||||
// copying, advancing the cursor. eof if fewer bytes are available.
|
||||
export fn borrowedread(m: *state, amt: i32) ([]u8 | io.eof) = {
|
||||
if (m.len - m.pos < amt) {
|
||||
let e: io.eof;
|
||||
return e;
|
||||
};
|
||||
let r: []u8;
|
||||
r.ptr = m.ptr + (m.pos: u64);
|
||||
r.len = amt;
|
||||
m.pos += amt;
|
||||
return r;
|
||||
};
|
||||
|
||||
// ---- vtable callbacks ------------------------------------------------
|
||||
|
||||
fn readfn(s: *io.stream, buf: []u8) (i32 | io.eof | io.closed) = {
|
||||
let m: *state = s.ctx: *state;
|
||||
if (m.pos >= m.len) {
|
||||
let e: io.eof;
|
||||
return e;
|
||||
};
|
||||
let avail: i32 = m.len - m.pos;
|
||||
let n: i32 = buf.len;
|
||||
if (avail < n) { n = avail; };
|
||||
let i: i32 = 0;
|
||||
for (i < n) {
|
||||
buf[i] = m.ptr[m.pos + i];
|
||||
i += 1;
|
||||
};
|
||||
m.pos += n;
|
||||
return n;
|
||||
};
|
||||
|
||||
fn fixedwrite(s: *io.stream, buf: []u8) (i32 | io.closed) = {
|
||||
let m: *state = s.ctx: *state;
|
||||
if (m.pos >= m.len) { return 0; };
|
||||
let space: i32 = m.len - m.pos;
|
||||
let n: i32 = buf.len;
|
||||
if (space < n) { n = space; };
|
||||
let i: i32 = 0;
|
||||
for (i < n) {
|
||||
m.ptr[m.pos + i] = buf[i];
|
||||
i += 1;
|
||||
};
|
||||
m.pos += n;
|
||||
return n;
|
||||
};
|
||||
|
||||
fn dynamicwrite(s: *io.stream, buf: []u8) (i32 | io.closed) = {
|
||||
let m: *state = s.ctx: *state;
|
||||
let need: i32 = m.pos + buf.len;
|
||||
if (need > m.cap) { dynamicgrow(m, need); };
|
||||
let i: i32 = 0;
|
||||
for (i < buf.len) {
|
||||
m.ptr[m.pos + i] = buf[i];
|
||||
i += 1;
|
||||
};
|
||||
m.pos += buf.len;
|
||||
if (m.pos > m.len) { m.len = m.pos; };
|
||||
return buf.len;
|
||||
};
|
||||
|
||||
fn dynamicclose(s: *io.stream) (void | io.closed) = {
|
||||
let m: *state = s.ctx: *state;
|
||||
if (m.cap > 0) { os.free(m.ptr: *void, m.cap: u64); };
|
||||
m.ptr = nil;
|
||||
m.len = 0;
|
||||
m.cap = 0;
|
||||
m.pos = 0;
|
||||
return;
|
||||
};
|
||||
|
||||
fn closenoop(s: *io.stream) (void | io.closed) = {
|
||||
return;
|
||||
};
|
||||
|
||||
// Double-and-copy growth. Initial bump from 0 lands at 8 to amortise
|
||||
// small write bursts without a tail of reallocs.
|
||||
//
|
||||
// `dynamicgrow`, not Hare's bare `grow`: cstage bundles all imported
|
||||
// modules into a flat TU and resolves private fns by unqualified name,
|
||||
// so historically two `fn grow` decls (here + the now-deleted bump
|
||||
// arena's `grow`) would have collided. Module-prefixed name kept the
|
||||
// symmetry with `dynamicwrite`/`dynamicclose`; retained pending task
|
||||
// #9 (module-aware private-fn scoping in cstage).
|
||||
fn dynamicgrow(m: *state, need: i32) void = {
|
||||
let newcap: i32 = m.cap;
|
||||
if (newcap < 8) { newcap = 8; };
|
||||
for (newcap < need) { newcap *= 2; };
|
||||
let nbuf: *u8 = rt.malloc(newcap: u64): *u8;
|
||||
let i: i32 = 0;
|
||||
for (i < m.len) {
|
||||
nbuf[i] = m.ptr[i];
|
||||
i += 1;
|
||||
};
|
||||
if (m.cap > 0) { os.free(m.ptr: *void, m.cap: u64); };
|
||||
m.ptr = nbuf;
|
||||
m.cap = newcap;
|
||||
};
|
||||
|
||||
// vstream — Hare-shaped vtable wrappers over memio. Project #94
|
||||
// fold-eFinal(PREP).
|
||||
//
|
||||
// Adds three constructors that return a memio `stream` BY VALUE (the
|
||||
// Hare shape, ref/hare/memio/stream.ha:46,58,64) alongside the
|
||||
// pre-vtable memio.fixed / memio.dynamic / memio.dynamicfrom in
|
||||
// memio.ww. The OLD surface stays untouched here — fold-eFinal (FLIP)
|
||||
// atomically flips the package shape: deletes the OLD constructors +
|
||||
// `state` + callbacks, renames the `_vstream`/`_v` suffix off (so
|
||||
// `fixed_vstream → fixed`, `stream` stays, `string_v → string`), and
|
||||
// migrates the few callers (lib/fmt/vstream.ww + the 776 probe).
|
||||
//
|
||||
// Hare's memio::fixed/dynamic/dynamic_from return a `stream` whose
|
||||
// FIRST field IS the `io::stream` (= `*vtable`). ww mirrors that
|
||||
// intrusively: `stream`'s first field is `vt: io.vtable` (the vtable
|
||||
// embedded INLINE) so a stack `stream` is castable to
|
||||
// `vstream = *vtable` via `&s.vt` — and the callbacks recover the
|
||||
// `io.stream = *vtable` via `&s.vt` — and the callbacks recover the
|
||||
// outer `stream` by casting the dispatch arg back to `*stream`. Same
|
||||
// intrusive shape as lib/bufio + lib/log over their embedded vtables.
|
||||
//
|
||||
// VALUE-RETURN (drew, gating): the constructor builds the struct in a
|
||||
// local `let r: stream;`, field-assigns every slot (including the
|
||||
// tagged vt sub-fields), and `return r;` — the proven sret round-trip
|
||||
// shape pinned by test/wcc/925 ("ident_return_rhs" row) and extended
|
||||
// to tagged-union-field structs by the 776 byte-id rows here. NO heap,
|
||||
// NO `nomem`: the alloc that forced the OLD `(io.vstream | nomem)`
|
||||
// return is gone, so the constructor cannot fail. Caller owns the
|
||||
// returned `stream` (stack ownership, no-GC) and passes `&s.vt` to the
|
||||
// io.st_* dispatchers — exactly Hare's `&s` into io::write.
|
||||
// let s: memio.stream = memio.fixed(buf);
|
||||
// io.write(&s.vt, bytes);
|
||||
// let view: str = memio.string(&s);
|
||||
//
|
||||
// Constructors return the `stream` BY VALUE (Hare shape,
|
||||
// ref/hare/memio/stream.ha:46,58,64): build the struct in a local,
|
||||
// field-assign every slot, and `return r;` (the proven sret round-trip
|
||||
// pinned by test/wcc/925 + 776). NO heap, NO `nomem`: the alloc that
|
||||
// forced an earlier heap return is gone, so the constructor cannot
|
||||
// fail. Caller owns the returned `stream` (stack ownership, no-GC) and
|
||||
// passes `&s.vt` to the io dispatchers — exactly Hare's `&s` into
|
||||
// io::write.
|
||||
//
|
||||
// Subset of Hare's surface: io's variants are {eof, error}, so memio
|
||||
// drops Hare's NONBLOCK flag (would need an `again` variant in lib/io).
|
||||
// string()'s utf8-validating constructor is omitted per CLAUDE.md
|
||||
// rule 9 carve-out — see [[string]]. Hare's seek / copy callbacks are
|
||||
// likewise absent: io's stream vtable has only read/write/close slots,
|
||||
// so memio can't wire a seeker or copier yet (io fold-2, #5).
|
||||
//
|
||||
// Cast workaround per #206-payoff (ken: KEEP the explicit casts; they
|
||||
// are cgen-neutral and sidestep the #214 over-acceptance surface). The
|
||||
// `(&fn_name): *io.<role>` cast at each store site is the Hare-faithful
|
||||
// minimum-touch route — same workaround test/wcc/775_io_vtable_run.c
|
||||
// uses for the bare-vtable init.
|
||||
// minimum-touch route — the #206 cast-drop is a separate deferred
|
||||
// payoff gated on #214.
|
||||
//
|
||||
// ptr/len/cap kept flat (no `buf: []u8`) per the memio.state note
|
||||
// (memio.ww:39): chained-dot writes through a state pointer into a
|
||||
// slice subfield miscompile silently (#195 family); the flat shape
|
||||
// sidesteps it. dynamicfrom_vstream uses the slice's `cap` (NOT `len`)
|
||||
// to track the allocated-capacity-to-free on close — mirrors the OLD
|
||||
// memio.dynamicfrom note (memio.ww:73).
|
||||
// ptr/len/cap kept flat (no `buf: []u8`) per a historical cgen note:
|
||||
// chained-dot writes through a state pointer into a slice subfield
|
||||
// miscompile silently (#195 family); the flat shape sidesteps it.
|
||||
// dynamicfrom uses the slice's `cap` (NOT `len`) to track the
|
||||
// allocated-capacity-to-free on close — passing a half-filled append
|
||||
// slice with len < cap and using only `buf.len` would under-free.
|
||||
|
||||
package memio;
|
||||
|
||||
@@ -14551,11 +14307,11 @@ import os;
|
||||
import rt;
|
||||
|
||||
// stream — Hare's memio::stream (ref/hare/memio/stream.ha:18). `vt` at
|
||||
// offset 0 for the intrusive stream→vstream cast (`&s.vt`) and the
|
||||
// offset 0 for the intrusive stream→io.stream cast (`&s.vt`) and the
|
||||
// callbacks' reverse `s: *stream` cast. Unified across fixed/dynamic
|
||||
// (Hare keeps a single `stream` over per-mode vtable singletons; ww
|
||||
// wires the per-mode callbacks post-construction instead). ptr/len/cap
|
||||
// flat per the memio.state note above.
|
||||
// flat per the header note.
|
||||
export type stream = struct {
|
||||
vt: io.vtable,
|
||||
ptr: *u8,
|
||||
@@ -14564,17 +14320,17 @@ export type stream = struct {
|
||||
pos: i32,
|
||||
};
|
||||
|
||||
// fixed_vstream — wire a stream over a caller-supplied buffer. Writes
|
||||
// never grow; they return 0 once `pos` reaches the end of the buffer
|
||||
// (Hare returns `nomem` here; ww surfaces 0 to mirror the OLD
|
||||
// memio.fixedwrite divergence — graduating to Hare's `nomem` return
|
||||
// needs the widen-from-bare-nomem path that #173 blocks).
|
||||
// fixed — wire a stream over a caller-supplied buffer. Writes never
|
||||
// grow; they return 0 once `pos` reaches the end of the buffer (Hare
|
||||
// returns `nomem` here; ww surfaces 0 — graduating to Hare's `nomem`
|
||||
// return needs the widen-from-bare-nomem path that #173-family work
|
||||
// gates).
|
||||
//
|
||||
// Mirrors ref/hare/memio/stream.ha:46.
|
||||
export fn fixed_vstream(buf: []u8) stream = {
|
||||
export fn fixed(buf: []u8) stream = {
|
||||
let r: stream;
|
||||
r.vt.reader = (&read_v): *io.reader;
|
||||
r.vt.writer = (&fixedwrite_v): *io.writer;
|
||||
r.vt.reader = (&readfn): *io.reader;
|
||||
r.vt.writer = (&fixedwrite): *io.writer;
|
||||
r.ptr = buf.ptr;
|
||||
r.len = buf.len;
|
||||
r.cap = buf.len;
|
||||
@@ -14582,15 +14338,15 @@ export fn fixed_vstream(buf: []u8) stream = {
|
||||
return r;
|
||||
};
|
||||
|
||||
// dynamic_vstream — wire a stream with no initial buffer. Writes grow
|
||||
// the backing allocation; [[io.st_close]] frees it.
|
||||
// dynamic — wire a stream with no initial buffer. Writes grow the
|
||||
// backing allocation; [[io.close]] frees it.
|
||||
//
|
||||
// Mirrors ref/hare/memio/stream.ha:58.
|
||||
export fn dynamic_vstream() stream = {
|
||||
export fn dynamic() stream = {
|
||||
let r: stream;
|
||||
r.vt.reader = (&read_v): *io.reader;
|
||||
r.vt.writer = (&dynamicwrite_v): *io.writer;
|
||||
r.vt.closer = (&dynamicclose_v): *io.closer;
|
||||
r.vt.reader = (&readfn): *io.reader;
|
||||
r.vt.writer = (&dynamicwrite): *io.writer;
|
||||
r.vt.closer = (&dynamicclose): *io.closer;
|
||||
r.ptr = nil;
|
||||
r.len = 0;
|
||||
r.cap = 0;
|
||||
@@ -14598,18 +14354,16 @@ export fn dynamic_vstream() stream = {
|
||||
return r;
|
||||
};
|
||||
|
||||
// dynamicfrom_vstream — like [[dynamic_vstream]] but seeded with an
|
||||
// existing slice. Ownership transfers; close frees `cap` bytes from
|
||||
// the slice's allocated capacity (NOT logical length) — passing a
|
||||
// half-filled append slice with len < cap and using only `buf.len`
|
||||
// would under-free on close.
|
||||
// dynamicfrom — like [[dynamic]] but seeded with an existing slice.
|
||||
// Ownership transfers; close frees `cap` bytes from the slice's
|
||||
// allocated capacity (NOT logical length).
|
||||
//
|
||||
// Mirrors ref/hare/memio/stream.ha:64.
|
||||
export fn dynamicfrom_vstream(buf: []u8) stream = {
|
||||
export fn dynamicfrom(buf: []u8) stream = {
|
||||
let r: stream;
|
||||
r.vt.reader = (&read_v): *io.reader;
|
||||
r.vt.writer = (&dynamicwrite_v): *io.writer;
|
||||
r.vt.closer = (&dynamicclose_v): *io.closer;
|
||||
r.vt.reader = (&readfn): *io.reader;
|
||||
r.vt.writer = (&dynamicwrite): *io.writer;
|
||||
r.vt.closer = (&dynamicclose): *io.closer;
|
||||
r.ptr = buf.ptr;
|
||||
r.len = buf.len;
|
||||
r.cap = buf.cap;
|
||||
@@ -14619,11 +14373,11 @@ export fn dynamicfrom_vstream(buf: []u8) stream = {
|
||||
|
||||
// ---- vtable callbacks ----------------------------------------------------
|
||||
|
||||
// read_v — recover the stream from the vstream's `*vtable` via the
|
||||
// readfn — recover the stream from the io.stream's `*vtable` via the
|
||||
// intrusive offset-0 cast. Single fn over the common header (Hare's
|
||||
// single `read` at ref/hare/memio/stream.ha:103); fixed and dynamic
|
||||
// share it because the read path is buffer-flavour-agnostic.
|
||||
fn read_v(s: io.vstream, buf: []u8) (size | io.eof | io.error) = {
|
||||
fn readfn(s: io.stream, buf: []u8) (size | io.eof | io.error) = {
|
||||
let m: *stream = s: *stream;
|
||||
if (m.pos >= m.len) {
|
||||
let e: io.eof;
|
||||
@@ -14641,7 +14395,7 @@ fn read_v(s: io.vstream, buf: []u8) (size | io.eof | io.error) = {
|
||||
return n: size;
|
||||
};
|
||||
|
||||
fn fixedwrite_v(s: io.vstream, buf: []u8) (size | io.error) = {
|
||||
fn fixedwrite(s: io.stream, buf: []u8) (size | io.error) = {
|
||||
let m: *stream = s: *stream;
|
||||
if (m.pos >= m.len) { return 0: size; };
|
||||
let space: i32 = m.len - m.pos;
|
||||
@@ -14656,10 +14410,10 @@ fn fixedwrite_v(s: io.vstream, buf: []u8) (size | io.error) = {
|
||||
return n: size;
|
||||
};
|
||||
|
||||
fn dynamicwrite_v(s: io.vstream, buf: []u8) (size | io.error) = {
|
||||
fn dynamicwrite(s: io.stream, buf: []u8) (size | io.error) = {
|
||||
let m: *stream = s: *stream;
|
||||
let need: i32 = m.pos + buf.len;
|
||||
if (need > m.cap) { dynamicgrow_v(m, need); };
|
||||
if (need > m.cap) { dynamicgrow(m, need); };
|
||||
let i: i32 = 0;
|
||||
for (i < buf.len) {
|
||||
m.ptr[m.pos + i] = buf[i];
|
||||
@@ -14670,7 +14424,7 @@ fn dynamicwrite_v(s: io.vstream, buf: []u8) (size | io.error) = {
|
||||
return buf.len: size;
|
||||
};
|
||||
|
||||
fn dynamicclose_v(s: io.vstream) (void | io.error) = {
|
||||
fn dynamicclose(s: io.stream) (void | io.error) = {
|
||||
let m: *stream = s: *stream;
|
||||
if (m.cap > 0) { os.free(m.ptr: *void, m.cap: u64); };
|
||||
m.ptr = nil;
|
||||
@@ -14680,11 +14434,11 @@ fn dynamicclose_v(s: io.vstream) (void | io.error) = {
|
||||
return;
|
||||
};
|
||||
|
||||
// Mirror of memio.ww:203 dynamicgrow. Double-and-copy with floor at
|
||||
// 8. Module-prefixed `_v` suffix vs the OLD memio.dynamicgrow keeps
|
||||
// the cstage flat-TU private-fn scope from colliding (memio.ww:200
|
||||
// note + task #9).
|
||||
fn dynamicgrow_v(d: *stream, need: i32) void = {
|
||||
// Double-and-copy growth with floor at 8. `dynamicgrow`, not Hare's
|
||||
// bare `grow`: cstage bundles all imported modules into a flat TU and
|
||||
// resolves private fns by unqualified name (task #9), so the
|
||||
// module-prefixed name keeps the symmetry with dynamicwrite/dynamicclose.
|
||||
fn dynamicgrow(d: *stream, need: i32) void = {
|
||||
let newcap: i32 = d.cap;
|
||||
if (newcap < 8) { newcap = 8; };
|
||||
for (newcap < need) { newcap *= 2; };
|
||||
@@ -14701,53 +14455,49 @@ fn dynamicgrow_v(d: *stream, need: i32) void = {
|
||||
|
||||
// ---- accessors over the common `stream` header -----------------------
|
||||
//
|
||||
// Single fn each (drew string-collapse): Hare's string/reset/buffer/
|
||||
// borrowedread all take `*stream` and read the flat header, so the
|
||||
// fixed/dynamic split the OLD V-side carried (fixed_string /
|
||||
// dynamic_string) collapses to one. FLIP drops the `_v` suffix
|
||||
// (string_v → string, …) and deletes the OLD `*state` twins in
|
||||
// memio.ww.
|
||||
// Single fn each: Hare's string/reset/buffer/borrowedread all take
|
||||
// `*stream` and read the flat header.
|
||||
|
||||
// string_v — bytes written so far, as a str view (buf[0..pos]).
|
||||
// string — bytes written so far, as a str view (buf[0..pos]).
|
||||
//
|
||||
// Mirrors ref/hare/memio/stream.ha:81 string(in: *stream). Hare returns
|
||||
// (str | utf8::invalid) — the validating constructor. ww returns a bare
|
||||
// `str` per the CLAUDE.md rule-9 frombytes carve-out: utf8.validate at
|
||||
// the IO source is opt-in, never wrapped per-construction; the honest
|
||||
// name reserves a future validating helper.
|
||||
export fn string_v(s: *stream) str = {
|
||||
export fn string(s: *stream) str = {
|
||||
let r: str;
|
||||
r.ptr = s.ptr;
|
||||
r.len = s.pos;
|
||||
return r;
|
||||
};
|
||||
|
||||
// buffer_v — borrowed []u8 view of bytes written so far (buf[0..pos]).
|
||||
// buffer — borrowed []u8 view of bytes written so far (buf[0..pos]).
|
||||
//
|
||||
// Mirrors ref/hare/memio/stream.ha:74 buffer(in: *stream).
|
||||
export fn buffer_v(s: *stream) []u8 = {
|
||||
export fn buffer(s: *stream) []u8 = {
|
||||
let r: []u8;
|
||||
r.ptr = s.ptr;
|
||||
r.len = s.pos;
|
||||
return r;
|
||||
};
|
||||
|
||||
// reset_v — rewind the cursor and truncate the logical content to 0.
|
||||
// reset — rewind the cursor and truncate the logical content to 0.
|
||||
// Backing storage is preserved; subsequent writes (dynamic) re-fill
|
||||
// from the start without reallocation.
|
||||
//
|
||||
// Mirrors ref/hare/memio/stream.ha:87 reset(in: *stream).
|
||||
export fn reset_v(s: *stream) void = {
|
||||
export fn reset(s: *stream) void = {
|
||||
s.pos = 0;
|
||||
s.len = 0;
|
||||
};
|
||||
|
||||
// borrowedread_v — return an `amt`-byte view starting at `pos` without
|
||||
// borrowedread — return an `amt`-byte view starting at `pos` without
|
||||
// copying, advancing the cursor. eof if fewer bytes are available.
|
||||
//
|
||||
// Mirrors ref/hare/memio/stream.ha:94 borrowedread(st: *stream, amt).
|
||||
// `amt: i32` (not Hare's `size`) per the i32-index convention.
|
||||
export fn borrowedread_v(s: *stream, amt: i32) ([]u8 | io.eof) = {
|
||||
export fn borrowedread(s: *stream, amt: i32) ([]u8 | io.eof) = {
|
||||
if (s.len - s.pos < amt) {
|
||||
let e: io.eof;
|
||||
return e;
|
||||
@@ -28984,14 +28734,13 @@ fn localfind(c: *cgen, name: str) i32 = {
|
||||
// Re-init per fn would abandon the buffer (no [[io.close]] path → no
|
||||
// [[os.free]]) and re-grow from 0 via the 8→…→65536 ladder for every
|
||||
// function. Same idiom as lib/log/log.ww:124 `ensureinit`.
|
||||
let cgoutstate: memio.state;
|
||||
let cgoutstream: io.stream;
|
||||
let cgoutstream: memio.stream;
|
||||
let cgoutmode: i32 = 0;
|
||||
let cgoutinit: i32 = 0;
|
||||
|
||||
fn cgout_enable() void = {
|
||||
if (cgoutinit == 0) {
|
||||
memio.dynamic(&cgoutstate, &cgoutstream);
|
||||
cgoutstream = memio.dynamic();
|
||||
cgoutinit = 1;
|
||||
};
|
||||
cgoutmode = 1;
|
||||
@@ -29000,9 +28749,9 @@ fn cgout_enable() void = {
|
||||
fn cgout_disable() void = { cgoutmode = 0; };
|
||||
|
||||
fn cgout_flush() void = {
|
||||
if (cgoutstate.pos > 0) {
|
||||
os.write(1, cgoutstate.ptr, cgoutstate.pos: u64);
|
||||
memio.reset(&cgoutstate);
|
||||
if (cgoutstream.pos > 0) {
|
||||
os.write(1, cgoutstream.ptr, cgoutstream.pos: u64);
|
||||
memio.reset(&cgoutstream);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -29011,9 +28760,10 @@ fn emitbytes(p: *u8, n: u64) void = {
|
||||
let buf: []u8;
|
||||
buf.ptr = p;
|
||||
buf.len = n: i32;
|
||||
// memio.dynamicwrite never returns io.closed (memio.ww:166);
|
||||
// bare-discard mirrors lib/log/log.ww:169 fmt.fprintln.
|
||||
io.write(&cgoutstream, buf);
|
||||
// io.write over the embedded vtable (&cgoutstream.vt = io.stream);
|
||||
// memio.dynamicwrite never errors. Bare-discard mirrors
|
||||
// lib/log/log.ww stdprintln. #94 fold-eFinal.
|
||||
io.write(&cgoutstream.vt, buf);
|
||||
} else {
|
||||
os.write(1, p, n);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -14054,54 +14054,35 @@ export fn checkfile(c: *checker, file: *node) void = {
|
||||
|
||||
// io — stream interface (Plan 9 Bio / Hare io::stream shape).
|
||||
//
|
||||
// No closures, no methods. A `stream` is a struct of function
|
||||
// pointers plus a `ctx: *void`. The error channel is the return
|
||||
// value of read/write/close — Hare-shaped tagged unions instead of
|
||||
// errno-style integer sentinels.
|
||||
// No closures, no methods. A `stream` is a pointer to a `vtable` of
|
||||
// function pointers (lib/io/stream.ww); read/write/close dispatch
|
||||
// through it. The error channel is the return value — Hare-shaped
|
||||
// tagged unions instead of errno-style integer sentinels.
|
||||
//
|
||||
// This file owns the eof / underread variant tags; lib/io/stream.ww
|
||||
// owns the `vtable` + `stream` + read/write/close dispatchers, and
|
||||
// lib/io/types.ww owns the error union, mode/whence enums, and the
|
||||
// reader/writer/closer fn-type aliases. #94 fold-eFinal collapsed the
|
||||
// pre-vtable `stream` struct + `closed` tag into the single vtable
|
||||
// surface; the dispatchers are read/write/close (over `stream`),
|
||||
// final over `handle` at io fold-2 (#5).
|
||||
package io;
|
||||
|
||||
// eof — read past the end of the stream. Hare uses the `done`
|
||||
// singleton for EOF; ww doesn't have `done` yet so we ship a
|
||||
// named-void variant tag.
|
||||
package io;
|
||||
|
||||
// named-void variant tag. Lifts to `done` with #93.
|
||||
export type eof = void;
|
||||
|
||||
// closed — operation attempted on a stream that has already been
|
||||
// closed. ww-specific: Hare's io collapses this into the wider
|
||||
// errors union, but our stream vtable has no handle-ownership
|
||||
// semantics, so a distinct tag is honest. Named void.
|
||||
export type closed = void;
|
||||
|
||||
// underread — an I/O handle hit eof partway through a fixed-size
|
||||
// read. Payload is the byte count actually delivered. Mirrors
|
||||
// Hare's `io::underread = !size`; ww uses i32 because the
|
||||
// underlying buffer-length type is i32 today.
|
||||
export type underread = !i32;
|
||||
|
||||
export type stream = struct {
|
||||
ctx: *void,
|
||||
read: fn(s: *stream, buf: []u8) (i32 | eof | closed),
|
||||
write: fn(s: *stream, buf: []u8) (i32 | closed),
|
||||
close: fn(s: *stream) (void | closed),
|
||||
};
|
||||
|
||||
export fn read(s: *stream, buf: []u8) (i32 | eof | closed) = {
|
||||
return s.read(s, buf);
|
||||
};
|
||||
|
||||
export fn write(s: *stream, buf: []u8) (i32 | closed) = {
|
||||
return s.write(s, buf);
|
||||
};
|
||||
|
||||
export fn close(s: *stream) (void | closed) = {
|
||||
return s.close(s);
|
||||
};
|
||||
|
||||
// stream — Hare-shaped vtable port. Project #94 fold-e1.
|
||||
// stream — Hare-shaped vtable surface. Project #94 fold-eFinal.
|
||||
//
|
||||
// Adds the additive vtable surface alongside lib/io/io.ww's pre-vtable
|
||||
// stream struct + read/write/close wrappers (which fold-e2-eN migrate
|
||||
// to vtable-backed implementations and then retire). Three exports:
|
||||
// The single io stream surface (the fold-eFinal collapse retired the
|
||||
// pre-vtable `stream` struct + `closed` tag). Three exports:
|
||||
//
|
||||
// vtable a struct of optional fn-pointer slots — reader/writer/
|
||||
// closer per ref/hare/io/stream.ha:36-42. Hare spells the
|
||||
@@ -14109,35 +14090,25 @@ export fn close(s: *stream) (void | closed) = {
|
||||
// spelling (#192), so each slot is a `(*T | void)` tagged
|
||||
// union (Plan-9-lean per the Nullable kept ruling; opt-in
|
||||
// at optionality boundaries only).
|
||||
// vstream `*vtable` alias matching Hare's `stream = *vtable`
|
||||
// (ref/hare/io/stream.ha:33). Named `vstream` rather than
|
||||
// `stream` because lib/io/io.ww still owns the legacy
|
||||
// `stream` struct name until fold-eN collapses both.
|
||||
// st_read /
|
||||
// st_write /
|
||||
// st_close the three dispatchers per ref/hare/io/stream.ha:44-68.
|
||||
// stream `*vtable` alias matching Hare's `stream = *vtable`
|
||||
// (ref/hare/io/stream.ha:33).
|
||||
// read /
|
||||
// write /
|
||||
// close the three dispatchers per ref/hare/io/stream.ha:44-68.
|
||||
// Each `match`es the matching vtable slot, returns
|
||||
// `errors.unsupported` (widened twice) when the slot is
|
||||
// void, otherwise calls through the fn pointer.
|
||||
// void, otherwise calls through the fn pointer. Hare keeps
|
||||
// these private (`st_read` …) behind a `handle`-typed
|
||||
// public `read`/`write`/`close`; ww has no `handle` yet
|
||||
// (io fold-2, #5), so the dispatchers ARE the public
|
||||
// surface and grow the `handle` match when #5 lands.
|
||||
//
|
||||
// Fold-e1 cgen/checker dependencies (all closed at HEAD): #191
|
||||
// (N_DOT on aliased-ptr receiver — `s.reader` resolves through the
|
||||
// `vstream = *vtable` alias), #198 (`is` on cross-module variant
|
||||
// tag), #199 α (concrete→tagged widen via NAMED-variant nominal
|
||||
// match, used by `let e: error = u;`), #200 (`as` payload-extract),
|
||||
// #201 (return-of-tagged-call forwards instead of rewinds), #205
|
||||
// (tagged→tagged subset accepts NAMED-variant nominal match, used
|
||||
// by `return e;` where ret type contains `error` as a direct
|
||||
// variant). Two direct widens compose for the void-arm `return`:
|
||||
// concrete `errors.unsupported` → tagged `error` via #199 α, then
|
||||
// tagged `error` → tagged `(size | eof | error)` via #205.
|
||||
//
|
||||
// Deferrals (drew-signed): `seeker` lands with fold-e2 once `off`
|
||||
// + `whence` plug into the signature; Hare's `?`-propagating
|
||||
// `st_close` body (`c(s)?;`) collapses to a direct `return (*c)(s);`
|
||||
// here because #173 (TRY-on-tagged-return both-stages broken) is
|
||||
// still open — the dispatcher's surface stays Hare-shaped, only the
|
||||
// internal try-prop is omitted.
|
||||
// Deferrals (drew-signed): `seeker` lands with io fold-2 (#5) once `off`
|
||||
// + `whence` plug into the signature. Hare's `?`-propagating `close`
|
||||
// body (`c(s)?;`) collapses to a direct `return (*c)(s);` here because
|
||||
// the dispatcher's surface stays Hare-shaped; only the internal
|
||||
// try-prop is omitted (#173 history, now closed — kept direct for the
|
||||
// void-arm fall-through shape).
|
||||
|
||||
package io;
|
||||
|
||||
@@ -14149,16 +14120,15 @@ export type vtable = struct {
|
||||
closer: (*closer | void),
|
||||
};
|
||||
|
||||
// ref/hare/io/stream.ha:33. `vstream` not `stream` while io.ww still
|
||||
// owns the legacy `stream` struct name.
|
||||
export type vstream = *vtable;
|
||||
// ref/hare/io/stream.ha:33.
|
||||
export type stream = *vtable;
|
||||
|
||||
// ref/hare/io/stream.ha:44-50. The void-arm `return e;` chains two
|
||||
// direct widens: `errors.unsupported → error` via #199 α, then
|
||||
// `error → (size | eof | error)` via #205. The call-arm spells the
|
||||
// fn-ptr call explicitly per #193 (ww requires `(*r)(args)` rather
|
||||
// than implicit `r(args)`).
|
||||
export fn st_read(s: vstream, buf: []u8) (size | eof | error) = {
|
||||
export fn read(s: stream, buf: []u8) (size | eof | error) = {
|
||||
match (s.reader) {
|
||||
case void => {
|
||||
let u: errors.unsupported;
|
||||
@@ -14170,7 +14140,7 @@ export fn st_read(s: vstream, buf: []u8) (size | eof | error) = {
|
||||
};
|
||||
|
||||
// ref/hare/io/stream.ha:53-59.
|
||||
export fn st_write(s: vstream, buf: []u8) (size | error) = {
|
||||
export fn write(s: stream, buf: []u8) (size | error) = {
|
||||
match (s.writer) {
|
||||
case void => {
|
||||
let u: errors.unsupported;
|
||||
@@ -14183,10 +14153,9 @@ export fn st_write(s: vstream, buf: []u8) (size | error) = {
|
||||
|
||||
// ref/hare/io/stream.ha:62-68. Hare propagates the close error via
|
||||
// `c(s)?;` then falls through to the void arm; ww collapses to a
|
||||
// direct return because #173 (TRY-on-tagged-return) is still open.
|
||||
// The surface (void | error) matches; only the internal try-prop
|
||||
// is omitted.
|
||||
export fn st_close(s: vstream) (void | error) = {
|
||||
// direct return for the void-arm fall-through shape. The surface
|
||||
// (void | error) matches.
|
||||
export fn close(s: stream) (void | error) = {
|
||||
match (s.closer) {
|
||||
case void => return void;
|
||||
case let c: *closer => return (*c)(s);
|
||||
@@ -14222,21 +14191,19 @@ export type exists = !void;
|
||||
export type unsupported = !void;
|
||||
|
||||
// types — error union, mode/whence enums, reader/writer/closer
|
||||
// fn-type aliases. Project #94 fold-1 step (d) (drew Fold D); the
|
||||
// fn-aliases re-targeted from *stream → vstream as part of fold-e1
|
||||
// (vtable port in stream.ww).
|
||||
// fn-type aliases. Project #94 fold-eFinal; the fn-aliases target
|
||||
// `stream` (= `*vtable`, the single io surface).
|
||||
//
|
||||
// Hare splits the io module across stream.ha + types.ha and ww does
|
||||
// the same: lib/io/io.ww owns the (pre-vtable) `stream`, `eof`, and
|
||||
// `underread` shapes; lib/io/stream.ww owns the Hare vtable port
|
||||
// (`vtable`, `vstream`, `st_read`/`st_write`/`st_close` dispatch);
|
||||
// this file owns the surrounding port. The three files share
|
||||
// `package io;` so cross-file refs resolve via the dir-enum concat
|
||||
// order (io.ww < stream.ww < types.ww — `vstream` lands before the
|
||||
// reader/writer/closer aliases below).
|
||||
// the same: lib/io/io.ww owns the `eof` and `underread` tags;
|
||||
// lib/io/stream.ww owns the vtable surface (`vtable`, `stream`,
|
||||
// `read`/`write`/`close` dispatch); this file owns the surrounding
|
||||
// port. The three files share `package io;` so cross-file refs
|
||||
// resolve via the dir-enum concat order (io.ww < stream.ww < types.ww
|
||||
// — `stream` lands before the reader/writer/closer aliases below).
|
||||
//
|
||||
// Deferrals (drew-signed): `handle`, `seeker`, `copier`, `strerror`
|
||||
// land with fold-e2+ (need the `handle` sum). Hare's `EOF = done`
|
||||
// land with io fold-2 (#5; need the `handle` sum). Hare's `EOF = done`
|
||||
// stays as `io.eof` until the `done` singleton ships (#93).
|
||||
|
||||
package io;
|
||||
@@ -14266,283 +14233,72 @@ export type whence = enum i32 {
|
||||
// ww enumerates the tags explicitly (ken's #204-block — spread of an
|
||||
// errors-side tagged into this union triggers a wrapper-vs-flatten
|
||||
// layout mismatch; #199b layout-extension is the deferred fix). The
|
||||
// st_read/st_write dispatchers in stream.ww return
|
||||
// `errors.unsupported` when the matching vtable slot is unset, so the
|
||||
// variant lands here directly.
|
||||
// read/write dispatchers in stream.ww return `errors.unsupported`
|
||||
// when the matching vtable slot is unset, so the variant lands here
|
||||
// directly.
|
||||
export type error = !(errors.unsupported | underread | nomem);
|
||||
|
||||
// ref/hare/io/types.ha:46. `eof` (not Hare's `done`) per the io.ww:8
|
||||
// rationale; lifts to `done` with #93. `vstream` forward-refs the
|
||||
// ref/hare/io/types.ha:46. `eof` (not Hare's `done`) per the io.ww
|
||||
// rationale; lifts to `done` with #93. `stream` forward-refs the
|
||||
// `*vtable` alias in stream.ww — the dispatcher receives the vtable
|
||||
// pointer directly (Hare's stream.ha:33 + types.ha:46 collapse).
|
||||
export type reader = fn(s: vstream, buf: []u8) (size | eof | error);
|
||||
export type reader = fn(s: stream, buf: []u8) (size | eof | error);
|
||||
|
||||
// ref/hare/io/types.ha:51. No `const` qualifier — ww has none, and
|
||||
// lib/io/io.ww:30 + lib/sort/sort.ww:18 drop it the same way.
|
||||
export type writer = fn(s: vstream, buf: []u8) (size | error);
|
||||
// lib/sort/sort.ww:18 drops it the same way.
|
||||
export type writer = fn(s: stream, buf: []u8) (size | error);
|
||||
|
||||
// ref/hare/io/types.ha:55.
|
||||
export type closer = fn(s: vstream) (void | error);
|
||||
export type closer = fn(s: stream) (void | error);
|
||||
|
||||
// memio — in-memory io stream.
|
||||
// memio — in-memory io stream. Project #94 fold-eFinal.
|
||||
//
|
||||
// Hare's memio:: surface, drop underscores. Two flavours behind a
|
||||
// single [[io.stream]]:
|
||||
// single [[io.stream]] (= `*io.vtable`):
|
||||
//
|
||||
// fixed caller owns the buffer, writes stop when full.
|
||||
// dynamic memio owns the buffer, writes grow it; close frees.
|
||||
//
|
||||
// Call shape divergence from Hare: the caller supplies both the
|
||||
// memio `state` and the `io.stream` slot, by pointer. ww cgen does
|
||||
// not yet implement &x.field or 32B-struct return-by-value, so the
|
||||
// Hare `let s = memio::fixed(buf)` shape isn't reachable; collapse
|
||||
// to a single returned struct when those land (lib/CLAUDE.md
|
||||
// "graduate in one go").
|
||||
//
|
||||
// let mem: memio.state;
|
||||
// let s: io.stream;
|
||||
// memio.fixed(&mem, &s, buf);
|
||||
// io.write(&s, bytes);
|
||||
//
|
||||
// Subset of Hare's surface: io.stream's variants are {eof, closed},
|
||||
// so memio drops Hare's NONBLOCK flag (would need an `again` variant
|
||||
// in lib/io). string()'s utf8-validating constructor is omitted per
|
||||
// CLAUDE.md rule 9 carve-out. Hare's seek / copy callbacks are
|
||||
// likewise absent: lib/io's stream vtable has only read/write/close
|
||||
// slots, so memio can't wire a seeker or copier even if we wanted
|
||||
// to. All three come back when their dependencies do.
|
||||
|
||||
package memio;
|
||||
|
||||
import io;
|
||||
import os;
|
||||
import rt;
|
||||
|
||||
// state — memio's per-stream bookkeeping. The caller owns the slot
|
||||
// and passes its address into a constructor. `ptr/len/cap` are the
|
||||
// slice fields kept flat to dodge a chained-dot write through the
|
||||
// state pointer (cgen doesn't store into `m.buf.ptr` reliably).
|
||||
export type state = struct {
|
||||
ptr: *u8,
|
||||
len: i32,
|
||||
cap: i32,
|
||||
pos: i32,
|
||||
};
|
||||
|
||||
// fixed — wire `s` over a caller-supplied buffer. Writes never grow;
|
||||
// they return 0 once `pos` reaches the end of the buffer.
|
||||
export fn fixed(m: *state, s: *io.stream, buf: []u8) void = {
|
||||
m.ptr = buf.ptr;
|
||||
m.len = buf.len;
|
||||
m.cap = buf.len;
|
||||
m.pos = 0;
|
||||
s.ctx = m: *void;
|
||||
s.read = readfn;
|
||||
s.write = fixedwrite;
|
||||
s.close = closenoop;
|
||||
};
|
||||
|
||||
// dynamic — wire `s` with no initial buffer. Writes grow the backing
|
||||
// allocation; [[io.close]] frees it.
|
||||
export fn dynamic(m: *state, s: *io.stream) void = {
|
||||
m.ptr = nil;
|
||||
m.len = 0;
|
||||
m.cap = 0;
|
||||
m.pos = 0;
|
||||
s.ctx = m: *void;
|
||||
s.read = readfn;
|
||||
s.write = dynamicwrite;
|
||||
s.close = dynamicclose;
|
||||
};
|
||||
|
||||
// dynamicfrom — like [[dynamic]] but seeded with an existing slice.
|
||||
// Ownership of the slice transfers to the stream; [[io.close]] frees
|
||||
// it. The slice must come from the runtime allocator: close calls
|
||||
// [[os.free]] with `m.cap` bytes, which is taken from `buf.cap` (the
|
||||
// slice's allocated capacity), not its logical length. Passing a
|
||||
// half-filled append slice (len < cap) and using only `buf.len` here
|
||||
// would under-free on close.
|
||||
export fn dynamicfrom(m: *state, s: *io.stream, buf: []u8) void = {
|
||||
m.ptr = buf.ptr;
|
||||
m.len = buf.len;
|
||||
m.cap = buf.cap;
|
||||
m.pos = 0;
|
||||
s.ctx = m: *void;
|
||||
s.read = readfn;
|
||||
s.write = dynamicwrite;
|
||||
s.close = dynamicclose;
|
||||
};
|
||||
|
||||
// buffer — borrowed view of bytes written so far (buf[..pos]).
|
||||
// Seek to the end before calling if the full buffer is wanted.
|
||||
export fn buffer(m: *state) []u8 = {
|
||||
let r: []u8;
|
||||
r.ptr = m.ptr;
|
||||
r.len = m.pos;
|
||||
return r;
|
||||
};
|
||||
|
||||
// string — bytes written so far, as a str view. Hare returns
|
||||
// (str | utf8::invalid); ww doesn't ship utf8 validation yet, so
|
||||
// this returns the unchecked view.
|
||||
export fn string(m: *state) str = {
|
||||
let r: str;
|
||||
r.ptr = m.ptr;
|
||||
r.len = m.pos;
|
||||
return r;
|
||||
};
|
||||
|
||||
// reset — rewind the cursor and truncate the logical content to 0.
|
||||
// Backing storage is preserved; subsequent writes (dynamic) re-fill
|
||||
// from the start without reallocation.
|
||||
export fn reset(m: *state) void = {
|
||||
m.pos = 0;
|
||||
m.len = 0;
|
||||
};
|
||||
|
||||
// borrowedread — return an `amt`-byte view starting at `pos` without
|
||||
// copying, advancing the cursor. eof if fewer bytes are available.
|
||||
export fn borrowedread(m: *state, amt: i32) ([]u8 | io.eof) = {
|
||||
if (m.len - m.pos < amt) {
|
||||
let e: io.eof;
|
||||
return e;
|
||||
};
|
||||
let r: []u8;
|
||||
r.ptr = m.ptr + (m.pos: u64);
|
||||
r.len = amt;
|
||||
m.pos += amt;
|
||||
return r;
|
||||
};
|
||||
|
||||
// ---- vtable callbacks ------------------------------------------------
|
||||
|
||||
fn readfn(s: *io.stream, buf: []u8) (i32 | io.eof | io.closed) = {
|
||||
let m: *state = s.ctx: *state;
|
||||
if (m.pos >= m.len) {
|
||||
let e: io.eof;
|
||||
return e;
|
||||
};
|
||||
let avail: i32 = m.len - m.pos;
|
||||
let n: i32 = buf.len;
|
||||
if (avail < n) { n = avail; };
|
||||
let i: i32 = 0;
|
||||
for (i < n) {
|
||||
buf[i] = m.ptr[m.pos + i];
|
||||
i += 1;
|
||||
};
|
||||
m.pos += n;
|
||||
return n;
|
||||
};
|
||||
|
||||
fn fixedwrite(s: *io.stream, buf: []u8) (i32 | io.closed) = {
|
||||
let m: *state = s.ctx: *state;
|
||||
if (m.pos >= m.len) { return 0; };
|
||||
let space: i32 = m.len - m.pos;
|
||||
let n: i32 = buf.len;
|
||||
if (space < n) { n = space; };
|
||||
let i: i32 = 0;
|
||||
for (i < n) {
|
||||
m.ptr[m.pos + i] = buf[i];
|
||||
i += 1;
|
||||
};
|
||||
m.pos += n;
|
||||
return n;
|
||||
};
|
||||
|
||||
fn dynamicwrite(s: *io.stream, buf: []u8) (i32 | io.closed) = {
|
||||
let m: *state = s.ctx: *state;
|
||||
let need: i32 = m.pos + buf.len;
|
||||
if (need > m.cap) { dynamicgrow(m, need); };
|
||||
let i: i32 = 0;
|
||||
for (i < buf.len) {
|
||||
m.ptr[m.pos + i] = buf[i];
|
||||
i += 1;
|
||||
};
|
||||
m.pos += buf.len;
|
||||
if (m.pos > m.len) { m.len = m.pos; };
|
||||
return buf.len;
|
||||
};
|
||||
|
||||
fn dynamicclose(s: *io.stream) (void | io.closed) = {
|
||||
let m: *state = s.ctx: *state;
|
||||
if (m.cap > 0) { os.free(m.ptr: *void, m.cap: u64); };
|
||||
m.ptr = nil;
|
||||
m.len = 0;
|
||||
m.cap = 0;
|
||||
m.pos = 0;
|
||||
return;
|
||||
};
|
||||
|
||||
fn closenoop(s: *io.stream) (void | io.closed) = {
|
||||
return;
|
||||
};
|
||||
|
||||
// Double-and-copy growth. Initial bump from 0 lands at 8 to amortise
|
||||
// small write bursts without a tail of reallocs.
|
||||
//
|
||||
// `dynamicgrow`, not Hare's bare `grow`: cstage bundles all imported
|
||||
// modules into a flat TU and resolves private fns by unqualified name,
|
||||
// so historically two `fn grow` decls (here + the now-deleted bump
|
||||
// arena's `grow`) would have collided. Module-prefixed name kept the
|
||||
// symmetry with `dynamicwrite`/`dynamicclose`; retained pending task
|
||||
// #9 (module-aware private-fn scoping in cstage).
|
||||
fn dynamicgrow(m: *state, need: i32) void = {
|
||||
let newcap: i32 = m.cap;
|
||||
if (newcap < 8) { newcap = 8; };
|
||||
for (newcap < need) { newcap *= 2; };
|
||||
let nbuf: *u8 = rt.malloc(newcap: u64): *u8;
|
||||
let i: i32 = 0;
|
||||
for (i < m.len) {
|
||||
nbuf[i] = m.ptr[i];
|
||||
i += 1;
|
||||
};
|
||||
if (m.cap > 0) { os.free(m.ptr: *void, m.cap: u64); };
|
||||
m.ptr = nbuf;
|
||||
m.cap = newcap;
|
||||
};
|
||||
|
||||
// vstream — Hare-shaped vtable wrappers over memio. Project #94
|
||||
// fold-eFinal(PREP).
|
||||
//
|
||||
// Adds three constructors that return a memio `stream` BY VALUE (the
|
||||
// Hare shape, ref/hare/memio/stream.ha:46,58,64) alongside the
|
||||
// pre-vtable memio.fixed / memio.dynamic / memio.dynamicfrom in
|
||||
// memio.ww. The OLD surface stays untouched here — fold-eFinal (FLIP)
|
||||
// atomically flips the package shape: deletes the OLD constructors +
|
||||
// `state` + callbacks, renames the `_vstream`/`_v` suffix off (so
|
||||
// `fixed_vstream → fixed`, `stream` stays, `string_v → string`), and
|
||||
// migrates the few callers (lib/fmt/vstream.ww + the 776 probe).
|
||||
//
|
||||
// Hare's memio::fixed/dynamic/dynamic_from return a `stream` whose
|
||||
// FIRST field IS the `io::stream` (= `*vtable`). ww mirrors that
|
||||
// intrusively: `stream`'s first field is `vt: io.vtable` (the vtable
|
||||
// embedded INLINE) so a stack `stream` is castable to
|
||||
// `vstream = *vtable` via `&s.vt` — and the callbacks recover the
|
||||
// `io.stream = *vtable` via `&s.vt` — and the callbacks recover the
|
||||
// outer `stream` by casting the dispatch arg back to `*stream`. Same
|
||||
// intrusive shape as lib/bufio + lib/log over their embedded vtables.
|
||||
//
|
||||
// VALUE-RETURN (drew, gating): the constructor builds the struct in a
|
||||
// local `let r: stream;`, field-assigns every slot (including the
|
||||
// tagged vt sub-fields), and `return r;` — the proven sret round-trip
|
||||
// shape pinned by test/wcc/925 ("ident_return_rhs" row) and extended
|
||||
// to tagged-union-field structs by the 776 byte-id rows here. NO heap,
|
||||
// NO `nomem`: the alloc that forced the OLD `(io.vstream | nomem)`
|
||||
// return is gone, so the constructor cannot fail. Caller owns the
|
||||
// returned `stream` (stack ownership, no-GC) and passes `&s.vt` to the
|
||||
// io.st_* dispatchers — exactly Hare's `&s` into io::write.
|
||||
// let s: memio.stream = memio.fixed(buf);
|
||||
// io.write(&s.vt, bytes);
|
||||
// let view: str = memio.string(&s);
|
||||
//
|
||||
// Constructors return the `stream` BY VALUE (Hare shape,
|
||||
// ref/hare/memio/stream.ha:46,58,64): build the struct in a local,
|
||||
// field-assign every slot, and `return r;` (the proven sret round-trip
|
||||
// pinned by test/wcc/925 + 776). NO heap, NO `nomem`: the alloc that
|
||||
// forced an earlier heap return is gone, so the constructor cannot
|
||||
// fail. Caller owns the returned `stream` (stack ownership, no-GC) and
|
||||
// passes `&s.vt` to the io dispatchers — exactly Hare's `&s` into
|
||||
// io::write.
|
||||
//
|
||||
// Subset of Hare's surface: io's variants are {eof, error}, so memio
|
||||
// drops Hare's NONBLOCK flag (would need an `again` variant in lib/io).
|
||||
// string()'s utf8-validating constructor is omitted per CLAUDE.md
|
||||
// rule 9 carve-out — see [[string]]. Hare's seek / copy callbacks are
|
||||
// likewise absent: io's stream vtable has only read/write/close slots,
|
||||
// so memio can't wire a seeker or copier yet (io fold-2, #5).
|
||||
//
|
||||
// Cast workaround per #206-payoff (ken: KEEP the explicit casts; they
|
||||
// are cgen-neutral and sidestep the #214 over-acceptance surface). The
|
||||
// `(&fn_name): *io.<role>` cast at each store site is the Hare-faithful
|
||||
// minimum-touch route — same workaround test/wcc/775_io_vtable_run.c
|
||||
// uses for the bare-vtable init.
|
||||
// minimum-touch route — the #206 cast-drop is a separate deferred
|
||||
// payoff gated on #214.
|
||||
//
|
||||
// ptr/len/cap kept flat (no `buf: []u8`) per the memio.state note
|
||||
// (memio.ww:39): chained-dot writes through a state pointer into a
|
||||
// slice subfield miscompile silently (#195 family); the flat shape
|
||||
// sidesteps it. dynamicfrom_vstream uses the slice's `cap` (NOT `len`)
|
||||
// to track the allocated-capacity-to-free on close — mirrors the OLD
|
||||
// memio.dynamicfrom note (memio.ww:73).
|
||||
// ptr/len/cap kept flat (no `buf: []u8`) per a historical cgen note:
|
||||
// chained-dot writes through a state pointer into a slice subfield
|
||||
// miscompile silently (#195 family); the flat shape sidesteps it.
|
||||
// dynamicfrom uses the slice's `cap` (NOT `len`) to track the
|
||||
// allocated-capacity-to-free on close — passing a half-filled append
|
||||
// slice with len < cap and using only `buf.len` would under-free.
|
||||
|
||||
package memio;
|
||||
|
||||
@@ -14551,11 +14307,11 @@ import os;
|
||||
import rt;
|
||||
|
||||
// stream — Hare's memio::stream (ref/hare/memio/stream.ha:18). `vt` at
|
||||
// offset 0 for the intrusive stream→vstream cast (`&s.vt`) and the
|
||||
// offset 0 for the intrusive stream→io.stream cast (`&s.vt`) and the
|
||||
// callbacks' reverse `s: *stream` cast. Unified across fixed/dynamic
|
||||
// (Hare keeps a single `stream` over per-mode vtable singletons; ww
|
||||
// wires the per-mode callbacks post-construction instead). ptr/len/cap
|
||||
// flat per the memio.state note above.
|
||||
// flat per the header note.
|
||||
export type stream = struct {
|
||||
vt: io.vtable,
|
||||
ptr: *u8,
|
||||
@@ -14564,17 +14320,17 @@ export type stream = struct {
|
||||
pos: i32,
|
||||
};
|
||||
|
||||
// fixed_vstream — wire a stream over a caller-supplied buffer. Writes
|
||||
// never grow; they return 0 once `pos` reaches the end of the buffer
|
||||
// (Hare returns `nomem` here; ww surfaces 0 to mirror the OLD
|
||||
// memio.fixedwrite divergence — graduating to Hare's `nomem` return
|
||||
// needs the widen-from-bare-nomem path that #173 blocks).
|
||||
// fixed — wire a stream over a caller-supplied buffer. Writes never
|
||||
// grow; they return 0 once `pos` reaches the end of the buffer (Hare
|
||||
// returns `nomem` here; ww surfaces 0 — graduating to Hare's `nomem`
|
||||
// return needs the widen-from-bare-nomem path that #173-family work
|
||||
// gates).
|
||||
//
|
||||
// Mirrors ref/hare/memio/stream.ha:46.
|
||||
export fn fixed_vstream(buf: []u8) stream = {
|
||||
export fn fixed(buf: []u8) stream = {
|
||||
let r: stream;
|
||||
r.vt.reader = (&read_v): *io.reader;
|
||||
r.vt.writer = (&fixedwrite_v): *io.writer;
|
||||
r.vt.reader = (&readfn): *io.reader;
|
||||
r.vt.writer = (&fixedwrite): *io.writer;
|
||||
r.ptr = buf.ptr;
|
||||
r.len = buf.len;
|
||||
r.cap = buf.len;
|
||||
@@ -14582,15 +14338,15 @@ export fn fixed_vstream(buf: []u8) stream = {
|
||||
return r;
|
||||
};
|
||||
|
||||
// dynamic_vstream — wire a stream with no initial buffer. Writes grow
|
||||
// the backing allocation; [[io.st_close]] frees it.
|
||||
// dynamic — wire a stream with no initial buffer. Writes grow the
|
||||
// backing allocation; [[io.close]] frees it.
|
||||
//
|
||||
// Mirrors ref/hare/memio/stream.ha:58.
|
||||
export fn dynamic_vstream() stream = {
|
||||
export fn dynamic() stream = {
|
||||
let r: stream;
|
||||
r.vt.reader = (&read_v): *io.reader;
|
||||
r.vt.writer = (&dynamicwrite_v): *io.writer;
|
||||
r.vt.closer = (&dynamicclose_v): *io.closer;
|
||||
r.vt.reader = (&readfn): *io.reader;
|
||||
r.vt.writer = (&dynamicwrite): *io.writer;
|
||||
r.vt.closer = (&dynamicclose): *io.closer;
|
||||
r.ptr = nil;
|
||||
r.len = 0;
|
||||
r.cap = 0;
|
||||
@@ -14598,18 +14354,16 @@ export fn dynamic_vstream() stream = {
|
||||
return r;
|
||||
};
|
||||
|
||||
// dynamicfrom_vstream — like [[dynamic_vstream]] but seeded with an
|
||||
// existing slice. Ownership transfers; close frees `cap` bytes from
|
||||
// the slice's allocated capacity (NOT logical length) — passing a
|
||||
// half-filled append slice with len < cap and using only `buf.len`
|
||||
// would under-free on close.
|
||||
// dynamicfrom — like [[dynamic]] but seeded with an existing slice.
|
||||
// Ownership transfers; close frees `cap` bytes from the slice's
|
||||
// allocated capacity (NOT logical length).
|
||||
//
|
||||
// Mirrors ref/hare/memio/stream.ha:64.
|
||||
export fn dynamicfrom_vstream(buf: []u8) stream = {
|
||||
export fn dynamicfrom(buf: []u8) stream = {
|
||||
let r: stream;
|
||||
r.vt.reader = (&read_v): *io.reader;
|
||||
r.vt.writer = (&dynamicwrite_v): *io.writer;
|
||||
r.vt.closer = (&dynamicclose_v): *io.closer;
|
||||
r.vt.reader = (&readfn): *io.reader;
|
||||
r.vt.writer = (&dynamicwrite): *io.writer;
|
||||
r.vt.closer = (&dynamicclose): *io.closer;
|
||||
r.ptr = buf.ptr;
|
||||
r.len = buf.len;
|
||||
r.cap = buf.cap;
|
||||
@@ -14619,11 +14373,11 @@ export fn dynamicfrom_vstream(buf: []u8) stream = {
|
||||
|
||||
// ---- vtable callbacks ----------------------------------------------------
|
||||
|
||||
// read_v — recover the stream from the vstream's `*vtable` via the
|
||||
// readfn — recover the stream from the io.stream's `*vtable` via the
|
||||
// intrusive offset-0 cast. Single fn over the common header (Hare's
|
||||
// single `read` at ref/hare/memio/stream.ha:103); fixed and dynamic
|
||||
// share it because the read path is buffer-flavour-agnostic.
|
||||
fn read_v(s: io.vstream, buf: []u8) (size | io.eof | io.error) = {
|
||||
fn readfn(s: io.stream, buf: []u8) (size | io.eof | io.error) = {
|
||||
let m: *stream = s: *stream;
|
||||
if (m.pos >= m.len) {
|
||||
let e: io.eof;
|
||||
@@ -14641,7 +14395,7 @@ fn read_v(s: io.vstream, buf: []u8) (size | io.eof | io.error) = {
|
||||
return n: size;
|
||||
};
|
||||
|
||||
fn fixedwrite_v(s: io.vstream, buf: []u8) (size | io.error) = {
|
||||
fn fixedwrite(s: io.stream, buf: []u8) (size | io.error) = {
|
||||
let m: *stream = s: *stream;
|
||||
if (m.pos >= m.len) { return 0: size; };
|
||||
let space: i32 = m.len - m.pos;
|
||||
@@ -14656,10 +14410,10 @@ fn fixedwrite_v(s: io.vstream, buf: []u8) (size | io.error) = {
|
||||
return n: size;
|
||||
};
|
||||
|
||||
fn dynamicwrite_v(s: io.vstream, buf: []u8) (size | io.error) = {
|
||||
fn dynamicwrite(s: io.stream, buf: []u8) (size | io.error) = {
|
||||
let m: *stream = s: *stream;
|
||||
let need: i32 = m.pos + buf.len;
|
||||
if (need > m.cap) { dynamicgrow_v(m, need); };
|
||||
if (need > m.cap) { dynamicgrow(m, need); };
|
||||
let i: i32 = 0;
|
||||
for (i < buf.len) {
|
||||
m.ptr[m.pos + i] = buf[i];
|
||||
@@ -14670,7 +14424,7 @@ fn dynamicwrite_v(s: io.vstream, buf: []u8) (size | io.error) = {
|
||||
return buf.len: size;
|
||||
};
|
||||
|
||||
fn dynamicclose_v(s: io.vstream) (void | io.error) = {
|
||||
fn dynamicclose(s: io.stream) (void | io.error) = {
|
||||
let m: *stream = s: *stream;
|
||||
if (m.cap > 0) { os.free(m.ptr: *void, m.cap: u64); };
|
||||
m.ptr = nil;
|
||||
@@ -14680,11 +14434,11 @@ fn dynamicclose_v(s: io.vstream) (void | io.error) = {
|
||||
return;
|
||||
};
|
||||
|
||||
// Mirror of memio.ww:203 dynamicgrow. Double-and-copy with floor at
|
||||
// 8. Module-prefixed `_v` suffix vs the OLD memio.dynamicgrow keeps
|
||||
// the cstage flat-TU private-fn scope from colliding (memio.ww:200
|
||||
// note + task #9).
|
||||
fn dynamicgrow_v(d: *stream, need: i32) void = {
|
||||
// Double-and-copy growth with floor at 8. `dynamicgrow`, not Hare's
|
||||
// bare `grow`: cstage bundles all imported modules into a flat TU and
|
||||
// resolves private fns by unqualified name (task #9), so the
|
||||
// module-prefixed name keeps the symmetry with dynamicwrite/dynamicclose.
|
||||
fn dynamicgrow(d: *stream, need: i32) void = {
|
||||
let newcap: i32 = d.cap;
|
||||
if (newcap < 8) { newcap = 8; };
|
||||
for (newcap < need) { newcap *= 2; };
|
||||
@@ -14701,53 +14455,49 @@ fn dynamicgrow_v(d: *stream, need: i32) void = {
|
||||
|
||||
// ---- accessors over the common `stream` header -----------------------
|
||||
//
|
||||
// Single fn each (drew string-collapse): Hare's string/reset/buffer/
|
||||
// borrowedread all take `*stream` and read the flat header, so the
|
||||
// fixed/dynamic split the OLD V-side carried (fixed_string /
|
||||
// dynamic_string) collapses to one. FLIP drops the `_v` suffix
|
||||
// (string_v → string, …) and deletes the OLD `*state` twins in
|
||||
// memio.ww.
|
||||
// Single fn each: Hare's string/reset/buffer/borrowedread all take
|
||||
// `*stream` and read the flat header.
|
||||
|
||||
// string_v — bytes written so far, as a str view (buf[0..pos]).
|
||||
// string — bytes written so far, as a str view (buf[0..pos]).
|
||||
//
|
||||
// Mirrors ref/hare/memio/stream.ha:81 string(in: *stream). Hare returns
|
||||
// (str | utf8::invalid) — the validating constructor. ww returns a bare
|
||||
// `str` per the CLAUDE.md rule-9 frombytes carve-out: utf8.validate at
|
||||
// the IO source is opt-in, never wrapped per-construction; the honest
|
||||
// name reserves a future validating helper.
|
||||
export fn string_v(s: *stream) str = {
|
||||
export fn string(s: *stream) str = {
|
||||
let r: str;
|
||||
r.ptr = s.ptr;
|
||||
r.len = s.pos;
|
||||
return r;
|
||||
};
|
||||
|
||||
// buffer_v — borrowed []u8 view of bytes written so far (buf[0..pos]).
|
||||
// buffer — borrowed []u8 view of bytes written so far (buf[0..pos]).
|
||||
//
|
||||
// Mirrors ref/hare/memio/stream.ha:74 buffer(in: *stream).
|
||||
export fn buffer_v(s: *stream) []u8 = {
|
||||
export fn buffer(s: *stream) []u8 = {
|
||||
let r: []u8;
|
||||
r.ptr = s.ptr;
|
||||
r.len = s.pos;
|
||||
return r;
|
||||
};
|
||||
|
||||
// reset_v — rewind the cursor and truncate the logical content to 0.
|
||||
// reset — rewind the cursor and truncate the logical content to 0.
|
||||
// Backing storage is preserved; subsequent writes (dynamic) re-fill
|
||||
// from the start without reallocation.
|
||||
//
|
||||
// Mirrors ref/hare/memio/stream.ha:87 reset(in: *stream).
|
||||
export fn reset_v(s: *stream) void = {
|
||||
export fn reset(s: *stream) void = {
|
||||
s.pos = 0;
|
||||
s.len = 0;
|
||||
};
|
||||
|
||||
// borrowedread_v — return an `amt`-byte view starting at `pos` without
|
||||
// borrowedread — return an `amt`-byte view starting at `pos` without
|
||||
// copying, advancing the cursor. eof if fewer bytes are available.
|
||||
//
|
||||
// Mirrors ref/hare/memio/stream.ha:94 borrowedread(st: *stream, amt).
|
||||
// `amt: i32` (not Hare's `size`) per the i32-index convention.
|
||||
export fn borrowedread_v(s: *stream, amt: i32) ([]u8 | io.eof) = {
|
||||
export fn borrowedread(s: *stream, amt: i32) ([]u8 | io.eof) = {
|
||||
if (s.len - s.pos < amt) {
|
||||
let e: io.eof;
|
||||
return e;
|
||||
@@ -28984,14 +28734,13 @@ fn localfind(c: *cgen, name: str) i32 = {
|
||||
// Re-init per fn would abandon the buffer (no [[io.close]] path → no
|
||||
// [[os.free]]) and re-grow from 0 via the 8→…→65536 ladder for every
|
||||
// function. Same idiom as lib/log/log.ww:124 `ensureinit`.
|
||||
let cgoutstate: memio.state;
|
||||
let cgoutstream: io.stream;
|
||||
let cgoutstream: memio.stream;
|
||||
let cgoutmode: i32 = 0;
|
||||
let cgoutinit: i32 = 0;
|
||||
|
||||
fn cgout_enable() void = {
|
||||
if (cgoutinit == 0) {
|
||||
memio.dynamic(&cgoutstate, &cgoutstream);
|
||||
cgoutstream = memio.dynamic();
|
||||
cgoutinit = 1;
|
||||
};
|
||||
cgoutmode = 1;
|
||||
@@ -29000,9 +28749,9 @@ fn cgout_enable() void = {
|
||||
fn cgout_disable() void = { cgoutmode = 0; };
|
||||
|
||||
fn cgout_flush() void = {
|
||||
if (cgoutstate.pos > 0) {
|
||||
os.write(1, cgoutstate.ptr, cgoutstate.pos: u64);
|
||||
memio.reset(&cgoutstate);
|
||||
if (cgoutstream.pos > 0) {
|
||||
os.write(1, cgoutstream.ptr, cgoutstream.pos: u64);
|
||||
memio.reset(&cgoutstream);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -29011,9 +28760,10 @@ fn emitbytes(p: *u8, n: u64) void = {
|
||||
let buf: []u8;
|
||||
buf.ptr = p;
|
||||
buf.len = n: i32;
|
||||
// memio.dynamicwrite never returns io.closed (memio.ww:166);
|
||||
// bare-discard mirrors lib/log/log.ww:169 fmt.fprintln.
|
||||
io.write(&cgoutstream, buf);
|
||||
// io.write over the embedded vtable (&cgoutstream.vt = io.stream);
|
||||
// memio.dynamicwrite never errors. Bare-discard mirrors
|
||||
// lib/log/log.ww stdprintln. #94 fold-eFinal.
|
||||
io.write(&cgoutstream.vt, buf);
|
||||
} else {
|
||||
os.write(1, p, n);
|
||||
};
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
/*
|
||||
* 775_io_vtable_run — project #94 fold-e1 sentinel. Pins the additive
|
||||
* lib/io/stream.ww vtable port: `vtable` (the three (*T|void) slots),
|
||||
* `vstream` (= *vtable), and the `st_read`/`st_write`/`st_close`
|
||||
* dispatchers (ref/hare/io/stream.ha:33-68). Coexists with lib/io/io.ww's
|
||||
* legacy `stream` struct + read/write/close wrappers (fold-e2+ migrates
|
||||
* them to vtable-backed implementations and retires the legacy surface).
|
||||
* 775_io_vtable_run — project #94 fold-eFinal sentinel. Pins the
|
||||
* single-surface lib/io/stream.ww vtable port: `vtable` (the three
|
||||
* (*T|void) slots), `stream` (= *vtable), and the read/write/close
|
||||
* dispatchers (ref/hare/io/stream.ha:33-68). The fold-eFinal FLIP
|
||||
* retired the pre-vtable `stream` struct + `closed` tag, leaving this
|
||||
* as the sole io surface. (Comment prose below predates the FLIP and
|
||||
* still says `vstream`/`st_read`/`st_write`/`st_close` for the same
|
||||
* `stream`/`read`/`write`/`close`.)
|
||||
*
|
||||
* Each row imports io, allocates a vtable, optionally points one slot at
|
||||
* a user fn (via the `(&fn): *io.<role>` cast — see SIBLINGS below),
|
||||
@@ -48,7 +50,7 @@
|
||||
* | callee row; catches a constant-fold
|
||||
* | mistake in the dispatch path.
|
||||
*
|
||||
* SIBLINGS (filed inline, NOT fixed here — fold-e1 is purely additive):
|
||||
* DEFERRALS / RELATED (NOT addressed by these rows):
|
||||
*
|
||||
* - #199b layout-extension (wrapped-slot tag-remap): when widening a
|
||||
* NAMED wrapper-typed value (e.g. `error`) into a tagged parent
|
||||
@@ -109,16 +111,16 @@ static const struct row rows[] = {
|
||||
{ "reader_set_happy",
|
||||
"package main;\n"
|
||||
"import io;\n"
|
||||
"fn myread(s: io.vstream, buf: []u8) (size | io.eof | io.error) = {\n"
|
||||
"fn myread(s: io.stream, buf: []u8) (size | io.eof | io.error) = {\n"
|
||||
" return 7: size;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let vt: io.vtable;\n"
|
||||
" vt.reader = (&myread): *io.reader;\n"
|
||||
" let v: io.vstream = &vt;\n"
|
||||
" let v: io.stream = &vt;\n"
|
||||
" let buf: [4]u8;\n"
|
||||
" let bs: []u8 = buf[0:4];\n"
|
||||
" let r = io.st_read(v, bs);\n"
|
||||
" let r = io.read(v, bs);\n"
|
||||
" if (r is size) {\n"
|
||||
" let n = r as size;\n"
|
||||
" if (n == 7: size) { return 70; };\n"
|
||||
@@ -133,10 +135,10 @@ static const struct row rows[] = {
|
||||
"import io;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let vt: io.vtable;\n"
|
||||
" let v: io.vstream = &vt;\n"
|
||||
" let v: io.stream = &vt;\n"
|
||||
" let buf: [4]u8;\n"
|
||||
" let bs: []u8 = buf[0:4];\n"
|
||||
" let _r = io.st_read(v, bs);\n"
|
||||
" let _r = io.read(v, bs);\n"
|
||||
" return 11;\n"
|
||||
"};\n",
|
||||
11,
|
||||
@@ -144,16 +146,16 @@ static const struct row rows[] = {
|
||||
{ "writer_set_happy",
|
||||
"package main;\n"
|
||||
"import io;\n"
|
||||
"fn mywrite(s: io.vstream, buf: []u8) (size | io.error) = {\n"
|
||||
"fn mywrite(s: io.stream, buf: []u8) (size | io.error) = {\n"
|
||||
" return buf.len: size;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let vt: io.vtable;\n"
|
||||
" vt.writer = (&mywrite): *io.writer;\n"
|
||||
" let v: io.vstream = &vt;\n"
|
||||
" let v: io.stream = &vt;\n"
|
||||
" let buf: [5]u8;\n"
|
||||
" let bs: []u8 = buf[0:5];\n"
|
||||
" let r = io.st_write(v, bs);\n"
|
||||
" let r = io.write(v, bs);\n"
|
||||
" if (r is size) {\n"
|
||||
" let n = r as size;\n"
|
||||
" if (n == 5: size) { return 80; };\n"
|
||||
@@ -168,10 +170,10 @@ static const struct row rows[] = {
|
||||
"import io;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let vt: io.vtable;\n"
|
||||
" let v: io.vstream = &vt;\n"
|
||||
" let v: io.stream = &vt;\n"
|
||||
" let buf: [3]u8;\n"
|
||||
" let bs: []u8 = buf[0:3];\n"
|
||||
" let _r = io.st_write(v, bs);\n"
|
||||
" let _r = io.write(v, bs);\n"
|
||||
" return 22;\n"
|
||||
"};\n",
|
||||
22,
|
||||
@@ -181,8 +183,8 @@ static const struct row rows[] = {
|
||||
"import io;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let vt: io.vtable;\n"
|
||||
" let v: io.vstream = &vt;\n"
|
||||
" let r = io.st_close(v);\n"
|
||||
" let v: io.stream = &vt;\n"
|
||||
" let r = io.close(v);\n"
|
||||
" if (r is void) { return 50; };\n"
|
||||
" return 99;\n"
|
||||
"};\n",
|
||||
@@ -191,14 +193,14 @@ static const struct row rows[] = {
|
||||
{ "closer_set",
|
||||
"package main;\n"
|
||||
"import io;\n"
|
||||
"fn myclose(s: io.vstream) (void | io.error) = {\n"
|
||||
"fn myclose(s: io.stream) (void | io.error) = {\n"
|
||||
" return void;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let vt: io.vtable;\n"
|
||||
" vt.closer = (&myclose): *io.closer;\n"
|
||||
" let v: io.vstream = &vt;\n"
|
||||
" let r = io.st_close(v);\n"
|
||||
" let v: io.stream = &vt;\n"
|
||||
" let r = io.close(v);\n"
|
||||
" if (r is void) { return 60; };\n"
|
||||
" return 99;\n"
|
||||
"};\n",
|
||||
@@ -207,10 +209,10 @@ static const struct row rows[] = {
|
||||
{ "branched_readers",
|
||||
"package main;\n"
|
||||
"import io;\n"
|
||||
"fn r1(s: io.vstream, buf: []u8) (size | io.eof | io.error) = {\n"
|
||||
"fn r1(s: io.stream, buf: []u8) (size | io.eof | io.error) = {\n"
|
||||
" return 1: size;\n"
|
||||
"};\n"
|
||||
"fn r2(s: io.vstream, buf: []u8) (size | io.eof | io.error) = {\n"
|
||||
"fn r2(s: io.stream, buf: []u8) (size | io.eof | io.error) = {\n"
|
||||
" return 2: size;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
@@ -220,8 +222,8 @@ static const struct row rows[] = {
|
||||
" vt2.reader = (&r2): *io.reader;\n"
|
||||
" let buf: [4]u8;\n"
|
||||
" let bs: []u8 = buf[0:4];\n"
|
||||
" let a = io.st_read(&vt1, bs);\n"
|
||||
" let b = io.st_read(&vt2, bs);\n"
|
||||
" let a = io.read(&vt1, bs);\n"
|
||||
" let b = io.read(&vt2, bs);\n"
|
||||
" let asz: size = 0;\n"
|
||||
" let bsz: size = 0;\n"
|
||||
" if (a is size) { asz = a as size; };\n"
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
/*
|
||||
* 776_memio_vstream_run — project #94 fold-eFinal(PREP) sentinel. Pins
|
||||
* the additive lib/memio/vstream.ww parallel API: a single unified
|
||||
* 776_memio_vstream_run — project #94 fold-eFinal sentinel. Pins
|
||||
* the single-surface lib/memio: a unified
|
||||
* `stream` struct with `vt: io.vtable` as the first field for the
|
||||
* intrusive cast, three constructors (fixed_vstream / dynamic_vstream /
|
||||
* dynamicfrom_vstream) that return the `stream` BY VALUE (sret —
|
||||
* intrusive cast, three constructors (fixed / dynamic /
|
||||
* dynamicfrom) that return the `stream` BY VALUE (sret —
|
||||
* field-by-field build then `return r;`, ref/hare/memio/stream.ha:46,
|
||||
* 58,64), the unified read_v / fixedwrite_v / dynamicwrite_v /
|
||||
* dynamicclose_v callbacks that recover the outer stream via a
|
||||
* 58,64), the unified read / fixedwrite / dynamicwrite /
|
||||
* dynamicclose callbacks that recover the outer stream via a
|
||||
* vstream→*stream pointer cast, and the collapsed single accessors
|
||||
* (string_v / buffer_v / reset_v / borrowedread_v) over the common
|
||||
* header. Coexists with the pre-vtable memio.fixed/dynamic/dynamicfrom
|
||||
* + `state` surface in memio.ww (fold-eFinal FLIP retires the latter
|
||||
* and drops the `_vstream`/`_v` suffix wholesale).
|
||||
* (string / buffer / reset / borrowedread) over the common
|
||||
* header. The fold-eFinal FLIP collapsed the dual memio surface into
|
||||
* this one Hare-shaped surface (bare names, no suffix).
|
||||
*
|
||||
* Each row imports memio + io, calls one or more constructors, drives
|
||||
* the io.st_read/st_write/st_close dispatchers, and asserts an exit
|
||||
* the io.read/io.write/io.close dispatchers, and asserts an exit
|
||||
* constant. Both stages run; cs.s == ww.s byte-id on every row.
|
||||
*
|
||||
* row | what it pins
|
||||
* --------------------------+--------------------------------------
|
||||
* fixed_read_5 | fixed_vstream + io.st_read of 5 bytes
|
||||
* fixed_read_5 | fixed + io.read of 5 bytes
|
||||
* | over an 8-byte source. Asserts the
|
||||
* | return is `size` and equal to 5, and
|
||||
* | that out[0]/out[4] mirror the buffer.
|
||||
@@ -27,14 +26,14 @@
|
||||
* | stream field-by-field → return r (sret);
|
||||
* | caller takes &st.vt; dispatcher recovers
|
||||
* | via s: *stream).
|
||||
* dynamic_write_grow | dynamic_vstream + io.st_write of 3
|
||||
* dynamic_write_grow | dynamic + io.write of 3
|
||||
* | bytes. Asserts the write reports 3
|
||||
* | and that the dynamicgrow_v path
|
||||
* | and that the dynamicgrow path
|
||||
* | allocated the backing buffer (initial
|
||||
* | cap=0 forces growth on the first
|
||||
* | write). Closes via io.st_close to
|
||||
* | exercise dynamicclose_v's free.
|
||||
* dynamicfrom_alt_rw | dynamicfrom_vstream seeded with a
|
||||
* | write). Closes via io.close to
|
||||
* | exercise dynamicclose's free.
|
||||
* dynamicfrom_alt_rw | dynamicfrom seeded with a
|
||||
* | 4-byte slice. Alternates st_read
|
||||
* | (4 bytes back) → st_write (2 bytes,
|
||||
* | grows past cap=4 → realloc). Asserts
|
||||
@@ -42,7 +41,7 @@
|
||||
* | Single ctx flavour, two dispatcher
|
||||
* | paths through the same vtable.
|
||||
* branched_fixed | branched callee per #105: two
|
||||
* | fixed_vstream instances with
|
||||
* | fixed instances with
|
||||
* | different buffer sizes (3 and 5),
|
||||
* | runtime-selected by the exit code
|
||||
* | nudge. st_read on each returns the
|
||||
@@ -62,25 +61,24 @@
|
||||
* the order no longer matters and all 4 rows now run STAGE_CS|STAGE_WW
|
||||
* byte-id. The earlier `import os;`-FIRST workaround is retired here.
|
||||
*
|
||||
* SIBLINGS (filed inline, NOT fixed here — fold-e2 is purely
|
||||
* additive over fold-e1's frozen io.* surface):
|
||||
* DEFERRALS / RELATED (NOT addressed by this row):
|
||||
*
|
||||
* - #173 (TRY-on-tagged-return both-stages broken) blocks
|
||||
* fixed_write_v from returning Hare's `nomem` when full;
|
||||
* vstream.ww surfaces 0 instead (memio.ww:155 OLD divergence).
|
||||
* fixedwrite from returning Hare's `nomem` when full;
|
||||
* memio.ww surfaces 0 instead (documented divergence).
|
||||
* The probe doesn't pin Hare-equivalent nomem behaviour;
|
||||
* fold-eFinal can graduate once #173 closes.
|
||||
* it can graduate once #173 closes.
|
||||
*
|
||||
* - #195 (cgassign N_DOT TK_ASSIGN on aliased-ptr receiver):
|
||||
* not bitten — the value-return constructor builds the stream in
|
||||
* a local `let r: stream;` and field-assigns each slot (including
|
||||
* r.vt.reader) through a plain pointer-to-local, not an aliased-ptr
|
||||
* receiver. The flat ptr/len/cap fields (memio.ww:39) stay flat.
|
||||
* receiver. The flat ptr/len/cap fields stay flat.
|
||||
*
|
||||
* GATE POLARITY: must stay GREEN. A red here means vstream.ww
|
||||
* regressed, the alloc-struct-? path miscompiled, the intrusive
|
||||
* vstream→*ctx cast miscomputed offsets, or io.vtable's first-field
|
||||
* embed lost its offset-0 invariant.
|
||||
* GATE POLARITY: must stay GREEN. A red here means lib/memio
|
||||
* regressed, the sret value-return path miscompiled, the intrusive
|
||||
* io.stream→*stream cast miscomputed offsets, or io.vtable's
|
||||
* first-field embed lost its offset-0 invariant.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -118,11 +116,11 @@ static const struct row rows[] = {
|
||||
" let buf: [8]u8;\n"
|
||||
" buf[0] = 65u8; buf[1] = 66u8; buf[2] = 67u8; buf[3] = 68u8;\n"
|
||||
" buf[4] = 69u8; buf[5] = 70u8; buf[6] = 71u8; buf[7] = 72u8;\n"
|
||||
" let st: memio.stream = memio.fixed_vstream(buf[0:8]);\n"
|
||||
" let s: io.vstream = &st.vt;\n"
|
||||
" let st: memio.stream = memio.fixed(buf[0:8]);\n"
|
||||
" let s: io.stream = &st.vt;\n"
|
||||
" let out: [5]u8;\n"
|
||||
" let n: size = 0;\n"
|
||||
" let rd = io.st_read(s, out[0:5]);\n"
|
||||
" let rd = io.read(s, out[0:5]);\n"
|
||||
" if (rd is size) { n = rd as size; };\n"
|
||||
" if (n == 5: size && out[0] == 65u8 && out[4] == 69u8) { return 42; };\n"
|
||||
" return 99;\n"
|
||||
@@ -135,14 +133,14 @@ static const struct row rows[] = {
|
||||
"import io;\n"
|
||||
"import os;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let st: memio.stream = memio.dynamic_vstream();\n"
|
||||
" let s: io.vstream = &st.vt;\n"
|
||||
" let st: memio.stream = memio.dynamic();\n"
|
||||
" let s: io.stream = &st.vt;\n"
|
||||
" let src: [3]u8;\n"
|
||||
" src[0] = 88u8; src[1] = 89u8; src[2] = 90u8;\n"
|
||||
" let wr = io.st_write(s, src[0:3]);\n"
|
||||
" let wr = io.write(s, src[0:3]);\n"
|
||||
" let n: size = 0;\n"
|
||||
" if (wr is size) { n = wr as size; };\n"
|
||||
" let cl = io.st_close(s);\n"
|
||||
" let cl = io.close(s);\n"
|
||||
" if (cl is void && n == 3: size) { return 43; };\n"
|
||||
" return 99;\n"
|
||||
"};\n",
|
||||
@@ -156,15 +154,15 @@ static const struct row rows[] = {
|
||||
"export fn main() i32 = {\n"
|
||||
" let seed: [4]u8;\n"
|
||||
" seed[0] = 1u8; seed[1] = 2u8; seed[2] = 3u8; seed[3] = 4u8;\n"
|
||||
" let st: memio.stream = memio.dynamicfrom_vstream(seed[0:4]);\n"
|
||||
" let s: io.vstream = &st.vt;\n"
|
||||
" let st: memio.stream = memio.dynamicfrom(seed[0:4]);\n"
|
||||
" let s: io.stream = &st.vt;\n"
|
||||
" let out: [4]u8;\n"
|
||||
" let rd = io.st_read(s, out[0:4]);\n"
|
||||
" let rd = io.read(s, out[0:4]);\n"
|
||||
" let rn: size = 0;\n"
|
||||
" if (rd is size) { rn = rd as size; };\n"
|
||||
" let extra: [2]u8;\n"
|
||||
" extra[0] = 99u8; extra[1] = 100u8;\n"
|
||||
" let wr = io.st_write(s, extra[0:2]);\n"
|
||||
" let wr = io.write(s, extra[0:2]);\n"
|
||||
" let wn: size = 0;\n"
|
||||
" if (wr is size) { wn = wr as size; };\n"
|
||||
" if (rn == 4: size && wn == 2: size && out[0] == 1u8 && out[3] == 4u8) { return 44; };\n"
|
||||
@@ -182,14 +180,14 @@ static const struct row rows[] = {
|
||||
" a[0] = 10u8; a[1] = 11u8; a[2] = 12u8;\n"
|
||||
" let b: [5]u8;\n"
|
||||
" b[0] = 20u8; b[1] = 21u8; b[2] = 22u8; b[3] = 23u8; b[4] = 24u8;\n"
|
||||
" let sta: memio.stream = memio.fixed_vstream(a[0:3]);\n"
|
||||
" let stb: memio.stream = memio.fixed_vstream(b[0:5]);\n"
|
||||
" let sa: io.vstream = &sta.vt;\n"
|
||||
" let sb: io.vstream = &stb.vt;\n"
|
||||
" let sta: memio.stream = memio.fixed(a[0:3]);\n"
|
||||
" let stb: memio.stream = memio.fixed(b[0:5]);\n"
|
||||
" let sa: io.stream = &sta.vt;\n"
|
||||
" let sb: io.stream = &stb.vt;\n"
|
||||
" let outa: [3]u8;\n"
|
||||
" let outb: [5]u8;\n"
|
||||
" let rda = io.st_read(sa, outa[0:3]);\n"
|
||||
" let rdb = io.st_read(sb, outb[0:5]);\n"
|
||||
" let rda = io.read(sa, outa[0:3]);\n"
|
||||
" let rdb = io.read(sb, outb[0:5]);\n"
|
||||
" let na: size = 0;\n"
|
||||
" let nb: size = 0;\n"
|
||||
" if (rda is size) { na = rda as size; };\n"
|
||||
@@ -200,10 +198,10 @@ static const struct row rows[] = {
|
||||
45,
|
||||
STAGE_CS | STAGE_WW, 1 },
|
||||
/* accessors_string_buffer_reset — fold-eFinal PREP: the collapsed
|
||||
* single accessors over the common `stream` header (string_v /
|
||||
* buffer_v / reset_v, ref/hare/memio/stream.ha:81,74,87). Writes 3
|
||||
* single accessors over the common `stream` header (string /
|
||||
* buffer / reset, ref/hare/memio/stream.ha:81,74,87). Writes 3
|
||||
* bytes through the value-returned fixed stream, then asserts
|
||||
* string_v (str view), buffer_v ([]u8 view), and reset_v (rewind to
|
||||
* string (str view), buffer ([]u8 view), and reset (rewind to
|
||||
* 0) all read the flat header correctly. Pins the value-return
|
||||
* field integrity (st.vt + st.ptr/len/cap/pos survive sret) AND the
|
||||
* accessor collapse. cs.s == ww.s byte-id. */
|
||||
@@ -214,23 +212,23 @@ static const struct row rows[] = {
|
||||
"import os;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let buf: [16]u8;\n"
|
||||
" let st: memio.stream = memio.fixed_vstream(buf[0:16]);\n"
|
||||
" let s: io.vstream = &st.vt;\n"
|
||||
" let st: memio.stream = memio.fixed(buf[0:16]);\n"
|
||||
" let s: io.stream = &st.vt;\n"
|
||||
" let payload: [3]u8;\n"
|
||||
" payload[0] = 65u8; payload[1] = 66u8; payload[2] = 67u8;\n"
|
||||
" let wr = io.st_write(s, payload[0:3]);\n"
|
||||
" let wr = io.write(s, payload[0:3]);\n"
|
||||
" if (!(wr is size)) { return 90; };\n"
|
||||
" let v: str = memio.string_v(&st);\n"
|
||||
" let v: str = memio.string(&st);\n"
|
||||
" if (v.len != 3 || v[0] != 65u8 || v[2] != 67u8) { return 91; };\n"
|
||||
" let bv: []u8 = memio.buffer_v(&st);\n"
|
||||
" let bv: []u8 = memio.buffer(&st);\n"
|
||||
" if (bv.len != 3 || bv[1] != 66u8) { return 92; };\n"
|
||||
" memio.reset_v(&st);\n"
|
||||
" if (memio.string_v(&st).len != 0) { return 93; };\n"
|
||||
" memio.reset(&st);\n"
|
||||
" if (memio.string(&st).len != 0) { return 93; };\n"
|
||||
" return 46;\n"
|
||||
"};\n",
|
||||
46,
|
||||
STAGE_CS | STAGE_WW, 1 },
|
||||
/* borrowedread_view_eof — the collapsed borrowedread_v
|
||||
/* borrowedread_view_eof — the collapsed borrowedread
|
||||
* (ref/hare/memio/stream.ha:94): returns an amt-byte borrowed view
|
||||
* advancing the cursor, eof when fewer remain. Reads 3 of 4 seeded
|
||||
* bytes (view len 3), then a 2-byte borrowedread over the remaining
|
||||
@@ -243,13 +241,13 @@ static const struct row rows[] = {
|
||||
"export fn main() i32 = {\n"
|
||||
" let seed: [4]u8;\n"
|
||||
" seed[0] = 1u8; seed[1] = 2u8; seed[2] = 3u8; seed[3] = 4u8;\n"
|
||||
" let st: memio.stream = memio.dynamicfrom_vstream(seed[0:4]);\n"
|
||||
" let br = memio.borrowedread_v(&st, 3);\n"
|
||||
" let st: memio.stream = memio.dynamicfrom(seed[0:4]);\n"
|
||||
" let br = memio.borrowedread(&st, 3);\n"
|
||||
" match (br) {\n"
|
||||
" case let b: []u8 => { if (b.len != 3 || b[0] != 1u8 || b[2] != 3u8) { return 91; }; };\n"
|
||||
" case io.eof => { return 92; };\n"
|
||||
" };\n"
|
||||
" let br2 = memio.borrowedread_v(&st, 2);\n"
|
||||
" let br2 = memio.borrowedread(&st, 2);\n"
|
||||
" match (br2) {\n"
|
||||
" case let b: []u8 => { return 93; };\n"
|
||||
" case io.eof => { return 47; };\n"
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
/*
|
||||
* 777_fmt_vstream_run — project #94 fold-e3 sentinel. Pins the
|
||||
* additive lib/fmt/vstream.ww Option C parallel API: stack-resident
|
||||
* fd_ctx with `vt: io.vtable` as the first field for the intrusive
|
||||
* vstream cast, four wrappers (fdprint_v / fdprintln_v / fdprintf_v /
|
||||
* fdprintfln_v) that take a raw fd, allocate fd_ctx on their own
|
||||
* frame (never escapes — ken's dispatcher-CONTAINED discipline), and
|
||||
* dispatch through io.st_write via the wired fdsinkwrite_v callback.
|
||||
* Coexists with the pre-vtable fdprint / fdprintln / fdprintf /
|
||||
* fdprintfln surface in fmt.ww (fold-eFinal, task #50, retires the
|
||||
* latter once io fold-2 ports `handle = (file | int)`).
|
||||
* 777_fmt_vstream_run — project #94 fold-eFinal sentinel. Pins the
|
||||
* single-surface lib/fmt fd shim: stack-resident fd_ctx with
|
||||
* `vt: io.vtable` as the first field for the intrusive io.stream
|
||||
* cast, four wrappers (fdprint / fdprintln / fdprintf / fdprintfln)
|
||||
* that take a raw fd, allocate fd_ctx on their own frame (never
|
||||
* escapes — ken's dispatcher-CONTAINED discipline), and dispatch
|
||||
* through io.write via the wired fdsinkwrite callback. The fd shim
|
||||
* has no Hare counterpart; it folds into fNNN-over-handle once io
|
||||
* fold-2 (#5) ports `handle = (file | int)`.
|
||||
*
|
||||
* Each row imports fmt + os + io, opens a per-process /tmp output
|
||||
* file, calls one V wrapper to write through the fd, closes, re-
|
||||
@@ -34,25 +33,25 @@
|
||||
*
|
||||
* row | what it pins
|
||||
* --------------------------+--------------------------------------
|
||||
* fdprintf_v_int | fdprintf_v with `"v={}\n"`-style
|
||||
* fdprintf_v_int | fdprintf with `"v={}\n"`-style
|
||||
* | format + one i64 arg. Asserts the
|
||||
* | full intrusive shape (stack fd_ctx →
|
||||
* | &c.vt → io.st_write → fdsinkwrite_v
|
||||
* | &c.vt → io.write → fdsinkwrite
|
||||
* | → os.write) plus the {n}-placeholder
|
||||
* | parser routed through vformatfield.
|
||||
* fdprintln_v_multi | fdprintln_v with three positional
|
||||
* fdprintln_v_multi | fdprintln with three positional
|
||||
* | args (i64 + str + bool). Asserts the
|
||||
* | space-separator + trailing newline
|
||||
* | shape mirrors OLD fdprintln (fmt.ww:
|
||||
* | 145), routed through io.st_write.
|
||||
* fdprint_v_raw | fdprint_v with one str arg, no
|
||||
* | 145), routed through io.write.
|
||||
* fdprint_v_raw | fdprint with one str arg, no
|
||||
* | format mods (zero-format raw). Pins
|
||||
* | the per-arg loop without the
|
||||
* | placeholder parser (vfprint code
|
||||
* | path, fmt/vstream.ww).
|
||||
* branched_fdprintf_v | branched callee per #105: two
|
||||
* | placeholder parser (fprint code
|
||||
* | path, fmt.ww).
|
||||
* branched_fdprintf | branched callee per #105: two
|
||||
* | distinct format strings runtime-
|
||||
* | selected from a single fdprintf_v
|
||||
* | selected from a single fdprintf
|
||||
* | call site. Catches a constant-fold
|
||||
* | mistake in the format-string
|
||||
* | dispatch (mirror of 776's
|
||||
@@ -63,24 +62,24 @@
|
||||
* (wwstage checker ordering-sensitivity on os.tryread/trywrite/
|
||||
* tryopen's bare `return r;` over a tagged return type).
|
||||
*
|
||||
* SIBLINGS (filed inline, NOT fixed here — fold-e3 is purely
|
||||
* additive over fold-e1/e2's frozen io.* + memio.* surface):
|
||||
* DEFERRALS / RELATED (NOT addressed by these rows):
|
||||
*
|
||||
* - io fold-2 (handle port): drew-deferred per fold-d/fold-e3
|
||||
* precedent. Hare's ref/hare/fmt/wrappers.ha:9-25 routes through
|
||||
* io::handle; fdNNN_v collapses into bare fNNN_v once handle =
|
||||
* (file | int) lands. eFinal (#50) graduates.
|
||||
* - io fold-2 (handle port): drew-deferred. Hare's
|
||||
* ref/hare/fmt/wrappers.ha:9-25 routes through io::handle; fdNNN
|
||||
* collapses into bare fNNN once handle = (file | int) lands.
|
||||
* #5 graduates.
|
||||
*
|
||||
* - #206 (bare &fn → (*alias|void)): 2 cast sites per wrapper
|
||||
* vtable wire-up; drop out wholesale on close.
|
||||
* vtable wire-up; explicit casts KEPT (ken/#214 — cast-drop
|
||||
* deferred, gated on #214).
|
||||
*
|
||||
* - #173 (TRY-on-tagged-return both-stages broken): fdsinkwrite_v
|
||||
* - #173 (TRY-on-tagged-return both-stages broken): fdsinkwrite
|
||||
* constructs nomem and widens to io.error explicitly rather
|
||||
* than using `os.trywrite(...)?`; same memio.vstream.ww shape.
|
||||
* than using `os.trywrite(...)?`; same shape memio.ww adopts.
|
||||
*
|
||||
* GATE POLARITY: must stay GREEN. A red here means vstream.ww
|
||||
* regressed, the stack fd_ctx → vstream cast miscomputed offsets,
|
||||
* the {n}-placeholder dispatch through vformatfield regressed, or
|
||||
* GATE POLARITY: must stay GREEN. A red here means lib/fmt
|
||||
* regressed, the stack fd_ctx → io.stream cast miscomputed offsets,
|
||||
* the {n}-placeholder dispatch through formatfield regressed, or
|
||||
* io.vtable's first-field embed lost its offset-0 invariant.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
@@ -119,7 +118,7 @@ static const struct row rows[] = {
|
||||
" let path: str = \"/tmp/wwfmtv_777_a\";\n"
|
||||
" let fd: i32 = os.open(path, os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC, 0o644: i32);\n"
|
||||
" if (fd < 0) { return 90; };\n"
|
||||
" let wr = fmt.fdprintf_v(fd, \"v={}\\n\", 42i64);\n"
|
||||
" let wr = fmt.fdprintf(fd, \"v={}\\n\", 42i64);\n"
|
||||
" let nw: size = 0;\n"
|
||||
" match (wr) {\n"
|
||||
" case let n: size => { nw = n; };\n"
|
||||
@@ -147,7 +146,7 @@ static const struct row rows[] = {
|
||||
" let path: str = \"/tmp/wwfmtv_777_b\";\n"
|
||||
" let fd: i32 = os.open(path, os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC, 0o644: i32);\n"
|
||||
" if (fd < 0) { return 90; };\n"
|
||||
" let wr = fmt.fdprintln_v(fd, 7i64, \"hi\", true);\n"
|
||||
" let wr = fmt.fdprintln(fd, 7i64, \"hi\", true);\n"
|
||||
" let nw: size = 0;\n"
|
||||
" match (wr) {\n"
|
||||
" case let n: size => { nw = n; };\n"
|
||||
@@ -175,7 +174,7 @@ static const struct row rows[] = {
|
||||
" let path: str = \"/tmp/wwfmtv_777_c\";\n"
|
||||
" let fd: i32 = os.open(path, os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC, 0o644: i32);\n"
|
||||
" if (fd < 0) { return 90; };\n"
|
||||
" let wr = fmt.fdprint_v(fd, \"raw\");\n"
|
||||
" let wr = fmt.fdprint(fd, \"raw\");\n"
|
||||
" let nw: size = 0;\n"
|
||||
" match (wr) {\n"
|
||||
" case let n: size => { nw = n; };\n"
|
||||
@@ -194,7 +193,7 @@ static const struct row rows[] = {
|
||||
"};\n",
|
||||
44,
|
||||
STAGE_CS, 0 },
|
||||
{ "branched_fdprintf_v",
|
||||
{ "branched_fdprintf",
|
||||
"package main;\n"
|
||||
"import os;\n"
|
||||
"import fmt;\n"
|
||||
@@ -208,7 +207,7 @@ static const struct row rows[] = {
|
||||
" let f2: str = \"B{}\";\n"
|
||||
" let fmtstr: str = f1;\n"
|
||||
" if (sel == 0) { fmtstr = f2; };\n"
|
||||
" let wr = fmt.fdprintf_v(fd, fmtstr, 9i64);\n"
|
||||
" let wr = fmt.fdprintf(fd, fmtstr, 9i64);\n"
|
||||
" let nw: size = 0;\n"
|
||||
" match (wr) {\n"
|
||||
" case let n: size => { nw = n; };\n"
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
/*
|
||||
* 778_bufio_vstream_run — project #94 fold-eFinal(PREP) sentinel. Pins
|
||||
* the lib/bufio/vstream.ww parallel API — a COLLAPSE of the OLD bufio
|
||||
* surface onto the io.vtable, NOT a port of Hare's missing scanner
|
||||
* 778_bufio_vstream_run — project #94 fold-eFinal sentinel. Pins
|
||||
* the single-surface lib/bufio over io.stream — the unified surface
|
||||
* the eFinal FLIP collapsed to, NOT a port of Hare's missing scanner
|
||||
* features (those are #217, OUT of eFinal). Covers:
|
||||
* - buffered stream: `bufio_ctx` (vt@offset0 for the intrusive cast),
|
||||
* `bufio_vstream` (returns the ctx BY VALUE; sret field-by-field)
|
||||
* over an underlying **io.vstream** src — every underlying read/
|
||||
* write/close goes through io.st_read/io.st_write/io.st_close (the
|
||||
* - buffered stream: `stream` (vt@offset0 for the intrusive cast),
|
||||
* `init` (returns the stream BY VALUE; sret field-by-field)
|
||||
* over an underlying **io.stream** src — every underlying read/
|
||||
* write/close goes through io.read/io.write/io.close (the
|
||||
* vtable dispatchers), io.error forwards directly (no nomem-widen).
|
||||
* Plus setflush_v / flush_v (public + internal drain) / unread_v /
|
||||
* isbuffered_v (fn-ptr-equality discriminator).
|
||||
* - scanner: `scanner_v` (value-return newscannerbuf_v over an
|
||||
* io.vstream src) + scanbyte_v / scanbytes_v (single-byte delim) /
|
||||
* scanline_v / finish_v.
|
||||
* Coexists with the pre-vtable bufio.init / scanner / isbuffered surface
|
||||
* in bufio.ww (fold-eFinal FLIP retires the latter + drops the `_v`
|
||||
* suffix: bufio_ctx→stream, bufio_vstream→init, scanner_v→scanner,
|
||||
* scanbytes_v→scanbytes, newscannerbuf_v→newscannerbuf, …).
|
||||
* Plus setflush / flush (public + internal drain) / unread /
|
||||
* isbuffered (fn-ptr-equality discriminator).
|
||||
* - scanner: `scanner` (value-return newscannerbuf over an
|
||||
* io.stream src) + scanbyte / scanbytes (single-byte delim) /
|
||||
* scanline / finish.
|
||||
*
|
||||
* Each row imports os + bufio + memio + io (NOT fmt — bufio doesn't
|
||||
* transitively pull fmt, so #209's formattable-match bail doesn't
|
||||
@@ -25,89 +21,79 @@
|
||||
*
|
||||
* row | what it pins
|
||||
* --------------------------+--------------------------------------
|
||||
* stream_write_flush | bufio_vstream init + io.st_write
|
||||
* | through bwrite_v + bufio.flush_v via
|
||||
* | io.st_close. Asserts the data
|
||||
* stream_write_flush | bufio.init + io.write
|
||||
* | through bwrite + bufio.flush via
|
||||
* | io.close. Asserts the data
|
||||
* | reaches the underlying memio buffer
|
||||
* | after close drains wbuf. Full chain:
|
||||
* | bufio_ctx built field-by-field →
|
||||
* | stream built field-by-field →
|
||||
* | return r (sret) → caller takes &b.vt →
|
||||
* | dispatcher recovers via *bufio_ctx →
|
||||
* | flush_v → io.write(b.src, ...).
|
||||
* stream_read_unread | bufio_vstream read path: io.st_read
|
||||
* | dispatcher recovers via *stream →
|
||||
* | flush → io.write(b.src, ...).
|
||||
* stream_read_unread | init read path: io.read
|
||||
* | refills rbuf from src, serves first
|
||||
* | N bytes. Pins bread_v's read path
|
||||
* | through the intrusive cast. unread_v
|
||||
* | N bytes. Pins bread's read path
|
||||
* | through the intrusive cast. unread
|
||||
* | not exposed yet (no bufio.unread
|
||||
* | equivalent on the V API surface
|
||||
* | until eFinal); this row exercises
|
||||
* | the base read path instead.
|
||||
* isbuffered_v_discriminate | isbuffered_v on a fresh bufio_vstream
|
||||
* | returns true; isbuffered_v on a
|
||||
* | plain memio.fixed_vstream (no bufio
|
||||
* isbuffered_v_discriminate | isbuffered on a fresh init
|
||||
* | returns true; isbuffered on a
|
||||
* | plain memio.fixed (no bufio
|
||||
* | wrap) returns false. Pins the
|
||||
* | fn-ptr-equality discriminator
|
||||
* | (ref/hare/bufio/stream.ha:179) over
|
||||
* | the new `_v` callback symbols.
|
||||
* isbuffered_v_boundary | NEW isbuffered_v on bufio_vstream
|
||||
* | returns true AND OLD bufio.isbuffered
|
||||
* | on a bytewise-cast view of the same
|
||||
* | vstream returns false. Boundary
|
||||
* | check: the OLD discriminator reads
|
||||
* | (s.read == bread) but the bytes at
|
||||
* | &bufio_ctx.vt are vt.reader (tagged
|
||||
* | union) — the cast reinterprets vt's
|
||||
* | tag+ptr layout as the OLD stream's
|
||||
* | ctx/read/write/close 4-fnptr layout,
|
||||
* | and the OLD `bread` symbol address
|
||||
* | is distinct from the NEW `bread_v`,
|
||||
* | so the OLD discriminator returns
|
||||
* | false. Proves the OLD + NEW worlds
|
||||
* | use independent fn symbols.
|
||||
* branched_bufio_vstream | branched callee per #105: two
|
||||
* | the bread/bwrite callback symbols.
|
||||
* isbuffered_v_boundary | isbuffered discriminates by vtable
|
||||
* | identity: true on the bufio.init
|
||||
* | wrapper stream, false on the
|
||||
* | underlying (non-bufio) memio stream.
|
||||
* | Pins the fn-ptr-equality discriminator
|
||||
* | (ref/hare/bufio/stream.ha:179) over
|
||||
* | the single-surface io.stream.
|
||||
* branched_bufio_wrap | branched callee per #105: two
|
||||
* | distinct underlying *io.stream's
|
||||
* | runtime-selected; bufio_vstream
|
||||
* | wraps the picked one. st_read
|
||||
* | runtime-selected; bufio.init
|
||||
* | wraps the picked one. io.read
|
||||
* | returns the matching source's
|
||||
* | content. Catches a constant-fold
|
||||
* | mistake in the dispatch path
|
||||
* | (mirror of 775's branched_readers
|
||||
* | for the bufio-wrap side).
|
||||
*
|
||||
* SIBLINGS (filed inline, NOT fixed here — fold-e4 is purely
|
||||
* additive over fold-e1/e2/e3's frozen io.* + memio.* + fmt.*
|
||||
* surface):
|
||||
* DEFERRALS / RELATED (NOT addressed by this row):
|
||||
*
|
||||
* - io fold-2 (handle port): drew-deferred. Hare's
|
||||
* ref/hare/bufio/stream.ha:69 takes `src: io::handle`; once
|
||||
* handle = (file | int) lands, bufio_vstream's src parameter
|
||||
* collapses accordingly. eFinal (#50) graduates.
|
||||
* handle = (file | int) lands, init's src parameter
|
||||
* collapses accordingly. #5 graduates.
|
||||
*
|
||||
* - #206 (bare &fn → (*alias|void)): 5 cast sites in vstream.ww
|
||||
* (3 vtable wire-up + 2 in isbuffered_v); drop out wholesale
|
||||
* on close.
|
||||
* - #206 (bare &fn → (*alias|void)): 5 cast sites in bufio.ww
|
||||
* (3 vtable wire-up + 2 in isbuffered); explicit casts KEPT
|
||||
* (ken/#214 — cast-drop deferred, gated on #214).
|
||||
*
|
||||
* - #173 (TRY-on-tagged-return both-stages broken): flush_v
|
||||
* - #173 (TRY-on-tagged-return both-stages broken): flush
|
||||
* constructs nomem and widens to io.error explicitly rather
|
||||
* than using `io.write(...)?`; same memio.vstream.ww +
|
||||
* fmt.vstream.ww shape.
|
||||
* than using `io.write(...)?`; same shape memio.ww + fmt.ww
|
||||
* adopt.
|
||||
*
|
||||
* - #207 (struct-lit multi-tagged-field copy drops past first) and
|
||||
* #210 (slice-field struct-lit drop): not reachable from the
|
||||
* value-return form — bufio_ctx is built field-by-field in a
|
||||
* value-return form — stream is built field-by-field in a
|
||||
* local then sret-returned, no `alloc(T{...})` struct-lit.
|
||||
*
|
||||
* BOOTSTRAP-EMBED CHECK: bufio is NOT embedded in any selfhost
|
||||
* combined.ww (grep confirmed). Adding lib/bufio/vstream.ww does
|
||||
* NOT require a Makefile regen (#110); only lib/bufio/ test paths
|
||||
* see the new module. 990-997 byte-id gates stay green by virtue
|
||||
* combined.ww (grep confirmed), so the fold-eFinal bufio collapse
|
||||
* does NOT require a Makefile regen (#110); only lib/bufio/ test
|
||||
* paths see the surface. 990-997 byte-id gates stay green by virtue
|
||||
* of bufio being test-only.
|
||||
*
|
||||
* GATE POLARITY: must stay GREEN. A red here means vstream.ww
|
||||
* regressed, the alloc-struct-? path miscompiled, the intrusive
|
||||
* vstream→*bufio_ctx cast miscomputed offsets, the io.vtable
|
||||
* GATE POLARITY: must stay GREEN. A red here means lib/bufio
|
||||
* regressed, the sret value-return path miscompiled, the intrusive
|
||||
* io.stream→*stream cast miscomputed offsets, the io.vtable
|
||||
* first-field embed lost its offset-0 invariant, or the fn-ptr-
|
||||
* equality discriminator in isbuffered_v stopped resolving.
|
||||
* equality discriminator in isbuffered stopped resolving.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -144,20 +130,20 @@ static const struct row rows[] = {
|
||||
"import io;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let raw: [16]u8;\n"
|
||||
" let mst: memio.stream = memio.fixed_vstream(raw[0:16]);\n"
|
||||
" let msrc: io.vstream = &mst.vt;\n"
|
||||
" let mst: memio.stream = memio.fixed(raw[0:16]);\n"
|
||||
" let msrc: io.stream = &mst.vt;\n"
|
||||
" let rbuf: [8]u8;\n"
|
||||
" let wbuf: [8]u8;\n"
|
||||
" let b: bufio.bufio_ctx = bufio.bufio_vstream(msrc, rbuf[0:8], wbuf[0:8]);\n"
|
||||
" let vs: io.vstream = &b.vt;\n"
|
||||
" let b: bufio.stream = bufio.init(msrc, rbuf[0:8], wbuf[0:8]);\n"
|
||||
" let vs: io.stream = &b.vt;\n"
|
||||
" let payload: [5]u8;\n"
|
||||
" payload[0] = 104u8; payload[1] = 105u8;\n"
|
||||
" payload[2] = 33u8; payload[3] = 98u8; payload[4] = 121u8;\n"
|
||||
" let wr = io.st_write(vs, payload[0:5]);\n"
|
||||
" let wr = io.write(vs, payload[0:5]);\n"
|
||||
" let nw: size = 0;\n"
|
||||
" if (wr is size) { nw = wr as size; };\n"
|
||||
" if (mst.pos != 0) { return 91; };\n"
|
||||
" let cl = io.st_close(vs);\n"
|
||||
" let cl = io.close(vs);\n"
|
||||
" if (cl is io.error) { return 92; };\n"
|
||||
" if (mst.pos != 5) { return 93; };\n"
|
||||
" if (nw != 5: size) { return 94; };\n"
|
||||
@@ -176,19 +162,19 @@ static const struct row rows[] = {
|
||||
" let raw: [8]u8;\n"
|
||||
" raw[0] = 65u8; raw[1] = 66u8; raw[2] = 67u8; raw[3] = 68u8;\n"
|
||||
" raw[4] = 69u8; raw[5] = 70u8; raw[6] = 71u8; raw[7] = 72u8;\n"
|
||||
" let mst: memio.stream = memio.fixed_vstream(raw[0:8]);\n"
|
||||
" let msrc: io.vstream = &mst.vt;\n"
|
||||
" let mst: memio.stream = memio.fixed(raw[0:8]);\n"
|
||||
" let msrc: io.stream = &mst.vt;\n"
|
||||
" let rbuf: [8]u8;\n"
|
||||
" let wbuf: [4]u8;\n"
|
||||
" let b: bufio.bufio_ctx = bufio.bufio_vstream(msrc, rbuf[0:8], wbuf[0:4]);\n"
|
||||
" let vs: io.vstream = &b.vt;\n"
|
||||
" let b: bufio.stream = bufio.init(msrc, rbuf[0:8], wbuf[0:4]);\n"
|
||||
" let vs: io.stream = &b.vt;\n"
|
||||
" let out: [4]u8;\n"
|
||||
" let rd1 = io.st_read(vs, out[0:3]);\n"
|
||||
" let rd1 = io.read(vs, out[0:3]);\n"
|
||||
" let n1: size = 0;\n"
|
||||
" if (rd1 is size) { n1 = rd1 as size; };\n"
|
||||
" if (n1 != 3: size) { return 91; };\n"
|
||||
" if (out[0] != 65u8 || out[2] != 67u8) { return 92; };\n"
|
||||
" let rd2 = io.st_read(vs, out[0:4]);\n"
|
||||
" let rd2 = io.read(vs, out[0:4]);\n"
|
||||
" let n2: size = 0;\n"
|
||||
" if (rd2 is size) { n2 = rd2 as size; };\n"
|
||||
" if (n2 != 4: size) { return 93; };\n"
|
||||
@@ -205,17 +191,17 @@ static const struct row rows[] = {
|
||||
"import io;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let raw: [8]u8;\n"
|
||||
" let mst: memio.stream = memio.fixed_vstream(raw[0:8]);\n"
|
||||
" let msrc: io.vstream = &mst.vt;\n"
|
||||
" let mst: memio.stream = memio.fixed(raw[0:8]);\n"
|
||||
" let msrc: io.stream = &mst.vt;\n"
|
||||
" let rbuf: [4]u8;\n"
|
||||
" let wbuf: [4]u8;\n"
|
||||
" let b: bufio.bufio_ctx = bufio.bufio_vstream(msrc, rbuf[0:4], wbuf[0:4]);\n"
|
||||
" let vsbuf: io.vstream = &b.vt;\n"
|
||||
" let b: bufio.stream = bufio.init(msrc, rbuf[0:4], wbuf[0:4]);\n"
|
||||
" let vsbuf: io.stream = &b.vt;\n"
|
||||
" let buf2: [4]u8;\n"
|
||||
" let st2: memio.stream = memio.fixed_vstream(buf2[0:4]);\n"
|
||||
" let vsplain: io.vstream = &st2.vt;\n"
|
||||
" if (!bufio.isbuffered_v(vsbuf)) { return 91; };\n"
|
||||
" if (bufio.isbuffered_v(vsplain)) { return 92; };\n"
|
||||
" let st2: memio.stream = memio.fixed(buf2[0:4]);\n"
|
||||
" let vsplain: io.stream = &st2.vt;\n"
|
||||
" if (!bufio.isbuffered(vsbuf)) { return 91; };\n"
|
||||
" if (bufio.isbuffered(vsplain)) { return 92; };\n"
|
||||
" return 48;\n"
|
||||
"};\n",
|
||||
48,
|
||||
@@ -228,30 +214,19 @@ static const struct row rows[] = {
|
||||
"import io;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let raw: [8]u8;\n"
|
||||
" let mst: memio.stream = memio.fixed_vstream(raw[0:8]);\n"
|
||||
" let msrc: io.vstream = &mst.vt;\n"
|
||||
" let mst: memio.stream = memio.fixed(raw[0:8]);\n"
|
||||
" let msrc: io.stream = &mst.vt;\n"
|
||||
" let rbuf: [4]u8;\n"
|
||||
" let wbuf: [4]u8;\n"
|
||||
" let bc: bufio.bufio_ctx = bufio.bufio_vstream(msrc, rbuf[0:4], wbuf[0:4]);\n"
|
||||
" let vs: io.vstream = &bc.vt;\n"
|
||||
" if (!bufio.isbuffered_v(vs)) { return 91; };\n"
|
||||
" let raw2: u64 = vs: u64;\n"
|
||||
" let asold: *io.stream = raw2: *io.stream;\n"
|
||||
" if (bufio.isbuffered(asold)) { return 92; };\n"
|
||||
" let b: bufio.stream;\n"
|
||||
" let rb: [4]u8;\n"
|
||||
" let wb: [4]u8;\n"
|
||||
" let raw3: [8]u8;\n"
|
||||
" let mem3: memio.state;\n"
|
||||
" let m3: io.stream;\n"
|
||||
" memio.fixed(&mem3, &m3, raw3[0:8]);\n"
|
||||
" bufio.init(&b, &m3, rb[0:4], wb[0:4]);\n"
|
||||
" if (!bufio.isbuffered(&b.vtable)) { return 93; };\n"
|
||||
" let bc: bufio.stream = bufio.init(msrc, rbuf[0:4], wbuf[0:4]);\n"
|
||||
" let vs: io.stream = &bc.vt;\n"
|
||||
" if (!bufio.isbuffered(vs)) { return 91; };\n"
|
||||
" if (bufio.isbuffered(msrc)) { return 92; };\n"
|
||||
" return 49;\n"
|
||||
"};\n",
|
||||
49,
|
||||
STAGE_CS | STAGE_WW, 1 },
|
||||
{ "branched_bufio_vstream",
|
||||
{ "branched_bufio_wrap",
|
||||
"package main;\n"
|
||||
"import os;\n"
|
||||
"import bufio;\n"
|
||||
@@ -262,17 +237,17 @@ static const struct row rows[] = {
|
||||
" raA[0] = 10u8; raA[1] = 11u8; raA[2] = 12u8; raA[3] = 13u8;\n"
|
||||
" let raB: [4]u8;\n"
|
||||
" raB[0] = 20u8; raB[1] = 21u8; raB[2] = 22u8; raB[3] = 23u8;\n"
|
||||
" let mstA: memio.stream = memio.fixed_vstream(raA[0:4]);\n"
|
||||
" let mstB: memio.stream = memio.fixed_vstream(raB[0:4]);\n"
|
||||
" let mstA: memio.stream = memio.fixed(raA[0:4]);\n"
|
||||
" let mstB: memio.stream = memio.fixed(raB[0:4]);\n"
|
||||
" let sel: i32 = 1;\n"
|
||||
" let src: io.vstream = &mstA.vt;\n"
|
||||
" let src: io.stream = &mstA.vt;\n"
|
||||
" if (sel == 0) { src = &mstB.vt; };\n"
|
||||
" let rbuf: [4]u8;\n"
|
||||
" let wbuf: [4]u8;\n"
|
||||
" let bc: bufio.bufio_ctx = bufio.bufio_vstream(src, rbuf[0:4], wbuf[0:4]);\n"
|
||||
" let vs: io.vstream = &bc.vt;\n"
|
||||
" let bc: bufio.stream = bufio.init(src, rbuf[0:4], wbuf[0:4]);\n"
|
||||
" let vs: io.stream = &bc.vt;\n"
|
||||
" let out: [4]u8;\n"
|
||||
" let rd = io.st_read(vs, out[0:4]);\n"
|
||||
" let rd = io.read(vs, out[0:4]);\n"
|
||||
" let n: size = 0;\n"
|
||||
" if (rd is size) { n = rd as size; };\n"
|
||||
" if (n != 4: size) { return 91; };\n"
|
||||
@@ -282,9 +257,9 @@ static const struct row rows[] = {
|
||||
51,
|
||||
STAGE_CS | STAGE_WW, 1 },
|
||||
/* scanner_lines — fold-eFinal PREP: the value-return scanner over an
|
||||
* io.vstream src. newscannerbuf_v (Hare newscanner_buf) → scanline_v
|
||||
* io.stream src. newscannerbuf (Hare newscanner_buf) → scanline
|
||||
* twice ("ab", "c") → trailing "def" with no newline is EOF_DISCARD
|
||||
* → io.eof → finish_v. Pins the scanner reads through io.st_read
|
||||
* → io.eof → finish. Pins the scanner reads through io.read
|
||||
* (vtable dispatch) on the underlying memio stream. cs.s==ww.s. */
|
||||
{ "scanner_lines",
|
||||
"package main;\n"
|
||||
@@ -297,36 +272,36 @@ static const struct row rows[] = {
|
||||
" raw[0]=97u8; raw[1]=98u8; raw[2]=10u8;\n"
|
||||
" raw[3]=99u8; raw[4]=10u8;\n"
|
||||
" raw[5]=100u8; raw[6]=101u8; raw[7]=102u8;\n"
|
||||
" let mst: memio.stream = memio.dynamicfrom_vstream(raw[0:8]);\n"
|
||||
" let vs: io.vstream = &mst.vt;\n"
|
||||
" let mst: memio.stream = memio.dynamicfrom(raw[0:8]);\n"
|
||||
" let vs: io.stream = &mst.vt;\n"
|
||||
" let win: [16]u8;\n"
|
||||
" let sc: bufio.scanner_v = bufio.newscannerbuf_v(vs, win[0:16]);\n"
|
||||
" let sc: bufio.scanner = bufio.newscannerbuf(vs, win[0:16]);\n"
|
||||
" let ok: i32 = 0;\n"
|
||||
" match (bufio.scanline_v(&sc)) {\n"
|
||||
" match (bufio.scanline(&sc)) {\n"
|
||||
" case let s: str => { if (s.len == 2 && s[0] == 97u8 && s[1] == 98u8) { ok += 1; }; };\n"
|
||||
" case io.eof => { return 81; };\n"
|
||||
" case let e: io.error => { return 82; };\n"
|
||||
" case bufio.overflow => { return 83; };\n"
|
||||
" };\n"
|
||||
" match (bufio.scanline_v(&sc)) {\n"
|
||||
" match (bufio.scanline(&sc)) {\n"
|
||||
" case let s: str => { if (s.len == 1 && s[0] == 99u8) { ok += 1; }; };\n"
|
||||
" case io.eof => { return 84; };\n"
|
||||
" case let e: io.error => { return 85; };\n"
|
||||
" case bufio.overflow => { return 86; };\n"
|
||||
" };\n"
|
||||
" match (bufio.scanline_v(&sc)) {\n"
|
||||
" match (bufio.scanline(&sc)) {\n"
|
||||
" case let s: str => { return 87; };\n"
|
||||
" case io.eof => { ok += 1; };\n"
|
||||
" case let e: io.error => { return 88; };\n"
|
||||
" case bufio.overflow => { return 89; };\n"
|
||||
" };\n"
|
||||
" bufio.finish_v(&sc);\n"
|
||||
" bufio.finish(&sc);\n"
|
||||
" if (ok != 3) { return 90; };\n"
|
||||
" return 52;\n"
|
||||
"};\n",
|
||||
52,
|
||||
STAGE_CS | STAGE_WW, 1 },
|
||||
/* scanner_byte_bytes — scanbyte_v pops 'A'; scanbytes_v(',') then
|
||||
/* scanner_byte_bytes — scanbyte pops 'A'; scanbytes(',') then
|
||||
* tokenizes "B" and "CD" from "AB,CD,". Single-byte delim (the OLD
|
||||
* scantok behaviour; multibyte is #217). cs.s==ww.s. */
|
||||
{ "scanner_byte_bytes",
|
||||
@@ -339,24 +314,24 @@ static const struct row rows[] = {
|
||||
" let raw: [6]u8;\n"
|
||||
" raw[0]=65u8; raw[1]=66u8; raw[2]=44u8;\n"
|
||||
" raw[3]=67u8; raw[4]=68u8; raw[5]=44u8;\n"
|
||||
" let mst: memio.stream = memio.dynamicfrom_vstream(raw[0:6]);\n"
|
||||
" let vs: io.vstream = &mst.vt;\n"
|
||||
" let mst: memio.stream = memio.dynamicfrom(raw[0:6]);\n"
|
||||
" let vs: io.stream = &mst.vt;\n"
|
||||
" let win: [8]u8;\n"
|
||||
" let sc: bufio.scanner_v = bufio.newscannerbuf_v(vs, win[0:8]);\n"
|
||||
" let sc: bufio.scanner = bufio.newscannerbuf(vs, win[0:8]);\n"
|
||||
" let fb: u8 = 0u8;\n"
|
||||
" match (bufio.scanbyte_v(&sc)) {\n"
|
||||
" match (bufio.scanbyte(&sc)) {\n"
|
||||
" case let b: u8 => { fb = b; };\n"
|
||||
" case io.eof => { return 81; };\n"
|
||||
" case let e: io.error => { return 82; };\n"
|
||||
" };\n"
|
||||
" if (fb != 65u8) { return 83; };\n"
|
||||
" match (bufio.scanbytes_v(&sc, 44u8)) {\n"
|
||||
" match (bufio.scanbytes(&sc, 44u8)) {\n"
|
||||
" case let bs: []u8 => { if (bs.len != 1 || bs[0] != 66u8) { return 84; }; };\n"
|
||||
" case io.eof => { return 85; };\n"
|
||||
" case let e: io.error => { return 86; };\n"
|
||||
" case bufio.overflow => { return 87; };\n"
|
||||
" };\n"
|
||||
" match (bufio.scanbytes_v(&sc, 44u8)) {\n"
|
||||
" match (bufio.scanbytes(&sc, 44u8)) {\n"
|
||||
" case let bs: []u8 => { if (bs.len != 2 || bs[0] != 67u8 || bs[1] != 68u8) { return 88; }; };\n"
|
||||
" case io.eof => { return 89; };\n"
|
||||
" case let e: io.error => { return 90; };\n"
|
||||
@@ -366,9 +341,9 @@ static const struct row rows[] = {
|
||||
"};\n",
|
||||
53,
|
||||
STAGE_CS | STAGE_WW, 1 },
|
||||
/* stream_setflush — setflush_v swaps the auto-flush byte-set to ';';
|
||||
/* stream_setflush — setflush swaps the auto-flush byte-set to ';';
|
||||
* writing "ab;" auto-flushes (contains ';') so the sink advances to
|
||||
* 3 with no explicit flush. Pins setflush_v + the flush-scan path.
|
||||
* 3 with no explicit flush. Pins setflush + the flush-scan path.
|
||||
* cs.s==ww.s. */
|
||||
{ "stream_setflush",
|
||||
"package main;\n"
|
||||
@@ -378,18 +353,18 @@ static const struct row rows[] = {
|
||||
"import io;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let sinkbuf: [16]u8;\n"
|
||||
" let sink: memio.stream = memio.fixed_vstream(sinkbuf[0:16]);\n"
|
||||
" let sinkvs: io.vstream = &sink.vt;\n"
|
||||
" let sink: memio.stream = memio.fixed(sinkbuf[0:16]);\n"
|
||||
" let sinkvs: io.stream = &sink.vt;\n"
|
||||
" let rb: [4]u8;\n"
|
||||
" let wb: [8]u8;\n"
|
||||
" let b: bufio.bufio_ctx = bufio.bufio_vstream(sinkvs, rb[0:4], wb[0:8]);\n"
|
||||
" let bvs: io.vstream = &b.vt;\n"
|
||||
" let b: bufio.stream = bufio.init(sinkvs, rb[0:4], wb[0:8]);\n"
|
||||
" let bvs: io.stream = &b.vt;\n"
|
||||
" let semi: [1]u8;\n"
|
||||
" semi[0] = 59u8;\n"
|
||||
" bufio.setflush_v(&b, semi[0:1]);\n"
|
||||
" bufio.setflush(&b, semi[0:1]);\n"
|
||||
" let payload: [3]u8;\n"
|
||||
" payload[0]=97u8; payload[1]=98u8; payload[2]=59u8;\n"
|
||||
" let wr = io.st_write(bvs, payload[0:3]);\n"
|
||||
" let wr = io.write(bvs, payload[0:3]);\n"
|
||||
" if (!(wr is size)) { return 81; };\n"
|
||||
" if (sink.pos != 3) { return 82; };\n"
|
||||
" if (sinkbuf[0] != 97u8 || sinkbuf[2] != 59u8) { return 83; };\n"
|
||||
@@ -397,9 +372,9 @@ static const struct row rows[] = {
|
||||
"};\n",
|
||||
54,
|
||||
STAGE_CS | STAGE_WW, 1 },
|
||||
/* stream_unread — read "XYZ" then unread_v("XY") pushes the two
|
||||
/* stream_unread — read "XYZ" then unread("XY") pushes the two
|
||||
* bytes back in front of the read buffer; the next read returns them
|
||||
* first. Pins unread_v's in-place shift through *bufio_ctx.
|
||||
* first. Pins unread's in-place shift through *stream.
|
||||
* cs.s==ww.s. */
|
||||
{ "stream_unread",
|
||||
"package main;\n"
|
||||
@@ -410,22 +385,22 @@ static const struct row rows[] = {
|
||||
"export fn main() i32 = {\n"
|
||||
" let raw: [4]u8;\n"
|
||||
" raw[0]=88u8; raw[1]=89u8; raw[2]=90u8;\n"
|
||||
" let mst: memio.stream = memio.dynamicfrom_vstream(raw[0:3]);\n"
|
||||
" let vs: io.vstream = &mst.vt;\n"
|
||||
" let mst: memio.stream = memio.dynamicfrom(raw[0:3]);\n"
|
||||
" let vs: io.stream = &mst.vt;\n"
|
||||
" let rbuf: [4]u8;\n"
|
||||
" let wbuf: [4]u8;\n"
|
||||
" let b: bufio.bufio_ctx = bufio.bufio_vstream(vs, rbuf[0:4], wbuf[0:4]);\n"
|
||||
" let bvs: io.vstream = &b.vt;\n"
|
||||
" let b: bufio.stream = bufio.init(vs, rbuf[0:4], wbuf[0:4]);\n"
|
||||
" let bvs: io.stream = &b.vt;\n"
|
||||
" let out: [4]u8;\n"
|
||||
" let rd = io.st_read(bvs, out[0:3]);\n"
|
||||
" let rd = io.read(bvs, out[0:3]);\n"
|
||||
" let n: size = 0;\n"
|
||||
" if (rd is size) { n = rd as size; };\n"
|
||||
" if (n != 3: size || out[0] != 88u8 || out[2] != 90u8) { return 81; };\n"
|
||||
" let push: [2]u8;\n"
|
||||
" push[0]=88u8; push[1]=89u8;\n"
|
||||
" bufio.unread_v(&b, push[0:2]);\n"
|
||||
" bufio.unread(&b, push[0:2]);\n"
|
||||
" let out2: [2]u8;\n"
|
||||
" let rd2 = io.st_read(bvs, out2[0:2]);\n"
|
||||
" let rd2 = io.read(bvs, out2[0:2]);\n"
|
||||
" let n2: size = 0;\n"
|
||||
" if (rd2 is size) { n2 = rd2 as size; };\n"
|
||||
" if (n2 != 2: size || out2[0] != 88u8 || out2[1] != 89u8) { return 82; };\n"
|
||||
@@ -564,7 +539,7 @@ main(void)
|
||||
int got = run_row(cdrv, cwd, &rows[i], seq++);
|
||||
if (got != rows[i].want_exit) {
|
||||
fprintf(stderr,
|
||||
"bufio_vstream_run[cs][%s]: exit=%d want=%d\n",
|
||||
"init_run[cs][%s]: exit=%d want=%d\n",
|
||||
rows[i].label, got, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
@@ -574,7 +549,7 @@ main(void)
|
||||
int got = run_row(wdrv, cwd, &rows[i], seq++);
|
||||
if (got != rows[i].want_exit) {
|
||||
fprintf(stderr,
|
||||
"bufio_vstream_run[ww][%s]: exit=%d want=%d\n",
|
||||
"init_run[ww][%s]: exit=%d want=%d\n",
|
||||
rows[i].label, got, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
@@ -582,7 +557,7 @@ main(void)
|
||||
total++;
|
||||
if (asm_byte_identical(cdrv, wdrv, cwd, &rows[i], seq++) != 0) {
|
||||
fprintf(stderr,
|
||||
"bufio_vstream_run[byte-id][%s]: cstage vs wwstage asm differs\n",
|
||||
"init_run[byte-id][%s]: cstage vs wwstage asm differs\n",
|
||||
rows[i].label);
|
||||
fail++;
|
||||
}
|
||||
@@ -591,13 +566,13 @@ main(void)
|
||||
}
|
||||
|
||||
if (!wwpresent)
|
||||
fprintf(stderr, "bufio_vstream_run: skip wwstage (no %s)\n", wdrv);
|
||||
fprintf(stderr, "init_run: skip wwstage (no %s)\n", wdrv);
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "bufio_vstream_run: %d/%d fixtures failed\n",
|
||||
fprintf(stderr, "init_run: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("bufio_vstream_run: %d/%d ok\n", total, total);
|
||||
printf("init_run: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
/*
|
||||
* 779_log_vstream_run — project #94 fold-e5 sentinel. Pins the
|
||||
* additive lib/log/vstream.ww Option C parallel API: module-static
|
||||
* stderrsink_ctx_g with `vt: io.vtable` as the first field for the
|
||||
* intrusive vstream cast, the 10 _v variants of OLD log.ww's surface
|
||||
* (new_v / lprintln_v / println_v / lprintfln_v / printfln_v / lfatal_v
|
||||
* / fatal_v / lfatalf_v / fatalf_v / setlogger_v), a vlogger vtable +
|
||||
* vstdlogger over io.vstream sink, and lazy ensureinit_v wiring the
|
||||
* stderr default + silent_v / default_v / global_v *vlogger globals.
|
||||
* Coexists with the pre-vtable log.logger / log.stdlogger / log.new /
|
||||
* log.println / etc. surface in log.ww (fold-eFinal, task #50, retires
|
||||
* the latter and drops the `_v` suffix wholesale to match Hare's bare
|
||||
* names).
|
||||
* 779_log_vstream_run — project #94 fold-eFinal sentinel. Pins the
|
||||
* single-surface lib/log: module-static stderrsink_ctx_g with
|
||||
* `vt: io.vtable` as the first field for the intrusive cast, the 10
|
||||
* exported variants of log.ww's surface (new / lprintln / println /
|
||||
* lprintfln / printfln / lfatal / fatal / lfatalf / fatalf /
|
||||
* setlogger), a logger vtable + stdlogger over an io.stream sink, and
|
||||
* lazy ensureinit wiring the stderr default + silent / default /
|
||||
* global *logger globals. The fold-eFinal FLIP collapsed the dual
|
||||
* surface into this one Hare-shaped surface (bare names, no suffix).
|
||||
*
|
||||
* Each row imports os + log + memio + io. log itself imports fmt,
|
||||
* so the formattable / field types flow through transitively.
|
||||
@@ -18,8 +15,8 @@
|
||||
* Cstage-only per row (no STAGE_WW, no byte_id) — pre-existing
|
||||
* wwstage bug #209 (sibling of #190): the wwstage checker bails on
|
||||
* match-arm-over-formattable when fmt is imported transitively, which
|
||||
* lib/log does for the vlogger vtable signatures + the fmt.vfprint /
|
||||
* vfprintf dispatch in stdprintln_v / stdprintfln_v. The bug bites the
|
||||
* lib/log does for the logger vtable signatures + the fmt.fprint /
|
||||
* fprintf dispatch in stdprintln / stdprintfln. The bug bites the
|
||||
* OLD log surface identically — `log.println("hi")` from a probe
|
||||
* trips the same trace. Existing 970 logtest runs the @test fixture
|
||||
* under cstage `ww run` only, so the OLD surface is fine via cstage.
|
||||
@@ -30,70 +27,63 @@
|
||||
*
|
||||
* row | what it pins
|
||||
* --------------------------+--------------------------------------
|
||||
* println_v_default_stderr | log.println_v through the [[default_v]]
|
||||
* println_v_default_stderr | log.println through the [[default]]
|
||||
* | global, routed to fd 2 via the
|
||||
* | module-static stderrsink_ctx_g. Asserts
|
||||
* | ensureinit_v wires + dispatches without
|
||||
* | ensureinit wires + dispatches without
|
||||
* | crashing; stderr capture is left to the
|
||||
* | shell runner. Pins the full chain:
|
||||
* | println_v → lprintln_v(global_v) →
|
||||
* | (*vlogger).println_v → stdprintln_v →
|
||||
* | fmt.vfprint + io.st_write("\n").
|
||||
* | println → lprintln(global) →
|
||||
* | (*logger).println → stdprintln →
|
||||
* | fmt.fprint + io.write("\n").
|
||||
* printfln_v_default_stderr | Same chain as row 1 but through the
|
||||
* | format-string slot (printfln_v →
|
||||
* | lprintfln_v(global_v) → stdprintfln_v
|
||||
* | → fmt.vfprintf + io.st_write("\n")).
|
||||
* | format-string slot (printfln →
|
||||
* | lprintfln(global) → stdprintfln
|
||||
* | → fmt.fprintf + io.write("\n")).
|
||||
* | Pins the {n}-placeholder dispatch
|
||||
* | through vformatfield reaches the
|
||||
* | default sink.
|
||||
* lprintln_v_custom_sink | memio.fixed_vstream-backed vstdlogger;
|
||||
* | log.new_v wires the vtable + sink,
|
||||
* | log.lprintln_v dispatches "hi" + 7i64
|
||||
* | through stdprintln_v. Asserts the
|
||||
* lprintln_v_custom_sink | memio.fixed-backed stdlogger;
|
||||
* | log.new wires the vtable + sink,
|
||||
* | log.lprintln dispatches "hi" + 7i64
|
||||
* | through stdprintln. Asserts the
|
||||
* | exact bytes ("hi 7\n") land in the
|
||||
* | caller's buffer (fixed_vstream borrows
|
||||
* | caller's buffer (fixed borrows
|
||||
* | the slice, so the bytes are visible
|
||||
* | back through buf[]).
|
||||
* branched_lprintln_v | branched callee per #105: two distinct
|
||||
* | vstdlogger sinks runtime-selected via
|
||||
* | *vlogger pointer. lprintln_v dispatches
|
||||
* branched_lprintln | branched callee per #105: two distinct
|
||||
* | stdlogger sinks runtime-selected via
|
||||
* | *logger pointer. lprintln dispatches
|
||||
* | through the picked sink only; the
|
||||
* | other stays empty. Catches a constant-
|
||||
* | fold mistake in the fn-ptr dispatch
|
||||
* | (mirror of 778's branched_bufio_vstream).
|
||||
* | (mirror of 778's branched_bufio_wrap).
|
||||
*
|
||||
* SIBLINGS (filed inline, NOT fixed here — fold-e5 is purely
|
||||
* additive over fold-e1/e2/e3/e4's frozen io.* + memio.* + fmt.* +
|
||||
* bufio.* surface plus the minimal `export` bump on fmt.vfprint /
|
||||
* fmt.vfprintf):
|
||||
* DEFERRALS / RELATED (NOT addressed by this row):
|
||||
*
|
||||
* - eFinal (#50): atomic flip + delete OLD log surface + drop the
|
||||
* `_v` suffix wholesale.
|
||||
* - #206 (bare &fn → (*alias|void)): 2 cast sites at ensureinit
|
||||
* in lib/log/log.ww; the explicit casts are KEPT (ken/#214 —
|
||||
* cast-drop is a separate deferred payoff gated on #214).
|
||||
*
|
||||
* - #206 (bare &fn → (*alias|void)): 2 cast sites at ensureinit_v
|
||||
* in lib/log/vstream.ww; drop out wholesale on close.
|
||||
*
|
||||
* - #173 (TRY-on-tagged-return both-stages broken): stderrwrite_v
|
||||
* - #173 (TRY-on-tagged-return both-stages broken): stderrwrite
|
||||
* constructs nomem and widens to io.error explicitly rather than
|
||||
* using `os.trywrite(...)?`; same shape memio.vstream.ww +
|
||||
* fmt.vstream.ww + bufio.vstream.ww adopt.
|
||||
* using `os.trywrite(...)?`; same shape memio.ww + fmt.ww +
|
||||
* bufio.ww adopt.
|
||||
*
|
||||
* - #209 (wwstage formattable match-arm bail): every row is
|
||||
* STAGE_CS-only; byte-id deferred until #209 lands.
|
||||
*
|
||||
* BOOTSTRAP-EMBED CHECK: log is NOT embedded in any selfhost
|
||||
* combined.ww (grep `package log\|import log` returned empty pre-
|
||||
* impl). Adding lib/log/vstream.ww + the two `export` bumps on
|
||||
* lib/fmt/vstream.ww does NOT require a Makefile regen (#110); only
|
||||
* lib/log/ + lib/fmt/ test paths see the new module surface. 990-997
|
||||
* byte-id gates stay green by virtue of log being test-only and the
|
||||
* fmt vstream.ww changes being non-embedded.
|
||||
* combined.ww (grep `package log\|import log` returns empty), so the
|
||||
* fold-eFinal log collapse does not require a Makefile regen (#110);
|
||||
* only lib/log/ + lib/fmt/ test paths see the surface. 990-997
|
||||
* byte-id gates stay green by virtue of log being test-only.
|
||||
*
|
||||
* GATE POLARITY: must stay GREEN. A red here means vstream.ww
|
||||
* GATE POLARITY: must stay GREEN. A red here means lib/log
|
||||
* regressed, the module-static stderrsink_ctx_g zero-init or chained
|
||||
* vt-field assigns regressed, the intrusive vstream→*stderrsink_ctx
|
||||
* cast miscomputed offsets, ensureinit_v's one-shot guard broke, or
|
||||
* the fn-ptr dispatch through vlogger.println_v / printfln_v stopped
|
||||
* vt-field assigns regressed, the intrusive io.stream→*stderrsink_ctx
|
||||
* cast miscomputed offsets, ensureinit's one-shot guard broke, or
|
||||
* the fn-ptr dispatch through logger.println / printfln stopped
|
||||
* resolving.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
@@ -128,7 +118,7 @@ static const struct row rows[] = {
|
||||
"import os;\n"
|
||||
"import log;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" log.println_v(\"hello\");\n"
|
||||
" log.println(\"hello\");\n"
|
||||
" return 42;\n"
|
||||
"};\n",
|
||||
42,
|
||||
@@ -138,7 +128,7 @@ static const struct row rows[] = {
|
||||
"import os;\n"
|
||||
"import log;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" log.printfln_v(\"v={}\", 7i64);\n"
|
||||
" log.printfln(\"v={}\", 7i64);\n"
|
||||
" return 43;\n"
|
||||
"};\n",
|
||||
43,
|
||||
@@ -151,10 +141,10 @@ static const struct row rows[] = {
|
||||
"import io;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let buf: [16]u8;\n"
|
||||
" let st: memio.stream = memio.fixed_vstream(buf[0:16]);\n"
|
||||
" let vs: io.vstream = &st.vt;\n"
|
||||
" let sl: log.vstdlogger = log.new_v(vs);\n"
|
||||
" log.lprintln_v(&sl.logger, \"hi\", 7i64);\n"
|
||||
" let st: memio.stream = memio.fixed(buf[0:16]);\n"
|
||||
" let vs: io.stream = &st.vt;\n"
|
||||
" let sl: log.stdlogger = log.new(vs);\n"
|
||||
" log.lprintln(&sl.logger, \"hi\", 7i64);\n"
|
||||
" if (buf[0] != 104u8) { return 91; };\n"
|
||||
" if (buf[1] != 105u8) { return 92; };\n"
|
||||
" if (buf[2] != 32u8) { return 93; };\n"
|
||||
@@ -164,7 +154,7 @@ static const struct row rows[] = {
|
||||
"};\n",
|
||||
44,
|
||||
STAGE_CS, 0 },
|
||||
{ "branched_lprintln_v",
|
||||
{ "branched_lprintln",
|
||||
"package main;\n"
|
||||
"import os;\n"
|
||||
"import log;\n"
|
||||
@@ -173,16 +163,16 @@ static const struct row rows[] = {
|
||||
"export fn main() i32 = {\n"
|
||||
" let bufA: [16]u8;\n"
|
||||
" let bufB: [16]u8;\n"
|
||||
" let stA: memio.stream = memio.fixed_vstream(bufA[0:16]);\n"
|
||||
" let vsA: io.vstream = &stA.vt;\n"
|
||||
" let stB: memio.stream = memio.fixed_vstream(bufB[0:16]);\n"
|
||||
" let vsB: io.vstream = &stB.vt;\n"
|
||||
" let slA: log.vstdlogger = log.new_v(vsA);\n"
|
||||
" let slB: log.vstdlogger = log.new_v(vsB);\n"
|
||||
" let stA: memio.stream = memio.fixed(bufA[0:16]);\n"
|
||||
" let vsA: io.stream = &stA.vt;\n"
|
||||
" let stB: memio.stream = memio.fixed(bufB[0:16]);\n"
|
||||
" let vsB: io.stream = &stB.vt;\n"
|
||||
" let slA: log.stdlogger = log.new(vsA);\n"
|
||||
" let slB: log.stdlogger = log.new(vsB);\n"
|
||||
" let sel: i32 = 1;\n"
|
||||
" let chosen: *log.vlogger = &slA.logger;\n"
|
||||
" let chosen: *log.logger = &slA.logger;\n"
|
||||
" if (sel == 0) { chosen = &slB.logger; };\n"
|
||||
" log.lprintln_v(chosen, \"pick\");\n"
|
||||
" log.lprintln(chosen, \"pick\");\n"
|
||||
" if (bufA[0] != 112u8) { return 91; };\n"
|
||||
" if (bufA[1] != 105u8) { return 92; };\n"
|
||||
" if (bufA[2] != 99u8) { return 93; };\n"
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
/*
|
||||
* 780_fmt_vstream_mods_run — project #94 fold-e6 sentinel. Pins the
|
||||
* V-side modifier-formatting machinery (vrawleni64 / vrawlenstr /
|
||||
* vrawlenf64 / vrawlen / vformatraw / vformatone) in lib/fmt/
|
||||
* vstream.ww. Before fold-e6, the V printf path parsed mods via
|
||||
* scanmods but dropped them after parse: vformatfield routed straight
|
||||
* to vwriteone (no width / alignment / pad / sign / base / prec
|
||||
* honoured). fold-e6 widens vformatfield to take `*mods` and routes
|
||||
* through vformatone — same dispatch shape as OLD formatfield at
|
||||
* fmt.ww:648. Coexists with the OLD modifier path at fmt.ww:443-641
|
||||
* (fmt.ww UNCHANGED this fold); fold-eFinal (task #50) collapses both
|
||||
* surfaces.
|
||||
* 780_fmt_vstream_mods_run — project #94 fold-eFinal sentinel. Pins the
|
||||
* modifier-formatting machinery (rawleni64 / rawlenstr / rawlenf64 /
|
||||
* rawlen / formatraw / formatone) in lib/fmt/fmt.ww: formatfield takes
|
||||
* `*mods` and routes through formatone so the {:mods} placeholder
|
||||
* honours width / alignment / pad / sign / base / prec. The fold-eFinal
|
||||
* FLIP collapsed the dual fmt surface into this one (the former `v*`
|
||||
* modifier helpers merged into the bare names).
|
||||
*
|
||||
* Each row imports fmt + os + io, opens a per-process /tmp output
|
||||
* file, calls fdprintf_v with a {:mods} format string + one arg, and
|
||||
* file, calls fdprintf with a {:mods} format string + one arg, and
|
||||
* asserts the exact byte content read back.
|
||||
*
|
||||
* Cstage-only per row — pre-existing wwstage bug #209 (sibling of
|
||||
@@ -48,28 +44,22 @@
|
||||
* (wwstage checker ordering-sensitivity on os.tryread/trywrite/
|
||||
* tryopen's bare `return r;` over a tagged return type).
|
||||
*
|
||||
* SIBLINGS (filed inline, NOT fixed here — fold-e6 is purely additive
|
||||
* over fold-e1/e2/e3/e4/e5's frozen io.* + memio.* + fmt.* + bufio.*
|
||||
* + log.* surface):
|
||||
* DEFERRALS / RELATED (NOT addressed by these rows):
|
||||
*
|
||||
* - #209 (wwstage formattable match-arm bail): blocks STAGE_WW
|
||||
* here, same as 777_fmt_vstream_run. Cstage-only until close.
|
||||
* - eFinal (#50): collapses V + OLD surfaces; the `v*` names rename
|
||||
* off and the OLD rawlen* / formatraw / formatone delete. The v*
|
||||
* duplication of the rawlen-family today is transient — eFinal
|
||||
* dedupes.
|
||||
*
|
||||
* BOOTSTRAP-EMBED CHECK: fmt is NOT embedded in any selfhost
|
||||
* combined.ww (grep confirmed pre-impl). Adding modifier formatting
|
||||
* to lib/fmt/vstream.ww does NOT require a Makefile regen (#110);
|
||||
* only test paths see the new machinery. 990-997 byte-id gates stay
|
||||
* green by virtue of fmt being test-only on the bootstrap surface.
|
||||
* combined.ww (grep confirmed), so the fold-eFinal fmt collapse does
|
||||
* NOT require a Makefile regen (#110); only test paths see the
|
||||
* machinery. 990-997 byte-id gates stay green by virtue of fmt being
|
||||
* test-only on the bootstrap surface.
|
||||
*
|
||||
* GATE POLARITY: must stay GREEN. A red here means vformatone /
|
||||
* vformatraw / vrawlen miscomputed bytes, vformatfield regressed its
|
||||
* *mods route, scanmods/modsinit (shared from fmt.ww) drifted, or
|
||||
* the strconv.u64tos base path miscompiled under fdprintf_v's
|
||||
* intrusive vstream→*fd_ctx dispatch.
|
||||
* GATE POLARITY: must stay GREEN. A red here means formatone /
|
||||
* formatraw / rawlen miscomputed bytes, formatfield regressed its
|
||||
* *mods route, scanmods/modsinit drifted, or the strconv.u64tos base
|
||||
* path miscompiled under fdprintf's intrusive io.stream→*fd_ctx
|
||||
* dispatch.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -107,7 +97,7 @@ static const struct row rows[] = {
|
||||
" let path: str = \"/tmp/wwfmtv_780_a\";\n"
|
||||
" let fd: i32 = os.open(path, os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC, 0o644: i32);\n"
|
||||
" if (fd < 0) { return 90; };\n"
|
||||
" let wr = fmt.fdprintf_v(fd, \"{:5}\", 42i64);\n"
|
||||
" let wr = fmt.fdprintf(fd, \"{:5}\", 42i64);\n"
|
||||
" let nw: size = 0;\n"
|
||||
" match (wr) {\n"
|
||||
" case let n: size => { nw = n; };\n"
|
||||
@@ -135,7 +125,7 @@ static const struct row rows[] = {
|
||||
" let path: str = \"/tmp/wwfmtv_780_b\";\n"
|
||||
" let fd: i32 = os.open(path, os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC, 0o644: i32);\n"
|
||||
" if (fd < 0) { return 90; };\n"
|
||||
" let wr = fmt.fdprintf_v(fd, \"{:.3}\", \"hello\");\n"
|
||||
" let wr = fmt.fdprintf(fd, \"{:.3}\", \"hello\");\n"
|
||||
" let nw: size = 0;\n"
|
||||
" match (wr) {\n"
|
||||
" case let n: size => { nw = n; };\n"
|
||||
@@ -163,7 +153,7 @@ static const struct row rows[] = {
|
||||
" let path: str = \"/tmp/wwfmtv_780_c\";\n"
|
||||
" let fd: i32 = os.open(path, os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC, 0o644: i32);\n"
|
||||
" if (fd < 0) { return 90; };\n"
|
||||
" let wr = fmt.fdprintf_v(fd, \"{:x}\", 255i64);\n"
|
||||
" let wr = fmt.fdprintf(fd, \"{:x}\", 255i64);\n"
|
||||
" let nw: size = 0;\n"
|
||||
" match (wr) {\n"
|
||||
" case let n: size => { nw = n; };\n"
|
||||
@@ -191,7 +181,7 @@ static const struct row rows[] = {
|
||||
" let path: str = \"/tmp/wwfmtv_780_d\";\n"
|
||||
" let fd: i32 = os.open(path, os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC, 0o644: i32);\n"
|
||||
" if (fd < 0) { return 90; };\n"
|
||||
" let wr = fmt.fdprintf_v(fd, \"{:+}\", 7i64);\n"
|
||||
" let wr = fmt.fdprintf(fd, \"{:+}\", 7i64);\n"
|
||||
" let nw: size = 0;\n"
|
||||
" match (wr) {\n"
|
||||
" case let n: size => { nw = n; };\n"
|
||||
@@ -219,7 +209,7 @@ static const struct row rows[] = {
|
||||
" let path: str = \"/tmp/wwfmtv_780_e\";\n"
|
||||
" let fd: i32 = os.open(path, os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC, 0o644: i32);\n"
|
||||
" if (fd < 0) { return 90; };\n"
|
||||
" let wr = fmt.fdprintf_v(fd, \"{:_05}\", 42i64);\n"
|
||||
" let wr = fmt.fdprintf(fd, \"{:_05}\", 42i64);\n"
|
||||
" let nw: size = 0;\n"
|
||||
" match (wr) {\n"
|
||||
" case let n: size => { nw = n; };\n"
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
/*
|
||||
* 781_fmt_vstream_compositions_run — project #94 fold-e7 sentinel.
|
||||
* Pins the V-side composition wrappers in lib/fmt/vstream.ww —
|
||||
* vfprintln / vfprintfln / vbsprintf / vasprintf — plus the two
|
||||
* memio enablers fold-e7 bundles alongside (memio.fixed_string /
|
||||
* memio.dynamic_string in lib/memio/vstream.ww). Coexists with the
|
||||
* OLD fmt.fprintln / fprintfln / bsprintf / asprintf surface in
|
||||
* fmt.ww:240,740,839,873 (fmt.ww UNCHANGED this fold; eFinal
|
||||
* task #50 collapses both surfaces and renames the `v` suffix off).
|
||||
* 781_fmt_vstream_compositions_run — project #94 fold-eFinal sentinel.
|
||||
* Pins the composition wrappers in lib/fmt/fmt.ww —
|
||||
* fprintln / fprintfln / bsprintf / asprintf — plus the memio
|
||||
* accessor (memio.string in lib/memio/memio.ww) they read back
|
||||
* through. The fold-eFinal FLIP collapsed the dual fmt + memio
|
||||
* surfaces into one (the former `v`-suffixed wrappers and the
|
||||
* fixed_string / dynamic_string accessors merged into the bare names).
|
||||
*
|
||||
* The two memio helpers are direct enablers (vbsprintf reads its
|
||||
* written prefix via fixed_string; vasprintf reads its grown buffer
|
||||
* via dynamic_string), drew-approved per
|
||||
* feedback_refactor_routing_same_class_drops (prereqs, not churn).
|
||||
* memio.string is the direct enabler: bsprintf reads its written
|
||||
* prefix via memio.string; asprintf reads its grown buffer via the
|
||||
* same accessor.
|
||||
*
|
||||
* Each row imports fmt + os + io, drives one of the four V wrappers,
|
||||
* and asserts the exact bytes / size emitted. Per-row unique exit
|
||||
@@ -19,40 +17,39 @@
|
||||
*
|
||||
* row | what it pins
|
||||
* --------------------------+--------------------------------------
|
||||
* fdprintln_v_run_basic | fdprintln_v wired vstream sink
|
||||
* | (inline vfprint + vputbytes "\n").
|
||||
* fdprintln_v_run_basic | fdprintln wired vstream sink
|
||||
* | (inline fprint + vputbytes "\n").
|
||||
* | One i64 arg + str arg + bool arg;
|
||||
* | asserts space-separator plus trailing
|
||||
* | newline byte content.
|
||||
* fdprintfln_v_run_fmt | fdprintfln_v wired vstream sink with
|
||||
* fdprintfln_v_run_fmt | fdprintfln wired vstream sink with
|
||||
* | a {n}-placeholder format string.
|
||||
* | Asserts placeholder substitution +
|
||||
* | trailing newline shape (mirror of
|
||||
* | fmt.fprintfln, fmt.ww:740).
|
||||
* vfprintln_basic | fmt.vfprintln dispatched DIRECTLY
|
||||
* | through memio.fixed_vstream + read
|
||||
* | back via memio.fixed_string. Pins
|
||||
* | the new V-side composition symbol
|
||||
* | itself (fdprintln_v open-codes the
|
||||
* | same vfprint+vputbytes chain rather
|
||||
* | than calling vfprintln, so without
|
||||
* | this row the new symbol ships
|
||||
* | uncovered).
|
||||
* vfprintfln_basic | fmt.vfprintfln direct dispatch via
|
||||
* | memio.fixed_vstream + fixed_string,
|
||||
* fprintln_basic | fmt.fprintln dispatched DIRECTLY
|
||||
* | through memio.fixed + read
|
||||
* | back via memio.string. Pins
|
||||
* | the composition symbol itself
|
||||
* | (fdprintln open-codes the same
|
||||
* | fprint+putbytes chain rather than
|
||||
* | calling fprintln, so without this
|
||||
* | row the symbol ships uncovered).
|
||||
* fprintfln_basic | fmt.fprintfln direct dispatch via
|
||||
* | memio.fixed + memio.string,
|
||||
* | same direct-symbol rationale as
|
||||
* | vfprintln_basic.
|
||||
* bsprintf_v_basic | vbsprintf into a caller-supplied
|
||||
* | fprintln_basic.
|
||||
* bsprintf_v_basic | bsprintf into a caller-supplied
|
||||
* | [16]u8 buffer through
|
||||
* | memio.fixed_vstream;
|
||||
* | memio.fixed_string returns the prefix
|
||||
* | memio.fixed;
|
||||
* | memio.string returns the prefix
|
||||
* | view. Asserts both the bytes in the
|
||||
* | caller's buffer AND the returned str
|
||||
* | view (ptr == buf + 0, len == 5).
|
||||
* asprintf_v_basic | vasprintf into a heap-grown str
|
||||
* | through memio.dynamic_vstream;
|
||||
* | memio.dynamic_string + shrink-copy +
|
||||
* | io.st_close. Asserts the owned str
|
||||
* asprintf_v_basic | asprintf into a heap-grown str
|
||||
* | through memio.dynamic;
|
||||
* | memio.string + shrink-copy +
|
||||
* | io.close. Asserts the owned str
|
||||
* | bytes, then frees via os.free.
|
||||
*
|
||||
* Cstage-only per row (no STAGE_WW, no byte_id) — pre-existing
|
||||
@@ -68,32 +65,26 @@
|
||||
* document (wwstage checker ordering-sensitivity on os.tryread /
|
||||
* trywrite / tryopen's bare `return r;` over a tagged return type).
|
||||
*
|
||||
* SIBLINGS (filed inline, NOT fixed here — fold-e7 is purely
|
||||
* additive over fold-e1/e2/e3/e4/e5/e6's frozen io.* + memio.* +
|
||||
* fmt.* + bufio.* + log.* surface):
|
||||
* DEFERRALS / RELATED (NOT addressed by these rows):
|
||||
*
|
||||
* - #209 (wwstage formattable match-arm bail): blocks STAGE_WW
|
||||
* here, same as 777/780. Cstage-only until close.
|
||||
* - #173 (TRY-on-tagged-return both-stages broken): vbsprintf
|
||||
* - #173 (TRY-on-tagged-return both-stages broken): bsprintf
|
||||
* widens nomem into io.error inline rather than `memio.
|
||||
* fixed_vstream(buf)?` for the same reason memio.vstream.ww
|
||||
* does at line 87-99.
|
||||
* - eFinal (#50): collapses V + OLD surfaces; the `v` names
|
||||
* rename off and the OLD fprintln / fprintfln / bsprintf /
|
||||
* asprintf delete.
|
||||
* fixed(buf)?` for the same reason memio.ww does.
|
||||
*
|
||||
* BOOTSTRAP-EMBED CHECK: fmt is NOT embedded in any selfhost
|
||||
* combined.ww (grep verified pre-impl). memio.vstream.ww IS embedded
|
||||
* in w6c + wwdump combined.ww — the fold-e7 memio helpers ride along
|
||||
* via the regen committed in this same commit (per #110 SSoT). 990/
|
||||
* 994/995 byte-id gates + combined_ww_fresh stay green by virtue of
|
||||
* the regen.
|
||||
* combined.ww (grep verified). memio IS embedded in w6c + wwdump
|
||||
* combined.ww — the fold-eFinal memio collapse rides along via the
|
||||
* regen committed in this same commit (per #110 SSoT). 990/994/995
|
||||
* byte-id gates + combined_ww_fresh stay green by virtue of the
|
||||
* regen.
|
||||
*
|
||||
* GATE POLARITY: must stay GREEN. A red here means vfprintln /
|
||||
* vfprintfln dropped the newline byte, vbsprintf returned a stale
|
||||
* memio.fixed_string view (intrusive cast broke), vasprintf failed
|
||||
* to shrink-copy / close cleanly, or the underlying vfprint /
|
||||
* vfprintf primitives regressed.
|
||||
* GATE POLARITY: must stay GREEN. A red here means fprintln /
|
||||
* fprintfln dropped the newline byte, bsprintf returned a stale
|
||||
* memio.string view (intrusive cast broke), asprintf failed
|
||||
* to shrink-copy / close cleanly, or the underlying fprint /
|
||||
* fprintf primitives regressed.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -131,7 +122,7 @@ static const struct row rows[] = {
|
||||
" let path: str = \"/tmp/wwfmtv_781_a\";\n"
|
||||
" let fd: i32 = os.open(path, os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC, 0o644: i32);\n"
|
||||
" if (fd < 0) { return 90; };\n"
|
||||
" let wr = fmt.fdprintln_v(fd, 1i64, \"hi\", true);\n"
|
||||
" let wr = fmt.fdprintln(fd, 1i64, \"hi\", true);\n"
|
||||
" let nw: size = 0;\n"
|
||||
" match (wr) {\n"
|
||||
" case let n: size => { nw = n; };\n"
|
||||
@@ -163,7 +154,7 @@ static const struct row rows[] = {
|
||||
" let path: str = \"/tmp/wwfmtv_781_b\";\n"
|
||||
" let fd: i32 = os.open(path, os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC, 0o644: i32);\n"
|
||||
" if (fd < 0) { return 90; };\n"
|
||||
" let wr = fmt.fdprintfln_v(fd, \"x={}\", 7i64);\n"
|
||||
" let wr = fmt.fdprintfln(fd, \"x={}\", 7i64);\n"
|
||||
" let nw: size = 0;\n"
|
||||
" match (wr) {\n"
|
||||
" case let n: size => { nw = n; };\n"
|
||||
@@ -182,7 +173,7 @@ static const struct row rows[] = {
|
||||
"};\n",
|
||||
61,
|
||||
STAGE_CS, 0 },
|
||||
{ "vfprintln_basic",
|
||||
{ "fprintln_basic",
|
||||
"package main;\n"
|
||||
"import os;\n"
|
||||
"import fmt;\n"
|
||||
@@ -190,16 +181,16 @@ static const struct row rows[] = {
|
||||
"import memio;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let buf: [16]u8;\n"
|
||||
" let st: memio.stream = memio.fixed_vstream(buf[0:16]);\n"
|
||||
" let vs: io.vstream = &st.vt;\n"
|
||||
" let wr = fmt.vfprintln(vs, 1i64, \"hi\", true);\n"
|
||||
" let st: memio.stream = memio.fixed(buf[0:16]);\n"
|
||||
" let vs: io.stream = &st.vt;\n"
|
||||
" let wr = fmt.fprintln(vs, 1i64, \"hi\", true);\n"
|
||||
" let nw: size = 0;\n"
|
||||
" match (wr) {\n"
|
||||
" case let n: size => { nw = n; };\n"
|
||||
" case io.error => { return 91; };\n"
|
||||
" };\n"
|
||||
" if (nw != 10: size) { return 92; };\n"
|
||||
" let view: str = memio.string_v(&st);\n"
|
||||
" let view: str = memio.string(&st);\n"
|
||||
" if (view.len != 10) { return 93; };\n"
|
||||
" if (view[0] != 49u8) { return 94; };\n"
|
||||
" if (view[1] != 32u8) { return 95; };\n"
|
||||
@@ -209,7 +200,7 @@ static const struct row rows[] = {
|
||||
"};\n",
|
||||
64,
|
||||
STAGE_CS, 0 },
|
||||
{ "vfprintfln_basic",
|
||||
{ "fprintfln_basic",
|
||||
"package main;\n"
|
||||
"import os;\n"
|
||||
"import fmt;\n"
|
||||
@@ -217,16 +208,16 @@ static const struct row rows[] = {
|
||||
"import memio;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let buf: [16]u8;\n"
|
||||
" let st: memio.stream = memio.fixed_vstream(buf[0:16]);\n"
|
||||
" let vs: io.vstream = &st.vt;\n"
|
||||
" let wr = fmt.vfprintfln(vs, \"x={}\", 7i64);\n"
|
||||
" let st: memio.stream = memio.fixed(buf[0:16]);\n"
|
||||
" let vs: io.stream = &st.vt;\n"
|
||||
" let wr = fmt.fprintfln(vs, \"x={}\", 7i64);\n"
|
||||
" let nw: size = 0;\n"
|
||||
" match (wr) {\n"
|
||||
" case let n: size => { nw = n; };\n"
|
||||
" case io.error => { return 91; };\n"
|
||||
" };\n"
|
||||
" if (nw != 4: size) { return 92; };\n"
|
||||
" let view: str = memio.string_v(&st);\n"
|
||||
" let view: str = memio.string(&st);\n"
|
||||
" if (view.len != 4) { return 93; };\n"
|
||||
" if (view[0] != 120u8 || view[1] != 61u8) { return 94; };\n"
|
||||
" if (view[2] != 55u8 || view[3] != 10u8) { return 95; };\n"
|
||||
@@ -241,7 +232,7 @@ static const struct row rows[] = {
|
||||
"import io;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let buf: [16]u8;\n"
|
||||
" let r = fmt.vbsprintf(buf[0:16], \"v={}\", 42i64);\n"
|
||||
" let r = fmt.bsprintf(buf[0:16], \"v={}\", 42i64);\n"
|
||||
" let view: str = \"\";\n"
|
||||
" match (r) {\n"
|
||||
" case let s: str => { view = s; };\n"
|
||||
@@ -261,7 +252,7 @@ static const struct row rows[] = {
|
||||
"import fmt;\n"
|
||||
"import io;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let view: str = fmt.vasprintf(\"hello={}\", 9i64);\n"
|
||||
" let view: str = fmt.asprintf(\"hello={}\", 9i64);\n"
|
||||
" if (view.len != 7) { return 90; };\n"
|
||||
" if (view[0] != 104u8 || view[1] != 101u8 || view[2] != 108u8) { return 91; };\n"
|
||||
" if (view[3] != 108u8 || view[4] != 111u8) { return 92; };\n"
|
||||
|
||||
@@ -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 +
|
||||
|
||||
Reference in New Issue
Block a user