lib: io.handle union + seeker + handle-typed read/write/close/seek dispatch (#5)
Ports ref/hare/io handle.ha: a handle is (file | *stream); ww stream is already *vtable (#94 collapse) so the payload is (file | stream). file=i32 (Hare int is 32-bit, ww int is 8B word -- width-faithful, USER-ruled). read/write/close/seek/tell match on the handle: file-arm to os.read/write/close/lseek (os plays Hare sys role), stream-arm to the unchanged st_* vtable bodies; seeker is the 4th vtable slot (copier deferred). On a file-arm syscall error the stub returns errors.unsupported with a #199b marker -- faithful errno to io.error needs io.error to spread ...errors.error (#204/#199b-blocked); only the error value is lossy until then, the type stays faithful. Regenerates w6c/wwdump combined.ww.
This commit is contained in:
7
Makefile
7
Makefile
@@ -329,6 +329,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
|||||||
$(BIN)/test_isas_spread_variant \
|
$(BIN)/test_isas_spread_variant \
|
||||||
$(BIN)/test_tagged_widen_named_variant \
|
$(BIN)/test_tagged_widen_named_variant \
|
||||||
$(BIN)/test_io_vtable_run \
|
$(BIN)/test_io_vtable_run \
|
||||||
|
$(BIN)/test_io_handle_run \
|
||||||
$(BIN)/test_memio_vstream_run \
|
$(BIN)/test_memio_vstream_run \
|
||||||
$(BIN)/test_fmt_vstream_run \
|
$(BIN)/test_fmt_vstream_run \
|
||||||
$(BIN)/test_fmt_vstream_mods_run \
|
$(BIN)/test_fmt_vstream_mods_run \
|
||||||
@@ -707,6 +708,12 @@ $(BIN)/test_io_vtable_run: test/wcc/775_io_vtable_run.c \
|
|||||||
$(LIB)/libwwrt.a | $(BIN)
|
$(LIB)/libwwrt.a | $(BIN)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
|
$(BIN)/test_io_handle_run: test/wcc/791_io_handle_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_memio_vstream_run: test/wcc/776_memio_vstream_run.c \
|
$(BIN)/test_memio_vstream_run: test/wcc/776_memio_vstream_run.c \
|
||||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||||
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
|
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
|
||||||
|
|||||||
115
lib/io/stream.ww
115
lib/io/stream.ww
@@ -31,23 +31,31 @@
|
|||||||
|
|
||||||
package io;
|
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
|
// ref/hare/io/stream.ha:36-42. Nullable spelling per #192 (ww parser
|
||||||
// rejects `nullable *T`); each slot is `(*T | void)`.
|
// 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 {
|
export type vtable = struct {
|
||||||
reader: (*reader | void),
|
reader: (*reader | void),
|
||||||
writer: (*writer | void),
|
writer: (*writer | void),
|
||||||
closer: (*closer | void),
|
closer: (*closer | void),
|
||||||
|
seeker: (*seeker | void),
|
||||||
};
|
};
|
||||||
|
|
||||||
// ref/hare/io/stream.ha:33.
|
// ref/hare/io/stream.ha:33.
|
||||||
export type stream = *vtable;
|
export type stream = *vtable;
|
||||||
|
|
||||||
// ref/hare/io/stream.ha:44-50. The void-arm `return e;` chains two
|
// 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
|
// direct widens: `errors.unsupported → error` via #199 α, then
|
||||||
// `error → (size | eof | error)` via #205. The call-arm spells the
|
// `error → (size | eof | error)` via #205. The call-arm spells the
|
||||||
// fn-ptr call explicitly per #193 (ww requires `(*r)(args)` rather
|
// fn-ptr call explicitly per #193 (ww requires `(*r)(args)` rather
|
||||||
// than implicit `r(args)`).
|
// than implicit `r(args)`).
|
||||||
export fn read(s: stream, buf: []u8) (size | eof | error) = {
|
fn st_read(s: stream, buf: []u8) (size | eof | error) = {
|
||||||
match (s.reader) {
|
match (s.reader) {
|
||||||
case void => {
|
case void => {
|
||||||
let u: errors.unsupported;
|
let u: errors.unsupported;
|
||||||
@@ -58,8 +66,8 @@ export fn read(s: stream, buf: []u8) (size | eof | error) = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// ref/hare/io/stream.ha:53-59.
|
// ref/hare/io/stream.ha:53-60.
|
||||||
export fn write(s: stream, buf: []u8) (size | error) = {
|
fn st_write(s: stream, buf: []u8) (size | error) = {
|
||||||
match (s.writer) {
|
match (s.writer) {
|
||||||
case void => {
|
case void => {
|
||||||
let u: errors.unsupported;
|
let u: errors.unsupported;
|
||||||
@@ -74,9 +82,104 @@ export fn write(s: stream, buf: []u8) (size | error) = {
|
|||||||
// `c(s)?;` then falls through to the void arm; ww collapses to a
|
// `c(s)?;` then falls through to the void arm; ww collapses to a
|
||||||
// direct return for the void-arm fall-through shape. The surface
|
// direct return for the void-arm fall-through shape. The surface
|
||||||
// (void | error) matches.
|
// (void | error) matches.
|
||||||
export fn close(s: stream) (void | error) = {
|
fn st_close(s: stream) (void | error) = {
|
||||||
match (s.closer) {
|
match (s.closer) {
|
||||||
case void => return void;
|
case void => return void;
|
||||||
case let c: *closer => return (*c)(s);
|
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);
|
||||||
|
};
|
||||||
|
|||||||
@@ -37,6 +37,21 @@ export type whence = enum i32 {
|
|||||||
END = 2,
|
END = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ref/hare/io/+linux/platform_file.ha:13. Hare's `file = int`; on
|
||||||
|
// linux/x86_64 Hare's `int` is 32-bit, but ww's `int` is an 8B machine
|
||||||
|
// word, so a literal `int` here would be width-wrong for a fd. i32 is
|
||||||
|
// width-faithful to Hare's real fd width (USER-ruled 2026-05-31).
|
||||||
|
export type file = i32;
|
||||||
|
|
||||||
|
// ref/hare/io/arch+x86_64.ha:6. ABI-compatible with POSIX off_t.
|
||||||
|
export type off = i64;
|
||||||
|
|
||||||
|
// ref/hare/io/handle.ha:12. Hare spells it `(file | *stream)`; ww's
|
||||||
|
// `stream` is already `*vtable` (the #94 collapse absorbed Hare's
|
||||||
|
// `*stream` indirection), so the faithful ww payload is `(file |
|
||||||
|
// stream)`, NOT a literal `*stream` which would be a double-pointer.
|
||||||
|
export type handle = (file | stream);
|
||||||
|
|
||||||
// ref/hare/io/types.ha:11 spreads `...errors::error` into the union;
|
// ref/hare/io/types.ha:11 spreads `...errors::error` into the union;
|
||||||
// ww enumerates the tags explicitly (ken's #204-block — spread of an
|
// ww enumerates the tags explicitly (ken's #204-block — spread of an
|
||||||
// errors-side tagged into this union triggers a wrapper-vs-flatten
|
// errors-side tagged into this union triggers a wrapper-vs-flatten
|
||||||
@@ -58,3 +73,8 @@ export type writer = fn(s: stream, buf: []u8) (size | error);
|
|||||||
|
|
||||||
// ref/hare/io/types.ha:55.
|
// ref/hare/io/types.ha:55.
|
||||||
export type closer = fn(s: stream) (void | error);
|
export type closer = fn(s: stream) (void | error);
|
||||||
|
|
||||||
|
// ref/hare/io/types.ha:76. Hare's `fn(s: *stream, off: off, w: whence)`;
|
||||||
|
// ww's `stream` is already `*vtable` (the #94 collapse), so the param is
|
||||||
|
// `stream` directly, mirroring the reader/writer/closer aliases above.
|
||||||
|
export type seeker = fn(s: stream, off: off, w: whence) (off | error);
|
||||||
|
|||||||
@@ -14250,23 +14250,31 @@ export type underread = !i32;
|
|||||||
|
|
||||||
package io;
|
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
|
// ref/hare/io/stream.ha:36-42. Nullable spelling per #192 (ww parser
|
||||||
// rejects `nullable *T`); each slot is `(*T | void)`.
|
// 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 {
|
export type vtable = struct {
|
||||||
reader: (*reader | void),
|
reader: (*reader | void),
|
||||||
writer: (*writer | void),
|
writer: (*writer | void),
|
||||||
closer: (*closer | void),
|
closer: (*closer | void),
|
||||||
|
seeker: (*seeker | void),
|
||||||
};
|
};
|
||||||
|
|
||||||
// ref/hare/io/stream.ha:33.
|
// ref/hare/io/stream.ha:33.
|
||||||
export type stream = *vtable;
|
export type stream = *vtable;
|
||||||
|
|
||||||
// ref/hare/io/stream.ha:44-50. The void-arm `return e;` chains two
|
// 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
|
// direct widens: `errors.unsupported → error` via #199 α, then
|
||||||
// `error → (size | eof | error)` via #205. The call-arm spells the
|
// `error → (size | eof | error)` via #205. The call-arm spells the
|
||||||
// fn-ptr call explicitly per #193 (ww requires `(*r)(args)` rather
|
// fn-ptr call explicitly per #193 (ww requires `(*r)(args)` rather
|
||||||
// than implicit `r(args)`).
|
// than implicit `r(args)`).
|
||||||
export fn read(s: stream, buf: []u8) (size | eof | error) = {
|
fn st_read(s: stream, buf: []u8) (size | eof | error) = {
|
||||||
match (s.reader) {
|
match (s.reader) {
|
||||||
case void => {
|
case void => {
|
||||||
let u: errors.unsupported;
|
let u: errors.unsupported;
|
||||||
@@ -14277,8 +14285,8 @@ export fn read(s: stream, buf: []u8) (size | eof | error) = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// ref/hare/io/stream.ha:53-59.
|
// ref/hare/io/stream.ha:53-60.
|
||||||
export fn write(s: stream, buf: []u8) (size | error) = {
|
fn st_write(s: stream, buf: []u8) (size | error) = {
|
||||||
match (s.writer) {
|
match (s.writer) {
|
||||||
case void => {
|
case void => {
|
||||||
let u: errors.unsupported;
|
let u: errors.unsupported;
|
||||||
@@ -14293,13 +14301,108 @@ export fn write(s: stream, buf: []u8) (size | error) = {
|
|||||||
// `c(s)?;` then falls through to the void arm; ww collapses to a
|
// `c(s)?;` then falls through to the void arm; ww collapses to a
|
||||||
// direct return for the void-arm fall-through shape. The surface
|
// direct return for the void-arm fall-through shape. The surface
|
||||||
// (void | error) matches.
|
// (void | error) matches.
|
||||||
export fn close(s: stream) (void | error) = {
|
fn st_close(s: stream) (void | error) = {
|
||||||
match (s.closer) {
|
match (s.closer) {
|
||||||
case void => return void;
|
case void => return void;
|
||||||
case let c: *closer => return (*c)(s);
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
// errors — domain-agnostic error types. Mirrors ref/hare/errors/.
|
// errors — domain-agnostic error types. Mirrors ref/hare/errors/.
|
||||||
//
|
//
|
||||||
// Named-void tagged-union variants, so `(T | errors.invalid | ...)`
|
// Named-void tagged-union variants, so `(T | errors.invalid | ...)`
|
||||||
@@ -14475,6 +14578,21 @@ export type whence = enum i32 {
|
|||||||
END = 2,
|
END = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ref/hare/io/+linux/platform_file.ha:13. Hare's `file = int`; on
|
||||||
|
// linux/x86_64 Hare's `int` is 32-bit, but ww's `int` is an 8B machine
|
||||||
|
// word, so a literal `int` here would be width-wrong for a fd. i32 is
|
||||||
|
// width-faithful to Hare's real fd width (USER-ruled 2026-05-31).
|
||||||
|
export type file = i32;
|
||||||
|
|
||||||
|
// ref/hare/io/arch+x86_64.ha:6. ABI-compatible with POSIX off_t.
|
||||||
|
export type off = i64;
|
||||||
|
|
||||||
|
// ref/hare/io/handle.ha:12. Hare spells it `(file | *stream)`; ww's
|
||||||
|
// `stream` is already `*vtable` (the #94 collapse absorbed Hare's
|
||||||
|
// `*stream` indirection), so the faithful ww payload is `(file |
|
||||||
|
// stream)`, NOT a literal `*stream` which would be a double-pointer.
|
||||||
|
export type handle = (file | stream);
|
||||||
|
|
||||||
// ref/hare/io/types.ha:11 spreads `...errors::error` into the union;
|
// ref/hare/io/types.ha:11 spreads `...errors::error` into the union;
|
||||||
// ww enumerates the tags explicitly (ken's #204-block — spread of an
|
// ww enumerates the tags explicitly (ken's #204-block — spread of an
|
||||||
// errors-side tagged into this union triggers a wrapper-vs-flatten
|
// errors-side tagged into this union triggers a wrapper-vs-flatten
|
||||||
@@ -14497,6 +14615,11 @@ export type writer = fn(s: stream, buf: []u8) (size | error);
|
|||||||
// ref/hare/io/types.ha:55.
|
// ref/hare/io/types.ha:55.
|
||||||
export type closer = fn(s: stream) (void | error);
|
export type closer = fn(s: stream) (void | error);
|
||||||
|
|
||||||
|
// ref/hare/io/types.ha:76. Hare's `fn(s: *stream, off: off, w: whence)`;
|
||||||
|
// ww's `stream` is already `*vtable` (the #94 collapse), so the param is
|
||||||
|
// `stream` directly, mirroring the reader/writer/closer aliases above.
|
||||||
|
export type seeker = fn(s: stream, off: off, w: whence) (off | error);
|
||||||
|
|
||||||
// memio — in-memory io stream. Project #94 fold-eFinal.
|
// memio — in-memory io stream. Project #94 fold-eFinal.
|
||||||
//
|
//
|
||||||
// Hare's memio:: surface, drop underscores. Two flavours behind a
|
// Hare's memio:: surface, drop underscores. Two flavours behind a
|
||||||
|
|||||||
@@ -14250,23 +14250,31 @@ export type underread = !i32;
|
|||||||
|
|
||||||
package io;
|
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
|
// ref/hare/io/stream.ha:36-42. Nullable spelling per #192 (ww parser
|
||||||
// rejects `nullable *T`); each slot is `(*T | void)`.
|
// 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 {
|
export type vtable = struct {
|
||||||
reader: (*reader | void),
|
reader: (*reader | void),
|
||||||
writer: (*writer | void),
|
writer: (*writer | void),
|
||||||
closer: (*closer | void),
|
closer: (*closer | void),
|
||||||
|
seeker: (*seeker | void),
|
||||||
};
|
};
|
||||||
|
|
||||||
// ref/hare/io/stream.ha:33.
|
// ref/hare/io/stream.ha:33.
|
||||||
export type stream = *vtable;
|
export type stream = *vtable;
|
||||||
|
|
||||||
// ref/hare/io/stream.ha:44-50. The void-arm `return e;` chains two
|
// 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
|
// direct widens: `errors.unsupported → error` via #199 α, then
|
||||||
// `error → (size | eof | error)` via #205. The call-arm spells the
|
// `error → (size | eof | error)` via #205. The call-arm spells the
|
||||||
// fn-ptr call explicitly per #193 (ww requires `(*r)(args)` rather
|
// fn-ptr call explicitly per #193 (ww requires `(*r)(args)` rather
|
||||||
// than implicit `r(args)`).
|
// than implicit `r(args)`).
|
||||||
export fn read(s: stream, buf: []u8) (size | eof | error) = {
|
fn st_read(s: stream, buf: []u8) (size | eof | error) = {
|
||||||
match (s.reader) {
|
match (s.reader) {
|
||||||
case void => {
|
case void => {
|
||||||
let u: errors.unsupported;
|
let u: errors.unsupported;
|
||||||
@@ -14277,8 +14285,8 @@ export fn read(s: stream, buf: []u8) (size | eof | error) = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// ref/hare/io/stream.ha:53-59.
|
// ref/hare/io/stream.ha:53-60.
|
||||||
export fn write(s: stream, buf: []u8) (size | error) = {
|
fn st_write(s: stream, buf: []u8) (size | error) = {
|
||||||
match (s.writer) {
|
match (s.writer) {
|
||||||
case void => {
|
case void => {
|
||||||
let u: errors.unsupported;
|
let u: errors.unsupported;
|
||||||
@@ -14293,13 +14301,108 @@ export fn write(s: stream, buf: []u8) (size | error) = {
|
|||||||
// `c(s)?;` then falls through to the void arm; ww collapses to a
|
// `c(s)?;` then falls through to the void arm; ww collapses to a
|
||||||
// direct return for the void-arm fall-through shape. The surface
|
// direct return for the void-arm fall-through shape. The surface
|
||||||
// (void | error) matches.
|
// (void | error) matches.
|
||||||
export fn close(s: stream) (void | error) = {
|
fn st_close(s: stream) (void | error) = {
|
||||||
match (s.closer) {
|
match (s.closer) {
|
||||||
case void => return void;
|
case void => return void;
|
||||||
case let c: *closer => return (*c)(s);
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
// errors — domain-agnostic error types. Mirrors ref/hare/errors/.
|
// errors — domain-agnostic error types. Mirrors ref/hare/errors/.
|
||||||
//
|
//
|
||||||
// Named-void tagged-union variants, so `(T | errors.invalid | ...)`
|
// Named-void tagged-union variants, so `(T | errors.invalid | ...)`
|
||||||
@@ -14475,6 +14578,21 @@ export type whence = enum i32 {
|
|||||||
END = 2,
|
END = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ref/hare/io/+linux/platform_file.ha:13. Hare's `file = int`; on
|
||||||
|
// linux/x86_64 Hare's `int` is 32-bit, but ww's `int` is an 8B machine
|
||||||
|
// word, so a literal `int` here would be width-wrong for a fd. i32 is
|
||||||
|
// width-faithful to Hare's real fd width (USER-ruled 2026-05-31).
|
||||||
|
export type file = i32;
|
||||||
|
|
||||||
|
// ref/hare/io/arch+x86_64.ha:6. ABI-compatible with POSIX off_t.
|
||||||
|
export type off = i64;
|
||||||
|
|
||||||
|
// ref/hare/io/handle.ha:12. Hare spells it `(file | *stream)`; ww's
|
||||||
|
// `stream` is already `*vtable` (the #94 collapse absorbed Hare's
|
||||||
|
// `*stream` indirection), so the faithful ww payload is `(file |
|
||||||
|
// stream)`, NOT a literal `*stream` which would be a double-pointer.
|
||||||
|
export type handle = (file | stream);
|
||||||
|
|
||||||
// ref/hare/io/types.ha:11 spreads `...errors::error` into the union;
|
// ref/hare/io/types.ha:11 spreads `...errors::error` into the union;
|
||||||
// ww enumerates the tags explicitly (ken's #204-block — spread of an
|
// ww enumerates the tags explicitly (ken's #204-block — spread of an
|
||||||
// errors-side tagged into this union triggers a wrapper-vs-flatten
|
// errors-side tagged into this union triggers a wrapper-vs-flatten
|
||||||
@@ -14497,6 +14615,11 @@ export type writer = fn(s: stream, buf: []u8) (size | error);
|
|||||||
// ref/hare/io/types.ha:55.
|
// ref/hare/io/types.ha:55.
|
||||||
export type closer = fn(s: stream) (void | error);
|
export type closer = fn(s: stream) (void | error);
|
||||||
|
|
||||||
|
// ref/hare/io/types.ha:76. Hare's `fn(s: *stream, off: off, w: whence)`;
|
||||||
|
// ww's `stream` is already `*vtable` (the #94 collapse), so the param is
|
||||||
|
// `stream` directly, mirroring the reader/writer/closer aliases above.
|
||||||
|
export type seeker = fn(s: stream, off: off, w: whence) (off | error);
|
||||||
|
|
||||||
// memio — in-memory io stream. Project #94 fold-eFinal.
|
// memio — in-memory io stream. Project #94 fold-eFinal.
|
||||||
//
|
//
|
||||||
// Hare's memio:: surface, drop underscores. Two flavours behind a
|
// Hare's memio:: surface, drop underscores. Two flavours behind a
|
||||||
|
|||||||
378
test/wcc/791_io_handle_run.c
Normal file
378
test/wcc/791_io_handle_run.c
Normal file
@@ -0,0 +1,378 @@
|
|||||||
|
/*
|
||||||
|
* 791_io_handle_run — project #5 commit-1 sentinel. Pins the lib/io
|
||||||
|
* handle dispatch surface: `handle = (file | stream)` and the
|
||||||
|
* read/write/close/seek/tell dispatchers (ref/hare/io/handle.ha:16-67)
|
||||||
|
* over BOTH arms:
|
||||||
|
*
|
||||||
|
* - file-arm: a real OS fd (io.file = i32) routed to
|
||||||
|
* os.{read,write,lseek,close} — the Q2 layering (lib/io imports os,
|
||||||
|
* os is the import floor). The file-arm syscall-error path returns
|
||||||
|
* the #199b errors.unsupported placeholder (see lib/io/types.ww:40),
|
||||||
|
* pinned by the file_arm_error_stub row (a never-opened fd forces
|
||||||
|
* EBADF on every file-arm dispatcher).
|
||||||
|
* - stream-arm: a memio (or hand-built) vtable routed to the private
|
||||||
|
* st_read/st_write/st_seek (ref/hare/io/stream.ha:44-77).
|
||||||
|
*
|
||||||
|
* row | what it pins
|
||||||
|
* -------------------------+--------------------------------------
|
||||||
|
* file_arm_roundtrip | open(/tmp) → io.write "ABC" → io.seek
|
||||||
|
* | SET 0 → io.read back → io.seek END == 3
|
||||||
|
* | → io.tell == 3 → io.close. Real fd
|
||||||
|
* | through every file-arm dispatcher;
|
||||||
|
* | SET+END+CUR(via tell) whence. exit 42.
|
||||||
|
* stream_write_read | memio.fixed: io.write 3 bytes, verify
|
||||||
|
* | the backing buffer, then read them back
|
||||||
|
* | through io.read on a fresh fixed stream.
|
||||||
|
* | exit 43.
|
||||||
|
* stream_seek_unsupported | memio wires no seeker slot; io.seek
|
||||||
|
* | takes st_seek's void-arm and returns an
|
||||||
|
* | io.error (not an off). exit 44.
|
||||||
|
* stream_seeker_tell | hand-built vtable WITH a seeker fn:
|
||||||
|
* | io.seek returns the seeker's off, and
|
||||||
|
* | io.tell == seek(s, 0, CUR). exit 45.
|
||||||
|
* file_arm_error_stub | never-opened fd (9999) → every file-arm
|
||||||
|
* | dispatcher's syscall returns -1 (EBADF)
|
||||||
|
* | → the #199b errors.unsupported stub
|
||||||
|
* | comes back as io.error. exit 46.
|
||||||
|
*
|
||||||
|
* Both stages run each row; cs.s == ww.s byte-id is asserted per row
|
||||||
|
* (rule-10). GATE POLARITY: must stay GREEN — a red means the handle
|
||||||
|
* union decompose, a dispatcher arm, or the seeker slot regressed.
|
||||||
|
*/
|
||||||
|
#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[] = {
|
||||||
|
{ "file_arm_roundtrip",
|
||||||
|
"package main;\n"
|
||||||
|
"import io;\n"
|
||||||
|
"import os;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let cflags: os.flag = os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC;\n"
|
||||||
|
" let fd: i32 = 0;\n"
|
||||||
|
" match (os.tryopen(\"/tmp/ww_io791_handle.dat\", cflags, 420i32)) {\n"
|
||||||
|
" case let e: os.oserror => return 81;\n"
|
||||||
|
" case let f: i32 => fd = f;\n"
|
||||||
|
" };\n"
|
||||||
|
" let h: io.file = fd;\n"
|
||||||
|
" let data: [3]u8;\n"
|
||||||
|
" data[0] = 65u8; data[1] = 66u8; data[2] = 67u8;\n"
|
||||||
|
" match (io.write(h, data[0:3])) {\n"
|
||||||
|
" case let n: size => { if (n != 3: size) { return 82; }; };\n"
|
||||||
|
" case let e: io.error => return 83;\n"
|
||||||
|
" };\n"
|
||||||
|
" match (io.seek(h, 0: io.off, io.whence.SET)) {\n"
|
||||||
|
" case let o: io.off => { if (o != 0: io.off) { return 84; }; };\n"
|
||||||
|
" case let e: io.error => return 85;\n"
|
||||||
|
" };\n"
|
||||||
|
" let rb: [3]u8;\n"
|
||||||
|
" match (io.read(h, rb[0:3])) {\n"
|
||||||
|
" case let n: size => { if (n != 3: size) { return 86; }; };\n"
|
||||||
|
" case let z: io.eof => return 87;\n"
|
||||||
|
" case let e: io.error => return 88;\n"
|
||||||
|
" };\n"
|
||||||
|
" if (rb[0] != 65u8 || rb[1] != 66u8 || rb[2] != 67u8) { return 89; };\n"
|
||||||
|
" match (io.seek(h, 0: io.off, io.whence.END)) {\n"
|
||||||
|
" case let o: io.off => { if (o != 3: io.off) { return 93; }; };\n"
|
||||||
|
" case let e: io.error => return 94;\n"
|
||||||
|
" };\n"
|
||||||
|
" match (io.tell(h)) {\n"
|
||||||
|
" case let o: io.off => { if (o != 3: io.off) { return 90; }; };\n"
|
||||||
|
" case let e: io.error => return 91;\n"
|
||||||
|
" };\n"
|
||||||
|
" match (io.close(h)) {\n"
|
||||||
|
" case void => void;\n"
|
||||||
|
" case let e: io.error => return 92;\n"
|
||||||
|
" };\n"
|
||||||
|
" return 42;\n"
|
||||||
|
"};\n",
|
||||||
|
42,
|
||||||
|
STAGE_CS | STAGE_WW, 1 },
|
||||||
|
{ "stream_write_read",
|
||||||
|
"package main;\n"
|
||||||
|
"import io;\n"
|
||||||
|
"import memio;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let buf: [8]u8;\n"
|
||||||
|
" let st: memio.stream = memio.fixed(buf[0:8]);\n"
|
||||||
|
" let s: io.stream = &st.vt;\n"
|
||||||
|
" let data: [3]u8;\n"
|
||||||
|
" data[0] = 88u8; data[1] = 89u8; data[2] = 90u8;\n"
|
||||||
|
" match (io.write(s, data[0:3])) {\n"
|
||||||
|
" case let n: size => { if (n != 3: size) { return 71; }; };\n"
|
||||||
|
" case let e: io.error => return 72;\n"
|
||||||
|
" };\n"
|
||||||
|
" if (buf[0] != 88u8 || buf[1] != 89u8 || buf[2] != 90u8) { return 73; };\n"
|
||||||
|
" let st2: memio.stream = memio.fixed(buf[0:8]);\n"
|
||||||
|
" let s2: io.stream = &st2.vt;\n"
|
||||||
|
" let rb: [3]u8;\n"
|
||||||
|
" match (io.read(s2, rb[0:3])) {\n"
|
||||||
|
" case let n: size => { if (n != 3: size) { return 74; }; };\n"
|
||||||
|
" case let z: io.eof => return 75;\n"
|
||||||
|
" case let e: io.error => return 76;\n"
|
||||||
|
" };\n"
|
||||||
|
" if (rb[0] != 88u8 || rb[1] != 89u8 || rb[2] != 90u8) { return 77; };\n"
|
||||||
|
" return 43;\n"
|
||||||
|
"};\n",
|
||||||
|
43,
|
||||||
|
STAGE_CS | STAGE_WW, 1 },
|
||||||
|
{ "stream_seek_unsupported",
|
||||||
|
"package main;\n"
|
||||||
|
"import io;\n"
|
||||||
|
"import memio;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let buf: [4]u8;\n"
|
||||||
|
" let st: memio.stream = memio.fixed(buf[0:4]);\n"
|
||||||
|
" let s: io.stream = &st.vt;\n"
|
||||||
|
" match (io.seek(s, 0: io.off, io.whence.SET)) {\n"
|
||||||
|
" case let o: io.off => return 61;\n"
|
||||||
|
" case let e: io.error => return 44;\n"
|
||||||
|
" };\n"
|
||||||
|
"};\n",
|
||||||
|
44,
|
||||||
|
STAGE_CS | STAGE_WW, 1 },
|
||||||
|
{ "stream_seeker_tell",
|
||||||
|
"package main;\n"
|
||||||
|
"import io;\n"
|
||||||
|
"fn myseek(s: io.stream, off: io.off, w: io.whence) (io.off | io.error) = {\n"
|
||||||
|
" return off + 100: io.off;\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let vt: io.vtable;\n"
|
||||||
|
" vt.seeker = (&myseek): *io.seeker;\n"
|
||||||
|
" let s: io.stream = &vt;\n"
|
||||||
|
" match (io.seek(s, 5: io.off, io.whence.SET)) {\n"
|
||||||
|
" case let o: io.off => { if (o != 105: io.off) { return 51; }; };\n"
|
||||||
|
" case let e: io.error => return 52;\n"
|
||||||
|
" };\n"
|
||||||
|
" match (io.tell(s)) {\n"
|
||||||
|
" case let o: io.off => { if (o != 100: io.off) { return 53; }; };\n"
|
||||||
|
" case let e: io.error => return 54;\n"
|
||||||
|
" };\n"
|
||||||
|
" return 45;\n"
|
||||||
|
"};\n",
|
||||||
|
45,
|
||||||
|
STAGE_CS | STAGE_WW, 1 },
|
||||||
|
{ "file_arm_error_stub",
|
||||||
|
"package main;\n"
|
||||||
|
"import io;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
/* A never-opened fd: every file-arm syscall returns -1 (EBADF),
|
||||||
|
* driving the #199b errors.unsupported stub on all four
|
||||||
|
* dispatchers (write/read/seek/close). Since that stub is the
|
||||||
|
* only error-producing path reachable here, an io.error arm
|
||||||
|
* taken == the placeholder came back. */
|
||||||
|
" let h: io.file = 9999i32;\n"
|
||||||
|
" let data: [3]u8;\n"
|
||||||
|
" data[0] = 65u8; data[1] = 66u8; data[2] = 67u8;\n"
|
||||||
|
" match (io.write(h, data[0:3])) {\n"
|
||||||
|
" case let n: size => return 61;\n"
|
||||||
|
" case let e: io.error => void;\n"
|
||||||
|
" };\n"
|
||||||
|
" match (io.read(h, data[0:3])) {\n"
|
||||||
|
" case let n: size => return 62;\n"
|
||||||
|
" case let z: io.eof => return 63;\n"
|
||||||
|
" case let e: io.error => void;\n"
|
||||||
|
" };\n"
|
||||||
|
" match (io.seek(h, 0: io.off, io.whence.SET)) {\n"
|
||||||
|
" case let o: io.off => return 64;\n"
|
||||||
|
" case let e: io.error => void;\n"
|
||||||
|
" };\n"
|
||||||
|
" match (io.close(h)) {\n"
|
||||||
|
" case void => return 65;\n"
|
||||||
|
" case let e: io.error => void;\n"
|
||||||
|
" };\n"
|
||||||
|
" return 46;\n"
|
||||||
|
"};\n",
|
||||||
|
46,
|
||||||
|
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/ioh_%d_d_%d", getpid(), seq);
|
||||||
|
snprintf(base, sizeof base, "main791");
|
||||||
|
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/ioh_%d_c_%d", getpid(), seq);
|
||||||
|
snprintf(tdw, sizeof tdw, "/tmp/ioh_%d_w_%d", getpid(), seq);
|
||||||
|
snprintf(base, sizeof base, "main791");
|
||||||
|
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_handle_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_handle_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_handle_run[byte-id][%s]: cstage vs wwstage asm differs\n",
|
||||||
|
rows[i].label);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!wwpresent)
|
||||||
|
fprintf(stderr, "io_handle_run: skip wwstage (no %s)\n", wdrv);
|
||||||
|
|
||||||
|
if (fail) {
|
||||||
|
fprintf(stderr, "io_handle_run: %d/%d fixtures failed\n",
|
||||||
|
fail, total);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("io_handle_run: %d/%d ok\n", total, total);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user