Files
sx/library/modules/std/socket.sx
agra 989e18b760 feat: tuple syntax cutover — Tuple(...) type + .(...) value
Replace the bare-paren tuple grammar with explicit, position-unambiguous
forms, mirroring how structs work:

  type     `(A, B)`        -> `Tuple(A, B)`          (named keeps `:`)
  value    `(a, b)`        -> `.(a, b)`              (named uses `=`)
  typed    (new)           -> `Tuple(A, B).(a, b)`   (like `Point.{...}`)
  failable `-> (T, !)`     -> `-> T !`
           `-> (T1, T2, !)`-> `-> Tuple(T1, T2) !`   (channel outside Tuple)

Bare `(...)` is now grouping only, everywhere; a comma in bare parens is a
hard error with a migration hint. Grouping, function types `(A, B) -> R`,
param lists, lambdas, and match bindings are unaffected.

`Tuple(...)` is strictly a TYPE in every position (including `size_of` /
`type_info` args); a tuple VALUE comes only from `.(...)` (anonymous) or
`Tuple(...).(...)` (explicitly typed). A bare `Tuple(1, 2)` is a tuple
type with non-type elements -> rejected.

The ~110 tuple-bearing corpus files were migrated with a one-shot
AST-aware migrator (the `sx migrate` tool from the prior commit, removed
here). New examples: 0130 (new syntax), 0131 (typed construction), 1060
(named-tuple failable return). 1116 golden updated for the new hint text.
2026-06-25 17:53:57 +03:00

138 lines
4.7 KiB
Plaintext

// POSIX socket module (macOS only)
// sockaddr_in layout, errno symbol, and constants are platform-specific;
// per-OS selection is PLAN-HTTPZ C3.
libc :: #library "c";
// POSIX socket API
socket :: (domain: i32, kind: i32, protocol: i32) -> i32 extern libc;
setsockopt :: (fd: i32, level: i32, optname: i32, optval: *i32, optlen: u32) -> i32 extern libc;
bind :: (fd: i32, addr: *SockAddr, addrlen: u32) -> i32 extern libc;
listen :: (fd: i32, backlog: i32) -> i32 extern libc;
accept :: (fd: i32, addr: *SockAddr, addrlen: *u32) -> i32 extern libc;
connect :: (fd: i32, addr: *SockAddr, addrlen: u32) -> i32 extern libc;
read :: (fd: i32, buf: [*]u8, count: usize) -> isize extern libc;
write :: (fd: i32, buf: [*]u8, count: usize) -> isize extern libc;
close :: (fd: i32) -> i32 extern libc;
shutdown :: (fd: i32, how: i32) -> i32 extern libc;
socketpair :: (domain: i32, kind: i32, protocol: i32, fds: *i32) -> i32 extern libc;
fcntl :: (fd: i32, cmd: i32, ..args: []i32) -> i32 extern libc;
// Constants (macOS)
AF_UNIX :i32: 1;
AF_INET :i32: 2;
SOCK_STREAM :i32: 1;
SOL_SOCKET :i32: 0xFFFF;
SO_REUSEADDR :i32: 0x4;
SHUT_RD :i32: 0;
SHUT_WR :i32: 1;
SHUT_RDWR :i32: 2;
// macOS sockaddr_in (16 bytes, has sin_len field)
SockAddr :: struct {
sin_len: u8;
sin_family: u8;
sin_port: u16;
sin_addr: u32 = 0;
sin_zero: u64 = 0;
}
htons :: (port: i64) -> u16 {
cast(u16) (((port & 0xFF) << 8) | ((port >> 8) & 0xFF))
}
// ── nonblocking I/O (PLAN-HTTPZ S2) ──────────────────────────────────
// Typed wrappers over the readiness-loop syscall patterns: callers see
// WouldBlock / Closed / Fault, never a raw -1/errno pair. EINTR is
// retried internally — a readiness loop has nothing useful to do with
// an interrupted syscall except issue it again.
// errno resolves to a real function under the C macro: `__error` on
// darwin, `__errno_location` on linux (C3 selects per-OS).
errno_slot :: () -> *i32 extern libc "__error";
// fcntl file-status flags + errno values (macOS).
F_GETFL :i32: 3;
F_SETFL :i32: 4;
O_NONBLOCK :i32: 4;
EINTR :i32: 4;
EPIPE :i32: 32;
EAGAIN :i32: 35; // == EWOULDBLOCK on darwin (and linux's 11 == its EWOULDBLOCK)
EINPROGRESS :i32: 36;
ECONNABORTED :i32: 53;
ECONNRESET :i32: 54;
errno :: () -> i32 {
return errno_slot().*;
}
is_wouldblock :: (e: i32) -> bool {
return e == EAGAIN;
}
// Put `fd` in nonblocking mode (reads/writes/accepts return EAGAIN
// instead of parking the thread). False when fcntl refuses.
set_nonblocking :: (fd: i32) -> bool {
flags := fcntl(fd, F_GETFL);
if flags < 0 { return false; }
return fcntl(fd, F_SETFL, flags | O_NONBLOCK) >= 0;
}
// Failure classes for the _nb wrappers.
// WouldBlock — no data/space/pending connection right now; wait for
// readiness and retry.
// Closed — the peer is gone: EOF on read, EPIPE/ECONNRESET on
// write, ECONNRESET on read.
// Fault — any other errno (caller treats the fd as broken).
SockErr :: error {
WouldBlock,
Closed,
Fault,
}
// Accept one pending connection on a nonblocking listener. A connection
// that died between queueing and accept (ECONNABORTED) is skipped, not
// surfaced — the listener is fine.
accept_nb :: (fd: i32) -> i32 !SockErr {
while true {
c := accept(fd, null, null);
if c >= 0 { return c; }
e := errno();
if e == EINTR or e == ECONNABORTED { continue; }
if is_wouldblock(e) { raise error.WouldBlock; }
raise error.Fault;
}
raise error.Fault;
}
// Read up to `cap` bytes. Returns the byte count (> 0); an orderly EOF
// or a peer reset is Closed.
read_nb :: (fd: i32, buf: [*]u8, cap: usize) -> i64 !SockErr {
while true {
n := read(fd, buf, cap);
if n > 0 { return xx n; }
if n == 0 { raise error.Closed; }
e := errno();
if e == EINTR { continue; }
if is_wouldblock(e) { raise error.WouldBlock; }
if e == ECONNRESET { raise error.Closed; }
raise error.Fault;
}
raise error.Fault;
}
// Write up to `len` bytes, returning how many the kernel took (possibly
// fewer — the caller continues from there on the next writability).
write_nb :: (fd: i32, buf: [*]u8, len: usize) -> i64 !SockErr {
while true {
n := write(fd, buf, len);
if n >= 0 { return xx n; }
e := errno();
if e == EINTR { continue; }
if is_wouldblock(e) { raise error.WouldBlock; }
if e == EPIPE or e == ECONNRESET { raise error.Closed; }
raise error.Fault;
}
raise error.Fault;
}