@scriptc/runtime 0.0.22 → 0.0.24

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_number.c CHANGED
@@ -24,6 +24,15 @@
24
24
  * unchanged. Provides d2d(), d2d_small_int(), decimalLength17(), div10(). */
25
25
  #include "../vendor/ryu/d2s.c"
26
26
 
27
+ /* ECMA ToUint32, shared by bitwise operators and split's limit: non-finite
28
+ * values become zero, finite values truncate toward zero and wrap mod 2^32. */
29
+ uint32_t scr_to_uint32(double d) {
30
+ if (!isfinite(d)) return 0;
31
+ double t = fmod(trunc(d), 4294967296.0);
32
+ if (t < 0) t += 4294967296.0;
33
+ return (uint32_t)t;
34
+ }
35
+
27
36
  /* The Ryū digit core, shared by the ECMA placement below and the Intl
28
37
  * en-US number formatter (scr_lib.c): the shortest round-tripping digit
29
38
  * string for a positive finite double — value = 0.digits × 10^n with no
package/src/scr_regex.c CHANGED
@@ -624,8 +624,10 @@ ScrStr *scr_regex_replace_all(ScrStr *s, ScrRegex *re, ScrStr *rep) {
624
624
 
625
625
  /* ── split ────────────────────────────────────────────────────────────── */
626
626
 
627
- ScrArr *scr_regex_split(ScrStr *s, ScrRegex *re) {
627
+ ScrArr *scr_regex_split_limit(ScrStr *s, ScrRegex *re, double limit_num) {
628
628
  uint8_t *bc = scr_regex_bc(re);
629
+ uint32_t limit = scr_to_uint32(limit_num);
630
+ if (limit == 0) return scr_arr_new(SCR_ELEM_STR, 0);
629
631
  if (lre_get_capture_count(bc) > 1) {
630
632
  /* JS splices every capture group's value into the result between the
631
633
  * pieces, changing the array's SHAPE per match — not modeled this
@@ -675,6 +677,11 @@ ScrArr *scr_regex_split(ScrStr *s, ScrRegex *re) {
675
677
  q = scr_advance(u, len, start, unicode);
676
678
  } else {
677
679
  scr_arr_push_ref(out, scr_str_from_utf16(u, p, start));
680
+ if (out->len == limit) {
681
+ free(capture);
682
+ free(u);
683
+ return out;
684
+ }
678
685
  p = end;
679
686
  q = p;
680
687
  }
@@ -685,6 +692,10 @@ ScrArr *scr_regex_split(ScrStr *s, ScrRegex *re) {
685
692
  return out;
686
693
  }
687
694
 
695
+ ScrArr *scr_regex_split(ScrStr *s, ScrRegex *re) {
696
+ return scr_regex_split_limit(s, re, 4294967295.0);
697
+ }
698
+
688
699
  /* ── String.prototype.toLowerCase / toUpperCase (the static path) ──────
689
700
  * ECMA-262 Default Case Conversion via the vendored libunicode's
690
701
  * lre_case_conv — the exact tables and algorithm the engine's own
package/src/scr_runtime.h CHANGED
@@ -197,9 +197,15 @@ void scr_library_bytes_out(ScrBytes *b, const uint8_t **out, size_t *out_len);
197
197
  * collection walks the buffer (markGray: trial-decrement internal edges;
198
198
  * scan: restore externally-referenced subgraphs; collectWhite: free the
199
199
  * dead cycle members, releasing only edges that LEAVE the white set).
200
- * Collection points: program exit (before the RC audit), event-loop
201
- * quiescence, and a root-buffer threshold (SCR_CYCLE_THRESHOLD env var,
202
- * default 256). There is no concurrent or incremental collection.
200
+ * It is GENERATIONAL: each header carries a generation, a pass names the
201
+ * oldest one it will walk, and objects that survive a pass are promoted out
202
+ * of the nursery so later nursery passes never re-walk them. That is what
203
+ * keeps a pass proportional to recent allocation rather than to the whole
204
+ * live heap — see the generation note in scr_cycle.c for the soundness
205
+ * argument and the schedule. Collection points: program exit (before the RC
206
+ * audit), event-loop quiescence, and the per-generation triggers.
207
+ * SCR_CYCLE_THRESHOLD pins the nursery trigger to a fixed candidate count.
208
+ * There is no concurrent or incremental collection.
203
209
  *
204
210
  * Contract for trace/teardown pairs (the compiler emits them for shapes,
205
211
  * the runtime owns its own): trace(obj) visits exactly the strong
@@ -214,16 +220,44 @@ typedef void (*ScrTraceVisit)(void *child, void *ctx);
214
220
  typedef void (*ScrTraceFn)(void *obj, ScrTraceVisit visit, void *ctx);
215
221
  typedef void (*ScrCycFreeFn)(void *obj);
216
222
 
217
- enum { SCR_CYC_BLACK = 0, SCR_CYC_PURPLE = 1, SCR_CYC_GRAY = 2, SCR_CYC_WHITE = 3 };
223
+ /* DOOMED is WHITE that collectWhite has already gathered: it stops the
224
+ * gather recursing twice, and distinguishes "about to be freed" from a
225
+ * survivor for the re-buffering step (see scr_cycle.c). */
226
+ enum {
227
+ SCR_CYC_BLACK = 0, SCR_CYC_PURPLE = 1, SCR_CYC_GRAY = 2, SCR_CYC_WHITE = 3,
228
+ SCR_CYC_DOOMED = 4
229
+ };
230
+
231
+ /* Generations. A candidate sits in the buffer named by its own `gen`, and a
232
+ * pass walks only objects at or below the generation it collects. */
233
+ enum { SCR_CYC_NURSERY = 0, SCR_CYC_MATURE = 1, SCR_CYC_NGENS = 2 };
218
234
 
219
235
  typedef struct ScrCycHdr {
220
236
  ScrTraceFn trace;
221
237
  ScrCycFreeFn free_fn;
222
238
  uint32_t color; /* SCR_CYC_* */
223
- uint32_t buffered; /* 1 = sitting in the candidate-root buffer */
239
+ uint16_t buffered; /* 1 = sitting in its generation's candidate buffer */
240
+ uint16_t gen; /* SCR_CYC_NURSERY..SCR_CYC_MATURE (the walk filter) */
224
241
  size_t buf_index; /* position there (O(1) removal when rc hits 0) */
225
242
  } ScrCycHdr;
226
243
 
244
+ /* This layout is an ABI, not an implementation detail: the LLVM backend
245
+ * inlines scr_cyc_mark_live as a raw `store i32 0` at obj-16 (it is a
246
+ * static inline here, so there is no symbol to call) and reaches the header
247
+ * at obj-32. Three sites emit it — llvm/shapes.ts, llvm/classes.ts,
248
+ * llvm/emitter.ts. Nothing but `color` may share those four bytes: a field
249
+ * placed in them is silently zeroed by every retain, which is invisible to
250
+ * the type system and to the C compiler. Hence the assertions. */
251
+ _Static_assert(sizeof(ScrCycHdr) == 32, "LLVM backend reads the header at obj-32");
252
+ _Static_assert(offsetof(ScrCycHdr, color) == 16,
253
+ "LLVM backend's inlined mark-live stores i32 0 at obj-16");
254
+ _Static_assert(sizeof(((ScrCycHdr *)0)->color) == 4,
255
+ "mark-live is an i32 store: color must own all four bytes");
256
+ _Static_assert(SCR_CYC_BLACK == 0,
257
+ "the emitted mark-live stores the LITERAL 0, not the enumerator "
258
+ "— reordering the colors would make every compiled retain write "
259
+ "the wrong one");
260
+
227
261
  static inline ScrCycHdr *scr_cyc_hdr(void *obj) { return (ScrCycHdr *)obj - 1; }
228
262
 
229
263
  /* Zeroed allocation with a cycle header in front; returns the OBJECT
@@ -243,9 +277,18 @@ static inline void scr_cyc_mark_live(void *obj) {
243
277
  scr_cyc_hdr(obj)->color = SCR_CYC_BLACK;
244
278
  }
245
279
 
246
- /* Run one full trial-deletion pass over the buffered candidates now. */
280
+ /* Full sweep: trial-deletion passes over EVERY generation, to a fixpoint.
281
+ * This is the exit / session-reset entry point (the RC audit runs straight
282
+ * after and wants nothing reclaimable left), and it costs a walk of the
283
+ * live heap — do not put it on a per-turn path. */
247
284
  void scr_collect_cycles(void);
248
285
 
286
+ /* One pass on the normal generational schedule, for callers that reach a
287
+ * natural collection point rather than a threshold (the event loop between
288
+ * turns). Cheap: usually a nursery pass; a waiting mature backlog ages into
289
+ * a bounded full pass so sparse roots cannot float forever. */
290
+ void scr_cyc_collect_scheduled(void);
291
+
249
292
  /* ── class hierarchies (single inheritance) ───────────────────────────
250
293
  * Classes in an `extends` hierarchy share a two-word object prefix: the
251
294
  * usual `size_t rc`, then a pointer to the class's static vtable (emitted
@@ -569,12 +612,14 @@ ScrStr *scr_str_cp_at(ScrStr *s, double i);
569
612
  ScrStr *scr_str_trim_start(ScrStr *s);
570
613
  ScrStr *scr_str_trim_end(ScrStr *s);
571
614
 
572
- /* split(separator) with a STRING separator, no limit: empty separator
615
+ /* split(separator[, limit]) with a STRING separator: limit uses ToUint32;
616
+ * the no-limit wrapper supplies 2^32-1. Empty separator
573
617
  * splits into single UTF-16 code units (each half of an astral char is
574
618
  * U+FFFD — divergence, see above); otherwise splits on every occurrence,
575
619
  * empty pieces kept. Borrows both; returns a +1 string[] (SCR_ELEM_STR). */
576
620
  struct ScrArr;
577
621
  struct ScrArr *scr_str_split(ScrStr *s, ScrStr *sep);
622
+ struct ScrArr *scr_str_split_limit(ScrStr *s, ScrStr *sep, double limit);
578
623
 
579
624
  /* padStart(maxLength, fill)/padEnd — ECMA StringPad, UTF-16 unit counts.
580
625
  * Target at or below the length (or empty fill) returns the receiver
@@ -677,6 +722,12 @@ void scr_qs_parse_into(ScrMap *out, const ScrStr *qs, const ScrStr *sep,
677
722
  const ScrStr *eq, double max_keys, uint32_t str_tag,
678
723
  uint32_t arr_tag);
679
724
 
725
+ /* node:util.parseArgs — the static checked-dynamic boundary. `config` is
726
+ * the documented JSON-safe configuration object; the returned dyn tree is
727
+ * a fresh ParsedResults object whose `values` child has null prototype.
728
+ * Borrows config; returns +1, or NULL with a Node-coded TypeError pending. */
729
+ struct ScrDyn *scr_util_parse_args(const struct ScrDyn *config);
730
+
680
731
  /* querystring.stringify — Node's stringify over a borrowed dyn value (the
681
732
  * frontend dynFroms the typed record; JS-world dyn values pass straight
682
733
  * through). Non-object dyn values answer "" like Node; object keys iterate in
@@ -837,6 +888,15 @@ double scr_arr_push_f64(ScrArr *a, double v);
837
888
  double scr_arr_push_bool(ScrArr *a, bool v);
838
889
  double scr_arr_push_ref(ScrArr *a, void *v);
839
890
 
891
+ /* unshift returns the new length and _ref takes ownership. The spread form
892
+ * borrows a same-element-kind source, retains copied refs, and snapshots
893
+ * self-spread. reverse mutates in place and returns the receiver at +1. */
894
+ double scr_arr_unshift_f64(ScrArr *a, double v);
895
+ double scr_arr_unshift_bool(ScrArr *a, bool v);
896
+ double scr_arr_unshift_ref(ScrArr *a, void *v);
897
+ double scr_arr_unshift_spread(ScrArr *a, const ScrArr *src);
898
+ ScrArr *scr_arr_reverse(ScrArr *a);
899
+
840
900
  /* pop traps on an empty array; _ref transfers ownership out (+1 to the
841
901
  * caller, no release). */
842
902
  double scr_arr_pop_f64(ScrArr *a);
@@ -940,8 +1000,10 @@ ScrStr *scr_regex_flags(ScrRegex *re); /* +1 */
940
1000
  ScrStr *scr_regex_replace(ScrStr *s, ScrRegex *re, ScrStr *rep);
941
1001
  /* replaceAll: throws Node's TypeError when /g is missing (may-throw). */
942
1002
  ScrStr *scr_regex_replace_all(ScrStr *s, ScrRegex *re, ScrStr *rep);
943
- /* split: capture-free patterns only — capture groups throw (may-throw). */
1003
+ /* split: capture-free patterns only — capture groups throw (may-throw).
1004
+ * limit uses ToUint32; the no-limit wrapper supplies 2^32-1. */
944
1005
  ScrArr *scr_regex_split(ScrStr *s, ScrRegex *re);
1006
+ ScrArr *scr_regex_split_limit(ScrStr *s, ScrRegex *re, double limit);
945
1007
  /* matchAll drained eagerly: +1 string[][] of honest match slices; throws
946
1008
  * Node's TypeError on a non-global regex (catchable). */
947
1009
  ScrArr *scr_regex_match_all(ScrStr *s, ScrRegex *re);
@@ -2004,6 +2066,11 @@ void scr_fs_chmod(ScrStr *path, double mode);
2004
2066
  void scr_fs_chown(ScrStr *path, double uid, double gid);
2005
2067
  void scr_fs_copyfile(ScrStr *src, ScrStr *dest);
2006
2068
  void scr_fs_rename(ScrStr *oldpath, ScrStr *newpath);
2069
+ /* The callback rename worker uses the non-throwing syscall seam off the
2070
+ * runtime thread, then materializes any Node-shaped Error back on the main
2071
+ * thread. raw returns 0 on success or a positive errno value on failure. */
2072
+ int scr_fs_rename_raw(const ScrStr *oldpath, const ScrStr *newpath);
2073
+ void scr_fs_rename_error(int error, const ScrStr *oldpath, const ScrStr *newpath);
2007
2074
  void scr_fs_write_file_mode(ScrStr *path, ScrStr *data, double mode);
2008
2075
  void scr_fs_mkdir_mode(ScrStr *path, double mode);
2009
2076
  void scr_fs_mkdir_recursive_mode(ScrStr *path, double mode);
@@ -2036,6 +2103,7 @@ double scr_process_columns(double fd);
2036
2103
  * stat(2) — follows symlinks, like Node's stat family. scr_fs_stat
2037
2104
  * THROWS like the other sync calls. */
2038
2105
  typedef struct ScrStats ScrStats;
2106
+ typedef struct ScrFileHandle ScrFileHandle;
2039
2107
  typedef struct ScrPromise ScrPromise; /* full section further down */
2040
2108
 
2041
2109
  ScrStats *scr_fs_stat(ScrStr *path); /* +1, or throws */
@@ -2048,6 +2116,9 @@ bool scr_stats_is_file(ScrStats *s);
2048
2116
  bool scr_stats_is_dir(ScrStats *s);
2049
2117
  bool scr_stats_is_symlink(ScrStats *s); /* lstat snapshots only */
2050
2118
  double scr_stats_size(ScrStats *s);
2119
+ double scr_stats_blocks(ScrStats *s); /* allocated size in 512-byte units */
2120
+ double scr_stats_nlink(ScrStats *s);
2121
+ double scr_stats_atime_ms(ScrStats *s); /* ms with the sub-second fraction */
2051
2122
  double scr_stats_mtime_ms(ScrStats *s); /* ms with the ns fraction */
2052
2123
 
2053
2124
  /* fs/promises: the SAME sync operations, minting an already-settled
@@ -2058,6 +2129,7 @@ double scr_stats_mtime_ms(ScrStats *s); /* ms with the ns fraction */
2058
2129
  * scr_lib.c without the fiber slice). */
2059
2130
  ScrPromise *scr_fsp_read_file(ScrStr *path);
2060
2131
  ScrPromise *scr_fsp_write_file(ScrStr *path, ScrStr *data);
2132
+ ScrPromise *scr_fsp_write_file_mode(ScrStr *path, ScrStr *data, double mode);
2061
2133
  ScrPromise *scr_fsp_mkdir(ScrStr *path);
2062
2134
  ScrPromise *scr_fsp_mkdir_mode(ScrStr *path, double mode);
2063
2135
  ScrPromise *scr_fsp_mkdir_recursive(ScrStr *path);
@@ -2067,6 +2139,24 @@ ScrPromise *scr_fsp_chmod(ScrStr *path, double mode);
2067
2139
  ScrPromise *scr_fsp_readdir(ScrStr *path);
2068
2140
  ScrPromise *scr_fsp_rm(ScrStr *path);
2069
2141
  ScrPromise *scr_fsp_stat(ScrStr *path);
2142
+ ScrPromise *scr_fsp_rename(ScrStr *oldpath, ScrStr *newpath);
2143
+ ScrPromise *scr_fsp_open(ScrStr *path, ScrStr *flags, double mode);
2144
+ ScrPromise *scr_file_handle_close_promise(ScrFileHandle *h);
2145
+ ScrPromise *scr_file_handle_read_file_promise(ScrFileHandle *h, ScrStr *encoding);
2146
+ ScrPromise *scr_file_handle_read_file_bytes_promise(ScrFileHandle *h, ScrStr *encoding);
2147
+ ScrPromise *scr_file_handle_write_file_promise(ScrFileHandle *h, ScrStr *data, ScrStr *encoding);
2148
+ ScrPromise *scr_file_handle_write_file_bytes_promise(ScrFileHandle *h, ScrBytes *data, ScrStr *encoding);
2149
+ ScrPromise *scr_file_handle_stat_promise(ScrFileHandle *h);
2150
+
2151
+ /* fs.rename: the syscall is submitted to a native worker immediately and
2152
+ * its error-first callback fires on a later event-loop turn through an
2153
+ * emitted, program-shaped adapter. Both paths and the callback are borrowed
2154
+ * at entry; the callback MOVES into the operation. err is borrowed and NULL
2155
+ * on success. */
2156
+ typedef void (*ScrFsRenameFn)(ScrClosure *cb, ScrError *err);
2157
+ void scr_fs_rename_async(ScrStr *oldpath, ScrStr *newpath,
2158
+ ScrClosure *cb /*moves*/, ScrFsRenameFn fn);
2159
+ void scr_fs_rename_thunk0(ScrClosure *cb, ScrError *err);
2070
2160
 
2071
2161
  /* ── node:timers/promises (scr_async.c) ──────────────────────────────
2072
2162
  * The promisified pair: a PENDING void promise a one-shot heap timer /
@@ -2416,7 +2506,36 @@ ScrStr *scr_path_win32_to_namespaced_path(ScrStr *path);
2416
2506
  * (failure throws the path-less "EBADF: bad file descriptor, close").
2417
2507
  * The pair behind spawn's fd-stdio form. */
2418
2508
  double scr_fs_open(ScrStr *path, ScrStr *flags);
2419
- double scr_fs_read_sync(double fd, ScrBytes *buf, double offset, double length);
2509
+ ScrFileHandle *scr_file_handle_open(ScrStr *path, ScrStr *flags, double mode);
2510
+ ScrFileHandle *scr_file_handle_retain(ScrFileHandle *h);
2511
+ void scr_file_handle_release(ScrFileHandle *h);
2512
+ void *scr_file_handle_retain_v(void *p);
2513
+ void scr_file_handle_release_v(void *p);
2514
+ double scr_file_handle_fd(ScrFileHandle *h);
2515
+ void scr_file_handle_close(ScrFileHandle *h);
2516
+ double scr_file_handle_read(ScrFileHandle *h, ScrBytes *buf, double offset,
2517
+ double length, double position, bool length_default);
2518
+ double scr_file_handle_write_bytes(ScrFileHandle *h, ScrBytes *buf,
2519
+ double offset, double length,
2520
+ double position, bool length_default);
2521
+ double scr_file_handle_write_str(ScrFileHandle *h, ScrStr *data,
2522
+ double position, ScrStr *encoding);
2523
+ ScrStr *scr_file_handle_read_file(ScrFileHandle *h);
2524
+ ScrBytes *scr_file_handle_read_file_bytes(ScrFileHandle *h);
2525
+ void scr_file_handle_write_file(ScrFileHandle *h, ScrStr *data);
2526
+ void scr_file_handle_write_file_bytes(ScrFileHandle *h, ScrBytes *data);
2527
+ ScrStats *scr_file_handle_stat(ScrFileHandle *h);
2528
+ /* position == -1 reads from and advances the descriptor's current offset;
2529
+ * nonnegative positions leave that offset unchanged (pread/ReadFile seam). */
2530
+ double scr_fs_read_sync(double fd, ScrBytes *buf, double offset, double length,
2531
+ double position);
2532
+ /* writeSync's classic Buffer window and utf8 string forms. position -1 (or
2533
+ * any other non-safe/nonnegative-integer value) writes at and advances the
2534
+ * current descriptor offset; safe nonnegative positions leave it unchanged. */
2535
+ double scr_fs_write_sync(double fd, ScrBytes *buf, double offset, double length,
2536
+ double position);
2537
+ double scr_fs_write_str_sync(double fd, ScrStr *data, double position,
2538
+ ScrStr *encoding);
2420
2539
  void scr_fs_close(double fd);
2421
2540
 
2422
2541
  /* ── WHATWG URL (scr_url.c) ──────────────────────────────────────────
@@ -2714,6 +2833,7 @@ typedef enum {
2714
2833
  SCR_DYNH_FETCH_RESPONSE, /* native static-fetch Response */
2715
2834
  SCR_DYNH_FETCH_HEADERS, /* native static-fetch response Headers */
2716
2835
  SCR_DYNH_EVENT, /* native static-fetch abort Event */
2836
+ SCR_DYNH_ABORT_CONTROLLER, /* native static-fetch AbortController */
2717
2837
  SCR_DYNH_COUNT,
2718
2838
  } ScrDynHandleTag;
2719
2839
 
@@ -2980,6 +3100,7 @@ ScrStr *scr_dyn_string_coerce(const ScrDyn *d);
2980
3100
  * called, their throws propagating) — the WHATWG USVString conversions.
2981
3101
  * Borrows; +1 or NULL with the exception pending. */
2982
3102
  ScrStr *scr_dyn_string_coerce_js(const ScrDyn *d);
3103
+ bool scr_dyn_number_coerce_js(const ScrDyn *d, double *out);
2983
3104
 
2984
3105
  /* `d instanceof TypeError` (and the other builtin error classes) on a
2985
3106
  * checked-dynamic value: the from_error cache resolves the dyn encoding
@@ -3349,6 +3470,8 @@ typedef struct {
3349
3470
  } ScrJsonBuf;
3350
3471
 
3351
3472
  void scr_jb_init(ScrJsonBuf *b);
3473
+ /* Append one borrowed runtime string verbatim (no JSON quoting). */
3474
+ void scr_jb_put_str(ScrJsonBuf *b, const ScrStr *s);
3352
3475
  /* Push a container onto the circular-detection stack before serializing
3353
3476
  * its members. If `v` is already ON the stack, throws V8's exact
3354
3477
  * "Converting circular structure to JSON" TypeError (the --> starting at /
@@ -3664,6 +3787,10 @@ bool scr_immediate_has_ref(double handle);
3664
3787
  * teardown (they must never run yet must not leak). cb ownership moves
3665
3788
  * in. */
3666
3789
  void scr_next_tick(ScrClosure *cb);
3790
+ /* A successful process.stdout/stderr.write completion on that same queue.
3791
+ * The callback and its program-shaped Error | null adapter both MOVE into
3792
+ * the entry; the adapter is invoked with NULL (Node's success argument). */
3793
+ void scr_process_write_callback(ScrClosure *cb, ScrFsRenameFn fn);
3667
3794
  /* A raw C-hook entry on the SAME queue: the stream unit enqueues one
3668
3795
  * marker per deferred stream emission, so stream ticks and user
3669
3796
  * nextTicks run in true FIFO order (in Node they are the same queue).
@@ -3907,9 +4034,11 @@ long scr_promise_live_count(void);
3907
4034
  * emitted main calls scr_fetch_install in either form. */
3908
4035
  void scr_fetch_install(void);
3909
4036
  ScrPromise *scr_fetch_static(ScrStr *url, ScrDyn *init); /* +1 promise<Response handle> */
4037
+ ScrDyn *scr_fetch_response_new(ScrDyn *body, ScrDyn *init); /* borrowed args; +1 Response handle or NULL pending */
3910
4038
  ScrPromise *scr_fetch_response_json(ScrDyn *response); /* +1 promise<dyn> */
3911
4039
  ScrPromise *scr_fetch_response_text(ScrDyn *response); /* +1 promise<dyn> */
3912
4040
  ScrPromise *scr_fetch_response_bytes(ScrDyn *response); /* +1 promise<dyn> */
4041
+ ScrDyn *scr_fetch_abort_controller_new(void); /* +1 AbortController handle */
3913
4042
  /* Borrowed number; +1 AbortSignal handle or NULL pending. */
3914
4043
  ScrDyn *scr_fetch_abort_timeout(ScrDyn *delay);
3915
4044
  ScrDyn *scr_fetch_abort_now(ScrDyn *reason); /* borrowed reason; +1 handle */
@@ -4398,10 +4527,13 @@ bool scr_num_same_value(double a, double b);
4398
4527
  * en-US is the one embedded locale. Result +1; never throws. */
4399
4528
  ScrStr *scr_intl_num_format_en_us(double x);
4400
4529
 
4401
- /* ── Date, the composed slice (scr_lib.c) ─────────────────────────────
4402
- * Date values have no representation; the runtime surface is exactly
4403
- * Date.now() and toISOString over a millisecond time value. */
4530
+ /* ── Date, the read-only value slice (scr_lib.c) ──────────────────────
4531
+ * A Date value is its TimeClip'd epoch-millisecond scalar. Identity and
4532
+ * mutation are frontend-fenced; construction, storage, getters, and ISO
4533
+ * formatting are exact over this representation. */
4404
4534
  double scr_date_now(void); /* integer ms since epoch, like Node */
4535
+ double scr_date_new_ms(double ms); /* TimeClip (NaN when invalid) */
4536
+ double scr_date_get_time(double ms);
4405
4537
  /* Node's exact ISO 8601 UTC format (expanded ±YYYYYY years outside
4406
4538
  * 0–9999). THROWS Node's "Invalid time value" RangeError on NaN or
4407
4539
  * |ms| > 8.64e15 and returns NULL; +1 otherwise. */
@@ -4415,13 +4547,39 @@ double scr_date_parse_get_time(ScrStr *s);
4415
4547
  * and years past V8's ±1e6 MakeDay bound. Never throws. */
4416
4548
  double scr_date_utc(double y, double mo, double d,
4417
4549
  double h, double mi, double s, double ms);
4418
-
4419
- /* ── bitwise operators (scr_lib.c) ────────────────────────────────────
4550
+ double scr_date_get_full_year(double ms, bool utc);
4551
+ double scr_date_get_month(double ms, bool utc);
4552
+ double scr_date_get_date(double ms, bool utc);
4553
+ double scr_date_get_day(double ms, bool utc);
4554
+ double scr_date_get_hours(double ms, bool utc);
4555
+ double scr_date_get_minutes(double ms, bool utc);
4556
+ double scr_date_get_seconds(double ms, bool utc);
4557
+ double scr_date_get_milliseconds(double ms);
4558
+ double scr_date_get_timezone_offset(double ms);
4559
+ /* One-argument ABI wrappers used by the LLVM lib-call table. */
4560
+ double scr_date_get_full_year_local(double ms);
4561
+ double scr_date_get_full_year_utc(double ms);
4562
+ double scr_date_get_month_local(double ms);
4563
+ double scr_date_get_month_utc(double ms);
4564
+ double scr_date_get_date_local(double ms);
4565
+ double scr_date_get_date_utc(double ms);
4566
+ double scr_date_get_day_local(double ms);
4567
+ double scr_date_get_day_utc(double ms);
4568
+ double scr_date_get_hours_local(double ms);
4569
+ double scr_date_get_hours_utc(double ms);
4570
+ double scr_date_get_minutes_local(double ms);
4571
+ double scr_date_get_minutes_utc(double ms);
4572
+ double scr_date_get_seconds_local(double ms);
4573
+ double scr_date_get_seconds_utc(double ms);
4574
+
4575
+ /* ── numeric coercion + bitwise operators ─────────────────────────────
4420
4576
  * JS-exact ToInt32/ToUint32 semantics: NaN/±Infinity → 0, truncation
4421
4577
  * toward zero, modular wrap into 32 bits; the operation runs in 32-bit
4422
4578
  * space (shift counts mask to 5 bits, `>>` is an arithmetic shift spelled
4423
4579
  * portably — no C UB/implementation-defined shifts) and the result
4424
- * returns to f64: `>>>` as Uint32, everything else as Int32. */
4580
+ * returns to f64: `>>>` as Uint32, everything else as Int32.
4581
+ * scr_to_uint32 lives in scr_number.c; the bitwise operations in scr_lib.c. */
4582
+ uint32_t scr_to_uint32(double d);
4425
4583
  double scr_bit_and(double a, double b);
4426
4584
  double scr_bit_or(double a, double b);
4427
4585
  double scr_bit_xor(double a, double b);
@@ -4618,12 +4776,22 @@ void scr_bytes_set_from(ScrBytes *dst, const ScrBytes *src, double offset);
4618
4776
  * compiler fences other encodings). */
4619
4777
  ScrStr *scr_bytes_to_str(const ScrBytes *b, const ScrStr *enc);
4620
4778
  ScrStr *scr_bytes_to_str_range(const ScrBytes *b, const ScrStr *enc, double start, double end);
4779
+ /* Runtime-valued Buffer.toString encoding: aliases/case canonicalize;
4780
+ * unknown names throw ERR_UNKNOWN_ENCODING. +1, or NULL when throwing. */
4781
+ ScrStr *scr_bytes_to_str_checked(const ScrBytes *b, const ScrStr *enc);
4782
+ ScrStr *scr_bytes_to_str_checked_range(const ScrBytes *b, const ScrStr *enc, double start, double end);
4621
4783
 
4622
4784
  /* WHATWG TextDecoder.decode over u8 bytes (utf-8, default options): the
4623
4785
  * same replacement decode as toString("utf8") with the leading BOM
4624
4786
  * stripped. Borrows; +1; never throws. */
4625
4787
  ScrStr *scr_text_decode(const ScrBytes *b);
4626
4788
 
4789
+ /* TextDecoder with a compile-time WHATWG legacy-encoding id. The frontend
4790
+ * owns label canonicalization and emits only the ids understood by
4791
+ * scr_bytes.c; keeping this separate from scr_text_decode means default
4792
+ * UTF-8 users do not retain the legacy mapping tables. Borrows; +1. */
4793
+ ScrStr *scr_text_decode_legacy(const ScrBytes *b, double encoding);
4794
+
4627
4795
  /* Buffer.from(string, enc): "utf8" copies the bytes; "hex" parses pairs
4628
4796
  * and stops at the first invalid/odd tail (Node-lenient); "base64" and
4629
4797
  * "base64url" decode the standard AND url-safe alphabets, skipping
@@ -4818,10 +4986,12 @@ ScrPromise *scr_fsp_read_file_bytes(ScrStr *path); /* +1 */
4818
4986
  * RangeError catchably (same check as scr_crypto_random_string). */
4819
4987
  ScrBytes *scr_crypto_random_bytes(double n); /* +1 */
4820
4988
 
4821
- /* process.stdout/stderr.write(buf): the raw byte writes' Buffer overloads
4822
- * (same streams and buffering as the string forms). Constantly true. */
4823
- bool scr_process_stdout_write_bytes(const ScrBytes *b);
4824
- bool scr_process_stderr_write_bytes(const ScrBytes *b);
4989
+ /* process.stdout/stderr.write(buf[, encoding]): the raw byte writes' Buffer
4990
+ * overloads (same streams and buffering as the string forms). The encoding
4991
+ * is evaluated by the caller and ignored here, as Node does for bytes.
4992
+ * Constantly true. */
4993
+ bool scr_process_stdout_write_bytes(const ScrBytes *b, const ScrStr *encoding);
4994
+ bool scr_process_stderr_write_bytes(const ScrBytes *b, const ScrStr *encoding);
4825
4995
 
4826
4996
  /* The Node-shaped fs error thrower (scr_lib.c): formats "ENOENT: no such
4827
4997
  * file or directory, open 'x'" and throws it catchably. Shared with
package/src/scr_string.c CHANGED
@@ -678,7 +678,9 @@ ScrStr *scr_str_trim_end(ScrStr *s) {
678
678
  static const struct { size_t rc; size_t len; size_t cap; char data[4]; }
679
679
  scr_lit_fffd = {SIZE_MAX, 3, 3, "\xEF\xBF\xBD"};
680
680
 
681
- /* split(separator) with a STRING separator, no limit (ECMA-262 22.1.3.23):
681
+ /* split(separator, limit) with a STRING separator (ECMA-262 22.1.3.23):
682
+ * limit is ToUint32'd; zero returns [] and reaching the limit stops before
683
+ * any later separator probes. The no-limit wrapper supplies 2^32-1.
682
684
  * an empty separator splits into single UTF-16 code units ("".split("") is
683
685
  * [] — no probe matches nothing); a non-empty separator splits on every
684
686
  * byte-level occurrence (well-formed UTF-8 is self-synchronizing, so byte
@@ -688,8 +690,10 @@ static const struct { size_t rc; size_t len; size_t cap; char data[4]; }
688
690
  * yield the two lone surrogate halves, each half is U+FFFD here
689
691
  * (divergence 2 — the same substitution the island's boundary marshal
690
692
  * applied). Borrows both; returns a +1 string[]. */
691
- ScrArr *scr_str_split(ScrStr *s, ScrStr *sep) {
693
+ ScrArr *scr_str_split_limit(ScrStr *s, ScrStr *sep, double limit_num) {
692
694
  ScrArr *out = scr_arr_new(SCR_ELEM_STR, 0);
695
+ uint32_t limit = scr_to_uint32(limit_num);
696
+ if (limit == 0) return out;
693
697
  if (sep->len == 0) {
694
698
  size_t i = 0;
695
699
  while (i < s->len) {
@@ -697,10 +701,12 @@ ScrArr *scr_str_split(ScrStr *s, ScrStr *sep) {
697
701
  uint32_t cp = scr_utf8_decode(s->data + i, &adv);
698
702
  if (cp >= 0x10000) { /* two units in JS: both halves become U+FFFD */
699
703
  scr_arr_push_ref(out, scr_str_retain((ScrStr *)&scr_lit_fffd));
704
+ if (out->len == limit) return out;
700
705
  scr_arr_push_ref(out, scr_str_retain((ScrStr *)&scr_lit_fffd));
701
706
  } else {
702
707
  scr_arr_push_ref(out, scr_str_from_span(s->data + i, adv));
703
708
  }
709
+ if (out->len == limit) return out;
704
710
  i += adv;
705
711
  }
706
712
  return out;
@@ -712,12 +718,17 @@ ScrArr *scr_str_split(ScrStr *s, ScrStr *sep) {
712
718
  if (!found) break;
713
719
  size_t at = (size_t)(found - s->data);
714
720
  scr_arr_push_ref(out, scr_str_from_span(s->data + start, at - start));
721
+ if (out->len == limit) return out;
715
722
  start = at + sep->len;
716
723
  }
717
724
  scr_arr_push_ref(out, scr_str_from_span(s->data + start, s->len - start));
718
725
  return out;
719
726
  }
720
727
 
728
+ ScrArr *scr_str_split(ScrStr *s, ScrStr *sep) {
729
+ return scr_str_split_limit(s, sep, 4294967295.0);
730
+ }
731
+
721
732
  /* StringPad (ECMA-262 22.1.3.16/17): target length in UTF-16 units, the
722
733
  * filler built from whole repetitions of `fill` plus a truncated prefix.
723
734
  * A target at or below the length (or an empty fill) returns the receiver