@scriptc/runtime 0.0.3 → 0.0.5

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_runtime.h CHANGED
@@ -39,6 +39,81 @@ char *strcasestr(const char *hay, const char *needle);
39
39
  * flush-at-exit, RC audit registration (when built with -DSCR_RC_AUDIT). */
40
40
  void scr_init(void);
41
41
 
42
+ /* ── the trap funnel (scr_console.c; scr_library.c under -DSCR_LIB) ──────
43
+ * Every unrecoverable runtime trap — OOM, semantic range traps, internal-
44
+ * invariant failures — funnels through this pair instead of open-coded
45
+ * fputs/fprintf + abort. Executable builds expand to exactly the historical
46
+ * behavior (the message's bytes on stderr, then abort — the default lane
47
+ * must not change by a byte). Library builds (-DSCR_LIB) route the message to
48
+ * the host-registered panic sink and abort only as the last resort: before
49
+ * registration, or if the sink returns (the ruled host-contract violation —
50
+ * a conforming sink longjmps to a host frame BELOW the entry, never back
51
+ * into library frames). Messages keep their trailing newline in both lanes so
52
+ * the funnel is a pure indirection over identical bytes. */
53
+ _Noreturn void scr_trap(const char *msg);
54
+ _Noreturn void scr_trap_fmt(const char *fmt, ...);
55
+
56
+ /* ── library mode (scr_library.c, linked only into library artifacts) ─────
57
+ * A library artifact has no main, no event loop, no signal handlers, no
58
+ * atexit registrations, and never touches host stdio modes or buffering:
59
+ * initialization runs inside the profile-named init entry (re-runnable
60
+ * deterministically), traps route to the sink above, and buffer-class
61
+ * results live in a library-owned result arena. Everything here compiles only
62
+ * under -DSCR_LIB; executable builds never contain it. */
63
+ #ifdef SCR_LIB
64
+ typedef struct ScrStr ScrStr; /* full definitions below (C11 repeat) */
65
+ typedef struct ScrBytes ScrBytes;
66
+ /* The host's panic sink: msg is UTF-8, valid only for the duration of the
67
+ * call; address is the trap site's return address (0 when the toolchain
68
+ * cannot supply one); ctx is the registration's opaque pointer. The sink
69
+ * must not call back into any library entry and must not unwind or longjmp
70
+ * back into library frames. */
71
+ typedef void (*ScrLibSinkFn)(void *ctx, const uint8_t *msg, size_t msg_len,
72
+ uint64_t address);
73
+ void scr_library_set_sink(ScrLibSinkFn fn, void *ctx); /* latest wins */
74
+
75
+ /* Entry prologue: aborts deterministically when the library is poisoned (a
76
+ * trap already fired — no profile entry may run again; recovery is process
77
+ * restart). reset_arena additionally drops the result arena (the
78
+ * auto-reset posture, and the reset/collect entries' shared body). */
79
+ void scr_library_entry(bool reset_arena);
80
+ void scr_library_arena_reset(void);
81
+ /* The mode-provided collect entry's body: arena reset + a full cycle
82
+ * collection (snapshot-invariant by construction — collection frees only
83
+ * unreachable cycles). */
84
+ void scr_library_collect(void);
85
+
86
+ /* Full session reset, called by the generated init entry AFTER the program
87
+ * TU released and zeroed its globals: pending-exception clear, arena
88
+ * reset, the reset registry's drains (the units' repointed atexit halves),
89
+ * the library's interned process values, a cycle collection, and — under
90
+ * SCR_RC_AUDIT — the zero-live-heap assertion (a failure is a trap through
91
+ * the sink, never _Exit). */
92
+ void scr_library_reset(void);
93
+ /* Where a unit would atexit() a lazy teardown, library builds register it
94
+ * here instead (called on every scr_library_reset, registered once). */
95
+ void scr_library_register_reset(void (*fn)(void));
96
+ /* An escaped exception at an entry boundary: renders the same "Uncaught
97
+ * ..." text the executable epilogue prints, releases the payload, and
98
+ * routes the text through the trap funnel. No-op when nothing is pending.
99
+ * Defined in scr_exception.c (it owns the cell). */
100
+ void scr_library_check_exc(void);
101
+
102
+ /* Marshalling helpers the generated wrappers call (both emissions share
103
+ * these bodies, which is how the two lanes stay identical by
104
+ * construction). Inbound is borrowed-and-copied; outbound values MOVE into
105
+ * the result arena and stay valid until the next arena reset. String
106
+ * results are NUL-terminated after *out_len bytes (ScrStr's layout). */
107
+ 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 */
109
+ void scr_library_str_out(ScrStr *s, const uint8_t **out, size_t *out_len);
110
+ void scr_library_bytes_out(ScrBytes *b, const uint8_t **out, size_t *out_len);
111
+
112
+ #define scr_atexit(fn) scr_library_register_reset(fn)
113
+ #else
114
+ #define scr_atexit(fn) atexit(fn)
115
+ #endif /* SCR_LIB */
116
+
42
117
  /* ── cycle collection (scr_cycle.c) ───────────────────────────────────
43
118
  * Reference counting alone cannot free cycles, so every object that can
44
119
  * participate in one — capture boxes, heap closures, unions, promises, and
@@ -484,6 +559,64 @@ ScrStr *scr_str_encode_uri_component(ScrStr *s);
484
559
  * malformed") and returns NULL. Borrows s; result +1. */
485
560
  ScrStr *scr_str_decode_uri_component(ScrStr *s);
486
561
 
562
+ /* The non-throwing core of decodeURIComponent: NULL on malformed input
563
+ * instead of the URIError — the try/catch shape node:querystring's
564
+ * unescape wraps around decodeURIComponent (scr_qs.c's strict pass). */
565
+ ScrStr *scr_str_decode_uri_component_try(ScrStr *s);
566
+
567
+ /* ── node:querystring (scr_qs.c — LINK-GATED by moduleUsesQs; the
568
+ * scr_url_params.c precedent: pure data transforms, no loop hooks).
569
+ * querystring.escape needs no entry here: Node's qsEscape encodes exactly
570
+ * the component unreserved set, so the frontend lowers it to
571
+ * scr_str_encode_uri_component (always linked; a program using only
572
+ * escape never pulls this unit). */
573
+
574
+ /* querystring.unescape — Node's qsUnescape: strict decodeURIComponent
575
+ * first, and on failure the lenient legacy unescapeBuffer(s).toString()
576
+ * (valid %XX escapes decode to their byte, malformed escapes copy
577
+ * literally, every non-escape UTF-16 CODE UNIT truncates to its low byte
578
+ * — Node's Buffer element write — and the byte buffer decodes as UTF-8
579
+ * with U+FFFD replacement per maximal subpart, Buffer.toString's rule).
580
+ * Borrows s; result +1; never throws. */
581
+ ScrStr *scr_qs_unescape(const ScrStr *s);
582
+
583
+ /* querystring.parse — Node v24's scan state machine, byte-wise over the
584
+ * UTF-8 storage (equivalent to the code-unit scan for well-formed input:
585
+ * the machine compares sep/eq sequences positionally and treats '%'/'+'/
586
+ * hex as ASCII, and multibyte alignment is preserved). Decoded pairs land
587
+ * in `out` — the PURE-index-signature Dict record's overflow map (the
588
+ * emitters pass rec->sc_ovf) — grouped like Node's addKeyVal: a first
589
+ * value stores the `str_tag` union arm, a repeat REPLACES it with a
590
+ * two-element string[] wrapped in `arr_tag`, later repeats push. sep/eq
591
+ * fall back to "&"/"=" when empty (Node's falsy rule; the frontend
592
+ * completes omitted/null arguments to the defaults). max_keys is Node's
593
+ * rule exactly: > 0 caps the PAIR count (empty skipped segments count,
594
+ * like Node's --pairs), anything else (0, negatives, NaN) is unlimited;
595
+ * the frontend completes the omitted option to 1000. Only the DEFAULT
596
+ * decoder runs (custom decodeURIComponent options fence at compile time),
597
+ * so '+' means ' ' and segments decode with scr_qs_unescape's semantics
598
+ * only when they carry a full valid %XX triple (Node's encodeCheck).
599
+ * Borrows everything; never throws. */
600
+ typedef struct ScrMap ScrMap; /* full definition below (C11 repeat) */
601
+ void scr_qs_parse_into(ScrMap *out, const ScrStr *qs, const ScrStr *sep,
602
+ const ScrStr *eq, double max_keys, uint32_t str_tag,
603
+ uint32_t arr_tag);
604
+
605
+ /* querystring.stringify — Node's stringify over a borrowed DOM value (the
606
+ * frontend dynFroms the typed record; JS-world dyn values pass straight
607
+ * through). Non-object DOMs answer "" like Node; object keys iterate in
608
+ * JS own-key order (scr_dyn_obj_keys — array-index keys ascending first,
609
+ * then insertion order, Node's ObjectKeys). Values serialize per Node's
610
+ * encodeStringified: strings escape, finite numbers render shortest-
611
+ * roundtrip then escape ('1e+21' → '1e%2B21'), booleans are bare
612
+ * true/false, arrays expand to repeated keys (empty arrays emit NOTHING,
613
+ * key included), and everything else (null, undefined, nested objects/
614
+ * arrays, functions) is the empty value — key and eq still emitted.
615
+ * sep/eq fall back to "&"/"=" when empty (Node's `sep ||= '&'`). Result
616
+ * +1; never throws. */
617
+ ScrStr *scr_qs_stringify(const struct ScrDyn *obj, const ScrStr *sep,
618
+ const ScrStr *eq);
619
+
487
620
  /* encodeURI — the same ECMA-262 Encode() with the reserved set and '#'
488
621
  * kept unescaped (scr_encode_uri_impl's keep_reserved arm). Total by the
489
622
  * well-formed-UTF-8 invariant. Borrows s; result +1. */
@@ -1675,6 +1808,9 @@ double scr_process_umask(double mask);
1675
1808
  * has no answer). scr_active_resources (scr_async.c) walks the loop's
1676
1809
  * own bookkeeping — 'Timeout'/'Immediate' strings, +1. */
1677
1810
  double scr_process_uptime(void);
1811
+ /* perf_hooks performance.now(): fractional ms since process start (the
1812
+ * uptime anchor — Node's timeOrigin for a compiled program). */
1813
+ double scr_perf_now(void);
1678
1814
  double scr_cpu_user(void);
1679
1815
  double scr_cpu_system(void);
1680
1816
  double scr_cpu_user_diff(double prev);
package/src/scr_string.c CHANGED
@@ -15,8 +15,7 @@ long scr_str_live_count(void) { return scr_live_strings; }
15
15
  #endif
16
16
 
17
17
  static void scr_oom(void) {
18
- fputs("scriptc: out of memory\n", stderr);
19
- abort();
18
+ scr_trap("scriptc: out of memory\n");
20
19
  }
21
20
 
22
21
  /* ── UTF-16 index cache ───────────────────────────────────────────────
@@ -519,8 +518,7 @@ ScrStr *scr_str_slice(ScrStr *s, double start, double end) {
519
518
  ScrStr *scr_str_repeat(ScrStr *s, double count) {
520
519
  double n = scr_to_integer_or_infinity(count);
521
520
  if (n < 0 || (isinf(n) && n > 0)) {
522
- fputs("scriptc: RangeError: Invalid count value\n", stderr);
523
- abort();
521
+ scr_trap("scriptc: RangeError: Invalid count value\n");
524
522
  }
525
523
  if (n == 0 || s->len == 0) return scr_str_empty();
526
524
  /* n is a finite non-negative integer here. Reject sizes malloc could not
@@ -1212,7 +1210,12 @@ static int scr_uri_hex_byte(const char *p, size_t rem) {
1212
1210
  return (hi << 4) | lo;
1213
1211
  }
1214
1212
 
1215
- ScrStr *scr_str_decode_uri_component(ScrStr *s) {
1213
+ /* The non-throwing core of decodeURIComponent: NULL on malformed input
1214
+ * (bad hex, invalid UTF-8 octets) instead of the URIError — the
1215
+ * querystring unit's unescape needs exactly the try/catch shape Node's
1216
+ * qsUnescape wraps around decodeURIComponent (scr_qs.c), and the throwing
1217
+ * entry point below stays byte-identical by rethrowing over NULL. */
1218
+ ScrStr *scr_str_decode_uri_component_try(ScrStr *s) {
1216
1219
  /* Decoding only ever shrinks (%XX → 1 byte), so len is a safe cap. */
1217
1220
  ScrStr *out = scr_str_alloc_raw(0, s->len);
1218
1221
  size_t w = 0;
@@ -1258,7 +1261,15 @@ ScrStr *scr_str_decode_uri_component(ScrStr *s) {
1258
1261
  return out;
1259
1262
  malformed:
1260
1263
  scr_str_release(out);
1261
- scr_uri_malformed();
1262
1264
  return NULL;
1263
1265
  }
1264
1266
 
1267
+ ScrStr *scr_str_decode_uri_component(ScrStr *s) {
1268
+ ScrStr *out = scr_str_decode_uri_component_try(s);
1269
+ if (!out) {
1270
+ scr_uri_malformed();
1271
+ return NULL;
1272
+ }
1273
+ return out;
1274
+ }
1275
+
package/src/scr_symbol.c CHANGED
@@ -44,8 +44,7 @@ void scr_sym_release_v(void *p) { scr_sym_release(p); }
44
44
  ScrSym *scr_sym_new(ScrStr *desc) {
45
45
  ScrSym *s = malloc(sizeof(ScrSym));
46
46
  if (!s) {
47
- fputs("scriptc: out of memory\n", stderr);
48
- abort();
47
+ scr_trap("scriptc: out of memory\n");
49
48
  }
50
49
  s->rc = 1;
51
50
  s->desc = desc ? scr_str_retain(desc) : NULL;
@@ -75,7 +74,7 @@ ScrSym *scr_sym_for(ScrStr *key) {
75
74
  /* First request for this key: a fresh symbol whose description IS the
76
75
  * key (the spec's Symbol.for behavior), chained into the registry with
77
76
  * the registry's own reference. */
78
- if (!g_sym_registry) atexit(scr_sym_registry_cleanup);
77
+ if (!g_sym_registry) scr_atexit(scr_sym_registry_cleanup);
79
78
  ScrSym *s = scr_sym_new(key);
80
79
  s->reg_key = scr_str_retain(key);
81
80
  s->reg_next = g_sym_registry;
package/src/scr_url.c CHANGED
@@ -69,8 +69,7 @@ static void ub_init(UrlBuf *b) {
69
69
  b->len = 0;
70
70
  b->data = malloc(b->cap);
71
71
  if (!b->data) {
72
- fputs("scriptc: out of memory\n", stderr);
73
- abort();
72
+ scr_trap("scriptc: out of memory\n");
74
73
  }
75
74
  }
76
75
 
@@ -79,8 +78,7 @@ static void ub_append(UrlBuf *b, const char *bytes, size_t n) {
79
78
  while (b->len + n > b->cap) b->cap *= 2;
80
79
  char *grown = realloc(b->data, b->cap);
81
80
  if (!grown) {
82
- fputs("scriptc: out of memory\n", stderr);
83
- abort();
81
+ scr_trap("scriptc: out of memory\n");
84
82
  }
85
83
  b->data = grown;
86
84
  }
@@ -182,8 +180,7 @@ static ScrStr *parse_rooted_path(const char *raw, size_t len, bool special) {
182
180
  size_t seg_starts_cap = 8, seg_count = 0;
183
181
  size_t *seg_starts = malloc(seg_starts_cap * sizeof(size_t));
184
182
  if (!seg_starts) {
185
- fputs("scriptc: out of memory\n", stderr);
186
- abort();
183
+ scr_trap("scriptc: out of memory\n");
187
184
  }
188
185
  size_t i = 0;
189
186
  /* One leading separator opens the first segment (rooted). */
@@ -206,7 +203,7 @@ static ScrStr *parse_rooted_path(const char *raw, size_t len, bool special) {
206
203
  if (seg_count == seg_starts_cap) {
207
204
  seg_starts_cap *= 2;
208
205
  seg_starts = realloc(seg_starts, seg_starts_cap * sizeof(size_t));
209
- if (!seg_starts) abort();
206
+ if (!seg_starts) scr_trap("scriptc: out of memory\n");
210
207
  }
211
208
  seg_starts[seg_count++] = out.len;
212
209
  }
@@ -215,7 +212,7 @@ static ScrStr *parse_rooted_path(const char *raw, size_t len, bool special) {
215
212
  if (seg_count == seg_starts_cap) {
216
213
  seg_starts_cap *= 2;
217
214
  seg_starts = realloc(seg_starts, seg_starts_cap * sizeof(size_t));
218
- if (!seg_starts) abort();
215
+ if (!seg_starts) scr_trap("scriptc: out of memory\n");
219
216
  }
220
217
  seg_starts[seg_count++] = out.len;
221
218
  }
@@ -224,8 +221,7 @@ static ScrStr *parse_rooted_path(const char *raw, size_t len, bool special) {
224
221
  seg_starts_cap *= 2;
225
222
  seg_starts = realloc(seg_starts, seg_starts_cap * sizeof(size_t));
226
223
  if (!seg_starts) {
227
- fputs("scriptc: out of memory\n", stderr);
228
- abort();
224
+ scr_trap("scriptc: out of memory\n");
229
225
  }
230
226
  }
231
227
  seg_starts[seg_count++] = out.len;
@@ -355,8 +351,7 @@ ScrUrl *scr_url_new(ScrStr *input) {
355
351
  while (e > b && (unsigned char)raw[e - 1] <= 0x20) e--;
356
352
  char *buf = malloc(e - b + 1);
357
353
  if (!buf) {
358
- fputs("scriptc: out of memory\n", stderr);
359
- abort();
354
+ scr_trap("scriptc: out of memory\n");
360
355
  }
361
356
  size_t len = 0;
362
357
  for (size_t i = b; i < e; i++) {
@@ -526,8 +521,7 @@ ScrUrl *scr_url_new(ScrStr *input) {
526
521
 
527
522
  ScrUrl *u = malloc(sizeof(ScrUrl));
528
523
  if (!u) {
529
- fputs("scriptc: out of memory\n", stderr);
530
- abort();
524
+ scr_trap("scriptc: out of memory\n");
531
525
  }
532
526
  u->rc = 1;
533
527
  u->scheme = scheme_str;
@@ -738,8 +732,7 @@ ScrStr *scr_url_str_to_path(ScrStr *input) {
738
732
  static ScrUrl *scr_url_new_file(ScrStr *host, ScrStr *encoded_path) {
739
733
  ScrUrl *u = malloc(sizeof(ScrUrl));
740
734
  if (!u) {
741
- fputs("scriptc: out of memory\n", stderr);
742
- abort();
735
+ scr_trap("scriptc: out of memory\n");
743
736
  }
744
737
  u->rc = 1;
745
738
  u->scheme = scr_str_new("file", 4);
@@ -26,8 +26,7 @@ static void ub_init(UrlBuf *b) {
26
26
  b->len = 0;
27
27
  b->data = malloc(b->cap);
28
28
  if (!b->data) {
29
- fputs("scriptc: out of memory\n", stderr);
30
- abort();
29
+ scr_trap("scriptc: out of memory\n");
31
30
  }
32
31
  }
33
32
 
@@ -36,8 +35,7 @@ static void ub_append(UrlBuf *b, const char *bytes, size_t n) {
36
35
  while (b->len + n > b->cap) b->cap *= 2;
37
36
  char *grown = realloc(b->data, b->cap);
38
37
  if (!grown) {
39
- fputs("scriptc: out of memory\n", stderr);
40
- abort();
38
+ scr_trap("scriptc: out of memory\n");
41
39
  }
42
40
  b->data = grown;
43
41
  }
@@ -94,8 +92,7 @@ struct ScrSearchParams {
94
92
  static ScrSearchParams *sp_alloc(void) {
95
93
  ScrSearchParams *sp = malloc(sizeof(ScrSearchParams));
96
94
  if (!sp) {
97
- fputs("scriptc: out of memory\n", stderr);
98
- abort();
95
+ scr_trap("scriptc: out of memory\n");
99
96
  }
100
97
  sp->rc = 1;
101
98
  sp->len = 0;
@@ -103,8 +100,7 @@ static ScrSearchParams *sp_alloc(void) {
103
100
  sp->names = malloc(sp->cap * sizeof(ScrStr *));
104
101
  sp->vals = malloc(sp->cap * sizeof(ScrStr *));
105
102
  if (!sp->names || !sp->vals) {
106
- fputs("scriptc: out of memory\n", stderr);
107
- abort();
103
+ scr_trap("scriptc: out of memory\n");
108
104
  }
109
105
  sp->owner = NULL;
110
106
  return sp;
@@ -142,8 +138,7 @@ static void sp_push(ScrSearchParams *sp, ScrStr *name, ScrStr *value) {
142
138
  ScrStr **gn = realloc(sp->names, sp->cap * sizeof(ScrStr *));
143
139
  ScrStr **gv = realloc(sp->vals, sp->cap * sizeof(ScrStr *));
144
140
  if (!gn || !gv) {
145
- fputs("scriptc: out of memory\n", stderr);
146
- abort();
141
+ scr_trap("scriptc: out of memory\n");
147
142
  }
148
143
  sp->names = gn;
149
144
  sp->vals = gv;
@@ -161,8 +156,7 @@ static ScrStr *sp_scrub_utf8(const char *in_c, size_t n) {
161
156
  const unsigned char *in = (const unsigned char *)in_c;
162
157
  char *out = malloc(n * 3 + 1);
163
158
  if (!out) {
164
- fputs("scriptc: out of memory\n", stderr);
165
- abort();
159
+ scr_trap("scriptc: out of memory\n");
166
160
  }
167
161
  size_t o = 0;
168
162
  uint32_t cp = 0;
package/src/scr_zlib.c CHANGED
@@ -18,8 +18,7 @@
18
18
  #include <zlib.h>
19
19
 
20
20
  static void scr_zlib_oom(void) {
21
- fputs("scriptc: out of memory\n", stderr);
22
- abort();
21
+ scr_trap("scriptc: out of memory\n");
23
22
  }
24
23
 
25
24
  ScrBytes *scr_zlib_deflate(const ScrBytes *data) {
@@ -3155,7 +3155,7 @@ int mbedtls_ecp_gen_keypair(mbedtls_ecp_group *grp,
3155
3155
  }
3156
3156
 
3157
3157
  /*
3158
- * Generate a keypair, prettier wrapper
3158
+ * Generate a keypair, the formatter wrapper
3159
3159
  */
3160
3160
  int mbedtls_ecp_gen_key(mbedtls_ecp_group_id grp_id, mbedtls_ecp_keypair *key,
3161
3161
  int (*f_rng)(void *, unsigned char *, size_t), void *p_rng)