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:
@@ -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);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user