Files
ww/lib/io/stream.ww
Hojun-Cho 90dc6369c9 comments: drop retired lint markers, re-cite migrated carriers
peel-ok/sizelint-ok/primsize-ok annotations lose their tools; sites
keep the WHY in plain words. Citations of retired carriers move to
their fixture or @test successors (949_errtype_compare -> r949_*,
900_stdlib -> library owners).
2026-08-07 23:21:04 +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 io.ww remains a standalone
// frontend input with no cross-file type references.
//
// 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 io.ww stays 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;
};