mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-24 06:14:01 +08:00
Merge main
This commit is contained in:
@@ -44,6 +44,10 @@ See the [test harness guide](./tests/harness/README.md) for focused checks, plat
|
||||
|
||||
When changing native assembly or object emission, runtime-pack selection, or the C runtime, rebuild the relevant native artifacts explicitly. Install CMake, Ninja, the pinned LLVM 22 development package, and Zig 0.16.0 on the target host. Run the matching `@scriptc/llvm-<platform>` and `@scriptc/runtime-<platform>` packages' `build:native` scripts. The macOS full test suite also needs those generated artifacts.
|
||||
|
||||
## Runtime headers
|
||||
|
||||
`packages/runtime/src/scr_runtime.h` includes the per-module runtime headers (`scr_string.h`, `scr_dyn.h`, `scr_http.h`, and so on) in dependency order and holds no declarations itself. Add a new declaration to the header of the runtime unit that defines it: `scr_<unit>.h` for `scr_<unit>.c`, or the header whose first comment names that unit. The comment at the top of `scr_runtime.h` lists the ordering rules. To move hunks from a branch that still edits the single-file `scr_runtime.h`, run `node scripts/runtime-header-hunks.mjs <diff>`. It prints a patch for the module headers.
|
||||
|
||||
## Documentation
|
||||
|
||||
The docs site is a standalone pnpm workspace under `docs/`. Install its dependencies with `pnpm --dir docs install` and start it with `pnpm --dir docs dev`. Reuse an existing dev server when one is running.
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
* the class mechanically, from four directions:
|
||||
*
|
||||
* 1. Source scan: every fully-literal `declare ... @scr_*` template
|
||||
* string in the backend/llvm sources is checked against the header.
|
||||
* string in the backend/llvm sources is checked against the header
|
||||
* (scr_runtime.h with its module headers expanded in include order).
|
||||
* 2. Emitted scan: a curated fs/path/os-heavy corpus slice compiles
|
||||
* through the LLVM backend and every `declare @scr_*` in the emitted
|
||||
* .ll — including the generic LIB_FN_SYMS path, whose signatures are
|
||||
@@ -42,7 +43,7 @@ import { compile } from "../src/index.js";
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
const repoRoot = join(import.meta.dirname, "../../..");
|
||||
const headerPath = join(repoRoot, "packages/runtime/src/scr_runtime.h");
|
||||
const runtimeHeaderDir = join(repoRoot, "packages/runtime/src");
|
||||
const llvmSrcDir = join(import.meta.dirname, "../src/backend/llvm");
|
||||
const corpusDir = join(repoRoot, "tests/corpus");
|
||||
|
||||
@@ -152,13 +153,30 @@ function splitParams(argsText: string): string[] {
|
||||
return out;
|
||||
}
|
||||
|
||||
/** scr_runtime.h with every quoted runtime include expanded in place, each
|
||||
* header once — the declarations a runtime TU sees, in order. */
|
||||
async function readRuntimeHeader(
|
||||
name = "scr_runtime.h",
|
||||
seen = new Set<string>(),
|
||||
): Promise<string> {
|
||||
seen.add(name);
|
||||
const text = await readFile(join(runtimeHeaderDir, name), "utf8");
|
||||
const parts: string[] = [];
|
||||
for (const line of text.split("\n")) {
|
||||
const include = line.match(/^#include "(scr_[a-z0-9_]+\.h)"/);
|
||||
if (include === null) parts.push(line);
|
||||
else if (!seen.has(include[1]!)) parts.push(await readRuntimeHeader(include[1]!, seen));
|
||||
}
|
||||
return parts.join("\n");
|
||||
}
|
||||
|
||||
/** Parse scr_runtime.h into name → prototype, plus the extern data
|
||||
* symbols (vtable globals and the like) the emitter references by name.
|
||||
* Only `;`-terminated prototypes count: a `static inline` definition has
|
||||
* no linkage symbol the emitter could declare, so a declare against one
|
||||
* must report as missing. */
|
||||
async function parseHeader(): Promise<{ protos: Map<string, CProto>; dataSyms: Set<string> }> {
|
||||
const raw = await readFile(headerPath, "utf8");
|
||||
const raw = await readRuntimeHeader();
|
||||
const src = raw
|
||||
.replace(/\\\r?\n/g, "")
|
||||
.replace(/\/\*[\s\S]*?\*\//g, " ")
|
||||
@@ -312,7 +330,7 @@ describe("LLVM backend declares match scr_runtime.h prototypes", () => {
|
||||
const llvm = emitLlvmModule(mod, { runtimeAbiMarker: true });
|
||||
expect(llvm).toContain(`declare void @${RUNTIME_ABI_MARKER}()`);
|
||||
expect(llvm).toContain(`call void @${RUNTIME_ABI_MARKER}()`);
|
||||
expect(await readFile(headerPath, "utf8")).toContain(`void ${RUNTIME_ABI_MARKER}(void);`);
|
||||
expect(await readRuntimeHeader()).toContain(`void ${RUNTIME_ABI_MARKER}(void);`);
|
||||
expect(await readFile(join(repoRoot, "packages/runtime/src/scr_console.c"), "utf8")).toContain(
|
||||
`void ${RUNTIME_ABI_MARKER}(void) {}`,
|
||||
);
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5550-locale-compare-ascii-words.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -0,0 +1,425 @@
|
||||
/* scriptc runtime — arrays and Math over arrays (scr_array.c). Included by
|
||||
* scr_runtime.h, which says where new declarations go. */
|
||||
#ifndef SCR_ARRAY_H
|
||||
#define SCR_ARRAY_H
|
||||
|
||||
#include "scr_qs.h"
|
||||
|
||||
/* ── arrays ─────────────────────────────────────────────────────────
|
||||
* Monomorphic, growable, refcounted. Elements live in 8-byte slots
|
||||
* (doubles/pointers via memcpy and casts), except SCR_ELEM_BOOL, whose
|
||||
* dense storage holds one byte (0 or 1) per element; `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_ops) as one
|
||||
* constant descriptor per element type, stored once per array; 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;
|
||||
|
||||
/* The RC entry points of an SCR_ELEM_REF array's element type: one constant
|
||||
* per element type (emitted by the compiler, or static in the runtime),
|
||||
* shared by every array of that type. retain/release are the type's `_v`
|
||||
* adapters; trace is non-NULL exactly when the element type carries a cycle
|
||||
* header. */
|
||||
typedef struct ScrArrOps {
|
||||
void *(*retain)(void *);
|
||||
void (*release)(void *);
|
||||
ScrTraceFn trace;
|
||||
} ScrArrOps;
|
||||
|
||||
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 */
|
||||
/* One ScrElemKind. */
|
||||
uint8_t elem;
|
||||
/* 1 exactly when ops->trace is non-NULL: the element type carries a cycle
|
||||
* header, so the array is CYCLE-CAPABLE (an element can point back at the
|
||||
* array that owns it) and allocates with the hidden collector header —
|
||||
* its 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. Kept beside `elem` so the RC fast paths test it with one load. */
|
||||
uint8_t cyc;
|
||||
uint16_t spare;
|
||||
/* A lower bound on the hole-free prefix: filled <= len, filled <= cap,
|
||||
* and every dense state below it is SCR_ARR_VALUE. Element reads below
|
||||
* it need no state check and element writes below it leave the states
|
||||
* alone. Any operation that may store another state (a hole, present
|
||||
* undefined) or move states below it lowers it first (scr_arr_cut);
|
||||
* 0 is always valid. */
|
||||
uint32_t filled;
|
||||
/* SCR_ELEM_REF only; NULL for every other element kind. */
|
||||
const ScrArrOps *ops;
|
||||
/* Dense values: `cap` 8-byte slots, or `cap` bytes for SCR_ELEM_BOOL
|
||||
* (scr_arr_slot_width). Sparse entries and properties keep 8-byte slots
|
||||
* for every kind; a bool slot is 0 or 1 wherever it lives. */
|
||||
uint64_t *data;
|
||||
uint8_t *present; /* one state per dense slot; heap arrays share data allocation */
|
||||
/* Rarely used storage (sparse indices, noncanonical numeric properties,
|
||||
* match-result metadata) lives out of line: NULL until first needed, so
|
||||
* the common dense array stays small. */
|
||||
struct ScrArrExt *ext;
|
||||
} ScrArr;
|
||||
|
||||
typedef struct ScrArrExt {
|
||||
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;
|
||||
struct ScrDyn *metadata; /* heterogeneous named properties of match results */
|
||||
} ScrArrExt;
|
||||
/* The LLVM backend mirrors ScrArr as %ScrArr: its inline retain/release read
|
||||
* `cyc` (field 4, i8) to decide mark-live / candidate buffering exactly like
|
||||
* scr_arr_retain and scr_arr_release; its inline element access uses filled
|
||||
* (field 6, i32), data (field 8) and present (field 9). */
|
||||
_Static_assert(offsetof(ScrArr, cyc) == 3 * sizeof(size_t) + 1,
|
||||
"inline RC fast paths read ScrArr.cyc as %ScrArr field 4");
|
||||
_Static_assert(offsetof(ScrArr, filled) == 3 * sizeof(size_t) + 4,
|
||||
"inline element access reads ScrArr.filled as %ScrArr field 6");
|
||||
_Static_assert(offsetof(ScrArr, data) == 3 * sizeof(size_t) + 8 + sizeof(void *),
|
||||
"inline element reads use ScrArr.data as %ScrArr field 8");
|
||||
|
||||
/* Bytes per dense value slot. */
|
||||
static inline size_t scr_arr_slot_width(const ScrArr *a) {
|
||||
return a->elem == SCR_ELEM_BOOL ? 1 : sizeof(uint64_t);
|
||||
}
|
||||
|
||||
/* The dense value at `index` (< cap) as an 8-byte slot, and its store. */
|
||||
static inline uint64_t scr_arr_dense_get(const ScrArr *a, size_t index) {
|
||||
if (a->elem == SCR_ELEM_BOOL) return ((const uint8_t *)a->data)[index];
|
||||
return a->data[index];
|
||||
}
|
||||
|
||||
static inline void scr_arr_dense_put(ScrArr *a, size_t index, uint64_t slot) {
|
||||
if (a->elem == SCR_ELEM_BOOL) ((uint8_t *)a->data)[index] = (uint8_t)slot;
|
||||
else a->data[index] = slot;
|
||||
}
|
||||
|
||||
/* Lower the hole-free prefix to at most `index`: call before storing a
|
||||
* non-value state at `index`, or before moving states into [index, ...). */
|
||||
static inline void scr_arr_cut(ScrArr *a, size_t index) {
|
||||
if (a->filled > index) a->filled = (uint32_t)index;
|
||||
}
|
||||
|
||||
/* The first index in [f, end) whose state is not a present value (or end).
|
||||
* Compares eight states per step: bulk copies (slice, concat, spread) end
|
||||
* with this scan over the whole copied range. */
|
||||
static inline size_t scr_arr_value_run(const uint8_t *present, size_t f, size_t end) {
|
||||
const uint64_t values = 0x0101010101010101ull * SCR_ARR_VALUE;
|
||||
while (end - f >= 8) {
|
||||
uint64_t word;
|
||||
memcpy(&word, present + f, sizeof word);
|
||||
if (word != values) break;
|
||||
f += 8;
|
||||
}
|
||||
while (f < end && present[f] == SCR_ARR_VALUE) f++;
|
||||
return f;
|
||||
}
|
||||
|
||||
/* Extend the hole-free prefix over populated dense states. Starts at the
|
||||
* current bound, so after an append it costs only the appended range. */
|
||||
static inline void scr_arr_refill(ScrArr *a) {
|
||||
size_t end = a->len < a->cap ? a->len : a->cap;
|
||||
size_t f = a->filled;
|
||||
if (f < end) f = scr_arr_value_run(a->present, f, end);
|
||||
a->filled = (uint32_t)f;
|
||||
}
|
||||
|
||||
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: `ops` must outlive the array (a constant; the
|
||||
* compiler emits one per element type) and its retain/release must be
|
||||
* non-NULL; ops->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_ops(const ScrArrOps *ops, size_t initial_cap);
|
||||
/* An empty array expected to receive a few elements: its first slots live
|
||||
* in the array's own block (one allocation; the block of a plain empty
|
||||
* array plus five 8-byte slots). Otherwise exactly scr_arr_new(elem, 0) and
|
||||
* scr_arr_new_ops(ops, 0). Returns +1. */
|
||||
ScrArr *scr_arr_new_small(ScrElemKind elem);
|
||||
ScrArr *scr_arr_new_ops_small(const ScrArrOps *ops);
|
||||
|
||||
static inline ScrArr *scr_arr_retain(ScrArr *a) {
|
||||
if (a->rc != SIZE_MAX) {
|
||||
a->rc++;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
/* Releases ref elements (SCR_ELEM_STR/ARR/BYTES/REF) recursively at rc == 0.
|
||||
* NULL-tolerant (uninitialized locals). Cycle-capable arrays (`cyc`) feed
|
||||
* the candidate-root buffer like every headered type. */
|
||||
void scr_arr_release(ScrArr *a);
|
||||
void scr_arr_copy_metadata(ScrArr *a, struct ScrDyn *target);
|
||||
/* The array's out-of-line storage, allocated (zeroed) on first use. */
|
||||
ScrArrExt *scr_arr_ext(ScrArr *a);
|
||||
/* Moves inline dense slots (kept in the header block of a small array) to
|
||||
* their own allocation, so the storage pointers may be swapped with another
|
||||
* array's. No-op for other arrays. */
|
||||
void scr_arr_outline(ScrArr *a);
|
||||
/* Swaps the sparse slots and numeric properties of two arrays; each keeps
|
||||
* its own match metadata (refresh of a cached typed view, llvm/dyn.ts). */
|
||||
void scr_arr_swap_indexed_ext(ScrArr *a, ScrArr *b);
|
||||
|
||||
/* 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);
|
||||
/* Mutating indexed operations borrow the receiver and value, preserve
|
||||
* sparse presence, and return the receiver retained. */
|
||||
ScrArr *scr_arr_copy_within(ScrArr *a, double target, double start, double end);
|
||||
ScrArr *scr_arr_fill_f64(ScrArr *a, double value, double start, double end);
|
||||
ScrArr *scr_arr_fill_bool(ScrArr *a, bool value, double start, double end);
|
||||
ScrArr *scr_arr_fill_ref(ScrArr *a, void *value, double start, double end);
|
||||
ScrArr *scr_arr_fill_undefined(ScrArr *a, double start, double end);
|
||||
/* 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 */
|
||||
void *scr_arr_borrow_ref(ScrArr *a, double i); /* same checks; borrowed */
|
||||
/* Borrow a non-null reference element, or NULL for a hole/undefined/missing
|
||||
* property. Keep the array alive and retain before removing its element.
|
||||
* This read does not invoke retain callbacks or mutate runtime state. */
|
||||
void *scr_arr_peek_ref(const ScrArr *a, double i);
|
||||
|
||||
/* 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);
|
||||
/* Compiler-built slot buffers do not alias array storage. Both operations
|
||||
* move ownership of every slot and preserve the evaluated argument order. */
|
||||
double scr_arr_push_many(ScrArr *a, size_t count, const uint64_t *slots);
|
||||
double scr_arr_unshift_many(ScrArr *a, size_t count, const uint64_t *slots);
|
||||
/* 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);
|
||||
/* Default primitive String ordering; stable, borrowed receiver, owned result. */
|
||||
ScrArr *scr_arr_sort_primitive(ScrArr *a, bool copy);
|
||||
/* Stable comparator sort of the first `count` slots of a private, compacted
|
||||
* snapshot (every slot a value; f64 or reference elements). `f` is a borrowed
|
||||
* closure of `arity` (0..2) element parameters returning a number. Elements
|
||||
* move without reference-count traffic; a pending exception from `f` stops
|
||||
* the sort and leaves the snapshot unchanged. */
|
||||
struct ScrClosure;
|
||||
/* A nonzero `order` says `f` orders number keys ascending (1) or descending
|
||||
* (-1) without side effects: f64 elements are their own keys, and reference
|
||||
* elements read theirs through the borrowed `key` closure (element ->
|
||||
* number). Keys that are all numbers other than NaN and -0 then sort without
|
||||
* calling `f`, with the same stable result. */
|
||||
void scr_arr_sort_values(ScrArr *a, double count, struct ScrClosure *f, uint32_t arity,
|
||||
int32_t order, struct ScrClosure *key);
|
||||
/* Comparator sort of a dense array in place: `a.sort(f)`, or with `owned`
|
||||
* the private copy of `a.toSorted(f)`. Returns false without calling `f`
|
||||
* when `a` has holes, storage-level undefined or a sparse tail, or is
|
||||
* published; the caller then sorts a compacted snapshot. Otherwise sorts
|
||||
* and returns true; a pending exception from `f` leaves a shared `a`
|
||||
* unwritten (an owned copy then holds a permutation of its values). */
|
||||
/* The borrowed 8-byte slots of `a` when every index below its length holds
|
||||
* a value in dense storage (length in *len); NULL for holes, storage-level
|
||||
* undefined, sparse tails, empty arrays and boolean elements. */
|
||||
const uint64_t *scr_arr_dense_slots(const ScrArr *a, size_t *len);
|
||||
bool scr_arr_sort_dense(ScrArr *a, struct ScrClosure *f, uint32_t arity, bool owned,
|
||||
int32_t order, struct ScrClosure *key);
|
||||
/* V8's TimSort over n slots with a caller comparison: less(ctx, x, y)
|
||||
* answers whether x orders before y (comparefn(x, y) < 0), or sets *failed,
|
||||
* with an exception pending, to stop the sort. Returns false after a
|
||||
* failure; `values` is then unchanged, and it holds every slot exactly once,
|
||||
* in its original order, whenever `less` runs. */
|
||||
typedef bool (*ScrSortLess)(void *ctx, uint64_t x, uint64_t y, bool *failed);
|
||||
bool scr_sort_slots_with(uint64_t *values, size_t n, ScrSortLess less, void *ctx);
|
||||
|
||||
/* 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);
|
||||
/* The same splice in statement position: inserts `count` owned value slots
|
||||
* (moved in) and releases the removed elements without building a result
|
||||
* array. Borrows a. */
|
||||
void scr_arr_splice_drop(ScrArr *a, double start, double deleteCount, size_t count,
|
||||
const uint64_t *slots);
|
||||
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);
|
||||
|
||||
#endif /* SCR_ARRAY_H */
|
||||
@@ -0,0 +1,168 @@
|
||||
/* scriptc runtime — node:assert (scr_assert.c). Included by scr_runtime.h,
|
||||
* which says where new declarations go. */
|
||||
#ifndef SCR_ASSERT_H
|
||||
#define SCR_ASSERT_H
|
||||
|
||||
#include "scr_http2.h"
|
||||
|
||||
/* ── 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 */
|
||||
/* AssertionError properties: a failure path records actual, expected and
|
||||
* operator before it throws (scr_assert.c). */
|
||||
void scr_assert_fail(ScrStr *message, bool generated); /* takes ownership; always throws */
|
||||
void scr_assert_record(ScrDyn *actual, ScrDyn *expected, const char *op); /* values move in */
|
||||
void scr_assert_record_dyn(ScrDyn *actual, ScrDyn *expected, ScrStr *op); /* borrows */
|
||||
const char *scr_assert_eq_operator(bool negated, bool deep);
|
||||
ScrError *scr_assert_error_new(ScrDyn *options);
|
||||
ScrStr *scr_assert_inspect_str(const ScrStr *s); /* util.inspect quoting; +1 */
|
||||
void scr_assert_ok(bool pass, ScrStr *message, bool generated);
|
||||
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);
|
||||
/* No-error assertions share Node's unwanted-exception/rejection message.
|
||||
* The frontend reads and coerces actual?.message, preserving getter throws. */
|
||||
void scr_assert_unwanted_error(ScrStr *actual_message, bool rejection, ScrStr *msg, bool has_msg);
|
||||
bool scr_assert_no_error_predicate(ScrDyn *predicate, ScrDyn *actual);
|
||||
bool scr_assert_regex_dyn_test(ScrRegex *re, ScrDyn *actual);
|
||||
/* 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): RegExp.test semantics,
|
||||
* including global/sticky lastIndex transitions. */
|
||||
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);
|
||||
|
||||
#endif /* SCR_ASSERT_H */
|
||||
@@ -0,0 +1,389 @@
|
||||
/* scriptc runtime — promises, fibers, the event loop, AsyncLocalStorage,
|
||||
* promise combinators and timers (scr_async.c, scr_async_dyn.c). Included by
|
||||
* scr_runtime.h, which says where new declarations go. */
|
||||
#ifndef SCR_ASYNC_H
|
||||
#define SCR_ASYNC_H
|
||||
|
||||
#include "scr_dyn.h"
|
||||
|
||||
/* ── 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);
|
||||
|
||||
/* ── 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);
|
||||
void scr_promise_share_identity(ScrPromise *dst, ScrPromise *src);
|
||||
bool scr_promise_identity_equal(ScrPromise *a, ScrPromise *b);
|
||||
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);
|
||||
/* `await f(...)` of a fiber-backed async f: spawn and await in one call,
|
||||
* returning the awaited value (a rejection re-throws into the caller). */
|
||||
void scr_async_spawn_await_void(void (*entry)(ScrFiber *, void *), void *argpack);
|
||||
double scr_async_spawn_await_f64(void (*entry)(ScrFiber *, void *), void *argpack);
|
||||
bool scr_async_spawn_await_bool(void (*entry)(ScrFiber *, void *), void *argpack);
|
||||
ScrStr *scr_async_spawn_await_str(void (*entry)(ScrFiber *, void *), void *argpack);
|
||||
void *scr_async_spawn_await_ref(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);
|
||||
/* Fiberless async call (native targets): the compiler proves an async body
|
||||
* can never park its execution context and runs it directly on the
|
||||
* caller's stack between enter and leave. The frame saves what a fresh
|
||||
* fiber would isolate — the Error.stack frame chain and the
|
||||
* AsyncLocalStorage context — and leave restores both, returning a fresh
|
||||
* pending promise (+1) the caller settles from the body's outcome. */
|
||||
typedef struct ScrAsyncInline {
|
||||
void *stack;
|
||||
void *als;
|
||||
} ScrAsyncInline;
|
||||
void scr_async_inline_enter(ScrAsyncInline *frame);
|
||||
ScrPromise *scr_async_inline_leave(ScrAsyncInline *frame);
|
||||
/* Leaves the frame without creating the call's promise: `await f()` of a
|
||||
* fiberless f consumes the result directly (the emitted await-call entry
|
||||
* still takes the await's microtask turn). */
|
||||
void scr_async_inline_exit(ScrAsyncInline *frame);
|
||||
|
||||
/* 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);
|
||||
ScrDyn *scr_await_dyn_value_settled(ScrDyn *value);
|
||||
void scr_promise_resolve_dyn(ScrPromise *destination, ScrDyn *value);
|
||||
/* `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 SCR_TL ScrAlsCtx **scr_als_active;
|
||||
#ifdef SCR_WORKERS
|
||||
ScrAlsCtx **scr_als_slot(void);
|
||||
#define SCR_ALS_SLOT() scr_als_slot()
|
||||
#else
|
||||
#define SCR_ALS_SLOT() scr_als_active
|
||||
#endif
|
||||
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 SCR_TL 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 SCR_TL 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).
|
||||
* scr_process_emit_warning takes the ARGUMENT VECTOR as one dyn array,
|
||||
* applies Node's full grammar/TypeErrors at the call and emits on the
|
||||
* next tick, as Node does; scr_emit_warning is the C-side entry and
|
||||
* reports synchronously. */
|
||||
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);
|
||||
/* scr_emit_warning once a 'warning' listener exists (scr_lib.c; set by the
|
||||
* listener registration): units that cannot link the dyn-async unit route
|
||||
* their warnings through it, and print the default report otherwise. */
|
||||
extern SCR_TL void (*scr_warning_emit_hook)(const char *name, const char *code, ScrStr *message);
|
||||
/* process.noDeprecation (which 0) and process.throwDeprecation (which 1):
|
||||
* the stored value (+1; undefined until assigned), the assignment
|
||||
* (borrowed), and the truthiness the warning paths test. */
|
||||
ScrDyn *scr_process_deprecation_flag(double which);
|
||||
void scr_process_set_deprecation_flag(double which, ScrDyn *value);
|
||||
bool scr_process_deprecation_flag_on(int which);
|
||||
/* Node's emitWarning tail on the next tick (util.deprecate): a
|
||||
* DeprecationWarning is dropped under noDeprecation and thrown from its
|
||||
* own tick under throwDeprecation; otherwise the report and the 'warning'
|
||||
* listeners run. scr_warning_new builds the string form's warning value
|
||||
* (borrows; +1). */
|
||||
void scr_warning_emit_deferred(ScrDyn *warning);
|
||||
ScrDyn *scr_warning_new(ScrStr *message, const char *type, const ScrStr *code);
|
||||
/* 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);
|
||||
|
||||
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_join: subscribe the result to an entry with a reaction job that
|
||||
* runs one microtask turn after the entry settles (or after the join, for
|
||||
* an entry already settled) — 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; the entry counts as HANDLED from the join. race_add is the same
|
||||
* callback run inside the entry's own settlement (or now, if settled),
|
||||
* for destinations that view the entry at another inner type. The
|
||||
* payload accessors return retained/by-value fulfillment payloads for
|
||||
* the emitted adapters (losing entries keep their settlements). */
|
||||
void scr_promise_race_join(ScrPromise *race, ScrPromise *in,
|
||||
void (*adapt)(ScrPromise *dst, ScrPromise *src));
|
||||
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: each entry settles the
|
||||
* result through a reaction job one microtask turn after it settles,
|
||||
* values land at their INPUT index regardless of settlement order, the
|
||||
* first rejection's job wins (every entry counts as handled), 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));
|
||||
|
||||
/* ── Promise.allSettled / Promise.any ─────────────────────────────────
|
||||
* The same countdown and per-entry reaction jobs as Promise.all, with
|
||||
* the mode deciding what an entry's job does. allSettled stores EVERY
|
||||
* settlement through `store` (the emitted or dyn settled-record builder)
|
||||
* and fulfills with the array once every entry has settled. any settles
|
||||
* the result from the first fulfillment (same inner type — a copy),
|
||||
* stores each rejection reason into `errors` at its input index, and
|
||||
* once every entry has rejected calls `fail(result, errors)`, which
|
||||
* rejects the result (the AggregateError). The empty array fulfills /
|
||||
* fails immediately. Both BORROW their arrays and return +1.
|
||||
*
|
||||
* The builder underneath (ScrCombine) takes entries one at a time, so a
|
||||
* caller that runs other work between entries — the dyn path's
|
||||
* thenable jobs — keeps Node's queue order: settled entries batch into
|
||||
* one job until scr_promise_combine_flush closes the batch. */
|
||||
typedef void (*ScrAllStore)(ScrArr *a, double i, ScrPromise *src);
|
||||
typedef void (*ScrAnyFail)(ScrPromise *result, ScrArr *errors);
|
||||
enum { SCR_COMBINE_ALL = 0, SCR_COMBINE_ALL_SETTLED = 1, SCR_COMBINE_ANY = 2 };
|
||||
typedef struct ScrCombine {
|
||||
struct ScrAllState *st;
|
||||
ScrPromise *result;
|
||||
size_t next; /* input index of the next entry */
|
||||
size_t batched; /* settled entries counted in the open batch */
|
||||
ScrPromise *decided; /* the open batch's first deciding entry (+1) */
|
||||
bool open; /* the open batch holds at least one entry */
|
||||
} ScrCombine;
|
||||
void scr_promise_combine_begin(ScrCombine *c, int mode, size_t n, ScrArr *values,
|
||||
ScrAllStore store, ScrAnyFail fail);
|
||||
void scr_promise_combine_add(ScrCombine *c, ScrPromise *in); /* borrowed */
|
||||
void scr_promise_combine_flush(ScrCombine *c);
|
||||
ScrPromise *scr_promise_combine_end(ScrCombine *c); /* +1 */
|
||||
ScrPromise *scr_promise_all_settled(ScrArr *ps, ScrArr *values, ScrAllStore store);
|
||||
ScrPromise *scr_promise_any(ScrArr *ps, ScrArr *errors, ScrAllStore store, ScrAnyFail fail);
|
||||
/* The typed Promise.any entry (scr_async_dyn.c): reasons collect as dyn
|
||||
* values and exhaustion rejects with an AggregateError. */
|
||||
ScrPromise *scr_promise_any_typed(ScrArr *ps);
|
||||
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_is_rejected(const ScrPromise *p);
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
#endif /* SCR_ASYNC_H */
|
||||
@@ -0,0 +1,72 @@
|
||||
/* scriptc runtime — ECMAScript bigint values (scr_bigint.c). Included by
|
||||
* scr_runtime.h, which says where new declarations go. */
|
||||
#ifndef SCR_BIGINT_H
|
||||
#define SCR_BIGINT_H
|
||||
|
||||
#include "scr_lib.h"
|
||||
|
||||
/* ── 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);
|
||||
uint64_t scr_bigint_hash(const ScrBigInt *value); /* normalized value, no allocation */
|
||||
bool scr_bigint_eq_string(ScrBigInt *a, ScrStr *b); /* StringToBigInt failure is false */
|
||||
double scr_bigint_cmp_string(ScrBigInt *a, ScrStr *b); /* 2 = invalid StringToBigInt */
|
||||
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);
|
||||
/* Small-value bigint trees the compiler evaluates in 64-bit registers:
|
||||
* mag64 stores the low 64 bits of the magnitude and its length in 64-bit
|
||||
* words, and returns the sign (-1, 0, 1); from_mag64 builds +-mag (+1).
|
||||
* wrap_eval is the exact fallback over a compiler-built postfix program
|
||||
* (leaves, and numbers for BigInt(number) operands); +1 or NULL with an
|
||||
* exception pending. */
|
||||
int scr_bigint_mag64(const ScrBigInt *value, uint64_t *mag, uint32_t *words);
|
||||
/* Whether a StringToBigInt text with this many significant digits in radix
|
||||
* 2, 8, 10 or 16 can fit the maximum BigInt size (no size arithmetic that
|
||||
* can wrap; the parser's guard before it allocates). */
|
||||
bool scr_bigint_digits_fit(size_t digits, unsigned radix);
|
||||
ScrBigInt *scr_bigint_from_mag64(uint64_t mag, bool negative);
|
||||
ScrBigInt *scr_bigint_wrap_eval(const char *program, ScrBigInt *const *leaves, const double *numbers);
|
||||
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);
|
||||
|
||||
#endif /* SCR_BIGINT_H */
|
||||
@@ -0,0 +1,83 @@
|
||||
/* scriptc runtime — Blob and File (scr_blob.c). Included by scr_runtime.h,
|
||||
* which says where new declarations go. */
|
||||
#ifndef SCR_BLOB_H
|
||||
#define SCR_BLOB_H
|
||||
|
||||
#include "scr_dyn.h"
|
||||
|
||||
/* ── Blob / File (scr_blob.c) ──────────────────────────────────────────
|
||||
* Node 24's lib/internal/blob.js and file.js: immutable bytes copied from
|
||||
* the sources at construction, shared by slices and structured clones.
|
||||
* The constructors take the argument vector as one dyn array (borrowed)
|
||||
* and validate it like Node; +1 handle, or NULL with the exception
|
||||
* pending. slice takes its (start, end, contentType) vector the same way.
|
||||
* The readers answer +1 promises (string, u8 bytes, and an ArrayBuffer
|
||||
* dyn value) that settle on Node's microtask turn for the Blob's part
|
||||
* count. scr_blob_is answers `v instanceof Blob` (kind 0) or `File` (1). */
|
||||
ScrDyn *scr_blob_new(ScrDyn *args);
|
||||
ScrDyn *scr_file_new(ScrDyn *args);
|
||||
ScrDyn *scr_blob_slice(ScrDyn *self, ScrDyn *args);
|
||||
ScrPromise *scr_blob_text(ScrDyn *self);
|
||||
ScrPromise *scr_blob_bytes(ScrDyn *self);
|
||||
ScrPromise *scr_blob_array_buffer(ScrDyn *self);
|
||||
bool scr_blob_is(const ScrDyn *value, double kind);
|
||||
|
||||
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_global_native_init(ScrArr *known, ScrDyn *(*console_get)(void));
|
||||
ScrDyn *scr_console_native(void);
|
||||
/* node:console Console instances: a native handle over the stdout/stderr
|
||||
* targets — process stdio (fd 1/2) or a writable stream bound by
|
||||
* scr_console_bind (scr_stream.c, which also installs the stream writer so
|
||||
* console-only programs never link the stream unit) — and the
|
||||
* ignoreErrors flag. new answers a +1 handle with both targets set to
|
||||
* the given fds (0 = a stream is bound next); bind retains the stream.
|
||||
* write prints `text\n` to the selected target: a stream receives ONE
|
||||
* write, wrapped in Node's ignoreErrors handling (a noop 'error' listener
|
||||
* around the call, a completion handler that swallows the write's own
|
||||
* error, synchronous throws discarded); the global console handle writes
|
||||
* the process streams like console.log/error. */
|
||||
typedef struct ScrConsoleInst {
|
||||
size_t rc;
|
||||
double fd[2]; /* 1/2 process stdio targets; 0 when stream[i] is bound */
|
||||
ScrStream *stream[2]; /* owned */
|
||||
ScrClosure *on_written[2]; /* owned: the per-stream completion handlers */
|
||||
bool ignore_errors;
|
||||
void (*stream_write)(struct ScrConsoleInst *, int which, ScrStr *line);
|
||||
void (*stream_drop)(struct ScrConsoleInst *); /* releases the bound streams */
|
||||
} ScrConsoleInst;
|
||||
ScrDyn *scr_console_new(double out_fd, double err_fd, bool ignore_errors);
|
||||
void scr_console_bind(ScrDyn *console, double which, ScrStream *stream);
|
||||
void scr_console_inst_write(ScrDyn *console, ScrStr *text, bool to_stderr);
|
||||
ScrDyn *scr_process_stdio(double fd);
|
||||
bool scr_dyn_process_stdio_is(const ScrDyn *value);
|
||||
double scr_dyn_process_stdio_fd(const ScrDyn *value, const ScrDynPath *path);
|
||||
|
||||
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 throw "not supported yet" for it. +1, or NULL with a pending exception. */
|
||||
ScrDyn *scr_dyn_handle_key_get(const ScrDyn *d, const ScrStr *k);
|
||||
|
||||
#endif /* SCR_BLOB_H */
|
||||
@@ -0,0 +1,423 @@
|
||||
/* scriptc runtime — typed arrays and Buffer (scr_bytes.c). Included by
|
||||
* scr_runtime.h, which says where new declarations go. */
|
||||
#ifndef SCR_BYTES_H
|
||||
#define SCR_BYTES_H
|
||||
|
||||
#include "scr_events.h"
|
||||
|
||||
/* ── 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 typed ELEMENT reads trap like arrays; invalid writes are
|
||||
* ignored as in JavaScript. 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 ScrSharedBytes ScrSharedBytes;
|
||||
|
||||
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 is_data_view; /* DataView is distinct from a numeric typed array. */
|
||||
bool external; /* Root aliases foreign memory; releasing it never frees data. */
|
||||
ScrSharedBytes *shared; /* Neutral backing, borrowed by views from their local owner. */
|
||||
} ScrBytes;
|
||||
|
||||
/* Internal scoped locking is only for leaf memory operations. Never hold this
|
||||
* guard across a script callback, blocking I/O, or a fiber suspension. */
|
||||
void scr_shared_enter(void);
|
||||
void scr_shared_leave(void);
|
||||
bool scr_shared_guard(const ScrBytes *, const ScrBytes *);
|
||||
void scr_shared_guard_release(bool *);
|
||||
#if defined(SCR_WORKERS) || (defined(SCR_LIB) && defined(SCR_THREAD_INSTANCES))
|
||||
#define SCR_SHARED_GUARD(a, b) bool scr_shared_held __attribute__((cleanup(scr_shared_guard_release))) = scr_shared_guard(a, b)
|
||||
#else
|
||||
#define SCR_SHARED_GUARD(a, b) ((void)0)
|
||||
#endif
|
||||
ScrSharedBytes *scr_shared_retain(ScrSharedBytes *);
|
||||
void scr_shared_release(ScrSharedBytes *);
|
||||
void scr_bytes_make_shared(ScrBytes *);
|
||||
ScrBytes *scr_shared_wrap(ScrSharedBytes *);
|
||||
void scr_bytes_read(const ScrBytes *, size_t, void *, size_t);
|
||||
void scr_bytes_write(ScrBytes *, size_t, const void *, size_t);
|
||||
ScrStr *scr_bytes_string(const ScrBytes *, size_t, size_t);
|
||||
ScrBytes *scr_bytes_snapshot(const ScrBytes *);
|
||||
void scr_bytes_snapshot_release(ScrBytes **);
|
||||
ScrBytes *scr_bytes_local_copy(ScrBytes *); /* +1; shared inputs are copied */
|
||||
void scr_bytes_require_unshared(const ScrBytes *);
|
||||
/* Snapshot before passing byte pointers to I/O or callback-capable consumers.
|
||||
* The copy is local, so neither a lock nor shared raw memory escapes. */
|
||||
#if defined(SCR_WORKERS) || (defined(SCR_LIB) && defined(SCR_THREAD_INSTANCES))
|
||||
#define SCR_BYTES_SNAPSHOT(name) \
|
||||
ScrBytes *scr_snapshot_##name __attribute__((cleanup(scr_bytes_snapshot_release))) = scr_bytes_snapshot(name); \
|
||||
if (scr_snapshot_##name) name = scr_snapshot_##name
|
||||
#else
|
||||
#define SCR_BYTES_SNAPSHOT(name) ((void)0)
|
||||
#endif
|
||||
ScrDyn *scr_shared_array_buffer_new(ScrDyn *);
|
||||
ScrDyn *scr_worker_data(void);
|
||||
ScrDyn *scr_worker_parent_port(void);
|
||||
ScrDyn *scr_worker_new(double root, ScrStr *filename, ScrDyn *options);
|
||||
bool scr_shared_array_buffer_is(const ScrDyn *);
|
||||
bool scr_buffer_storage_is(const ScrDyn *);
|
||||
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);
|
||||
|
||||
/* `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_constructor(void);
|
||||
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 */
|
||||
ScrBytes *scr_bytes_take_data(uint8_t *data, size_t len); /* +1, consumes malloc storage */
|
||||
|
||||
/* Same-element-kind copy, preserving the raw representation. Cross-kind
|
||||
* construction uses scr_bytes_convert. 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. Invalid typed reads trap; invalid writes are ignored.
|
||||
* Checked reads preserve JavaScript's undefined result. Valid 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);
|
||||
double scr_bytes_get_or_nan(const ScrBytes *b, double i); /* NaN when the index is invalid */
|
||||
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 */
|
||||
/* In-place numeric sort of a typed array: order 0 is the default
|
||||
* TypedArray order (NaN last, -0 before +0); 1 or -1 is a recognized
|
||||
* numeric comparator's, refused (false, nothing written) when a float
|
||||
* element is NaN or -0. Runs no script code. */
|
||||
bool scr_bytes_sort_numeric(ScrBytes *b, int32_t order);
|
||||
/* Comparator sort of a typed array with the comparison schedule Node
|
||||
* uses; `f` is a borrowed closure of `arity` (0..2) number parameters.
|
||||
* A comparator exception stays pending and nothing is written back. */
|
||||
void scr_bytes_sort_compare(ScrBytes *b, struct ScrClosure *f, uint32_t arity);
|
||||
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 stays lone; 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);
|
||||
double scr_text_decoder_encoding(const ScrDyn *label);
|
||||
ScrStr *scr_text_decoder_name(double encoding);
|
||||
ScrDyn *scr_text_encode_into(const ScrDyn *source, const ScrDyn *destination);
|
||||
bool scr_buffer_is_ascii(const ScrDyn *input);
|
||||
bool scr_buffer_is_utf8(const ScrDyn *input);
|
||||
ScrBytes *scr_buffer_transcode(const ScrDyn *source, const ScrDyn *from, const ScrDyn *to);
|
||||
ScrBytes *scr_bytes_buffer_source(const ScrDyn *value);
|
||||
ScrStr *scr_text_decode_buffer_source(const ScrDyn *value);
|
||||
ScrStr *scr_text_decode_options(const ScrBytes *b, bool fatal, bool ignore_bom);
|
||||
ScrStr *scr_text_decode_stream(ScrDyn *state, const ScrBytes *b, double encoding, bool fatal, bool ignore_bom, bool stream);
|
||||
ScrStr *scr_text_decode_legacy_options(const ScrBytes *b, double encoding, bool fatal, bool ignore_bom);
|
||||
|
||||
/* 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);
|
||||
ScrBytes *scr_bytes_from_str_checked(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);
|
||||
double scr_buffer_byte_length_dyn(ScrDyn *value, 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 without an error; 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);
|
||||
double scr_fs_timestamp_value(const ScrDyn *t, const char *name);
|
||||
void scr_fs_utimes_checked(const ScrDyn *path, const ScrDyn *atime, const ScrDyn *mtime);
|
||||
void scr_fs_lutimes_checked(const ScrDyn *path, const ScrDyn *atime, const ScrDyn *mtime);
|
||||
void scr_fs_futimes_checked(const ScrDyn *fd, const ScrDyn *atime, const ScrDyn *mtime);
|
||||
/* 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 */
|
||||
|
||||
#endif /* SCR_BYTES_H */
|
||||
@@ -0,0 +1,289 @@
|
||||
/* scriptc runtime — child_process (scr_child.c). Included by scr_runtime.h,
|
||||
* which says where new declarations go. */
|
||||
#ifndef SCR_CHILD_H
|
||||
#define SCR_CHILD_H
|
||||
|
||||
#include "scr_lib.h"
|
||||
|
||||
/* ── 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);
|
||||
/* Runtime options use the same spawn core; validation and explicit
|
||||
* unsupported-option refusals throw before starting a child. Borrows. */
|
||||
ScrChild *scr_spawn_dynamic(ScrStr *cmd, ScrArr *args, const ScrDyn *options);
|
||||
void scr_child_on_spawn(ScrChild *child, ScrClosure *cb); /* moves zero-argument callback */
|
||||
/* 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);
|
||||
void scr_child_ref(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);
|
||||
void scr_child_on_dyn(ScrChild *child, ScrStr *event, ScrDyn *listener); /* borrowed */
|
||||
/* Optional native-stream bridge. The table avoids a stream dependency
|
||||
* in programs that only use the child-process surface. */
|
||||
typedef struct ScrChildStreamOps {
|
||||
ScrStream *(*readable)(void);
|
||||
ScrStream *(*writable)(ScrClosure *, ScrStreamChunkInv, ScrClosure *, ScrStreamPlainInv);
|
||||
void *(*retain)(void *);
|
||||
void (*release)(void *);
|
||||
ScrTraceFn trace;
|
||||
bool (*push)(ScrStream *, ScrBytes *);
|
||||
bool (*end)(ScrStream *);
|
||||
void (*write_done)(ScrStream *, ScrError *);
|
||||
void (*final_done)(ScrStream *, ScrError *);
|
||||
} ScrChildStreamOps;
|
||||
extern const ScrChildStreamOps scr_stream_child_ops;
|
||||
ScrDyn *scr_stream_dyn_view(ScrStream *stream); /* borrowed; +1 live method/property view */
|
||||
void scr_child_dyn_streams_install(const ScrChildStreamOps *ops,
|
||||
ScrDyn *(*readable_box)(void *), ScrDyn *(*writable_box)(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);
|
||||
|
||||
#endif /* SCR_CHILD_H */
|
||||
@@ -0,0 +1,148 @@
|
||||
/* scriptc runtime — boxes and closures (scr_closure.c). Included by
|
||||
* scr_runtime.h, which says where new declarations go. */
|
||||
#ifndef SCR_CLOSURE_H
|
||||
#define SCR_CLOSURE_H
|
||||
|
||||
#include "scr_map.h"
|
||||
|
||||
/* ── 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;
|
||||
|
||||
/* A box can lie on a cycle only when its trace visits its payload: a
|
||||
* closure, or a class instance that carries a cycle header. Every other box
|
||||
* is a leaf of the object graph, so its releases buffer no candidate. The
|
||||
* emitted inline release (llvm/shapes.ts, the box row) mirrors this test,
|
||||
* reading `kind` and `obj_trace` at the asserted offsets. */
|
||||
static inline bool scr_box_traced(const ScrBox *b) {
|
||||
return b->kind == SCR_BOX_FUNC || b->obj_trace != NULL;
|
||||
}
|
||||
_Static_assert(SCR_BOX_FUNC == 4, "the emitted box release compares kind against 4");
|
||||
#if UINTPTR_MAX == UINT64_MAX
|
||||
_Static_assert(offsetof(ScrBox, kind) == 8 && offsetof(ScrBox, obj_trace) == 32,
|
||||
"the emitted box release reads kind at 8 and obj_trace at 32");
|
||||
#else
|
||||
_Static_assert(offsetof(ScrBox, kind) == 4 && offsetof(ScrBox, obj_trace) == 16,
|
||||
"the emitted box release reads kind at 4 and obj_trace at 16");
|
||||
#endif
|
||||
|
||||
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++;
|
||||
}
|
||||
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;
|
||||
uint32_t function_kind; /* low bits: 0 ordinary, 1 generator, 2 async, 3 async generator; bit 2: ordinary own prototype; bit 3: checked callable adapter in caps[0] */
|
||||
/* The root's `length` as boxed values report it; meaningful only when
|
||||
* identity is set (the root's own boxes carry their arity). */
|
||||
uint32_t identity_length;
|
||||
/* The JS function this closure stands for. NULL means the closure is
|
||||
* its own identity. A signature adapter (the same function viewed
|
||||
* through a different native ABI) points at the root of the function
|
||||
* it adapts, so chains of adapters collapse to one root and `===`,
|
||||
* SameValue, searches and own properties all observe one function.
|
||||
* Borrowed: the adapter keeps its original alive through its captures. */
|
||||
struct ScrClosure *identity;
|
||||
/* The same function with every reference argument borrowed (the caller
|
||||
* keeps each one alive across the call and releases nothing extra), or
|
||||
* NULL. Same signature as `fn`; only callers that hold their arguments
|
||||
* for the whole call may enter through it. */
|
||||
void *fn_borrowed;
|
||||
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++;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
void scr_closure_release(ScrClosure *c); /* releases the boxes; NULL-tolerant */
|
||||
|
||||
/* The identity root of a function value (NULL-tolerant). */
|
||||
static inline ScrClosure *scr_closure_identity(ScrClosure *c) {
|
||||
return c != NULL && c->identity != NULL ? c->identity : c;
|
||||
}
|
||||
|
||||
/* JS function identity: two closures are the same function when their
|
||||
* identity roots match. */
|
||||
bool scr_closure_identity_equal(ScrClosure *a, ScrClosure *b);
|
||||
|
||||
/* Mark `adapter` as a signature view of `original` (both borrowed);
|
||||
* `length` is the original's arity when it is a root. */
|
||||
void scr_closure_adopt_identity(ScrClosure *adapter, ScrClosure *original, uint32_t length);
|
||||
|
||||
/* Mark `adapter` as a typed view of a dynamic function value (both
|
||||
* borrowed). Other callables keep the adapter as its own identity. */
|
||||
void scr_dyn_adopt_identity(ScrClosure *adapter, const struct ScrDyn *value);
|
||||
|
||||
#endif /* SCR_CLOSURE_H */
|
||||
@@ -0,0 +1,77 @@
|
||||
/* scriptc runtime — console (scr_console.c). Included by scr_runtime.h, which
|
||||
* says where new declarations go. */
|
||||
#ifndef SCR_CONSOLE_H
|
||||
#define SCR_CONSOLE_H
|
||||
|
||||
#include "scr_http2.h"
|
||||
|
||||
/* ── console ──────────────────────────────────────────────────────────── */
|
||||
|
||||
enum {
|
||||
SCR_ARG_F64, /* a number argument: inspect rendering (-0 prints "-0") */
|
||||
SCR_ARG_STR, /* a string, verbatim */
|
||||
SCR_ARG_BOOL, /* true / false */
|
||||
SCR_ARG_NUM, /* a number inside a string concatenation: String() rendering
|
||||
* (-0 prints "0") — scr_console_*_parts only */
|
||||
SCR_ARG_KIND = 0xff,
|
||||
SCR_ARG_GLUE = 0x100, /* flag: this part continues the previous argument
|
||||
* (no separating space) — scr_console_*_parts only */
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
int tag; /* SCR_ARG_KIND bits, optionally | SCR_ARG_GLUE */
|
||||
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);
|
||||
/* The same lines where the compiler passed a string-concatenation argument
|
||||
* (template literal, `"a" + n`) as its parts — glued with SCR_ARG_GLUE and
|
||||
* numbers as SCR_ARG_NUM — so the joined string is never materialized.
|
||||
* Separate entry points: programs using these tags cannot link against a
|
||||
* runtime that does not understand them. */
|
||||
void scr_console_log_parts(size_t n, const ScrLogArg *args);
|
||||
void scr_console_error_parts(size_t n, const ScrLogArg *args);
|
||||
/* A Console instance's line on fd 1 or 2, without the global console's
|
||||
* group indentation. */
|
||||
void scr_console_write_fd(int fd, size_t n, const ScrLogArg *args);
|
||||
/* The global console's line rewrite while a group is open (scr_console.c;
|
||||
* installed by scr_console_group): the indented heap copy of a rendered
|
||||
* line, its length in *used. */
|
||||
extern SCR_TL char *(*scr_console_indenter)(const char *line, size_t *used);
|
||||
|
||||
/* The global console's remaining members (scr_console_native.c). Arguments
|
||||
* are borrowed; string results are +1. */
|
||||
ScrStr *scr_console_label(ScrDyn *label);
|
||||
void scr_console_count(ScrStr *label);
|
||||
ScrStr *scr_console_count_reset(ScrDyn *label);
|
||||
ScrStr *scr_console_time(ScrStr *label);
|
||||
ScrStr *scr_console_time_end(ScrStr *label);
|
||||
ScrStr *scr_console_time_log(ScrStr *label);
|
||||
void scr_console_group(double delta);
|
||||
void scr_console_clear(void);
|
||||
void scr_console_table(ScrDyn *data, ScrDyn *properties);
|
||||
ScrStr *scr_console_dir_dyn(ScrDyn *value, double depth);
|
||||
/* console.table's cell rendering (scr_inspect.c): Node's _inspect — depth
|
||||
* 0 (-1 for objects with more than two keys), maxArrayLength 3, breakLength
|
||||
* Infinity. +1. */
|
||||
ScrStr *scr_insp_table_cell(ScrDyn *value);
|
||||
/* Node's getStringWidth over UTF-8 text (the non-ICU tables). */
|
||||
size_t scr_insp_display_width(const char *data, size_t len);
|
||||
/* process.emitWarning(message) with the default Warning type, emitted on
|
||||
* the next tick like Node's; an empty message emits nothing
|
||||
* (scr_async_dyn.c). Borrows. */
|
||||
void scr_process_emit_warning_text(ScrStr *message);
|
||||
|
||||
bool scr_fetch_web_is(const ScrDyn *value, const ScrStr *name);
|
||||
bool scr_fetch_stream_is(const ScrDyn *value);
|
||||
|
||||
#endif /* SCR_CONSOLE_H */
|
||||
@@ -0,0 +1,399 @@
|
||||
/* scriptc runtime — libc shims, runtime instance state, process entry, the
|
||||
* trap funnel, library mode and host-callback channels (scr_console.c,
|
||||
* scr_library.c, scr_context.c). Included by scr_runtime.h, which says where
|
||||
* new declarations go. */
|
||||
#ifndef SCR_CORE_H
|
||||
#define SCR_CORE_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
|
||||
|
||||
/* ── runtime instance 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. Worker executables use the same isolation for their
|
||||
* collectors, caches and scheduler state. Expands to nothing for ordinary
|
||||
* executables and classic libraries. Immutable tables stay shared. */
|
||||
#if (defined(SCR_LIB) && defined(SCR_THREAD_INSTANCES)) || defined(SCR_WORKERS)
|
||||
#define SCR_TL _Thread_local
|
||||
#else
|
||||
#define SCR_TL
|
||||
#endif
|
||||
|
||||
/* Entry points that only run when a JS exception is being raised. noinline
|
||||
* keeps the throw machinery out of every caller: the LLVM helper never
|
||||
* imports noinline runtime bodies into program code, so a compiled function
|
||||
* whose only slow path throws stays small enough to inline and needs no
|
||||
* callee-saved registers on its fast path. cold tells the C compiler that
|
||||
* runtime paths reaching them are unlikely. */
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#define SCR_COLD __attribute__((cold, noinline))
|
||||
#else
|
||||
#define SCR_COLD
|
||||
#endif
|
||||
|
||||
/* Digest compression functions start on a cache line. The release
|
||||
* runtime keeps the compiler's default function alignment, so without it
|
||||
* their speed moves with the size of unrelated code before them. WASM
|
||||
* code has no addresses to align. */
|
||||
#if (defined(__GNUC__) || defined(__clang__)) && !defined(__wasm__)
|
||||
#define SCR_DIGEST_ALIGNED __attribute__((aligned(64)))
|
||||
#else
|
||||
#define SCR_DIGEST_ALIGNED
|
||||
#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_v20(void);
|
||||
void scr_runtime_workers_v8(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);
|
||||
|
||||
/* glibc added arc4random_buf in 2.36. Older GNU targets use the same kernel
|
||||
* CSPRNG as the musl shim, with no global state or persistent descriptor. */
|
||||
#ifdef __GLIBC__
|
||||
#if !__GLIBC_PREREQ(2, 36)
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/random.h>
|
||||
static inline void scr_glibc_random_buf(void *buf, size_t n) {
|
||||
unsigned char *p = buf;
|
||||
while (n > 0) {
|
||||
ssize_t got = getrandom(p, n, 0);
|
||||
if (got > 0) {
|
||||
p += (size_t)got;
|
||||
n -= (size_t)got;
|
||||
} else if (got < 0 && errno == EINTR) {
|
||||
continue;
|
||||
} else {
|
||||
scr_trap("scriptc: getrandom failed\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
#define arc4random_buf scr_glibc_random_buf
|
||||
#endif
|
||||
#endif
|
||||
_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 unchanged. */
|
||||
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 unchanged — 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 — rounding
|
||||
* without a diagnostic 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)
|
||||
#elif defined(SCR_WORKERS)
|
||||
/* Executable workers drain their own cleanup stack before their TLS dies.
|
||||
* Registering their callbacks with libc would execute them on the process
|
||||
* exit thread against a different runtime instance. */
|
||||
int scr_context_atexit(void (*fn)(void));
|
||||
void scr_context_cleanup(void);
|
||||
void scr_context_enter(uint64_t thread_id);
|
||||
uint64_t scr_context_thread_id(void);
|
||||
void scr_context_stop_flag(const void *flag);
|
||||
void scr_context_stop(int code);
|
||||
bool scr_context_stopping(void);
|
||||
bool scr_context_checkpoint(void);
|
||||
bool scr_context_quiet(void);
|
||||
#if !defined(__APPLE__) && !defined(_WIN32) && !defined(__CYGWIN__) && !defined(__wasi__)
|
||||
/* ELF worker executables (the LLVM emitter's executableTls targets): every
|
||||
* script thread owns an exact pending word in the executable's static TLS
|
||||
* block, which emitted pending checks load with one thread-pointer-relative
|
||||
* access and no out-of-line slow path (scr_exception.c). */
|
||||
#define SCR_EXC_FLAG 1
|
||||
#define SCR_EXC_FLAG_PENDING 1u /* the active cell's kind is not NONE */
|
||||
#define SCR_EXC_FLAG_STOP 2u /* the context must stop (never cleared) */
|
||||
extern SCR_TL _Atomic uint32_t scr_exc_flag;
|
||||
/* Requests the stop of the thread that owns `flag` (scr_worker.c holds the
|
||||
* owner's word only while its script runs). */
|
||||
void scr_exc_flag_request_stop(_Atomic uint32_t *flag);
|
||||
#else
|
||||
/* The process-wide exception alert that emitted pending checks of other
|
||||
* worker executables test inline (scr_exception.c): nonzero while some
|
||||
* script thread may have an exception pending in its active cell or must
|
||||
* stop. */
|
||||
extern _Atomic uint32_t scr_exc_alert;
|
||||
void scr_exc_alert_add(int delta); /* worker.terminate() references */
|
||||
#endif
|
||||
void scr_exc_alert_thread_exit(void);
|
||||
void scr_loop_context_shutdown(void);
|
||||
extern SCR_TL void (*scr_context_report_error)(void);
|
||||
typedef struct ScrContextEnv ScrContextEnv;
|
||||
ScrContextEnv *scr_context_env_capture(void);
|
||||
void scr_context_env_free(ScrContextEnv *environment);
|
||||
void scr_context_env_enter(ScrContextEnv *environment);
|
||||
bool scr_context_env_set(const char *name, const char *value);
|
||||
const char *scr_context_getenv(const char *name);
|
||||
struct ScrArr *scr_context_env_pairs(void);
|
||||
#define scr_atexit(fn) scr_context_atexit(fn)
|
||||
#else
|
||||
#define scr_atexit(fn) atexit(fn)
|
||||
#endif /* SCR_LIB */
|
||||
|
||||
/* Also used by ordinary event-loop builds, where this always returns true. */
|
||||
bool scr_context_is_main(void);
|
||||
|
||||
/* Native stack guard. Compiled functions that can recurse compare their
|
||||
* stack pointer against scr_stack_guard on entry and throw Node's catchable
|
||||
* RangeError below it. The guard sits a reserve above the lowest usable
|
||||
* address of the running stack (thread or async fiber), leaving room for the
|
||||
* throw, unwinding, finally blocks, catch handlers and the uncaught-error
|
||||
* report. Zero (the default) disables the check on that stack. */
|
||||
extern SCR_TL uintptr_t scr_stack_guard;
|
||||
void scr_stack_guard_init(void);
|
||||
SCR_COLD void scr_stack_overflow(void);
|
||||
#if defined(SCR_WORKERS) && !defined(SCR_LIB) && defined(__APPLE__) && \
|
||||
(defined(__aarch64__) || defined(__x86_64__))
|
||||
/* Darwin worker executables: reading a thread-local variable calls its TLV
|
||||
* thunk, so the emitted entry checks read the running stack's guard from a
|
||||
* pthread thread-specific-data slot instead, addressed from the thread
|
||||
* pointer (TPIDRRO_EL0 on arm64, %gs on x86-64) plus this byte offset.
|
||||
* scr_stack_guard_init validates the slot on first use; zero means the
|
||||
* direct read is unavailable and the checks call scr_stack_guard_get. */
|
||||
#define SCR_STACK_GUARD_TSD 1
|
||||
extern uintptr_t scr_stack_guard_tsd;
|
||||
uintptr_t scr_stack_guard_get(void);
|
||||
void scr_stack_guard_set(uintptr_t guard);
|
||||
#define SCR_STACK_GUARD_SET(guard) scr_stack_guard_set(guard)
|
||||
#else
|
||||
#define SCR_STACK_GUARD_SET(guard) (scr_stack_guard = (guard))
|
||||
#endif
|
||||
#if defined(__has_feature)
|
||||
#if __has_feature(address_sanitizer)
|
||||
#define SCR_STACK_RESERVE_MAX ((size_t)2 << 20)
|
||||
#endif
|
||||
#endif
|
||||
#ifndef SCR_STACK_RESERVE_MAX
|
||||
#define SCR_STACK_RESERVE_MAX ((size_t)256 << 10)
|
||||
#endif
|
||||
static inline uintptr_t scr_stack_guard_for(uintptr_t low, size_t size) {
|
||||
size_t reserve = size / 4;
|
||||
if (reserve > SCR_STACK_RESERVE_MAX) reserve = SCR_STACK_RESERVE_MAX;
|
||||
return low + reserve;
|
||||
}
|
||||
double scr_context_thread_number(void);
|
||||
double scr_worker_root(void);
|
||||
int scr_worker_argc(void);
|
||||
char **scr_worker_argv(void);
|
||||
|
||||
#ifdef SCR_WORKERS
|
||||
#define scr_getenv(name) scr_context_getenv(name)
|
||||
#else
|
||||
#define scr_getenv(name) getenv(name)
|
||||
#endif
|
||||
|
||||
#endif /* SCR_CORE_H */
|
||||
@@ -0,0 +1,165 @@
|
||||
/* scriptc runtime — node:crypto (scr_lib.c, scr_crypto_async.c). Included by
|
||||
* scr_runtime.h, which says where new declarations go. */
|
||||
#ifndef SCR_CRYPTO_H
|
||||
#define SCR_CRYPTO_H
|
||||
|
||||
#include "scr_lib.h"
|
||||
|
||||
/* ── 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);
|
||||
ScrDyn *scr_crypto_native(void);
|
||||
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);
|
||||
/* The option and any-value forms of createHash, copy, update, digest and
|
||||
* crypto.hash, with Node 24.22's validation and errors. */
|
||||
ScrCryptoHash *scr_crypto_hash_new_opts(ScrStr *alg, ScrDyn *options);
|
||||
ScrCryptoHash *scr_crypto_hash_copy_opts(ScrCryptoHash *h, ScrDyn *options);
|
||||
ScrCryptoHash *scr_crypto_hmac_new_dyn(ScrStr *alg, ScrDyn *key, ScrDyn *options);
|
||||
ScrCryptoHash *scr_crypto_hash_update_enc(ScrCryptoHash *h, ScrStr *data, ScrStr *enc);
|
||||
ScrCryptoHash *scr_crypto_hash_update_dyn(ScrCryptoHash *h, ScrDyn *data, ScrDyn *enc);
|
||||
ScrDyn *scr_crypto_hash_digest_dyn(ScrCryptoHash *h, ScrDyn *enc);
|
||||
ScrDyn *scr_crypto_hash_oneshot(ScrStr *alg, ScrDyn *input, ScrDyn *options);
|
||||
bool scr_crypto_timing_safe_equal(ScrBytes *left, ScrBytes *right);
|
||||
bool scr_crypto_timing_safe_equal_dyn(ScrDyn *left, ScrDyn *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);
|
||||
double scr_crypto_random_int_max(double max);
|
||||
ScrDyn *scr_crypto_random_fill_sync_dyn(ScrDyn *buf, ScrDyn *offset, ScrDyn *size);
|
||||
double scr_crypto_random_fill_args(ScrBytes *view, bool typed_array, double offset, double size,
|
||||
int form);
|
||||
void scr_crypto_random_int_refill(void);
|
||||
bool scr_crypto_random_int_check(double min, double max, bool min_given, double *range);
|
||||
bool scr_crypto_random_int_try(double min, double range, double *out);
|
||||
ScrStr *scr_crypto_random_uuid_opts(ScrDyn *options);
|
||||
ScrStr *scr_crypto_random_uuid_v7(ScrDyn *options);
|
||||
/* Secret KeyObjects (scr_lib.c). */
|
||||
ScrDyn *scr_crypto_create_secret_key(ScrDyn *key, ScrDyn *encoding);
|
||||
ScrDyn *scr_crypto_generate_key_sync(ScrDyn *type, ScrDyn *options);
|
||||
ScrBytes *scr_crypto_key_object_input(ScrDyn *key);
|
||||
bool scr_crypto_is_key_object(const ScrDyn *value);
|
||||
const unsigned char *scr_key_object_bytes(const ScrDyn *value, size_t *len);
|
||||
ScrBytes *scr_crypto_pbkdf2(ScrBytes *password, ScrBytes *salt,
|
||||
double iterations, double keylen, ScrStr *digest);
|
||||
ScrBytes *scr_crypto_pbkdf2_job(ScrBytes *password, ScrBytes *salt, double iterations,
|
||||
double keylen, ScrStr *digest, const char **failure);
|
||||
ScrBytes *scr_crypto_hkdf_bytes(ScrStr *digest, ScrBytes *ikm, ScrBytes *salt,
|
||||
ScrBytes *info, double keylen);
|
||||
ScrDyn *scr_crypto_hkdf(ScrStr *digest, ScrBytes *ikm, ScrBytes *salt,
|
||||
ScrBytes *info, double keylen);
|
||||
ScrBytes *scr_crypto_scrypt(ScrBytes *password, ScrBytes *salt, double keylen, ScrDyn *options);
|
||||
ScrBytes *scr_crypto_scrypt_derive(ScrBytes *password, ScrBytes *salt, double keylen,
|
||||
double n, double r, double p, double maxmem);
|
||||
typedef void (*ScrCryptoBytesFn)(ScrClosure *cb, ScrError *error /* borrowed */, ScrBytes *value /* moves */);
|
||||
void scr_crypto_defer_bytes(ScrBytes *value /* moves */, ScrError *error /* moves */, ScrClosure *cb /* moves */,
|
||||
ScrCryptoBytesFn fn);
|
||||
void scr_crypto_defer_dyn(ScrDyn *value /* moves */, ScrError *error /* moves */, ScrClosure *cb /* moves */,
|
||||
ScrCryptoBytesFn fn);
|
||||
/* randomFill and randomInt with callbacks (scr_crypto_async.c). `form` is
|
||||
* randomFill's argument count before the callback. */
|
||||
void scr_crypto_random_fill_async(ScrBytes *buf, double offset, double size, double form,
|
||||
ScrClosure *cb, ScrCryptoBytesFn fn);
|
||||
void scr_crypto_random_fill_async_dyn(ScrDyn *buf, ScrDyn *offset, ScrDyn *size, double form,
|
||||
ScrClosure *cb, ScrCryptoBytesFn fn);
|
||||
void scr_crypto_generate_key_async(ScrDyn *type, ScrDyn *options, ScrClosure *cb,
|
||||
ScrCryptoBytesFn fn);
|
||||
typedef void (*ScrCryptoIntFn)(ScrClosure *cb, double value);
|
||||
void scr_crypto_random_int_async(double min, double max, double min_given, ScrClosure *cb,
|
||||
ScrCryptoIntFn 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);
|
||||
void scr_crypto_hkdf_async(ScrStr *digest, ScrBytes *ikm, ScrBytes *salt,
|
||||
ScrBytes *info, double keylen, ScrClosure *cb, ScrCryptoBytesFn fn);
|
||||
void scr_crypto_scrypt_async(ScrBytes *password, ScrBytes *salt, double keylen,
|
||||
ScrDyn *options, ScrClosure *cb, ScrCryptoBytesFn fn);
|
||||
|
||||
/* The digest primitives beyond MD5/SHA-1/SHA-2 (scr_hash.c). Each final
|
||||
* reads a copy of its context. */
|
||||
typedef struct ScrKeccakCtx {
|
||||
uint64_t s[25];
|
||||
unsigned char buf[200];
|
||||
size_t rate; /* bytes absorbed per permutation */
|
||||
size_t len; /* buffered bytes */
|
||||
unsigned char pad; /* domain bits: 0x06 SHA-3, 0x01 Keccak, 0x1f SHAKE, 0x04 KECCAK-KMAC */
|
||||
} ScrKeccakCtx;
|
||||
void scr_keccak_init(ScrKeccakCtx *ctx, size_t rate, unsigned char pad);
|
||||
void scr_keccak_update(ScrKeccakCtx *ctx, const unsigned char *data, size_t len);
|
||||
void scr_keccak_final(const ScrKeccakCtx *ctx, unsigned char *out, size_t len);
|
||||
typedef struct ScrRipemd160Ctx {
|
||||
uint32_t h[5];
|
||||
uint64_t bytes;
|
||||
unsigned char buf[64];
|
||||
size_t len;
|
||||
} ScrRipemd160Ctx;
|
||||
void scr_ripemd160_init(ScrRipemd160Ctx *ctx);
|
||||
void scr_ripemd160_update(ScrRipemd160Ctx *ctx, const unsigned char *data, size_t len);
|
||||
void scr_ripemd160_final(const ScrRipemd160Ctx *ctx, unsigned char out[20]);
|
||||
typedef struct ScrBlake2bCtx {
|
||||
uint64_t h[8];
|
||||
uint64_t t[2];
|
||||
unsigned char buf[128];
|
||||
size_t len;
|
||||
} ScrBlake2bCtx;
|
||||
void scr_blake2b_init(ScrBlake2bCtx *ctx);
|
||||
void scr_blake2b_update(ScrBlake2bCtx *ctx, const unsigned char *data, size_t len);
|
||||
void scr_blake2b_final(const ScrBlake2bCtx *ctx, unsigned char out[64]);
|
||||
typedef struct ScrBlake2sCtx {
|
||||
uint32_t h[8];
|
||||
uint32_t t[2];
|
||||
unsigned char buf[64];
|
||||
size_t len;
|
||||
} ScrBlake2sCtx;
|
||||
void scr_blake2s_init(ScrBlake2sCtx *ctx);
|
||||
void scr_blake2s_update(ScrBlake2sCtx *ctx, const unsigned char *data, size_t len);
|
||||
void scr_blake2s_final(const ScrBlake2sCtx *ctx, unsigned char out[32]);
|
||||
typedef struct ScrSm3Ctx {
|
||||
uint32_t h[8];
|
||||
uint64_t bytes;
|
||||
unsigned char buf[64];
|
||||
size_t len;
|
||||
} ScrSm3Ctx;
|
||||
void scr_sm3_init(ScrSm3Ctx *ctx);
|
||||
void scr_sm3_update(ScrSm3Ctx *ctx, const unsigned char *data, size_t len);
|
||||
void scr_sm3_final(const ScrSm3Ctx *ctx, unsigned char out[32]);
|
||||
|
||||
size_t scr_crypto_digest_raw(const char *alg, const unsigned char *data, size_t len,
|
||||
unsigned char out[64]);
|
||||
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[64]);
|
||||
/* 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);
|
||||
|
||||
#endif /* SCR_CRYPTO_H */
|
||||
@@ -0,0 +1,79 @@
|
||||
/* scriptc runtime — node:diagnostics_channel (scr_dc.c). Included by
|
||||
* scr_runtime.h, which says where new declarations go. */
|
||||
#ifndef SCR_DC_H
|
||||
#define SCR_DC_H
|
||||
|
||||
#include "scr_lib.h"
|
||||
|
||||
/* ── 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);
|
||||
|
||||
#endif /* SCR_DC_H */
|
||||
@@ -0,0 +1,72 @@
|
||||
/* scriptc runtime — node:dgram and node:dns (scr_dgram.c). Included by
|
||||
* scr_runtime.h, which says where new declarations go. */
|
||||
#ifndef SCR_DGRAM_H
|
||||
#define SCR_DGRAM_H
|
||||
|
||||
#include "scr_http2.h"
|
||||
|
||||
/* ── 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));
|
||||
|
||||
#endif /* SCR_DGRAM_H */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,266 @@
|
||||
/* scriptc runtime — class hierarchies, class objects, and the Error,
|
||||
* DOMException and AggregateError objects (scr_error.c, scr_object.c).
|
||||
* Included by scr_runtime.h, which says where new declarations go. */
|
||||
#ifndef SCR_ERROR_H
|
||||
#define SCR_ERROR_H
|
||||
|
||||
#include "scr_string.h"
|
||||
|
||||
/* ── 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;
|
||||
|
||||
/* ── 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 *prototype_data;
|
||||
struct ScrBox *static_data;
|
||||
struct ScrBox *caps[];
|
||||
} ScrClassObj;
|
||||
|
||||
static inline ScrClassObj *scr_classobj_retain(ScrClassObj *c) {
|
||||
if (c && c->rc != SIZE_MAX) {
|
||||
c->rc++;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
void scr_classobj_release(ScrClassObj *c);
|
||||
void scr_classobj_clear_properties(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);
|
||||
struct ScrDyn *scr_dyn_class_prototype(struct ScrDyn *value, struct ScrDyn *base);
|
||||
struct ScrDyn *scr_dyn_class_base_prototype(struct ScrDyn *constructor);
|
||||
struct ScrDyn *scr_dyn_class_inherit(struct ScrDyn *constructor, struct ScrDyn *base);
|
||||
void scr_dyn_class_super(struct ScrDyn *constructor, struct ScrDyn *receiver, struct ScrDyn *arguments);
|
||||
struct ScrDyn *scr_dyn_assign_prototype(struct ScrDyn *target, struct ScrDyn *sources, struct ScrDyn *protected_keys);
|
||||
struct ScrDyn *scr_dyn_bag_get(struct ScrDyn *bag, ScrStr *key, struct ScrDyn *receiver);
|
||||
struct ScrDyn *scr_dyn_reflect_get(struct ScrDyn *target, struct ScrDyn *key, struct ScrDyn *receiver);
|
||||
bool scr_dyn_reflect_set(struct ScrDyn *target, struct ScrDyn *key, struct ScrDyn *value, struct ScrDyn *receiver);
|
||||
bool scr_dyn_reflect_define(struct ScrDyn *receiver, struct ScrDyn *key, struct ScrDyn *value);
|
||||
bool scr_dyn_abstract_eq(const struct ScrDyn *left, const struct ScrDyn *right);
|
||||
int scr_dyn_function_kind(const struct ScrDyn *value);
|
||||
void scr_dyn_bag_set(struct ScrDyn *bag, ScrStr *key, struct ScrDyn *value, struct ScrDyn *receiver);
|
||||
/* 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);
|
||||
|
||||
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 */
|
||||
bool message_present; /* super() leaves the inherited message property visible */
|
||||
bool message_enumerable; /* assignment creates an enumerable own property */
|
||||
bool name_present;
|
||||
bool name_enumerable;
|
||||
ScrStr *stack_frames; /* captured native source frames */
|
||||
ScrStr *stack; /* lazily formatted, cached stack */
|
||||
double system_errno; /* present exactly when system_call is non-NULL */
|
||||
ScrStr *system_call;
|
||||
ScrStr *system_path;
|
||||
ScrStr *system_dest;
|
||||
} 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_AGGREGATE = 8, /* AggregateError — ScrAggregateError, the wider layout */
|
||||
SCR_ERR_ASSERTION = 9, /* node:assert's AssertionError — the Error layout */
|
||||
SCR_ERR_COUNT = 10,
|
||||
};
|
||||
|
||||
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;
|
||||
bool message_present;
|
||||
bool message_enumerable;
|
||||
bool name_present;
|
||||
bool name_enumerable;
|
||||
ScrStr *stack_frames;
|
||||
ScrStr *stack;
|
||||
double system_errno;
|
||||
ScrStr *system_call;
|
||||
ScrStr *system_path;
|
||||
ScrStr *system_dest;
|
||||
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. */
|
||||
SCR_COLD void scr_throw_domex(const char *name, const char *message);
|
||||
SCR_COLD void scr_throw_domex_str(const char *name, ScrStr *message);
|
||||
|
||||
/* AggregateError: the ScrError prefix plus the `errors` list (a dyn
|
||||
* array; NULL until set). Like DOMException's slots it is HIDDEN from
|
||||
* the compiler's IR field list (user `extends AggregateError` is fenced)
|
||||
* and read through scr_aggregate_errors. The dyn half (construction from
|
||||
* an iterable's elements, the list itself) lives in scr_json.c; the
|
||||
* teardown drops the list through the hook it installs. */
|
||||
typedef struct ScrAggregateError {
|
||||
ScrError base;
|
||||
struct ScrDyn *errors;
|
||||
} ScrAggregateError;
|
||||
ScrError *scr_aggregate_alloc(void);
|
||||
void scr_aggregate_install_errors_drop(void (*fn)(void *obj));
|
||||
/* new AggregateError(errors, message?, options?) runs in spec order:
|
||||
* scr_aggregate_new installs the message and cause (both borrowed, dyn
|
||||
* undefined when absent; +1, NULL with an exception pending when the
|
||||
* message conversion throws), then the caller iterates `errors` and
|
||||
* hands the list to scr_aggregate_set_errors. */
|
||||
ScrError *scr_aggregate_new(const struct ScrDyn *message, const struct ScrDyn *options);
|
||||
void scr_aggregate_set_errors(ScrError *e, struct ScrDyn *errors); /* both borrowed */
|
||||
struct ScrDyn *scr_aggregate_errors(ScrError *e); /* +1 */
|
||||
|
||||
/* 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. */
|
||||
SCR_COLD 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_define_cause(ScrError *e, struct ScrDyn *value);
|
||||
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);
|
||||
void scr_error_install_message_reader(ScrStr *(*reader)(ScrError *));
|
||||
void scr_error_install_name_reader(ScrStr *(*reader)(ScrError *));
|
||||
ScrStr *scr_error_default_name(const ScrError *e);
|
||||
ScrStr *scr_error_stack(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 takes ownership of both strings and picks the
|
||||
* builtin vtable whose class name matches `name` (Error otherwise). */
|
||||
SCR_COLD void scr_throw_error(int kind, ScrStr *message);
|
||||
SCR_COLD void scr_throw_error_msg(int kind, const char *message, size_t len);
|
||||
SCR_COLD 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. */
|
||||
SCR_COLD 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);
|
||||
SCR_COLD 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. */
|
||||
SCR_COLD void scr_throw_node_coded(double kind, const ScrStr *code, const ScrStr *msg);
|
||||
|
||||
#endif /* SCR_ERROR_H */
|
||||
@@ -0,0 +1,164 @@
|
||||
/* scriptc runtime — EventTarget, Event and CustomEvent (scr_event_target.c).
|
||||
* Included by scr_runtime.h, which says where new declarations go. */
|
||||
#ifndef SCR_EVENT_TARGET_H
|
||||
#define SCR_EVENT_TARGET_H
|
||||
|
||||
#include "scr_dyn.h"
|
||||
|
||||
/* ── EventTarget / Event / CustomEvent (scr_event_target.c) ───────────
|
||||
* Node's internal/event_target over checked-dynamic values. A target's
|
||||
* listeners live in a ScrEventTargetCore: per event type, a linked list
|
||||
* in registration order whose nodes keep their `next` link after removal,
|
||||
* so a dispatch that cached a removed node continues where Node's does.
|
||||
* Plain `new EventTarget()` values are SCR_DYNH_EVENT_TARGET handles;
|
||||
* AbortSignal embeds a core of its own (MaxListeners 0, the `onabort`
|
||||
* event-handler attribute). Events are ScrEvent handles (SCR_DYNH_EVENT,
|
||||
* SCR_DYNH_CUSTOM_EVENT for CustomEvent). Listener exceptions, and
|
||||
* rejections of a listener's returned promise or of a handleEvent call,
|
||||
* are reported as uncaught exceptions from a nextTick, like Node. */
|
||||
typedef struct ScrEtRoot ScrEtRoot;
|
||||
typedef struct ScrEtHandler ScrEtHandler;
|
||||
typedef struct ScrEtHook ScrEtHook;
|
||||
typedef struct ScrEventTargetCore {
|
||||
ScrEtRoot *roots; /* per event type, in first-add order */
|
||||
ScrEtHandler *handlers; /* event-handler attributes (`onabort`) */
|
||||
ScrEtHook *hooks; /* options.signal removal hooks aimed here (weak) */
|
||||
/* A compiled subclass instance's own capsule (+1), kept from its first
|
||||
* method call so every later `this` and event target is the same value.
|
||||
* The instance holds the core, so this is a cycle the collector reclaims
|
||||
* through the scr_event_target_state_* helpers. NULL otherwise. */
|
||||
ScrDyn *self;
|
||||
double max_listeners; /* 0 = no leak warning (AbortSignal) */
|
||||
bool warned;
|
||||
const char *cls; /* the warning's target name ("EventTarget") */
|
||||
bool tracked; /* in the exit teardown registry */
|
||||
struct ScrEventTargetCore *tracked_prev, *tracked_next;
|
||||
} ScrEventTargetCore;
|
||||
|
||||
typedef struct ScrEvent {
|
||||
size_t rc;
|
||||
ScrStr *type;
|
||||
ScrDyn *target; /* +1, NULL until dispatched */
|
||||
ScrDyn *detail; /* CustomEvent's detail (+1); NULL for Event */
|
||||
double time_stamp;
|
||||
bool custom, bubbles, cancelable, composed, trusted;
|
||||
bool default_prevented, propagation_stopped, stop, dispatching, in_passive;
|
||||
bool tracked;
|
||||
struct ScrEvent *tracked_prev, *tracked_next;
|
||||
} ScrEvent;
|
||||
|
||||
void scr_et_core_init(ScrEventTargetCore *core, double max_listeners, const char *cls);
|
||||
/* Drops every listener and handler (teardown and target destruction). */
|
||||
void scr_et_core_drop(ScrEventTargetCore *core);
|
||||
/* The EventTarget methods over a core: `self` is the target's own dyn
|
||||
* value (borrowed — `this` for listeners and the events' target), args
|
||||
* borrowed. +1 result, or NULL with the exception pending. */
|
||||
ScrDyn *scr_et_add_listener(ScrEventTargetCore *core, ScrDyn *const *args, size_t argc);
|
||||
ScrDyn *scr_et_remove_listener(ScrEventTargetCore *core, ScrDyn *const *args, size_t argc);
|
||||
ScrDyn *scr_et_dispatch_event(ScrEventTargetCore *core, ScrDyn *self, ScrDyn *const *args, size_t argc);
|
||||
/* Dispatches an event handle (borrowed) without the argument checks. */
|
||||
void scr_et_dispatch(ScrEventTargetCore *core, ScrDyn *self, ScrDyn *event);
|
||||
/* `on<type>` attributes (Node's defineEventHandler): get answers +1 (null
|
||||
* when unset); set registers the wrapper listener on first assignment. */
|
||||
ScrDyn *scr_et_handler_get(ScrEventTargetCore *core, const char *type);
|
||||
void scr_et_handler_set(ScrEventTargetCore *core, const char *type, const ScrDyn *value);
|
||||
/* A trusted (isTrusted) Event handle of the given type, +1. */
|
||||
ScrDyn *scr_event_new_trusted(const char *type);
|
||||
/* new EventTarget() / new Event(type, options) / new CustomEvent(type,
|
||||
* options): `args` is the argument vector as one dyn array (borrowed).
|
||||
* +1 handle, or NULL with the exception pending. */
|
||||
ScrDyn *scr_event_target_new(void);
|
||||
ScrDyn *scr_event_new(ScrDyn *args);
|
||||
ScrDyn *scr_custom_event_new(ScrDyn *args);
|
||||
/* `v instanceof Event` (kind 0), `CustomEvent` (1), `EventTarget` (2). */
|
||||
bool scr_event_is(const ScrDyn *v, double kind);
|
||||
/* The AbortSignal unit's view of its handles for options.signal: the
|
||||
* signal's core and aborted flag, or NULL for a non-signal value.
|
||||
* Installed by the AbortSignal unit; NULL when no signal can exist. */
|
||||
extern SCR_TL ScrEventTargetCore *(*scr_et_signal_core_fn)(const ScrDyn *d, bool *aborted);
|
||||
/* The compiled `listener.handleEvent` read (dyn → dyn; the runtime keeps a
|
||||
* reference), installed by %main when a program class declares
|
||||
* handleEvent: dispatch reads a capsule listener's handleEvent through it,
|
||||
* which reaches the class's compiled methods. Without it a capsule's own
|
||||
* view answers. */
|
||||
void scr_event_handle_event_reader(ScrDyn *reader);
|
||||
/* events.addAbortListener(signal, listener): +1 disposable, or NULL. */
|
||||
ScrDyn *scr_events_add_abort_listener(ScrDyn *signal, ScrDyn *listener);
|
||||
|
||||
/* Compiled subclasses (`class Bus extends EventTarget`, `class Ping
|
||||
* extends Event`): an instance lays out like a compiler-emitted hierarchy
|
||||
* class (rc, vt, then this prefix, then the subclass fields — the
|
||||
* ScrEmitter arrangement). super() creates the engine state behind the
|
||||
* prefix pointer; `cls` is the concrete class's name, stamped by the
|
||||
* emitted allocation (the leak warning and util.inspect print it). The
|
||||
* instances' checked-dynamic capsules carry the RC adapters below, which
|
||||
* is how the engine recognizes them: a target's capsule is the `this` of
|
||||
* its listeners and the events' target, and an event's capsule is what
|
||||
* its listeners receive. */
|
||||
typedef struct ScrEventTargetObj {
|
||||
size_t rc;
|
||||
const ScrVt *vt;
|
||||
ScrEventTargetCore *core; /* NULL until super() */
|
||||
const char *cls;
|
||||
} ScrEventTargetObj;
|
||||
|
||||
typedef struct ScrEventObj {
|
||||
size_t rc;
|
||||
const ScrVt *vt;
|
||||
ScrEvent *ev; /* NULL until super() */
|
||||
const char *cls;
|
||||
} ScrEventObj;
|
||||
|
||||
void *scr_event_target_obj_retain_v(void *o);
|
||||
void scr_event_target_obj_release_v(void *o); /* dispatches through vt */
|
||||
void *scr_event_obj_retain_v(void *o);
|
||||
void scr_event_obj_release_v(void *o);
|
||||
void *scr_event_target_obj_retain(void *o);
|
||||
void scr_event_target_obj_release(void *o);
|
||||
void *scr_event_obj_retain(void *o);
|
||||
void scr_event_obj_release(void *o);
|
||||
/* super() / super(type, options): `args` is the argument vector as one
|
||||
* dyn array (borrowed); a bad type or options value leaves Node's
|
||||
* exception pending. */
|
||||
void scr_event_target_obj_init(void *obj);
|
||||
void scr_event_obj_init(void *obj, ScrDyn *args);
|
||||
void scr_event_obj_init_type(void *obj, ScrStr *type); /* super(type), type borrowed */
|
||||
/* The emitted teardown's and collector's prefix helpers (NULL-tolerant):
|
||||
* trace visits the listener values, `on<type>` handlers and the cached
|
||||
* capsule of a target, and the target and detail of an event; gcfree
|
||||
* frees the state without releasing those traced edges. */
|
||||
void scr_event_target_state_drop(ScrEventTargetCore *core);
|
||||
void scr_event_target_state_trace(ScrEventTargetCore *core, ScrTraceVisit visit, void *ctx);
|
||||
void scr_event_target_state_gcfree(ScrEventTargetCore *core);
|
||||
void scr_event_state_drop(ScrEvent *ev);
|
||||
void scr_event_state_trace(ScrEvent *ev, ScrTraceVisit visit, void *ctx);
|
||||
void scr_event_state_gcfree(ScrEvent *ev);
|
||||
/* The inherited members on typed receivers: `method`/`key` name an
|
||||
* EventTarget or Event member, `args` is the argument vector as one dyn
|
||||
* array, `self` the target's own capsule (borrowed). +1 results, or NULL
|
||||
* with the exception pending. */
|
||||
ScrDyn *scr_event_target_obj_call(void *obj, ScrDyn *self, ScrStr *method, ScrDyn *args);
|
||||
/* dispatchEvent(event): the bool result, false with the exception pending. */
|
||||
bool scr_event_target_obj_dispatch(void *obj, ScrDyn *self, ScrDyn *event);
|
||||
ScrDyn *scr_event_obj_call(void *obj, ScrStr *method, ScrDyn *args);
|
||||
ScrDyn *scr_event_obj_get(void *obj, ScrStr *key);
|
||||
void scr_event_obj_set(void *obj, ScrStr *key, ScrDyn *value);
|
||||
|
||||
/* The same members through a capsule (property reads and writes, method
|
||||
* calls, util.inspect, string conversion), installed by the EventTarget
|
||||
* unit with its first value and NULL before. get answers +1 or NULL (not
|
||||
* an inherited member); set and invoke answer whether they handled the
|
||||
* member. */
|
||||
typedef struct ScrEventRefHooks {
|
||||
ScrDyn *(*get)(const ScrDyn *d, const ScrStr *key);
|
||||
bool (*set)(ScrDyn *d, const ScrStr *key, const ScrDyn *value);
|
||||
bool (*invoke)(ScrDyn *d, const char *method, ScrDyn *const *args, size_t argc,
|
||||
const char *what, ScrDyn **out);
|
||||
ScrDyn *(*inspect_view)(const ScrDyn *d, const char **cls);
|
||||
/* Object.prototype.toString's answer with the inherited @@toStringTag
|
||||
* ("[object EventTarget]", "[object Event]"), or NULL. */
|
||||
const char *(*string_tag)(const ScrDyn *d);
|
||||
} ScrEventRefHooks;
|
||||
extern SCR_TL const ScrEventRefHooks *scr_event_ref_hooks;
|
||||
|
||||
#endif /* SCR_EVENT_TARGET_H */
|
||||
@@ -0,0 +1,56 @@
|
||||
/* scriptc runtime — the events unit: signals, exit listeners and stdin
|
||||
* (scr_events.c). Included by scr_runtime.h, which says where new
|
||||
* declarations go. */
|
||||
#ifndef SCR_EVENTS_H
|
||||
#define SCR_EVENTS_H
|
||||
|
||||
#include "scr_child.h"
|
||||
#include "scr_async.h"
|
||||
|
||||
/* ── 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);
|
||||
|
||||
#endif /* SCR_EVENTS_H */
|
||||
@@ -0,0 +1,149 @@
|
||||
/* scriptc runtime — node:events EventEmitter (scr_events_emitter.c). Included
|
||||
* by scr_runtime.h, which says where new declarations go. */
|
||||
#ifndef SCR_EVENTS_EMITTER_H
|
||||
#define SCR_EVENTS_EMITTER_H
|
||||
|
||||
#include "scr_closure.h"
|
||||
|
||||
/* ── 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 */
|
||||
/* Internal owner hook; observes listener presence without running script. */
|
||||
void scr_emitter_observe(ScrEmitter *em,
|
||||
void (*observe)(void *, const ScrStr *, bool), void *context);
|
||||
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.
|
||||
* The fixed shims cover the common short tuples. Longer adapters use the
|
||||
* opaque cursor interface below without depending on the va_list ABI. */
|
||||
#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);
|
||||
/* Invoke cb->fn(cb, cursor). The cursor borrows a private va_list copy for
|
||||
* this call; each next operation reads one borrowed pointer argument. */
|
||||
void scr_ee_inv_args(ScrClosure *cb, va_list ap);
|
||||
void *scr_ee_arg_next(void *cursor);
|
||||
/* 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_flex(ScrEmitter *em, ScrDyn *error);
|
||||
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[] */
|
||||
/* events.errorMonitor's event key (immortal). Every name parameter above
|
||||
* accepts it; the registry matches it by identity, so no string name,
|
||||
* whatever its bytes, reaches the errorMonitor listeners. */
|
||||
ScrStr *scr_emitter_error_monitor(void);
|
||||
/* 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 SCR_TL void (*scr_emitter_on_hook)(ScrEmitter *em, ScrStr *name);
|
||||
|
||||
#endif /* SCR_EVENTS_EMITTER_H */
|
||||
@@ -0,0 +1,199 @@
|
||||
/* scriptc runtime — exceptions and catch bindings (scr_exception.c). Included
|
||||
* by scr_runtime.h, which says where new declarations go. */
|
||||
#ifndef SCR_EXCEPTION_H
|
||||
#define SCR_EXCEPTION_H
|
||||
|
||||
#include "scr_stream.h"
|
||||
|
||||
/* ── 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,
|
||||
/* Noncatchable context shutdown. Owns no payload; generated unwinding
|
||||
* releases locals while bypassing user catch and finally handlers. */
|
||||
SCR_EXC_TERMINATE,
|
||||
} 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 ScrStackFrame {
|
||||
struct ScrStackFrame *previous;
|
||||
const char *text;
|
||||
} ScrStackFrame;
|
||||
|
||||
void scr_stack_enter(ScrStackFrame *frame, const char *text);
|
||||
void scr_stack_leave(ScrStackFrame *frame);
|
||||
ScrStr *scr_stack_capture(void); /* +1, NULL when no source frames exist */
|
||||
double scr_stack_limit_get(void);
|
||||
void scr_stack_limit_set(double limit);
|
||||
|
||||
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;
|
||||
ScrStackFrame *stack; /* execution state; never moves with an exception */
|
||||
} ScrExcCell;
|
||||
|
||||
/* Runtime-internal (fiber machinery in scr_async.c) — never emitted. */
|
||||
ScrExcCell *scr_exc_swap_cell(ScrExcCell *cell); /* NULL = main's cell */
|
||||
/* The active cell, read inline by generated exception polls (thread-local
|
||||
* builds use NULL for the main cell until scr_init installs it). */
|
||||
extern SCR_TL ScrExcCell *scr_exc_active;
|
||||
ScrExcCell *scr_exc_current_cell(void); /* the ACTIVE cell */
|
||||
/* Runtime code that moved a payload into or out of the active cell (or a
|
||||
* cell that may be active) without the throw/take/clear entry points. */
|
||||
void scr_exc_cell_changed(void);
|
||||
|
||||
bool scr_exc_pending(void);
|
||||
|
||||
#if !((defined(SCR_LIB) && defined(SCR_THREAD_INSTANCES)) || defined(SCR_WORKERS))
|
||||
/* Runtime units test the active cell's kind inline, as emitted exception
|
||||
* polls do (scr_exc_active is never NULL outside thread-instanced libraries
|
||||
* and worker executables, which also poll cross-thread termination in
|
||||
* scr_exc_pending). The out-of-line definition in scr_exception.c stays
|
||||
* for those lanes and for emitted code's slow paths. */
|
||||
static inline bool scr_exc_pending_inline(void) { return scr_exc_active->kind != SCR_EXC_NONE; }
|
||||
#define scr_exc_pending() scr_exc_pending_inline()
|
||||
#endif
|
||||
_Static_assert(offsetof(ScrExcCell, kind) == 0 && sizeof(ScrExcKind) == 4 && SCR_EXC_NONE == 0,
|
||||
"the LLVM emitter's inline pending check loads an i32 kind at offset 0");
|
||||
|
||||
SCR_COLD void scr_throw_f64(double v);
|
||||
SCR_COLD void scr_throw_bool(bool v);
|
||||
SCR_COLD 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. */
|
||||
SCR_COLD 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. */
|
||||
SCR_COLD void scr_throw_ref_classified(void *v, void *(*retain)(void *), void (*release)(void *),
|
||||
ScrTraceFn trace, bool object);
|
||||
SCR_COLD 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. */
|
||||
SCR_COLD 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
|
||||
* participates in cycle collection because a closure may capture the
|
||||
* binding and its payload may retain that closure. */
|
||||
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 */
|
||||
void scr_caught_trace_v(void *c, ScrTraceVisit visit, void *ctx);
|
||||
/* 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 SCR_TL 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);
|
||||
|
||||
#endif /* SCR_EXCEPTION_H */
|
||||
@@ -0,0 +1,103 @@
|
||||
/* scriptc runtime — outbound FFI retained callbacks (scr_ffi.c). Included by
|
||||
* scr_runtime.h, which says where new declarations go. */
|
||||
#ifndef SCR_FFI_H
|
||||
#define SCR_FFI_H
|
||||
|
||||
#include "scr_closure.h"
|
||||
|
||||
/* ── 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));
|
||||
|
||||
#endif /* SCR_FFI_H */
|
||||
@@ -0,0 +1,62 @@
|
||||
/* scriptc runtime — file descriptors and fs.promises FileHandle
|
||||
* (scr_file_handle.c). Included by scr_runtime.h, which says where new
|
||||
* declarations go. */
|
||||
#ifndef SCR_FILE_HANDLE_H
|
||||
#define SCR_FILE_HANDLE_H
|
||||
|
||||
#include "scr_lib.h"
|
||||
|
||||
/* 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);
|
||||
/* Engine callers supply the target OS's flags, rather than the frontend's
|
||||
* portable constant mask. Argument validation happens before this seam. */
|
||||
double scr_fs_open_native(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);
|
||||
ScrPromise *scr_file_handle_sync_promise(ScrFileHandle *h);
|
||||
ScrPromise *scr_file_handle_datasync_promise(ScrFileHandle *h);
|
||||
ScrPromise *scr_file_handle_truncate_promise(ScrFileHandle *h, double length);
|
||||
ScrPromise *scr_file_handle_chmod_promise(ScrFileHandle *h, double mode);
|
||||
double scr_file_handle_readv(ScrFileHandle *h, ScrArr *buffers, double position);
|
||||
double scr_file_handle_writev(ScrFileHandle *h, ScrArr *buffers, double position);
|
||||
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);
|
||||
double scr_fs_readv_sync(double fd, ScrArr *buffers, double position);
|
||||
double scr_fs_writev_sync(double fd, ScrArr *buffers, double position);
|
||||
/* Unlike scalar readSync's zero-length fast path, a nonempty vector of
|
||||
* empty views still tests the descriptor and positioned-read capability. */
|
||||
double scr_fs_read_zero(double fd, double position);
|
||||
void scr_fs_close(double fd);
|
||||
|
||||
#endif /* SCR_FILE_HANDLE_H */
|
||||
@@ -0,0 +1,184 @@
|
||||
/* scriptc runtime — sync and async generators (scr_async.c). Included by
|
||||
* scr_runtime.h, which says where new declarations go. */
|
||||
#ifndef SCR_GENERATOR_H
|
||||
#define SCR_GENERATOR_H
|
||||
|
||||
#include "scr_events.h"
|
||||
|
||||
/* ── 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 *));
|
||||
/* A synchronous generator whose body the compiler emitted as an LLVM
|
||||
* switched coroutine: no fiber stack; resumes call into the coroutine frame
|
||||
* on the resumer's stack. The body registers its frame with
|
||||
* scr_coro_started and ends in scr_gen_coro_finish (the trampoline
|
||||
* epilogue of the fiber form). Native targets only. */
|
||||
ScrGen *scr_gen_new_stackless(void (*entry)(ScrFiber *, void *), void *argpack,
|
||||
void (*drop_args)(void *));
|
||||
void scr_coro_started(void *handle);
|
||||
/* The OUT stores of a coroutine body's yield (its suspension follows). */
|
||||
void scr_coro_yield_f64(ScrFiber *self, double v);
|
||||
void scr_coro_yield_bool(ScrFiber *self, bool v);
|
||||
void scr_coro_yield_ref(ScrFiber *self, void *v, void (*release)(void *)); /* moves */
|
||||
void scr_gen_coro_finish(ScrFiber *self);
|
||||
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);
|
||||
/* A valueless next() that answers done (the compiler's own drives). */
|
||||
bool scr_gen_step(ScrGen *g);
|
||||
/* IteratorClose: a valueless return() whose result is dropped. */
|
||||
void scr_gen_close(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 */
|
||||
ScrDyn *scr_gen_take_in_dyn(void); /* dyn channel: +1; NONE → undefined */
|
||||
/* yield* captures next/return/throw before ordinary yield unwinding. Ref
|
||||
* converters borrow their typed payload and return an owned dyn value. */
|
||||
ScrDyn *scr_gen_delegate_resume(ScrDyn *(*next_ref)(void *),
|
||||
ScrDyn *(*return_ref)(void *),
|
||||
ScrDyn *(*caught_ref)(const ScrCaught *));
|
||||
|
||||
/* yield* over a typed generator delegate. start runs the first valueless
|
||||
* next(); flag 1 marks a typed next channel, which a suspended delegate
|
||||
* cannot take undefined into (the SC1071 fence), and flag 2 a completion
|
||||
* read as a return type that cannot hold undefined. forward runs right
|
||||
* after the delegating body's yield resumes and passes the consumer's
|
||||
* next(v), return(v), or throw(e) to the delegate; next is its form for a
|
||||
* resume with nothing pending. `modes` packs the next, return, and
|
||||
* completion channel transfers (two bits each) and the typed-completion
|
||||
* flag (64; 4 for next); the converters box a borrowed ref payload into
|
||||
* an owned dyn. All answer whether the delegate is done. */
|
||||
bool scr_gen_delegate_start(ScrGen *d, unsigned flags);
|
||||
bool scr_gen_delegate_next(ScrGen *d, unsigned flags, ScrDyn *(*to_dyn)(void *));
|
||||
bool scr_gen_delegate_forward(ScrGen *d, unsigned modes, ScrDyn *(*next_dyn)(void *),
|
||||
ScrDyn *(*ret_dyn)(void *), ScrDyn *(*done_dyn)(void *));
|
||||
|
||||
/* 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
|
||||
|
||||
void scr_fetch_install(void);
|
||||
ScrPromise *scr_fetch_input(ScrDyn *input, ScrDyn *init);
|
||||
ScrDyn *scr_fetch_function(void);
|
||||
void scr_global_fetch_install(ScrDyn *(*get)(void));
|
||||
ScrDyn *scr_fetch_request_new(ScrDyn *input, ScrDyn *init);
|
||||
ScrDyn *scr_fetch_headers_new(ScrDyn *init);
|
||||
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 */
|
||||
|
||||
#endif /* SCR_GENERATOR_H */
|
||||
@@ -0,0 +1,349 @@
|
||||
/* scriptc runtime — node:http (scr_http.c). Included by scr_runtime.h, which
|
||||
* says where new declarations go. */
|
||||
#ifndef SCR_HTTP_H
|
||||
#define SCR_HTTP_H
|
||||
|
||||
#include "scr_tls.h"
|
||||
|
||||
/* ── 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*/);
|
||||
void scr_http_res_on_end_callback(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 an implicit scheme switch. 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
|
||||
|
||||
#endif /* SCR_HTTP_H */
|
||||
@@ -0,0 +1,207 @@
|
||||
/* scriptc runtime — node:http2 (scr_http2.c). Included by scr_runtime.h,
|
||||
* which says where new declarations go. */
|
||||
#ifndef SCR_HTTP2_H
|
||||
#define SCR_HTTP2_H
|
||||
|
||||
#include "scr_http.h"
|
||||
|
||||
/* ── 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);
|
||||
|
||||
#endif /* SCR_HTTP2_H */
|
||||
@@ -0,0 +1,128 @@
|
||||
/* scriptc runtime — util.inspect (scr_inspect.c). Included by scr_runtime.h,
|
||||
* which says where new declarations go. */
|
||||
#ifndef SCR_INSPECT_H
|
||||
#define SCR_INSPECT_H
|
||||
|
||||
#include "scr_bytes.h"
|
||||
|
||||
/* ── 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);
|
||||
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);
|
||||
/* A nested util.inspect inside a custom rendering (PerformanceEntry's):
|
||||
* begin sets a fresh inspect context aside the caller's; end restores it
|
||||
* and answers `prefix inner` with inner's newlines re-indented at the
|
||||
* caller's level (+1; borrows both). */
|
||||
void scr_insp_nested_begin(void);
|
||||
ScrStr *scr_insp_nested_end(ScrStr *prefix, ScrStr *inner);
|
||||
|
||||
/* 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 nullish/empty encodings, a decoded string otherwise — or NULL
|
||||
* with the exception pending. */
|
||||
ScrDyn *scr_fs_read_file_sync_dyn(ScrStr *path, const ScrDyn *enc);
|
||||
ScrStr *scr_fs_read_file_encoded(ScrStr *path, ScrStr *encoding);
|
||||
ScrStr *scr_fs_read_fd_encoded(double fd, ScrStr *encoding);
|
||||
ScrDyn *scr_fs_callback_value(ScrStr *member);
|
||||
ScrDyn *scr_fs_callback_call(ScrStr *member, const ScrDyn *args);
|
||||
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);
|
||||
|
||||
#endif /* SCR_INSPECT_H */
|
||||
@@ -0,0 +1,135 @@
|
||||
/* scriptc runtime — JSON parsing and the JSON output buffer (scr_json.c).
|
||||
* Included by scr_runtime.h, which says where new declarations go. */
|
||||
#ifndef SCR_JSON_H
|
||||
#define SCR_JSON_H
|
||||
|
||||
#include "scr_dyn.h"
|
||||
|
||||
/* 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);
|
||||
|
||||
/* Compiler-emitted description of a statically known JSON.parse target
|
||||
* (`JSON.parse(text) as T`). Constant data in the program image; the LLVM
|
||||
* backend mirrors this layout ({size, size, ptr, ptr, ptr, ptr, ptr} and
|
||||
* {ptr, size, size, ptr}). Records list their declared fields (at most
|
||||
* 64), each with its byte offset in the record struct; bool fields are one
|
||||
* byte, f64 fields doubles, every other kind a +1 pointer. */
|
||||
enum {
|
||||
SCR_JSCHEMA_F64 = 0,
|
||||
SCR_JSCHEMA_BOOL = 1,
|
||||
SCR_JSCHEMA_STR = 2,
|
||||
SCR_JSCHEMA_REC = 3,
|
||||
SCR_JSCHEMA_ARR = 4,
|
||||
};
|
||||
typedef struct ScrJsonSchema ScrJsonSchema;
|
||||
typedef struct {
|
||||
const char *name;
|
||||
size_t name_len;
|
||||
size_t offset;
|
||||
const ScrJsonSchema *type;
|
||||
} ScrJsonSchemaField;
|
||||
struct ScrJsonSchema {
|
||||
size_t kind;
|
||||
size_t nfields;
|
||||
const ScrJsonSchemaField *fields; /* records */
|
||||
const ScrJsonSchema *elem; /* arrays */
|
||||
void *(*rec_new)(void); /* records: zeroed instance, rc 1 */
|
||||
ScrArr *(*arr_new)(size_t); /* arrays: empty array of the element kind */
|
||||
void (*release)(void *); /* records and arrays */
|
||||
};
|
||||
|
||||
/* One-pass parse straight into the native layout. Returns the +1 record
|
||||
* or array, or NULL WITHOUT throwing whenever the checked-dynamic route
|
||||
* (scr_json_parse + dynCheck) might fail or differ; the caller then runs
|
||||
* that route, which reports the exact error. */
|
||||
void *scr_json_parse_schema(const ScrStr *text, const ScrJsonSchema *schema);
|
||||
|
||||
/* 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. An array replacer selects the object
|
||||
* keys to serialize. 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);
|
||||
ScrDyn *scr_json_stringify_value(const ScrDyn *value, const ScrDyn *replacer, const ScrDyn *space);
|
||||
|
||||
/* 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;
|
||||
/* Emitted JSON writers append constant text inline (llvm/walkers.ts
|
||||
* appendBytes) through this prefix. */
|
||||
_Static_assert(offsetof(ScrJsonBuf, data) == 0 && offsetof(ScrJsonBuf, len) == sizeof(char *) &&
|
||||
offsetof(ScrJsonBuf, cap) == sizeof(char *) + sizeof(size_t),
|
||||
"LLVM backend expects ScrJsonBuf to start with { data, len, cap }");
|
||||
|
||||
void scr_jb_init(ScrJsonBuf *b);
|
||||
/* Append a borrowed byte span verbatim, including embedded NUL bytes. */
|
||||
void scr_jb_write(ScrJsonBuf *b, const char *s, size_t n);
|
||||
/* scr_jb_write for text that holds no lone surrogate (fixed text, JSON
|
||||
* tokens), which cannot complete a surrogate pair at the seam. */
|
||||
void scr_jb_write_utf8(ScrJsonBuf *b, const char *s, size_t n);
|
||||
/* 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);
|
||||
/* Append String(number), including NaN/Infinity, without a temporary string. */
|
||||
void scr_jb_put_number(ScrJsonBuf *b, double v);
|
||||
/* Quoted + escaped JSON string: \" \\ \n \r \t \b \f, other control chars
|
||||
* as \u00XX, lone surrogates as \udXXX, everything else (UTF-8 included)
|
||||
* verbatim — exactly the JS JSON.stringify escape set. */
|
||||
void scr_jb_put_json_str(ScrJsonBuf *b, const ScrStr *s);
|
||||
|
||||
/* 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 */
|
||||
/* Reformat compiler-produced compact JSON with an already resolved gap.
|
||||
* Borrows both inputs; gap_len is a byte length, including embedded NUL.
|
||||
* Returns a new owned string, or retains compact when the gap is empty. */
|
||||
ScrStr *scr_json_indent(const ScrStr *compact, const char *gap, size_t gap_len);
|
||||
|
||||
#endif /* SCR_JSON_H */
|
||||
@@ -0,0 +1,416 @@
|
||||
/* scriptc runtime — the standard library: process, modules, performance
|
||||
* timing and node:fs (scr_lib.c). Included by scr_runtime.h, which says where
|
||||
* new declarations go. */
|
||||
#ifndef SCR_LIB_H
|
||||
#define SCR_LIB_H
|
||||
|
||||
#include "scr_exception.h"
|
||||
|
||||
/* ── 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);
|
||||
|
||||
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) */
|
||||
|
||||
bool scr_stdio_write(int fd, const void *data, size_t len);
|
||||
bool scr_stdio_write_str(int fd, const ScrStr *s);
|
||||
int scr_stdio_write_raw(int fd, const void *data, size_t len);
|
||||
/* `wide` marks string bytes that may hold lone surrogates, which become
|
||||
* U+FFFD when they reach the stream itself. */
|
||||
extern SCR_TL bool (*scr_stdio_write_hook)(int fd, const void *data, size_t len, bool wide);
|
||||
/* 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 */
|
||||
/* NODE_DEBUG as the process started (NULL when unset), for util.debuglog. */
|
||||
const char *scr_node_debug_initial(void);
|
||||
/* 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);
|
||||
/* Nonzero hands the address-space teardown after scr_process_exit to a
|
||||
* helper process (Linux, `--detached-exit`). scr_lib.c defines a weak 0;
|
||||
* the program object of such an executable defines it as 1. */
|
||||
extern const unsigned char scr_detached_exit;
|
||||
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);
|
||||
/* perf_hooks user timing (scr_perf.c): mark, measure, the getEntries
|
||||
* family and the clears take the call's dyn argument vector (borrowed)
|
||||
* and apply Node's validation and errors. mark and measure answer 1 and
|
||||
* the getters the entry count; the answered entries stay readable through
|
||||
* the scr_perf_entry_* accessors (index into the answer) until the next
|
||||
* timeline call. which: 0 getEntries, 1 getEntriesByName, 2
|
||||
* getEntriesByType. Accessor results are +1. */
|
||||
double scr_perf_mark(ScrDyn *args);
|
||||
double scr_perf_measure(ScrDyn *args);
|
||||
double scr_perf_entries(ScrDyn *args, double which);
|
||||
void scr_perf_clear_marks(ScrDyn *args);
|
||||
void scr_perf_clear_measures(ScrDyn *args);
|
||||
ScrStr *scr_perf_entry_name(double i);
|
||||
ScrStr *scr_perf_entry_type(double i);
|
||||
double scr_perf_entry_start(double i);
|
||||
double scr_perf_entry_duration(double i);
|
||||
ScrDyn *scr_perf_entry_detail(double i);
|
||||
/* The entry objects themselves: entry_keep stores the program's object
|
||||
* for the last mark or measure (borrowed, retained), entry_ref answers
|
||||
* entry i's stored object (+1), so the getters return the same objects
|
||||
* mark() and measure() did. */
|
||||
void scr_perf_entry_keep(ScrDyn *ref);
|
||||
ScrDyn *scr_perf_entry_ref(double i);
|
||||
uint64_t scr_hrtime_ns(void);
|
||||
ScrDyn *scr_process_hrtime_value(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);
|
||||
/* process.memoryUsage(): take samples rss and the runtime heap once;
|
||||
* field(k) reads that sample (k: 0 rss, 1 heapTotal, 2 heapUsed,
|
||||
* 3 external, 4 arrayBuffers). heapTotal is the bytes the runtime heap
|
||||
* holds from the system, heapUsed the bytes in live allocations, and
|
||||
* external/arrayBuffers are 0 (byte storage lives in the same heap).
|
||||
* memoryUsage.rss() is the resident set alone. An unreadable resident
|
||||
* set throws Node's uv_resident_set_memory error. */
|
||||
void scr_process_memory_usage_take(void);
|
||||
double scr_process_memory_usage_field(double k);
|
||||
double scr_process_rss(void);
|
||||
/* The runtime heap's footprint (scr_alloc.c): bytes held from the system
|
||||
* and bytes in live allocations, for the calling thread's allocator plus
|
||||
* the system allocator where it reports them. */
|
||||
void scr_heap_stats(double *total, double *used);
|
||||
/* process.ppid: the parent's pid at the time of the read. */
|
||||
double scr_process_ppid(void);
|
||||
/* process.title: libuv's process title — argv[0] until set; a set
|
||||
* truncates to the original argument area (POSIX) and becomes the name
|
||||
* ps shows, or the console title (Windows). "node" without arguments
|
||||
* (library sessions). +1 / borrowed. */
|
||||
ScrStr *scr_process_title(void);
|
||||
void scr_process_set_title(const ScrStr *title);
|
||||
/* process.argv0: the original argv[0] the binary was started with. +1. */
|
||||
ScrStr *scr_process_argv0(void);
|
||||
/* process.release: the stable { name, lts, sourceUrl, headersUrl[,
|
||||
* libUrl] } object of the compatibility target's release. +1. */
|
||||
ScrDyn *scr_process_release(void);
|
||||
/* geteuid(2) / getegid(2) / getgroups(2) (with the effective gid added
|
||||
* when missing, like Node). Windows has no such members: the calls throw
|
||||
* the TypeError Node's missing member does. */
|
||||
double scr_process_geteuid(void);
|
||||
double scr_process_getegid(void);
|
||||
ScrArr *scr_process_getgroups(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);
|
||||
|
||||
/* Sequentially consistent integer operations and shared Int32 wait queues. */
|
||||
typedef struct ScrBytes ScrBytes;
|
||||
ScrStr *scr_atomics_wait(ScrBytes *, double, double, double);
|
||||
double scr_atomics_notify(ScrBytes *, double, double);
|
||||
double scr_atomics_op(ScrBytes *, double, double, double, double);
|
||||
|
||||
/* 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_fdatasync(double fd);
|
||||
void scr_fs_ftruncate(double fd, double length);
|
||||
void scr_fs_utimes(ScrStr *path, double atime, double mtime, bool nofollow);
|
||||
void scr_fs_invalid_path(const ScrDyn *value, const ScrStr *path);
|
||||
void scr_fs_invalid_named_path(const ScrDyn *value, const ScrStr *path, const char *name);
|
||||
void scr_fs_futimes(double fd, double atime, double mtime);
|
||||
void scr_fs_link(ScrStr *source, ScrStr *dest);
|
||||
void scr_fs_symlink(ScrStr *target, ScrStr *destination, int kind);
|
||||
int scr_fs_symlink_infer(ScrStr *absolute);
|
||||
ScrBytes *scr_fs_readlink_bytes(ScrStr *path);
|
||||
void scr_fs_link_checked(const ScrDyn *existing, const ScrDyn *destination);
|
||||
void scr_fs_symlink_checked(const ScrDyn *target, const ScrDyn *destination, const ScrDyn *type);
|
||||
void scr_fs_symlink_promise_checked(const ScrDyn *target, const ScrDyn *destination, const ScrDyn *type);
|
||||
ScrDyn *scr_fs_readlink_checked(const ScrDyn *path, const ScrDyn *options, bool promise);
|
||||
ScrDyn *scr_fs_readlink_dyn(const ScrDyn *path, const ScrDyn *options);
|
||||
ScrDyn *scr_fs_statfs(ScrStr *path, bool bigint);
|
||||
ScrDyn *scr_fs_statfs_checked(const ScrDyn *path, const ScrDyn *options);
|
||||
ScrPromise *scr_fsp_statfs(const ScrDyn *path, const ScrDyn *options);
|
||||
ScrStr *scr_fs_readlink_str(const ScrDyn *path, const ScrDyn *options);
|
||||
ScrBytes *scr_fs_readlink_buffer(const ScrDyn *path, const ScrDyn *options);
|
||||
ScrPromise *scr_fsp_link(const ScrDyn *existing, const ScrDyn *destination);
|
||||
ScrPromise *scr_fsp_symlink(const ScrDyn *target, const ScrDyn *destination, const ScrDyn *type);
|
||||
ScrPromise *scr_fsp_readlink_dyn(const ScrDyn *path, const ScrDyn *options);
|
||||
ScrPromise *scr_fsp_readlink_str(const ScrDyn *path, const ScrDyn *options);
|
||||
ScrPromise *scr_fsp_readlink_buffer(const ScrDyn *path, const ScrDyn *options);
|
||||
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_utimes(const ScrDyn *path, const ScrDyn *atime, const ScrDyn *mtime);
|
||||
ScrPromise *scr_fsp_lutimes(const ScrDyn *path, const ScrDyn *atime, const ScrDyn *mtime);
|
||||
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_utimes_promise(ScrFileHandle *h, const ScrDyn *atime, const ScrDyn *mtime);
|
||||
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);
|
||||
|
||||
#endif /* SCR_LIB_H */
|
||||
@@ -0,0 +1,313 @@
|
||||
/* scriptc runtime — Map and Set (scr_map.c). Included by scr_runtime.h, which
|
||||
* says where new declarations go. */
|
||||
#ifndef SCR_MAP_H
|
||||
#define SCR_MAP_H
|
||||
|
||||
#include "scr_array.h"
|
||||
|
||||
/* ── 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).
|
||||
*
|
||||
* Primitive keys compare by value and reference keys by identity. Numbers
|
||||
* use SameValueZero: NaN equals NaN and -0 is stored as +0. Union wrappers
|
||||
* are representations, never identities; their arm descriptors select
|
||||
* the primitive or reference comparison, including null and undefined.
|
||||
*
|
||||
* 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,
|
||||
SCR_MAP_KEY_BIGINT, SCR_MAP_KEY_BOOL, SCR_MAP_KEY_UNION_VALUE,
|
||||
SCR_MAP_KEY_NULL, SCR_MAP_KEY_UNDEFINED /* tag-only union arms */
|
||||
} 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 */
|
||||
uint64_t hash; /* nonzero cached hash; zero = tombstone (key/val released) */
|
||||
} ScrMapEntry;
|
||||
|
||||
/* Checked views use the original storage. Slot converters borrow on reads
|
||||
* and return owned references on writes, just like typed Map operations. */
|
||||
typedef struct {
|
||||
const char *type;
|
||||
struct ScrDyn *(*key_box)(uint64_t);
|
||||
uint64_t (*key_unbox)(const struct ScrDyn *);
|
||||
bool (*key_matches)(const struct ScrDyn *);
|
||||
struct ScrDyn *(*val_box)(uint64_t);
|
||||
uint64_t (*val_unbox)(const struct ScrDyn *);
|
||||
} ScrMapDynOps;
|
||||
|
||||
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;
|
||||
/* Reference-represented keys, including bigint and union values. */
|
||||
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) */
|
||||
/* Two bits per inserted entry hash (scr_map.c scr_map_filter_bits): a
|
||||
* lookup whose bits are not all set is absent without touching the index
|
||||
* or the entries. Deletions leave their bits; compaction recomputes it. */
|
||||
uint64_t filter;
|
||||
size_t ecap; /* entries capacity */
|
||||
ScrMapEntry *entries;
|
||||
size_t nbuckets; /* zero for small linear maps; otherwise power of two >= 2 * nentries */
|
||||
void *buckets; /* compact tagged indices, or size_t indices for large tables */
|
||||
size_t iter_depth; /* > 0: an iteration is active — no compaction */
|
||||
const ScrMapDynOps *dyn_ops;
|
||||
const uint8_t *union_keys; /* immutable key-kind table indexed by union tag */
|
||||
/* Direct index for number keys (SCR_MAP_KEY_F64 only). While ndense is
|
||||
* nonzero every live key is an integer in [0, ndense) and dense[k] holds
|
||||
* its entry index + 1 (0 = absent); the map then keeps no bucket table.
|
||||
* A key outside that shape moves the map to hashing; whenever the bucket
|
||||
* table would grow, compact integer keys move it back. Iteration order is
|
||||
* always the entries array's. */
|
||||
uint32_t *dense;
|
||||
size_t ndense;
|
||||
/* Every entry below this index is a tombstone. A walk that starts at or
|
||||
* below it advances it to the first live entry (scr_map_iter_skip);
|
||||
* compaction and clear reset it. A map that deletes from the front (a
|
||||
* queue, an LRU cache) then finds its first live entry without walking
|
||||
* the whole deleted prefix again. */
|
||||
size_t head;
|
||||
} ScrMap;
|
||||
/* The LLVM backend's inline retain/release read both trace slots through
|
||||
* its %ScrMapRc prefix type (fields 5 and 8), mirroring scr_map_retain and
|
||||
* scr_map_release's `key_trace || val_trace` header test. */
|
||||
_Static_assert(offsetof(ScrMap, val_trace) == sizeof(size_t) + 2 * sizeof(uint32_t) + 2 * sizeof(void *),
|
||||
"inline RC fast paths read ScrMap.val_trace as %ScrMapRc field 5");
|
||||
_Static_assert(offsetof(ScrMap, key_trace) == offsetof(ScrMap, val_trace) + 3 * sizeof(void *),
|
||||
"inline RC fast paths read ScrMap.key_trace as %ScrMapRc field 8");
|
||||
/* The LLVM backend's inline number-key lookup (sc_map_entry_f64) reads
|
||||
* entries, dense and ndense through %ScrMapIx fields 13, 19 and 20, and
|
||||
* entries as %ScrMapEntry { key, val, hash }; %ScrMapIx field 11 is the
|
||||
* i64 filter. */
|
||||
_Static_assert(offsetof(ScrMap, filter) ==
|
||||
(offsetof(ScrMap, nlive) + sizeof(size_t) + _Alignof(uint64_t) - 1) / _Alignof(uint64_t) * _Alignof(uint64_t),
|
||||
"%ScrMapIx field 11 is ScrMap.filter, directly after nlive");
|
||||
_Static_assert(offsetof(ScrMap, nlive) == sizeof(size_t) + 2 * sizeof(uint32_t) + 6 * sizeof(void *) + sizeof(size_t),
|
||||
"%ScrMapIx field 10 is ScrMap.nlive");
|
||||
/* The inline iteration steps (sc_map_iter_entry) read the entry count as
|
||||
* %ScrMapIx field 9 and test each entry's hash, the third field. */
|
||||
_Static_assert(offsetof(ScrMap, nentries) + sizeof(size_t) == offsetof(ScrMap, nlive),
|
||||
"%ScrMapIx field 9 is ScrMap.nentries, directly before nlive");
|
||||
_Static_assert(offsetof(ScrMapEntry, hash) == 16, "inline iteration reads ScrMapEntry.hash at byte 16");
|
||||
_Static_assert(offsetof(ScrMap, entries) == offsetof(ScrMap, filter) + sizeof(uint64_t) + sizeof(size_t),
|
||||
"inline number-key lookups read ScrMap.entries as %ScrMapIx field 13");
|
||||
_Static_assert(offsetof(ScrMap, dense) == offsetof(ScrMap, entries) + 6 * sizeof(void *),
|
||||
"inline number-key lookups read ScrMap.dense as %ScrMapIx field 19");
|
||||
_Static_assert(offsetof(ScrMap, ndense) == offsetof(ScrMap, dense) + sizeof(void *),
|
||||
"inline number-key lookups read ScrMap.ndense as %ScrMapIx field 20");
|
||||
_Static_assert(sizeof(ScrMapEntry) == 24 && offsetof(ScrMapEntry, val) == 8,
|
||||
"inline number-key lookups read ScrMapEntry.val at byte 8");
|
||||
/* Entry for a number key, or NULL when absent. The pointer is valid until
|
||||
* the next mutation; generated code reads the value slot through it. */
|
||||
const ScrMapEntry *scr_map_entry_f64(const ScrMap *m, double key);
|
||||
/* Entry for a string key, or NULL when absent, on the same terms. Typed
|
||||
* reference reads retain the value with its static type's retain instead
|
||||
* of the collection's adapter. */
|
||||
const ScrMapEntry *scr_map_entry_str(const ScrMap *m, const ScrStr *key);
|
||||
|
||||
/* Called once on a fresh UNION_VALUE collection, before its first insert. */
|
||||
void scr_map_union_keys(ScrMap *map, const uint8_t *kinds);
|
||||
/* String-key lookups borrow an exact byte span. Reference results return
|
||||
* +1; scalar outputs are written only on a hit, like the string getters. */
|
||||
bool scr_map_get_span_f64(const ScrMap *m, const char *bytes, size_t length, double *out);
|
||||
bool scr_map_get_span_bool(const ScrMap *m, const char *bytes, size_t length, bool *out);
|
||||
void *scr_map_get_span_ref(const ScrMap *m, const char *bytes, size_t length);
|
||||
|
||||
/* Fresh shallow copies with compact storage and independent mutation.
|
||||
* keys_only selects a Set; checked views materialize their boxed values. */
|
||||
ScrMap *scr_map_clone(const ScrMap *source, bool keys_only);
|
||||
ScrMap *scr_map_clone_dyn(const ScrMap *source, bool keys_only);
|
||||
void scr_map_values_into(ScrMap *set, const ScrMap *source, bool checked);
|
||||
|
||||
void scr_map_dyn_attach(ScrMap *map, const ScrMapDynOps *ops);
|
||||
struct ScrDyn *scr_map_dyn_key(const ScrMap *map, double index);
|
||||
struct ScrDyn *scr_map_dyn_value(const ScrMap *map, double index);
|
||||
struct ScrDyn *scr_map_dyn_get(ScrMap *map, const struct ScrDyn *key);
|
||||
bool scr_map_dyn_has(ScrMap *map, const struct ScrDyn *key, bool remove);
|
||||
void scr_map_dyn_set(ScrMap *map, const struct ScrDyn *key, const struct ScrDyn *value, bool set);
|
||||
|
||||
/* 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_bool(const ScrMap *m, bool 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_bool(ScrMap *m, bool 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_bool_f64(ScrMap *m, bool key, double v);
|
||||
void scr_map_set_bool_bool(ScrMap *m, bool key, bool v);
|
||||
void scr_map_set_bool_ref(ScrMap *m, bool 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_bool_f64(const ScrMap *m, bool key, double *out);
|
||||
bool scr_map_get_bool_bool(const ScrMap *m, bool key, bool *out);
|
||||
void *scr_map_get_bool_ref(const ScrMap *m, bool 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);
|
||||
/* The first live index at or after i, or iter_count when none is left.
|
||||
* iter_start is the first live index of the map (for-of starts there). */
|
||||
double scr_map_iter_skip(ScrMap *m, double i);
|
||||
double scr_map_iter_start(ScrMap *m);
|
||||
double scr_map_iter_key_f64(const ScrMap *m, double i);
|
||||
bool scr_map_iter_key_bool(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_bool(const ScrMap *s);
|
||||
ScrArr *scr_set_to_arr_str(const ScrMap *s);
|
||||
ScrArr *scr_set_to_arr_ref(const ScrMap *s, const ScrArrOps *ops);
|
||||
ScrArr *scr_set_to_arr_dyn(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, as for arrays (scr_arr_new_ops). */
|
||||
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);
|
||||
/* Publication makes objects immortal and never frees them, so the RC audit
|
||||
* stops counting them (scr_message.c). */
|
||||
void scr_str_live_forget(void);
|
||||
void scr_dyn_live_forget(void);
|
||||
void scr_arr_live_forget(void);
|
||||
void scr_map_live_forget(void);
|
||||
void scr_union_live_forget(void);
|
||||
#endif
|
||||
|
||||
/* For the same reason LeakSanitizer must not report a published object
|
||||
* whose last reference is dropped (scr_message.c, scr_publish.c). Ignored
|
||||
* chunks are leak-scan roots, so everything an immortal object reaches is
|
||||
* covered and the verdict on the rest of the heap is unchanged. Weak: ASan
|
||||
* runtimes without a leak checker leave it NULL; other builds compile the
|
||||
* hook away. */
|
||||
#if defined(__has_feature)
|
||||
#if __has_feature(address_sanitizer)
|
||||
#define SCR_LSAN_IMMORTAL 1
|
||||
void __lsan_ignore_object(const void *p) __attribute__((weak));
|
||||
#endif
|
||||
#endif
|
||||
static inline void scr_lsan_immortal(const void *obj) {
|
||||
#ifdef SCR_LSAN_IMMORTAL
|
||||
if (__lsan_ignore_object) __lsan_ignore_object(obj);
|
||||
#else
|
||||
(void)obj;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif /* SCR_MAP_H */
|
||||
@@ -0,0 +1,342 @@
|
||||
/* scriptc runtime — node:net (scr_net.c) and the socket transport hooks.
|
||||
* Included by scr_runtime.h, which says where new declarations go. */
|
||||
#ifndef SCR_NET_H
|
||||
#define SCR_NET_H
|
||||
|
||||
#include "scr_bytes.h"
|
||||
|
||||
/* ── 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 throws the "not
|
||||
* supported yet" Error). */
|
||||
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 throw the "not
|
||||
* supported yet" Error;
|
||||
* 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);
|
||||
/* Several protocol-layer slices in order as one write: a single vectored
|
||||
* syscall when the bytes can go out immediately, else the same buffering
|
||||
* as consecutive scr_net_sock_write_native calls. */
|
||||
typedef struct {
|
||||
const char *data;
|
||||
size_t len;
|
||||
} ScrNetSlice;
|
||||
void scr_net_sock_writev_native(ScrNetSocket *s, const ScrNetSlice *slices, 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 */
|
||||
ScrNetSocket *scr_net_connect_checked(double port, ScrStr *host /*borrowed, nullable*/, ScrClosure *cb /*moves, nullable*/); /* +1 */
|
||||
bool scr_net_host_chk(const ScrStr *host /*borrowed, nullable*/);
|
||||
/* 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. */
|
||||
|
||||
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 tlsClientError
|
||||
* default, which tears down without an 'error' event). `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 without an 'error'
|
||||
* event, 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);
|
||||
|
||||
#endif /* SCR_NET_H */
|
||||
@@ -0,0 +1,150 @@
|
||||
/* scriptc runtime — numbers, String.fromCharCode, Number statics, Date and
|
||||
* numeric coercion (scr_number.c, scr_lib.c). Included by scr_runtime.h,
|
||||
* which says where new declarations go. */
|
||||
#ifndef SCR_NUMBER_H
|
||||
#define SCR_NUMBER_H
|
||||
|
||||
#include "scr_events.h"
|
||||
|
||||
/* ── 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);
|
||||
|
||||
/* Clinger's exact fast path for a VALIDATED decimal span ([+-]? digits
|
||||
* with optional fraction and exponent; no whitespace, no Infinity): sets
|
||||
* *out and returns true when the value is m × 10^e with m <= 2^53 and a
|
||||
* small |e| (one correctly rounded multiply/divide, bit-identical to
|
||||
* strtod); false means the caller must take its strtod path. */
|
||||
bool scr_decimal_fast(const char *p, size_t n, double *out);
|
||||
/* The same fast path with whole-span validation folded into its scan: -1
|
||||
* when p[0..n) is not such a decimal literal, 1 with *out set when exact,
|
||||
* 0 when valid but the caller must use strtod. */
|
||||
int scr_decimal_scan(const char *p, size_t n, double *out);
|
||||
|
||||
/* Number.prototype.toFixed's digit string for finite |x| < 1e21 and
|
||||
* 0 <= f <= 100: the spec's n (closest to |x| × 10^f on the EXACT binary
|
||||
* value, ties up) with the point placed f digits from the right and "-"
|
||||
* for x < 0. Writes NUL-terminated text into buf (>= 128 bytes); returns
|
||||
* the length. */
|
||||
size_t scr_f64_to_fixed(double x, int f, char *buf);
|
||||
|
||||
/* 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, other surrogates stay lone. 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.
|
||||
* ToUint32 and the bitwise operations live in scr_number.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);
|
||||
|
||||
#endif /* SCR_NUMBER_H */
|
||||
@@ -0,0 +1,34 @@
|
||||
/* scriptc runtime — class instance accounting (scr_object.c). Included by
|
||||
* scr_runtime.h, which says where new declarations go. */
|
||||
#ifndef SCR_OBJECT_H
|
||||
#define SCR_OBJECT_H
|
||||
|
||||
#include "scr_core.h"
|
||||
|
||||
/* ── 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);
|
||||
/* Emitted by the RC-audit lane's ProgramLifetime constructors, after the
|
||||
* allocation note: such instances are never freed (the exit audit skips its
|
||||
* leak report, and LeakSanitizer scans them as roots). */
|
||||
void scr_obj_lifetime_note(const void *obj);
|
||||
#ifdef SCR_RC_AUDIT
|
||||
long scr_obj_live_count(void);
|
||||
long scr_obj_lifetime_count(void);
|
||||
#else
|
||||
/* Only the RC audit lane counts live objects. Other runtimes keep the
|
||||
* out-of-line no-op symbols for direct-emission objects that still call
|
||||
* them (scr_object.c), while runtime-internal call sites compile away. */
|
||||
#define scr_obj_alloc_note() ((void)0)
|
||||
#define scr_obj_free_note() ((void)0)
|
||||
#endif
|
||||
#ifdef SCR_RC_AUDIT
|
||||
/* A released lifetime region freed `n` program-lifetime instances. */
|
||||
void scr_obj_lifetime_forget(long n);
|
||||
#endif
|
||||
|
||||
#endif /* SCR_OBJECT_H */
|
||||
@@ -0,0 +1,81 @@
|
||||
/* scriptc runtime — node:os (scr_lib.c, scr_os.c). Included by scr_runtime.h,
|
||||
* which says where new declarations go. */
|
||||
#ifndef SCR_OS_H
|
||||
#define SCR_OS_H
|
||||
|
||||
#include "scr_exception.h"
|
||||
|
||||
/* ── 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 */
|
||||
/* node:os host facts (scr_os.c), libuv's answers: uname machine/version,
|
||||
* the host name, "LE"/"BE", free memory in bytes, uptime in seconds and
|
||||
* the 1/5/15-minute load averages (+1 fresh results; never throw). */
|
||||
ScrStr *scr_os_machine(void);
|
||||
ScrStr *scr_os_version(void);
|
||||
ScrStr *scr_os_hostname(void);
|
||||
ScrStr *scr_os_endianness(void);
|
||||
double scr_os_freemem(void);
|
||||
double scr_os_uptime(void);
|
||||
ScrArr *scr_os_loadavg(void);
|
||||
/* os.getPriority(pid): validateInt32, then the scheduling priority;
|
||||
* failures throw Node's RangeError or SystemError (ERR_SYSTEM_ERROR). */
|
||||
double scr_os_get_priority(double pid);
|
||||
/* os.cpus(): take answers the CPU count of a fresh snapshot; model and
|
||||
* field (0 speed, 1 user, 2 nice, 3 sys, 4 idle, 5 irq) read it. */
|
||||
double scr_os_cpus_take(void);
|
||||
ScrStr *scr_os_cpus_model(double i);
|
||||
double scr_os_cpus_field(double i, double field);
|
||||
/* os.constants: the per-thread null-prototype constants object (+1). */
|
||||
ScrDyn *scr_os_constants(void);
|
||||
double scr_os_available_parallelism(void); /* uv_available_parallelism: CPUs this process may use, >= 1 */
|
||||
/* 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);
|
||||
|
||||
#endif /* SCR_OS_H */
|
||||
@@ -0,0 +1,65 @@
|
||||
/* scriptc runtime — node:path (scr_path.c) and path globs (scr_glob.c).
|
||||
* Included by scr_runtime.h, which says where new declarations go. */
|
||||
#ifndef SCR_PATH_H
|
||||
#define SCR_PATH_H
|
||||
|
||||
#include "scr_array.h"
|
||||
|
||||
/* ── 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 exactly
|
||||
* (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);
|
||||
/* path.parse / path.format for both families: parse answers a fresh
|
||||
* string[] [root, dir, base, ext, name]; format takes the pathObject
|
||||
* fields with undefined spelled "" (Node reads each through `||`). */
|
||||
ScrArr *scr_path_parse(ScrStr *path);
|
||||
ScrArr *scr_path_win32_parse(ScrStr *path);
|
||||
ScrStr *scr_path_format(ScrStr *root, ScrStr *dir, ScrStr *base, ScrStr *name, ScrStr *ext);
|
||||
ScrStr *scr_path_win32_format(ScrStr *root, ScrStr *dir, ScrStr *base, ScrStr *name,
|
||||
ScrStr *ext);
|
||||
/* path.matchesGlob (scr_glob.c): Node 24's minimatch with matchGlobPattern's
|
||||
* options, case-insensitive on macOS and Windows hosts like Node. Borrows
|
||||
* both; a brace sequence over 2^32 - 1 elements throws Node's RangeError, a
|
||||
* pattern over 65536 units its TypeError, and a segment regex invalid under
|
||||
* the u flag its SyntaxError. scr_glob_match is the same with the family
|
||||
* and case mode explicit (the oracle tests use it). */
|
||||
bool scr_path_matches_glob(ScrStr *path, ScrStr *pattern);
|
||||
bool scr_path_win32_matches_glob(ScrStr *path, ScrStr *pattern);
|
||||
bool scr_glob_match(ScrStr *path, ScrStr *pattern, bool windows, bool nocase);
|
||||
|
||||
#endif /* SCR_PATH_H */
|
||||
@@ -0,0 +1,120 @@
|
||||
/* scriptc runtime — node:querystring (scr_qs.c), the node:util helpers
|
||||
* (scr_util.c), URI and base64 codecs. Included by scr_runtime.h, which says
|
||||
* where new declarations go. */
|
||||
#ifndef SCR_QS_H
|
||||
#define SCR_QS_H
|
||||
|
||||
#include "scr_string_methods.h"
|
||||
|
||||
/* ── 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_qs_escape beside the component encoder (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);
|
||||
/* querystring.parse(qs) with the default separators and maxKeys, as a
|
||||
* fresh checked-dynamic null-prototype object (url.parse's parsed query).
|
||||
* Borrows qs; +1; never throws. */
|
||||
struct ScrDyn *scr_qs_parse_dyn(const ScrStr *qs);
|
||||
|
||||
/* 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);
|
||||
ScrStr *scr_util_style_text(const struct ScrDyn *format, const struct ScrDyn *text, const struct ScrDyn *options);
|
||||
/* Node's terminal color decision (FORCE_COLOR, NO_COLOR, TERM, CI hosts)
|
||||
* once the stream is a terminal (scr_util_style.c). */
|
||||
bool scr_util_terminal_colors(void);
|
||||
/* util.inherits, util.deprecate, util.callbackify and util.debuglog
|
||||
* (scr_util_callable.c). Arguments are borrowed; function results are +1
|
||||
* native dyn functions, or NULL with Node's TypeError pending. */
|
||||
void scr_util_inherits(struct ScrDyn *ctor, struct ScrDyn *super_ctor);
|
||||
struct ScrDyn *scr_util_deprecate(struct ScrDyn *fn, struct ScrDyn *msg, struct ScrDyn *code,
|
||||
struct ScrDyn *options);
|
||||
struct ScrDyn *scr_util_callbackify(struct ScrDyn *original);
|
||||
struct ScrDyn *scr_util_debuglog(struct ScrDyn *section, struct ScrDyn *callback);
|
||||
|
||||
/* Node/libuv system error utilities. Arguments are checked without coercion;
|
||||
* entries is a fresh array of [number, [name, message]] pairs (+1). */
|
||||
ScrStr *scr_util_system_error_name(const struct ScrDyn *error);
|
||||
ScrStr *scr_util_system_error_message(const struct ScrDyn *error);
|
||||
struct ScrDyn *scr_util_system_error_entries(void);
|
||||
|
||||
/* Node 24's dotenv grammar, shared by util.parseEnv and process.loadEnvFile.
|
||||
* Arguments are borrowed; parseEnv returns a fresh ordinary object (+1).
|
||||
* Validation and file failures leave a coded exception pending. */
|
||||
struct ScrDyn *scr_util_parse_env(const struct ScrDyn *content);
|
||||
/* Checked primitives, ordinary records/arrays and byte views. Opaque native
|
||||
* objects and accessor descriptors retain explicit catchable refusals. */
|
||||
bool scr_util_is_deep_strict_equal(const struct ScrDyn *a, const struct ScrDyn *b,
|
||||
const struct ScrDyn *skip_prototype);
|
||||
void scr_process_load_env_file(const struct ScrDyn *path);
|
||||
|
||||
/* 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; a lone surrogate throws URIError [ERR_INVALID_URI] (NULL). */
|
||||
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). Throws the same
|
||||
* URIError on a lone surrogate. 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);
|
||||
|
||||
#endif /* SCR_QS_H */
|
||||
@@ -0,0 +1,493 @@
|
||||
/* scriptc runtime — cycle collection (scr_cycle.c), the small-object
|
||||
* allocator (scr_alloc.c) and cross-thread publication (scr_publish.c).
|
||||
* Included by scr_runtime.h, which says where new declarations go. */
|
||||
#ifndef SCR_RC_H
|
||||
#define SCR_RC_H
|
||||
|
||||
#include "scr_core.h"
|
||||
|
||||
/* ── 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 8-byte header directly BEFORE the object: the
|
||||
* registry id of its type's descriptor (a trace function enumerating the
|
||||
* object's cycle-capable children, and a teardown function freeing the
|
||||
* object WITHOUT releasing traced children) and the trial-deletion
|
||||
* bookkeeping (color, generation and 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; gather: 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 one
|
||||
* generation (nursery, mature, old) so later restricted 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.
|
||||
* SCR_CYCLE_GROWTH_CAP lowers how far past its size the heap may grow
|
||||
* between passes at an older level that find nothing (16 times its size by
|
||||
* default; see scr_cycle.c).
|
||||
* 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);
|
||||
|
||||
/* Colors belong to the collector alone: retains and releases never write
|
||||
* them (a buffered entry is a candidate until a pass drains it). DOOMED
|
||||
* identifies the gathered dead set during re-buffering and weak observer
|
||||
* teardown. WHITE candidates may still become black when a later outside
|
||||
* root restores them; only the settled white set becomes DOOMED. */
|
||||
enum { SCR_CYC_BLACK = 0, SCR_CYC_GRAY = 1, SCR_CYC_WHITE = 2, SCR_CYC_DOOMED = 3 };
|
||||
|
||||
/* 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; a pass at
|
||||
* SCR_CYC_OLD is a full pass. */
|
||||
enum { SCR_CYC_NURSERY = 0, SCR_CYC_MATURE = 1, SCR_CYC_OLD = 2, SCR_CYC_NGENS = 3 };
|
||||
|
||||
/* A headered type's collector entry points, shared by all its objects: the
|
||||
* header names its descriptor by a registry id (`id`, assigned on the type's
|
||||
* first allocation) instead of carrying a pointer, which keeps the header at
|
||||
* 8 bytes. Emitted shapes define one per traced type (llvm/shapes.ts);
|
||||
* runtime families define their own. Descriptors are writable (the id) and
|
||||
* must outlive every object of the type. */
|
||||
typedef struct ScrCycType {
|
||||
ScrTraceFn trace;
|
||||
ScrCycFreeFn free_fn;
|
||||
/* Index of the type's per-thread collector statistics (scr_cycle.c's
|
||||
* nursery bypass): a runtime family's SCR_CYC_KIND_*, or for emitted
|
||||
* descriptors their number counting from SCR_CYC_KIND_EMITTED. 0 keeps
|
||||
* no statistics. Statistics only steer
|
||||
* when a candidate is walked, never what a pass frees, so two types that
|
||||
* share an index (separately emitted code in one process) are merely
|
||||
* judged together. */
|
||||
uint32_t kind;
|
||||
/* Registry id (scr_cyc_types), 0 until scr_cyc_register assigns it.
|
||||
* Emitted allocation paths read it and take the slow path while it is 0. */
|
||||
uint32_t id;
|
||||
} ScrCycType;
|
||||
/* Runtime families' kinds. Emitted descriptors count from
|
||||
* SCR_CYC_KIND_EMITTED (llvm/alloc.ts FIRST_EMITTED_CYC_KIND). */
|
||||
enum {
|
||||
SCR_CYC_KIND_NONE = 0,
|
||||
SCR_CYC_KIND_ARRAY,
|
||||
SCR_CYC_KIND_PROMISE,
|
||||
SCR_CYC_KIND_BOX,
|
||||
SCR_CYC_KIND_CLOSURE,
|
||||
SCR_CYC_KIND_ERROR,
|
||||
SCR_CYC_KIND_DOMEXCEPTION,
|
||||
SCR_CYC_KIND_EMITTER,
|
||||
SCR_CYC_KIND_CAUGHT,
|
||||
SCR_CYC_KIND_DYN,
|
||||
SCR_CYC_KIND_WEAK_MAP,
|
||||
SCR_CYC_KIND_MAP,
|
||||
SCR_CYC_KIND_CLASS_OBJECT,
|
||||
SCR_CYC_KIND_STREAM,
|
||||
SCR_CYC_KIND_UNION,
|
||||
SCR_CYC_KIND_WORKER_VIEW,
|
||||
SCR_CYC_KIND_EMITTED = 32
|
||||
};
|
||||
/* Eight bytes in front of the object: the descriptor's registry id, the
|
||||
* collector color (SCR_CYC_*) and generation (SCR_CYC_NURSERY..SCR_CYC_OLD,
|
||||
* the walk filter), and `buf`: SCR_CYC_BUFFERED_BIT while the object sits in
|
||||
* its generation's candidate buffer, with its position there below it (O(1)
|
||||
* removal when rc hits 0). */
|
||||
typedef struct ScrCycHdr {
|
||||
uint16_t id;
|
||||
uint8_t color;
|
||||
uint8_t gen;
|
||||
uint32_t buf;
|
||||
} ScrCycHdr;
|
||||
#define SCR_CYC_BUFFERED_BIT 0x80000000u
|
||||
|
||||
/* This layout is an ABI, not an implementation detail. The emitted
|
||||
* allocation stores the id and zeroes the rest; every emitted release of a
|
||||
* headered object (llvm/shapes.ts cycleRootLines, used by the record, class
|
||||
* and inline RC helpers) mirrors scr_cyc_on_release's early return by
|
||||
* testing the sign of `buf` as an i32 at obj-4, and the emitted inline free
|
||||
* reads the generation as an i8 at obj-5. Hence the assertions. */
|
||||
_Static_assert(sizeof(ScrCycHdr) == 8, "LLVM backend expects an 8-byte cycle header");
|
||||
_Static_assert(offsetof(ScrCycHdr, id) == 0 && sizeof(((ScrCycHdr *)0)->id) == 2,
|
||||
"the emitted allocation stores the registry id as an i16 at the block start");
|
||||
_Static_assert(sizeof(ScrCycHdr) - offsetof(ScrCycHdr, buf) == 4,
|
||||
"the emitted release fast path tests `buf` as an i32 at obj-4");
|
||||
_Static_assert(sizeof(ScrCycHdr) - offsetof(ScrCycHdr, gen) == 5,
|
||||
"LLVM backend's inline free reads the generation as an i8 at obj-5");
|
||||
_Static_assert(sizeof(ScrCycHdr) % _Alignof(double) == 0,
|
||||
"cycle-headered payloads must preserve double alignment");
|
||||
|
||||
static inline ScrCycHdr *scr_cyc_hdr(void *obj) { return (ScrCycHdr *)obj - 1; }
|
||||
|
||||
/* Registry of descriptors by id (copies, so a header reaches its entry
|
||||
* points in one step); ids are assigned on first allocation and shared by
|
||||
* every thread. */
|
||||
#define SCR_CYC_MAX_TYPES ((uint32_t)1 << 15)
|
||||
extern ScrCycType scr_cyc_types[SCR_CYC_MAX_TYPES];
|
||||
uint32_t scr_cyc_register(ScrCycType *type);
|
||||
static inline uint32_t scr_cyc_type_id(ScrCycType *type) {
|
||||
/* Acquire pairs with scr_cyc_register's publishing compare-exchange, so
|
||||
* a thread that sees the id also sees its scr_cyc_types entry. */
|
||||
uint32_t id = __atomic_load_n(&type->id, __ATOMIC_ACQUIRE);
|
||||
return id ? id : scr_cyc_register(type);
|
||||
}
|
||||
|
||||
static inline const ScrCycType *scr_cyc_type(const ScrCycHdr *h) { return &scr_cyc_types[h->id]; }
|
||||
static inline unsigned scr_cyc_color(const ScrCycHdr *h) { return h->color; }
|
||||
static inline void scr_cyc_set_color(ScrCycHdr *h, unsigned color) { h->color = (uint8_t)color; }
|
||||
static inline unsigned scr_cyc_gen(const ScrCycHdr *h) { return h->gen; }
|
||||
static inline void scr_cyc_set_gen(ScrCycHdr *h, unsigned gen) { h->gen = (uint8_t)gen; }
|
||||
static inline bool scr_cyc_is_buffered(const ScrCycHdr *h) {
|
||||
return (h->buf & SCR_CYC_BUFFERED_BIT) != 0;
|
||||
}
|
||||
static inline uint32_t scr_cyc_buf_index(const ScrCycHdr *h) {
|
||||
return h->buf & ~SCR_CYC_BUFFERED_BIT;
|
||||
}
|
||||
/* Buffered at position `index` of its generation's buffer, or not at all. */
|
||||
static inline void scr_cyc_set_buffered(ScrCycHdr *h, uint32_t index) {
|
||||
h->buf = SCR_CYC_BUFFERED_BIT | index;
|
||||
}
|
||||
static inline void scr_cyc_clear_buffered(ScrCycHdr *h) { h->buf = 0; }
|
||||
|
||||
/* Zeroed allocation with a cycle header in front; returns the OBJECT
|
||||
* pointer (header at scr_cyc_hdr). `type` must outlive the object (a
|
||||
* static descriptor). Aborts on OOM. */
|
||||
void *scr_cyc_alloc(size_t size, ScrCycType *type);
|
||||
void scr_cyc_free(void *obj); /* frees the block, header included */
|
||||
|
||||
/* ── small-object allocator (scr_alloc.c) ───────────────────────────────
|
||||
* Runtime objects are small, short-lived and freed by the thread that made
|
||||
* them — the pattern a general-purpose malloc pays the most for. Blocks of
|
||||
* up to SCR_SA_MAX bytes come from per-size-class free lists refilled by a
|
||||
* bump pointer; the fast paths below inline into every runtime unit.
|
||||
*
|
||||
* Contracts match malloc/calloc/realloc/free (NULL on OOM), except for
|
||||
* alignment: blocks are 8-byte aligned, and 16-byte aligned when the size
|
||||
* is a multiple of 16, which covers every type whose size is a multiple of
|
||||
* its alignment. scr_mem_free and scr_mem_realloc accept ANY pointer — a
|
||||
* block outside the allocator's reservation goes back to the system — so a
|
||||
* free site can be switched without proving where its pointers came from.
|
||||
* The converse does not hold: a block from scr_mem_alloc/calloc/realloc
|
||||
* must never reach the system free/realloc. Emitted code reaches the same
|
||||
* allocator through scr_rt_calloc/scr_rt_free.
|
||||
*
|
||||
* Compiled to the plain system calls under SCR_RC_AUDIT and AddressSanitizer
|
||||
* (the audit lane must see every logical free as a real free), on targets
|
||||
* without a cheap address-space reservation (wasm32, Windows, 32-bit), and
|
||||
* in thread-instanced library archives, where several runtime instances
|
||||
* run on embedder threads the runtime does not manage. Elsewhere the
|
||||
* allocator state is unsynchronized: runtime objects are only allocated
|
||||
* and freed on their runtime thread (native worker jobs never touch them).
|
||||
*
|
||||
* Worker executables (SCR_WORKERS) give every script thread its own
|
||||
* allocator: `scr_sa` is thread-local and describes the thread's ARENA, one
|
||||
* of SCR_SA_ARENAS equal slices of a single process-wide reservation, so
|
||||
* the owning arena of any block is address arithmetic. A thread claims an
|
||||
* arena on its first allocation and parks it (free lists and bump pointers
|
||||
* intact) when it exits; the next thread to claim it continues from there.
|
||||
* On Linux an exiting thread that carved much first returns the pages of
|
||||
* its arena that hold only free blocks (scr_alloc.c). Script objects stay on their thread, so a free
|
||||
* normally hits the caller's own arena inline. A block freed by another
|
||||
* thread goes to the owning arena's lock-free remote list, which the owner
|
||||
* drains on its slow path; published (immortal) objects are never freed,
|
||||
* so they stay valid after their allocating thread exits. Threads beyond
|
||||
* SCR_SA_ARENAS, or a refused reservation, use the system allocator. */
|
||||
#include <stdlib.h>
|
||||
#if defined(SCR_RC_AUDIT) || defined(__SANITIZE_ADDRESS__) || defined(__wasi__) || \
|
||||
defined(__wasm__) || defined(_WIN32) || UINTPTR_MAX != UINT64_MAX || \
|
||||
(defined(SCR_LIB) && defined(SCR_THREAD_INSTANCES))
|
||||
#define SCR_SMALL_ALLOC 0
|
||||
#elif defined(__has_feature)
|
||||
#if __has_feature(address_sanitizer)
|
||||
#define SCR_SMALL_ALLOC 0
|
||||
#else
|
||||
#define SCR_SMALL_ALLOC 1
|
||||
#endif
|
||||
#else
|
||||
#define SCR_SMALL_ALLOC 1
|
||||
#endif
|
||||
|
||||
void *scr_mem_realloc(void *p, size_t n);
|
||||
void *scr_rt_calloc(size_t n); /* emitted-code entry points */
|
||||
void scr_rt_free(void *p);
|
||||
/* Asks the allocator's pre-fault helper (scr_alloc.c) to fault the pages
|
||||
* of [lo, hi), aligned to 256 KiB, in ahead of use; builds without the
|
||||
* helper ignore it. */
|
||||
void scr_mem_prefault(void *lo, void *hi);
|
||||
|
||||
/* Size classes: requests up to 128 bytes take a class per 8-byte step
|
||||
* (small objects often sit just under a 16-byte boundary), larger ones a
|
||||
* class per 16-byte step up to 512. Class c below SCR_SA_FINE_MAX / 8
|
||||
* holds (c + 1) * 8-byte blocks, except that the class of 49 to 56-byte
|
||||
* requests (SCR_SA_WIDE) holds 64-byte blocks: a 56-byte block straddles a
|
||||
* cache line in 3 of every 4 slots, which costs more on scattered access
|
||||
* to records of that size than the smaller block saves. Widening the block
|
||||
* rather than sharing the 64-byte class keeps the class lookup free of a
|
||||
* special case. Slices start page-aligned, so a class whose block size is
|
||||
* a multiple of 16 keeps its blocks 16-byte aligned. */
|
||||
#define SCR_SA_FINE_MAX 128u /* the largest request with an 8-byte-step class */
|
||||
#define SCR_SA_WIDE 6u /* the class of 49 to 56-byte requests */
|
||||
#define SCR_SA_NCLASS 40u
|
||||
#define SCR_SA_MAX 512u
|
||||
|
||||
/* The class of a block of n bytes, 1 <= n <= SCR_SA_MAX. */
|
||||
static inline unsigned scr_sa_class(size_t n) {
|
||||
unsigned k = (unsigned)((n - 1) >> 3);
|
||||
return k < SCR_SA_FINE_MAX / 8 ? k : (k + SCR_SA_FINE_MAX / 8) >> 1;
|
||||
}
|
||||
/* The block size of class c. */
|
||||
static inline size_t scr_sa_class_size(unsigned c) {
|
||||
if (c < SCR_SA_FINE_MAX / 8) return (size_t)(c + 1 + (c == SCR_SA_WIDE)) * 8;
|
||||
return (size_t)(c + 1 - SCR_SA_FINE_MAX / 16) * 16;
|
||||
}
|
||||
|
||||
typedef struct ScrSaBlock {
|
||||
struct ScrSaBlock *next;
|
||||
} ScrSaBlock;
|
||||
|
||||
/* One reservation split into SCR_SA_NCLASS equal slices, one per class, so
|
||||
* a block's class is its slice index: (p - base) >> shift. span is 0 until
|
||||
* the first refill reserves (and stays 0 if the host refuses).
|
||||
*
|
||||
* scr_sa exists in EVERY build, and its layout is an ABI: the LLVM backend
|
||||
* inlines the fast paths into emitted constructors and releases
|
||||
* (llvm/alloc.ts) as `{ i64, i64, i32, [40 x ptr], [40 x ptr], [40 x ptr] }`
|
||||
* on 64-bit targets. Where SCR_SMALL_ALLOC is 0 the state is never written,
|
||||
* so the emitted fast paths see no free block, no bump room and an empty
|
||||
* span, and always take their out-of-line slow path (scr_rt_calloc /
|
||||
* scr_rt_free → the system allocator) — the sanitizer and RC-audit lanes
|
||||
* therefore see every logical allocation and free without the compiler
|
||||
* knowing which runtime it links against. The emitted fast paths also skip
|
||||
* scr_obj_alloc_note/scr_obj_free_note, which is sound because those are
|
||||
* no-ops whenever the allocator is live (SCR_RC_AUDIT implies
|
||||
* SCR_SMALL_ALLOC == 0). */
|
||||
typedef struct ScrSaState {
|
||||
uintptr_t base, span;
|
||||
unsigned shift;
|
||||
ScrSaBlock *free[SCR_SA_NCLASS];
|
||||
char *bump[SCR_SA_NCLASS], *lim[SCR_SA_NCLASS];
|
||||
} ScrSaState;
|
||||
/* Worker executables keep one allocator per script thread (see above). Its
|
||||
* state shares one thread-local block with the collector's live counts and
|
||||
* the weak dispose hook (ScrThreadHot below): on Darwin every distinct
|
||||
* thread-local costs a call per function that touches it, and allocation
|
||||
* and free paths touch all four. */
|
||||
#ifdef SCR_WORKERS
|
||||
/* The per-thread state of the allocation and free fast paths, in one
|
||||
* thread-local block; its layout is an ABI (llvm/alloc.ts addresses the
|
||||
* fields at fixed offsets from one llvm.threadlocal.address). */
|
||||
typedef struct ScrThreadHot {
|
||||
ScrSaState sa;
|
||||
size_t cyc_live;
|
||||
size_t cyc_old_freed;
|
||||
void (*weak_dispose_hook)(void *);
|
||||
} ScrThreadHot;
|
||||
extern _Thread_local ScrThreadHot scr_thread_hot;
|
||||
#define scr_sa (scr_thread_hot.sa)
|
||||
#define scr_cyc_live (scr_thread_hot.cyc_live)
|
||||
#define scr_cyc_old_freed (scr_thread_hot.cyc_old_freed)
|
||||
#define scr_weak_dispose_hook (scr_thread_hot.weak_dispose_hook)
|
||||
#if UINTPTR_MAX == UINT64_MAX
|
||||
_Static_assert(offsetof(ScrThreadHot, sa) == 0 && offsetof(ScrThreadHot, cyc_live) == 984 &&
|
||||
offsetof(ScrThreadHot, cyc_old_freed) == 992 &&
|
||||
offsetof(ScrThreadHot, weak_dispose_hook) == 1000,
|
||||
"llvm/alloc.ts addresses ScrThreadHot fields at these offsets");
|
||||
#endif
|
||||
#else
|
||||
extern ScrSaState scr_sa;
|
||||
#endif
|
||||
#if UINTPTR_MAX == UINT64_MAX
|
||||
_Static_assert(offsetof(ScrSaState, span) == 8 && offsetof(ScrSaState, shift) == 16 &&
|
||||
offsetof(ScrSaState, free) == 24 && offsetof(ScrSaState, bump) == 344 &&
|
||||
offsetof(ScrSaState, lim) == 664 && sizeof(ScrSaState) == 984,
|
||||
"llvm/alloc.ts inlines the small-object allocator against this layout");
|
||||
#endif
|
||||
#if defined(SCR_RC_AUDIT) && SCR_SMALL_ALLOC
|
||||
#error "emitted allocation fast paths skip the RC-audit object notes"
|
||||
#endif
|
||||
|
||||
#ifdef SCR_WORKERS
|
||||
/* A block outside the calling thread's arena: another arena's (remote
|
||||
* free) or the system's. */
|
||||
void scr_sa_free_foreign(void *p);
|
||||
/* The calling script thread is done with its arena (worker exit). */
|
||||
void scr_sa_thread_park(void);
|
||||
/* Waits until the arenas of exited threads are trimmed (tests). */
|
||||
void scr_sa_wait_trims(void);
|
||||
#define SCR_SA_FREE_FOREIGN(p) scr_sa_free_foreign(p)
|
||||
#else
|
||||
#define SCR_SA_FREE_FOREIGN(p) free(p)
|
||||
#endif
|
||||
|
||||
#if SCR_SMALL_ALLOC
|
||||
/* Refill miss: reserves on first use, falls back to the system allocator
|
||||
* for large blocks, n == 0, an exhausted slice, or a refused reservation. */
|
||||
void *scr_sa_slow(size_t n, bool zero);
|
||||
|
||||
static inline void *scr_sa_take(size_t n, bool zero) {
|
||||
if (n - 1 < SCR_SA_MAX) { /* n == 0 wraps to the slow path */
|
||||
unsigned c = scr_sa_class(n);
|
||||
ScrSaBlock *b = scr_sa.free[c];
|
||||
if (b) {
|
||||
scr_sa.free[c] = b->next;
|
||||
if (zero) memset(b, 0, n); /* recycled blocks are dirty */
|
||||
return b;
|
||||
}
|
||||
size_t sz = scr_sa_class_size(c);
|
||||
char *p = scr_sa.bump[c];
|
||||
if ((size_t)(scr_sa.lim[c] - p) >= sz) { /* fresh mmap memory is zero */
|
||||
scr_sa.bump[c] = p + sz;
|
||||
return p;
|
||||
}
|
||||
}
|
||||
return scr_sa_slow(n, zero);
|
||||
}
|
||||
|
||||
static inline void *scr_mem_alloc(size_t n) { return scr_sa_take(n, false); }
|
||||
static inline void *scr_mem_calloc(size_t n) { return scr_sa_take(n, true); }
|
||||
|
||||
static inline void scr_mem_free(void *p) {
|
||||
uintptr_t off = (uintptr_t)p - scr_sa.base;
|
||||
if (off < scr_sa.span) {
|
||||
ScrSaBlock *b = p;
|
||||
unsigned c = (unsigned)(off >> scr_sa.shift);
|
||||
b->next = scr_sa.free[c];
|
||||
scr_sa.free[c] = b;
|
||||
return;
|
||||
}
|
||||
SCR_SA_FREE_FOREIGN(p);
|
||||
}
|
||||
#else
|
||||
static inline void *scr_mem_alloc(size_t n) { return malloc(n); }
|
||||
static inline void *scr_mem_calloc(size_t n) { return calloc(1, n); }
|
||||
static inline void scr_mem_free(void *p) { free(p); }
|
||||
#endif
|
||||
|
||||
/* Inline twins of scr_cyc_alloc/scr_cyc_free for hot runtime allocation
|
||||
* sites (union boxes, arrays, boxes, closures, maps, dyn nodes). With a
|
||||
* constant size the size class and the zeroing fold at compile time. The
|
||||
* live count is exported for these and for the emitted inline paths
|
||||
* (llvm/alloc.ts), which do the same three things: header (the registry
|
||||
* id; the rest starts zero: SCR_CYC_BLACK, SCR_CYC_NURSERY, unbuffered),
|
||||
* live count, block. */
|
||||
#ifndef SCR_WORKERS
|
||||
extern SCR_TL size_t scr_cyc_live;
|
||||
extern SCR_TL size_t scr_cyc_old_freed;
|
||||
extern SCR_TL void (*scr_weak_dispose_hook)(void *);
|
||||
#endif
|
||||
static inline void *scr_cyc_alloc_inline(size_t size, ScrCycType *type) {
|
||||
if (size > SIZE_MAX - sizeof(ScrCycHdr)) scr_trap("scriptc: out of memory\n");
|
||||
uint32_t id = scr_cyc_type_id(type);
|
||||
ScrCycHdr *h = (ScrCycHdr *)scr_mem_calloc(sizeof(ScrCycHdr) + size);
|
||||
if (!h) scr_trap("scriptc: out of memory\n");
|
||||
h->id = (uint16_t)id;
|
||||
scr_cyc_live++;
|
||||
return h + 1;
|
||||
}
|
||||
static inline void scr_cyc_free_inline(void *obj) {
|
||||
if (scr_weak_dispose_hook) scr_weak_dispose_hook(obj);
|
||||
scr_cyc_live--;
|
||||
if (scr_cyc_gen(scr_cyc_hdr(obj)) == SCR_CYC_OLD) scr_cyc_old_freed++;
|
||||
scr_mem_free(scr_cyc_hdr(obj));
|
||||
}
|
||||
_Static_assert(SCR_CYC_NURSERY == 0, "zeroed cycle headers start in the nursery");
|
||||
_Static_assert(sizeof(((ScrCycHdr *)0)->gen) == 1 && SCR_CYC_OLD == 2,
|
||||
"LLVM backend's inline free compares an i8 generation against 2");
|
||||
|
||||
/* Dispose an object whose reference count already reached zero. The caller
|
||||
* removes its cycle candidate first. Nested disposals keep bounded stack
|
||||
* depth; deeper objects wait until the outer disposal drains the worklist.
|
||||
* The callback owns the object and must release its children and free it. */
|
||||
void scr_rc_destroy(void *obj, void (*destroy)(void *));
|
||||
|
||||
/* 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. Retains need no hook: a candidate
|
||||
* re-retained after it was buffered simply stays a candidate, and the pass
|
||||
* that walks it finds it externally referenced. */
|
||||
void scr_cyc_on_release(void *obj);
|
||||
void scr_cyc_on_dead(void *obj);
|
||||
|
||||
/* Inline halves of the hooks for runtime release paths, mirroring the
|
||||
* emitted ones (llvm/shapes.ts): a release of an already-buffered
|
||||
* object, and the death of an object that was never buffered, need no call.
|
||||
* Most releases of long-lived containers take the first early return. */
|
||||
static inline void scr_cyc_released(void *obj) {
|
||||
if (!scr_cyc_is_buffered(scr_cyc_hdr(obj))) scr_cyc_on_release(obj);
|
||||
}
|
||||
static inline void scr_cyc_dead(void *obj) {
|
||||
if (scr_cyc_is_buffered(scr_cyc_hdr(obj))) scr_cyc_on_dead(obj);
|
||||
}
|
||||
|
||||
/* 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);
|
||||
void scr_cyc_context_cleanup(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);
|
||||
|
||||
/* Publication (@scriptc/threads publish; scr_cycle.c): makes everything a
|
||||
* root reaches immortal and immutable. Compiler-emitted per-type walkers
|
||||
* mark their object (scr_pub_mark: false when already immortal) and push
|
||||
* their references; containers, strings and dyn subgraphs have runtime
|
||||
* walkers. scr_publish throws a TypeError, leaving the graph unchanged,
|
||||
* when it reaches an unpublishable dyn value. */
|
||||
typedef struct ScrPub ScrPub;
|
||||
typedef void (*ScrPubFn)(void *v, ScrPub *ctx);
|
||||
/* What a marked object is, for the RC audit's live counts (scr_pub_mark). */
|
||||
enum {
|
||||
SCR_PUB_KIND_LEAF = 0, /* not counted (bigints) */
|
||||
SCR_PUB_KIND_STRING = 1,
|
||||
SCR_PUB_KIND_ARRAY = 2,
|
||||
SCR_PUB_KIND_MAP = 3,
|
||||
SCR_PUB_KIND_OBJECT = 4,
|
||||
SCR_PUB_KIND_UNION = 5,
|
||||
SCR_PUB_KIND_DYN = 6,
|
||||
};
|
||||
void scr_publish(void *root, ScrPubFn fn);
|
||||
void scr_pub_push(ScrPub *ctx, void *v, ScrPubFn fn);
|
||||
bool scr_pub_mark(ScrPub *ctx, void *obj, bool cyc, int kind);
|
||||
void scr_pub_refuse(ScrPub *ctx, const char *message);
|
||||
void scr_pub_refuse_at(ScrPub *ctx, const char *message, const char *detail);
|
||||
void scr_pub_str_fn(void *v, ScrPub *ctx);
|
||||
void scr_pub_leaf_fn(void *v, ScrPub *ctx);
|
||||
void scr_pub_dyn_fn(void *v, ScrPub *ctx);
|
||||
void scr_pub_arr(void *v, ScrPub *ctx, ScrPubFn elem);
|
||||
void scr_pub_map(void *v, ScrPub *ctx, ScrPubFn key, ScrPubFn val);
|
||||
void scr_publish_refuse(const char *message);
|
||||
/* Writes into published objects (emitted guards): Node's frozen-object
|
||||
* TypeErrors. scr_throw_published_array answers false (and throws nothing)
|
||||
* when Node's operation would not write the array. */
|
||||
struct ScrArr;
|
||||
SCR_COLD void scr_throw_published_field(const char *prop, const char *owner);
|
||||
bool scr_throw_published_array(int op, const struct ScrArr *a, double index, double n);
|
||||
SCR_COLD void scr_throw_published_collection(bool set);
|
||||
|
||||
#endif /* SCR_RC_H */
|
||||
@@ -0,0 +1,113 @@
|
||||
/* scriptc runtime — node:readline (scr_readline.c) and the event-loop entry
|
||||
* points (scr_async.c). Included by scr_runtime.h, which says where new
|
||||
* declarations go. */
|
||||
#ifndef SCR_READLINE_H
|
||||
#define SCR_READLINE_H
|
||||
|
||||
#include "scr_events.h"
|
||||
|
||||
/* ── node:readline and node:readline/promises (scr_readline.c, linked
|
||||
* under the events gate) ─────────────────────────────────────────────
|
||||
* The non-terminal Interface (the file header has the model). Interfaces
|
||||
* are f64 handles. A stream input or output goes through ScrRlIo, which
|
||||
* scr_stream.c provides (scr_stream_rl_open), so stdin-only programs
|
||||
* never link the stream unit; the stream side feeds input through
|
||||
* scr_rl_input_*. Signal-taking entry points get addAbortListener as
|
||||
* `add` (scr_events_add_abort_listener), passed only by programs that
|
||||
* link the event-target unit. Listener event numbers: 0 'line',
|
||||
* 1 'close', 2 'pause', 3 'resume', 4 'error'. */
|
||||
typedef struct ScrRlIo {
|
||||
void (*pause)(void *s);
|
||||
void (*resume)(void *s);
|
||||
void (*detach)(void *s, double id); /* removes the interface's input listeners */
|
||||
void (*write)(void *s, ScrStr *text); /* borrows text */
|
||||
void (*release)(void *s);
|
||||
} ScrRlIo;
|
||||
double scr_rl_open(const ScrRlIo *io, void *in /*+1 or NULL: stdin*/, void *out /*+1 or NULL*/,
|
||||
bool out_stdout, double crlf_delay, ScrStr *prompt, ScrDyn *signal,
|
||||
ScrAbortAddFn add);
|
||||
void scr_rl_input_bytes(double id, ScrBytes *chunk);
|
||||
void scr_rl_input_str(double id, ScrStr *chunk);
|
||||
void scr_rl_input_end(double id);
|
||||
void scr_rl_input_error(double id, ScrError *err);
|
||||
/* question: throws ERR_USE_AFTER_CLOSE on a closed interface (may-throw);
|
||||
* fn(cb, +1 answer). The promise form never throws. */
|
||||
void scr_rl_question(double id, ScrStr *query, ScrClosure *cb /*moves*/,
|
||||
void (*fn)(ScrClosure *, ScrStr *));
|
||||
ScrPromise *scr_rl_question_promise(double id, ScrStr *query, ScrDyn *signal, ScrAbortAddFn add);
|
||||
void scr_rl_close(double id);
|
||||
void scr_rl_pause(double id); /* may throw ERR_USE_AFTER_CLOSE */
|
||||
void scr_rl_resume(double id); /* may throw ERR_USE_AFTER_CLOSE */
|
||||
void scr_rl_prompt(double id); /* may throw ERR_USE_AFTER_CLOSE */
|
||||
void scr_rl_set_prompt(double id, ScrStr *prompt);
|
||||
ScrStr *scr_rl_get_prompt(double id); /* +1 */
|
||||
void scr_rl_write(double id, ScrStr *data); /* may throw ERR_USE_AFTER_CLOSE */
|
||||
void scr_rl_on(double id, double ev, ScrClosure *cb /*moves*/, void *fn, bool once);
|
||||
void scr_rl_iter_open(double id);
|
||||
ScrPromise *scr_rl_iter_next(double id); /* +1 promise<dyn>: the line, or undefined */
|
||||
void scr_rl_iter_return(double id);
|
||||
/* Listener adapters: line/answer (fn(cb, +1 text)) and error listeners. */
|
||||
void scr_rl_answer_thunk0(ScrClosure *cb, ScrStr *answer);
|
||||
void scr_rl_answer_thunk_str(ScrClosure *cb, ScrStr *answer);
|
||||
void scr_rl_error_thunk(ScrClosure *cb, ScrError *err);
|
||||
void scr_rl_error_thunk0(ScrClosure *cb, ScrError *err);
|
||||
void scr_rl_answer_thunk_dyn(ScrClosure *cb, ScrStr *answer);
|
||||
void scr_rl_error_thunk_dyn(ScrClosure *cb, ScrError *err);
|
||||
/* createInterface with a stream input and/or output (scr_stream.c): in is
|
||||
* the input stream or NULL for process.stdin; out the output stream or
|
||||
* NULL (out_stdout picks process.stdout). Both borrowed, like prompt
|
||||
* (NULL: the default "> "). */
|
||||
double scr_stream_rl_open(ScrStream *in, ScrStream *out, bool out_stdout, double crlf_delay,
|
||||
ScrStr *prompt, ScrDyn *signal, ScrAbortAddFn add);
|
||||
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 SCR_TL void (*scr_process_exit_hook)(double code);
|
||||
extern SCR_TL 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);
|
||||
void scr_loop_commonjs_entry(void);
|
||||
bool scr_report_unhandled_rejections(void); /* true = exit 1 */
|
||||
void scr_discard_unhandled_rejections(void);
|
||||
long scr_abandoned_fiber_count(void);
|
||||
|
||||
|
||||
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);
|
||||
|
||||
#endif /* SCR_READLINE_H */
|
||||
@@ -0,0 +1,98 @@
|
||||
/* scriptc runtime — regular expressions (scr_regex.c). Included by
|
||||
* scr_runtime.h, which says where new declarations go. */
|
||||
#ifndef SCR_REGEX_H
|
||||
#define SCR_REGEX_H
|
||||
|
||||
#include "scr_array.h"
|
||||
|
||||
typedef int32_t (*ScrRegexMatcherFn)(const uint8_t *s, int32_t len, int32_t start,
|
||||
uint8_t **capture);
|
||||
|
||||
typedef struct ScrRegex {
|
||||
size_t rc; /* SIZE_MAX = immortal literal template */
|
||||
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 */
|
||||
double last_index;
|
||||
struct ScrRegex *literal; /* immutable bytecode owner for literal instances */
|
||||
void *native; /* one-byte-subject matcher built with bc (NULL: lre_exec only) */
|
||||
/* Build-time matcher of a literal template (NULL: none). Searches the
|
||||
* ASCII bytes s[0..len) from start (one anchored attempt when sticky);
|
||||
* on a match stores 2 * ncap pointers into s (NULL when unset) and
|
||||
* returns 1, otherwise 0. A negative len returns ncap. */
|
||||
ScrRegexMatcherFn matcher;
|
||||
} ScrRegex;
|
||||
|
||||
/* Backtrack stack of build-time regex matchers: grow *stack (initially the
|
||||
* matcher's inline buffer) to at least `need` words; traps on OOM. */
|
||||
void scr_regex_stack_grow(int32_t **stack, int32_t *cap, int32_t *inline_buf, int32_t need);
|
||||
void scr_regex_stack_free(int32_t *stack);
|
||||
|
||||
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);
|
||||
ScrRegex *scr_regex_literal(ScrRegex *literal); /* fresh state, shared cached bytecode */
|
||||
double scr_regex_last_index(ScrRegex *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); /* exec state transitions */
|
||||
/* s.match(re): +1 string[] of whole matches under /g, otherwise
|
||||
* [whole, ...captures] (nonparticipating captures hold ""), or NULL for
|
||||
* no match. Non-global /y uses exec state transitions. Borrows both. */
|
||||
ScrArr *scr_regex_match(ScrStr *s, ScrRegex *re);
|
||||
/* re.exec(s), string-first ABI: match slice with global/sticky lastIndex
|
||||
* transitions. Borrows both; +1 or NULL. */
|
||||
ScrArr *scr_regex_exec(ScrStr *s, ScrRegex *re);
|
||||
/* exec for a result read only through constant indices and null tests:
|
||||
* bit k of `mask` (k < 53) selects the captures that are materialized; the
|
||||
* rest, and the index/input properties, are left out. Same lastIndex
|
||||
* transitions and NULL result as scr_regex_exec. Borrows both; +1 or NULL. */
|
||||
ScrArr *scr_regex_exec_groups(ScrStr *s, ScrRegex *re, double mask);
|
||||
/* scr_regex_exec_groups whose captures in `lazy` (a subset of `mask`) are
|
||||
* read only through scr_regex_group_eq/_number/_parse_int: they may stay
|
||||
* spans of the subject instead of strings. */
|
||||
ScrArr *scr_regex_exec_spans(ScrStr *s, ScrRegex *re, double mask, double lazy);
|
||||
/* s.match(re) read only through constant indices and null tests: a global
|
||||
* regex answers scr_regex_match; otherwise scr_regex_exec_spans. */
|
||||
ScrArr *scr_regex_match_groups(ScrStr *s, ScrRegex *re, double mask, double lazy);
|
||||
/* Reads of a lazy capture k of such a result (borrowed): `capture === lit`,
|
||||
* Number(capture) and parseInt(capture, radix). */
|
||||
bool scr_regex_group_eq(ScrArr *m, double k, ScrStr *lit);
|
||||
double scr_regex_group_number(ScrArr *m, double k);
|
||||
double scr_regex_group_parse_int(ScrArr *m, double k, double radix);
|
||||
/* 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);
|
||||
|
||||
#endif /* SCR_REGEX_H */
|
||||
@@ -0,0 +1,111 @@
|
||||
/* scriptc runtime — counted lifetime regions (scr_region.c). Included by
|
||||
* scr_runtime.h, which says where new declarations go. */
|
||||
#ifndef SCR_REGION_H
|
||||
#define SCR_REGION_H
|
||||
|
||||
#include "scr_core.h"
|
||||
|
||||
/* ── counted lifetime regions (scr_region.c) ───────────────────────────
|
||||
* Programs that construct a LifetimeRegion (@scriptc/experimental) allocate
|
||||
* every ProgramLifetime instance in the thread's CURRENT region instead of
|
||||
* the small-object allocator. A region owns SCR_RGN_CHUNK-aligned chunks;
|
||||
* each class packs its instances into segments of a chunk, so a region's
|
||||
* objects are enumerable without a header. Instead of a per-object count,
|
||||
* the class's retain and release adjust the chunk's first word (`count`);
|
||||
* with the first reference each object starts with, it gives the number of
|
||||
* references to the chunk's objects.
|
||||
*
|
||||
* release() condemns a region. A condemned region is freed only when a walk
|
||||
* of its objects, and of the ordinary objects they own, explains every
|
||||
* counted reference: trial deletion restricted to the region. References
|
||||
* the walk cannot see (locals, globals, objects outside the walked graph,
|
||||
* shapes it does not know) stay unexplained and keep the region. Freeing
|
||||
* releases every reference field of every object, which tears the owned
|
||||
* ordinary objects down through their normal paths, runs the cycle
|
||||
* collector for the owned cycles, and then requires every count to be 0.
|
||||
* Region 0 of each thread (the default) is never freed: programs that do
|
||||
* not use regions keep ProgramLifetime's behavior.
|
||||
*
|
||||
* Only references to objects of regions that can be freed are counted:
|
||||
* until the process creates its first region, scr_region_counting is 0 and
|
||||
* retains and releases touch nothing, and chunks of the default regions
|
||||
* have no owner, so references to their objects are never counted. Every
|
||||
* reference to an object of a non-default region is created after that
|
||||
* region exists, so its counts are exact.
|
||||
*
|
||||
* The layouts below are an ABI with the emitted code (llvm/regions.ts):
|
||||
* the inline allocation fast path bumps the class's ScrRgnCursor, and the
|
||||
* retain/release bodies test scr_region_counting, then the chunk's owner,
|
||||
* then adjust its count. */
|
||||
#define SCR_RGN_CHUNK_SHIFT 21
|
||||
#define SCR_RGN_CHUNK ((size_t)1 << SCR_RGN_CHUNK_SHIFT)
|
||||
|
||||
typedef struct ScrRgnWalk ScrRgnWalk;
|
||||
|
||||
/* One per ProgramLifetime class, emitted as a mutable global: `slot` is
|
||||
* assigned process-wide on the first allocation (-1 until then). */
|
||||
typedef struct ScrRgnClass {
|
||||
size_t size; /* instance size (the stride rounds it to 8) */
|
||||
void (*visit)(void *obj, ScrRgnWalk *w); /* reports each reference field */
|
||||
void (*release_fields)(void *obj); /* releases each reference field */
|
||||
const char *name;
|
||||
int32_t slot;
|
||||
} ScrRgnClass;
|
||||
|
||||
/* The calling thread's allocation cursor for one class in its current
|
||||
* region: emitted per class (thread-local in worker executables). NULL
|
||||
* fields send the next allocation to scr_region_alloc. */
|
||||
typedef struct ScrRgnCursor {
|
||||
char *bump, *limit;
|
||||
} ScrRgnCursor;
|
||||
|
||||
/* The emitted type table (terminated by a NULL release): the release entry
|
||||
* point a container stores for a type (shapes.ts vAdapters), and the walker
|
||||
* of its objects. A NULL walker marks a ProgramLifetime class. */
|
||||
typedef struct ScrRgnType {
|
||||
void (*release)(void *);
|
||||
void (*walk)(void *obj, ScrRgnWalk *w);
|
||||
} ScrRgnType;
|
||||
|
||||
typedef struct ScrRgnChunk {
|
||||
int64_t count; /* word 0: references to this chunk's objects, less one per object */
|
||||
void *owner; /* word 1: the owning thread; NULL in default regions */
|
||||
struct ScrRegion *region;
|
||||
struct ScrRgnChunk *next;
|
||||
char *bump; /* the next free segment byte */
|
||||
int64_t found; /* references the last walk saw into this chunk */
|
||||
} ScrRgnChunk;
|
||||
_Static_assert(offsetof(ScrRgnChunk, count) == 0 && offsetof(ScrRgnChunk, owner) == sizeof(int64_t),
|
||||
"llvm/regions.ts addresses the chunk count at 0 and the owner at 8");
|
||||
|
||||
static inline ScrRgnChunk *scr_rgn_chunk_of(const void *obj) {
|
||||
return (ScrRgnChunk *)((uintptr_t)obj & ~(uintptr_t)(SCR_RGN_CHUNK - 1));
|
||||
}
|
||||
|
||||
/* 1 once any thread has created a region (never reset); emitted retains
|
||||
* and releases count nothing while it is 0. */
|
||||
extern _Atomic uint8_t scr_region_counting;
|
||||
|
||||
/* The allocation slow path: a zeroed instance of `cls` in the current
|
||||
* region. Refills `cursor`. */
|
||||
void *scr_region_alloc(ScrRgnCursor *cursor, ScrRgnClass *cls);
|
||||
/* new LifetimeRegion(): a fresh region id (ids are never reused). `types`
|
||||
* is the program's constant type table. Retries condemned regions. */
|
||||
double scr_region_create(const ScrRgnType *types);
|
||||
/* run(fn): makes region `id` current until the matching leave. An id this
|
||||
* thread does not own (another thread's, or a freed region's) selects the
|
||||
* thread's default region. Calls nest. */
|
||||
void scr_region_enter(double id);
|
||||
void scr_region_leave(void);
|
||||
/* release(): condemns region `id` and tries to free it. */
|
||||
void scr_region_release(double id);
|
||||
|
||||
/* Walk callbacks for emitted visits and walkers. scr_region_ref reports a
|
||||
* reference to a ProgramLifetime instance; scr_region_slot reports a
|
||||
* reference of the type whose container release entry point is `release`
|
||||
* (unknown entry points are not walked, which keeps their references
|
||||
* unexplained). Both tolerate NULL. */
|
||||
void scr_region_ref(ScrRgnWalk *w, void *obj);
|
||||
void scr_region_slot(ScrRgnWalk *w, void *obj, void (*release)(void *));
|
||||
|
||||
#endif /* SCR_REGION_H */
|
||||
+64
-9139
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,340 @@
|
||||
/* scriptc runtime — node:stream (scr_stream.c). Included by scr_runtime.h,
|
||||
* which says where new declarations go. */
|
||||
#ifndef SCR_STREAM_H
|
||||
#define SCR_STREAM_H
|
||||
|
||||
#include "scr_events_emitter.h"
|
||||
|
||||
/* ── 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) */
|
||||
/* events.addAbortListener (scr_events_add_abort_listener): passed to the
|
||||
* signal-taking stream and readline entry points by programs that link
|
||||
* the event-target unit. */
|
||||
typedef ScrDyn *(*ScrAbortAddFn)(ScrDyn *signal, ScrDyn *listener);
|
||||
|
||||
typedef struct ScrStream {
|
||||
size_t rc;
|
||||
const ScrVt *vt;
|
||||
ScrEeReg *reg; /* ScrEmitter prefix */
|
||||
const char *cls; /* ScrEmitter prefix (display name) */
|
||||
ScrStreamState *st;
|
||||
} ScrStream;
|
||||
|
||||
extern SCR_TL 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 "not supported yet" 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);
|
||||
ScrStream *scr_stream_on_dyn(ScrStream *s, ScrStr *name, ScrDyn *cb, bool once, bool prepend);
|
||||
ScrDyn *scr_stream_read_dyn(ScrStream *s, ScrDyn *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, arrayBuffer
|
||||
* a +1 ArrayBuffer dyn over them. 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);
|
||||
ScrPromise *scr_sc_array_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);
|
||||
/* stream.addAbortSignal(signal, s) and the constructors' signal option
|
||||
* (from_option: a falsy signal is ignored). Borrows; answers s +1, or
|
||||
* NULL with ERR_INVALID_ARG_TYPE pending. */
|
||||
ScrStream *scr_stream_add_abort_signal(ScrStream *s, ScrDyn *signal, bool from_option,
|
||||
ScrAbortAddFn add);
|
||||
/* finished(s, options[, cb]) — Node's eos with its listeners and state
|
||||
* predicates: readable/writable are -1 (the default), 0 or 1; on_error is
|
||||
* options.error !== false; signal is NULL or borrowed (ERR_INVALID_ARG_TYPE
|
||||
* pending and NULL answered for a non-signal). The callback forms answer
|
||||
* the +1 cleanup closure (cb moves; the typed form takes its invoke
|
||||
* adapter); the promise form rejects instead of throwing. */
|
||||
ScrClosure *scr_stream_finished_opts(ScrStream *s, ScrClosure *cb, ScrStreamErrInv inv,
|
||||
double readable, double writable, bool on_error,
|
||||
ScrDyn *signal, ScrAbortAddFn add);
|
||||
ScrClosure *scr_stream_finished_opts_dyn(ScrStream *s, ScrDyn *cb, double readable,
|
||||
double writable, bool on_error, ScrDyn *signal,
|
||||
ScrAbortAddFn add);
|
||||
ScrPromise *scr_sp_finished_opts(ScrStream *s, double readable, double writable, bool on_error,
|
||||
bool cleanup, ScrDyn *signal, ScrAbortAddFn add);
|
||||
/* stream/promises pipeline(...streams, { signal, end }): end false leaves
|
||||
* the destination open; a non-signal value rejects with
|
||||
* ERR_INVALID_ARG_TYPE. Streams borrowed. */
|
||||
ScrPromise *scr_sp_pipeline_opts(double n, ScrStream **streams, bool end, ScrDyn *signal,
|
||||
ScrAbortAddFn add);
|
||||
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);
|
||||
|
||||
#endif /* SCR_STREAM_H */
|
||||
@@ -3302,6 +3302,9 @@ static SCR_TL uint16_t scr_coll_ascii_primaries[128];
|
||||
* the single-pass primary comparison. */
|
||||
static SCR_TL bool scr_coll_ascii_one_pass;
|
||||
static SCR_TL bool scr_coll_ascii_levels;
|
||||
/* The covered ASCII bytes are exactly TAB..CR and 0x20..0x7E: the
|
||||
* single-pass comparison then checks eight bytes at a time. */
|
||||
static SCR_TL bool scr_coll_ascii_printable;
|
||||
/* Code points below 0x180 that start a contraction, as a bit set; higher
|
||||
* ones are looked up in scr_coll_contractions. */
|
||||
#define SCR_COLL_BITS_LIMIT 0x180
|
||||
@@ -3335,6 +3338,10 @@ static __attribute__((noinline)) void scr_coll_ascii_init(void) {
|
||||
for (uint32_t c = 0; c < 128; c++) scr_coll_ascii_plain[c] = scr_coll_ascii_table[c].ok && !in_contraction[c];
|
||||
scr_coll_ascii_one_pass = !zero_primary && !contraction;
|
||||
scr_coll_ascii_levels = !contraction;
|
||||
uint64_t printable[2] = {0, 0};
|
||||
for (uint32_t c = 0; c < 128; c++)
|
||||
if ((c >= 0x09 && c <= 0x0d) || (c >= 0x20 && c <= 0x7e)) printable[c >> 6] |= UINT64_C(1) << (c & 63);
|
||||
scr_coll_ascii_printable = scr_coll_ascii_mask[0] == printable[0] && scr_coll_ascii_mask[1] == printable[1];
|
||||
for (uint32_t c = 0; c < SCR_COLL_SCAN_LIMIT; c++) {
|
||||
const ScrCollEntry *entry = scr_coll_lookup(c);
|
||||
uint8_t klass = entry == NULL || entry->n == 255 ? 2 : 0;
|
||||
@@ -3537,7 +3544,7 @@ static bool scr_coll_ascii_tail(const unsigned char *p, size_t from, size_t len)
|
||||
* over the rest of both strings, because an uncovered byte anywhere moves
|
||||
* the whole comparison to code-point order. Returns -1 or 1, 0 when the
|
||||
* primaries are all equal, or 2 when a byte is not covered. */
|
||||
static int scr_coll_ascii_primary(const ScrStr *a, const ScrStr *b) {
|
||||
static int scr_coll_ascii_primary_bytes(const ScrStr *a, const ScrStr *b) {
|
||||
const unsigned char *x = (const unsigned char *)a->data, *y = (const unsigned char *)b->data;
|
||||
size_t n = a->len, m = b->len, k = n < m ? n : m, i = 0;
|
||||
int r = 0;
|
||||
@@ -3558,6 +3565,104 @@ static int scr_coll_ascii_primary(const ScrStr *a, const ScrStr *b) {
|
||||
return r;
|
||||
}
|
||||
|
||||
#define SCR_COLL_ONES UINT64_C(0x0101010101010101)
|
||||
#define SCR_COLL_HIGHS (SCR_COLL_ONES * 0x80)
|
||||
|
||||
/* The 0 < n < 8 bytes at offset i of s's data in one word, the other lanes
|
||||
* zero. On little-endian targets: the eight bytes ending at the last of
|
||||
* them, shifted down; they start in the string's header when i + n < 8, so
|
||||
* the load stays inside the object. Elsewhere: fixed-size loads (a
|
||||
* variable-size memcpy is a library call). */
|
||||
_Static_assert(offsetof(ScrStr, data) >= 8, "a short load may start in the header");
|
||||
static inline uint64_t scr_coll_partial(const ScrStr *s, size_t i, size_t n) {
|
||||
#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
uint64_t v;
|
||||
memcpy(&v, (const unsigned char *)s + offsetof(ScrStr, data) + i + n - 8, 8);
|
||||
return v >> (8 * (8 - n));
|
||||
#else
|
||||
const unsigned char *p = (const unsigned char *)s->data + i;
|
||||
uint64_t w = 0;
|
||||
size_t o = 0;
|
||||
if (n & 4) {
|
||||
uint32_t v;
|
||||
memcpy(&v, p, 4);
|
||||
w = v;
|
||||
o = 4;
|
||||
}
|
||||
if (n & 2) {
|
||||
uint16_t v;
|
||||
memcpy(&v, p + o, 2);
|
||||
w |= (uint64_t)v << (8 * o);
|
||||
o += 2;
|
||||
}
|
||||
if (n & 1) w |= (uint64_t)p[o] << (8 * o);
|
||||
return w;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Up to eight bytes at offset i of s's data (n > 0 of them) as a word,
|
||||
* padded with spaces (a covered byte). */
|
||||
static inline uint64_t scr_coll_word(const ScrStr *s, size_t i, size_t n) {
|
||||
uint64_t w;
|
||||
if (n >= 8) {
|
||||
memcpy(&w, s->data + i, 8);
|
||||
return w;
|
||||
}
|
||||
return scr_coll_partial(s, i, n) | (SCR_COLL_ONES * 0x20) << (8 * n);
|
||||
}
|
||||
|
||||
/* Whether a byte of w is outside 0x20..0x7E: below 0x20, DEL, or non-ASCII. */
|
||||
static inline bool scr_coll_word_unusual(uint64_t w) {
|
||||
return (((w - SCR_COLL_ONES * 0x20) & ~w) | (w + SCR_COLL_ONES) | w) & SCR_COLL_HIGHS;
|
||||
}
|
||||
|
||||
/* 0 when s's bytes from offset `from` on are all 0x20..0x7E, 2 when one of
|
||||
* them is non-ASCII, 1 otherwise. */
|
||||
static inline int scr_coll_printable_tail(const ScrStr *s, size_t from) {
|
||||
for (size_t i = from; i < s->len; i += 8) {
|
||||
uint64_t w = scr_coll_word(s, i, s->len - i);
|
||||
if (w & SCR_COLL_HIGHS) return 2;
|
||||
if (scr_coll_word_unusual(w)) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* scr_coll_ascii_primary_bytes eight bytes at a time when the covered ASCII
|
||||
* bytes are TAB..CR and 0x20..0x7E (scr_coll_ascii_printable): words of
|
||||
* printable bytes need no per-byte check, and equal words have equal
|
||||
* primaries. A non-ASCII byte anywhere answers 2 at once; a word with any
|
||||
* other byte outside 0x20..0x7E (a tab, say) sends the comparison to the
|
||||
* per-byte pass. */
|
||||
static int scr_coll_ascii_primary(const ScrStr *a, const ScrStr *b) {
|
||||
if (!scr_coll_ascii_printable) return scr_coll_ascii_primary_bytes(a, b);
|
||||
const unsigned char *x = (const unsigned char *)a->data, *y = (const unsigned char *)b->data;
|
||||
size_t n = a->len, m = b->len, k = n < m ? n : m, i = 0;
|
||||
int r = 0;
|
||||
for (; i < k; i += 8) {
|
||||
uint64_t wx = scr_coll_word(a, i, k - i), wy = scr_coll_word(b, i, k - i);
|
||||
if ((wx | wy) & SCR_COLL_HIGHS) return 2; /* a non-ASCII byte */
|
||||
if (scr_coll_word_unusual(wx) || scr_coll_word_unusual(wy)) return scr_coll_ascii_primary_bytes(a, b);
|
||||
if (wx == wy) continue;
|
||||
size_t end = k - i < 8 ? k : i + 8;
|
||||
for (size_t j = i; j < end; j++) {
|
||||
unsigned pc = scr_coll_ascii_primaries[x[j]], pd = scr_coll_ascii_primaries[y[j]];
|
||||
if (pc != pd) {
|
||||
r = pc < pd ? -1 : 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (r != 0) {
|
||||
i = end;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i > k) i = k;
|
||||
int tail = scr_coll_printable_tail(a, i) | scr_coll_printable_tail(b, i);
|
||||
if (tail != 0) return tail & 2 ? 2 : scr_coll_ascii_primary_bytes(a, b);
|
||||
if (r == 0 && n != m) r = n < m ? -1 : 1;
|
||||
return r;
|
||||
}
|
||||
|
||||
static bool scr_coll_ascii_covered(const ScrCollAscii *table, const ScrStr *s) {
|
||||
const unsigned char *p = (const unsigned char *)s->data;
|
||||
bool ok = true;
|
||||
@@ -3585,22 +3690,28 @@ static int scr_coll_ascii_level(const ScrCollAscii *table, const ScrStr *a, cons
|
||||
}
|
||||
}
|
||||
|
||||
double scr_str_locale_compare(ScrStr *a, ScrStr *b) {
|
||||
/* Equal bytes have equal elements at every level. */
|
||||
if (a == b || (a->len == b->len && memcmp(a->data, b->data, a->len) == 0)) return 0;
|
||||
const ScrCollAscii *table = scr_coll_ascii();
|
||||
if (scr_coll_ascii_one_pass) {
|
||||
int r = scr_coll_ascii_primary(a, b);
|
||||
if (r != 2) {
|
||||
for (int level = 1; level < 3 && r == 0; level++) r = scr_coll_ascii_level(table, a, b, level);
|
||||
return (double)r;
|
||||
}
|
||||
} else if (scr_coll_ascii_levels && scr_coll_ascii_covered(table, a) &&
|
||||
scr_coll_ascii_covered(table, b)) {
|
||||
int r = 0;
|
||||
for (int level = 0; level < 3 && r == 0; level++) r = scr_coll_ascii_level(table, a, b, level);
|
||||
return (double)r;
|
||||
}
|
||||
/* Whether two strings have the same bytes; strings of up to 16 bytes
|
||||
* compare as two words. */
|
||||
static inline bool scr_coll_same_bytes(const ScrStr *a, const ScrStr *b) {
|
||||
size_t n = a->len;
|
||||
if (n != b->len) return false;
|
||||
const unsigned char *x = (const unsigned char *)a->data, *y = (const unsigned char *)b->data;
|
||||
if (n > 16) return memcmp(x, y, n) == 0;
|
||||
if (n < 8) return n == 0 || scr_coll_partial(a, 0, n) == scr_coll_partial(b, 0, n);
|
||||
uint64_t x0, y0, x1, y1;
|
||||
memcpy(&x0, x, 8);
|
||||
memcpy(&y0, y, 8);
|
||||
memcpy(&x1, x + n - 8, 8);
|
||||
memcpy(&y1, y + n - 8, 8);
|
||||
return x0 == y0 && x1 == y1;
|
||||
}
|
||||
|
||||
/* Strings outside the ASCII paths: a scan finds uncovered characters
|
||||
* (code-point order) and characters that need element lists. Out of line,
|
||||
* so the ASCII paths do not set up the element buffers' stack frame. */
|
||||
static __attribute__((noinline)) double scr_coll_compare_lists(const ScrCollAscii *table, ScrStr *a,
|
||||
ScrStr *b) {
|
||||
if (scr_coll_same_bytes(a, b)) return 0;
|
||||
int scan_a = scr_coll_scan(a);
|
||||
if (scan_a == 2) return scr_str_cmp(a, b) < 0 ? -1 : 1; /* bytes differ (checked above) */
|
||||
int scan_b = scr_coll_scan(b);
|
||||
@@ -3628,3 +3739,26 @@ double scr_str_locale_compare(ScrStr *a, ScrStr *b) {
|
||||
if (eb.e != eb.inline_buf) free(eb.e);
|
||||
return (double)r;
|
||||
}
|
||||
|
||||
/* Equal bytes have equal elements at every level, so each path checks for
|
||||
* them before comparing secondary weights; the single-pass ASCII path does
|
||||
* so only after the primaries tie. */
|
||||
double scr_str_locale_compare(ScrStr *a, ScrStr *b) {
|
||||
if (a == b) return 0;
|
||||
const ScrCollAscii *table = scr_coll_ascii();
|
||||
if (scr_coll_ascii_one_pass) {
|
||||
int r = scr_coll_ascii_primary(a, b);
|
||||
if (r != 2) {
|
||||
if (r == 0 && scr_coll_same_bytes(a, b)) return 0;
|
||||
for (int level = 1; level < 3 && r == 0; level++) r = scr_coll_ascii_level(table, a, b, level);
|
||||
return (double)r;
|
||||
}
|
||||
} else if (scr_coll_ascii_levels && scr_coll_ascii_covered(table, a) &&
|
||||
scr_coll_ascii_covered(table, b)) {
|
||||
if (scr_coll_same_bytes(a, b)) return 0;
|
||||
int r = 0;
|
||||
for (int level = 0; level < 3 && r == 0; level++) r = scr_coll_ascii_level(table, a, b, level);
|
||||
return (double)r;
|
||||
}
|
||||
return scr_coll_compare_lists(table, a, b);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
/* scriptc runtime — strings: layout, lone surrogates (WTF-8), concatenation
|
||||
* and comparison (scr_string.c). Included by scr_runtime.h, which says where
|
||||
* new declarations go. */
|
||||
#ifndef SCR_STRING_H
|
||||
#define SCR_STRING_H
|
||||
|
||||
#include "scr_rc.h"
|
||||
|
||||
/* ── 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. Builders and concat results may have spare capacity 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.
|
||||
*
|
||||
* On 64-bit little-endian targets the capacity word is split: the low half
|
||||
* is cap (strings never exceed SCR_STR_MAX_CAP bytes; JavaScript strings are
|
||||
* far shorter) and the high half caches the string's Map key hash (0 = not
|
||||
* computed). Emitted literals and runtime statics still initialize one
|
||||
* size-sized word to len, which leaves the hash half zero. Only uniquely
|
||||
* owned heap strings are ever mutated, and every such path clears the hash
|
||||
* (scr_str_hash_forget); immortal strings are never written, so they always
|
||||
* hash on demand.
|
||||
*
|
||||
* Bit 31 of the capacity word (on every target) is the proven-ASCII fact:
|
||||
* when set, every byte is below 0x80, so UTF-16 indices equal byte offsets
|
||||
* and .length == len. A clear bit means "not known", never "non-ASCII":
|
||||
* constructors set it only where it is cheap, and string reads that prove
|
||||
* ASCII set it later. Capacity therefore stops at 2^31 - 1 bytes, which
|
||||
* still holds every string V8 can represent (2^29 - 24 UTF-16 units, at
|
||||
* most three UTF-8 bytes each). Generated code reads the bit for inline
|
||||
* charCodeAt/length (llvm/strings.ts); emitted literals and runtime
|
||||
* statics set it in their capacity word. scr_str_hash_forget clears it
|
||||
* with the hash: every in-place mutation already forgets cached facts.
|
||||
*/
|
||||
#if SIZE_MAX > UINT32_MAX && defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
#define SCR_STR_HASH_CACHE 1
|
||||
#else
|
||||
#define SCR_STR_HASH_CACHE 0
|
||||
#endif
|
||||
#define SCR_STR_MAX_CAP ((size_t)0x7fffffff)
|
||||
typedef struct ScrStr {
|
||||
size_t rc;
|
||||
size_t len;
|
||||
#if SCR_STR_HASH_CACHE
|
||||
uint32_t cap : 31;
|
||||
uint32_t ascii : 1;
|
||||
uint32_t hash;
|
||||
#else
|
||||
size_t cap : 31;
|
||||
size_t ascii : 1;
|
||||
#endif
|
||||
char data[];
|
||||
} ScrStr;
|
||||
|
||||
/* The capacity word of a static string: cap == len, the proven-ASCII bit,
|
||||
* plus the precomputed 32-bit key hash where the header caches one (0
|
||||
* leaves it to be computed). */
|
||||
#define SCR_STR_ASCII_BIT ((size_t)1 << 31)
|
||||
#if SCR_STR_HASH_CACHE
|
||||
#define SCR_STR_CAP_WORD(len, hash32, ascii) \
|
||||
((size_t)(len) | ((ascii) ? SCR_STR_ASCII_BIT : 0) | \
|
||||
((size_t)(uint32_t)(hash32) << 32))
|
||||
#else
|
||||
#define SCR_STR_CAP_WORD(len, hash32, ascii) ((size_t)(len) | ((ascii) ? SCR_STR_ASCII_BIT : 0))
|
||||
#endif
|
||||
|
||||
void scr_str_hash_prime(ScrStr *s); /* scr_map.c; before a string becomes immortal */
|
||||
/* Forget every cached content fact (key hash, proven ASCII) before a
|
||||
* uniquely owned string's bytes change. */
|
||||
static inline void scr_str_hash_forget(ScrStr *s) {
|
||||
#if SCR_STR_HASH_CACHE
|
||||
s->hash = 0;
|
||||
#endif
|
||||
s->ascii = 0;
|
||||
}
|
||||
|
||||
/* True only when every byte is proven below 0x80 (see above). */
|
||||
static inline bool scr_str_known_ascii(const ScrStr *s) { return s->ascii; }
|
||||
|
||||
/* Record a proven-ASCII fact. Immortal strings may live in read-only
|
||||
* memory and are shared across threads, so they are never written. */
|
||||
static inline void scr_str_note_ascii(ScrStr *s) {
|
||||
if (s->rc != SIZE_MAX) s->ascii = 1;
|
||||
}
|
||||
|
||||
/* memcpy for the short, variable-length copies of string construction
|
||||
* (identifiers, keys, number text): fixed-size overlapping moves instead of
|
||||
* a libc call. Longer spans keep the platform's bulk copy. */
|
||||
static inline void scr_copy_bytes(char *dst, const char *src, size_t n) {
|
||||
if (n <= 16) {
|
||||
if (n >= 8) {
|
||||
uint64_t a, b;
|
||||
memcpy(&a, src, 8);
|
||||
memcpy(&b, src + n - 8, 8);
|
||||
memcpy(dst, &a, 8);
|
||||
memcpy(dst + n - 8, &b, 8);
|
||||
} else if (n >= 4) {
|
||||
uint32_t a, b;
|
||||
memcpy(&a, src, 4);
|
||||
memcpy(&b, src + n - 4, 4);
|
||||
memcpy(dst, &a, 4);
|
||||
memcpy(dst + n - 4, &b, 4);
|
||||
} else if (n) {
|
||||
dst[0] = src[0];
|
||||
dst[n / 2] = src[n / 2];
|
||||
dst[n - 1] = src[n - 1];
|
||||
}
|
||||
return;
|
||||
}
|
||||
memcpy(dst, src, n);
|
||||
}
|
||||
|
||||
/* Whether a byte span is all ASCII: 32 bytes per step, stopping at the
|
||||
* first chunk with a high bit. */
|
||||
static inline bool scr_bytes_all_ascii(const char *p, size_t n) {
|
||||
const uint64_t high = UINT64_C(0x8080808080808080);
|
||||
size_t i = 0;
|
||||
for (; n - i >= 32; i += 32) {
|
||||
uint64_t w[4];
|
||||
memcpy(w, p + i, sizeof w);
|
||||
if ((w[0] | w[1] | w[2] | w[3]) & high) return false;
|
||||
}
|
||||
uint64_t acc = 0;
|
||||
for (; n - i >= 8; i += 8) {
|
||||
uint64_t w;
|
||||
memcpy(&w, p + i, 8);
|
||||
acc |= w;
|
||||
}
|
||||
for (; i < n; i++) acc |= (unsigned char)p[i];
|
||||
return (acc & high) == 0;
|
||||
}
|
||||
|
||||
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: 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) */
|
||||
|
||||
/* ── lone surrogates: canonical WTF-8 storage ─────────────────────────
|
||||
* String storage is UTF-8 extended to lone UTF-16 surrogates: an unpaired
|
||||
* surrogate is its generalized 3-byte encoding ED A0..BF 80..BF, while a
|
||||
* high surrogate directly followed by a low one is always stored as the
|
||||
* pair's 4-byte UTF-8 sequence. Canonical storage keeps byte equality,
|
||||
* hashing and searching exact. Builders preserve it by fusing a trailing
|
||||
* lone high surrogate with a leading lone low one (scr_wtf8_joins). A lone
|
||||
* surrogate is exactly as long as U+FFFD, so byte sinks transcode to UTF-8
|
||||
* (scr_str_utf8) without changing any length or offset. */
|
||||
|
||||
/* Set once a lone surrogate may exist anywhere in the process: program
|
||||
* startup stamps it when a literal holds one, and every runtime producer
|
||||
* notes it (scr_wtf8_put, scr_wtf8_note). Until then no string can hold a
|
||||
* lone surrogate, so builders and byte sinks skip all surrogate work. It is
|
||||
* process-wide (strings cross threads) and never cleared. */
|
||||
extern bool scr_wtf8_lone;
|
||||
static inline bool scr_wtf8_any(void) { return __atomic_load_n(&scr_wtf8_lone, __ATOMIC_RELAXED); }
|
||||
void scr_wtf8_note(void);
|
||||
|
||||
/* Whether p starts a lone surrogate (p[1] must be readable: string data
|
||||
* is NUL-terminated). */
|
||||
static inline bool scr_wtf8_surrogate_at(const char *p) {
|
||||
return (unsigned char)p[0] == 0xED && (unsigned char)p[1] >= 0xA0;
|
||||
}
|
||||
|
||||
/* Whether p starts a lone low surrogate (p[1] readable, as above). */
|
||||
static inline bool scr_wtf8_low_at(const char *p) {
|
||||
return (unsigned char)p[0] == 0xED && (unsigned char)p[1] >= 0xB0;
|
||||
}
|
||||
|
||||
/* Whether appending b to a puts a lone high surrogate directly before a
|
||||
* lone low surrogate. Tests the first byte of b before anything else, so
|
||||
* ordinary text costs one compare. */
|
||||
static inline bool scr_wtf8_joins(const char *a, size_t alen, const char *b, size_t blen) {
|
||||
return __builtin_expect(scr_wtf8_any() && (unsigned char)b[0] == 0xED && blen >= 3 &&
|
||||
(unsigned char)b[1] >= 0xB0 && alen >= 3 &&
|
||||
(unsigned char)a[alen - 3] == 0xED &&
|
||||
((unsigned char)a[alen - 2] & 0xF0) == 0xA0,
|
||||
0);
|
||||
}
|
||||
|
||||
/* The first lone surrogate in a span, or NULL. memchr-driven. */
|
||||
const char *scr_wtf8_find_surrogate(const char *p, size_t n);
|
||||
/* Fuse every adjacent lone high/low pair in place; returns the new length. */
|
||||
size_t scr_wtf8_fuse(char *p, size_t n);
|
||||
/* Restore canonical storage after a builder joined a lone high surrogate
|
||||
* to a lone low one: fuses in place and drops cached content facts. The
|
||||
* _from form starts at a byte offset before which storage is canonical. */
|
||||
void scr_str_fuse_surrogates(ScrStr *s);
|
||||
void scr_str_fuse_from(ScrStr *s, size_t from);
|
||||
/* Replace every lone surrogate with U+FFFD in place (same length). */
|
||||
void scr_wtf8_replace_surrogates(char *p, size_t n);
|
||||
static inline void scr_wtf8_to_utf8(char *p, size_t n) {
|
||||
if (__builtin_expect(scr_wtf8_any(), 0)) scr_wtf8_replace_surrogates(p, n);
|
||||
}
|
||||
/* A string's bytes as well-formed UTF-8 for a byte sink: s->data itself,
|
||||
* or (only when s holds a lone surrogate) a malloc'd NUL-terminated copy
|
||||
* with each one replaced by U+FFFD, returned in *owned for the caller to
|
||||
* release with scr_str_utf8_end. *owned is NULL on the fast path. */
|
||||
const char *scr_str_utf8_copy(const ScrStr *s, char **owned);
|
||||
static inline const char *scr_str_utf8(const ScrStr *s, char **owned) {
|
||||
*owned = NULL;
|
||||
if (s->ascii || !scr_wtf8_any()) return s->data;
|
||||
return scr_str_utf8_copy(s, owned);
|
||||
}
|
||||
static inline void scr_str_utf8_end(char *owned) {
|
||||
if (owned) free(owned);
|
||||
}
|
||||
static inline bool scr_str_has_surrogate(const ScrStr *s) {
|
||||
return scr_wtf8_any() && !s->ascii && scr_wtf8_find_surrogate(s->data, s->len) != NULL;
|
||||
}
|
||||
/* Encode one UTF-16 code unit or code point as WTF-8 (1-4 bytes). */
|
||||
size_t scr_wtf8_put(char *dst, uint32_t cp);
|
||||
|
||||
/* Borrow both args, return +1. */
|
||||
ScrStr *scr_str_concat(ScrStr *a, ScrStr *b);
|
||||
/* Borrow each part without mutation; return one independently owned result. */
|
||||
ScrStr *scr_str_concat_parts(ScrStr *const *parts, size_t count);
|
||||
/* Concatenate up to 16 string or number operands (parts[i] == NULL selects
|
||||
* nums[i], formatted like String(n)) onto an optional borrowed head, +1
|
||||
* result. The head may be appended in place under scr_str_concat's rule for
|
||||
* its left operand; parts are never mutated. */
|
||||
ScrStr *scr_str_concat_mixed(ScrStr *head, ScrStr *const *parts, const double *nums, size_t count);
|
||||
/* The same with the left operand / head MOVED in (`s = s + x` when nothing
|
||||
* else can observe `s`): one call instead of a snapshot retain, the
|
||||
* binding's release and the snapshot's release around a borrowing concat. */
|
||||
ScrStr *scr_str_concat_move(ScrStr *a, ScrStr *b);
|
||||
ScrStr *scr_str_concat_mixed_move(ScrStr *head, ScrStr *const *parts, const double *nums,
|
||||
size_t count);
|
||||
/* a + String(x): scr_str_concat's contract with a number right operand. */
|
||||
ScrStr *scr_str_concat_f64(ScrStr *a, double x);
|
||||
|
||||
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);
|
||||
/* a.localeCompare(b) with no locales/options: Node's root-locale order for
|
||||
* Latin-script text, code-point order outside it (scr_string.c). */
|
||||
double scr_str_locale_compare(ScrStr *a, ScrStr *b);
|
||||
|
||||
/* ECMAScript string-list ordering: compare UTF-16 code units even though
|
||||
* ScrStr stores WTF-8. Returns <0, 0, >0. */
|
||||
int scr_str_cmp_u16(ScrStr *a, ScrStr *b);
|
||||
|
||||
#endif /* SCR_STRING_H */
|
||||
@@ -0,0 +1,255 @@
|
||||
/* scriptc runtime — the string methods, URI coding and number parsing
|
||||
* (scr_string.c, scr_idna.c). Included by scr_runtime.h, which says where new
|
||||
* declarations go. */
|
||||
#ifndef SCR_STRING_METHODS_H
|
||||
#define SCR_STRING_METHODS_H
|
||||
|
||||
#include "scr_error.h"
|
||||
|
||||
/* ── 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.
|
||||
*
|
||||
* Where JS produces a *lone surrogate* — charAt on half of an astral
|
||||
* pair, or a slice boundary that splits a pair — the result holds that
|
||||
* surrogate (canonical WTF-8, see above).
|
||||
*/
|
||||
|
||||
/* .length — number of UTF-16 code units (astral chars count as 2). */
|
||||
double scr_str_utf16_len(ScrStr *s);
|
||||
|
||||
/* The receiver of an indexed scan whose UTF-16 code units the index cache
|
||||
* holds (scr_string.c), published so emitted length and charCodeAt reads of
|
||||
* that string are a compare and a load. Cleared whenever its entry drops
|
||||
* the units (release, append, eviction); all fields zero otherwise. Emitted
|
||||
* code reads it only where SCR_TL is empty. */
|
||||
typedef struct ScrStrScan {
|
||||
const ScrStr *s;
|
||||
const uint16_t *units;
|
||||
size_t n;
|
||||
} ScrStrScan;
|
||||
extern SCR_TL ScrStrScan scr_str_scan;
|
||||
|
||||
/* 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 keep the half character as a lone surrogate. 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);
|
||||
|
||||
/* Scalar-only local slices retain their original owner without copying.
|
||||
* Bounds and split-surrogate replacements are snapshotted at construction. */
|
||||
typedef struct ScrStringSlice {
|
||||
size_t start, length;
|
||||
uint32_t split;
|
||||
} ScrStringSlice;
|
||||
void scr_str_slice_range(ScrStr *s, double start, double end, bool substring,
|
||||
ScrStringSlice *out);
|
||||
double scr_str_slice_char_code_at(ScrStr *s, const ScrStringSlice *range, double index);
|
||||
|
||||
/* 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);
|
||||
ScrStr *scr_str_normalize(const ScrStr *s, const ScrStr *form);
|
||||
/* NFC over a code point buffer (scr_regex.c, beside normalize): a fresh
|
||||
* malloc'd buffer the caller frees, its length in *out_len. Borrows input. */
|
||||
uint32_t *scr_nfc_code_points(const uint32_t *input, size_t length, size_t *out_len);
|
||||
|
||||
/* UTS #46 domain conversion (scr_idna.c — the url unit's IDNA tables; it
|
||||
* rides the regex switch for NFC). to_ascii answers "" on failure;
|
||||
* to_unicode decodes each valid Punycode label and never fails. Both
|
||||
* borrow the UTF-8 span and return +1. */
|
||||
ScrStr *scr_idna_to_ascii(const char *p, size_t n);
|
||||
ScrStr *scr_idna_to_unicode(const char *p, size_t n);
|
||||
|
||||
/* charAt(i): 1-code-unit string; out of range → empty string. Half of an
|
||||
* astral pair → that lone surrogate. 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
|
||||
* a lone surrogate); otherwise splits on every occurrence,
|
||||
* empty pieces kept. Borrows both; returns a +1 string[] (SCR_ELEM_STR). */
|
||||
struct ScrArr;
|
||||
/* String.prototype.replace/replaceAll with a string search value and a
|
||||
* string replacement: GetSubstitution without captures ($$, $&, $`, $';
|
||||
* every other '$' spelling stays literal). An empty search value matches
|
||||
* at 0, or at every UTF-16 position under replaceAll. Borrows; +1. */
|
||||
ScrStr *scr_str_replace_literal(ScrStr *s, ScrStr *search, ScrStr *rep, bool all);
|
||||
/* Every occurrence of the ASCII byte chars[k] replaced by the k-th
|
||||
* NUL-separated text of `texts`, in one pass (at most 16 distinct
|
||||
* characters; the compiler fuses chains of global one-character
|
||||
* replacements into it). Borrows all three; +1. */
|
||||
ScrStr *scr_str_replace_chars(ScrStr *s, ScrStr *chars, ScrStr *texts);
|
||||
/* The same over a nonempty canonical WTF-8 needle (literal regex
|
||||
* patterns share it). Borrows; +1. */
|
||||
ScrStr *scr_str_replace_bytes(ScrStr *s, const char *pat, size_t plen, ScrStr *rep, bool all);
|
||||
/* First occurrence of a nonempty needle in a byte span, or NULL. */
|
||||
const char *scr_str_find_bytes(const char *hay, size_t hay_len, const char *needle,
|
||||
size_t needle_len);
|
||||
struct ScrArr *scr_str_split(ScrStr *s, ScrStr *sep);
|
||||
struct ScrArr *scr_str_split_limit(ScrStr *s, ScrStr *sep, double limit);
|
||||
/* Internal dense split traversal. The cursor borrows input strings, returns
|
||||
* one owned piece per step, and uses NULL only for exhaustion. Its layout
|
||||
* is shared with generated stack storage, including wasm32. */
|
||||
typedef struct ScrSplitCursor {
|
||||
size_t offset;
|
||||
uint32_t remaining;
|
||||
uint32_t pending_unit; /* the low surrogate still owed by a split pair */
|
||||
char unit[4]; /* the bytes of the current surrogate piece */
|
||||
} ScrSplitCursor;
|
||||
void scr_str_split_cursor_init(ScrSplitCursor *cursor, uint32_t limit);
|
||||
/* Optional scratch is a separately owned, NULL-initialized string slot.
|
||||
* A piece that has not escaped can reuse it after the preceding yield is
|
||||
* released. The caller releases the slot on every exit; NULL disables reuse. */
|
||||
ScrStr *scr_str_split_cursor_next(ScrStr *s, ScrStr *sep, ScrSplitCursor *cursor,
|
||||
ScrStr **scratch);
|
||||
/* Borrowed split bytes remain valid while the source snapshot is owned.
|
||||
* Materialization returns +1 and uses the same optional scratch contract. */
|
||||
const char *scr_str_split_cursor_span(ScrStr *s, ScrStr *sep, ScrSplitCursor *cursor,
|
||||
size_t *length);
|
||||
ScrStr *scr_str_split_materialize(const char *bytes, size_t length, ScrStr **scratch);
|
||||
|
||||
/* 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 keeps its
|
||||
* high half as a lone surrogate. 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(): whether s holds no lone surrogate, and
|
||||
* s with each lone surrogate replaced by U+FFFD (s itself, retained, when
|
||||
* it has none). Borrow s; toWellFormed +1. */
|
||||
bool scr_str_is_well_formed(ScrStr *s);
|
||||
ScrStr *scr_str_to_well_formed(ScrStr *s);
|
||||
/* Node text utilities: borrowed input, +1 result or NULL+pending. The VT
|
||||
* helper lives in the optional regex unit; USV conversion uses native strings. */
|
||||
ScrStr *scr_util_strip_vt(const struct ScrDyn *input);
|
||||
ScrStr *scr_util_to_usv_string(const struct ScrDyn *input);
|
||||
ScrStr *scr_util_to_string(const struct ScrDyn *input); /* ToString alone */
|
||||
void scr_dyn_install_to_primitive_symbol(struct ScrDyn *key);
|
||||
|
||||
/* 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), a lone surrogate \uNNNN-escapes, the rest
|
||||
* passes through. Total. 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);
|
||||
/* parseInt over a borrowed UTF-8 byte span. */
|
||||
double scr_parse_int_bytes(const char *p, size_t n, 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);
|
||||
/* StringToNumber over a borrowed UTF-8 byte span. */
|
||||
double scr_bytes_to_number(const char *p, size_t len);
|
||||
|
||||
/* 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). A lone surrogate throws the
|
||||
* spec's URIError ("URI malformed"). Borrows s; result +1. */
|
||||
ScrStr *scr_str_encode_uri_component(ScrStr *s);
|
||||
/* querystring.escape: the component encoder, throwing Node's URIError
|
||||
* [ERR_INVALID_URI] on a lone surrogate. Borrows s; result +1. */
|
||||
ScrStr *scr_qs_escape(ScrStr *s);
|
||||
/* lib/internal/querystring.js encodeStr over UTF-16 code units: ASCII
|
||||
* outside `no_escape` and every non-ASCII code unit percent-encode as
|
||||
* UTF-8, and ANY surrogate pairs with the code unit after it (Node's own
|
||||
* rule); only a surrogate in last position throws URIError
|
||||
* [ERR_INVALID_URI] and returns NULL. Borrows s; +1. */
|
||||
ScrStr *scr_qs_encode_str(const ScrStr *s, const signed char no_escape[128]);
|
||||
|
||||
/* 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);
|
||||
ScrStr *scr_str_decode_uri(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);
|
||||
|
||||
#endif /* SCR_STRING_METHODS_H */
|
||||
@@ -0,0 +1,31 @@
|
||||
/* scriptc runtime — ES Symbol values (scr_symbol.c). Included by
|
||||
* scr_runtime.h, which says where new declarations go. */
|
||||
#ifndef SCR_SYMBOL_H
|
||||
#define SCR_SYMBOL_H
|
||||
|
||||
#include "scr_string.h"
|
||||
|
||||
/* ── 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) */
|
||||
|
||||
#endif /* SCR_SYMBOL_H */
|
||||
@@ -0,0 +1,45 @@
|
||||
/* scriptc runtime — node:test (scr_test.c). Included by scr_runtime.h, which
|
||||
* says where new declarations go. */
|
||||
#ifndef SCR_TEST_H
|
||||
#define SCR_TEST_H
|
||||
|
||||
#include "scr_bytes.h"
|
||||
|
||||
/* ── 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
|
||||
|
||||
#endif /* SCR_TEST_H */
|
||||
@@ -0,0 +1,136 @@
|
||||
/* scriptc runtime — node:tls and node:https (scr_tls.c, scr_tls_ca.c).
|
||||
* Included by scr_runtime.h, which says where new declarations go. */
|
||||
#ifndef SCR_TLS_H
|
||||
#define SCR_TLS_H
|
||||
|
||||
#include "scr_net.h"
|
||||
|
||||
/* ── 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 without an 'error' event (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 and *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);
|
||||
|
||||
#endif /* SCR_TLS_H */
|
||||
@@ -0,0 +1,91 @@
|
||||
/* scriptc runtime — unions and the void*-signature RC adapters (scr_union.c).
|
||||
* Included by scr_runtime.h, which says where new declarations go. */
|
||||
#ifndef SCR_UNION_H
|
||||
#define SCR_UNION_H
|
||||
|
||||
#include "scr_rc.h"
|
||||
|
||||
/* ── 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;
|
||||
/* The speed posture's inline union release (shapes.ts) reads arm_trace at
|
||||
* this byte offset to skip buffering untraced boxes, like scr_union_release. */
|
||||
_Static_assert(offsetof(ScrUnion, arm_trace) == 2 * sizeof(size_t) + 2 * sizeof(void *),
|
||||
"inline RC fast paths read ScrUnion.arm_trace at 32 (64-bit) / 16 (32-bit)");
|
||||
|
||||
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++;
|
||||
}
|
||||
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);
|
||||
|
||||
#endif /* SCR_UNION_H */
|
||||
@@ -0,0 +1,112 @@
|
||||
/* scriptc runtime — WHATWG URL and URLSearchParams (scr_url.c). Included by
|
||||
* scr_runtime.h, which says where new declarations go. */
|
||||
#ifndef SCR_URL_H
|
||||
#define SCR_URL_H
|
||||
|
||||
#include "scr_exception.h"
|
||||
|
||||
/* ── WHATWG URL (scr_url.c) ──────────────────────────────────────────
|
||||
* A mutable, refcounted URL value with selected component setters. 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 */
|
||||
bool scr_url_can_parse(ScrStr *input);
|
||||
bool scr_url_can_parse_base(ScrStr *input, ScrStr *base);
|
||||
ScrDyn *scr_url_parse(ScrStr *input); /* +1 native URL or null; invalid URLs do not throw */
|
||||
ScrDyn *scr_url_parse_base(ScrStr *input, ScrStr *base);
|
||||
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() */
|
||||
void scr_url_set(ScrUrl *u, ScrStr *field, ScrStr *value); /* borrowed; href may throw */
|
||||
bool scr_url_set_component(ScrUrl *u, ScrStr *field, ScrStr *value); /* borrowed; false for ignored writes */
|
||||
void scr_url_set_checked(ScrUrl *u, ScrStr *field, const ScrDyn *value); /* borrowed; conversion may throw */
|
||||
/* node:url's legacy surface (scr_url_legacy.c — linked with the regex,
|
||||
* querystring and dyn-async units). url.parse() answers a checked-dynamic
|
||||
* Url instance; format() accepts a Url-shaped object, a string, or a
|
||||
* native URL (with its options bag); resolve() takes two strings. `warn`
|
||||
* emits DEP0169 once per process (false at node_modules call sites).
|
||||
* domainToASCII/Unicode take the argument and whether it was omitted
|
||||
* (ERR_MISSING_ARGS). All borrow; +1, or NULL with Node's error pending. */
|
||||
ScrDyn *scr_url_legacy_parse(const ScrDyn *url, const ScrDyn *parse_query, const ScrDyn *slashes,
|
||||
bool warn);
|
||||
ScrStr *scr_url_legacy_format(const ScrDyn *object, const ScrDyn *options, bool warn);
|
||||
ScrStr *scr_url_legacy_resolve(const ScrDyn *from, const ScrDyn *to, bool warn);
|
||||
ScrStr *scr_url_domain_to_ascii(const ScrDyn *domain, bool missing);
|
||||
ScrStr *scr_url_domain_to_unicode(const ScrDyn *domain, bool missing);
|
||||
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 exactly: '+' 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 */
|
||||
|
||||
#endif /* SCR_URL_H */
|
||||
@@ -0,0 +1,38 @@
|
||||
/* scriptc runtime — fs.watch (scr_watch.c). Included by scr_runtime.h, which
|
||||
* says where new declarations go. */
|
||||
#ifndef SCR_WATCH_H
|
||||
#define SCR_WATCH_H
|
||||
|
||||
#include "scr_bytes.h"
|
||||
|
||||
/* ── 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));
|
||||
|
||||
#endif /* SCR_WATCH_H */
|
||||
@@ -0,0 +1,25 @@
|
||||
/* scriptc runtime — zlib (scr_zlib.c). Included by scr_runtime.h, which says
|
||||
* where new declarations go. */
|
||||
#ifndef SCR_ZLIB_H
|
||||
#define SCR_ZLIB_H
|
||||
|
||||
#include "scr_bytes.h"
|
||||
|
||||
/* ── 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);
|
||||
|
||||
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);
|
||||
|
||||
#endif /* SCR_ZLIB_H */
|
||||
@@ -0,0 +1,68 @@
|
||||
import { execFile } from "node:child_process";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
const srcDir = join(import.meta.dirname, "../src");
|
||||
|
||||
/** The module headers scr_runtime.h includes, in include order. */
|
||||
const MODULE_HEADERS = [
|
||||
...readFileSync(join(srcDir, "scr_runtime.h"), "utf8").matchAll(/^#include "(scr_\w+\.h)"/gm),
|
||||
].map((m) => m[1]!);
|
||||
|
||||
const DEFINE_SETS = [[], ["-DSCR_LIB"], ["-DSCR_LIB", "-DSCR_THREAD_INSTANCES"], ["-DSCR_WORKERS"]];
|
||||
|
||||
// Each module header includes what it uses, so a runtime unit can include
|
||||
// only the headers it needs. A header that relies on an earlier one it does
|
||||
// not include fails here; -Werror=visibility catches a `struct T *`
|
||||
// parameter whose tag is not declared at file scope yet.
|
||||
test("every module header compiles on its own", async () => {
|
||||
expect(MODULE_HEADERS.length).toBeGreaterThan(40);
|
||||
const dir = await mkdtemp(join(tmpdir(), "scriptc-runtime-headers-"));
|
||||
try {
|
||||
const failures: string[] = [];
|
||||
await Promise.all(
|
||||
MODULE_HEADERS.map(async (header) => {
|
||||
const source = join(dir, `${header}.c`);
|
||||
await writeFile(source, `#include "${header}"\n`);
|
||||
for (const defines of DEFINE_SETS) {
|
||||
try {
|
||||
await execFileAsync("clang", [
|
||||
"-std=c11",
|
||||
"-fsyntax-only",
|
||||
"-Werror=visibility",
|
||||
"-Werror=implicit-function-declaration",
|
||||
...(process.platform === "linux" ? ["-D_GNU_SOURCE"] : []),
|
||||
...defines,
|
||||
"-I",
|
||||
srcDir,
|
||||
source,
|
||||
]);
|
||||
} catch (error) {
|
||||
failures.push(
|
||||
`${header} ${defines.join(" ")}: ${(error as { stderr?: string }).stderr}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
expect(failures).toEqual([]);
|
||||
} finally {
|
||||
await rm(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("scr_runtime.h holds no declarations of its own", () => {
|
||||
const text = readFileSync(join(srcDir, "scr_runtime.h"), "utf8")
|
||||
.replace(/\/\*[\s\S]*?\*\//g, "")
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line !== "");
|
||||
expect(text.filter((line) => !/^#(include "scr_\w+\.h"|ifndef|define|endif)/.test(line))).toEqual(
|
||||
[],
|
||||
);
|
||||
});
|
||||
@@ -30,7 +30,9 @@ export const fileExclusions = [
|
||||
|
||||
// Pull requests run the full suite on Linux. Their macOS lane keeps the
|
||||
// darwin clang/libc/kqueue oracles: the Node differential corpus, plus every
|
||||
// file whose assertions run only on darwin or take darwin-specific branches.
|
||||
// file whose assertions run only on darwin or take darwin-specific branches
|
||||
// (including the Mach-O library localization arm and the arm64 runtime
|
||||
// string oracle).
|
||||
// Pushes to main and the nightly schedule still run the full suite on macOS.
|
||||
export const darwinCorpusFiles = ["tests/harness/differential.test.ts"];
|
||||
export const darwinFiles = [
|
||||
@@ -42,9 +44,13 @@ export const darwinFiles = [
|
||||
"packages/compiler/test/native-codegen-integration.test.ts",
|
||||
"packages/compiler/test/source-debugging.test.ts",
|
||||
"packages/runtime-darwin-arm64/test/archive.test.ts",
|
||||
"packages/runtime/test/string.test.ts",
|
||||
"tests/harness/ffi-callback-pool.test.ts",
|
||||
"tests/harness/ffi-catalog.test.ts",
|
||||
"tests/harness/ffi-frameworks.test.ts",
|
||||
"tests/harness/library-callbacks.test.ts",
|
||||
"tests/harness/library-dispatch.test.ts",
|
||||
"tests/harness/library-multi.test.ts",
|
||||
"tests/harness/native-object-example.test.ts",
|
||||
"tests/harness/runtime-tree-shaking.test.ts",
|
||||
"tests/harness/terminal-geometry.test.ts",
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
/* Retargets a diff of the single-file packages/runtime/src/scr_runtime.h
|
||||
* onto the per-module runtime headers that scr_runtime.h now includes.
|
||||
*
|
||||
* Each hunk's old side (context and removed lines) is located in exactly one
|
||||
* module header. When the full old side spans two headers or matches more
|
||||
* than once, leading and trailing context lines are dropped until a single
|
||||
* match remains; a hunk whose changed lines span two headers is split at its
|
||||
* inner context runs. Placed hunks get three lines of context from the
|
||||
* module header again. Hunks that cannot be placed are listed on stderr,
|
||||
* left out of the output and make the exit status 1. The output is a patch
|
||||
* for `git apply` (add `--reject` to keep going past a hunk that no longer
|
||||
* applies).
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/runtime-header-hunks.mjs [--ref <rev>] [diff-file]
|
||||
* Reads the diff from stdin when no file is given. Module headers come from
|
||||
* the working tree, or from <rev> with --ref. */
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { parseArgs } from "node:util";
|
||||
|
||||
const HEADER_DIR = "packages/runtime/src";
|
||||
const UMBRELLA = "scr_runtime.h";
|
||||
const repoRoot = join(import.meta.dirname, "..");
|
||||
|
||||
const { values, positionals } = parseArgs({
|
||||
options: { ref: { type: "string" }, help: { type: "boolean", default: false } },
|
||||
allowPositionals: true,
|
||||
});
|
||||
if (values.help) {
|
||||
console.log("Usage: node scripts/runtime-header-hunks.mjs [--ref <rev>] [diff-file]");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
function readHeader(name) {
|
||||
const path = `${HEADER_DIR}/${name}`;
|
||||
if (values.ref === undefined) return readFileSync(join(repoRoot, path), "utf8");
|
||||
return execFileSync("git", ["show", `${values.ref}:${path}`], {
|
||||
cwd: repoRoot,
|
||||
encoding: "utf8",
|
||||
});
|
||||
}
|
||||
|
||||
function splitLines(text) {
|
||||
const lines = text.split("\n");
|
||||
if (lines[lines.length - 1] === "") lines.pop();
|
||||
return lines;
|
||||
}
|
||||
|
||||
/** Module headers in umbrella include order: name → lines. */
|
||||
function moduleHeaders() {
|
||||
const headers = new Map();
|
||||
for (const line of splitLines(readHeader(UMBRELLA))) {
|
||||
const include = line.match(/^#include "(scr_[a-z0-9_]+\.h)"/);
|
||||
if (include !== null) headers.set(include[1], splitLines(readHeader(include[1])));
|
||||
}
|
||||
if (headers.size === 0) throw new Error(`${UMBRELLA} includes no module headers`);
|
||||
return headers;
|
||||
}
|
||||
|
||||
/** Hunks of the scr_runtime.h file sections of a unified diff. */
|
||||
function parseHunks(diff) {
|
||||
const hunks = [];
|
||||
let inUmbrella = false;
|
||||
let hunk = null;
|
||||
for (const line of diff.split("\n")) {
|
||||
if (line.startsWith("diff --git ")) {
|
||||
inUmbrella = false;
|
||||
hunk = null;
|
||||
} else if (line.startsWith("+++ ")) {
|
||||
inUmbrella = line.endsWith(`${HEADER_DIR}/${UMBRELLA}`);
|
||||
hunk = null;
|
||||
} else if (line.startsWith("--- ")) {
|
||||
hunk = null;
|
||||
} else if (inUmbrella && line.startsWith("@@")) {
|
||||
const m = line.match(/^@@ -(\d+)(?:,\d+)? \+\d+(?:,\d+)? @@(.*)$/);
|
||||
if (m === null) throw new Error(`unreadable hunk header: ${line}`);
|
||||
hunk = { oldStart: Number(m[1]), section: m[2], lines: [] };
|
||||
hunks.push(hunk);
|
||||
} else if (hunk !== null && /^[ +-]/.test(line)) {
|
||||
hunk.lines.push(line);
|
||||
} else if (hunk !== null && line === "") {
|
||||
// Some tools drop the leading space of empty context lines.
|
||||
hunk.lines.push(" ");
|
||||
}
|
||||
}
|
||||
return hunks;
|
||||
}
|
||||
|
||||
const oldSide = (lines) => lines.filter((l) => l[0] !== "+").map((l) => l.slice(1));
|
||||
|
||||
/** Every start index where `needle` occurs in `hay`. */
|
||||
function findAll(hay, needle) {
|
||||
const found = [];
|
||||
for (let i = 0; i + needle.length <= hay.length; i++) {
|
||||
let j = 0;
|
||||
while (j < needle.length && hay[i + j] === needle[j]) j++;
|
||||
if (j === needle.length) found.push(i);
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
/** Refill context from the header to three lines on each side: git apply
|
||||
* anchors a hunk without leading or trailing context to the start or end
|
||||
* of the file. */
|
||||
function withContext(match, lines, text) {
|
||||
const context = 3;
|
||||
let lead = 0;
|
||||
while (lead < lines.length && lines[lead][0] === " ") lead++;
|
||||
let trail = 0;
|
||||
while (trail < lines.length - lead && lines[lines.length - 1 - trail][0] === " ") trail++;
|
||||
const before = Math.min(Math.max(context - lead, 0), match.at);
|
||||
const end = match.at + oldSide(lines).length;
|
||||
const after = Math.min(Math.max(context - trail, 0), text.length - end);
|
||||
return {
|
||||
name: match.name,
|
||||
at: match.at - before,
|
||||
lines: [
|
||||
...text.slice(match.at - before, match.at).map((l) => ` ${l}`),
|
||||
...lines,
|
||||
...text.slice(end, end + after).map((l) => ` ${l}`),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/** Locate one hunk: the trimmed hunk lines plus the header and 0-based line. */
|
||||
function locate(hunk, headers) {
|
||||
const lines = hunk.lines;
|
||||
let lead = 0;
|
||||
while (lead < lines.length && lines[lead][0] === " ") lead++;
|
||||
let trail = 0;
|
||||
while (trail < lines.length - lead && lines[lines.length - 1 - trail][0] === " ") trail++;
|
||||
// Try the least trimming first.
|
||||
for (let drop = 0; drop <= lead + trail; drop++) {
|
||||
for (let dl = Math.min(drop, lead); dl >= 0 && drop - dl <= trail; dl--) {
|
||||
const kept = lines.slice(dl, lines.length - (drop - dl));
|
||||
const needle = oldSide(kept);
|
||||
// A pure insertion needs at least one context line to anchor it.
|
||||
if (needle.length === 0) continue;
|
||||
const matches = [];
|
||||
for (const [name, text] of headers)
|
||||
for (const at of findAll(text, needle)) matches.push({ name, at });
|
||||
if (matches.length === 1) return withContext(matches[0], kept, headers.get(matches[0].name));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Merge hunks (sorted by position) whose old sides overlap. The overlap
|
||||
* must be context in both. */
|
||||
function mergeOverlapping(list, name) {
|
||||
const out = [];
|
||||
for (const p of list) {
|
||||
const prev = out[out.length - 1];
|
||||
const prevEnd = prev === undefined ? -1 : prev.at + oldSide(prev.lines).length;
|
||||
if (prev === undefined || p.at >= prevEnd) {
|
||||
out.push({ ...p, lines: [...p.lines] });
|
||||
continue;
|
||||
}
|
||||
const overlap = prevEnd - p.at;
|
||||
const prevTail = prev.lines.filter((l) => l[0] !== "+").slice(-overlap);
|
||||
let skip = 0;
|
||||
for (let seen = 0; seen < overlap && skip < p.lines.length; skip++) {
|
||||
if (p.lines[skip][0] !== " ") break;
|
||||
seen++;
|
||||
}
|
||||
if (skip < Math.min(overlap, p.lines.length) || prevTail.some((l) => l[0] !== " "))
|
||||
throw new Error(`hunks overlap in ${name} near line ${p.at + 1}`);
|
||||
prev.lines.push(...p.lines.slice(skip));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function formatPatch(placed) {
|
||||
const byHeader = new Map();
|
||||
for (const p of placed) {
|
||||
if (!byHeader.has(p.name)) byHeader.set(p.name, []);
|
||||
byHeader.get(p.name).push(p);
|
||||
}
|
||||
let out = "";
|
||||
for (const [name, list] of byHeader) {
|
||||
list.sort((a, b) => a.at - b.at);
|
||||
const merged = mergeOverlapping(list, name);
|
||||
const path = `${HEADER_DIR}/${name}`;
|
||||
out += `diff --git a/${path} b/${path}\n--- a/${path}\n+++ b/${path}\n`;
|
||||
let shift = 0;
|
||||
for (const p of merged) {
|
||||
const oldCount = p.lines.filter((l) => l[0] !== "+").length;
|
||||
const newCount = p.lines.filter((l) => l[0] !== "-").length;
|
||||
out += `@@ -${p.at + 1},${oldCount} +${p.at + 1 + shift},${newCount} @@${p.section}\n`;
|
||||
out += p.lines.join("\n") + "\n";
|
||||
shift += newCount - oldCount;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Split a hunk at its inner context runs: one piece per run of changed
|
||||
* lines, each with the context on both sides of it. */
|
||||
function pieces(hunk) {
|
||||
const runs = [];
|
||||
for (let i = 0; i < hunk.lines.length;) {
|
||||
if (hunk.lines[i][0] === " ") {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
let j = i;
|
||||
while (j < hunk.lines.length && hunk.lines[j][0] !== " ") j++;
|
||||
runs.push([i, j]);
|
||||
i = j;
|
||||
}
|
||||
return runs.map((_, k) => {
|
||||
const start = k === 0 ? 0 : runs[k - 1][1];
|
||||
const end = k === runs.length - 1 ? hunk.lines.length : runs[k + 1][0];
|
||||
return { ...hunk, lines: hunk.lines.slice(start, end) };
|
||||
});
|
||||
}
|
||||
|
||||
const diff = readFileSync(positionals[0] ?? 0, "utf8");
|
||||
const headers = moduleHeaders();
|
||||
const hunks = parseHunks(diff);
|
||||
const placed = [];
|
||||
let missed = 0;
|
||||
for (const hunk of hunks) {
|
||||
const whole = locate(hunk, headers);
|
||||
const parts = whole !== null ? [{ ...whole, section: hunk.section }] : [];
|
||||
if (whole === null) {
|
||||
// The hunk spans two headers: place each changed run on its own.
|
||||
// Runs that land next to each other merge again in formatPatch.
|
||||
for (const piece of pieces(hunk)) {
|
||||
const where = locate(piece, headers);
|
||||
if (where === null) {
|
||||
parts.length = 0;
|
||||
break;
|
||||
}
|
||||
parts.push({ ...where, section: hunk.section });
|
||||
}
|
||||
}
|
||||
if (parts.length === 0) {
|
||||
missed++;
|
||||
process.stderr.write(`not placed: ${UMBRELLA} hunk at old line ${hunk.oldStart}\n`);
|
||||
process.stderr.write(hunk.lines.map((l) => ` ${l}`).join("\n") + "\n");
|
||||
continue;
|
||||
}
|
||||
for (const part of parts) {
|
||||
placed.push(part);
|
||||
process.stderr.write(
|
||||
`${UMBRELLA}:${hunk.oldStart} -> ${HEADER_DIR}/${part.name}:${part.at + 1}\n`,
|
||||
);
|
||||
}
|
||||
}
|
||||
process.stdout.write(formatPatch(placed));
|
||||
process.stderr.write(`${hunks.length - missed} of ${hunks.length} hunks placed\n`);
|
||||
process.exitCode = missed === 0 ? 0 : 1;
|
||||
@@ -0,0 +1,35 @@
|
||||
// localeCompare over ASCII strings of every length from 0 to 24 (across
|
||||
// the eight-byte words the runtime compares at a time): a case, letter,
|
||||
// digit or punctuation difference at each position, a tab, newline or
|
||||
// carriage return at each position, and an accented letter at each
|
||||
// position, against Node.
|
||||
function sign(x: number): number {
|
||||
return x < 0 ? -1 : x > 0 ? 1 : 0;
|
||||
}
|
||||
const base = "abcdefghijklmnopqrstuvwxyz";
|
||||
const swaps = ["A", "b", "0", "-", " ", "z", "\t", "\n", "\r", "é", "É"];
|
||||
let out = "";
|
||||
let equal = 0;
|
||||
for (let n = 0; n <= 24; n++) {
|
||||
const s = base.slice(0, n);
|
||||
for (let i = 0; i <= n; i++) {
|
||||
for (const w of swaps) {
|
||||
const t = s.slice(0, i) + w + s.slice(i + 1);
|
||||
const u = s.slice(0, i) + w + s.slice(i);
|
||||
for (const [x, y] of [[s, t], [t, s], [s, u], [u, s], [t, u], [t.toUpperCase(), t]]) {
|
||||
const r = sign(x!.localeCompare(y!));
|
||||
out += r + 1;
|
||||
if (r === 0) equal++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let hash = 2166136261;
|
||||
for (let i = 0; i < out.length; i++) hash = Math.imul(hash ^ out.charCodeAt(i), 16777619) >>> 0;
|
||||
console.log(out.length, equal, hash.toString(16));
|
||||
// Equal strings built separately, and prefixes of each other.
|
||||
const words = ["", "a", "ab", "abcdefg", "abcdefgh", "abcdefghi", "abcdefghijklmnop", "abcdefghijklmnopq"];
|
||||
for (const w of words) {
|
||||
const copy = (w + "x").slice(0, w.length);
|
||||
console.log(JSON.stringify(w), w.localeCompare(copy), w.localeCompare(w + "a"), (w + "a").localeCompare(w), w.toUpperCase().localeCompare(copy));
|
||||
}
|
||||
Reference in New Issue
Block a user