@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_qs.c ADDED
@@ -0,0 +1,496 @@
1
+ /* node:querystring (scr_runtime.h has the API contract): Node v24's
2
+ * legacy query-string codec — NOT URLSearchParams (the escaping and '+'
3
+ * rules differ; that surface lives in scr_url_params.c). LINK-GATED:
4
+ * compiled into the binary only when the IR uses the qs surface
5
+ * (moduleUsesQs — the scr_url_params gating precedent), so qs-free
6
+ * programs pay zero bytes. querystring.escape never reaches this unit at
7
+ * all: Node's qsEscape encodes exactly the component unreserved set, so
8
+ * the frontend lowers it to the always-linked
9
+ * scr_str_encode_uri_component.
10
+ *
11
+ * Everything here is a quirk-faithful port of lib/querystring.js (Node is
12
+ * the oracle): parse's interleaved sep/eq scan with its naive
13
+ * partial-match resets and the encodeCheck fast path, unescapeBuffer's
14
+ * UTF-16-code-unit byte truncation, and stringify's encodeStringified
15
+ * value rules. The scans run byte-wise over the runtime's UTF-8 storage —
16
+ * equivalent to Node's code-unit scans for well-formed input, because the
17
+ * machine only assigns meaning to ASCII ('%', '+', hex, and the sep/eq
18
+ * sequences positionally) and UTF-8 multibyte alignment mirrors UTF-16
19
+ * unit alignment. */
20
+ #include "scr_runtime.h"
21
+
22
+ #include <math.h>
23
+ #include <stdio.h>
24
+ #include <stdlib.h>
25
+ #include <string.h>
26
+
27
+ /* ── a tiny growable byte buffer (scr_url_params.c's, redeclared) ────── */
28
+
29
+ typedef struct {
30
+ char *data;
31
+ size_t len;
32
+ size_t cap;
33
+ } QsBuf;
34
+
35
+ static void qb_init(QsBuf *b) {
36
+ b->cap = 64;
37
+ b->len = 0;
38
+ b->data = malloc(b->cap);
39
+ if (!b->data) {
40
+ fputs("scriptc: out of memory\n", stderr);
41
+ abort();
42
+ }
43
+ }
44
+
45
+ static void qb_append(QsBuf *b, const char *bytes, size_t n) {
46
+ if (b->len + n > b->cap) {
47
+ while (b->len + n > b->cap) b->cap *= 2;
48
+ char *grown = realloc(b->data, b->cap);
49
+ if (!grown) {
50
+ fputs("scriptc: out of memory\n", stderr);
51
+ abort();
52
+ }
53
+ b->data = grown;
54
+ }
55
+ memcpy(b->data + b->len, bytes, n);
56
+ b->len += n;
57
+ }
58
+
59
+ static void qb_push(QsBuf *b, char c) { qb_append(b, &c, 1); }
60
+
61
+ static void qb_append_str(QsBuf *b, const ScrStr *s) {
62
+ qb_append(b, s->data, s->len);
63
+ }
64
+
65
+ static ScrStr *qb_take(QsBuf *b) {
66
+ ScrStr *s = scr_str_new(b->data, b->len);
67
+ free(b->data);
68
+ return s;
69
+ }
70
+
71
+ /* ── shared scan helpers ─────────────────────────────────────────────── */
72
+
73
+ static int qs_unhex(unsigned c) {
74
+ if (c >= '0' && c <= '9') return (int)(c - '0');
75
+ if (c >= 'A' && c <= 'F') return (int)(c - 'A' + 10);
76
+ if (c >= 'a' && c <= 'f') return (int)(c - 'a' + 10);
77
+ return -1;
78
+ }
79
+
80
+ /* One UTF-8 code point at p (well-formed by the runtime invariant);
81
+ * *adv gets the byte length. */
82
+ static uint32_t qs_utf8_decode(const char *p, size_t *adv) {
83
+ unsigned char b0 = (unsigned char)p[0];
84
+ if (b0 < 0x80) {
85
+ *adv = 1;
86
+ return b0;
87
+ }
88
+ if (b0 < 0xE0) {
89
+ *adv = 2;
90
+ return (uint32_t)((b0 & 0x1Fu) << 6) | ((unsigned char)p[1] & 0x3Fu);
91
+ }
92
+ if (b0 < 0xF0) {
93
+ *adv = 3;
94
+ return (uint32_t)((b0 & 0x0Fu) << 12) |
95
+ (uint32_t)(((unsigned char)p[1] & 0x3Fu) << 6) |
96
+ ((unsigned char)p[2] & 0x3Fu);
97
+ }
98
+ *adv = 4;
99
+ return (uint32_t)((b0 & 0x07u) << 18) |
100
+ (uint32_t)(((unsigned char)p[1] & 0x3Fu) << 12) |
101
+ (uint32_t)(((unsigned char)p[2] & 0x3Fu) << 6) |
102
+ ((unsigned char)p[3] & 0x3Fu);
103
+ }
104
+
105
+ static void qs_utf8_encode(QsBuf *b, uint32_t cp) {
106
+ char tmp[4];
107
+ if (cp < 0x80) {
108
+ tmp[0] = (char)cp;
109
+ qb_append(b, tmp, 1);
110
+ } else if (cp < 0x800) {
111
+ tmp[0] = (char)(0xC0 | (cp >> 6));
112
+ tmp[1] = (char)(0x80 | (cp & 0x3F));
113
+ qb_append(b, tmp, 2);
114
+ } else if (cp < 0x10000) {
115
+ tmp[0] = (char)(0xE0 | (cp >> 12));
116
+ tmp[1] = (char)(0x80 | ((cp >> 6) & 0x3F));
117
+ tmp[2] = (char)(0x80 | (cp & 0x3F));
118
+ qb_append(b, tmp, 3);
119
+ } else {
120
+ tmp[0] = (char)(0xF0 | (cp >> 18));
121
+ tmp[1] = (char)(0x80 | ((cp >> 12) & 0x3F));
122
+ tmp[2] = (char)(0x80 | ((cp >> 6) & 0x3F));
123
+ tmp[3] = (char)(0x80 | (cp & 0x3F));
124
+ qb_append(b, tmp, 4);
125
+ }
126
+ }
127
+
128
+ /* ── unescape (Node's qsUnescape) ────────────────────────────────────── */
129
+
130
+ /* The string's UTF-16 code units (astral chars split into surrogate
131
+ * pairs) — unescapeBuffer's scan is defined over these, byte-truncation
132
+ * quirk included, so the fallback builds them first. Caller frees. */
133
+ static uint16_t *qs_utf16_units(const ScrStr *s, size_t *out_n) {
134
+ /* One unit per byte is a safe cap (ASCII 1:1; multibyte shrinks). */
135
+ uint16_t *units = malloc(sizeof(uint16_t) * (s->len ? s->len : 1));
136
+ if (!units) {
137
+ fputs("scriptc: out of memory\n", stderr);
138
+ abort();
139
+ }
140
+ size_t n = 0, i = 0;
141
+ while (i < s->len) {
142
+ size_t adv;
143
+ uint32_t cp = qs_utf8_decode(s->data + i, &adv);
144
+ i += adv;
145
+ if (cp >= 0x10000) {
146
+ cp -= 0x10000;
147
+ units[n++] = (uint16_t)(0xD800 + (cp >> 10));
148
+ units[n++] = (uint16_t)(0xDC00 + (cp & 0x3FF));
149
+ } else {
150
+ units[n++] = (uint16_t)cp;
151
+ }
152
+ }
153
+ *out_n = n;
154
+ return units;
155
+ }
156
+
157
+ /* WHATWG UTF-8 decode with replacement (maximal subpart) — the semantics
158
+ * of Buffer.prototype.toString('utf8') the fallback path ends with. */
159
+ static ScrStr *qs_utf8_replace_decode(const unsigned char *p, size_t n) {
160
+ QsBuf out;
161
+ qb_init(&out);
162
+ size_t i = 0;
163
+ while (i < n) {
164
+ unsigned char b0 = p[i];
165
+ if (b0 < 0x80) {
166
+ qb_push(&out, (char)b0);
167
+ i++;
168
+ continue;
169
+ }
170
+ int need;
171
+ unsigned lo = 0x80, hi = 0xBF;
172
+ if (b0 >= 0xC2 && b0 <= 0xDF) need = 1;
173
+ else if (b0 == 0xE0) { need = 2; lo = 0xA0; }
174
+ else if (b0 == 0xED) { need = 2; hi = 0x9F; }
175
+ else if (b0 >= 0xE1 && b0 <= 0xEF) need = 2;
176
+ else if (b0 == 0xF0) { need = 3; lo = 0x90; }
177
+ else if (b0 == 0xF4) { need = 3; hi = 0x8F; }
178
+ else if (b0 >= 0xF1 && b0 <= 0xF3) need = 3;
179
+ else { /* 0x80..0xC1, 0xF5..0xFF: never a leading byte */
180
+ qs_utf8_encode(&out, 0xFFFD);
181
+ i++;
182
+ continue;
183
+ }
184
+ /* Consume as many valid continuations as exist (maximal subpart):
185
+ * an invalid or missing continuation replaces the consumed prefix
186
+ * with ONE U+FFFD and rescans at the offending byte. */
187
+ uint32_t cp = b0 & (uint32_t)(0xFF >> (need + 2));
188
+ size_t j = i + 1;
189
+ int k = 0;
190
+ bool ok = true;
191
+ for (; k < need; k++, j++) {
192
+ if (j >= n) { ok = false; break; }
193
+ unsigned c = p[j];
194
+ unsigned clo = (k == 0) ? lo : 0x80, chi = (k == 0) ? hi : 0xBF;
195
+ if (c < clo || c > chi) { ok = false; break; }
196
+ cp = (cp << 6) | (c & 0x3F);
197
+ }
198
+ if (!ok) {
199
+ qs_utf8_encode(&out, 0xFFFD);
200
+ i = j; /* prefix consumed; rescan at the offending byte */
201
+ continue;
202
+ }
203
+ qs_utf8_encode(&out, cp);
204
+ i = j;
205
+ }
206
+ return qb_take(&out);
207
+ }
208
+
209
+ /* Node's unescapeBuffer(s) (decodeSpaces false — neither qsUnescape nor
210
+ * the exported unescape passes it) over the UTF-16 units, then the
211
+ * replacement decode of the byte buffer. */
212
+ static ScrStr *qs_unescape_buffer(const ScrStr *s) {
213
+ size_t n;
214
+ uint16_t *units = qs_utf16_units(s, &n);
215
+ unsigned char *out = malloc(n ? n : 1);
216
+ if (!out) {
217
+ fputs("scriptc: out of memory\n", stderr);
218
+ abort();
219
+ }
220
+ size_t w = 0, index = 0;
221
+ /* maxLength = s.length - 2 as a signed comparison (n < 2 must refuse
222
+ * every escape, matching Node's `index < maxLength` on negatives). */
223
+ while (index < n) {
224
+ unsigned cur = units[index];
225
+ if (cur == '%' && n >= 3 && index < n - 2) {
226
+ unsigned c1 = units[index + 1];
227
+ int hexHigh = (c1 < 256) ? qs_unhex(c1) : -1;
228
+ if (hexHigh < 0) {
229
+ out[w++] = '%';
230
+ index++;
231
+ continue;
232
+ }
233
+ unsigned c2 = units[index + 2];
234
+ int hexLow = (c2 < 256) ? qs_unhex(c2) : -1;
235
+ if (hexLow < 0) {
236
+ out[w++] = '%';
237
+ index++; /* Node: ++index then index-- — net one step */
238
+ continue;
239
+ }
240
+ cur = (unsigned)(hexHigh * 16 + hexLow);
241
+ index += 2;
242
+ }
243
+ out[w++] = (unsigned char)(cur & 0xFF); /* Buffer write truncates */
244
+ index++;
245
+ }
246
+ free(units);
247
+ ScrStr *res = qs_utf8_replace_decode(out, w);
248
+ free(out);
249
+ return res;
250
+ }
251
+
252
+ ScrStr *scr_qs_unescape(const ScrStr *s) {
253
+ ScrStr *strict = scr_str_decode_uri_component_try((ScrStr *)s);
254
+ if (strict) return strict;
255
+ return qs_unescape_buffer(s);
256
+ }
257
+
258
+ /* ── parse ───────────────────────────────────────────────────────────── */
259
+
260
+ /* addKeyVal's tail: decode-if-encoded, then group into the overflow map
261
+ * (first value = the str_tag arm; a repeat REPLACES it with a two-element
262
+ * string[] in the arr_tag arm; later repeats push). key/value move in. */
263
+ static void qs_add_key_val(ScrMap *out, ScrStr *key, ScrStr *value,
264
+ bool key_encoded, bool val_encoded,
265
+ uint32_t str_tag, uint32_t arr_tag) {
266
+ if (key->len > 0 && key_encoded) {
267
+ ScrStr *dec = scr_qs_unescape(key);
268
+ scr_str_release(key);
269
+ key = dec;
270
+ }
271
+ if (value->len > 0 && val_encoded) {
272
+ ScrStr *dec = scr_qs_unescape(value);
273
+ scr_str_release(value);
274
+ value = dec;
275
+ }
276
+ ScrUnion *cell = scr_map_get_str_ref(out, key);
277
+ if (!cell) {
278
+ scr_map_set_str_ref(out, key,
279
+ scr_union_new_ref(str_tag, value, &scr_str_retain_v,
280
+ &scr_str_release_v, NULL));
281
+ scr_str_release(key);
282
+ return;
283
+ }
284
+ if (cell->tag == str_tag) {
285
+ /* obj[key] = [curValue, value] */
286
+ ScrArr *rows = scr_arr_new(SCR_ELEM_STR, 2);
287
+ scr_arr_push_ref(rows, scr_str_retain_v(scr_union_peek(cell)));
288
+ scr_arr_push_ref(rows, value);
289
+ scr_map_set_str_ref(out, key,
290
+ scr_union_new_ref(arr_tag, rows, &scr_arr_retain_v,
291
+ &scr_arr_release_v, NULL));
292
+ } else {
293
+ scr_arr_push_ref((ScrArr *)scr_union_peek(cell), value);
294
+ }
295
+ scr_union_release(cell);
296
+ scr_str_release(key);
297
+ }
298
+
299
+ void scr_qs_parse_into(ScrMap *out, const ScrStr *qs, const ScrStr *sep,
300
+ const ScrStr *eq, double max_keys, uint32_t str_tag,
301
+ uint32_t arr_tag) {
302
+ if (qs->len == 0) return;
303
+ /* Node's falsy rule: undefined/null/'' all mean the default. */
304
+ const char *sep_b = (sep && sep->len) ? sep->data : "&";
305
+ size_t sep_len = (sep && sep->len) ? sep->len : 1;
306
+ const char *eq_b = (eq && eq->len) ? eq->data : "=";
307
+ size_t eq_len = (eq && eq->len) ? eq->len : 1;
308
+
309
+ /* pairs: Node's `maxKeys > 0 ? maxKeys : -1` as a double so Infinity
310
+ * decrements forever, exactly like Node's -1 sentinel. */
311
+ double pairs = max_keys > 0 ? max_keys : -1;
312
+
313
+ const char *p = qs->data;
314
+ size_t n = qs->len;
315
+ QsBuf key, value;
316
+ qb_init(&key);
317
+ qb_init(&value);
318
+ size_t last_pos = 0, sep_idx = 0, eq_idx = 0;
319
+ bool key_encoded = false, val_encoded = false;
320
+ int encode_check = 0;
321
+ bool returned = false;
322
+
323
+ for (size_t i = 0; i < n; ++i) {
324
+ unsigned char code = (unsigned char)p[i];
325
+
326
+ /* Try matching the pair separator (e.g. '&'). */
327
+ if (code == (unsigned char)sep_b[sep_idx]) {
328
+ if (++sep_idx == sep_len) {
329
+ /* Key/value pair separator match. */
330
+ size_t end = i - sep_idx + 1;
331
+ if (eq_idx < eq_len) {
332
+ /* No (entire) key/value separator seen. */
333
+ if (last_pos < end) {
334
+ qb_append(&key, p + last_pos, end - last_pos);
335
+ } else if (key.len == 0) {
336
+ /* An empty substring between separators. */
337
+ if (--pairs == 0) { returned = true; break; }
338
+ last_pos = i + 1;
339
+ sep_idx = eq_idx = 0;
340
+ continue;
341
+ }
342
+ } else if (last_pos < end) {
343
+ qb_append(&value, p + last_pos, end - last_pos);
344
+ }
345
+
346
+ qs_add_key_val(out, scr_str_new(key.data, key.len),
347
+ scr_str_new(value.data, value.len), key_encoded,
348
+ val_encoded, str_tag, arr_tag);
349
+
350
+ if (--pairs == 0) { returned = true; break; }
351
+ key_encoded = val_encoded = false;
352
+ key.len = value.len = 0;
353
+ encode_check = 0;
354
+ last_pos = i + 1;
355
+ sep_idx = eq_idx = 0;
356
+ }
357
+ } else {
358
+ sep_idx = 0;
359
+ /* Try matching the key/value separator (e.g. '=') if we haven't. */
360
+ if (eq_idx < eq_len) {
361
+ if (code == (unsigned char)eq_b[eq_idx]) {
362
+ if (++eq_idx == eq_len) {
363
+ /* Key/value separator match. */
364
+ size_t end = i - eq_idx + 1;
365
+ if (last_pos < end) qb_append(&key, p + last_pos, end - last_pos);
366
+ encode_check = 0;
367
+ last_pos = i + 1;
368
+ }
369
+ continue;
370
+ }
371
+ eq_idx = 0;
372
+ if (!key_encoded) {
373
+ /* Match a valid encoded byte once, to minimize decode calls. */
374
+ if (code == '%') {
375
+ encode_check = 1;
376
+ continue;
377
+ } else if (encode_check > 0) {
378
+ if (qs_unhex(code) >= 0) {
379
+ if (++encode_check == 3) key_encoded = true;
380
+ continue;
381
+ } else {
382
+ encode_check = 0;
383
+ }
384
+ }
385
+ }
386
+ if (code == '+') {
387
+ if (last_pos < i) qb_append(&key, p + last_pos, i - last_pos);
388
+ qb_push(&key, ' ');
389
+ last_pos = i + 1;
390
+ continue;
391
+ }
392
+ }
393
+ if (code == '+') {
394
+ if (last_pos < i) qb_append(&value, p + last_pos, i - last_pos);
395
+ qb_push(&value, ' ');
396
+ last_pos = i + 1;
397
+ } else if (!val_encoded) {
398
+ if (code == '%') {
399
+ encode_check = 1;
400
+ } else if (encode_check > 0) {
401
+ if (qs_unhex(code) >= 0) {
402
+ if (++encode_check == 3) val_encoded = true;
403
+ } else {
404
+ encode_check = 0;
405
+ }
406
+ }
407
+ }
408
+ }
409
+ }
410
+
411
+ if (!returned) {
412
+ /* Leftover key or value data. */
413
+ bool ended_empty = false;
414
+ if (last_pos < n) {
415
+ if (eq_idx < eq_len) qb_append(&key, p + last_pos, n - last_pos);
416
+ else if (sep_idx < sep_len) qb_append(&value, p + last_pos, n - last_pos);
417
+ } else if (eq_idx == 0 && key.len == 0) {
418
+ ended_empty = true; /* ended on an empty substring */
419
+ }
420
+ if (!ended_empty) {
421
+ qs_add_key_val(out, scr_str_new(key.data, key.len),
422
+ scr_str_new(value.data, value.len), key_encoded,
423
+ val_encoded, str_tag, arr_tag);
424
+ }
425
+ }
426
+ free(key.data);
427
+ free(value.data);
428
+ }
429
+
430
+ /* ── stringify ───────────────────────────────────────────────────────── */
431
+
432
+ /* Node's encodeStringified over one DOM value: strings escape, finite
433
+ * numbers render then escape, booleans are bare, everything else is the
434
+ * empty value. Appends to b. */
435
+ static void qs_stringify_value(QsBuf *b, const ScrDyn *v) {
436
+ switch (v->kind) {
437
+ case SCR_DYN_STR: {
438
+ ScrStr *enc = scr_str_encode_uri_component(v->v.str);
439
+ qb_append_str(b, enc);
440
+ scr_str_release(enc);
441
+ return;
442
+ }
443
+ case SCR_DYN_NUM: {
444
+ if (!isfinite(v->v.num)) return;
445
+ ScrStr *num = scr_f64_to_scrstr(v->v.num);
446
+ ScrStr *enc = scr_str_encode_uri_component(num);
447
+ qb_append_str(b, enc);
448
+ scr_str_release(enc);
449
+ scr_str_release(num);
450
+ return;
451
+ }
452
+ case SCR_DYN_BOOL:
453
+ if (v->v.b) qb_append(b, "true", 4);
454
+ else qb_append(b, "false", 5);
455
+ return;
456
+ default:
457
+ return; /* null/undefined/objects/arrays/functions → '' */
458
+ }
459
+ }
460
+
461
+ ScrStr *scr_qs_stringify(const ScrDyn *obj, const ScrStr *sep,
462
+ const ScrStr *eq) {
463
+ const char *sep_b = (sep && sep->len) ? sep->data : "&";
464
+ size_t sep_len = (sep && sep->len) ? sep->len : 1;
465
+ const char *eq_b = (eq && eq->len) ? eq->data : "=";
466
+ size_t eq_len = (eq && eq->len) ? eq->len : 1;
467
+ if (!obj || obj->kind != SCR_DYN_OBJ) return scr_str_new("", 0);
468
+
469
+ QsBuf fields;
470
+ qb_init(&fields);
471
+ /* JS own-key order (array-index keys ascending first) — ObjectKeys. */
472
+ ScrDyn *keys = scr_dyn_obj_keys((ScrDyn *)obj);
473
+ for (size_t i = 0; i < keys->v.arr.len; i++) {
474
+ const ScrDyn *kd = keys->v.arr.items[i];
475
+ const ScrStr *k = kd->v.str;
476
+ const ScrDyn *v = scr_dyn_obj_get(obj, k->data, k->len);
477
+ if (!v) continue; /* unreachable: keys came from the object */
478
+ ScrStr *ks = scr_str_encode_uri_component((ScrStr *)k);
479
+ if (v->kind == SCR_DYN_ARR) {
480
+ for (size_t j = 0; j < v->v.arr.len; j++) {
481
+ if (fields.len) qb_append(&fields, sep_b, sep_len);
482
+ qb_append_str(&fields, ks);
483
+ qb_append(&fields, eq_b, eq_len);
484
+ qs_stringify_value(&fields, v->v.arr.items[j]);
485
+ }
486
+ } else {
487
+ if (fields.len) qb_append(&fields, sep_b, sep_len);
488
+ qb_append_str(&fields, ks);
489
+ qb_append(&fields, eq_b, eq_len);
490
+ qs_stringify_value(&fields, v);
491
+ }
492
+ scr_str_release(ks);
493
+ }
494
+ scr_dyn_release(keys);
495
+ return qb_take(&fields);
496
+ }
package/src/scr_regex.c CHANGED
@@ -37,8 +37,7 @@
37
37
  #include "libregexp.h"
38
38
 
39
39
  static void scr_regex_oom(void) {
40
- fputs("scriptc: out of memory\n", stderr);
41
- abort();
40
+ scr_trap("scriptc: out of memory\n");
42
41
  }
43
42
 
44
43
  /* ── libregexp host hooks ─────────────────────────────────────────────
@@ -102,7 +101,7 @@ static void scr_note_compiled(ScrRegex *re) {
102
101
  scr_compiled = realloc(scr_compiled, scr_compiled_cap * sizeof *scr_compiled);
103
102
  if (!scr_compiled) scr_regex_oom();
104
103
  }
105
- if (scr_compiled_len == 0) atexit(scr_regex_free_bytecodes);
104
+ if (scr_compiled_len == 0) scr_atexit(scr_regex_free_bytecodes);
106
105
  scr_compiled[scr_compiled_len++] = re;
107
106
  }
108
107
 
@@ -119,9 +118,8 @@ static int scr_lre_flags(const ScrStr *flags) {
119
118
  case 'u': mask |= LRE_FLAG_UNICODE; break;
120
119
  case 'y': mask |= LRE_FLAG_STICKY; break;
121
120
  default:
122
- fprintf(stderr, "scriptc: internal error: unexpected regex flag '%c'\n",
123
- flags->data[i]);
124
- abort();
121
+ scr_trap_fmt("scriptc: internal error: unexpected regex flag '%c'\n",
122
+ flags->data[i]);
125
123
  }
126
124
  }
127
125
  return mask;
@@ -197,9 +195,8 @@ static uint8_t *scr_regex_bc(ScrRegex *re) {
197
195
  * tsc's parser has already caught plain syntax errors, so this is
198
196
  * rare). */
199
197
  fflush(stdout);
200
- fprintf(stderr, "scriptc: SyntaxError: Invalid regular expression: /%s/%s: %s\n",
201
- re->source->data, re->flags->data, error_msg);
202
- abort();
198
+ scr_trap_fmt("scriptc: SyntaxError: Invalid regular expression: /%s/%s: %s\n",
199
+ re->source->data, re->flags->data, error_msg);
203
200
  }
204
201
  re->bc = bc;
205
202
  scr_note_compiled(re);
@@ -315,8 +312,7 @@ static int scr_exec(uint8_t **capture, const uint8_t *bc, const uint16_t *u,
315
312
  int rc = lre_exec(capture, bc, (const uint8_t *)u, index, len, 1, lre_opaque());
316
313
  if (rc < 0) {
317
314
  fflush(stdout);
318
- fputs("scriptc: regular expression execution failed\n", stderr);
319
- abort();
315
+ scr_trap("scriptc: regular expression execution failed\n");
320
316
  }
321
317
  return rc;
322
318
  }
@@ -351,11 +347,9 @@ bool scr_regex_test(ScrRegex *re, ScrStr *s) {
351
347
  * iteration this slice does not model (the frontend rejects the sites
352
348
  * it can see; values that flow through variables land here). */
353
349
  fflush(stdout);
354
- fprintf(stderr,
355
- "scriptc: test() on a regex with the 'g' or 'y' flag is not "
356
- "supported (stateful lastIndex); drop the flag, or use "
357
- "replace/replaceAll/split\n");
358
- abort();
350
+ scr_trap("scriptc: test() on a regex with the 'g' or 'y' flag is not "
351
+ "supported (stateful lastIndex); drop the flag, or use "
352
+ "replace/replaceAll/split\n");
359
353
  }
360
354
  int len;
361
355
  uint16_t *u = scr_to_utf16(s, &len);
@@ -379,11 +373,9 @@ ScrArr *scr_regex_match(ScrStr *s, ScrRegex *re) {
379
373
  uint8_t *bc = scr_regex_bc(re);
380
374
  if (lre_get_flags(bc) & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) {
381
375
  fflush(stdout);
382
- fprintf(stderr,
383
- "scriptc: match() on a regex with the 'g' or 'y' flag is not "
384
- "supported (an every-match array is a different shape); drop the "
385
- "flag, or use replace/replaceAll/split\n");
386
- abort();
376
+ scr_trap("scriptc: match() on a regex with the 'g' or 'y' flag is not "
377
+ "supported (an every-match array is a different shape); drop the "
378
+ "flag, or use replace/replaceAll/split\n");
387
379
  }
388
380
  int len;
389
381
  uint16_t *u = scr_to_utf16(s, &len);
@@ -803,7 +795,7 @@ static bool scr_assert_regex_hits(ScrRegex *re, ScrStr *s) {
803
795
  static ScrStr *scr_assert_regex_render(ScrRegex *re) {
804
796
  size_t cap = re->source->len + re->flags->len + 2;
805
797
  char *buf = malloc(cap);
806
- if (!buf) abort();
798
+ if (!buf) scr_trap("scriptc: out of memory\n");
807
799
  size_t n = 0;
808
800
  buf[n++] = '/';
809
801
  memcpy(buf + n, re->source->data, re->source->len);
@@ -831,7 +823,7 @@ static void scr_assert_regex_input_fail(bool negated, ScrRegex *re, ScrStr *inpu
831
823
  const char *mid = ". Input:\n\n";
832
824
  size_t cap = strlen(head) + rre->len + strlen(mid) + insp->len + 1;
833
825
  char *buf = malloc(cap);
834
- if (!buf) abort();
826
+ if (!buf) scr_trap("scriptc: out of memory\n");
835
827
  size_t n = 0;
836
828
  memcpy(buf + n, head, strlen(head));
837
829
  n += strlen(head);
@@ -924,8 +916,7 @@ ScrRegex *scr_regex_new(ScrStr *pattern, ScrStr *flags) {
924
916
  }
925
917
  ScrRegex *re = calloc(1, sizeof *re);
926
918
  if (!re) {
927
- fputs("scriptc: out of memory\n", stderr);
928
- abort();
919
+ scr_trap("scriptc: out of memory\n");
929
920
  }
930
921
  re->rc = 1;
931
922
  re->source = pattern->len > 0 ? scr_str_retain(pattern) : scr_str_new("(?:)", 4);