lib/io: port ref/hare/io/stream.ha vtable surface (#94 fold-e1)
Additive: keeps lib/io/io.ww's pre-vtable `stream` struct + `read`/`write`/`close` wrappers (fold-e2-eN migrates the legacy surface to vtable-backed implementations and retires it). lib/io/stream.ww — vtable struct (reader/writer/closer slots, spelled `(*T | void)` per #192 — ww parser rejects `nullable *T`), vstream = *vtable, and the st_read/st_write/st_close dispatchers per ref/hare/io/stream.ha:33-68. Void-arm `return e;` chains two direct widens: concrete `errors.unsupported` → `error` (#199 α) then `error` → (size|eof|error) (#205 NAMED-variant nominal at tagged→tagged subset). Hare's `?`-propagating st_close collapses to a direct `return (*c)(s);` because #173 is still open; the surface stays Hare-shaped. lib/io/types.ww — retarget reader/writer/closer fn-aliases from *stream to vstream. Extend `error` union to include `errors.unsupported` explicitly (no spread — per ken's #204-block the wrapper-vs-flatten layout asymmetry would mis-widen; the deferred fix is filed as #199b layout-extension). test/wcc/775_io_vtable_run.c — 7-row sentinel: reader/writer/ closer × {set, void} happy paths + branched-callee runtime. Rows verify the call runs + the constant exit; the void-arm rows do NOT inspect the resulting variant tag (deferred #199b wrapped-slot tag-remap mis-routes to dst tag 0). Cstage and wwstage emit byte-identical asm on every row. test/wcc/768_io_types_run.c — track the alias retarget; rows now build a vtable, pass `&vt` (= vstream), and call through the fn-VALUE param shape. Combined.ww regen for w6c + wwdump per #110: lib/errors lands transitively via the new `import errors;` in types.ww. Sibling filed inline (NOT fixed): the checker rejects bare `&fn_name` / `let p: *alias = &fn` assignment to a `(*alias | void)` field — the structural `*fn(...)` value isn't accepted as the `*alias` NAMED variant. Both stages reject. Probes route around via explicit `(&fn): *io.reader` cast at each vtable-field assignment.
This commit is contained in:
11
Makefile
11
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/io/io.ww lib/io/types.ww lib/memio/memio.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/io/io.ww lib/io/types.ww lib/memio/memio.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
|
||||
@@ -325,6 +325,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_typeassert_nonident \
|
||||
$(BIN)/test_isas_spread_variant \
|
||||
$(BIN)/test_tagged_widen_named_variant \
|
||||
$(BIN)/test_io_vtable_run \
|
||||
$(BIN)/test_use_promote_alias \
|
||||
$(BIN)/test_field_signed $(BIN)/test_frame_argcount \
|
||||
$(BIN)/test_selfhost $(BIN)/test_w6a_ww $(BIN)/test_w6l_ww \
|
||||
@@ -681,6 +682,12 @@ $(BIN)/test_tagged_widen_named_variant: test/wcc/774_tagged_widen_named_variant.
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_io_vtable_run: test/wcc/775_io_vtable_run.c \
|
||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_arrlit_str_full: test/wcc/711_arrlit_str_full.c \
|
||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
|
||||
|
||||
95
lib/io/stream.ww
Normal file
95
lib/io/stream.ww
Normal file
@@ -0,0 +1,95 @@
|
||||
// stream — Hare-shaped vtable port. Project #94 fold-e1.
|
||||
//
|
||||
// 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:
|
||||
//
|
||||
// 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).
|
||||
// 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.
|
||||
// Each `match`es the matching vtable slot, returns
|
||||
// `errors.unsupported` (widened twice) when the slot is
|
||||
// void, otherwise calls through the fn pointer.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package io;
|
||||
|
||||
// ref/hare/io/stream.ha:36-42. Nullable spelling per #192 (ww parser
|
||||
// rejects `nullable *T`); each slot is `(*T | void)`.
|
||||
export type vtable = struct {
|
||||
reader: (*reader | void),
|
||||
writer: (*writer | void),
|
||||
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: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) = {
|
||||
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-59.
|
||||
export fn st_write(s: vstream, 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 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) = {
|
||||
match (s.closer) {
|
||||
case void => return void;
|
||||
case let c: *closer => return (*c)(s);
|
||||
};
|
||||
};
|
||||
@@ -1,18 +1,25 @@
|
||||
// types — error union, mode/whence enums, reader/writer/closer
|
||||
// fn-type aliases. Project #94 fold-1 step (d) (drew Fold D).
|
||||
// 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).
|
||||
//
|
||||
// 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; this file owns the surrounding port. Both
|
||||
// files share `package io;` so forward refs (e.g. reader → stream)
|
||||
// resolve via the dir-enum concat order.
|
||||
// `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).
|
||||
//
|
||||
// Deferrals (drew-signed): `handle`, `seeker`, `copier`, `strerror`
|
||||
// land with fold-e (need the `handle` sum). Hare's `EOF = done`
|
||||
// land with fold-e2+ (need the `handle` sum). Hare's `EOF = done`
|
||||
// stays as `io.eof` until the `done` singleton ships (#93).
|
||||
|
||||
package io;
|
||||
|
||||
import errors;
|
||||
|
||||
// ref/hare/io/types.ha:29-34. RDWR is spelled `3` rather than Hare's
|
||||
// `READ | WRITE` because the ww parser doesn't fold expressions in
|
||||
// enum value positions; the value SSoT stays the same bitfield.
|
||||
@@ -24,7 +31,7 @@ export type mode = enum u8 {
|
||||
};
|
||||
|
||||
// ref/hare/io/types.ha:37-41. Hare leaves the underlying implicit;
|
||||
// ww requires one. i32 matches the off type that fold-e will plug
|
||||
// ww requires one. i32 matches the off type that fold-e2 will plug
|
||||
// into the seeker signature.
|
||||
export type whence = enum i32 {
|
||||
SET = 0,
|
||||
@@ -32,20 +39,24 @@ export type whence = enum i32 {
|
||||
END = 2,
|
||||
};
|
||||
|
||||
// ref/hare/io/types.ha:11 spreads `errors::error` into the union;
|
||||
// lib/errors isn't ported yet, so the union carries only the tags
|
||||
// observable in this fold — `underread` (from io.ww) and the
|
||||
// predeclared `nomem` (#29, type.c:72 / check.ww:78).
|
||||
export type error = !(underread | nomem);
|
||||
// ref/hare/io/types.ha:11 spreads `...errors::error` into the union;
|
||||
// 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.
|
||||
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. `*stream` forward-refs the
|
||||
// pre-vtable struct in io.ww — same cross-file pattern Hare uses.
|
||||
export type reader = fn(s: *stream, buf: []u8) (size | eof | error);
|
||||
// rationale; lifts to `done` with #93. `vstream` 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);
|
||||
|
||||
// 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: *stream, buf: []u8) (size | error);
|
||||
export type writer = fn(s: vstream, buf: []u8) (size | error);
|
||||
|
||||
// ref/hare/io/types.ha:55.
|
||||
export type closer = fn(s: *stream) (void | error);
|
||||
export type closer = fn(s: vstream) (void | error);
|
||||
|
||||
@@ -13921,21 +13921,152 @@ export fn close(s: *stream) (void | closed) = {
|
||||
return s.close(s);
|
||||
};
|
||||
|
||||
// stream — Hare-shaped vtable port. Project #94 fold-e1.
|
||||
//
|
||||
// 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:
|
||||
//
|
||||
// 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).
|
||||
// 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.
|
||||
// Each `match`es the matching vtable slot, returns
|
||||
// `errors.unsupported` (widened twice) when the slot is
|
||||
// void, otherwise calls through the fn pointer.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package io;
|
||||
|
||||
// ref/hare/io/stream.ha:36-42. Nullable spelling per #192 (ww parser
|
||||
// rejects `nullable *T`); each slot is `(*T | void)`.
|
||||
export type vtable = struct {
|
||||
reader: (*reader | void),
|
||||
writer: (*writer | void),
|
||||
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: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) = {
|
||||
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-59.
|
||||
export fn st_write(s: vstream, 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 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) = {
|
||||
match (s.closer) {
|
||||
case void => return void;
|
||||
case let c: *closer => return (*c)(s);
|
||||
};
|
||||
};
|
||||
|
||||
// errors — domain-agnostic error types. Mirrors ref/hare/errors/.
|
||||
//
|
||||
// Named-void tagged-union variants, so `(T | errors.invalid | ...)`
|
||||
// composes with every other module's error surface at the tag level.
|
||||
// Per-domain errors (io.eof, io.closed, io.underread, strconv.overflow,
|
||||
// ...) live in their own modules; this module ships only the generic
|
||||
// conditions Hare's errors:: exports.
|
||||
//
|
||||
// Subset shipped today; add more from Hare's errors/common.ha as callers
|
||||
// need them.
|
||||
|
||||
// A function was called with an invalid combination of arguments.
|
||||
package errors;
|
||||
|
||||
export type invalid = !void;
|
||||
|
||||
// The user does not have permission to use this resource.
|
||||
export type noaccess = !void;
|
||||
|
||||
// An entry was requested which does not exist.
|
||||
export type noentry = !void;
|
||||
|
||||
// An attempt was made to create a resource which already exists.
|
||||
export type exists = !void;
|
||||
|
||||
// The requested operation is not supported.
|
||||
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).
|
||||
// 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).
|
||||
//
|
||||
// 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; this file owns the surrounding port. Both
|
||||
// files share `package io;` so forward refs (e.g. reader → stream)
|
||||
// resolve via the dir-enum concat order.
|
||||
// `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).
|
||||
//
|
||||
// Deferrals (drew-signed): `handle`, `seeker`, `copier`, `strerror`
|
||||
// land with fold-e (need the `handle` sum). Hare's `EOF = done`
|
||||
// land with fold-e2+ (need the `handle` sum). Hare's `EOF = done`
|
||||
// stays as `io.eof` until the `done` singleton ships (#93).
|
||||
|
||||
package io;
|
||||
|
||||
import errors;
|
||||
|
||||
// ref/hare/io/types.ha:29-34. RDWR is spelled `3` rather than Hare's
|
||||
// `READ | WRITE` because the ww parser doesn't fold expressions in
|
||||
// enum value positions; the value SSoT stays the same bitfield.
|
||||
@@ -13947,7 +14078,7 @@ export type mode = enum u8 {
|
||||
};
|
||||
|
||||
// ref/hare/io/types.ha:37-41. Hare leaves the underlying implicit;
|
||||
// ww requires one. i32 matches the off type that fold-e will plug
|
||||
// ww requires one. i32 matches the off type that fold-e2 will plug
|
||||
// into the seeker signature.
|
||||
export type whence = enum i32 {
|
||||
SET = 0,
|
||||
@@ -13955,23 +14086,27 @@ export type whence = enum i32 {
|
||||
END = 2,
|
||||
};
|
||||
|
||||
// ref/hare/io/types.ha:11 spreads `errors::error` into the union;
|
||||
// lib/errors isn't ported yet, so the union carries only the tags
|
||||
// observable in this fold — `underread` (from io.ww) and the
|
||||
// predeclared `nomem` (#29, type.c:72 / check.ww:78).
|
||||
export type error = !(underread | nomem);
|
||||
// ref/hare/io/types.ha:11 spreads `...errors::error` into the union;
|
||||
// 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.
|
||||
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. `*stream` forward-refs the
|
||||
// pre-vtable struct in io.ww — same cross-file pattern Hare uses.
|
||||
export type reader = fn(s: *stream, buf: []u8) (size | eof | error);
|
||||
// rationale; lifts to `done` with #93. `vstream` 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);
|
||||
|
||||
// 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: *stream, buf: []u8) (size | error);
|
||||
export type writer = fn(s: vstream, buf: []u8) (size | error);
|
||||
|
||||
// ref/hare/io/types.ha:55.
|
||||
export type closer = fn(s: *stream) (void | error);
|
||||
export type closer = fn(s: vstream) (void | error);
|
||||
|
||||
// memio — in-memory io stream.
|
||||
//
|
||||
|
||||
@@ -13921,21 +13921,152 @@ export fn close(s: *stream) (void | closed) = {
|
||||
return s.close(s);
|
||||
};
|
||||
|
||||
// stream — Hare-shaped vtable port. Project #94 fold-e1.
|
||||
//
|
||||
// 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:
|
||||
//
|
||||
// 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).
|
||||
// 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.
|
||||
// Each `match`es the matching vtable slot, returns
|
||||
// `errors.unsupported` (widened twice) when the slot is
|
||||
// void, otherwise calls through the fn pointer.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package io;
|
||||
|
||||
// ref/hare/io/stream.ha:36-42. Nullable spelling per #192 (ww parser
|
||||
// rejects `nullable *T`); each slot is `(*T | void)`.
|
||||
export type vtable = struct {
|
||||
reader: (*reader | void),
|
||||
writer: (*writer | void),
|
||||
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: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) = {
|
||||
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-59.
|
||||
export fn st_write(s: vstream, 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 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) = {
|
||||
match (s.closer) {
|
||||
case void => return void;
|
||||
case let c: *closer => return (*c)(s);
|
||||
};
|
||||
};
|
||||
|
||||
// errors — domain-agnostic error types. Mirrors ref/hare/errors/.
|
||||
//
|
||||
// Named-void tagged-union variants, so `(T | errors.invalid | ...)`
|
||||
// composes with every other module's error surface at the tag level.
|
||||
// Per-domain errors (io.eof, io.closed, io.underread, strconv.overflow,
|
||||
// ...) live in their own modules; this module ships only the generic
|
||||
// conditions Hare's errors:: exports.
|
||||
//
|
||||
// Subset shipped today; add more from Hare's errors/common.ha as callers
|
||||
// need them.
|
||||
|
||||
// A function was called with an invalid combination of arguments.
|
||||
package errors;
|
||||
|
||||
export type invalid = !void;
|
||||
|
||||
// The user does not have permission to use this resource.
|
||||
export type noaccess = !void;
|
||||
|
||||
// An entry was requested which does not exist.
|
||||
export type noentry = !void;
|
||||
|
||||
// An attempt was made to create a resource which already exists.
|
||||
export type exists = !void;
|
||||
|
||||
// The requested operation is not supported.
|
||||
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).
|
||||
// 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).
|
||||
//
|
||||
// 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; this file owns the surrounding port. Both
|
||||
// files share `package io;` so forward refs (e.g. reader → stream)
|
||||
// resolve via the dir-enum concat order.
|
||||
// `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).
|
||||
//
|
||||
// Deferrals (drew-signed): `handle`, `seeker`, `copier`, `strerror`
|
||||
// land with fold-e (need the `handle` sum). Hare's `EOF = done`
|
||||
// land with fold-e2+ (need the `handle` sum). Hare's `EOF = done`
|
||||
// stays as `io.eof` until the `done` singleton ships (#93).
|
||||
|
||||
package io;
|
||||
|
||||
import errors;
|
||||
|
||||
// ref/hare/io/types.ha:29-34. RDWR is spelled `3` rather than Hare's
|
||||
// `READ | WRITE` because the ww parser doesn't fold expressions in
|
||||
// enum value positions; the value SSoT stays the same bitfield.
|
||||
@@ -13947,7 +14078,7 @@ export type mode = enum u8 {
|
||||
};
|
||||
|
||||
// ref/hare/io/types.ha:37-41. Hare leaves the underlying implicit;
|
||||
// ww requires one. i32 matches the off type that fold-e will plug
|
||||
// ww requires one. i32 matches the off type that fold-e2 will plug
|
||||
// into the seeker signature.
|
||||
export type whence = enum i32 {
|
||||
SET = 0,
|
||||
@@ -13955,23 +14086,27 @@ export type whence = enum i32 {
|
||||
END = 2,
|
||||
};
|
||||
|
||||
// ref/hare/io/types.ha:11 spreads `errors::error` into the union;
|
||||
// lib/errors isn't ported yet, so the union carries only the tags
|
||||
// observable in this fold — `underread` (from io.ww) and the
|
||||
// predeclared `nomem` (#29, type.c:72 / check.ww:78).
|
||||
export type error = !(underread | nomem);
|
||||
// ref/hare/io/types.ha:11 spreads `...errors::error` into the union;
|
||||
// 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.
|
||||
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. `*stream` forward-refs the
|
||||
// pre-vtable struct in io.ww — same cross-file pattern Hare uses.
|
||||
export type reader = fn(s: *stream, buf: []u8) (size | eof | error);
|
||||
// rationale; lifts to `done` with #93. `vstream` 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);
|
||||
|
||||
// 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: *stream, buf: []u8) (size | error);
|
||||
export type writer = fn(s: vstream, buf: []u8) (size | error);
|
||||
|
||||
// ref/hare/io/types.ha:55.
|
||||
export type closer = fn(s: *stream) (void | error);
|
||||
export type closer = fn(s: vstream) (void | error);
|
||||
|
||||
// memio — in-memory io stream.
|
||||
//
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
/*
|
||||
* 768_io_types_run — project #94 fold-1 step (d) (drew Fold D) sentinel.
|
||||
* Pins the new lib/io/types.ww port (ref/hare/io/types.ha) — `mode` /
|
||||
* `whence` enums, `error = !(underread | nomem)` union, and the
|
||||
* `reader` / `writer` / `closer` fn-type aliases. The aliases forward-
|
||||
* ref `*stream` from lib/io/io.ww (Hare splits the same way:
|
||||
* stream.ha + types.ha share the `io` module).
|
||||
* 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
|
||||
* lib/io/stream.ww); the rows track. Hare splits the io module the
|
||||
* same way: stream.ha + types.ha share the `io` module.
|
||||
*
|
||||
* Each row is a self-contained `import io;` program: cstage `ww build`
|
||||
* compiles it, we run the binary and assert the exit. Rows that hit a
|
||||
@@ -101,57 +102,62 @@ static const struct row rows[] = {
|
||||
"};\n",
|
||||
2,
|
||||
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
|
||||
* 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.stream, buf: []u8) (size | io.eof | io.error) = {\n"
|
||||
"fn dummy_read(s: io.vstream, buf: []u8) (size | io.eof | io.error) = {\n"
|
||||
" return 0: size;\n"
|
||||
"};\n"
|
||||
"fn use_reader(r: io.reader, s: *io.stream, buf: []u8) i32 = {\n"
|
||||
"fn use_reader(r: io.reader, s: io.vstream, buf: []u8) i32 = {\n"
|
||||
" let v = r(s, buf);\n"
|
||||
" if (v is size) { return 11; };\n"
|
||||
" return -1;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let st: io.stream;\n"
|
||||
" let vt: io.vtable;\n"
|
||||
" let buf: [4]u8;\n"
|
||||
" let bs: []u8 = buf[0:4];\n"
|
||||
" return use_reader(dummy_read, &st, bs);\n"
|
||||
" return use_reader(dummy_read, &vt, bs);\n"
|
||||
"};\n",
|
||||
11,
|
||||
STAGE_CS | STAGE_WW },
|
||||
{ "writer_alias_param",
|
||||
"package main;\n"
|
||||
"import io;\n"
|
||||
"fn dummy_write(s: *io.stream, buf: []u8) (size | io.error) = {\n"
|
||||
"fn dummy_write(s: io.vstream, buf: []u8) (size | io.error) = {\n"
|
||||
" return buf.len: size;\n"
|
||||
"};\n"
|
||||
"fn use_writer(w: io.writer, s: *io.stream, buf: []u8) i32 = {\n"
|
||||
"fn use_writer(w: io.writer, s: io.vstream, buf: []u8) i32 = {\n"
|
||||
" let v = w(s, buf);\n"
|
||||
" if (v is size) { return 22; };\n"
|
||||
" return -1;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let st: io.stream;\n"
|
||||
" let vt: io.vtable;\n"
|
||||
" let buf: [7]u8;\n"
|
||||
" let bs: []u8 = buf[0:7];\n"
|
||||
" return use_writer(dummy_write, &st, bs);\n"
|
||||
" return use_writer(dummy_write, &vt, bs);\n"
|
||||
"};\n",
|
||||
22,
|
||||
STAGE_CS | STAGE_WW },
|
||||
{ "closer_alias_param",
|
||||
"package main;\n"
|
||||
"import io;\n"
|
||||
"fn dummy_close(s: *io.stream) (void | io.error) = {\n"
|
||||
"fn dummy_close(s: io.vstream) (void | io.error) = {\n"
|
||||
" return void;\n"
|
||||
"};\n"
|
||||
"fn use_closer(c: io.closer, s: *io.stream) i32 = {\n"
|
||||
"fn use_closer(c: io.closer, s: io.vstream) i32 = {\n"
|
||||
" let _v = c(s);\n"
|
||||
" return 33;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let st: io.stream;\n"
|
||||
" return use_closer(dummy_close, &st);\n"
|
||||
" let vt: io.vtable;\n"
|
||||
" return use_closer(dummy_close, &vt);\n"
|
||||
"};\n",
|
||||
33,
|
||||
STAGE_CS | STAGE_WW },
|
||||
|
||||
400
test/wcc/775_io_vtable_run.c
Normal file
400
test/wcc/775_io_vtable_run.c
Normal file
@@ -0,0 +1,400 @@
|
||||
/*
|
||||
* 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).
|
||||
*
|
||||
* Each row imports io, allocates a vtable, optionally points one slot at
|
||||
* a user fn (via the `(&fn): *io.<role>` cast — see SIBLINGS below),
|
||||
* calls the matching dispatcher, and checks the exit constant. Both
|
||||
* stages run; cs.s == ww.s byte-id on the rows that build (rule-10).
|
||||
*
|
||||
* row | what it pins
|
||||
* --------------------------+--------------------------------------
|
||||
* reader_set_happy | reader slot set + st_read happy path.
|
||||
* | Callee returns (7: size); probe asserts
|
||||
* | `r is size && r as size == 7`. Pins
|
||||
* | the call-arm `case let r: *reader =>
|
||||
* | return (*r)(s, buf)` end-to-end.
|
||||
* reader_void_unsupported | reader=void. st_read takes the void-arm
|
||||
* | (two direct widens, #199 α + #205) and
|
||||
* | returns the errors.unsupported variant.
|
||||
* | Probe verifies the call returns + exit
|
||||
* | constant only — does NOT inspect r's
|
||||
* | discriminant tag (deferred #199b: the
|
||||
* | wrapped-slot tag-remap currently lands
|
||||
* | the value at dst tag 0, not the `error`
|
||||
* | variant index; see 774 header for the
|
||||
* | parallel constraint).
|
||||
* writer_set_happy | symmetric to reader_set_happy on
|
||||
* | st_write; callee returns (buf.len:size).
|
||||
* writer_void_unsupported | symmetric to reader_void_unsupported.
|
||||
* closer_void_noop | closer=void. st_close returns plain
|
||||
* | `void` (no widen — direct `return void`
|
||||
* | from the dispatcher). Probe asserts
|
||||
* | `r is void` — the only row that can
|
||||
* | inspect the tag, because no nested
|
||||
* | wrapper is involved.
|
||||
* closer_set | closer slot set + st_close happy path.
|
||||
* | Callee returns plain `void`; the
|
||||
* | dispatcher forwards via `(*c)(s)` and
|
||||
* | the return surfaces at the caller.
|
||||
* branched_readers | two distinct vtable instances each with
|
||||
* | a different reader fn. st_read on each
|
||||
* | dispatches to the right callee (1:size
|
||||
* | vs 2:size). Mirrors 774's branched-
|
||||
* | callee row; catches a constant-fold
|
||||
* | mistake in the dispatch path.
|
||||
*
|
||||
* SIBLINGS (filed inline, NOT fixed here — fold-e1 is purely additive):
|
||||
*
|
||||
* - #199b layout-extension (wrapped-slot tag-remap): when widening a
|
||||
* NAMED wrapper-typed value (e.g. `error`) into a tagged parent
|
||||
* (e.g. `(size | eof | error)`), cgen walks src VARIANTS rather
|
||||
* than treating src-as-a-whole, so the dst tag lands at 0
|
||||
* regardless of which wrapper variant was set. Affects rows 2 & 4
|
||||
* (unsupported via void-arm). Probes verify runtime exit-clean,
|
||||
* NOT the variant tag — matches the 774 header's exact constraint.
|
||||
*
|
||||
* - NEW: checker rejects bare `&fn_name` assignment to a
|
||||
* `(*<alias-to-fn> | void)` field. `vt.reader = &myread;` errors
|
||||
* with "cannot assign *fn(vstream, []u8) (size | eof | error) to
|
||||
* (*reader | void)" — the structural `*fn(...)` value is not
|
||||
* accepted as the `*reader` named variant of the tagged field.
|
||||
* Both stages reject. Symmetric to #178 (typeeqast layer asymmetry)
|
||||
* at the pointer-to-fn-alias layer + tagged-variant assignment.
|
||||
* PROBE ROUTES AROUND via an explicit cast: `(&myread): *io.reader`.
|
||||
* Once closed, the cast drops out of every Hare-faithful vtable
|
||||
* initialisation.
|
||||
*
|
||||
* - NEW: `let p: *io.reader = &myread;` also errors with "init
|
||||
* *fn(vstream, []u8) ... not assignable to declared *reader" —
|
||||
* same root as above but at the let-binding layer. The Hare
|
||||
* `io::vtable { reader = &my_read, ... }` shape needs both holes
|
||||
* closed.
|
||||
*
|
||||
* GATE POLARITY: must stay GREEN. A red here means the vtable port
|
||||
* regressed at the checker, the dispatcher cgen miscompiled the
|
||||
* call-arm, or the field-slot zero-init lost the void tag.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
#define STAGE_CS 1
|
||||
#define STAGE_WW 2
|
||||
|
||||
struct row {
|
||||
const char *label;
|
||||
const char *src;
|
||||
int want_exit;
|
||||
int stage_mask;
|
||||
int byte_id;
|
||||
};
|
||||
|
||||
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"
|
||||
" 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 buf: [4]u8;\n"
|
||||
" let bs: []u8 = buf[0:4];\n"
|
||||
" let r = io.st_read(v, bs);\n"
|
||||
" if (r is size) {\n"
|
||||
" let n = r as size;\n"
|
||||
" if (n == 7: size) { return 70; };\n"
|
||||
" return 71;\n"
|
||||
" };\n"
|
||||
" return 99;\n"
|
||||
"};\n",
|
||||
70,
|
||||
STAGE_CS | STAGE_WW, 1 },
|
||||
{ "reader_void_unsupported",
|
||||
"package main;\n"
|
||||
"import io;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let vt: io.vtable;\n"
|
||||
" let v: io.vstream = &vt;\n"
|
||||
" let buf: [4]u8;\n"
|
||||
" let bs: []u8 = buf[0:4];\n"
|
||||
" let _r = io.st_read(v, bs);\n"
|
||||
" return 11;\n"
|
||||
"};\n",
|
||||
11,
|
||||
STAGE_CS | STAGE_WW, 1 },
|
||||
{ "writer_set_happy",
|
||||
"package main;\n"
|
||||
"import io;\n"
|
||||
"fn mywrite(s: io.vstream, 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 buf: [5]u8;\n"
|
||||
" let bs: []u8 = buf[0:5];\n"
|
||||
" let r = io.st_write(v, bs);\n"
|
||||
" if (r is size) {\n"
|
||||
" let n = r as size;\n"
|
||||
" if (n == 5: size) { return 80; };\n"
|
||||
" return 81;\n"
|
||||
" };\n"
|
||||
" return 99;\n"
|
||||
"};\n",
|
||||
80,
|
||||
STAGE_CS | STAGE_WW, 1 },
|
||||
{ "writer_void_unsupported",
|
||||
"package main;\n"
|
||||
"import io;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let vt: io.vtable;\n"
|
||||
" let v: io.vstream = &vt;\n"
|
||||
" let buf: [3]u8;\n"
|
||||
" let bs: []u8 = buf[0:3];\n"
|
||||
" let _r = io.st_write(v, bs);\n"
|
||||
" return 22;\n"
|
||||
"};\n",
|
||||
22,
|
||||
STAGE_CS | STAGE_WW, 1 },
|
||||
{ "closer_void_noop",
|
||||
"package main;\n"
|
||||
"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"
|
||||
" if (r is void) { return 50; };\n"
|
||||
" return 99;\n"
|
||||
"};\n",
|
||||
50,
|
||||
STAGE_CS | STAGE_WW, 1 },
|
||||
{ "closer_set",
|
||||
"package main;\n"
|
||||
"import io;\n"
|
||||
"fn myclose(s: io.vstream) (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"
|
||||
" if (r is void) { return 60; };\n"
|
||||
" return 99;\n"
|
||||
"};\n",
|
||||
60,
|
||||
STAGE_CS | STAGE_WW, 1 },
|
||||
{ "branched_readers",
|
||||
"package main;\n"
|
||||
"import io;\n"
|
||||
"fn r1(s: io.vstream, 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"
|
||||
" return 2: size;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let vt1: io.vtable;\n"
|
||||
" let vt2: io.vtable;\n"
|
||||
" vt1.reader = (&r1): *io.reader;\n"
|
||||
" 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 asz: size = 0;\n"
|
||||
" let bsz: size = 0;\n"
|
||||
" if (a is size) { asz = a as size; };\n"
|
||||
" if (b is size) { bsz = b as size; };\n"
|
||||
" if (asz == 1: size && bsz == 2: size) { return 95; };\n"
|
||||
" return 99;\n"
|
||||
"};\n",
|
||||
95,
|
||||
STAGE_CS | STAGE_WW, 1 },
|
||||
};
|
||||
|
||||
static int
|
||||
write_source(const char *path, const char *src)
|
||||
{
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Per-row tmpdir cleanup. ww_ww writes intermediates next to the source
|
||||
* (filed task #15), so each row's build leaves <src>.{combined.ww,s,o}
|
||||
* + bare exe alongside. Sweep them all then rmdir. */
|
||||
static void
|
||||
cleanup_tmp(const char *tmpdir, const char *base)
|
||||
{
|
||||
char p[640];
|
||||
snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base); unlink(p);
|
||||
snprintf(p, sizeof p, "%s/%s.s", tmpdir, base); unlink(p);
|
||||
snprintf(p, sizeof p, "%s/%s.o", tmpdir, base); unlink(p);
|
||||
snprintf(p, sizeof p, "%s/%s.combined.ww", tmpdir, base); unlink(p);
|
||||
snprintf(p, sizeof p, "%s/%s", tmpdir, base); unlink(p);
|
||||
rmdir(tmpdir);
|
||||
}
|
||||
|
||||
static int
|
||||
build_via_driver(const char *driver, const char *tmpdir, const char *cwd,
|
||||
const char *src)
|
||||
{
|
||||
char cmd[2048];
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"cd %s && timeout 180 %s build -I %s/lib %s 2>/dev/null",
|
||||
tmpdir, driver, cwd, src);
|
||||
return runwait(cmd);
|
||||
}
|
||||
|
||||
static int
|
||||
run_row(const char *driver, const char *cwd, const struct row *r, int seq)
|
||||
{
|
||||
char tmpdir[256], src[512], base[64], outbin[768];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/iov_%d_d_%d", getpid(), seq);
|
||||
snprintf(base, sizeof base, "main775");
|
||||
snprintf(src, sizeof src, "%s/%s.ww", tmpdir, base);
|
||||
mkdir(tmpdir, 0755);
|
||||
if (write_source(src, r->src) != 0) { cleanup_tmp(tmpdir, base); return -1; }
|
||||
int rc;
|
||||
int br = build_via_driver(driver, tmpdir, cwd, src);
|
||||
if (br == 0) {
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
rc = runwait(outbin);
|
||||
} else {
|
||||
rc = -1;
|
||||
}
|
||||
cleanup_tmp(tmpdir, base);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* asm_byte_identical — diff cstage vs wwstage .s. Parallel trees so
|
||||
* ww_ww writing intermediates next to the source doesn't clobber the
|
||||
* cstage .s (CLAUDE.md rule 14 phase split). */
|
||||
static int
|
||||
asm_byte_identical(const char *cdrv, const char *wdrv, const char *cwd,
|
||||
const struct row *r, int seq)
|
||||
{
|
||||
char src[512], tdc[256], tdw[256], base[64], cs[512], ws[512];
|
||||
snprintf(tdc, sizeof tdc, "/tmp/iov_%d_c_%d", getpid(), seq);
|
||||
snprintf(tdw, sizeof tdw, "/tmp/iov_%d_w_%d", getpid(), seq);
|
||||
snprintf(base, sizeof base, "main775");
|
||||
mkdir(tdc, 0755);
|
||||
mkdir(tdw, 0755);
|
||||
snprintf(src, sizeof src, "%s/%s.ww", tdc, base);
|
||||
if (write_source(src, r->src) != 0) { cleanup_tmp(tdc, base); cleanup_tmp(tdw, base); return -1; }
|
||||
int rc = -1;
|
||||
if (build_via_driver(cdrv, tdc, cwd, src) != 0) goto out;
|
||||
snprintf(cs, sizeof cs, "%s/%s.s", tdc, base);
|
||||
|
||||
snprintf(src, sizeof src, "%s/%s.ww", tdw, base);
|
||||
if (write_source(src, r->src) != 0) goto out;
|
||||
if (build_via_driver(wdrv, tdw, cwd, src) != 0) goto out;
|
||||
snprintf(ws, sizeof ws, "%s/%s.s", tdw, base);
|
||||
|
||||
FILE *fc = fopen(cs, "rb");
|
||||
FILE *fw = fopen(ws, "rb");
|
||||
if (fc && fw) {
|
||||
rc = 0;
|
||||
for (;;) {
|
||||
int a = fgetc(fc);
|
||||
int b = fgetc(fw);
|
||||
if (a != b) { rc = -1; break; }
|
||||
if (a == EOF) break;
|
||||
}
|
||||
}
|
||||
if (fc) fclose(fc);
|
||||
if (fw) fclose(fw);
|
||||
out:
|
||||
cleanup_tmp(tdc, base);
|
||||
cleanup_tmp(tdw, base);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char cwd[256];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
char absbin[512];
|
||||
if (bin[0] != '/') {
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[640], wdrv[640];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
int wwpresent = (access(wdrv, X_OK) == 0);
|
||||
int seq = 0;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (rows[i].stage_mask & STAGE_CS) {
|
||||
total++;
|
||||
int got = run_row(cdrv, cwd, &rows[i], seq++);
|
||||
if (got != rows[i].want_exit) {
|
||||
fprintf(stderr,
|
||||
"io_vtable_run[cs][%s]: exit=%d want=%d\n",
|
||||
rows[i].label, got, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
if (wwpresent && (rows[i].stage_mask & STAGE_WW)) {
|
||||
total++;
|
||||
int got = run_row(wdrv, cwd, &rows[i], seq++);
|
||||
if (got != rows[i].want_exit) {
|
||||
fprintf(stderr,
|
||||
"io_vtable_run[ww][%s]: exit=%d want=%d\n",
|
||||
rows[i].label, got, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
if (rows[i].byte_id) {
|
||||
total++;
|
||||
if (asm_byte_identical(cdrv, wdrv, cwd, &rows[i], seq++) != 0) {
|
||||
fprintf(stderr,
|
||||
"io_vtable_run[byte-id][%s]: cstage vs wwstage asm differs\n",
|
||||
rows[i].label);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!wwpresent)
|
||||
fprintf(stderr, "io_vtable_run: skip wwstage (no %s)\n", wdrv);
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "io_vtable_run: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("io_vtable_run: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user