@scriptc/runtime 0.0.4 → 0.0.6

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.4",
3
+ "version": "0.0.6",
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_array.c CHANGED
@@ -13,8 +13,7 @@ long scr_arr_live_count(void) { return scr_live_arrays; }
13
13
  #endif
14
14
 
15
15
  static void scr_arr_oom(void) {
16
- fputs("scriptc: out of memory\n", stderr);
17
- abort();
16
+ scr_trap("scriptc: out of memory\n");
18
17
  }
19
18
 
20
19
  /* JS would return undefined for an OOB read and create holes for a far OOB
@@ -23,10 +22,8 @@ static void scr_arr_oom(void) {
23
22
  static void scr_arr_trap_oob(double i, size_t len) {
24
23
  char buf[32];
25
24
  scr_f64_to_str(i, buf);
26
- fprintf(stderr,
27
- "scriptc: RangeError: array index %s out of bounds (length %zu)\n",
28
- buf, len);
29
- abort();
25
+ scr_trap_fmt("scriptc: RangeError: array index %s out of bounds (length %zu)\n",
26
+ buf, len);
30
27
  }
31
28
 
32
29
  /* Validate i as an element index. limit is a->len for reads, a->len + 1 for
@@ -264,8 +261,7 @@ double scr_arr_push_ref(ScrArr *a, void *v) {
264
261
 
265
262
  static uint64_t scr_arr_pop_slot(ScrArr *a) {
266
263
  if (a->len == 0) {
267
- fputs("scriptc: RangeError: pop() on an empty array\n", stderr);
268
- abort();
264
+ scr_trap("scriptc: RangeError: pop() on an empty array\n");
269
265
  }
270
266
  return a->data[--a->len];
271
267
  }
@@ -283,8 +279,7 @@ void *scr_arr_pop_ref(ScrArr *a) { return scr_slot_to_ptr(scr_arr_pop_slot(a));
283
279
  * ownership moves out to the caller (no retain). */
284
280
  static uint64_t scr_arr_shift_slot(ScrArr *a) {
285
281
  if (a->len == 0) {
286
- fputs("scriptc: internal error: shift() on an empty array\n", stderr);
287
- abort();
282
+ scr_trap("scriptc: internal error: shift() on an empty array\n");
288
283
  }
289
284
  uint64_t s = a->data[0];
290
285
  a->len--;
@@ -456,8 +451,7 @@ ScrStr *scr_arr_join(ScrArr *a, ScrStr *sep) {
456
451
  case SCR_ELEM_BYTES:
457
452
  case SCR_ELEM_REF:
458
453
  /* The compiler rejects join on ref-element arrays (SC1090). */
459
- fputs("scriptc: internal error: join on a ref-element array\n", stderr);
460
- abort();
454
+ scr_trap("scriptc: internal error: join on a ref-element array\n");
461
455
  }
462
456
  }
463
457
  ScrStr *out = scr_str_new(buf, len);
package/src/scr_assert.c CHANGED
@@ -32,8 +32,7 @@
32
32
  #include <string.h>
33
33
 
34
34
  static void scr_assert_oom(void) {
35
- fputs("scriptc: out of memory\n", stderr);
36
- abort();
35
+ scr_trap("scriptc: out of memory\n");
37
36
  }
38
37
 
39
38
  /* ── a tiny append-only byte buffer for message assembly ─────────────── */
@@ -151,6 +150,37 @@ void scr_assert_ok(bool pass, ScrStr *message) {
151
150
 
152
151
  /* SameValue over doubles — Object.is, the comparison of strictEqual AND
153
152
  * deepStrictEqual for numbers (NaN equals NaN; +0 and -0 differ). */
153
+ /* ── deepStrictEqual over cyclic values ────────────────────────────────
154
+ * RECURSIVE record types permit reference cycles; Node's deep equality
155
+ * memoizes (value1, value2) pairs so equal cyclic structures compare
156
+ * true instead of recursing forever. The compiler-emitted per-type
157
+ * helpers over cycle-capable types wrap their walks in enter/leave: a
158
+ * PAIR already being compared answers equal (the coinductive step —
159
+ * Node's memo behavior exactly). The stack is global (comparisons never
160
+ * interleave; the emitted walks cannot throw mid-compare). */
161
+ static struct { const void *a, *b; } *g_deq_stack;
162
+ static size_t g_deq_len;
163
+ static size_t g_deq_cap;
164
+
165
+ bool scr_assert_deq_enter(const void *a, const void *b) {
166
+ for (size_t i = 0; i < g_deq_len; i++) {
167
+ if (g_deq_stack[i].a == a && g_deq_stack[i].b == b) return true;
168
+ }
169
+ if (g_deq_len == g_deq_cap) {
170
+ g_deq_cap = g_deq_cap ? g_deq_cap * 2 : 8;
171
+ g_deq_stack = realloc(g_deq_stack, g_deq_cap * sizeof *g_deq_stack);
172
+ if (!g_deq_stack) scr_assert_oom();
173
+ }
174
+ g_deq_stack[g_deq_len].a = a;
175
+ g_deq_stack[g_deq_len].b = b;
176
+ g_deq_len++;
177
+ return false;
178
+ }
179
+
180
+ void scr_assert_deq_leave(void) {
181
+ if (g_deq_len > 0) g_deq_len--;
182
+ }
183
+
154
184
  bool scr_assert_same_value_f64(double a, double b) {
155
185
  if (a == b) return a != 0.0 || signbit(a) == signbit(b);
156
186
  return isnan(a) && isnan(b);
package/src/scr_bytes.c CHANGED
@@ -17,8 +17,7 @@ long scr_bytes_live_count(void) { return scr_live_bytes; }
17
17
  #endif
18
18
 
19
19
  static void scr_bytes_oom(void) {
20
- fputs("scriptc: out of memory\n", stderr);
21
- abort();
20
+ scr_trap("scriptc: out of memory\n");
22
21
  }
23
22
 
24
23
  size_t scr_bytes_elem_size(ScrBytesElem elem) {
@@ -98,10 +97,8 @@ static size_t scr_bytes_check_index(const ScrBytes *b, double i) {
98
97
  if (!(i >= 0) || i != trunc(i) || i >= (double)b->len) {
99
98
  char buf[32];
100
99
  scr_f64_to_str(i, buf);
101
- fprintf(stderr,
102
- "scriptc: RangeError: typed array index %s out of bounds (length %zu)\n",
103
- buf, b->len);
104
- abort();
100
+ scr_trap_fmt("scriptc: RangeError: typed array index %s out of bounds (length %zu)\n",
101
+ buf, b->len);
105
102
  }
106
103
  return (size_t)i;
107
104
  }
@@ -12,8 +12,7 @@
12
12
  #include <string.h>
13
13
 
14
14
  static void scr_bytes_io_oom(void) {
15
- fputs("scriptc: out of memory\n", stderr);
16
- abort();
15
+ scr_trap("scriptc: out of memory\n");
17
16
  }
18
17
 
19
18
  /* ── fs (the Buffer forms of scr_lib.c's utf8 pair) ────────────────────── */
package/src/scr_closure.c CHANGED
@@ -146,8 +146,7 @@ void *scr_box_get_ref(ScrBox *b) {
146
146
  case SCR_BOX_OBJ:
147
147
  return b->obj_retain(p);
148
148
  default:
149
- fputs("scriptc: internal error: box_get_ref on a scalar box\n", stderr);
150
- abort();
149
+ scr_trap("scriptc: internal error: box_get_ref on a scalar box\n");
151
150
  }
152
151
  }
153
152
 
package/src/scr_console.c CHANGED
@@ -14,6 +14,7 @@
14
14
  static long scr_abandoned_fibers = 0;
15
15
  void scr_note_abandoned_fibers(long n) { scr_abandoned_fibers = n; }
16
16
 
17
+ #ifndef SCR_LIB
17
18
  #ifdef SCR_RC_AUDIT
18
19
  extern long scr_str_live_count(void); /* scr_string.c */
19
20
  extern long scr_arr_live_count(void); /* scr_array.c */
@@ -96,6 +97,10 @@ void scr_init(void) {
96
97
  #endif
97
98
  atexit(scr_collect_cycles_at_exit);
98
99
  }
100
+ #endif /* !SCR_LIB — a library artifact never touches host stdio modes/buffering and
101
+ * registers no atexit handlers: scr_init and its exit hooks (the
102
+ * audit's _Exit(99) included) are executable-lane machinery; the
103
+ * library session reset lives in scr_library.c. */
99
104
 
100
105
  /* ONE formatter for both console streams — console.error/warn print
101
106
  * byte-identically to console.log in Node (same inspect rendering), only
package/src/scr_cycle.c CHANGED
@@ -39,8 +39,7 @@
39
39
  #define SCR_RC(obj) (*(size_t *)(obj))
40
40
 
41
41
  static void scr_cyc_oom(void) {
42
- fputs("scriptc: out of memory\n", stderr);
43
- abort();
42
+ scr_trap("scriptc: out of memory\n");
44
43
  }
45
44
 
46
45
  void *scr_cyc_alloc(size_t size, ScrTraceFn trace, ScrCycFreeFn free_fn) {
package/src/scr_error.c CHANGED
@@ -14,8 +14,7 @@
14
14
  #include <stdlib.h>
15
15
 
16
16
  static void scr_error_oom(void) {
17
- fputs("scriptc: out of memory\n", stderr);
18
- abort();
17
+ scr_trap("scriptc: out of memory\n");
19
18
  }
20
19
 
21
20
  static bool scr_error_traced = false;
@@ -247,8 +246,7 @@ void scr_undef_global_read(ScrStr *name) {
247
246
  size_t len = name->len + sizeof suffix - 1;
248
247
  char *msg = malloc(len + 1);
249
248
  if (!msg) {
250
- fputs("scriptc: out of memory\n", stderr);
251
- abort();
249
+ scr_trap("scriptc: out of memory\n");
252
250
  }
253
251
  memcpy(msg, name->data, name->len);
254
252
  memcpy(msg + name->len, suffix, sizeof suffix);
@@ -54,8 +54,7 @@
54
54
  #endif
55
55
 
56
56
  static void scr_ee_oom(void) {
57
- fputs("scriptc: out of memory\n", stderr);
58
- abort();
57
+ scr_trap("scriptc: out of memory\n");
59
58
  }
60
59
 
61
60
  /* One registered listener. Shared between the live list and emit
@@ -13,6 +13,28 @@
13
13
  #include <stdio.h>
14
14
  #include <stdlib.h>
15
15
 
16
+ #ifndef SCR_LIB
17
+ /* The trap funnel's EXECUTABLE expansion: exactly the historical
18
+ * fputs/vfprintf-to-stderr + abort every trap site used to open-code — the
19
+ * default lane's bytes and behavior are unchanged by construction. Library
20
+ * builds (-DSCR_LIB) get the sink-routing definitions from scr_library.c
21
+ * instead; this pair compiles out there. Lives HERE (the failure-channel
22
+ * unit, present in every link including the runtime's own unit-test
23
+ * subsets) rather than in the console unit. */
24
+ _Noreturn void scr_trap(const char *msg) {
25
+ fputs(msg, stderr);
26
+ abort();
27
+ }
28
+
29
+ _Noreturn void scr_trap_fmt(const char *fmt, ...) {
30
+ va_list ap;
31
+ va_start(ap, fmt);
32
+ vfprintf(stderr, fmt, ap);
33
+ va_end(ap);
34
+ abort();
35
+ }
36
+ #endif /* !SCR_LIB */
37
+
16
38
  static ScrExcCell scr_main_exc; /* fiber zero (the main stack) */
17
39
  static ScrExcCell *scr_cur = &scr_main_exc;
18
40
 
@@ -60,8 +82,7 @@ void scr_exc_clear(void) { scr_exc_reset(); }
60
82
  ScrCaught *scr_exc_take(void) {
61
83
  ScrCaught *c = calloc(1, sizeof *c);
62
84
  if (!c) {
63
- fputs("scriptc: out of memory\n", stderr);
64
- abort();
85
+ scr_trap("scriptc: out of memory\n");
65
86
  }
66
87
  c->rc = 1;
67
88
  c->kind = scr_exc_kind;
@@ -154,6 +175,70 @@ ScrStr *scr_caught_to_string(const ScrCaught *c) {
154
175
  return scr_str_new("", 0);
155
176
  }
156
177
 
178
+ #ifdef SCR_LIB
179
+ /* An exception that escaped user code into a generated entry wrapper: the
180
+ * host contract is that declared entries do not throw, so an escape is a
181
+ * contract-shaped failure exactly like a trap. Render the same "Uncaught
182
+ * ..." first line the executable epilogue prints (scr_exc_print_uncaught's
183
+ * arms, buffer-writing), release the payload, and route the text through
184
+ * the trap funnel — one failure channel at the boundary. */
185
+ void scr_library_check_exc(void) {
186
+ if (!scr_exc_pending()) return;
187
+ static char buf[1024]; /* the message is copied out before the payload dies */
188
+ size_t n = 0;
189
+ const char prefix[] = "Uncaught ";
190
+ memcpy(buf, prefix, sizeof prefix - 1);
191
+ n = sizeof prefix - 1;
192
+ const size_t cap = sizeof buf - 2; /* room for '\n' + NUL */
193
+ switch (scr_exc_kind) {
194
+ case SCR_EXC_F64:
195
+ n += scr_f64_to_str(scr_exc_f64, buf + n);
196
+ break;
197
+ case SCR_EXC_BOOL: {
198
+ const char *b = scr_exc_bool ? "true" : "false";
199
+ size_t bl = strlen(b);
200
+ memcpy(buf + n, b, bl);
201
+ n += bl;
202
+ break;
203
+ }
204
+ case SCR_EXC_STR: {
205
+ const ScrStr *s = (const ScrStr *)scr_exc_payload;
206
+ size_t take = s->len > cap - n ? cap - n : s->len;
207
+ memcpy(buf + n, s->data, take);
208
+ n += take;
209
+ break;
210
+ }
211
+ case SCR_EXC_OBJ:
212
+ if (scr_error_is(scr_exc_payload)) {
213
+ ScrStr *s = scr_error_to_string((ScrError *)scr_exc_payload);
214
+ size_t take = s->len > cap - n ? cap - n : s->len;
215
+ memcpy(buf + n, s->data, take);
216
+ n += take;
217
+ scr_str_release(s);
218
+ break;
219
+ }
220
+ /* fall through: non-Error hierarchy objects render like other refs */
221
+ case SCR_EXC_REF: {
222
+ const char obj[] = "[object]";
223
+ memcpy(buf + n, obj, sizeof obj - 1);
224
+ n += sizeof obj - 1;
225
+ break;
226
+ }
227
+ case SCR_EXC_NONE:
228
+ case SCR_EXC_GENRET: { /* unreachable through the pending() gate */
229
+ const char none[] = "(no exception)";
230
+ memcpy(buf + n, none, sizeof none - 1);
231
+ n += sizeof none - 1;
232
+ break;
233
+ }
234
+ }
235
+ buf[n++] = '\n';
236
+ buf[n] = '\0';
237
+ scr_exc_reset(); /* the payload is released before the funnel poisons */
238
+ scr_trap(buf);
239
+ }
240
+ #endif /* SCR_LIB */
241
+
157
242
  void scr_throw_f64(double v) {
158
243
  scr_exc_reset();
159
244
  scr_exc_kind = SCR_EXC_F64;
package/src/scr_inspect.c CHANGED
@@ -31,8 +31,7 @@
31
31
  #include <string.h>
32
32
 
33
33
  static void insp_oom(void) {
34
- fputs("scriptc: out of memory\n", stderr);
35
- abort();
34
+ scr_trap("scriptc: out of memory\n");
36
35
  }
37
36
 
38
37
  /* ── a tiny append-only byte buffer ──────────────────────────────────── */
@@ -246,10 +245,13 @@ static double g_cur_depth; /* ctx.currentDepth (last-entered composite) */
246
245
 
247
246
  static InspFrame *insp_top(void) { return &g_frames[g_nframes - 1]; }
248
247
 
248
+ static void insp_circ_reset(void); /* circular-reference state, below */
249
+
249
250
  /* Begin a non-empty composite: formatRaw's `recurseTimes += 1;
250
251
  * ctx.currentDepth = recurseTimes` plus the uniform +2 the children
251
252
  * format under. `recurse` is the CHILDREN's recursion depth. */
252
253
  void scr_insp_begin(double recurse) {
254
+ if (recurse == 1) insp_circ_reset(); /* a fresh top-level value: Node's per-inspect ctx */
253
255
  if (g_nframes == g_frames_cap) {
254
256
  g_frames_cap = g_frames_cap ? g_frames_cap * 2 : 8;
255
257
  g_frames = realloc(g_frames, g_frames_cap * sizeof(InspFrame));
@@ -278,6 +280,85 @@ void scr_insp_entry(ScrStr *s, bool is_num) {
278
280
  if (!is_num) f->all_num = false;
279
281
  }
280
282
 
283
+ /* ── circular references (Node's <ref *N> / [Circular *N]) ────────────
284
+ * Recursive record/class types permit runtime reference cycles, and Node
285
+ * renders them with formatValue's seen/circular machinery: a value
286
+ * already ON the current traversal stack renders as `[Circular *N]` (N
287
+ * assigned at first detection, in discovery order), and every rendering
288
+ * of a so-numbered value gets the `<ref *N> ` prefix at its close. The
289
+ * compiler-emitted helpers over CYCLE-CAPABLE composites drive exactly
290
+ * that protocol: circ_check first (before the empty-literal and depth
291
+ * answers — Node's order: a circular value beyond the depth budget still
292
+ * says Circular), seen_push after begin, ref_wrap around end's result.
293
+ * The circular MAP persists for one whole top-level inspect — begin(1)
294
+ * is the per-value reset (a root composite's first frame). */
295
+ typedef struct {
296
+ const void *ptr;
297
+ int id; /* circular id (0 while only on the stack) */
298
+ } InspSeenEnt;
299
+
300
+ static InspSeenEnt *g_seen;
301
+ static size_t g_nseen;
302
+ static size_t g_seen_cap;
303
+ /* Detection-numbered circular targets (persist across the call). */
304
+ static const void *g_circ[64];
305
+ static int g_ncirc;
306
+
307
+ static void insp_circ_reset(void) {
308
+ g_nseen = 0;
309
+ g_ncirc = 0;
310
+ }
311
+
312
+ static int insp_circ_id(const void *v) {
313
+ for (int i = 0; i < g_ncirc; i++) {
314
+ if (g_circ[i] == v) return i + 1;
315
+ }
316
+ return 0;
317
+ }
318
+
319
+ double scr_insp_circ_check(const void *v) {
320
+ for (size_t i = 0; i < g_nseen; i++) {
321
+ if (g_seen[i].ptr != v) continue;
322
+ int id = insp_circ_id(v);
323
+ if (id == 0 && g_ncirc < (int)(sizeof g_circ / sizeof *g_circ)) {
324
+ g_circ[g_ncirc++] = v;
325
+ id = g_ncirc;
326
+ }
327
+ return (double)id;
328
+ }
329
+ return 0;
330
+ }
331
+
332
+ void scr_insp_seen_push(const void *v) {
333
+ if (g_nseen == g_seen_cap) {
334
+ g_seen_cap = g_seen_cap ? g_seen_cap * 2 : 8;
335
+ g_seen = realloc(g_seen, g_seen_cap * sizeof(InspSeenEnt));
336
+ if (!g_seen) insp_oom();
337
+ }
338
+ g_seen[g_nseen].ptr = v;
339
+ g_seen[g_nseen].id = 0;
340
+ g_nseen++;
341
+ }
342
+
343
+ ScrStr *scr_insp_circular(double id) {
344
+ char buf[32];
345
+ int n = snprintf(buf, sizeof buf, "[Circular *%.0f]", id);
346
+ return scr_str_new(buf, (size_t)n);
347
+ }
348
+
349
+ ScrStr *scr_insp_ref_wrap(const void *v, ScrStr *s) {
350
+ if (g_nseen > 0) g_nseen--;
351
+ int id = insp_circ_id(v);
352
+ if (id == 0) return scr_str_retain(s);
353
+ char buf[32];
354
+ int n = snprintf(buf, sizeof buf, "<ref *%d> ", id);
355
+ ScrStr *out = scr_str_alloc_raw(s->len + (size_t)n, s->len + (size_t)n);
356
+ memcpy(out->data, buf, (size_t)n);
357
+ memcpy(out->data + n, s->data, s->len);
358
+ out->data[out->len] = '\0';
359
+ return out;
360
+ }
361
+
281
362
  /* remainingText: the "... N more items" tail entry. */
282
363
  ScrStr *scr_insp_more_items(double remaining) {
283
364
  char buf[64];