@scriptc/runtime 0.0.9 → 0.0.11

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_island.c CHANGED
@@ -380,6 +380,9 @@ enum {
380
380
  ISL_H_ITERN,
381
381
  ISL_H_ITER,
382
382
  ISL_H_CALLSPREAD,
383
+ ISL_H_OBJWALK,
384
+ ISL_H_HASOWN,
385
+ ISL_H_ASSIGN,
383
386
  ISL_H_COUNT,
384
387
  };
385
388
 
@@ -428,7 +431,13 @@ static const char isl_prelude[] =
428
431
  "throw new TypeError(w+\" is not iterable (cannot read property \"+s+\")\");"
429
432
  "if(typeof s[Symbol.iterator]!==\"function\")"
430
433
  "throw new TypeError(\"Spread syntax requires ...iterable[Symbol.iterator] to be a function\");"
431
- "return f(...p,...s)}]";
434
+ "return f(...p,...s)},"
435
+ /* ISL_H_OBJWALK / ISL_H_HASOWN / ISL_H_ASSIGN: the Object statics a
436
+ * wrapped (SCR_DYN_JSVAL) receiver routes here — the engine's own
437
+ * semantics (own-key order, getters running, ToObject refusals). */
438
+ "(o,m)=>m===0?Object.keys(o):m===1?Object.values(o):Object.entries(o),"
439
+ "(o,k)=>Object.hasOwn(o,k),"
440
+ "(t,s)=>Object.assign(t,s)]";
432
441
 
433
442
  static void isl_free_boot(void);
434
443
  static void isl_prom_wraps_teardown(void);
@@ -963,8 +972,9 @@ ScrJsval *scr_jsval_from_json(const ScrStr *json) {
963
972
  * STATIC code the engine cannot re-enter through a data copy). */
964
973
  static const char *isl_dyn_unmarshalable(const ScrDyn *d) {
965
974
  switch (d->kind) {
966
- case SCR_DYN_FUNC:
967
- return "a function";
975
+ /* SCR_DYN_FUNC is NOT here: a boxed function crosses as the generic
976
+ * host-function shim (isl_dynfn_new below) — the routed-call lane's
977
+ * uniform argument conversion. */
968
978
  case SCR_DYN_HANDLE:
969
979
  return "a runtime handle";
970
980
  case SCR_DYN_PROMISE:
@@ -986,8 +996,16 @@ static const char *isl_dyn_unmarshalable(const ScrDyn *d) {
986
996
  }
987
997
  }
988
998
 
999
+ static JSValue isl_dynfn_new(const ScrDyn *d); /* the DOM-function shim, below */
1000
+
989
1001
  static JSValue isl_from_dyn(const ScrDyn *d) {
990
1002
  switch (d->kind) {
1003
+ 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
1007
+ * crossing mints a fresh engine function (documented). */
1008
+ return isl_dynfn_new(d);
991
1009
  case SCR_DYN_UNDEF:
992
1010
  return JS_UNDEFINED;
993
1011
  case SCR_DYN_NULL:
@@ -1028,6 +1046,10 @@ static JSValue isl_from_dyn(const ScrDyn *d) {
1028
1046
  }
1029
1047
  return obj;
1030
1048
  }
1049
+ case SCR_DYN_JSVAL:
1050
+ /* An engine value riding inside DOM data: embed the SAME engine
1051
+ * value by reference — the identity round trip, member position. */
1052
+ return JS_DupValue(isl_ctx, d->v.jsval.cell->v);
1031
1053
  default:
1032
1054
  /* Pre-scanned away — defensive. */
1033
1055
  return JS_ThrowTypeError(isl_ctx, "unmarshalable dynamic value");
@@ -1035,6 +1057,11 @@ static JSValue isl_from_dyn(const ScrDyn *d) {
1035
1057
  }
1036
1058
 
1037
1059
  ScrJsval *scr_jsval_from_dyn(const ScrDyn *d) {
1060
+ /* The identity round trip: an engine value that crossed into the DOM
1061
+ * (scr_dyn_from_jsval) and back is the SAME engine value, by reference
1062
+ * — the one direction that used to throw (SEMANTICS.md supersedes the
1063
+ * "one unbridgeable mix"). */
1064
+ if (d->kind == SCR_DYN_JSVAL) return scr_jsval_retain(d->v.jsval.cell);
1038
1065
  isl_entry();
1039
1066
  const char *bad = isl_dyn_unmarshalable(d);
1040
1067
  if (bad != NULL) {
@@ -1052,6 +1079,279 @@ ScrJsval *scr_jsval_from_dyn(const ScrDyn *d) {
1052
1079
  return isl_cell_new(v);
1053
1080
  }
1054
1081
 
1082
+ /* ── the jsval→DOM crossing (SCR_DYN_JSVAL's constructor + ops) ───────
1083
+ * 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
1086
+ * the armed operations (typeof/truthy/String()/===, the narrowing
1087
+ * tests) through these installed ops; everything un-armed there keeps
1088
+ * the loud "not supported yet" ladder. */
1089
+
1090
+ static void isl_dynjs_release(ScrJsval *cell) { scr_jsval_release(cell); }
1091
+ static ScrStr *isl_dynjs_typeof(ScrJsval *cell) { return scr_jsval_typeof(cell); }
1092
+ static bool isl_dynjs_truthy(ScrJsval *cell) { return scr_jsval_truthy(cell) != 0; }
1093
+ static ScrStr *isl_dynjs_to_str(ScrJsval *cell) { return scr_jsval_to_str(cell); }
1094
+ static bool isl_dynjs_strict_eq(ScrJsval *a, ScrJsval *b) {
1095
+ /* The engine's === through the pinned helper (a bridged surprise
1096
+ * answers false — strict equality itself cannot throw in JS). */
1097
+ return scr_jsval_cmp(SCR_JSOP_EQ, a, b) == 1;
1098
+ }
1099
+ static bool isl_dynjs_is_array(ScrJsval *cell) { return JS_IsArray(cell->v); }
1100
+ static bool isl_dynjs_is_error(ScrJsval *cell) { return JS_IsError(cell->v); }
1101
+
1102
+ static const ScrDynJsvalOps isl_dynjs_ops;
1103
+
1104
+ /* The jsval→DOM wrap over a RAW engine value (BORROWED) — the scalar
1105
+ * normalization the cell constructor applies, shared by the routed-op
1106
+ * result conversions (which hold a JSValue, not a cell). Composites mint
1107
+ * a fresh cell over the SAME engine value (identity is the value — the
1108
+ * engine's === and the unwrap both go through it). */
1109
+ static ScrDyn *isl_dyn_from_value(JSValue v) {
1110
+ if (JS_IsUndefined(v)) return scr_dyn_retain(scr_dyn_undefined());
1111
+ if (JS_IsNull(v)) return scr_dyn_new_null();
1112
+ if (JS_IsBool(v)) return scr_dyn_new_bool(JS_ToBool(isl_ctx, v) > 0);
1113
+ if (JS_IsNumber(v)) {
1114
+ double num = 0;
1115
+ JS_ToFloat64(isl_ctx, &num, v); /* cannot fail on a number */
1116
+ return scr_dyn_new_num(num);
1117
+ }
1118
+ if (JS_IsString(v)) {
1119
+ ScrStr *s = isl_js_to_str(v); /* cannot bridge on a string */
1120
+ ScrDyn *d = scr_dyn_new_str(s);
1121
+ scr_str_release(s);
1122
+ return d;
1123
+ }
1124
+ return scr_dyn_alloc_jsval(isl_cell_new(JS_DupValue(isl_ctx, v)), &isl_dynjs_ops);
1125
+ }
1126
+
1127
+ /* ── the routed operation set (the ScrDynJsvalOps rows scr_json.c and
1128
+ * scr_dyn_invoke.c dispatch through) ─────────────────────────────────
1129
+ * Keys enter as engine strings through the GETIDX/SETIDX helpers (any
1130
+ * byte content, canonical-index semantics are the engine's own); dyn
1131
+ * arguments cross through scr_jsval_from_dyn (wrapped cells by
1132
+ * reference, data as the usual deep copy, FUNC boxes through the shim);
1133
+ * results wrap back scalar-normalized. Engine throws bridge catchably
1134
+ * with the engine's message. */
1135
+
1136
+ static ScrDyn *isl_dynjs_key_get(ScrJsval *cell, const ScrStr *k) {
1137
+ isl_entry();
1138
+ JSValue key = JS_NewStringLen(isl_ctx, k->data, k->len);
1139
+ JSValue argv[2] = {cell->v, key};
1140
+ JSValue r = JS_Call(isl_ctx, isl_helpers[ISL_H_GETIDX], JS_UNDEFINED, 2, argv);
1141
+ JS_FreeValue(isl_ctx, key);
1142
+ if (JS_IsException(r)) {
1143
+ isl_bridge_exception();
1144
+ return NULL;
1145
+ }
1146
+ ScrDyn *d = isl_dyn_from_value(r);
1147
+ JS_FreeValue(isl_ctx, r);
1148
+ return d;
1149
+ }
1150
+
1151
+ static bool isl_dynjs_key_set(ScrJsval *cell, const ScrStr *k, const ScrDyn *v) {
1152
+ ScrJsval *vj = scr_jsval_from_dyn(v);
1153
+ if (!vj) return false; /* unmarshalable value — the catchable TypeError */
1154
+ isl_entry();
1155
+ JSValue key = JS_NewStringLen(isl_ctx, k->data, k->len);
1156
+ JSValue argv[3] = {cell->v, key, vj->v};
1157
+ JSValue r = JS_Call(isl_ctx, isl_helpers[ISL_H_SETIDX], JS_UNDEFINED, 3, argv);
1158
+ JS_FreeValue(isl_ctx, key);
1159
+ scr_jsval_release(vj);
1160
+ if (JS_IsException(r)) {
1161
+ isl_bridge_exception();
1162
+ return false;
1163
+ }
1164
+ JS_FreeValue(isl_ctx, r);
1165
+ return true;
1166
+ }
1167
+
1168
+ /* Convert argc dyn arguments for a routed call; frees what it built on
1169
+ * failure. Returns false with the exception pending. */
1170
+ static bool isl_dynjs_args_in(ScrDyn *const *args, size_t argc, ScrJsval **cells) {
1171
+ for (size_t i = 0; i < argc; i++) {
1172
+ cells[i] = scr_jsval_from_dyn(args[i]);
1173
+ if (!cells[i]) {
1174
+ for (size_t j = 0; j < i; j++) scr_jsval_release(cells[j]);
1175
+ return false;
1176
+ }
1177
+ }
1178
+ return true;
1179
+ }
1180
+
1181
+ static ScrDyn *isl_dynjs_call(ScrJsval *cell, ScrDyn *const *args, size_t argc) {
1182
+ ScrJsval *stack_cells[8];
1183
+ ScrJsval **cells = argc <= 8 ? stack_cells : malloc(argc * sizeof *cells);
1184
+ if (!isl_dynjs_args_in(args, argc, cells)) {
1185
+ if (cells != stack_cells) free(cells);
1186
+ return NULL;
1187
+ }
1188
+ ScrJsval *r = scr_jsval_call(cell, (int)argc, cells);
1189
+ for (size_t i = 0; i < argc; i++) scr_jsval_release(cells[i]);
1190
+ if (cells != stack_cells) free(cells);
1191
+ if (!r) return NULL;
1192
+ ScrDyn *d = isl_dyn_from_value(r->v);
1193
+ scr_jsval_release(r);
1194
+ return d;
1195
+ }
1196
+
1197
+ static ScrDyn *isl_dynjs_invoke(ScrJsval *cell, const char *method, ScrDyn *const *args, size_t argc, const char *what) {
1198
+ isl_entry();
1199
+ JSValue fn = JS_GetPropertyStr(isl_ctx, cell->v, method); /* owned */
1200
+ if (JS_IsException(fn)) {
1201
+ isl_bridge_exception();
1202
+ return NULL;
1203
+ }
1204
+ if (!JS_IsFunction(isl_ctx, fn)) {
1205
+ /* Node's spelled TypeError (V8's text), front-run before the
1206
+ * engine's terser "not a function". */
1207
+ JS_FreeValue(isl_ctx, fn);
1208
+ ScrJsonBuf b;
1209
+ scr_jb_init(&b);
1210
+ scr_jb_puts(&b, what);
1211
+ scr_jb_puts(&b, " is not a function");
1212
+ scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&b));
1213
+ return NULL;
1214
+ }
1215
+ ScrJsval *stack_cells[8];
1216
+ ScrJsval **cells = argc <= 8 ? stack_cells : malloc(argc * sizeof *cells);
1217
+ if (!isl_dynjs_args_in(args, argc, cells)) {
1218
+ JS_FreeValue(isl_ctx, fn);
1219
+ if (cells != stack_cells) free(cells);
1220
+ return NULL;
1221
+ }
1222
+ JSValue stack_args[8];
1223
+ JSValue *argv = argc <= 8 ? stack_args : malloc(argc * sizeof(JSValue));
1224
+ for (size_t i = 0; i < argc; i++) argv[i] = cells[i]->v;
1225
+ JSValue r = JS_Call(isl_ctx, fn, cell->v, (int)argc, argv); /* this = receiver */
1226
+ if (argv != stack_args) free(argv);
1227
+ JS_FreeValue(isl_ctx, fn);
1228
+ for (size_t i = 0; i < argc; i++) scr_jsval_release(cells[i]);
1229
+ if (cells != stack_cells) free(cells);
1230
+ if (JS_IsException(r)) {
1231
+ isl_bridge_exception();
1232
+ return NULL;
1233
+ }
1234
+ ScrDyn *d = isl_dyn_from_value(r);
1235
+ JS_FreeValue(isl_ctx, r);
1236
+ return d;
1237
+ }
1238
+
1239
+ static bool isl_dynjs_is_nullish(ScrJsval *cell) {
1240
+ return JS_IsUndefined(cell->v) || JS_IsNull(cell->v);
1241
+ }
1242
+
1243
+ static ScrDyn *isl_dynjs_obj_walk(ScrJsval *cell, int mode) {
1244
+ isl_entry();
1245
+ JSValue m = JS_NewInt32(isl_ctx, mode);
1246
+ JSValue argv[2] = {cell->v, m};
1247
+ JSValue r = JS_Call(isl_ctx, isl_helpers[ISL_H_OBJWALK], JS_UNDEFINED, 2, argv);
1248
+ if (JS_IsException(r)) {
1249
+ isl_bridge_exception();
1250
+ return NULL;
1251
+ }
1252
+ /* The engine array unpacks into a NATIVE DOM array (keys are DOM
1253
+ * strings, values wrap per element, entries become native pairs) so
1254
+ * the results iterate/index/measure at native speed. */
1255
+ int64_t len = 0;
1256
+ JSValue lv = JS_GetPropertyStr(isl_ctx, r, "length");
1257
+ JS_ToInt64(isl_ctx, &len, lv);
1258
+ JS_FreeValue(isl_ctx, lv);
1259
+ ScrDyn *out = scr_dyn_new_arr();
1260
+ for (int64_t i = 0; i < len; i++) {
1261
+ JSValue e = JS_GetPropertyUint32(isl_ctx, r, (uint32_t)i);
1262
+ if (mode == 2) {
1263
+ JSValue k = JS_GetPropertyUint32(isl_ctx, e, 0);
1264
+ JSValue v = JS_GetPropertyUint32(isl_ctx, e, 1);
1265
+ ScrDyn *pair = scr_dyn_new_arr();
1266
+ scr_dyn_arr_push(pair, isl_dyn_from_value(k));
1267
+ scr_dyn_arr_push(pair, isl_dyn_from_value(v));
1268
+ scr_dyn_arr_push(out, pair);
1269
+ JS_FreeValue(isl_ctx, k);
1270
+ JS_FreeValue(isl_ctx, v);
1271
+ } else {
1272
+ scr_dyn_arr_push(out, isl_dyn_from_value(e));
1273
+ }
1274
+ JS_FreeValue(isl_ctx, e);
1275
+ }
1276
+ JS_FreeValue(isl_ctx, r);
1277
+ return out;
1278
+ }
1279
+
1280
+ static int isl_dynjs_has_own(ScrJsval *cell, const ScrStr *k) {
1281
+ isl_entry();
1282
+ JSValue key = JS_NewStringLen(isl_ctx, k->data, k->len);
1283
+ JSValue argv[2] = {cell->v, key};
1284
+ JSValue r = JS_Call(isl_ctx, isl_helpers[ISL_H_HASOWN], JS_UNDEFINED, 2, argv);
1285
+ JS_FreeValue(isl_ctx, key);
1286
+ if (JS_IsException(r)) {
1287
+ isl_bridge_exception();
1288
+ return -1;
1289
+ }
1290
+ int b = JS_ToBool(isl_ctx, r);
1291
+ JS_FreeValue(isl_ctx, r);
1292
+ return b > 0 ? 1 : 0;
1293
+ }
1294
+
1295
+ static bool isl_dynjs_assign(ScrJsval *cell, const ScrDyn *src) {
1296
+ ScrJsval *sj = scr_jsval_from_dyn(src);
1297
+ if (!sj) return false;
1298
+ isl_entry();
1299
+ JSValue argv[2] = {cell->v, sj->v};
1300
+ JSValue r = JS_Call(isl_ctx, isl_helpers[ISL_H_ASSIGN], JS_UNDEFINED, 2, argv);
1301
+ scr_jsval_release(sj);
1302
+ if (JS_IsException(r)) {
1303
+ isl_bridge_exception();
1304
+ return false;
1305
+ }
1306
+ JS_FreeValue(isl_ctx, r);
1307
+ return true;
1308
+ }
1309
+
1310
+ static ScrStr *isl_dynjs_to_json(ScrJsval *cell) { return scr_jsval_to_json(cell); }
1311
+
1312
+ static const ScrDynJsvalOps isl_dynjs_ops = {
1313
+ isl_dynjs_release,
1314
+ isl_dynjs_typeof,
1315
+ isl_dynjs_truthy,
1316
+ isl_dynjs_to_str,
1317
+ isl_dynjs_strict_eq,
1318
+ isl_dynjs_is_array,
1319
+ isl_dynjs_is_error,
1320
+ isl_dynjs_key_get,
1321
+ isl_dynjs_key_set,
1322
+ isl_dynjs_call,
1323
+ isl_dynjs_invoke,
1324
+ isl_dynjs_is_nullish,
1325
+ isl_dynjs_obj_walk,
1326
+ isl_dynjs_has_own,
1327
+ isl_dynjs_assign,
1328
+ isl_dynjs_to_json,
1329
+ };
1330
+
1331
+ ScrDyn *scr_dyn_from_jsval(ScrJsval *cell) {
1332
+ isl_entry();
1333
+ JSValue v = cell->v;
1334
+ /* Scalar normalization: engine-reported scalars become the NATIVE DOM
1335
+ * 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 —
1337
+ * stays untouched and the JSVAL kind never competes with them. */
1338
+ if (JS_IsUndefined(v)) return scr_dyn_retain(scr_dyn_undefined());
1339
+ if (JS_IsNull(v)) return scr_dyn_new_null();
1340
+ if (JS_IsBool(v)) return scr_dyn_new_bool(JS_ToBool(isl_ctx, v) > 0);
1341
+ if (JS_IsNumber(v)) {
1342
+ double num = 0;
1343
+ JS_ToFloat64(isl_ctx, &num, v); /* cannot fail on a number */
1344
+ return scr_dyn_new_num(num);
1345
+ }
1346
+ if (JS_IsString(v)) {
1347
+ ScrStr *s = isl_js_to_str(v); /* cannot bridge on a string */
1348
+ ScrDyn *d = scr_dyn_new_str(s);
1349
+ scr_str_release(s);
1350
+ return d;
1351
+ }
1352
+ return scr_dyn_alloc_jsval(scr_jsval_retain(cell), &isl_dynjs_ops);
1353
+ }
1354
+
1055
1355
  /* ── operators (through the pinned prelude helpers) ───────────────────── */
1056
1356
 
1057
1357
  ScrJsval *scr_jsval_binop(int op, ScrJsval *a, ScrJsval *b) {
@@ -1353,9 +1653,14 @@ static const JSClassDef isl_hostfn_class = {
1353
1653
  .finalizer = isl_hostfn_finalizer,
1354
1654
  };
1355
1655
 
1656
+ static JSClassID isl_dynfn_class_id; /* the DOM-function shim, below */
1657
+ static const JSClassDef isl_dynfn_class;
1658
+
1356
1659
  static void isl_register_hostfn_class(void) {
1357
1660
  JS_NewClassID(isl_rt, &isl_hostfn_class_id);
1358
1661
  JS_NewClass(isl_rt, isl_hostfn_class_id, &isl_hostfn_class);
1662
+ JS_NewClassID(isl_rt, &isl_dynfn_class_id);
1663
+ JS_NewClass(isl_rt, isl_dynfn_class_id, &isl_dynfn_class);
1359
1664
  isl_register_bridge_class();
1360
1665
  }
1361
1666
 
@@ -1513,6 +1818,87 @@ ScrJsval *scr_jsval_from_closure(ScrClosure *c, int arity,
1513
1818
  return isl_cell_new(fn);
1514
1819
  }
1515
1820
 
1821
+ /* ── the generic DOM-function shim (a boxed SCR_DYN_FUNC entering the
1822
+ * island) ─────────────────────────────────────────────────────────────
1823
+ * 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
1826
+ * thunk validates them against the closure's declared parameter types
1827
+ * and runs it, and the DOM result converts back through the from_dyn
1828
+ * rules (wrapped cells by reference, data as a deep copy, nested
1829
+ * functions through this same shim). OWNERSHIP: the engine function's
1830
+ * opaque box owns ONE reference on the whole ScrDyn FUNC node (closure
1831
+ * and descriptor ride inside); the engine finalizer releases it — at
1832
+ * teardown that runs before the RC audit, the isl_hostfn story. A
1833
+ * scriptc exception thrown inside reverse-bridges to an engine throw;
1834
+ * the receiver (`this`) is deliberately not forwarded (the typed
1835
+ * host-function stance — dyn thunks read the ambient receiver, which no
1836
+ * engine call site binds). Each crossing mints a FRESH engine function:
1837
+ * re-crossing identity is not preserved (SEMANTICS.md). */
1838
+
1839
+ static JSClassID isl_dynfn_class_id = 0;
1840
+
1841
+ static void isl_dynfn_finalizer(JSRuntime *rt, JSValueConst val) {
1842
+ (void)rt;
1843
+ ScrDyn *box = JS_GetOpaque(val, isl_dynfn_class_id);
1844
+ if (box) scr_dyn_release(box);
1845
+ }
1846
+
1847
+ static const JSClassDef isl_dynfn_class = {
1848
+ .class_name = "ScrDynFn",
1849
+ .finalizer = isl_dynfn_finalizer,
1850
+ };
1851
+
1852
+ static JSValue isl_dynfn_invoke(JSContext *ctx, JSValueConst this_val, int argc,
1853
+ JSValueConst *argv, int magic, JSValueConst *func_data) {
1854
+ (void)this_val;
1855
+ (void)magic;
1856
+ ScrDyn *box = JS_GetOpaque(func_data[0], isl_dynfn_class_id);
1857
+ if (!box) return JS_ThrowTypeError(ctx, "detached scriptc function");
1858
+ ScrDyn *stack_args[8];
1859
+ ScrDyn **dargs = argc <= 8 ? stack_args : malloc((size_t)argc * sizeof *dargs);
1860
+ for (int i = 0; i < argc; i++) dargs[i] = isl_dyn_from_value(argv[i]);
1861
+ bool strayed_before = isl_anchor_strayed;
1862
+ isl_host_depth++;
1863
+ ScrDyn *r = box->v.fn.thunk(box->v.fn.clo, dargs, (size_t)argc);
1864
+ isl_host_depth--;
1865
+ if (isl_anchor_strayed && !strayed_before) {
1866
+ /* The isl_hostfn_invoke re-anchor dance: a fiber the callback spawned
1867
+ * moved the engine's overflow anchor off this stack. */
1868
+ isl_anchor_here();
1869
+ isl_anchor_strayed = strayed_before;
1870
+ }
1871
+ for (int i = 0; i < argc; i++) scr_dyn_release(dargs[i]);
1872
+ if (dargs != stack_args) free(dargs);
1873
+ if (scr_exc_pending()) {
1874
+ if (r) scr_dyn_release(r);
1875
+ return isl_throw_pending(ctx);
1876
+ }
1877
+ if (!r) return JS_UNDEFINED;
1878
+ /* The thunk's DOM result converts back per the from_dyn rules; a kind
1879
+ * with no crossing (a handle, a promise) throws the same catchable
1880
+ * TypeError from_dyn would. */
1881
+ const char *bad = isl_dyn_unmarshalable(r);
1882
+ if (bad != NULL) {
1883
+ scr_dyn_release(r);
1884
+ return JS_ThrowTypeError(ctx, "an 'unknown' value holding %s cannot enter dynamically-executed code", bad);
1885
+ }
1886
+ JSValue out = isl_from_dyn(r);
1887
+ scr_dyn_release(r);
1888
+ return out; /* JS_EXCEPTION passes through as the engine throw */
1889
+ }
1890
+
1891
+ /* A fresh engine function over a boxed DOM function (borrows d — the
1892
+ * opaque box retains it). */
1893
+ static JSValue isl_dynfn_new(const ScrDyn *d) {
1894
+ JSValue boxv = JS_NewObjectClass(isl_ctx, isl_dynfn_class_id);
1895
+ JS_SetOpaque(boxv, scr_dyn_retain((ScrDyn *)d));
1896
+ JSValueConst data[1] = {boxv};
1897
+ JSValue fn = JS_NewCFunctionData(isl_ctx, isl_dynfn_invoke, (int)d->v.fn.arity, 0, 1, data);
1898
+ JS_FreeValue(isl_ctx, boxv); /* fn's func_data holds its own reference */
1899
+ return fn;
1900
+ }
1901
+
1516
1902
  /* The typed adapters' absence test for `T | undefined` parameters. */
1517
1903
  bool scr_jsval_is_undefined(ScrJsval *v) { return JS_IsUndefined(v->v); }
1518
1904