Protocol method declarations now declare their receiver explicitly as the first parameter — 'self: *Self' (or 'self: Self') — matching the impl method signature, instead of the old implicit-receiver form where the listed params were only the extra args. That asymmetry repeatedly caused confusion over whether the first param was the receiver or an argument. The parser validates the first param is 'self' typed Self/*Self, then strips it, so all downstream lowering and the dispatch ABI are unchanged (impl blocks and call sites are unaffected). A protocol method missing the receiver is now a parse error. Migrated all 129 protocol method signatures across library + examples (+ one inline-sx test in sema.zig) to the explicit form. Updated specs.md + readme.md. New: examples/0418-protocols-explicit-receiver.sx (feature), examples/1190-diagnostics-protocol-missing-receiver.sx (negative/diagnostic).
132 lines
5.8 KiB
Plaintext
132 lines
5.8 KiB
Plaintext
// The compiler-coupled prelude primitives: #builtin declarations, the libc
|
|
// escape hatch, and the types the compiler resolves by NAME program-wide
|
|
// (`Context`, `Allocator`, `Into`, `Source_Location`, `string`). Consumers
|
|
// never import this file directly — std.sx re-exports every name here.
|
|
|
|
Vector :: ($N: int, $T: Type) -> Type #builtin;
|
|
// `out` writes a string straight to fd 1 via libc `write` — a plain sx function,
|
|
// NOT a compiler builtin. At comptime it runs through the evaluator's host-FFI
|
|
// escape (the VM's dlsym path / the interp's extern call); at runtime it's an
|
|
// ordinary libc call. `libc_write` is the raw escape hatch (cf. libc_malloc/free).
|
|
libc_write :: (fd: i32, buf: [*]u8, count: usize) -> isize extern libc "write";
|
|
out :: (str: string) -> void {
|
|
libc_write(1, str.ptr, xx str.len);
|
|
}
|
|
// sqrt :: (x: $T) -> T #builtin;
|
|
// sin :: (x: $T) -> T #builtin;
|
|
// cos :: (x: $T) -> T #builtin;
|
|
size_of :: ($T: Type) -> i64 #builtin;
|
|
align_of :: ($T: Type) -> i64 #builtin;
|
|
// Low-level libc bindings, used by allocator implementations to avoid
|
|
// recursing through `context.allocator`. The bare `malloc`/`free`
|
|
// spellings are NOT declared: the Allocator protocol + the std/mem.sx
|
|
// helpers are the allocation surface (`free` is the typed slice helper
|
|
// there). Raw libc escape hatch: `libc_malloc` / `libc_free`.
|
|
libc_malloc :: (size: i64) -> *void extern libc "malloc";
|
|
libc_free :: (ptr: *void) -> void extern libc "free";
|
|
|
|
memcpy :: (dst: *void, src: *void, size: i64) -> *void extern libc "memcpy";
|
|
memset :: (dst: *void, val: i64, size: i64) -> void extern libc "memset";
|
|
type_of :: (val: $T) -> Type #builtin;
|
|
type_name :: ($T: Type) -> string #builtin;
|
|
field_count :: ($T: Type) -> i64 #builtin;
|
|
field_name :: ($T: Type, idx: i64) -> string #builtin;
|
|
field_value :: (s: $T, idx: i64) -> Any #builtin;
|
|
is_flags :: ($T: Type) -> bool #builtin;
|
|
type_is_unsigned :: ($T: Type) -> bool #builtin;
|
|
field_value_int :: ($T: Type, idx: i64) -> i64 #builtin;
|
|
field_index :: ($T: Type, val: T) -> i64 #builtin;
|
|
error_tag_name :: (e: $T) -> string #builtin;
|
|
|
|
// Comptime type metaprogramming (`declare` / `define` / `type_info` /
|
|
// `field_type`) lives in the on-demand `modules/std/meta.sx`, NOT here —
|
|
// declaring its data types in the always-loaded prelude would intern them into
|
|
// every module's type table and shift every `.ir` snapshot. Import
|
|
// `modules/std/meta.sx` to construct or reflect types at comptime.
|
|
|
|
// Call-site location, synthesized by the `#caller_location` directive when it
|
|
// is a parameter's default value (ERR E4.1b). `process.exit` / `assert` use it
|
|
// to report where they were invoked.
|
|
Source_Location :: struct {
|
|
file: string;
|
|
line: i32;
|
|
col: i32;
|
|
func: string;
|
|
}
|
|
string :: []u8 #builtin;
|
|
|
|
// --- Allocator protocol (impls live in std/mem.sx) ---
|
|
|
|
// Bytes-level primitives carry the `_bytes` suffix so the typed
|
|
// helpers in std/mem.sx own the bare names (`alloc(T, n)`, `free(s)`).
|
|
Allocator :: protocol #inline {
|
|
alloc_bytes :: (self: *Self, size: i64) -> *void;
|
|
dealloc_bytes :: (self: *Self, ptr: *void);
|
|
}
|
|
|
|
// --- Io capability protocol (impls live in std/io.sx) ---
|
|
//
|
|
// `Io` is threaded on `Context` exactly like `Allocator`: a `#inline`
|
|
// protocol whose default impl (the stateless `CBlockingIo` in std/io.sx)
|
|
// is installed in the process-wide `__sx_default_context`. Async runtime
|
|
// is sx LIBRARY code — the compiler provides only the primitives (inline
|
|
// asm, `abi(.naked)`, atomics) + fiber-safe codegen. The protocol is the
|
|
// minimum the fiber scheduler [B1.3+] needs; everything ergonomic
|
|
// (`async` / `await` / `cancel` / `timeout`) is a generic free-fn on top
|
|
// (std/io.sx), the same way `alloc(T,n)` sits over `alloc_bytes`.
|
|
//
|
|
// spawn_raw — submit a task; opaque handle (B1.3 fiber bootstrap).
|
|
// suspend_raw— suspend current fiber; `!` so cancel can raise out.
|
|
// ready — wake a parked fiber (B1.4/B1.5).
|
|
// poll — drive one step; blocking impl returns 0.
|
|
// now_ms — clock hook (a PROTOCOL method so the deterministic-sim
|
|
// Io [B1.4] can return a fake clock — the B1.4 keystone).
|
|
// arm_timer — register a timer; backs `timeout` (B1.4).
|
|
//
|
|
// `ParkToken` is an opaque per-suspension token (unused by the blocking
|
|
// impl). `SpawnOpts.pin` is inert in the M:1 model. (`PinTarget.on` —
|
|
// pin to a specific `Thread` — is deferred with the M:N model; the
|
|
// `on_thread` variant is a placeholder until a `Thread` type exists.)
|
|
PinTarget :: enum { any; main; on_thread; }
|
|
|
|
SpawnOpts :: struct {
|
|
pin: PinTarget = .any;
|
|
}
|
|
|
|
ParkToken :: struct {
|
|
handle: *void = null;
|
|
}
|
|
|
|
Io :: protocol #inline {
|
|
spawn_raw :: (self: *Self, entry: *void, arg: *void, opts: SpawnOpts) -> *void;
|
|
suspend_raw :: (self: *Self, park: ParkToken) -> !;
|
|
ready :: (self: *Self, park: ParkToken);
|
|
poll :: (self: *Self, deadline_ms: i64) -> i64;
|
|
now_ms :: (self: *Self) -> i64;
|
|
arm_timer :: (self: *Self, deadline_ms: i64, park: ParkToken) -> *void;
|
|
}
|
|
|
|
// --- Context ---
|
|
//
|
|
// `allocator` MUST stay at field index 0 (the heap-alloc lowering path
|
|
// hardcodes it — src/ir/lower/call.zig). `io` is appended LAST so `data`
|
|
// keeps its existing index 1 (minimizes the comptime-VM fallback churn).
|
|
// Both Zig materializers of `__sx_default_context` (protocol.zig
|
|
// `emitDefaultContextGlobal` + comptime_vm.zig `materializeDefaultContext`)
|
|
// install the inline `CBlockingIo → Io` vtable at the new field.
|
|
|
|
Context :: struct {
|
|
allocator: Allocator;
|
|
data: *void;
|
|
io: Io;
|
|
}
|
|
|
|
// User-space `xx` extension. `xx val : T` where the built-in conversion
|
|
// ladder makes no progress falls through to an `impl Into(T) for Source`
|
|
// lookup; the compiler monomorphises `convert` for the (Source, T) pair
|
|
// and emits a direct call. Compile-time only — no vtable, no runtime
|
|
// dispatch.
|
|
Into :: protocol(Target: Type) {
|
|
convert :: (self: *Self) -> Target;
|
|
}
|