Files
ww/lib/io/stream.ww
Hojun-Cho f3750ae3ce lib/io: empty() stream; wcc/cgen: #129 sretretsize + #130 global tagged-field store
io.empty (discard+EOF stream, ref/hare/io/empty.ha:4-17) — needed by getopt's
two-pass printusage width measurement. Diverges from Hare's `const empty: *stream`:
a `let _empty_vt` + `fn empty()` that wires the fn-ptr slots per call, because
const-init of a vtable struct with fn-ptr fields is blocked (#118, ruled accept).

Co-discovered while making empty() byte-identical across stages: three
wwstage-only cgen fixes (cstage was already correct; wwstage aligned down):
- #129 sretretsize: consult the same-module pointer-alias before structlookup's
  any-module struct fallback (io.stream = *vtable was mis-sized as memio's 56B
  struct -> spurious sret save).
- #129 callsretsize: swap curmod to the callee's module before sret-size
  classification (cross-module callee context).
- #130 cgassign global-struct tagged-union field store: add the missing arm
  (was a 1-word store) mirroring cstage cgen.c:4893-4912.

The three are inseparable from io.empty here — splitting them out leaves a
divergent-asm intermediate (993/995 red), so they ride one commit per the
one-class gate-repair carve-out (#133-expanded precedent). Regenerates the
embedded combined.ww; 989_lib_byteid pins bufio + fmt graduated to M_ID.
(cgenexpr.ww fix-3 inline comment cites the #129 cluster; narrow to #130 on
next touch to avoid a regen for a comment.)
2026-06-07 06:19:13 +09:00

223 lines
7.4 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// stream — Hare-shaped vtable surface. Project #94 fold-eFinal.
//
// The single io stream surface (the fold-eFinal collapse retired the
// pre-vtable `stream` struct + `closed` tag). Exports:
//
// vtable a struct of optional fn-pointer slots — reader/writer/
// closer per ref/hare/io/stream.ha:36-42. Hare spells the
// nullable as `nullable *T`; ww parser rejects that
// 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).
// 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. 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.
// empty the discard+EOF stream (ref/hare/io/empty.ha:13). Lives
// here rather than io.ww so that 900_stdlib can compile
// io.ww standalone (io.ww has no cross-file type refs).
//
// 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;
// Q2 layering (ratified): the file-arm of the handle dispatchers routes
// to os.{read,write,close,lseek}; ww `os` plays Hare's `sys` role, so
// `lib/io import os` is the correct direction (os is the import floor).
import os;
// ref/hare/io/stream.ha:36-42. Nullable spelling per #192 (ww parser
// rejects `nullable *T`); each slot is `(*T | void)`. The `seeker` slot
// (4th) lands with io fold-2 (#5); `copier` stays deferred.
export type vtable = struct {
reader: (*reader | void),
writer: (*writer | void),
closer: (*closer | void),
seeker: (*seeker | void),
};
// ref/hare/io/stream.ha:33.
export type stream = *vtable;
// ref/hare/io/stream.ha:44-51. Private stream-arm body behind the
// handle-typed [[read]] (#5). 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)`).
fn st_read(s: stream, buf: []u8) (size | eof | error) = {
match (s.reader) {
case void => {
let u: errors.unsupported;
let e: error = u;
return e;
};
case let r: *reader => return (*r)(s, buf);
};
};
// ref/hare/io/stream.ha:53-60.
fn st_write(s: stream, buf: []u8) (size | error) = {
match (s.writer) {
case void => {
let u: errors.unsupported;
let e: error = u;
return e;
};
case let w: *writer => return (*w)(s, buf);
};
};
// 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 for the void-arm fall-through shape. The surface
// (void | error) matches.
fn st_close(s: stream) (void | error) = {
match (s.closer) {
case void => return void;
case let c: *closer => return (*c)(s);
};
};
// ref/hare/io/stream.ha:70-77. Clone of st_read's slot-dispatch shape
// over the new `seeker` vtable slot.
fn st_seek(s: stream, off: off, w: whence) (off | error) = {
match (s.seeker) {
case void => {
let u: errors.unsupported;
let e: error = u;
return e;
};
case let sk: *seeker => return (*sk)(s, off, w);
};
};
// ref/hare/io/handle.ha:16-23. The handle-typed public read: the
// file-arm routes to os.read (Q2 layering), the stream-arm delegates to
// the unchanged st_read vtable dispatch above.
export fn read(h: handle, buf: []u8) (size | eof | error) = {
match (h) {
case let fd: file => {
let r: i64 = os.read(fd, buf.ptr, buf.len: u64);
if (r < 0) {
// #199b: faithful errno→io.error needs the ...errors.error spread; see lib/io/types.ww:40
let u: errors.unsupported;
let e: error = u;
return e;
};
if (r == 0) {
let z: eof;
return z;
};
return r: size;
};
case let s: stream => return st_read(s, buf);
};
};
// ref/hare/io/handle.ha:27-36.
export fn write(h: handle, buf: []u8) (size | error) = {
match (h) {
case let fd: file => {
let r: i64 = os.write(fd, buf.ptr, buf.len: u64);
if (r < 0) {
// #199b: faithful errno→io.error needs the ...errors.error spread; see lib/io/types.ww:40
let u: errors.unsupported;
let e: error = u;
return e;
};
return r: size;
};
case let s: stream => return st_write(s, buf);
};
};
// ref/hare/io/handle.ha:44-51. Hare propagates each arm's error via `?`
// then falls through; ww returns directly (matching st_close's shape).
export fn close(h: handle) (void | error) = {
match (h) {
case let fd: file => {
let r: i32 = os.close(fd);
if (r < 0) {
// #199b: faithful errno→io.error needs the ...errors.error spread; see lib/io/types.ww:40
let u: errors.unsupported;
let e: error = u;
return e;
};
return void;
};
case let s: stream => return st_close(s);
};
};
// ref/hare/io/handle.ha:55-62. The io.whence → os.whence cast bridges
// the two enum copies (same SET/CUR/END values); os.lseek owns the
// syscall.
export fn seek(h: handle, off: off, w: whence) (off | error) = {
match (h) {
case let fd: file => {
let r: i64 = os.lseek(fd, off, w: os.whence);
if (r < 0) {
// #199b: faithful errno→io.error needs the ...errors.error spread; see lib/io/types.ww:40
let u: errors.unsupported;
let e: error = u;
return e;
};
return r: off;
};
case let s: stream => return st_seek(s, off, w);
};
};
// ref/hare/io/handle.ha:65-67.
export fn tell(h: handle) (off | error) = {
return seek(h, 0, whence.CUR);
};
// ---- empty stream -------------------------------------------------------
//
// ref/hare/io/empty.ha:4-17.
//
// Hare uses `const _empty_vt: vtable = { ... }` + `const empty: *stream`.
// ww can't const-init a vtable struct with fn-ptr fields (#118), so the
// vtable is a module-level `let` and [[empty]] is a function that wires
// the fn-ptr slots on every call and returns the stream pointer.
// Single-assignment on the same words: idempotent under re-entry.
// Lives in stream.ww (not io.ww) so that 900_stdlib can compile io.ww
// standalone without referencing the cross-file vtable/reader/writer types.
fn _empty_read(s: stream, buf: []u8) (size | eof | error) = {
let e: eof;
return e;
};
fn _empty_write(s: stream, buf: []u8) (size | error) = {
return buf.len: size;
};
let _empty_vt: vtable;
// empty — a stream that discards all writes (returning their size) and
// returns EOF on every read. Mirrors ref/hare/io/empty.ha:13.
// #118: const vtable init with fn-ptr fields is unwired (emit_struct_data
// needs a two-pass reloc extension, node_fnptr_sym reusable). Using a
// mutable let + per-call wiring until #118 lands.
export fn empty() stream = {
_empty_vt.reader = (&_empty_read): *reader;
_empty_vt.writer = (&_empty_write): *writer;
return &_empty_vt;
};