@scriptc/runtime 0.0.7 → 0.0.9

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.9",
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",
package/src/scr_bytes.c CHANGED
@@ -312,6 +312,49 @@ double scr_dataview_get(const ScrBytes *b, double byte_off, ScrDataViewGet kind,
312
312
  return 0; /* unreachable */
313
313
  }
314
314
 
315
+ void scr_dataview_set(ScrBytes *b, double byte_off, double value, ScrDataViewGet kind, bool le) {
316
+ size_t width = scr_dataview_get_size(kind);
317
+ /* ToIndex + the view-relative bounds check — the getters' ONE message. */
318
+ double off = (byte_off != byte_off) ? 0 : trunc(byte_off);
319
+ if (!(off >= 0) || off > 9007199254740991.0 || off + (double)width > (double)b->len) {
320
+ static const char msg[] = "Offset is outside the bounds of the DataView";
321
+ scr_throw_error_msg(SCR_ERR_RANGE, msg, sizeof msg - 1);
322
+ return;
323
+ }
324
+ /* Coerce to the stored bit pattern. The integer kinds share ToUint32's
325
+ * 2^32 residue (signed/unsigned store the same bits; narrower widths
326
+ * take the low bytes — 2^width divides 2^32, so the residues agree);
327
+ * F32 rounds double→float to nearest even, exactly the spec. */
328
+ uint64_t u = 0;
329
+ switch (kind) {
330
+ case SCR_DV_U8:
331
+ case SCR_DV_I8:
332
+ case SCR_DV_U16:
333
+ case SCR_DV_I16:
334
+ case SCR_DV_U32:
335
+ case SCR_DV_I32:
336
+ u = (uint64_t)scr_bytes_to_u32(value);
337
+ break;
338
+ case SCR_DV_F32: {
339
+ float f = (float)value;
340
+ uint32_t bits;
341
+ memcpy(&bits, &f, 4);
342
+ u = bits;
343
+ break;
344
+ }
345
+ case SCR_DV_F64:
346
+ memcpy(&u, &value, 8);
347
+ break;
348
+ default:
349
+ return; /* BIG kinds never lower (bigint arguments) */
350
+ }
351
+ /* Scatter host-independently; le false is the JS big-endian default. */
352
+ uint8_t *p = b->data + (size_t)off;
353
+ for (size_t i = 0; i < width; i++) {
354
+ p[le ? i : width - 1 - i] = (uint8_t)(u >> (8 * i));
355
+ }
356
+ }
357
+
315
358
  /* ── encodings (u8 only — the compiler routes only u8 receivers here) ──── */
316
359
 
317
360
  static const char scr_hex_digits[] = "0123456789abcdef";
@@ -886,16 +929,19 @@ ScrBytes *scr_bytes_from_arr(ScrBytesElem elem, const ScrArr *arr) {
886
929
 
887
930
  static size_t scr_bytes_received(double v, char out[48]); /* the numeric section below */
888
931
 
889
- /* validateOffset(name, 0, max): non-integers are 'an integer', the rest
890
- * '>= 0 && <= max' (Node spells '&&' here, unlike the read/write
891
- * families' 'and'). max < 0 means copy's no-upper-bound '>= 0' render. */
892
- static bool scr_bytes_validate_off(const char *name, double value, double max) {
893
- if (floor(value) == value && value >= 0 && (max < 0 || value <= max)) return true;
932
+ /* validateOffset(name, 0, max): non-integers are 'an integer' (Number.
933
+ * isInteger ±Infinity render 'an integer' too), the rest '>= 0 && <=
934
+ * max' (Node spells '&&' here, unlike the read/write families' 'and').
935
+ * max < 0 means copy's no-upper-bound '>= 0' render. Exported: the
936
+ * checked-dynamic compare/equals validators (scr_bytes_io.c) run the
937
+ * same ladder after their own type gate. */
938
+ bool scr_bytes_validate_off(const char *name, double value, double max) {
939
+ if (isfinite(value) && floor(value) == value && value >= 0 && (max < 0 || value <= max)) return true;
894
940
  char recv[48];
895
941
  scr_bytes_received(value, recv);
896
942
  char msg[160];
897
943
  int mlen;
898
- if (floor(value) != value) {
944
+ if (floor(value) != value || !isfinite(value)) {
899
945
  mlen = snprintf(msg, sizeof msg,
900
946
  "The value of \"%s\" is out of range. It must be an integer. Received %s",
901
947
  name, recv);
@@ -909,7 +955,7 @@ static bool scr_bytes_validate_off(const char *name, double value, double max) {
909
955
  "The value of \"%s\" is out of range. It must be >= 0 && <= %s. Received %s",
910
956
  name, maxb, recv);
911
957
  }
912
- scr_throw_error_msg(SCR_ERR_RANGE, msg, (size_t)mlen);
958
+ scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)mlen, "ERR_OUT_OF_RANGE");
913
959
  return false;
914
960
  }
915
961
 
@@ -994,7 +1040,7 @@ static ScrBytes *scr_bytes_fill_core(ScrBytes *b, const uint8_t *pat, size_t pat
994
1040
  bool zero_ok, double nargs, double offset, double end) {
995
1041
  if (patn == 0 && !zero_ok) {
996
1042
  static const char msg[] = "The argument 'value' is invalid. Received <Buffer >";
997
- scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
1043
+ scr_throw_error_msg_code(SCR_ERR_TYPE, msg, sizeof msg - 1, "ERR_INVALID_ARG_VALUE");
998
1044
  return NULL;
999
1045
  }
1000
1046
  size_t n = (size_t)nargs;
@@ -1065,7 +1111,7 @@ ScrBytes *scr_bytes_swap(ScrBytes *b, double width) {
1065
1111
  if (b->len % w != 0) {
1066
1112
  char msg[64];
1067
1113
  int mlen = snprintf(msg, sizeof msg, "Buffer size must be a multiple of %zu-bits", w * 8);
1068
- scr_throw_error_msg(SCR_ERR_RANGE, msg, (size_t)mlen);
1114
+ scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)mlen, "ERR_INVALID_BUFFER_SIZE");
1069
1115
  return NULL;
1070
1116
  }
1071
1117
  for (size_t g = 0; g < b->len; g += w) {
@@ -1244,7 +1290,7 @@ static void scr_bytes_bounds_error(double value, double length, const char *type
1244
1290
  type ? type : "offset", recv);
1245
1291
  } else if (length < 0) {
1246
1292
  static const char oob[] = "Attempt to access memory outside buffer bounds";
1247
- scr_throw_error_msg(SCR_ERR_RANGE, oob, sizeof oob - 1);
1293
+ scr_throw_error_msg_code(SCR_ERR_RANGE, oob, sizeof oob - 1, "ERR_BUFFER_OUT_OF_BOUNDS");
1248
1294
  return;
1249
1295
  } else {
1250
1296
  char lenbuf[32];
@@ -1253,7 +1299,7 @@ static void scr_bytes_bounds_error(double value, double length, const char *type
1253
1299
  "The value of \"%s\" is out of range. It must be >= %d and <= %s. Received %s",
1254
1300
  type ? type : "offset", type ? 1 : 0, lenbuf, recv);
1255
1301
  }
1256
- scr_throw_error_msg(SCR_ERR_RANGE, msg, (size_t)mlen);
1302
+ scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)mlen, "ERR_OUT_OF_RANGE");
1257
1303
  }
1258
1304
 
1259
1305
  /* The shared offset gate: width bytes at offset must lie inside b. */
@@ -1297,7 +1343,7 @@ static bool scr_bytes_check_int(const ScrBytes *b, double value, double offset,
1297
1343
  "The value of \"value\" is out of range. It must be >= %s and <= %s. Received %s",
1298
1344
  minb, maxb, recv);
1299
1345
  }
1300
- scr_throw_error_msg(SCR_ERR_RANGE, msg, (size_t)mlen);
1346
+ scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)mlen, "ERR_OUT_OF_RANGE");
1301
1347
  return false;
1302
1348
  }
1303
1349
  return scr_bytes_rw_check(b, offset, width);
@@ -7,9 +7,11 @@
7
7
  #include "scr_runtime.h"
8
8
 
9
9
  #include <errno.h>
10
+ #include <math.h>
10
11
  #include <stdio.h>
11
12
  #include <stdlib.h>
12
13
  #include <string.h>
14
+ #include <time.h>
13
15
 
14
16
  static void scr_bytes_io_oom(void) {
15
17
  scr_trap("scriptc: out of memory\n");
@@ -138,7 +140,7 @@ ScrBytes *scr_crypto_random_bytes(double n) {
138
140
  msg, sizeof msg,
139
141
  "The value of \"size\" is out of range. It must be >= 0 && <= 2147483647. Received %.*s",
140
142
  (int)numlen, num);
141
- scr_throw_error_msg(SCR_ERR_RANGE, msg, (size_t)mlen);
143
+ scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)mlen, "ERR_OUT_OF_RANGE");
142
144
  return NULL;
143
145
  }
144
146
  ScrBytes *b = scr_bytes_new(SCR_BYTES_U8, n);
@@ -158,3 +160,97 @@ bool scr_process_stderr_write_bytes(const ScrBytes *b) {
158
160
  fwrite(b->data, 1, b->len * scr_bytes_elem_size(b->elem), stderr);
159
161
  return true;
160
162
  }
163
+
164
+ /* ── the checked-dynamic Buffer compare/equals validators ──────────────
165
+ * Node's argument ladders for buf.equals / buf.compare / Buffer.compare
166
+ * over DOM-boxed arguments (the invalid-input probes: string needles,
167
+ * '0' offsets, null/object range args). A well-typed dyn still computes
168
+ * the real answer — validation, not a constant fence. */
169
+
170
+ /* A bytes payload or the API's own ERR_INVALID_ARG_TYPE (borrowed). */
171
+ static ScrBytes *scr_bytes_chk_u8(const ScrDyn *d, const char *argname) {
172
+ if (d->kind != SCR_DYN_BYTES) {
173
+ scr_dyn_arg_type_fail(argname, "an instance of Buffer or Uint8Array", d);
174
+ return NULL;
175
+ }
176
+ return d->v.bytes;
177
+ }
178
+
179
+ double scr_buffer_compare_chk(const ScrDyn *a, const ScrDyn *b) {
180
+ ScrBytes *b1 = scr_bytes_chk_u8(a, "buf1");
181
+ if (!b1) return 0;
182
+ ScrBytes *b2 = scr_bytes_chk_u8(b, "buf2");
183
+ if (!b2) return 0;
184
+ return scr_bytes_compare(b1, b2, 0, 0, 0, 0, 0);
185
+ }
186
+
187
+ bool scr_bytes_equals_chk(const ScrBytes *recv, const ScrDyn *other) {
188
+ ScrBytes *o = scr_bytes_chk_u8(other, "otherBuffer");
189
+ if (!o) return false;
190
+ return scr_bytes_equals(recv, o);
191
+ }
192
+
193
+ /* One offset slot: undefined takes the Node default, non-numbers throw
194
+ * ERR_INVALID_ARG_TYPE "of type number", numbers run validateOffset. */
195
+ static bool scr_bytes_chk_off(const ScrDyn *d, const char *name, double max,
196
+ double dflt, double *out) {
197
+ if (d->kind == SCR_DYN_UNDEF) {
198
+ *out = dflt;
199
+ return true;
200
+ }
201
+ if (d->kind != SCR_DYN_NUM) {
202
+ scr_dyn_arg_type_fail(name, "of type number", d);
203
+ return false;
204
+ }
205
+ *out = d->v.num;
206
+ return scr_bytes_validate_off(name, *out, max);
207
+ }
208
+
209
+ double scr_bytes_compare_chk(const ScrBytes *src, const ScrDyn *target,
210
+ const ScrDyn *ts, const ScrDyn *te,
211
+ const ScrDyn *ss, const ScrDyn *se) {
212
+ ScrBytes *t = scr_bytes_chk_u8(target, "target");
213
+ if (!t) return 0;
214
+ double tsv, tev, ssv, sev;
215
+ if (!scr_bytes_chk_off(ts, "targetStart", 9007199254740991.0, 0, &tsv)) return 0;
216
+ if (!scr_bytes_chk_off(te, "targetEnd", (double)t->len, (double)t->len, &tev)) return 0;
217
+ if (!scr_bytes_chk_off(ss, "sourceStart", 9007199254740991.0, 0, &ssv)) return 0;
218
+ if (!scr_bytes_chk_off(se, "sourceEnd", (double)src->len, (double)src->len, &sev)) return 0;
219
+ /* Every slot validated or defaulted above; nargs 4 revalidates the
220
+ * now-known-good numbers (a no-op) and keeps one comparison core. */
221
+ return scr_bytes_compare(src, t, 4, tsv, tev, ssv, sev);
222
+ }
223
+
224
+ /* new Buffer(number, encoding) — the deprecated ctor's string arm with a
225
+ * non-string first argument: Node's exact ERR_INVALID_ARG_TYPE (the
226
+ * throwing path never fires DEP0005, so compiled silence matches). */
227
+ ScrBytes *scr_buffer_new_string_fail(const ScrDyn *got) {
228
+ scr_dyn_arg_type_fail("string", "of type string", got);
229
+ return NULL;
230
+ }
231
+
232
+ /* fs._toUnixTimestamp — the seconds coercion the utimes family runs on
233
+ * its time arguments (fs.js's toUnixTimestamp, underscore-exported):
234
+ * numeric STRINGS pass ToNumber's loose-equality gate (+time == time —
235
+ * whitespace-only strings answer 0), finite numbers pass (negatives
236
+ * answer now/1000, Node's "past times" shape), everything else throws
237
+ * Node's exact ERR_INVALID_ARG_TYPE. Borrowed; the throw is pending on
238
+ * the dummy 0 return. */
239
+ double scr_fs_to_unix_timestamp(const ScrDyn *t) {
240
+ if (t->kind == SCR_DYN_STR) {
241
+ double n = scr_string_to_number(t->v.str);
242
+ /* +time == time: NaN fails; every parsed number loosely equals its
243
+ * own source string by construction of ToNumber. */
244
+ if (n == n) return n;
245
+ }
246
+ if (t->kind == SCR_DYN_NUM && isfinite(t->v.num)) {
247
+ if (t->v.num < 0) {
248
+ struct timespec ts;
249
+ clock_gettime(CLOCK_REALTIME, &ts);
250
+ return ((double)ts.tv_sec * 1000.0 + (double)ts.tv_nsec / 1e6) / 1000.0;
251
+ }
252
+ return t->v.num;
253
+ }
254
+ scr_dyn_arg_type_fail("time", "an instance of Date or an Time in seconds", t);
255
+ return 0;
256
+ }
@@ -1,77 +1,19 @@
1
1
  /* The checked-dynamic HANDLE support unit — everything the handle
2
2
  * dispatchers (scr_http.c / scr_net.c) and the emitter unit's dyn
3
- * registrations share BEYOND the DOM core: errors.js's
4
- * determineSpecificType renderer and the ERR_INVALID_ARG_TYPE throwers
5
- * (the listener gate), and the runtime-built listener adapter closures
6
- * whose fire thunks box event tuples back into the DOM. Split out of
7
- * scr_json.c so handle-free binaries keep their exact size class:
8
- * cc.ts compiles this unit exactly when a user of it links (the net or
9
- * emitter gate http implies net).
3
+ * registrations share BEYOND the DOM core: the listener gate over the
4
+ * ERR_INVALID_ARG_TYPE throwers (the throwers themselves live in
5
+ * scr_json.c beside the DOM core the always-linked bytes/fs argument
6
+ * validators call them too), and the runtime-built listener adapter
7
+ * closures whose fire thunks box event tuples back into the DOM. Split
8
+ * out of scr_json.c so handle-free binaries keep their exact size
9
+ * class: cc.ts compiles this unit exactly when a user of it links (the
10
+ * net or emitter gate — http implies net).
10
11
  */
11
12
  #include "scr_runtime.h"
12
13
 
13
14
  #include <stdio.h>
14
15
  #include <string.h>
15
16
 
16
- /* errors.js's determineSpecificType over a DOM value — the "Received
17
- * ..." tail of Node's ERR_INVALID_ARG_TYPE messages. Renders into buf
18
- * when the shape needs a payload; returns the text either way. */
19
- const char *scr_dyn_specific_type(const ScrDyn *cb, char *detail, size_t cap) {
20
- const char *d = detail;
21
- switch (cb->kind) {
22
- case SCR_DYN_NULL: d = "null"; break;
23
- case SCR_DYN_UNDEF: d = "undefined"; break;
24
- case SCR_DYN_OBJ: d = "an instance of Object"; break;
25
- case SCR_DYN_ARR: d = "an instance of Array"; break;
26
- case SCR_DYN_BYTES: d = "an instance of Uint8Array"; break;
27
- case SCR_DYN_FUNC: d = "function"; break; /* callers usually return before this */
28
- case SCR_DYN_HANDLE:
29
- snprintf(detail, cap, "an instance of %s", scr_dyn_handle_cls(cb));
30
- break;
31
- case SCR_DYN_PROMISE: d = "an instance of Promise"; break;
32
- case SCR_DYN_BOOL:
33
- snprintf(detail, cap, "type boolean (%s)", cb->v.b ? "true" : "false");
34
- break;
35
- case SCR_DYN_NUM: {
36
- char num[32];
37
- size_t n = scr_f64_to_str(cb->v.num, num);
38
- snprintf(detail, cap, "type number (%.*s)", (int)n, num);
39
- break;
40
- }
41
- case SCR_DYN_STR: {
42
- const ScrStr *sv = cb->v.str;
43
- char insp[32];
44
- size_t n = 0;
45
- insp[n++] = '\'';
46
- for (size_t i = 0; i < sv->len && n < 28; i++) insp[n++] = sv->data[i];
47
- if (sv->len + 2 > 28) {
48
- n = 25;
49
- memcpy(insp + n, "...", 3);
50
- n += 3;
51
- } else {
52
- insp[n++] = '\'';
53
- }
54
- snprintf(detail, cap, "type string (%.*s)", (int)n, insp);
55
- break;
56
- }
57
- default: d = "an instance of Object"; break;
58
- }
59
- return d;
60
- }
61
-
62
- /* Node's ERR_INVALID_ARG_TYPE thrower ("The \"chunk\" argument must be
63
- * of type string or an instance of Buffer or Uint8Array. Received type
64
- * number (5)") — the handle dispatchers' per-arg gates. `expected` is
65
- * the full "of type ..."/"an instance of ..." clause. */
66
- void scr_dyn_arg_type_fail(const char *argname, const char *expected, const ScrDyn *got) {
67
- char detail[64];
68
- const char *d = scr_dyn_specific_type(got, detail, sizeof detail);
69
- char msg[224];
70
- int len = snprintf(msg, sizeof msg,
71
- "The \"%s\" argument must be %s. Received %s", argname, expected, d);
72
- scr_throw_error_msg_code(SCR_ERR_TYPE, msg, (size_t)len, "ERR_INVALID_ARG_TYPE");
73
- }
74
-
75
17
  /* Node's ERR_INVALID_ARG_TYPE listener gate (errors.js's
76
18
  * determineSpecificType shapes — the scr_emitter_check_listener wording,
77
19
  * shared here so the gated units need not link the emitter unit). */
package/src/scr_error.c CHANGED
@@ -237,6 +237,21 @@ void scr_throw_error_msg_code(int kind, const char *message, size_t len,
237
237
  scr_error_traced ? &scr_error_trace : NULL);
238
238
  }
239
239
 
240
+ /* A compiler-resolved Node-parity throw (the always-throwing lowered
241
+ * arms: ERR_INVALID_THIS receivers, ERR_MISSING_ARGS ladders, the
242
+ * symbol-to-string TypeError): builds the builtin error of `kind` with
243
+ * `code` stamped when non-empty. Borrows both strings; always throws
244
+ * (catchably — call sites are compiler-emitted pending checks). */
245
+ void scr_throw_node_coded(double kind, const ScrStr *code, const ScrStr *msg) {
246
+ ScrError *e = scr_error_new((int)kind, (ScrStr *)msg); /* borrowed in */
247
+ if (code->len > 0) {
248
+ scr_str_release(e->code); /* NULL-safe */
249
+ e->code = scr_str_retain((ScrStr *)code);
250
+ }
251
+ scr_throw_obj(e, &scr_error_retain_v, &scr_error_release_v,
252
+ scr_error_traced ? &scr_error_trace : NULL);
253
+ }
254
+
240
255
  /* A read of a `declare`d const nothing defines (the bundler-define
241
256
  * pattern — __VERSION__): Node running the source throws ReferenceError
242
257
  * "<name> is not defined" at the access. Borrows `name`; always throws
@@ -662,6 +662,18 @@ ScrArr *scr_emitter_listeners(ScrEmitter *em, ScrStr *name) {
662
662
 
663
663
  /* ── max listeners ────────────────────────────────────────────────────── */
664
664
 
665
+ /* The checked-dynamic setMaxListeners ladder: non-numbers throw Node's
666
+ * ERR_INVALID_ARG_TYPE ("The \"setMaxListeners\" argument must be of
667
+ * type number. Received ..."), numbers run the range gate below.
668
+ * Borrowed dyn; +1 receiver either way (the pending check unwinds). */
669
+ ScrEmitter *scr_emitter_set_max_chk(ScrEmitter *em, const ScrDyn *n) {
670
+ if (n->kind != SCR_DYN_NUM) {
671
+ scr_dyn_arg_type_fail("setMaxListeners", "of type number", n);
672
+ return scr_emitter_retain(em);
673
+ }
674
+ return scr_emitter_set_max(em, n->v.num);
675
+ }
676
+
665
677
  ScrEmitter *scr_emitter_set_max(ScrEmitter *em, double n) {
666
678
  if (!(n >= 0)) { /* negatives and NaN — Node's validateNumber */
667
679
  char num[32];
@@ -682,6 +694,28 @@ double scr_emitter_get_max(ScrEmitter *em) {
682
694
  return scr_emitter_default_max;
683
695
  }
684
696
 
697
+ /* The checked-dynamic defaultMaxListeners ladder — `name` picks the
698
+ * message's slot ("setMaxListeners" for the static call,
699
+ * "defaultMaxListeners" for the module-property assignment, Node's own
700
+ * split). Borrowed. */
701
+ void scr_emitter_set_default_max_chk(const ScrDyn *n, const ScrStr *name) {
702
+ if (n->kind != SCR_DYN_NUM) {
703
+ scr_dyn_arg_type_fail(name->data, "of type number", n);
704
+ return;
705
+ }
706
+ if (!(n->v.num >= 0)) {
707
+ char num[32];
708
+ size_t nlen = scr_f64_to_str(n->v.num, num);
709
+ char msg[128];
710
+ int len = snprintf(msg, sizeof msg,
711
+ "The value of \"%s\" is out of range. It must be >= 0. Received %.*s",
712
+ name->data, (int)nlen, num);
713
+ scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE");
714
+ return;
715
+ }
716
+ scr_emitter_set_default_max(n->v.num);
717
+ }
718
+
685
719
  void scr_emitter_set_default_max(double n) {
686
720
  if (!(n >= 0)) { /* negatives and NaN — Node's validateNumber(n, "setMaxListeners", 0) */
687
721
  char num[32];
@@ -181,10 +181,33 @@ ScrStr *scr_caught_to_string(const ScrCaught *c) {
181
181
  * contract-shaped failure exactly like a trap. Render the same "Uncaught
182
182
  * ..." first line the executable epilogue prints (scr_exc_print_uncaught's
183
183
  * arms, buffer-writing), release the payload, and route the text through
184
- * the trap funnel — one failure channel at the boundary. */
184
+ * the trap funnel — one failure channel at the boundary. The funnel
185
+ * (scr_library.c) assembles the rendered line into the structured
186
+ * trap-teaching form (code SC4013, the trapping entry's symbol) before
187
+ * delivery; only the 0x01-led verbatim path below bypasses assembly. */
185
188
  void scr_library_check_exc(void) {
186
189
  if (!scr_exc_pending()) return;
187
190
  static char buf[1024]; /* the message is copied out before the payload dies */
191
+ /* The ratified verbatim rule: a thrown message that ALREADY begins with
192
+ * the structured trap-teaching marker (0x01) is delivered exactly as
193
+ * authored — no "Uncaught " prefix, no added newline — which is how a
194
+ * facade-authored structured teaching rides the throw channel through to
195
+ * the sink. Both throw idioms qualify: a thrown string, and an Error
196
+ * whose .message starts with the marker. Everything else keeps the
197
+ * baseline "Uncaught ..." shape below, whose first byte is printable —
198
+ * the guarantee the marker's unambiguity rests on. */
199
+ const ScrStr *marked = NULL;
200
+ if (scr_exc_kind == SCR_EXC_STR) {
201
+ marked = (const ScrStr *)scr_exc_payload;
202
+ } else if (scr_exc_kind == SCR_EXC_OBJ && scr_error_is(scr_exc_payload)) {
203
+ marked = ((const ScrError *)scr_exc_payload)->message;
204
+ }
205
+ if (marked != NULL && marked->len > 0 && (uint8_t)marked->data[0] == 0x01) {
206
+ size_t take = marked->len > sizeof buf ? sizeof buf : marked->len;
207
+ memcpy(buf, marked->data, take);
208
+ scr_exc_reset(); /* the payload is released before the funnel poisons */
209
+ scr_trap_len(buf, take);
210
+ }
188
211
  size_t n = 0;
189
212
  const char prefix[] = "Uncaught ";
190
213
  memcpy(buf, prefix, sizeof prefix - 1);
package/src/scr_island.c CHANGED
@@ -379,6 +379,7 @@ enum {
379
379
  ISL_H_DESTRCHECK,
380
380
  ISL_H_ITERN,
381
381
  ISL_H_ITER,
382
+ ISL_H_CALLSPREAD,
382
383
  ISL_H_COUNT,
383
384
  };
384
385
 
@@ -417,7 +418,17 @@ static const char isl_prelude[] =
417
418
  "else if(typeof v===\"number\")d=\"number \"+v;else if(typeof v===\"boolean\")d=\"boolean \"+v;"
418
419
  "else if(typeof v===\"function\")d=\"function\";else d=\"object\";"
419
420
  "throw new TypeError(d+\" is not iterable (cannot read property Symbol(Symbol.iterator))\")}"
420
- "return v[Symbol.iterator]()}]";
421
+ "return v[Symbol.iterator]()},"
422
+ /* ISL_H_CALLSPREAD: spread application (`f(...pre, ...s)` — the
423
+ * rest-forwarding idiom's call): REAL spread syntax, so iterator
424
+ * protocols are the engine's own; the guards front-run V8's exact
425
+ * spread-call TypeError texts (nullish spells the spread expression
426
+ * `w`, everything else the generic Spread-syntax text). */
427
+ "(f,p,s,w)=>{if(s===undefined||s===null)"
428
+ "throw new TypeError(w+\" is not iterable (cannot read property \"+s+\")\");"
429
+ "if(typeof s[Symbol.iterator]!==\"function\")"
430
+ "throw new TypeError(\"Spread syntax requires ...iterable[Symbol.iterator] to be a function\");"
431
+ "return f(...p,...s)}]";
421
432
 
422
433
  static void isl_free_boot(void);
423
434
  static void isl_prom_wraps_teardown(void);
@@ -1274,6 +1285,23 @@ ScrJsval *scr_jsval_call(ScrJsval *f, int argc, ScrJsval **argv) {
1274
1285
  return isl_cell_new(r);
1275
1286
  }
1276
1287
 
1288
+ /* Spread application on an island callee (jsOp callSpread) — the prelude
1289
+ * helper's real `f(...pre, ...spread)`, so iterator protocols are the
1290
+ * engine's own and the guards front-run V8's exact spread-call TypeError
1291
+ * texts (`what` is the spread expression's source spelling). Borrows
1292
+ * everything; +1 out, or NULL with the engine exception bridged. */
1293
+ ScrJsval *scr_jsval_call_spread(ScrJsval *f, ScrJsval *pre, ScrJsval *spread, const ScrStr *what) {
1294
+ isl_entry();
1295
+ JSValue argv[4] = {f->v, pre->v, spread->v, JS_NewStringLen(isl_ctx, what->data, what->len)};
1296
+ JSValue r = JS_Call(isl_ctx, isl_helpers[ISL_H_CALLSPREAD], JS_UNDEFINED, 4, argv);
1297
+ JS_FreeValue(isl_ctx, argv[3]);
1298
+ if (JS_IsException(r)) {
1299
+ isl_bridge_exception();
1300
+ return NULL;
1301
+ }
1302
+ return isl_cell_new(r);
1303
+ }
1304
+
1277
1305
  /* `new X(...)` on an island callee (jsOp construct). Borrows everything;
1278
1306
  * +1 out, or NULL with the engine exception bridged. */
1279
1307
  ScrJsval *scr_jsval_construct(ScrJsval *f, int argc, ScrJsval **argv) {
@@ -1412,8 +1440,27 @@ static JSValue isl_hostfn_invoke(JSContext *ctx, JSValueConst this_val, int argc
1412
1440
  IslHostFn *b = JS_GetOpaque(func_data[0], isl_hostfn_class_id);
1413
1441
  if (!b) return JS_ThrowTypeError(ctx, "detached scriptc host function");
1414
1442
  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);
1443
+ int ncells;
1444
+ if (b->arity < 0) {
1445
+ /* The ISLAND-REST shape (negative arity = -(leading declared + 1)):
1446
+ * leading params pad/drop like any host call; the trailing cell is a
1447
+ * fresh ENGINE ARRAY of the surplus arguments — the closure's rest
1448
+ * binding IS the engine's own arguments array. */
1449
+ int leading = -b->arity - 1;
1450
+ for (int i = 0; i < leading; i++) {
1451
+ cells[i] = isl_cell_new(i < argc ? JS_DupValue(ctx, argv[i]) : JS_UNDEFINED);
1452
+ }
1453
+ JSValue rest = JS_NewArray(ctx);
1454
+ for (int i = leading; i < argc; i++) {
1455
+ JS_SetPropertyUint32(ctx, rest, (uint32_t)(i - leading), JS_DupValue(ctx, argv[i]));
1456
+ }
1457
+ cells[leading] = isl_cell_new(rest);
1458
+ ncells = leading + 1;
1459
+ } else {
1460
+ for (int i = 0; i < b->arity; i++) {
1461
+ cells[i] = isl_cell_new(i < argc ? JS_DupValue(ctx, argv[i]) : JS_UNDEFINED);
1462
+ }
1463
+ ncells = b->arity;
1417
1464
  }
1418
1465
  bool strayed_before = isl_anchor_strayed;
1419
1466
  isl_host_depth++;
@@ -1429,7 +1476,7 @@ static JSValue isl_hostfn_invoke(JSContext *ctx, JSValueConst this_val, int argc
1429
1476
  isl_anchor_here();
1430
1477
  isl_anchor_strayed = strayed_before;
1431
1478
  }
1432
- for (int i = 0; i < b->arity; i++) scr_jsval_release(cells[i]);
1479
+ for (int i = 0; i < ncells; i++) scr_jsval_release(cells[i]);
1433
1480
  if (scr_exc_pending()) {
1434
1481
  if (r) scr_jsval_release(r);
1435
1482
  return isl_throw_pending(ctx);
@@ -1443,7 +1490,9 @@ static JSValue isl_hostfn_invoke(JSContext *ctx, JSValueConst this_val, int argc
1443
1490
  ScrJsval *scr_jsval_from_closure(ScrClosure *c, int arity,
1444
1491
  ScrJsval *(*adapt)(ScrClosure *, ScrJsval **)) {
1445
1492
  isl_entry();
1446
- if (arity > ISL_HOSTFN_MAX_ARITY) {
1493
+ /* Negative arity = the island-rest shape; the CELL count is the leading
1494
+ * declared params + the one rest-array slot. */
1495
+ if ((arity < 0 ? -arity : arity) > ISL_HOSTFN_MAX_ARITY) {
1447
1496
  fprintf(stderr, "scriptc: island callback arity %d exceeds %d\n", arity,
1448
1497
  ISL_HOSTFN_MAX_ARITY);
1449
1498
  abort(); /* the frontend fences this; reaching here is a compiler bug */
@@ -1459,7 +1508,7 @@ ScrJsval *scr_jsval_from_closure(ScrClosure *c, int arity,
1459
1508
  JSValue box = JS_NewObjectClass(isl_ctx, isl_hostfn_class_id);
1460
1509
  JS_SetOpaque(box, b);
1461
1510
  JSValueConst data[1] = {box};
1462
- JSValue fn = JS_NewCFunctionData(isl_ctx, isl_hostfn_invoke, arity, 0, 1, data);
1511
+ JSValue fn = JS_NewCFunctionData(isl_ctx, isl_hostfn_invoke, arity < 0 ? -arity - 1 : arity, 0, 1, data);
1463
1512
  JS_FreeValue(isl_ctx, box); /* fn's func_data holds its own reference */
1464
1513
  return isl_cell_new(fn);
1465
1514
  }
@@ -1717,6 +1766,60 @@ ScrJsval *scr_jsval_obj_lit(int npairs, ScrJsval **kv) {
1717
1766
  return isl_cell_new(o);
1718
1767
  }
1719
1768
 
1769
+ /* The engine-native TemplateStringsArray for an island tag call: `kv`
1770
+ * carries n cooked strings then n raw strings — a fresh array whose
1771
+ * `.raw` property holds the raw spellings, exactly the object a tagged
1772
+ * template hands its tag (a JSON marshal would drop `.raw`, and tags
1773
+ * dispatch on it). */
1774
+ ScrJsval *scr_jsval_tpl_strings(int n, ScrJsval **kv) {
1775
+ isl_entry();
1776
+ JSValue cooked = JS_NewArray(isl_ctx);
1777
+ JSValue raw = JS_NewArray(isl_ctx);
1778
+ for (int i = 0; i < n; i++) {
1779
+ JS_SetPropertyUint32(isl_ctx, cooked, (uint32_t)i, JS_DupValue(isl_ctx, kv[i]->v));
1780
+ JS_SetPropertyUint32(isl_ctx, raw, (uint32_t)i, JS_DupValue(isl_ctx, kv[n + i]->v));
1781
+ }
1782
+ JS_SetPropertyStr(isl_ctx, cooked, "raw", raw); /* consumes raw */
1783
+ return isl_cell_new(cooked);
1784
+ }
1785
+
1786
+ /* Spread completion for an island-native literal: copies `src`'s own
1787
+ * enumerable properties onto `obj` — the engine's own Object.assign (the
1788
+ * spec's CopyDataProperties; null/undefined sources spread nothing) — and
1789
+ * answers the target retained (+1). NULL with the exception pending when
1790
+ * a source getter throws. */
1791
+ ScrJsval *scr_jsval_obj_spread(ScrJsval *obj, ScrJsval *src) {
1792
+ isl_entry();
1793
+ if (JS_IsNull(src->v) || JS_IsUndefined(src->v)) return scr_jsval_retain(obj);
1794
+ JSValue global = JS_GetGlobalObject(isl_ctx);
1795
+ JSValue object_ctor = JS_GetPropertyStr(isl_ctx, global, "Object");
1796
+ JS_FreeValue(isl_ctx, global);
1797
+ JSValue assign = JS_GetPropertyStr(isl_ctx, object_ctor, "assign");
1798
+ JS_FreeValue(isl_ctx, object_ctor);
1799
+ JSValueConst args[2] = { obj->v, src->v };
1800
+ JSValue r = JS_Call(isl_ctx, assign, JS_UNDEFINED, 2, args);
1801
+ JS_FreeValue(isl_ctx, assign);
1802
+ if (JS_IsException(r)) {
1803
+ isl_bridge_exception();
1804
+ return NULL;
1805
+ }
1806
+ JS_FreeValue(isl_ctx, r); /* assign answers the target itself */
1807
+ return scr_jsval_retain(obj);
1808
+ }
1809
+
1810
+ /* Getter completion for an island-native literal: defines `key` on `obj`
1811
+ * as an engine GETTER invoking `fn` (a marshaled host function), and
1812
+ * answers the object retained (+1) so builds chain. Enumerable +
1813
+ * configurable, no setter — exactly a JS object-literal `get k() {}`. */
1814
+ ScrJsval *scr_jsval_define_getter(ScrJsval *obj, ScrJsval *key, ScrJsval *fn) {
1815
+ isl_entry();
1816
+ JSAtom k = JS_ValueToAtom(isl_ctx, key->v);
1817
+ JS_DefinePropertyGetSet(isl_ctx, obj->v, k, JS_DupValue(isl_ctx, fn->v), JS_UNDEFINED,
1818
+ JS_PROP_ENUMERABLE | JS_PROP_CONFIGURABLE);
1819
+ JS_FreeAtom(isl_ctx, k);
1820
+ return scr_jsval_retain(obj);
1821
+ }
1822
+
1720
1823
  ScrJsval *scr_jsval_arr_lit(int n, ScrJsval **elems) {
1721
1824
  isl_entry();
1722
1825
  JSValue a = JS_NewArray(isl_ctx);