@scriptc/runtime 0.0.7 → 0.0.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scriptc/runtime",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "scriptc native runtime — C sources, compiled into every scriptc binary",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://scriptc.dev",
@@ -185,6 +185,26 @@ ScrStr *scr_caught_to_string(const ScrCaught *c) {
185
185
  void scr_library_check_exc(void) {
186
186
  if (!scr_exc_pending()) return;
187
187
  static char buf[1024]; /* the message is copied out before the payload dies */
188
+ /* The ratified verbatim rule: a thrown message that ALREADY begins with
189
+ * the structured trap-teaching marker (0x01) is delivered exactly as
190
+ * authored — no "Uncaught " prefix, no added newline — which is how a
191
+ * facade-authored structured teaching rides the throw channel through to
192
+ * the sink. Both throw idioms qualify: a thrown string, and an Error
193
+ * whose .message starts with the marker. Everything else keeps the
194
+ * baseline "Uncaught ..." shape below, whose first byte is printable —
195
+ * the guarantee the marker's unambiguity rests on. */
196
+ const ScrStr *marked = NULL;
197
+ if (scr_exc_kind == SCR_EXC_STR) {
198
+ marked = (const ScrStr *)scr_exc_payload;
199
+ } else if (scr_exc_kind == SCR_EXC_OBJ && scr_error_is(scr_exc_payload)) {
200
+ marked = ((const ScrError *)scr_exc_payload)->message;
201
+ }
202
+ if (marked != NULL && marked->len > 0 && (uint8_t)marked->data[0] == 0x01) {
203
+ size_t take = marked->len > sizeof buf ? sizeof buf : marked->len;
204
+ memcpy(buf, marked->data, take);
205
+ scr_exc_reset(); /* the payload is released before the funnel poisons */
206
+ scr_trap_len(buf, take);
207
+ }
188
208
  size_t n = 0;
189
209
  const char prefix[] = "Uncaught ";
190
210
  memcpy(buf, prefix, sizeof prefix - 1);
package/src/scr_island.c CHANGED
@@ -1412,8 +1412,27 @@ static JSValue isl_hostfn_invoke(JSContext *ctx, JSValueConst this_val, int argc
1412
1412
  IslHostFn *b = JS_GetOpaque(func_data[0], isl_hostfn_class_id);
1413
1413
  if (!b) return JS_ThrowTypeError(ctx, "detached scriptc host function");
1414
1414
  ScrJsval *cells[ISL_HOSTFN_MAX_ARITY];
1415
- for (int i = 0; i < b->arity; i++) {
1416
- cells[i] = isl_cell_new(i < argc ? JS_DupValue(ctx, argv[i]) : JS_UNDEFINED);
1415
+ int ncells;
1416
+ if (b->arity < 0) {
1417
+ /* The ISLAND-REST shape (negative arity = -(leading declared + 1)):
1418
+ * leading params pad/drop like any host call; the trailing cell is a
1419
+ * fresh ENGINE ARRAY of the surplus arguments — the closure's rest
1420
+ * binding IS the engine's own arguments array. */
1421
+ int leading = -b->arity - 1;
1422
+ for (int i = 0; i < leading; i++) {
1423
+ cells[i] = isl_cell_new(i < argc ? JS_DupValue(ctx, argv[i]) : JS_UNDEFINED);
1424
+ }
1425
+ JSValue rest = JS_NewArray(ctx);
1426
+ for (int i = leading; i < argc; i++) {
1427
+ JS_SetPropertyUint32(ctx, rest, (uint32_t)(i - leading), JS_DupValue(ctx, argv[i]));
1428
+ }
1429
+ cells[leading] = isl_cell_new(rest);
1430
+ ncells = leading + 1;
1431
+ } else {
1432
+ for (int i = 0; i < b->arity; i++) {
1433
+ cells[i] = isl_cell_new(i < argc ? JS_DupValue(ctx, argv[i]) : JS_UNDEFINED);
1434
+ }
1435
+ ncells = b->arity;
1417
1436
  }
1418
1437
  bool strayed_before = isl_anchor_strayed;
1419
1438
  isl_host_depth++;
@@ -1429,7 +1448,7 @@ static JSValue isl_hostfn_invoke(JSContext *ctx, JSValueConst this_val, int argc
1429
1448
  isl_anchor_here();
1430
1449
  isl_anchor_strayed = strayed_before;
1431
1450
  }
1432
- for (int i = 0; i < b->arity; i++) scr_jsval_release(cells[i]);
1451
+ for (int i = 0; i < ncells; i++) scr_jsval_release(cells[i]);
1433
1452
  if (scr_exc_pending()) {
1434
1453
  if (r) scr_jsval_release(r);
1435
1454
  return isl_throw_pending(ctx);
@@ -1443,7 +1462,9 @@ static JSValue isl_hostfn_invoke(JSContext *ctx, JSValueConst this_val, int argc
1443
1462
  ScrJsval *scr_jsval_from_closure(ScrClosure *c, int arity,
1444
1463
  ScrJsval *(*adapt)(ScrClosure *, ScrJsval **)) {
1445
1464
  isl_entry();
1446
- if (arity > ISL_HOSTFN_MAX_ARITY) {
1465
+ /* Negative arity = the island-rest shape; the CELL count is the leading
1466
+ * declared params + the one rest-array slot. */
1467
+ if ((arity < 0 ? -arity : arity) > ISL_HOSTFN_MAX_ARITY) {
1447
1468
  fprintf(stderr, "scriptc: island callback arity %d exceeds %d\n", arity,
1448
1469
  ISL_HOSTFN_MAX_ARITY);
1449
1470
  abort(); /* the frontend fences this; reaching here is a compiler bug */
@@ -1459,7 +1480,7 @@ ScrJsval *scr_jsval_from_closure(ScrClosure *c, int arity,
1459
1480
  JSValue box = JS_NewObjectClass(isl_ctx, isl_hostfn_class_id);
1460
1481
  JS_SetOpaque(box, b);
1461
1482
  JSValueConst data[1] = {box};
1462
- JSValue fn = JS_NewCFunctionData(isl_ctx, isl_hostfn_invoke, arity, 0, 1, data);
1483
+ JSValue fn = JS_NewCFunctionData(isl_ctx, isl_hostfn_invoke, arity < 0 ? -arity - 1 : arity, 0, 1, data);
1463
1484
  JS_FreeValue(isl_ctx, box); /* fn's func_data holds its own reference */
1464
1485
  return isl_cell_new(fn);
1465
1486
  }
@@ -1717,6 +1738,60 @@ ScrJsval *scr_jsval_obj_lit(int npairs, ScrJsval **kv) {
1717
1738
  return isl_cell_new(o);
1718
1739
  }
1719
1740
 
1741
+ /* The engine-native TemplateStringsArray for an island tag call: `kv`
1742
+ * carries n cooked strings then n raw strings — a fresh array whose
1743
+ * `.raw` property holds the raw spellings, exactly the object a tagged
1744
+ * template hands its tag (a JSON marshal would drop `.raw`, and tags
1745
+ * dispatch on it). */
1746
+ ScrJsval *scr_jsval_tpl_strings(int n, ScrJsval **kv) {
1747
+ isl_entry();
1748
+ JSValue cooked = JS_NewArray(isl_ctx);
1749
+ JSValue raw = JS_NewArray(isl_ctx);
1750
+ for (int i = 0; i < n; i++) {
1751
+ JS_SetPropertyUint32(isl_ctx, cooked, (uint32_t)i, JS_DupValue(isl_ctx, kv[i]->v));
1752
+ JS_SetPropertyUint32(isl_ctx, raw, (uint32_t)i, JS_DupValue(isl_ctx, kv[n + i]->v));
1753
+ }
1754
+ JS_SetPropertyStr(isl_ctx, cooked, "raw", raw); /* consumes raw */
1755
+ return isl_cell_new(cooked);
1756
+ }
1757
+
1758
+ /* Spread completion for an island-native literal: copies `src`'s own
1759
+ * enumerable properties onto `obj` — the engine's own Object.assign (the
1760
+ * spec's CopyDataProperties; null/undefined sources spread nothing) — and
1761
+ * answers the target retained (+1). NULL with the exception pending when
1762
+ * a source getter throws. */
1763
+ ScrJsval *scr_jsval_obj_spread(ScrJsval *obj, ScrJsval *src) {
1764
+ isl_entry();
1765
+ if (JS_IsNull(src->v) || JS_IsUndefined(src->v)) return scr_jsval_retain(obj);
1766
+ JSValue global = JS_GetGlobalObject(isl_ctx);
1767
+ JSValue object_ctor = JS_GetPropertyStr(isl_ctx, global, "Object");
1768
+ JS_FreeValue(isl_ctx, global);
1769
+ JSValue assign = JS_GetPropertyStr(isl_ctx, object_ctor, "assign");
1770
+ JS_FreeValue(isl_ctx, object_ctor);
1771
+ JSValueConst args[2] = { obj->v, src->v };
1772
+ JSValue r = JS_Call(isl_ctx, assign, JS_UNDEFINED, 2, args);
1773
+ JS_FreeValue(isl_ctx, assign);
1774
+ if (JS_IsException(r)) {
1775
+ isl_bridge_exception();
1776
+ return NULL;
1777
+ }
1778
+ JS_FreeValue(isl_ctx, r); /* assign answers the target itself */
1779
+ return scr_jsval_retain(obj);
1780
+ }
1781
+
1782
+ /* Getter completion for an island-native literal: defines `key` on `obj`
1783
+ * as an engine GETTER invoking `fn` (a marshaled host function), and
1784
+ * answers the object retained (+1) so builds chain. Enumerable +
1785
+ * configurable, no setter — exactly a JS object-literal `get k() {}`. */
1786
+ ScrJsval *scr_jsval_define_getter(ScrJsval *obj, ScrJsval *key, ScrJsval *fn) {
1787
+ isl_entry();
1788
+ JSAtom k = JS_ValueToAtom(isl_ctx, key->v);
1789
+ JS_DefinePropertyGetSet(isl_ctx, obj->v, k, JS_DupValue(isl_ctx, fn->v), JS_UNDEFINED,
1790
+ JS_PROP_ENUMERABLE | JS_PROP_CONFIGURABLE);
1791
+ JS_FreeAtom(isl_ctx, k);
1792
+ return scr_jsval_retain(obj);
1793
+ }
1794
+
1720
1795
  ScrJsval *scr_jsval_arr_lit(int n, ScrJsval **elems) {
1721
1796
  isl_entry();
1722
1797
  JSValue a = JS_NewArray(isl_ctx);
package/src/scr_json.c CHANGED
@@ -1078,6 +1078,26 @@ void scr_dyn_key_set(ScrDyn *recv, ScrStr *key, ScrDyn *value) {
1078
1078
  scr_dyn_obj_set(recv, key->data, key->len, scr_dyn_retain(value));
1079
1079
  return;
1080
1080
  }
1081
+ if (recv->kind == SCR_DYN_ARR) {
1082
+ /* An INDEX write on a DOM array (`args[i] = v` — the variadic-rest
1083
+ * rebuild): a canonical numeric key sets/extends the element, holes
1084
+ * padding with undefined exactly like JS length growth. Non-index
1085
+ * keys keep the throw below (DOM arrays carry no expando table). */
1086
+ size_t idx = 0;
1087
+ int is_index = key->len > 0 && !(key->len > 1 && key->data[0] == '0');
1088
+ for (size_t i = 0; is_index && i < key->len; i++) {
1089
+ if (key->data[i] < '0' || key->data[i] > '9') is_index = 0;
1090
+ else idx = idx * 10 + (size_t)(key->data[i] - '0');
1091
+ }
1092
+ if (is_index) {
1093
+ while (recv->v.arr.len <= idx) {
1094
+ scr_dyn_arr_push(recv, scr_dyn_retain(scr_dyn_undefined()));
1095
+ }
1096
+ scr_dyn_release(recv->v.arr.items[idx]);
1097
+ recv->v.arr.items[idx] = scr_dyn_retain(value);
1098
+ return;
1099
+ }
1100
+ }
1081
1101
  if (recv->kind == SCR_DYN_HANDLE) {
1082
1102
  scr_dyn_handle_key_set(recv, key, value);
1083
1103
  return;
@@ -2089,6 +2109,48 @@ static ScrDyn *scr_dyn_objwalk(const ScrDyn *v, ScrObjWalk mode) {
2089
2109
  }
2090
2110
 
2091
2111
  ScrDyn *scr_dyn_obj_keys(const ScrDyn *v) { return scr_dyn_objwalk(v, SCR_OBJWALK_KEYS); }
2112
+
2113
+ /* Object.assign over DOM values: copies `src`'s own members onto `target`
2114
+ * (last write wins) and answers the target retained (+1). Nullish
2115
+ * receivers throw Node's ToObject TypeError; nullish sources copy
2116
+ * nothing; non-object sources copy nothing DOM-representable. */
2117
+ ScrDyn *scr_dyn_assign(ScrDyn *target, const ScrDyn *src) {
2118
+ if (target->kind == SCR_DYN_UNDEF || target->kind == SCR_DYN_NULL) {
2119
+ const char *m = "Cannot convert undefined or null to object";
2120
+ scr_throw_error_msg(SCR_ERR_TYPE, m, strlen(m));
2121
+ return NULL;
2122
+ }
2123
+ if (target->kind == SCR_DYN_OBJ && src->kind == SCR_DYN_OBJ) {
2124
+ for (size_t i = 0; i < src->v.obj.len; i++) {
2125
+ scr_dyn_obj_set(target, src->v.obj.entries[i].key,
2126
+ src->v.obj.entries[i].key_len,
2127
+ scr_dyn_retain(src->v.obj.entries[i].value));
2128
+ }
2129
+ }
2130
+ return scr_dyn_retain(target);
2131
+ }
2132
+
2133
+ bool scr_dyn_has_own(const ScrDyn *v, const ScrStr *key) {
2134
+ if (v->kind == SCR_DYN_UNDEF || v->kind == SCR_DYN_NULL) {
2135
+ const char *m = "Cannot convert undefined or null to object";
2136
+ scr_throw_error_msg(SCR_ERR_TYPE, m, strlen(m));
2137
+ return false;
2138
+ }
2139
+ if (v->kind == SCR_DYN_OBJ) {
2140
+ return scr_dyn_obj_get(v, key->data, key->len) != NULL;
2141
+ }
2142
+ if (v->kind == SCR_DYN_ARR) {
2143
+ if (key->len == 6 && memcmp(key->data, "length", 6) == 0) return true;
2144
+ size_t idx = 0;
2145
+ int is_index = key->len > 0 && !(key->len > 1 && key->data[0] == '0');
2146
+ for (size_t i = 0; is_index && i < key->len; i++) {
2147
+ if (key->data[i] < '0' || key->data[i] > '9') is_index = 0;
2148
+ else idx = idx * 10 + (size_t)(key->data[i] - '0');
2149
+ }
2150
+ return is_index != 0 && idx < v->v.arr.len;
2151
+ }
2152
+ return false;
2153
+ }
2092
2154
  ScrDyn *scr_dyn_obj_values(const ScrDyn *v) { return scr_dyn_objwalk(v, SCR_OBJWALK_VALUES); }
2093
2155
  ScrDyn *scr_dyn_obj_entries(const ScrDyn *v) { return scr_dyn_objwalk(v, SCR_OBJWALK_ENTRIES); }
2094
2156
 
package/src/scr_library.c CHANGED
@@ -60,6 +60,14 @@ __attribute__((noinline)) _Noreturn void scr_trap(const char *msg) {
60
60
  scr_library_trap_deliver(msg, strlen(msg), SCR_TRAP_ADDR());
61
61
  }
62
62
 
63
+ __attribute__((noinline)) _Noreturn void scr_trap_len(const char *msg, size_t len) {
64
+ /* The length-delimited funnel entry: structured trap-teaching messages
65
+ * and verbatim 0x01-led thrown messages are byte-counted, never
66
+ * NUL-scanned (a thrown JS string may embed NUL). Same poison-deliver-
67
+ * abort discipline as scr_trap. */
68
+ scr_library_trap_deliver(msg, len, SCR_TRAP_ADDR());
69
+ }
70
+
63
71
  __attribute__((noinline)) _Noreturn void scr_trap_fmt(const char *fmt, ...) {
64
72
  static char buf[512]; /* no malloc on the invariant-failure path */
65
73
  va_list ap;
@@ -125,13 +133,16 @@ ScrStr *scr_library_str_in(const uint8_t *p, size_t len) {
125
133
  return scr_str_new(len == 0 ? "" : (const char *)p, len);
126
134
  }
127
135
 
128
- ScrBytes *scr_library_bytes_in(const uint8_t *p, size_t len) {
136
+ ScrBytes *scr_library_bytes_in(const uint8_t *p, size_t len, const char *trap_msg) {
129
137
  ScrBytes *b = scr_bytes_new(SCR_BYTES_U8, (double)len);
130
138
  if (b == NULL) {
131
139
  /* scr_bytes_new throws only for lengths past 2^53-1 — an impossible
132
- * host buffer; funnel the contract violation instead of a NULL deref. */
140
+ * host buffer; funnel the contract violation instead of a NULL deref.
141
+ * The message is the wrapper's compiler-assembled structured
142
+ * trap-teaching form (code SC4012, the trapping export's symbol, the
143
+ * profile's teaching and remediation) — opaque bytes here. */
133
144
  scr_exc_clear();
134
- scr_trap("scriptc: library inbound bytes length out of range\n");
145
+ scr_trap(trap_msg);
135
146
  }
136
147
  if (len > 0 && p != NULL) memcpy(b->data, p, len);
137
148
  return b;
package/src/scr_object.c CHANGED
@@ -14,6 +14,24 @@ ScrStr *scr_classobj_name(ScrClassObj *c) {
14
14
  return scr_str_retain((ScrStr *)c->name);
15
15
  }
16
16
 
17
+ /* The keyed-write MISS on a fixed-shape (signature-free) record: JS would
18
+ * ADD the property, which a monomorphic struct cannot — the write throws
19
+ * the catchable TypeError naming the key instead (the documented
20
+ * divergence; the emitted per-shape keyed-write helpers call this after
21
+ * the declared-field chain misses). */
22
+ void scr_record_key_miss(ScrStr *k) {
23
+ const char *base = "Cannot add property '";
24
+ ScrStr *head = scr_str_new(base, strlen(base));
25
+ ScrStr *with_key = scr_str_concat(head, k);
26
+ scr_str_release(head);
27
+ const char *tail_c = "' to a fixed-shape object";
28
+ ScrStr *tail = scr_str_new(tail_c, strlen(tail_c));
29
+ ScrStr *msg = scr_str_concat(with_key, tail);
30
+ scr_str_release(with_key);
31
+ scr_str_release(tail);
32
+ scr_throw_error(SCR_ERR_TYPE, msg); /* takes ownership */
33
+ }
34
+
17
35
  #ifdef SCR_RC_AUDIT
18
36
  static long scr_live_objects = 0;
19
37
  long scr_obj_live_count(void) { return scr_live_objects; }
package/src/scr_runtime.h CHANGED
@@ -67,7 +67,22 @@ typedef struct ScrBytes ScrBytes;
67
67
  * call; address is the trap site's return address (0 when the toolchain
68
68
  * cannot supply one); ctx is the registration's opaque pointer. The sink
69
69
  * must not call back into any library entry and must not unwind or longjmp
70
- * back into library frames. */
70
+ * back into library frames.
71
+ *
72
+ * Message shape (the ratified structured trap-teaching encoding): a
73
+ * BASELINE message is plain text whose first byte is printable (>= 0x20) —
74
+ * no emitter path ever produces an unstructured message starting below
75
+ * 0x20. A STRUCTURED message begins with the marker byte 0x01 followed by
76
+ * the human teaching text and 0x1F-separated fields:
77
+ *
78
+ * 0x01 text 0x1F code 0x1F symbol [ 0x1F remediation ]
79
+ *
80
+ * so msg_len > 0 && msg[0] == 0x01 is the one version test. Parse: split
81
+ * the bytes after the marker on 0x1F — field 0 is the human text, 1 the
82
+ * diagnostic code, 2 the trapping symbol as the host linked it, 3 the
83
+ * remediation; a missing or empty field means none; ignore any field past
84
+ * the fourth. Fields are (pointer, length) — never assume NUL termination.
85
+ * A plain-text host may print the whole buffer: the teaching leads it. */
71
86
  typedef void (*ScrLibSinkFn)(void *ctx, const uint8_t *msg, size_t msg_len,
72
87
  uint64_t address);
73
88
  void scr_library_set_sink(ScrLibSinkFn fn, void *ctx); /* latest wins */
@@ -96,16 +111,29 @@ void scr_library_register_reset(void (*fn)(void));
96
111
  /* An escaped exception at an entry boundary: renders the same "Uncaught
97
112
  * ..." text the executable epilogue prints, releases the payload, and
98
113
  * routes the text through the trap funnel. No-op when nothing is pending.
99
- * Defined in scr_exception.c (it owns the cell). */
114
+ * The ratified verbatim rule: a thrown message that ALREADY begins with the
115
+ * structured marker 0x01 (a thrown string, or an Error whose .message
116
+ * starts with it) is delivered byte-for-byte — no "Uncaught " prefix, no
117
+ * added newline — which is how facade-authored structured teachings ride
118
+ * the throw channel to the sink. Defined in scr_exception.c (it owns the
119
+ * cell). */
100
120
  void scr_library_check_exc(void);
101
121
 
122
+ /* The length-taking funnel entry (library lane only): delivers exactly the
123
+ * given bytes to the sink — the verbatim path above needs it because a
124
+ * structured message is length-delimited, never NUL-scanned. */
125
+ _Noreturn void scr_trap_len(const char *msg, size_t len);
126
+
102
127
  /* Marshalling helpers the generated wrappers call (both emissions share
103
128
  * these bodies, which is how the two lanes stay identical by
104
129
  * construction). Inbound is borrowed-and-copied; outbound values MOVE into
105
130
  * the result arena and stay valid until the next arena reset. String
106
131
  * results are NUL-terminated after *out_len bytes (ScrStr's layout). */
107
132
  ScrStr *scr_library_str_in(const uint8_t *p, size_t len); /* +1 */
108
- ScrBytes *scr_library_bytes_in(const uint8_t *p, size_t len); /* +1, u8 */
133
+ /* trap_msg is the wrapper's compiler-assembled host-contract trap message
134
+ * (structured trap-teaching bytes naming this entry's symbol), delivered
135
+ * through the funnel when len falls outside the marshalling class. */
136
+ ScrBytes *scr_library_bytes_in(const uint8_t *p, size_t len, const char *trap_msg); /* +1, u8 */
109
137
  void scr_library_str_out(ScrStr *s, const uint8_t **out, size_t *out_len);
110
138
  void scr_library_bytes_out(ScrBytes *b, const uint8_t **out, size_t *out_len);
111
139
 
@@ -287,6 +315,10 @@ void *scr_classobj_retain_v(void *c);
287
315
  void scr_classobj_release_v(void *c);
288
316
  /* `X.name` (+1 — a no-op retain on the interned immortal). */
289
317
  ScrStr *scr_classobj_name(ScrClassObj *c);
318
+ /* The keyed-write miss on a fixed-shape record: throws the catchable
319
+ * TypeError naming the key (JS would add the property — the documented
320
+ * monomorphic-struct divergence). scr_object.c. */
321
+ void scr_record_key_miss(ScrStr *k);
290
322
 
291
323
  /* ── error objects (scr_error.c) ──────────────────────────────────────
292
324
  * `Error` and its lib subclasses (TypeError/RangeError/SyntaxError) are a
@@ -2640,6 +2672,13 @@ ScrDyn *scr_dyn_obj_get(const ScrDyn *d, const char *key, size_t key_len);
2640
2672
  * member nodes. null/undefined receivers throw Node's catchable
2641
2673
  * TypeError. */
2642
2674
  ScrDyn *scr_dyn_obj_keys(const ScrDyn *v);
2675
+ /* Object.hasOwn over a DOM receiver: OBJ member presence, ARR index
2676
+ * bounds ("length" included); nullish receivers throw Node's ToObject
2677
+ * TypeError; every other kind answers false. */
2678
+ bool scr_dyn_has_own(const ScrDyn *v, const ScrStr *key);
2679
+ /* Object.assign over DOM values (+1 target back; ToObject TypeError on a
2680
+ * nullish target). */
2681
+ ScrDyn *scr_dyn_assign(ScrDyn *target, const ScrDyn *src);
2643
2682
  ScrDyn *scr_dyn_obj_values(const ScrDyn *v);
2644
2683
  ScrDyn *scr_dyn_obj_entries(const ScrDyn *v);
2645
2684
 
@@ -3765,6 +3804,16 @@ ScrPromise *scr_jsval_bridge_promise(ScrJsval *v, int payload);
3765
3804
  * marshaled strings) or an array from elements. Borrow argv; +1 out.
3766
3805
  * Cannot fail (allocation failure aborts, like every runtime OOM). */
3767
3806
  ScrJsval *scr_jsval_obj_lit(int npairs, ScrJsval **kv);
3807
+ /* Getter completion for an island literal: defines `key` on `obj` as an
3808
+ * engine getter invoking `fn`; answers the object retained (+1). */
3809
+ ScrJsval *scr_jsval_define_getter(ScrJsval *obj, ScrJsval *key, ScrJsval *fn);
3810
+ /* The engine TemplateStringsArray for an island tag call: n cooked then n
3811
+ * raw strings; the result array carries `.raw` (+1). */
3812
+ ScrJsval *scr_jsval_tpl_strings(int n, ScrJsval **kv);
3813
+ /* Spread completion for an island literal: engine CopyDataProperties of
3814
+ * `src` onto `obj`; answers the target (+1), NULL + pending on a getter
3815
+ * throw. */
3816
+ ScrJsval *scr_jsval_obj_spread(ScrJsval *obj, ScrJsval *src);
3768
3817
  ScrJsval *scr_jsval_arr_lit(int n, ScrJsval **elems);
3769
3818
 
3770
3819
  /* Marshal out (island → static): validated, STRICT extraction — a