@scriptc/cli-darwin-arm64 0.2.4 → 0.2.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.
Files changed (121) hide show
  1. package/dist/THIRD_PARTY_NOTICES +1 -1
  2. package/dist/bin/scriptc +0 -0
  3. package/dist/bin/scriptc.json +1 -1
  4. package/dist/lib/declarations/scriptc-node-fallback.d.ts +915 -215
  5. package/dist/lib/declarations/scriptc-overrides.d.ts +5 -1
  6. package/dist/lib/declarations/scriptc.d.ts +22 -2
  7. package/dist/lib/llvm/bin/scriptc-llvm-codegen +0 -0
  8. package/dist/lib/llvm/package.json +1 -1
  9. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/default/scr_array.o +0 -0
  10. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/default/scr_bytes.o +0 -0
  11. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/default/scr_bytes_io.o +0 -0
  12. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/default/scr_copying.o +0 -0
  13. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/default/scr_json.o +0 -0
  14. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/default/scr_lib.o +0 -0
  15. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/default/scr_map.o +0 -0
  16. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/default/scr_number.o +0 -0
  17. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/default/scr_regex.o +0 -0
  18. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/default/scr_string.o +0 -0
  19. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/dynamic/scr_array.o +0 -0
  20. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/dynamic/scr_bytes.o +0 -0
  21. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/dynamic/scr_bytes_io.o +0 -0
  22. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/dynamic/scr_copying.o +0 -0
  23. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/dynamic/scr_json.o +0 -0
  24. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/dynamic/scr_lib.o +0 -0
  25. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/dynamic/scr_map.o +0 -0
  26. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/dynamic/scr_number.o +0 -0
  27. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/dynamic/scr_regex.o +0 -0
  28. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/dynamic/scr_string.o +0 -0
  29. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/dynamic-text-decoder-legacy/scr_bytes.o +0 -0
  30. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/text-decoder-legacy/scr_bytes.o +0 -0
  31. package/dist/lib/runtime-darwin-arm64/artifacts/library-dev/runtime/default/scr_array.o +0 -0
  32. package/dist/lib/runtime-darwin-arm64/artifacts/library-dev/runtime/default/scr_bytes.o +0 -0
  33. package/dist/lib/runtime-darwin-arm64/artifacts/library-dev/runtime/default/scr_bytes_io.o +0 -0
  34. package/dist/lib/runtime-darwin-arm64/artifacts/library-dev/runtime/default/scr_copying.o +0 -0
  35. package/dist/lib/runtime-darwin-arm64/artifacts/library-dev/runtime/default/scr_json.o +0 -0
  36. package/dist/lib/runtime-darwin-arm64/artifacts/library-dev/runtime/default/scr_lib.o +0 -0
  37. package/dist/lib/runtime-darwin-arm64/artifacts/library-dev/runtime/default/scr_map.o +0 -0
  38. package/dist/lib/runtime-darwin-arm64/artifacts/library-dev/runtime/default/scr_number.o +0 -0
  39. package/dist/lib/runtime-darwin-arm64/artifacts/library-dev/runtime/default/scr_regex.o +0 -0
  40. package/dist/lib/runtime-darwin-arm64/artifacts/library-dev/runtime/default/scr_string.o +0 -0
  41. package/dist/lib/runtime-darwin-arm64/artifacts/library-dev/runtime/text-decoder-legacy/scr_bytes.o +0 -0
  42. package/dist/lib/runtime-darwin-arm64/artifacts/library-release/runtime/default/scr_array.o +0 -0
  43. package/dist/lib/runtime-darwin-arm64/artifacts/library-release/runtime/default/scr_bytes.o +0 -0
  44. package/dist/lib/runtime-darwin-arm64/artifacts/library-release/runtime/default/scr_bytes_io.o +0 -0
  45. package/dist/lib/runtime-darwin-arm64/artifacts/library-release/runtime/default/scr_copying.o +0 -0
  46. package/dist/lib/runtime-darwin-arm64/artifacts/library-release/runtime/default/scr_json.o +0 -0
  47. package/dist/lib/runtime-darwin-arm64/artifacts/library-release/runtime/default/scr_lib.o +0 -0
  48. package/dist/lib/runtime-darwin-arm64/artifacts/library-release/runtime/default/scr_map.o +0 -0
  49. package/dist/lib/runtime-darwin-arm64/artifacts/library-release/runtime/default/scr_number.o +0 -0
  50. package/dist/lib/runtime-darwin-arm64/artifacts/library-release/runtime/default/scr_regex.o +0 -0
  51. package/dist/lib/runtime-darwin-arm64/artifacts/library-release/runtime/default/scr_string.o +0 -0
  52. package/dist/lib/runtime-darwin-arm64/artifacts/library-release/runtime/text-decoder-legacy/scr_bytes.o +0 -0
  53. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-dev/runtime/default/scr_array.o +0 -0
  54. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-dev/runtime/default/scr_bytes.o +0 -0
  55. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-dev/runtime/default/scr_bytes_io.o +0 -0
  56. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-dev/runtime/default/scr_copying.o +0 -0
  57. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-dev/runtime/default/scr_json.o +0 -0
  58. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-dev/runtime/default/scr_lib.o +0 -0
  59. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-dev/runtime/default/scr_map.o +0 -0
  60. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-dev/runtime/default/scr_number.o +0 -0
  61. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-dev/runtime/default/scr_regex.o +0 -0
  62. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-dev/runtime/default/scr_string.o +0 -0
  63. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-dev/runtime/text-decoder-legacy/scr_bytes.o +0 -0
  64. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-release/runtime/default/scr_array.o +0 -0
  65. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-release/runtime/default/scr_bytes.o +0 -0
  66. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-release/runtime/default/scr_bytes_io.o +0 -0
  67. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-release/runtime/default/scr_copying.o +0 -0
  68. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-release/runtime/default/scr_json.o +0 -0
  69. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-release/runtime/default/scr_lib.o +0 -0
  70. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-release/runtime/default/scr_map.o +0 -0
  71. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-release/runtime/default/scr_number.o +0 -0
  72. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-release/runtime/default/scr_regex.o +0 -0
  73. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-release/runtime/default/scr_string.o +0 -0
  74. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-release/runtime/text-decoder-legacy/scr_bytes.o +0 -0
  75. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/default/scr_array.o +0 -0
  76. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/default/scr_bytes.o +0 -0
  77. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/default/scr_bytes_io.o +0 -0
  78. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/default/scr_copying.o +0 -0
  79. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/default/scr_json.o +0 -0
  80. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/default/scr_lib.o +0 -0
  81. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/default/scr_map.o +0 -0
  82. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/default/scr_number.o +0 -0
  83. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/default/scr_regex.o +0 -0
  84. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/default/scr_string.o +0 -0
  85. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/default/scr_util_compare.o +0 -0
  86. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/dynamic/scr_array.o +0 -0
  87. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/dynamic/scr_bytes.o +0 -0
  88. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/dynamic/scr_bytes_io.o +0 -0
  89. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/dynamic/scr_copying.o +0 -0
  90. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/dynamic/scr_json.o +0 -0
  91. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/dynamic/scr_lib.o +0 -0
  92. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/dynamic/scr_map.o +0 -0
  93. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/dynamic/scr_number.o +0 -0
  94. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/dynamic/scr_regex.o +0 -0
  95. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/dynamic/scr_string.o +0 -0
  96. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/dynamic/scr_util_compare.o +0 -0
  97. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/dynamic-text-decoder-legacy/scr_bytes.o +0 -0
  98. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/text-decoder-legacy/scr_bytes.o +0 -0
  99. package/dist/lib/runtime-darwin-arm64/package.json +1 -1
  100. package/dist/lib/runtime-darwin-arm64/runtime-pack.json +181 -181
  101. package/dist/lib/runtime-sources/package.json +1 -1
  102. package/dist/lib/runtime-sources/src/scr_array.c +267 -6
  103. package/dist/lib/runtime-sources/src/scr_async.test.ts +16 -6
  104. package/dist/lib/runtime-sources/src/scr_bytes.c +437 -216
  105. package/dist/lib/runtime-sources/src/scr_bytes.test.c +85 -0
  106. package/dist/lib/runtime-sources/src/scr_bytes_io.c +0 -34
  107. package/dist/lib/runtime-sources/src/scr_copying.c +12 -2
  108. package/dist/lib/runtime-sources/src/scr_json.c +479 -150
  109. package/dist/lib/runtime-sources/src/scr_json.test.c +323 -1
  110. package/dist/lib/runtime-sources/src/scr_json.test.ts +58 -25
  111. package/dist/lib/runtime-sources/src/scr_key.h +68 -0
  112. package/dist/lib/runtime-sources/src/scr_lib.c +132 -73
  113. package/dist/lib/runtime-sources/src/scr_map.c +50 -66
  114. package/dist/lib/runtime-sources/src/scr_number.c +2 -4
  115. package/dist/lib/runtime-sources/src/scr_numeric.h +25 -0
  116. package/dist/lib/runtime-sources/src/scr_numeric.test.c +37 -0
  117. package/dist/lib/runtime-sources/src/scr_numeric.test.ts +33 -0
  118. package/dist/lib/runtime-sources/src/scr_regex.c +238 -167
  119. package/dist/lib/runtime-sources/src/scr_runtime.h +37 -6
  120. package/dist/lib/runtime-sources/src/scr_string.c +155 -42
  121. package/package.json +1 -1
@@ -13,10 +13,9 @@
13
13
  * lre_compile rejects aborts with a clear
14
14
  * message (Node throws SyntaxError at parse time — documented divergence;
15
15
  * rare, since tsc's parser has already syntax-checked the literal).
16
- * - Subjects are matched as UTF-16: lre_exec runs over a per-call
17
- * conversion of the UTF-8 subject (cbuf_type 1), so every index libregexp
18
- * reports is a UTF-16 code-unit index — .length/slice/charCodeAt exact,
19
- * like Node. Correctness-first: the conversion is O(n) per call.
16
+ * - ASCII subjects borrow their existing bytes; other subjects use a
17
+ * per-call UTF-16 conversion. Both engine representations report UTF-16
18
+ * code-unit indices, matching .length/slice/charCodeAt.
20
19
  * - Match loops mirror quickjs.c's own (JS_RegExpDelete /
21
20
  * js_regexp_Symbol_replace): capture buffers are sized by
22
21
  * lre_get_alloc_count (NOT capture_count*2 — the executor writes
@@ -304,6 +303,8 @@ ScrStr *scr_regex_to_string(ScrRegex *re) {
304
303
  /* Subject as freshly-malloc'd UTF-16 code units (strings are well-formed
305
304
  * UTF-8, so no validation). Unit count ≤ byte count. */
306
305
  static uint16_t *scr_to_utf16(const ScrStr *s, int *plen) {
306
+ if (s->len > (size_t)INT32_MAX - 1 || s->len > SIZE_MAX / sizeof(uint16_t))
307
+ scr_regex_oom();
307
308
  uint16_t *u = malloc((s->len ? s->len : 1) * sizeof(uint16_t));
308
309
  if (!u) scr_regex_oom();
309
310
  int n = 0;
@@ -339,53 +340,98 @@ static uint16_t *scr_to_utf16(const ScrStr *s, int *plen) {
339
340
  return u;
340
341
  }
341
342
 
342
- /* Append u[start..end) as UTF-8. A lone surrogate (a non-/u match boundary
343
- * split an astral pair) becomes U+FFFD — the house rule for JS results that
344
- * are not well-formed UTF-16 (same divergence class as charAt/slice,
345
- * SEMANTICS.md). */
343
+ /* Decode one UTF-16 code point. A boundary that splits a surrogate pair
344
+ * produces U+FFFD, as in the runtime's string slicing contract. */
345
+ static uint32_t scr_regex_codepoint(const uint16_t *units, int *index, int end) {
346
+ uint32_t cp = units[(*index)++];
347
+ if (cp >= 0xD800 && cp < 0xDC00 && *index < end &&
348
+ units[*index] >= 0xDC00 && units[*index] < 0xE000)
349
+ return 0x10000 + ((cp - 0xD800) << 10) + (units[(*index)++] - 0xDC00);
350
+ return cp >= 0xD800 && cp < 0xE000 ? 0xFFFD : cp;
351
+ }
352
+
353
+ static size_t scr_regex_encode(char *out, uint32_t cp) {
354
+ if (cp < 0x80) {
355
+ out[0] = (char)cp;
356
+ return 1;
357
+ }
358
+ if (cp < 0x800) {
359
+ out[0] = (char)(0xC0 | (cp >> 6));
360
+ out[1] = (char)(0x80 | (cp & 0x3F));
361
+ return 2;
362
+ }
363
+ if (cp < 0x10000) {
364
+ out[0] = (char)(0xE0 | (cp >> 12));
365
+ out[1] = (char)(0x80 | ((cp >> 6) & 0x3F));
366
+ out[2] = (char)(0x80 | (cp & 0x3F));
367
+ return 3;
368
+ }
369
+ out[0] = (char)(0xF0 | (cp >> 18));
370
+ out[1] = (char)(0x80 | ((cp >> 12) & 0x3F));
371
+ out[2] = (char)(0x80 | ((cp >> 6) & 0x3F));
372
+ out[3] = (char)(0x80 | (cp & 0x3F));
373
+ return 4;
374
+ }
375
+
346
376
  static void scr_jb_put_utf16(ScrJsonBuf *b, const uint16_t *u, int start, int end) {
347
- for (int i = start; i < end; i++) {
348
- uint32_t cp = u[i];
349
- if (cp >= 0xD800 && cp < 0xDC00 && i + 1 < end && u[i + 1] >= 0xDC00 &&
350
- u[i + 1] < 0xE000) {
351
- cp = 0x10000 + ((cp - 0xD800) << 10) + (u[i + 1] - 0xDC00);
352
- i++;
353
- } else if (cp >= 0xD800 && cp < 0xE000) {
354
- cp = 0xFFFD;
355
- }
356
- if (cp < 0x80) {
357
- scr_jb_putc(b, (char)cp);
358
- } else if (cp < 0x800) {
359
- scr_jb_putc(b, (char)(0xC0 | (cp >> 6)));
360
- scr_jb_putc(b, (char)(0x80 | (cp & 0x3F)));
361
- } else if (cp < 0x10000) {
362
- scr_jb_putc(b, (char)(0xE0 | (cp >> 12)));
363
- scr_jb_putc(b, (char)(0x80 | ((cp >> 6) & 0x3F)));
364
- scr_jb_putc(b, (char)(0x80 | (cp & 0x3F)));
365
- } else {
366
- scr_jb_putc(b, (char)(0xF0 | (cp >> 18)));
367
- scr_jb_putc(b, (char)(0x80 | ((cp >> 12) & 0x3F)));
368
- scr_jb_putc(b, (char)(0x80 | ((cp >> 6) & 0x3F)));
369
- scr_jb_putc(b, (char)(0x80 | (cp & 0x3F)));
377
+ char bytes[256];
378
+ size_t used = 0;
379
+ for (int i = start; i < end;) {
380
+ if (used > sizeof bytes - 4) {
381
+ scr_jb_write(b, bytes, used);
382
+ used = 0;
370
383
  }
384
+ used += scr_regex_encode(bytes + used, scr_regex_codepoint(u, &i, end));
371
385
  }
386
+ scr_jb_write(b, bytes, used);
372
387
  }
373
388
 
374
- static ScrStr *scr_str_from_utf16(const uint16_t *u, int start, int end) {
375
- ScrJsonBuf b;
376
- scr_jb_init(&b);
377
- scr_jb_put_utf16(&b, u, start, end);
378
- return scr_jb_finish(&b);
389
+ /* Matching owns only the converted subject and oversized register buffer.
390
+ * The caller keeps the original immutable string alive. ASCII bytes are
391
+ * already the engine's one-byte representation, even under /u or /i. */
392
+ typedef struct ScrRegexInput {
393
+ const uint8_t *data;
394
+ uint16_t *utf16;
395
+ int len;
396
+ int wide;
397
+ uint8_t **capture;
398
+ uint8_t *registers[32];
399
+ } ScrRegexInput;
400
+
401
+ static bool scr_text_ascii(const ScrStr *s) {
402
+ size_t i = 0;
403
+ for (; s->len - i >= sizeof(uint64_t); i += sizeof(uint64_t)) {
404
+ uint64_t word;
405
+ memcpy(&word, s->data + i, sizeof word);
406
+ if (word & UINT64_C(0x8080808080808080)) return false;
407
+ }
408
+ for (; i < s->len; i++) {
409
+ if ((unsigned char)s->data[i] >= 0x80) return false;
410
+ }
411
+ return true;
379
412
  }
380
413
 
381
- /* ── exec plumbing ────────────────────────────────────────────────────── */
414
+ static void scr_regex_input_init(ScrRegexInput *input, const uint8_t *bc,
415
+ const ScrStr *s) {
416
+ if (s->len > (size_t)INT32_MAX - 1) scr_regex_oom();
417
+ input->wide = !scr_text_ascii(s);
418
+ input->utf16 = input->wide ? scr_to_utf16(s, &input->len) : NULL;
419
+ input->data = input->wide ? (const uint8_t *)input->utf16 : (const uint8_t *)s->data;
420
+ if (!input->wide) input->len = (int)s->len;
421
+ /* The executor writes temporary registers beyond the capture pairs. */
422
+ int count = lre_get_alloc_count(bc);
423
+ input->capture = count <= 32 ? input->registers : malloc((size_t)count * sizeof(uint8_t *));
424
+ if (!input->capture) scr_regex_oom();
425
+ }
426
+
427
+ static void scr_regex_input_dispose(ScrRegexInput *input) {
428
+ if (input->capture != input->registers) free(input->capture);
429
+ free(input->utf16);
430
+ }
382
431
 
383
- /* One lre_exec over the UTF-16 subject. Returns 1 (match), 0 (no match);
384
- * engine-level failures (OOM, corrupt bytecode — no timeout handler is
385
- * installed) abort like every runtime OOM. */
386
- static int scr_exec(uint8_t **capture, const uint8_t *bc, const uint16_t *u,
387
- int index, int len) {
388
- int rc = lre_exec(capture, bc, (const uint8_t *)u, index, len, 1, lre_opaque());
432
+ static int scr_exec(ScrRegexInput *input, const uint8_t *bc, int index) {
433
+ int rc = lre_exec(input->capture, bc, input->data, index, input->len,
434
+ input->wide, lre_opaque());
389
435
  if (rc < 0) {
390
436
  fflush(stdout);
391
437
  scr_trap("scriptc: regular expression execution failed\n");
@@ -393,24 +439,43 @@ static int scr_exec(uint8_t **capture, const uint8_t *bc, const uint16_t *u,
393
439
  return rc;
394
440
  }
395
441
 
396
- /* Capture buffer sized by lre_get_alloc_count: the register-based executor
397
- * writes temporary registers past the capture pairs (quickjs.c documents
398
- * sizing by capture_count*2 as a silent heap overflow). */
399
- static uint8_t **scr_capture_alloc(const uint8_t *bc) {
400
- int n = lre_get_alloc_count(bc);
401
- uint8_t **cap = malloc((n > 0 ? (size_t)n : 1) * sizeof *cap);
402
- if (!cap) scr_regex_oom();
403
- return cap;
442
+ static int scr_capture_index(const ScrRegexInput *input, const uint8_t *capture) {
443
+ return (int)((capture - input->data) >> input->wide);
444
+ }
445
+
446
+ static void scr_regex_put_span(ScrJsonBuf *b, const ScrRegexInput *input,
447
+ int start, int end) {
448
+ if (input->wide) scr_jb_put_utf16(b, input->utf16, start, end);
449
+ else scr_jb_write(b, (const char *)input->data + start, (size_t)(end - start));
450
+ }
451
+
452
+ static ScrStr *scr_regex_span(const ScrRegexInput *input, int start, int end) {
453
+ if (!input->wide)
454
+ return scr_str_new((const char *)input->data + start, (size_t)(end - start));
455
+ /* Captures have a known span: allocate its exact UTF-8 length instead
456
+ * of inheriting a general builder's size hint from an earlier document. */
457
+ size_t bytes = 0;
458
+ for (int i = start; i < end;) {
459
+ uint32_t cp = scr_regex_codepoint(input->utf16, &i, end);
460
+ size_t width = cp < 0x80 ? 1 : cp < 0x800 ? 2 : cp < 0x10000 ? 3 : 4;
461
+ if (width > SIZE_MAX - sizeof(ScrStr) - 1 - bytes) scr_regex_oom();
462
+ bytes += width;
463
+ }
464
+ ScrStr *out = scr_str_alloc_raw(bytes, bytes);
465
+ size_t offset = 0;
466
+ for (int i = start; i < end;)
467
+ offset += scr_regex_encode(out->data + offset, scr_regex_codepoint(input->utf16, &i, end));
468
+ out->data[bytes] = '\0';
469
+ return out;
404
470
  }
405
471
 
406
472
  /* ECMA-262 AdvanceStringIndex: past a whole surrogate pair under /u, one
407
- * code unit otherwise — the rule that keeps zero-length matches from
408
- * looping forever. */
409
- static int scr_advance(const uint16_t *u, int len, int i, bool unicode) {
410
- if (unicode && i + 1 < len && u[i] >= 0xD800 && u[i] < 0xDC00 &&
411
- u[i + 1] >= 0xDC00 && u[i + 1] < 0xE000) {
473
+ * code unit otherwise. ASCII cannot contain a surrogate pair. */
474
+ static int scr_advance(const ScrRegexInput *input, int i, bool unicode) {
475
+ const uint16_t *u = input->utf16;
476
+ if (unicode && input->wide && i + 1 < input->len &&
477
+ u[i] >= 0xD800 && u[i] < 0xDC00 && u[i + 1] >= 0xDC00 && u[i + 1] < 0xE000)
412
478
  return i + 2;
413
- }
414
479
  return i + 1;
415
480
  }
416
481
 
@@ -427,40 +492,17 @@ static int scr_regex_start(ScrRegex *re, int len) {
427
492
  return (int)floor(index);
428
493
  }
429
494
 
430
- /* A predicate needs only the match boundary, not the captured strings or
431
- * result metadata. Short ASCII subjects already have the engine's one-byte
432
- * representation. Bound that scan so a long mixed-text subject does not
433
- * traverse an ASCII prefix twice before matching. Other subjects use exec's
434
- * UTF-16 conversion. Register storage is bounded by alloc_count, not captures. */
495
+ /* A predicate needs only boundaries; it never materializes result strings. */
435
496
  bool scr_regex_test(ScrRegex *re, ScrStr *s) {
436
497
  uint8_t *bc = scr_regex_bc(re);
437
- bool ascii = s->len <= 256;
438
- size_t i = 0;
439
- for (; ascii && s->len - i >= sizeof(uint64_t); i += sizeof(uint64_t)) {
440
- uint64_t word;
441
- memcpy(&word, s->data + i, sizeof word);
442
- if (word & UINT64_C(0x8080808080808080)) ascii = false;
443
- }
444
- for (; ascii && i < s->len; i++) {
445
- if ((unsigned char)s->data[i] >= 0x80) ascii = false;
446
- }
447
- int len;
448
- uint16_t *converted = ascii ? NULL : scr_to_utf16(s, &len);
449
- const uint8_t *subject = ascii ? (const uint8_t *)s->data : (const uint8_t *)converted;
450
- if (ascii) len = (int)s->len;
451
- uint8_t *registers[32];
452
- uint8_t **capture = lre_get_alloc_count(bc) <= 32 ? registers : scr_capture_alloc(bc);
498
+ ScrRegexInput input;
499
+ scr_regex_input_init(&input, bc, s);
453
500
  bool stateful = (lre_get_flags(bc) & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) != 0;
454
- int pos = stateful ? scr_regex_start(re, len) : 0;
455
- int rc = pos < 0 ? 0 : lre_exec(capture, bc, subject, pos, len, ascii ? 0 : 1, lre_opaque());
456
- if (rc < 0) {
457
- fflush(stdout);
458
- scr_trap("scriptc: regular expression execution failed\n");
459
- }
501
+ int pos = stateful ? scr_regex_start(re, input.len) : 0;
502
+ int rc = pos < 0 ? 0 : scr_exec(&input, bc, pos);
460
503
  if (stateful) re->last_index = rc == 1
461
- ? (double)((capture[1] - subject) >> (ascii ? 0 : 1)) : 0;
462
- if (capture != registers) free(capture);
463
- free(converted);
504
+ ? (double)scr_capture_index(&input, input.capture[1]) : 0;
505
+ scr_regex_input_dispose(&input);
464
506
  return rc == 1;
465
507
  }
466
508
 
@@ -474,20 +516,20 @@ bool scr_regex_test(ScrRegex *re, ScrStr *s) {
474
516
  ScrArr *scr_regex_exec(ScrStr *s, ScrRegex *re) {
475
517
  uint8_t *bc = scr_regex_bc(re);
476
518
  bool stateful = (lre_get_flags(bc) & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) != 0;
477
- int len;
478
- uint16_t *u = scr_to_utf16(s, &len);
479
- uint8_t **capture = scr_capture_alloc(bc);
519
+ ScrRegexInput input;
520
+ scr_regex_input_init(&input, bc, s);
521
+ int len = input.len;
522
+ uint8_t **capture = input.capture;
480
523
  int pos = stateful ? scr_regex_start(re, len) : 0;
481
- int rc = pos < 0 ? 0 : scr_exec(capture, bc, u, pos, len);
524
+ int rc = pos < 0 ? 0 : scr_exec(&input, bc, pos);
482
525
  if (stateful) re->last_index = rc == 1
483
- ? (double)((capture[1] - (const uint8_t *)u) >> 1) : 0;
526
+ ? (double)scr_capture_index(&input, capture[1]) : 0;
484
527
  ScrArr *out = NULL;
485
528
  if (rc == 1) {
486
- const uint8_t *ubase = (const uint8_t *)u;
487
529
  int count = lre_get_capture_count(bc);
488
530
  out = scr_arr_new(SCR_ELEM_STR, (size_t)count);
489
531
  out->metadata = scr_dyn_new_obj();
490
- scr_dyn_obj_set(out->metadata, "index", 5, scr_dyn_new_num((double)((capture[0] - ubase) >> 1)));
532
+ scr_dyn_obj_set(out->metadata, "index", 5, scr_dyn_new_num((double)scr_capture_index(&input, capture[0])));
491
533
  scr_dyn_obj_set(out->metadata, "input", 5, scr_dyn_new_str(s));
492
534
  for (int k = 0; k < count; k++) {
493
535
  const uint8_t *cs = capture[2 * k], *ce = capture[2 * k + 1];
@@ -495,12 +537,11 @@ ScrArr *scr_regex_exec(ScrStr *s, ScrRegex *re) {
495
537
  scr_arr_set_undefined(out, (double)out->len);
496
538
  } else {
497
539
  scr_arr_push_ref(
498
- out, scr_str_from_utf16(u, (int)((cs - ubase) >> 1), (int)((ce - ubase) >> 1)));
540
+ out, scr_regex_span(&input, scr_capture_index(&input, cs), scr_capture_index(&input, ce)));
499
541
  }
500
542
  }
501
543
  }
502
- free(capture);
503
- free(u);
544
+ scr_regex_input_dispose(&input);
504
545
  return out; /* +1, or NULL (no match) */
505
546
  }
506
547
 
@@ -515,22 +556,21 @@ ScrArr *scr_regex_match(ScrStr *s, ScrRegex *re) {
515
556
  if (!(flags & LRE_FLAG_GLOBAL)) return scr_regex_exec(s, re);
516
557
  re->last_index = 0;
517
558
  bool unicode = (flags & (LRE_FLAG_UNICODE | LRE_FLAG_UNICODE_SETS)) != 0;
518
- int len;
519
- uint16_t *u = scr_to_utf16(s, &len);
520
- uint8_t **capture = scr_capture_alloc(bc);
521
- const uint8_t *ubase = (const uint8_t *)u;
559
+ ScrRegexInput input;
560
+ scr_regex_input_init(&input, bc, s);
561
+ int len = input.len;
562
+ uint8_t **capture = input.capture;
522
563
  ScrArr *out = NULL;
523
564
  int pos = scr_regex_start(re, len);
524
565
  if (pos < 0) pos = len + 1;
525
- while (pos <= len && scr_exec(capture, bc, u, pos, len) == 1) {
526
- int start = (int)((capture[0] - ubase) >> 1);
527
- int end = (int)((capture[1] - ubase) >> 1);
566
+ while (pos <= len && scr_exec(&input, bc, pos) == 1) {
567
+ int start = scr_capture_index(&input, capture[0]);
568
+ int end = scr_capture_index(&input, capture[1]);
528
569
  if (!out) out = scr_arr_new(SCR_ELEM_STR, 4);
529
- scr_arr_push_ref(out, scr_str_from_utf16(u, start, end));
530
- pos = start == end ? scr_advance(u, len, end, unicode) : end;
570
+ scr_arr_push_ref(out, scr_regex_span(&input, start, end));
571
+ pos = start == end ? scr_advance(&input, end, unicode) : end;
531
572
  }
532
- free(capture);
533
- free(u);
573
+ scr_regex_input_dispose(&input);
534
574
  return out;
535
575
  }
536
576
 
@@ -543,14 +583,13 @@ ScrArr *scr_regex_match(ScrStr *s, ScrRegex *re) {
543
583
  * bytecode omits the implicit forward scan) — exactly Node. */
544
584
  double scr_regex_search(ScrStr *s, ScrRegex *re) {
545
585
  uint8_t *bc = scr_regex_bc(re);
546
- int len;
547
- uint16_t *u = scr_to_utf16(s, &len);
548
- uint8_t **capture = scr_capture_alloc(bc);
549
- int rc = scr_exec(capture, bc, u, 0, len);
586
+ ScrRegexInput input;
587
+ scr_regex_input_init(&input, bc, s);
588
+ uint8_t **capture = input.capture;
589
+ int rc = scr_exec(&input, bc, 0);
550
590
  double out = -1;
551
- if (rc == 1) out = (double)((capture[0] - (const uint8_t *)u) >> 1);
552
- free(capture);
553
- free(u);
591
+ if (rc == 1) out = (double)scr_capture_index(&input, capture[0]);
592
+ scr_regex_input_dispose(&input);
554
593
  return out;
555
594
  }
556
595
 
@@ -574,17 +613,17 @@ static ScrArr *scr_regex_match_all_core(ScrStr *s, ScrRegex *re, ScrArr *indices
574
613
  }
575
614
  bool unicode = (re_flags & (LRE_FLAG_UNICODE | LRE_FLAG_UNICODE_SETS)) != 0;
576
615
  int capture_count = lre_get_capture_count(bc);
577
- int len;
578
- uint16_t *u = scr_to_utf16(s, &len);
579
- uint8_t **capture = scr_capture_alloc(bc);
580
- const uint8_t *ubase = (const uint8_t *)u;
616
+ ScrRegexInput input;
617
+ scr_regex_input_init(&input, bc, s);
618
+ int len = input.len;
619
+ uint8_t **capture = input.capture;
581
620
  ScrArr *out = scr_arr_new(SCR_ELEM_ARR, 4);
582
621
  int pos = scr_regex_start(re, len);
583
622
  if (pos < 0) pos = len + 1;
584
623
  while (pos <= len) {
585
- if (scr_exec(capture, bc, u, pos, len) != 1) break;
586
- int start = (int)((capture[0] - ubase) >> 1);
587
- int end = (int)((capture[1] - ubase) >> 1);
624
+ if (scr_exec(&input, bc, pos) != 1) break;
625
+ int start = scr_capture_index(&input, capture[0]);
626
+ int end = scr_capture_index(&input, capture[1]);
588
627
  ScrArr *row = scr_arr_new(SCR_ELEM_STR, (size_t)capture_count);
589
628
  for (int k = 0; k < capture_count; k++) {
590
629
  const uint8_t *cs = capture[2 * k], *ce = capture[2 * k + 1];
@@ -592,15 +631,14 @@ static ScrArr *scr_regex_match_all_core(ScrStr *s, ScrRegex *re, ScrArr *indices
592
631
  scr_arr_set_undefined(row, (double)row->len);
593
632
  } else {
594
633
  scr_arr_push_ref(
595
- row, scr_str_from_utf16(u, (int)((cs - ubase) >> 1), (int)((ce - ubase) >> 1)));
634
+ row, scr_regex_span(&input, scr_capture_index(&input, cs), scr_capture_index(&input, ce)));
596
635
  }
597
636
  }
598
637
  scr_arr_push_ref(out, row);
599
638
  if (indices != NULL) scr_arr_push_f64(indices, (double)start);
600
- pos = end == start ? scr_advance(u, len, end, unicode) : end;
639
+ pos = end == start ? scr_advance(&input, end, unicode) : end;
601
640
  }
602
- free(capture);
603
- free(u);
641
+ scr_regex_input_dispose(&input);
604
642
  return out; /* +1 (possibly empty — Node's no-match drain is []) */
605
643
  }
606
644
 
@@ -628,17 +666,18 @@ ScrArr *scr_regex_match_all_into(ScrStr *s, ScrRegex *re, ScrArr *indices) {
628
666
  * — also Node's unterminated-'$<name' answer either way. The template is
629
667
  * scanned byte-wise: every '$' directive is ASCII, and UTF-8 continuation
630
668
  * bytes can never alias it. */
631
- static void scr_put_substitution(ScrJsonBuf *b, const uint16_t *u, int len,
669
+ static void scr_put_substitution(ScrJsonBuf *b, const ScrRegexInput *input,
632
670
  int start, int end, uint8_t **capture,
633
671
  int capture_count, const char *groupnames,
634
672
  const ScrStr *rep) {
635
- const uint8_t *ubase = (const uint8_t *)u;
636
673
  size_t i = 0;
637
674
  while (i < rep->len) {
638
675
  char c = rep->data[i];
639
676
  if (c != '$' || i + 1 >= rep->len) {
640
- scr_jb_putc(b, c);
641
- i++;
677
+ const char *next = memchr(rep->data + i + 1, '$', rep->len - i - 1);
678
+ size_t end = next ? (size_t)(next - rep->data) : rep->len;
679
+ scr_jb_write(b, rep->data + i, end - i);
680
+ i = end;
642
681
  continue;
643
682
  }
644
683
  char c1 = rep->data[i + 1];
@@ -646,13 +685,13 @@ static void scr_put_substitution(ScrJsonBuf *b, const uint16_t *u, int len,
646
685
  scr_jb_putc(b, '$');
647
686
  i += 2;
648
687
  } else if (c1 == '&') {
649
- scr_jb_put_utf16(b, u, start, end);
688
+ scr_regex_put_span(b, input, start, end);
650
689
  i += 2;
651
690
  } else if (c1 == '`') {
652
- scr_jb_put_utf16(b, u, 0, start);
691
+ scr_regex_put_span(b, input, 0, start);
653
692
  i += 2;
654
693
  } else if (c1 == '\'') {
655
- scr_jb_put_utf16(b, u, end, len);
694
+ scr_regex_put_span(b, input, end, input->len);
656
695
  i += 2;
657
696
  } else if (c1 >= '0' && c1 <= '9') {
658
697
  int k = c1 - '0';
@@ -667,7 +706,7 @@ static void scr_put_substitution(ScrJsonBuf *b, const uint16_t *u, int len,
667
706
  if (k >= 1 && k < capture_count) {
668
707
  const uint8_t *cs = capture[2 * k], *ce = capture[2 * k + 1];
669
708
  if (cs && ce) {
670
- scr_jb_put_utf16(b, u, (int)((cs - ubase) >> 1), (int)((ce - ubase) >> 1));
709
+ scr_regex_put_span(b, input, scr_capture_index(input, cs), scr_capture_index(input, ce));
671
710
  }
672
711
  i += adv;
673
712
  } else {
@@ -693,7 +732,7 @@ static void scr_put_substitution(ScrJsonBuf *b, const uint16_t *u, int len,
693
732
  if (glen == name_len && memcmp(p, name, name_len) == 0) {
694
733
  const uint8_t *cs = capture[2 * k], *ce = capture[2 * k + 1];
695
734
  if (cs && ce) {
696
- scr_jb_put_utf16(b, u, (int)((cs - ubase) >> 1), (int)((ce - ubase) >> 1));
735
+ scr_regex_put_span(b, input, scr_capture_index(input, cs), scr_capture_index(input, ce));
697
736
  break; /* at most one duplicate participates */
698
737
  }
699
738
  }
@@ -718,31 +757,34 @@ static ScrStr *scr_replace_impl(ScrStr *s, ScrRegex *re, ScrStr *rep) {
718
757
  bool unicode = (re_flags & (LRE_FLAG_UNICODE | LRE_FLAG_UNICODE_SETS)) != 0;
719
758
  int capture_count = lre_get_capture_count(bc);
720
759
  const char *groupnames = lre_get_groupnames(bc);
721
- int len;
722
- uint16_t *u = scr_to_utf16(s, &len);
723
- uint8_t **capture = scr_capture_alloc(bc);
724
- const uint8_t *ubase = (const uint8_t *)u;
760
+ ScrRegexInput input;
761
+ scr_regex_input_init(&input, bc, s);
762
+ int len = input.len;
763
+ uint8_t **capture = input.capture;
725
764
  ScrJsonBuf b;
726
765
  scr_jb_init(&b);
727
766
  if (global) re->last_index = 0;
728
767
  int next = 0, pos = sticky ? scr_regex_start(re, len) : 0;
729
768
  if (pos < 0) pos = len + 1;
730
769
  if (sticky && !global) re->last_index = 0;
770
+ bool matched = false;
771
+ bool literal = memchr(rep->data, '$', rep->len) == NULL;
731
772
  while (pos <= len) {
732
- if (scr_exec(capture, bc, u, pos, len) != 1) break;
733
- int start = (int)((capture[0] - ubase) >> 1);
734
- int end = (int)((capture[1] - ubase) >> 1);
773
+ if (scr_exec(&input, bc, pos) != 1) break;
774
+ matched = true;
775
+ int start = scr_capture_index(&input, capture[0]);
776
+ int end = scr_capture_index(&input, capture[1]);
735
777
  if (sticky && !global) re->last_index = end;
736
- scr_jb_put_utf16(&b, u, next, start);
737
- scr_put_substitution(&b, u, len, start, end, capture, capture_count, groupnames, rep);
778
+ scr_regex_put_span(&b, &input, next, start);
779
+ if (literal) scr_jb_put_str(&b, rep);
780
+ else scr_put_substitution(&b, &input, start, end, capture, capture_count, groupnames, rep);
738
781
  next = end;
739
782
  if (!global) break;
740
- pos = end == start ? scr_advance(u, len, end, unicode) : end;
783
+ pos = end == start ? scr_advance(&input, end, unicode) : end;
741
784
  }
742
- scr_jb_put_utf16(&b, u, next, len);
743
- free(capture);
744
- free(u);
745
- return scr_jb_finish(&b);
785
+ if (matched) scr_regex_put_span(&b, &input, next, len);
786
+ scr_regex_input_dispose(&input);
787
+ return matched ? scr_jb_finish(&b) : scr_str_retain(s);
746
788
  }
747
789
 
748
790
  ScrStr *scr_regex_replace(ScrStr *s, ScrRegex *re, ScrStr *rep) {
@@ -825,10 +867,10 @@ ScrArr *scr_regex_split_limit(ScrStr *s, ScrRegex *re, double limit_num) {
825
867
  int re_flags = lre_get_flags(bc);
826
868
  bool unicode = (re_flags & (LRE_FLAG_UNICODE | LRE_FLAG_UNICODE_SETS)) != 0;
827
869
  bool sticky = (re_flags & LRE_FLAG_STICKY) != 0;
828
- int len;
829
- uint16_t *u = scr_to_utf16(s, &len);
830
- uint8_t **capture = scr_capture_alloc(bc);
831
- const uint8_t *ubase = (const uint8_t *)u;
870
+ ScrRegexInput input;
871
+ scr_regex_input_init(&input, bc, s);
872
+ int len = input.len;
873
+ uint8_t **capture = input.capture;
832
874
  ScrArr *out = scr_arr_new(SCR_ELEM_STR, 0);
833
875
 
834
876
  /* ECMA-262 22.2.6.14 (Symbol.split). The spec probes every
@@ -838,31 +880,30 @@ ScrArr *scr_regex_split_limit(ScrStr *s, ScrRegex *re, double limit_num) {
838
880
  * loop. A genuinely sticky pattern keeps the spec's probe-and-advance. */
839
881
  if (len == 0) {
840
882
  /* Empty subject: one probe — a match yields [], no match yields [""]. */
841
- if (scr_exec(capture, bc, u, 0, len) != 1) {
883
+ if (scr_exec(&input, bc, 0) != 1) {
842
884
  scr_arr_push_ref(out, scr_str_new("", 0));
843
885
  }
844
- free(capture);
845
- free(u);
886
+ scr_regex_input_dispose(&input);
846
887
  return out;
847
888
  }
848
889
  int p = 0, q = 0;
849
890
  while (q < len) {
850
- if (scr_exec(capture, bc, u, q, len) != 1) {
891
+ if (scr_exec(&input, bc, q) != 1) {
851
892
  if (!sticky) break; /* no match anywhere right of q */
852
- q = scr_advance(u, len, q, unicode);
893
+ q = scr_advance(&input, q, unicode);
853
894
  continue;
854
895
  }
855
- int start = (int)((capture[0] - ubase) >> 1);
856
- int end = (int)((capture[1] - ubase) >> 1);
896
+ int start = scr_capture_index(&input, capture[0]);
897
+ int end = scr_capture_index(&input, capture[1]);
857
898
  /* The sticky algorithm never probes the position after the subject.
858
899
  * A forward search can find a zero-width match there; exclude it. */
859
900
  if (start == len) break;
860
901
  if (end == p) {
861
902
  /* Zero-length match adjacent to the previous split point: advance
862
903
  * (start == q == p here — the search cannot skip a match). */
863
- q = scr_advance(u, len, start, unicode);
904
+ q = scr_advance(&input, start, unicode);
864
905
  } else {
865
- scr_arr_push_ref(out, scr_str_from_utf16(u, p, start));
906
+ scr_arr_push_ref(out, scr_regex_span(&input, p, start));
866
907
  if (out->len == limit) goto done;
867
908
  for (int group = 1; group < capture_count; group++) {
868
909
  const uint8_t *from = capture[2 * group];
@@ -872,8 +913,8 @@ ScrArr *scr_regex_split_limit(ScrStr *s, ScrRegex *re, double limit_num) {
872
913
  // distinct from both empty strings and holes.
873
914
  scr_arr_set_undefined(out, (double)out->len);
874
915
  } else {
875
- scr_arr_push_ref(out, scr_str_from_utf16(u,
876
- (int)((from - ubase) >> 1), (int)((to - ubase) >> 1)));
916
+ scr_arr_push_ref(out, scr_regex_span(&input,
917
+ scr_capture_index(&input, from), scr_capture_index(&input, to)));
877
918
  }
878
919
  if (out->len == limit) goto done;
879
920
  }
@@ -881,10 +922,9 @@ ScrArr *scr_regex_split_limit(ScrStr *s, ScrRegex *re, double limit_num) {
881
922
  q = p;
882
923
  }
883
924
  }
884
- scr_arr_push_ref(out, scr_str_from_utf16(u, p, len));
925
+ scr_arr_push_ref(out, scr_regex_span(&input, p, len));
885
926
  done:
886
- free(capture);
887
- free(u);
927
+ scr_regex_input_dispose(&input);
888
928
  return out;
889
929
  }
890
930
 
@@ -985,10 +1025,38 @@ static void scr_case_put_cp(ScrJsonBuf *b, uint32_t cp) {
985
1025
  }
986
1026
 
987
1027
  static ScrStr *scr_str_case_conv(const ScrStr *s, int to_lower) {
1028
+ unsigned char first = to_lower ? 'A' : 'a';
1029
+ unsigned char last = to_lower ? 'Z' : 'z';
1030
+ if (scr_text_ascii(s)) {
1031
+ size_t changed = 0;
1032
+ while (changed < s->len &&
1033
+ ((unsigned char)s->data[changed] < first || (unsigned char)s->data[changed] > last))
1034
+ changed++;
1035
+ if (changed == s->len) return scr_str_retain((ScrStr *)s);
1036
+ ScrStr *out = scr_str_alloc_raw(s->len, s->len);
1037
+ memcpy(out->data, s->data, changed);
1038
+ for (size_t i = changed; i < s->len; i++) {
1039
+ unsigned char c = (unsigned char)s->data[i];
1040
+ out->data[i] = (char)(c >= first && c <= last ? c ^ 0x20 : c);
1041
+ }
1042
+ out->data[s->len] = '\0';
1043
+ return out;
1044
+ }
988
1045
  ScrJsonBuf b;
989
1046
  scr_jb_init(&b);
990
1047
  uint32_t res[LRE_CC_RES_LEN_MAX];
991
1048
  for (size_t i = 0; i < s->len;) {
1049
+ if ((unsigned char)s->data[i] < 0x80) {
1050
+ size_t start = i;
1051
+ while (i < s->len && (unsigned char)s->data[i] < 0x80) i++;
1052
+ size_t output = b.len;
1053
+ scr_jb_write(&b, s->data + start, i - start);
1054
+ for (size_t j = output; j < b.len; j++) {
1055
+ unsigned char c = (unsigned char)b.data[j];
1056
+ if (c >= first && c <= last) b.data[j] = (char)(c ^ 0x20);
1057
+ }
1058
+ continue;
1059
+ }
992
1060
  size_t adv;
993
1061
  uint32_t c = scr_case_decode(s->data + i, &adv);
994
1062
  int l;
@@ -1024,6 +1092,9 @@ ScrStr *scr_str_normalize(const ScrStr *s, const ScrStr *form) {
1024
1092
  scr_throw_error(SCR_ERR_RANGE, scr_str_new(message, sizeof message - 1));
1025
1093
  return NULL;
1026
1094
  }
1095
+ /* Every ASCII string is already normalized in all four forms. Validate
1096
+ * the form first: even an empty subject must reject an invalid form. */
1097
+ if (scr_text_ascii(s)) return scr_str_retain((ScrStr *)s);
1027
1098
  if (s->len > INT32_MAX / sizeof(uint32_t)) scr_regex_oom();
1028
1099
  uint32_t *input = malloc((s->len + 1) * sizeof(uint32_t));
1029
1100
  if (!input) scr_regex_oom();