lib: port errors.errno + os errno/strerror sys-layer (#6)
Hare-faithful port of errors::errno (ref/hare/errors/{rt,common,opaque}.ha): the 13 named common error conditions, opaque_data/opaque_ (the type-erased tail whose strerror fn-ptr defers to os.strerror), and errno(os.errno) error mapping the ~12 mapped errnos to named conditions and wrapping the unmapped tail in opaque_. The raw errno type (!i32, kernel-int width, distinct from oserror's !i64 negative raw return), the E* constants, and the strerror message table live in lib/os: ww folds Hare's sys role into os, so os is the import floor that lib/io and lib/errors build on -- documented in lib/CLAUDE.md (os never imports io or errors). errors.error is explicitly enumerated, matching Hare; the ...errors::error spread is only io.error's (blocked by #199b). Prereq for post-eFinal #5's faithful io error mapping; retires the nomem-collapse interim. Adds errnotest (mapping / opaque-tail / strerror) + test/wcc/902_errno_run. Landing required two wwstage cgen fixes (#9 struct-variant-large-union return, #11 deref-store alias narrow). Divergences cited at-site: bare-type-name return -> let+return; switch fall-through vs Hare's exhaustiveness-only default; opaque_ const dropped.
This commit is contained in:
5
Makefile
5
Makefile
@@ -353,6 +353,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_hex_run $(BIN)/test_utf8_run $(BIN)/test_bytes_run \
|
||||
$(BIN)/test_decimal_run $(BIN)/test_stof_run $(BIN)/test_ftos_run \
|
||||
$(BIN)/test_memio_run $(BIN)/test_temp_run $(BIN)/test_getopt_run \
|
||||
$(BIN)/test_errno_run \
|
||||
$(BIN)/test_base32_run $(BIN)/test_base64_run \
|
||||
$(BIN)/test_adler32_run $(BIN)/test_crc16_run \
|
||||
$(BIN)/test_crc32_run $(BIN)/test_crc64_run \
|
||||
@@ -1306,6 +1307,10 @@ $(BIN)/test_getopt_run: test/wcc/982_getopt_run.c $(BIN)/ww $(BIN)/w6c \
|
||||
$(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_errno_run: test/wcc/902_errno_run.c $(BIN)/ww $(BIN)/w6c \
|
||||
$(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_base32_run: test/wcc/983_base32_run.c $(BIN)/ww $(BIN)/w6c \
|
||||
$(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
@@ -53,7 +53,12 @@ Modules with intentional divergence:
|
||||
- `lib/os` and `lib/net` stay below the Hare abstraction — they are
|
||||
syscall wrappers, not the high-level `io::handle` / `net::socket`
|
||||
API. Use them as the foundation that `lib/io` and the buffered
|
||||
layers build on.
|
||||
layers build on. `lib/os` also plays Hare's `sys` role (ww folds
|
||||
`sys` into `os`), so it is the import floor: lib/os must never import
|
||||
io OR errors — everything points down to os; os's only edge is the
|
||||
import-free time leaf. This is what lets `errors` import `os` (for
|
||||
`os.errno` / `os.strerror`) without a cycle, mirroring Hare's
|
||||
`sys ← errors`, `sys ← io`.
|
||||
- `lib/io` keeps the ww-specific `stream` struct (vtable of fn
|
||||
pointers, no closures, no methods). The Hare `io::handle` family
|
||||
needs language features we don't have yet.
|
||||
|
||||
123
lib/errors/errnotest.ww
Normal file
123
lib/errors/errnotest.ww
Normal file
@@ -0,0 +1,123 @@
|
||||
// errnotest — exercises [[errors.errno]] and the [[errors.opaque_]]
|
||||
// tail. Run with `out/bin/ww run lib/errors/errnotest.ww`.
|
||||
//
|
||||
// Parallel `[N]T` arrays of (errno, expected-tag) rather than a
|
||||
// `[N]struct{...}` table — the cstage cgen's chained `arr[i].field`
|
||||
// store gap (task #6). [[errtag]] collapses each named-void variant to
|
||||
// a small int so the row body is a single integer compare.
|
||||
|
||||
package errors;
|
||||
|
||||
import errors;
|
||||
import os;
|
||||
|
||||
@symbol("rt_syscall") fn syscall1ww(num: i64, a: i64) i64;
|
||||
fn doexit(code: i32) void = {
|
||||
syscall1ww(60i64, code: i64);
|
||||
};
|
||||
|
||||
// signalled — bumped by main before each test so a failing exit code
|
||||
// pinpoints the offending case.
|
||||
let signalled: i32 = 0;
|
||||
fn fail() void = { doexit(signalled + 10); };
|
||||
|
||||
fn streq(a: str, b: str) bool = {
|
||||
if (a.len != b.len) { return false; };
|
||||
let i: i32 = 0;
|
||||
for (i < a.len) {
|
||||
if (a[i] != b[i]) { return false; };
|
||||
i += 1;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
// errtag — the union discriminant as a stable int. opaque_ is 99 so a
|
||||
// misrouted named condition can't masquerade as the unmapped tail.
|
||||
fn errtag(e: errors.error) int = {
|
||||
match (e) {
|
||||
case errors.busy => return 1;
|
||||
case errors.exists => return 2;
|
||||
case errors.invalid => return 3;
|
||||
case errors.noaccess => return 4;
|
||||
case errors.noentry => return 5;
|
||||
case errors.overflow => return 6;
|
||||
case errors.unsupported => return 7;
|
||||
case errors.timeout => return 8;
|
||||
case errors.cancelled => return 9;
|
||||
case errors.refused => return 10;
|
||||
case errors.interrupted => return 11;
|
||||
case errors.again => return 12;
|
||||
case errors.netunreachable => return 13;
|
||||
case let o: errors.opaque_ => return 99;
|
||||
};
|
||||
};
|
||||
|
||||
// ---- errno → named condition --------------------------------------
|
||||
@test fn mapping() void = {
|
||||
let ins: [12]os.errno;
|
||||
ins[0] = os.ECONNREFUSED;
|
||||
ins[1] = os.ECANCELED;
|
||||
ins[2] = os.EOVERFLOW;
|
||||
ins[3] = os.EACCES;
|
||||
ins[4] = os.EINVAL;
|
||||
ins[5] = os.EEXIST;
|
||||
ins[6] = os.ENOENT;
|
||||
ins[7] = os.ETIMEDOUT;
|
||||
ins[8] = os.EBUSY;
|
||||
ins[9] = os.EINTR;
|
||||
ins[10] = os.EAGAIN;
|
||||
ins[11] = os.ENETUNREACH;
|
||||
|
||||
let exp: [12]int;
|
||||
exp[0] = 10; // refused
|
||||
exp[1] = 9; // cancelled
|
||||
exp[2] = 6; // overflow
|
||||
exp[3] = 4; // noaccess
|
||||
exp[4] = 3; // invalid
|
||||
exp[5] = 2; // exists
|
||||
exp[6] = 5; // noentry
|
||||
exp[7] = 8; // timeout
|
||||
exp[8] = 1; // busy
|
||||
exp[9] = 11; // interrupted
|
||||
exp[10] = 12; // again
|
||||
exp[11] = 13; // netunreachable
|
||||
|
||||
let i: i32 = 0;
|
||||
for (i < 12) {
|
||||
// store-to-local, not inline errtag(errors.errno(ins[i])): the
|
||||
// inline form hits #222 (large >4-eightbyte union sret-arg
|
||||
// clobber, cs==ww). store-then-consume is the idiomatic shape
|
||||
// regardless — it is how errno is actually used (cf
|
||||
// lib/io/stream.ww:53, and Hare's errors/rt.ha callers).
|
||||
let er: errors.error = errors.errno(ins[i]);
|
||||
if (errtag(er) != exp[i]) { fail(); };
|
||||
i += 1;
|
||||
};
|
||||
};
|
||||
|
||||
// ---- unmapped errno → opaque_ tail --------------------------------
|
||||
@test fn opaquetail() void = {
|
||||
// EIO (5) is outside the mapped set, so it wraps opaque_.
|
||||
let e: errors.error = errors.errno(5);
|
||||
if (errtag(e) != 99) { fail(); };
|
||||
match (e) {
|
||||
case let o: errors.opaque_ =>
|
||||
if (!streq((*o.strerror)(&o.data), "Unknown error")) { fail(); };
|
||||
case =>
|
||||
fail();
|
||||
};
|
||||
};
|
||||
|
||||
// ---- os.strerror mapped path --------------------------------------
|
||||
@test fn strerrortext() void = {
|
||||
if (!streq(os.strerror(os.ENOENT), "No such file or directory")) { fail(); };
|
||||
if (!streq(os.strerror(os.EINVAL), "Invalid argument")) { fail(); };
|
||||
if (!streq(os.strerror(5), "Unknown error")) { fail(); };
|
||||
};
|
||||
|
||||
export fn main() i32 = {
|
||||
signalled = 1; mapping();
|
||||
signalled = 2; opaquetail();
|
||||
signalled = 3; strerrortext();
|
||||
return 0;
|
||||
};
|
||||
@@ -3,15 +3,23 @@
|
||||
// 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.
|
||||
// ...) live in their own modules; this module ships the generic
|
||||
// conditions Hare's errors:: exports plus the [[errno]] bridge that
|
||||
// wraps a raw [[os.errno]] into a portable [[error]].
|
||||
|
||||
// A function was called with an invalid combination of arguments.
|
||||
package errors;
|
||||
|
||||
import os;
|
||||
|
||||
// The named conditions, ordered per ref/hare/errors/common.ha.
|
||||
|
||||
// The requested resource is not available.
|
||||
export type busy = !void;
|
||||
|
||||
// An attempt was made to create a resource which already exists.
|
||||
export type exists = !void;
|
||||
|
||||
// A function was called with an invalid combination of arguments.
|
||||
export type invalid = !void;
|
||||
|
||||
// The user does not have permission to use this resource.
|
||||
@@ -20,8 +28,108 @@ 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 caused a numeric overflow condition.
|
||||
export type overflow = !void;
|
||||
|
||||
// The requested operation is not supported.
|
||||
export type unsupported = !void;
|
||||
|
||||
// The requested operation timed out.
|
||||
export type timeout = !void;
|
||||
|
||||
// The requested operation was cancelled.
|
||||
export type cancelled = !void;
|
||||
|
||||
// A connection attempt was refused.
|
||||
export type refused = !void;
|
||||
|
||||
// An operation was interrupted.
|
||||
export type interrupted = !void;
|
||||
|
||||
// The user should attempt an operation again.
|
||||
export type again = !void;
|
||||
|
||||
// Network unreachable
|
||||
export type netunreachable = !void;
|
||||
|
||||
// Up to 24 bytes of arbitrary, 8-byte-aligned storage for the opaque
|
||||
// error type's domain-specific data. ref/hare/errors/opaque.ha:41.
|
||||
export type opaque_data = [3]u64;
|
||||
|
||||
// An "opaque" error wraps an implementation-specific underlying error
|
||||
// behind a function that stringifies it plus a small storage area.
|
||||
// ref/hare/errors/opaque.ha:32. The `strerror` field keeps Hare's `*fn`
|
||||
// pointer (filled via `(&fn): *fn(...)`, mirroring io's `*reader` slot,
|
||||
// lib/io/types.ww:53); only Hare's `const` is dropped (ww has none, cf
|
||||
// lib/io/types.ww:57).
|
||||
export type opaque_ = !struct {
|
||||
strerror: *fn(op: *opaque_data) str,
|
||||
data: opaque_data,
|
||||
};
|
||||
|
||||
// A tagged union of all error types. ref/hare/errors/common.ha:43.
|
||||
// Enumerated explicitly rather than spread via Hare's `...error` —
|
||||
// ww's spread-flatten layout is the deferred #199b / #204 fix.
|
||||
export type error = !(
|
||||
busy |
|
||||
exists |
|
||||
invalid |
|
||||
noaccess |
|
||||
noentry |
|
||||
overflow |
|
||||
unsupported |
|
||||
timeout |
|
||||
cancelled |
|
||||
refused |
|
||||
interrupted |
|
||||
again |
|
||||
netunreachable |
|
||||
opaque_
|
||||
);
|
||||
|
||||
// Wraps an [[os.errno]] to produce an [[error]], which may be
|
||||
// [[opaque_]]. ref/hare/errors/rt.ha:9. The mapped errnos become named
|
||||
// conditions; the unmapped tail is carried in an [[opaque_]] whose
|
||||
// stringifier defers to [[os.strerror]].
|
||||
export fn errno(e: os.errno) error = {
|
||||
// A `!void` condition is returned by instance, not by name: a bare
|
||||
// `return refused;` would emit an undefined symbol reference (the
|
||||
// type, not a value). cf lib/io/stream.ww:53-56. The instance
|
||||
// auto-widens to the [[error]] union on return.
|
||||
switch (e) {
|
||||
case os.ECONNREFUSED: { let r: refused; return r; };
|
||||
case os.ECANCELED: { let r: cancelled; return r; };
|
||||
case os.EOVERFLOW: { let r: overflow; return r; };
|
||||
case os.EACCES: { let r: noaccess; return r; };
|
||||
case os.EINVAL: { let r: invalid; return r; };
|
||||
case os.EEXIST: { let r: exists; return r; };
|
||||
case os.ENOENT: { let r: noentry; return r; };
|
||||
case os.ETIMEDOUT: { let r: timeout; return r; };
|
||||
case os.EBUSY: { let r: busy; return r; };
|
||||
case os.EINTR: { let r: interrupted; return r; };
|
||||
case os.EAGAIN: { let r: again; return r; };
|
||||
case os.ENETUNREACH: { let r: netunreachable; return r; };
|
||||
};
|
||||
|
||||
// An unmapped errno falls through the switch into the opaque_ wrap
|
||||
// below. ww switches aren't required exhaustive, so Hare's terminal
|
||||
// `case => void;` (rt.ha:24, present only to satisfy exhaustiveness)
|
||||
// is dropped rather than written as an explicit no-op default —
|
||||
// matching the sibling [[os.strerror]] fall-through.
|
||||
//
|
||||
// ww has no `static assert`; Hare guards size(errno) <=
|
||||
// size(opaque_data) there. The invariant holds structurally:
|
||||
// opaque_data is [3]u64 (24B), errno is one machine int.
|
||||
let err: opaque_;
|
||||
err.strerror = (&rt_strerror): *fn(op: *opaque_data) str;
|
||||
let ptr = (&err.data): *os.errno;
|
||||
*ptr = e;
|
||||
return err;
|
||||
};
|
||||
|
||||
// rt_strerror — the [[opaque_]] stringifier for an errno wrapped by
|
||||
// [[errno]]. ref/hare/errors/rt.ha:31.
|
||||
fn rt_strerror(op: *opaque_data) str = {
|
||||
let e = (op): *os.errno;
|
||||
return os.strerror(*e);
|
||||
};
|
||||
|
||||
51
lib/os/os.ww
51
lib/os/os.ww
@@ -167,6 +167,57 @@ export fn lseek(fd: i32, off: i64, w: whence) i64 = {
|
||||
// errors::errno carried inside io::error.
|
||||
export type oserror = !i64;
|
||||
|
||||
// errno — the raw Linux errno as a positive code (ref/hare/sys/+linux/
|
||||
// errno.ha:5, `errno = !int`). ww folds Hare's `sys` role into os
|
||||
// (lib/CLAUDE.md), so the sys::errno machinery lands here. Spelled i32
|
||||
// rather than int: Linux errnos are kernel ints (32-bit), keeping os's
|
||||
// kernel-facing surface uniformly i32. Distinct from [[oserror]] (!i64,
|
||||
// the syscall's *negative* raw return) — the two model different
|
||||
// things, so they are not unified; the negative→positive normalization
|
||||
// lives at the oserror→errors.error boundary in those callers.
|
||||
export type errno = !i32;
|
||||
|
||||
// Mapped errno values, ref/hare/sys/+linux/errno.ha:559-682. Positive,
|
||||
// matching Hare's defs (the kernel returns -N; the wrap-to-positive is
|
||||
// the caller's concern). Subset: exactly the errnos [[errors.errno]]
|
||||
// maps to a named condition; grow as callers surface more.
|
||||
export def ENOENT: errno = 2;
|
||||
export def EINTR: errno = 4;
|
||||
export def EAGAIN: errno = 11;
|
||||
export def EACCES: errno = 13;
|
||||
export def EBUSY: errno = 16;
|
||||
export def EEXIST: errno = 17;
|
||||
export def EINVAL: errno = 22;
|
||||
export def EOVERFLOW: errno = 75;
|
||||
export def ENETUNREACH: errno = 101;
|
||||
export def ETIMEDOUT: errno = 110;
|
||||
export def ECONNREFUSED: errno = 111;
|
||||
export def ECANCELED: errno = 125;
|
||||
|
||||
// strerror — human-readable text for an [[errno]] (Hare's
|
||||
// sys::strerror, ref/hare/sys/+linux/errno.ha:18). FAITHFUL MINIMAL
|
||||
// SUBSET: the mapped errnos above plus a generic fallback; grow the
|
||||
// switch as callers surface more (lib/CLAUDE.md documented-subset, not
|
||||
// a workaround). Messages verbatim from the reference. Hare's
|
||||
// unknown_errno formats the numeric value; that is deferred.
|
||||
export fn strerror(err: errno) str = {
|
||||
switch (err) {
|
||||
case ENOENT: return "No such file or directory";
|
||||
case EINTR: return "Interrupted system call";
|
||||
case EAGAIN: return "Resource temporarily unavailable";
|
||||
case EACCES: return "Permission denied";
|
||||
case EBUSY: return "Device or resource busy";
|
||||
case EEXIST: return "File exists";
|
||||
case EINVAL: return "Invalid argument";
|
||||
case EOVERFLOW: return "Value too large for defined data type";
|
||||
case ENETUNREACH: return "Network is unreachable";
|
||||
case ETIMEDOUT: return "Connection timed out";
|
||||
case ECONNREFUSED: return "Connection refused";
|
||||
case ECANCELED: return "Operation canceled";
|
||||
};
|
||||
return "Unknown error";
|
||||
};
|
||||
|
||||
// filesize — byte length of an open fd via lseek-to-end-and-back.
|
||||
export fn filesize(fd: i32) (i64 | oserror) = {
|
||||
let end: i64 = lseek(fd, 0i64, whence.END);
|
||||
|
||||
@@ -265,6 +265,57 @@ export fn lseek(fd: i32, off: i64, w: whence) i64 = {
|
||||
// errors::errno carried inside io::error.
|
||||
export type oserror = !i64;
|
||||
|
||||
// errno — the raw Linux errno as a positive code (ref/hare/sys/+linux/
|
||||
// errno.ha:5, `errno = !int`). ww folds Hare's `sys` role into os
|
||||
// (lib/CLAUDE.md), so the sys::errno machinery lands here. Spelled i32
|
||||
// rather than int: Linux errnos are kernel ints (32-bit), keeping os's
|
||||
// kernel-facing surface uniformly i32. Distinct from [[oserror]] (!i64,
|
||||
// the syscall's *negative* raw return) — the two model different
|
||||
// things, so they are not unified; the negative→positive normalization
|
||||
// lives at the oserror→errors.error boundary in those callers.
|
||||
export type errno = !i32;
|
||||
|
||||
// Mapped errno values, ref/hare/sys/+linux/errno.ha:559-682. Positive,
|
||||
// matching Hare's defs (the kernel returns -N; the wrap-to-positive is
|
||||
// the caller's concern). Subset: exactly the errnos [[errors.errno]]
|
||||
// maps to a named condition; grow as callers surface more.
|
||||
export def ENOENT: errno = 2;
|
||||
export def EINTR: errno = 4;
|
||||
export def EAGAIN: errno = 11;
|
||||
export def EACCES: errno = 13;
|
||||
export def EBUSY: errno = 16;
|
||||
export def EEXIST: errno = 17;
|
||||
export def EINVAL: errno = 22;
|
||||
export def EOVERFLOW: errno = 75;
|
||||
export def ENETUNREACH: errno = 101;
|
||||
export def ETIMEDOUT: errno = 110;
|
||||
export def ECONNREFUSED: errno = 111;
|
||||
export def ECANCELED: errno = 125;
|
||||
|
||||
// strerror — human-readable text for an [[errno]] (Hare's
|
||||
// sys::strerror, ref/hare/sys/+linux/errno.ha:18). FAITHFUL MINIMAL
|
||||
// SUBSET: the mapped errnos above plus a generic fallback; grow the
|
||||
// switch as callers surface more (lib/CLAUDE.md documented-subset, not
|
||||
// a workaround). Messages verbatim from the reference. Hare's
|
||||
// unknown_errno formats the numeric value; that is deferred.
|
||||
export fn strerror(err: errno) str = {
|
||||
switch (err) {
|
||||
case ENOENT: return "No such file or directory";
|
||||
case EINTR: return "Interrupted system call";
|
||||
case EAGAIN: return "Resource temporarily unavailable";
|
||||
case EACCES: return "Permission denied";
|
||||
case EBUSY: return "Device or resource busy";
|
||||
case EEXIST: return "File exists";
|
||||
case EINVAL: return "Invalid argument";
|
||||
case EOVERFLOW: return "Value too large for defined data type";
|
||||
case ENETUNREACH: return "Network is unreachable";
|
||||
case ETIMEDOUT: return "Connection timed out";
|
||||
case ECONNREFUSED: return "Connection refused";
|
||||
case ECANCELED: return "Operation canceled";
|
||||
};
|
||||
return "Unknown error";
|
||||
};
|
||||
|
||||
// filesize — byte length of an open fd via lseek-to-end-and-back.
|
||||
export fn filesize(fd: i32) (i64 | oserror) = {
|
||||
let end: i64 = lseek(fd, 0i64, whence.END);
|
||||
|
||||
@@ -265,6 +265,57 @@ export fn lseek(fd: i32, off: i64, w: whence) i64 = {
|
||||
// errors::errno carried inside io::error.
|
||||
export type oserror = !i64;
|
||||
|
||||
// errno — the raw Linux errno as a positive code (ref/hare/sys/+linux/
|
||||
// errno.ha:5, `errno = !int`). ww folds Hare's `sys` role into os
|
||||
// (lib/CLAUDE.md), so the sys::errno machinery lands here. Spelled i32
|
||||
// rather than int: Linux errnos are kernel ints (32-bit), keeping os's
|
||||
// kernel-facing surface uniformly i32. Distinct from [[oserror]] (!i64,
|
||||
// the syscall's *negative* raw return) — the two model different
|
||||
// things, so they are not unified; the negative→positive normalization
|
||||
// lives at the oserror→errors.error boundary in those callers.
|
||||
export type errno = !i32;
|
||||
|
||||
// Mapped errno values, ref/hare/sys/+linux/errno.ha:559-682. Positive,
|
||||
// matching Hare's defs (the kernel returns -N; the wrap-to-positive is
|
||||
// the caller's concern). Subset: exactly the errnos [[errors.errno]]
|
||||
// maps to a named condition; grow as callers surface more.
|
||||
export def ENOENT: errno = 2;
|
||||
export def EINTR: errno = 4;
|
||||
export def EAGAIN: errno = 11;
|
||||
export def EACCES: errno = 13;
|
||||
export def EBUSY: errno = 16;
|
||||
export def EEXIST: errno = 17;
|
||||
export def EINVAL: errno = 22;
|
||||
export def EOVERFLOW: errno = 75;
|
||||
export def ENETUNREACH: errno = 101;
|
||||
export def ETIMEDOUT: errno = 110;
|
||||
export def ECONNREFUSED: errno = 111;
|
||||
export def ECANCELED: errno = 125;
|
||||
|
||||
// strerror — human-readable text for an [[errno]] (Hare's
|
||||
// sys::strerror, ref/hare/sys/+linux/errno.ha:18). FAITHFUL MINIMAL
|
||||
// SUBSET: the mapped errnos above plus a generic fallback; grow the
|
||||
// switch as callers surface more (lib/CLAUDE.md documented-subset, not
|
||||
// a workaround). Messages verbatim from the reference. Hare's
|
||||
// unknown_errno formats the numeric value; that is deferred.
|
||||
export fn strerror(err: errno) str = {
|
||||
switch (err) {
|
||||
case ENOENT: return "No such file or directory";
|
||||
case EINTR: return "Interrupted system call";
|
||||
case EAGAIN: return "Resource temporarily unavailable";
|
||||
case EACCES: return "Permission denied";
|
||||
case EBUSY: return "Device or resource busy";
|
||||
case EEXIST: return "File exists";
|
||||
case EINVAL: return "Invalid argument";
|
||||
case EOVERFLOW: return "Value too large for defined data type";
|
||||
case ENETUNREACH: return "Network is unreachable";
|
||||
case ETIMEDOUT: return "Connection timed out";
|
||||
case ECONNREFUSED: return "Connection refused";
|
||||
case ECANCELED: return "Operation canceled";
|
||||
};
|
||||
return "Unknown error";
|
||||
};
|
||||
|
||||
// filesize — byte length of an open fd via lseek-to-end-and-back.
|
||||
export fn filesize(fd: i32) (i64 | oserror) = {
|
||||
let end: i64 = lseek(fd, 0i64, whence.END);
|
||||
@@ -14167,15 +14218,23 @@ export fn close(s: stream) (void | error) = {
|
||||
// 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.
|
||||
// ...) live in their own modules; this module ships the generic
|
||||
// conditions Hare's errors:: exports plus the [[errno]] bridge that
|
||||
// wraps a raw [[os.errno]] into a portable [[error]].
|
||||
|
||||
// A function was called with an invalid combination of arguments.
|
||||
package errors;
|
||||
|
||||
import os;
|
||||
|
||||
// The named conditions, ordered per ref/hare/errors/common.ha.
|
||||
|
||||
// The requested resource is not available.
|
||||
export type busy = !void;
|
||||
|
||||
// An attempt was made to create a resource which already exists.
|
||||
export type exists = !void;
|
||||
|
||||
// A function was called with an invalid combination of arguments.
|
||||
export type invalid = !void;
|
||||
|
||||
// The user does not have permission to use this resource.
|
||||
@@ -14184,12 +14243,112 @@ 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 caused a numeric overflow condition.
|
||||
export type overflow = !void;
|
||||
|
||||
// The requested operation is not supported.
|
||||
export type unsupported = !void;
|
||||
|
||||
// The requested operation timed out.
|
||||
export type timeout = !void;
|
||||
|
||||
// The requested operation was cancelled.
|
||||
export type cancelled = !void;
|
||||
|
||||
// A connection attempt was refused.
|
||||
export type refused = !void;
|
||||
|
||||
// An operation was interrupted.
|
||||
export type interrupted = !void;
|
||||
|
||||
// The user should attempt an operation again.
|
||||
export type again = !void;
|
||||
|
||||
// Network unreachable
|
||||
export type netunreachable = !void;
|
||||
|
||||
// Up to 24 bytes of arbitrary, 8-byte-aligned storage for the opaque
|
||||
// error type's domain-specific data. ref/hare/errors/opaque.ha:41.
|
||||
export type opaque_data = [3]u64;
|
||||
|
||||
// An "opaque" error wraps an implementation-specific underlying error
|
||||
// behind a function that stringifies it plus a small storage area.
|
||||
// ref/hare/errors/opaque.ha:32. The `strerror` field keeps Hare's `*fn`
|
||||
// pointer (filled via `(&fn): *fn(...)`, mirroring io's `*reader` slot,
|
||||
// lib/io/types.ww:53); only Hare's `const` is dropped (ww has none, cf
|
||||
// lib/io/types.ww:57).
|
||||
export type opaque_ = !struct {
|
||||
strerror: *fn(op: *opaque_data) str,
|
||||
data: opaque_data,
|
||||
};
|
||||
|
||||
// A tagged union of all error types. ref/hare/errors/common.ha:43.
|
||||
// Enumerated explicitly rather than spread via Hare's `...error` —
|
||||
// ww's spread-flatten layout is the deferred #199b / #204 fix.
|
||||
export type error = !(
|
||||
busy |
|
||||
exists |
|
||||
invalid |
|
||||
noaccess |
|
||||
noentry |
|
||||
overflow |
|
||||
unsupported |
|
||||
timeout |
|
||||
cancelled |
|
||||
refused |
|
||||
interrupted |
|
||||
again |
|
||||
netunreachable |
|
||||
opaque_
|
||||
);
|
||||
|
||||
// Wraps an [[os.errno]] to produce an [[error]], which may be
|
||||
// [[opaque_]]. ref/hare/errors/rt.ha:9. The mapped errnos become named
|
||||
// conditions; the unmapped tail is carried in an [[opaque_]] whose
|
||||
// stringifier defers to [[os.strerror]].
|
||||
export fn errno(e: os.errno) error = {
|
||||
// A `!void` condition is returned by instance, not by name: a bare
|
||||
// `return refused;` would emit an undefined symbol reference (the
|
||||
// type, not a value). cf lib/io/stream.ww:53-56. The instance
|
||||
// auto-widens to the [[error]] union on return.
|
||||
switch (e) {
|
||||
case os.ECONNREFUSED: { let r: refused; return r; };
|
||||
case os.ECANCELED: { let r: cancelled; return r; };
|
||||
case os.EOVERFLOW: { let r: overflow; return r; };
|
||||
case os.EACCES: { let r: noaccess; return r; };
|
||||
case os.EINVAL: { let r: invalid; return r; };
|
||||
case os.EEXIST: { let r: exists; return r; };
|
||||
case os.ENOENT: { let r: noentry; return r; };
|
||||
case os.ETIMEDOUT: { let r: timeout; return r; };
|
||||
case os.EBUSY: { let r: busy; return r; };
|
||||
case os.EINTR: { let r: interrupted; return r; };
|
||||
case os.EAGAIN: { let r: again; return r; };
|
||||
case os.ENETUNREACH: { let r: netunreachable; return r; };
|
||||
};
|
||||
|
||||
// An unmapped errno falls through the switch into the opaque_ wrap
|
||||
// below. ww switches aren't required exhaustive, so Hare's terminal
|
||||
// `case => void;` (rt.ha:24, present only to satisfy exhaustiveness)
|
||||
// is dropped rather than written as an explicit no-op default —
|
||||
// matching the sibling [[os.strerror]] fall-through.
|
||||
//
|
||||
// ww has no `static assert`; Hare guards size(errno) <=
|
||||
// size(opaque_data) there. The invariant holds structurally:
|
||||
// opaque_data is [3]u64 (24B), errno is one machine int.
|
||||
let err: opaque_;
|
||||
err.strerror = (&rt_strerror): *fn(op: *opaque_data) str;
|
||||
let ptr = (&err.data): *os.errno;
|
||||
*ptr = e;
|
||||
return err;
|
||||
};
|
||||
|
||||
// rt_strerror — the [[opaque_]] stringifier for an errno wrapped by
|
||||
// [[errno]]. ref/hare/errors/rt.ha:31.
|
||||
fn rt_strerror(op: *opaque_data) str = {
|
||||
let e = (op): *os.errno;
|
||||
return os.strerror(*e);
|
||||
};
|
||||
|
||||
// types — error union, mode/whence enums, reader/writer/closer
|
||||
// fn-type aliases. Project #94 fold-eFinal; the fn-aliases target
|
||||
// `stream` (= `*vtable`, the single io surface).
|
||||
|
||||
@@ -265,6 +265,57 @@ export fn lseek(fd: i32, off: i64, w: whence) i64 = {
|
||||
// errors::errno carried inside io::error.
|
||||
export type oserror = !i64;
|
||||
|
||||
// errno — the raw Linux errno as a positive code (ref/hare/sys/+linux/
|
||||
// errno.ha:5, `errno = !int`). ww folds Hare's `sys` role into os
|
||||
// (lib/CLAUDE.md), so the sys::errno machinery lands here. Spelled i32
|
||||
// rather than int: Linux errnos are kernel ints (32-bit), keeping os's
|
||||
// kernel-facing surface uniformly i32. Distinct from [[oserror]] (!i64,
|
||||
// the syscall's *negative* raw return) — the two model different
|
||||
// things, so they are not unified; the negative→positive normalization
|
||||
// lives at the oserror→errors.error boundary in those callers.
|
||||
export type errno = !i32;
|
||||
|
||||
// Mapped errno values, ref/hare/sys/+linux/errno.ha:559-682. Positive,
|
||||
// matching Hare's defs (the kernel returns -N; the wrap-to-positive is
|
||||
// the caller's concern). Subset: exactly the errnos [[errors.errno]]
|
||||
// maps to a named condition; grow as callers surface more.
|
||||
export def ENOENT: errno = 2;
|
||||
export def EINTR: errno = 4;
|
||||
export def EAGAIN: errno = 11;
|
||||
export def EACCES: errno = 13;
|
||||
export def EBUSY: errno = 16;
|
||||
export def EEXIST: errno = 17;
|
||||
export def EINVAL: errno = 22;
|
||||
export def EOVERFLOW: errno = 75;
|
||||
export def ENETUNREACH: errno = 101;
|
||||
export def ETIMEDOUT: errno = 110;
|
||||
export def ECONNREFUSED: errno = 111;
|
||||
export def ECANCELED: errno = 125;
|
||||
|
||||
// strerror — human-readable text for an [[errno]] (Hare's
|
||||
// sys::strerror, ref/hare/sys/+linux/errno.ha:18). FAITHFUL MINIMAL
|
||||
// SUBSET: the mapped errnos above plus a generic fallback; grow the
|
||||
// switch as callers surface more (lib/CLAUDE.md documented-subset, not
|
||||
// a workaround). Messages verbatim from the reference. Hare's
|
||||
// unknown_errno formats the numeric value; that is deferred.
|
||||
export fn strerror(err: errno) str = {
|
||||
switch (err) {
|
||||
case ENOENT: return "No such file or directory";
|
||||
case EINTR: return "Interrupted system call";
|
||||
case EAGAIN: return "Resource temporarily unavailable";
|
||||
case EACCES: return "Permission denied";
|
||||
case EBUSY: return "Device or resource busy";
|
||||
case EEXIST: return "File exists";
|
||||
case EINVAL: return "Invalid argument";
|
||||
case EOVERFLOW: return "Value too large for defined data type";
|
||||
case ENETUNREACH: return "Network is unreachable";
|
||||
case ETIMEDOUT: return "Connection timed out";
|
||||
case ECONNREFUSED: return "Connection refused";
|
||||
case ECANCELED: return "Operation canceled";
|
||||
};
|
||||
return "Unknown error";
|
||||
};
|
||||
|
||||
// filesize — byte length of an open fd via lseek-to-end-and-back.
|
||||
export fn filesize(fd: i32) (i64 | oserror) = {
|
||||
let end: i64 = lseek(fd, 0i64, whence.END);
|
||||
|
||||
@@ -265,6 +265,57 @@ export fn lseek(fd: i32, off: i64, w: whence) i64 = {
|
||||
// errors::errno carried inside io::error.
|
||||
export type oserror = !i64;
|
||||
|
||||
// errno — the raw Linux errno as a positive code (ref/hare/sys/+linux/
|
||||
// errno.ha:5, `errno = !int`). ww folds Hare's `sys` role into os
|
||||
// (lib/CLAUDE.md), so the sys::errno machinery lands here. Spelled i32
|
||||
// rather than int: Linux errnos are kernel ints (32-bit), keeping os's
|
||||
// kernel-facing surface uniformly i32. Distinct from [[oserror]] (!i64,
|
||||
// the syscall's *negative* raw return) — the two model different
|
||||
// things, so they are not unified; the negative→positive normalization
|
||||
// lives at the oserror→errors.error boundary in those callers.
|
||||
export type errno = !i32;
|
||||
|
||||
// Mapped errno values, ref/hare/sys/+linux/errno.ha:559-682. Positive,
|
||||
// matching Hare's defs (the kernel returns -N; the wrap-to-positive is
|
||||
// the caller's concern). Subset: exactly the errnos [[errors.errno]]
|
||||
// maps to a named condition; grow as callers surface more.
|
||||
export def ENOENT: errno = 2;
|
||||
export def EINTR: errno = 4;
|
||||
export def EAGAIN: errno = 11;
|
||||
export def EACCES: errno = 13;
|
||||
export def EBUSY: errno = 16;
|
||||
export def EEXIST: errno = 17;
|
||||
export def EINVAL: errno = 22;
|
||||
export def EOVERFLOW: errno = 75;
|
||||
export def ENETUNREACH: errno = 101;
|
||||
export def ETIMEDOUT: errno = 110;
|
||||
export def ECONNREFUSED: errno = 111;
|
||||
export def ECANCELED: errno = 125;
|
||||
|
||||
// strerror — human-readable text for an [[errno]] (Hare's
|
||||
// sys::strerror, ref/hare/sys/+linux/errno.ha:18). FAITHFUL MINIMAL
|
||||
// SUBSET: the mapped errnos above plus a generic fallback; grow the
|
||||
// switch as callers surface more (lib/CLAUDE.md documented-subset, not
|
||||
// a workaround). Messages verbatim from the reference. Hare's
|
||||
// unknown_errno formats the numeric value; that is deferred.
|
||||
export fn strerror(err: errno) str = {
|
||||
switch (err) {
|
||||
case ENOENT: return "No such file or directory";
|
||||
case EINTR: return "Interrupted system call";
|
||||
case EAGAIN: return "Resource temporarily unavailable";
|
||||
case EACCES: return "Permission denied";
|
||||
case EBUSY: return "Device or resource busy";
|
||||
case EEXIST: return "File exists";
|
||||
case EINVAL: return "Invalid argument";
|
||||
case EOVERFLOW: return "Value too large for defined data type";
|
||||
case ENETUNREACH: return "Network is unreachable";
|
||||
case ETIMEDOUT: return "Connection timed out";
|
||||
case ECONNREFUSED: return "Connection refused";
|
||||
case ECANCELED: return "Operation canceled";
|
||||
};
|
||||
return "Unknown error";
|
||||
};
|
||||
|
||||
// filesize — byte length of an open fd via lseek-to-end-and-back.
|
||||
export fn filesize(fd: i32) (i64 | oserror) = {
|
||||
let end: i64 = lseek(fd, 0i64, whence.END);
|
||||
|
||||
@@ -265,6 +265,57 @@ export fn lseek(fd: i32, off: i64, w: whence) i64 = {
|
||||
// errors::errno carried inside io::error.
|
||||
export type oserror = !i64;
|
||||
|
||||
// errno — the raw Linux errno as a positive code (ref/hare/sys/+linux/
|
||||
// errno.ha:5, `errno = !int`). ww folds Hare's `sys` role into os
|
||||
// (lib/CLAUDE.md), so the sys::errno machinery lands here. Spelled i32
|
||||
// rather than int: Linux errnos are kernel ints (32-bit), keeping os's
|
||||
// kernel-facing surface uniformly i32. Distinct from [[oserror]] (!i64,
|
||||
// the syscall's *negative* raw return) — the two model different
|
||||
// things, so they are not unified; the negative→positive normalization
|
||||
// lives at the oserror→errors.error boundary in those callers.
|
||||
export type errno = !i32;
|
||||
|
||||
// Mapped errno values, ref/hare/sys/+linux/errno.ha:559-682. Positive,
|
||||
// matching Hare's defs (the kernel returns -N; the wrap-to-positive is
|
||||
// the caller's concern). Subset: exactly the errnos [[errors.errno]]
|
||||
// maps to a named condition; grow as callers surface more.
|
||||
export def ENOENT: errno = 2;
|
||||
export def EINTR: errno = 4;
|
||||
export def EAGAIN: errno = 11;
|
||||
export def EACCES: errno = 13;
|
||||
export def EBUSY: errno = 16;
|
||||
export def EEXIST: errno = 17;
|
||||
export def EINVAL: errno = 22;
|
||||
export def EOVERFLOW: errno = 75;
|
||||
export def ENETUNREACH: errno = 101;
|
||||
export def ETIMEDOUT: errno = 110;
|
||||
export def ECONNREFUSED: errno = 111;
|
||||
export def ECANCELED: errno = 125;
|
||||
|
||||
// strerror — human-readable text for an [[errno]] (Hare's
|
||||
// sys::strerror, ref/hare/sys/+linux/errno.ha:18). FAITHFUL MINIMAL
|
||||
// SUBSET: the mapped errnos above plus a generic fallback; grow the
|
||||
// switch as callers surface more (lib/CLAUDE.md documented-subset, not
|
||||
// a workaround). Messages verbatim from the reference. Hare's
|
||||
// unknown_errno formats the numeric value; that is deferred.
|
||||
export fn strerror(err: errno) str = {
|
||||
switch (err) {
|
||||
case ENOENT: return "No such file or directory";
|
||||
case EINTR: return "Interrupted system call";
|
||||
case EAGAIN: return "Resource temporarily unavailable";
|
||||
case EACCES: return "Permission denied";
|
||||
case EBUSY: return "Device or resource busy";
|
||||
case EEXIST: return "File exists";
|
||||
case EINVAL: return "Invalid argument";
|
||||
case EOVERFLOW: return "Value too large for defined data type";
|
||||
case ENETUNREACH: return "Network is unreachable";
|
||||
case ETIMEDOUT: return "Connection timed out";
|
||||
case ECONNREFUSED: return "Connection refused";
|
||||
case ECANCELED: return "Operation canceled";
|
||||
};
|
||||
return "Unknown error";
|
||||
};
|
||||
|
||||
// filesize — byte length of an open fd via lseek-to-end-and-back.
|
||||
export fn filesize(fd: i32) (i64 | oserror) = {
|
||||
let end: i64 = lseek(fd, 0i64, whence.END);
|
||||
@@ -14167,15 +14218,23 @@ export fn close(s: stream) (void | error) = {
|
||||
// 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.
|
||||
// ...) live in their own modules; this module ships the generic
|
||||
// conditions Hare's errors:: exports plus the [[errno]] bridge that
|
||||
// wraps a raw [[os.errno]] into a portable [[error]].
|
||||
|
||||
// A function was called with an invalid combination of arguments.
|
||||
package errors;
|
||||
|
||||
import os;
|
||||
|
||||
// The named conditions, ordered per ref/hare/errors/common.ha.
|
||||
|
||||
// The requested resource is not available.
|
||||
export type busy = !void;
|
||||
|
||||
// An attempt was made to create a resource which already exists.
|
||||
export type exists = !void;
|
||||
|
||||
// A function was called with an invalid combination of arguments.
|
||||
export type invalid = !void;
|
||||
|
||||
// The user does not have permission to use this resource.
|
||||
@@ -14184,12 +14243,112 @@ 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 caused a numeric overflow condition.
|
||||
export type overflow = !void;
|
||||
|
||||
// The requested operation is not supported.
|
||||
export type unsupported = !void;
|
||||
|
||||
// The requested operation timed out.
|
||||
export type timeout = !void;
|
||||
|
||||
// The requested operation was cancelled.
|
||||
export type cancelled = !void;
|
||||
|
||||
// A connection attempt was refused.
|
||||
export type refused = !void;
|
||||
|
||||
// An operation was interrupted.
|
||||
export type interrupted = !void;
|
||||
|
||||
// The user should attempt an operation again.
|
||||
export type again = !void;
|
||||
|
||||
// Network unreachable
|
||||
export type netunreachable = !void;
|
||||
|
||||
// Up to 24 bytes of arbitrary, 8-byte-aligned storage for the opaque
|
||||
// error type's domain-specific data. ref/hare/errors/opaque.ha:41.
|
||||
export type opaque_data = [3]u64;
|
||||
|
||||
// An "opaque" error wraps an implementation-specific underlying error
|
||||
// behind a function that stringifies it plus a small storage area.
|
||||
// ref/hare/errors/opaque.ha:32. The `strerror` field keeps Hare's `*fn`
|
||||
// pointer (filled via `(&fn): *fn(...)`, mirroring io's `*reader` slot,
|
||||
// lib/io/types.ww:53); only Hare's `const` is dropped (ww has none, cf
|
||||
// lib/io/types.ww:57).
|
||||
export type opaque_ = !struct {
|
||||
strerror: *fn(op: *opaque_data) str,
|
||||
data: opaque_data,
|
||||
};
|
||||
|
||||
// A tagged union of all error types. ref/hare/errors/common.ha:43.
|
||||
// Enumerated explicitly rather than spread via Hare's `...error` —
|
||||
// ww's spread-flatten layout is the deferred #199b / #204 fix.
|
||||
export type error = !(
|
||||
busy |
|
||||
exists |
|
||||
invalid |
|
||||
noaccess |
|
||||
noentry |
|
||||
overflow |
|
||||
unsupported |
|
||||
timeout |
|
||||
cancelled |
|
||||
refused |
|
||||
interrupted |
|
||||
again |
|
||||
netunreachable |
|
||||
opaque_
|
||||
);
|
||||
|
||||
// Wraps an [[os.errno]] to produce an [[error]], which may be
|
||||
// [[opaque_]]. ref/hare/errors/rt.ha:9. The mapped errnos become named
|
||||
// conditions; the unmapped tail is carried in an [[opaque_]] whose
|
||||
// stringifier defers to [[os.strerror]].
|
||||
export fn errno(e: os.errno) error = {
|
||||
// A `!void` condition is returned by instance, not by name: a bare
|
||||
// `return refused;` would emit an undefined symbol reference (the
|
||||
// type, not a value). cf lib/io/stream.ww:53-56. The instance
|
||||
// auto-widens to the [[error]] union on return.
|
||||
switch (e) {
|
||||
case os.ECONNREFUSED: { let r: refused; return r; };
|
||||
case os.ECANCELED: { let r: cancelled; return r; };
|
||||
case os.EOVERFLOW: { let r: overflow; return r; };
|
||||
case os.EACCES: { let r: noaccess; return r; };
|
||||
case os.EINVAL: { let r: invalid; return r; };
|
||||
case os.EEXIST: { let r: exists; return r; };
|
||||
case os.ENOENT: { let r: noentry; return r; };
|
||||
case os.ETIMEDOUT: { let r: timeout; return r; };
|
||||
case os.EBUSY: { let r: busy; return r; };
|
||||
case os.EINTR: { let r: interrupted; return r; };
|
||||
case os.EAGAIN: { let r: again; return r; };
|
||||
case os.ENETUNREACH: { let r: netunreachable; return r; };
|
||||
};
|
||||
|
||||
// An unmapped errno falls through the switch into the opaque_ wrap
|
||||
// below. ww switches aren't required exhaustive, so Hare's terminal
|
||||
// `case => void;` (rt.ha:24, present only to satisfy exhaustiveness)
|
||||
// is dropped rather than written as an explicit no-op default —
|
||||
// matching the sibling [[os.strerror]] fall-through.
|
||||
//
|
||||
// ww has no `static assert`; Hare guards size(errno) <=
|
||||
// size(opaque_data) there. The invariant holds structurally:
|
||||
// opaque_data is [3]u64 (24B), errno is one machine int.
|
||||
let err: opaque_;
|
||||
err.strerror = (&rt_strerror): *fn(op: *opaque_data) str;
|
||||
let ptr = (&err.data): *os.errno;
|
||||
*ptr = e;
|
||||
return err;
|
||||
};
|
||||
|
||||
// rt_strerror — the [[opaque_]] stringifier for an errno wrapped by
|
||||
// [[errno]]. ref/hare/errors/rt.ha:31.
|
||||
fn rt_strerror(op: *opaque_data) str = {
|
||||
let e = (op): *os.errno;
|
||||
return os.strerror(*e);
|
||||
};
|
||||
|
||||
// types — error union, mode/whence enums, reader/writer/closer
|
||||
// fn-type aliases. Project #94 fold-eFinal; the fn-aliases target
|
||||
// `stream` (= `*vtable`, the single io surface).
|
||||
|
||||
@@ -265,6 +265,57 @@ export fn lseek(fd: i32, off: i64, w: whence) i64 = {
|
||||
// errors::errno carried inside io::error.
|
||||
export type oserror = !i64;
|
||||
|
||||
// errno — the raw Linux errno as a positive code (ref/hare/sys/+linux/
|
||||
// errno.ha:5, `errno = !int`). ww folds Hare's `sys` role into os
|
||||
// (lib/CLAUDE.md), so the sys::errno machinery lands here. Spelled i32
|
||||
// rather than int: Linux errnos are kernel ints (32-bit), keeping os's
|
||||
// kernel-facing surface uniformly i32. Distinct from [[oserror]] (!i64,
|
||||
// the syscall's *negative* raw return) — the two model different
|
||||
// things, so they are not unified; the negative→positive normalization
|
||||
// lives at the oserror→errors.error boundary in those callers.
|
||||
export type errno = !i32;
|
||||
|
||||
// Mapped errno values, ref/hare/sys/+linux/errno.ha:559-682. Positive,
|
||||
// matching Hare's defs (the kernel returns -N; the wrap-to-positive is
|
||||
// the caller's concern). Subset: exactly the errnos [[errors.errno]]
|
||||
// maps to a named condition; grow as callers surface more.
|
||||
export def ENOENT: errno = 2;
|
||||
export def EINTR: errno = 4;
|
||||
export def EAGAIN: errno = 11;
|
||||
export def EACCES: errno = 13;
|
||||
export def EBUSY: errno = 16;
|
||||
export def EEXIST: errno = 17;
|
||||
export def EINVAL: errno = 22;
|
||||
export def EOVERFLOW: errno = 75;
|
||||
export def ENETUNREACH: errno = 101;
|
||||
export def ETIMEDOUT: errno = 110;
|
||||
export def ECONNREFUSED: errno = 111;
|
||||
export def ECANCELED: errno = 125;
|
||||
|
||||
// strerror — human-readable text for an [[errno]] (Hare's
|
||||
// sys::strerror, ref/hare/sys/+linux/errno.ha:18). FAITHFUL MINIMAL
|
||||
// SUBSET: the mapped errnos above plus a generic fallback; grow the
|
||||
// switch as callers surface more (lib/CLAUDE.md documented-subset, not
|
||||
// a workaround). Messages verbatim from the reference. Hare's
|
||||
// unknown_errno formats the numeric value; that is deferred.
|
||||
export fn strerror(err: errno) str = {
|
||||
switch (err) {
|
||||
case ENOENT: return "No such file or directory";
|
||||
case EINTR: return "Interrupted system call";
|
||||
case EAGAIN: return "Resource temporarily unavailable";
|
||||
case EACCES: return "Permission denied";
|
||||
case EBUSY: return "Device or resource busy";
|
||||
case EEXIST: return "File exists";
|
||||
case EINVAL: return "Invalid argument";
|
||||
case EOVERFLOW: return "Value too large for defined data type";
|
||||
case ENETUNREACH: return "Network is unreachable";
|
||||
case ETIMEDOUT: return "Connection timed out";
|
||||
case ECONNREFUSED: return "Connection refused";
|
||||
case ECANCELED: return "Operation canceled";
|
||||
};
|
||||
return "Unknown error";
|
||||
};
|
||||
|
||||
// filesize — byte length of an open fd via lseek-to-end-and-back.
|
||||
export fn filesize(fd: i32) (i64 | oserror) = {
|
||||
let end: i64 = lseek(fd, 0i64, whence.END);
|
||||
|
||||
@@ -13,7 +13,6 @@ static const char *modules[] = {
|
||||
"lib/types/types.ww",
|
||||
"lib/ascii/ascii.ww",
|
||||
"lib/io/io.ww",
|
||||
"lib/errors/errors.ww",
|
||||
"lib/strconv/strconv.ww",
|
||||
"lib/sort/sort.ww",
|
||||
"lib/path/path.ww",
|
||||
@@ -30,19 +29,21 @@ static const char *modules[] = {
|
||||
"lib/math/random/random.ww",
|
||||
"lib/time/time.ww",
|
||||
"lib/c/libc/libc.ww",
|
||||
/* lib/bufio/bufio.ww, lib/bytes/bytes.ww, lib/fmt/fmt.ww,
|
||||
* lib/os/os.ww, and lib/strings/strings.ww moved off this list:
|
||||
* each has cross-module type refs that only resolve once the
|
||||
* driver concatenates `use`d modules. bufio / fmt graduated to
|
||||
* io.stream-based sinks (io.stream = *io.vtable, io.eof, io.error); lib/os
|
||||
* carries time.instant in filestat post-Commit B;
|
||||
* lib/strings.iterator + lib/strings.next reference utf8.decoder
|
||||
* / utf8.done; lib/bytes.tokenize references os.assert +
|
||||
* types.I64_MAX/MIN per ref/hare/bytes/tokenize.ha:23-24,42-43.
|
||||
* Coverage lives at lib/bufio/bufiotest.ww + lib/bytes/bytestest.ww
|
||||
* + lib/fmt/fmttest.ww + lib/os/stattest.ww +
|
||||
* lib/strings/stringstest.ww (wired at 998_bufio_run.c,
|
||||
* 967_bytes_run.c, 970_fmt_run.c, 976_stat_run.c,
|
||||
/* lib/bufio/bufio.ww, lib/bytes/bytes.ww, lib/errors/errors.ww,
|
||||
* lib/fmt/fmt.ww, lib/os/os.ww, and lib/strings/strings.ww moved
|
||||
* off this list: each has cross-module type refs that only resolve
|
||||
* once the driver concatenates `use`d modules. bufio / fmt
|
||||
* graduated to io.stream-based sinks (io.stream = *io.vtable,
|
||||
* io.eof, io.error); lib/os carries time.instant in filestat
|
||||
* post-Commit B; lib/errors.errno references os.errno / os.E* /
|
||||
* os.strerror (ww folds Hare's sys role into os); lib/strings.iterator
|
||||
* + lib/strings.next reference utf8.decoder / utf8.done;
|
||||
* lib/bytes.tokenize references os.assert + types.I64_MAX/MIN per
|
||||
* ref/hare/bytes/tokenize.ha:23-24,42-43. Coverage lives at
|
||||
* lib/bufio/bufiotest.ww + lib/bytes/bytestest.ww +
|
||||
* lib/errors/errnotest.ww + lib/fmt/fmttest.ww + lib/os/stattest.ww
|
||||
* + lib/strings/stringstest.ww (wired at 998_bufio_run.c,
|
||||
* 967_bytes_run.c, 902_errno_run.c, 970_fmt_run.c, 976_stat_run.c,
|
||||
* 966_strings_run.c), plus the bufio.scanline / fmt.println e2e
|
||||
* rows in test/wcc/700_e2e.c. */
|
||||
"lib/net/net.ww",
|
||||
|
||||
51
test/wcc/902_errno_run.c
Normal file
51
test/wcc/902_errno_run.c
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 902_errno_run — execute the lib/errors errno @test fixture under the
|
||||
* C-side `ww run` driver and assert exit 0.
|
||||
*
|
||||
* Sibling to 982_getopt_run / 980_memio_run. errnotest.ww carries its
|
||||
* own `export fn main()` that drives the @test fns and signals which
|
||||
* case failed via the exit code, so this file is a thin wrapper — no
|
||||
* @test scanning, no synthetic main generation.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.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;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/errors/errnotest.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww run %s", bin, path);
|
||||
(void)cwd;
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "errno_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("errno_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user