mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-02 00:25:34 +08:00
- Preserve fields, accessors, added properties, and cyclic references across untyped class boundaries. - Support platform signal names and native exception handlers that resume queued work.
7119 lines
415 KiB
C
7119 lines
415 KiB
C
/* scriptc runtime — public API.
|
||
* Every scriptc binary compiles these sources in; there is no shared library.
|
||
*
|
||
* Prefix conventions: the runtime owns "scr_" and "SCR_" macros/"Scr" type names.
|
||
* Compiler-emitted symbols use "sc_f_" (functions), "sc_l_" (locals),
|
||
* "sc_t" (temps) and never collide.
|
||
*/
|
||
#ifndef SCR_RUNTIME_H
|
||
#define SCR_RUNTIME_H
|
||
|
||
#include <stdarg.h> /* va_list in the emitter listener adapters */
|
||
#include <stdbool.h>
|
||
#include <stddef.h>
|
||
#include <stdint.h>
|
||
#include <string.h> /* memcpy in the inline slot accessors */
|
||
#include <sys/types.h> /* ssize_t in the transport ops table */
|
||
|
||
/* ── libc shims ─────────────────────────────────────────────────────────
|
||
* Win32's missing POSIX/BSD functions live in scr_win.c. Zig's musl sysroot
|
||
* additionally lacks arc4random_buf; scr_musl.c supplies it from Linux's
|
||
* getrandom syscall. Both files are selected by native-toolchain.ts only for their target. */
|
||
#if defined(_WIN32) && defined(_MSC_VER)
|
||
/* MSVC's C headers do not define POSIX ssize_t. Keep the runtime's byte-count
|
||
* interfaces pointer-sized on Windows without requiring a Windows SDK header
|
||
* (and leave the Zig/MinGW compatibility route's native typedef intact). */
|
||
typedef intptr_t ssize_t;
|
||
#endif
|
||
#ifdef _WIN32
|
||
#include <time.h> /* time_t / struct tm for the gmtime_r shim */
|
||
char *stpcpy(char *dst, const char *src);
|
||
void arc4random_buf(void *buf, size_t n);
|
||
struct tm *gmtime_r(const time_t *t, struct tm *out);
|
||
char *strcasestr(const char *hay, const char *needle);
|
||
#elif defined(SCR_MUSL)
|
||
void arc4random_buf(void *buf, size_t n);
|
||
#endif
|
||
|
||
/* ── thread-instanced library state ─────────────────────────────────────
|
||
* Archives built under the profile's abi.instance_per_thread compile every
|
||
* TU with -DSCR_THREAD_INSTANCES, and SCR_TL moves each unit's mutable
|
||
* state — and the emitted program's globals — into thread-local storage.
|
||
* A thread that calls the profile's init entry then owns a complete,
|
||
* independent instance: its own collector, result arena, panic-sink
|
||
* registration, poison flag, and program state. The instance's lifetime is
|
||
* the thread's; the one-thread-per-instance contract (an instance is never
|
||
* entered from two threads) is unchanged — this mode adds instances, not
|
||
* thread awareness. Expands to nothing everywhere else, so executable
|
||
* builds and classic library builds carry the exact bytes they always
|
||
* carried. Truly immutable tables (static const data) stay shared. */
|
||
#if defined(SCR_LIB) && defined(SCR_THREAD_INSTANCES)
|
||
#define SCR_TL _Thread_local
|
||
#else
|
||
#define SCR_TL
|
||
#endif
|
||
|
||
/* ── process ──────────────────────────────────────────────────────────── */
|
||
|
||
/* Called once at the top of main: private stdout formatter buffer,
|
||
* flush-at-exit, RC audit registration (when built with -DSCR_RC_AUDIT).
|
||
* JavaScript-visible writes flush before returning. */
|
||
void scr_init(void);
|
||
/* Program objects emitted by the bundled LLVM helper reference this symbol.
|
||
* Its versioned spelling makes a mismatched manual runtime link fail before
|
||
* the program can start. */
|
||
void scr_runtime_abi_v4(void);
|
||
|
||
/* ── the trap funnel (scr_console.c; scr_library.c under -DSCR_LIB) ──────
|
||
* Every unrecoverable runtime trap — OOM, semantic range traps, internal-
|
||
* invariant failures — funnels through this pair instead of open-coded
|
||
* fputs/fprintf + abort. Executable builds expand to exactly the historical
|
||
* behavior (the message's bytes on stderr, then abort — the default lane
|
||
* must not change by a byte). Library builds (-DSCR_LIB) route the message to
|
||
* the host-registered panic sink and abort only as the last resort: before
|
||
* registration, or if the sink returns (the ruled host-contract violation —
|
||
* a conforming sink longjmps to a host frame BELOW the entry, never back
|
||
* into library frames). Messages keep their trailing newline in both lanes;
|
||
* the library funnel additionally assembles every DETECTED trap into the
|
||
* structured trap-teaching form before delivery (a message that already
|
||
* begins with the 0x01 marker passes verbatim) — see ScrLibSinkFn below. */
|
||
_Noreturn void scr_trap(const char *msg);
|
||
_Noreturn void scr_trap_fmt(const char *fmt, ...);
|
||
|
||
/* ── library mode (scr_library.c, linked only into library artifacts) ─────
|
||
* A library artifact has no main, no event loop, no signal handlers, no
|
||
* atexit registrations, and never touches host stdio modes or buffering:
|
||
* initialization runs inside the profile-named init entry (re-runnable
|
||
* deterministically), traps route to the sink above, and buffer-class
|
||
* results live in a library-owned result arena. Everything here compiles only
|
||
* under -DSCR_LIB; executable builds never contain it. */
|
||
#ifdef SCR_LIB
|
||
typedef struct ScrStr ScrStr; /* full definitions below (C11 repeat) */
|
||
typedef struct ScrBytes ScrBytes;
|
||
/* The host's panic sink: msg is UTF-8, valid only for the duration of the
|
||
* call; address is the trap site's return address (0 when the toolchain
|
||
* cannot supply one); ctx is the registration's opaque pointer. The sink
|
||
* must not call back into any library entry and must not unwind or longjmp
|
||
* back into library frames.
|
||
*
|
||
* Message shape (the ratified structured trap-teaching encoding): a
|
||
* BASELINE message is plain text whose first byte is printable (>= 0x20) —
|
||
* no emitter path ever produces an unstructured message starting below
|
||
* 0x20. A STRUCTURED message begins with the marker byte 0x01 followed by
|
||
* the human teaching text and 0x1F-separated fields:
|
||
*
|
||
* 0x01 text 0x1F code 0x1F symbol [ 0x1F remediation ]
|
||
*
|
||
* so msg_len > 0 && msg[0] == 0x01 is the one version test. Parse: split
|
||
* the bytes after the marker on 0x1F — field 0 is the human text, 1 the
|
||
* diagnostic code, 2 the trapping symbol as the host linked it, 3 the
|
||
* remediation; a missing or empty field means none; ignore any field past
|
||
* the fourth. Fields are (pointer, length) — never assume NUL termination.
|
||
* A plain-text host may print the whole buffer: the teaching leads it.
|
||
*
|
||
* Every trap the runtime DETECTS arrives structured: the funnel assembles
|
||
* the baseline human line into field 0 unchanged, a stable code for the
|
||
* trap kind (the compiler registry's runtime family — SC4013–SC4019 plus
|
||
* the SC4025 unregistered-callback and SC4026 callback-re-entry traps,
|
||
* classified in scr_library.c), the
|
||
* entry symbol recorded by the trapping
|
||
* entry's prologue, and the profile's remediation for that code when the
|
||
* program TU's overlay table declares one (the whole fourth field is
|
||
* absent otherwise). A message that already begins with the marker — a
|
||
* facade-authored structured throw, or the wrapper's compile-time-
|
||
* assembled SC4012 contract trap — passes through byte-for-byte. */
|
||
typedef void (*ScrLibSinkFn)(void *ctx, const uint8_t *msg, size_t msg_len,
|
||
uint64_t address);
|
||
void scr_library_set_sink(ScrLibSinkFn fn, void *ctx); /* latest wins */
|
||
|
||
/* ── host-callback channels ───────────────────────────────────────────────
|
||
* The panic sink's registration pattern generalized into a synchronous
|
||
* outbound seam: the profile declares named channels (bytes/scalar
|
||
* signatures), the generated registration symbol maps a channel name to a
|
||
* slot index here, and compiled call sites fetch the slot through
|
||
* scr_library_cb_require — which returns the host's pointer or delivers
|
||
* the call site's trap message through the funnel (the SC4025 detected
|
||
* trap) when the host never registered. The typed shape of each stored
|
||
* pointer is the channel's, with the opaque context first:
|
||
*
|
||
* <ret> (*)(void *ctx, <params...>)
|
||
*
|
||
* Registration is a pure store like the sink's (no entry prologue, no
|
||
* poison guard, legal before init); latest wins, NULL clears, and
|
||
* registrations persist across init/reset. While a host callback is active,
|
||
* its registration entry is rejected before name dispatch or a store, just
|
||
* like every runtime-touching ABI entry. Slots are per-copy of this
|
||
* state, exactly the sink's story: per-archive under abi.localize_runtime,
|
||
* per-thread instance under abi.instance_per_thread (SCR_TL) — a callback
|
||
* registered on thread T fires only for T's instance. The host's callback
|
||
* runs on the calling thread inside the entry's dynamic extent and must
|
||
* NOT call back into any library entry (exports, init, reset, collect, sink
|
||
* registration, or callback registration) or unwind/longjmp across library
|
||
* frames: read the borrowed buffers, copy what outlives the call, return.
|
||
* A re-entry is a detected SC4026 trap: it poisons only this library
|
||
* instance, delivers exactly once to the already-registered sink, names the
|
||
* attempted inner ABI symbol in structured field 2, then aborts if the sink
|
||
* returns. A later host-loop turn may enter normally after the callback has
|
||
* returned. Buffer parameters are borrowed for the duration of the call
|
||
* only. */
|
||
#define SCR_LIB_MAX_CALLBACKS 32 /* keep in step with LIB_MAX_CALLBACKS (library/library-profile.ts) */
|
||
/* The stored shape: generated call sites cast a slot's pointer to the
|
||
* channel's typed shape before calling. */
|
||
typedef void (*ScrLibCbFn)(void);
|
||
void scr_library_cb_set(size_t slot, ScrLibCbFn fn, void *ctx);
|
||
/* The call-site fetch: the registered pointer, or the funnel trap with
|
||
* trap_msg (never returns NULL). */
|
||
ScrLibCbFn scr_library_cb_require(size_t slot, const char *trap_msg);
|
||
void *scr_library_cb_ctx(size_t slot);
|
||
/* Generated typed call sites bracket only the actual host-function call.
|
||
* End is reached only after a normal return; an illegal unwind deliberately
|
||
* leaves the depth active so the next ABI entry is rejected. */
|
||
void scr_library_callback_begin(void);
|
||
void scr_library_callback_end(void);
|
||
/* Registration wrappers bypass scr_library_entry because their normal path
|
||
* is a pure store. They call this first so callback-time registration is
|
||
* rejected before dispatch, NULL handling, or mutation. */
|
||
void scr_library_callback_entry_guard(const char *entry_symbol);
|
||
|
||
/* Entry prologue: aborts deterministically when the library is poisoned (a
|
||
* trap already fired — no profile entry may run again; recovery is process
|
||
* restart). reset_arena additionally drops the result arena (the
|
||
* auto-reset posture, and the reset/collect entries' shared body).
|
||
* entry_symbol is the generated entry's external symbol exactly as the
|
||
* host linked it (a static string in the program TU): the prologue records
|
||
* it in the funnel's current-entry slot so a detected trap's structured
|
||
* message can name the trapping entry. A host callback's attempted nested
|
||
* entry is rejected first and replaces this slot with that inner symbol.
|
||
* Init and the mode entries (reset, collect) record theirs too. The two
|
||
* profile identity getters are the explicit pure-data exception: they touch
|
||
* no mutable runtime state and remain callable before init and after poison. */
|
||
void scr_library_entry(bool reset_arena, const char *entry_symbol);
|
||
void scr_library_arena_reset(void);
|
||
/* The mode-provided collect entry's body: arena reset + a full cycle
|
||
* collection (snapshot-invariant by construction — collection frees only
|
||
* unreachable cycles). */
|
||
void scr_library_collect(void);
|
||
|
||
/* Full session reset, called by the generated init entry AFTER the program
|
||
* TU released and zeroed its globals: pending-exception clear, arena
|
||
* reset, the reset registry's drains (the units' repointed atexit halves),
|
||
* the library's interned process values, a cycle collection, and — under
|
||
* SCR_RC_AUDIT — the zero-live-heap assertion (a failure is a trap through
|
||
* the sink, never _Exit). */
|
||
void scr_library_reset(void);
|
||
/* Where a unit would atexit() a lazy teardown, library builds register it
|
||
* here instead (called on every scr_library_reset, registered once). */
|
||
void scr_library_register_reset(void (*fn)(void));
|
||
/* An escaped exception at an entry boundary: renders the same "Uncaught
|
||
* ..." text the executable epilogue prints, releases the payload, and
|
||
* routes the text through the trap funnel. No-op when nothing is pending.
|
||
* The ratified verbatim rule: a thrown message that ALREADY begins with the
|
||
* structured marker 0x01 (a thrown string, or an Error whose .message
|
||
* starts with it) is delivered byte-for-byte — no "Uncaught " prefix, no
|
||
* added newline — which is how facade-authored structured teachings ride
|
||
* the throw channel to the sink. Defined in scr_exception.c (it owns the
|
||
* cell). */
|
||
void scr_library_check_exc(void);
|
||
|
||
/* The length-taking funnel entry (library lane only): delivers exactly the
|
||
* given bytes to the sink — the verbatim path above needs it because a
|
||
* structured message is length-delimited, never NUL-scanned. */
|
||
_Noreturn void scr_trap_len(const char *msg, size_t len);
|
||
|
||
/* The runtime-trap overlay table, DEFINED by the generated program TU
|
||
* (both emissions emit identical data) and consumed by the funnel when it
|
||
* assembles a detected trap's structured message: flat triples of
|
||
* (code, teaching-or-NULL, remediation-or-NULL), one per runtime trap code
|
||
* (the SC4013–SC4019 family plus SC4025 and SC4026) the profile declares
|
||
* text for;
|
||
* _len counts triples. A declared teaching replaces the baseline human line as field 0;
|
||
* a declared remediation becomes the optional fourth field. */
|
||
extern const char *const scr_library_trap_overlays[];
|
||
extern const size_t scr_library_trap_overlays_len;
|
||
|
||
/* Marshalling helpers the generated wrappers call (both emissions share
|
||
* these bodies, which is how the two lanes stay identical by
|
||
* construction). Inbound is borrowed-and-copied; outbound values MOVE into
|
||
* the result arena and stay valid until the next arena reset. String
|
||
* results are NUL-terminated after *out_len bytes (ScrStr's layout). */
|
||
ScrStr *scr_library_str_in(const uint8_t *p, size_t len); /* +1 */
|
||
/* trap_msg is the wrapper's compiler-assembled host-contract trap message
|
||
* (structured trap-teaching bytes naming this entry's symbol), delivered
|
||
* through the funnel when len falls outside the marshalling class. */
|
||
ScrBytes *scr_library_bytes_in(const uint8_t *p, size_t len, const char *trap_msg); /* +1, u8 */
|
||
/* The inbound declared-integer edge (ask 4's i64/u64 parameter classes):
|
||
* exact conversion for |v| <= 2^53-1, the host-contract trap (same
|
||
* assembled SC4012 message shape as the bytes trap) past it — silent
|
||
* rounding is a coercion the author never wrote. */
|
||
double scr_library_i64_in(int64_t v, const char *trap_msg);
|
||
double scr_library_u64_in(uint64_t v, const char *trap_msg);
|
||
void scr_library_str_out(ScrStr *s, const uint8_t **out, size_t *out_len);
|
||
void scr_library_bytes_out(ScrBytes *b, const uint8_t **out, size_t *out_len);
|
||
|
||
#define scr_atexit(fn) scr_library_register_reset(fn)
|
||
#else
|
||
#define scr_atexit(fn) atexit(fn)
|
||
#endif /* SCR_LIB */
|
||
|
||
/* ── cycle collection (scr_cycle.c) ───────────────────────────────────
|
||
* Reference counting alone cannot free cycles, so every object that can
|
||
* participate in one — capture boxes, heap closures, unions, promises, and
|
||
* compiler-emitted class/record shapes with cycle-capable fields — is
|
||
* allocated with a hidden header directly BEFORE the object: a trace
|
||
* function (enumerates the object's cycle-capable children), a teardown
|
||
* function (frees the object WITHOUT releasing traced children), and the
|
||
* trial-deletion bookkeeping (color + candidate-root buffer state). Types
|
||
* that can never be in a cycle — strings above all, plus arrays of scalar/
|
||
* string/bytes elements, dyn trees, and shapes whose fields are all acyclic
|
||
* — keep the lean 1-word `rc` header and pay nothing. Arrays and maps are
|
||
* cycle-capable exactly when their element/value type is (a record element
|
||
* can point back at the array holding it).
|
||
*
|
||
* The collector is synchronous Bacon–Rajan trial deletion: a release that
|
||
* leaves a candidate's rc above zero buffers it as a possible cycle root;
|
||
* collection walks the buffer (markGray: trial-decrement internal edges;
|
||
* scan: restore externally-referenced subgraphs; collectWhite: free the
|
||
* dead cycle members, releasing only edges that LEAVE the white set).
|
||
* It is GENERATIONAL: each header carries a generation, a pass names the
|
||
* oldest one it will walk, and objects that survive a pass are promoted out
|
||
* of the nursery so later nursery passes never re-walk them. That is what
|
||
* keeps a pass proportional to recent allocation rather than to the whole
|
||
* live heap — see the generation note in scr_cycle.c for the soundness
|
||
* argument and the schedule. Collection points: program exit (before the RC
|
||
* audit), event-loop quiescence, and the per-generation triggers.
|
||
* SCR_CYCLE_THRESHOLD pins the nursery trigger to a fixed candidate count.
|
||
* There is no concurrent or incremental collection.
|
||
*
|
||
* Contract for trace/teardown pairs (the compiler emits them for shapes,
|
||
* the runtime owns its own): trace(obj) visits exactly the strong
|
||
* references to children that themselves carry a cycle header (visit
|
||
* callbacks tolerate NULL and immortal children); the teardown releases
|
||
* exactly the complement (strings, arrays, acyclic shapes), frees internal
|
||
* buffers, and frees the block via scr_cyc_free. Every heap object's
|
||
* FIRST member is `size_t rc`, which is what lets the collector adjust
|
||
* counts generically.
|
||
*/
|
||
typedef void (*ScrTraceVisit)(void *child, void *ctx);
|
||
typedef void (*ScrTraceFn)(void *obj, ScrTraceVisit visit, void *ctx);
|
||
typedef void (*ScrCycFreeFn)(void *obj);
|
||
|
||
/* DOOMED is WHITE that collectWhite has already gathered: it stops the
|
||
* gather recursing twice, and distinguishes "about to be freed" from a
|
||
* survivor for the re-buffering step (see scr_cycle.c). */
|
||
enum {
|
||
SCR_CYC_BLACK = 0, SCR_CYC_PURPLE = 1, SCR_CYC_GRAY = 2, SCR_CYC_WHITE = 3,
|
||
SCR_CYC_DOOMED = 4
|
||
};
|
||
|
||
/* Generations. A candidate sits in the buffer named by its own `gen`, and a
|
||
* pass walks only objects at or below the generation it collects. */
|
||
enum { SCR_CYC_NURSERY = 0, SCR_CYC_MATURE = 1, SCR_CYC_NGENS = 2 };
|
||
|
||
typedef struct ScrCycHdr {
|
||
ScrTraceFn trace;
|
||
ScrCycFreeFn free_fn;
|
||
uint32_t color; /* SCR_CYC_* */
|
||
uint16_t buffered; /* 1 = sitting in its generation's candidate buffer */
|
||
uint16_t gen; /* SCR_CYC_NURSERY..SCR_CYC_MATURE (the walk filter) */
|
||
size_t buf_index; /* position there (O(1) removal when rc hits 0) */
|
||
} ScrCycHdr;
|
||
|
||
/* This layout is an ABI, not an implementation detail. The LLVM backend
|
||
* inlines scr_cyc_mark_live as a raw `store i32 0`: color is at obj-16 on
|
||
* 64-bit targets and obj-12 on wasm32. Three sites emit it —
|
||
* llvm/shapes.ts, llvm/classes.ts, and llvm/emitter.ts. Nothing but `color`
|
||
* may share those four bytes: a field placed in them is silently zeroed by
|
||
* every retain, which is invisible to the type system and to the C
|
||
* compiler. Hence the target-width assertions. */
|
||
#if UINTPTR_MAX == UINT64_MAX
|
||
_Static_assert(sizeof(ScrCycHdr) == 32, "LLVM backend expects a 32-byte cycle header");
|
||
_Static_assert(offsetof(ScrCycHdr, color) == 16,
|
||
"LLVM backend's inlined mark-live stores i32 0 at obj-16");
|
||
#elif UINTPTR_MAX == UINT32_MAX
|
||
_Static_assert(sizeof(ScrCycHdr) == 20, "LLVM backend expects a 20-byte cycle header");
|
||
_Static_assert(offsetof(ScrCycHdr, color) == 8,
|
||
"LLVM backend's inlined mark-live stores i32 0 at obj-12");
|
||
#endif
|
||
_Static_assert(sizeof(((ScrCycHdr *)0)->color) == 4,
|
||
"mark-live is an i32 store: color must own all four bytes");
|
||
_Static_assert(SCR_CYC_BLACK == 0,
|
||
"the emitted mark-live stores the LITERAL 0, not the enumerator "
|
||
"— reordering the colors would make every compiled retain write "
|
||
"the wrong one");
|
||
|
||
static inline ScrCycHdr *scr_cyc_hdr(void *obj) { return (ScrCycHdr *)obj - 1; }
|
||
|
||
/* Zeroed allocation with a cycle header in front; returns the OBJECT
|
||
* pointer (header at scr_cyc_hdr). Aborts on OOM. */
|
||
void *scr_cyc_alloc(size_t size, ScrTraceFn trace, ScrCycFreeFn free_fn);
|
||
void scr_cyc_free(void *obj); /* frees the block, header included */
|
||
|
||
/* RC hooks for cycle-headered types. on_release: rc was decremented and
|
||
* stayed above zero — buffer the object as a possible cycle root (may run
|
||
* a collection when the buffer crosses the threshold; the caller must not
|
||
* touch the object afterwards). on_dead: rc hit zero — drop any buffer
|
||
* entry BEFORE tearing the object down. mark_live: retain hook (a
|
||
* re-retained candidate is certainly not garbage). */
|
||
void scr_cyc_on_release(void *obj);
|
||
void scr_cyc_on_dead(void *obj);
|
||
static inline void scr_cyc_mark_live(void *obj) {
|
||
scr_cyc_hdr(obj)->color = SCR_CYC_BLACK;
|
||
}
|
||
|
||
/* Full sweep: trial-deletion passes over EVERY generation, to a fixpoint.
|
||
* This is the exit / session-reset entry point (the RC audit runs straight
|
||
* after and wants nothing reclaimable left), and it costs a walk of the
|
||
* live heap — do not put it on a per-turn path. */
|
||
void scr_collect_cycles(void);
|
||
|
||
/* One pass on the normal generational schedule, for callers that reach a
|
||
* natural collection point rather than a threshold (the event loop between
|
||
* turns). Cheap: usually a nursery pass; a waiting mature backlog ages into
|
||
* a bounded full pass so sparse roots cannot float forever. */
|
||
void scr_cyc_collect_scheduled(void);
|
||
|
||
/* ── class hierarchies (single inheritance) ───────────────────────────
|
||
* Classes in an `extends` hierarchy share a two-word object prefix: the
|
||
* usual `size_t rc`, then a pointer to the class's static vtable (emitted
|
||
* per class by the compiler; standalone classes carry no vtable word and
|
||
* none of this applies). A derived class embeds its base's fields as a
|
||
* layout PREFIX, so an upcast is a pointer reinterpret and base-field
|
||
* offsets agree through any static type.
|
||
*
|
||
* The vtable begins with this header; the compiler-emitted concrete
|
||
* per-hierarchy struct appends one member per virtual method slot (only
|
||
* methods actually overridden somewhere get slots — never-overridden
|
||
* methods keep direct static calls). `pre`/`post` are the class's preorder
|
||
* interval in the whole-program class forest: `x instanceof C` is the O(1)
|
||
* range check `C.pre <= x->vt->pre && x->vt->pre <= C.post`. `release` is
|
||
* the class's own whole-object release: releasing through a base-typed
|
||
* pointer must tear down the DERIVED object's fields, so the emitted
|
||
* release of every hierarchy class dispatches through the stored vtable
|
||
* (retain needs no dispatch — rc is at offset 0 in every layout). The
|
||
* cycle collector needs no vtable involvement: a cycle-capable hierarchy
|
||
* object's header trace/teardown are stamped with the concrete class's
|
||
* functions at allocation, which already is dynamic dispatch. */
|
||
typedef struct ScrVt {
|
||
size_t pre, post;
|
||
void (*release)(void *obj);
|
||
} ScrVt;
|
||
|
||
/* ── strings ──────────────────────────────────────────────────────────
|
||
* UTF-8 bytes, refcounted, immutable. rc == SIZE_MAX marks an immortal
|
||
* interned literal (emitted as a static object; retain/release are no-ops).
|
||
* data is NUL-terminated for C convenience; len excludes the NUL.
|
||
*
|
||
* cap is the usable byte capacity of data[] excluding the NUL (allocation
|
||
* is sizeof(ScrStr) + cap + 1); cap == len for interned literals and plain
|
||
* allocations. Spare capacity (cap > len) exists only on concat results so
|
||
* scr_str_concat can append in place when the left operand is uniquely
|
||
* owned (rc == 1) — observable immutability is preserved: a string with
|
||
* rc > 1 or rc == SIZE_MAX is never mutated.
|
||
*/
|
||
typedef struct ScrStr {
|
||
size_t rc;
|
||
size_t len;
|
||
size_t cap;
|
||
char data[];
|
||
} ScrStr;
|
||
|
||
ScrStr *scr_str_new(const char *bytes, size_t len); /* returns +1 */
|
||
/* Native callback boundary: copy and WHATWG-decode a UTF-8 span, replacing
|
||
* malformed subsequences with U+FFFD. NULL with len == 0 is an empty span. */
|
||
ScrStr *scr_str_from_utf8_lossy(const uint8_t *bytes, size_t len); /* +1 */
|
||
|
||
/* Internal allocators for buffer builders (scr_json.c): a +1 string with
|
||
* UNINITIALIZED data (the builder fills bytes, then len and the NUL), and
|
||
* an rc==1-only realloc that grows capacity in place. Both keep the RC
|
||
* audit's live count exact. */
|
||
ScrStr *scr_str_alloc_raw(size_t len, size_t cap);
|
||
ScrStr *scr_str_regrow(ScrStr *s, size_t newcap);
|
||
|
||
static inline ScrStr *scr_str_retain(ScrStr *s) {
|
||
if (s->rc != SIZE_MAX) s->rc++;
|
||
return s;
|
||
}
|
||
|
||
void scr_str_release(ScrStr *s); /* NULL-tolerant (uninitialized locals) */
|
||
|
||
/* Borrow both args, return +1. */
|
||
ScrStr *scr_str_concat(ScrStr *a, ScrStr *b);
|
||
|
||
bool scr_str_eq(ScrStr *a, ScrStr *b);
|
||
|
||
/* memcmp byte order == code-point order (see SEMANTICS.md: diverges from
|
||
* JS UTF-16 code-unit order only for non-BMP vs U+E000..U+FFFF). Returns
|
||
* <0, 0, >0. */
|
||
int scr_str_cmp(ScrStr *a, ScrStr *b);
|
||
|
||
/* ECMAScript string-list ordering: compare UTF-16 code units even though
|
||
* ScrStr stores well-formed UTF-8. Returns <0, 0, >0. */
|
||
int scr_str_cmp_u16(ScrStr *a, ScrStr *b);
|
||
|
||
/* ── class objects (classes as first-class values) ────────────────────
|
||
* Top-level classes use immortal templates. Evaluating a function-local
|
||
* class creates a fresh, traced object that owns its captured binding boxes.
|
||
* Every construct thunk takes the borrowed class object before source
|
||
* arguments. Instances of a local class retain that object for identity and
|
||
* method captures. `pre`/`post` still describe the compiled class hierarchy;
|
||
* local-class instanceof additionally compares the fresh object identity. */
|
||
typedef struct ScrClassObj {
|
||
size_t rc;
|
||
size_t pre, post;
|
||
void *ctor;
|
||
const ScrStr *name;
|
||
size_t ncaps;
|
||
size_t length;
|
||
struct ScrBox *caps[];
|
||
} ScrClassObj;
|
||
|
||
static inline ScrClassObj *scr_classobj_retain(ScrClassObj *c) {
|
||
if (c && c->rc != SIZE_MAX) {
|
||
c->rc++;
|
||
scr_cyc_mark_live(c);
|
||
}
|
||
return c;
|
||
}
|
||
void scr_classobj_release(ScrClassObj *c);
|
||
ScrClassObj *scr_classobj_new(const ScrClassObj *template, size_t ncaps);
|
||
void scr_classobj_trace_v(void *c, ScrTraceVisit visit, void *ctx);
|
||
/* void*-signature RC adapters (container slots) — scr_object.c. */
|
||
void *scr_classobj_retain_v(void *c);
|
||
void scr_classobj_release_v(void *c);
|
||
/* `X.name` (+1 — a no-op retain on the interned immortal). */
|
||
ScrStr *scr_classobj_name(ScrClassObj *c);
|
||
/* The keyed-write miss on a fixed-shape record: throws the catchable
|
||
* TypeError naming the key (JS would add the property — the documented
|
||
* monomorphic-struct divergence). scr_object.c. */
|
||
void scr_record_key_miss(ScrStr *k);
|
||
|
||
/* ── error objects (scr_error.c) ──────────────────────────────────────
|
||
* `Error` and its standard subclasses are a
|
||
* RUNTIME-PROVIDED hierarchy: ScrError lays out exactly like a compiler-
|
||
* emitted hierarchy class (rc, vt, then the fields), so `class MyError
|
||
* extends Error` compiles as an ordinary derived class whose struct embeds
|
||
* this prefix, and every vtable mechanism (base-typed release, preorder-
|
||
* interval instanceof) applies unchanged.
|
||
*
|
||
* The builtin classes' vtables live HERE as mutable globals because
|
||
* the runtime itself creates error instances (JSON/dynCheck/regex failures,
|
||
* the island bridge) — but their preorder intervals depend on the whole
|
||
* program's class forest, which only the compiler knows. Every emitted
|
||
* main() stamps pre/post into these vtables before user code runs (the
|
||
* defaults below only cover the moments before that), so runtime-made and
|
||
* compiler-made error objects always agree on instanceof.
|
||
*
|
||
* Cycle capability is hierarchy-uniform and program-dependent (a user
|
||
* subclass may hold closures): when the compiler's fixpoint marks the Error
|
||
* hierarchy cycle-capable, main() also calls scr_error_set_traced() and
|
||
* the runtime allocates its own error objects with collector headers. */
|
||
typedef struct ScrError {
|
||
size_t rc;
|
||
const ScrVt *vt;
|
||
ScrStr *name; /* "TypeError", or whatever the user assigned */
|
||
ScrStr *message; /* "" when constructed without one, like Node */
|
||
ScrStr *code; /* NULL = absent (Node: no `code` property); fs/exec
|
||
* throw sites stamp the errno name ("ENOENT"). Part of
|
||
* the layout prefix: the compiler's %Error class defs
|
||
* carry a matching third field, so user subclasses
|
||
* embed the slot and release it NULL-guarded. */
|
||
struct ScrDyn *error_cause; /* NULL = absent; dyn undefined = present */
|
||
bool cause_enumerable; /* assignment creates an enumerable own property */
|
||
} ScrError;
|
||
|
||
enum {
|
||
SCR_ERR_ERROR = 0,
|
||
SCR_ERR_TYPE = 1,
|
||
SCR_ERR_RANGE = 2,
|
||
SCR_ERR_SYNTAX = 3,
|
||
SCR_ERR_DOMEX = 4, /* DOMException — ScrDomException, the wider layout */
|
||
SCR_ERR_REFERENCE = 5,
|
||
SCR_ERR_EVAL = 6,
|
||
SCR_ERR_URI = 7,
|
||
SCR_ERR_COUNT = 8,
|
||
};
|
||
|
||
extern SCR_TL ScrVt scr_error_vts[SCR_ERR_COUNT]; /* indexed by SCR_ERR_*; main() stamps pre/post */
|
||
|
||
struct ScrDyn; /* full declaration below (the checked-dynamic tree section) */
|
||
|
||
/* DOMException: the ScrError prefix (identical member order — an upcast is
|
||
* a pointer reinterpret) plus the WebIDL slots. The extra slots are HIDDEN
|
||
* from the compiler's IR field list (user `extends DOMException` is fenced
|
||
* so no subclass layout ever overlaps them); reads go through the
|
||
* scr_domex_* accessors. */
|
||
typedef struct ScrDomException {
|
||
size_t rc;
|
||
const ScrVt *vt;
|
||
ScrStr *name; /* "Error" default, or the resolved WebIDL name */
|
||
ScrStr *message; /* "" when constructed without one */
|
||
ScrStr *code; /* the Node string-code slot (stays NULL here) */
|
||
struct ScrDyn *error_cause; /* shared ScrError prefix; unused by DOMException */
|
||
bool cause_enumerable;
|
||
double dom_code; /* the WebIDL legacy code (0 when the name is off-table) */
|
||
bool has_cause; /* the options form carried a `cause` member */
|
||
struct ScrDyn *cause; /* owned; NULL when has_cause is false */
|
||
} ScrDomException;
|
||
|
||
/* new DOMException(message?, nameOrOptions?) — both dyn args borrowed,
|
||
* NULL-tolerant (NULL = absent = the dyn undefined). Returns +1. Never
|
||
* throws (dyn ToString is total). Lives in scr_json.c (the args are dyn
|
||
* values); the dyn-free half below stays in scr_error.c so the error
|
||
* unit links without the checked-dynamic tree. */
|
||
ScrError *scr_domex_new(const struct ScrDyn *message, const struct ScrDyn *name_or_options);
|
||
/* scr_error.c's dyn-free DOMException half: the blank allocation the
|
||
* constructors fill, the WebIDL name→legacy-code table, and the cause
|
||
* teardown hook scr_json.c installs before any cause can exist. */
|
||
ScrError *scr_domex_alloc(void);
|
||
double scr_domex_code_of(const ScrStr *name);
|
||
void scr_domex_install_cause_drop(void (*fn)(void *obj));
|
||
double scr_domex_code(ScrError *e); /* borrowed receiver */
|
||
bool scr_domex_has_cause(ScrError *e); /* borrowed receiver */
|
||
struct ScrDyn *scr_domex_cause(ScrError *e); /* +1 (dyn undefined when absent) */
|
||
/* structuredClone of a DOMException: WebIDL serialization — name/message
|
||
* copy, the code re-derives, cause does not serialize. Borrowed receiver
|
||
* and options (validated; throws on bad options / non-empty transfer).
|
||
* +1, NULL after a throw. */
|
||
ScrError *scr_domex_clone(ScrError *e, const struct ScrDyn *options);
|
||
/* Throw a fresh DOMException through the exception cell (atob/btoa's
|
||
* InvalidCharacterError sites). Copies both C strings; the _str form
|
||
* takes ownership of the message. */
|
||
void scr_throw_domex(const char *name, const char *message);
|
||
void scr_throw_domex_str(const char *name, ScrStr *message);
|
||
|
||
/* Switch runtime-side error allocation to collector-headered (called from
|
||
* main() when the compiler's cycle fixpoint marks the hierarchy). */
|
||
void scr_error_set_traced(void);
|
||
|
||
/* Allocate + initialize (name = the kind's builtin name, message retained
|
||
* from the borrowed argument; NULL means ""). Returns +1. */
|
||
ScrError *scr_error_new(int kind, ScrStr *message);
|
||
/* Initialize the error prefix of an already-allocated (zeroed) object —
|
||
* the super(message) call of a compiled `extends Error` constructor. Both
|
||
* arguments are borrowed. */
|
||
void scr_error_init(void *obj, int kind, ScrStr *message);
|
||
/* ECMAScript constructor arguments: dyn undefined defaults the message
|
||
* to ""; an options object's present cause is retained. All args borrowed.
|
||
* Message conversion can throw; new returns NULL with an exception pending.
|
||
* Kept separate so runtime-only throw sites need not link scr_json.c. */
|
||
ScrError *scr_error_new_options(int kind, const struct ScrDyn *message, const struct ScrDyn *options);
|
||
void scr_error_init_options(void *obj, int kind, const struct ScrDyn *message, const struct ScrDyn *options);
|
||
void scr_error_install_cause_drop(void (*fn)(void *obj));
|
||
bool scr_error_has_cause(ScrError *e);
|
||
struct ScrDyn *scr_error_cause(ScrError *e); /* +1, undefined when absent */
|
||
void scr_error_set_cause(ScrError *e, struct ScrDyn *value); /* borrowed operands */
|
||
void scr_error_delete_cause(ScrError *e);
|
||
/* ECMA Error.prototype.toString: "", name, message, or "name: message".
|
||
* Borrows e, returns +1. */
|
||
ScrStr *scr_error_to_string(ScrError *e);
|
||
|
||
ScrError *scr_error_retain(ScrError *e);
|
||
void scr_error_release(ScrError *e); /* dispatches through e->vt */
|
||
void *scr_error_retain_v(void *e);
|
||
void scr_error_release_v(void *e);
|
||
void scr_error_trace(void *e, ScrTraceVisit visit, void *ctx); /* no headered children */
|
||
ScrTraceFn scr_error_trace_arg(void); /* &scr_error_trace when traced, else NULL */
|
||
|
||
/* True when a thrown hierarchy object (SCR_EXC_OBJ payload) is an Error —
|
||
* its vtable's preorder lies inside Error's stamped interval. */
|
||
bool scr_error_is(const void *obj);
|
||
|
||
/* Throw a fresh builtin error through the exception cell. scr_throw_error
|
||
* takes ownership of message; the _msg form copies the C string. The _named
|
||
* form (island bridge) takes ownership of both strings and picks the
|
||
* builtin vtable whose class name matches `name` (Error otherwise). */
|
||
void scr_throw_error(int kind, ScrStr *message);
|
||
void scr_throw_error_msg(int kind, const char *message, size_t len);
|
||
void scr_throw_error_named(ScrStr *name, ScrStr *message);
|
||
/* A read of a declare-d const nothing defines: throws Node's catchable
|
||
* ReferenceError "<name> is not defined". Borrows name; always throws. */
|
||
void scr_undef_global_read(ScrStr *name);
|
||
/* The `code` slot (NodeJS.ErrnoException's .code): set stamps a fresh
|
||
* string from the C literal (replacing any previous value); get answers
|
||
* +1 or NULL when absent (the compiler's undefined arm). The _msg_code
|
||
* thrower is scr_throw_error_msg with the code stamped on the payload. */
|
||
void scr_error_set_code(ScrError *e, const char *code);
|
||
ScrStr *scr_error_code(ScrError *e);
|
||
void scr_throw_error_msg_code(int kind, const char *message, size_t len, const char *code);
|
||
/* The compiler-resolved Node-parity throw (error.nodeThrow): builtin
|
||
* error of `kind`, `code` stamped when non-empty. Borrows both. */
|
||
void scr_throw_node_coded(double kind, const ScrStr *code, const ScrStr *msg);
|
||
|
||
/* ── string methods ─────────────────────────────────────────────────
|
||
* ECMA-262 observable semantics over UTF-8 storage with UTF-16 code-unit
|
||
* indices. A per-instance side cache keeps length/cursor state and sparse
|
||
* navigation checkpoints for large strings, making warmed
|
||
* non-local indexed operations bounded by one checkpoint interval. All
|
||
* double index/count arguments go through ToIntegerOrInfinity (NaN → 0, trunc
|
||
* toward zero, ±Infinity kept), exactly like JS. Every function borrows its
|
||
* ScrStr arguments; functions returning ScrStr* return a +1 reference.
|
||
*
|
||
* One documented divergence (JS lone surrogates are unrepresentable in
|
||
* well-formed UTF-8): where JS would produce a *lone surrogate* — charAt on
|
||
* half of an astral pair, or a slice boundary that splits a pair — the
|
||
* result contains U+FFFD (EF BF BD) in its place. Numeric results
|
||
* (charCodeAt) are NOT affected: they return the exact surrogate code unit
|
||
* value, computed from the code point.
|
||
*/
|
||
|
||
/* .length — number of UTF-16 code units (astral chars count as 2). */
|
||
double scr_str_utf16_len(ScrStr *s);
|
||
|
||
/* charCodeAt(i): the i-th UTF-16 code unit as a number; for astral chars
|
||
* returns the high (0xD800+) or low (0xDC00+) surrogate value depending on
|
||
* which half i addresses. NaN when ToIntegerOrInfinity(i) is outside
|
||
* [0, length) — note charCodeAt(1.5) is index 1, matching Node. */
|
||
double scr_str_char_code_at(ScrStr *s, double i);
|
||
|
||
/* indexOf(needle, fromIndex): UTF-16 index of the first occurrence at or
|
||
* after fromIndex (clamped to [0, length]), or -1. Empty needle returns the
|
||
* clamped fromIndex per spec. */
|
||
double scr_str_index_of(ScrStr *s, ScrStr *needle, double fromIndex);
|
||
|
||
/* lastIndexOf(needle): the one-argument form returns the last occurrence's
|
||
* UTF-16 index, or -1. Empty needle returns length. */
|
||
double scr_str_last_index_of(ScrStr *s, ScrStr *needle);
|
||
|
||
/* lastIndexOf(needle, position): search at or before the clamped UTF-16
|
||
* position. NaN starts at the end, including for an empty needle. */
|
||
double scr_str_last_index_of_from(ScrStr *s, ScrStr *needle, double position);
|
||
|
||
/* includes(needle) — no position argument. Empty needle → true. */
|
||
bool scr_str_includes(ScrStr *s, ScrStr *needle);
|
||
|
||
/* startsWith(needle) / endsWith(needle) — one-argument forms. */
|
||
bool scr_str_starts_with(ScrStr *s, ScrStr *needle);
|
||
bool scr_str_ends_with(ScrStr *s, ScrStr *needle);
|
||
|
||
/* Positioned forms clamp UTF-16 indices to [0, length]. The empty needle
|
||
* matches at every clamped position. */
|
||
bool scr_str_starts_with_from(ScrStr *s, ScrStr *needle, double position);
|
||
bool scr_str_ends_with_from(ScrStr *s, ScrStr *needle, double end_position);
|
||
|
||
/* slice(start, end): UTF-16 indices, negatives count from length, clamped
|
||
* to [0, length]; empty when start >= end. The frontend passes
|
||
* end = Infinity for the one-argument form. Boundaries that split an astral
|
||
* pair yield U+FFFD for the half character (divergence, see above).
|
||
* Returns +1. */
|
||
ScrStr *scr_str_slice(ScrStr *s, double start, double end);
|
||
/* substring: clamp-and-swap (negatives clamp to 0, start > end swaps). */
|
||
ScrStr *scr_str_substring(ScrStr *s, double start, double end);
|
||
|
||
/* repeat(count): count < 0 or Infinity is a JS RangeError; scriptc has no
|
||
* exceptions, so it prints "scriptc: RangeError: Invalid count value" to
|
||
* stderr and abort()s. Returns +1. */
|
||
ScrStr *scr_str_repeat(ScrStr *s, double count);
|
||
|
||
/* trim(): strips the exact JS WhiteSpace ∪ LineTerminator set from both
|
||
* ends (U+0009-U+000D, U+0020, U+00A0, U+1680, U+2000..U+200A, U+2028,
|
||
* U+2029, U+202F, U+205F, U+3000, U+FEFF). Returns +1. */
|
||
ScrStr *scr_str_trim(ScrStr *s);
|
||
|
||
/* toLowerCase()/toUpperCase(): ECMA-262 Default Case Conversion (final
|
||
* sigma included) via libunicode's lre_case_conv — implemented in
|
||
* scr_regex.c, so they link exactly into lre-using binaries (the compiler
|
||
* sets the regex link flag for case-conversion sites). Borrow s; +1. */
|
||
ScrStr *scr_str_to_lower(const ScrStr *s);
|
||
ScrStr *scr_str_to_upper(const ScrStr *s);
|
||
|
||
/* charAt(i): 1-code-unit string; out of range → empty string. Half of an
|
||
* astral pair → U+FFFD (divergence, see above). Returns +1. */
|
||
ScrStr *scr_str_char_at(ScrStr *s, double i);
|
||
|
||
/* The string iterator's step (for-of over strings): the full code-POINT
|
||
* character starting at UTF-16 index i — astral chars come back whole
|
||
* (two units); the consuming loop advances by the result's length.
|
||
* Out of range → empty string. Returns +1. */
|
||
ScrStr *scr_str_cp_at(ScrStr *s, double i);
|
||
|
||
/* trimStart()/trimEnd(): the one-sided halves of trim. Returns +1. */
|
||
ScrStr *scr_str_trim_start(ScrStr *s);
|
||
ScrStr *scr_str_trim_end(ScrStr *s);
|
||
|
||
/* split(separator[, limit]) with a STRING separator: limit uses ToUint32;
|
||
* the no-limit wrapper supplies 2^32-1. Empty separator
|
||
* splits into single UTF-16 code units (each half of an astral char is
|
||
* U+FFFD — divergence, see above); otherwise splits on every occurrence,
|
||
* empty pieces kept. Borrows both; returns a +1 string[] (SCR_ELEM_STR). */
|
||
struct ScrArr;
|
||
struct ScrArr *scr_str_split(ScrStr *s, ScrStr *sep);
|
||
struct ScrArr *scr_str_split_limit(ScrStr *s, ScrStr *sep, double limit);
|
||
|
||
/* padStart(maxLength, fill)/padEnd — ECMA StringPad, UTF-16 unit counts.
|
||
* Target at or below the length (or empty fill) returns the receiver
|
||
* (retained). A truncated fill that would split an astral char emits
|
||
* U+FFFD for the kept half. Oversized pads abort (the repeat() policy).
|
||
* Borrow both; return +1. */
|
||
ScrStr *scr_str_pad_start(ScrStr *s, double maxLength, ScrStr *fill);
|
||
ScrStr *scr_str_pad_end(ScrStr *s, double maxLength, ScrStr *fill);
|
||
|
||
/* isWellFormed()/toWellFormed(): storage is well-formed by invariant
|
||
* (lone surrogates became U+FFFD at their producers, SEMANTICS.md 2), so
|
||
* isWellFormed is constant true and toWellFormed the identity (per spec
|
||
* both are no-ops on well-formed input). Borrow s; toWellFormed +1. */
|
||
bool scr_str_is_well_formed(ScrStr *s);
|
||
ScrStr *scr_str_to_well_formed(ScrStr *s);
|
||
|
||
/* RegExp.escape(s) — ES2025 EncodeForRegExpEscape per code point: leading
|
||
* ASCII alphanumeric hex-escapes, SyntaxCharacters and '/' take a
|
||
* backslash, other punctuators/whitespace/line terminators hex-escape
|
||
* (\xNN / \uNNNN, lowercase), the rest passes through. Total (the spec's
|
||
* surrogate arm is unreachable over well-formed storage). Lives in
|
||
* scr_regex.c (no engine needed — the placement keeps the always-linked
|
||
* string TU out of hello-world's size class); use sites flip the regex
|
||
* link switch (moduleUsesRegex). Borrows; +1. */
|
||
ScrStr *scr_regexp_escape(ScrStr *s);
|
||
|
||
/* parseInt(s, radix) — ECMA-262 19.2.5 exactly: JS whitespace skip, sign,
|
||
* ToInt32 radix (0 → 10 with the 0x hex escape; outside 2..36 → NaN),
|
||
* longest digit prefix, exact value correctly rounded (overflow →
|
||
* ±Infinity). The frontend completes an omitted radix to 0. Borrows s. */
|
||
double scr_parse_int(ScrStr *s, double radix);
|
||
double scr_parse_float(ScrStr *s);
|
||
|
||
/* ToNumber(string) — ECMA-262 7.1.4.1 StringToNumber exactly: trim
|
||
* StrWhiteSpace (the JS set, line terminators included) from both ends,
|
||
* empty/whitespace-only → +0, then the WHOLE remaining span must be one
|
||
* StrNumericLiteral — a signed decimal literal ("Infinity" exact-case
|
||
* included, correctly rounded via strtod over the validated span) or an
|
||
* UNSIGNED 0x/0o/0b integer literal (exact value rounded to nearest-even;
|
||
* a sign on those is NaN, per grammar). Any trailing garbage → NaN.
|
||
* Borrows s; never throws. */
|
||
double scr_string_to_number(ScrStr *s);
|
||
|
||
/* encodeURIComponent — ECMA-262 Encode with the component unreserved set
|
||
* (ALPHA/DIGIT/- _ . ! ~ * ' ( )): every other byte of the UTF-8 string
|
||
* percent-encodes as uppercase %XX (the spec's per-code-point UTF-8
|
||
* encoding IS a byte scan over UTF-8 storage). Never throws — the spec's
|
||
* URIError is the unpaired surrogate, which well-formed UTF-8 cannot
|
||
* hold. Borrows s; result +1. */
|
||
ScrStr *scr_str_encode_uri_component(ScrStr *s);
|
||
|
||
/* decodeURIComponent — ECMA-262 Decode with the empty reserved set: %XX
|
||
* escapes decode bytewise (raw non-escape bytes copy through) and escaped
|
||
* multibyte sequences must be strictly valid UTF-8 (overlong forms,
|
||
* surrogates, >U+10FFFF refused — UTF8-decode without replacement). Bad
|
||
* hex or an invalid sequence THROWS the spec's catchable URIError ("URI
|
||
* malformed") and returns NULL. Borrows s; result +1. */
|
||
ScrStr *scr_str_decode_uri_component(ScrStr *s);
|
||
|
||
/* The non-throwing core of decodeURIComponent: NULL on malformed input
|
||
* instead of the URIError — the try/catch shape node:querystring's
|
||
* unescape wraps around decodeURIComponent (scr_qs.c's strict pass). */
|
||
ScrStr *scr_str_decode_uri_component_try(ScrStr *s);
|
||
|
||
/* ── node:querystring (scr_qs.c — LINK-GATED by moduleUsesQs; the
|
||
* scr_url_params.c precedent: pure data transforms, no loop hooks).
|
||
* querystring.escape needs no entry here: Node's qsEscape encodes exactly
|
||
* the component unreserved set, so the frontend lowers it to
|
||
* scr_str_encode_uri_component (always linked; a program using only
|
||
* escape never pulls this unit). */
|
||
|
||
/* querystring.unescape — Node's qsUnescape: strict decodeURIComponent
|
||
* first, and on failure the lenient legacy unescapeBuffer(s).toString()
|
||
* (valid %XX escapes decode to their byte, malformed escapes copy
|
||
* literally, every non-escape UTF-16 CODE UNIT truncates to its low byte
|
||
* — Node's Buffer element write — and the byte buffer decodes as UTF-8
|
||
* with U+FFFD replacement per maximal subpart, Buffer.toString's rule).
|
||
* Borrows s; result +1; never throws. */
|
||
ScrStr *scr_qs_unescape(const ScrStr *s);
|
||
|
||
/* querystring.parse — Node v24's scan state machine, byte-wise over the
|
||
* UTF-8 storage (equivalent to the code-unit scan for well-formed input:
|
||
* the machine compares sep/eq sequences positionally and treats '%'/'+'/
|
||
* hex as ASCII, and multibyte alignment is preserved). Decoded pairs land
|
||
* in `out` — the PURE-index-signature Dict record's overflow map (the
|
||
* emitters pass rec->sc_ovf) — grouped like Node's addKeyVal: a first
|
||
* value stores the `str_tag` union arm, a repeat REPLACES it with a
|
||
* two-element string[] wrapped in `arr_tag`, later repeats push. sep/eq
|
||
* fall back to "&"/"=" when empty (Node's falsy rule; the frontend
|
||
* completes omitted/null arguments to the defaults). max_keys is Node's
|
||
* rule exactly: > 0 caps the PAIR count (empty skipped segments count,
|
||
* like Node's --pairs), anything else (0, negatives, NaN) is unlimited;
|
||
* the frontend completes the omitted option to 1000. Only the DEFAULT
|
||
* decoder runs (custom decodeURIComponent options fence at compile time),
|
||
* so '+' means ' ' and segments decode with scr_qs_unescape's semantics
|
||
* only when they carry a full valid %XX triple (Node's encodeCheck).
|
||
* Borrows everything; never throws. */
|
||
typedef struct ScrMap ScrMap; /* full definition below (C11 repeat) */
|
||
void scr_qs_parse_into(ScrMap *out, const ScrStr *qs, const ScrStr *sep,
|
||
const ScrStr *eq, double max_keys, uint32_t str_tag,
|
||
uint32_t arr_tag);
|
||
|
||
/* node:util.parseArgs — the static checked-dynamic boundary. `config` is
|
||
* the documented JSON-safe configuration object; the returned dyn tree is
|
||
* a fresh ParsedResults object whose `values` child has null prototype.
|
||
* Borrows config; returns +1, or NULL with a Node-coded TypeError pending. */
|
||
struct ScrDyn *scr_util_parse_args(const struct ScrDyn *config);
|
||
|
||
/* querystring.stringify — Node's stringify over a borrowed dyn value (the
|
||
* frontend dynFroms the typed record; JS-world dyn values pass straight
|
||
* through). Non-object dyn values answer "" like Node; object keys iterate in
|
||
* JS own-key order (scr_dyn_obj_keys — array-index keys ascending first,
|
||
* then insertion order, Node's ObjectKeys). Values serialize per Node's
|
||
* encodeStringified: strings escape, finite numbers render shortest-
|
||
* roundtrip then escape ('1e+21' → '1e%2B21'), booleans are bare
|
||
* true/false, arrays expand to repeated keys (empty arrays emit NOTHING,
|
||
* key included), and everything else (null, undefined, nested objects/
|
||
* arrays, functions) is the empty value — key and eq still emitted.
|
||
* sep/eq fall back to "&"/"=" when empty (Node's `sep ||= '&'`). Result
|
||
* +1; never throws. */
|
||
ScrStr *scr_qs_stringify(const struct ScrDyn *obj, const ScrStr *sep,
|
||
const ScrStr *eq);
|
||
|
||
/* encodeURI — the same ECMA-262 Encode() with the reserved set and '#'
|
||
* kept unescaped (scr_encode_uri_impl's keep_reserved arm). Total by the
|
||
* well-formed-UTF-8 invariant. Borrows s; result +1. */
|
||
ScrStr *scr_encode_uri(ScrStr *s);
|
||
/* atob/btoa — the WHATWG base64 globals (Node globals since v16). The
|
||
* argument is a borrowed dyn value: WebIDL ToString runs here over the
|
||
* dyn kind (Node's atob(null) decodes "null"). scr_atob decodes
|
||
* forgiving-base64 (ASCII whitespace stripped, %4==0 strips up to two
|
||
* '=', %4==1 refuses, leftover bits discarded) into the latin1 code
|
||
* points as a UTF-8 string; malformed input THROWS the catchable
|
||
* DOMException InvalidCharacterError ("The string to be decoded is not
|
||
* correctly encoded.") and returns NULL. scr_btoa encodes the string's
|
||
* code points (any over U+00FF throws InvalidCharacterError "Invalid
|
||
* character"). scr_b64_missing_arg is the zero-argument call of either:
|
||
* always throws Node's TypeError [ERR_MISSING_ARGS] and returns NULL.
|
||
* Results +1. */
|
||
ScrStr *scr_atob(const struct ScrDyn *data);
|
||
ScrStr *scr_btoa(const struct ScrDyn *data);
|
||
ScrStr *scr_b64_missing_arg(void);
|
||
|
||
/* ── arrays ─────────────────────────────────────────────────────────
|
||
* Monomorphic, growable, refcounted. Elements live in 8-byte slots
|
||
* (doubles/bools/pointers via memcpy and casts); `elem` records what the
|
||
* slots hold so the array can release its own reference elements — ScrStr's
|
||
* layout is untouched (interned-literal statics depend on it), so the
|
||
* element kind lives here instead of in a shared object header. A separate
|
||
* presence bitset distinguishes a hole from a present `undefined`-carrying
|
||
* reference value. Very large indices stay in a sorted sparse side store;
|
||
* the dense slot array is retained for ordinary packed arrays.
|
||
*
|
||
* Index arguments are doubles (JS numbers). A valid array index is a
|
||
* non-negative integer below 2^32-1; reads must be < len and writes may
|
||
* extend len through a sparse hole. Noncanonical numeric keys are ordinary
|
||
* properties stored by their JavaScript number-to-string spelling. Anything
|
||
* else prints
|
||
* scriptc: RangeError: array index <i> out of bounds (length <n>)
|
||
* to stderr and abort()s in the proven-presence accessors. `scr_arr_has` is
|
||
* the shared presence query used before those accessors when the language
|
||
* surface permits an undefined result.
|
||
*
|
||
* Ownership: _get_ref returns +1 (retains before returning); _set_ref and
|
||
* _push_ref take ownership of the new element (and _set_ref releases the
|
||
* old one); _pop_ref transfers the element's reference out to the caller.
|
||
*/
|
||
|
||
typedef enum {
|
||
SCR_ELEM_F64,
|
||
SCR_ELEM_BOOL,
|
||
SCR_ELEM_STR, /* slots are ScrStr* — released with the array */
|
||
SCR_ELEM_ARR, /* slots are ScrArr* — released recursively */
|
||
SCR_ELEM_BYTES, /* slots are ScrBytes* — released with the array */
|
||
/* Slots are void* to values the runtime cannot lay out itself — records,
|
||
* class instances, unions, and cycle-capable inner arrays. The element
|
||
* type's RC entry points arrive at construction (scr_arr_new_ref) and are
|
||
* stored ONCE per array, the SCR_BOX_OBJ / map-value technique; every
|
||
* array is still monomorphic (one element type, one set of entry points). */
|
||
SCR_ELEM_REF,
|
||
} ScrElemKind;
|
||
|
||
typedef enum {
|
||
SCR_ARR_HOLE = 0,
|
||
SCR_ARR_VALUE = 1,
|
||
SCR_ARR_UNDEFINED = 2,
|
||
} ScrArrState;
|
||
|
||
typedef struct ScrArr {
|
||
size_t rc; /* SIZE_MAX = immortal (unused for arrays; kept per convention) */
|
||
size_t len;
|
||
size_t cap; /* dense capacity; sparse entries cover indices >= cap */
|
||
ScrElemKind elem;
|
||
/* SCR_ELEM_REF only; NULL for every other element kind. elem_trace is
|
||
* non-NULL exactly when the element type carries a cycle header: such
|
||
* arrays are CYCLE-CAPABLE (an element can point back at the array that
|
||
* owns it) and allocate with the hidden collector header — their trace
|
||
* visits every element, and the collector teardown frees only the slot
|
||
* storage (all elements are traced children). Arrays of acyclic ref
|
||
* elements and the historic element kinds keep the lean 1-word header. */
|
||
void *(*elem_retain)(void *);
|
||
void (*elem_release)(void *);
|
||
ScrTraceFn elem_trace;
|
||
uint64_t *data;
|
||
uint8_t *present; /* one ScrArrState per dense slot */
|
||
struct ScrArrSparseSlot *sparse;
|
||
size_t sparse_len;
|
||
size_t sparse_cap;
|
||
struct ScrArrProp *props; /* noncanonical numeric properties */
|
||
size_t prop_len;
|
||
size_t prop_cap;
|
||
} ScrArr;
|
||
|
||
typedef struct ScrArrSparseSlot {
|
||
size_t index;
|
||
uint64_t slot;
|
||
uint8_t state;
|
||
} ScrArrSparseSlot;
|
||
|
||
typedef struct ScrArrProp {
|
||
char *key;
|
||
size_t key_len;
|
||
uint64_t slot;
|
||
uint8_t state; /* SCR_ARR_VALUE or SCR_ARR_UNDEFINED */
|
||
} ScrArrProp;
|
||
|
||
ScrArr *scr_arr_new(ScrElemKind elem, size_t initial_cap); /* returns +1 */
|
||
|
||
/* SCR_ELEM_REF construction: retain/release must be non-NULL (the compiler
|
||
* passes the element type's `_v` adapters); trace is non-NULL iff the
|
||
* element type carries a cycle header (the array then allocates with the
|
||
* collector header — see the struct comment). Returns +1. */
|
||
ScrArr *scr_arr_new_ref(void *(*elem_retain)(void *),
|
||
void (*elem_release)(void *),
|
||
ScrTraceFn elem_trace, size_t initial_cap);
|
||
|
||
static inline ScrArr *scr_arr_retain(ScrArr *a) {
|
||
if (a->rc != SIZE_MAX) {
|
||
a->rc++;
|
||
if (a->elem_trace) scr_cyc_mark_live(a);
|
||
}
|
||
return a;
|
||
}
|
||
|
||
/* Releases ref elements (SCR_ELEM_STR/ARR/BYTES/REF) recursively at rc == 0.
|
||
* NULL-tolerant (uninitialized locals). Cycle-capable arrays (elem_trace
|
||
* non-NULL) feed the candidate-root buffer like every headered type. */
|
||
void scr_arr_release(ScrArr *a);
|
||
|
||
/* Trace entry point for cycle-capable arrays — stored in their collector
|
||
* header and passed wherever a container stores an ARRAY payload's RC entry
|
||
* points and the array type is cycle-capable (boxes, union arms, map
|
||
* values, promise payloads), exactly like scr_map_trace_v. */
|
||
void scr_arr_trace_v(void *a, ScrTraceVisit visit, void *ctx);
|
||
|
||
double scr_arr_len(ScrArr *a);
|
||
/* Shared hole/property query. Canonical indices answer from indexed storage;
|
||
* noncanonical numeric keys answer from the ordinary-property vector. Missing
|
||
* keys and indices >= length answer false without trapping; callers that need
|
||
* a value must still use the proven-presence get accessors below. */
|
||
bool scr_arr_has(const ScrArr *a, double i);
|
||
double scr_arr_state(const ScrArr *a, double i);
|
||
double scr_arr_next_present(const ScrArr *a, double start);
|
||
/* Set Array.prototype.length semantics for the supported native array
|
||
* surface. Growth creates holes without allocating a dense prefix; shrinking
|
||
* releases removed reference elements. Invalid lengths set a catchable
|
||
* RangeError and leave the array unchanged. */
|
||
void scr_arr_set_len(ScrArr *a, double length);
|
||
/* Copy one slot from src into dst, retaining reference elements when the
|
||
* source slot is present. The destination must already have a length that
|
||
* includes dst_index. Holes remain holes. */
|
||
void scr_arr_copy_index(ScrArr *dst, size_t dst_index, const ScrArr *src,
|
||
size_t src_index);
|
||
/* Copy a range while preserving holes. `dst_start` and `src_start` are the
|
||
* starts of equal-length ranges; with reverse=true the source range is
|
||
* copied in reverse order. Reference elements are retained. */
|
||
void scr_arr_copy_range(ScrArr *dst, size_t dst_start, const ScrArr *src,
|
||
size_t src_start, size_t count, bool reverse);
|
||
void scr_arr_copy_range_ex(ScrArr *dst, size_t dst_start, const ScrArr *src,
|
||
size_t src_start, size_t count, bool reverse,
|
||
bool materialize_holes);
|
||
/* Math.max(...xs) / Math.min(...xs) over an f64-element array: the JS
|
||
* fold (NaN poisons, ±0 by the JS preferences, empty → ∓Infinity). */
|
||
double scr_math_max_arr(ScrArr *a);
|
||
double scr_math_min_arr(ScrArr *a);
|
||
double scr_math_hypot_arr(ScrArr *a);
|
||
/* The scalar Math statics (scr_lib.c): min/max are the ECMA two-argument
|
||
* folds (NaN poisons; max prefers +0, min prefers -0 — NOT C's fmin/fmax);
|
||
* random is a uniform [0,1) double at 53-bit granularity from
|
||
* arc4random_buf (SEMANTICS.md 62: Node's distribution, not its sequence). */
|
||
double scr_math_min(double a, double b);
|
||
double scr_math_round(double x);
|
||
double scr_math_pow(double base, double exponent);
|
||
double scr_math_clz32(double x);
|
||
double scr_math_fround(double x);
|
||
double scr_math_imul(double a, double b);
|
||
double scr_math_max(double a, double b);
|
||
double scr_math_random(void);
|
||
|
||
double scr_arr_get_f64(ScrArr *a, double i); /* trap missing/hole */
|
||
/* ToNumber(a[i]) for f64 storage: borrows a; missing/hole/undefined -> NaN. */
|
||
double scr_arr_get_number(const ScrArr *a, double i);
|
||
/* Strict equality of ordinary indexed reads on matching primitive arrays.
|
||
* Borrows both arrays, preserves undefined vs NaN, and never allocates. */
|
||
bool scr_arr_index_eq(const ScrArr *a, double i, const ScrArr *b, double j);
|
||
bool scr_arr_get_bool(ScrArr *a, double i); /* trap missing/hole */
|
||
void *scr_arr_get_ref(ScrArr *a, double i); /* trap missing/hole; +1 */
|
||
|
||
/* i == len appends; the _ref variant releases the old element (when
|
||
* replacing) and takes ownership of the new one. */
|
||
void scr_arr_set_f64(ScrArr *a, double i, double v);
|
||
void scr_arr_set_bool(ScrArr *a, double i, bool v);
|
||
void scr_arr_set_ref(ScrArr *a, double i, void *v);
|
||
void scr_arr_set_undefined(ScrArr *a, double i);
|
||
bool scr_arr_delete(ScrArr *a, double i);
|
||
|
||
/* slice(start?, end?): a fresh +1 shallow copy of the index range —
|
||
* ToIntegerOrInfinity indices, negatives from the end, clamping; ref
|
||
* elements retain into the copy. Borrows a. */
|
||
ScrArr *scr_arr_slice(ScrArr *a, double start, double end);
|
||
|
||
/* ES2023 copying methods. All borrow their inputs and return fresh +1
|
||
* shallow copies. with() raises Node's catchable RangeError for an invalid
|
||
* relative index; the ref variant retains the borrowed replacement. */
|
||
ScrArr *scr_arr_to_reversed(const ScrArr *a);
|
||
ScrArr *scr_arr_to_spliced(const ScrArr *a, double start,
|
||
double delete_count, const ScrArr *items);
|
||
ScrArr *scr_arr_with_f64(ScrArr *a, double index, double value);
|
||
ScrArr *scr_arr_with_bool(ScrArr *a, double index, bool value);
|
||
ScrArr *scr_arr_with_ref(ScrArr *a, double index, void *value);
|
||
ScrArr *scr_arr_with_undefined(ScrArr *a, double index);
|
||
|
||
/* push returns the new length (JS-exact); _ref takes ownership. */
|
||
double scr_arr_push_f64(ScrArr *a, double v);
|
||
double scr_arr_push_bool(ScrArr *a, bool v);
|
||
double scr_arr_push_ref(ScrArr *a, void *v);
|
||
/* Append a same-element-kind array through the JS iterator contract: holes
|
||
* become present undefined states. The source is borrowed and self-spread
|
||
* duplicates the original indexed state. */
|
||
double scr_arr_push_spread(ScrArr *a, const ScrArr *src);
|
||
/* Append a concat argument by indexed property copy, preserving holes. */
|
||
double scr_arr_concat_copy(ScrArr *a, const ScrArr *src);
|
||
|
||
/* unshift returns the new length and _ref takes ownership. The spread form
|
||
* borrows a same-element-kind source, retains copied refs, and snapshots
|
||
* self-spread. reverse mutates in place and returns the receiver at +1. */
|
||
double scr_arr_unshift_f64(ScrArr *a, double v);
|
||
double scr_arr_unshift_bool(ScrArr *a, bool v);
|
||
double scr_arr_unshift_ref(ScrArr *a, void *v);
|
||
double scr_arr_unshift_spread(ScrArr *a, const ScrArr *src);
|
||
ScrArr *scr_arr_reverse(ScrArr *a);
|
||
|
||
/* pop traps on an empty array; _ref transfers ownership out (+1 to the
|
||
* caller, no release). */
|
||
double scr_arr_pop_f64(ScrArr *a);
|
||
bool scr_arr_pop_bool(ScrArr *a);
|
||
void *scr_arr_pop_ref(ScrArr *a);
|
||
uint8_t scr_arr_pop_state(ScrArr *a, uint64_t *slot_out);
|
||
|
||
/* shift: the first element out, tail sliding down. The emitter guards the
|
||
* empty array (JS's undefined — the `elem | undefined` union), so empty
|
||
* here is an internal error; _ref transfers ownership out like pop. */
|
||
double scr_arr_shift_f64(ScrArr *a);
|
||
bool scr_arr_shift_bool(ScrArr *a);
|
||
void *scr_arr_shift_ref(ScrArr *a);
|
||
uint8_t scr_arr_shift_state(ScrArr *a, uint64_t *slot_out);
|
||
|
||
/* splice(start, deleteCount) — the REMOVAL forms: Node-exact
|
||
* relative/clamped start, count clamped to [0, len - start] (+Infinity =
|
||
* to the end). Returns the removed elements in order as a fresh +1 array,
|
||
* ownership MOVED out of the receiver. Borrows a. */
|
||
ScrArr *scr_arr_splice(ScrArr *a, double start, double deleteCount);
|
||
ScrArr *scr_arr_splice_insert(ScrArr *a, double start, double deleteCount,
|
||
const ScrArr *items);
|
||
ScrArr *scr_arr_flat_copy(const ScrArr *a, ScrArr *out, bool flatten);
|
||
|
||
/* indexOf: first index whose element strictly equals (JS ===) the needle,
|
||
* or -1. Per element kind: f64 by value (NaN never matches — NaN !== NaN;
|
||
* -0 matches 0), bool by value, SCR_ELEM_STR by CONTENT (JS strings are
|
||
* primitives), SCR_ELEM_ARR/SCR_ELEM_REF by reference identity (JS object
|
||
* equality; the compiler fences union-element arrays, whose boxes are
|
||
* compiler artifacts pointer identity would misjudge). The _ref variants
|
||
* BORROW the needle (no ownership change). */
|
||
double scr_arr_index_of_f64(ScrArr *a, double v);
|
||
double scr_arr_index_of_bool(ScrArr *a, bool v);
|
||
double scr_arr_index_of_ref(ScrArr *a, void *v);
|
||
|
||
/* includes: SameValueZero — exactly indexOf's equality EXCEPT NaN matches
|
||
* NaN (JS: [NaN].indexOf(NaN) === -1 but [NaN].includes(NaN) === true). */
|
||
bool scr_arr_includes_f64(ScrArr *a, double v);
|
||
bool scr_arr_includes_bool(ScrArr *a, bool v);
|
||
bool scr_arr_includes_ref(ScrArr *a, void *v);
|
||
|
||
/* join(sep): elements stringified exactly like JS String(x) — numbers via
|
||
* scr_f64_to_str (shortest roundtrip), booleans "true"/"false", strings
|
||
* verbatim — with sep between. f64/bool/str element kinds only (the
|
||
* compiler rejects the rest). Borrows both args; returns +1. */
|
||
ScrStr *scr_arr_join(ScrArr *a, ScrStr *sep);
|
||
/* String.raw over the raw literals and pre-stringified substitutions
|
||
* (both SCR_ELEM_STR): the spec's interleave — extra substitutions drop,
|
||
* missing ones skip. Borrows both; +1 result; never throws. */
|
||
ScrStr *scr_str_raw(ScrArr *raw, ScrArr *subs);
|
||
|
||
/* ── regular expressions (scr_regex.c — linked ONLY when the program
|
||
* contains a regex literal; see native-toolchain.ts) ────────────────────────────────
|
||
* The engine is quickjs-ng's libregexp (the same bytecode interpreter the
|
||
* dynamic island uses), compiled standalone. A ScrRegex is today ALWAYS an
|
||
* immortal interned literal: the compiler emits one static per distinct
|
||
* (pattern, flags) pair, exactly like string literals, and the bytecode is
|
||
* compiled lazily on first use (cached on the struct, freed at exit).
|
||
* Subjects are matched as UTF-16 (converted per call from the UTF-8
|
||
* storage), so all observable index behavior is UTF-16-exact like Node.
|
||
*
|
||
* Statefulness fence: /g and /y regexes carry mutable lastIndex in JS.
|
||
* Supported only where iteration is internal (replace/replaceAll/split);
|
||
* test() on a g/y-flagged regex aborts with a clear message.
|
||
*
|
||
* Ownership: subjects/replacements are BORROWED; string/array results
|
||
* return +1. replace_all without /g THROWS a catchable TypeError
|
||
* (callers are compiler-emitted pending
|
||
* checks); every other failure mode aborts.
|
||
*/
|
||
typedef struct ScrRegex {
|
||
size_t rc; /* SIZE_MAX = immortal (every regex literal) */
|
||
ScrStr *source; /* pattern text between the slashes */
|
||
ScrStr *flags; /* flags text, source order (alphabet fenced to gimsuy) */
|
||
uint8_t *bc; /* lazily compiled libregexp bytecode; NULL until first use */
|
||
} ScrRegex;
|
||
|
||
static inline ScrRegex *scr_regex_retain(ScrRegex *re) {
|
||
if (re->rc != SIZE_MAX) re->rc++;
|
||
return re;
|
||
}
|
||
|
||
void scr_regex_release(ScrRegex *re); /* NULL-tolerant */
|
||
void *scr_regex_retain_v(void *re);
|
||
void scr_regex_release_v(void *re);
|
||
|
||
/* new RegExp(pattern, flags): a heap ScrRegex over the same engine. The
|
||
* pattern compiles EAGERLY — an invalid pattern or flag throws Node's
|
||
* catchable SyntaxError at construction (detail text is libregexp's,
|
||
* approximate fidelity; e.name exact). An empty pattern stores the
|
||
* spec's "(?:)" source. Borrows both; +1, NULL after a throw. */
|
||
ScrRegex *scr_regex_new(ScrStr *pattern, ScrStr *flags);
|
||
|
||
bool scr_regex_test(ScrRegex *re, ScrStr *s); /* aborts on /g or /y */
|
||
/* s.match(re): +1 string[] of [whole, ...captures] (nonparticipating
|
||
* captures hold "" — SEMANTICS.md), or NULL for no match. Aborts on /g
|
||
* or /y like test(). Borrows both. */
|
||
ScrArr *scr_regex_match(ScrStr *s, ScrRegex *re);
|
||
/* s.search(re): the first match's UTF-16 index, or -1 — a fresh exec from
|
||
* position 0 (Symbol.search never touches lastIndex, so no flag fence:
|
||
* /g is irrelevant, /y anchors at 0). Borrows both; never throws. */
|
||
double scr_regex_search(ScrStr *s, ScrRegex *re);
|
||
ScrStr *scr_regex_source(ScrRegex *re); /* +1 */
|
||
ScrStr *scr_regex_flags(ScrRegex *re); /* +1 */
|
||
ScrStr *scr_regex_to_string(ScrRegex *re); /* +1 */
|
||
/* replace: first match without /g, every match with it. */
|
||
ScrStr *scr_regex_replace(ScrStr *s, ScrRegex *re, ScrStr *rep);
|
||
/* replaceAll: throws Node's TypeError when /g is missing (may-throw). */
|
||
ScrStr *scr_regex_replace_all(ScrStr *s, ScrRegex *re, ScrStr *rep);
|
||
/* split: captured substrings are interleaved with the split pieces;
|
||
* unmatched groups become present undefined elements, not holes.
|
||
* limit uses ToUint32; the no-limit wrapper supplies 2^32-1. */
|
||
ScrArr *scr_regex_split(ScrStr *s, ScrRegex *re);
|
||
ScrArr *scr_regex_split_limit(ScrStr *s, ScrRegex *re, double limit);
|
||
/* matchAll drained eagerly: +1 string[][] of honest match slices; throws
|
||
* Node's TypeError on a non-global regex (catchable). */
|
||
ScrArr *scr_regex_match_all(ScrStr *s, ScrRegex *re);
|
||
/* matchAll + the companion-index drain: also pushes each match's UTF-16
|
||
* start index (f64) onto `indices` — the .index the for-of-over-matchAll
|
||
* desugar reads. Same throw/result contract as scr_regex_match_all. */
|
||
ScrArr *scr_regex_match_all_into(ScrStr *s, ScrRegex *re, ScrArr *indices);
|
||
|
||
/* ── maps (scr_map.c) ───────────────────────────────────────────────
|
||
* ES Map<K, V> with the compact-dict layout: a dense, insertion-ordered
|
||
* entries array (deletions become tombstones; compaction happens on growth,
|
||
* and NEVER while an iteration is active) plus an open-addressing bucket
|
||
* table of entry indices. That is what gives JS's observable semantics for
|
||
* free: forEach visits in insertion order, entries added during iteration
|
||
* are visited (they append), deleted entries are skipped (tombstones), and
|
||
* a delete + re-add moves the key to the end (Node-verified).
|
||
*
|
||
* Keys are identity references, string (content), or number with SameValueZero: NaN equals NaN
|
||
* (canonicalized before hashing) and -0 is normalized to +0 at insertion,
|
||
* exactly like JS (a stored -0 key reads back as +0). Hash is FNV-1a over
|
||
* the reference address, string bytes, or canonicalized f64 bit pattern.
|
||
*
|
||
* Values are one uniform kind per map (like ScrArr elements): f64, bool, or
|
||
* a refcounted pointer whose RC entry points arrive as function pointers at
|
||
* construction (the SCR_BOX_OBJ technique — the runtime cannot know
|
||
* per-class/per-record layouts). key_trace and val_trace are non-NULL when
|
||
* their respective types carry cycle headers. Either side can point back
|
||
* at its map, so either adapter requires a hidden collector header. Trace
|
||
* visits each live edge on the traced sides; collector teardown releases
|
||
* the untraced complement. Maps with neither adapter use a lean RC header.
|
||
*
|
||
* Ownership: set BORROWS the key (the map retains string/reference keys it stores)
|
||
* and OWNS the value (+1 moves in; replacing releases the old value).
|
||
* get/has/delete borrow the key; get returns +1 on ref values (NULL = not
|
||
* found) or fills an out-param and returns a found flag for scalars.
|
||
* delete releases the entry's key and value. iter_key_str/ref and iter_val_ref
|
||
* return +1. iter_enter/iter_exit bracket a forEach loop: while the depth
|
||
* is nonzero, growth keeps tombstones (indices stay stable) and clear only
|
||
* tombstones entries — live-iteration semantics stay exact.
|
||
*/
|
||
|
||
/* REF keys hash/compare pointer identity and carry their own RC adapters.
|
||
* UNION_REF owns the union wrapper but hashes/compares its reference payload;
|
||
* the frontend permits only unions of reference-identity arms for this kind.
|
||
* DYN owns a checked-value box and compares its JavaScript value using
|
||
* SameValueZero, including reference identity for object payloads.
|
||
* These key kinds serve both Maps and Sets. */
|
||
typedef enum { SCR_MAP_KEY_F64, SCR_MAP_KEY_STR, SCR_MAP_KEY_REF, SCR_MAP_KEY_UNION_REF, SCR_MAP_KEY_DYN } ScrMapKeyKind;
|
||
typedef enum { SCR_MAP_VAL_F64, SCR_MAP_VAL_BOOL, SCR_MAP_VAL_REF } ScrMapValKind;
|
||
|
||
typedef struct {
|
||
uint64_t key; /* double bits (normalized), ScrStr* (owned), or ref ptr (owned) */
|
||
uint64_t val; /* double bits, bool, or owned pointer */
|
||
bool live; /* false = tombstone (key/val already released) */
|
||
} ScrMapEntry;
|
||
|
||
typedef struct ScrMap {
|
||
size_t rc; /* SIZE_MAX = immortal (unused for maps; kept per convention) */
|
||
ScrMapKeyKind key_kind;
|
||
ScrMapValKind val_kind;
|
||
/* SCR_MAP_VAL_REF only; val_trace non-NULL iff the value type carries a
|
||
* cycle header. The map has a header when either trace is non-NULL. */
|
||
void *(*val_retain)(void *);
|
||
void (*val_release)(void *);
|
||
ScrTraceFn val_trace;
|
||
/* SCR_MAP_KEY_REF, SCR_MAP_KEY_UNION_REF and SCR_MAP_KEY_DYN only. */
|
||
void *(*key_retain)(void *);
|
||
void (*key_release)(void *);
|
||
ScrTraceFn key_trace;
|
||
size_t nentries; /* dense entries used, tombstones included */
|
||
size_t nlive; /* live entries (Map.size) */
|
||
size_t ecap; /* entries capacity */
|
||
ScrMapEntry *entries;
|
||
size_t nbuckets; /* power of two; >= 2 * ecap so probes terminate */
|
||
size_t *buckets; /* entry indices; SIZE_MAX = empty */
|
||
size_t iter_depth; /* > 0: an iteration is active — no compaction */
|
||
} ScrMap;
|
||
|
||
/* retain/release/trace are NULL for scalar value kinds; retain/release are
|
||
* required for SCR_MAP_VAL_REF; trace is non-NULL iff the value type is
|
||
* cycle-capable (the map then allocates with the collector header). */
|
||
ScrMap *scr_map_new(ScrMapKeyKind key_kind, ScrMapValKind val_kind,
|
||
void *(*val_retain)(void *), void (*val_release)(void *),
|
||
ScrTraceFn val_trace); /* returns +1 */
|
||
/* Typed identity keys retain their original representation for iteration.
|
||
* UNION_REF compares the wrapped reference, so reboxing cannot change key
|
||
* identity. The compiler admits only unions of identity-bearing arms.
|
||
* Either trace adapter requires a collector header on the map itself. */
|
||
ScrMap *scr_map_new_typed(ScrMapKeyKind key_kind, ScrMapValKind val_kind,
|
||
void *(*key_retain)(void *), void (*key_release)(void *),
|
||
ScrTraceFn key_trace,
|
||
void *(*val_retain)(void *), void (*val_release)(void *),
|
||
ScrTraceFn val_trace);
|
||
ScrMap *scr_map_retain(ScrMap *m);
|
||
void scr_map_release(ScrMap *m); /* NULL-tolerant */
|
||
void *scr_map_retain_v(void *m);
|
||
void scr_map_release_v(void *m);
|
||
void scr_map_trace_v(void *m, ScrTraceVisit visit, void *ctx);
|
||
|
||
double scr_map_size(const ScrMap *m); /* live entries (Map.size) */
|
||
void scr_map_clear(ScrMap *m);
|
||
|
||
bool scr_map_has_f64(const ScrMap *m, double key);
|
||
bool scr_map_has_str(const ScrMap *m, const ScrStr *key);
|
||
bool scr_map_has_ref(const ScrMap *m, const void *key);
|
||
bool scr_map_delete_f64(ScrMap *m, double key);
|
||
bool scr_map_delete_str(ScrMap *m, const ScrStr *key);
|
||
bool scr_map_delete_ref(ScrMap *m, const void *key);
|
||
|
||
/* set: key borrowed (string/reference keys are retained when stored), value moves in
|
||
* for _ref (replacing releases the old value; the stored key is kept, like
|
||
* JS — only the value changes on overwrite). */
|
||
void scr_map_set_f64_f64(ScrMap *m, double key, double v);
|
||
void scr_map_set_f64_bool(ScrMap *m, double key, bool v);
|
||
void scr_map_set_f64_ref(ScrMap *m, double key, void *v);
|
||
void scr_map_set_str_f64(ScrMap *m, ScrStr *key, double v);
|
||
void scr_map_set_str_bool(ScrMap *m, ScrStr *key, bool v);
|
||
void scr_map_set_str_ref(ScrMap *m, ScrStr *key, void *v);
|
||
void scr_map_set_ref_f64(ScrMap *m, void *key, double v);
|
||
void scr_map_set_ref_bool(ScrMap *m, void *key, bool v);
|
||
void scr_map_set_ref_ref(ScrMap *m, void *key, void *v);
|
||
|
||
/* get: scalar variants fill *out and return the found flag; ref variants
|
||
* return +1 or NULL (values are never NULL, so NULL means "absent"). The
|
||
* compiler wraps the result into the `V | undefined` union type-directedly
|
||
* (the runtime knows no tags). */
|
||
bool scr_map_get_f64_f64(const ScrMap *m, double key, double *out);
|
||
bool scr_map_get_f64_bool(const ScrMap *m, double key, bool *out);
|
||
void *scr_map_get_f64_ref(const ScrMap *m, double key);
|
||
bool scr_map_get_str_f64(const ScrMap *m, const ScrStr *key, double *out);
|
||
bool scr_map_get_str_bool(const ScrMap *m, const ScrStr *key, bool *out);
|
||
void *scr_map_get_str_ref(const ScrMap *m, const ScrStr *key);
|
||
bool scr_map_get_ref_f64(const ScrMap *m, const void *key, double *out);
|
||
bool scr_map_get_ref_bool(const ScrMap *m, const void *key, bool *out);
|
||
void *scr_map_get_ref_ref(const ScrMap *m, const void *key);
|
||
|
||
/* Iteration primitives behind the compiler's forEach desugar: an index loop
|
||
* over the dense entries array, re-reading iter_count every pass (appends
|
||
* during iteration are visited) and skipping tombstones via iter_live.
|
||
* iter_key/iter_val on an out-of-range or dead index abort (the desugar
|
||
* guards with iter_live first — reaching one is a compiler bug). */
|
||
double scr_map_iter_count(const ScrMap *m); /* entries incl. tombstones */
|
||
bool scr_map_iter_live(const ScrMap *m, double i);
|
||
double scr_map_iter_key_f64(const ScrMap *m, double i);
|
||
ScrStr *scr_map_iter_key_str(const ScrMap *m, double i); /* +1 */
|
||
void *scr_map_iter_key_ref(const ScrMap *m, double i); /* +1 */
|
||
double scr_map_iter_val_f64(const ScrMap *m, double i);
|
||
bool scr_map_iter_val_bool(const ScrMap *m, double i);
|
||
void *scr_map_iter_val_ref(const ScrMap *m, double i); /* +1 */
|
||
void scr_map_iter_enter(ScrMap *m);
|
||
void scr_map_iter_exit(ScrMap *m);
|
||
|
||
/* Seeded Set construction (`new Set(values)`): add() every element of one
|
||
* borrowed T[] in order — duplicates keep their first insertion position
|
||
* (SameValueZero, exactly JS). `set` is a set-shaped map (f64 value kind,
|
||
* stored values 0); the array's element kind matches the set's key kind. */
|
||
void scr_set_add_all(ScrMap *set, ScrArr *values);
|
||
|
||
/* `[...set]` (scr_lib.c): the live entries drained into a fresh +1 elem[]
|
||
* in insertion order. Borrows the set; string elements retained in. */
|
||
ScrArr *scr_set_to_arr_f64(const ScrMap *s);
|
||
ScrArr *scr_set_to_arr_str(const ScrMap *s);
|
||
ScrArr *scr_set_to_arr_ref(const ScrMap *s);
|
||
|
||
/* REF-element Set construction: a set-shaped map whose keys are refcounted
|
||
* pointers under identity hashing (see SCR_MAP_KEY_REF above). The element
|
||
* type's `_v` adapters arrive once, the scr_arr_new_ref technique. */
|
||
ScrMap *scr_set_new_ref(void *(*elem_retain)(void *), void (*elem_release)(void *));
|
||
|
||
/* The live STRING keys of a map in JS OWN-KEY ORDER (Object.keys/values/
|
||
* entries over an index-signature record's overflow): canonical array
|
||
* indices first in ascending numeric order, then the rest in insertion
|
||
* order. Fresh ScrStr* array (+1); the map is borrowed. */
|
||
ScrArr *scr_map_keys_js_order(const ScrMap *m);
|
||
|
||
#ifdef SCR_RC_AUDIT
|
||
long scr_map_live_count(void);
|
||
#endif
|
||
|
||
/* ── closures ───────────────────────────────────────────────────────
|
||
* A closure is a function pointer plus the boxes of its captured bindings.
|
||
* A box is one shared, refcounted variable cell: every function that
|
||
* captures a binding (and the function that declared it) reads and writes
|
||
* the same box, which is what makes mutation through a closure visible
|
||
* everywhere (JS shared-binding semantics).
|
||
*
|
||
* The compiler emits the callee's C signature as (ScrClosure *env, params…);
|
||
* the callee borrows env->caps[] (the closure owns them). rc == SIZE_MAX
|
||
* marks an immortal closure (the compiler interns one per top-level
|
||
* function referenced as a value, so `f === f` is true like in JS).
|
||
*
|
||
* Ownership: box _get_ref returns +1; _set_ref releases the old value and
|
||
* takes ownership of the new one; releasing a box releases its contents;
|
||
* releasing a closure releases its boxes.
|
||
*/
|
||
|
||
typedef enum {
|
||
SCR_BOX_F64,
|
||
SCR_BOX_BOOL,
|
||
SCR_BOX_STR,
|
||
SCR_BOX_ARR,
|
||
SCR_BOX_FUNC,
|
||
SCR_BOX_OBJ, /* class instance: retain/release via the fn ptrs below */
|
||
} ScrBoxKind;
|
||
|
||
typedef struct ScrBox {
|
||
size_t rc;
|
||
ScrBoxKind kind;
|
||
/* SCR_BOX_OBJ only: class instances are per-class C structs the runtime
|
||
* knows nothing about, so the compiler supplies their RC entry points.
|
||
* obj_trace is non-NULL exactly when the payload type carries a cycle
|
||
* header (the box's own trace then visits the payload; NULL means the
|
||
* payload is acyclic and the box releases it itself at teardown). */
|
||
void *(*obj_retain)(void *);
|
||
void (*obj_release)(void *);
|
||
ScrTraceFn obj_trace;
|
||
uint64_t slot; /* double/bool/pointer via memcpy and casts */
|
||
} ScrBox;
|
||
|
||
ScrBox *scr_box_new(ScrBoxKind kind); /* returns +1, slot zeroed */
|
||
ScrBox *scr_box_new_obj(void *(*retain)(void *), void (*release)(void *),
|
||
ScrTraceFn trace);
|
||
|
||
static inline ScrBox *scr_box_retain(ScrBox *b) {
|
||
if (b->rc != SIZE_MAX) {
|
||
b->rc++;
|
||
scr_cyc_mark_live(b);
|
||
}
|
||
return b;
|
||
}
|
||
|
||
void scr_box_release(ScrBox *b); /* releases ref contents by kind; NULL-tolerant */
|
||
|
||
double scr_box_get_f64(ScrBox *b);
|
||
bool scr_box_get_bool(ScrBox *b);
|
||
void *scr_box_get_ref(ScrBox *b); /* returns +1 */
|
||
void scr_box_set_f64(ScrBox *b, double v);
|
||
void scr_box_set_bool(ScrBox *b, bool v);
|
||
void scr_box_set_ref(ScrBox *b, void *v); /* releases old, owns new */
|
||
|
||
typedef struct ScrClosure {
|
||
size_t rc; /* SIZE_MAX = immortal (interned top-level function value) */
|
||
void *fn;
|
||
size_t ncaps;
|
||
/* The function's OWN-PROPERTY table: a box whose payload is an OBJ
|
||
* ScrDyn (owned; lazily allocated by Object.defineProperties —
|
||
* test/common copying name/length onto the mustCall wrapper), NULL
|
||
* until first defined. It lives on the CLOSURE, not the dyn box:
|
||
* boxing the same function value twice (the declaration's box, the
|
||
* return's box) yields distinct boxes sharing one closure, and JS has
|
||
* ONE function object — property writes must be visible through every
|
||
* box. A ScrBox so this unit stays dyn-free (the payload's release
|
||
* rides the box, like every capture). Untraced by the cycle collector
|
||
* (like the dyn→closure edge): a cycle through it is merely never
|
||
* collected. */
|
||
ScrBox *props;
|
||
ScrBox *caps[];
|
||
} ScrClosure;
|
||
|
||
ScrClosure *scr_closure_new(void *fn, size_t ncaps); /* +1; caller fills caps with +1 box refs */
|
||
|
||
static inline ScrClosure *scr_closure_retain(ScrClosure *c) {
|
||
if (c->rc != SIZE_MAX) {
|
||
c->rc++;
|
||
scr_cyc_mark_live(c);
|
||
}
|
||
return c;
|
||
}
|
||
|
||
void scr_closure_release(ScrClosure *c); /* releases the boxes; NULL-tolerant */
|
||
|
||
/* ── outbound FFI retained callbacks (scr_ffi.c) ─────────────────────
|
||
* One compiler-emitted table per retained callback descriptor. For
|
||
* context-bearing descriptors, entries are counted rather than
|
||
* deduplicated: registering the same closure twice requires two matching
|
||
* releases. Raw singleton slots replace instead: commit_slot retires
|
||
* every pin the new registration superseded — a duplicate of the same
|
||
* closure included — so after any number of set calls exactly one
|
||
* release is pending. The table owns one closure reference per entry and
|
||
* joins a process-global teardown list on first use. Script-thread retained
|
||
* callbacks do not contribute event-loop liveness; foreign registrations do,
|
||
* and their queued invocations keep released closure pins alive until the
|
||
* script-thread dispatch has finished. */
|
||
typedef struct ScrFfiTable {
|
||
ScrClosure **entries;
|
||
size_t len;
|
||
size_t cap;
|
||
struct ScrFfiTable *next;
|
||
bool linked;
|
||
/* Raw retained singletons only: the compiler-emitted global the
|
||
* trampoline dispatches through. Teardown and release disarm it so a
|
||
* late native invocation hits the trampoline's NULL trap instead of a
|
||
* freed closure. NULL for context-bearing descriptors. */
|
||
ScrClosure **slot;
|
||
/* Foreign descriptors only. The global FFI queue lock protects these
|
||
* fields together with entries/len/cap. Retired pins were explicitly
|
||
* released while a queued invocation could still name them; dispatch
|
||
* drops them on the script thread after this table's queue reaches zero. */
|
||
ScrClosure **retired;
|
||
size_t retired_len;
|
||
size_t retired_cap;
|
||
size_t queued;
|
||
/* Reserved process-loop identity captured by queued calls. Executables use
|
||
* one global loop today; library mode can make this per instance later
|
||
* without changing the post/call ABI. */
|
||
void *loop;
|
||
/* Optional format-5 teardown. NULL keeps format-4 tables on the compact
|
||
* always-linked path; foreign tables point into scr_ffi_queue.c. */
|
||
void (*teardown)(struct ScrFfiTable *table);
|
||
} ScrFfiTable;
|
||
|
||
void scr_ffi_link(ScrFfiTable *table);
|
||
void scr_ffi_retain(ScrFfiTable *table, ScrClosure *callback);
|
||
void scr_ffi_retain_foreign(ScrFfiTable *table, ScrClosure *callback);
|
||
/* Raw singleton registration, split around the native set call:
|
||
* retain_slot pins the incoming closure BEFORE the call without touching
|
||
* the current registration; commit_slot repoints the slot and retires the
|
||
* superseded pins after the call returns. */
|
||
void scr_ffi_retain_slot(ScrFfiTable *table, ScrClosure **slot, ScrClosure *callback);
|
||
void scr_ffi_commit_slot(ScrFfiTable *table, ScrClosure *callback);
|
||
/* Traps unless the registration exists — emitted BEFORE a native release
|
||
* call so a bogus release cannot reach native code. */
|
||
void scr_ffi_require(ScrFfiTable *table, ScrClosure *callback);
|
||
void scr_ffi_release(ScrFfiTable *table, ScrClosure *callback);
|
||
void scr_ffi_release_optional(ScrFfiTable *table, ScrClosure *callback);
|
||
void scr_ffi_require_foreign(ScrFfiTable *table, ScrClosure *callback);
|
||
void scr_ffi_release_foreign(ScrFfiTable *table, ScrClosure *callback);
|
||
void scr_ffi_teardown(ScrFfiTable *table);
|
||
void scr_ffi_teardown_all(void);
|
||
|
||
/* Format-5 foreign-thread delivery. A generated native trampoline creates a
|
||
* plain malloc-backed call, stages scalar values or copied byte spans, and
|
||
* posts it. It never touches closure RC, exception cells, fibers, or other
|
||
* script-thread-only runtime state. The generated dispatch thunk runs later
|
||
* on the event loop and materializes script strings/bytes there. */
|
||
typedef struct ScrFfiCall ScrFfiCall;
|
||
typedef void (*ScrFfiDispatchFn)(ScrClosure *callback, ScrFfiCall *call);
|
||
|
||
ScrFfiCall *scr_ffi_call_new(ScrFfiTable *table, ScrClosure *callback,
|
||
ScrFfiDispatchFn dispatch, size_t nargs);
|
||
void scr_ffi_call_set_f64(ScrFfiCall *call, size_t index, double value);
|
||
void scr_ffi_call_set_bool(ScrFfiCall *call, size_t index, uint8_t value);
|
||
void scr_ffi_call_set_u8(ScrFfiCall *call, size_t index, uint8_t value);
|
||
void scr_ffi_call_set_u32(ScrFfiCall *call, size_t index, uint32_t value);
|
||
void scr_ffi_call_set_i32(ScrFfiCall *call, size_t index, int32_t value);
|
||
void scr_ffi_call_copy_cstring(ScrFfiCall *call, size_t index, const char *value);
|
||
void scr_ffi_call_copy_string(ScrFfiCall *call, size_t index,
|
||
const uint8_t *value, size_t len);
|
||
void scr_ffi_call_copy_bytes(ScrFfiCall *call, size_t index,
|
||
const uint8_t *value, size_t len);
|
||
void scr_ffi_post(ScrFfiCall *call);
|
||
|
||
double scr_ffi_call_get_f64(const ScrFfiCall *call, size_t index);
|
||
bool scr_ffi_call_get_bool(const ScrFfiCall *call, size_t index);
|
||
double scr_ffi_call_get_u8(const ScrFfiCall *call, size_t index);
|
||
double scr_ffi_call_get_u32(const ScrFfiCall *call, size_t index);
|
||
double scr_ffi_call_get_i32(const ScrFfiCall *call, size_t index);
|
||
const uint8_t *scr_ffi_call_get_data(const ScrFfiCall *call, size_t index);
|
||
size_t scr_ffi_call_get_len(const ScrFfiCall *call, size_t index);
|
||
|
||
void scr_ffi_install(void);
|
||
void scr_ffi_stop(void);
|
||
void scr_ffi_teardown_foreign(ScrFfiTable *table);
|
||
void scr_loop_set_ffi(bool (*pending)(void), bool (*dispatch)(void),
|
||
int (*pollfd)(void), void (*stop)(void));
|
||
|
||
/* ── unions ─────────────────────────────────────────────────────────
|
||
* A union value (`A | B`) is an IMMUTABLE tagged box: a refcounted header,
|
||
* the arm's tag (its index in the compiler's canonical arm order), and one
|
||
* 8-byte payload slot (double/bool/pointer via memcpy, like ScrBox and
|
||
* ScrArr slots). Reassigning a union-typed variable constructs a NEW box —
|
||
* immutability keeps the RC story trivial.
|
||
*
|
||
* Scalar arms (f64/bool) live in the slot with NULL RC entry points. Ref
|
||
* arms OWN their payload (+1 moves in at construction) and carry the arm's
|
||
* retain/release as function pointers, exactly like SCR_BOX_OBJ: the
|
||
* runtime cannot know per-class/per-record struct layouts, so the compiler
|
||
* supplies its emitted `_v` adapters (and the runtime provides `_v`
|
||
* adapters for its own refcounted kinds below).
|
||
*
|
||
* Ownership: _new_ref takes ownership of the payload; scr_union_peek
|
||
* BORROWS the payload pointer (the compiler retains through the arm's
|
||
* concrete helper when it needs +1); releasing the union releases the
|
||
* payload through the stored fn ptr.
|
||
*/
|
||
|
||
typedef struct ScrUnion {
|
||
size_t rc; /* SIZE_MAX = immortal (unused for unions; kept per convention) */
|
||
uint32_t tag;
|
||
/* Ref arms only: the payload's RC entry points. NULL for scalar arms.
|
||
* arm_trace is non-NULL exactly when the arm type carries a cycle header
|
||
* (like ScrBox.obj_trace: the union's trace then visits the payload). */
|
||
void *(*arm_retain)(void *);
|
||
void (*arm_release)(void *);
|
||
ScrTraceFn arm_trace;
|
||
uint64_t slot; /* double/bool/pointer via memcpy and casts */
|
||
} ScrUnion;
|
||
|
||
ScrUnion *scr_union_new_f64(uint32_t tag, double v); /* returns +1 */
|
||
ScrUnion *scr_union_new_bool(uint32_t tag, bool v); /* returns +1 */
|
||
/* Takes ownership of v (+1 moves in); retain/release must be non-NULL;
|
||
* trace is non-NULL iff the arm type carries a cycle header. */
|
||
ScrUnion *scr_union_new_ref(uint32_t tag, void *v,
|
||
void *(*retain)(void *),
|
||
void (*release)(void *),
|
||
ScrTraceFn trace);
|
||
|
||
static inline ScrUnion *scr_union_retain(ScrUnion *u) {
|
||
if (u->rc != SIZE_MAX) {
|
||
u->rc++;
|
||
scr_cyc_mark_live(u);
|
||
}
|
||
return u;
|
||
}
|
||
|
||
void scr_union_release(ScrUnion *u); /* releases a ref payload; NULL-tolerant */
|
||
|
||
double scr_union_get_f64(ScrUnion *u);
|
||
bool scr_union_get_bool(ScrUnion *u);
|
||
|
||
/* BORROWED payload pointer of a ref arm (no ownership change). */
|
||
static inline void *scr_union_peek(const ScrUnion *u) {
|
||
void *p;
|
||
memcpy(&p, &u->slot, sizeof p);
|
||
return p;
|
||
}
|
||
|
||
/* void*-signature RC adapters for the runtime's own refcounted kinds —
|
||
* stored in ScrUnion (ref arms) and ScrBox (SCR_BOX_OBJ-style union boxes)
|
||
* alongside the compiler's emitted per-class/per-record `_v` adapters. */
|
||
void *scr_str_retain_v(void *s);
|
||
void scr_str_release_v(void *s);
|
||
void *scr_arr_retain_v(void *a);
|
||
void scr_arr_release_v(void *a);
|
||
void *scr_closure_retain_v(void *c);
|
||
void scr_closure_release_v(void *c);
|
||
void *scr_union_retain_v(void *u);
|
||
void scr_union_release_v(void *u);
|
||
|
||
/* Trace entry points for the runtime's cycle-headered kinds — passed
|
||
* wherever a container stores a payload's RC entry points (ScrBox obj
|
||
* slots, union ref arms, promise payloads, the exception cell) and the
|
||
* payload type is one of these. The compiler passes its emitted per-shape
|
||
* trace for cycle-capable classes/records, and NULL for acyclic types. */
|
||
void scr_closure_trace_v(void *c, ScrTraceVisit visit, void *ctx);
|
||
|
||
/* ── node:events EventEmitter (scr_events_emitter.c, link-gated) ──────
|
||
* The runtime-provided emitter base class — the ScrError precedent:
|
||
* ScrEmitter lays out exactly like a compiler-emitted hierarchy class
|
||
* (rc, vt, then the prefix fields), so `class My extends EventEmitter`
|
||
* compiles as an ordinary derived class whose struct embeds this prefix
|
||
* (`ScrEeReg *` then `const char *`, stamped by the emitted allocation),
|
||
* and every vtable mechanism applies unchanged. The vtable of BARE
|
||
* emitters lives here as a mutable global; the emitted main() stamps its
|
||
* preorder interval (the scr_error_vts story). The emitter hierarchy is
|
||
* UNCONDITIONALLY cycle-capable — the registry owns listener closures —
|
||
* so bare instances always allocate collector-headered and emitted
|
||
* subclasses are always in the traced set.
|
||
*
|
||
* Dispatch: listeners are invoked through compiler-emitted va_list
|
||
* adapters; scr_emitter_emit's variadic tail carries the event's typed
|
||
* argument tuple (frontend-unified per event name), every argument
|
||
* BORROWED — each adapter retains the +1 its callee owns. emit and
|
||
* emit_error may leave an exception pending (a listener threw; an
|
||
* unhandled 'error' throws its payload). */
|
||
typedef struct ScrEeReg ScrEeReg; /* the listener registry (lazy) */
|
||
typedef void (*ScrEeInvoke)(ScrClosure *cb, va_list ap);
|
||
|
||
typedef struct ScrEmitter {
|
||
size_t rc;
|
||
const ScrVt *vt;
|
||
ScrEeReg *reg; /* NULL until the first listener/setMaxListeners */
|
||
const char *cls; /* display name for the leak warning ("EventEmitter") */
|
||
} ScrEmitter;
|
||
|
||
extern SCR_TL ScrVt scr_emitter_vt; /* main() stamps pre/post */
|
||
extern SCR_TL double scr_emitter_default_max;
|
||
|
||
ScrEmitter *scr_emitter_new(void); /* +1, collector-headered */
|
||
void scr_emitter_init(void *obj); /* super() into the prefix (no-op today) */
|
||
ScrEmitter *scr_emitter_retain(ScrEmitter *em);
|
||
void scr_emitter_release(ScrEmitter *em); /* dispatches through em->vt */
|
||
void *scr_emitter_retain_v(void *em);
|
||
void scr_emitter_release_v(void *em);
|
||
void scr_emitter_trace(void *obj, ScrTraceVisit visit, void *ctx);
|
||
|
||
/* The emitted-subclass prefix helpers (the compiler's direct release /
|
||
* trace / collector teardown call these for the embedded registry). */
|
||
void scr_emitter_reg_drop(ScrEeReg *reg);
|
||
void scr_emitter_reg_trace(ScrEeReg *reg, ScrTraceVisit visit, void *ctx);
|
||
void scr_emitter_reg_gcfree(ScrEeReg *reg);
|
||
|
||
/* on/once/prependListener/prependOnceListener: cb MOVES, name borrowed;
|
||
* returns em +1 (the `return this` chaining value). 'newListener' fires
|
||
* BEFORE the add; the leak warning prints at the crossing add. */
|
||
ScrEmitter *scr_emitter_on(ScrEmitter *em, ScrStr *name, ScrClosure *cb /*moves*/,
|
||
ScrEeInvoke inv, bool once, bool prepend);
|
||
/* The dyn-adapted registration family (JS-lane checked-dynamic
|
||
* listeners). check_listener throws Node's ERR_INVALID_ARG_TYPE TypeError
|
||
* (catchable, pending on return) when cb is not a function value; the
|
||
* emitted registration helper calls it FIRST, so on_dyn/off_dyn take the
|
||
* SCR_DYN_FUNC kind as given. on_dyn registers the compiler-built adapter
|
||
* (what emit invokes — it boxes the tuple to dyn and calls the original
|
||
* through the checked-dynamic machinery) and keeps the original (the dyn
|
||
* box's underlying closure) as the entry's IDENTITY: off/removeListener
|
||
* match it, listenerCount(name, fn) counts it, listeners() answers it.
|
||
* cb is borrowed, adapter MOVES; both return em +1 (chaining). */
|
||
typedef struct ScrDyn ScrDyn; /* full definition below (C11 repeat) */
|
||
void scr_emitter_check_listener(const ScrDyn *cb);
|
||
ScrEmitter *scr_emitter_on_dyn(ScrEmitter *em, ScrStr *name, const ScrDyn *cb,
|
||
ScrClosure *adapter /*moves*/,
|
||
ScrEeInvoke inv, bool once, bool prepend);
|
||
/* Computed-name listeners retain the original closure and its checked-
|
||
* dynamic call thunk, preserving the number of arguments passed to emit.
|
||
* The frontend keeps these names disjoint from fixed-tuple event names. */
|
||
ScrEmitter *scr_emitter_on_flex(ScrEmitter *em, ScrStr *name, const ScrDyn *cb,
|
||
bool once, bool prepend);
|
||
ScrEmitter *scr_emitter_off_dyn(ScrEmitter *em, ScrStr *name, const ScrDyn *cb);
|
||
/* The adapter-mediated registration (the LLVM backend's listeners): the
|
||
* ADAPTER closure is what emit invokes, the ORIGINAL closure is the
|
||
* entry's identity (the on_dyn split, with a plain-closure identity).
|
||
* Both MOVE in; returns em +1 (chaining). */
|
||
ScrEmitter *scr_emitter_on_via(ScrEmitter *em, ScrStr *name, ScrClosure *orig /*moves*/,
|
||
ScrClosure *adapter /*moves*/,
|
||
ScrEeInvoke inv, bool once, bool prepend);
|
||
/* Fixed-arity invoke shims for backends that cannot read a va_list
|
||
* (LLVM textual IR is not va_arg-portable): shim k reads k POINTER-SIZE
|
||
* slots off the emit tuple and calls cb->fn behind the fixed signature
|
||
* `void (ScrClosure *, void * ×k)` — the fixed-signature adapter the
|
||
* backend provides. Such backends pass every user tuple argument
|
||
* pointer-classed at the emit site: scalar values point at call-lived
|
||
* typed stack slots and reference values ride directly. The runtime's own
|
||
* emits (data/error/pipe/meta) carry pointers only, so the shims read every
|
||
* tuple either lane produces on 32- and 64-bit targets.
|
||
* SCR_EE_FIXED_MAX is the registry's audited arity ceiling — backends
|
||
* refuse listeners past it rather than guess. */
|
||
#define SCR_EE_FIXED_MAX 4
|
||
void scr_ee_inv_fixed0(ScrClosure *cb, va_list ap);
|
||
void scr_ee_inv_fixed1(ScrClosure *cb, va_list ap);
|
||
void scr_ee_inv_fixed2(ScrClosure *cb, va_list ap);
|
||
void scr_ee_inv_fixed3(ScrClosure *cb, va_list ap);
|
||
void scr_ee_inv_fixed4(ScrClosure *cb, va_list ap);
|
||
/* removeListener/off (LAST matching occurrence, Node's search order) and
|
||
* removeAllListeners; both return em +1. 'removeListener' fires after
|
||
* each removal when listened for. */
|
||
ScrEmitter *scr_emitter_off(ScrEmitter *em, ScrStr *name, ScrClosure *cb /*borrowed*/);
|
||
ScrEmitter *scr_emitter_remove_all(ScrEmitter *em, ScrStr *name, bool all);
|
||
/* emit: snapshot dispatch in listener order (once entries leave before
|
||
* running); returns whether the event had listeners. The _error form is
|
||
* emit('error', err): no listener ⇒ THROWS err (borrowed; +1 taken). */
|
||
bool scr_emitter_emit(ScrEmitter *em, ScrStr *name, ...);
|
||
bool scr_emitter_emit_flex(ScrEmitter *em, ScrStr *name, ScrDyn *const *args, size_t argc);
|
||
bool scr_emitter_emit_error(ScrEmitter *em, ScrStr *name, ScrError *err);
|
||
double scr_emitter_listener_count(ScrEmitter *em, ScrStr *name);
|
||
double scr_emitter_listener_count_fn(ScrEmitter *em, ScrStr *name, ScrClosure *fn);
|
||
double scr_emitter_listener_count_dyn(ScrEmitter *em, ScrStr *name, const ScrDyn *fn);
|
||
ScrArr *scr_emitter_event_names(ScrEmitter *em); /* +1 string[] */
|
||
/* Pre-create a name's eventNames() rank with no listener (Node's stream
|
||
* classes pre-create their known _events keys; empty = absent for every
|
||
* other read). The stream constructors call this. */
|
||
void scr_emitter_reserve(ScrEmitter *em, const char *name);
|
||
ScrArr *scr_emitter_listeners(ScrEmitter *em, ScrStr *name); /* +1 closures */
|
||
ScrEmitter *scr_emitter_set_max(ScrEmitter *em, double n); /* returns em +1 */
|
||
double scr_emitter_get_max(ScrEmitter *em);
|
||
void scr_emitter_set_default_max(double n);
|
||
double scr_emitter_get_default_max(void);
|
||
/* True when the emitter has at least one listener for `name` — the
|
||
* stream unit's registration probe (and anyone else's). */
|
||
bool scr_emitter_has(ScrEmitter *em, const char *name);
|
||
/* Post-registration hook (one registrant: scr_stream_install) — called at
|
||
* the END of scr_emitter_on with the emitter and the event name, so the
|
||
* stream unit can start flowing on the first 'data' listener. NULL when
|
||
* the stream unit is not linked; emitter behavior is byte-identical. */
|
||
extern void (*scr_emitter_on_hook)(ScrEmitter *em, ScrStr *name);
|
||
|
||
/* ── node:stream (scr_stream.c, link-gated by moduleUsesStream) ───────
|
||
* The runtime-provided stream classes (Readable/Writable/Duplex/
|
||
* Transform/PassThrough) behind the options-object constructor forms.
|
||
* ONE layout for all five: the ScrEmitter prefix (so every emitter
|
||
* mechanism — listener registry, emit dispatch, upcasts — applies
|
||
* unchanged) plus a lazily-detailed state block. User `extends` of these
|
||
* classes compiles (phase 2): the emitted subclass struct embeds this
|
||
* full prefix (one extra slot over an emitter subclass — the state
|
||
* pointer), allocation stamps the SUBCLASS vtable and display name and
|
||
* leaves st NULL, and the constructor's super(options) call lands on a
|
||
* scr_stream_init_* entry below. Stream identity for such instances is
|
||
* preorder-interval membership under a runtime stream vtable (main()
|
||
* stamps the intervals, the emitter-vt story).
|
||
*
|
||
* Event timing: Node defers most stream emissions by a process.nextTick.
|
||
* Here those deferrals ride a dedicated TICK QUEUE drained at the top of
|
||
* every loop turn (before the events/net/timer stations — the closest
|
||
* station to nextTick; SEMANTICS.md documents the microtask-order
|
||
* divergence), registered via scr_loop_set_stream at install. Emits run
|
||
* user code synchronously on the dispatching stack and may leave an
|
||
* exception pending, exactly like scr_emitter_emit — an unhandled
|
||
* 'error' event throws its payload (Node's crash).
|
||
*
|
||
* Ownership: constructors take their option callbacks +1 (moves);
|
||
* chunks are BORROWED at push/write (the buffer retains its copy);
|
||
* chaining forms return the receiver +1; read()/errored answer +1 or
|
||
* NULL. The *_done completion entries take err/data +1 (moves) — they
|
||
* are called by compiler-emitted completion-callback closures. */
|
||
typedef struct ScrStreamState ScrStreamState;
|
||
typedef struct ScrBytes ScrBytes; /* full definition below (C11 repeat) */
|
||
|
||
typedef struct ScrStream {
|
||
size_t rc;
|
||
const ScrVt *vt;
|
||
ScrEeReg *reg; /* ScrEmitter prefix */
|
||
const char *cls; /* ScrEmitter prefix (display name) */
|
||
ScrStreamState *st;
|
||
} ScrStream;
|
||
|
||
extern ScrVt scr_readable_vt, scr_writable_vt, scr_duplex_vt,
|
||
scr_transform_vt, scr_passthrough_vt; /* main() stamps pre/post */
|
||
|
||
/* Compiler-emitted option-callback invoke adapters (the leading-`this`
|
||
* closures): the runtime calls the user's read/write/final/destroy/
|
||
* transform/flush through these. */
|
||
typedef void (*ScrStreamReadInv)(ScrClosure *cb, ScrStream *s, double size);
|
||
typedef void (*ScrStreamChunkInv)(ScrClosure *cb, ScrStream *s, ScrBytes *chunk);
|
||
typedef void (*ScrStreamPlainInv)(ScrClosure *cb, ScrStream *s);
|
||
typedef void (*ScrStreamErrInv)(ScrClosure *cb, ScrStream *s, ScrError *err);
|
||
|
||
/* Constructors (+1 result; option callbacks MOVE; NULL = absent). hwm < 0
|
||
* means the byte default (65536 — Node 24's getDefaultHighWaterMark). */
|
||
ScrStream *scr_stream_new_readable(double hwm, bool auto_destroy, bool emit_close,
|
||
ScrClosure *read, ScrStreamReadInv read_inv,
|
||
ScrClosure *destroy, ScrStreamErrInv destroy_inv);
|
||
ScrStream *scr_stream_new_writable(double hwm, bool auto_destroy, bool emit_close,
|
||
ScrClosure *write, ScrStreamChunkInv write_inv,
|
||
ScrClosure *final_cb, ScrStreamPlainInv final_inv,
|
||
ScrClosure *destroy, ScrStreamErrInv destroy_inv);
|
||
ScrStream *scr_stream_new_duplex(double rhwm, double whwm, bool auto_destroy,
|
||
bool emit_close, bool allow_half_open, bool readable_side, bool writable_side,
|
||
ScrClosure *read, ScrStreamReadInv read_inv,
|
||
ScrClosure *write, ScrStreamChunkInv write_inv,
|
||
ScrClosure *final_cb, ScrStreamPlainInv final_inv,
|
||
ScrClosure *destroy, ScrStreamErrInv destroy_inv);
|
||
ScrStream *scr_stream_new_transform(double rhwm, double whwm, bool auto_destroy,
|
||
bool emit_close, bool allow_half_open, bool readable_side, bool writable_side,
|
||
ScrClosure *transform, ScrStreamChunkInv transform_inv,
|
||
ScrClosure *flush, ScrStreamPlainInv flush_inv,
|
||
ScrClosure *destroy, ScrStreamErrInv destroy_inv);
|
||
ScrStream *scr_stream_new_passthrough(double rhwm, double whwm, bool auto_destroy,
|
||
bool emit_close, bool allow_half_open, bool readable_side, bool writable_side,
|
||
ScrClosure *transform, ScrStreamChunkInv transform_inv,
|
||
ScrClosure *flush, ScrStreamPlainInv flush_inv,
|
||
ScrClosure *destroy, ScrStreamErrInv destroy_inv);
|
||
|
||
/* Subclass initialization (the emitted super(options) call over an
|
||
* ALREADY-ALLOCATED subclass struct — same tails as the constructors;
|
||
* receiver borrowed, callbacks move). Overridden underscore methods
|
||
* (_read/_write/...) arrive as compiler-synthesized wrapper closures
|
||
* that dispatch through the receiver's vtable. */
|
||
void scr_stream_init_readable(ScrStream *s, double hwm, bool auto_destroy, bool emit_close,
|
||
ScrClosure *read, ScrStreamReadInv read_inv,
|
||
ScrClosure *destroy, ScrStreamErrInv destroy_inv);
|
||
void scr_stream_init_writable(ScrStream *s, double hwm, bool auto_destroy, bool emit_close,
|
||
ScrClosure *write, ScrStreamChunkInv write_inv,
|
||
ScrClosure *final_cb, ScrStreamPlainInv final_inv,
|
||
ScrClosure *destroy, ScrStreamErrInv destroy_inv);
|
||
void scr_stream_init_duplex(ScrStream *s, double rhwm, double whwm, bool auto_destroy,
|
||
bool emit_close, bool allow_half_open, bool readable_side, bool writable_side,
|
||
ScrClosure *read, ScrStreamReadInv read_inv,
|
||
ScrClosure *write, ScrStreamChunkInv write_inv,
|
||
ScrClosure *final_cb, ScrStreamPlainInv final_inv,
|
||
ScrClosure *destroy, ScrStreamErrInv destroy_inv);
|
||
void scr_stream_init_transform(ScrStream *s, double rhwm, double whwm, bool auto_destroy,
|
||
bool emit_close, bool allow_half_open, bool readable_side, bool writable_side,
|
||
ScrClosure *transform, ScrStreamChunkInv transform_inv,
|
||
ScrClosure *flush, ScrStreamPlainInv flush_inv,
|
||
ScrClosure *destroy, ScrStreamErrInv destroy_inv);
|
||
void scr_stream_init_passthrough(ScrStream *s, double rhwm, double whwm, bool auto_destroy,
|
||
bool emit_close, bool allow_half_open, bool readable_side, bool writable_side,
|
||
ScrClosure *transform, ScrStreamChunkInv transform_inv,
|
||
ScrClosure *flush, ScrStreamPlainInv flush_inv,
|
||
ScrClosure *destroy, ScrStreamErrInv destroy_inv);
|
||
|
||
/* The dyn-options twins (a checked-dynamic options record at
|
||
* construction — `super(options)` forwarding a JS-lane parameter, `new
|
||
* Readable(dynVar)`): the option walk runs at runtime with Node's own
|
||
* reading rules (unknown keys ignored; consumed-but-unlowered options
|
||
* throw the loud unsupported Error — MAY THROW, NULL result / no init
|
||
* with the exception pending). opts is BORROWED; the fallback closures
|
||
* (a subclass's overridden underscore-method wrappers, shadowed by
|
||
* options callbacks per Node's instance-property rule) MOVE. */
|
||
ScrStream *scr_stream_new_readable_dyn(struct ScrDyn *opts);
|
||
ScrStream *scr_stream_new_writable_dyn(struct ScrDyn *opts);
|
||
ScrStream *scr_stream_new_duplex_dyn(struct ScrDyn *opts);
|
||
ScrStream *scr_stream_new_transform_dyn(struct ScrDyn *opts);
|
||
ScrStream *scr_stream_new_passthrough_dyn(struct ScrDyn *opts);
|
||
void scr_stream_init_readable_dyn(ScrStream *s, struct ScrDyn *opts,
|
||
ScrClosure *read_fb, ScrStreamReadInv read_fb_inv,
|
||
ScrClosure *destroy_fb, ScrStreamErrInv destroy_fb_inv);
|
||
void scr_stream_init_writable_dyn(ScrStream *s, struct ScrDyn *opts,
|
||
ScrClosure *write_fb, ScrStreamChunkInv write_fb_inv,
|
||
ScrClosure *final_fb, ScrStreamPlainInv final_fb_inv,
|
||
ScrClosure *destroy_fb, ScrStreamErrInv destroy_fb_inv);
|
||
void scr_stream_init_duplex_dyn(ScrStream *s, struct ScrDyn *opts,
|
||
ScrClosure *read_fb, ScrStreamReadInv read_fb_inv,
|
||
ScrClosure *write_fb, ScrStreamChunkInv write_fb_inv,
|
||
ScrClosure *final_fb, ScrStreamPlainInv final_fb_inv,
|
||
ScrClosure *destroy_fb, ScrStreamErrInv destroy_fb_inv);
|
||
void scr_stream_init_transform_dyn(ScrStream *s, struct ScrDyn *opts,
|
||
ScrClosure *transform_fb, ScrStreamChunkInv transform_fb_inv,
|
||
ScrClosure *flush_fb, ScrStreamPlainInv flush_fb_inv,
|
||
ScrClosure *destroy_fb, ScrStreamErrInv destroy_fb_inv);
|
||
void scr_stream_init_passthrough_dyn(ScrStream *s, struct ScrDyn *opts,
|
||
ScrClosure *transform_fb, ScrStreamChunkInv transform_fb_inv,
|
||
ScrClosure *flush_fb, ScrStreamPlainInv flush_fb_inv,
|
||
ScrClosure *destroy_fb, ScrStreamErrInv destroy_fb_inv);
|
||
|
||
/* stream.finished(s, cb) — the callback form: cb fires once at the
|
||
* stream's terminal point (right after 'close'; asynchronously when
|
||
* already closed) through the ErrInv ABI with the finish status (NULL /
|
||
* the error / ERR_STREAM_PREMATURE_CLOSE). Returns the +1 CLEANUP
|
||
* closure (a () => void unhooking the callback). cb MOVES. A registered
|
||
* watcher marks lifecycle errors handled (no unhandled-'error' crash —
|
||
* Node's eos registers listeners). */
|
||
ScrClosure *scr_stream_finished(ScrStream *s, ScrClosure *cb, ScrStreamErrInv inv);
|
||
/* The dyn-valued finished/pipeline callback inv (a mustCall wrapper in
|
||
* the slot): success calls with NO arguments, errors box through the
|
||
* boundary encoding. cb's cap boxes the dyn callable. */
|
||
void scr_stream_finished_dyn_inv(ScrClosure *cb, ScrStream *s, ScrError *err);
|
||
/* stream.pipeline(s1..sn, cb) — the callback form over stream arguments:
|
||
* chains pipes (end: true), destroys every other stream with the first
|
||
* error (Node's destroyer, pipeline order), and calls cb once after the
|
||
* LAST 'close' with that error (or NULL). Streams borrowed; cb MOVES;
|
||
* answers the destination +1 (pipeline's return value). */
|
||
ScrStream *scr_stream_pipeline(double n, ScrStream **streams, ScrClosure *cb, ScrStreamErrInv inv);
|
||
/* The dyn-valued twins (the callback as a checked-dynamic value —
|
||
* mustCall wrappers; borrowed). */
|
||
ScrClosure *scr_stream_finished_dyn(ScrStream *s, struct ScrDyn *cb);
|
||
ScrStream *scr_stream_pipeline_dyn(double n, ScrStream **streams, struct ScrDyn *cb);
|
||
|
||
/* Emitted subclass RC/trace delegates for the state block (NULL-safe —
|
||
* an exception before super(options) leaves st unset). The registry
|
||
* slot rides scr_emitter_reg_* like any emitter subclass. */
|
||
void scr_stream_st_release(ScrStreamState *st);
|
||
void scr_stream_st_gcfree(ScrStreamState *st);
|
||
void scr_stream_st_trace(ScrStreamState *st, ScrTraceVisit visit, void *ctx);
|
||
|
||
/* The dyn-flavored completion-callback glues (the JS lane's implicitly-
|
||
* any option callbacks / underscore methods): the emitted invoke thunks
|
||
* mint a callable dyn (scr_dyn_new_func) over a 1-cap closure boxing the
|
||
* retained stream, with the kind's glue — arguments arrive as dyn
|
||
* values (error: null/undefined, a {message} object, or a string;
|
||
* transform/flush data: bytes, a string, or absent). */
|
||
typedef struct ScrDyn ScrDyn; /* full declaration below (C11 repeat) */
|
||
ScrDyn *scr_stream_done_dyn_w(ScrClosure *clo, ScrDyn *const *args, size_t argc);
|
||
ScrDyn *scr_stream_done_dyn_f(ScrClosure *clo, ScrDyn *const *args, size_t argc);
|
||
ScrDyn *scr_stream_done_dyn_d(ScrClosure *clo, ScrDyn *const *args, size_t argc);
|
||
ScrDyn *scr_stream_done_dyn_t(ScrClosure *clo, ScrDyn *const *args, size_t argc);
|
||
ScrDyn *scr_stream_done_dyn_l(ScrClosure *clo, ScrDyn *const *args, size_t argc);
|
||
|
||
/* The readable surface. push/unshift borrow their chunk (strings convert
|
||
* utf8, Node's decodeStrings default); push answers the below-hwm bool. */
|
||
bool scr_stream_push(ScrStream *s, ScrBytes *chunk);
|
||
bool scr_stream_push_str(ScrStream *s, ScrStr *str);
|
||
/* push(chunk, enc): the per-call literal encoding (canonical); overrides
|
||
* the stream's defaultEncoding. Borrows both. */
|
||
bool scr_stream_push_str_enc(ScrStream *s, ScrStr *str, ScrStr *enc);
|
||
/* The defaultEncoding option's push side: how push(string) decodes chunks
|
||
* (Buffer.from(chunk, enc)). Canonical literal, never "utf8". Receiver
|
||
* answers +1 (the setEncoding chaining shape). */
|
||
ScrStream *scr_stream_set_push_encoding(ScrStream *s, ScrStr *enc);
|
||
bool scr_stream_push_null(ScrStream *s);
|
||
void scr_stream_unshift(ScrStream *s, ScrBytes *chunk);
|
||
void scr_stream_unshift_str(ScrStream *s, ScrStr *str);
|
||
ScrBytes *scr_stream_read(ScrStream *s, double size); /* +1 or NULL; size < 0 = all/first; throws once encoded (the static type is Buffer|null) */
|
||
/* setEncoding(enc): string-chunk mode — pushes decode through the
|
||
* StringDecoder, 'data' delivers strings (the emit ABI carries both
|
||
* payload slots; see scr_stream_emit_data), lengths count JS string
|
||
* units. Borrows the canonical encoding name; receiver +1. */
|
||
ScrStream *scr_stream_set_encoding(ScrStream *s, ScrStr *enc);
|
||
typedef struct ScrPromise ScrPromise; /* full declaration below (C11 repeat) */
|
||
/* for-await: a +1 promise of the next chunk — buffered content (one
|
||
* whole entry in Readable.from mode, the whole buffer otherwise —
|
||
* Node's iterator read()), the EOF sentinel (an empty Buffer / dyn
|
||
* undefined), or a rejection with the stream's error. The _dyn twin
|
||
* boxes by runtime tag (the JS lane; encoded chunks arrive as dyn
|
||
* strings); the typed form throws on encoded streams (chunks are
|
||
* Buffers in typed code). At most one parked waiter (the loop awaits
|
||
* each chunk before asking again). */
|
||
bool scr_stream_push_dyn(ScrStream *s, const ScrDyn *d); /* borrows d; tag dispatch */
|
||
bool scr_stream_write_dyn(ScrStream *s, const ScrDyn *d, ScrClosure *cb); /* cb moves */
|
||
ScrPromise *scr_stream_next_chunk(ScrStream *s);
|
||
ScrPromise *scr_stream_next_chunk_dyn(ScrStream *s);
|
||
/* node:stream/promises — the promise forms over the finished/pipeline
|
||
* machinery above: a pending void promise the terminal watcher settles
|
||
* (fulfilled on a clean finish, rejected with the finish status
|
||
* otherwise — the stream's error or ERR_STREAM_PREMATURE_CLOSE).
|
||
* Streams borrowed; +1 promises. */
|
||
ScrPromise *scr_sp_finished(ScrStream *s);
|
||
ScrPromise *scr_sp_pipeline(double n, ScrStream **streams);
|
||
/* node:stream/consumers — the promise consumers over the readable
|
||
* machinery: accumulate every chunk (string chunks as their utf8 bytes)
|
||
* and settle at the terminal point (right after 'close', the eos timing
|
||
* Node's consumers share) — text answers the utf8 decode, json parses
|
||
* the text (malformed input rejects with the parse's SyntaxError; the
|
||
* result is a +1 dyn tree), buffer the concatenated bytes. Stream
|
||
* errors reject; an early close rejects ERR_STREAM_PREMATURE_CLOSE; a
|
||
* stream with no readable side rejects Node's async-iterable TypeError.
|
||
* Streams borrowed; +1 promises. */
|
||
ScrPromise *scr_sc_text(ScrStream *s);
|
||
ScrPromise *scr_sc_json(ScrStream *s);
|
||
ScrPromise *scr_sc_buffer(ScrStream *s);
|
||
/* Readable.from(array): +1 fully-seeded object-entry stream (one WHOLE
|
||
* chunk per element — strings or Buffers per the flag; hwm 1, already
|
||
* EOF'd). Borrows arr. */
|
||
ScrStream *scr_stream_from_arr(ScrArr *arr, bool strings);
|
||
ScrStream *scr_stream_pause(ScrStream *s); /* recv +1 */
|
||
ScrStream *scr_stream_resume(ScrStream *s); /* recv +1 */
|
||
bool scr_stream_is_paused(ScrStream *s);
|
||
double scr_stream_flowing(ScrStream *s); /* -1 = null, 0 = false, 1 = true */
|
||
ScrStream *scr_stream_pipe(ScrStream *src, ScrStream *dst, bool end); /* dst +1 */
|
||
ScrStream *scr_stream_unpipe(ScrStream *src, ScrStream *dst /*NULL = all*/); /* recv +1 */
|
||
|
||
/* The writable surface. write borrows its chunk; cb (when non-NULL)
|
||
* MOVES and fires after the chunk's user write completes. */
|
||
bool scr_stream_write(ScrStream *s, ScrBytes *chunk, ScrClosure *cb /*moves*/);
|
||
bool scr_stream_write_str(ScrStream *s, ScrStr *str, ScrClosure *cb /*moves*/);
|
||
bool scr_stream_write_null(ScrStream *s); /* throws ERR_STREAM_NULL_VALUES */
|
||
ScrStream *scr_stream_end(ScrStream *s, ScrBytes *chunk_b, ScrStr *chunk_s,
|
||
ScrClosure *cb /*moves*/); /* recv +1 */
|
||
void scr_stream_cork(ScrStream *s);
|
||
void scr_stream_uncork(ScrStream *s);
|
||
|
||
/* Shared lifecycle. destroy's err is BORROWED (may be NULL). */
|
||
ScrStream *scr_stream_destroy(ScrStream *s, ScrError *err); /* recv +1 */
|
||
/* Readable async-iterator return(): destroy with Node's AbortError payload;
|
||
* the iterator's internal error consumer suppresses an unhandled crash when
|
||
* no user error listener exists. Receiver +1. */
|
||
ScrStream *scr_stream_iterator_close(ScrStream *s);
|
||
double scr_stream_prop(ScrStream *s, const char *name);
|
||
ScrError *scr_stream_errored(ScrStream *s); /* +1 or NULL */
|
||
|
||
/* The underscore-method assignment surface (`r._read = fn` after
|
||
* construction — Node's own-property shadow of the prototype method):
|
||
* the option-callback slot swaps its closure (+1 moves in, the old one
|
||
* releases) and its compiler-emitted invoke thunk; the next dispatch
|
||
* uses the new callback, Node's timing. */
|
||
void scr_stream_set_read(ScrStream *s, ScrClosure *cb, ScrStreamReadInv inv);
|
||
void scr_stream_set_write(ScrStream *s, ScrClosure *cb, ScrStreamChunkInv inv);
|
||
void scr_stream_set_final(ScrStream *s, ScrClosure *cb, ScrStreamPlainInv inv);
|
||
void scr_stream_set_destroy(ScrStream *s, ScrClosure *cb, ScrStreamErrInv inv);
|
||
void scr_stream_set_transform(ScrStream *s, ScrClosure *cb, ScrStreamChunkInv inv);
|
||
void scr_stream_set_flush(ScrStream *s, ScrClosure *cb, ScrStreamPlainInv inv);
|
||
|
||
/* Completion entries for the compiler-emitted done closures (err/data
|
||
* MOVE; NULLs = absent). */
|
||
void scr_stream_write_done(ScrStream *s, ScrError *err);
|
||
void scr_stream_final_done(ScrStream *s, ScrError *err);
|
||
void scr_stream_destroy_done(ScrStream *s, ScrError *err);
|
||
void scr_stream_transform_done(ScrStream *s, ScrError *err, ScrBytes *data, ScrStr *data_str);
|
||
void scr_stream_flush_done(ScrStream *s, ScrError *err, ScrBytes *data, ScrStr *data_str);
|
||
|
||
/* RC / trace / install (the emitter-unit shapes). */
|
||
ScrStream *scr_stream_retain(ScrStream *s);
|
||
void scr_stream_release(ScrStream *s);
|
||
void *scr_stream_retain_v(void *s);
|
||
void scr_stream_release_v(void *s);
|
||
void scr_stream_trace(void *obj, ScrTraceVisit visit, void *ctx);
|
||
void scr_stream_install(void);
|
||
|
||
/* The loop's stream hook (scr_async.c): `pending` keeps the loop alive
|
||
* while deferred ticks exist; `dispatch` drains them at every turn top —
|
||
* the FIRST station (nextTick-most). Stream-free builds keep both NULL
|
||
* and the loop is byte-identical. */
|
||
void scr_loop_set_stream(bool (*pending)(void), void (*dispatch)(void));
|
||
void scr_union_trace_v(void *u, ScrTraceVisit visit, void *ctx);
|
||
void scr_promise_trace_v(void *p, ScrTraceVisit visit, void *ctx);
|
||
|
||
/* ── exceptions ─────────────────────────────────────────────────────
|
||
* One in-flight exception cell per execution context — the main stack plus
|
||
* one per fiber (scr_async.c swaps the active cell on fiber switches).
|
||
* `throw` stores the thrown value (taking ownership of refcounted payloads)
|
||
* and sets the pending flag; the COMPILER emits a pending check after every call that can throw
|
||
* and unwinds by releasing its frames/scopes and returning a dummy value —
|
||
* no setjmp/longjmp (longjmp would skip the emitted RC releases). A `catch`
|
||
* takes the exception by clearing the cell (the supported catch form is
|
||
* bindingless, so the payload is released, not read); an exception still
|
||
* pending when the entry function returns is uncaught: main prints it to
|
||
* stderr and exits 1 (Node's uncaught exit code).
|
||
*
|
||
* Ref payloads carry their RC entry points as function pointers, exactly
|
||
* like ScrUnion/SCR_BOX_OBJ: the runtime cannot know per-class/per-record
|
||
* struct layouts. A throw while an exception is already pending REPLACES it
|
||
* (the old payload is released) — that is JS's behavior for a throw inside
|
||
* a `finally` running on the exception path.
|
||
*
|
||
* Ownership: scr_throw_str/_ref take ownership (+1 moves into the cell);
|
||
* scr_exc_clear and scr_exc_print_uncaught release the payload, so a
|
||
* pending exception never survives them (the RC audit stays meaningful).
|
||
*/
|
||
|
||
typedef enum {
|
||
SCR_EXC_NONE = 0,
|
||
SCR_EXC_F64,
|
||
SCR_EXC_BOOL,
|
||
SCR_EXC_STR,
|
||
SCR_EXC_REF, /* array/closure/standalone object/record/union: RC via the fn ptrs */
|
||
/* A HIERARCHY-class instance (the payload carries a vtable word after
|
||
* rc): RC exactly like REF, but the dynamic class is inspectable — catch
|
||
* bindings run instanceof against the payload's preorder interval, and
|
||
* the uncaught/unhandled printers render Error instances as
|
||
* "name: message" instead of "[object]". */
|
||
SCR_EXC_OBJ,
|
||
/* The generator-return sentinel (`gen.return(v)` injected at a suspended
|
||
* yield): PENDING like an exception — the emitted pending checks unwind
|
||
* through finally blocks — but it is a RETURN completion, not a throw:
|
||
* catch handlers in generator bodies re-unwind on it (the emitted
|
||
* sentinel prologue), and the generator trampoline consumes it (clears
|
||
* the cell, promotes the parked return value to the completion value).
|
||
* Carries NO payload (the value rides the generator's ret slot), so
|
||
* scr_exc_clear/reset need no arm. Never escapes a generator fiber. */
|
||
SCR_EXC_GENRET,
|
||
/* Refcounted values whose JS typeof is not object (Symbol, BigInt,
|
||
* functions, and checked-dynamic primitives). The payload uses the
|
||
* same ownership as REF. */
|
||
SCR_EXC_PRIMITIVE_REF,
|
||
} ScrExcKind;
|
||
|
||
/* One cell per fiber (JS has one exception in flight per execution
|
||
* context); scr_async.c swaps the active cell on fiber switches. */
|
||
typedef struct ScrExcCell {
|
||
ScrExcKind kind;
|
||
double f64;
|
||
bool b;
|
||
void *payload;
|
||
void *(*retain_fn)(void *);
|
||
void (*release_fn)(void *);
|
||
/* Non-NULL iff the REF payload type carries a cycle header — carried
|
||
* along so a rejection moving the payload into a promise keeps the
|
||
* promise's payload edge traceable. */
|
||
ScrTraceFn trace_fn;
|
||
} ScrExcCell;
|
||
|
||
/* Runtime-internal (fiber machinery in scr_async.c) — never emitted. */
|
||
ScrExcCell *scr_exc_swap_cell(ScrExcCell *cell); /* NULL = main's cell */
|
||
ScrExcCell *scr_exc_current_cell(void); /* the ACTIVE cell */
|
||
|
||
bool scr_exc_pending(void);
|
||
|
||
void scr_throw_f64(double v);
|
||
void scr_throw_bool(bool v);
|
||
void scr_throw_str(ScrStr *v); /* takes ownership */
|
||
/* Takes ownership of v; retain/release must be non-NULL (the compiler
|
||
* passes the payload type's `_v` adapters, like scr_union_new_ref); trace
|
||
* is non-NULL iff the payload type carries a cycle header. */
|
||
void scr_throw_ref(void *v, void *(*retain)(void *), void (*release)(void *),
|
||
ScrTraceFn trace);
|
||
/* Store a reference whose JS typeof was checked at the throw site. */
|
||
void scr_throw_ref_classified(void *v, void *(*retain)(void *), void (*release)(void *),
|
||
ScrTraceFn trace, bool object);
|
||
void scr_throw_primitive_ref(void *v, void *(*retain)(void *), void (*release)(void *),
|
||
ScrTraceFn trace);
|
||
/* Same ownership contract as scr_throw_ref; the payload must be a
|
||
* hierarchy-class instance (vtable word present) — see SCR_EXC_OBJ. */
|
||
void scr_throw_obj(void *v, void *(*retain)(void *), void (*release)(void *),
|
||
ScrTraceFn trace);
|
||
|
||
/* Catch: discard the in-flight exception (releases a refcounted payload). */
|
||
void scr_exc_clear(void);
|
||
|
||
/* ── catch bindings ───────────────────────────────────────────────────
|
||
* `catch (e)` binds the in-flight exception as a refcounted SNAPSHOT box:
|
||
* entering the catch MOVES the cell's payload into a fresh ScrCaught (the
|
||
* pending flag clears), the binding releases with its scope like any
|
||
* refcounted local, and every runtime question a catch body may ask —
|
||
* typeof-style kind tests (emitted as direct kind reads), instanceof over
|
||
* hierarchy payloads, payload extraction, rethrow — reads the box. The box
|
||
* itself carries no collector header: bindings live only in frames (the
|
||
* frontend rejects captures), so no cycle can pass through it, and its
|
||
* payload reference simply counts as an external edge — trial deletion
|
||
* restores it on scan. */
|
||
typedef struct ScrCaught {
|
||
size_t rc;
|
||
ScrExcKind kind; /* never SCR_EXC_NONE */
|
||
double f64;
|
||
bool b;
|
||
void *payload;
|
||
void *(*retain_fn)(void *);
|
||
void (*release_fn)(void *);
|
||
ScrTraceFn trace_fn;
|
||
} ScrCaught;
|
||
|
||
ScrCaught *scr_exc_take(void); /* moves the pending cell into a fresh box (+1) */
|
||
ScrCaught *scr_caught_retain(ScrCaught *c);
|
||
void scr_caught_release(ScrCaught *c); /* NULL-tolerant */
|
||
/* Replace the pending disposal error with a SuppressedError and consume the
|
||
* original body-error snapshot. The current exception cell must be pending. */
|
||
void scr_exc_suppress(ScrCaught *suppressed);
|
||
/* Borrows the box; re-raises the saved exception exactly (payload RETAINED
|
||
* — the binding stays live until its scope exits). */
|
||
void scr_rethrow(const ScrCaught *c);
|
||
/* `e instanceof C` on a catch binding: an OBJ payload whose vtable preorder
|
||
* lies inside C's interval. False for every other payload kind. */
|
||
bool scr_caught_instanceof(const ScrCaught *c, size_t pre, size_t post);
|
||
/* `typeof e === "object"` on a catch binding. Throw sites choose REF or
|
||
* PRIMITIVE_REF from the value's JS typeof before storing the payload. */
|
||
bool scr_caught_is_object(const ScrCaught *c);
|
||
/* `String(e)` / `${e}` on a catch binding: JS's String() over the snapshot
|
||
* — numbers/booleans/strings by value, Error payloads via
|
||
* scr_error_to_string ("name: message" — String(e) carries no stack in
|
||
* Node either), every other ref payload "[object Object]" (exact for
|
||
* records and toString-less class instances; a documented approximation
|
||
* for thrown arrays/closures/union boxes). Borrows the box; returns +1. */
|
||
ScrStr *scr_caught_to_string(const ScrCaught *c);
|
||
/* `e as C` on a catch binding (scr_lib.c): an OBJ payload inside C's
|
||
* preorder interval extracts (retained, +1); every other payload THROWS a
|
||
* catchable TypeError naming `cls`. Callers are compiler-emitted pending
|
||
* checks. */
|
||
void *scr_caught_check_obj(const ScrCaught *c, size_t pre, size_t post,
|
||
const char *cls);
|
||
/* The snapshot as a dyn value (scr_json.c): identity-preserving for dyn
|
||
* payloads and %Error instances, scalars by value, the type-erased empty
|
||
* object for the rest (SEMANTICS.md 67). Borrows the box; +1. */
|
||
typedef struct ScrDyn ScrDyn; /* full definition below (C11 repeat) */
|
||
ScrDyn *scr_caught_to_dyn(const ScrCaught *c);
|
||
|
||
/* Uncaught path (called by main before exiting 1): flushes stdout, prints
|
||
* "Uncaught <value>" to stderr (strings raw, numbers JS-exact, booleans
|
||
* true/false, ref payloads as "[object]"), and releases the payload. */
|
||
void scr_exc_print_uncaught(void);
|
||
/* Optional native process exception registry. Hook returns 1 when handled,
|
||
* 0 when unhandled, and -1 when a handler itself threw (Node exit 7). */
|
||
extern int (*scr_uncaught_exception_hook)(bool from_promise);
|
||
bool scr_exc_handle_uncaught(bool from_promise);
|
||
void scr_process_on_uncaught_exception(ScrDyn *fn, bool once, bool monitor);
|
||
void scr_process_off_uncaught_exception(ScrDyn *fn, bool monitor);
|
||
|
||
/* ── standard library: process + node:fs (scr_lib.c) ─────────────────
|
||
* Called once at the top of main, right after scr_init: stashes argc/argv
|
||
* (the interned process.argv array is built lazily on first read) and
|
||
* registers an atexit cleanup that releases the library's interned values
|
||
* BEFORE the RC audit runs (atexit is LIFO; scr_init registered the audit
|
||
* earlier). scriptc argv shape: ["scriptc", argv[0], argv[1], ...] so
|
||
* positions and length line up with Node's [node-path, script-path, ...args]
|
||
* — the argv[0]/argv[1] VALUES diverge (see SEMANTICS.md).
|
||
*/
|
||
void scr_lib_init(int argc, char **argv);
|
||
|
||
/* +1 on the ONE interned argv array (identity and mutation semantics match
|
||
* Node's stable process.argv). */
|
||
ScrArr *scr_process_argv(void);
|
||
|
||
/* CommonJS module graph introspection. Module values are process-lifetime
|
||
* numeric handles into a registry populated by the generated program before
|
||
* its entry initializer runs. Metadata arguments are borrowed; getters return
|
||
* owned (+1) strings/arrays. Negative parent handles distinguish null (-1)
|
||
* from undefined (-2); a cache miss is -1. */
|
||
void scr_module_registry_init(double count);
|
||
void scr_module_define(double module_id, ScrStr *filename, ScrStr *id,
|
||
ScrStr *path, ScrArr *paths, bool is_main);
|
||
void scr_module_enter(double module_id);
|
||
void scr_module_link(double parent_id, double child_id);
|
||
void scr_module_finish(double module_id);
|
||
void scr_module_fail(double module_id);
|
||
ScrStr *scr_module_filename(double module_id);
|
||
ScrStr *scr_module_id(double module_id);
|
||
ScrStr *scr_module_path(double module_id);
|
||
ScrArr *scr_module_paths(double module_id);
|
||
ScrArr *scr_module_children(double module_id);
|
||
double scr_module_parent(double module_id);
|
||
bool scr_module_loaded(double module_id);
|
||
double scr_module_cache_get(ScrStr *filename);
|
||
bool scr_module_cache_has(ScrStr *filename);
|
||
ScrArr *scr_module_cache_keys(void);
|
||
/* Raw argv accessors — the island's process shim builds the same
|
||
* ["scriptc", argv[0], ...] shape from the same stash. */
|
||
int scr_lib_arg_count(void);
|
||
const char *scr_lib_arg(int i);
|
||
/* Private self-reexec marker parsed and removed from process.argv by
|
||
* scr_lib_init. False in an ordinary parent invocation. */
|
||
bool scr_lib_fork_info(double *target, uintptr_t *read_handle,
|
||
uintptr_t *write_handle);
|
||
/* True only for a child_process call whose command and first argument both
|
||
* resolve to this executable — Node's process.execPath/process.argv[1]
|
||
* self-reexec shape. Child argv builders collapse that known script marker;
|
||
* direct startup never filters user arguments. */
|
||
bool scr_lib_should_collapse_reexec_arg(ScrStr *cmd, ScrArr *args);
|
||
ScrStr *scr_process_platform(void); /* +1 interned ("darwin", "linux", ...) */
|
||
ScrStr *scr_process_cwd(void); /* +1 fresh (getcwd) */
|
||
/* Submit one raw chunk to fd 1/2 and flush it before returning. Used by all
|
||
* JavaScript-visible console/process/readline/island output paths so the
|
||
* internal stdio formatting buffer never delays live output. */
|
||
bool scr_stdio_write(int fd, const void *data, size_t len);
|
||
int scr_stdio_write_raw(int fd, const void *data, size_t len);
|
||
extern bool (*scr_stdio_write_hook)(int fd, const void *data, size_t len);
|
||
/* process.stdout/.stderr .write — raw bytes (no newline or formatting; data
|
||
* borrowed), promptly visible and ordered with console output. Constantly
|
||
* true (the synchronous runtime never queues backpressure). */
|
||
bool scr_process_stdout_write(const ScrStr *data);
|
||
bool scr_process_stderr_write(const ScrStr *data);
|
||
/* The first-class WritableStream write: fd is the stream value itself
|
||
* (1 = stdout, 2 = stderr — process.stdout/stderr reads mint it). */
|
||
bool scr_proc_stream_write(double fd, const ScrStr *data);
|
||
/* getenv(3) behind process.env.<NAME>: +1 fresh string when the variable is
|
||
* set, NULL when absent. The compiler wraps the result into the interned
|
||
* `string | undefined` union type-directedly (the runtime knows no tags);
|
||
* the name is borrowed. Never throws. */
|
||
ScrStr *scr_env_get(const ScrStr *name);
|
||
/* setenv(3) behind process.env.NAME = v: later env reads and spawned
|
||
* children observe the write, like Node. Both borrowed. Never throws. */
|
||
void scr_env_set(const ScrStr *name, const ScrStr *value);
|
||
void scr_env_unset(const ScrStr *name); /* delete process.env.NAME */
|
||
/* The whole environment as alternating [k, v, k, v, ...] strings in
|
||
* environ order — the compiler's process.env snapshot builds its record
|
||
* from this. +1 fresh array. Never throws. */
|
||
ScrArr *scr_env_pairs(void);
|
||
/* getpid(2) / getuid(2) — POSIX-only target, always answer. */
|
||
double scr_process_pid(void);
|
||
double scr_process_getuid(void);
|
||
double scr_process_getgid(void);
|
||
/* process.execPath: the running binary's own resolved absolute path
|
||
* (_NSGetExecutablePath/readlink(/proc/self/exe) + realpath), interned on
|
||
* first read; +1 per read. Node's value is the node executable's path —
|
||
* SEMANTICS.md divergence 12 documents the difference. */
|
||
ScrStr *scr_process_exec_path(void);
|
||
/* process.arch: the binary's OWN architecture ("arm64", "x64") — Node's
|
||
* answer for its own build on the same machine. +1 interned. */
|
||
ScrStr *scr_process_arch(void);
|
||
/* process.versions.node: the runtime's Node COMPATIBILITY TARGET — no
|
||
* Node exists under a compiled binary, so this reports the version whose
|
||
* semantics the runtime implements (SEMANTICS.md divergence 60). +1
|
||
* interned. */
|
||
ScrStr *scr_process_versions_node(void);
|
||
ScrStr *scr_process_versions_openssl(void);
|
||
/* Stable native version dictionary (+1); node/openssl are compatibility
|
||
* targets, matching the scalar getters above. */
|
||
ScrDyn *scr_process_versions(void);
|
||
/* process.kill with Node's exact semantics: int32 pid validation (the
|
||
* ERR_INVALID_ARG_TYPE TypeError text), Node's signal-name table for the
|
||
* named form (unknown names throw the ERR_UNKNOWN_SIGNAL TypeError),
|
||
* signal 0 probes, kill(2) failure throws Node's `kill ESRCH`/`kill
|
||
* EPERM` Error. Both return Node's constant true; the signal string is
|
||
* borrowed. */
|
||
bool scr_process_kill(double pid, double signum);
|
||
bool scr_process_kill_named(double pid, const ScrStr *signal);
|
||
/* fflush stdout, then _Exit((int)code): no atexit handlers run — the RC
|
||
* audit is deliberately skipped (exiting mid-program leaves live values). */
|
||
void scr_process_exit(double code);
|
||
void scr_process_set_exit_code(double code);
|
||
double scr_process_exit_code_or_zero(void);
|
||
/* process._exiting: true once the exit sequence began (process.exit or
|
||
* the exit-listener runner set the flag). Never throws. */
|
||
extern SCR_TL bool scr_process_in_exit;
|
||
bool scr_process_exiting(void);
|
||
/* umask(2): mask < 0 reads without setting; otherwise sets and answers
|
||
* the previous mask. Never throws. */
|
||
double scr_process_umask(double mask);
|
||
/* The process introspection statics (scr_lib.c; Node's units). uptime:
|
||
* fractional seconds since the binary's load-time anchor. The CPU clocks
|
||
* answer microseconds; the *_diff forms subtract a prior sample, and
|
||
* scr_cpu_prev_validate throws Node's ERR_INVALID_ARG_VALUE RangeError
|
||
* (user first, then system) for negative/non-finite prev fields.
|
||
* rusage(idx) answers one resourceUsage() field by canonical index.
|
||
* available/constrained memory are libuv's numbers (0 where the platform
|
||
* has no answer). scr_active_resources (scr_async.c) walks the loop's
|
||
* own bookkeeping — 'Timeout'/'Immediate' strings, +1. */
|
||
double scr_process_uptime(void);
|
||
/* perf_hooks performance.now(): fractional ms since process start (the
|
||
* uptime anchor — Node's timeOrigin for a compiled program). */
|
||
double scr_perf_now(void);
|
||
double scr_cpu_user(void);
|
||
double scr_cpu_system(void);
|
||
double scr_cpu_user_diff(double prev);
|
||
double scr_cpu_system_diff(double prev);
|
||
double scr_thread_cpu_user(void);
|
||
double scr_thread_cpu_system(void);
|
||
double scr_thread_cpu_user_diff(double prev);
|
||
double scr_thread_cpu_system_diff(double prev);
|
||
void scr_cpu_prev_validate(double user, double system);
|
||
double scr_process_rusage(double idx);
|
||
double scr_available_memory(void);
|
||
double scr_constrained_memory(void);
|
||
ScrArr *scr_active_resources(void);
|
||
/* chdir(2) — throws Node's fs-shaped error on failure (syscall "chdir"). */
|
||
void scr_process_chdir(ScrStr *dir);
|
||
/* net's process-wide happy-eyeballs attempt budget (default 250ms) — in
|
||
* the core unit so the knob never links scr_net.c. Never throw. */
|
||
double scr_net_get_autosel_timeout(void);
|
||
void scr_net_set_autosel_timeout(double ms);
|
||
|
||
/* Synchronous fs, utf8-only (paths and contents are ScrStr bytes). All
|
||
* ScrStr arguments are BORROWED; string/array results return +1. On
|
||
* failure every function except scr_fs_exists THROWS through the exception
|
||
* cell (scr_throw_str) a CATCHABLE string formatted like Node's common
|
||
* error messages ("ENOENT: no such file or directory, open 'x'") and
|
||
* returns a dummy (false/NULL) — callers are compiler-emitted pending
|
||
* checks. scr_fs_exists mirrors Node's existsSync: errors become false,
|
||
* never a throw. */
|
||
ScrStr *scr_fs_read_file(ScrStr *path);
|
||
/* realpath(3) (+1 fresh); failures throw with Node's realpathSync
|
||
* spelling (syscall "lstat"). */
|
||
ScrStr *scr_fs_realpath(ScrStr *path);
|
||
ScrStr *scr_fs_realpath_promise(ScrStr *path);
|
||
void scr_fs_write_file(ScrStr *path, ScrStr *data);
|
||
void scr_fs_append_file(ScrStr *path, ScrStr *data);
|
||
bool scr_fs_exists(ScrStr *path);
|
||
void scr_fs_mkdir(ScrStr *path);
|
||
/* Files only, like Node's rmSync without `recursive` (a directory throws an
|
||
* EISDIR-coded error; use scr_fs_rmdir for empty directories). */
|
||
void scr_fs_rm(ScrStr *path);
|
||
void scr_fs_rmdir(ScrStr *path);
|
||
ScrArr *scr_fs_readdir(ScrStr *path); /* names, OS order, no "."/".." */
|
||
|
||
/* The fs option forms (same throw discipline). mkdir_recursive is Node's
|
||
* recursive algorithm (existing-dir target is a no-op; errors report
|
||
* Node's errno at Node's path). rm_opts is rmSync with the recursive/
|
||
* force booleans: force swallows ENOENT, recursive removes trees
|
||
* post-order, a directory without recursive keeps the EISDIR wording
|
||
* divergence. mkdtemp appends the six X's and returns the created path
|
||
* (+1). access takes the F_OK/R_OK/W_OK/X_OK bits. read_fd is a read(2)
|
||
* loop to EOF (Node's readFileSync(fd) shape — the stdin pattern);
|
||
* failures throw Node's no-path message ("EBADF: bad file descriptor,
|
||
* read"). */
|
||
void scr_fs_mkdir_recursive(ScrStr *path);
|
||
void scr_fs_rm_opts(ScrStr *path, bool recursive, bool force);
|
||
/* The maxRetries/retryDelay form: retries EBUSY/EMFILE/ENFILE/ENOTEMPTY/
|
||
* EPERM failures up to maxRetries times with Node's linear backoff
|
||
* (retryDelay ms longer per try); everything else throws immediately. */
|
||
void scr_fs_rm_opts_retry(ScrStr *path, bool recursive, bool force, double max_retries, double retry_delay);
|
||
ScrStr *scr_fs_mkdtemp(ScrStr *prefix);
|
||
void scr_fs_access(ScrStr *path, double mode);
|
||
ScrStr *scr_fs_read_fd(double fd);
|
||
|
||
/* The wider sync fs slice (same throw discipline; `.code` stamped like
|
||
* the rest). unlink/chmod/chown wrap the syscalls 1:1. copyfile copies
|
||
* contents into a created-or-truncated destination carrying the SOURCE's
|
||
* mode; its errors quote both paths ("copyfile 'src' -> 'dest'", Node's
|
||
* shape). write_file_mode is writeFileSync(p, data, { mode }) — the mode
|
||
* applies at creation only. The mkdir *_mode twins take mkdirSync's
|
||
* explicit mode (the recursive walk passes it to every directory it
|
||
* creates). */
|
||
void scr_fs_unlink(ScrStr *path);
|
||
void scr_fs_chmod(ScrStr *path, double mode);
|
||
void scr_fs_chown(ScrStr *path, double uid, double gid);
|
||
void scr_fs_copyfile(ScrStr *src, ScrStr *dest);
|
||
void scr_fs_rename(ScrStr *oldpath, ScrStr *newpath);
|
||
/* The callback rename worker uses the non-throwing syscall seam off the
|
||
* runtime thread, then materializes any Node-shaped Error back on the main
|
||
* thread. raw returns 0 on success or a positive errno value on failure. */
|
||
int scr_fs_rename_raw(const ScrStr *oldpath, const ScrStr *newpath);
|
||
void scr_fs_rename_error(int error, const ScrStr *oldpath, const ScrStr *newpath);
|
||
void scr_fs_write_file_mode(ScrStr *path, ScrStr *data, double mode);
|
||
void scr_fs_mkdir_mode(ScrStr *path, double mode);
|
||
void scr_fs_mkdir_recursive_mode(ScrStr *path, double mode);
|
||
|
||
/* Atomics.wait(int32Array, idx, expected, timeoutMs): "not-equal" when
|
||
* the element differs from expected, else a real nanosleep for the
|
||
* timeout and "timed-out" (+1 string either way). scriptc has no
|
||
* threads, so nothing can ever notify — for every compilable program
|
||
* this IS the spec's behavior and "ok" is unreachable (the compiler
|
||
* requires the timeout argument; an infinite wait would be a certain
|
||
* deadlock). Out-of-range indices trap like every bytes access. */
|
||
typedef struct ScrBytes ScrBytes; /* full definition below (C11 repeat) */
|
||
ScrStr *scr_atomics_wait(ScrBytes *arr, double idx, double expected, double timeout_ms);
|
||
|
||
/* isatty(3) over an fd (0/1/2): a real boolean — false where Node's
|
||
* non-TTY streams expose undefined (SEMANTICS.md). stdin_destroy is a
|
||
* documented no-op. */
|
||
bool scr_process_is_tty(double fd);
|
||
void scr_process_stdin_destroy(void);
|
||
void scr_process_stdin_set_raw_mode(bool raw);
|
||
|
||
/* Terminal width for process.stdout/stderr.columns (fd 1/2):
|
||
* ioctl(TIOCGWINSZ), -1 when the fd is not a TTY or the ioctl refuses —
|
||
* the emitter wraps a non-negative width into the `number | undefined`
|
||
* union's number arm and -1 into its undefined arm (Node's non-TTY
|
||
* `.columns` is undefined). Never throws. */
|
||
double scr_process_columns(double fd);
|
||
/* Terminal height; same optional-number sentinel contract as columns. */
|
||
double scr_process_rows(double fd);
|
||
|
||
/* Stats values (statSync/lstatSync and their fs.promises twins): an immutable
|
||
* snapshot of stat(2). scr_fs_stat follows symlinks; scr_fs_lstat does not.
|
||
* Both THROW like the other sync calls. */
|
||
typedef struct ScrStats ScrStats;
|
||
typedef struct ScrFileHandle ScrFileHandle;
|
||
typedef struct ScrPromise ScrPromise; /* full section further down */
|
||
|
||
ScrStats *scr_fs_stat(ScrStr *path); /* +1, or throws */
|
||
ScrStats *scr_fs_lstat(ScrStr *path); /* +1, or throws; NO follow (lstat) */
|
||
ScrStats *scr_fs_fstat(double fd); /* +1, or throws */
|
||
void scr_fs_fchmod(double fd, double mode);
|
||
void scr_fs_fsync(double fd);
|
||
void scr_fs_link(ScrStr *source, ScrStr *dest);
|
||
ScrStats *scr_stats_retain(ScrStats *s);
|
||
void scr_stats_release(ScrStats *s);
|
||
void *scr_stats_retain_v(void *p);
|
||
void scr_stats_release_v(void *p);
|
||
bool scr_stats_is_file(ScrStats *s);
|
||
bool scr_stats_is_dir(ScrStats *s);
|
||
bool scr_stats_is_symlink(ScrStats *s); /* lstat snapshots only */
|
||
double scr_stats_dev(ScrStats *s);
|
||
double scr_stats_ino(ScrStats *s);
|
||
double scr_stats_size(ScrStats *s);
|
||
double scr_stats_blocks(ScrStats *s); /* allocated size in 512-byte units */
|
||
double scr_stats_nlink(ScrStats *s);
|
||
double scr_stats_atime_ms(ScrStats *s); /* ms with the sub-second fraction */
|
||
double scr_stats_mtime_ms(ScrStats *s); /* ms with the ns fraction */
|
||
double scr_stats_ctime_ms(ScrStats *s); /* ms with the ns fraction */
|
||
|
||
/* fs/promises: the SAME sync operations, minting an already-settled
|
||
* promise — success fulfills, failure REJECTS with the would-be thrown
|
||
* error (catchable at the await, like Node). The syscall blocks the loop:
|
||
* I/O never interleaves with timers/other fibers (SEMANTICS.md). Implemented in
|
||
* scr_async.c beside the promise machinery (the runtime unit tests link
|
||
* scr_lib.c without the fiber slice). */
|
||
ScrPromise *scr_fsp_read_file(ScrStr *path);
|
||
ScrPromise *scr_fsp_write_file(ScrStr *path, ScrStr *data);
|
||
ScrPromise *scr_fsp_write_file_mode(ScrStr *path, ScrStr *data, double mode);
|
||
ScrPromise *scr_fsp_mkdir(ScrStr *path);
|
||
ScrPromise *scr_fsp_mkdir_mode(ScrStr *path, double mode);
|
||
ScrPromise *scr_fsp_mkdir_recursive(ScrStr *path);
|
||
ScrPromise *scr_fsp_mkdir_recursive_mode(ScrStr *path, double mode);
|
||
ScrPromise *scr_fsp_unlink(ScrStr *path);
|
||
ScrPromise *scr_fsp_chmod(ScrStr *path, double mode);
|
||
ScrPromise *scr_fsp_readdir(ScrStr *path);
|
||
ScrPromise *scr_fsp_rm(ScrStr *path);
|
||
ScrPromise *scr_fsp_stat(ScrStr *path);
|
||
ScrPromise *scr_fsp_realpath(ScrStr *path);
|
||
ScrPromise *scr_fsp_lstat(ScrStr *path);
|
||
ScrPromise *scr_fsp_rename(ScrStr *oldpath, ScrStr *newpath);
|
||
ScrPromise *scr_fsp_open(ScrStr *path, ScrStr *flags, double mode);
|
||
ScrPromise *scr_file_handle_close_promise(ScrFileHandle *h);
|
||
ScrPromise *scr_file_handle_read_file_promise(ScrFileHandle *h, ScrStr *encoding);
|
||
ScrPromise *scr_file_handle_read_file_bytes_promise(ScrFileHandle *h, ScrStr *encoding);
|
||
ScrPromise *scr_file_handle_write_file_promise(ScrFileHandle *h, ScrStr *data, ScrStr *encoding);
|
||
ScrPromise *scr_file_handle_write_file_bytes_promise(ScrFileHandle *h, ScrBytes *data, ScrStr *encoding);
|
||
ScrPromise *scr_file_handle_stat_promise(ScrFileHandle *h);
|
||
|
||
/* Shared executable worker pool. `work` runs on one of four native worker
|
||
* threads and must not touch runtime RC/exception state; `after` and
|
||
* `destroy` run on the main runtime thread. WASI runs `work` on a later
|
||
* loop turn. The payload is owned by the submission until destroy. */
|
||
typedef void (*ScrWorkFn)(void *payload);
|
||
void scr_work_submit(void *payload, ScrWorkFn work, ScrWorkFn after,
|
||
ScrWorkFn destroy);
|
||
|
||
/* fs.rename: the syscall is submitted to a native worker immediately and
|
||
* its error-first callback fires on a later event-loop turn through an
|
||
* emitted, program-shaped adapter. Both paths and the callback are borrowed
|
||
* at entry; the callback MOVES into the operation. err is borrowed and NULL
|
||
* on success. */
|
||
typedef void (*ScrFsRenameFn)(ScrClosure *cb, ScrError *err);
|
||
void scr_fs_rename_async(ScrStr *oldpath, ScrStr *newpath,
|
||
ScrClosure *cb /*moves*/, ScrFsRenameFn fn);
|
||
void scr_fs_rename_thunk0(ScrClosure *cb, ScrError *err);
|
||
|
||
/* ── node:timers/promises (scr_async.c) ──────────────────────────────
|
||
* The promisified pair: a PENDING void promise a one-shot heap timer /
|
||
* the immediate queue fulfills (the armed entry keeps the loop alive
|
||
* until it fires, like Node). Results +1; neither throws. */
|
||
ScrPromise *scr_tp_set_timeout(double ms);
|
||
ScrPromise *scr_tp_set_immediate(void);
|
||
|
||
/* ── node:diagnostics_channel (scr_dc.c — linked when the IR carries
|
||
* dc.* libCalls) ─────────────────────────────────────────────────────
|
||
* A process-global name→channel registry; channel handles are 1-based
|
||
* f64 indexes into it (Node's channels are process-lived too, its
|
||
* WeakRef machinery aside — the registry tears down atexit before the
|
||
* RC audit). Subscribers are dyn FUNCTION values, identity-matched by
|
||
* unsubscribe (the underlying closure, so re-boxings of one function
|
||
* still match — JS has ONE function object). publish calls each
|
||
* subscriber of a SNAPSHOT with (message, name); a subscriber's throw
|
||
* propagates out (MAY THROW — the documented divergence from Node's
|
||
* triggerUncaughtException). subscribe/unsubscribe throw Node's
|
||
* ERR_INVALID_ARG_TYPE TypeError for non-function subscribers (MAY
|
||
* THROW). Names and subscriber args are BORROWED; chanName's result +1. */
|
||
double scr_dc_channel(ScrStr *name);
|
||
void scr_dc_subscribe(ScrStr *name, ScrDyn *cb);
|
||
bool scr_dc_unsubscribe(ScrStr *name, ScrDyn *cb);
|
||
bool scr_dc_has_subscribers(ScrStr *name);
|
||
void scr_dc_publish(double handle, ScrDyn *message);
|
||
void scr_dc_chan_subscribe(double handle, ScrDyn *cb);
|
||
bool scr_dc_chan_unsubscribe(double handle, ScrDyn *cb);
|
||
bool scr_dc_chan_has_subscribers(double handle);
|
||
ScrStr *scr_dc_chan_name(double handle);
|
||
|
||
/* TracingChannel (dc.tracingChannel): a registry entry of the five event
|
||
* channels (tracing:<name>:start|end|asyncStart|asyncEnd|error), handled
|
||
* as a 1-based f64 exactly like Channel. subscribe/unsubscribe walk the
|
||
* handlers object's five event keys (truthy slots route to the per-channel
|
||
* add/remove — a non-function slot throws there, Node's order);
|
||
* unsubscribe answers Node's all-found conjunction. traceSync/traceCallback
|
||
* mirror Node's publish choreography over the checked-dynamic tree: ctx must be an OBJ (the
|
||
* lowering builds the default `{}`), fn/thisArg/args are dyn values (args
|
||
* an ARR of the call arguments); the traced call binds thisArg as the
|
||
* ambient receiver; result/error stamp onto ctx before end/error publish;
|
||
* a traced throw and a subscriber throw both propagate (MAY THROW). All
|
||
* arguments BORROWED; trace results +1 (or NULL with the exception
|
||
* pending). traceCallback wraps args[position] (JS-style negative
|
||
* indexing) in a native wrapper publishing error/result + asyncStart/
|
||
* asyncEnd around the original callback, throwing Node's
|
||
* ERR_INVALID_ARG_TYPE when the slot is not a function. */
|
||
double scr_dc_tracing_channel(ScrStr *name);
|
||
double scr_dc_tracing_channel_of(double start, double end, double async_start,
|
||
double async_end, double error);
|
||
double scr_dc_tc_channel(double h, double idx); /* 0=start..4=error */
|
||
bool scr_dc_tc_has_subscribers(double h);
|
||
void scr_dc_tc_subscribe(double h, ScrDyn *handlers);
|
||
bool scr_dc_tc_unsubscribe(double h, ScrDyn *handlers);
|
||
ScrDyn *scr_dc_tc_trace_sync(double h, ScrDyn *fn, ScrDyn *ctx, ScrDyn *this_arg, ScrDyn *args);
|
||
ScrDyn *scr_dc_tc_trace_callback(double h, ScrDyn *fn, double position, ScrDyn *ctx,
|
||
ScrDyn *this_arg, ScrDyn *args);
|
||
/* bindStore / runStores (the AsyncLocalStorage integration): per-store
|
||
* SET semantics (a re-bind replaces the transform; a non-function
|
||
* transform means identity); runStores enters every bound store with
|
||
* transform(data), publishes inside the entered contexts, runs fn with
|
||
* thisArg bound and arguments forwarded, and restores in reverse. The
|
||
* tracing choreography enters the start channel's stores around
|
||
* traceSync/traceCallback/tracePromise bodies and the asyncStart
|
||
* channel's around traceCallback's wrapped callback. */
|
||
void scr_dc_chan_bind_store(double handle, double als_id, ScrDyn *transform);
|
||
bool scr_dc_chan_unbind_store(double handle, double als_id);
|
||
ScrDyn *scr_dc_chan_run_stores(double handle, ScrDyn *data, ScrDyn *fn, ScrDyn *this_arg,
|
||
ScrDyn *args);
|
||
/* tracePromise: publishes start, runs fn (throw → error + end + rethrow,
|
||
* NULL result with the exception pending), wraps a non-promise result,
|
||
* publishes end, and returns the REACTION promise (+1) — a fiber that
|
||
* awaits the traced promise, stamps ctx.result/ctx.error, publishes
|
||
* asyncStart/asyncEnd (plus error first on rejection), and settles the
|
||
* result with the passed-through outcome. No-subscriber calls skip the
|
||
* choreography (Node's early exit) but still answer a promise. */
|
||
ScrPromise *scr_dc_tc_trace_promise(double h, ScrDyn *fn, ScrDyn *ctx, ScrDyn *this_arg,
|
||
ScrDyn *args);
|
||
|
||
/* ── child_process (scr_child.c) ─────────────────────────────────────
|
||
* The synchronous slice: spawnSync as posix_spawnp + piped utf8 capture +
|
||
* waitpid — blocks until the child is reaped (no zombie can outlive the
|
||
* call). NEVER throws: spawn failure (nonexistent binary, EACCES) is data
|
||
* — has_status false and empty outputs — like Node's `error` property
|
||
* (Node types stdout/stderr null there; divergence in SEMANTICS.md).
|
||
* A signal-killed child also has no status (Node's status: null). */
|
||
typedef struct ScrSpawnRes ScrSpawnRes;
|
||
|
||
ScrSpawnRes *scr_spawn_sync(ScrStr *cmd, ScrArr *args); /* +1, never throws */
|
||
/* The options form (cp.spawnSyncOpts): timeout_ms > 0 sends killsignal
|
||
* (a signal NAME; "" = SIGTERM) at the deadline and the result carries
|
||
* error: ETIMEDOUT + the signal — never a throw. stdio modes: stdin 0 =
|
||
* /dev/null, 2 = inherit; stdout/stderr 0 = capture, 1 = ignore, 2 =
|
||
* inherit (non-captured outputs read "" — the documented stance). */
|
||
ScrSpawnRes *scr_spawn_sync_opts(ScrStr *cmd, ScrArr *args, double timeout_ms,
|
||
ScrStr *killsignal, double in_mode,
|
||
double out_mode, double err_mode);
|
||
/* The runtime-string stdio entry: the compiler proved the value is
|
||
* "pipe" | "ignore" | "inherit" by type; the mode mapping happens here. */
|
||
ScrSpawnRes *scr_spawn_sync_stdio_str(ScrStr *cmd, ScrArr *args, double timeout_ms,
|
||
ScrStr *killsignal, ScrStr *stdio);
|
||
/* execFileSync/execSync behind one entry: posix_spawn(p) + piped utf8
|
||
* capture + waitpid with Node's exact error shapes — non-zero exit or
|
||
* signal death throws Error("Command failed: <cmd>[\n<stderr>]"), spawn
|
||
* failure throws "spawnSync <file> ENOENT", a timeout SIGTERMs the child
|
||
* and throws "spawnSync <file> ETIMEDOUT" (messages only — Node's
|
||
* status/stdout/stderr error properties don't exist, SEMANTICS.md).
|
||
* shell=true runs /bin/sh -c (args must be ["-c", cmd]); has_input false
|
||
* means the input option is ABSENT (stdin from /dev/null — Node's model
|
||
* for an undefined member), true feeds the bytes to a stdin pipe ("" is
|
||
* the pipe with immediate EOF); cwd "" inherits; has_env false inherits
|
||
* environ, true REPLACES it with the [k,v,...] pairs; timeout_ms <= 0
|
||
* disables; stdout_mode 1 captures / 0 ignores / 2 inherits the parent's
|
||
* fd, plus bit 4 = the CHILD'S STDIN inherits (stdio "inherit"; the
|
||
* flag rides stdout_mode because the signature predates it); stderr_mode
|
||
* 0 captures AND echoes to the parent stderr after completion (Node's
|
||
* no-stdio-option default), 1 captures only, 2 ignores, 3 inherits.
|
||
* Result is the captured utf8 stdout (+1) — "" when nothing captures
|
||
* (Node answers null there; discarded results cannot tell). All args
|
||
* borrowed. */
|
||
ScrStr *scr_exec_sync(ScrStr *cmd, ScrArr *args, bool shell, ScrStr *input,
|
||
bool has_input, ScrStr *cwd, bool has_env, ScrArr *env_pairs,
|
||
double timeout_ms, double stdout_mode, double stderr_mode);
|
||
/* The promisified-execFile capture (cp.execCapture): the same core in the
|
||
* async shape — both streams captured (no echo), Node's ASYNC error
|
||
* messages on the throw paths ("Command failed: <cmd>\n<stderr>" with the
|
||
* unconditional newline, "spawn <file> <ERR>" with .code, timeouts as
|
||
* ordinary SIGTERM command failures). +1 ScrSpawnRes on success (stdout +
|
||
* stderr; status unused). All args borrowed. */
|
||
ScrSpawnRes *scr_exec_capture(ScrStr *cmd, ScrArr *args, ScrStr *cwd,
|
||
bool has_env, ScrArr *env_pairs, double timeout_ms);
|
||
ScrSpawnRes *scr_spawn_res_retain(ScrSpawnRes *r);
|
||
void scr_spawn_res_release(ScrSpawnRes *r);
|
||
void *scr_spawn_res_retain_v(void *p);
|
||
void scr_spawn_res_release_v(void *p);
|
||
bool scr_spawn_res_has_status(ScrSpawnRes *r);
|
||
double scr_spawn_res_status(ScrSpawnRes *r);
|
||
ScrStr *scr_spawn_res_stdout(ScrSpawnRes *r); /* +1 */
|
||
ScrStr *scr_spawn_res_stderr(ScrSpawnRes *r); /* +1 */
|
||
/* result.signal — the termination signal's name when a signal killed the
|
||
* child (a timeout's killSignal included), the null arm otherwise; the
|
||
* emitter wraps type-directedly over the has/get pair like status. */
|
||
bool scr_spawn_res_has_signal(ScrSpawnRes *r);
|
||
ScrStr *scr_spawn_res_signal(ScrSpawnRes *r); /* +1; has_signal only */
|
||
/* Node's `error` property: +1 %Error ("spawnSync <file> ENOENT", `code`
|
||
* stamped) when the spawn itself failed, NULL otherwise (the undefined
|
||
* arm — the emitter wraps type-directedly, the envGet convention). */
|
||
ScrError *scr_spawn_res_error(ScrSpawnRes *r);
|
||
|
||
/* argv construction shared by spawnSync and spawn: a NULL-terminated
|
||
* vector borrowing the strings (alive while cmd/args are); free() the
|
||
* vector only. */
|
||
char **scr_child_argv(ScrStr *cmd, ScrArr *args);
|
||
|
||
/* The asynchronous child: spawn starts it immediately (stdio all on
|
||
* /dev/null — the compiler fences everything but { stdio: "ignore" })
|
||
* and registers it with the event loop, which sweeps waitpid(WNOHANG) at
|
||
* quiescence (scr_children_poll) and fires listeners at reap; between
|
||
* turns the loop's sleep waits on a kqueue PROC filter, so an exit wakes
|
||
* it immediately (scr_children_wait; ~1ms polling is the fallback). "exit"
|
||
* fires once with the code (or none — signal death → the null arm);
|
||
* "error" fires ONLY for spawn failure, and with no listener registered
|
||
* it prints the error and exits 1 (Node's unhandled-'error' behavior).
|
||
* Listener callbacks MOVE in and are released when the child settles; a
|
||
* listener registered after settling is released and never fires. The
|
||
* adapters bridge the runtime's fixed firing ABI to the callback's
|
||
* compiled shape: exit_thunk0/err_thunk0 ignore the payload (zero-param
|
||
* listeners), err_thunk_error constructs the %Error instance, and the
|
||
* `(code: number | null)` shape is compiler-emitted (union tags are
|
||
* program data). */
|
||
typedef struct ScrChild ScrChild;
|
||
/* A piped child-output stream (child.stdout / child.stderr — stdio mode
|
||
* 3): refcounted like the child; 'data' fires one Buffer chunk per
|
||
* read(2) (≤64KB, consumer-driven — no listener, no read: the pipe is
|
||
* the buffer), 'end' fires once at EOF and always BEFORE the child's
|
||
* own 'exit' (Node's pinned ordering; the settle path drains first). A
|
||
* flowing stream (data consumer, not yet EOF) keeps the loop alive. */
|
||
typedef struct ScrChildStream ScrChildStream;
|
||
/* A piped child-input writer (child.stdin — stdio mode 3 on fd 0).
|
||
* write copies borrowed string/bytes data into a nonblocking queue and
|
||
* returns false once buffered data reaches the stream high-water mark;
|
||
* drain fires when that queue empties. end closes after queued data and
|
||
* then fires finish; destroy closes immediately without finish. Errors
|
||
* are asynchronous and use the child error adapters. */
|
||
typedef struct ScrChildWriter ScrChildWriter;
|
||
/* The data adapter: the chunk arrives BORROWED (multiple listeners see
|
||
* the same chunk); adapters retain what they keep. */
|
||
typedef void (*ScrChildStreamDataFn)(ScrClosure *cb, ScrBytes *chunk);
|
||
typedef void (*ScrChildStreamDataStrFn)(ScrClosure *cb, ScrStr *chunk);
|
||
/* signal_name: the termination signal's name (static storage) when a
|
||
* signal killed the child, NULL otherwise — Node's second exit-listener
|
||
* parameter; zero/one-param adapters ignore it. */
|
||
typedef void (*ScrChildExitFn)(ScrClosure *cb, bool has_code, double code,
|
||
const char *signal_name);
|
||
typedef void (*ScrChildErrFn)(ScrClosure *cb, ScrStr *msg);
|
||
/* execFile's fixed runtime firing ABI. error/stdout/stderr arrive as +1
|
||
* values and MOVE through the compiler-emitted adapter into the callback
|
||
* (or are released there when the callback omits a suffix). */
|
||
typedef void (*ScrExecFileFn)(ScrClosure *cb, ScrError *error,
|
||
ScrStr *stdout_value, ScrStr *stderr_value);
|
||
/* JSON IPC callback ABIs. Message values are borrowed checked-dynamic
|
||
* trees; send errors are +1 and move through the adapter (NULL = success). */
|
||
typedef void (*ScrIpcMessageFn)(ScrClosure *cb, ScrDyn *message);
|
||
typedef void (*ScrIpcSendFn)(ScrClosure *cb, ScrError *error);
|
||
|
||
ScrChild *scr_spawn(ScrStr *cmd, ScrArr *args); /* +1, never throws */
|
||
ScrChild *scr_fork(double target, ScrArr *args, double in_mode,
|
||
double out_mode, double err_mode, bool has_env,
|
||
ScrArr *env_pairs, ScrStr *cwd); /* +1 */
|
||
/* The options form (cp.spawnOpts): PER-SLOT stdio modes — 0 = ignore
|
||
* (/dev/null), 1 = inherit, 2 = fd (out/err only: out_fd/err_fd dup2
|
||
* into the child's slot, the openSync daemon-log idiom), 3 = pipe
|
||
* (stdin's write end becomes child.stdin; out/err read ends become the
|
||
* child.stdout/stderr streams);
|
||
* detached = POSIX_SPAWN_SETSID (own session/process group); shell runs
|
||
* the complete command through /bin/sh -c or cmd.exe /d /s /c; env
|
||
* REPLACES the child environment when has_env ([k,v,...] pairs); cwd ""
|
||
* = inherit. */
|
||
ScrChild *scr_spawn_opts(ScrStr *cmd, ScrArr *args, double in_mode,
|
||
double out_mode, double err_mode, double out_fd,
|
||
double err_fd, bool detached, bool shell, bool has_env,
|
||
ScrArr *env_pairs, ScrStr *cwd);
|
||
/* The callback slice: file + args, default options. The returned child has
|
||
* all three stdio slots piped; stdout/stderr are captured internally and
|
||
* the callback moves into the child registry until settlement. */
|
||
ScrChild *scr_exec_file(ScrStr *cmd, ScrArr *args, ScrClosure *cb /*moves*/,
|
||
ScrExecFileFn fn);
|
||
ScrChild *scr_child_retain(ScrChild *c);
|
||
void scr_child_release(ScrChild *c);
|
||
void *scr_child_retain_v(void *p);
|
||
void scr_child_release_v(void *p);
|
||
void scr_child_on_exit(ScrChild *c, ScrClosure *cb /*moves*/, ScrChildExitFn fn);
|
||
void scr_child_on_close(ScrChild *c, ScrClosure *cb /*moves*/, ScrChildExitFn fn);
|
||
void scr_child_on_error(ScrChild *c, ScrClosure *cb /*moves*/, ScrChildErrFn fn);
|
||
bool scr_child_ipc_connected(ScrChild *c);
|
||
bool scr_child_ipc_send(ScrChild *c, ScrStr *json);
|
||
bool scr_child_ipc_send_cb(ScrChild *c, ScrStr *json,
|
||
ScrClosure *cb /*moves*/, ScrIpcSendFn fn);
|
||
void scr_child_ipc_disconnect(ScrChild *c);
|
||
void scr_child_ipc_on_message(ScrChild *c, ScrClosure *cb /*moves*/,
|
||
ScrIpcMessageFn fn, bool once);
|
||
void scr_child_ipc_on_disconnect(ScrChild *c, ScrClosure *cb /*moves*/,
|
||
bool once);
|
||
double scr_process_fork_target(double target_count);
|
||
bool scr_process_ipc_connected(void);
|
||
bool scr_process_ipc_send(ScrStr *json);
|
||
bool scr_process_ipc_send_cb(ScrStr *json, ScrClosure *cb /*moves*/,
|
||
ScrIpcSendFn fn);
|
||
void scr_process_ipc_disconnect(void);
|
||
void scr_process_ipc_on_message(ScrClosure *cb /*moves*/,
|
||
ScrIpcMessageFn fn, bool once);
|
||
void scr_process_ipc_on_disconnect(ScrClosure *cb /*moves*/, bool once);
|
||
void scr_child_exit_thunk0(ScrClosure *cb, bool has_code, double code,
|
||
const char *signal_name);
|
||
void scr_child_err_thunk0(ScrClosure *cb, ScrStr *msg);
|
||
void scr_child_err_thunk_error(ScrClosure *cb, ScrStr *msg);
|
||
/* The stream surface: child.stdout/stderr answer +1 handles (NULL when
|
||
* the slot was not piped — Node's null); listeners MOVE in and release
|
||
* at EOF/exit-cleanup (post-'end' registrations release immediately and
|
||
* never fire, the stdin rule). The runtime-provided data adapters cover
|
||
* zero-param, Buffer, and string shapes; setEncoding uses the shared
|
||
* StringDecoder core so split multibyte sequences remain intact.
|
||
* Union-param listeners get compiler-emitted Buffer adapters. */
|
||
ScrChildStream *scr_child_stream_retain(ScrChildStream *s);
|
||
void scr_child_stream_release(ScrChildStream *s);
|
||
void *scr_child_stream_retain_v(void *p);
|
||
void scr_child_stream_release_v(void *p);
|
||
ScrChildStream *scr_child_stdout(ScrChild *c); /* +1, or NULL */
|
||
ScrChildStream *scr_child_stderr(ScrChild *c); /* +1, or NULL */
|
||
void scr_child_stream_on_data(ScrChildStream *s, ScrClosure *cb /*moves*/,
|
||
ScrChildStreamDataFn fn, bool once);
|
||
void scr_child_stream_on_data_str(ScrChildStream *s, ScrClosure *cb /*moves*/,
|
||
ScrChildStreamDataStrFn fn, bool once);
|
||
void scr_child_stream_on_end(ScrChildStream *s, ScrClosure *cb /*moves*/, bool once);
|
||
ScrChildStream *scr_child_stream_set_encoding(ScrChildStream *s, ScrStr *enc); /* +1 */
|
||
void scr_child_stream_thunk0(ScrClosure *cb, ScrBytes *chunk);
|
||
void scr_child_stream_thunk_bytes(ScrClosure *cb, ScrBytes *chunk);
|
||
void scr_child_stream_thunk_str(ScrClosure *cb, ScrStr *chunk);
|
||
ScrChildWriter *scr_child_writer_retain(ScrChildWriter *w);
|
||
void scr_child_writer_release(ScrChildWriter *w);
|
||
void *scr_child_writer_retain_v(void *p);
|
||
void scr_child_writer_release_v(void *p);
|
||
ScrChildWriter *scr_child_stdin(ScrChild *c); /* +1, or NULL */
|
||
bool scr_child_writer_write_string(ScrChildWriter *w, const ScrStr *data);
|
||
bool scr_child_writer_write_bytes(ScrChildWriter *w, const ScrBytes *data);
|
||
void scr_child_writer_end(ScrChildWriter *w);
|
||
void scr_child_writer_destroy(ScrChildWriter *w);
|
||
bool scr_child_writer_writable(ScrChildWriter *w);
|
||
void scr_child_writer_on_drain(ScrChildWriter *w, ScrClosure *cb /*moves*/, bool once);
|
||
void scr_child_writer_on_finish(ScrChildWriter *w, ScrClosure *cb /*moves*/, bool once);
|
||
void scr_child_writer_on_error(ScrChildWriter *w, ScrClosure *cb /*moves*/,
|
||
ScrChildErrFn fn, bool once);
|
||
/* The lifecycle members, Node's exact shapes (pinned by the corpus):
|
||
* pid is undefined (has_pid false) exactly on spawn failure; exitCode is
|
||
* null while running / after a signal death, the code after a normal
|
||
* exit, -errno once a spawn failure settled; killed flips on any
|
||
* successful kill() send (signal 0 included); kill answers false after
|
||
* the reap or on spawn failure and throws the Unknown-signal TypeError
|
||
* on bad names (may-throw); unref() drops the child from the loop's
|
||
* keep-alive set (reffed_pending is the loop's liveness half; teardown
|
||
* releases whatever the loop never reaped so the RC audit stays clean). */
|
||
bool scr_child_has_pid(ScrChild *c);
|
||
double scr_child_pid(ScrChild *c);
|
||
bool scr_child_has_exit_code(ScrChild *c);
|
||
double scr_child_exit_code(ScrChild *c);
|
||
bool scr_child_killed(ScrChild *c);
|
||
bool scr_child_kill(ScrChild *c, const ScrStr *signal);
|
||
bool scr_child_kill_num(ScrChild *c, double signum);
|
||
void scr_child_unref(ScrChild *c);
|
||
/* Installs ChildProcess's checked-dynamic handle identity bridge. Emitted
|
||
* programs call this only when the child_process surface is present. */
|
||
void scr_child_dyn_install(void);
|
||
bool scr_children_reffed_pending(void);
|
||
void scr_children_teardown(void);
|
||
/* Node's signal-name table (scr_lib.c), shared with child.kill: the
|
||
* resolved signal number, or -1 for names outside the table. */
|
||
int scr_signal_from_name(const ScrStr *signal);
|
||
/* The reverse walk (spawnSync's result.signal): Node's spelling for the
|
||
* host number, NULL outside the table. Static storage. */
|
||
const char *scr_signal_name(int sig);
|
||
/* The loop's half: pending() joins the exhaustion test (a live child
|
||
* keeps the process alive, like Node); poll() reaps and fires. wait()
|
||
* takes the loop's quiescent sleep while children are pending — it blocks
|
||
* on the child watch (kqueue on BSD, pidfd epoll on Linux) up to
|
||
* max_wait_ms (the next timer deadline), so a
|
||
* child's NOTE_EXIT wakes the loop immediately; it returns false (and the
|
||
* loop falls back to the ~1ms polling cap) when it can't wake for every
|
||
* pending child: non-kqueue platforms, spawn failures awaiting their
|
||
* first-pass settle, or a child whose exit filter could not be armed. */
|
||
bool scr_children_pending(void);
|
||
/* Work already queued in userspace: dispatch can progress without another
|
||
* pipe/exit notification. Connected channels and blocked writes are not ready. */
|
||
bool scr_children_ready(void);
|
||
bool scr_children_failed_pending(void);
|
||
void scr_children_poll(void);
|
||
bool scr_children_wait(double max_wait_ms);
|
||
|
||
/* ── node:crypto (scr_lib.c) ─────────────────────────────────────────
|
||
* Randomness plus the incremental MD5/SHA-1/SHA-256 Hash/Hmac and PBKDF2
|
||
* utility slice. The composed string-producing paths remain fast paths;
|
||
* first-class handles and Buffer results use the same digest core. */
|
||
ScrStr *scr_crypto_random_uuid(void);
|
||
ScrStr *scr_crypto_random_string(double n, ScrStr *enc); /* +1, or throws */
|
||
/* The fused createHash chain and crypto.hash. Strings hash their UTF-8
|
||
* bytes; byte values hash their raw storage. Unknown runtime algorithms
|
||
* preserve each Node API's distinct error message, class, and code. */
|
||
ScrStr *scr_crypto_hash_digest_str(ScrStr *alg, ScrStr *data, ScrStr *enc);
|
||
ScrStr *scr_crypto_hash_digest_bytes(ScrStr *alg, ScrBytes *data, ScrStr *enc);
|
||
typedef struct ScrCryptoHash ScrCryptoHash;
|
||
ScrCryptoHash *scr_crypto_hash_new(ScrStr *alg);
|
||
ScrCryptoHash *scr_crypto_hmac_new_str(ScrStr *alg, ScrStr *key);
|
||
ScrCryptoHash *scr_crypto_hmac_new_bytes(ScrStr *alg, ScrBytes *key);
|
||
ScrCryptoHash *scr_crypto_hash_retain(ScrCryptoHash *h);
|
||
void scr_crypto_hash_release(ScrCryptoHash *h);
|
||
void *scr_crypto_hash_retain_v(void *p);
|
||
void scr_crypto_hash_release_v(void *p);
|
||
ScrCryptoHash *scr_crypto_hash_update_str(ScrCryptoHash *h, ScrStr *data);
|
||
ScrCryptoHash *scr_crypto_hash_update_bytes(ScrCryptoHash *h, ScrBytes *data);
|
||
ScrCryptoHash *scr_crypto_hash_copy(ScrCryptoHash *h);
|
||
ScrStr *scr_crypto_hash_digest_string(ScrCryptoHash *h, ScrStr *enc);
|
||
ScrBytes *scr_crypto_hash_digest_buffer(ScrCryptoHash *h);
|
||
bool scr_crypto_timing_safe_equal(ScrBytes *left, ScrBytes *right);
|
||
ScrBytes *scr_crypto_random_fill(ScrBytes *bytes, double offset, double size);
|
||
ScrBytes *scr_crypto_random_fill_rest(ScrBytes *bytes, double offset);
|
||
double scr_crypto_random_int(double min, double max);
|
||
ScrBytes *scr_crypto_pbkdf2(ScrBytes *password, ScrBytes *salt,
|
||
double iterations, double keylen, ScrStr *digest);
|
||
typedef void (*ScrCryptoBytesFn)(ScrClosure *cb, ScrBytes *value /* moves */);
|
||
void scr_crypto_defer_bytes(ScrBytes *value /* moves */, ScrClosure *cb /* moves */,
|
||
ScrCryptoBytesFn fn);
|
||
void scr_crypto_random_bytes_async(double size, ScrClosure *cb /* moves */,
|
||
ScrCryptoBytesFn fn);
|
||
void scr_crypto_pbkdf2_async(ScrBytes *password, ScrBytes *salt,
|
||
double iterations, double keylen, ScrStr *digest,
|
||
ScrClosure *cb /* moves */, ScrCryptoBytesFn fn);
|
||
/* One-shot raw digest/HMAC by algorithm name ("md5" | "sha1" | "sha256")
|
||
* — the island crypto shim's bridge (scr_island.c host hooks). Digest
|
||
* bytes into out (≥32); returns the digest length, 0 for an unknown
|
||
* algorithm. */
|
||
size_t scr_crypto_digest_raw(const char *alg, const unsigned char *data, size_t len,
|
||
unsigned char out[32]);
|
||
size_t scr_crypto_hmac_raw(const char *alg, const unsigned char *key, size_t keylen,
|
||
const unsigned char *data, size_t len, unsigned char out[32]);
|
||
/* The composed `new crypto.X509Certificate(data).fingerprint` read (the
|
||
* handle never materializes): the SHA-1 of the DER, uppercase
|
||
* colon-separated — PEM or raw-DER input; anything else throws Node's
|
||
* ERR_OSSL_PEM_NO_START_LINE Error. Borrowed; +1 (may throw). */
|
||
ScrStr *scr_crypto_x509_fingerprint(ScrBytes *data);
|
||
ScrStr *scr_crypto_x509_fingerprint_str(ScrStr *pem);
|
||
/* The Validity window in Node's ASN1_TIME_print shape ("Jul 1 00:00:00
|
||
* 2026 GMT"); same PEM contract as the fingerprint pair. */
|
||
ScrStr *scr_crypto_x509_valid_from(ScrBytes *data);
|
||
ScrStr *scr_crypto_x509_valid_from_str(ScrStr *pem);
|
||
ScrStr *scr_crypto_x509_valid_to(ScrBytes *data);
|
||
ScrStr *scr_crypto_x509_valid_to_str(ScrStr *pem);
|
||
|
||
/* ── node:path (scr_path.c) ──────────────────────────────────────────
|
||
* BOTH of Node's implementations, ported exactly: the scr_path_* family
|
||
* is posix, the scr_path_win32_* family is Node v24's path.win32 (the
|
||
* compiler binds the bare module by TARGET; the path.posix/path.win32
|
||
* namespaces bind their family anywhere). All arguments are BORROWED;
|
||
* every string result is fresh (+1). Nothing throws. join/resolve take
|
||
* the call's variadic arguments packed into ONE string[] (the compiler
|
||
* builds the array literal); the resolves consult getcwd like Node
|
||
* (failure aborts, like process.cwd), the win32 one after the per-drive
|
||
* "=X:" env vars. basename always receives a suffix — "" (a Node no-op)
|
||
* when the call omitted it. */
|
||
ScrStr *scr_path_join(ScrArr *parts);
|
||
ScrStr *scr_path_resolve(ScrArr *parts);
|
||
ScrStr *scr_path_normalize(ScrStr *path);
|
||
ScrStr *scr_path_dirname(ScrStr *path);
|
||
ScrStr *scr_path_basename(ScrStr *path, ScrStr *suffix);
|
||
ScrStr *scr_path_extname(ScrStr *path);
|
||
bool scr_path_is_absolute(ScrStr *path);
|
||
ScrStr *scr_path_relative(ScrStr *from, ScrStr *to);
|
||
/* posix.toNamespacedPath — the identity (Node: "Non-op on posix
|
||
* systems"). +1 via retain. */
|
||
ScrStr *scr_path_to_namespaced_path(ScrStr *path);
|
||
/* path.win32.* — Node v24's win32 implementation ported byte-for-byte
|
||
* (backslash output, both slashes on input, UNC/device/drive-letter
|
||
* roots, the reserved device names, the CVE-2024-36139 normalize
|
||
* hardening); join/resolve keep the packed-string[] ABI. On win32
|
||
* TARGETS these back the bare `path` module (Node on Windows IS
|
||
* path.win32); everywhere they are the path.win32 namespace. resolve
|
||
* consults the per-drive "=X:" env vars then the cwd, like Node's.
|
||
* Same borrow/+1 contract as the posix set; nothing throws. */
|
||
ScrStr *scr_path_win32_join(ScrArr *parts);
|
||
ScrStr *scr_path_win32_resolve(ScrArr *parts);
|
||
ScrStr *scr_path_win32_normalize(ScrStr *path);
|
||
ScrStr *scr_path_win32_dirname(ScrStr *path);
|
||
ScrStr *scr_path_win32_basename(ScrStr *path, ScrStr *suffix);
|
||
ScrStr *scr_path_win32_extname(ScrStr *path);
|
||
bool scr_path_win32_is_absolute(ScrStr *path);
|
||
ScrStr *scr_path_win32_relative(ScrStr *from, ScrStr *to);
|
||
ScrStr *scr_path_win32_to_namespaced_path(ScrStr *path);
|
||
|
||
/* fs.openSync(path, flags) → the raw fd as f64 (Node's string flag
|
||
* grammar; unknown flags throw Node's ERR_INVALID_ARG_VALUE TypeError
|
||
* text, open(2) failures the Node fs error shape), and fs.closeSync(fd)
|
||
* (failure throws the path-less "EBADF: bad file descriptor, close").
|
||
* The pair behind spawn's fd-stdio form. */
|
||
double scr_fs_open(ScrStr *path, ScrStr *flags);
|
||
double scr_fs_open_numeric(ScrStr *path, double flags, double mode);
|
||
ScrFileHandle *scr_file_handle_open(ScrStr *path, ScrStr *flags, double mode);
|
||
ScrFileHandle *scr_file_handle_retain(ScrFileHandle *h);
|
||
void scr_file_handle_release(ScrFileHandle *h);
|
||
void *scr_file_handle_retain_v(void *p);
|
||
void scr_file_handle_release_v(void *p);
|
||
double scr_file_handle_fd(ScrFileHandle *h);
|
||
void scr_file_handle_close(ScrFileHandle *h);
|
||
double scr_file_handle_read(ScrFileHandle *h, ScrBytes *buf, double offset,
|
||
double length, double position, bool length_default);
|
||
double scr_file_handle_write_bytes(ScrFileHandle *h, ScrBytes *buf,
|
||
double offset, double length,
|
||
double position, bool length_default);
|
||
double scr_file_handle_write_str(ScrFileHandle *h, ScrStr *data,
|
||
double position, ScrStr *encoding);
|
||
ScrStr *scr_file_handle_read_file(ScrFileHandle *h);
|
||
ScrBytes *scr_file_handle_read_file_bytes(ScrFileHandle *h);
|
||
void scr_file_handle_write_file(ScrFileHandle *h, ScrStr *data);
|
||
void scr_file_handle_write_file_bytes(ScrFileHandle *h, ScrBytes *data);
|
||
ScrStats *scr_file_handle_stat(ScrFileHandle *h);
|
||
/* position == -1 reads from and advances the descriptor's current offset;
|
||
* nonnegative positions leave that offset unchanged (pread/ReadFile seam). */
|
||
double scr_fs_read_sync(double fd, ScrBytes *buf, double offset, double length,
|
||
double position);
|
||
/* writeSync's classic Buffer window and utf8 string forms. position -1 (or
|
||
* any other non-safe/nonnegative-integer value) writes at and advances the
|
||
* current descriptor offset; safe nonnegative positions leave it unchanged. */
|
||
double scr_fs_write_sync(double fd, ScrBytes *buf, double offset, double length,
|
||
double position);
|
||
double scr_fs_write_str_sync(double fd, ScrStr *data, double position,
|
||
ScrStr *encoding);
|
||
void scr_fs_close(double fd);
|
||
|
||
/* ── WHATWG URL (scr_url.c) ──────────────────────────────────────────
|
||
* An immutable, refcounted URL value, parsed once at construction. The
|
||
* parser covers the WHATWG algorithm's common ground exactly (see
|
||
* scr_url.c's header comment for the covered surface and the documented
|
||
* divergences: no IDNA, no IPv6, opaque paths verbatim).
|
||
*
|
||
* scr_url_new THROWS a catchable TypeError ("Invalid URL") through the
|
||
* exception cell on unparsable input and returns NULL; the getters never
|
||
* throw (borrowed receiver, +1 string). scr_url_to_path / _str_to_path
|
||
* throw Node's fileURLToPath TypeErrors (non-file scheme, encoded '/',
|
||
* non-empty host); scr_url_from_path resolves against the cwd and never
|
||
* throws. Compiler-emitted pending checks follow every throwing call. */
|
||
typedef struct ScrUrl ScrUrl;
|
||
|
||
ScrUrl *scr_url_new(ScrStr *input); /* +1, or throws */
|
||
ScrUrl *scr_url_new_base(ScrStr *input, ScrStr *base); /* +1, or throws */
|
||
ScrUrl *scr_url_retain(ScrUrl *u);
|
||
void scr_url_release(ScrUrl *u);
|
||
void *scr_url_retain_v(void *p);
|
||
void scr_url_release_v(void *p);
|
||
ScrStr *scr_url_protocol(ScrUrl *u); /* +1 "https:" */
|
||
ScrStr *scr_url_origin(ScrUrl *u); /* +1 "https://host" or "null" */
|
||
ScrStr *scr_url_username(ScrUrl *u); /* +1 encoded username, possibly empty */
|
||
ScrStr *scr_url_password(ScrUrl *u); /* +1 encoded password, possibly empty */
|
||
ScrStr *scr_url_host(ScrUrl *u); /* +1 "host[:port]" (defaults stripped) */
|
||
ScrStr *scr_url_hostname(ScrUrl *u); /* +1 port-less host ("" when none) */
|
||
ScrStr *scr_url_port(ScrUrl *u); /* +1 "" or normalized non-default port */
|
||
ScrStr *scr_url_pathname(ScrUrl *u); /* +1 */
|
||
ScrStr *scr_url_href(ScrUrl *u); /* +1; also toString() */
|
||
ScrStr *scr_url_to_path(ScrUrl *u); /* +1, or throws */
|
||
ScrStr *scr_url_str_to_path(ScrStr *s); /* +1, or throws */
|
||
ScrUrl *scr_url_from_path(ScrStr *path); /* +1; throws on win32 UNC malformations only */
|
||
ScrUrl *scr_url_from_path_platform(ScrStr *path, bool windows); /* +1; explicit options.windows */
|
||
/* The bridge pair's win32 arms as direct entry points (the public pair
|
||
* selects by TARGET at compile time — Node's isWindows): the host-side
|
||
* differential tests exercise the Windows behavior from any platform,
|
||
* like Node's { windows: true } options on fileURLToPath/pathToFileURL. */
|
||
ScrStr *scr_url_to_path_w32(ScrUrl *u); /* +1, or throws */
|
||
ScrUrl *scr_url_from_path_w32(ScrStr *path); /* +1, or throws */
|
||
|
||
/* ── URLSearchParams (scr_url.c) ─────────────────────────────────────
|
||
* A refcounted, MUTABLE ordered list of decoded (name, value) pairs —
|
||
* the WHATWG application/x-www-form-urlencoded surface. Standalone
|
||
* (scr_sp_new/parse/copy/from_pairs) or the LIVE view of a URL's query
|
||
* (scr_url_search_params — cached on the URL so every read answers the
|
||
* same identity, and every list mutation re-serializes into the URL's
|
||
* query: href reflects immediately; an emptied list drops the '?').
|
||
* Parsing/serialization match Node byte-for-byte: '+' is space, %XX
|
||
* decodes (malformed escapes verbatim, decoded bytes UTF-8-scrubbed to
|
||
* U+FFFD), serialization keeps [A-Za-z0-9*\-._], space → '+', uppercase
|
||
* %XX otherwise. sort() is stable, by UTF-16 code units. Only
|
||
* scr_sp_from_pairs throws (ERR_INVALID_TUPLE on a non-[name, value]
|
||
* row); everything else never does. */
|
||
typedef struct ScrSearchParams ScrSearchParams;
|
||
|
||
ScrSearchParams *scr_sp_new(void); /* +1 empty list */
|
||
ScrSearchParams *scr_sp_parse(ScrStr *init); /* +1; strips ONE leading '?' */
|
||
ScrSearchParams *scr_sp_copy(ScrSearchParams *src); /* +1 snapshot */
|
||
ScrSearchParams *scr_sp_from_pairs(ScrArr *pairs); /* +1, or throws (string[][] rows) */
|
||
/* The record-literal init desugar: append (name, value), answer sp +1. */
|
||
ScrSearchParams *scr_sp_with(ScrSearchParams *sp, ScrStr *name, ScrStr *value);
|
||
ScrSearchParams *scr_sp_retain(ScrSearchParams *sp);
|
||
void scr_sp_release(ScrSearchParams *sp);
|
||
void *scr_sp_retain_v(void *p);
|
||
void scr_sp_release_v(void *p);
|
||
ScrSearchParams *scr_url_search_params(ScrUrl *u); /* +1 live cached view */
|
||
ScrStr *scr_url_search(ScrUrl *u); /* +1 "?..." or "" */
|
||
ScrStr *scr_url_hash(ScrUrl *u); /* +1 "#..." or "" */
|
||
void scr_sp_append(ScrSearchParams *sp, ScrStr *name, ScrStr *value);
|
||
void scr_sp_set(ScrSearchParams *sp, ScrStr *name, ScrStr *value);
|
||
void scr_sp_delete(ScrSearchParams *sp, ScrStr *name);
|
||
void scr_sp_delete_value(ScrSearchParams *sp, ScrStr *name, ScrStr *value);
|
||
ScrStr *scr_sp_get(ScrSearchParams *sp, ScrStr *name); /* +1 first value, or NULL */
|
||
ScrArr *scr_sp_get_all(ScrSearchParams *sp, ScrStr *name); /* +1 string[] */
|
||
bool scr_sp_has(ScrSearchParams *sp, ScrStr *name);
|
||
bool scr_sp_has_value(ScrSearchParams *sp, ScrStr *name, ScrStr *value);
|
||
void scr_sp_sort(ScrSearchParams *sp);
|
||
double scr_sp_size(ScrSearchParams *sp);
|
||
ScrStr *scr_sp_to_string(ScrSearchParams *sp); /* +1 urlencoded serialization */
|
||
/* Index-walk iteration (the compiler's for-of/forEach desugar re-reads
|
||
* scr_sp_size each pass — live, like the spec's index-based iterator). */
|
||
ScrStr *scr_sp_key_at(ScrSearchParams *sp, double i); /* +1; "" out of range */
|
||
ScrStr *scr_sp_val_at(ScrSearchParams *sp, double i); /* +1; "" out of range */
|
||
|
||
/* ── ES Symbol values (scr_symbol.c — linked only when the IR uses the
|
||
* symbol surface) ─────────────────────────────────────────────────────
|
||
* A symbol is a runtime-unique IDENTITY: the pointer IS the identity
|
||
* (`===` is a pointer compare; every scr_sym_new allocation is a distinct
|
||
* symbol even for equal descriptions — JS exactly). Heap, refcounted,
|
||
* IMMUTABLE; holds at most two strings (description + registry key), so
|
||
* never part of a cycle, no trace. Symbol.for's global registry interns
|
||
* per KEY (byte equality) and is spec-permanent — entries are never
|
||
* evicted; an atexit cleanup (registered at first use, so LIFO runs it
|
||
* BEFORE scr_init's RC audit) releases the chain so audited string counts
|
||
* stay exact. None of these throw. */
|
||
typedef struct ScrSym ScrSym;
|
||
|
||
ScrSym *scr_sym_new(ScrStr *desc); /* borrows desc (NULL = no description); +1 fresh identity */
|
||
ScrSym *scr_sym_for(ScrStr *key); /* borrows key; +1 on the interned per-key symbol */
|
||
ScrSym *scr_sym_retain(ScrSym *s);
|
||
void scr_sym_release(ScrSym *s);
|
||
void *scr_sym_retain_v(void *p);
|
||
void scr_sym_release_v(void *p);
|
||
ScrStr *scr_sym_desc(ScrSym *s); /* +1 description, or NULL when absent */
|
||
ScrStr *scr_sym_key_for(ScrSym *s); /* +1 registry key, or NULL (not registered) */
|
||
ScrStr *scr_sym_to_string(ScrSym *s); /* +1 "Symbol(desc)" ("Symbol()" when absent) */
|
||
|
||
/* ── ECMAScript bigint values (scr_bigint.c, linked on demand) ─────────
|
||
* Immutable arbitrary-precision signed integers. The implementation uses
|
||
* little-endian base-2^32 limbs, but callers see only value semantics:
|
||
* equality and ordering compare numbers, never pointers. */
|
||
typedef struct ScrBigInt ScrBigInt;
|
||
|
||
ScrBigInt *scr_bigint_retain(ScrBigInt *v);
|
||
void scr_bigint_release(ScrBigInt *v);
|
||
void *scr_bigint_retain_v(void *v);
|
||
void scr_bigint_release_v(void *v);
|
||
ScrBigInt *scr_bigint_parse(ScrStr *text); /* borrowed text; +1 or throws */
|
||
ScrBigInt *scr_bigint_from_f64(double value); /* +1 or throws */
|
||
ScrBigInt *scr_bigint_from_u64(uint64_t value);
|
||
ScrBigInt *scr_bigint_from_i64(int64_t value);
|
||
ScrBigInt *scr_bigint_from_pointer(void *value);
|
||
uint64_t scr_bigint_to_u64(const ScrBigInt *value); /* low 64 bits, modulo 2^64 */
|
||
int64_t scr_bigint_to_i64(const ScrBigInt *value);
|
||
bool scr_bigint_pointer_fits(const ScrBigInt *value);
|
||
bool scr_bigint_u64_fits(const ScrBigInt *value);
|
||
bool scr_bigint_i64_fits(const ScrBigInt *value);
|
||
void *scr_bigint_to_pointer(const ScrBigInt *value); /* low pointer-width bits; raw address, borrowed */
|
||
ScrBigInt *scr_bigint_neg(ScrBigInt *value);
|
||
ScrBigInt *scr_bigint_not(ScrBigInt *value);
|
||
ScrBigInt *scr_bigint_add(ScrBigInt *a, ScrBigInt *b);
|
||
ScrBigInt *scr_bigint_sub(ScrBigInt *a, ScrBigInt *b);
|
||
ScrBigInt *scr_bigint_mul(ScrBigInt *a, ScrBigInt *b);
|
||
ScrBigInt *scr_bigint_div(ScrBigInt *a, ScrBigInt *b); /* throws on zero */
|
||
ScrBigInt *scr_bigint_mod(ScrBigInt *a, ScrBigInt *b); /* throws on zero */
|
||
ScrBigInt *scr_bigint_pow(ScrBigInt *a, ScrBigInt *b); /* throws on negative exponent */
|
||
ScrBigInt *scr_bigint_and(ScrBigInt *a, ScrBigInt *b);
|
||
ScrBigInt *scr_bigint_or(ScrBigInt *a, ScrBigInt *b);
|
||
ScrBigInt *scr_bigint_xor(ScrBigInt *a, ScrBigInt *b);
|
||
ScrBigInt *scr_bigint_shl(ScrBigInt *a, ScrBigInt *count);
|
||
ScrBigInt *scr_bigint_shr(ScrBigInt *a, ScrBigInt *count);
|
||
bool scr_bigint_eq(ScrBigInt *a, ScrBigInt *b);
|
||
bool scr_bigint_eq_string(ScrBigInt *a, ScrStr *b); /* StringToBigInt failure is false */
|
||
double scr_bigint_cmp_f64(ScrBigInt *a, ScrBigInt *b); /* -1, 0, 1 */
|
||
double scr_bigint_cmp_number(ScrBigInt *a, double b); /* -1, 0, 1; 2 = NaN */
|
||
bool scr_bigint_truthy(ScrBigInt *value);
|
||
ScrStr *scr_bigint_to_string(ScrBigInt *value, double radix); /* +1 or throws */
|
||
ScrStr *scr_bigint_inspect(ScrBigInt *value); /* decimal plus trailing n; +1 */
|
||
double scr_bigint_to_f64(ScrBigInt *value);
|
||
ScrBigInt *scr_bigint_as_uint_n(double bits, ScrBigInt *value);
|
||
ScrBigInt *scr_bigint_as_int_n(double bits, ScrBigInt *value);
|
||
ScrBigInt *scr_bigint_buffer_read(ScrBytes *bytes, double offset, bool sign, bool le);
|
||
double scr_bigint_buffer_write(ScrBytes *bytes, ScrBigInt *value, double offset, bool sign, bool le);
|
||
ScrBigInt *scr_bigint_dataview_get(ScrBytes *bytes, double offset, bool sign, bool le);
|
||
void scr_bigint_dataview_set(ScrBytes *bytes, double offset, ScrBigInt *value, bool le);
|
||
|
||
/* ── node:os (scr_lib.c) ─────────────────────────────────────────────
|
||
* os.platform() lowers to scr_process_platform. homedir is $HOME else
|
||
* getpwuid(3) (failure aborts); tmpdir is Node's $TMPDIR/$TMP/$TEMP
|
||
* cascade else /tmp, one trailing slash trimmed. Both +1 fresh. */
|
||
ScrStr *scr_os_homedir(void);
|
||
ScrStr *scr_os_tmpdir(void);
|
||
ScrStr *scr_os_release(void); /* uname(2) release — +1 fresh */
|
||
ScrStr *scr_os_type(void); /* uname(2) sysname — +1 fresh */
|
||
double scr_os_totalmem(void); /* total physical memory, bytes */
|
||
/* os.userInfo()'s field trio (pw_name / pw_shell / pw_dir — the passwd
|
||
* home, not the $HOME cascade). +1 fresh; abort on lookup failure. */
|
||
ScrStr *scr_os_user_name(void);
|
||
ScrStr *scr_os_user_shell(void);
|
||
ScrStr *scr_os_user_homedir(void);
|
||
|
||
/* fs.readdirSync(path, { withFileTypes: true }): one readdir pass
|
||
* snapshotting name + entry kind (scr_lib.c) — the emitter walks it to
|
||
* assemble the typed Dirent record rows inline. Entry kinds use libuv's
|
||
* UV_DIRENT encoding (1 file, 2 dir, 3 link, 4 fifo, 5 socket, 6 char,
|
||
* 7 block, 0 unknown); a DT_UNKNOWN d_type falls back to lstat(2) —
|
||
* Node's own getDirents rule. OS order, no "."/"..". scr_fs_scandir
|
||
* throws Node's scandir errno error and answers NULL then; count/free are
|
||
* NULL-tolerant so the promise emitter can turn that pending error into a
|
||
* rejection, while the name accessor returns +1. */
|
||
typedef struct ScrScandir ScrScandir;
|
||
ScrScandir *scr_fs_scandir(ScrStr *path); /* or throws (NULL) */
|
||
size_t scr_fs_scandir_count(const ScrScandir *s); /* NULL -> 0 */
|
||
ScrStr *scr_fs_scandir_name(const ScrScandir *s, size_t i); /* +1 */
|
||
double scr_fs_scandir_type(const ScrScandir *s, size_t i);
|
||
void scr_fs_scandir_free(ScrScandir *s); /* NULL-tolerant */
|
||
|
||
/* os.networkInterfaces(): a getifaddrs(3) snapshot (scr_lib.c) the emitter
|
||
* walks to build the typed Dict<NetworkInterfaceInfo[]> record inline. Row
|
||
* selection and fields follow libuv/Node (IFF_UP && IFF_RUNNING,
|
||
* AF_INET/AF_INET6; internal = IFF_LOOPBACK; MAC from the link-level
|
||
* sibling entry, zeros when absent; cidr = address/<netmask prefix>, NULL
|
||
* for a split netmask — Node's null). String accessors return +1; a
|
||
* getifaddrs failure yields an empty snapshot. */
|
||
typedef struct ScrIfaddrs ScrIfaddrs;
|
||
ScrIfaddrs *scr_os_ifaddrs(void);
|
||
size_t scr_os_ifaddrs_count(const ScrIfaddrs *s);
|
||
ScrStr *scr_os_ifaddrs_name(const ScrIfaddrs *s, size_t i);
|
||
ScrStr *scr_os_ifaddrs_address(const ScrIfaddrs *s, size_t i);
|
||
ScrStr *scr_os_ifaddrs_netmask(const ScrIfaddrs *s, size_t i);
|
||
ScrStr *scr_os_ifaddrs_family(const ScrIfaddrs *s, size_t i); /* "IPv4"/"IPv6" */
|
||
ScrStr *scr_os_ifaddrs_mac(const ScrIfaddrs *s, size_t i);
|
||
bool scr_os_ifaddrs_internal(const ScrIfaddrs *s, size_t i);
|
||
bool scr_os_ifaddrs_ipv6(const ScrIfaddrs *s, size_t i);
|
||
ScrStr *scr_os_ifaddrs_cidr(const ScrIfaddrs *s, size_t i); /* +1 or NULL */
|
||
double scr_os_ifaddrs_scopeid(const ScrIfaddrs *s, size_t i);
|
||
void scr_os_ifaddrs_free(ScrIfaddrs *s);
|
||
|
||
/* ── JSON + dynamic values (scr_json.c) ─────────────────────────────
|
||
* A dyn value — the compiled form of `unknown` — is a refcounted JSON dyn
|
||
* tree: the result of JSON.parse, inert until a CHECKED CAST validates it
|
||
* against a static type. The compiler emits the validation walkers
|
||
* (sc_dc_* builders / sc_dm_* match predicates) and the type-directed
|
||
* stringify serializers (sc_jw_*) per program; this runtime slice provides
|
||
* the checked-dynamic tree itself, the RFC 8259 parser, the shared failure path
|
||
* (scr_dyn_check_fail), and the output buffer the serializers append to.
|
||
*
|
||
* Ownership: the checked-dynamic tree owns its children (strings, items, entry keys+values);
|
||
* releasing the root releases the tree recursively. scr_json_parse borrows
|
||
* the text and returns +1 — or THROWS a catchable string shaped like Node's
|
||
* V8 messages ("Unexpected end of JSON input"; approximate fidelity, see
|
||
* SEMANTICS.md) and returns NULL. scr_dyn_obj_get returns a BORROWED
|
||
* member (the compiler's builders retain what they extract themselves).
|
||
*/
|
||
|
||
typedef enum {
|
||
SCR_DYN_NULL,
|
||
SCR_DYN_BOOL,
|
||
SCR_DYN_NUM,
|
||
SCR_DYN_STR,
|
||
SCR_DYN_ARR,
|
||
SCR_DYN_OBJ,
|
||
/* The undefined VALUE. Never produced by the parser (JSON text has no
|
||
* undefined) — it enters the checked-dynamic tree through index-signature overflow reads
|
||
* (a missing key), optional-chain unit paths on dyn results, and the
|
||
* compiler's static→dyn converters (an undefined-armed union's unit
|
||
* arm). One immortal instance (scr_dyn_undefined); JSON serialization
|
||
* drops object members holding it and prints null for array slots,
|
||
* exactly Node. dynCheck matches it against exactly the undefined arm. */
|
||
SCR_DYN_UNDEF,
|
||
/* A numeric typed-array/Buffer value retained by reference. Boxing and
|
||
* checked extraction preserve the view, exact element kind, and backing
|
||
* allocation. typeof answers "object"; String() joins numeric elements
|
||
* (Buffer keeps its encoding behavior); JSON writes an index-keyed object.
|
||
* Exact-brand tests accept Buffer as Uint8Array. */
|
||
SCR_DYN_BYTES,
|
||
/* A FUNCTION value. Never produced by the parser — it enters the checked-dynamic tree
|
||
* through the compiler's static→dyn converters (a typed closure flowing
|
||
* into an `unknown` slot: `mustCall(fn)`-style untyped JS helpers). Owns
|
||
* a ScrClosure plus a compiler-emitted CALL THUNK that validates dyn
|
||
* arguments into the closure's declared parameter types (per-arg
|
||
* dynCheck — JS arity semantics: extra args ignored, missing args are
|
||
* the undefined dyn value) and converts the result back to a dyn value.
|
||
* `sig` is the compiler's interned signature key: dynCheck against an
|
||
* IDENTICAL function type unwraps the closure directly; any other
|
||
* (adaptable) function target wraps the box in a per-target adapter
|
||
* closure. typeof answers "function", truthiness is true, String()
|
||
* renders the native-code form (source text is gone — SEMANTICS.md),
|
||
* JSON serialization treats it like undefined (dropped from objects,
|
||
* null in arrays), and util.inspect prints [Function: name]. */
|
||
SCR_DYN_FUNC,
|
||
/* A NATIVE HANDLE value (httpReq/httpRes/netSocket crossing the checked-
|
||
* dynamic boundary — `server.on('request', mustCall((req, res) => ...))`
|
||
* makes the listener dyn, so the event tuple must box). Never produced
|
||
* by the parser — it enters the checked-dynamic tree through the compiler's static→dyn
|
||
* converters and the per-target function adapters. Boxes by REFERENCE
|
||
* (a retained pointer + a handle-type tag): handles are stateful I/O
|
||
* objects, so identity is the HANDLE, not the box — strict equality
|
||
* compares (tag, pointer), and dynCheck against the same handle type
|
||
* unwraps the pointer (+1) instead of copying. Member CALLS and the
|
||
* property reads/writes with static equivalents dispatch through a
|
||
* per-tag ops table (scr_dyn_handle_install — scr_http.c/scr_net.c
|
||
* register at main(), the vtable-stamp story) onto the SAME entry
|
||
* points the static lowerings use; unknown members meet the loud
|
||
* "not supported yet" ladder, never a silent wrong answer. typeof
|
||
* answers "object", truthiness is true, String() renders
|
||
* "[object Object]" (Object.prototype.toString — Node's answer for
|
||
* these classes), JSON serialization and util.inspect fence loudly. */
|
||
SCR_DYN_HANDLE,
|
||
/* A PROMISE value. Never produced by the parser — it enters the checked-dynamic tree
|
||
* through the compiler's static→dyn converters (a promise flowing into
|
||
* an `any`/`unknown` slot: the traced-function boundary of
|
||
* dc.tracingChannel.tracePromise, a dyn-boxed async closure's return).
|
||
* Boxes by REFERENCE (a retained ScrPromise + the boundary contract
|
||
* that a dyn-crossing promise settles with a dyn payload: promise<dyn>
|
||
* boxes its ScrPromise directly, other inner types box an ADAPTER
|
||
* promise whose emitted settle callback converts the payload — see
|
||
* scr_dyn_new_promise_adapting). Identity is the PROMISE, not the box
|
||
* (strict equality compares the pointers; Promise.resolve identity
|
||
* survives one crossing, a re-boxed typed promise mints a fresh
|
||
* adapter — SEMANTICS.md). typeof answers "object", truthiness is
|
||
* true, String() renders "[object Promise]" (Object.prototype.toString
|
||
* — promises have no own toString), JSON serialization writes {} (no
|
||
* own enumerable properties, exactly Node), and util.inspect fences
|
||
* loudly. The dyn→promise edge is NOT visible to the cycle collector
|
||
* (the dyn→closure stance): a cycle THROUGH a dyn-boxed promise is
|
||
* merely never collected. */
|
||
SCR_DYN_PROMISE,
|
||
/* An ISLAND (engine-held) value — the jsval→dyn crossing. Never
|
||
* produced by the parser — it enters the checked-dynamic tree through the gated
|
||
* constructor scr_dyn_from_jsval (scr_island.c): an 'any'-typed value
|
||
* flowing into an 'unknown'/'object'/JS-residue slot. Boxes by
|
||
* REFERENCE (a retained ScrJsval cell). The constructor SCALAR-
|
||
* NORMALIZES: engine numbers/strings/booleans/null/undefined convert
|
||
* to the native dyn kinds at wrap time, so JSVAL nodes only ever hold
|
||
* engine objects, arrays, and functions (plus the symbol/bigint edge —
|
||
* kinds the checked-dynamic tree cannot represent at all). Identity is the CELL's
|
||
* engine value: strict equality routes to the engine's === (two wraps
|
||
* of one engine value compare equal), and scr_jsval_from_dyn unwraps
|
||
* the SAME cell back (+1) — the boundary is identity-preserving for
|
||
* engine-born values. typeof/truthiness/String() route to the engine
|
||
* per use (scr_dyn_jsval_ops); every dyn walk without an armed route
|
||
* (JSON, structuredClone, deepStrictEqual, inspect, keyed access,
|
||
* calls, iteration) throws the LOUD "not supported yet" ladder — never
|
||
* a silent wrong answer. The dyn→cell edge is NOT visible to the cycle
|
||
* collector (the dyn→closure stance): a cycle dyn → cell → engine
|
||
* object → host closure → dyn is merely never collected (the
|
||
* documented cross-boundary-cycle divergence). */
|
||
SCR_DYN_JSVAL,
|
||
/* A compiler-owned typed reference. Program class instances use this for
|
||
* ordinary `unknown` storage; native Web streams also use it when their
|
||
* contract must expose the exact input reference again. The capsule
|
||
* retains the original value so an exact checked cast preserves identity;
|
||
* `materialize` supplies the ordinary own-field/data view to generic dyn
|
||
* consumers. LLVM hardcodes these kind numbers; append new kinds. */
|
||
SCR_DYN_TYPED_REF,
|
||
/* Native Proxy over a checked plain object; keep new tags at the end. */
|
||
SCR_DYN_PROXY,
|
||
SCR_DYN_BIGINT, /* exact native integer, retained by the box */
|
||
} ScrDynKind;
|
||
|
||
/* The handle-type tags the checked-dynamic tree can carry. The set is deliberately the
|
||
* HANDLE kinds whose member surfaces already have complete static
|
||
* lowerings (the http/net receiver surface); new kinds join by adding a
|
||
* tag + an ops registration in their owning unit. */
|
||
typedef enum {
|
||
SCR_DYNH_HTTP_REQ, /* ScrHttpReq — IncomingMessage (server req & client res) */
|
||
SCR_DYNH_HTTP_RES, /* ScrHttpRes — ServerResponse */
|
||
SCR_DYNH_NET_SOCKET, /* ScrNetSocket — net.Socket */
|
||
SCR_DYNH_NET_SERVER, /* ScrNetServer — net.Server (http.Server rides the same handle) */
|
||
SCR_DYNH_H2_SESSION, /* ScrH2Session — Http2Session (client & server) */
|
||
SCR_DYNH_H2_STREAM, /* ScrH2Stream — Http2Stream (client & server) */
|
||
SCR_DYNH_HTTP_CLIENT, /* ScrHttpClientReq — http.ClientRequest */
|
||
SCR_DYNH_HTTP_AGENT, /* ScrHttpAgent — http.Agent / https.Agent */
|
||
SCR_DYNH_ABORT_SIGNAL, /* native static-fetch AbortSignal */
|
||
SCR_DYNH_WEB_STREAM, /* native WHATWG ReadableStream */
|
||
SCR_DYNH_WEB_READER, /* native ReadableStreamDefaultReader */
|
||
SCR_DYNH_WEB_CONTROLLER, /* native ReadableStreamDefaultController */
|
||
SCR_DYNH_FETCH_RESPONSE, /* native static-fetch Response */
|
||
SCR_DYNH_FETCH_HEADERS, /* native static-fetch response Headers */
|
||
SCR_DYNH_EVENT, /* native static-fetch abort Event */
|
||
SCR_DYNH_ABORT_CONTROLLER, /* native static-fetch AbortController */
|
||
SCR_DYNH_CHILD, /* child_process.ChildProcess */
|
||
SCR_DYNH_FILE_HANDLE, /* fs/promises.FileHandle */
|
||
SCR_DYNH_ARRAY_BUFFER, /* fixed-length ArrayBuffer storage */
|
||
SCR_DYNH_SEGMENTER, /* default Unicode grapheme segmentation */
|
||
SCR_DYNH_SEGMENTS, /* iterable immutable segmentation result */
|
||
SCR_DYNH_BUILTIN_MODULE, /* native builtin export object */
|
||
SCR_DYNH_GLOBAL, /* native global object identity */
|
||
SCR_DYNH_WEAK_MAP, /* native weak-key metadata */
|
||
SCR_DYNH_WEAK_SET, /* native weak membership */
|
||
SCR_DYNH_SET, /* native Set<unknown>, shared backing map */
|
||
SCR_DYNH_REGEXP, /* native RegExp, shared compiled pattern */
|
||
SCR_DYNH_STDIO, /* stable process stdin/stdout/stderr values */
|
||
SCR_DYNH_URL, /* native WHATWG URL */
|
||
SCR_DYNH_COUNT,
|
||
} ScrDynHandleTag;
|
||
|
||
typedef struct ScrDyn ScrDyn;
|
||
typedef struct ScrBytes ScrBytes; /* full definition below (C11 repeat) */
|
||
typedef struct ScrClosure ScrClosure; /* full definition below (C11 repeat) */
|
||
typedef struct ScrDynTypedCast {
|
||
const char *type_key;
|
||
size_t type_key_len;
|
||
void *ptr;
|
||
void *(*retain)(void *);
|
||
void (*release)(void *);
|
||
struct ScrDynTypedCast *next;
|
||
} ScrDynTypedCast;
|
||
typedef struct ScrJsval ScrJsval; /* opaque island cell (C11 repeat; the
|
||
* always-linked dyn core never touches
|
||
* its engine value — only the gated ops
|
||
* installed by scr_dyn_from_jsval do) */
|
||
|
||
/* The compiler-emitted call glue carried by a SCR_DYN_FUNC box: checks the
|
||
* dyn arguments against the boxed closure's declared parameter types (a
|
||
* mismatch throws the catchable path-annotated TypeError and returns NULL
|
||
* with the exception pending), calls through the closure, and returns the
|
||
* result as an owned (+1) dyn value. `args` entries are BORROWED. */
|
||
typedef ScrDyn *(*ScrDynThunk)(ScrClosure *clo, ScrDyn *const *args, size_t argc);
|
||
|
||
/* Object member. Keys are malloc'd UTF-8 bytes (NUL-terminated for
|
||
* convenience; key_len excludes the NUL) — duplicate keys were already
|
||
* collapsed at parse time (later wins, like JS JSON.parse). */
|
||
typedef struct {
|
||
char *key;
|
||
size_t key_len;
|
||
ScrDyn *value; /* owned */
|
||
ScrDyn *getter; /* owned when this is an accessor property */
|
||
ScrDyn *setter; /* owned when this is an accessor property */
|
||
bool accessor;
|
||
/* Ordinary object insertion sets all three attributes true. */
|
||
bool writable;
|
||
bool enumerable;
|
||
bool configurable;
|
||
} ScrDynEntry;
|
||
|
||
struct ScrDyn {
|
||
size_t rc; /* SIZE_MAX = immortal (unused for dyn; kept per convention) */
|
||
ScrDynKind kind;
|
||
/* SCR_DYN_BYTES flavor: true when the value REPRESENTS a Node Buffer
|
||
* (stream chunks, buffer-typed boundary crossings) — Buffer's string
|
||
* coercion and toString() decode utf8, where a plain Uint8Array joins
|
||
* its elements ("1,2,3"). Everything else ignores it. */
|
||
bool buffer;
|
||
/* SCR_DYN_OBJ flavor: true for Object.create(null)'s dictionary — an
|
||
* object with NO prototype. Method dispatch needs nothing (the checked-dynamic tree's
|
||
* OBJ dispatch is already own-member-only, which IS Node's null-proto
|
||
* answer); util.inspect prefixes "[Object: null prototype]", and
|
||
* deepStrictEqual separates it from plain objects (Node compares
|
||
* prototypes — the bytes `buffer` gate's stance). Keyed reads/writes,
|
||
* Object.keys/entries/assign, JSON, and typeof are flag-blind, and
|
||
* fresh copies (structuredClone) DROP the flag — Node's serialization
|
||
* answers a plain object too. */
|
||
bool null_proto;
|
||
/* A deep typed-to-dyn copy has no stable native reference identity.
|
||
* Weak collections must refuse it instead of weakly tracking its box. */
|
||
bool copied_from_native;
|
||
bool non_extensible; /* native objects and arrays; fits the header padding */
|
||
union {
|
||
bool b;
|
||
double num;
|
||
ScrStr *str; /* owned */
|
||
ScrBytes *bytes; /* owned (SCR_DYN_BYTES) */
|
||
ScrBigInt *bigint; /* owned (SCR_DYN_BIGINT) */
|
||
struct { size_t len; size_t cap; ScrDyn **items; struct ScrDyn *properties; bool sealed; bool frozen; } arr; /* owned */
|
||
struct {
|
||
size_t len;
|
||
size_t cap;
|
||
ScrDynEntry *entries;
|
||
/* Optional identity of the typed record that produced this ordinary
|
||
* snapshot. Empty records and callback interfaces can retain their
|
||
* source across repeated static-to-dynamic crossings. */
|
||
/* Owned through source_access(source_identity, false). Callback
|
||
* interfaces may request a fresh snapshot with the true arm. */
|
||
void *source_identity;
|
||
ScrDyn *(*source_access)(void *, bool materialize);
|
||
} obj; /* owned */
|
||
/* SCR_DYN_FUNC: the boxed closure (owned) + its call descriptor. `sig`
|
||
* and `name` are static compiler-emitted literals (never freed); name
|
||
* may be NULL (anonymous — inspect prints [Function (anonymous)]).
|
||
* The dyn→closure edge is NOT visible to the cycle collector (ScrDyn
|
||
* has no trace header): trial deletion treats it as an external root,
|
||
* so nothing dangles — a cycle THROUGH a dyn-boxed function is merely
|
||
* never collected (documented divergence). */
|
||
struct { ScrClosure *clo; ScrDynThunk thunk; const char *sig; const char *name; uint32_t arity; ScrClassObj *class_obj; } fn;
|
||
/* Native handles normally keep conservative ownership edges. Sets
|
||
* backed by cycle-capable maps expose their edge to the collector.
|
||
* Cache that allocation property: during collection the map may be
|
||
* freed before its box, so disposal must not inspect the map again. */
|
||
struct { void *ptr; ScrDynHandleTag tag; bool traced; } handle;
|
||
/* SCR_DYN_TYPED_REF: original static value + its compiler-emitted
|
||
* identity tag, RC adapters, and ordinary dyn snapshot adapter. */
|
||
struct {
|
||
void *ptr;
|
||
void *(*retain)(void *);
|
||
void (*release)(void *);
|
||
const char *type_key;
|
||
size_t type_key_len;
|
||
ScrDyn *(*materialize)(void *);
|
||
void (*commit)(void *, const ScrDyn *);
|
||
/* Both caches belong to this capsule. ReadableStream canonicalizes
|
||
* capsules for repeated source references, so the refreshed dyn view
|
||
* and every structurally converted static view keep JS reference
|
||
* identity while the source reference remains observable. */
|
||
ScrDyn *materialized;
|
||
ScrDynTypedCast *casts;
|
||
bool traced; /* native referent has a cycle header */
|
||
} typed_ref;
|
||
/* SCR_DYN_PROMISE: the retained promise. The boundary contract: it
|
||
* settles with a dyn payload (SCR_EXC_REF ScrDyn fulfillment or a
|
||
* void fulfillment awaiters read as the undefined value) — the
|
||
* emitted converters guarantee it (direct box for promise<dyn>,
|
||
* adapter promise otherwise). */
|
||
ScrPromise *promise;
|
||
/* SCR_DYN_JSVAL: the retained island cell (engine objects/arrays/
|
||
* functions only — the constructor scalar-normalizes; see the kind's
|
||
* comment). Released through the installed ops so this always-linked
|
||
* core never references the gated island unit. */
|
||
struct { ScrJsval *cell; } jsval;
|
||
struct { ScrDyn *target; ScrDyn *handler; } proxy; /* both owned */
|
||
} v;
|
||
/* Keep the checked value payload at its established offset: generated
|
||
* native converters read array and object fields directly. */
|
||
struct ScrDyn *prototype;
|
||
};
|
||
|
||
static inline ScrDyn *scr_dyn_retain(ScrDyn *d) {
|
||
if (d->rc != SIZE_MAX) {
|
||
d->rc++;
|
||
scr_cyc_mark_live(d);
|
||
}
|
||
return d;
|
||
}
|
||
|
||
void scr_dyn_release(ScrDyn *d); /* releases the tree recursively; NULL-tolerant */
|
||
|
||
/* RFC 8259 parse of a UTF-8 string. Borrows text; returns +1, or throws a
|
||
* catchable Node-flavored SyntaxError string and returns NULL (callers are
|
||
* compiler-emitted pending checks — json.parse is in the may-throw seed). */
|
||
ScrDyn *scr_json_parse(ScrStr *text);
|
||
|
||
/* Native JSON callbacks. All inputs borrowed, result owned (+1), NULL on
|
||
* pending exception. Stringify returns a dyn string OR actual undefined
|
||
* when the replacer omits the root. gap has already applied space rules. */
|
||
ScrDyn *scr_json_parse_reviver(ScrStr *text, const ScrDyn *reviver);
|
||
ScrDyn *scr_json_stringify_replacer(const ScrDyn *value, const ScrDyn *replacer, const ScrStr *gap);
|
||
|
||
|
||
/* BORROWED member lookup on a SCR_DYN_OBJ; NULL when the key is absent. */
|
||
ScrDyn *scr_dyn_obj_get(const ScrDyn *d, const char *key, size_t key_len);
|
||
/* Own-enumerable check on a SCR_DYN_OBJ. */
|
||
bool scr_dyn_obj_enumerable(const ScrDyn *d, const char *key, size_t key_len);
|
||
/* Literal discriminator tests used before selecting a typed record layout.
|
||
* Missing fields and different primitive kinds never match. Borrowed args. */
|
||
bool scr_dyn_field_eq_str(const ScrDyn *d, const ScrStr *key, const ScrStr *value);
|
||
bool scr_dyn_field_eq_num(const ScrDyn *d, const ScrStr *key, double value);
|
||
bool scr_dyn_field_eq_bool(const ScrDyn *d, const ScrStr *key, bool value);
|
||
|
||
/* Object.keys/values/entries over the checked-dynamic tree: JS own-key order (array-index
|
||
* keys ascending first), dyn-array results (+1); values/entries RETAIN
|
||
* member nodes. null/undefined receivers throw Node's catchable
|
||
* TypeError. */
|
||
ScrDyn *scr_dyn_obj_keys(const ScrDyn *v);
|
||
ScrDyn *scr_dyn_for_in_keys(const ScrDyn *v);
|
||
/* Snapshot all own string keys of a SCR_DYN_OBJ in JS order. Returns +1. */
|
||
ScrDyn *scr_dyn_obj_own_keys(const ScrDyn *v);
|
||
ScrDyn *scr_dyn_get_own_property_names(const ScrDyn *value);
|
||
/* Object.hasOwn over a dyn receiver: OBJ member presence, ARR index
|
||
* bounds ("length" included); nullish receivers throw Node's ToObject
|
||
* TypeError; every other kind answers false. */
|
||
bool scr_dyn_has_own(const ScrDyn *v, const ScrStr *key);
|
||
bool scr_dyn_property_is_enumerable(const ScrDyn *value, const ScrStr *key);
|
||
/* Object.assign over dyn values (+1 target back; ToObject TypeError on a
|
||
* nullish target). Sources visit own keys in order and copy entries that
|
||
* are enumerable when reached; ARR/STR/BYTES expose index keys. Nullish
|
||
* and scalar/function/handle sources copy nothing. */
|
||
ScrDyn *scr_dyn_assign(ScrDyn *target, const ScrDyn *src);
|
||
ScrDyn *scr_dyn_copy_data_properties(ScrDyn *target, const ScrDyn *src);
|
||
/* Variadic Object.assign (the spread-source form): the compiler packs
|
||
* every source into one fresh dyn array — pack_push retains a plain
|
||
* source in (BORROWED), pack_push_spread flattens a spread source through
|
||
* the spread-call walk (V8's exact TypeError texts, `what` spelling the
|
||
* spread expression; MAY THROW pending) — then assign_all copies each
|
||
* pack element's own members onto the target left to right and answers
|
||
* the target retained (+1; ToObject TypeError on a nullish target). */
|
||
void scr_dyn_pack_push(ScrDyn *pack, ScrDyn *v);
|
||
void scr_dyn_pack_push_spread(ScrDyn *pack, const ScrDyn *src, const ScrStr *what);
|
||
/* The iterated-path twin — a spread that is NOT the single last argument
|
||
* takes V8's iterator-protocol failure texts, which describe the VALUE
|
||
* ("object null", "number 5", ...) instead of spelling the expression. */
|
||
void scr_dyn_pack_push_spread_iter(ScrDyn *pack, const ScrDyn *src);
|
||
ScrDyn *scr_dyn_assign_all(ScrDyn *target, const ScrDyn *sources);
|
||
ScrDyn *scr_dyn_obj_values(const ScrDyn *v);
|
||
ScrDyn *scr_dyn_obj_entries(const ScrDyn *v);
|
||
|
||
/* dyn construction — the compiler-emitted static→dyn converters (sc_td_*)
|
||
* and index-signature overflow machinery build dyn values directly.
|
||
* Constructors return +1; _new_str RETAINS the string into the node.
|
||
* arr_push and obj_set take OWNERSHIP of the value (+1 moves in); obj_set
|
||
* COPIES the key bytes and replaces a duplicate key's value (later wins,
|
||
* like JS). scr_dyn_undefined returns THE immortal undefined value
|
||
* (rc == SIZE_MAX — retains/releases are no-ops). */
|
||
ScrDyn *scr_dyn_undefined(void);
|
||
ScrDyn *scr_dyn_new_null(void);
|
||
ScrDyn *scr_dyn_new_bool(bool b);
|
||
ScrDyn *scr_dyn_new_num(double n);
|
||
ScrDyn *scr_dyn_native_set_new(const ScrDyn *seed);
|
||
ScrDyn *scr_dyn_native_set(ScrMap *value); /* borrowed backing, +1 box */
|
||
bool scr_dyn_native_set_is(const ScrDyn *value);
|
||
ScrRegex *scr_regex_new_checked(const ScrDyn *pattern, const ScrDyn *flags);
|
||
ScrDyn *scr_dyn_native_url(ScrUrl *value);
|
||
bool scr_dyn_native_url_is(const ScrDyn *value);
|
||
ScrDyn *scr_dyn_native_regex(ScrRegex *value);
|
||
bool scr_dyn_native_regex_is(const ScrDyn *value);
|
||
ScrDyn *scr_dyn_freeze(ScrDyn *value);
|
||
bool scr_dyn_is_frozen(const ScrDyn *value);
|
||
ScrDyn *scr_dyn_new_bigint(ScrBigInt *value); /* borrows and retains value */
|
||
ScrDyn *scr_dyn_new_str(ScrStr *s);
|
||
ScrDyn *scr_dyn_new_arr(void);
|
||
ScrDyn *scr_dyn_new_obj(void);
|
||
/* The ordinary object snapshot plus a retained typed source. source_access
|
||
* releases that source when materialize=false and returns a fresh snapshot
|
||
* when true. */
|
||
ScrDyn *scr_dyn_new_obj_with_identity(
|
||
void *source, void *(*source_retain)(void *),
|
||
ScrDyn *(*source_access)(void *, bool materialize));
|
||
bool scr_dyn_obj_same_source(const ScrDyn *a, const ScrDyn *b);
|
||
void *scr_dyn_obj_source_cast(const ScrDyn *d,
|
||
ScrDyn *(*source_access)(void *, bool), void *(*source_retain)(void *));
|
||
/* Object.create(null): the fresh null-prototype dictionary (see the
|
||
* null_proto flavor flag above). */
|
||
ScrDyn *scr_dyn_new_obj_null_proto(void);
|
||
ScrDyn *scr_dyn_obj_create(ScrDyn *prototype);
|
||
ScrDyn *scr_dyn_obj_create_with_properties(ScrDyn *prototype, ScrDyn *descriptors);
|
||
ScrDyn *scr_dyn_get_prototype(ScrDyn *object);
|
||
ScrDyn *scr_dyn_get_own_property_descriptors(ScrDyn *object);
|
||
ScrDyn *scr_dyn_set_prototype(ScrDyn *object, ScrDyn *prototype);
|
||
ScrDyn *scr_dyn_prevent_extensions(ScrDyn *object);
|
||
bool scr_dyn_is_extensible(ScrDyn *object);
|
||
ScrDyn *scr_dyn_seal(ScrDyn *object);
|
||
bool scr_dyn_is_sealed(const ScrDyn *object);
|
||
ScrDyn *scr_dyn_proxy_new(const ScrDyn *target, const ScrDyn *handler);
|
||
ScrDyn *scr_dyn_proxy_get(const ScrDyn *proxy, const ScrStr *key);
|
||
bool scr_dyn_proxy_has(const ScrDyn *proxy, const ScrStr *key);
|
||
ScrDyn *scr_dyn_own_descriptor(const ScrDyn *value, const ScrStr *key);
|
||
void scr_dyn_proxy_set(ScrDyn *proxy, ScrStr *key, ScrDyn *value);
|
||
void scr_dyn_proxy_delete(ScrDyn *proxy, const ScrStr *key);
|
||
void scr_dyn_proxy_unsupported(const char *operation);
|
||
/* Retains the native view, preserving identity and its shared backing
|
||
* allocation across the checked-dynamic boundary. Borrows b. */
|
||
ScrDyn *scr_dyn_new_bytes(const ScrBytes *b);
|
||
ScrDyn *scr_dyn_bytes_key_get(const ScrDyn *value, const ScrStr *key);
|
||
/* The Buffer-flavored twin (stream chunks): string coercion/toString
|
||
* decode utf8 instead of joining elements. */
|
||
ScrDyn *scr_dyn_new_buffer(const ScrBytes *b);
|
||
/* Identity-preserving transit capsule used by ReadableStream.from over
|
||
* typed arrays. The constructor retains `ptr`; matching unbox returns +1.
|
||
* materialize lazily creates and then retains one detached dyn snapshot.
|
||
* The cast cache lets a non-exact dynCheck reuse one safe, compiler-built
|
||
* target view instead of raw-casting structurally different layouts. */
|
||
ScrDyn *scr_dyn_new_typed_ref(
|
||
void *ptr, void *(*retain)(void *), void (*release)(void *),
|
||
const char *type_key, size_t type_key_len,
|
||
ScrDyn *(*materialize)(void *),
|
||
void (*commit)(void *, const ScrDyn *));
|
||
ScrDyn *scr_dyn_new_typed_ref_traced(
|
||
void *ptr, void *(*retain)(void *), void (*release)(void *),
|
||
const char *type_key, size_t type_key_len,
|
||
ScrDyn *(*materialize)(void *),
|
||
void (*commit)(void *, const ScrDyn *));
|
||
bool scr_dyn_typed_ref_is(
|
||
const ScrDyn *d, const char *type_key, size_t type_key_len);
|
||
bool scr_dyn_typed_ref_is_key(const ScrDyn *d, const ScrStr *type_key);
|
||
void *scr_dyn_typed_ref_unbox(const ScrDyn *d); /* +1 */
|
||
ScrDyn *scr_dyn_class_view_unavailable(void *ptr);
|
||
ScrDyn *scr_dyn_typed_ref_materialize(const ScrDyn *d); /* +1 */
|
||
void scr_dyn_typed_ref_commit(ScrDyn *d);
|
||
void *scr_dyn_typed_ref_cached_cast(
|
||
const ScrDyn *d, const char *type_key, size_t type_key_len); /* +1/NULL */
|
||
void scr_dyn_typed_ref_cache_cast(
|
||
ScrDyn *d, const char *type_key, size_t type_key_len, void *ptr,
|
||
void *(*retain)(void *), void (*release)(void *));
|
||
/* Retains a SCR_DYN_BYTES view (+1) for checked extraction. */
|
||
ScrBytes *scr_dyn_bytes_unbox(const ScrDyn *d);
|
||
/* Exact numeric typed-array brand; elem is a ScrBytesElem tag. */
|
||
bool scr_dyn_bytes_is(const ScrDyn *d, int elem);
|
||
void scr_dyn_arr_push(ScrDyn *arr, ScrDyn *item);
|
||
/* Spread completion for a runtime-arity argument list (`f(...xs)` in the
|
||
* checked-dynamic tier): flattens `src` into `arr` per JS's spread over the
|
||
* dyn's iterable kinds — arrays element-by-element (retained), strings by
|
||
* code POINT (the string iterator), bytes by byte — and throws V8's exact
|
||
* SPREAD-CALL TypeError for every other kind (pending; callers check):
|
||
* nullish sources spell the spread expression (`what`), everything else is
|
||
* the generic "Spread syntax requires ..." text. Borrows src. */
|
||
void scr_dyn_arr_push_spread(ScrDyn *arr, const ScrDyn *src, const char *what);
|
||
/* Destructuring pack over a dyn source: iterable kinds (arrays, strings by
|
||
* code point, bytes) collect into a fresh array (+1); every other kind
|
||
* throws V8's destructuring TypeError — `msg` verbatim when non-empty (the
|
||
* compile-time source spelling), else the runtime kind wording. Borrows
|
||
* both; NULL with the exception pending on the throw. */
|
||
ScrDyn *scr_dyn_iter_pack(const ScrDyn *src, const ScrStr *msg);
|
||
/* Map constructor seeds: retain array inputs for live iteration, accept
|
||
* nullish inputs as empty, and validate each entry before reading 0/1. */
|
||
ScrDyn *scr_dyn_map_seed_entries(const ScrDyn *src);
|
||
ScrDyn *scr_dyn_map_seed_entry(const ScrDyn *entry);
|
||
bool scr_weak_map_is(const ScrDyn *value);
|
||
bool scr_weak_set_is(const ScrDyn *value);
|
||
ScrDyn *scr_weak_map_new(ScrDyn *entries);
|
||
ScrDyn *scr_weak_set_new(ScrDyn *values);
|
||
ScrDyn *scr_dyn_from_entries(ScrDyn *entries);
|
||
ScrDyn *scr_dyn_mark_snapshot(ScrDyn *value); /* consumes and returns +1 */
|
||
/* Optional non-owning observers, installed by weak collections. */
|
||
extern SCR_TL void (*scr_weak_dispose_hook)(void *);
|
||
void scr_weak_dispose(void *object);
|
||
/* The for-of-over-dyn pack accessors (the emitted index loop drives them
|
||
* over a scr_dyn_iter_pack result, which is ARR by construction).
|
||
* scr_dyn_arr_len answers 0 for non-ARR kinds; scr_dyn_arr_at answers the
|
||
* undefined singleton past the end (both never throw). scr_dyn_arr_at is
|
||
* +1. */
|
||
double scr_dyn_arr_len(const ScrDyn *d);
|
||
ScrDyn *scr_dyn_arr_at(const ScrDyn *d, double i);
|
||
ScrDyn *scr_dyn_arr_named_get(const ScrDyn *d, const ScrStr *key);
|
||
void scr_dyn_obj_set(ScrDyn *obj, const char *key, size_t key_len, ScrDyn *value);
|
||
ScrDyn *scr_dyn_define_property(ScrDyn *target, ScrDyn *key, ScrDyn *descriptor);
|
||
ScrDyn *scr_dyn_get_own_property_descriptor(ScrDyn *target, ScrDyn *key);
|
||
ScrDyn *scr_dyn_array_proto_call(ScrDyn *target, ScrStr *method, ScrDyn *args);
|
||
/* The checked-dynamic keyed WRITE (`h.k = v` on a dyn receiver): OBJ sets
|
||
* the member (JS: later writes win, insertion order); undefined/null and
|
||
* non-object kinds throw Node's catchable TypeErrors (strict-mode
|
||
* wording). All three operands BORROWED (the value is retained in). */
|
||
void scr_dyn_key_set(ScrDyn *recv, ScrStr *key, ScrDyn *value);
|
||
void scr_dyn_key_set_computed(ScrDyn *recv, ScrDyn *key, ScrDyn *value);
|
||
void scr_dyn_key_delete(ScrDyn *recv, const ScrStr *key, bool strict);
|
||
ScrDyn *scr_dyn_global_symbol_get(ScrSym *key); /* borrowed key; +1 value */
|
||
void scr_dyn_global_symbol_set(ScrSym *key, ScrDyn *value); /* both borrowed */
|
||
bool scr_dyn_global_symbol_has(ScrSym *key);
|
||
void scr_dyn_global_symbol_delete(ScrSym *key);
|
||
/* `key in v` with a runtime key (OBJ own members, ARR length/valid indices,
|
||
* Proxy has traps). Traps and unsupported representations may throw. */
|
||
bool scr_dyn_has_key(const ScrDyn *v, const ScrStr *key);
|
||
/* Bare `typeof v` on a dyn value: the dyn kind's JS answer (+1 string;
|
||
* null answers "object"). Never throws. */
|
||
ScrStr *scr_dyn_typeof(const ScrDyn *d);
|
||
/* Object.prototype.toString.call over the native checked-dynamic tree; +1. */
|
||
ScrStr *scr_dyn_object_tag(const ScrDyn *d);
|
||
/* Receiver-kind-dispatched toString() (Buffer-flavored bytes decode per
|
||
* enc — utf8 default; strings/numbers/booleans/arrays/objects answer
|
||
* JS-exactly; undefined/null throw the catchable TypeError). Borrows; +1. */
|
||
ScrStr *scr_dyn_to_string(const ScrDyn *d, const ScrStr *enc);
|
||
/* The method-call spelling `d.toString(enc?)`: identical, except a
|
||
* null-prototype dictionary throws "<what> is not a function" — its
|
||
* prototype chain has no toString (Node's answer). */
|
||
ScrStr *scr_dyn_to_string_method(const ScrDyn *d, const ScrStr *enc, const ScrStr *what);
|
||
ScrStr *scr_dyn_to_string_argument(const ScrDyn *d, const ScrDyn *argument, const ScrStr *what);
|
||
/* JS String() over the dyn kind (units render "null"/"undefined" where
|
||
* scr_dyn_to_string throws) — the web globals' WebIDL ToString. +1. */
|
||
ScrStr *scr_dyn_string_coerce(const ScrDyn *d);
|
||
/* JS ToString WITH the object protocol (user toString/valueOf members
|
||
* called, their throws propagating) — the WHATWG USVString conversions.
|
||
* Borrows; +1 or NULL with the exception pending. */
|
||
ScrStr *scr_dyn_string_coerce_js(const ScrDyn *d);
|
||
bool scr_dyn_number_coerce_js(const ScrDyn *d, double *out);
|
||
/* Direct-return ABI wrapper for compiler libCalls: JS ToNumber, or NaN
|
||
* with the exception pending when an object hook throws/refuses. */
|
||
double scr_dyn_number_coerce(const ScrDyn *d);
|
||
double scr_dyn_number_constructor(const ScrDyn *d);
|
||
ScrDyn *scr_dyn_add(const ScrDyn *left, const ScrDyn *right); /* borrowed; +1 or NULL/pending */
|
||
|
||
/* `d instanceof TypeError` (and the other builtin error classes) on a
|
||
* checked-dynamic value: the from_error cache resolves the dyn encoding
|
||
* back to its runtime error and the class's stamped preorder interval
|
||
* answers. A dyn value that never came from an error answers false. */
|
||
bool scr_dyn_err_instanceof(const ScrDyn *d, double kind);
|
||
/* Property read on a checked-dynamic object. Own fields take precedence;
|
||
* runtime Error instances inherit their exact builtin constructor token.
|
||
* The result is owned. */
|
||
ScrDyn *scr_dyn_obj_read(const ScrDyn *d, const char *key, size_t key_len);
|
||
/* JS typeof comparison, including null's "object" result. */
|
||
bool scr_dyn_is_object(const ScrDyn *d);
|
||
void scr_dyn_throw(ScrDyn *d); /* moves; preserves boxed native Error classification */
|
||
|
||
/* structuredClone over the checked-dynamic tree: JSON-safe data + bytes deep-copy;
|
||
* functions/handles throw the spec's catchable DataCloneError; cycles
|
||
* throw the scriptc fence (the checked-dynamic tree cannot represent them). Option
|
||
* validation (shared with scr_domex_clone) throws Node's exact
|
||
* TypeErrors; any non-empty transfer list throws DataCloneError. The
|
||
* _missing form is the zero-argument call: always throws Node's
|
||
* ERR_MISSING_ARGS. Args borrowed (options NULL-tolerant); results +1. */
|
||
ScrDyn *scr_structured_clone(const ScrDyn *value, const ScrDyn *options);
|
||
ScrDyn *scr_structured_clone_missing(void);
|
||
ScrDyn *scr_structured_clone_transfer_fail(void); /* always throws DataCloneError */
|
||
/* Validates a structuredClone options dyn value (throws on failure —
|
||
* callers must check scr_exc_pending). Borrowed, NULL-tolerant. */
|
||
void scr_sc_validate_options(const ScrDyn *options);
|
||
/* An %Error as the boundary's dyn shape ({name, message[, code]}).
|
||
* Borrows; +1. */
|
||
ScrDyn *scr_dyn_from_error(const ScrError *e);
|
||
/* The reverse extraction, riding the same identity cache: a dyn error
|
||
* that came from a runtime ScrError answers THAT instance (+1;
|
||
* out-and-back crossings compare reference-equal); alien %error objects
|
||
* rebuild once and enter the cache. Borrows d. */
|
||
ScrError *scr_error_from_dyn(const ScrDyn *d); /* scr_async_dyn.c (gated) */
|
||
/* The cache's runtime-internal access pair (scr_json.c owns the storage;
|
||
* the gated extraction reads/writes through these). */
|
||
ScrError *scr_errdyn_err_of(const ScrDyn *d); /* +1 or NULL */
|
||
void scr_errdyn_put(ScrError *e, ScrDyn *d); /* retains both sides */
|
||
/* ToBoolean over the dyn kind (JS-exact); borrowed, never throws. */
|
||
bool scr_dyn_truthy(const ScrDyn *d);
|
||
/* JS String() over a dyn value (arrays join, [object Object], the error
|
||
* encoding renders "name: message"); borrowed, result +1, never throws. */
|
||
ScrStr *scr_dyn_display(const ScrDyn *d);
|
||
/* JS === over two dyn values: scalars by value, units by kind, everything
|
||
* reference-shaped by node identity. Borrowed; never throws. */
|
||
bool scr_dyn_strict_eq(const ScrDyn *a, const ScrDyn *b);
|
||
/* Prototype-method dispatch on a dyn receiver (`recv.m(...)` where `m` is
|
||
* a name a dyn-representable prototype declares — push/slice/forEach/
|
||
* apply/...): implemented (kind, name) pairs run JS-exact semantics; a
|
||
* real-but-unimplemented method throws a LOUD "not supported yet" Error;
|
||
* a name the kind's prototype lacks throws Node's "<what> is not a
|
||
* function"; OBJ receivers call the own member. recv/args borrowed,
|
||
* result owned (+1). MAY THROW (NULL with the exception pending). */
|
||
ScrDyn *scr_dyn_invoke(ScrDyn *recv, const char *method, ScrDyn *const *args, size_t argc, const char *what);
|
||
/* Keyed read on a FUNC node: the own-property table first (defineProperties
|
||
* writes land there), then "name" (the box's best-effort static name; ""
|
||
* for anonymous) and "length" (the boxed arity). Returns +1, or NULL when
|
||
* the key answers nothing (the caller's undefined). Never throws. */
|
||
ScrDyn *scr_dyn_fn_get(const ScrDyn *d, const char *key, size_t key_len);
|
||
/* Object.defineProperties over dyn values (targets: OBJ and FUNC): each
|
||
* descriptor's `value` becomes an own data property. OBJ targets preserve
|
||
* writable/enumerable/configurable; FUNC targets still use plain properties.
|
||
* get/set throw the loud unsupported Error.
|
||
* Returns the target (+1, JS's return), or NULL with a pending catchable
|
||
* TypeError (non-object target/descriptor — Node's messages). */
|
||
ScrDyn *scr_dyn_define_props(ScrDyn *target, ScrDyn *descs);
|
||
/* Boxes a closure as a callable dyn value. Ownership of `clo` MOVES in
|
||
* (callers retain first when they keep their own reference); `sig`/`name`
|
||
* must be static literals (the box never frees them; name may be NULL). */
|
||
ScrDyn *scr_dyn_new_func(ScrClosure *clo, ScrDynThunk thunk, uint32_t arity, const char *sig, const char *name);
|
||
ScrDyn *scr_dyn_new_class(ScrClassObj *cls, const char *type_key);
|
||
bool scr_dyn_class_is(const ScrDyn *value, const char *type_key);
|
||
/* Calls a dyn value: a non-function kind throws the catchable TypeError
|
||
* "<what> is not a function" (Node's wording — `what` is the call site's
|
||
* callee spelling) and returns NULL; a function kind delegates to the
|
||
* boxed thunk (per-arg checks live there). `args` entries are BORROWED;
|
||
* the result is owned (+1), or NULL with the exception pending. */
|
||
ScrDyn *scr_dyn_call(const ScrDyn *d, ScrDyn *const *args, size_t argc, const char *what);
|
||
/* scr_dyn_call over a dyn ARRAY's elements (the spread-application form —
|
||
* `f(...args)` after the emitted argument array is built): argv IS the
|
||
* array's items. Borrows both; result owned (+1), or NULL pending. */
|
||
ScrDyn *scr_dyn_apply(const ScrDyn *d, const ScrDyn *args, const char *what);
|
||
/* Function.bind borrows its target and arguments, returns a fresh callable. */
|
||
ScrDyn *scr_dyn_bind(ScrDyn *target, ScrDyn *const *args, size_t argc);
|
||
|
||
/* Path spine for dynCheck error messages — a compile-time-shaped linked
|
||
* list the emitted builders stack-allocate per recursion level: `key`
|
||
* non-NULL for an object member segment, else `index` is an array index.
|
||
* Rendered root-first as "$", "$.items[2].price", ... */
|
||
typedef struct ScrDynPath {
|
||
const struct ScrDynPath *parent;
|
||
const char *key;
|
||
size_t index;
|
||
} ScrDynPath;
|
||
ScrMap *scr_dyn_native_set_check(const ScrDyn *value, const ScrDynPath *path);
|
||
ScrStr *scr_url_checked_to_path(const ScrDyn *value);
|
||
ScrUrl *scr_dyn_native_url_check(const ScrDyn *value, const ScrDynPath *path);
|
||
ScrRegex *scr_dyn_native_regex_check(const ScrDyn *value, const ScrDynPath *path);
|
||
ScrClassObj *scr_dyn_class_check(const ScrDyn *value, const char *type_key, const ScrDynPath *path);
|
||
|
||
/* The dynCheck failure path: builds "TypeError: expected <want> at <path>,
|
||
* got <kind>" (got == NULL renders as "undefined" — a missing object
|
||
* member) and THROWS it through the exception cell (catchable; uncaught it
|
||
* prints as "Uncaught TypeError: ..." and exits 1). This is
|
||
* scriptc-specific behavior — JS `as` never checks (SEMANTICS.md). */
|
||
void scr_dyn_check_fail(const ScrDynPath *path, const char *want, const ScrDyn *got);
|
||
|
||
/* ── native handles in the checked-dynamic tree (SCR_DYN_HANDLE) ────────────────────────
|
||
* Per-tag dispatch ops, registered by the handle's owning unit
|
||
* (scr_http_dyn_install / scr_net_dyn_install — emitted main() calls
|
||
* them exactly when the unit is linked, the scr_net_install story), so
|
||
* this always-linked core never references gated units. */
|
||
typedef struct ScrDynHandleOps {
|
||
const char *cls; /* Node's constructor name — error texts and dynCheck's "got ..." */
|
||
void *(*retain)(void *h);
|
||
void (*release)(void *h);
|
||
/* Member CALL on a dyn handle (`res.end(...)`): dispatch (tag, method)
|
||
* onto the static lowering's entry point with per-arg checks per the
|
||
* static signature. `self` is the receiving box (`return this` methods
|
||
* answer it +1). Result owned (+1) or NULL with the exception pending.
|
||
* Real-but-unimplemented members throw the loud "not supported yet"
|
||
* ladder; names the class's surface never had throw Node's
|
||
* "<what> is not a function". */
|
||
ScrDyn *(*invoke)(void *h, ScrDyn *self, const char *method, ScrDyn *const *args, size_t argc, const char *what);
|
||
/* Property READ with a static equivalent (req.url, res.statusCode):
|
||
* +1 boxed value; NULL = no such modeled property (the caller answers
|
||
* the undefined singleton — the checked-dynamic tree's own-property stance); may throw
|
||
* the loud ladder for real-but-unmodeled names (NULL + pending). */
|
||
ScrDyn *(*get)(void *h, const char *key, size_t key_len);
|
||
/* Property WRITE with a static equivalent (res.statusCode = 200):
|
||
* true = handled (value borrowed); false = not a modeled property (the
|
||
* caller throws the loud ladder — Node would take an expando, but a
|
||
* silent per-box expando would break handle identity). May throw its
|
||
* own catchable error (returns true with the exception pending). */
|
||
bool (*set)(void *h, const char *key, size_t key_len, const ScrDyn *value);
|
||
/* Cross-unit pipe DESTINATION hook (`socket.pipe(res)` — the source's
|
||
* unit cannot name the destination's entry points without breaking
|
||
* link gating): the destination accepts a source handle box, or
|
||
* answers false for a pairing it does not model (the caller's loud
|
||
* fence). NULL when the tag accepts no pipes. */
|
||
bool (*pipe_from)(void *dst, const ScrDyn *src);
|
||
/* Immutable native iterables may drain into a fresh array. Mutable or
|
||
* effectful iterators need a separate step/close protocol. NULL = not iterable. */
|
||
ScrDyn *(*iter_pack)(void *h);
|
||
} ScrDynHandleOps;
|
||
|
||
ScrDyn *scr_intl_segmenter_new(void);
|
||
ScrStr *scr_process_builtin_id(ScrDyn *id, ScrArr *known);
|
||
ScrDyn *scr_process_builtin_module(ScrStr *id, ScrDyn *getter);
|
||
ScrDyn *scr_process_builtin_unsupported(ScrStr *id, ScrStr *member);
|
||
ScrDyn *scr_global_native(ScrArr *known);
|
||
ScrDyn *scr_process_stdio(double fd);
|
||
|
||
void scr_dyn_handle_install(ScrDynHandleTag tag, const ScrDynHandleOps *ops);
|
||
void scr_file_handle_dyn_install(void);
|
||
/* The tag's class display name ("IncomingMessage") — error texts across
|
||
* units; answers "object" for an uninstalled tag (error paths only). */
|
||
const char *scr_dyn_handle_cls(const ScrDyn *d);
|
||
/* The installed ops for a HANDLE box (aborts on a missing install — an
|
||
* internal error: emitted programs install a unit's ops whenever they
|
||
* can box its handles). The dispatch unit's entry into the registry. */
|
||
const ScrDynHandleOps *scr_dyn_handle_ops_of(const ScrDyn *d);
|
||
/* Boxes a handle by reference (+1 box; retains h through the tag's ops).
|
||
* The tag MUST be installed — emitted programs install a unit's ops
|
||
* whenever they can box its handles. */
|
||
ScrDyn *scr_dyn_new_handle(void *h, ScrDynHandleTag tag);
|
||
/* dynCheck extraction (`u as IncomingMessage`, a boxed listener's typed
|
||
* parameter): tag match answers the RETAINED pointer (+1 — reference
|
||
* identity, no copy); anything else throws the path-annotated catchable
|
||
* TypeError and returns NULL. */
|
||
void *scr_dyn_handle_unbox(const ScrDyn *d, ScrDynHandleTag tag, const ScrDynPath *path, const char *want);
|
||
/* Keyed read on a HANDLE box (the emitted sc_dyn_key_get's arm): the
|
||
* tag's modeled properties answer boxed values; everything else answers
|
||
* the undefined singleton (the checked-dynamic tree's own-property stance — SEMANTICS.md)
|
||
* unless the ops fence it loudly. +1, or NULL with a pending exception. */
|
||
ScrDyn *scr_dyn_handle_key_get(const ScrDyn *d, const ScrStr *k);
|
||
|
||
/* ── promises in the checked-dynamic tree (SCR_DYN_PROMISE) ────────────────────────────
|
||
* Boxes by REFERENCE (+1 box; retains p). The boundary contract: p
|
||
* settles with a dyn payload — promise<dyn> boxes directly; other inner
|
||
* types go through scr_dyn_new_promise_adapting, which parks an emitted
|
||
* payload-converting adapter (the Promise.race cb-waiter machinery) on
|
||
* `src` and boxes the fresh destination promise instead. Rejections copy
|
||
* raw through the same machinery (reasons are dynamically tagged) and
|
||
* count as HANDLED on src — the boxed destination is the tracked one,
|
||
* like a JS .then chain. */
|
||
ScrDyn *scr_dyn_new_promise(ScrPromise *p); /* scr_async_dyn.c (gated) */
|
||
/* The allocator view the gated boxes use; installs the release arm's
|
||
* promise-release edge (runtime-internal). */
|
||
ScrDyn *scr_dyn_alloc_promise(void (*release_fn)(ScrPromise *p));
|
||
extern void (*scr_dyn_promise_release_fn)(ScrPromise *p);
|
||
ScrDyn *scr_dyn_new_promise_adapting(ScrPromise *src,
|
||
void (*adapt)(ScrPromise *dst, ScrPromise *src));
|
||
/* BORROWED peek at the boxed promise; NULL when d is not a promise box. */
|
||
ScrPromise *scr_dyn_promise_of(const ScrDyn *d);
|
||
|
||
/* ── island values in the checked-dynamic tree (SCR_DYN_JSVAL) ─────────────────────────
|
||
* Engine routing ops for JSVAL nodes, installed by the gated constructor
|
||
* (scr_dyn_from_jsval, scr_island.c — the scr_dyn_alloc_promise hook
|
||
* story: JSVAL nodes exist only after the constructor ran, so the ops
|
||
* are always installed when a dispatch arm meets the kind, and a
|
||
* dynamic-free link never references engine symbols). Contracts mirror
|
||
* the scr_jsval_* entries they route to. */
|
||
typedef struct ScrDynJsvalOps {
|
||
void (*release)(ScrJsval *cell);
|
||
ScrStr *(*type_of)(ScrJsval *cell); /* engine typeof; +1, never throws */
|
||
bool (*truthy)(ScrJsval *cell); /* engine ToBoolean; never throws */
|
||
/* String(v) in the engine (the full ToString protocol — user toString
|
||
* runs, its throw bridges): +1, or NULL with the exception pending. */
|
||
ScrStr *(*to_str)(ScrJsval *cell);
|
||
bool (*strict_eq)(ScrJsval *a, ScrJsval *b); /* engine ===; never throws */
|
||
bool (*is_array)(ScrJsval *cell); /* Array.isArray, engine-side */
|
||
bool (*is_error)(ScrJsval *cell); /* native Error instance, engine-side */
|
||
/* ── the routed operation set (lane dyn-routing-ops) ────────────────
|
||
* Each routes to the engine at the moment of use and converts at the
|
||
* boundary: dyn ARGUMENTS cross through scr_jsval_from_dyn (wrapped
|
||
* cells unwrap by reference, dyn data deep-copies, dyn FUNC boxes
|
||
* cross through the generic host-function shim), engine RESULTS come
|
||
* back through scr_dyn_from_jsval (scalar-normalizing). Fallible ops
|
||
* bridge the ENGINE's exception catchably and answer NULL/false/-1. */
|
||
ScrDyn *(*key_get)(ScrJsval *cell, const ScrStr *k); /* o[k]; +1 or NULL pending */
|
||
bool (*key_set)(ScrJsval *cell, const ScrStr *k, const ScrDyn *v); /* false = pending */
|
||
ScrDyn *(*call)(ScrJsval *cell, ScrDyn *const *args, size_t argc); /* f(...); +1 or NULL pending */
|
||
/* o.m(...) — the ENGINE's own prototypes run (JS-exact flatMap/map/
|
||
* forEach/...). A missing or non-callable member throws Node's
|
||
* "<what> is not a function" (the call site's spelling — V8's text,
|
||
* front-run before the engine's terser claim). */
|
||
ScrDyn *(*invoke)(ScrJsval *cell, const char *method, ScrDyn *const *args, size_t argc, const char *what);
|
||
bool (*is_nullish)(ScrJsval *cell); /* engine undefined/null; never throws
|
||
* (always false today — the wrap
|
||
* constructor scalar-normalizes) */
|
||
/* Object.keys/values/entries (mode 0/1/2) as a NATIVE dyn array (+1):
|
||
* keys are dyn strings, values wrap per element, entries are native
|
||
* dyn pairs. NULL with the engine's exception pending on refusal. */
|
||
ScrDyn *(*obj_walk)(ScrJsval *cell, int mode);
|
||
int (*has_own)(ScrJsval *cell, const ScrStr *k); /* 0/1; -1 = pending */
|
||
/* Object.assign(target, src) with the ENGINE target: src converts per
|
||
* member semantics (a wrapped src spreads by reference; dyn data
|
||
* enters as the usual deep copy). false = pending. */
|
||
bool (*assign)(ScrJsval *cell, const ScrDyn *src);
|
||
/* JSON.stringify text of the engine value (+1) — the engine's own
|
||
* stringify (toJSON protocols, cycle TypeErrors). NULL + pending when
|
||
* not JSON-representable. */
|
||
ScrStr *(*to_json)(ScrJsval *cell);
|
||
/* Drain the ENGINE's own iterator protocol into a fresh dyn array
|
||
* (elements wrap back scalar-normalized) — the for-of/destructuring/
|
||
* spread arm over a wrapped value. The guard's TypeError wording on a
|
||
* non-iterable: spread true takes V8's spread-call text; otherwise the
|
||
* compile-time spelling `spell` verbatim when non-NULL (the named-
|
||
* source form), else the kind wording. An iterating getter/next throw
|
||
* bridges with the ENGINE's message. +1, or NULL with the exception
|
||
* pending. */
|
||
ScrDyn *(*iter_drain)(ScrJsval *cell, bool spread, const ScrStr *spell);
|
||
} ScrDynJsvalOps;
|
||
|
||
/* The allocator view the gated constructor uses (installs the ops);
|
||
* ownership of `cell` MOVES in (the caller retains first). */
|
||
ScrDyn *scr_dyn_alloc_jsval(ScrJsval *cell, const ScrDynJsvalOps *ops);
|
||
/* The installed ops (traps on a missing install — impossible unless a
|
||
* JSVAL node was forged without the constructor). */
|
||
const ScrDynJsvalOps *scr_dyn_jsval_ops(void);
|
||
/* dynTest arms that need the ENGINE's answer on a JSVAL node, or the
|
||
* materialized answer for a live typed-reference capsule. Each answers
|
||
* false for every other kind (callers test unconditionally — the emitted
|
||
* narrowing tests stay branch-free). Never throw. */
|
||
bool scr_dyn_isl_typeof_is(const ScrDyn *d, const char *name);
|
||
bool scr_dyn_isl_is_array(const ScrDyn *d);
|
||
bool scr_dyn_isl_is_error(const ScrDyn *d);
|
||
/* The JSVAL honesty ladder: when d is a JSVAL node, THROWS the catchable
|
||
* "<what> on an island value held in 'unknown' is not supported yet"
|
||
* Error and returns false; every other kind returns false untouched.
|
||
* Callers gate un-armed operations with it — never a silent wrong
|
||
* answer (the retired fence-box bug). */
|
||
bool scr_dyn_isl_fence(const ScrDyn *d, const char *what);
|
||
/* The emitted keyed READ's JSVAL arm (sc_dyn_key_get): routes o[k] to the
|
||
* engine through the installed ops and wraps the result back (+1, scalars
|
||
* normalized) — the retired `.length -> fence` row. d MUST be a JSVAL
|
||
* node; NULL with the engine's exception bridged catchably. */
|
||
ScrDyn *scr_dyn_isl_key_get(const ScrDyn *d, const ScrStr *k);
|
||
/* The `??`/optional-chain nullish test over a dyn value: UNDEF/NULL
|
||
* native, JSVAL through the engine's own test (defensively — the wrap
|
||
* constructor scalar-normalizes engine null/undefined away), every other
|
||
* kind false. Never throws. */
|
||
bool scr_dyn_is_nullish(const ScrDyn *d);
|
||
/* scr_dyn_isl_tostr_buf (the display walkers' JSVAL arm) is declared
|
||
* with the ScrJsonBuf surface below. */
|
||
|
||
/* ── the ambient receiver (JS `this` inside listener/callback bodies) ──
|
||
* Node calls a handle's listeners with `this` bound to the emitting
|
||
* handle (server.listen(0, function() { this.address().port })), and a
|
||
* dyn OBJ method call binds the object. Compiled closures carry no
|
||
* receiver parameter; instead the CALLING site binds the receiver for
|
||
* the call's synchronous extent (push/pop nest strictly — the runtime is
|
||
* single-threaded and fires listeners synchronously) and a plain-function
|
||
* `this` read (JS entries only — TypeScript keeps the noImplicitThis
|
||
* fence) answers the innermost binding: the emitted libCall dyn.this.
|
||
* With no binding the read answers the strict-mode undefined singleton,
|
||
* exactly the old stance. The receiver survives into nested plain CALLS
|
||
* made during the window (where Node would re-bind undefined) and does
|
||
* NOT survive a resumed async body — both are documented divergences;
|
||
* the payoff is that test/common's mustCall wrapper (`return
|
||
* fn.apply(this, arguments)`) forwards the receiver for free. */
|
||
void scr_dyn_this_push(void *h, ScrDynHandleTag tag); /* h BORROWED (the firing site holds it); NULL binds undefined */
|
||
void scr_dyn_this_push_dyn(const ScrDyn *v); /* retained for the window */
|
||
void scr_dyn_this_pop(void);
|
||
/* The innermost binding as a dyn value (+1): a boxed handle, the pushed
|
||
* dyn value, or the undefined singleton (empty stack, NULL handle, or a
|
||
* handle whose tag has no installed ops — a unit that fires without its
|
||
* dyn half never binds). */
|
||
ScrDyn *scr_dyn_this_get(void);
|
||
|
||
/* The data-chunk encoding window (setEncoding — scr_json.c's note): the
|
||
* firing site opens/closes it around a 'data' pass; scr_dyn_new_chunk
|
||
* answers a Buffer-flavored bytes box, or a string inside the window. */
|
||
void scr_dyn_chunk_enc(bool utf8);
|
||
ScrDyn *scr_dyn_new_chunk(const ScrBytes *b);
|
||
|
||
/* util.format's %j over a dyn value: the JSON.stringify text (+1),
|
||
* "undefined" when the root drops (undefined/function), or NULL with a
|
||
* pending exception (a runtime handle inside the tree fences loudly). */
|
||
ScrStr *scr_dyn_format_j(const ScrDyn *d);
|
||
/* Node's ERR_INVALID_ARG_TYPE listener gate over a dyn value — shared by
|
||
* the emitter unit's checked-dynamic registrations and the handle
|
||
* dispatchers' .on(...) paths (both render errors.js's
|
||
* determineSpecificType shapes). Throws when cb is not a function kind. */
|
||
void scr_dyn_check_listener(const ScrDyn *cb, const char *argname);
|
||
/* errors.js's determineSpecificType tail ("type number (5)", "an
|
||
* instance of Object") rendered into buf when payload-carrying; the
|
||
* generic ERR_INVALID_ARG_TYPE thrower over it (`expected` is the whole
|
||
* "of type ..." clause). The handle dispatchers' per-arg gates. */
|
||
const char *scr_dyn_specific_type(const ScrDyn *v, char *buf, size_t cap);
|
||
/* ERR_INVALID_ARG_TYPE with the runtime-rendered Received tail (the
|
||
* error.argTypeThrow libCall). Borrows all three; always throws. */
|
||
void scr_throw_arg_type(const ScrStr *argname, const ScrStr *expected, const ScrDyn *got);
|
||
void scr_dyn_arg_type_fail(const char *argname, const char *expected, const ScrDyn *got);
|
||
/* The property flavor ("The \"options.x\" property must be ...") — the
|
||
* option-bag validators' gate (error.propTypeThrow). Always throws. */
|
||
void scr_throw_prop_type(const ScrStr *name, const ScrStr *expected, const ScrDyn *got);
|
||
void scr_dyn_prop_type_fail(const char *name, const char *expected, const ScrDyn *got);
|
||
/* Node's ERR_INVALID_ARG_VALUE ("The argument 'encoding' is invalid
|
||
* encoding. Received 'no'") — reason NULL renders "is invalid".
|
||
* TypeError; always throws catchably. */
|
||
void scr_dyn_arg_value_fail(const char *name, const char *reason, const ScrDyn *got);
|
||
/* The ERR_INVALID_ARG_VALUE/%j "Received" renderer (inspect-lite:
|
||
* strings quote, scalars print plain, deep shapes sketch). */
|
||
const char *scr_dyn_inspect_lite(const ScrDyn *v, char *buf, size_t cap);
|
||
/* A ladder's post-validation refuse: throws the compiler-rendered SC2020
|
||
* statement-fence text verbatim (Node's validation errors run first). */
|
||
void scr_throw_lowering_fence(const ScrStr *msg);
|
||
/* ERR_OUT_OF_RANGE's "Received" number rendering (Node's
|
||
* addNumericalSeparator underscores past 2^32) — scr_bytes.c's renderer,
|
||
* shared by the fs/net/tls option-ladder validators. */
|
||
size_t scr_num_received(double v, char out[48]);
|
||
/* Listener-closure builders for the handle dispatchers' .on(...) paths:
|
||
* a runtime-built ScrClosure whose capture is the boxed dyn listener and
|
||
* whose invoke boxes the event tuple back into the checked-dynamic tree and calls through
|
||
* the checked-dynamic machinery. The matching fn-pointer shapes are the
|
||
* per-event thunk types the registration entries take (fire0's zero-arg
|
||
* convention / ScrNetDataFn / ScrChildErrFn). cb borrowed; result +1. */
|
||
ScrClosure *scr_dyn_listener_closure0(const ScrDyn *cb);
|
||
ScrClosure *scr_dyn_listener_closure_data(const ScrDyn *cb);
|
||
ScrClosure *scr_dyn_listener_closure_err(const ScrDyn *cb);
|
||
/* The generic pair for fire thunks the OWNING units define (handle-boxing
|
||
* tuples this unit cannot spell: 'connection' sockets, 'request' pairs):
|
||
* the same one-capture closure with a caller-supplied fire, and the
|
||
* capture read those fires start from (+1). */
|
||
ScrClosure *scr_dyn_listener_closure_fn(const ScrDyn *cb, void *fire);
|
||
ScrDyn *scr_dyn_listener_fn(ScrClosure *cb);
|
||
typedef void (*ScrDynListenerDataFn)(ScrClosure *cb, ScrBytes *chunk);
|
||
typedef void (*ScrDynListenerErrFn)(ScrClosure *cb, ScrStr *msg);
|
||
extern void scr_dyn_listener_fire0(ScrClosure *cb);
|
||
extern void scr_dyn_listener_fire_data(ScrClosure *cb, ScrBytes *chunk);
|
||
extern void scr_dyn_listener_fire_err(ScrClosure *cb, ScrStr *msg);
|
||
|
||
/* Output buffer for the compiler-emitted type-directed JSON serializers.
|
||
* Value-typed and stack-allocated by the emitted code; _finish hands the
|
||
* bytes over as a +1 ScrStr and frees the buffer storage (including the
|
||
* circular-detection stack below). */
|
||
struct ScrJsonSeenEnt;
|
||
typedef struct {
|
||
char *data;
|
||
size_t len;
|
||
size_t cap;
|
||
/* Circular-structure detection for RECURSIVE record types (cyclic
|
||
* values must throw Node's exact TypeError, never recurse forever):
|
||
* the stack of container values currently being serialized, each with
|
||
* its outgoing edge label (the member being written). Only walkers over
|
||
* cycle-CAPABLE types (the collector-fixpoint set) maintain it; acyclic
|
||
* types keep the zero-cost path. */
|
||
struct ScrJsonSeenEnt *seen;
|
||
size_t seen_len;
|
||
size_t seen_cap;
|
||
} ScrJsonBuf;
|
||
|
||
void scr_jb_init(ScrJsonBuf *b);
|
||
/* Append one borrowed runtime string verbatim (no JSON quoting). */
|
||
void scr_jb_put_str(ScrJsonBuf *b, const ScrStr *s);
|
||
/* Push a container onto the circular-detection stack before serializing
|
||
* its members. If `v` is already ON the stack, throws V8's exact
|
||
* "Converting circular structure to JSON" TypeError (the --> starting at /
|
||
* |property/index hops/--- closes the circle rendering, ellipsis rules
|
||
* included) and returns false — the caller returns immediately; the
|
||
* emitted stringify site runs the pending check. `is_array` picks the
|
||
* constructor name in the message ('Array' for arrays AND tuple shapes —
|
||
* their JS values are arrays — 'Object' for records). */
|
||
bool scr_jb_enter(ScrJsonBuf *b, const void *v, bool is_array);
|
||
void scr_jb_leave(ScrJsonBuf *b);
|
||
/* Record the edge currently being serialized on the stack top: a static
|
||
* property name (emitted C literal), an overflow key (borrowed for the
|
||
* duration of the member write), or an array/tuple index. */
|
||
void scr_jb_edge_prop(ScrJsonBuf *b, const char *name);
|
||
void scr_jb_edge_key(ScrJsonBuf *b, const ScrStr *key);
|
||
void scr_jb_edge_idx(ScrJsonBuf *b, size_t i);
|
||
|
||
/* Circular guard for the compiler-emitted typed→dyn converters (sc_td_*
|
||
* over cycle-capable containers): enter TRAPS on a value already being
|
||
* converted (a cyclic value has no finite dyn copy — Node shares the
|
||
* reference instead; SEMANTICS.md), else pushes. */
|
||
void scr_dyn_from_enter(const void *v);
|
||
void scr_dyn_from_leave(void);
|
||
void scr_jb_putc(ScrJsonBuf *b, char c);
|
||
void scr_jb_puts(ScrJsonBuf *b, const char *s);
|
||
/* JS JSON.stringify number rules: NaN/±Infinity → null, -0 → 0, else
|
||
* shortest-roundtrip via scr_f64_to_str. */
|
||
void scr_jb_put_f64(ScrJsonBuf *b, double v);
|
||
/* Quoted + escaped JSON string: \" \\ \n \r \t \b \f, other control chars
|
||
* as \u00XX, everything else (UTF-8 included) verbatim — exactly the JS
|
||
* JSON.stringify escape set for well-formed strings. */
|
||
void scr_jb_put_json_str(ScrJsonBuf *b, const ScrStr *s);
|
||
/* String(v) of a JSVAL node appended into b (the emitted sc_ds display
|
||
* walkers' arm; scr_dyn_display/to_string route here too): the engine's
|
||
* ToString. A bridged failure (throwing user toString, a symbol) leaves
|
||
* the exception PENDING and appends nothing — the loud path, never a
|
||
* fabricated rendering. */
|
||
void scr_dyn_isl_tostr_buf(ScrJsonBuf *b, const ScrDyn *d);
|
||
/* JSON-serialize a dyn value into the buffer — the sc_jw_* walker for the
|
||
* dyn leaves the compiler cannot type: object members holding undefined
|
||
* DROP, array slots holding undefined print null (exactly Node). */
|
||
void scr_jb_put_dyn(ScrJsonBuf *b, const ScrDyn *d);
|
||
ScrStr *scr_jb_finish(ScrJsonBuf *b); /* returns +1; frees the buffer */
|
||
|
||
void *scr_dyn_retain_v(void *d);
|
||
void scr_dyn_release_v(void *d);
|
||
void scr_dyn_trace_v(void *d, ScrTraceVisit visit, void *ctx);
|
||
|
||
#ifdef SCR_RC_AUDIT
|
||
long scr_dyn_live_count(void);
|
||
#endif
|
||
|
||
/* ── class instances ─────────────────────────────────────────────────
|
||
* Objects are per-class C structs emitted by the compiler (shared header:
|
||
* `size_t rc` first), with per-class new/retain/release helpers also
|
||
* emitted. The runtime provides only the RC-audit hooks those helpers call.
|
||
*/
|
||
void scr_obj_alloc_note(void);
|
||
void scr_obj_free_note(void);
|
||
#ifdef SCR_RC_AUDIT
|
||
long scr_obj_live_count(void);
|
||
#endif
|
||
|
||
/* ── async: promises, fibers, event loop ────────────────────────────
|
||
* Async function bodies run on stackful fibers; `await` parks the fiber on
|
||
* a promise; a dependency-free loop (microtasks before timers, FIFO
|
||
* tiebreaks) drives everything after %main returns. See scr_async.c.
|
||
*/
|
||
ScrPromise *scr_promise_new(void);
|
||
ScrPromise *scr_promise_retain(ScrPromise *p);
|
||
void scr_promise_release(ScrPromise *p);
|
||
void *scr_promise_retain_v(void *p);
|
||
void scr_promise_release_v(void *p);
|
||
|
||
typedef struct ScrFiber ScrFiber;
|
||
/* Spawn + run eagerly to the first suspension; returns the promise, +1. */
|
||
ScrPromise *scr_async_spawn(void (*entry)(ScrFiber *, void *), void *argpack);
|
||
/* Runtime-authored C waiters cannot themselves become LLVM switched
|
||
* coroutines on wasm32. Spawn after `dependency` settles so their first
|
||
* (and only) await is extraction from an already-settled promise, while
|
||
* retaining the ordinary promise-job hop on every target. +1. */
|
||
ScrPromise *scr_async_spawn_after(ScrPromise *dependency,
|
||
void (*entry)(ScrFiber *, void *),
|
||
void *argpack);
|
||
|
||
/* wasm32-wasi lowers async functions and generators through LLVM switched
|
||
* coroutines instead of the native stack-switching implementations. The
|
||
* first two adapters are emitted into the program module; the remaining
|
||
* helpers let scr_async.c register, queue, and finish those coroutine
|
||
* frames through the same ScrFiber scheduler. These declarations are part
|
||
* of the LLVM/runtime ABI even though native targets never call them. */
|
||
void scr_wasi_coro_resume(void *handle);
|
||
void scr_wasi_coro_destroy(void *handle);
|
||
void scr_wasi_coro_started(void *handle);
|
||
void scr_wasi_await_prepare(ScrFiber *self, ScrPromise *p);
|
||
void scr_wasi_await_hop_prepare(ScrFiber *self);
|
||
bool scr_wasi_module_await_prepare(ScrFiber *self, ScrPromise *p);
|
||
void scr_wasi_async_finish(ScrFiber *self);
|
||
void scr_wasi_gen_finish(ScrFiber *self);
|
||
|
||
double scr_await_f64(ScrPromise *p); /* rejected promises re-throw */
|
||
bool scr_await_bool(ScrPromise *p);
|
||
ScrStr *scr_await_str(ScrPromise *p); /* +1 */
|
||
void *scr_await_ref(ScrPromise *p); /* +1 via the stored retain */
|
||
void scr_await_void(ScrPromise *p);
|
||
/* Internal ESM dependency wait: parks while pending but, unlike a
|
||
* JavaScript await expression, does not hop when already settled. */
|
||
void scr_module_await(ScrPromise *p);
|
||
/* After the root-aware event loop returns, inspect the executable's async
|
||
* module-root promise without another microtask hop: 0 = fulfilled,
|
||
* 1 = rejected, 13 = still pending with no ref'd work capable of settling
|
||
* it (Node's unsettled top-level-await exit status). A rejected root is
|
||
* marked observed here but re-thrown separately; earlier-checkpoint
|
||
* rejections were already decided by the loop and same-checkpoint
|
||
* competitors are suppressed by the executable-module verdict. */
|
||
int scr_promise_finish_top_level(ScrPromise *p);
|
||
void scr_promise_rethrow_top_level(ScrPromise *p);
|
||
/* The checked-dynamic tree-crossing await (SCR_DYN_PROMISE's boundary contract): the
|
||
* payload as a dyn value (+1; void fulfillments answer the undefined
|
||
* value), or NULL with the rejection re-thrown into the awaiter. */
|
||
ScrDyn *scr_await_dyn(ScrPromise *p);
|
||
/* `await v` over a checked-dynamic VALUE: dyn promises adopt, everything
|
||
* else takes JS's one-hop non-thenable await and answers itself (+1). */
|
||
ScrDyn *scr_await_dyn_value(ScrDyn *v);
|
||
/* .then/.catch/.finally over a dyn promise (scr_dyn_invoke's promise arm
|
||
* and the compiled dyn-receiver path): one reaction fiber per
|
||
* registration — awaits src, runs the checked-dynamic tree handler (non-callable handlers
|
||
* pass the settlement through; a returned dyn promise is adopted), and
|
||
* settles a fresh result promise, answered BOXED (+1). Microtask-exact
|
||
* ordering rides the fiber machinery. */
|
||
ScrDyn *scr_dyn_promise_then(ScrPromise *src, ScrDyn *onf, ScrDyn *onr, ScrDyn *onfin);
|
||
/* ── AsyncLocalStorage (node:async_hooks — scr_async.c) ───────────────
|
||
* Stores are process-lived f64 handles; the CONTEXT is an immutable
|
||
* refcounted snapshot of (store → dyn value) entries riding the fiber
|
||
* machinery: one active-slot pointer (the exc-cell pattern) swapped at
|
||
* every fiber switch, inherited by spawned fibers (Node's init-time
|
||
* capture). enter answers the PREVIOUS snapshot (owned) for restore —
|
||
* strict nesting is the callers' contract (run/runStores wrap calls). */
|
||
/* The snapshot layout (full declaration — the ScrExcCell stance: the
|
||
* fiber machinery in scr_async.c owns the active slot and the RC pair;
|
||
* the gated API TU (scr_async_dyn.c) builds and reads snapshots). */
|
||
typedef struct {
|
||
double id;
|
||
ScrDyn *value; /* owned */
|
||
} ScrAlsEntry;
|
||
typedef struct ScrAlsCtx {
|
||
size_t rc;
|
||
size_t len;
|
||
ScrAlsEntry entries[]; /* flexible */
|
||
} ScrAlsCtx;
|
||
/* Runtime-internal (the fiber machinery's always-linked core). */
|
||
extern ScrAlsCtx **scr_als_active;
|
||
ScrAlsCtx *scr_als_ctx_retain(ScrAlsCtx *c);
|
||
void scr_als_ctx_release(ScrAlsCtx *c);
|
||
double scr_als_new(void);
|
||
ScrDyn *scr_als_get(double id); /* +1; the undefined value when unset */
|
||
ScrAlsCtx *scr_als_enter(double id, ScrDyn *value); /* borrows value; prev out (owned) */
|
||
ScrAlsCtx *scr_als_enter_absent(double id); /* exit()'s cleared arm */
|
||
void scr_als_restore(ScrAlsCtx *prev); /* moves prev back in */
|
||
void scr_als_enter_with(double id, ScrDyn *value); /* no restore point (enterWith) */
|
||
void scr_als_disable(double id);
|
||
/* run/exit: enter (or clear), call the dyn function with the forwarded
|
||
* argument vector, restore (the finally). Result +1 or NULL pending. */
|
||
ScrDyn *scr_als_run(double id, ScrDyn *value, ScrDyn *fn, ScrDyn *args);
|
||
ScrDyn *scr_als_exit_run(double id, ScrDyn *fn, ScrDyn *args);
|
||
|
||
/* process.on/once('unhandledRejection', fn): dyn listeners dispatched
|
||
* per never-observed rejection at the end of its nextTick/microtask
|
||
* checkpoint — (reason, promise), suppressing the default report and the
|
||
* exit-1. `once` auto-removes after one delivery; off removes by closure
|
||
* identity (the warning registry's story). Throws Node's
|
||
* ERR_INVALID_ARG_TYPE on a non-function. */
|
||
void scr_process_on_unhandled_rejection(ScrDyn *fn, bool once);
|
||
void scr_process_off_unhandled_rejection(ScrDyn *fn);
|
||
/* process.on/once/off('rejectionHandled', fn): the sibling registry. A
|
||
* promise handled after its unhandledRejection delivery fires once, with
|
||
* the promise as Node's payload. */
|
||
void scr_process_on_rejection_handled(ScrDyn *fn, bool once);
|
||
void scr_process_off_rejection_handled(ScrDyn *fn);
|
||
/* The checkpoint delivery hook the registration above installs
|
||
* (scr_async_dyn.c → scr_report_unhandled_rejections; NULL = default
|
||
* report). Runtime-internal. */
|
||
extern bool (*scr_urj_deliver_fn)(ScrPromise *p);
|
||
/* The late-handled hook (scr_async_dyn.c installs it when a
|
||
* 'rejectionHandled' listener registers): scr_async.c calls it when a
|
||
* promise the report already delivered as unhandled gains a handler.
|
||
* Runtime-internal. */
|
||
extern void (*scr_rjh_notify_fn)(ScrPromise *p);
|
||
/* The attach-time handled mark (a dyn then/catch carrying a rejection
|
||
* handler, or the module loader taking ownership of an evaluation
|
||
* promise): marks pending and rejected sources observed at Node's attach
|
||
* moment, firing the late-handled hook when the report already delivered
|
||
* it. Runtime-internal. */
|
||
void scr_promise_mark_handled(ScrPromise *p);
|
||
/* A rejected promise's reason as a dyn value (identity-preserving for
|
||
* dyn payloads and %Error instances). +1. */
|
||
ScrDyn *scr_promise_reason_dyn(const ScrPromise *p);
|
||
/* process warnings (scr_lib.c — always linked so any unit can emit a
|
||
* deprecation): dyn listeners plus Node's default stderr report
|
||
* ("(node:pid) [CODE] Name: message" and a detail second line). Emission
|
||
* is SYNCHRONOUS at the call (Node defers a tick — SEMANTICS.md 138's
|
||
* precedent). scr_process_emit_warning takes the ARGUMENT VECTOR as one
|
||
* dyn array and applies Node's full grammar/TypeErrors; scr_emit_warning
|
||
* is the C-side deprecation entry. */
|
||
void scr_process_on_warning(ScrDyn *fn);
|
||
void scr_process_off_warning(ScrDyn *fn);
|
||
void scr_process_emit_warning(ScrDyn *args);
|
||
void scr_emit_warning(const char *name, const char *code, ScrStr *message);
|
||
/* The promise-or-absent await's unit arm: one microtask hop, like JS's
|
||
* await of any non-thenable (and like awaiting a settled promise). */
|
||
void scr_await_hop(void);
|
||
|
||
void scr_promise_fulfill_f64(ScrPromise *p, double v);
|
||
void scr_promise_fulfill_bool(ScrPromise *p, bool v);
|
||
void scr_promise_fulfill_str(ScrPromise *p, ScrStr *v); /* moves in */
|
||
/* trace: non-NULL iff the payload type carries a cycle header (a promise
|
||
* fulfilled with a cycle-capable value is itself a cycle member candidate:
|
||
* its trace visits the payload). */
|
||
void scr_promise_fulfill_ref(ScrPromise *p, void *v, void *(*retain)(void *), void (*release)(void *), ScrTraceFn trace);
|
||
void scr_promise_fulfill_void(ScrPromise *p);
|
||
|
||
/* Reject `p` with the exception pending in the active cell (moved out —
|
||
* the cell resets); wakes waiters, enters the unhandled ledger. No-op
|
||
* with a clean cell; an already-settled `p` just clears the cell. The
|
||
* island promise bridge's rejection half (scr_island.c). */
|
||
void scr_promise_reject_pending(ScrPromise *p);
|
||
|
||
/* Mint an ALREADY-SETTLED promise from a just-run synchronous operation:
|
||
* a pending exception in the active cell moves in as the REJECTION (the
|
||
* cell resets — callers' pending checks see it clean) and the payload is
|
||
* dropped; otherwise the payload fulfills. The fs/promises bridge. */
|
||
ScrPromise *scr_promise_settled_str(ScrStr *v); /* moves v in */
|
||
ScrPromise *scr_promise_settled_void(void);
|
||
ScrPromise *scr_promise_settled_ref(void *v, void *(*retain)(void *), void (*release)(void *), ScrTraceFn trace);
|
||
|
||
/* ── Promise.race (compiler-emitted combinator plumbing) ──────────────
|
||
* race_add: settle the result from an already-settled entry, or park a
|
||
* callback waiter that fires inside the entry's settle — fulfillments run
|
||
* `adapt` (an emitted per-type adapter building the result's inner type
|
||
* from the entry's payload; scr_promise_adapt_copy is the same-type
|
||
* one), rejections copy raw and count as HANDLED on the entry. The
|
||
* payload accessors return retained/by-value fulfillment payloads for
|
||
* the emitted adapters (losing entries keep their settlements). */
|
||
void scr_promise_race_add(ScrPromise *race, ScrPromise *in,
|
||
void (*adapt)(ScrPromise *dst, ScrPromise *src));
|
||
|
||
/* ── Promise.all (compiler-emitted combinator plumbing) ───────────────
|
||
* BORROWS the Promise<T>[] entries array and the pre-capacity (empty,
|
||
* cap >= ps->len) values array — NULL values/store for Promise<void>
|
||
* entries, whose result fulfills void — retains what it keeps, and
|
||
* returns the result promise +1. Node-exact: values land at their INPUT
|
||
* index regardless of settlement order, the first rejection in
|
||
* SETTLEMENT order wins (later ones count as handled on their entries),
|
||
* and the empty array fulfills immediately. The store helpers are the
|
||
* per-element-kind payload writers the compiler picks from. */
|
||
ScrPromise *scr_promise_all(ScrArr *ps, ScrArr *values,
|
||
void (*store)(ScrArr *a, double i, ScrPromise *src));
|
||
void scr_promise_all_store_f64(ScrArr *a, double i, ScrPromise *src);
|
||
void scr_promise_all_store_bool(ScrArr *a, double i, ScrPromise *src);
|
||
void scr_promise_all_store_str(ScrArr *a, double i, ScrPromise *src);
|
||
void scr_promise_all_store_ref(ScrArr *a, double i, ScrPromise *src);
|
||
void scr_promise_adapt_copy(ScrPromise *dst, ScrPromise *src);
|
||
double scr_promise_payload_f64(ScrPromise *p);
|
||
bool scr_promise_payload_bool(ScrPromise *p);
|
||
ScrStr *scr_promise_payload_str(ScrPromise *p); /* +1 */
|
||
void *scr_promise_payload_ref(ScrPromise *p); /* +1 via the stored retain */
|
||
/* Thin payload views for the gated dyn-async TU (scr_async_dyn.c). */
|
||
int scr_promise_payload_kind(const ScrPromise *p); /* ScrExcKind */
|
||
bool scr_promise_payload_is_dyn(const ScrPromise *p);
|
||
double scr_promise_payload_num(const ScrPromise *p);
|
||
bool scr_promise_payload_flag(const ScrPromise *p);
|
||
bool scr_promise_await_settled(ScrPromise *p); /* park/hop; false = rethrown */
|
||
void scr_promise_payload_release(const ScrPromise *p, void *v);
|
||
|
||
void scr_set_timeout(ScrClosure *cb, double ms); /* cb ownership moves in */
|
||
/* setInterval/clearInterval: the handle is the number the fallback
|
||
* declarations promise (ids start at 1, so `if (handle)` narrows like
|
||
* Node's Timeout truthiness). A live interval keeps the loop alive;
|
||
* clearInterval removes it eagerly (clearing from inside its own callback
|
||
* works). An unknown handle is a no-op, like Node. */
|
||
double scr_set_interval(ScrClosure *cb, double ms); /* cb ownership moves in */
|
||
/* `new Promise(setImmediate)`: a fresh promise an armed immediate
|
||
* fulfills with the undefined dyn value (+1). */
|
||
ScrPromise *scr_immediate_promise(void);
|
||
void scr_clear_interval(double handle);
|
||
/* The island timer bridge (scr_web.c): a one-shot entry WITH a clear
|
||
* handle (island clearTimeout cancels; ids share the interval space so
|
||
* scr_clear_interval serves both), and the teardown sweep that releases
|
||
* every armed closure before the engine dies. */
|
||
double scr_set_timeout_handle(ScrClosure *cb, double ms); /* cb ownership moves in */
|
||
void scr_timers_teardown(void);
|
||
/* Timeout.unref()/ref()/hasRef(): loop-liveness bookkeeping over the timer
|
||
* handle (setTimeout/setInterval's id). unref drops the timer from the
|
||
* liveness count — it still fires if the loop runs on, but never keeps the
|
||
* process alive by itself (Node's semantics); ref restores it; hasRef
|
||
* answers the current state. Absent/NaN handles are tolerated no-ops. */
|
||
void scr_timer_unref(double handle);
|
||
void scr_timer_ref(double handle);
|
||
bool scr_timer_has_ref(double handle);
|
||
/* Timeout.refresh(): re-arm to now + the original delay. Works on armed
|
||
* heap entries and from inside the firing timer's own callback (the loop
|
||
* re-arms after the callback returns); a one-shot that already fired on
|
||
* an earlier turn is gone and no-ops (documented divergence). */
|
||
void scr_timer_refresh(double handle);
|
||
/* setImmediate/clearImmediate — Node's check phase: FIFO once per loop
|
||
* turn after due timers, immediates queued mid-phase wait for the next
|
||
* turn. The handle is an f64 id in its own space (clearTimeout of an
|
||
* Immediate is a no-op, like Node); the Immediate ref trio mirrors the
|
||
* Timeout one (unref'd pending immediates don't keep the loop alive and
|
||
* never fire once nothing reffed remains). scr_timers_teardown sweeps
|
||
* the immediate queue too. */
|
||
double scr_set_immediate(ScrClosure *cb); /* cb ownership moves in */
|
||
/* queueMicrotask: the callback enters the SAME FIFO promise continuations
|
||
* ride (one microtask order, like V8's queue); a throw is an UNCAUGHT
|
||
* exception, never a rejection. cb ownership moves in. The _dyn form
|
||
* (checked-dynamic arguments) throws Node's ERR_INVALID_ARG_TYPE
|
||
* synchronously on a non-function; borrowed. */
|
||
void scr_queue_microtask(ScrClosure *cb);
|
||
void scr_queue_microtask_dyn(const ScrDyn *cb);
|
||
/* setImmediate AS A dyn VALUE (the traceCallback shape): a minted dyn
|
||
* callable that schedules args[0](args[1..]) on the immediate queue and
|
||
* answers undefined; a non-function first argument throws Node's
|
||
* ERR_INVALID_ARG_TYPE. Result +1. */
|
||
struct ScrDyn *scr_set_immediate_dyn_value(void);
|
||
void scr_clear_immediate(double handle);
|
||
void scr_immediate_unref(double handle);
|
||
void scr_immediate_ref(double handle);
|
||
bool scr_immediate_has_ref(double handle);
|
||
/* process.nextTick: enqueue a zero-param callback on the user tick
|
||
* queue — drained BEFORE promise jobs at every loop checkpoint, to
|
||
* joint exhaustion with them (Node's tick-then-microtask order).
|
||
* Pending ticks keep the loop alive; ticks left queued at termination
|
||
* ('exit' listeners, the uncaught paths) never run and release in
|
||
* scr_timers_teardown — and in scr_nticks_teardown, which the
|
||
* exit-listener runner calls for ticks enqueued AFTER the loop's own
|
||
* teardown (they must never run yet must not leak). cb ownership moves
|
||
* in. */
|
||
void scr_next_tick(ScrClosure *cb);
|
||
/* A successful process.stdout/stderr.write completion on that same queue.
|
||
* The callback and its program-shaped Error | null adapter both MOVE into
|
||
* the entry; the adapter is invoked with NULL (Node's success argument). */
|
||
void scr_process_write_callback(ScrClosure *cb, ScrFsRenameFn fn);
|
||
/* A raw C-hook entry on the SAME queue: the stream unit enqueues one
|
||
* marker per deferred stream emission, so stream ticks and user
|
||
* nextTicks run in true FIFO order (in Node they are the same queue).
|
||
* Teardown drops markers without running them. */
|
||
void scr_next_tick_raw(void (*fn)(void));
|
||
void scr_nticks_teardown(void);
|
||
/* ── the events unit (scr_events.c — OPTIONAL, link-gated) ────────────
|
||
* Process signal/exit events and the piped-stdin surface. The unit links
|
||
* ONLY into binaries whose IR uses these surfaces (moduleUsesProcessEvents
|
||
* — the scr_regex/scr_fetch/scr_zlib precedent); the emitted main calls
|
||
* scr_events_install(), which fills the loop's nullable event hooks and
|
||
* the two scr_lib.c hooks below. Event-free builds pay zero bytes.
|
||
*
|
||
* Signal events (process.on/once/off of "SIGINT"/"SIGTERM"): sigaction +
|
||
* self-pipe, dispatched at loop turns as macrotasks. Watching replaces the
|
||
* default disposition; removing the last listener restores it. Signal
|
||
* listeners do NOT keep the loop alive (Node). `on` MOVES the callback in;
|
||
* `off` borrows and removes the first pointer-identical entry.
|
||
*
|
||
* The process 'exit' event: listeners run synchronously at normal
|
||
* termination (the unit's atexit) and inside process.exit() (through
|
||
* scr_process_exit_hook); the adapter receives the exit code (runtime
|
||
* thunks below for the 0-param and (code) shapes).
|
||
*
|
||
* process.stdin events (piped-stdin slice) and the for-await chunk
|
||
* source. Listener callbacks MOVE in; data adapters get the chunk
|
||
* BORROWED (the bytes thunk retains for the callee). next_chunk returns a
|
||
* +1 promise of the next chunk — the EMPTY bytes value is the done
|
||
* sentinel (a POSIX read never delivers an empty data chunk). While a
|
||
* consumer exists (data listener or parked chunk promise) stdin keeps the
|
||
* loop alive, like Node's flowing stdin. */
|
||
typedef struct ScrBytes ScrBytes; /* full definition below (C11 repeat) */
|
||
void scr_events_install(void);
|
||
void scr_signal_on(ScrStr *name, ScrDyn *cb, bool once);
|
||
void scr_signal_off(ScrStr *name, ScrDyn *cb);
|
||
void scr_process_on_exit(ScrClosure *cb, void (*fn)(ScrClosure *, double), bool once);
|
||
void scr_process_off_exit(ScrClosure *cb);
|
||
void scr_run_exit_listeners(double code);
|
||
void scr_exit_thunk0(ScrClosure *cb, double code);
|
||
void scr_exit_thunk_code(ScrClosure *cb, double code);
|
||
void scr_stdin_on_data(ScrClosure *cb, void (*fn)(ScrClosure *, ScrBytes *), bool once);
|
||
void scr_stdin_on_end(ScrClosure *cb, bool once);
|
||
void scr_stdin_on_error(ScrClosure *cb, ScrChildErrFn fn, bool once);
|
||
ScrPromise *scr_stdin_next_chunk(void); /* +1 */
|
||
void scr_stdin_destroy(void);
|
||
bool scr_stdin_pending(void);
|
||
/* node:readline's hooks into the stdin unit: removal by closure identity
|
||
* (close() detaches the shared consumer) and the ended probe (EOF seen or
|
||
* destroyed — a later createInterface is born dead). */
|
||
void scr_stdin_remove_data(ScrClosure *cb);
|
||
bool scr_stdin_ended(void);
|
||
|
||
/* ── node:readline (scr_readline.c, linked under the events gate) ──────
|
||
* The question/close slice, Node's semantics pinned under pipes (the file
|
||
* header has the full model): create answers the f64 interface handle;
|
||
* question writes the query to stdout and delivers the next line's text
|
||
* through the adapter (fn(cb, +1 answer)) — throwing Node's
|
||
* "readline was closed" on a closed interface (may-throw); close fires
|
||
* the 'close' listeners SYNCHRONOUSLY (Node's inline emit) and detaches
|
||
* the stdin consumer; onClose registers a zero-arg listener (moves).
|
||
* Answer adapters: thunk0 ignores the line, thunk_str passes it. */
|
||
double scr_rl_create(void);
|
||
void scr_rl_question(double id, const ScrStr *query, ScrClosure *cb /*moves*/,
|
||
void (*fn)(ScrClosure *, ScrStr *));
|
||
void scr_rl_close(double id);
|
||
void scr_rl_on_close(double id, ScrClosure *cb /*moves*/);
|
||
void scr_rl_answer_thunk0(ScrClosure *cb, ScrStr *answer);
|
||
void scr_rl_answer_thunk_str(ScrClosure *cb, ScrStr *answer);
|
||
void scr_stdin_data_thunk0(ScrClosure *cb, ScrBytes *chunk);
|
||
void scr_stdin_data_thunk_bytes(ScrClosure *cb, ScrBytes *chunk);
|
||
/* The loop-side registration (scr_async.c, always linked) and the
|
||
* scr_lib.c hooks the unit fills. The abnormal-exit code hint lives in
|
||
* scr_async.c so the uncaught/unhandled reporters can note it whether or
|
||
* not the unit is present. */
|
||
void scr_loop_set_events(bool (*pending)(void), bool (*watching)(void),
|
||
void (*dispatch)(void), int (*pollfds)(int out[2]));
|
||
extern void (*scr_process_exit_hook)(double code);
|
||
extern void (*scr_stdin_destroy_hook)(void);
|
||
void scr_exit_code_note(int code);
|
||
int scr_exit_code_hint_get(void);
|
||
/* The pollable child-exit wake fd (the child kqueue / pidfd epoll) for the loop's
|
||
* poll(2) sleep, or -1 when it can't wake for every pending child. */
|
||
int scr_children_wake_fd(void);
|
||
double scr_now_ms(void); /* the loop's monotonic clock, in ms */
|
||
/* Run to ordinary loop exhaustion, except that a non-NULL executable
|
||
* module-root promise stops the loop as soon as it is rejected at a
|
||
* microtask checkpoint. Fulfilled roots do not stop the loop: Node keeps
|
||
* running ref'd work scheduled by a successfully evaluated module.
|
||
* Returns true when a default/listener-crashing unhandled rejection
|
||
* already selected and reported exit status 1. */
|
||
bool scr_loop_run(ScrPromise *top_level);
|
||
/* External I/O hook, polled at loop quiescence like the child registry:
|
||
* `pending` keeps the loop alive; `poll` makes progress and may SLEEP up
|
||
* to max_wait_ms (on real fds — socket readiness wakes it early), so the
|
||
* loop skips its own nanosleep for that turn. One registrant: the dynamic
|
||
* island (engine promise jobs + in-flight fetch transfers). Static builds
|
||
* never set it. */
|
||
void scr_loop_set_io(bool (*pending)(void), void (*poll)(double max_wait_ms));
|
||
bool scr_report_unhandled_rejections(void); /* true = exit 1 */
|
||
void scr_discard_unhandled_rejections(void);
|
||
/* The island's unhandled-rejection ledger joins the report above (one
|
||
* registrant, set at engine boot; static builds never set it): called with
|
||
* print=true when the static ledger reported nothing, prints its FIRST
|
||
* never-observed rejection in the same voice, frees the ledger either way,
|
||
* and returns whether it had any. */
|
||
void scr_loop_set_island_rejections(bool (*fn)(bool print),
|
||
int (*drain_jobs)(void));
|
||
/* The island's earliest armed timer deadline (scr_island_timers_deadline;
|
||
* HUGE_VAL when none) joins the loop's sleep computation: an armed
|
||
* AbortSignal.timeout must fire on time while the loop sleeps on socket
|
||
* readiness, without keeping the loop alive by itself (Node's unref'd
|
||
* timer). One registrant, set at engine boot; static builds never set it. */
|
||
void scr_loop_set_island_deadline(double (*fn)(void));
|
||
long scr_abandoned_fiber_count(void);
|
||
/* True while executing on an async fiber (vs the main stack) — the island
|
||
* sizes its engine stack budget per stack (see scr_island.c). */
|
||
bool scr_on_fiber(void);
|
||
/* Identity of the running fiber (NULL on the main stack) — the island's
|
||
* stack-anchor bookkeeping tells STACKS apart, not just fiber-vs-main
|
||
* (a host callback may spawn a fiber that re-enters the engine). */
|
||
void *scr_fiber_self(void);
|
||
void scr_note_abandoned_fibers(long n); /* scr_console.c owns the flag */
|
||
|
||
/* new Promise(executor): kind 0 f64, 1 bool, 2 str, 3 void; ref-kind
|
||
* resolve thunks are emitted (they know the concrete RC helpers) over
|
||
* scr_resolve_ref_impl, constructed via scr_make_resolve_fn. The
|
||
* executor runs synchronously; an escaping throw rejects the promise. */
|
||
ScrClosure *scr_make_resolve(ScrPromise *p, int kind);
|
||
ScrClosure *scr_make_resolve_fn(ScrPromise *p, void *fn);
|
||
void scr_resolve_ref_impl(ScrClosure *self, void *v, void *(*retain)(void *), void (*release)(void *), ScrTraceFn trace);
|
||
/* The reject closure: `(reason: Error) => void` — stores the reason as a
|
||
* SCR_EXC_OBJ payload (the thrown-Error representation) and wakes waiters;
|
||
* a reject after any settle is a no-op (first settle wins, exactly JS). */
|
||
ScrClosure *scr_make_reject(ScrPromise *p);
|
||
void scr_promise_run_executor(ScrPromise *p, ScrClosure *exec, ScrClosure *resolve);
|
||
void scr_promise_run_executor0(ScrPromise *p, ScrClosure *exec);
|
||
void scr_promise_run_executor2(ScrPromise *p, ScrClosure *exec, ScrClosure *resolve, ScrClosure *reject);
|
||
|
||
/* The promise a fiber settles (borrowed) — the emitted trampolines fulfill
|
||
* through it. */
|
||
ScrPromise *scr_fiber_promise(ScrFiber *f);
|
||
|
||
/* ── sync generators (function*) ──────────────────────────────────────
|
||
* A generator is a refcounted handle (ScrGen) over a fiber created
|
||
* SUSPENDED — nothing runs until the first resume — plus three
|
||
* type-erased value slots (kind + payload + release fn, the exception
|
||
* cell's technique): OUT (the yielded value, or the completion value once
|
||
* done), IN (the `.next(v)` argument), and RET (a parked `.return(v)`
|
||
* value, promoted to OUT by the trampoline when the GENRET unwind
|
||
* completes). The emitted code is type-directed: setters/takes move
|
||
* ownership (+1 in, +1 out); a NONE slot is JS's undefined.
|
||
*
|
||
* Resume protocol (consumer side, synchronous — no event loop):
|
||
* - scr_gen_resume: `.next()`. Starts or resumes the fiber and returns
|
||
* when it yields or completes. A body exception (or an injected
|
||
* `.throw`) moves into the CALLER's cell — the emitted pending check
|
||
* after the resume propagates it. Resuming a RUNNING generator throws
|
||
* Node's TypeError; resuming a DONE one is a no-op (OUT stays NONE —
|
||
* `{ value: undefined, done: true }`).
|
||
* - scr_gen_resume_return: `.return(v)` with v already parked in RET.
|
||
* UNSTARTED/DONE: the body never runs (the unstarted fiber tears down,
|
||
* its argpack dropped); RET promotes to OUT. SUSPENDED: the GENRET
|
||
* sentinel enters the fiber's cell and the fiber resumes — finallys
|
||
* run; a finally that YIELDS parks the sentinel (done stays false; a
|
||
* later resume continues the unwind); a finally that THROWS replaces it.
|
||
* - scr_gen_resume_throw: `.throw(e)` with e pending in the CALLER's cell
|
||
* (the emitted scr_throw_* just ran). SUSPENDED: the payload moves into
|
||
* the fiber's cell and the fiber resumes — the body's own try/catch
|
||
* sees it. UNSTARTED/DONE: the generator becomes done and the payload
|
||
* stays pending in the caller (the `.throw` call itself throws).
|
||
*
|
||
* Abandonment: releasing a SUSPENDED generator leaks its fiber
|
||
* deliberately (unwinding would run user finallys Node's GC never runs);
|
||
* the fiber stays in the live count, so programs that abandon one get the
|
||
* abandoned-fiber RC-audit note (the loop-exhaustion story). An UNSTARTED
|
||
* generator tears down cleanly: drop_args releases the packed arguments.
|
||
*/
|
||
typedef struct ScrGen ScrGen;
|
||
|
||
ScrGen *scr_gen_new(void (*entry)(ScrFiber *, void *), void *argpack,
|
||
void (*drop_args)(void *));
|
||
ScrGen *scr_async_gen_new(void (*entry)(ScrFiber *, void *), void *argpack,
|
||
void (*drop_args)(void *),
|
||
void (*settle)(ScrGen *, ScrPromise *));
|
||
ScrGen *scr_gen_retain(ScrGen *g);
|
||
void scr_gen_release(ScrGen *g); /* NULL-tolerant */
|
||
void *scr_gen_retain_v(void *g);
|
||
void scr_gen_release_v(void *g);
|
||
|
||
void scr_gen_resume(ScrGen *g);
|
||
void scr_gen_resume_return(ScrGen *g);
|
||
void scr_gen_resume_throw(ScrGen *g);
|
||
bool scr_gen_done(ScrGen *g);
|
||
|
||
/* Async-generator resume methods. Reference arguments move into the
|
||
* queued request; every function returns a +1 Promise<IteratorResult>. */
|
||
ScrPromise *scr_async_gen_next_none(ScrGen *g);
|
||
ScrPromise *scr_async_gen_next_f64(ScrGen *g, double value);
|
||
ScrPromise *scr_async_gen_next_bool(ScrGen *g, bool value);
|
||
ScrPromise *scr_async_gen_next_ref(ScrGen *g, void *value,
|
||
void (*release)(void *));
|
||
ScrPromise *scr_async_gen_return_none(ScrGen *g);
|
||
ScrPromise *scr_async_gen_return_f64(ScrGen *g, double value);
|
||
ScrPromise *scr_async_gen_return_bool(ScrGen *g, bool value);
|
||
ScrPromise *scr_async_gen_return_ref(ScrGen *g, void *value,
|
||
void (*release)(void *));
|
||
ScrPromise *scr_async_gen_throw(ScrGen *g);
|
||
|
||
/* IN slot (consumer stores before resume; body takes after the yield). */
|
||
void scr_gen_in_f64(ScrGen *g, double v);
|
||
void scr_gen_in_bool(ScrGen *g, bool v);
|
||
void scr_gen_in_ref(ScrGen *g, void *v, void (*release)(void *)); /* moves */
|
||
void scr_gen_in_none(ScrGen *g);
|
||
double scr_gen_take_in_f64(void); /* body side: the running fiber's gen */
|
||
bool scr_gen_take_in_bool(void);
|
||
void *scr_gen_take_in_ref(void); /* +1 moved out; NONE → NULL */
|
||
|
||
/* RET slot (consumer parks the `.return(v)` value before resume_return). */
|
||
void scr_gen_ret_f64(ScrGen *g, double v);
|
||
void scr_gen_ret_bool(ScrGen *g, bool v);
|
||
void scr_gen_ret_ref(ScrGen *g, void *v, void (*release)(void *)); /* moves */
|
||
void scr_gen_ret_none(ScrGen *g);
|
||
|
||
/* OUT slot (body yields / the emitted trampoline completes; consumer
|
||
* takes after resume). The no-gen-argument yield forms run on the CURRENT
|
||
* fiber's generator and switch back to the resumer; control returns when
|
||
* the consumer resumes again (the emitted pending check right after sees
|
||
* an injected .throw payload or the GENRET sentinel). */
|
||
void scr_gen_yield_f64(double v);
|
||
void scr_gen_yield_bool(bool v);
|
||
void scr_gen_yield_ref(void *v, void (*release)(void *)); /* moves */
|
||
void scr_async_gen_hop_done(void);
|
||
void scr_gen_out_f64(ScrGen *g, double v); /* trampoline completion stores */
|
||
void scr_gen_out_bool(ScrGen *g, bool v);
|
||
void scr_gen_out_ref(ScrGen *g, void *v, void (*release)(void *)); /* moves */
|
||
bool scr_gen_out_has(ScrGen *g); /* false = undefined completion value */
|
||
double scr_gen_take_out_f64(ScrGen *g);
|
||
bool scr_gen_take_out_bool(ScrGen *g);
|
||
void *scr_gen_take_out_ref(ScrGen *g); /* +1 moved out; NONE → NULL */
|
||
|
||
/* The trampoline's GENRET epilogue: true iff the sentinel is pending in
|
||
* the ACTIVE cell (also the emitted catch-prologue test in generator
|
||
* bodies). ret_to_out promotes the parked return value to the completion
|
||
* value. gen_of answers the generator a fiber belongs to (borrowed). */
|
||
bool scr_exc_genret_pending(void);
|
||
void scr_gen_ret_to_out(ScrGen *g);
|
||
ScrGen *scr_gen_of_fiber(ScrFiber *f);
|
||
|
||
#ifdef SCR_RC_AUDIT
|
||
long scr_promise_live_count(void);
|
||
#endif
|
||
|
||
/* ── fetch (scr_fetch.c) ──────────────────────────────────────────────
|
||
* The native bridge over scr_net + scr_tls + scr_http's client parser.
|
||
* Static fetch(url) resolves at the response head with native Response
|
||
* and ReadableStream handles; Response.json consumes that same stream.
|
||
* AbortSignal and streaming request bodies stay native too. Under
|
||
* --dynamic the same TU boots the broader engine web surface. The
|
||
* emitted main calls scr_fetch_install in either form. */
|
||
void scr_fetch_install(void);
|
||
ScrPromise *scr_fetch_static(ScrStr *url, ScrDyn *init); /* +1 promise<Response handle> */
|
||
ScrDyn *scr_fetch_response_new(ScrDyn *body, ScrDyn *init); /* borrowed args; +1 Response handle or NULL pending */
|
||
ScrPromise *scr_fetch_response_json(ScrDyn *response); /* +1 promise<dyn> */
|
||
ScrPromise *scr_fetch_response_text(ScrDyn *response); /* +1 promise<dyn> */
|
||
ScrPromise *scr_fetch_response_array_buffer(ScrDyn *response); /* +1 promise<dyn> */
|
||
ScrPromise *scr_fetch_response_bytes(ScrDyn *response); /* +1 promise<dyn> */
|
||
ScrDyn *scr_fetch_abort_controller_new(void); /* +1 AbortController handle */
|
||
/* Borrowed number; +1 AbortSignal handle or NULL pending. */
|
||
ScrDyn *scr_fetch_abort_timeout(ScrDyn *delay);
|
||
ScrDyn *scr_fetch_abort_now(ScrDyn *reason); /* borrowed reason; +1 handle */
|
||
ScrDyn *scr_fetch_abort_any(ScrDyn *signals); /* borrowed array; +1 handle or NULL pending */
|
||
ScrDyn *scr_fetch_stream_new(ScrDyn *source); /* borrowed source; +1 handle or NULL pending */
|
||
ScrDyn *scr_fetch_stream_from(ScrDyn *iterable); /* borrowed iterable; +1 handle or NULL pending */
|
||
ScrDyn *scr_fetch_stream_from_array(
|
||
ScrArr *iterable,
|
||
ScrDyn *(*item)(ScrArr *, double)); /* borrowed array/callback; +1 handle */
|
||
ScrDyn *scr_fetch_stream_from_bytes(
|
||
ScrBytes *iterable); /* borrowed bytes; +1 handle */
|
||
ScrDyn *scr_fetch_stream_from_string(
|
||
ScrStr *iterable); /* borrowed string; +1 handle */
|
||
ScrPromise *scr_fetch_reader_read(ScrDyn *reader); /* borrowed handle; +1 */
|
||
|
||
/* ── dynamic island (scr_island.c; --dynamic builds ONLY) ────────────
|
||
* The embedded QuickJS-ng engine. Compiled and linked only under
|
||
* -DSCR_DYNAMIC; static builds never reference these symbols (nor the
|
||
* engine headers — this block deliberately names no engine type). One
|
||
* runtime+context per process, created lazily on first entry, torn down
|
||
* at exit before the RC audit; every entry re-anchors the engine's
|
||
* stack-overflow check so calls from ucontext fibers are safe. Not
|
||
* reentrant from engine callbacks.
|
||
*/
|
||
#ifdef SCR_DYNAMIC
|
||
/* Evaluate UTF-8 source in the island's global scope; returns
|
||
* String(result) as a +1 ScrStr. Borrows code. An island exception is
|
||
* bridged into the exception cell as a catchable string ("TypeError:
|
||
* boom" — String(e)) and NULL comes back — callers are compiler-emitted
|
||
* pending checks (may-throw, like the fs.* surface). */
|
||
ScrStr *scr_island_eval(ScrStr *code);
|
||
|
||
/* The libregexp opaque for scr_regex.c (--dynamic builds share the engine
|
||
* archive's libregexp, whose host hooks want the island's JSContext):
|
||
* lazily boots the engine and re-anchors its stack check, then hands the
|
||
* context out as an opaque pointer. */
|
||
void *scr_island_lre_opaque(void);
|
||
|
||
/* ── embedded npm modules ─────────────────────────────────────────────
|
||
* npm package code is embedded into the binary at BUILD time: the emitted
|
||
* C carries every reached module's source as a static string plus the
|
||
* (importer, specifier) → target resolution edges, and registers both
|
||
* tables at the top of main. The island's module loader and its CommonJS
|
||
* require shim resolve exclusively from these tables — binaries never
|
||
* read node_modules at runtime. Edge targets are module keys or "node:*"
|
||
* builtins, which the island shims (events, path, process, fs stubs,
|
||
* child_process stubs). */
|
||
typedef struct ScrIslandModule {
|
||
const char *key; /* resolved path — the loader's cache key */
|
||
/* UTF-8 source — or raw-DEFLATE bytes when src_raw > 0 (the emitter
|
||
* compresses big module texts; the loader inflates LAZILY at first
|
||
* load through the inflater the emitted main installs, so a module a
|
||
* run never loads never inflates — and its pages never fault in). */
|
||
const char *src;
|
||
size_t len; /* byte length of src AS STORED */
|
||
size_t src_raw; /* 0: src is plain text; else the inflated byte length */
|
||
int format; /* 0 = ESM, 1 = CommonJS, 2 = JSON */
|
||
/* CommonJS only: the ESM facade the loader evaluates when an ES module
|
||
* imports this file — default (module.exports itself) plus the named
|
||
* exports LEXED from the source at BUILD time with the compiler's port
|
||
* of Node's vendored CJS lexer. NULL for ESM sources (compiled
|
||
* natively) and JSON (default-only wrapper, like Node). The same
|
||
* compression contract as src, via esm_raw. */
|
||
const char *esm;
|
||
size_t esm_len;
|
||
size_t esm_raw;
|
||
} ScrIslandModule;
|
||
|
||
typedef struct ScrIslandEdge {
|
||
const char *from;
|
||
const char *spec;
|
||
const char *to;
|
||
/* Which call forms this resolution serves — Node picks the "exports"
|
||
* condition set from the CALL FORM, so a dual package embeds TWO edges
|
||
* for one (from, spec): its "import" condition's target behind kind 1
|
||
* and its "require" condition's behind kind 2. 0 answers both lookups
|
||
* (relative files, builtins). */
|
||
int kind; /* 0 = any, 1 = import, 2 = require */
|
||
} ScrIslandEdge;
|
||
|
||
/* Registers the emitted tables (static data; no copy, no engine boot).
|
||
* Must run before any island entry that imports — main calls it right
|
||
* after scr_lib_init. */
|
||
void scr_island_modules(const ScrIslandModule *mods, size_t nmods,
|
||
const ScrIslandEdge *edges, size_t nedges);
|
||
|
||
/* The inflater for compressed embedded module text (raw DEFLATE, exact
|
||
* inflated size known from the table row). The emitted main installs
|
||
* scr_zlib.c's scr_zlib_inflate_exact exactly when some module compressed
|
||
* at build time — index.ts links scr_zlib.c/libz on the same predicate,
|
||
* so compression-free builds keep their link line and the island keeps no
|
||
* zlib reference of its own. */
|
||
void scr_island_set_inflate(bool (*inflate)(const unsigned char *src, size_t src_len,
|
||
unsigned char *dst, size_t dst_len));
|
||
/* scr_zlib.c: one-shot raw-DEFLATE inflate into a caller-sized buffer;
|
||
* true only when the stream ends exactly at dst_len. */
|
||
bool scr_zlib_inflate_exact(const unsigned char *src, size_t src_len,
|
||
unsigned char *dst, size_t dst_len);
|
||
|
||
/* The island process shim's implicit exit status (process.exitCode):
|
||
* Node's set-it-and-return-normally contract. The emitted main returns
|
||
* this after the loop drains; 0 when never set. The version advances on
|
||
* every assignment so an exit listener can override a provisional status. */
|
||
int scr_island_exit_code(void);
|
||
size_t scr_island_exit_code_version(void);
|
||
|
||
/* ── web-platform globals (scr_web.c) ─────────────────────────────────
|
||
* The pure-JS prelude defining the island's WHATWG subset (streams et
|
||
* al.), evaluated at engine boot before any embedded module. `jsctx` is
|
||
* the island's JSContext as an opaque pointer — engine types stay out of
|
||
* this header. */
|
||
void scr_island_web_boot(void *jsctx);
|
||
|
||
/* Runs the engine's pending promise-reaction jobs to exhaustion (no-op
|
||
* before the engine boots). Returns the number executed. The loop's io
|
||
* hook drains through this so island async chains progress at quiescence,
|
||
* exactly where Node runs its microtasks. */
|
||
int scr_island_drain_jobs(void);
|
||
|
||
/* Re-anchors the engine's stack-overflow check to the CURRENT stack (the
|
||
* every-island-entry rule) — for units entering the engine from loop
|
||
* dispatch stations (the fetch bridge's net callbacks) rather than
|
||
* through an emitted island op. */
|
||
void scr_island_host_enter(void);
|
||
|
||
/* Bridges the engine's CURRENT pending exception into the scriptc
|
||
* pending cell (the island timer bridge fires engine callbacks from the
|
||
* static heap; a throw there must become the loop's uncaught report). */
|
||
void scr_island_bridge_exception(void);
|
||
|
||
/* ── island timers (scr_web.c) ────────────────────────────────────────
|
||
* The machinery behind AbortSignal.timeout: one-shot engine callbacks
|
||
* armed by the web prelude's host.timer. Deliberately UNREF'd, like
|
||
* Node's AbortSignal.timeout timer — an armed timer never keeps the loop
|
||
* alive; it fires only if other work (a live transfer, engine jobs) keeps
|
||
* the process running, and the island's io hook caps its sleeps at the
|
||
* earliest deadline so a fetch timeout fires on time. Deadlines are on
|
||
* the scr_now_ms clock; _deadline returns HUGE_VAL when none are armed.
|
||
* Teardown frees unfired callbacks so the engine audit stays zero. */
|
||
double scr_island_timers_deadline(void);
|
||
bool scr_island_timers_due(void);
|
||
bool scr_island_timers_fire_due(void); /* true = fired at least one */
|
||
void scr_island_timers_teardown(void);
|
||
|
||
/* ── dynamic fetch hooks ──────────────────────────────────────────────
|
||
* The native bridge registers NO pending/poll hooks (its
|
||
* transfers live on real sockets the loop's poller sleeps on); the curl
|
||
* reference implementation (scr_fetch_curl.c, SCRIPTC_FETCH_CURL=1 —
|
||
* one release as the flip's reference) still registers all four so the
|
||
* loop can sleep on curl's fds. */
|
||
void scr_island_set_fetch(void (*boot)(void *jsctx), bool (*pending)(void),
|
||
void (*poll)(double max_wait_ms), void (*teardown)(void));
|
||
|
||
/* ── the island's node:http/https client bridge (scr_net_island.c) ────
|
||
* The one TU referencing BOTH the socket units and the island (the
|
||
* scr_zlib_island.c precedent): native-toolchain.ts compiles it exactly when a
|
||
* --dynamic build links the socket units, and the emitted main calls
|
||
* scr_net_island_install before any island entry. `attach` runs while
|
||
* the island builds its bootstrap host object (jsctx and host_obj are
|
||
* the engine's context and host-object pointers, opaque here — engine
|
||
* types stay out of this header) and adds the bridge's host functions; `teardown`
|
||
* frees engine values still held by in-flight requests before the
|
||
* engine dies, the fetch-teardown story. Without the bridge the island
|
||
* keeps its "does not provide the 'node:http' builtin" refusal. */
|
||
void scr_island_set_netmod(void (*attach)(void *jsctx, void *host_obj), void (*teardown)(void));
|
||
void scr_net_island_install(void);
|
||
|
||
/* ── island value handles (the `any` runtime type) ────────────────────
|
||
* A ScrJsval is an ordinary refcounted scriptc heap cell owning ONE
|
||
* engine value. It is opaque here (the engine's value type must not leak
|
||
* into static-build translation units) and deliberately NOT
|
||
* cycle-collector-traced: its internal references live in the engine's
|
||
* own GC world, and cycles that cross the boundary are documented as
|
||
* uncollectable (SEMANTICS.md). Releasing the last reference frees the
|
||
* engine value — or only the cell itself after engine teardown (the
|
||
* engine frees every value it still owns when it goes down, so a
|
||
* post-teardown release must not touch it).
|
||
*
|
||
* Conventions, mirroring the rest of the runtime: operands are BORROWED,
|
||
* refcounted results come back owned (+1). Fallible operations bridge
|
||
* the engine exception into the exception cell (catchable) and report
|
||
* failure via NULL (pointer results) or -1 (int results) — callers are
|
||
* compiler-emitted pending checks. Every entry re-anchors the engine's
|
||
* stack check, so all of these are fiber-safe. */
|
||
typedef struct ScrJsval ScrJsval;
|
||
|
||
ScrJsval *scr_jsval_retain(ScrJsval *v);
|
||
void scr_jsval_release(ScrJsval *v);
|
||
void *scr_jsval_retain_v(void *v);
|
||
void scr_jsval_release_v(void *v);
|
||
|
||
/* Binary operator codes for scr_jsval_binop / scr_jsval_cmp. The JS
|
||
* semantics (ToPrimitive, coercion) come from prelude helper closures
|
||
* evaluated at island init, not from C reimplementations. */
|
||
#define SCR_JSOP_ADD 0
|
||
#define SCR_JSOP_SUB 1
|
||
#define SCR_JSOP_MUL 2
|
||
#define SCR_JSOP_DIV 3
|
||
#define SCR_JSOP_MOD 4
|
||
#define SCR_JSOP_POW 5
|
||
#define SCR_JSOP_LT 6
|
||
#define SCR_JSOP_LE 7
|
||
#define SCR_JSOP_GT 8
|
||
#define SCR_JSOP_GE 9
|
||
#define SCR_JSOP_EQ 10 /* strict === */
|
||
#define SCR_JSOP_NEQ 11 /* strict !== */
|
||
#define SCR_JSOP_COUNT 12
|
||
|
||
/* Marshal in (static → island). Each returns a fresh +1 cell. from_str
|
||
* borrows s; from_json borrows JSON text produced by the emitted
|
||
* type-directed serializers (a DEEP COPY into the island — the documented
|
||
* aliasing divergence) and cannot fail on that trusted input. */
|
||
ScrJsval *scr_jsval_from_f64(double v);
|
||
ScrJsval *scr_jsval_from_bool(bool v);
|
||
ScrJsval *scr_jsval_from_str(const ScrStr *s);
|
||
ScrJsval *scr_jsval_from_json(const ScrStr *json);
|
||
/* A CHECKED-DYNAMIC (dyn) value entering the island — deep copy for data
|
||
* kinds (a boxed handle/promise throws the catchable TypeError). A JSVAL
|
||
* node unwraps to its OWN cell (+1) — an engine value that crossed into
|
||
* the checked-dynamic tree and back is the SAME engine value, by reference (nested JSVAL
|
||
* members embed their engine values directly). A boxed FUNCTION crosses
|
||
* as one generic host-function shim over its uniform ScrDynThunk: the
|
||
* shim wraps engine arguments as dyn values (scalar-normalizing), calls
|
||
* the thunk, and converts the dyn result back — each crossing mints a
|
||
* fresh engine function (identity is not preserved for re-crossings;
|
||
* SEMANTICS.md). NULL with a pending exception on failure. Borrows d. */
|
||
ScrJsval *scr_jsval_from_dyn(const ScrDyn *d);
|
||
|
||
/* The jsval→dyn crossing (the IR's dynFromJsval): wraps an island value
|
||
* as a SCR_DYN_JSVAL node, SCALAR-NORMALIZING first — engine numbers/
|
||
* strings/booleans/null/undefined convert to the native dyn kinds (the
|
||
* strict exits cannot fail on engine-reported scalars), so JSVAL nodes
|
||
* only ever hold engine objects/arrays/functions (and the symbol/bigint
|
||
* edge). Installs the checked-dynamic tree's engine-routing ops on first use. Borrows
|
||
* the cell (retains it into the node); +1 out; never throws. */
|
||
ScrDyn *scr_dyn_from_jsval(ScrJsval *cell);
|
||
|
||
/* Engine operations. Arithmetic yields a fresh cell (NULL = bridged);
|
||
* comparisons yield 0/1 (-1 = bridged); truthy/not never fail. */
|
||
ScrJsval *scr_jsval_binop(int op, ScrJsval *a, ScrJsval *b);
|
||
int scr_jsval_cmp(int op, ScrJsval *a, ScrJsval *b);
|
||
/* `v instanceof C` in the engine (JS_IsInstanceOf — the spec operator,
|
||
* Symbol.hasInstance included). 0/1; -1 = bridged (non-object RHS throws
|
||
* the engine's own TypeError, catchably). */
|
||
int scr_jsval_instance_of(ScrJsval *v, ScrJsval *c);
|
||
ScrJsval *scr_jsval_neg(ScrJsval *a);
|
||
/* GetIterator over an island value (the for-of head over 'any'): the
|
||
* engine's protocol lookup; NULL + pending TypeError when not iterable. */
|
||
ScrJsval *scr_jsval_iter_new(ScrJsval *a);
|
||
ScrJsval *scr_jsval_plus(ScrJsval *a); /* unary + (ToNumber) */
|
||
int scr_jsval_truthy(ScrJsval *a);
|
||
ScrStr *scr_jsval_typeof(ScrJsval *a);
|
||
bool scr_jsval_is_object(ScrJsval *a);
|
||
ScrStr *scr_jsval_to_str(ScrJsval *a); /* String(v); NULL = bridged */
|
||
|
||
/* Property/element access and calls. Names are NUL-terminated ScrStr
|
||
* (identifier property names from source); computed keys are jsvals. */
|
||
ScrJsval *scr_jsval_get_prop(ScrJsval *o, const ScrStr *name);
|
||
ScrJsval *scr_jsval_global_get(const ScrStr *name);
|
||
int scr_jsval_set_prop(ScrJsval *o, const ScrStr *name, ScrJsval *v);
|
||
ScrJsval *scr_jsval_get_idx(ScrJsval *o, ScrJsval *key);
|
||
/* Destructuring guards over island values: RequireObjectCoercible with
|
||
* V8's exact TypeError text (pass-through result, +1), and GetIterator +
|
||
* the pattern's width as a fresh engine array (undefined-padded,
|
||
* IteratorClose per spec); both throw catchably, NULL + pending on throw. */
|
||
ScrJsval *scr_jsval_destr_check(ScrJsval *v, const char *spell, const char *first);
|
||
ScrJsval *scr_jsval_iter_n(ScrJsval *v, double n);
|
||
int scr_jsval_set_idx(ScrJsval *o, ScrJsval *key, ScrJsval *v);
|
||
ScrJsval *scr_jsval_call_method(ScrJsval *o, const ScrStr *name, int argc, ScrJsval **argv);
|
||
ScrJsval *scr_jsval_call_this(ScrJsval *f, ScrJsval *receiver, int argc, ScrJsval **argv);
|
||
/* `o.name?.(...)`: a nullish member answers the engine's undefined;
|
||
* anything else calls with this = o (non-callables throw in the engine). */
|
||
ScrJsval *scr_jsval_opt_call_method(ScrJsval *o, const ScrStr *name, int argc, ScrJsval **argv);
|
||
ScrJsval *scr_jsval_call(ScrJsval *f, int argc, ScrJsval **argv);
|
||
/* Spread application on an island callee — `f(...pre, ...spread)` through
|
||
* the prelude helper's REAL spread syntax (iterator protocols are the
|
||
* engine's own; the guards front-run V8's exact spread-call TypeError
|
||
* texts). `pre` is the engine array of leading fixed arguments; `what` the
|
||
* spread expression's source spelling (the nullish text spells it).
|
||
* Borrows everything; +1 out, or NULL with the exception bridged. */
|
||
ScrJsval *scr_jsval_call_spread(ScrJsval *f, ScrJsval *pre, ScrJsval *spread, const ScrStr *what);
|
||
/* `new X(...)` on an island callee (jsOp construct) — JS_CallConstructor.
|
||
* Borrows everything; +1 out, or NULL with the exception bridged. */
|
||
ScrJsval *scr_jsval_construct(ScrJsval *f, int argc, ScrJsval **argv);
|
||
|
||
/* A scriptc closure entering the island as an engine function (the
|
||
* package-callback pattern: `.action((a, b) => ...)`). `adapt` is the
|
||
* compiler-emitted per-signature adapter — one uniform shape over the
|
||
* closure ABI: borrowed argument cells in (the closure ABI consumes its
|
||
* params, so the adapter retains them), the +1 result cell (or NULL for
|
||
* void) out. The wrapper retains the closure; the engine finalizer
|
||
* releases it (before the RC audit — teardown precedes it). Calls from
|
||
* the engine pad missing arguments with undefined and drop surplus, like
|
||
* any JS call; a scriptc exception thrown inside reverse-bridges to an
|
||
* engine throw (strings stay strings — the two bridges round-trip). */
|
||
ScrJsval *scr_jsval_from_closure(ScrClosure *c, int arity,
|
||
ScrJsval *(*adapt)(ScrClosure *, ScrJsval **));
|
||
|
||
/* True iff the cell holds the engine's undefined — the TYPED host-call
|
||
* adapters ask before converting a `T | undefined` parameter (an absent
|
||
* or undefined engine argument takes the undefined arm; anything else
|
||
* converts through the exit machinery). */
|
||
bool scr_jsval_is_undefined(ScrJsval *v);
|
||
|
||
/* Optional chains on island values: the nullish test on the HANDLE
|
||
* (`x?.y` on 'any' — undefined OR null short-circuits, JS-exact) and the
|
||
* engine's own undefined/null as +1 cells (the unit path's result, and
|
||
* the union lift's unit arms). All infallible. */
|
||
bool scr_jsval_is_nullish(ScrJsval *v);
|
||
ScrJsval *scr_jsval_undefined(void);
|
||
ScrJsval *scr_jsval_null(void);
|
||
|
||
/* Non-JSON marshals IN: a URL crosses as an engine URL instance built
|
||
* from its href (the first call installs a minimal island URL class when
|
||
* no global exists — construction re-parses through scr_url.c's WHATWG
|
||
* parser; href/protocol/pathname readable, other components throw, see
|
||
* SEMANTICS.md). Borrows; +1 cell out, or NULL with the exception
|
||
* bridged. scr_jsval_from_bytes is its typed-array sibling — declared
|
||
* with the bytes surface below. */
|
||
ScrJsval *scr_jsval_from_url(ScrUrl *u);
|
||
|
||
/* Payload tags for scr_jsval_from_promise — the fulfillment types an
|
||
* async island callback may resolve with (the compiler fences the rest). */
|
||
enum {
|
||
SCR_ISLP_VOID = 0,
|
||
SCR_ISLP_F64 = 1,
|
||
SCR_ISLP_BOOL = 2,
|
||
SCR_ISLP_STR = 3,
|
||
SCR_ISLP_JSVAL = 4,
|
||
/* Promise<any[]> — the fulfillment is a native array of engine cells
|
||
* (the jsval-element-array spelling): it re-enters the engine as a
|
||
* fresh engine array whose ELEMENTS are the same engine values (the
|
||
* loadPlugins shape: identity crosses, the spine is a copy). */
|
||
SCR_ISLP_JSVAL_ARR = 5,
|
||
};
|
||
|
||
/* Wrap a scriptc promise as an ENGINE promise (async callbacks crossing
|
||
* into the island: the package sees a real thenable). Takes ownership of
|
||
* `p` (+1 moves in). A waiter fiber awaits it and settles the engine
|
||
* capability when it settles — fulfillment marshals per `payload`
|
||
* (SCR_ISLP_*), rejection reverse-bridges the reason (Errors cross as
|
||
* real engine Errors). The await OBSERVES a rejection for the static
|
||
* ledger, and the engine's rejection tracker takes over for the wrapper
|
||
* promise — one report, one voice, whichever world drops the ball.
|
||
* Wrappers still pending at teardown free their engine values then (the
|
||
* island audit sees zero live allocations). NULL only if the engine
|
||
* cannot mint a capability (exception bridged). */
|
||
ScrJsval *scr_jsval_from_promise(ScrPromise *p, int payload);
|
||
|
||
/* The REVERSE bridge: an ENGINE promise (a package call's jsval whose
|
||
* declared type is Promise<T>) settles a fresh STATIC promise, so
|
||
* `await pkgCall()` parks a fiber and `.catch/.finally` desugar over it.
|
||
* Borrows `v`; the +1 result is pending until the engine promise settles
|
||
* (subscription is Promise.resolve(v).then(onF, onR) through a pinned
|
||
* prelude helper — thenables and plain values behave like `await`).
|
||
* Fulfillment stores the retained value cell (SCR_ISLP_JSVAL) or nothing
|
||
* (SCR_ISLP_VOID; the compiler passes no other tags); rejection converts
|
||
* the reason exactly like a bridged exception (engine Errors become
|
||
* ScrErrors picked by name) and rejects — re-thrown at the await,
|
||
* ledger-tracked if never observed. The .then marks the engine promise
|
||
* HANDLED for the island's rejection tracker, so exactly one world
|
||
* reports a dropped rejection. Bridging the same engine promise twice
|
||
* just makes two static observers of one settlement. NULL only on an
|
||
* engine-level surprise minting the subscription (exception bridged). */
|
||
ScrPromise *scr_jsval_bridge_promise(ScrJsval *v, int payload);
|
||
|
||
/* Island-native literals: build an object from key/value pairs (keys are
|
||
* marshaled strings) or an array from elements. Borrow argv; +1 out.
|
||
* Cannot fail (allocation failure aborts, like every runtime OOM). */
|
||
ScrJsval *scr_jsval_obj_lit(int npairs, ScrJsval **kv);
|
||
/* Getter completion for an island literal: defines `key` on `obj` as an
|
||
* engine getter invoking `fn`; answers the object retained (+1). */
|
||
ScrJsval *scr_jsval_define_getter(ScrJsval *obj, ScrJsval *key, ScrJsval *fn);
|
||
/* The engine TemplateStringsArray for an island tag call: n cooked then n
|
||
* raw strings; the result array carries `.raw` (+1). */
|
||
ScrJsval *scr_jsval_tpl_strings(int n, ScrJsval **kv);
|
||
/* Spread completion for an island literal: engine CopyDataProperties of
|
||
* `src` onto `obj`; answers the target (+1), NULL + pending on a getter
|
||
* throw. */
|
||
ScrJsval *scr_jsval_obj_spread(ScrJsval *obj, ScrJsval *src);
|
||
ScrJsval *scr_jsval_arr_lit(int n, ScrJsval **elems);
|
||
|
||
/* Marshal out (island → static): validated, STRICT extraction — a
|
||
* non-number refuses to exit as number (no coercion), throwing a
|
||
* catchable path-less TypeError like the dynCheck walkers'. Composite
|
||
* targets exit via scr_jsval_to_json + the existing json.parse +
|
||
* dynCheck pipeline instead (to_json throws if the value is not
|
||
* JSON-representable, e.g. contains a function). */
|
||
int scr_jsval_exit_f64(ScrJsval *v, double *out);
|
||
int scr_jsval_exit_bool(ScrJsval *v, bool *out);
|
||
ScrStr *scr_jsval_exit_str(ScrJsval *v);
|
||
/* Uint8Array exit (engine Buffers pass — they ARE Uint8Arrays): a fresh
|
||
* u8 COPY (+1), the boundary's aliasing stance; NULL = thrown. */
|
||
ScrBytes *scr_jsval_exit_bytes(ScrJsval *v);
|
||
/* `any[]`-declared slot exit (the jsval-element-array spelling):
|
||
* Array.isArray-gated, elements BY REFERENCE into a native array of
|
||
* engine cells (identity crosses, the spine is a snapshot copy). +1;
|
||
* NULL = thrown. */
|
||
ScrArr *scr_jsval_exit_jsval_arr(ScrJsval *v);
|
||
ScrStr *scr_jsval_to_json(ScrJsval *v);
|
||
|
||
/* The import boundary (libCall island.import): loads an embedded module —
|
||
* the package's runtime entry — through the island's module system and
|
||
* takes one export as an owned (+1) jsval. `name` is an export name,
|
||
* "default", or "*" (the namespace object; for CommonJS entries, an
|
||
* interop object over module.exports). The engine's module registry (and
|
||
* the require shim's cache) make repeated imports lookups, not
|
||
* re-evaluations. `specifier` is the specifier as WRITTEN at the import
|
||
* site: an ESM entry that does not provide a requested named export
|
||
* throws Node's link-time SyntaxError naming it ("The requested module
|
||
* 'x' does not provide an export named 'y'"). Borrows all args. NULL =
|
||
* bridged exception (a package's top-level code can throw). */
|
||
ScrJsval *scr_jsval_import(const ScrStr *key, const ScrStr *name, const ScrStr *specifier);
|
||
|
||
/* Dynamic import() (libCall island.importDyn): loads a module — an
|
||
* embedded module's key or a builtin shim's "node:x" key — through the
|
||
* island's module system and answers an owned (+1) jsval holding an
|
||
* ENGINE PROMISE of the namespace object. ALWAYS a promise: load and
|
||
* evaluation failures REJECT it (Node's dynamic-import shape). Borrows
|
||
* the key. NULL only on an engine-level surprise minting the rejected
|
||
* promise. */
|
||
ScrJsval *scr_jsval_import_dyn(const ScrStr *key);
|
||
|
||
/* The deferred boundary failure (libCall island.castFail): throws a
|
||
* catchable TypeError naming the target type — a checked cast of an
|
||
* island value to a type with no validated exit fails at RUNTIME, at the
|
||
* cast, instead of refusing the build. Borrows both args; the exception
|
||
* is pending on return. */
|
||
void scr_jsval_cast_fail(ScrJsval *v, const ScrStr *target);
|
||
#endif /* SCR_DYNAMIC */
|
||
|
||
/* ── numbers ────────────────────────────────────────────────────────────
|
||
* JS-exact Number-to-string (ECMA-262 §6.1.6.1.20 Number::toString, radix
|
||
* 10): shortest digit string that round-trips, ECMA placement rules.
|
||
* Writes NUL-terminated result into buf (>= 32 bytes), returns length.
|
||
*/
|
||
size_t scr_f64_to_str(double x, char *buf);
|
||
|
||
/* The Ryū digit core (scr_number.c), shared with the Intl en-US number
|
||
* formatter: the shortest round-tripping digits of a POSITIVE finite
|
||
* double — value = 0.digits × 10^n, no trailing zeros, NUL-terminated.
|
||
* Returns k, the digit count (≤ 17). */
|
||
int scr_f64_digits(double x, char digits[18], int *n_out);
|
||
|
||
/* ToString for template literals / string coercion. Returns +1. */
|
||
ScrStr *scr_f64_to_scrstr(double x);
|
||
ScrStr *scr_bool_to_scrstr(bool b); /* interned "true"/"false" */
|
||
|
||
/* ── String surface (scr_lib.c) ───────────────────────────────────────
|
||
* fromCharCode: ONE packed f64[] of UTF-16 code units — ToUint16 each,
|
||
* adjacent surrogate pairs combine, lone surrogates become U+FFFD
|
||
* (divergence 1's policy). Borrowed args; the string result is +1; neither
|
||
* throws. */
|
||
ScrStr *scr_str_from_char_code(ScrArr *codes);
|
||
/* Single numeric argument; avoids argument packing. Defined in scr_string.c
|
||
* to share its character cache. Same ToUint16 and lone-surrogate behavior. */
|
||
ScrStr *scr_str_from_char_code_one(double code);
|
||
/* The spread-typed-array form (String.fromCharCode(...bytes) — the
|
||
* magic-number ASCII probe); same semantics per element. */
|
||
ScrStr *scr_str_from_char_code_bytes(ScrBytes *codes);
|
||
/* Numeric Unicode code points, borrowed source; +1 string or NULL with
|
||
* a pending RangeError. Adjacent surrogate code points combine. */
|
||
ScrStr *scr_str_from_code_point(ScrArr *codes);
|
||
ScrStr *scr_str_from_code_point_bytes(ScrBytes *codes);
|
||
|
||
/* ── Number statics (scr_lib.c) ───────────────────────────────────────
|
||
* JS-exact by construction: Number.isFinite/isNaN/isInteger/isSafeInteger
|
||
* never coerce, and the compiler only routes f64-typed arguments here.
|
||
* None throws. */
|
||
bool scr_num_is_finite(double x);
|
||
bool scr_num_is_nan(double x);
|
||
bool scr_num_is_integer(double x);
|
||
bool scr_num_is_safe_integer(double x);
|
||
/* Number.prototype formatters: toExponential() is the fraction-digits-free
|
||
* shortest correctly-rounded mantissa; toFixed0 is the non-throwing omitted
|
||
* argument form. toFixed implements an explicit 0..100 fractionDigits with
|
||
* exact binary-value rounding and THROWS V8's catchable RangeError outside
|
||
* that range. All successful results are +1. */
|
||
ScrStr *scr_num_to_exponential(double x);
|
||
ScrStr *scr_num_to_fixed0(double x);
|
||
ScrStr *scr_num_to_fixed(double x, double fraction_digits);
|
||
|
||
/* Object.is over two numbers — the spec's SameValue on doubles: NaN
|
||
* equals NaN, +0 differs from -0, everything else is ==. Never throws. */
|
||
bool scr_num_same_value(double a, double b);
|
||
|
||
/* Intl.NumberFormat("en-US").format(x) / x.toLocaleString("en-US") with
|
||
* default options: decimal notation, 0–3 fraction digits rounded half-up
|
||
* on the shortest round-tripping decimal (ICU's rounding input — NOT
|
||
* toFixed's exact-value rounding), "," grouping every three integer
|
||
* digits, "∞"/"NaN" texts, "-0" for negative inputs rounding to zero.
|
||
* en-US is the one embedded locale. Result +1; never throws. */
|
||
ScrStr *scr_intl_num_format_en_us(double x);
|
||
|
||
/* ── Date, the read-only value slice (scr_lib.c) ──────────────────────
|
||
* A Date value is its TimeClip'd epoch-millisecond scalar. Identity and
|
||
* mutation are frontend-fenced; construction, storage, getters, and ISO
|
||
* formatting are exact over this representation. */
|
||
double scr_date_now(void); /* integer ms since epoch, like Node */
|
||
double scr_date_new_ms(double ms); /* TimeClip (NaN when invalid) */
|
||
double scr_date_get_time(double ms);
|
||
/* Node's exact ISO 8601 UTC format (expanded ±YYYYYY years outside
|
||
* 0–9999). THROWS Node's "Invalid time value" RangeError on NaN or
|
||
* |ms| > 8.64e15 and returns NULL; +1 otherwise. */
|
||
ScrStr *scr_date_to_iso(double ms);
|
||
/* new Date(dateString).getTime(): the BOUNDED parse (the X509 validity
|
||
* shape + ECMA's format with an explicit offset); NaN elsewhere. */
|
||
double scr_date_parse_get_time(ScrStr *s);
|
||
/* Date.UTC over already-number arguments (frontend completes the spec's
|
||
* defaults): MakeDay/MakeTime/TimeClip exactly — 0–99 maps to 1900+year,
|
||
* month/date rollover, NaN for non-finite parts, out-of-range results,
|
||
* and years past V8's ±1e6 MakeDay bound. Never throws. */
|
||
double scr_date_utc(double y, double mo, double d,
|
||
double h, double mi, double s, double ms);
|
||
double scr_date_get_full_year(double ms, bool utc);
|
||
double scr_date_get_month(double ms, bool utc);
|
||
double scr_date_get_date(double ms, bool utc);
|
||
double scr_date_get_day(double ms, bool utc);
|
||
double scr_date_get_hours(double ms, bool utc);
|
||
double scr_date_get_minutes(double ms, bool utc);
|
||
double scr_date_get_seconds(double ms, bool utc);
|
||
double scr_date_get_milliseconds(double ms);
|
||
double scr_date_get_timezone_offset(double ms);
|
||
/* One-argument ABI wrappers used by the LLVM lib-call table. */
|
||
double scr_date_get_full_year_local(double ms);
|
||
double scr_date_get_full_year_utc(double ms);
|
||
double scr_date_get_month_local(double ms);
|
||
double scr_date_get_month_utc(double ms);
|
||
double scr_date_get_date_local(double ms);
|
||
double scr_date_get_date_utc(double ms);
|
||
double scr_date_get_day_local(double ms);
|
||
double scr_date_get_day_utc(double ms);
|
||
double scr_date_get_hours_local(double ms);
|
||
double scr_date_get_hours_utc(double ms);
|
||
double scr_date_get_minutes_local(double ms);
|
||
double scr_date_get_minutes_utc(double ms);
|
||
double scr_date_get_seconds_local(double ms);
|
||
double scr_date_get_seconds_utc(double ms);
|
||
|
||
/* ── numeric coercion + bitwise operators ─────────────────────────────
|
||
* JS-exact ToInt32/ToUint32 semantics: NaN/±Infinity → 0, truncation
|
||
* toward zero, modular wrap into 32 bits; the operation runs in 32-bit
|
||
* space (shift counts mask to 5 bits, `>>` is an arithmetic shift spelled
|
||
* portably — no C UB/implementation-defined shifts) and the result
|
||
* returns to f64: `>>>` as Uint32, everything else as Int32.
|
||
* scr_to_uint32 lives in scr_number.c; the bitwise operations in scr_lib.c. */
|
||
uint32_t scr_to_uint32(double d);
|
||
double scr_bit_and(double a, double b);
|
||
double scr_bit_or(double a, double b);
|
||
double scr_bit_xor(double a, double b);
|
||
double scr_bit_shl(double a, double b);
|
||
double scr_bit_shr(double a, double b);
|
||
double scr_bit_ushr(double a, double b);
|
||
double scr_bit_not(double a);
|
||
|
||
/* ── typed arrays / Buffer (scr_bytes.c) ──────────────────────────────
|
||
* ONE runtime representation for the supported numeric typed arrays,
|
||
* Node's Buffer (a Uint8Array subclass), and DataView: a refcounted,
|
||
* MUTABLE, fixed-length element buffer. An ScrBytes either OWNS its
|
||
* storage (backing == NULL, byteOffset 0) or is a VIEW: its `data` points
|
||
* INTO an owner's storage and its `backing` retains that owner (chain
|
||
* depth is always exactly 1 — views over views resolve to the owner at
|
||
* construction), so reads/writes through the view alias the source
|
||
* JS-exactly. Views come from DataView (scr_dataview_new) and from
|
||
* subarray()/Buffer-slice() (scr_bytes_subarray — Buffer's slice is
|
||
* subarray's deprecated alias in Node); only the plain typed arrays'
|
||
* slice() copies (scr_bytes_slice, matching JS).
|
||
* Elements are scalars only and the backing edge is acyclic by
|
||
* construction (owners point at nothing): never part of a cycle, no
|
||
* trace. Element reads widen to double; writes coerce JS-exactly (ToUint8
|
||
* / ToUint32 modular truncation, double→float rounding for f32).
|
||
* Out-of-bounds ELEMENT access traps like arrays (JS returns undefined /
|
||
* ignores the write — documented divergence); the Node-shaped operations
|
||
* (construction lengths, set(), the read/write numeric families, the
|
||
* DataView constructor and getters) THROW catchable RangeErrors exactly
|
||
* where Node throws. */
|
||
|
||
typedef enum ScrBytesElem {
|
||
SCR_BYTES_U8, /* Uint8Array / Buffer */
|
||
SCR_BYTES_U32, /* Uint32Array */
|
||
SCR_BYTES_F32, /* Float32Array */
|
||
SCR_BYTES_I32, /* Int32Array (reads sign-extend; writes ToInt32-wrap) */
|
||
SCR_BYTES_F64, /* Float64Array */
|
||
SCR_BYTES_I8, /* Int8Array */
|
||
SCR_BYTES_U16, /* Uint16Array */
|
||
SCR_BYTES_I16, /* Int16Array */
|
||
SCR_BYTES_U8C, /* Uint8ClampedArray */
|
||
} ScrBytesElem;
|
||
|
||
typedef struct ScrBytes {
|
||
size_t rc;
|
||
size_t len; /* ELEMENT count, fixed at construction */
|
||
ScrBytesElem elem;
|
||
uint8_t *data; /* len * elem_size bytes; owned unless backing is set */
|
||
/* NULL for owners. A view (DataView, subarray, Buffer-slice) sets this
|
||
* to the retained OWNER it aliases (chain depth is always exactly 1:
|
||
* views over views resolve to the owner at construction) and its `data`
|
||
* points into backing->data — released, never freed. */
|
||
struct ScrBytes *backing;
|
||
bool is_buffer; /* Buffer brand belongs to the view, not its backing. */
|
||
bool external; /* Root aliases foreign memory; releasing it never frees data. */
|
||
} ScrBytes;
|
||
|
||
ScrBytes *scr_bytes_from_external(void *data, size_t length);
|
||
ScrDyn *scr_ffi_memory_module(ScrDyn *catalog);
|
||
ScrDyn *scr_ffi_argument(ScrDyn *value, ScrStr *type);
|
||
|
||
size_t scr_bytes_elem_size(ScrBytesElem elem); /* 1, 2, 4, or 8 */
|
||
const char *scr_bytes_elem_name(ScrBytesElem elem);
|
||
double scr_bytes_to_u8_clamp(double value);
|
||
|
||
/* node:string_decoder's StringDecoder (scr_bytes.c, beside the decoders
|
||
* it shares): the decoder value is a record holding the CANONICAL
|
||
* encoding name and an f64 packing the pending partial sequence (count +
|
||
* up to 3 raw bytes — every encoding buffers at most 3); the compiler's
|
||
* interned %strdec helpers thread both through these pure functions.
|
||
* write answers the decoded complete prefix of pending+chunk, next the
|
||
* packed new pending, end the buffered partial's flush (utf8 replacement
|
||
* chars, base64's padded remainder, utf16le's held bytes; the stateless
|
||
* latin1/ascii/hex flush nothing). Node-exact per encoding — utf8's
|
||
* incomplete-tail walk, base64's mod-3 groups, utf16le's odd-byte and
|
||
* trailing-lead-surrogate holds. None throws. */
|
||
ScrStr *scr_strdec_write(const ScrStr *enc, double pending, const ScrBytes *chunk);
|
||
double scr_strdec_next(const ScrStr *enc, double pending, const ScrBytes *chunk);
|
||
ScrStr *scr_strdec_end(const ScrStr *enc, double pending);
|
||
|
||
#ifdef SCR_DYNAMIC
|
||
/* Static → island typed-array marshal (scr_island.c; --dynamic builds
|
||
* only): an engine typed array of the same element kind — a COPY, the
|
||
* boundary's copy stance. Borrows; +1 cell, or NULL bridged. */
|
||
ScrJsval *scr_jsval_from_bytes(const ScrBytes *b);
|
||
#endif
|
||
|
||
/* `new Uint8Array(n)` / Buffer.alloc(n): zero-filled. n goes through
|
||
* ToIndex like JS (NaN → 0, truncate toward zero; negative or > 2^53-1
|
||
* THROWS Node's "Invalid typed array length" RangeError catchably and
|
||
* returns NULL with the exception pending). */
|
||
ScrBytes *scr_bytes_new(ScrBytesElem elem, double n); /* +1 */
|
||
/* Shared u8 view over exactly the source view's raw bytes. Borrows; +1. */
|
||
ScrBytes *scr_bytes_raw_view(ScrBytes *bytes);
|
||
ScrBytes *scr_bytes_as_buffer(ScrBytes *bytes); /* borrows; +1, Buffer factory only */
|
||
/* Views retain the root allocation; offsets are relative to its complete
|
||
* byte storage, including when src is itself a subarray. */
|
||
ScrBytes *scr_bytes_buffer_view(ScrBytes *src, ScrBytesElem elem,
|
||
double offset, bool has_len, double length);
|
||
ScrDyn *scr_array_buffer_new(ScrDyn *length);
|
||
ScrDyn *scr_array_buffer_from_bytes(ScrBytes *view);
|
||
bool scr_array_buffer_is(const ScrDyn *value);
|
||
bool scr_array_buffer_is_view(const ScrDyn *value);
|
||
double scr_array_buffer_byte_length_getter(void);
|
||
ScrDyn *scr_array_buffer_byte_length_descriptor(ScrDyn *getter);
|
||
ScrBytes *scr_array_buffer_view(ScrBytesElem elem, const ScrDyn *buffer,
|
||
const ScrDyn *offset, const ScrDyn *length);
|
||
ScrBytes *scr_array_buffer_view_u8c(ScrDyn *, ScrDyn *, ScrDyn *);
|
||
ScrBytes *scr_array_buffer_view_i8(ScrDyn *, ScrDyn *, ScrDyn *);
|
||
ScrBytes *scr_array_buffer_view_u16(ScrDyn *, ScrDyn *, ScrDyn *);
|
||
ScrBytes *scr_array_buffer_view_i16(ScrDyn *, ScrDyn *, ScrDyn *);
|
||
ScrBytes *scr_array_buffer_view_u8(ScrDyn *, ScrDyn *, ScrDyn *);
|
||
ScrBytes *scr_array_buffer_view_u32(ScrDyn *, ScrDyn *, ScrDyn *);
|
||
ScrBytes *scr_array_buffer_view_i32(ScrDyn *, ScrDyn *, ScrDyn *);
|
||
ScrBytes *scr_array_buffer_view_f32(ScrDyn *, ScrDyn *, ScrDyn *);
|
||
ScrBytes *scr_array_buffer_view_f64(ScrDyn *, ScrDyn *, ScrDyn *);
|
||
ScrBytes *scr_array_buffer_view_dv(ScrDyn *, ScrDyn *, ScrDyn *);
|
||
/* Native callback boundary: exact owned u8 copy. NULL with len == 0 is an
|
||
* empty span. */
|
||
ScrBytes *scr_bytes_from_data(const uint8_t *data, size_t len); /* +1 */
|
||
|
||
/* `new Uint8Array(src)` / Buffer.from(u8): a same-elem-kind copy (the
|
||
* compiler fences cross-kind construction). Borrows src. Never throws. */
|
||
ScrBytes *scr_bytes_copy(const ScrBytes *src); /* +1 */
|
||
|
||
/* `new Uint8Array([1, 2, 3])` / Buffer.from(number[]): each f64 element
|
||
* coerces per the element kind (ToUint8/ToUint32/float). Borrows arr
|
||
* (SCR_ELEM_F64). Never throws. */
|
||
ScrBytes *scr_bytes_from_arr(ScrBytesElem elem, const ScrArr *arr); /* +1 */
|
||
|
||
static inline ScrBytes *scr_bytes_retain(ScrBytes *b) {
|
||
if (b->rc != SIZE_MAX) b->rc++;
|
||
return b;
|
||
}
|
||
void scr_bytes_release(ScrBytes *b); /* NULL-tolerant */
|
||
void *scr_bytes_retain_v(void *b);
|
||
void scr_bytes_release_v(void *b);
|
||
#ifdef SCR_RC_AUDIT
|
||
long scr_bytes_live_count(void);
|
||
#endif
|
||
|
||
double scr_bytes_len(const ScrBytes *b); /* element count */
|
||
double scr_bytes_byte_len(const ScrBytes *b); /* len * elem size */
|
||
|
||
/* `.byteOffset`: 0 for owners (scriptc typed arrays always own their
|
||
* whole storage — SEMANTICS.md notes the divergence from Node's Buffer
|
||
* pooling), the view's offset into its owner's storage for a DataView. */
|
||
double scr_bytes_byte_offset(const ScrBytes *b);
|
||
|
||
/* `new DataView(src.buffer, byteOffset?, byteLength?)` — src is the bytes
|
||
* value whose `.buffer` the program named (any elem kind; a DataView src
|
||
* resolves to ITS owner, so offsets stay relative to the one buffer,
|
||
* exactly like JS). Both indices go through ToIndex (NaN → 0, truncate
|
||
* toward zero); a bad offset THROWS Node's "Start offset %s is outside
|
||
* the bounds of the buffer" and a bad/overflowing length "Invalid
|
||
* DataView length %s" (catchable RangeErrors, NULL result with the
|
||
* exception pending). has_len distinguishes an omitted byteLength (view
|
||
* to the end of the buffer) from an explicit one. Borrows src; the
|
||
* result retains the owner. */
|
||
ScrBytes *scr_dataview_new(ScrBytes *src, double byte_off, bool has_len, double byte_len); /* +1 */
|
||
|
||
/* DataView getters. LE flag false = big-endian (the JS default). The
|
||
* BIGU64/BIGI64 kinds answer the composed Number(view.getBigUint64/
|
||
* getBigInt64(...)) lowering: the 8-byte integer converted to double
|
||
* (round-to-nearest-even — exactly Number(bigint)). Offsets go through
|
||
* ToIndex; any invalid or out-of-range offset THROWS Node's "Offset is
|
||
* outside the bounds of the DataView" RangeError catchably (0 result
|
||
* with the exception pending). */
|
||
typedef enum ScrDataViewGet {
|
||
SCR_DV_U8, SCR_DV_I8, SCR_DV_U16, SCR_DV_I16, SCR_DV_U32, SCR_DV_I32,
|
||
SCR_DV_F32, SCR_DV_F64, SCR_DV_BIGU64, SCR_DV_BIGI64,
|
||
} ScrDataViewGet;
|
||
double scr_dataview_get(const ScrBytes *b, double byte_off, ScrDataViewGet kind, bool le);
|
||
|
||
/* DataView setters (the integer/float kinds only — the BIG kinds never
|
||
* lower: bigint arguments have no representation). Values coerce
|
||
* JS-exactly: the integer kinds by modular truncation (ToUint32's residue
|
||
* — the narrower widths store its low bytes, the same 2^width residue),
|
||
* F32 by double→float round-to-nearest-even. Offsets go through ToIndex
|
||
* with the getters' one RangeError. */
|
||
void scr_dataview_set(ScrBytes *b, double byte_off, double value, ScrDataViewGet kind, bool le);
|
||
|
||
/* Element read/write. Any invalid index — negative, fractional, NaN, or
|
||
* out of bounds — TRAPS like the array runtime (SEMANTICS.md documents
|
||
* the divergence from JS's undefined-read/ignored-write). Writes coerce
|
||
* JS-exactly: u8/u32 by modular truncation (NaN/±Infinity → 0, truncate
|
||
* toward zero, wrap mod 2^8/2^32), f32 by double→float rounding. */
|
||
double scr_bytes_get(const ScrBytes *b, double i);
|
||
void scr_bytes_set(ScrBytes *b, double i, double v);
|
||
/* Replace a live typed-array capsule's fixed-size payload from its dyn
|
||
* snapshot. Source and target have the same compiler-checked bytes type. */
|
||
void scr_bytes_copy_contents(ScrBytes *dst, const ScrBytes *src);
|
||
|
||
/* TypedArray.prototype.slice(start, end): relative indices clamp like
|
||
* string/array slice (ToIntegerOrInfinity, negatives from the end); the
|
||
* result is a fresh same-kind copy. Never throws. */
|
||
ScrBytes *scr_bytes_slice(const ScrBytes *b, double start, double end); /* +1 */
|
||
/* Independent element-coerced copy; same-kind copies preserve raw bits. */
|
||
ScrBytes *scr_bytes_convert(ScrBytesElem elem, const ScrBytes *src); /* +1 */
|
||
/* Checked native constructor/from input; no JavaScript engine required.
|
||
* `from` selects iterable/array-like semantics, otherwise primitives are
|
||
* converted to lengths. Borrows source, returns +1 or NULL+pending. */
|
||
ScrBytes *scr_bytes_from_dyn(ScrBytesElem elem, const ScrDyn *value, bool from);
|
||
|
||
/* In-place, overlapping copy with relative/clamped element indices;
|
||
* returns the retained receiver, including for a shared subarray view. */
|
||
ScrBytes *scr_bytes_copy_within(ScrBytes *b, double target, double start, double end); /* +1 */
|
||
|
||
/* ES2023 typed-array copying methods. Both preserve the receiver's element
|
||
* kind and return a fresh +1 owner. with() raises Node's catchable
|
||
* "Invalid typed array index" RangeError for an invalid relative index. */
|
||
ScrBytes *scr_bytes_to_reversed(const ScrBytes *b); /* +1 */
|
||
ScrBytes *scr_bytes_with(const ScrBytes *b, double index, double value); /* +1 */
|
||
|
||
/* Numeric typed-array iteration drained into number[], and Uint8Array.join.
|
||
* Inputs borrowed; results fresh +1. */
|
||
ScrArr *scr_bytes_to_arr(const ScrBytes *b); /* +1 */
|
||
ScrStr *scr_bytes_join(const ScrBytes *b, const ScrStr *separator); /* +1 */
|
||
|
||
/* TypedArray.prototype.fill on non-u8 receivers: per-element fill with
|
||
* the element write's coercion, slice-clamped relative indices; answers
|
||
* the receiver +1 (chaining). Never throws. */
|
||
ScrBytes *scr_bytes_fill_elem(ScrBytes *b, double v, double start, double end); /* +1 */
|
||
|
||
/* TypedArray.prototype.subarray(start, end) — and Buffer's slice(), its
|
||
* deprecated alias: a same-elem VIEW aliasing the receiver's storage
|
||
* (mutations visible both ways, JS-exactly). The view retains the OWNER
|
||
* (chain depth exactly 1, the DataView rule) and its byteOffset composes.
|
||
* Same index clamping as slice; never throws. */
|
||
ScrBytes *scr_bytes_subarray(ScrBytes *b, double start, double end); /* +1 */
|
||
|
||
/* dst.set(src, offset): overlapping-safe, element-coerced copy.
|
||
* offset goes through ToIntegerOrInfinity; a negative offset or
|
||
* src.len + offset > dst.len THROWS Node's "offset is out of bounds"
|
||
* RangeError catchably. */
|
||
void scr_bytes_set_from(ScrBytes *dst, const ScrBytes *src, double offset);
|
||
/* Array-like source with sequential element coercion and catchable errors.
|
||
* Earlier writes remain visible when a later coercion throws. */
|
||
void scr_bytes_set_from_dyn(ScrBytes *dst, const ScrDyn *src, double offset);
|
||
|
||
/* buf.toString(enc) on u8 bytes: "utf8" decodes with WHATWG per-maximal-
|
||
* subpart U+FFFD replacement (Node-exact for invalid sequences), "hex" is
|
||
* lowercase pairs, "base64"/"base64url" the standard/url alphabets (url
|
||
* unpadded), "latin1" maps bytes to U+00XX, "ascii" masks to & 0x7f, and
|
||
* "utf16le" decodes LE code units (surrogate pairs combine; a LONE
|
||
* surrogate surfaces as U+FFFD — the documented divergence; an odd tail
|
||
* byte drops). Aliases arrive NORMALIZED (the compiler folds "binary",
|
||
* "ucs2", "utf-8", ...); NULL selects the default UTF-8 encoding for
|
||
* implicit native coercions. Borrows both; +1 result. Never throws (the
|
||
* compiler fences other encodings). */
|
||
ScrStr *scr_bytes_to_str(const ScrBytes *b, const ScrStr *enc);
|
||
ScrStr *scr_bytes_to_str_range(const ScrBytes *b, const ScrStr *enc, double start, double end);
|
||
/* Runtime-valued Buffer.toString encoding: aliases/case canonicalize;
|
||
* unknown names throw ERR_UNKNOWN_ENCODING. +1, or NULL when throwing. */
|
||
ScrStr *scr_bytes_to_str_checked(const ScrBytes *b, const ScrStr *enc);
|
||
ScrStr *scr_bytes_to_str_checked_range(const ScrBytes *b, const ScrStr *enc, double start, double end);
|
||
|
||
/* WHATWG TextDecoder.decode over u8 bytes (utf-8, default options): the
|
||
* same replacement decode as toString("utf8") with the leading BOM
|
||
* stripped. Borrows; +1; never throws. */
|
||
ScrStr *scr_text_decode(const ScrBytes *b);
|
||
|
||
/* TextDecoder with a compile-time WHATWG legacy-encoding id. The frontend
|
||
* owns label canonicalization and emits only the ids understood by
|
||
* scr_bytes.c; keeping this separate from scr_text_decode means default
|
||
* UTF-8 users do not retain the legacy mapping tables. Borrows; +1. */
|
||
ScrStr *scr_text_decode_legacy(const ScrBytes *b, double encoding);
|
||
|
||
/* Buffer.from(string, enc): "utf8" copies the bytes; "hex" parses pairs
|
||
* and stops at the first invalid/odd tail (Node-lenient); "base64" and
|
||
* "base64url" decode the standard AND url-safe alphabets, skipping
|
||
* invalid bytes (Node-lenient); "latin1"/"ascii" write each UTF-16
|
||
* unit's low byte (astral code points contribute their two surrogates');
|
||
* "utf16le" writes LE code units, surrogate pairs included. Borrows
|
||
* both; +1 u8 result. Never throws. */
|
||
ScrBytes *scr_bytes_from_str(const ScrStr *s, const ScrStr *enc);
|
||
|
||
/* Buffer.byteLength(string, enc) — enc NORMALIZED like from_str — and
|
||
* Buffer.isEncoding(name) (case-insensitive over Node's alias set).
|
||
* Borrow; never throw. */
|
||
double scr_bytes_byte_length_str(ScrStr *s, const ScrStr *enc);
|
||
bool scr_bytes_is_encoding(const ScrStr *s);
|
||
|
||
/* equals / compare / indexOf-lastIndexOf / fill / copy / swap / write on
|
||
* u8 bytes, Node-exact (corpus 1663). compare and copy take nargs — the
|
||
* count of PRESENT index args (defaults skip validation like Node);
|
||
* their ladders and fill/write's mirror validateOffset ('an integer',
|
||
* then '>= 0 && <= max' — Node spells '&&' here) and copy's C++ ladder
|
||
* (fractionals truncate silently; targetStart/sourceEnd check only
|
||
* >= 0). indexOf coerces byteOffset (NaN = search everything) and steps
|
||
* by `align` (2 for utf16le needles — matches land on even offsets).
|
||
* fill repeats the pattern over [offset, end) and answers the RECEIVER
|
||
* (+1, chaining); the string form zero-fills on an empty pattern where
|
||
* the bytes form throws Node's TypeError. swap reverses w-byte groups in
|
||
* place (+1 receiver; ERR_INVALID_BUFFER_SIZE off the width). write
|
||
* encodes and truncates at unit boundaries (whole UTF-8 code points,
|
||
* even utf16le lengths), returning the bytes written. concat_len
|
||
* truncates/zero-pads the concatenation to the validated total. */
|
||
bool scr_bytes_equals(const ScrBytes *a, const ScrBytes *b);
|
||
double scr_bytes_compare(const ScrBytes *src, const ScrBytes *target, double nargs,
|
||
double ts, double te, double ss, double se);
|
||
/* Node's validateOffset ladder (buffer.js): non-integers (±Infinity and
|
||
* NaN included) throw the 'an integer' ERR_OUT_OF_RANGE RangeError, the
|
||
* rest the '>= 0 && <= max' render; max < 0 drops the upper bound.
|
||
* Returns false after arming the pending throw. */
|
||
bool scr_bytes_validate_off(const char *name, double value, double max);
|
||
/* The checked-dynamic compare/equals validators (scr_bytes_io.c): Node's
|
||
* argument ladder over dyn-boxed arguments — a non-bytes value throws
|
||
* ERR_INVALID_ARG_TYPE with the API's own argument name ("buf1"/"buf2",
|
||
* "otherBuffer", "target"), non-number offsets ERR_INVALID_ARG_TYPE
|
||
* "of type number", out-of-range numbers the validateOffset RangeError;
|
||
* an undefined offset takes its Node default. All arguments BORROWED. */
|
||
double scr_buffer_compare_chk(const ScrDyn *a, const ScrDyn *b);
|
||
ScrBytes *scr_buffer_from_dyn(const ScrDyn *value, const ScrStr *encoding); /* +1 or NULL+pending; borrowed args */
|
||
bool scr_bytes_equals_chk(const ScrBytes *recv, const ScrDyn *other);
|
||
double scr_bytes_compare_chk(const ScrBytes *src, const ScrDyn *target,
|
||
const ScrDyn *ts, const ScrDyn *te,
|
||
const ScrDyn *ss, const ScrDyn *se);
|
||
/* new Buffer(number, encoding)'s string-arm type error (always throws;
|
||
* borrowed). */
|
||
ScrBytes *scr_buffer_new_string_fail(const ScrDyn *got);
|
||
/* fs._toUnixTimestamp over a dyn time value: numeric strings and finite
|
||
* numbers coerce (negatives answer now/1000), the rest throw Node's
|
||
* ERR_INVALID_ARG_TYPE. Borrowed. */
|
||
double scr_fs_to_unix_timestamp(const ScrDyn *t);
|
||
/* The fs argument-validation ladders (the fs.*Chk libCalls): Node-order
|
||
* validation over dyn values with Node's exact typed errors; a pass
|
||
* meets the real operation where one exists (mkdtempSync, macOS
|
||
* lchmodSync) or the compiler-rendered fence. All borrowed; the Chk
|
||
* forms without results always leave an exception pending. */
|
||
ScrDyn *scr_fs_exists_async(const ScrDyn *path, const ScrDyn *cb);
|
||
void scr_fs_mkdtemp_chk(const ScrDyn *prefix, const ScrDyn *cb, const ScrStr *fence);
|
||
ScrStr *scr_fs_mkdtemp_sync_chk(const ScrDyn *prefix, const ScrDyn *opts, const ScrStr *fence);
|
||
void scr_fs_read_file_chk(const ScrDyn *path, const ScrDyn *opts, const ScrDyn *cb, const ScrStr *fence);
|
||
void scr_fs_opendir_chk(const ScrDyn *path, const ScrDyn *opts, const ScrStr *fence);
|
||
void scr_fs_watch_file_chk(const ScrDyn *path, const ScrDyn *listener, const ScrStr *fence);
|
||
void scr_fs_lchmod_chk(const ScrDyn *path, const ScrDyn *mode, const ScrDyn *cb, const ScrStr *fence);
|
||
ScrDyn *scr_fs_lchmod_sync_chk(const ScrDyn *path, const ScrDyn *mode);
|
||
ScrPromise *scr_fsp_lchmod_chk(const ScrDyn *path, const ScrDyn *mode);
|
||
void scr_fs_read_chk(const ScrDyn *fd, const ScrDyn *buffer, const ScrDyn *offset,
|
||
const ScrDyn *length, const ScrDyn *position, const ScrStr *fence);
|
||
void scr_fs_stream_opts_chk(const ScrDyn *path, const ScrDyn *opts, const ScrStr *fence);
|
||
/* The checked-dynamic max-listeners ladders (scr_events_emitter.c). */
|
||
ScrEmitter *scr_emitter_set_max_chk(ScrEmitter *em, const ScrDyn *n);
|
||
void scr_emitter_set_default_max_chk(const ScrDyn *n, const ScrStr *name);
|
||
double scr_bytes_index_of(const ScrBytes *b, const ScrBytes *needle, double off, double align, bool fwd);
|
||
double scr_bytes_index_of_num(const ScrBytes *b, double v, double off, bool fwd);
|
||
ScrBytes *scr_bytes_fill(ScrBytes *b, const ScrBytes *pattern, double nargs, double offset, double end);
|
||
ScrBytes *scr_bytes_fill_num(ScrBytes *b, double v, double nargs, double offset, double end);
|
||
ScrBytes *scr_bytes_fill_str(ScrBytes *b, const ScrStr *s, const ScrStr *enc,
|
||
double nargs, double offset, double end);
|
||
double scr_bytes_copy_into(const ScrBytes *src, ScrBytes *dst, double nargs,
|
||
double ts, double ss, double se);
|
||
ScrBytes *scr_bytes_swap(ScrBytes *b, double width);
|
||
double scr_bytes_write_str(ScrBytes *b, const ScrStr *s, const ScrStr *enc,
|
||
double offset, double len, bool has_len);
|
||
ScrBytes *scr_bytes_concat_len(const ScrArr *list, double total);
|
||
|
||
/* Buffer.concat(list): list is a SCR_ELEM_BYTES array of u8 bytes values;
|
||
* the result is one fresh copy of their concatenation. Borrows the list.
|
||
* Never throws. */
|
||
ScrBytes *scr_bytes_concat(const ScrArr *list); /* +1 */
|
||
|
||
/* ── util.inspect (scr_inspect.c — linked ONLY when the program calls
|
||
* util.inspect/format; see native-toolchain.ts) ─────────────────────────────────────
|
||
* The runtime half of the static inspect rendering: the compiler
|
||
* synthesizes one traversal helper per static type; these entries own
|
||
* Node's exact scalar formatting and the layout engine (frames,
|
||
* break-length, grid grouping) for the DEFAULT options. All arguments
|
||
* BORROWED; ScrStr results +1. Nothing here throws.
|
||
*
|
||
* The frame protocol, mirroring Node's formatRaw: a non-empty composite
|
||
* calls begin(recurse+1) (indentation +2, currentDepth), renders each
|
||
* child to a string and entry()s it (is_num drives the grid's
|
||
* padStart/padEnd order), then end(base, brace0, brace1, recurse+1,
|
||
* array_extras, trailing_more) reduces to one string at the parent
|
||
* indentation. Empty composites and depth placeholders never touch the
|
||
* stack (the frontend emits their literals directly). */
|
||
ScrStr *scr_insp_f64(double x); /* JS ToString, except -0 → "-0" */
|
||
ScrStr *scr_insp_str(ScrStr *s); /* the quoting ladder + line splitting */
|
||
ScrStr *scr_insp_regex(ScrRegex *re); /* /source/flags */
|
||
ScrStr *scr_insp_buffer(ScrBytes *b); /* <Buffer aa bb ...>, 50-byte cap */
|
||
/* The stackless error form: [Name: message] / [Name], plus the stamped
|
||
* code slot as its one extra property ({ code: 'X' }); [Name] beyond
|
||
* depth when a code makes it composite. */
|
||
ScrStr *scr_insp_error(ScrError *e, double recurse, double depth);
|
||
/* The checked-dynamic tree: shape lives in the value, so the whole
|
||
* traversal is here. dyn-boxed bytes render in the checked-dynamic tree's documented
|
||
* Uint8Array identity. */
|
||
ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth);
|
||
/* format's %s / rest-args twin: dyn strings pass verbatim. */
|
||
ScrStr *scr_insp_dyn_s(ScrDyn *d, double depth);
|
||
#ifdef SCR_DYNAMIC
|
||
/* Island `any` (scr_inspect_island.c — linked when a --dynamic build's
|
||
* IR carries insp.* libCalls): the scalar kinds render exactly (typeof
|
||
* dispatch); composites THROW a catchable TypeError (callers are
|
||
* compiler-emitted pending checks). */
|
||
ScrStr *scr_insp_jsval(ScrJsval *v, double recurse, double depth);
|
||
#endif
|
||
void scr_insp_begin(double recurse);
|
||
void scr_insp_entry(ScrStr *s, bool is_num);
|
||
/* Circular references, Node-exact (<ref *N> / [Circular *N]): the
|
||
* compiler-emitted helpers over CYCLE-CAPABLE composites call circ_check
|
||
* FIRST (a value already on the traversal stack answers its circular id,
|
||
* assigned in discovery order — the caller renders scr_insp_circular and
|
||
* descends no further), seen_push after begin, and ref_wrap around end's
|
||
* result (pops the stack; values in the circular map gain the "<ref *N> "
|
||
* prefix). State resets at each top-level value's first frame
|
||
* (scr_insp_begin(1)). ref_wrap BORROWS s and returns +1. */
|
||
double scr_insp_circ_check(const void *v);
|
||
void scr_insp_seen_push(const void *v);
|
||
ScrStr *scr_insp_circular(double id);
|
||
ScrStr *scr_insp_ref_wrap(const void *v, ScrStr *s);
|
||
ScrStr *scr_insp_more_items(double remaining); /* "... N more items" */
|
||
ScrStr *scr_insp_key(ScrStr *k); /* bare-or-quoted property-name ladder; +1 */
|
||
ScrStr *scr_insp_end(ScrStr *base, ScrStr *b0, ScrStr *b1, double recurse,
|
||
bool array_extras, bool trailing_more);
|
||
|
||
/* The buf.read* / buf.write* numeric families on u8 bytes, Node-exact:
|
||
* boundsError's ladder (non-integer offset → 'It must be an integer', a
|
||
* buffer shorter than the width → 'Attempt to access memory outside
|
||
* buffer bounds', otherwise the '>= 0 and <= max' render — big Received
|
||
* values carry Node's underscore separators) and checkInt's value gate
|
||
* for the integer writes (range first, widths past 4 bytes in the
|
||
* '2 ** N' form; NaN passes and writes zeros). All catchable. Fixed
|
||
* widths take a kind + littleEndian flag; the variable-width family
|
||
* (read/writeUIntLE-style) validates byteLength (1-6) first, then value
|
||
* (writes), then offset. Writes return offset + width; float kinds skip
|
||
* the value gate entirely (any double stores, f32 rounds to nearest). */
|
||
typedef enum {
|
||
SCR_BN_U8,
|
||
SCR_BN_I8,
|
||
SCR_BN_U16,
|
||
SCR_BN_I16,
|
||
SCR_BN_U32,
|
||
SCR_BN_I32,
|
||
SCR_BN_F32,
|
||
SCR_BN_F64,
|
||
} ScrBytesNumKind;
|
||
double scr_bytes_read_num(const ScrBytes *b, double offset, ScrBytesNumKind kind, bool le);
|
||
double scr_bytes_write_num(ScrBytes *b, double value, double offset, ScrBytesNumKind kind, bool le);
|
||
double scr_bytes_read_var(const ScrBytes *b, double offset, double byte_length, bool sign, bool le);
|
||
double scr_bytes_write_var(ScrBytes *b, double value, double offset, double byte_length, bool sign, bool le);
|
||
bool scr_bytes_read_u64_raw(const ScrBytes *b, double offset, bool le, uint64_t *out);
|
||
bool scr_bytes_write_u64_raw(ScrBytes *b, double offset, bool le, uint64_t value);
|
||
bool scr_dataview_read_u64_raw(const ScrBytes *b, double offset, bool le, uint64_t *out);
|
||
bool scr_dataview_write_u64_raw(ScrBytes *b, double offset, bool le, uint64_t value);
|
||
|
||
/* fs.readFileSync(path) [no encoding] / writeFileSync(path, buf) — the
|
||
* Buffer forms of scr_lib.c's utf8 pair, byte-exact and NUL-safe.
|
||
* Failures THROW catchably (scr_fs_throw, Node-shaped messages). */
|
||
ScrBytes *scr_fs_read_file_bytes(ScrStr *path); /* +1 */
|
||
/* readFileSync's runtime-encoding form (scr_bytes_io.c's note): +1 dyn
|
||
* value — a Buffer box for undefined/null, a string for utf8 — or NULL
|
||
* with the exception pending. */
|
||
ScrDyn *scr_fs_read_file_sync_dyn(ScrStr *path, const ScrDyn *enc);
|
||
ScrBytes *scr_fs_read_fd_bytes(double fd); /* +1; the fd form (scr_lib.c) */
|
||
void scr_fs_write_file_bytes(ScrStr *path, const ScrBytes *data);
|
||
void scr_fs_append_file_bytes(ScrStr *path, const ScrBytes *data);
|
||
|
||
/* fs/promises readFile(path) [no encoding]: the same read behind an
|
||
* already-settled promise — failure REJECTS (catchable at the await). */
|
||
ScrPromise *scr_fsp_read_file_bytes(ScrStr *path); /* +1 */
|
||
|
||
/* crypto.randomBytes(n) → a real u8 Buffer. Out-of-range n THROWS Node's
|
||
* RangeError catchably (same check as scr_crypto_random_string). */
|
||
ScrBytes *scr_crypto_random_bytes(double n); /* +1 */
|
||
|
||
/* process.stdout/stderr.write(buf[, encoding]): the raw byte writes' Buffer
|
||
* overloads (same streams and buffering as the string forms). The encoding
|
||
* is evaluated by the caller and ignored here, as Node does for bytes.
|
||
* Constantly true. */
|
||
bool scr_process_stdout_write_bytes(const ScrBytes *b, const ScrStr *encoding);
|
||
bool scr_process_stderr_write_bytes(const ScrBytes *b, const ScrStr *encoding);
|
||
|
||
/* The Node-shaped fs error thrower (scr_lib.c): formats "ENOENT: no such
|
||
* file or directory, open 'x'" and throws it catchably. Shared with
|
||
* scr_bytes.c's fs forms. */
|
||
void scr_fs_throw(int e, const char *op, const ScrStr *path);
|
||
|
||
/* ── zlib (scr_zlib.c — compiled and linked with -lz only when the
|
||
* program uses zlib, like scr_regex.c/libregexp; see native-toolchain.ts) ──────────
|
||
* deflateSync/inflateSync over u8 bytes with Node's default options
|
||
* (zlib format, default level/windowBits). deflate never throws (OOM
|
||
* aborts); inflate of corrupt input THROWS Node's error catchably
|
||
* ("incorrect header check", ...). Borrow their input; results +1. */
|
||
ScrBytes *scr_zlib_deflate(const ScrBytes *data);
|
||
ScrBytes *scr_zlib_inflate(const ScrBytes *data);
|
||
/* The format-mode variants (0 zlib / 1 raw / 2 gzip; inflate adds
|
||
* 3 = auto-detect). Static one-shot codecs call these directly, and
|
||
* scr_zlib_island.c bridges them into the embedded engine's node:zlib shim. */
|
||
ScrBytes *scr_zlib_deflate_mode(const ScrBytes *data, double mode, double level);
|
||
ScrBytes *scr_zlib_inflate_mode(const ScrBytes *data, double mode);
|
||
double scr_zlib_crc32(const ScrBytes *data, double value);
|
||
typedef void (*ScrZlibBytesFn)(ScrClosure *cb, ScrError *err,
|
||
ScrBytes *value /* moves, NULL on error */);
|
||
void scr_zlib_codec_async(const ScrBytes *data, double mode, bool compressing,
|
||
ScrClosure *cb /* moves */, ScrZlibBytesFn fn);
|
||
void scr_zlib_island_install(void);
|
||
|
||
/* ── node:net (scr_net.c — compiled and linked ONLY when the program
|
||
* uses the net surface, like scr_events.c; see native-toolchain.ts) ─────────────────
|
||
* TCP servers and sockets over the unit's own readiness poller
|
||
* (scr_platform.h: kqueue on macOS/BSD, epoll on Linux): refcounted handles
|
||
* whose listeners MOVE in and drop at settlement (the ScrChild ownership
|
||
* story), consumer-driven reads (the stdin discipline), deferred
|
||
* 'listening'/'error'/'close' emits delivered by the loop's dispatch
|
||
* hook. The emitted main calls scr_net_install() before %main; net-free
|
||
* builds keep their exact link line. Full design note atop scr_net.c. */
|
||
typedef struct ScrNetServer ScrNetServer;
|
||
typedef struct ScrNetSocket ScrNetSocket;
|
||
typedef void (*ScrNetConnFn)(ScrClosure *cb, ScrNetSocket *sock); /* sock +1 */
|
||
typedef void (*ScrNetDataFn)(ScrClosure *cb, ScrBytes *chunk); /* borrowed */
|
||
|
||
/* The listener-list family (snapshot firing, once-before-run): owned by
|
||
* scr_net.c, reused by scr_http.c for the request-body event lists. */
|
||
typedef struct ScrNetOnce ScrNetOnce;
|
||
|
||
typedef struct {
|
||
ScrClosure *cb; /* owned */
|
||
void *fn; /* adapter with the event's firing ABI */
|
||
bool once;
|
||
ScrNetOnce *shared_once; /* owned, nullable: one once registration mirrored across lists */
|
||
} ScrNetL;
|
||
|
||
typedef struct {
|
||
ScrNetL *ls;
|
||
size_t n, cap;
|
||
} ScrNetLs;
|
||
|
||
void scr_net_ls_add(ScrNetLs *l, ScrClosure *cb, void *fn, bool once);
|
||
ScrNetOnce *scr_net_once_new(void); /* +1 */
|
||
ScrNetOnce *scr_net_once_retain(ScrNetOnce *once); /* +1 */
|
||
void scr_net_once_release(ScrNetOnce *once);
|
||
void scr_net_ls_add_shared_once(ScrNetLs *l, ScrClosure *cb, void *fn,
|
||
ScrNetOnce *once /*moves*/);
|
||
bool scr_net_ls_has_live(const ScrNetLs *l);
|
||
void scr_net_ls_drop(ScrNetLs *l);
|
||
size_t scr_net_ls_snapshot(ScrNetLs *l, ScrNetL **out);
|
||
void scr_net_fire0(ScrNetLs *l);
|
||
/* The receiver-binding twins: the emitting handle rides the pass as the
|
||
* ambient receiver (scr_dyn_this_push — `this` in listener bodies).
|
||
* `self` BORROWED; NULL binds undefined (= the plain spellings above). */
|
||
void scr_net_fire0_this(ScrNetLs *l, void *self, ScrDynHandleTag tag);
|
||
void scr_net_fire_err_this(ScrNetLs *l, ScrStr *msg, void *self, ScrDynHandleTag tag);
|
||
|
||
/* Protocol-layer (scr_http.c) hooks: a C-level connection consumer on
|
||
* servers, and a C-level reader on sockets (counts as a consumer for
|
||
* read-arming; the context BORROWS its socket and is freed with it). */
|
||
typedef void (*ScrNetNativeConnFn)(void *ctx, ScrNetSocket *sock);
|
||
typedef void (*ScrNetNativeDataFn)(void *ctx, const char *buf, size_t n);
|
||
typedef void (*ScrNetNativeEventFn)(void *ctx);
|
||
typedef bool (*ScrNetNativeTimeoutHandledFn)(void *ctx);
|
||
typedef bool (*ScrNetNativeErrFn)(void *ctx, ScrStr *msg); /* true = consumed */
|
||
void scr_net_server_set_native_conn(ScrNetServer *s, ScrNetNativeConnFn fn, void *ctx, void (*ctx_free)(void *));
|
||
/* The HTTP-parser ctx ALIAS: scr_http.c stamps its server ctx here so
|
||
* late 'request' listener installs (scr_http_server_on_request) can
|
||
* reach it even after a TLS wrap replaced the native-conn hook (the
|
||
* https/http2 servers: scr_tls.c owns the native ctx, the http ctx sits
|
||
* inside it). BORROWED — owned via the native-conn chain, valid while
|
||
* the server lives; NULL on servers with no HTTP parser. */
|
||
void scr_net_server_set_http_ctx(ScrNetServer *s, void *ctx);
|
||
void *scr_net_server_get_http_ctx(ScrNetServer *s);
|
||
/* The protocol layer's settle hook: called once when the server settles
|
||
* ('close' emitted, lists dropped) with the http_ctx alias — scr_http.c
|
||
* drops its own request/upgrade/connect lists there (the settle-releases-
|
||
* listeners story across the layer seam). */
|
||
void scr_net_server_set_proto_settle(ScrNetServer *s, void (*fn)(void *));
|
||
bool scr_net_server_settled(ScrNetServer *s); /* 'close' already emitted */
|
||
/* The dyn-dispatch hook for HTTP server events: scr_http.c registers it
|
||
* at scr_http_dyn_install, and the netServer handle ops route
|
||
* on('request', ...) (and future http events) through it — link gating
|
||
* keeps scr_net.c from naming http entry points. Returns false for an
|
||
* event the http layer does not model (the caller fences loudly). */
|
||
void scr_net_set_dynh_http_on(bool (*fn)(ScrNetServer *, const char *, const ScrDyn *, bool));
|
||
void scr_net_sock_set_native_reader(ScrNetSocket *s, ScrNetNativeDataFn data, ScrNetNativeEventFn eof, ScrNetNativeEventFn closed, void *ctx, void (*ctx_free)(void *));
|
||
void scr_net_sock_set_native_idle_checker(ScrNetSocket *s, bool (*fn)(void *));
|
||
/* The upgrade handover: clear the reader's fn pointers, keep the ctx. */
|
||
void scr_net_sock_clear_native_reader(ScrNetSocket *s);
|
||
/* The accepting server (BORROWED; NULL on client sockets) — the protocol
|
||
* layer's receiver for server-level fires ('request': this === server). */
|
||
ScrNetServer *scr_net_sock_server(ScrNetSocket *s);
|
||
/* socket.setEncoding(enc): utf8 flips 'data' delivery to strings (the
|
||
* chunk-encoding window); real-but-unsupported encodings fence loudly;
|
||
* unknown names throw Node's ERR_UNKNOWN_ENCODING. May throw. */
|
||
void scr_net_sock_set_encoding(ScrNetSocket *s, ScrStr *enc /*borrowed*/);
|
||
bool scr_net_sock_destroyed(ScrNetSocket *s); /* socket.destroyed */
|
||
bool scr_net_sock_writable(ScrNetSocket *s); /* socket.writable */
|
||
bool scr_net_sock_established(ScrNetSocket *s); /* connected, including TLS handshake */
|
||
void scr_net_sock_set_native_events(ScrNetSocket *s, ScrNetNativeEventFn timeout, ScrNetNativeErrFn err);
|
||
void scr_net_sock_set_native_http_timeout(ScrNetSocket *s, ScrNetNativeTimeoutHandledFn fn);
|
||
void scr_net_sock_write_native(ScrNetSocket *s, const char *buf, size_t n);
|
||
/* The protocol layer's deferred-emit hook: `pending` joins the loop's
|
||
* liveness test, `sweep` runs at every net sweep top. */
|
||
void scr_net_set_proto_sweep(bool (*pending)(void), void (*sweep)(void));
|
||
void scr_net_fire_err(ScrNetLs *l, ScrStr *msg); /* unhandled => exit(1) */
|
||
|
||
ScrNetServer *scr_net_server_retain(ScrNetServer *s);
|
||
void scr_net_server_release(ScrNetServer *s);
|
||
void *scr_net_server_retain_v(void *p);
|
||
void scr_net_server_release_v(void *p);
|
||
ScrNetSocket *scr_net_sock_retain(ScrNetSocket *s);
|
||
void scr_net_sock_release(ScrNetSocket *s);
|
||
void *scr_net_sock_retain_v(void *p);
|
||
void scr_net_sock_release_v(void *p);
|
||
|
||
ScrNetServer *scr_net_create_server(ScrClosure *handler /*moves, nullable*/, ScrNetConnFn fn); /* +1 */
|
||
void scr_net_listen(ScrNetServer *s, double port, ScrClosure *cb /*moves, nullable*/);
|
||
/* listen({ port, host, ipv6Only, reusePort }): host is an IP literal ("" =
|
||
* the dual-stack any default); ipv6Only sets IPV6_V6ONLY before the bind.
|
||
* The old entry point ignores reusePort and is retained for old IR. */
|
||
void scr_net_listen_opts(ScrNetServer *s, double port, ScrStr *host /*borrowed*/,
|
||
bool ipv6_only, ScrClosure *cb /*moves, nullable*/);
|
||
/* Additive ABI for listen({ port, host, ipv6Only, reusePort }): reuse_port
|
||
* requests the platform matrix documented in scr_net.c. Unsupported
|
||
* platforms and kernels fail asynchronously; they never fall back to an
|
||
* ordinary single listener. */
|
||
void scr_net_listen_opts_reuse_port(ScrNetServer *s, double port, ScrStr *host /*borrowed*/,
|
||
bool ipv6_only, bool reuse_port,
|
||
ScrClosure *cb /*moves, nullable*/);
|
||
double scr_net_server_port(ScrNetServer *s); /* address().port */
|
||
bool scr_net_server_listening(ScrNetServer *s);
|
||
/* Writable http.Server timeout property storage. `field` is the compiler
|
||
* ABI selector: timeout, keepAliveTimeout, headersTimeout, requestTimeout,
|
||
* keepAliveTimeoutBuffer. Typed reads validate that no dynamic write left
|
||
* a non-number in the ordinary JS property slot. */
|
||
double scr_net_server_timeout_get(ScrNetServer *s, double field);
|
||
void scr_net_server_timeout_set(ScrNetServer *s, double field, double value);
|
||
void scr_net_server_set_timeout_plain(ScrNetServer *s, double ms);
|
||
void scr_net_server_set_timeout_cb(ScrNetServer *s, double ms, ScrClosure *cb /*moves*/, ScrNetConnFn fn);
|
||
void scr_net_server_on_timeout(ScrNetServer *s, ScrClosure *cb /*moves*/, ScrNetConnFn fn, bool once);
|
||
/* Marks the shared net-server handle as an HTTP/1 or HTTPS server. The
|
||
* dynamic handle uses this to keep HTTP-only fields off net/TLS/H2. */
|
||
void scr_net_server_enable_http_timeout_surface(ScrNetServer *s);
|
||
/* Constructor-option twin: undefined is absent; otherwise validates type
|
||
* and a non-negative safe integer before storing, matching Node's option
|
||
* ladder (plain property writes do not validate). */
|
||
bool scr_net_server_timeout_option_check(double field, double value);
|
||
void scr_net_server_timeout_option_set(ScrNetServer *s, double field, const struct ScrDyn *value);
|
||
ScrStr *scr_net_server_addr_ip(ScrNetServer *s); /* +1 — address().address */
|
||
ScrStr *scr_net_server_addr_family(ScrNetServer *s); /* +1 — address().family */
|
||
void scr_net_server_close(ScrNetServer *s, ScrClosure *cb /*moves, nullable*/);
|
||
void scr_net_server_close_all_connections(ScrNetServer *s);
|
||
void scr_net_server_close_idle_connections(ScrNetServer *s);
|
||
/* The REAL close behind `wrapper.close.bind(wrapper)` — never consults
|
||
* the override (the proxy-through idiom cannot recurse). */
|
||
void scr_net_server_close_direct(ScrNetServer *s, ScrClosure *cb /*moves, nullable*/);
|
||
/* wrapper.close = fn — the override MOVES in (a compiler-emitted
|
||
* zero-arg wrapper); reassignment releases the old one. */
|
||
void scr_net_server_set_close_override(ScrNetServer *s, ScrClosure *ov /*moves*/);
|
||
void scr_net_server_on_error(ScrNetServer *s, ScrClosure *cb /*moves*/, ScrChildErrFn fn, bool once);
|
||
void scr_net_server_on_close(ScrNetServer *s, ScrClosure *cb /*moves*/, bool once);
|
||
/* server.on('listening', cb): the deferred 'listening' list listen(port,
|
||
* cb) rides — fires once per successful bind; late registrations on an
|
||
* already-listening server never fire (Node's once-per-listen emit). */
|
||
void scr_net_server_on_listening(ScrNetServer *s, ScrClosure *cb /*moves*/, bool once);
|
||
void scr_net_server_on_connection(ScrNetServer *s, ScrClosure *cb /*moves*/, ScrNetConnFn fn, bool once);
|
||
/* 'secureConnection' — the deferred-connection list (TLS handshake
|
||
* timing); released unread on a server without deferral (a plain net
|
||
* server never fires it, like Node). */
|
||
void scr_net_server_on_secure_connection(ScrNetServer *s, ScrClosure *cb /*moves*/, ScrNetConnFn fn, bool once);
|
||
ScrNetSocket *scr_net_connect(double port, ScrStr *host /*borrowed, nullable*/, ScrClosure *cb /*moves, nullable*/); /* +1 */
|
||
/* connect with a validated autoSelectFamilyAttemptTimeout option: the
|
||
* budget runs Node's validateInt32-from-1 ladder (ERR_OUT_OF_RANGE /
|
||
* ERR_INVALID_ARG_TYPE) and is then inert — the single dial has nothing
|
||
* to time. +1, or NULL with the throw pending. */
|
||
ScrNetSocket *scr_net_connect_attempt(double port, ScrStr *host /*borrowed*/, const ScrDyn *t /*borrowed*/);
|
||
/* net.connect/createConnection over a RUNTIME option bag (computed
|
||
* keys): Node-order validation (objectMode trio, port, host,
|
||
* autoSelectFamily, attempt budget), then the compiler-rendered fence —
|
||
* always leaves an exception pending. Borrowed. */
|
||
void scr_net_connect_opts_chk(const ScrDyn *opts, const ScrStr *fence);
|
||
/* connect with a caller lookup (net.connect({ ..., lookup })): invokes
|
||
* lookup(hostname, options, answer-closure) synchronously; answer_fn is
|
||
* the emitted per-shape thunk that decodes the answer down to
|
||
* scr_net_lookup_answer. Dials the answered addresses in order. */
|
||
/* Blocking first-answer hostname resolution for the CLIENT bridges (the
|
||
* native fetch, the island's http/https client) — the dns.lookup
|
||
* precedent: getaddrinfo at call time. +1: the resolved IP, or a retain
|
||
* of `host` (already an IP literal / localhost / unresolvable — the dial
|
||
* then delivers Node's exact getaddrinfo ENOTFOUND cause). */
|
||
ScrStr *scr_net_blocking_lookup(ScrStr *host /*borrowed*/);
|
||
ScrNetSocket *scr_net_connect_lookup(double port, ScrStr *host /*borrowed*/,
|
||
ScrClosure *lookup /*moves*/, void *answer_fn); /* +1 */
|
||
void scr_net_lookup_answer(ScrClosure *self, bool has_err, ScrStr *msg /*borrowed, nullable*/,
|
||
ScrArr *ips /*borrowed, nullable*/);
|
||
void scr_net_sock_write_str(ScrNetSocket *s, ScrStr *data /*borrowed*/);
|
||
void scr_net_sock_write_bytes(ScrNetSocket *s, ScrBytes *data /*borrowed*/);
|
||
void scr_net_sock_end(ScrNetSocket *s);
|
||
void scr_net_sock_end_str(ScrNetSocket *s, ScrStr *data /*borrowed*/);
|
||
void scr_net_sock_end_bytes(ScrNetSocket *s, ScrBytes *data /*borrowed*/);
|
||
/* Checked-dynamic chunk forms (an untyped JS payload into a typed
|
||
* socket): STR/BYTES dispatch; end() takes undefined/null as no chunk;
|
||
* anything else throws Node's ERR_INVALID_ARG_TYPE chunk TypeError. */
|
||
void scr_net_sock_write_dynv(ScrNetSocket *s, const ScrDyn *d /*borrowed*/);
|
||
void scr_net_sock_end_dynv(ScrNetSocket *s, const ScrDyn *d /*borrowed*/);
|
||
void scr_net_sock_destroy(ScrNetSocket *s);
|
||
/* Flow control (pause/resume — see the struct's flag comments), the
|
||
* FIN-flushed destroy (destroySoon), TCP_NODELAY, the deferred write/
|
||
* finish callbacks (write(chunk, cb) / end(cb) — sweep-fired), and the
|
||
* counters/flags the compat surface reads. */
|
||
ScrNetSocket *scr_net_sock_pause(ScrNetSocket *s); /* +1: chaining */
|
||
ScrNetSocket *scr_net_sock_resume(ScrNetSocket *s); /* +1: chaining */
|
||
ScrNetSocket *scr_net_sock_set_nodelay(ScrNetSocket *s, bool enable); /* +1: chaining */
|
||
void scr_net_sock_set_keepalive(ScrNetSocket *s, bool enable, double delay_ms);
|
||
void scr_net_sock_destroy_soon(ScrNetSocket *s);
|
||
void scr_net_sock_on_finish(ScrNetSocket *s, ScrClosure *cb /*moves*/);
|
||
void scr_net_sock_on_write_flush(ScrNetSocket *s, ScrClosure *cb /*moves*/);
|
||
double scr_net_sock_bytes_written(ScrNetSocket *s);
|
||
bool scr_net_sock_readable(ScrNetSocket *s);
|
||
/* The deferred dial (the http agent's maxSockets queue): the socket
|
||
* registers "connecting" and buffers writes; dial_start runs the dial. */
|
||
ScrNetSocket *scr_net_connect_deferred(double port, ScrStr *host /*borrowed, nullable*/); /* +1 */
|
||
void scr_net_sock_dial_start(ScrNetSocket *s);
|
||
void scr_net_sock_set_timeout(ScrNetSocket *s, double ms);
|
||
void scr_net_sock_on_timeout(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once);
|
||
ScrStr *scr_net_sock_remote_address(ScrNetSocket *s); /* +1 or NULL (undefined arm) */
|
||
bool scr_net_sock_encrypted(ScrNetSocket *s); /* true = TLS transport (false maps to the undefined arm) */
|
||
void scr_net_sock_pipe(ScrNetSocket *src, ScrNetSocket *dst);
|
||
void scr_net_sock_on_data(ScrNetSocket *s, ScrClosure *cb /*moves*/, ScrNetDataFn fn, bool once);
|
||
void scr_net_sock_on_end(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once);
|
||
void scr_net_sock_on_close(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once);
|
||
void scr_net_sock_on_error(ScrNetSocket *s, ScrClosure *cb /*moves*/, ScrChildErrFn fn, bool once);
|
||
void scr_net_sock_on_connect(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once);
|
||
/* The paused-mode surface (the demux path: once('readable') + read(1) +
|
||
* unshift + emit('connection')). 'readable' listeners are consumers —
|
||
* arrived bytes buffer in the receive buffer and the event announces
|
||
* them (and EOF, where read answers NULL). read(n) answers exactly n
|
||
* buffered bytes (+1) or NULL — Node's less-than-n answer; n <= 0 drains
|
||
* everything. unshift returns bytes to the FRONT of the stream, ahead of
|
||
* anything still in the kernel. emit('connection', sock) routes a socket
|
||
* into another server's protocol layer — the TLS target's 'connection'
|
||
* defers to its handshake, the http target's parser drains the buffered
|
||
* peek first. */
|
||
void scr_net_sock_on_readable(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once);
|
||
ScrBytes *scr_net_sock_read_bytes(ScrNetSocket *s, double n); /* +1 or NULL */
|
||
void scr_net_sock_unshift_bytes(ScrNetSocket *s, ScrBytes *data /*borrowed*/);
|
||
void scr_net_server_emit_connection(ScrNetServer *srv, ScrNetSocket *sock /*borrowed*/);
|
||
void scr_net_data_thunk0(ScrClosure *cb, ScrBytes *chunk);
|
||
void scr_net_data_thunk_bytes(ScrClosure *cb, ScrBytes *chunk);
|
||
void scr_net_data_thunk_str(ScrClosure *cb, ScrBytes *chunk);
|
||
/* The dynCheck-adapted listener's flavor: boxes the chunk as a
|
||
* Buffer-flavored dyn (toString decodes utf8, Node's 'data' payload). */
|
||
void scr_net_data_thunk_dyn(ScrClosure *cb, ScrBytes *chunk);
|
||
void scr_net_conn_thunk0(ScrClosure *cb, ScrNetSocket *sock);
|
||
void scr_net_conn_thunk_sock(ScrClosure *cb, ScrNetSocket *sock);
|
||
void scr_net_install(void);
|
||
/* Registers the netSocket handle-dispatch ops (SCR_DYNH_NET_SOCKET) —
|
||
* emitted main() calls this alongside scr_net_install exactly when the
|
||
* net unit is linked, so the always-linked dyn core never references
|
||
* this unit's entry points. */
|
||
void scr_net_dyn_install(void);
|
||
#ifdef SCR_RC_AUDIT
|
||
long scr_net_live_count(void);
|
||
#endif
|
||
|
||
/* ── the socket TRANSPORT hooks (scr_tls.c — compiled only into
|
||
* TLS-using binaries; scr_net.c stays transport-agnostic through this
|
||
* ops table). A transport sits UNDER the socket kind: reads and writes
|
||
* redirect through xread/xwrite once installed (read(2)/write(2) shapes —
|
||
* >0 bytes, 0 EOF, -1 with errno, EAGAIN meaning retry on readiness),
|
||
* writes buffer until the handshake establishes, and readiness events
|
||
* drive `handshake` until it answers done (1) or failed (-1 — the
|
||
* transport reports the failure itself via scr_net_sock_transport_error
|
||
* first, Node-shaped message or NULL for the server's silent
|
||
* tlsClientError default). `on_established` (nullable) lets a protocol
|
||
* layer attach AFTER the handshake (the https parser); `pending` answers
|
||
* whether decrypted bytes sit buffered inside the transport (the sweep
|
||
* drains them — the poller can't see bytes that already left the kernel);
|
||
* `shutdown_write` sends the close_notify before the FIN. The ops table
|
||
* is static storage in the transport unit; tctx is freed via `free`
|
||
* when the socket handle dies. */
|
||
typedef struct ScrNetTransportOps {
|
||
ssize_t (*xread)(void *tctx, char *buf, size_t n);
|
||
ssize_t (*xwrite)(void *tctx, const char *buf, size_t n);
|
||
int (*handshake)(void *tctx);
|
||
void (*on_established)(void *tctx);
|
||
bool (*pending)(void *tctx);
|
||
void (*shutdown_write)(void *tctx);
|
||
void (*free)(void *tctx);
|
||
} ScrNetTransportOps;
|
||
void scr_net_sock_set_transport(ScrNetSocket *s, const ScrNetTransportOps *ops, void *tctx);
|
||
int scr_net_sock_fd(ScrNetSocket *s);
|
||
/* Drain up to n bytes of the socket's receive buffer (peeked/unshifted
|
||
* bytes — the demux path); consumers call it before the fd. */
|
||
size_t scr_net_sock_take_buffered(ScrNetSocket *s, char *buf, size_t n);
|
||
/* Arm the write filter for a handshake send that hit EAGAIN. */
|
||
void scr_net_sock_transport_want_write(ScrNetSocket *s);
|
||
/* The socket's transport context, guarded by the ops table identity —
|
||
* NULL when the socket carries no transport or a different one (the SNI
|
||
* answer path re-finds its engine from the boxed socket). */
|
||
void *scr_net_sock_transport_ctx_for(ScrNetSocket *s, const ScrNetTransportOps *ops);
|
||
/* Re-drive a parked handshake (the asynchronous SNI answer arrived —
|
||
* no fd readiness is coming: the client is waiting for the ServerHello). */
|
||
void scr_net_sock_transport_resume(ScrNetSocket *s);
|
||
/* Replay raw bytes at the FRONT of the receive buffer — the SNI
|
||
* pre-parse handing the consumed ClientHello back to the TLS bio. */
|
||
void scr_net_sock_replay(ScrNetSocket *s, const char *data, size_t len);
|
||
/* Transport-level failure: msg (borrowed, Node's message shape) defers
|
||
* 'error' + teardown to the sweep; NULL tears down silently — Node's
|
||
* default for an unhandled server-side handshake failure. */
|
||
void scr_net_sock_transport_error(ScrNetSocket *s, const char *msg);
|
||
/* Defer the server's 'connection' listeners to handshake completion —
|
||
* tls.createServer's handler is the 'secureConnection' event, fired with
|
||
* the socket only once the TLS layer stands (Node's timing). */
|
||
void scr_net_server_defer_connections(ScrNetServer *s);
|
||
/* The current native-connection hook (the https wrapper reads the http
|
||
* parser installer it is about to wrap). */
|
||
void scr_net_server_get_native_conn(ScrNetServer *s, ScrNetNativeConnFn *fn, void **ctx, void (**ctx_free)(void *));
|
||
|
||
/* ── the checked-dynamic TLS-member hooks (scr_tls.c registers them when
|
||
* linked — scr_net.c stays TLS-agnostic, the dynh_http_on precedent).
|
||
* Each answers whether it HANDLED the member: `get` fills *out (+1, or
|
||
* NULL for the undefined arm) for TLSSocket property reads (authorized,
|
||
* authorizationError); `on` registers TLS event names ('secureConnect');
|
||
* `invoke` claims TLSSocket method names (the honest per-member fences
|
||
* for the not-yet-modeled surface). Unhandled members fall through to
|
||
* the plain-socket dispatch unchanged. */
|
||
typedef struct ScrNetDynhTlsHooks {
|
||
bool (*get)(ScrNetSocket *s, const char *key, struct ScrDyn **out);
|
||
bool (*on)(ScrNetSocket *s, const char *event, const struct ScrDyn *cb, bool once);
|
||
bool (*invoke)(ScrNetSocket *s, const char *method, struct ScrDyn *const *args, size_t argc,
|
||
struct ScrDyn **out);
|
||
} ScrNetDynhTlsHooks;
|
||
void scr_net_set_dynh_tls(const ScrNetDynhTlsHooks *hooks);
|
||
|
||
/* ── node:tls + node:https (scr_tls.c — compiled and linked, with the
|
||
* vendored mbedTLS archive, only when the program uses them; design
|
||
* note atop the file). tls.createServer returns an ORDINARY
|
||
* ScrNetServer whose handler fires as 'secureConnection' (post-
|
||
* handshake) with a socket that behaves exactly like a net socket;
|
||
* https.createServer is http.createServer over the same transport;
|
||
* https.request is the http client dialed through a client transport
|
||
* (port 443 default, `ca` / rejectUnauthorized for local-CA flows —
|
||
* cert/key/ca all arrive as borrowed PEM bytes). */
|
||
ScrNetServer *scr_tls_create_server(const char *cert, size_t cert_len, const char *key, size_t key_len, ScrClosure *handler /*moves, nullable*/, ScrNetConnFn fn); /* +1 */
|
||
|
||
/* ── RUNTIME tls/https options records (the divergence-66 stance: a
|
||
* non-literal options value reads its members at runtime; members whose
|
||
* literal forms are compile fences become runtime gates that THROW the
|
||
* catchable fence instead of behaving differently; undocumented keys
|
||
* drop exactly like Node drops them). cert/key values are PEM strings,
|
||
* Buffers, or one-element arrays of those; `ca` (client side)
|
||
* concatenates array entries into one trust-anchor bundle. The *_dyn
|
||
* creators walk a dyn options record (the checked-dynamic JS lane's
|
||
* shape) and mint the same servers the literal path mints; NULL returns
|
||
* carry a pending exception. scr_tls_pem_from_dyn is the literal path's
|
||
* runtime-valued cert/key extraction (`what` names the fence). */
|
||
ScrBytes *scr_tls_pem_from_dyn(const struct ScrDyn *v, const char *what); /* +1 or NULL+pending */
|
||
ScrNetServer *scr_tls_create_server_dyn(const struct ScrDyn *opts /*borrowed*/, ScrClosure *handler /*moves, nullable*/, ScrNetConnFn fn); /* +1 or NULL+pending */
|
||
/* The https twin (its ScrHttpReqFn parameter is declared with the http
|
||
* section below): scr_https_create_server_dyn. */
|
||
|
||
/* ── tls.connect (scr_tls.c): the TLS client socket. Dials TCP like
|
||
* net.connect and installs the client transport; the callback (and the
|
||
* socket's 'connect'/'secureConnect' listeners) fires post-handshake —
|
||
* Node's secureConnect timing. `port` < 0 reads the port from the
|
||
* options record; `host` NULL/empty reads host (default "localhost").
|
||
* Implemented option members: port, host, rejectUnauthorized (default
|
||
* true; false runs the handshake with verification RECORDED, so
|
||
* socket.authorized / authorizationError answer Node's split), ca,
|
||
* servername (the SNI + verify name). Every other documented member
|
||
* throws the runtime fence when present; unknown keys drop. */
|
||
ScrNetSocket *scr_tls_connect_dyn(double port, ScrStr *host /*borrowed, nullable*/, const struct ScrDyn *opts /*borrowed, nullable*/, ScrClosure *cb /*moves, nullable*/); /* +1 or NULL+pending */
|
||
|
||
/* The TLSSocket member surface on the socket kind (all no-ops / honest
|
||
* defaults on plain sockets): authorized answers Node's verify verdict
|
||
* (false on servers and unverified clients), auth_error the verify-
|
||
* failure CODE STRING (+1; NULL = Node's null), on_secure_connect
|
||
* registers at the establishment-fired conn list (released unread on a
|
||
* plain socket — the event never fires there, like Node), on_session
|
||
* registers the received-ticket event (fires once with the serialized
|
||
* session as a Buffer; `fn` is the data-listener adapter ABI). */
|
||
bool scr_tls_sock_authorized(ScrNetSocket *s);
|
||
ScrStr *scr_tls_sock_auth_error(ScrNetSocket *s); /* +1 or NULL */
|
||
void scr_tls_sock_on_secure_connect(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once);
|
||
void scr_tls_sock_on_session(ScrNetSocket *s, ScrClosure *cb /*moves*/, void *fn, bool once);
|
||
|
||
/* ── h2 over TLS (scr_tls.c ↔ scr_http2.c): the ALPN=h2 server wrap and
|
||
* the https-authority client wrap. scr_http2.c calls both directly —
|
||
* every http2-using binary links the TLS unit (the moduleUsesTls
|
||
* switch), so no registration indirection is needed. */
|
||
void scr_tls_server_wrap_h2(ScrNetServer *s, const char *cert, size_t cert_len, const char *key, size_t key_len, ScrNetNativeConnFn h2_conn, void *h2_ctx /*moves*/, void (*h2_ctx_free)(void *), ScrClosure *sni_cb /*moves, nullable*/, void *sni_answer_fn);
|
||
void scr_tls_h2_client_wrap(ScrNetSocket *sock, ScrStr *host /*borrowed*/, bool reject_unauthorized, const char *ca /*borrowed, len 0 = none*/, size_t ca_len);
|
||
|
||
/* ── tls.SecureContext + the SNI callback (scr_tls.c) ─────────────────
|
||
* A SecureContext is an opaque refcounted parsed cert/key pair
|
||
* (tls.createSecureContext({ cert, key })). A server created with an
|
||
* SNI callback parses each connection's ClientHello for the server_name
|
||
* extension BEFORE the mbedTLS handshake begins (the callback may answer
|
||
* asynchronously — cert generation on demand is the portless flow — and
|
||
* mbedTLS's own f_sni must answer synchronously, so the runtime resolves
|
||
* the name first and replays the hello bytes into the bio), invokes the
|
||
* JS callback with (servername, answer-closure), and resumes the
|
||
* handshake when scr_tls_sni_answer arrives: has_err tears the socket
|
||
* down silently (Node's 'tlsClientError' default), a ctx serves its
|
||
* cert/key via mbedtls_ssl_set_hs_own_cert, NULL serves the default
|
||
* pair. `answer_fn` is the emitted per-shape thunk that decodes the
|
||
* callback's program-interned unions down to this call — it becomes the
|
||
* minted answer closure's fn (caps[0] boxes the socket). Connections
|
||
* whose ClientHello carries no server_name skip the callback and serve
|
||
* the default pair, exactly Node. */
|
||
typedef struct ScrSecureCtx ScrSecureCtx;
|
||
ScrSecureCtx *scr_tls_create_secure_context(const char *cert, size_t cert_len, const char *key, size_t key_len); /* +1 */
|
||
/* createSecureContext over a RUNTIME options record: Node's typed option
|
||
* validations first, then the pem walk (+1, or NULL with the exception
|
||
* pending). Borrowed. */
|
||
ScrSecureCtx *scr_tls_create_secure_context_dyn(const ScrDyn *opts);
|
||
/* tls.getCACertificates(type): validateString + the documented name set,
|
||
* then the compiler-rendered fence — always leaves an exception pending. */
|
||
void scr_tls_ca_certs_chk(const ScrDyn *type, const ScrStr *fence);
|
||
ScrSecureCtx *scr_secure_ctx_retain(ScrSecureCtx *c);
|
||
void scr_secure_ctx_release(ScrSecureCtx *c);
|
||
void *scr_secure_ctx_retain_v(void *p);
|
||
void scr_secure_ctx_release_v(void *p);
|
||
ScrNetServer *scr_https_create_server_sni(const char *cert, size_t cert_len, const char *key, size_t key_len, ScrClosure *sni_cb /*moves, nullable*/, void *answer_fn); /* +1 */
|
||
void scr_tls_sni_answer(ScrClosure *self, bool has_err, ScrSecureCtx *ctx /*moves, nullable*/);
|
||
#ifdef SCR_RC_AUDIT
|
||
long scr_secure_ctx_live_count(void);
|
||
#endif
|
||
|
||
/* ── node:tls, the CA-store introspection slice (scr_tls_ca.c — its own
|
||
* unit and link gate; NO mbedTLS, so a getCACertificates-only binary never
|
||
* builds the archive; native-toolchain.ts also compiles it whenever scr_tls.c does). The
|
||
* HOST roots (Windows' system certificate stores, the established bundle probe on
|
||
* POSIX) stand in for both Node's compiled-in Mozilla roots ('bundled',
|
||
* rootCertificates) and the platform store ('system') — the established
|
||
* SEMANTICS divergence, extended to introspection; 'extra' uses the
|
||
* NODE_EXTRA_CA_CERTS file captured before user code runs. Arrays are cached
|
||
* per type (+1 retained answers each call — Node's own caching, and the
|
||
* identity the suite pins with strictEqual). */
|
||
void scr_tls_ca_install(void); /* snapshots NODE_EXTRA_CA_CERTS + file bytes */
|
||
bool scr_tls_ca_extra_pem(const char **pem, size_t *len); /* borrowed launch snapshot */
|
||
#ifdef _WIN32
|
||
/* Enumerates Windows' trusted ROOT, intermediate CA, and TrustedPeople store
|
||
* locations, yielding only certificates whose effective Windows EKU policy
|
||
* permits TLS server authentication. DER is borrowed for the call. */
|
||
typedef void (*ScrTlsCaWindowsCertFn)(void *ctx, const unsigned char *der, size_t len);
|
||
bool scr_tls_ca_windows_cert_server_auth(const void *cert_context);
|
||
void scr_tls_ca_windows_certs(ScrTlsCaWindowsCertFn fn, void *ctx);
|
||
#endif
|
||
ScrArr *scr_tls_ca_get(ScrStr *type); /* +1; throws ERR_INVALID_ARG_VALUE on unknown types */
|
||
ScrArr *scr_tls_ca_root(void); /* +1; === getCACertificates("bundled") */
|
||
/* Replaces the 'default' set: entries filter to their PEM certificate
|
||
* blocks (deduped byte-exactly — Node dedupes by X509 identity); a
|
||
* non-empty array yielding no blocks throws Node's
|
||
* ERR_CRYPTO_OPERATION_FAILED and leaves the set unchanged. Borrows. */
|
||
void scr_tls_ca_set_default(ScrArr *certs);
|
||
/* scr_tls.c's anchor consult: true iff setDefaultCACertificates ran;
|
||
* *pem/*len then carry the concatenated NUL-terminated blocks (len 0 =
|
||
* the empty set — verification fails, Node's own consequence), and *gen
|
||
* a counter that bumps per set so the parsed chain re-parses on change. */
|
||
bool scr_tls_ca_default_override(const char **pem, size_t *len, uint64_t *gen);
|
||
|
||
/* ── node:http, the server slice (scr_http.c — compiled only when the
|
||
* program uses it; rides scr_net.c wholesale, design note atop the
|
||
* file). http.createServer returns an ORDINARY ScrNetServer (listen/
|
||
* close/address/'error' reuse the net surface); req/res are lean
|
||
* refcounted handles, listeners dropping at settlement like sockets. */
|
||
typedef struct ScrHttpReq ScrHttpReq;
|
||
typedef struct ScrHttpRes ScrHttpRes;
|
||
typedef void (*ScrHttpReqFn)(ScrClosure *cb, ScrHttpReq *req, ScrHttpRes *res); /* both +1 */
|
||
typedef void (*ScrHttpConnectH2Fn)(ScrClosure *cb, ScrHttpReq *req, ScrHttpRes *res); /* both +1 */
|
||
/* 'upgrade' listeners, both sides: (req-or-res, socket, head) — all +1.
|
||
* The parser steps aside (native reader cleared) and the socket is the
|
||
* listener's raw stream; `head` carries bytes read past the 101/request
|
||
* head. Requests with Connection: upgrade + an Upgrade header fire the
|
||
* server list INSTEAD of 'request'; a 101 response fires the client list
|
||
* INSTEAD of 'response'; either side with NO listener destroys the
|
||
* socket, Node's default. */
|
||
typedef void (*ScrHttpUpgradeFn)(ScrClosure *cb, ScrHttpReq *req, ScrNetSocket *sock, ScrBytes *head);
|
||
|
||
ScrHttpReq *scr_http_req_retain(ScrHttpReq *r);
|
||
void scr_http_req_release(ScrHttpReq *r);
|
||
void *scr_http_req_retain_v(void *p);
|
||
void scr_http_req_release_v(void *p);
|
||
ScrHttpRes *scr_http_res_retain(ScrHttpRes *r);
|
||
void scr_http_res_release(ScrHttpRes *r);
|
||
void *scr_http_res_retain_v(void *p);
|
||
void scr_http_res_release_v(void *p);
|
||
|
||
/* ── the h2 compat seam (scr_http.c ⇄ scr_http2.c) ─────────────────────
|
||
* Http2ServerRequest/Http2ServerResponse ARE the req/res handles above;
|
||
* scr_http2.c (linked only when the real h2 surface is used) installs
|
||
* the transport vtable — response write paths become HEADERS/DATA
|
||
* frames — and the request-registration hook that routes
|
||
* server.on("request", ...) on an h2-tagged server ctx to the h2
|
||
* request list. scr_http.c links WITHOUT scr_http2.c, so the seam is a
|
||
* pointer pair, never a hard symbol reference in that direction. */
|
||
typedef struct ScrHttpH2Ops {
|
||
void *(*retain)(void *stream);
|
||
void (*release)(void *stream);
|
||
/* Serialize + send the response HEADERS frame: :status first, then the
|
||
* set headers (connection-specific names dropped, h2's rule), then the
|
||
* implicit date unless add_date is false (res.sendDate = false). */
|
||
void (*respond)(void *stream, double status, ScrStr *const *names,
|
||
ScrStr *const *values, size_t nheaders, bool add_date);
|
||
void (*write)(void *stream, const char *data, size_t n);
|
||
void (*end)(void *stream, const char *data, size_t n); /* DATA + END_STREAM */
|
||
void (*destroy)(void *stream); /* res.destroy: RST */
|
||
} ScrHttpH2Ops;
|
||
|
||
void scr_http_set_h2_ops(const ScrHttpH2Ops *ops);
|
||
void scr_http_set_h2_request_hook(void (*hook)(void *h2ctx, ScrClosure *cb /*moves*/,
|
||
void *fn, bool once));
|
||
void scr_http_set_h2_connect_hook(void (*hook)(void *h2ctx, ScrClosure *cb /*moves*/,
|
||
void *h1_fn, void *h2_fn, bool once));
|
||
void scr_http_set_h2_upgrade_hook(void (*hook)(void *h2ctx, ScrClosure *cb /*moves*/,
|
||
void *fn, bool once));
|
||
|
||
/* The compat pair, built by scr_http2.c per server stream. */
|
||
ScrHttpReq *scr_http_h2_req_new(ScrNetSocket *sock /*borrowed, nullable*/,
|
||
void *stream /*borrowed, nullable*/); /* +1 */
|
||
void *scr_http_req_h2_stream(ScrHttpReq *r); /* +1 or NULL */
|
||
void *scr_http_req_h2_stream_or_throw(ScrHttpReq *r, ScrStr *member /*borrowed*/); /* +1 */
|
||
void scr_http_h2_req_line(ScrHttpReq *r, ScrStr *method /*borrowed, nullable*/,
|
||
ScrStr *url /*borrowed, nullable*/);
|
||
void scr_http_h2_req_header(ScrHttpReq *r, ScrStr *name /*borrowed*/, ScrStr *value /*borrowed*/);
|
||
void scr_http_h2_req_data(ScrHttpReq *r, const char *data, size_t n);
|
||
void scr_http_h2_req_end(ScrHttpReq *r);
|
||
void scr_http_h2_req_aborted(ScrHttpReq *r);
|
||
void scr_http_h2_req_close(ScrHttpReq *r);
|
||
ScrHttpRes *scr_http_h2_res_new(ScrNetSocket *sock /*borrowed, nullable*/,
|
||
void *stream /*borrowed*/); /* +1 */
|
||
void scr_http_h2_res_close(ScrHttpRes *r);
|
||
bool scr_http_h2_res_finished(ScrHttpRes *r);
|
||
void scr_http_req_on_aborted(ScrHttpReq *r, ScrClosure *cb /*moves*/, bool once);
|
||
ScrStr *scr_http_req_http_version(ScrHttpReq *r); /* +1 — "1.0"/"1.1"/"2.0" */
|
||
double scr_http_req_http_version_major(ScrHttpReq *r);
|
||
double scr_http_req_http_version_minor(ScrHttpReq *r);
|
||
bool scr_http_req_aborted_flag(ScrHttpReq *r);
|
||
bool scr_http_req_complete(ScrHttpReq *r);
|
||
|
||
/* Registers the httpReq/httpRes handle-dispatch ops (SCR_DYNH_HTTP_REQ /
|
||
* SCR_DYNH_HTTP_RES) — emitted main() calls this exactly when the http
|
||
* unit is linked (the scr_net_dyn_install story). */
|
||
void scr_http_dyn_install(void);
|
||
void scr_http_validate_header_name(ScrStr *name /*borrowed*/, ScrStr *label /*borrowed*/);
|
||
void scr_http_validate_header_value(ScrStr *name /*borrowed*/, const ScrDyn *value /*borrowed*/);
|
||
ScrDyn *scr_http_status_codes(void); /* +1, shared mutable Node v24 table */
|
||
ScrArr *scr_http_methods(void); /* +1, shared mutable Node v24 array */
|
||
|
||
ScrNetServer *scr_http_create_server(ScrClosure *handler /*moves, nullable*/, ScrHttpReqFn fn); /* +1 */
|
||
/* The unguarded h2-only stream call: throws Node's exact catchable
|
||
* TypeError (member read on undefined). Borrows; never returns. */
|
||
void scr_http2_stream_undef_call(ScrStr *member);
|
||
/* Late 'request' listener install (server.on/once("request", ...)) — the
|
||
* http2 allowHTTP1 server's handler route (created handler-less), and
|
||
* http.Server's 'request' event. Listeners resolve at REQUEST time (the
|
||
* emit), Node's dispatch. On a server with no HTTP parser (a plain net
|
||
* server) the closure releases unregistered: 'request' never fires
|
||
* there, like Node. */
|
||
void scr_http_server_on_request(ScrNetServer *s, ScrClosure *cb /*moves*/, ScrHttpReqFn fn, bool once);
|
||
void scr_http1_ctx_on_request(void *ctx, ScrClosure *cb /*moves*/, ScrHttpReqFn fn, bool once,
|
||
ScrNetOnce *shared_once /*moves, nullable*/);
|
||
void scr_http1_ctx_on_connect(void *ctx, ScrClosure *cb /*moves*/, ScrHttpUpgradeFn fn, bool once,
|
||
ScrNetOnce *shared_once /*moves, nullable*/);
|
||
void scr_http1_ctx_on_upgrade(void *ctx, ScrClosure *cb /*moves*/, ScrHttpUpgradeFn fn, bool once);
|
||
void scr_http1_ctx_settle(void *ctx);
|
||
ScrStr *scr_http_req_url(ScrHttpReq *r); /* +1 */
|
||
ScrStr *scr_http_req_method(ScrHttpReq *r); /* +1 */
|
||
ScrStr *scr_http_req_header(ScrHttpReq *r, ScrStr *name /*borrowed*/); /* +1 or NULL (undefined arm) */
|
||
ScrStr *scr_http_req_trailer(ScrHttpReq *r, ScrStr *name /*borrowed*/); /* +1 or NULL */
|
||
ScrArr *scr_http_req_header_values(ScrHttpReq *r, ScrStr *name /*borrowed*/); /* +1 or NULL */
|
||
ScrArr *scr_http_req_trailer_values(ScrHttpReq *r, ScrStr *name /*borrowed*/); /* +1 or NULL */
|
||
void scr_http_req_on_data(ScrHttpReq *r, ScrClosure *cb /*moves*/, ScrNetDataFn fn, bool once);
|
||
/* req.pipe(dest): the IncomingMessage body streams into the destination;
|
||
* natural end ends it (Node's pipe default). Declared below the client
|
||
* typedef for the ClientRequest leg. */
|
||
void scr_http_req_pipe_res(ScrHttpReq *r, ScrHttpRes *dst /*borrowed*/);
|
||
void scr_http_req_pipe_sock(ScrHttpReq *r, ScrNetSocket *dst /*borrowed*/);
|
||
void scr_http_req_on_end(ScrHttpReq *r, ScrClosure *cb /*moves*/, bool once);
|
||
void scr_http_res_set_header(ScrHttpRes *r, ScrStr *name /*borrowed*/, ScrStr *value /*borrowed*/);
|
||
void scr_http_res_write_head(ScrHttpRes *r, double status);
|
||
void scr_http_res_write_head_n(ScrHttpRes *r, double status, ScrArr *names /*borrowed*/, ScrArr *values /*borrowed*/);
|
||
void scr_http_res_write_continue(ScrHttpRes *r);
|
||
void scr_http_res_write_processing(ScrHttpRes *r);
|
||
void scr_http_res_write_early_hints(ScrHttpRes *r, ScrArr *pairs /*borrowed: [name, value, ...] */);
|
||
/* writeHead with a CHECKED-DYNAMIC headers object (borrowed): OBJ
|
||
* entries setHeader in insertion order (string/number values), then the
|
||
* head goes out; undefined/null = the plain head; may throw. */
|
||
void scr_http_res_write_head_dyn(ScrHttpRes *r, double status, const ScrDyn *headers);
|
||
void scr_http_res_write_str(ScrHttpRes *r, ScrStr *data /*borrowed*/);
|
||
void scr_http_res_write_bytes(ScrHttpRes *r, ScrBytes *data /*borrowed*/);
|
||
void scr_http_res_end(ScrHttpRes *r);
|
||
/* socket.pipe(res): raw source chunks become response body writes; EOF
|
||
* ends the response (the extended-CONNECT bridge leg). Borrows both. */
|
||
void scr_http_sock_pipe_res(ScrNetSocket *src, ScrHttpRes *dst /*borrowed*/);
|
||
void scr_http_res_end_str(ScrHttpRes *r, ScrStr *data /*borrowed*/);
|
||
void scr_http_res_end_bytes(ScrHttpRes *r, ScrBytes *data /*borrowed*/);
|
||
void scr_http_res_add_trailers(ScrHttpRes *r, ScrArr *pairs /*borrowed: [name, value, ...] */);
|
||
/* Checked-dynamic chunk forms (the scr_net_sock_write_dynv story). */
|
||
void scr_http_res_write_dynv(ScrHttpRes *r, const ScrDyn *d /*borrowed*/);
|
||
void scr_http_res_end_dynv(ScrHttpRes *r, const ScrDyn *d /*borrowed*/);
|
||
bool scr_http_res_headers_sent(ScrHttpRes *r);
|
||
bool scr_http_res_writable_ended(ScrHttpRes *r);
|
||
bool scr_http_res_send_date(ScrHttpRes *r);
|
||
void scr_http_res_set_send_date(ScrHttpRes *r, bool value);
|
||
bool scr_http_res_strict_content_length(ScrHttpRes *r);
|
||
void scr_http_res_set_strict_content_length(ScrHttpRes *r, bool value);
|
||
ScrHttpReq *scr_http_res_request(ScrHttpRes *r); /* +1 */
|
||
ScrNetSocket *scr_http_res_socket(ScrHttpRes *r); /* +1 or NULL after finish */
|
||
bool scr_http_res_writable_finished(ScrHttpRes *r);
|
||
/* The res member surface: statusCode (200 until assigned; inert once the
|
||
* head went out), statusMessage (the reason phrase — assigned value, or
|
||
* the code's default), the header CRUD trio, and end(cb)'s finish slot
|
||
* (fires deferred once the body went out — the res 'close' precedent). */
|
||
double scr_http_res_status_get(ScrHttpRes *r);
|
||
void scr_http_res_status_set(ScrHttpRes *r, double status);
|
||
ScrStr *scr_http_res_status_msg_get(ScrHttpRes *r); /* +1 */
|
||
void scr_http_res_status_msg_set(ScrHttpRes *r, ScrStr *msg /*borrowed*/);
|
||
ScrStr *scr_http_res_get_header(ScrHttpRes *r, ScrStr *name /*borrowed*/); /* +1 or NULL */
|
||
ScrArr *scr_http_res_get_header_names(ScrHttpRes *r); /* +1 */
|
||
ScrArr *scr_http_res_get_raw_header_names(ScrHttpRes *r); /* +1 */
|
||
ScrDyn *scr_http_res_get_headers(ScrHttpRes *r); /* +1 */
|
||
bool scr_http_res_has_header_named(ScrHttpRes *r, ScrStr *name /*borrowed*/);
|
||
void scr_http_res_remove_header(ScrHttpRes *r, ScrStr *name /*borrowed*/);
|
||
void scr_http_res_on_finish(ScrHttpRes *r, ScrClosure *cb /*moves*/);
|
||
/* createServer({ joinDuplicateHeaders: true }): repeated request-header
|
||
* names read back joined ", " (Node's option; the default keeps first). */
|
||
void scr_http_server_join_duplicate_headers(ScrNetServer *s);
|
||
/* createServer({ requireHostHeader: false }): allow HTTP/1.1 without Host. */
|
||
void scr_http_server_allow_missing_host_header(ScrNetServer *s);
|
||
void scr_http_handler_thunk0(ScrClosure *cb, ScrHttpReq *req, ScrHttpRes *res);
|
||
void scr_http_handler_thunk1(ScrClosure *cb, ScrHttpReq *req, ScrHttpRes *res);
|
||
void scr_http_handler_thunk2(ScrClosure *cb, ScrHttpReq *req, ScrHttpRes *res);
|
||
/* server.on("connect", ...) — fired instead of 'request' for CONNECT.
|
||
* HTTP/1 uses (req, socket, head); HTTP/2 compatibility uses (req, res). */
|
||
void scr_http_server_on_connect(ScrNetServer *s, ScrClosure *cb /*moves*/,
|
||
ScrHttpUpgradeFn h1_fn, ScrHttpConnectH2Fn h2_fn,
|
||
bool once);
|
||
void scr_http_server_on_upgrade(ScrNetServer *s, ScrClosure *cb /*moves*/, ScrHttpUpgradeFn fn, bool once);
|
||
ScrArr *scr_http_req_raw_headers(ScrHttpReq *r); /* +1 — [name, value, ...], arrival order/case */
|
||
ScrArr *scr_http_req_header_pairs(ScrHttpReq *r); /* +1 — lowercased names, the headers snapshot */
|
||
ScrArr *scr_http_req_raw_trailers(ScrHttpReq *r); /* +1 — wire order/case after end */
|
||
ScrArr *scr_http_req_trailer_pairs(ScrHttpReq *r); /* +1 — lowercased, joined snapshot after end */
|
||
ScrStr *scr_http_req_status_message(ScrHttpReq *r); /* +1 or NULL (undefined arm: server request) */
|
||
void scr_http_upgrade_thunk0(ScrClosure *cb, ScrHttpReq *req, ScrNetSocket *sock, ScrBytes *head);
|
||
void scr_http_upgrade_thunk1(ScrClosure *cb, ScrHttpReq *req, ScrNetSocket *sock, ScrBytes *head);
|
||
void scr_http_upgrade_thunk2(ScrClosure *cb, ScrHttpReq *req, ScrNetSocket *sock, ScrBytes *head);
|
||
void scr_http_upgrade_thunk3(ScrClosure *cb, ScrHttpReq *req, ScrNetSocket *sock, ScrBytes *head);
|
||
|
||
/* The member follow-ups (statusCode / socket / resume / destroy / the
|
||
* error+close listener slots on requests; destroy / close / the
|
||
* flat-pairs writeHead on responses). */
|
||
double scr_http_req_status(ScrHttpReq *r); /* < 0 = the undefined arm (server request) */
|
||
ScrNetSocket *scr_http_req_socket(ScrHttpReq *r); /* +1 */
|
||
void scr_http_req_resume(ScrHttpReq *r);
|
||
/* pause()/resume() hold and drain 'data'/'end' delivery (the parser keeps
|
||
* consuming; the drain rides the emit queue); setTimeout delegates to the
|
||
* socket's idle timer; the flags back req.destroyed/req.readable. */
|
||
void scr_http_req_pause(ScrHttpReq *r);
|
||
void scr_http_req_set_timeout(ScrHttpReq *r, double ms, ScrClosure *cb /*moves, nullable*/);
|
||
void scr_http_req_set_timeout_plain(ScrHttpReq *r, double ms);
|
||
bool scr_http_req_destroyed_flag(ScrHttpReq *r);
|
||
bool scr_http_req_readable(ScrHttpReq *r);
|
||
/* flushHeaders/cork/uncork/writableCorked, the res.req backref, the
|
||
* socket-delegated setTimeout, and write(chunk, cb)'s deferred callback. */
|
||
void scr_http_res_flush_headers(ScrHttpRes *r);
|
||
void scr_http_res_cork(ScrHttpRes *r);
|
||
void scr_http_res_uncork(ScrHttpRes *r);
|
||
double scr_http_res_writable_corked(ScrHttpRes *r);
|
||
bool scr_http_res_destroyed_flag(ScrHttpRes *r);
|
||
void scr_http_res_set_req(ScrHttpRes *r, ScrHttpReq *req /*borrowed, nullable*/);
|
||
void scr_http_res_set_timeout(ScrHttpRes *r, double ms, ScrClosure *cb /*moves, nullable*/);
|
||
void scr_http_res_set_timeout_plain(ScrHttpRes *r, double ms);
|
||
void scr_http_res_on_write_flush(ScrHttpRes *r, ScrClosure *cb /*moves*/);
|
||
/* req.setEncoding(enc) — the socket twin's contract; may throw. */
|
||
void scr_http_req_set_encoding(ScrHttpReq *r, ScrStr *enc /*borrowed*/);
|
||
void scr_http_req_destroy(ScrHttpReq *r);
|
||
void scr_http_req_on_error(ScrHttpReq *r, ScrClosure *cb /*moves*/, ScrChildErrFn fn, bool once);
|
||
void scr_http_req_on_close(ScrHttpReq *r, ScrClosure *cb /*moves*/, bool once);
|
||
void scr_http_res_destroy(ScrHttpRes *r);
|
||
void scr_http_res_on_close(ScrHttpRes *r, ScrClosure *cb /*moves*/, bool once);
|
||
void scr_http_res_write_head_pairs(ScrHttpRes *r, double status, ScrArr *pairs /*borrowed*/);
|
||
|
||
/* node:http, the CLIENT slice (http.request/http.get over the net client
|
||
* machinery — one dialed connection per request, no agent pooling; the
|
||
* wire head is Node's exactly). The response arrives as a ScrHttpReq. */
|
||
typedef struct ScrHttpClientReq ScrHttpClientReq;
|
||
typedef void (*ScrHttpRespFn)(ScrClosure *cb, ScrHttpReq *res); /* res +1 */
|
||
ScrHttpClientReq *scr_http_client_retain(ScrHttpClientReq *c);
|
||
void scr_http_client_release(ScrHttpClientReq *c);
|
||
void *scr_http_client_retain_v(void *p);
|
||
void scr_http_client_release_v(void *p);
|
||
ScrHttpClientReq *scr_http_request(ScrStr *host /*borrowed*/, double port,
|
||
ScrStr *path /*borrowed*/, ScrStr *method /*borrowed*/,
|
||
double timeout_ms, ScrArr *header_pairs /*borrowed*/,
|
||
bool auto_end, ScrClosure *cb /*moves, nullable*/,
|
||
ScrHttpRespFn fn); /* +1 */
|
||
/* The createConnection form: conn_cb (a `() => net.Socket` closure) runs
|
||
* once, synchronously, and supplies the exchange's socket. */
|
||
ScrHttpClientReq *scr_http_request_conn(ScrClosure *conn_cb /*moves*/, ScrStr *path /*borrowed*/, ScrStr *method /*borrowed*/, double timeout_ms, ScrArr *header_pairs /*borrowed*/, bool auto_end, ScrClosure *cb /*moves, nullable*/, ScrHttpRespFn fn); /* +1 */
|
||
void scr_http_client_write_str(ScrHttpClientReq *c, ScrStr *data /*borrowed*/);
|
||
void scr_http_client_write_bytes(ScrHttpClientReq *c, ScrBytes *data /*borrowed*/);
|
||
void scr_http_client_end(ScrHttpClientReq *c);
|
||
void scr_http_client_end_str(ScrHttpClientReq *c, ScrStr *data /*borrowed*/);
|
||
void scr_http_client_end_bytes(ScrHttpClientReq *c, ScrBytes *data /*borrowed*/);
|
||
void scr_http_client_flush_headers(ScrHttpClientReq *c);
|
||
void scr_http_client_set_header(ScrHttpClientReq *c, ScrStr *name /*borrowed*/, ScrStr *value /*borrowed*/);
|
||
ScrStr *scr_http_client_get_header(ScrHttpClientReq *c, ScrStr *name /*borrowed*/); /* +1 or NULL */
|
||
bool scr_http_client_has_header_named(ScrHttpClientReq *c, ScrStr *name /*borrowed*/);
|
||
void scr_http_client_remove_header(ScrHttpClientReq *c, ScrStr *name /*borrowed*/);
|
||
ScrArr *scr_http_client_get_header_names(ScrHttpClientReq *c); /* +1 */
|
||
ScrArr *scr_http_client_get_raw_header_names(ScrHttpClientReq *c); /* +1 */
|
||
ScrDyn *scr_http_client_get_headers(ScrHttpClientReq *c); /* +1 */
|
||
ScrStr *scr_http_client_method(ScrHttpClientReq *c); /* +1 */
|
||
ScrStr *scr_http_client_path(ScrHttpClientReq *c); /* +1 */
|
||
ScrStr *scr_http_client_host(ScrHttpClientReq *c); /* +1 */
|
||
ScrStr *scr_http_client_protocol(ScrHttpClientReq *c); /* +1 */
|
||
bool scr_http_client_headers_sent(ScrHttpClientReq *c);
|
||
bool scr_http_client_writable_ended(ScrHttpClientReq *c);
|
||
bool scr_http_client_writable_finished(ScrHttpClientReq *c);
|
||
ScrNetSocket *scr_http_client_socket(ScrHttpClientReq *c); /* +1 */
|
||
bool scr_http_client_reused_socket(ScrHttpClientReq *c);
|
||
void scr_http_client_set_nodelay(ScrHttpClientReq *c, bool enable);
|
||
void scr_http_client_set_socket_keepalive(ScrHttpClientReq *c, bool enable, double delay_ms);
|
||
void scr_http_client_set_timeout_cb(ScrHttpClientReq *c, double ms, ScrClosure *cb /*moves*/);
|
||
void scr_http_client_add_trailers(ScrHttpClientReq *c, ScrArr *pairs /*borrowed*/);
|
||
void scr_http_client_cork(ScrHttpClientReq *c);
|
||
void scr_http_client_uncork(ScrHttpClientReq *c);
|
||
double scr_http_client_writable_corked(ScrHttpClientReq *c);
|
||
/* Checked-dynamic chunk forms (the scr_net_sock_write_dynv story). */
|
||
void scr_http_client_write_dynv(ScrHttpClientReq *c, const ScrDyn *d /*borrowed*/);
|
||
void scr_http_client_end_dynv(ScrHttpClientReq *c, const ScrDyn *d /*borrowed*/);
|
||
void scr_http_client_set_timeout(ScrHttpClientReq *c, double ms);
|
||
void scr_http_client_destroy(ScrHttpClientReq *c);
|
||
void scr_http_client_abort(ScrHttpClientReq *c);
|
||
bool scr_http_client_aborted(ScrHttpClientReq *c);
|
||
bool scr_http_client_destroyed(ScrHttpClientReq *c);
|
||
void scr_http_client_on_response(ScrHttpClientReq *c, ScrClosure *cb /*moves*/, ScrHttpRespFn fn, bool once);
|
||
void scr_http_client_on_socket(ScrHttpClientReq *c, ScrClosure *cb /*moves*/, ScrNetConnFn fn, bool once);
|
||
void scr_http_client_on_finish(ScrHttpClientReq *c, ScrClosure *cb /*moves*/, bool once);
|
||
void scr_http_client_on_abort(ScrHttpClientReq *c, ScrClosure *cb /*moves*/, bool once);
|
||
void scr_http_client_on_error(ScrHttpClientReq *c, ScrClosure *cb /*moves*/, ScrChildErrFn fn, bool once);
|
||
void scr_http_client_on_timeout(ScrHttpClientReq *c, ScrClosure *cb /*moves*/, bool once);
|
||
void scr_http_client_on_close(ScrHttpClientReq *c, ScrClosure *cb /*moves*/, bool once);
|
||
void scr_http_client_on_upgrade(ScrHttpClientReq *c, ScrClosure *cb /*moves*/, ScrHttpUpgradeFn fn, bool once);
|
||
void scr_http_req_pipe_client(ScrHttpReq *r, ScrHttpClientReq *dst /*borrowed*/);
|
||
void scr_http_resp_thunk0(ScrClosure *cb, ScrHttpReq *res);
|
||
void scr_http_resp_thunk_res(ScrClosure *cb, ScrHttpReq *res);
|
||
/* The transport-parameterized client constructor (scr_tls.c's https
|
||
* entry): `wrap` (nullable) installs a transport on the freshly dialed
|
||
* socket before any bytes go out (they buffer until the handshake
|
||
* establishes), and `default_port` is the port the Host header omits
|
||
* (80 for http, 443 for https — Node's rule). scr_http_request is this
|
||
* with no wrap and port 80. */
|
||
ScrHttpClientReq *scr_http_request_ex(ScrStr *host /*borrowed*/, double port,
|
||
ScrStr *path /*borrowed*/, ScrStr *method /*borrowed*/,
|
||
double timeout_ms, ScrArr *header_pairs /*borrowed*/,
|
||
bool auto_end, ScrClosure *cb /*moves, nullable*/,
|
||
ScrHttpRespFn fn, int default_port,
|
||
void (*wrap)(ScrNetSocket *, void *), void *wrap_ctx); /* +1 */
|
||
/* The URL-string client form (http.get('http://host:port/path')): parses
|
||
* through the WHATWG unit; throws catchably on an unparsable input
|
||
* ("Invalid URL") or a non-http scheme (ERR_INVALID_PROTOCOL) and
|
||
* returns NULL with the exception pending. cb MOVES (nullable). */
|
||
ScrHttpClientReq *scr_http_request_url(ScrStr *url /*borrowed*/, ScrStr *method /*borrowed*/,
|
||
bool auto_end, ScrClosure *cb /*moves, nullable*/,
|
||
ScrHttpRespFn fn); /* +1 */
|
||
/* Its parse half, shared with scr_tls.c's https spelling: the scheme is
|
||
* checked against `secure` (the calling module), so a mismatch is Node's
|
||
* ERR_INVALID_PROTOCOL rather than a silent upgrade. false = the
|
||
* exception is pending and no out-parameter was written. */
|
||
bool scr_http_url_parts(ScrStr *url /*borrowed*/, bool secure, ScrStr **host_out /*+1*/,
|
||
double *port_out, ScrStr **path_out /*+1*/);
|
||
/* ── the http Agent (new http.Agent(opts) — option surface, getName, and
|
||
* the maxSockets queue over one-dial-per-request connections; keep-alive
|
||
* POOLING is not modeled: keepAlive: true fences at construction).
|
||
* agent_new answers the SCR_DYNH_HTTP_AGENT handle (+1 dyn) or throws.
|
||
* max_sockets/max_free arrive < 0 for "unset" (Infinity / 256);
|
||
* timeout_ms < 0 = no idle timer. request_agent_ex threads the agent dyn
|
||
* (undefined/null = the default path; false = one-shot with Connection:
|
||
* close; an Agent handle = queue accounting); port < 0 means "no port
|
||
* option" — the agent's (settable) defaultPort, then the scheme's. */
|
||
ScrDyn *scr_http_agent_new(bool secure, bool keep_alive, double ka_msecs,
|
||
double max_sockets, double max_free, double timeout_ms,
|
||
double port /* < 0 = unset: the option-merge default */); /* +1 */
|
||
ScrHttpClientReq *scr_http_request_agent_ex(ScrStr *host /*borrowed*/, double port,
|
||
ScrStr *path /*borrowed*/, ScrStr *method /*borrowed*/,
|
||
double timeout_ms, ScrArr *header_pairs /*borrowed*/,
|
||
bool auto_end, const ScrDyn *agent /*borrowed*/,
|
||
ScrClosure *cb /*moves, nullable*/,
|
||
ScrHttpRespFn fn, int default_port,
|
||
void (*wrap)(ScrNetSocket *, void *), void *wrap_ctx); /* +1 */
|
||
ScrHttpClientReq *scr_http_request_agent(ScrStr *host /*borrowed*/, double port,
|
||
ScrStr *path /*borrowed*/, ScrStr *method /*borrowed*/,
|
||
double timeout_ms, ScrArr *header_pairs /*borrowed*/,
|
||
bool auto_end, const ScrDyn *agent /*borrowed*/,
|
||
ScrClosure *cb /*moves, nullable*/, ScrHttpRespFn fn); /* +1 */
|
||
#ifdef SCR_RC_AUDIT
|
||
long scr_http_live_count(void);
|
||
#endif
|
||
|
||
/* ── node:http2 (scr_http2.c — REAL HTTP/2: h2c server + client over
|
||
* the net loop, with an in-tree RFC 7540 frame codec and RFC 7541 HPACK;
|
||
* the design note atop scr_http2.c has the full story). Sessions and
|
||
* streams are refcounted handles like the net pair; 'stream'/'response'
|
||
* payloads cross as flat [name, value, ...] pairs arrays and the EMITTED
|
||
* adapter closure builds the program-side headers record (the response
|
||
* :status rides separately as a number — no string→number conversion
|
||
* exists program-side). The srv-ctx `proto` tags (SCR_NET_PROTO_*) keep
|
||
* the two parser families apart on the shared http_ctx alias slot. */
|
||
enum { SCR_NET_PROTO_HTTP1 = 1, SCR_NET_PROTO_H2 = 2 };
|
||
|
||
typedef struct ScrH2Session ScrH2Session;
|
||
typedef struct ScrH2Stream ScrH2Stream;
|
||
typedef void (*ScrH2SessionErrorFn)(ScrClosure *cb, ScrStr *msg,
|
||
ScrH2Session *session); /* session +1 */
|
||
|
||
typedef void (*ScrH2StreamFn)(ScrClosure *cb, ScrH2Stream *st, ScrArr *pairs, double flags); /* st, pairs +1 */
|
||
typedef void (*ScrH2RespFn)(ScrClosure *cb, ScrArr *pairs, double status, double flags); /* pairs +1 */
|
||
typedef void (*ScrH2SessionFn)(ScrClosure *cb, ScrH2Session *sess); /* sess +1 */
|
||
typedef void (*ScrH2ConnectFn)(ScrClosure *cb, ScrH2Session *sess, ScrNetSocket *sock); /* both +1; sock nullable */
|
||
typedef void (*ScrH2GoawayFn)(ScrClosure *cb, double code, double last);
|
||
typedef void (*ScrH2SettingsFn)(ScrClosure *cb, ScrDyn *settings /*+1*/);
|
||
|
||
ScrH2Session *scr_http2_session_retain(ScrH2Session *s);
|
||
void scr_http2_session_release(ScrH2Session *s);
|
||
void *scr_http2_session_retain_v(void *p);
|
||
void scr_http2_session_release_v(void *p);
|
||
ScrH2Stream *scr_http2_stream_retain(ScrH2Stream *st);
|
||
void scr_http2_stream_release(ScrH2Stream *st);
|
||
void *scr_http2_stream_retain_v(void *p);
|
||
void scr_http2_stream_release_v(void *p);
|
||
|
||
/* server (an ordinary net server handle; listen/close/address/'error'
|
||
* ride the net surface — the scr_http.c shape) */
|
||
ScrNetServer *scr_http2_create_server(void); /* +1 */
|
||
/* createServer(handler): the eager (req, res) COMPAT handler — the first
|
||
* 'request' listener (Http2ServerRequest/Response over h2 streams). */
|
||
ScrNetServer *scr_http2_create_server_req(ScrClosure *handler /*moves*/, ScrHttpReqFn fn); /* +1 */
|
||
/* The REAL h2-over-TLS server (http2.createSecureServer({ cert, key })):
|
||
* the same session machinery behind an mbedTLS handshake whose ALPN
|
||
* advertises h2 alone — protocol-identical after establishment. */
|
||
ScrNetServer *scr_http2_create_secure_server(const char *cert, size_t cert_len, const char *key, size_t key_len, bool enable_connect_protocol); /* +1 */
|
||
ScrNetServer *scr_http2_create_secure_server_allow_http1(const char *cert, size_t cert_len, const char *key, size_t key_len, ScrClosure *handler /*moves, nullable*/, ScrHttpReqFn fn, ScrClosure *sni_cb /*moves, nullable*/, void *sni_answer_fn, bool enable_connect_protocol); /* +1 */
|
||
/* createSecureServer(options, handler): the eager COMPAT handler as the
|
||
* first 'request' listener over the ALPN=h2 server (the createServerReq
|
||
* route, secured). */
|
||
ScrNetServer *scr_http2_create_secure_server_req(const char *cert, size_t cert_len, const char *key, size_t key_len, ScrClosure *handler /*moves, nullable*/, ScrHttpReqFn fn, bool enable_connect_protocol); /* +1 */
|
||
/* createSecureServer with a RUNTIME options record (the divergence-66
|
||
* stance): allowHTTP1 picks the flavor at runtime, cert/key ride the
|
||
* shared TLS server walk (out-of-bounds members throw the catchable
|
||
* fence), h2 session-tuning keys drop like the literal walk ignores
|
||
* them. +1, or NULL with the exception pending. */
|
||
ScrNetServer *scr_http2_create_secure_server_dyn(const struct ScrDyn *opts /*borrowed*/, ScrClosure *handler /*moves, nullable*/, ScrHttpReqFn fn);
|
||
/* The TLS server-options walk (scr_tls.c) — runtime-internal, shared
|
||
* with scr_http2.c's createSecureServerDyn. */
|
||
bool scr_tls_srv_opts_walk(const struct ScrDyn *opts, const char *api, ScrBytes **cert_out, ScrBytes **key_out);
|
||
void scr_http2_server_on_stream(ScrNetServer *s, ScrClosure *cb /*moves*/, ScrH2StreamFn fn, bool once);
|
||
void scr_http2_server_on_session(ScrNetServer *s, ScrClosure *cb /*moves*/, ScrH2SessionFn fn, bool once);
|
||
|
||
/* client — h2c prior knowledge for http authorities, the TLS transport
|
||
* with ALPN ["h2"] for https ones (ca len 0 = the system anchors;
|
||
* reject_unauthorized false disables the verify gate — the local-CA
|
||
* self-connect shapes); throws catchably on bad input */
|
||
ScrH2Session *scr_http2_connect(ScrStr *url /*borrowed*/, bool reject_unauthorized,
|
||
const char *ca /*borrowed, len 0 = none*/, size_t ca_len,
|
||
ScrClosure *cb /*moves, nullable*/,
|
||
ScrH2ConnectFn fn); /* +1 */
|
||
/* end_stream: -1 = the method's payload-meaningless default, 0/1 explicit */
|
||
ScrH2Stream *scr_http2_session_request(ScrH2Session *s, ScrArr *pairs /*borrowed, nullable*/,
|
||
double end_stream); /* +1; may throw */
|
||
|
||
/* session */
|
||
void scr_http2_session_close(ScrH2Session *s, ScrClosure *cb /*moves, nullable*/);
|
||
void scr_http2_session_destroy(ScrH2Session *s);
|
||
void scr_http2_session_on_close(ScrH2Session *s, ScrClosure *cb /*moves*/, bool once);
|
||
void scr_http2_session_on_error(ScrH2Session *s, ScrClosure *cb /*moves*/, ScrChildErrFn fn, bool once);
|
||
void scr_http2_server_on_session_error(ScrNetServer *s, ScrClosure *cb /*moves*/,
|
||
ScrH2SessionErrorFn fn, bool once);
|
||
void scr_http2_session_error_thunk0(ScrClosure *cb, ScrStr *msg, ScrH2Session *session);
|
||
void scr_http2_session_error_thunk1(ScrClosure *cb, ScrStr *msg, ScrH2Session *session);
|
||
void scr_http2_session_error_thunk2(ScrClosure *cb, ScrStr *msg, ScrH2Session *session);
|
||
void scr_http2_session_on_connect(ScrH2Session *s, ScrClosure *cb /*moves*/, ScrH2ConnectFn fn, bool once);
|
||
void scr_http2_session_on_stream(ScrH2Session *s, ScrClosure *cb /*moves*/, ScrH2StreamFn fn, bool once);
|
||
void scr_http2_session_on_goaway(ScrH2Session *s, ScrClosure *cb /*moves*/, ScrH2GoawayFn fn, bool once);
|
||
/* The settings surface: session.settings(obj[, cb]) sends a SETTINGS
|
||
* frame; local/remote listener lists fire with the settings record on
|
||
* ACK/receipt; the property reads answer the tracked records. */
|
||
void scr_http2_session_settings(ScrH2Session *s, const ScrDyn *settings /*borrowed, nullable*/,
|
||
ScrClosure *cb /*moves, nullable*/, ScrH2SettingsFn cbfn);
|
||
void scr_http2_session_on_settings(ScrH2Session *s, ScrClosure *cb /*moves*/, ScrH2SettingsFn fn,
|
||
bool once, bool local);
|
||
ScrDyn *scr_http2_session_settings_get(ScrH2Session *s, bool local); /* +1 */
|
||
bool scr_http2_session_pending_settings_ack(ScrH2Session *s);
|
||
ScrDyn *scr_http2_get_default_settings(void); /* +1 */
|
||
void scr_http2_settings_thunk(ScrClosure *cb, ScrDyn *settings);
|
||
void scr_http2_settings_thunk0(ScrClosure *cb, ScrDyn *settings);
|
||
void scr_http2_session_settings_dyncb(ScrH2Session *s, const ScrDyn *settings /*borrowed*/,
|
||
const ScrDyn *cb /*borrowed*/);
|
||
void scr_http2_session_on_settings_dyn(ScrH2Session *s, const ScrDyn *cb /*borrowed*/,
|
||
bool once, bool local);
|
||
bool scr_http2_session_closed(ScrH2Session *s);
|
||
bool scr_http2_session_destroyed(ScrH2Session *s);
|
||
bool scr_http2_session_encrypted(ScrH2Session *s);
|
||
double scr_http2_session_type(ScrH2Session *s); /* 0 server / 1 client */
|
||
ScrStr *scr_http2_session_alpn(ScrH2Session *s); /* +1 */
|
||
ScrNetSocket *scr_http2_session_socket(ScrH2Session *s); /* +1, NULL after teardown */
|
||
|
||
/* stream */
|
||
void scr_http2_stream_respond(ScrH2Stream *st, ScrArr *pairs /*borrowed, nullable*/, bool end_stream);
|
||
void scr_http2_stream_write_str(ScrH2Stream *st, ScrStr *data /*borrowed*/);
|
||
void scr_http2_stream_write_bytes(ScrH2Stream *st, ScrBytes *data /*borrowed*/);
|
||
void scr_http2_stream_end(ScrH2Stream *st);
|
||
void scr_http2_stream_end_str(ScrH2Stream *st, ScrStr *data /*borrowed*/);
|
||
void scr_http2_stream_end_bytes(ScrH2Stream *st, ScrBytes *data /*borrowed*/);
|
||
void scr_http2_stream_close(ScrH2Stream *st, double code, ScrClosure *cb /*moves, nullable*/);
|
||
void scr_http2_stream_destroy(ScrH2Stream *st);
|
||
void scr_http2_stream_set_encoding(ScrH2Stream *st, ScrStr *enc /*borrowed*/); /* may throw */
|
||
ScrH2Stream *scr_http2_stream_set_encoding_ret(ScrH2Stream *st, ScrStr *enc /*borrowed*/); /* +1; may throw */
|
||
void scr_http2_stream_resume(ScrH2Stream *st);
|
||
void scr_http2_stream_pause(ScrH2Stream *st);
|
||
void scr_http2_stream_on_data(ScrH2Stream *st, ScrClosure *cb /*moves*/, ScrNetDataFn fn, bool once);
|
||
void scr_http2_stream_on_end(ScrH2Stream *st, ScrClosure *cb /*moves*/, bool once);
|
||
void scr_http2_stream_on_close(ScrH2Stream *st, ScrClosure *cb /*moves*/, bool once);
|
||
void scr_http2_stream_on_aborted(ScrH2Stream *st, ScrClosure *cb /*moves*/, bool once);
|
||
void scr_http2_stream_on_error(ScrH2Stream *st, ScrClosure *cb /*moves*/, ScrChildErrFn fn, bool once);
|
||
void scr_http2_stream_on_response(ScrH2Stream *st, ScrClosure *cb /*moves*/, ScrH2RespFn fn, bool once);
|
||
double scr_http2_stream_id(ScrH2Stream *st);
|
||
double scr_http2_stream_rst_code(ScrH2Stream *st);
|
||
bool scr_http2_stream_destroyed(ScrH2Stream *st);
|
||
bool scr_http2_stream_closed(ScrH2Stream *st);
|
||
bool scr_http2_stream_aborted(ScrH2Stream *st);
|
||
bool scr_http2_stream_pending(ScrH2Stream *st);
|
||
ScrH2Session *scr_http2_stream_session(ScrH2Stream *st); /* +1 */
|
||
|
||
/* the emitted-adapter thunks (arity + record-building live program-side) */
|
||
void scr_http2_stream_thunk(ScrClosure *cb, ScrH2Stream *st, ScrArr *pairs, double flags);
|
||
void scr_http2_resp_thunk(ScrClosure *cb, ScrArr *pairs, double status, double flags);
|
||
void scr_http2_session_thunk(ScrClosure *cb, ScrH2Session *sess);
|
||
void scr_http2_session_thunk0(ScrClosure *cb, ScrH2Session *sess);
|
||
void scr_http2_connect_thunk2(ScrClosure *cb, ScrH2Session *sess, ScrNetSocket *sock);
|
||
void scr_http2_connect_thunk1(ScrClosure *cb, ScrH2Session *sess, ScrNetSocket *sock);
|
||
void scr_http2_connect_thunk0(ScrClosure *cb, ScrH2Session *sess, ScrNetSocket *sock);
|
||
void scr_http2_goaway_thunk2(ScrClosure *cb, double code, double last);
|
||
void scr_http2_goaway_thunk1(ScrClosure *cb, double code, double last);
|
||
void scr_http2_goaway_thunk0(ScrClosure *cb, double code, double last);
|
||
|
||
void scr_net_sock_set_native_established(ScrNetSocket *s, ScrNetNativeEventFn fn);
|
||
|
||
/* Registers the h2 session/stream dyn handle ops (the emitted main()
|
||
* calls it when the unit is linked — the scr_net_dyn_install story). */
|
||
void scr_http2_dyn_install(void);
|
||
|
||
#ifdef SCR_RC_AUDIT
|
||
long scr_http2_live_count(void);
|
||
#endif
|
||
|
||
/* node:https (scr_tls.c — the http server/client over the TLS
|
||
* transport; see the tls block above for the ownership story). */
|
||
ScrNetServer *scr_https_create_server(const char *cert, size_t cert_len, const char *key, size_t key_len, ScrClosure *handler /*moves*/, ScrHttpReqFn fn); /* +1 */
|
||
/* The runtime-options-record twin (the tls block's divergence-66 walk). */
|
||
ScrNetServer *scr_https_create_server_dyn(const struct ScrDyn *opts /*borrowed*/, ScrClosure *handler /*moves, nullable*/, ScrHttpReqFn fn); /* +1 or NULL+pending */
|
||
ScrHttpClientReq *scr_https_request(ScrStr *host /*borrowed*/, double port,
|
||
ScrStr *path /*borrowed*/, ScrStr *method /*borrowed*/,
|
||
double timeout_ms, ScrArr *header_pairs /*borrowed*/,
|
||
bool auto_end, bool reject_unauthorized,
|
||
const char *ca /*borrowed, len 0 = none*/, size_t ca_len,
|
||
ScrClosure *cb /*moves, nullable*/, ScrHttpRespFn fn); /* +1 */
|
||
/* The URL-string form (https.get('https://host/path')): scr_http.c's
|
||
* shared parse, then the options form's dial with Node's no-options
|
||
* defaults (verification on, the default trust anchors). Throws
|
||
* catchably on an unparsable input or a non-https scheme. */
|
||
ScrHttpClientReq *scr_https_request_url(ScrStr *url /*borrowed*/, ScrStr *method /*borrowed*/,
|
||
bool auto_end, ScrClosure *cb /*moves, nullable*/,
|
||
ScrHttpRespFn fn); /* +1 */
|
||
/* The agent-threaded twin (the scr_http_request_agent story over TLS). */
|
||
ScrHttpClientReq *scr_https_request_agent(ScrStr *host /*borrowed*/, double port,
|
||
ScrStr *path /*borrowed*/, ScrStr *method /*borrowed*/,
|
||
double timeout_ms, ScrArr *header_pairs /*borrowed*/,
|
||
bool auto_end, bool reject_unauthorized,
|
||
const char *ca /*borrowed, len 0 = none*/, size_t ca_len,
|
||
const struct ScrDyn *agent /*borrowed*/,
|
||
ScrClosure *cb /*moves, nullable*/, ScrHttpRespFn fn); /* +1 */
|
||
/* The fetch unit's https leg (defined in scr_tls.c): a client transport
|
||
* context (SNI/verify against the URL hostname — the DIALED address may
|
||
* be a resolved IP) plus the wrap hook scr_http_request_ex installs on
|
||
* the fresh socket. The ctx moves into the transport at wrap time. */
|
||
void *scr_tls_fetch_client_ctx(ScrStr *host /*borrowed*/, bool reject_unauthorized);
|
||
void scr_tls_fetch_client_wrap(ScrNetSocket *sock, void *ctx);
|
||
/* The loop-side registration (scr_async.c, always linked): `pending`
|
||
* joins the exhaustion test (a live handle keeps the process alive, like
|
||
* Node's active TCP handles), `dispatch` runs at every turn top like the
|
||
* events hook, and `pollfd` exposes the unit's poller fd so the idle
|
||
* poll(2) sleep wakes on socket readiness (-1 = no fd yet). */
|
||
void scr_loop_set_net(bool (*pending)(void), void (*dispatch)(void), int (*pollfd)(void));
|
||
/* True when fibers are queued on the microtask ready queue — dispatch
|
||
* hooks use it to yield between event batches so promise jobs interleave
|
||
* (net's sweep/drain alternation). */
|
||
bool scr_loop_has_ready(void);
|
||
|
||
/* ── node:dgram + node:dns (scr_dgram.c — compiled only when the program
|
||
* uses either; design note atop the file). One lean refcounted handle
|
||
* kind (the netSocket ownership story: listeners MOVE in and drop at
|
||
* settlement); dns.lookup rides the same unit — getaddrinfo runs AT CALL
|
||
* TIME and the callback defers to the next loop turn. Self-contained: no
|
||
* symbol here requires scr_net.c to link. */
|
||
typedef struct ScrDgramSocket ScrDgramSocket;
|
||
/* The message adapter: msg and the rinfo parts arrive BORROWED (multiple
|
||
* listeners see the same datagram); adapters retain what they keep. */
|
||
typedef void (*ScrDgramMsgFn)(ScrClosure *cb, ScrBytes *msg, ScrStr *addr, ScrStr *family, double port, double size);
|
||
/* The dns.lookup adapter: errmsg is NULL on success; everything borrowed. */
|
||
typedef void (*ScrDnsLookupFn)(ScrClosure *cb, ScrStr *errmsg, ScrStr *addr, double family);
|
||
|
||
ScrDgramSocket *scr_dgram_retain(ScrDgramSocket *s);
|
||
void scr_dgram_release(ScrDgramSocket *s);
|
||
void *scr_dgram_retain_v(void *p);
|
||
void scr_dgram_release_v(void *p);
|
||
|
||
ScrDgramSocket *scr_dgram_create(bool reuse_addr); /* +1 */
|
||
/* bind/connect: host borrowed; "" binds 0.0.0.0 (bind's Node default).
|
||
* The callback (nullable, moves) registers as once('listening') /
|
||
* once('connect'). Both THROW Node's state errors on a bound/connected/
|
||
* closed socket; runtime failures (EADDRINUSE) are the async 'error'. */
|
||
void scr_dgram_bind(ScrDgramSocket *s, double port, ScrStr *host /*borrowed*/, ScrClosure *cb /*moves, nullable*/);
|
||
void scr_dgram_connect(ScrDgramSocket *s, double port, ScrStr *host /*borrowed*/, ScrClosure *cb /*moves, nullable*/);
|
||
void scr_dgram_send_str(ScrDgramSocket *s, ScrStr *data /*borrowed*/, double port, ScrStr *host /*borrowed*/);
|
||
void scr_dgram_send_bytes(ScrDgramSocket *s, ScrBytes *data /*borrowed*/, double port, ScrStr *host /*borrowed*/);
|
||
/* The send argument-validation ladder over dyn arguments (Node's
|
||
* signature shuffle, slice bounds, list/type contracts, port/address
|
||
* validation, and the connected-state errors); a fully-validated
|
||
* unconnected single-payload send RUNS, the rest meet the fence. */
|
||
void scr_dgram_send_chk(ScrDgramSocket *s, const ScrDyn *buffer, const ScrDyn *a1,
|
||
const ScrDyn *a2, const ScrDyn *a3, const ScrDyn *a4,
|
||
const ScrStr *fence);
|
||
/* address() parts: ip THROWS "Not running" before bind/connect (+1
|
||
* otherwise); family/port are only called after ip succeeded. */
|
||
ScrStr *scr_dgram_addr_ip(ScrDgramSocket *s); /* +1, may throw */
|
||
ScrStr *scr_dgram_addr_family(ScrDgramSocket *s); /* +1 */
|
||
double scr_dgram_addr_port(ScrDgramSocket *s);
|
||
void scr_dgram_close(ScrDgramSocket *s, ScrClosure *cb /*moves, nullable*/); /* throws on closed */
|
||
void scr_dgram_unref(ScrDgramSocket *s);
|
||
void scr_dgram_ref(ScrDgramSocket *s);
|
||
void scr_dgram_on_message(ScrDgramSocket *s, ScrClosure *cb /*moves*/, ScrDgramMsgFn fn, bool once);
|
||
void scr_dgram_on_error(ScrDgramSocket *s, ScrClosure *cb /*moves*/, ScrChildErrFn fn, bool once);
|
||
void scr_dgram_on_listening(ScrDgramSocket *s, ScrClosure *cb /*moves*/, bool once);
|
||
void scr_dgram_on_close(ScrDgramSocket *s, ScrClosure *cb /*moves*/, bool once);
|
||
void scr_dgram_on_connect(ScrDgramSocket *s, ScrClosure *cb /*moves*/, bool once);
|
||
/* The runtime-provided message adapters (zero/one-param listeners; the
|
||
* two-param rinfo shape is emitted per record shape). */
|
||
void scr_dgram_msg_thunk0(ScrClosure *cb, ScrBytes *msg, ScrStr *addr, ScrStr *family, double port, double size);
|
||
void scr_dgram_msg_thunk1(ScrClosure *cb, ScrBytes *msg, ScrStr *addr, ScrStr *family, double port, double size);
|
||
/* dns.lookup: hostname borrowed, family is 4 (AF_INET pinned by the
|
||
* frontend), the callback moves. getaddrinfo runs NOW (blocking —
|
||
* SEMANTICS.md); delivery defers to the next loop turn. */
|
||
void scr_dns_lookup(ScrStr *hostname /*borrowed*/, double family, ScrClosure *cb /*moves*/, ScrDnsLookupFn fn);
|
||
void scr_dns_thunk0(ScrClosure *cb, ScrStr *errmsg, ScrStr *addr, double family);
|
||
void scr_dgram_install(void);
|
||
#ifdef SCR_RC_AUDIT
|
||
long scr_dgram_live_count(void);
|
||
#endif
|
||
/* The loop-side registration (scr_async.c, always linked) — the net
|
||
* hook's exact shape, one more nullable slot set. */
|
||
void scr_loop_set_dgram(bool (*pending)(void), void (*dispatch)(void), int (*pollfd)(void));
|
||
|
||
/* ── fs.watch (scr_watch.c — compiled only when the program uses it;
|
||
* design note atop the file). FSWatcher handles over the unit's own
|
||
* event backend (kqueue EVFILT_VNODE on macOS/BSD, inotify on Linux):
|
||
* refcounted, listeners MOVE in (released at
|
||
* close/exit — the child ownership story), an open watcher keeps the
|
||
* loop alive until close() (Node's persistent default). */
|
||
typedef struct ScrWatcher ScrWatcher;
|
||
/* The event adapter: the name ("rename"/"change") arrives BORROWED
|
||
* (static storage); adapters build what their listener declared. */
|
||
typedef void (*ScrWatchFn)(ScrClosure *cb, const char *event);
|
||
|
||
ScrWatcher *scr_watcher_retain(ScrWatcher *w);
|
||
void scr_watcher_release(ScrWatcher *w);
|
||
void *scr_watcher_retain_v(void *p);
|
||
void scr_watcher_release_v(void *p);
|
||
|
||
/* fs.watch(path, listener?) — path borrowed, the callback (nullable)
|
||
* MOVES. Opens the path NOW and THROWS Node's fs error when it cannot
|
||
* ("ENOENT: ..., watch 'x'" — the polling-fallback catch shape). +1. */
|
||
ScrWatcher *scr_fs_watch(ScrStr *path, ScrClosure *cb, ScrWatchFn fn);
|
||
void scr_watcher_close(ScrWatcher *w); /* idempotent */
|
||
/* The runtime-provided listener adapters (zero-param, and the one-param
|
||
* (eventType: string) shape). */
|
||
void scr_watch_thunk0(ScrClosure *cb, const char *event);
|
||
void scr_watch_thunk_event(ScrClosure *cb, const char *event);
|
||
void scr_watch_install(void);
|
||
/* The loop-side registration (scr_async.c, always linked) — the net
|
||
* hook's exact shape, one more nullable slot set. */
|
||
void scr_loop_set_watch(bool (*pending)(void), void (*dispatch)(void), int (*pollfd)(void));
|
||
|
||
/* ── node:test (scr_test.c — compiled only when the program imports it;
|
||
* design note atop the file). Registration builds the test tree while
|
||
* module bodies run (describe callbacks execute AT registration, Node's
|
||
* collection phase); the first registration spawns the runner FIBER,
|
||
* which parks one microtask hop and then runs every test sequentially
|
||
* after main returns — sync bodies called on the fiber (its exception
|
||
* cell isolates throws), async bodies awaited through their spawn
|
||
* wrappers. Reporting is Node v24's spec-reporter shape on stdout;
|
||
* durations are real (nondeterministic — harnesses normalize).
|
||
* ScrStr arguments are BORROWED; closures MOVE. `mode`: 0 run, 1 skip,
|
||
* 2 todo. `flags`: 1 = async body, 2 = body takes the TestContext,
|
||
* 4 = { only: true }. `at` is "file:line:col" of the registration call
|
||
* (the failing-section "test at" line), "" when unknown. */
|
||
typedef struct ScrTestCtx ScrTestCtx;
|
||
ScrTestCtx *scr_testctx_retain(ScrTestCtx *t);
|
||
void scr_testctx_release(ScrTestCtx *t);
|
||
void *scr_testctx_retain_v(void *t);
|
||
void scr_testctx_release_v(void *t);
|
||
void scr_test_register(ScrStr *name, double mode, ScrStr *msg, ScrClosure *cb, double flags, ScrStr *at);
|
||
void scr_test_suite(ScrStr *name, double mode, ScrStr *msg, ScrClosure *cb, ScrStr *at);
|
||
/* before/after/beforeEach/afterEach on the CURRENT registration parent
|
||
* (`which`: 0 before, 1 after, 2 beforeEach, 3 afterEach). */
|
||
void scr_test_hook(double which, ScrClosure *cb, double flags);
|
||
/* t.test(...): registers under `t`, runs the subtest INLINE on the
|
||
* runner fiber, and returns the settled promise the `await` consumes (+1). */
|
||
ScrPromise *scr_test_sub(ScrTestCtx *t, ScrStr *name, double mode, ScrStr *msg, ScrClosure *cb, double flags, ScrStr *at);
|
||
void scr_test_ctx_skip(ScrTestCtx *t, ScrStr *msg);
|
||
void scr_test_ctx_todo(ScrTestCtx *t, ScrStr *msg);
|
||
void scr_test_ctx_diagnostic(ScrTestCtx *t, ScrStr *msg);
|
||
ScrStr *scr_test_ctx_name(ScrTestCtx *t); /* +1 */
|
||
/* Main's epilogue (emitted when moduleUsesNodeTest): 1 when any non-todo
|
||
* test failed, Node's exit contract. */
|
||
int scr_test_exit_code(void);
|
||
#ifdef SCR_RC_AUDIT
|
||
long scr_testctx_live_count(void);
|
||
#endif
|
||
|
||
/* ── node:assert (scr_assert.c; scr_assert_match lives in scr_regex.c —
|
||
* it needs the matcher, and every assert.match site carries a regex, so
|
||
* the regex link switch is already on). Failures throw a catchable
|
||
* AssertionError (%Error with name "AssertionError", code
|
||
* "ERR_ASSERTION"); the exception is pending on return, the fs pattern.
|
||
* Generated messages are Node's assertion_error.js scalar forms exactly
|
||
* (the design note atop scr_assert.c lists the documented divergences).
|
||
* All ScrStr/ScrError/ScrRegex arguments are BORROWED. `has_msg`
|
||
* distinguishes an omitted user message from an empty one (Node treats
|
||
* them differently per operator). */
|
||
void scr_assert_fail_msg(ScrStr *message); /* takes ownership; always throws */
|
||
ScrStr *scr_assert_inspect_str(const ScrStr *s); /* util.inspect quoting; +1 */
|
||
void scr_assert_ok(bool pass, ScrStr *message);
|
||
bool scr_assert_same_value_f64(double a, double b); /* Object.is */
|
||
/* deepStrictEqual over cyclic values: pair memo (enter answers true for
|
||
* a pair already being compared — Node's coinductive memo; leave pops).
|
||
* The compiler-emitted helpers over cycle-capable types wrap with these. */
|
||
bool scr_assert_deq_enter(const void *a, const void *b);
|
||
void scr_assert_deq_leave(void);
|
||
void scr_assert_eq_f64(double a, double b, bool negated, bool deep, ScrStr *msg, bool has_msg);
|
||
void scr_assert_eq_str(ScrStr *a, ScrStr *b, bool negated, bool deep, ScrStr *msg, bool has_msg);
|
||
void scr_assert_eq_bool(bool a, bool b, bool negated, bool deep, ScrStr *msg, bool has_msg);
|
||
/* BigInt scalar equality (scr_bigint_assert.c): included only when both
|
||
* optional runtime families are active, preserving their independent
|
||
* link gates. */
|
||
void scr_assert_eq_bigint(ScrBigInt *a, ScrBigInt *b, bool negated, bool deep,
|
||
ScrStr *msg, bool has_msg);
|
||
void scr_assert_deep_result(bool equal, bool negated, ScrStr *msg, bool has_msg);
|
||
/* deepStrictEqual's verdict over two typed-array/Buffer values of one
|
||
* static elem kind: brands_eq (Node compares prototypes — Buffer vs
|
||
* Uint8Array differ; a compile-time answer from the two arguments' checker
|
||
* types) AND same element count AND byte-identical storage. Both sides
|
||
* always evaluate (they are ordinary borrowed arguments), so a
|
||
* compile-time brand mismatch still runs their side effects. Never
|
||
* throws. */
|
||
bool scr_assert_bytes_deep_eq(const ScrBytes *a, const ScrBytes *b, bool brands_eq);
|
||
/* strictEqual/notStrictEqual over bytes values: reference identity
|
||
* (Object.is on objects), with Node's object-comparison headers —
|
||
* "Values have same structure but are not reference-equal:" when the
|
||
* contents (and static brands) match, the reference-equal expectation
|
||
* header otherwise. Header-only messages, the composite stance
|
||
* (SEMANTICS.md 102). Borrows a/b/msg; throws on the failing verdict. */
|
||
void scr_assert_ref_eq_bytes(const ScrBytes *a, const ScrBytes *b, bool negated,
|
||
bool brands_eq, ScrStr *msg, bool has_msg);
|
||
/* strictEqual/notStrictEqual over function values: reference identity
|
||
* with the same object-comparison headers (two distinct functions are
|
||
* never structure-equal, so the failing equal form always expects
|
||
* reference equality). Header-only messages (SEMANTICS.md 102). */
|
||
void scr_assert_ref_eq_fn(const ScrClosure *a, const ScrClosure *b, bool negated,
|
||
ScrStr *msg, bool has_msg);
|
||
/* The failing-EQUAL-operator message assembler (createErrDiff's scalar
|
||
* slice): exported for the spoke files that carry their own value
|
||
* inspection (scr_symbol.c's symbol comparison — the assert.match
|
||
* precedent). `ia`/`ib` are the inspected actual/expected bytes;
|
||
* `quotes` counts string-typed sides (2 quote chars each, excluded from
|
||
* the 12-char short-form budget); `strings` enables the stacked form's
|
||
* first-difference `^` indicator. Throws; borrows msg. */
|
||
void scr_assert_eq_fail(const char *ia, size_t la, const char *ib, size_t lb,
|
||
int quotes, bool both_zero, bool strings, bool deep,
|
||
ScrStr *msg, bool has_msg);
|
||
/* The failing NOT-equal-operator twin: `insp` is the inspected actual. */
|
||
void scr_assert_neq_fail(const char *insp, size_t ilen, bool deep,
|
||
ScrStr *msg, bool has_msg);
|
||
/* strictEqual/notStrictEqual/deepStrictEqual/notDeepStrictEqual where
|
||
* either operand is a checked-dynamic value (the frontend boxes a static
|
||
* side into the checked-dynamic tree first). Strict pair: SameValue over the dyn kinds —
|
||
* Object.is numbers, byte-equal strings, units by kind, node identity
|
||
* for arrays/objects/bytes, BOXED-CLOSURE identity for functions. Deep
|
||
* pair: the structural dyn walk (per-element arrays, key-set objects,
|
||
* brand-aware bytes via the buffer flavor bit, closure identity for
|
||
* functions). Generated messages reproduce assertion_error.js — the
|
||
* scalar simple/stacked forms byte-exactly, composite values rendered
|
||
* compact:false/sorted through the checked-dynamic tree and diffed with the real myers
|
||
* line printer (the design note atop scr_assert.c's dyn section lists
|
||
* the divergences). Borrows everything; throws on the failing verdict. */
|
||
void scr_assert_eq_dyn(ScrDyn *a, ScrDyn *b, bool negated, bool deep,
|
||
ScrStr *msg, bool has_msg);
|
||
/* assert.equal/notEqual over the frontend's Abstract Equality verdict;
|
||
* actual/expected are borrowed util.inspect renderings. */
|
||
void scr_assert_loose_result(bool equal, bool negated, ScrStr *actual,
|
||
ScrStr *expected, ScrStr *msg, bool has_msg);
|
||
/* assert.throws / assert.rejects whose callback returned (or whose
|
||
* promise fulfilled): "Missing expected exception|rejection" with Node's
|
||
* details — ` (${expected.name})` when the expected class/shape carries a
|
||
* name (has_ename), then `: message` or `.`. */
|
||
/* Node's expectsError over an error-INSTANCE expected: the expected dyn
|
||
* error's keys (minus the %error marker) each deep-compare against the
|
||
* caught value's; mismatches throw the deep-equal AssertionError. */
|
||
void scr_assert_expects_err_dyn(ScrDyn *actual, ScrDyn *expected, ScrStr *msg, bool has_msg);
|
||
void scr_assert_throws_none(bool rejection, ScrStr *ename, bool has_ename,
|
||
ScrStr *msg, bool has_msg);
|
||
void scr_assert_throws_mismatch(ScrStr *expected_name, ScrError *err,
|
||
ScrStr *msg, bool has_msg);
|
||
/* assert.throws(fn, <object shape>) — Node's expectedException over the
|
||
* static error surface (name/message/code). A tiny per-call accumulator:
|
||
* begin stashes the caught error, each slot call adds one expected key
|
||
* (key ids: 0 code, 1 message, 2 name — inspect's SORTED render order),
|
||
* and end runs the comparison, throwing Node's deep-equal Comparison
|
||
* diff (byte-exact: the bounded key set makes the full rendering
|
||
* enumerable) or the custom message. Regex-valued slots enter
|
||
* through scr_regex.c (scr_assert_shape_re) which tests eagerly and
|
||
* stores the verdict + rendered source, so this file stays libregexp-free;
|
||
* scr_assert_shape_actual is its borrowed key-value getter (NULL =
|
||
* absent). Straight-line calls between begin and end — no fiber
|
||
* suspension, so the accumulator is safely a static. */
|
||
void scr_assert_shape_begin(ScrError *err);
|
||
void scr_assert_shape_str(int key, ScrStr *v);
|
||
void scr_assert_shape_slot_re(int key, bool matched, ScrStr *rendered /* moves */);
|
||
ScrStr *scr_assert_shape_actual(int key); /* borrowed; NULL = absent */
|
||
void scr_assert_shape_end(ScrStr *msg, bool has_msg);
|
||
/* assert.doesNotReject whose rejection MATCHED (or had no expected):
|
||
* "Got unwanted rejection[: message].\nActual message: \"...\"". */
|
||
void scr_assert_unwanted_rejection(ScrError *err, ScrStr *msg, bool has_msg);
|
||
/* assert.ifError over the static surface: throws for ANY value the
|
||
* frontend routes here (Node: everything but null/undefined, falsy
|
||
* included) — "ifError got unwanted exception: " + the error's message
|
||
* (its name when the message is empty — Node reads constructor.name,
|
||
* identical for the builtin hierarchy, SEMANTICS.md 105's stance for
|
||
* subclasses) or the value's inspection. */
|
||
void scr_assert_iferror_err(ScrError *err);
|
||
void scr_assert_iferror_f64(double x);
|
||
void scr_assert_iferror_str(ScrStr *s);
|
||
void scr_assert_iferror_bool(bool b);
|
||
/* The checked-dynamic argument: dyn-kind dispatch — units pass quietly,
|
||
* %error-marked objects throw with the error's message, everything else
|
||
* with the inspection. */
|
||
void scr_assert_iferror_dyn(const ScrDyn *v);
|
||
/* assert.match / assert.doesNotMatch (scr_regex.c): a fresh exec from
|
||
* index 0 — Node's exec on a fresh regex; lastIndex statefulness is not
|
||
* modeled (SEMANTICS.md's regex stance), so g/y-flagged regexes test like
|
||
* their flag-free twins instead of aborting. */
|
||
void scr_assert_match(ScrStr *s, ScrRegex *re, bool negated, ScrStr *msg, bool has_msg);
|
||
/* assert.throws(fn, /regex/) whose thrown ERROR did not match: Node tests
|
||
* String(actual) ("Name: message") and reports the regex-mismatch message
|
||
* with the input inspected (scr_regex.c). Passing calls return. */
|
||
void scr_assert_throws_regex(ScrRegex *re, ScrError *err, ScrStr *msg, bool has_msg);
|
||
/* assert.throws(fn, {message: /re/, ...}): the regex-valued shape slot —
|
||
* tests the stashed actual key eagerly, stores verdict + rendered
|
||
* /source/flags into the accumulator (scr_regex.c). */
|
||
void scr_assert_shape_re(int key, ScrRegex *re);
|
||
/* assert.doesNotReject's regex predicate over String(error) — never
|
||
* throws (scr_regex.c). */
|
||
bool scr_assert_regex_err_test(ScrRegex *re, ScrError *err);
|
||
/* strictEqual/notStrictEqual/deep twins over symbol values: pointer
|
||
* identity (a symbol IS its identity), messages from the symbols'
|
||
* "Symbol(desc)" rendering — string-style stacked diffs with the `^`
|
||
* first-difference indicator, v24 exactly (scr_symbol.c). */
|
||
void scr_assert_eq_sym(ScrSym *a, ScrSym *b, bool negated, bool deep,
|
||
ScrStr *msg, bool has_msg);
|
||
|
||
/* ── console ──────────────────────────────────────────────────────────── */
|
||
|
||
typedef struct {
|
||
enum { SCR_ARG_F64, SCR_ARG_STR, SCR_ARG_BOOL } tag;
|
||
union {
|
||
double f;
|
||
ScrStr *s;
|
||
bool b;
|
||
} v;
|
||
} ScrLogArg;
|
||
|
||
/* Space-joined args + '\n', submitted before return. Borrows string args. */
|
||
void scr_console_log(size_t n, const ScrLogArg *args);
|
||
/* The stderr twin (console.error AND console.warn — one stream in Node):
|
||
* identical formatting, with stdout settled first so merged (2>&1) output
|
||
* keeps source order. */
|
||
void scr_console_error(size_t n, const ScrLogArg *args);
|
||
|
||
#endif /* SCR_RUNTIME_H */
|