@scriptc/runtime 0.0.10 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,10 @@
1
1
  /* Prototype-method dispatch on checked-dynamic receivers (scr_dyn_invoke)
2
- * and its companions: JS String() over the DOM (scr_dyn_display — join
2
+ * and its companions: JS String() over the checked-dynamic tree (scr_dyn_display — join
3
3
  * and the error texts need it standalone) and Object.defineProperties
4
- * over DOM values (scr_dyn_define_props). Linked only when the IR
4
+ * over dyn values (scr_dyn_define_props). Linked only when the IR
5
5
  * carries dynInvoke nodes or dyn.defineProps calls (cc.ts gates on
6
6
  * moduleUsesDynInvoke — the scr_assert.c precedent), so dispatch-free
7
- * binaries keep their exact size class. The DOM itself lives in
7
+ * binaries keep their exact size class. The checked-dynamic tree itself lives in
8
8
  * scr_json.c; this unit uses only its public surface plus ScrJsonBuf.
9
9
  */
10
10
  #include "scr_runtime.h"
@@ -16,8 +16,8 @@
16
16
 
17
17
  /* ── checked-dynamic METHOD DISPATCH (scr_dyn_invoke) ──────────────────
18
18
  *
19
- * `recv.m(args)` where recv is a DOM value and `m` is a name a
20
- * DOM-representable prototype declares (Array/String/Function shared
19
+ * `recv.m(args)` where recv is a dyn value and `m` is a name a
20
+ * dyn-representable prototype declares (Array/String/Function shared
21
21
  * names — push, slice, forEach, apply, ...): a stored-member read would
22
22
  * silently mis-answer real methods, so the dispatch runs HERE, over the
23
23
  * receiver's runtime kind. test/common's mustCall internals are the
@@ -38,10 +38,10 @@
38
38
  * recv/args are BORROWED; the result is owned (+1). MAY THROW (returns
39
39
  * NULL with the exception pending). */
40
40
 
41
- /* JS String() over a DOM value, runtime-side (join needs it standalone —
41
+ /* JS String() over a dyn value, runtime-side (join needs it standalone —
42
42
  * the emitted sc_ds walker exists only in programs that spell
43
43
  * String(unknown) themselves). Same rules: arrays join with "," and
44
- * null/undefined elements print empty, the DOM's error encoding renders
44
+ * null/undefined elements print empty, the checked-dynamic tree's error encoding renders
45
45
  * "name: message", functions render the native-code form, plain objects
46
46
  * are [object Object]. */
47
47
  static void scr_dyn_display_buf(ScrJsonBuf *b, const ScrDyn *d) {
@@ -192,7 +192,7 @@ static bool dyn_cb_check(ScrDyn *const *args, size_t argc) {
192
192
  ScrDyn *cb = argc > 0 ? args[0] : scr_dyn_undefined();
193
193
  if (cb->kind == SCR_DYN_FUNC) return true;
194
194
  /* An ENGINE function is callable — scr_dyn_call's JSVAL arm routes it
195
- * (the loops below call through scr_dyn_call, which converts the DOM
195
+ * (the loops below call through scr_dyn_call, which converts the checked-dynamic tree
196
196
  * element arguments per the uniform crossing). A wrapped NON-function
197
197
  * falls through to the display path: String(cb) renders through the
198
198
  * engine, exactly Node's message. */
@@ -215,7 +215,7 @@ static ScrDyn *scr_dynh_dispatch(ScrDyn *recv, const char *method, ScrDyn *const
215
215
  return ops->invoke(recv->v.handle.ptr, recv, method, args, argc, what);
216
216
  }
217
217
 
218
- /* JS Array.prototype.sort over a DOM array: the spec's snapshot-sort —
218
+ /* JS Array.prototype.sort over a dyn array: the spec's snapshot-sort —
219
219
  * elements copy (retained) into a work list, a stable merge sort orders
220
220
  * it (undefined elements sink to the end before any comparator runs),
221
221
  * and the ordered list writes back index by index, so a comparator that
@@ -339,7 +339,7 @@ ScrDyn *scr_dyn_invoke(ScrDyn *recv, const char *method, ScrDyn *const *args, si
339
339
  /* Island-held receivers: the ENGINE runs its own prototypes (JS-exact
340
340
  * flatMap/map/forEach/filter — Array.prototype is the engine's) through
341
341
  * scr_jsval_call_method; arguments cross per the uniform conversion
342
- * (wrapped cells by reference, DOM data deep-copied, FUNC boxes through
342
+ * (wrapped cells by reference, dyn data deep-copied, FUNC boxes through
343
343
  * the host shim), a missing member throws the engine's own TypeError,
344
344
  * and the result wraps back scalar-normalized. `what` is unused — the
345
345
  * engine's message names the failure. */
@@ -352,7 +352,7 @@ ScrDyn *scr_dyn_invoke(ScrDyn *recv, const char *method, ScrDyn *const *args, si
352
352
  if (recv->kind == SCR_DYN_OBJ) {
353
353
  ScrDyn *m = scr_dyn_obj_get(recv, method, strlen(method));
354
354
  if (m && (m->kind == SCR_DYN_FUNC ||
355
- /* a WRAPPED engine function stored as a DOM member: the
355
+ /* a WRAPPED engine function stored as a dyn member: the
356
356
  * routed call (scr_dyn_call's JSVAL arm) runs it. */
357
357
  (m->kind == SCR_DYN_JSVAL && scr_dyn_isl_typeof_is(m, "function")))) {
358
358
  /* JS binds the receiver for the call (`obj.method()` — this === obj):
@@ -584,8 +584,8 @@ ScrDyn *scr_dyn_invoke(ScrDyn *recv, const char *method, ScrDyn *const *args, si
584
584
  return scr_dyn_retain(scr_dyn_undefined()); /* forEach */
585
585
  }
586
586
  if (dyn_name_is(method, "flatMap")) {
587
- /* JS Array.prototype.flatMap over a DOM array: map + a depth-1
588
- * flatten. Native DOM-array results flatten element-by-element; a
587
+ /* JS Array.prototype.flatMap over a dyn array: map + a depth-1
588
+ * flatten. Native dyn-array results flatten element-by-element; a
589
589
  * WRAPPED engine array flattens through the routed keyed reads
590
590
  * (elements wrap back scalar-normalized); everything else pushes
591
591
  * as a single element (JS keeps non-array callback results whole).
@@ -682,9 +682,9 @@ ScrDyn *scr_dyn_invoke(ScrDyn *recv, const char *method, ScrDyn *const *args, si
682
682
  return NULL;
683
683
  }
684
684
 
685
- /* Object.defineProperties over DOM values (see scr_runtime.h). Value
685
+ /* Object.defineProperties over dyn values (see scr_runtime.h). Value
686
686
  * descriptors only: writable/enumerable/configurable accepted and IGNORED
687
- * (DOM properties are plain data properties — SEMANTICS.md); get/set
687
+ * (dyn properties are plain data properties — SEMANTICS.md); get/set
688
688
  * throw the loud unsupported Error, never a silent drop. */
689
689
  ScrDyn *scr_dyn_define_props(ScrDyn *target, ScrDyn *descs) {
690
690
  /* Island-held operands ARE objects to Node — the non-object TypeError
package/src/scr_error.c CHANGED
@@ -44,9 +44,9 @@ static void scr_error_gcfree(void *obj) {
44
44
  scr_cyc_free(e);
45
45
  }
46
46
 
47
- /* DOMException teardown: the ScrError fields plus the owned cause DOM
47
+ /* DOMException teardown: the ScrError fields plus the owned cause dyn
48
48
  * value. The cause is a ScrDyn — scr_json.c territory — and this file
49
- * must stay linkable WITHOUT the DOM (the runtime C-unit tests link
49
+ * must stay linkable WITHOUT the checked-dynamic tree (the runtime C-unit tests link
50
50
  * subsets), so the drop goes through a hook scr_json.c installs before
51
51
  * any cause can exist (scr_domex_new is the only producer). */
52
52
  static void (*scr_domex_cause_drop)(void *obj) = NULL;
@@ -273,7 +273,7 @@ void scr_undef_global_read(ScrStr *name) {
273
273
  /* ── DOMException ─────────────────────────────────────────────────────
274
274
  * The web-standard error shape (a Node global since v17): the ScrError
275
275
  * prefix plus the WebIDL slots (legacy numeric code, the options form's
276
- * cause). Construction resolves WebIDL's optionality over borrowed DOM
276
+ * cause). Construction resolves WebIDL's optionality over borrowed dyn
277
277
  * arguments — one place, shared by the compiled `new DOMException(...)`
278
278
  * and the runtime's own throw sites (atob/btoa). */
279
279
 
@@ -85,6 +85,12 @@ typedef struct ScrEeBucket {
85
85
  struct ScrEeReg {
86
86
  ScrEeBucket *head; /* singly linked, first-registration order */
87
87
  double max; /* -1 = unset (follows the default), 0 = unlimited */
88
+ /* Node's kShapeMode: set when names were PRE-CREATED (the stream
89
+ * constructors). In shape mode removeListener KEEPS an emptied name's
90
+ * rank (Node writes events[type] = undefined instead of deleting) —
91
+ * only removeAllListeners deletes: the named form drops the one key,
92
+ * the no-argument wipe resets everything and leaves shape mode. */
93
+ bool shape;
88
94
  };
89
95
 
90
96
  /* The vtable of BARE `new EventEmitter()` instances; the emitted main()
@@ -145,6 +151,21 @@ static ScrEeBucket *scr_ee_bucket_ensure(ScrEeReg *reg, ScrStr *name) {
145
151
  return b;
146
152
  }
147
153
 
154
+ /* Reserve a name's first-registration RANK without any listener: Node's
155
+ * stream classes pre-create their known _events keys (a V8 shape
156
+ * optimization eventNames() order observes — 'error'/'data'/... list
157
+ * before user events added earlier). An empty bucket is ABSENT for every
158
+ * read (n == 0 everywhere); the first add fills it in place, and a bucket
159
+ * emptied by removal still drops — Node deleting the key. */
160
+ void scr_emitter_reserve(ScrEmitter *em, const char *name) {
161
+ ScrEeReg *reg = scr_ee_reg_ensure(em);
162
+ reg->shape = true;
163
+ if (scr_ee_bucket_find(reg, name, strlen(name))) return;
164
+ ScrStr *n = scr_str_new(name, strlen(name));
165
+ scr_ee_bucket_ensure(reg, n);
166
+ scr_str_release(n);
167
+ }
168
+
148
169
  /* Drops an emptied bucket from the list (Node deletes the _events key; a
149
170
  * later add re-appends at the end). */
150
171
  static void scr_ee_bucket_drop(ScrEeReg *reg, ScrEeBucket *b) {
@@ -402,13 +423,14 @@ void scr_ee_inv_fixed4(ScrClosure *cb, va_list ap) {
402
423
 
403
424
  /* Removes one entry (by index) from a bucket's live list and fires the
404
425
  * 'removeListener' meta event AFTER the removal, Node's order. Drops the
405
- * bucket when emptied. */
426
+ * bucket when emptied — except in shape mode, where the emptied name
427
+ * keeps its rank (Node's events[type] = undefined). */
406
428
  static void scr_ee_remove_at(ScrEmitter *em, ScrEeBucket *b, size_t i) {
407
429
  ScrEeEntry *e = b->ls[i];
408
430
  memmove(b->ls + i, b->ls + i + 1, (b->n - i - 1) * sizeof *b->ls);
409
431
  b->n--;
410
432
  ScrStr *name = scr_str_retain(b->name);
411
- if (b->n == 0) scr_ee_bucket_drop(em->reg, b);
433
+ if (b->n == 0 && !em->reg->shape) scr_ee_bucket_drop(em->reg, b);
412
434
  scr_ee_entry_unref(e);
413
435
  scr_ee_emit_meta(em, "removeListener", name);
414
436
  scr_str_release(name);
@@ -444,7 +466,7 @@ ScrEmitter *scr_emitter_off_dyn(ScrEmitter *em, ScrStr *name, const ScrDyn *cb)
444
466
  * the simple '-quoted spelling — listener mistakes are words, not escape
445
467
  * zoos). */
446
468
  void scr_emitter_check_listener(const ScrDyn *cb) {
447
- /* The shared gate lives in the always-linked DOM core (scr_json.c) so
469
+ /* The shared gate lives in the always-linked dyn core (scr_json.c) so
448
470
  * the handle dispatchers' .on paths render the same shapes without
449
471
  * linking this unit. */
450
472
  scr_dyn_check_listener(cb, "listener");
@@ -456,6 +478,13 @@ void scr_emitter_check_listener(const ScrDyn *cb) {
456
478
  * firing the meta event — and the whole-emitter form does every other
457
479
  * event first (bucket order), 'removeListener' itself LAST. Returns em +1. */
458
480
  static void scr_ee_remove_all_named(ScrEmitter *em, ScrEeBucket *b, bool meta) {
481
+ if (b->n == 0) {
482
+ /* An empty rank-holding bucket reaches here only from the WIPE scan
483
+ * (the named form skips undefined keys — Node no-ops and the rank
484
+ * stays): drop it, the wipe resets _events wholesale. */
485
+ scr_ee_bucket_drop(em->reg, b);
486
+ return;
487
+ }
459
488
  if (!meta) {
460
489
  for (size_t i = 0; i < b->n; i++) scr_ee_entry_unref(b->ls[i]);
461
490
  b->n = 0;
@@ -497,7 +526,26 @@ ScrEmitter *scr_emitter_remove_all(ScrEmitter *em, ScrStr *name, bool all) {
497
526
  bool meta = scr_ee_has(reg, "removeListener");
498
527
  if (!all) {
499
528
  ScrEeBucket *b = scr_ee_bucket_find(reg, name->data, name->len);
500
- if (b) scr_ee_remove_all_named(em, b, meta);
529
+ /* A rank-holding empty bucket is Node's `events[type] === undefined`:
530
+ * the named form no-ops there (the rank survives). A POPULATED name
531
+ * deletes its key even in shape mode — but through the meta path each
532
+ * listener leaves via removeListener, whose shape rule KEEPS the
533
+ * emptied rank (Node's exact split). */
534
+ if (b && b->n > 0) {
535
+ scr_ee_remove_all_named(em, b, meta);
536
+ if (!meta) {
537
+ /* Node's non-meta named branch: the count hitting zero replaces
538
+ * _events wholesale — every rank (pre-created included) goes,
539
+ * though kShapeMode itself stays set (Node's exact quirk). */
540
+ bool any_live = false;
541
+ for (ScrEeBucket *w = reg->head; w; w = w->next) {
542
+ if (w->n > 0) { any_live = true; break; }
543
+ }
544
+ if (!any_live) {
545
+ while (reg->head) scr_ee_bucket_drop(reg, reg->head);
546
+ }
547
+ }
548
+ }
501
549
  return scr_emitter_retain(em);
502
550
  }
503
551
  /* Every event except 'removeListener' first, in bucket order; then
@@ -515,6 +563,10 @@ ScrEmitter *scr_emitter_remove_all(ScrEmitter *em, ScrStr *name, bool all) {
515
563
  }
516
564
  ScrEeBucket *rl = scr_ee_bucket_find(reg, "removeListener", 14);
517
565
  if (rl) scr_ee_remove_all_named(em, rl, meta);
566
+ /* The wipe replaces _events wholesale in Node — pre-created ranks are
567
+ * gone and the emitter leaves shape mode. */
568
+ while (reg->head) scr_ee_bucket_drop(reg, reg->head);
569
+ reg->shape = false;
518
570
  return scr_emitter_retain(em);
519
571
  }
520
572
 
@@ -633,12 +685,13 @@ double scr_emitter_listener_count_fn(ScrEmitter *em, ScrStr *name, ScrClosure *f
633
685
  }
634
686
 
635
687
  /* eventNames(): +1 string[] of the bucket names in first-registration
636
- * order (exactly Node's _events key order). */
688
+ * order (exactly Node's _events key order). Reserved-but-empty buckets
689
+ * (the stream pre-created keys) are invisible until a listener lands. */
637
690
  ScrArr *scr_emitter_event_names(ScrEmitter *em) {
638
691
  ScrArr *out = scr_arr_new(SCR_ELEM_STR, 0);
639
692
  if (em->reg) {
640
693
  for (ScrEeBucket *b = em->reg->head; b; b = b->next) {
641
- scr_arr_push_ref(out, scr_str_retain(b->name));
694
+ if (b->n > 0) scr_arr_push_ref(out, scr_str_retain(b->name));
642
695
  }
643
696
  }
644
697
  return out;
package/src/scr_http.c CHANGED
@@ -2774,7 +2774,7 @@ void scr_http_resp_thunk_res(ScrClosure *cb, ScrHttpReq *res) {
2774
2774
  *
2775
2775
  * The receiver surface behind `server.on('request', mustCall((req, res)
2776
2776
  * => ...))`: the mustCall wrapper makes the listener dyn, req/res box
2777
- * into the DOM as HANDLE values, and member uses inside the listener
2777
+ * into the checked-dynamic tree as HANDLE values, and member uses inside the listener
2778
2778
  * body dispatch HERE — onto the very entry points the static lowerings
2779
2779
  * use, with per-argument gates shaped like Node's (the libCall signature
2780
2780
  * table in validate.ts is the modeled surface; this dispatcher mirrors
@@ -2785,7 +2785,7 @@ void scr_http_resp_thunk_res(ScrClosure *cb, ScrHttpReq *res) {
2785
2785
  * - a member the class HAS but this table does not model: a LOUD
2786
2786
  * "not supported yet" Error — never a silent wrong answer;
2787
2787
  * - anything else: Node's "<spelling> is not a function" for calls,
2788
- * the undefined singleton for reads (the DOM's own-property answer;
2788
+ * the undefined singleton for reads (the checked-dynamic tree's own-property answer;
2789
2789
  * SEMANTICS.md documents the remainder).
2790
2790
  *
2791
2791
  * Ownership: invoke/get/set receive BORROWED handles/args and answer
@@ -3009,7 +3009,7 @@ static ScrDyn *scr_http_dynh_req_get(void *h, const char *key, size_t key_len) {
3009
3009
  double st = scr_http_req_status(r);
3010
3010
  /* Node's IncomingMessage constructor sets statusCode = null; only a
3011
3011
  * client response assigns a number (the static lane's typed surface
3012
- * spells this number|undefined — the DOM can afford Node's null). */
3012
+ * spells this number|undefined — the checked-dynamic tree can afford Node's null). */
3013
3013
  return st < 0 ? scr_dyn_new_null() : scr_dyn_new_num(st);
3014
3014
  }
3015
3015
  if (strcmp(key, "statusMessage") == 0) {
@@ -3049,7 +3049,7 @@ static bool scr_http_dynh_req_set(void *h, const char *key, size_t key_len, cons
3049
3049
 
3050
3050
  /* ── ServerResponse (SCR_DYNH_HTTP_RES) ──────────────────────────────── */
3051
3051
 
3052
- /* writeHead's header-object form over a DOM OBJ: flatten into the
3052
+ /* writeHead's header-object form over a dyn OBJ: flatten into the
3053
3053
  * [k0, v0, k1, v1, ...] pairs the static lowering feeds
3054
3054
  * scr_http_res_write_head_pairs (repeats append — array values expand to
3055
3055
  * consecutive same-name pairs). Numbers format through String(n). NULL =
package/src/scr_http2.c CHANGED
@@ -2589,7 +2589,7 @@ void scr_http2_settings_thunk0(ScrClosure *cb, ScrDyn *settings) {
2589
2589
  }
2590
2590
 
2591
2591
  /* The typed lane's DYN spellings (session receivers with mustCall-style
2592
- * callbacks): the raw DOM function crosses the lib boundary and fires
2592
+ * callbacks): the raw dyn function crosses the lib boundary and fires
2593
2593
  * through the same runtime-built listener closures the dyn handle ops
2594
2594
  * use — Node's exact payload shapes. */
2595
2595
  static void scr_h2_dyn_fire_settings(ScrClosure *cb, ScrDyn *settings /*+1*/);
@@ -2689,7 +2689,7 @@ void scr_http2_goaway_thunk0(ScrClosure *cb, double code, double last) {
2689
2689
  * parameter, or a handle stored in an untyped binding) dispatch their
2690
2690
  * members here, the scr_http.c req/res ops precedent: `.on(...)` wires a
2691
2691
  * runtime-built listener closure over the dyn callback, header events
2692
- * build a DOM headers object from the pairs, method calls decode dyn
2692
+ * build a dyn headers object from the pairs, method calls decode dyn
2693
2693
  * args onto the static entry points, and property reads box the scalar
2694
2694
  * answers. Real-but-unmodeled members throw the loud ladder; names the
2695
2695
  * surface never had throw Node's "<x> is not a function". */
@@ -2791,7 +2791,7 @@ static void scr_h2_dyn_fire_goaway(ScrClosure *cb, double code, double last) {
2791
2791
  scr_dyn_release(fn);
2792
2792
  }
2793
2793
 
2794
- /* Flatten a DOM headers object into the flat pairs array (numbers via
2794
+ /* Flatten a dyn headers object into the flat pairs array (numbers via
2795
2795
  * String(n); the :status/:method/... pseudo-headers pass through). */
2796
2796
  static ScrArr *scr_h2_dom_to_pairs(const ScrDyn *headers) {
2797
2797
  ScrArr *pairs = scr_arr_new(SCR_ELEM_STR, headers->v.obj.len * 2);
package/src/scr_inspect.c CHANGED
@@ -700,7 +700,7 @@ ScrStr *scr_insp_error(ScrError *e, double recurse, double depth) {
700
700
  return out;
701
701
  }
702
702
 
703
- /* ── dyn values (the checked-dynamic JSON DOM) ───────────────────────── */
703
+ /* ── dyn values (the checked-dynamic JSON dyn) ───────────────────────── */
704
704
 
705
705
  /* Property-name rendering: bare when the key matches Node's keyStrRegExp
706
706
  * (/^[a-zA-Z_][a-zA-Z_0-9]*$/, '__proto__' excepted), quoted otherwise. */
@@ -735,7 +735,7 @@ ScrStr *scr_insp_key(ScrStr *k) {
735
735
  /* The full inspect over a dyn tree — the one runtime type whose SHAPE
736
736
  * lives in the value, so the traversal lives here instead of a
737
737
  * synthesized helper. Same engine, same defaults. dyn-boxed bytes render
738
- * in the DOM's documented Uint8Array identity (SEMANTICS.md). */
738
+ * in the checked-dynamic tree's documented Uint8Array identity (SEMANTICS.md). */
739
739
  ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) {
740
740
  switch (d->kind) {
741
741
  case SCR_DYN_NULL:
@@ -837,7 +837,7 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) {
837
837
  return scr_str_new("", 0);
838
838
  }
839
839
  case SCR_DYN_JSVAL: {
840
- /* An island value inside the DOM: Node prints the engine object's
840
+ /* An island value inside the checked-dynamic tree: Node prints the engine object's
841
841
  * property dump — fence loudly, naming the engine typeof (the
842
842
  * scr_insp_jsval wording for bare 'any' composites). */
843
843
  ScrStr *t = scr_dyn_typeof(d); /* routes to the engine; +1 */
package/src/scr_island.c CHANGED
@@ -383,6 +383,7 @@ enum {
383
383
  ISL_H_OBJWALK,
384
384
  ISL_H_HASOWN,
385
385
  ISL_H_ASSIGN,
386
+ ISL_H_ITERDRAIN,
386
387
  ISL_H_COUNT,
387
388
  };
388
389
 
@@ -437,7 +438,22 @@ static const char isl_prelude[] =
437
438
  * semantics (own-key order, getters running, ToObject refusals). */
438
439
  "(o,m)=>m===0?Object.keys(o):m===1?Object.values(o):Object.entries(o),"
439
440
  "(o,k)=>Object.hasOwn(o,k),"
440
- "(t,s)=>Object.assign(t,s)]";
441
+ "(t,s)=>Object.assign(t,s),"
442
+ /* ISL_H_ITERDRAIN: the ENGINE's own iterator protocol drained into a
443
+ * fresh array (for-of/destructuring/spread over a wrapped value —
444
+ * generators, Maps, Sets, Symbol.iterator implementations all step
445
+ * exactly as Node runs them). The guard front-runs the not-iterable
446
+ * TypeError in the CALLER's wording: m=1 is V8's spread-call text,
447
+ * s (when defined) the compile-time source spelling verbatim, else
448
+ * the kind wording (iterN's). */
449
+ "(v,m,s)=>{if(v===undefined||v===null||typeof v[Symbol.iterator]!==\"function\"){"
450
+ "if(m===1)throw new TypeError(\"Spread syntax requires ...iterable[Symbol.iterator] to be a function\");"
451
+ "if(s!==undefined)throw new TypeError(s);"
452
+ "let d;if(v===undefined)d=\"undefined\";else if(v===null)d=\"object null\";"
453
+ "else if(typeof v===\"number\")d=\"number \"+v;else if(typeof v===\"boolean\")d=\"boolean \"+v;"
454
+ "else if(typeof v===\"function\")d=\"function\";else d=\"object\";"
455
+ "throw new TypeError(d+\" is not iterable (cannot read property Symbol(Symbol.iterator))\")}"
456
+ "const o=[];for(const x of v)o.push(x);return o}]";
441
457
 
442
458
  static void isl_free_boot(void);
443
459
  static void isl_prom_wraps_teardown(void);
@@ -963,11 +979,11 @@ ScrJsval *scr_jsval_from_json(const ScrStr *json) {
963
979
  return isl_cell_new(v);
964
980
  }
965
981
 
966
- /* ── marshal in: a CHECKED-DYNAMIC (DOM) value ────────────────────────
982
+ /* ── marshal in: a CHECKED-DYNAMIC (dyn) value ────────────────────────
967
983
  * `unknown` flowing into 'any'-typed code (`const cfg = isJson ?
968
- * JSON.parse(text) : islandParser(text)`): the DOM tree rebuilds as
984
+ * JSON.parse(text) : islandParser(text)`): the dyn tree rebuilds as
969
985
  * engine values — a DEEP COPY, the jsMarshal aliasing stance. Data kinds
970
- * only: a DOM carrying a boxed function, a native handle, or a promise
986
+ * only: a dyn carrying a boxed function, a native handle, or a promise
971
987
  * throws the catchable TypeError naming the kind (the box's thunk calls
972
988
  * STATIC code the engine cannot re-enter through a data copy). */
973
989
  static const char *isl_dyn_unmarshalable(const ScrDyn *d) {
@@ -996,14 +1012,14 @@ static const char *isl_dyn_unmarshalable(const ScrDyn *d) {
996
1012
  }
997
1013
  }
998
1014
 
999
- static JSValue isl_dynfn_new(const ScrDyn *d); /* the DOM-function shim, below */
1015
+ static JSValue isl_dynfn_new(const ScrDyn *d); /* the checked-dynamic tree-function shim, below */
1000
1016
 
1001
1017
  static JSValue isl_from_dyn(const ScrDyn *d) {
1002
1018
  switch (d->kind) {
1003
1019
  case SCR_DYN_FUNC:
1004
- /* A boxed DOM function enters as ONE generic host-function shim over
1005
- * its uniform call thunk (ScrDynThunk): engine args wrap as DOM
1006
- * values, the thunk runs, the DOM result converts back. Each
1020
+ /* A boxed dyn function enters as ONE generic host-function shim over
1021
+ * its uniform call thunk (ScrDynThunk): engine args wrap as dyn
1022
+ * values, the thunk runs, the dyn result converts back. Each
1007
1023
  * crossing mints a fresh engine function (documented). */
1008
1024
  return isl_dynfn_new(d);
1009
1025
  case SCR_DYN_UNDEF:
@@ -1017,7 +1033,7 @@ static JSValue isl_from_dyn(const ScrDyn *d) {
1017
1033
  case SCR_DYN_STR:
1018
1034
  return JS_NewStringLen(isl_ctx, d->v.str->data, d->v.str->len);
1019
1035
  case SCR_DYN_BYTES:
1020
- /* Only u8 payloads reach the DOM today (scr_json.c's stringify note). */
1036
+ /* Only u8 payloads reach the checked-dynamic tree today (scr_json.c's stringify note). */
1021
1037
  return JS_NewUint8ArrayCopy(isl_ctx, d->v.bytes->data, d->v.bytes->len);
1022
1038
  case SCR_DYN_ARR: {
1023
1039
  JSValue arr = JS_NewArray(isl_ctx);
@@ -1047,7 +1063,7 @@ static JSValue isl_from_dyn(const ScrDyn *d) {
1047
1063
  return obj;
1048
1064
  }
1049
1065
  case SCR_DYN_JSVAL:
1050
- /* An engine value riding inside DOM data: embed the SAME engine
1066
+ /* An engine value riding inside dyn data: embed the SAME engine
1051
1067
  * value by reference — the identity round trip, member position. */
1052
1068
  return JS_DupValue(isl_ctx, d->v.jsval.cell->v);
1053
1069
  default:
@@ -1057,7 +1073,7 @@ static JSValue isl_from_dyn(const ScrDyn *d) {
1057
1073
  }
1058
1074
 
1059
1075
  ScrJsval *scr_jsval_from_dyn(const ScrDyn *d) {
1060
- /* The identity round trip: an engine value that crossed into the DOM
1076
+ /* The identity round trip: an engine value that crossed into the checked-dynamic tree
1061
1077
  * (scr_dyn_from_jsval) and back is the SAME engine value, by reference
1062
1078
  * — the one direction that used to throw (SEMANTICS.md supersedes the
1063
1079
  * "one unbridgeable mix"). */
@@ -1079,10 +1095,10 @@ ScrJsval *scr_jsval_from_dyn(const ScrDyn *d) {
1079
1095
  return isl_cell_new(v);
1080
1096
  }
1081
1097
 
1082
- /* ── the jsval→DOM crossing (SCR_DYN_JSVAL's constructor + ops) ───────
1098
+ /* ── the jsval→dyn crossing (SCR_DYN_JSVAL's constructor + ops) ───────
1083
1099
  * The reverse direction: an 'any'-typed engine value flowing into an
1084
- * 'unknown'/'object'/JS-residue slot wraps BY REFERENCE as the DOM's
1085
- * island kind. The DOM core (scr_json.c) stays island-free — it routes
1100
+ * 'unknown'/'object'/JS-residue slot wraps BY REFERENCE as the checked-dynamic tree's
1101
+ * island kind. The dyn core (scr_json.c) stays island-free — it routes
1086
1102
  * the armed operations (typeof/truthy/String()/===, the narrowing
1087
1103
  * tests) through these installed ops; everything un-armed there keeps
1088
1104
  * the loud "not supported yet" ladder. */
@@ -1101,7 +1117,7 @@ static bool isl_dynjs_is_error(ScrJsval *cell) { return JS_IsError(cell->v); }
1101
1117
 
1102
1118
  static const ScrDynJsvalOps isl_dynjs_ops;
1103
1119
 
1104
- /* The jsval→DOM wrap over a RAW engine value (BORROWED) — the scalar
1120
+ /* The jsval→dyn wrap over a RAW engine value (BORROWED) — the scalar
1105
1121
  * normalization the cell constructor applies, shared by the routed-op
1106
1122
  * result conversions (which hold a JSValue, not a cell). Composites mint
1107
1123
  * a fresh cell over the SAME engine value (identity is the value — the
@@ -1249,7 +1265,7 @@ static ScrDyn *isl_dynjs_obj_walk(ScrJsval *cell, int mode) {
1249
1265
  isl_bridge_exception();
1250
1266
  return NULL;
1251
1267
  }
1252
- /* The engine array unpacks into a NATIVE DOM array (keys are DOM
1268
+ /* The engine array unpacks into a NATIVE dyn array (keys are dyn
1253
1269
  * strings, values wrap per element, entries become native pairs) so
1254
1270
  * the results iterate/index/measure at native speed. */
1255
1271
  int64_t len = 0;
@@ -1309,6 +1325,36 @@ static bool isl_dynjs_assign(ScrJsval *cell, const ScrDyn *src) {
1309
1325
 
1310
1326
  static ScrStr *isl_dynjs_to_json(ScrJsval *cell) { return scr_jsval_to_json(cell); }
1311
1327
 
1328
+ static ScrDyn *isl_dynjs_iter_drain(ScrJsval *cell, bool spread, const ScrStr *spell) {
1329
+ isl_entry();
1330
+ JSValue m = JS_NewInt32(isl_ctx, spread ? 1 : 0);
1331
+ JSValue s = spell && spell->len > 0
1332
+ ? JS_NewStringLen(isl_ctx, spell->data, spell->len)
1333
+ : JS_UNDEFINED;
1334
+ JSValue argv[3] = {cell->v, m, s};
1335
+ JSValue r = JS_Call(isl_ctx, isl_helpers[ISL_H_ITERDRAIN], JS_UNDEFINED, 3, argv);
1336
+ JS_FreeValue(isl_ctx, s);
1337
+ if (JS_IsException(r)) {
1338
+ isl_bridge_exception();
1339
+ return NULL;
1340
+ }
1341
+ /* The drained engine array unpacks into a fresh dyn array — elements
1342
+ * wrap back scalar-normalized (composites stay engine values by
1343
+ * reference), exactly the obj_walk unpack. */
1344
+ int64_t len = 0;
1345
+ JSValue lv = JS_GetPropertyStr(isl_ctx, r, "length");
1346
+ JS_ToInt64(isl_ctx, &len, lv);
1347
+ JS_FreeValue(isl_ctx, lv);
1348
+ ScrDyn *out = scr_dyn_new_arr();
1349
+ for (int64_t i = 0; i < len; i++) {
1350
+ JSValue e = JS_GetPropertyUint32(isl_ctx, r, (uint32_t)i);
1351
+ scr_dyn_arr_push(out, isl_dyn_from_value(e));
1352
+ JS_FreeValue(isl_ctx, e);
1353
+ }
1354
+ JS_FreeValue(isl_ctx, r);
1355
+ return out;
1356
+ }
1357
+
1312
1358
  static const ScrDynJsvalOps isl_dynjs_ops = {
1313
1359
  isl_dynjs_release,
1314
1360
  isl_dynjs_typeof,
@@ -1326,14 +1372,15 @@ static const ScrDynJsvalOps isl_dynjs_ops = {
1326
1372
  isl_dynjs_has_own,
1327
1373
  isl_dynjs_assign,
1328
1374
  isl_dynjs_to_json,
1375
+ isl_dynjs_iter_drain,
1329
1376
  };
1330
1377
 
1331
1378
  ScrDyn *scr_dyn_from_jsval(ScrJsval *cell) {
1332
1379
  isl_entry();
1333
1380
  JSValue v = cell->v;
1334
- /* Scalar normalization: engine-reported scalars become the NATIVE DOM
1381
+ /* Scalar normalization: engine-reported scalars become the NATIVE dyn
1335
1382
  * kinds at wrap time (the strict exits cannot fail on them), so every
1336
- * scalar path in the DOM core — ===, typeof tests, JSON of leaves —
1383
+ * scalar path in the dyn core — ===, typeof tests, JSON of leaves —
1337
1384
  * stays untouched and the JSVAL kind never competes with them. */
1338
1385
  if (JS_IsUndefined(v)) return scr_dyn_retain(scr_dyn_undefined());
1339
1386
  if (JS_IsNull(v)) return scr_dyn_new_null();
@@ -1653,7 +1700,7 @@ static const JSClassDef isl_hostfn_class = {
1653
1700
  .finalizer = isl_hostfn_finalizer,
1654
1701
  };
1655
1702
 
1656
- static JSClassID isl_dynfn_class_id; /* the DOM-function shim, below */
1703
+ static JSClassID isl_dynfn_class_id; /* the checked-dynamic tree-function shim, below */
1657
1704
  static const JSClassDef isl_dynfn_class;
1658
1705
 
1659
1706
  static void isl_register_hostfn_class(void) {
@@ -1818,13 +1865,13 @@ ScrJsval *scr_jsval_from_closure(ScrClosure *c, int arity,
1818
1865
  return isl_cell_new(fn);
1819
1866
  }
1820
1867
 
1821
- /* ── the generic DOM-function shim (a boxed SCR_DYN_FUNC entering the
1868
+ /* ── the generic dyn-function shim (a boxed SCR_DYN_FUNC entering the
1822
1869
  * island) ─────────────────────────────────────────────────────────────
1823
1870
  * ONE shim serves every boxed function because the box's call thunk is a
1824
- * single uniform C signature (ScrDynThunk): engine arguments wrap as DOM
1825
- * values (scalar-normalizing — the jsval→DOM constructor's stance), the
1871
+ * single uniform C signature (ScrDynThunk): engine arguments wrap as dyn
1872
+ * values (scalar-normalizing — the jsval→dyn constructor's stance), the
1826
1873
  * thunk validates them against the closure's declared parameter types
1827
- * and runs it, and the DOM result converts back through the from_dyn
1874
+ * and runs it, and the dyn result converts back through the from_dyn
1828
1875
  * rules (wrapped cells by reference, data as a deep copy, nested
1829
1876
  * functions through this same shim). OWNERSHIP: the engine function's
1830
1877
  * opaque box owns ONE reference on the whole ScrDyn FUNC node (closure
@@ -1875,7 +1922,7 @@ static JSValue isl_dynfn_invoke(JSContext *ctx, JSValueConst this_val, int argc,
1875
1922
  return isl_throw_pending(ctx);
1876
1923
  }
1877
1924
  if (!r) return JS_UNDEFINED;
1878
- /* The thunk's DOM result converts back per the from_dyn rules; a kind
1925
+ /* The thunk's dyn result converts back per the from_dyn rules; a kind
1879
1926
  * with no crossing (a handle, a promise) throws the same catchable
1880
1927
  * TypeError from_dyn would. */
1881
1928
  const char *bad = isl_dyn_unmarshalable(r);
@@ -1888,7 +1935,7 @@ static JSValue isl_dynfn_invoke(JSContext *ctx, JSValueConst this_val, int argc,
1888
1935
  return out; /* JS_EXCEPTION passes through as the engine throw */
1889
1936
  }
1890
1937
 
1891
- /* A fresh engine function over a boxed DOM function (borrows d — the
1938
+ /* A fresh engine function over a boxed dyn function (borrows d — the
1892
1939
  * opaque box retains it). */
1893
1940
  static JSValue isl_dynfn_new(const ScrDyn *d) {
1894
1941
  JSValue boxv = JS_NewObjectClass(isl_ctx, isl_dynfn_class_id);
@@ -1959,11 +2006,13 @@ static void isl_prom_wrap_entry(ScrFiber *self, void *arg) {
1959
2006
  bool b = false;
1960
2007
  ScrStr *s = NULL;
1961
2008
  ScrJsval *j = NULL;
2009
+ ScrArr *ja = NULL;
1962
2010
  switch (w->payload) {
1963
2011
  case SCR_ISLP_F64: f = scr_await_f64(w->p); break;
1964
2012
  case SCR_ISLP_BOOL: b = scr_await_bool(w->p); break;
1965
2013
  case SCR_ISLP_STR: s = scr_await_str(w->p); break;
1966
2014
  case SCR_ISLP_JSVAL: j = (ScrJsval *)scr_await_ref(w->p); break;
2015
+ case SCR_ISLP_JSVAL_ARR: ja = (ScrArr *)scr_await_ref(w->p); break;
1967
2016
  default: scr_await_void(w->p); break;
1968
2017
  }
1969
2018
  bool rejected = scr_exc_pending();
@@ -1977,11 +2026,26 @@ static void isl_prom_wrap_entry(ScrFiber *self, void *arg) {
1977
2026
  case SCR_ISLP_BOOL: v = JS_NewBool(isl_ctx, b); break;
1978
2027
  case SCR_ISLP_STR: v = s ? JS_NewStringLen(isl_ctx, s->data, s->len) : JS_UNDEFINED; break;
1979
2028
  case SCR_ISLP_JSVAL: v = j ? JS_DupValue(isl_ctx, j->v) : JS_UNDEFINED; break;
2029
+ case SCR_ISLP_JSVAL_ARR:
2030
+ /* A native array of engine cells fulfills: a fresh engine array
2031
+ * over the SAME engine values (identity crosses, spine a copy). */
2032
+ if (ja) {
2033
+ v = JS_NewArray(isl_ctx);
2034
+ for (size_t i = 0; i < ja->len; i++) {
2035
+ ScrJsval *cell = (ScrJsval *)scr_arr_get_ref(ja, (double)i); /* +1 */
2036
+ JS_SetPropertyUint32(isl_ctx, v, (uint32_t)i, JS_DupValue(isl_ctx, cell->v));
2037
+ scr_jsval_release(cell);
2038
+ }
2039
+ } else {
2040
+ v = JS_UNDEFINED;
2041
+ }
2042
+ break;
1980
2043
  default: v = JS_UNDEFINED; break;
1981
2044
  }
1982
2045
  }
1983
2046
  if (s) scr_str_release(s);
1984
2047
  if (j) scr_jsval_release(j);
2048
+ if (ja) scr_arr_release(ja);
1985
2049
  JSValue r = JS_Call(isl_ctx, rejected ? w->reject : w->resolve, JS_UNDEFINED, 1, &v);
1986
2050
  JS_FreeValue(isl_ctx, v);
1987
2051
  if (JS_IsException(r)) {
@@ -2095,6 +2159,20 @@ static JSValue isl_bridge_settle(JSContext *ctx, JSValueConst this_val, int argc
2095
2159
  /* fulfill_ref takes the +1 cell; awaiters retain their own out. */
2096
2160
  scr_promise_fulfill_ref(b->p, isl_cell_new(JS_DupValue(ctx, v)),
2097
2161
  scr_jsval_retain_v, scr_jsval_release_v, NULL);
2162
+ } else if (b->payload == SCR_ISLP_JSVAL_ARR) {
2163
+ /* An `any[]`-declared fulfillment (the inferred loadPlugins return):
2164
+ * the engine array exits Array.isArray-gated, elements BY REFERENCE
2165
+ * (the jsval-element-array exit). A lying fulfillment (non-array)
2166
+ * REJECTS the static promise with the boundary TypeError —
2167
+ * trust-but-verify at the settle, like every dyn→static edge. */
2168
+ ScrJsval *cell = isl_cell_new(JS_DupValue(ctx, v));
2169
+ ScrArr *arr = scr_jsval_exit_jsval_arr(cell);
2170
+ scr_jsval_release(cell);
2171
+ if (!arr) {
2172
+ scr_promise_reject_pending(b->p);
2173
+ } else {
2174
+ scr_promise_fulfill_ref(b->p, arr, scr_arr_retain_v, scr_arr_release_v, NULL);
2175
+ }
2098
2176
  } else {
2099
2177
  scr_promise_fulfill_void(b->p);
2100
2178
  }
@@ -9567,6 +9645,37 @@ ScrBytes *scr_jsval_exit_bytes(ScrJsval *v) {
9567
9645
  return b;
9568
9646
  }
9569
9647
 
9648
+ /* Validated exit of an engine value into an `any[]`-declared slot (the
9649
+ * jsval-element-array spelling — withPlugins' `loadPlugins(plugins)`
9650
+ * boundary): the engine's Array.isArray gates (a non-array refuses with
9651
+ * the catchable boundary TypeError), then elements copy BY REFERENCE
9652
+ * into a native array of engine cells — identity preserved, length a
9653
+ * snapshot (the exit's aliasing stance: element IDENTITY crosses, the
9654
+ * spine is a copy). +1, or NULL with the exception pending. */
9655
+ ScrArr *scr_jsval_exit_jsval_arr(ScrJsval *v) {
9656
+ isl_entry();
9657
+ if (JS_IsArray(v->v) <= 0) {
9658
+ isl_exit_fail("an array", v);
9659
+ return NULL;
9660
+ }
9661
+ JSValue lv = JS_GetPropertyStr(isl_ctx, v->v, "length");
9662
+ int64_t len = 0;
9663
+ JS_ToInt64(isl_ctx, &len, lv);
9664
+ JS_FreeValue(isl_ctx, lv);
9665
+ ScrArr *out = scr_arr_new_ref(&scr_jsval_retain_v, &scr_jsval_release_v, NULL,
9666
+ len > 0 ? (size_t)len : 0);
9667
+ for (int64_t i = 0; i < len; i++) {
9668
+ JSValue e = JS_GetPropertyUint32(isl_ctx, v->v, (uint32_t)i); /* getters run */
9669
+ if (JS_IsException(e)) {
9670
+ isl_bridge_exception();
9671
+ scr_arr_release(out);
9672
+ return NULL;
9673
+ }
9674
+ scr_arr_push_ref(out, isl_cell_new(e)); /* ownership moves in */
9675
+ }
9676
+ return out;
9677
+ }
9678
+
9570
9679
  /* Composite exit: engine JSON.stringify, feeding the existing
9571
9680
  * json.parse + dynCheck walker pipeline on the static side. A value
9572
9681
  * JSON cannot represent (function, undefined, symbol at the top) comes