@scriptc/runtime 0.0.11 → 0.0.13

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.
package/src/scr_json.c CHANGED
@@ -1,6 +1,6 @@
1
1
  /* JSON + dynamic values (see scr_runtime.h for the API contract).
2
2
  *
3
- * - The ScrDyn DOM is the runtime shape of `unknown`: refcounted, owning
3
+ * - The ScrDyn dyn is the runtime shape of `unknown`: refcounted, owning
4
4
  * its children; releasing the root frees the tree recursively.
5
5
  * - scr_json_parse is a full RFC 8259 recursive-descent parser: null/
6
6
  * true/false, numbers (strtod after a strict grammar check — doubles
@@ -270,7 +270,7 @@ void scr_jb_edge_idx(ScrJsonBuf *b, size_t i) {
270
270
 
271
271
  /* ── circular guard for the typed→dyn converters (sc_td_*) ────────────
272
272
  * A recursive-typed value crossing into a checked-dynamic slot DEEP-
273
- * COPIES into the DOM; a cyclic value has no finite copy. Node never
273
+ * COPIES into the checked-dynamic tree; a cyclic value has no finite copy. Node never
274
274
  * copies (an unknown-typed binding shares the reference), so there is no
275
275
  * Node-exact error to throw — the conversion TRAPS loudly instead
276
276
  * (SEMANTICS.md documents the divergence). The emitted converters over
@@ -300,7 +300,7 @@ void scr_dyn_from_leave(void) {
300
300
  if (g_td_nseen > 0) g_td_nseen--;
301
301
  }
302
302
 
303
- /* ── DOM lifecycle ─────────────────────────────────────────────────────
303
+ /* ── dyn lifecycle ─────────────────────────────────────────────────────
304
304
  * Parse/release churn (a JSON round-trip loop allocates and frees every
305
305
  * node each iteration) runs on freelists instead of calloc/free: one list
306
306
  * per shape so arr/obj nodes keep their items/entries buffer across
@@ -431,7 +431,7 @@ void scr_dyn_arr_push(ScrDyn *arr, ScrDyn *item) {
431
431
  }
432
432
 
433
433
  /* Spread completion for a runtime-arity argument list (`f(...xs)` in the
434
- * checked-dynamic tier): JS's spread over the DOM's iterable kinds —
434
+ * checked-dynamic tier): JS's spread over the checked-dynamic tree's iterable kinds —
435
435
  * arrays element-by-element (retained), strings by code POINT (the string
436
436
  * iterator; astral chars arrive unsplit), bytes by byte; every other kind
437
437
  * throws V8's exact SPREAD-CALL TypeError (catchable, pending — callers
@@ -473,16 +473,24 @@ void scr_dyn_arr_push_spread(ScrDyn *arr, const ScrDyn *src, const char *what) {
473
473
  return;
474
474
  }
475
475
  if (src->kind == SCR_DYN_JSVAL) {
476
- /* An engine array IS iterable the generic not-iterable TypeError
477
- * would be a wrong claim. Loud fence (lane dyn-routing-ops). */
478
- scr_dyn_isl_fence(src, "spread");
476
+ /* A wrapped engine value spreads through the ENGINE's own iterator
477
+ * protocol (the routed iter_drain Symbol.iterator implementations,
478
+ * generators, Maps step exactly as Node runs them); a non-iterable
479
+ * throws V8's spread-call text from the guard, an iterating throw
480
+ * bridges with the engine's message. */
481
+ ScrDyn *pack = scr_dyn_jsval_ops()->iter_drain(src->v.jsval.cell, true, NULL);
482
+ if (!pack) return; /* pending */
483
+ for (size_t i = 0; i < pack->v.arr.len; i++) {
484
+ scr_dyn_arr_push(arr, scr_dyn_retain(pack->v.arr.items[i]));
485
+ }
486
+ scr_dyn_release(pack);
479
487
  return;
480
488
  }
481
489
  static const char msg[] = "Spread syntax requires ...iterable[Symbol.iterator] to be a function";
482
490
  scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
483
491
  }
484
492
 
485
- /* Destructuring pack over a DOM source (`const [a, b] = d`, a destructured
493
+ /* Destructuring pack over a dyn source (`const [a, b] = d`, a destructured
486
494
  * dyn callback param): the spread walk's iterable kinds collect into a
487
495
  * FRESH array — arrays element-by-element (retained), strings by code
488
496
  * point, bytes by byte — and every other kind throws V8's DESTRUCTURING
@@ -499,12 +507,14 @@ ScrDyn *scr_dyn_iter_pack(const ScrDyn *src, const ScrStr *msg) {
499
507
  scr_dyn_arr_push_spread(out, src, ""); /* iterable kinds never consult `what` */
500
508
  return out;
501
509
  }
502
- /* An engine-held value may well BE iterable claiming "not iterable"
503
- * would be a wrong answer. The routing lane owns iteration; until it
504
- * arms this, the island fence names the operation loudly. */
510
+ /* A wrapped engine value packs through the ENGINE's own iterator
511
+ * protocol (the routed iter_drain): elements wrap back scalar-
512
+ * normalized; a non-iterable throws the destructuring kind wording
513
+ * from the engine-side guard, an iterating throw bridges with the
514
+ * engine's message. The compile-time spelling is not threaded through
515
+ * (the engine's wording names the value's own kind). */
505
516
  if (src->kind == SCR_DYN_JSVAL) {
506
- scr_dyn_isl_fence(src, "destructuring");
507
- return NULL;
517
+ return scr_dyn_jsval_ops()->iter_drain(src->v.jsval.cell, false, msg);
508
518
  }
509
519
  if (msg != NULL && msg->len > 0) {
510
520
  scr_throw_error(SCR_ERR_TYPE, scr_str_new(msg->data, msg->len));
@@ -531,6 +541,19 @@ ScrDyn *scr_dyn_iter_pack(const ScrDyn *src, const ScrStr *msg) {
531
541
  return NULL;
532
542
  }
533
543
 
544
+ /* The for-of-over-dyn pack accessors: the emitted index loop drives them
545
+ * over a scr_dyn_iter_pack result (ARR by construction — the defensive
546
+ * arms cover nothing reachable from that lowering). Never throw. */
547
+ double scr_dyn_arr_len(const ScrDyn *d) {
548
+ return d->kind == SCR_DYN_ARR ? (double)d->v.arr.len : 0;
549
+ }
550
+ ScrDyn *scr_dyn_arr_at(const ScrDyn *d, double i) {
551
+ if (d->kind != SCR_DYN_ARR || i < 0 || i >= (double)d->v.arr.len) {
552
+ return scr_dyn_retain(scr_dyn_undefined());
553
+ }
554
+ return scr_dyn_retain(d->v.arr.items[(size_t)i]);
555
+ }
556
+
534
557
  /* Takes ownership of key (malloc'd) and value. Duplicate keys: the LATER
535
558
  * value wins (like JS JSON.parse) — the old value is released and the new
536
559
  * key buffer freed (the surviving entry keeps its original, equal key). */
@@ -557,7 +580,7 @@ static void scr_dyn_obj_put(ScrDyn *obj, char *key, size_t key_len, ScrDyn *valu
557
580
  e->value = value;
558
581
  }
559
582
 
560
- /* ── DOM construction (compiler-emitted converters & overflow reads) ───── */
583
+ /* ── dyn construction (compiler-emitted converters & overflow reads) ───── */
561
584
 
562
585
  /* THE undefined value: one immortal node (rc == SIZE_MAX skips every
563
586
  * retain/release and the freelists never see it). */
@@ -622,7 +645,7 @@ ScrBytes *scr_dyn_bytes_copy_out(const ScrDyn *d) {
622
645
  static bool scr_dyn_chunk_utf8;
623
646
  void scr_dyn_chunk_enc(bool utf8) { scr_dyn_chunk_utf8 = utf8; }
624
647
 
625
- /* One 'data' payload as the DOM value the current window dictates:
648
+ /* One 'data' payload as the dyn value the current window dictates:
626
649
  * a Buffer-flavored bytes box, or a string inside a setEncoding window. */
627
650
  ScrDyn *scr_dyn_new_chunk(const ScrBytes *b) {
628
651
  if (scr_dyn_chunk_utf8) {
@@ -634,7 +657,7 @@ ScrDyn *scr_dyn_new_chunk(const ScrBytes *b) {
634
657
  return scr_dyn_new_buffer_copy(b);
635
658
  }
636
659
 
637
- /* A boxed static function value (the compiler's static→DOM converters).
660
+ /* A boxed static function value (the compiler's static→dyn converters).
638
661
  * Ownership of the closure MOVES in; sig/name are static literals. */
639
662
  ScrDyn *scr_dyn_new_func(ScrClosure *clo, ScrDynThunk thunk, uint32_t arity, const char *sig, const char *name) {
640
663
  ScrDyn *d = scr_dyn_alloc(SCR_DYN_FUNC);
@@ -654,7 +677,7 @@ ScrDyn *scr_dyn_new_func(ScrClosure *clo, ScrDynThunk thunk, uint32_t arity, con
654
677
  ScrDyn *scr_dyn_call(const ScrDyn *d, ScrDyn *const *args, size_t argc, const char *what) {
655
678
  if (d->kind == SCR_DYN_JSVAL) {
656
679
  /* An ENGINE callee: the call routes through scr_jsval_call with the
657
- * uniform argument conversion (wrapped cells by reference, DOM data
680
+ * uniform argument conversion (wrapped cells by reference, dyn data
658
681
  * deep-copied, FUNC boxes through the host shim); a non-callable
659
682
  * engine value throws the ENGINE's own TypeError, bridged catchably.
660
683
  * `what` is unused — the engine's message names the failure. */
@@ -672,14 +695,14 @@ ScrDyn *scr_dyn_call(const ScrDyn *d, ScrDyn *const *args, size_t argc, const ch
672
695
  return d->v.fn.thunk(d->v.fn.clo, args, argc);
673
696
  }
674
697
 
675
- /* scr_dyn_call over a DOM ARRAY's elements — the spread-application form
698
+ /* scr_dyn_call over a dyn ARRAY's elements — the spread-application form
676
699
  * (`f(...args)` after the emitted argument array is built). Borrows both;
677
700
  * result owned (+1), or NULL with the exception pending. */
678
701
  ScrDyn *scr_dyn_apply(const ScrDyn *d, const ScrDyn *args, const char *what) {
679
702
  return scr_dyn_call(d, args->v.arr.items, args->v.arr.len, what);
680
703
  }
681
704
 
682
- /* ── native handles in the DOM (SCR_DYN_HANDLE) ───────────────────────
705
+ /* ── native handles in the checked-dynamic tree (SCR_DYN_HANDLE) ───────────────────────
683
706
  * Per-tag ops stamped by the owning units at main() (scr_http_dyn_install
684
707
  * / scr_net_dyn_install — the scr_net_install hook story), so this
685
708
  * always-linked core never references gated units. A missing tag at use
@@ -710,10 +733,10 @@ const ScrDynHandleOps *scr_dyn_handle_ops_of(const ScrDyn *d) {
710
733
  return scr_dyn_handle_ops(d->v.handle.tag);
711
734
  }
712
735
 
713
- /* errors.js's determineSpecificType over a DOM value — the "Received
736
+ /* errors.js's determineSpecificType over a dyn value — the "Received
714
737
  * ..." tail of Node's ERR_INVALID_ARG_TYPE messages. Renders into buf
715
738
  * when the shape needs a payload; returns the text either way. Lives
716
- * beside the DOM core (not the gated handle unit) because the always-
739
+ * beside the dyn core (not the gated handle unit) because the always-
717
740
  * linked argument validators (bytes, fs) render through it too. */
718
741
  const char *scr_dyn_specific_type(const ScrDyn *cb, char *detail, size_t cap) {
719
742
  const char *d = detail;
@@ -886,7 +909,7 @@ ScrDyn *scr_dyn_alloc_promise(void (*release_fn)(ScrPromise *p)) {
886
909
  return scr_dyn_alloc(SCR_DYN_PROMISE);
887
910
  }
888
911
 
889
- /* ── island values in the DOM (SCR_DYN_JSVAL) ─────────────────────────
912
+ /* ── island values in the checked-dynamic tree (SCR_DYN_JSVAL) ─────────────────────────
890
913
  * The gated constructor (scr_dyn_from_jsval, scr_island.c) builds through
891
914
  * this allocator view and installs the engine-routing ops — the
892
915
  * scr_dyn_alloc_promise story: a dynamic-free link never references
@@ -973,7 +996,7 @@ ScrDyn *scr_dyn_handle_key_get(const ScrDyn *d, const ScrStr *k) {
973
996
  scr_dyn_release(r);
974
997
  return NULL;
975
998
  }
976
- /* Unmodeled names answer undefined — the DOM's own-property stance
999
+ /* Unmodeled names answer undefined — the checked-dynamic tree's own-property stance
977
1000
  * (real-but-unmodeled members fence loudly inside ops->get instead;
978
1001
  * SEMANTICS.md documents the remainder). */
979
1002
  return r ? r : scr_dyn_retain(scr_dyn_undefined());
@@ -1066,7 +1089,7 @@ void scr_dyn_obj_set(ScrDyn *obj, const char *key, size_t key_len, ScrDyn *value
1066
1089
  scr_dyn_obj_put(obj, copy, key_len, value);
1067
1090
  }
1068
1091
 
1069
- /* ToBoolean over a DOM value (`v || dflt`, `if (v)` on a dyn operand):
1092
+ /* ToBoolean over a dyn value (`v || dflt`, `if (v)` on a dyn operand):
1070
1093
  * bool by value; number falsy exactly for 0, -0, and NaN; string falsy
1071
1094
  * exactly when empty; obj/arr/bytes/func always true; undefined and null
1072
1095
  * always false — JS-exact for every kind. Borrowed; never throws. */
@@ -1089,13 +1112,13 @@ bool scr_dyn_truthy(const ScrDyn *d) {
1089
1112
  }
1090
1113
  }
1091
1114
 
1092
- /* Bare `typeof v` on a dyn value: the DOM kind's JS answer (+1 string).
1115
+ /* Bare `typeof v` on a dyn value: the dyn kind's JS answer (+1 string).
1093
1116
  * null answers "object" — JS's oldest wart, preserved. */
1094
1117
  ScrStr *scr_dyn_typeof(const ScrDyn *d) {
1095
1118
  const char *s;
1096
1119
  /* An island value answers the ENGINE's typeof — "object" for the
1097
1120
  * wrapped objects/arrays, "function" for engine functions (row 1 of
1098
- * the jsval→DOM op table; scalars normalized away at wrap time). */
1121
+ * the jsval→dyn op table; scalars normalized away at wrap time). */
1099
1122
  if (d->kind == SCR_DYN_JSVAL) return scr_dyn_jsval_ops()->type_of(d->v.jsval.cell);
1100
1123
  switch (d->kind) {
1101
1124
  case SCR_DYN_UNDEF: s = "undefined"; break;
@@ -1114,9 +1137,9 @@ ScrStr *scr_dyn_typeof(const ScrDyn *d) {
1114
1137
  return scr_str_new(s, strlen(s));
1115
1138
  }
1116
1139
 
1117
- /* ── JSON.stringify over a DOM value (util.format's %j) ───────────────
1140
+ /* ── JSON.stringify over a dyn value (util.format's %j) ───────────────
1118
1141
  * The RUNTIME walk the type-directed serializers deliberately avoid for
1119
- * static values — a dyn value has no static type, so the DOM's own kinds
1142
+ * static values — a dyn value has no static type, so the checked-dynamic tree's own kinds
1120
1143
  * drive it, JS-exactly: insertion-ordered objects with undefined/function
1121
1144
  * members OMITTED, arrays rendering those as null, Buffer's toJSON shape
1122
1145
  * ({"type":"Buffer","data":[...]}), shortest-roundtrip numbers, escaped
@@ -1191,7 +1214,7 @@ static bool scr_dyn_json_write(ScrJsonBuf *b, const ScrDyn *d) {
1191
1214
  case SCR_DYN_JSVAL: {
1192
1215
  /* The ENGINE's own JSON.stringify text splices in (toJSON protocols,
1193
1216
  * cycle TypeErrors — the engine's, bridged catchably). An engine
1194
- * FUNCTION is absent under stringify, like the DOM's FUNC kind. */
1217
+ * FUNCTION is absent under stringify, like the checked-dynamic tree's FUNC kind. */
1195
1218
  if (scr_dyn_isl_typeof_is(d, "function")) return false;
1196
1219
  ScrStr *j = scr_dyn_jsval_ops()->to_json(d->v.jsval.cell);
1197
1220
  if (!j) return true; /* pending exception; caller checks */
@@ -1226,11 +1249,11 @@ ScrStr *scr_dyn_format_j(const ScrDyn *d) {
1226
1249
  return scr_jb_finish(&b);
1227
1250
  }
1228
1251
 
1229
- /* An %Error instance as a DOM object ({name, message[, code]}) — the
1252
+ /* An %Error instance as a dyn object ({name, message[, code]}) — the
1230
1253
  * checked-dynamic boundary's error shape (the exception-snapshot
1231
1254
  * convention emit-walkers uses). Borrows e; +1 result.
1232
1255
  *
1233
- * IDENTITY-CACHED: one error instance boxes to ONE DOM node, however many
1256
+ * IDENTITY-CACHED: one error instance boxes to ONE dyn node, however many
1234
1257
  * times it crosses (Node passes the error OBJECT through, so `found.error
1235
1258
  * === thrown` and re-crossings compare reference-equal — the tracing
1236
1259
  * suite's shape). The cache retains both sides for the process (like the
@@ -1262,7 +1285,7 @@ ScrDyn *scr_dyn_from_error(const ScrError *e) {
1262
1285
  if (scr_errdyn_cache[i].err == e) return scr_dyn_retain(scr_errdyn_cache[i].dyn);
1263
1286
  }
1264
1287
  ScrDyn *d = scr_dyn_new_obj();
1265
- scr_dyn_obj_set(d, "%error", 6, scr_dyn_new_bool(true)); /* the DOM's error marker */
1288
+ scr_dyn_obj_set(d, "%error", 6, scr_dyn_new_bool(true)); /* the checked-dynamic tree's error marker */
1266
1289
  scr_dyn_obj_set(d, "name", 4, scr_dyn_new_str(e->name));
1267
1290
  scr_dyn_obj_set(d, "message", 7, scr_dyn_new_str(e->message));
1268
1291
  if (e->code) scr_dyn_obj_set(d, "code", 4, scr_dyn_new_str(e->code));
@@ -1293,13 +1316,13 @@ ScrDyn *scr_dyn_from_error(const ScrError *e) {
1293
1316
 
1294
1317
  /* The %Error EXTRACTION (dynCheck of `u as Error` / an instanceof-Error
1295
1318
  * narrow, and the dyn-boxed thunk's Error-typed parameters): the REVERSE
1296
- * of scr_dyn_from_error, riding the same identity cache — a DOM error
1319
+ * of scr_dyn_from_error, riding the same identity cache — a dyn error
1297
1320
  * that came from a runtime ScrError answers THAT instance (+1), so an
1298
1321
  * error crossing out and back compares reference-equal (the tracing
1299
1322
  * suite's shape); an alien %error object rebuilds a runtime error from
1300
1323
  * its name/message/code (the vtable kind resolves from the name so a
1301
1324
  * later `instanceof TypeError` still answers) and ENTERS the cache, so
1302
- * its next boxing answers the same DOM node. The DOM node is borrowed. */
1325
+ * its next boxing answers the same dyn node. The dyn node is borrowed. */
1303
1326
  ScrError *scr_error_from_dyn(const ScrDyn *d) {
1304
1327
  ScrError *hit = scr_errdyn_err_of(d);
1305
1328
  if (hit) return hit;
@@ -1355,7 +1378,7 @@ void scr_errdyn_put(ScrError *e, ScrDyn *d) {
1355
1378
  * dyn method surface — a stream's 'data'/for-await chunk is the common
1356
1379
  * receiver): bytes decode per the encoding (Node's Buffer.toString,
1357
1380
  * utf8 default), strings answer themselves, numbers/booleans format
1358
- * JS-exactly, arrays join their DOM elements with ',' (recursively via
1381
+ * JS-exactly, arrays join their dyn elements with ',' (recursively via
1359
1382
  * JS's Array.prototype.toString), plain objects answer
1360
1383
  * "[object Object]", and undefined/null throw Node's TypeError. Borrows
1361
1384
  * both; +1 result. */
@@ -1417,7 +1440,7 @@ ScrStr *scr_dyn_to_string(const ScrDyn *d, const ScrStr *enc) {
1417
1440
  return scr_str_new(f, sizeof f - 1);
1418
1441
  }
1419
1442
  case SCR_DYN_JSVAL: {
1420
- /* The engine's own ToString (row 2 of the jsval→DOM op table): the
1443
+ /* The engine's own ToString (row 2 of the jsval→dyn op table): the
1421
1444
  * real prototype chain runs — user toString included, its throw
1422
1445
  * bridging. A bridged failure follows this function's existing
1423
1446
  * throw shape (pending exception + the empty-string dummy). */
@@ -1453,7 +1476,7 @@ ScrStr *scr_dyn_to_string_method(const ScrDyn *d, const ScrStr *enc, const ScrSt
1453
1476
  return scr_dyn_to_string(d, enc);
1454
1477
  }
1455
1478
 
1456
- /* JS String() over the DOM kind — the WebIDL ToString the web globals
1479
+ /* JS String() over the dyn kind — the WebIDL ToString the web globals
1457
1480
  * (atob/btoa, DOMException's name resolution) run on their arguments:
1458
1481
  * the unit kinds RENDER ("null"/"undefined") where the .toString() twin
1459
1482
  * above throws Node's property-read TypeError; every other kind matches
@@ -1464,7 +1487,7 @@ ScrStr *scr_dyn_string_coerce(const ScrDyn *d) {
1464
1487
  return scr_dyn_to_string(d, NULL);
1465
1488
  }
1466
1489
 
1467
- /* JS ToString over a DOM value WITH the object protocol (the WHATWG
1490
+ /* JS ToString over a dyn value WITH the object protocol (the WHATWG
1468
1491
  * USVString conversions — URLSearchParams names/values): an OBJ whose
1469
1492
  * own 'toString' member is callable is invoked with zero arguments (its
1470
1493
  * throw propagates, catchably); a non-primitive answer falls through to
@@ -1504,16 +1527,37 @@ ScrStr *scr_dyn_string_coerce_js(const ScrDyn *d) {
1504
1527
  * ignore silently — the loud choice, SEMANTICS.md). Receiver, key, and
1505
1528
  * value are all BORROWED (the member retains the value in). */
1506
1529
  static const char *scr_dyn_kind_name(const ScrDyn *d);
1530
+ /* `key in v` with a RUNTIME key (the compile-time dynHasKey fold, per
1531
+ * value): OBJ answers own-member presence, ARR answers 'length' or a
1532
+ * valid dense index, every other kind false (tsc admits `in` only on
1533
+ * object-typed operands). Borrows both; never throws. */
1534
+ bool scr_dyn_has_key(const ScrDyn *v, const ScrStr *key) {
1535
+ if (v->kind == SCR_DYN_OBJ) return scr_dyn_obj_get(v, key->data, key->len) != NULL;
1536
+ if (v->kind == SCR_DYN_ARR) {
1537
+ if (key->len == 6 && memcmp(key->data, "length", 6) == 0) return true;
1538
+ if (key->len == 0 || key->len > 15) return false;
1539
+ size_t idx = 0;
1540
+ for (size_t i = 0; i < key->len; i++) {
1541
+ char c = key->data[i];
1542
+ if (c < '0' || c > '9') return false;
1543
+ if (i > 0 && idx == 0) return false; /* a leading zero is no canonical index */
1544
+ idx = idx * 10 + (size_t)(c - '0');
1545
+ }
1546
+ return idx < v->v.arr.len;
1547
+ }
1548
+ return false;
1549
+ }
1550
+
1507
1551
  void scr_dyn_key_set(ScrDyn *recv, ScrStr *key, ScrDyn *value) {
1508
1552
  if (recv->kind == SCR_DYN_OBJ) {
1509
1553
  scr_dyn_obj_set(recv, key->data, key->len, scr_dyn_retain(value));
1510
1554
  return;
1511
1555
  }
1512
1556
  if (recv->kind == SCR_DYN_ARR) {
1513
- /* An INDEX write on a DOM array (`args[i] = v` — the variadic-rest
1557
+ /* An INDEX write on a dyn array (`args[i] = v` — the variadic-rest
1514
1558
  * rebuild): a canonical numeric key sets/extends the element, holes
1515
1559
  * padding with undefined exactly like JS length growth. Non-index
1516
- * keys keep the throw below (DOM arrays carry no expando table). */
1560
+ * keys keep the throw below (dyn arrays carry no expando table). */
1517
1561
  size_t idx = 0;
1518
1562
  int is_index = key->len > 0 && !(key->len > 1 && key->data[0] == '0');
1519
1563
  for (size_t i = 0; is_index && i < key->len; i++) {
@@ -1573,7 +1617,7 @@ void scr_dyn_key_set(ScrDyn *recv, ScrStr *key, ScrDyn *value) {
1573
1617
  scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&b));
1574
1618
  }
1575
1619
 
1576
- /* Node's JSON.stringify over a DOM: object members holding undefined DROP,
1620
+ /* Node's JSON.stringify over a dyn: object members holding undefined DROP,
1577
1621
  * array slots holding undefined print null. A bare undefined never arrives
1578
1622
  * (the record serializer drops the entry first); print null defensively. */
1579
1623
  void scr_jb_put_dyn(ScrJsonBuf *b, const ScrDyn *d) {
@@ -1594,7 +1638,7 @@ void scr_jb_put_dyn(ScrJsonBuf *b, const ScrDyn *d) {
1594
1638
  return;
1595
1639
  case SCR_DYN_BYTES: {
1596
1640
  /* Node's JSON.stringify over a typed array: the index-keyed object
1597
- * form — {"0":1,"1":2}. u8 payloads only reach the DOM today. */
1641
+ * form — {"0":1,"1":2}. u8 payloads only reach the checked-dynamic tree today. */
1598
1642
  scr_jb_putc(b, '{');
1599
1643
  for (size_t i = 0; i < d->v.bytes->len; i++) {
1600
1644
  if (i > 0) scr_jb_putc(b, ',');
@@ -1633,7 +1677,7 @@ void scr_jb_put_dyn(ScrJsonBuf *b, const ScrDyn *d) {
1633
1677
  case SCR_DYN_JSVAL: {
1634
1678
  /* The ENGINE's own JSON.stringify text splices in (toJSON protocols,
1635
1679
  * cycle TypeErrors — all the engine's, bridged catchably). An engine
1636
- * FUNCTION serializes like the DOM's FUNC kind (dropped from objects
1680
+ * FUNCTION serializes like the checked-dynamic tree's FUNC kind (dropped from objects
1637
1681
  * by the member loop below; null defensively elsewhere). */
1638
1682
  if (scr_dyn_isl_typeof_is(d, "function")) {
1639
1683
  scr_jb_puts(b, "null");
@@ -2232,9 +2276,9 @@ ScrDyn *scr_json_parse(ScrStr *text) {
2232
2276
  void *scr_dyn_retain_v(void *d) { return scr_dyn_retain((ScrDyn *)d); }
2233
2277
  void scr_dyn_release_v(void *d) { scr_dyn_release((ScrDyn *)d); }
2234
2278
 
2235
- /* JS === over two DOM values: scalars by value (NaN false, ±0 equal via
2279
+ /* JS === over two dyn values: scalars by value (NaN false, ±0 equal via
2236
2280
  * C ==; strings bytewise), units by kind, everything reference-shaped by
2237
- * node IDENTITY (the DOM's object identity). Never throws. */
2281
+ * node IDENTITY (the checked-dynamic tree's object identity). Never throws. */
2238
2282
  bool scr_dyn_strict_eq(const ScrDyn *a, const ScrDyn *b) {
2239
2283
  if (a->kind != b->kind) return false;
2240
2284
  switch (a->kind) {
@@ -2261,7 +2305,7 @@ bool scr_dyn_strict_eq(const ScrDyn *a, const ScrDyn *b) {
2261
2305
  /* Identity is the ENGINE VALUE, not the box or even the cell: two
2262
2306
  * wraps of one engine value compare ===-equal (the engine's own
2263
2307
  * strict equality answers). Mixed kinds already answered false above
2264
- * — a DOM copy is a different object, which is Node's answer too. */
2308
+ * — a dyn copy is a different object, which is Node's answer too. */
2265
2309
  return a == b || scr_dyn_jsval_ops()->strict_eq(a->v.jsval.cell, b->v.jsval.cell);
2266
2310
  default: return a == b;
2267
2311
  }
@@ -2290,10 +2334,10 @@ ScrDyn *scr_dyn_fn_get(const ScrDyn *d, const char *key, size_t key_len) {
2290
2334
  return NULL;
2291
2335
  }
2292
2336
 
2293
- /* ── structuredClone over the DOM ─────────────────────────────────────
2337
+ /* ── structuredClone over the checked-dynamic tree ─────────────────────────────────────
2294
2338
  * The JSON-safe subset plus bytes, deep. Functions and handle kinds
2295
2339
  * throw the spec's catchable DataCloneError; cycles throw the scriptc
2296
- * fence (the DOM cannot represent them — Node clones cycles; documented
2340
+ * fence (the checked-dynamic tree cannot represent them — Node clones cycles; documented
2297
2341
  * divergence). Option validation throws Node's exact TypeErrors and is
2298
2342
  * shared with scr_domex_clone. */
2299
2343
 
@@ -2353,7 +2397,7 @@ static ScrDyn *scr_sc_clone(const ScrDyn *v, const ScrScParent *up) {
2353
2397
  for (const ScrScParent *p = up; p != NULL; p = p->up) {
2354
2398
  if (p->node == v) {
2355
2399
  static const char msg[] =
2356
- "structuredClone of cyclic values (the runtime's DOM cannot represent cycles) is not supported yet";
2400
+ "structuredClone of cyclic values (the checked-dynamic tree cannot represent cycles) is not supported yet";
2357
2401
  scr_throw_error_msg(SCR_ERR_ERROR, msg, sizeof msg - 1);
2358
2402
  return NULL;
2359
2403
  }
@@ -2392,7 +2436,7 @@ static ScrDyn *scr_sc_clone(const ScrDyn *v, const ScrScParent *up) {
2392
2436
  case SCR_DYN_FUNC:
2393
2437
  case SCR_DYN_HANDLE:
2394
2438
  default: {
2395
- /* Node renders the value's source text; the DOM has none — the
2439
+ /* Node renders the value's source text; the checked-dynamic tree has none — the
2396
2440
  * String() rendering stands in ("function () { [native code] } could
2397
2441
  * not be cloned."). */
2398
2442
  ScrStr *what = scr_dyn_string_coerce(v);
@@ -2434,7 +2478,7 @@ ScrDyn *scr_structured_clone_missing(void) {
2434
2478
 
2435
2479
  bool scr_dyn_err_instanceof(const ScrDyn *d, double kind) {
2436
2480
  /* A JSVAL node never came from a runtime ScrError, so the cache miss
2437
- * below answers false — the documented contract ("a DOM value that
2481
+ * below answers false — the documented contract ("a dyn value that
2438
2482
  * never came from an error answers false"). An ENGINE TypeError held
2439
2483
  * in 'unknown' thus answers false where Node answers true: covered by
2440
2484
  * lane dom-jsval-long-tail (needs the engine's class instanceof). */
@@ -2448,7 +2492,7 @@ bool scr_dyn_err_instanceof(const ScrDyn *d, double kind) {
2448
2492
  return false;
2449
2493
  }
2450
2494
 
2451
- /* ── Object.keys/values/entries over the DOM ──────────────────────────
2495
+ /* ── Object.keys/values/entries over the checked-dynamic tree ──────────────────────────
2452
2496
  * JS own-key order: array-index keys ascending first, then the rest in
2453
2497
  * insertion order. entries answers [key, value] pairs; values RETAIN
2454
2498
  * the member nodes (reference semantics, like JS). Strings/arrays/bytes
@@ -2471,7 +2515,7 @@ static bool scr_dyn_key_is_index(const char *key, size_t len, double *out) {
2471
2515
 
2472
2516
  typedef enum { SCR_OBJWALK_KEYS, SCR_OBJWALK_VALUES, SCR_OBJWALK_ENTRIES } ScrObjWalk;
2473
2517
 
2474
- /* A fresh key string boxed into the DOM: scr_dyn_new_str RETAINS its
2518
+ /* A fresh key string boxed into the checked-dynamic tree: scr_dyn_new_str RETAINS its
2475
2519
  * argument, so the local +1 drops right after. */
2476
2520
  static ScrDyn *scr_dyn_objwalk_key(const char *key, size_t key_len) {
2477
2521
  ScrStr *k = scr_str_new(key, key_len);
@@ -2507,15 +2551,15 @@ static ScrDyn *scr_dyn_objwalk(const ScrDyn *v, ScrObjWalk mode) {
2507
2551
  }
2508
2552
  if (v->kind == SCR_DYN_JSVAL) {
2509
2553
  /* The ENGINE walks its own object (own-key order, getters running,
2510
- * Object.entries' pairs) and the results come back as a NATIVE DOM
2511
- * array — keys are DOM strings, values wrap per element. */
2554
+ * Object.entries' pairs) and the results come back as a NATIVE dyn
2555
+ * array — keys are dyn strings, values wrap per element. */
2512
2556
  return scr_dyn_jsval_ops()->obj_walk(v->v.jsval.cell, (int)mode);
2513
2557
  }
2514
2558
  ScrDyn *out = scr_dyn_new_arr();
2515
2559
  if (v->kind == SCR_DYN_OBJ) {
2516
2560
  size_t n = v->v.obj.len;
2517
2561
  /* Two passes: array-index keys ascending, then the rest in insertion
2518
- * order (JS's own-key order). Index keys are rare in DOM objects —
2562
+ * order (JS's own-key order). Index keys are rare in dyn objects —
2519
2563
  * the ascending pass is a simple selection scan. */
2520
2564
  bool *is_index = malloc(n ? n * sizeof *is_index : 1);
2521
2565
  double *idx = malloc(n ? n * sizeof *idx : 1);
@@ -2525,7 +2569,7 @@ static ScrDyn *scr_dyn_objwalk(const ScrDyn *v, ScrObjWalk mode) {
2525
2569
  size_t index_count = 0;
2526
2570
  for (size_t i = 0; i < n; i++) {
2527
2571
  const ScrDynEntry *e = &v->v.obj.entries[i];
2528
- /* Reserved '%'-prefixed members (the DOM's error marker) never
2572
+ /* Reserved '%'-prefixed members (the checked-dynamic tree's error marker) never
2529
2573
  * appear in user objects — '%' cannot start a JS identifier-ish
2530
2574
  * JSON key from this runtime's own producers; skip defensively. */
2531
2575
  is_index[i] = scr_dyn_key_is_index(e->key, e->key_len, &idx[i]);
@@ -2576,7 +2620,7 @@ static ScrDyn *scr_dyn_objwalk(const ScrDyn *v, ScrObjWalk mode) {
2576
2620
  return out;
2577
2621
  }
2578
2622
  if (v->kind == SCR_DYN_STR) {
2579
- /* JS indexes strings by UTF-16 code units; the DOM stores UTF-8.
2623
+ /* JS indexes strings by UTF-16 code units; the checked-dynamic tree stores UTF-8.
2580
2624
  * Code points walk one at a time — an astral code point stays WHOLE
2581
2625
  * (one entry where JS lists two lone surrogates; documented
2582
2626
  * approximation, the keys stay dense). */
@@ -2620,7 +2664,7 @@ ScrDyn *scr_dyn_obj_keys(const ScrDyn *v) { return scr_dyn_objwalk(v, SCR_OBJWAL
2620
2664
  * UTF-16 code unit, like Node's String exotic object); nullish sources
2621
2665
  * copy nothing (Node skips them) and the scalar/function/handle kinds
2622
2666
  * have no own enumerable string keys. Non-OBJ targets copy nothing (the
2623
- * existing two-arg stance — a DOM array target has no property table). */
2667
+ * existing two-arg stance — a dyn array target has no property table). */
2624
2668
  static void scr_dyn_assign_from(ScrDyn *target, const ScrDyn *src) {
2625
2669
  if (target->kind != SCR_DYN_OBJ) return;
2626
2670
  if (src->kind == SCR_DYN_UNDEF || src->kind == SCR_DYN_NULL) return;
@@ -2651,7 +2695,7 @@ static void scr_dyn_assign_from(ScrDyn *target, const ScrDyn *src) {
2651
2695
  scr_dyn_release(pairs);
2652
2696
  }
2653
2697
 
2654
- /* Object.assign over DOM values: copies `src`'s own members onto `target`
2698
+ /* Object.assign over dyn values: copies `src`'s own members onto `target`
2655
2699
  * (last write wins) and answers the target retained (+1). Nullish
2656
2700
  * receivers throw Node's ToObject TypeError; nullish sources copy
2657
2701
  * nothing; index-keyed sources (arrays/strings/bytes) copy their index
@@ -2666,13 +2710,13 @@ ScrDyn *scr_dyn_assign(ScrDyn *target, const ScrDyn *src) {
2666
2710
  /* An ENGINE target: the copy runs in the engine (Object.assign's own
2667
2711
  * semantics — setters fire, own-enumerable order); the source enters
2668
2712
  * per the uniform conversion (a wrapped source spreads by reference,
2669
- * DOM data as the usual member deep copy). */
2713
+ * dyn data as the usual member deep copy). */
2670
2714
  if (!scr_dyn_jsval_ops()->assign(target->v.jsval.cell, src)) return NULL;
2671
2715
  return scr_dyn_retain(target);
2672
2716
  }
2673
2717
  if (src->kind == SCR_DYN_JSVAL) {
2674
- /* A wrapped SOURCE onto a DOM target: the engine lists its own
2675
- * [key, value] pairs (getters running) and each lands as a DOM
2718
+ /* A wrapped SOURCE onto a dyn target: the engine lists its own
2719
+ * [key, value] pairs (getters running) and each lands as a dyn
2676
2720
  * member — values wrap per element, scalars normalized. */
2677
2721
  if (target->kind == SCR_DYN_OBJ) {
2678
2722
  ScrDyn *entries = scr_dyn_jsval_ops()->obj_walk(src->v.jsval.cell, 2);
@@ -2692,7 +2736,7 @@ ScrDyn *scr_dyn_assign(ScrDyn *target, const ScrDyn *src) {
2692
2736
  }
2693
2737
 
2694
2738
  /* Variadic Object.assign's argument pack (the `Object.assign({},
2695
- * ...arr.map(f), tail)` shape): the compiler builds one fresh DOM array
2739
+ * ...arr.map(f), tail)` shape): the compiler builds one fresh dyn array
2696
2740
  * of sources — plain arguments push borrowed (+1 in), spread arguments
2697
2741
  * flatten through the spread-call walk (scr_dyn_arr_push_spread's V8
2698
2742
  * TypeError texts, `what` spelling the spread expression for the nullish
@@ -2721,6 +2765,18 @@ void scr_dyn_pack_push_spread_iter(ScrDyn *pack, const ScrDyn *src) {
2721
2765
  scr_dyn_arr_push_spread(pack, src, ""); /* iterable kinds never throw */
2722
2766
  return;
2723
2767
  }
2768
+ if (src->kind == SCR_DYN_JSVAL) {
2769
+ /* A wrapped engine value on the ITERATED path: the engine's own
2770
+ * protocol drains (the kind wording on a non-iterable — the
2771
+ * iterated path's value-describing texts, engine-side). */
2772
+ ScrDyn *drained = scr_dyn_jsval_ops()->iter_drain(src->v.jsval.cell, false, NULL);
2773
+ if (!drained) return; /* pending */
2774
+ for (size_t i = 0; i < drained->v.arr.len; i++) {
2775
+ scr_dyn_arr_push(pack, scr_dyn_retain(drained->v.arr.items[i]));
2776
+ }
2777
+ scr_dyn_release(drained);
2778
+ return;
2779
+ }
2724
2780
  ScrJsonBuf b;
2725
2781
  scr_jb_init(&b);
2726
2782
  switch (src->kind) {
@@ -2793,7 +2849,7 @@ ScrDyn *scr_dyn_obj_entries(const ScrDyn *v) { return scr_dyn_objwalk(v, SCR_OBJ
2793
2849
 
2794
2850
  /* ── DOMException's dyn-touching half ─────────────────────────────────
2795
2851
  * Construction/cause/clone live HERE (not scr_error.c) so the error unit
2796
- * stays linkable without the DOM (the runtime C-unit tests link
2852
+ * stays linkable without the checked-dynamic tree (the runtime C-unit tests link
2797
2853
  * subsets). The cause teardown installs through scr_error.c's hook
2798
2854
  * before any cause can exist. */
2799
2855
 
@@ -2852,10 +2908,10 @@ ScrError *scr_domex_clone(ScrError *e, const ScrDyn *options) {
2852
2908
  }
2853
2909
 
2854
2910
  /* ── atob/btoa — the WHATWG base64 globals (Node globals since v16) ───
2855
- * They live HERE (not scr_string.c) because the argument is a DOM value:
2856
- * WebIDL ToString runs over the DOM kind (Node's atob(null) decodes the
2911
+ * They live HERE (not scr_string.c) because the argument is a dyn value:
2912
+ * WebIDL ToString runs over the dyn kind (Node's atob(null) decodes the
2857
2913
  * string "null"), and the string unit must stay linkable without the
2858
- * DOM. atob is forgiving-base64 exactly — ASCII whitespace stripped, a
2914
+ * dyn. atob is forgiving-base64 exactly — ASCII whitespace stripped, a
2859
2915
  * %4==0 input sheds up to two trailing '=', %4==1 refuses, leftover
2860
2916
  * bits discard — decoding to the latin1 code points as a UTF-8 string;
2861
2917
  * btoa refuses any code point over U+00FF. Malformed input throws the