@scriptc/runtime 0.0.16 → 0.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/scr_async.c +133 -57
- package/src/scr_async_dyn.c +1 -2
- package/src/scr_bytes.c +0 -1
- package/src/scr_copying.c +157 -0
- package/src/scr_island.c +17 -11
- package/src/scr_lib.c +164 -0
- package/src/scr_runtime.h +68 -22
- package/src/scr_string.c +58 -1
- package/src/scr_url_params.c +1 -52
package/package.json
CHANGED
package/src/scr_async.c
CHANGED
|
@@ -106,7 +106,7 @@ struct ScrPromise {
|
|
|
106
106
|
size_t ncbs, cbs_cap;
|
|
107
107
|
/* Unhandled-rejection tracking: set when rejected, cleared on await. */
|
|
108
108
|
bool rejection_observed;
|
|
109
|
-
/* Set when the
|
|
109
|
+
/* Set when the checkpoint report delivered THIS promise to
|
|
110
110
|
* 'unhandledRejection' listeners — a handler attached after that is
|
|
111
111
|
* Node's 'rejectionHandled' moment (scr_prom_observe below). */
|
|
112
112
|
bool reported_unhandled;
|
|
@@ -193,7 +193,7 @@ ScrPromise *scr_promise_new(void) {
|
|
|
193
193
|
|
|
194
194
|
/* The 'rejectionHandled' hook (scr_async_dyn.c installs it at listener
|
|
195
195
|
* registration — the scr_urj_deliver_fn pattern, so listener-free
|
|
196
|
-
* binaries keep their size class): called when a promise the
|
|
196
|
+
* binaries keep their size class): called when a promise the checkpoint
|
|
197
197
|
* report already delivered as unhandled gains a handler. */
|
|
198
198
|
void (*scr_rjh_notify_fn)(ScrPromise *p) = NULL;
|
|
199
199
|
|
|
@@ -212,12 +212,15 @@ static void scr_prom_observe(ScrPromise *p) {
|
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
/* The attach-time handled mark (scr_async_dyn.c's dyn then/catch with a
|
|
215
|
-
* rejection handler
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
215
|
+
* rejection handler, plus the module loader's ownership of every module
|
|
216
|
+
* evaluation promise): Node marks a promise handled at ATTACH, including
|
|
217
|
+
* while it is still pending, not when a later reaction runs. The pending
|
|
218
|
+
* mark is essential for a module dependency whose importer aborts while
|
|
219
|
+
* evaluating a later sibling — the loader still owns that dependency's
|
|
220
|
+
* eventual rejection, so it must never surface as an unrelated unhandled
|
|
221
|
+
* rejection. */
|
|
219
222
|
void scr_promise_mark_handled(ScrPromise *p) {
|
|
220
|
-
|
|
223
|
+
scr_prom_observe(p);
|
|
221
224
|
}
|
|
222
225
|
|
|
223
226
|
ScrPromise *scr_promise_retain(ScrPromise *p) {
|
|
@@ -1229,24 +1232,31 @@ static void scr_await_yield(void) {
|
|
|
1229
1232
|
* turn — the same hop a settled promise takes. */
|
|
1230
1233
|
void scr_await_hop(void) { scr_await_yield(); }
|
|
1231
1234
|
|
|
1232
|
-
/*
|
|
1233
|
-
* RETAINED, not moved — a promise can be awaited more than
|
|
1235
|
+
/* Copies a rejection into the active execution context's exception cell.
|
|
1236
|
+
* The payload is RETAINED, not moved — a promise can be awaited more than
|
|
1237
|
+
* once, and the executable's top-level completion probe consumes the same
|
|
1238
|
+
* settlement from the main stack after the loop drains. */
|
|
1239
|
+
static void scr_promise_rethrow(ScrPromise *p) {
|
|
1240
|
+
switch (p->payload_kind) {
|
|
1241
|
+
case SCR_EXC_F64: scr_throw_f64(p->f64); break;
|
|
1242
|
+
case SCR_EXC_BOOL: scr_throw_bool(p->b); break;
|
|
1243
|
+
case SCR_EXC_STR: scr_throw_str(scr_str_retain((ScrStr *)p->payload)); break;
|
|
1244
|
+
case SCR_EXC_REF: scr_throw_ref(p->retain_fn(p->payload), p->retain_fn, p->release_fn, p->trace_fn); break;
|
|
1245
|
+
case SCR_EXC_OBJ: scr_throw_obj(p->retain_fn(p->payload), p->retain_fn, p->release_fn, p->trace_fn); break;
|
|
1246
|
+
case SCR_EXC_NONE:
|
|
1247
|
+
case SCR_EXC_GENRET: /* unreachable: the sentinel never settles a promise */
|
|
1248
|
+
scr_throw_str(scr_str_new("undefined", 9));
|
|
1249
|
+
break;
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
/* Await result extraction. Rejection re-throws into the awaiter. */
|
|
1234
1254
|
static bool scr_await_settled(ScrPromise *p) {
|
|
1235
1255
|
if (p->state != SCR_PROM_PENDING) scr_await_yield();
|
|
1236
1256
|
while (p->state == SCR_PROM_PENDING) scr_await_park(p);
|
|
1237
1257
|
scr_prom_observe(p);
|
|
1238
1258
|
if (p->state == SCR_PROM_REJECTED) {
|
|
1239
|
-
|
|
1240
|
-
case SCR_EXC_F64: scr_throw_f64(p->f64); break;
|
|
1241
|
-
case SCR_EXC_BOOL: scr_throw_bool(p->b); break;
|
|
1242
|
-
case SCR_EXC_STR: scr_throw_str(scr_str_retain((ScrStr *)p->payload)); break;
|
|
1243
|
-
case SCR_EXC_REF: scr_throw_ref(p->retain_fn(p->payload), p->retain_fn, p->release_fn, p->trace_fn); break;
|
|
1244
|
-
case SCR_EXC_OBJ: scr_throw_obj(p->retain_fn(p->payload), p->retain_fn, p->release_fn, p->trace_fn); break;
|
|
1245
|
-
case SCR_EXC_NONE:
|
|
1246
|
-
case SCR_EXC_GENRET: /* unreachable: the sentinel never settles a promise */
|
|
1247
|
-
scr_throw_str(scr_str_new("undefined", 9));
|
|
1248
|
-
break;
|
|
1249
|
-
}
|
|
1259
|
+
scr_promise_rethrow(p);
|
|
1250
1260
|
return false;
|
|
1251
1261
|
}
|
|
1252
1262
|
return true;
|
|
@@ -1263,6 +1273,32 @@ void *scr_await_ref(ScrPromise *p) {
|
|
|
1263
1273
|
}
|
|
1264
1274
|
void scr_await_void(ScrPromise *p) { scr_await_settled(p); }
|
|
1265
1275
|
|
|
1276
|
+
/* ECMAScript's INTERNAL module-dependency wait. Unlike a user-authored
|
|
1277
|
+
* await, an already-completed dependency does not introduce a promise-job
|
|
1278
|
+
* hop: the evaluator continues synchronously into the importer. A pending
|
|
1279
|
+
* dependency still parks this module fiber, and rejection propagates into
|
|
1280
|
+
* it exactly like an ordinary await. */
|
|
1281
|
+
void scr_module_await(ScrPromise *p) {
|
|
1282
|
+
while (p->state == SCR_PROM_PENDING) scr_await_park(p);
|
|
1283
|
+
scr_prom_observe(p);
|
|
1284
|
+
if (p->state == SCR_PROM_REJECTED) scr_promise_rethrow(p);
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
int scr_promise_finish_top_level(ScrPromise *p) {
|
|
1288
|
+
if (p->state == SCR_PROM_PENDING) {
|
|
1289
|
+
/* ECMAScript module evaluation is still pending, but Node's ref'd
|
|
1290
|
+
* event-loop work is exhausted. Node exits with its dedicated
|
|
1291
|
+
* unsettled-top-level-await status. */
|
|
1292
|
+
scr_exit_code_note(13);
|
|
1293
|
+
return 13;
|
|
1294
|
+
}
|
|
1295
|
+
scr_prom_observe(p);
|
|
1296
|
+
return p->state == SCR_PROM_REJECTED ? 1 : 0;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
void scr_promise_rethrow_top_level(ScrPromise *p) {
|
|
1300
|
+
if (p->state == SCR_PROM_REJECTED) scr_promise_rethrow(p);
|
|
1301
|
+
}
|
|
1266
1302
|
|
|
1267
1303
|
|
|
1268
1304
|
|
|
@@ -1704,7 +1740,7 @@ void scr_loop_set_stream(bool (*pending)(void), void (*dispatch)(void)) {
|
|
|
1704
1740
|
* microtask checkpoints between macrotasks). */
|
|
1705
1741
|
bool scr_loop_has_ready(void) { return scr_ready_len > 0; }
|
|
1706
1742
|
|
|
1707
|
-
|
|
1743
|
+
bool scr_loop_run(ScrPromise *top_level) {
|
|
1708
1744
|
/* The FIRST checkpoint after the synchronous main body runs promise
|
|
1709
1745
|
* jobs BEFORE the first tick drain: Node's main-module evaluation is
|
|
1710
1746
|
* itself awaited (the runMain continuation is a microtask queued after
|
|
@@ -1712,7 +1748,9 @@ void scr_loop_run(void) {
|
|
|
1712
1748
|
* scheduled during the body exactly once, at startup — differentially
|
|
1713
1749
|
* pinned. Every later checkpoint drains ticks first. */
|
|
1714
1750
|
bool first_checkpoint = true;
|
|
1751
|
+
bool rejection_failed = false;
|
|
1715
1752
|
for (;;) {
|
|
1753
|
+
if (top_level != NULL && top_level->state == SCR_PROM_REJECTED) break;
|
|
1716
1754
|
/* process.nextTick callbacks BEFORE promise jobs (Node's checkpoint
|
|
1717
1755
|
* order): the tick queue to exhaustion, then the microtask queue to
|
|
1718
1756
|
* exhaustion, and back while either has work — a microtask's
|
|
@@ -1733,7 +1771,7 @@ void scr_loop_run(void) {
|
|
|
1733
1771
|
} else {
|
|
1734
1772
|
raw(); /* one stream tick, FIFO with the user ticks around it */
|
|
1735
1773
|
}
|
|
1736
|
-
if (scr_exc_pending()) return;
|
|
1774
|
+
if (scr_exc_pending()) return false;
|
|
1737
1775
|
}
|
|
1738
1776
|
}
|
|
1739
1777
|
/* Microtasks to exhaustion (Node: promise jobs before timers). */
|
|
@@ -1743,7 +1781,26 @@ void scr_loop_run(void) {
|
|
|
1743
1781
|
scr_resume_fiber(f);
|
|
1744
1782
|
}
|
|
1745
1783
|
first_checkpoint = false;
|
|
1784
|
+
/* The ESM loader observes a rejected entry evaluation at a promise-job
|
|
1785
|
+
* checkpoint and terminates before later ref'd timers or I/O can run.
|
|
1786
|
+
* Wait until this checkpoint's ready queue is drained — the root's
|
|
1787
|
+
* rejection handler is itself promise-job ordered — then leave through
|
|
1788
|
+
* the normal teardown below. A fulfilled root deliberately does not
|
|
1789
|
+
* stop the loop. */
|
|
1790
|
+
if (top_level != NULL && top_level->state == SCR_PROM_REJECTED) break;
|
|
1746
1791
|
if (scr_nt_head != NULL) continue;
|
|
1792
|
+
/* Node decides unhandled rejections at the END of each complete
|
|
1793
|
+
* nextTick/microtask checkpoint, before advancing to timers or I/O.
|
|
1794
|
+
* A rejected executable module root wins over OTHER rejections from
|
|
1795
|
+
* this same checkpoint (the root check above); rejections from an
|
|
1796
|
+
* earlier checkpoint have already delivered here. Handled listeners
|
|
1797
|
+
* may enqueue more jobs or ref'd work, so return to the checkpoint
|
|
1798
|
+
* head instead of declaring the loop exhausted underneath them. */
|
|
1799
|
+
if (scr_report_unhandled_rejections()) {
|
|
1800
|
+
rejection_failed = true;
|
|
1801
|
+
break;
|
|
1802
|
+
}
|
|
1803
|
+
if (scr_ready_len > 0 || scr_nt_head != NULL || scr_nunhandled > 0) continue;
|
|
1747
1804
|
/* Quiescent between turns (microtasks drained, nothing running):
|
|
1748
1805
|
* collect any cycles the turn left behind. No-op on an empty buffer. */
|
|
1749
1806
|
scr_collect_cycles();
|
|
@@ -1754,7 +1811,7 @@ void scr_loop_run(void) {
|
|
|
1754
1811
|
* so those drain first. */
|
|
1755
1812
|
if (scr_stream_dispatch_fn != NULL) {
|
|
1756
1813
|
scr_stream_dispatch_fn();
|
|
1757
|
-
if (scr_exc_pending()) return; /* uncaught throw in a listener */
|
|
1814
|
+
if (scr_exc_pending()) return false; /* uncaught throw in a listener */
|
|
1758
1815
|
if (scr_ready_len > 0) continue;
|
|
1759
1816
|
if (scr_stream_pending_fn != NULL && scr_stream_pending_fn()) continue;
|
|
1760
1817
|
}
|
|
@@ -1765,7 +1822,7 @@ void scr_loop_run(void) {
|
|
|
1765
1822
|
* microtasks — restart the turn so those drain first. */
|
|
1766
1823
|
if (scr_events_dispatch_fn != NULL) {
|
|
1767
1824
|
scr_events_dispatch_fn();
|
|
1768
|
-
if (scr_exc_pending()) return; /* uncaught throw in a listener */
|
|
1825
|
+
if (scr_exc_pending()) return false; /* uncaught throw in a listener */
|
|
1769
1826
|
if (scr_ready_len > 0) continue;
|
|
1770
1827
|
}
|
|
1771
1828
|
/* Net dispatch (scr_net.c, when linked): accepts, arrived data,
|
|
@@ -1774,7 +1831,7 @@ void scr_loop_run(void) {
|
|
|
1774
1831
|
* microtasks — restart the turn so those drain first. */
|
|
1775
1832
|
if (scr_net_dispatch_fn != NULL) {
|
|
1776
1833
|
scr_net_dispatch_fn();
|
|
1777
|
-
if (scr_exc_pending()) return; /* uncaught throw in a listener */
|
|
1834
|
+
if (scr_exc_pending()) return false; /* uncaught throw in a listener */
|
|
1778
1835
|
if (scr_ready_len > 0) continue;
|
|
1779
1836
|
}
|
|
1780
1837
|
/* Dgram dispatch (scr_dgram.c, when linked): arrived datagrams and
|
|
@@ -1782,7 +1839,7 @@ void scr_loop_run(void) {
|
|
|
1782
1839
|
* dns.lookup callbacks) fire now — the net hook's exact station. */
|
|
1783
1840
|
if (scr_dgram_dispatch_fn != NULL) {
|
|
1784
1841
|
scr_dgram_dispatch_fn();
|
|
1785
|
-
if (scr_exc_pending()) return; /* uncaught throw in a listener */
|
|
1842
|
+
if (scr_exc_pending()) return false; /* uncaught throw in a listener */
|
|
1786
1843
|
if (scr_ready_len > 0) continue;
|
|
1787
1844
|
}
|
|
1788
1845
|
/* Watch dispatch (scr_watch.c, when linked): file events queued on
|
|
@@ -1790,7 +1847,7 @@ void scr_loop_run(void) {
|
|
|
1790
1847
|
* net hook's exact station. */
|
|
1791
1848
|
if (scr_watch_dispatch_fn != NULL) {
|
|
1792
1849
|
scr_watch_dispatch_fn();
|
|
1793
|
-
if (scr_exc_pending()) return; /* uncaught throw in a listener */
|
|
1850
|
+
if (scr_exc_pending()) return false; /* uncaught throw in a listener */
|
|
1794
1851
|
if (scr_ready_len > 0) continue;
|
|
1795
1852
|
}
|
|
1796
1853
|
/* Reap spawned children and fire their listeners (before timers,
|
|
@@ -1817,7 +1874,7 @@ void scr_loop_run(void) {
|
|
|
1817
1874
|
(scr_watch_pending_fn != NULL && scr_watch_pending_fn());
|
|
1818
1875
|
if (held) {
|
|
1819
1876
|
scr_children_poll();
|
|
1820
|
-
if (scr_exc_pending()) return; /* uncaught throw in a listener */
|
|
1877
|
+
if (scr_exc_pending()) return false; /* uncaught throw in a listener */
|
|
1821
1878
|
if (scr_ready_len > 0) continue;
|
|
1822
1879
|
}
|
|
1823
1880
|
}
|
|
@@ -2018,7 +2075,7 @@ void scr_loop_run(void) {
|
|
|
2018
2075
|
scr_closure_release(t.cb);
|
|
2019
2076
|
}
|
|
2020
2077
|
scr_firing_id = 0;
|
|
2021
|
-
if (scr_exc_pending()) return; /* uncaught throw in a callback: main handles it */
|
|
2078
|
+
if (scr_exc_pending()) return false; /* uncaught throw in a callback: main handles it */
|
|
2022
2079
|
if (scr_ready_len > 0) break; /* drain microtasks before more timers */
|
|
2023
2080
|
}
|
|
2024
2081
|
/* The check phase: immediates queued BEFORE this phase started run
|
|
@@ -2042,12 +2099,13 @@ void scr_loop_run(void) {
|
|
|
2042
2099
|
}
|
|
2043
2100
|
((void (*)(ScrClosure *))cb->fn)(cb);
|
|
2044
2101
|
scr_closure_release(cb);
|
|
2045
|
-
if (scr_exc_pending()) return; /* uncaught throw: main handles it */
|
|
2102
|
+
if (scr_exc_pending()) return false; /* uncaught throw: main handles it */
|
|
2046
2103
|
while (scr_ready_len > 0) {
|
|
2047
2104
|
ScrFiber *f = scr_ready[scr_ready_head++];
|
|
2048
2105
|
scr_ready_len--;
|
|
2049
2106
|
scr_resume_fiber(f);
|
|
2050
2107
|
}
|
|
2108
|
+
if (top_level != NULL && top_level->state == SCR_PROM_REJECTED) break;
|
|
2051
2109
|
}
|
|
2052
2110
|
/* Fully drained: reset the cursor so the array reuses its slots. */
|
|
2053
2111
|
if (scr_immediates_head == scr_nimmediates) {
|
|
@@ -2056,12 +2114,11 @@ void scr_loop_run(void) {
|
|
|
2056
2114
|
}
|
|
2057
2115
|
}
|
|
2058
2116
|
}
|
|
2059
|
-
/*
|
|
2060
|
-
*
|
|
2061
|
-
* never fire, so release their closures here or the RC audit counts
|
|
2062
|
-
* as leaks.
|
|
2063
|
-
*
|
|
2064
|
-
* audit skip. */
|
|
2117
|
+
/* Exit can now leave UNREF'd timers armed in the heap (ordinary
|
|
2118
|
+
* exhaustion, a fatal module root, or an unhandled rejection). They
|
|
2119
|
+
* never fire, so release their closures here or the RC audit counts
|
|
2120
|
+
* them as leaks. Uncaught callback throws still return above for main
|
|
2121
|
+
* to report through the existing exceptional teardown path. */
|
|
2065
2122
|
scr_timers_teardown();
|
|
2066
2123
|
/* Same story for unref'd children the loop never reaped: release the
|
|
2067
2124
|
* registry's references (their listeners never fire — the process is
|
|
@@ -2069,6 +2126,7 @@ void scr_loop_run(void) {
|
|
|
2069
2126
|
scr_children_teardown();
|
|
2070
2127
|
scr_fibers_abandoned = scr_fibers_live;
|
|
2071
2128
|
scr_note_abandoned_fibers(scr_fibers_abandoned);
|
|
2129
|
+
return rejection_failed;
|
|
2072
2130
|
}
|
|
2073
2131
|
|
|
2074
2132
|
/* The island's half of the unhandled-rejection report (scr_island.c
|
|
@@ -2077,21 +2135,18 @@ void scr_loop_run(void) {
|
|
|
2077
2135
|
* first-unhandled-rejection death. Returns whether the island had any.
|
|
2078
2136
|
* Static builds never set it. */
|
|
2079
2137
|
static bool (*scr_island_rejections_fn)(bool print) = NULL;
|
|
2138
|
+
static int (*scr_island_jobs_drain_fn)(void) = NULL;
|
|
2080
2139
|
|
|
2081
|
-
void scr_loop_set_island_rejections(bool (*fn)(bool print)
|
|
2140
|
+
void scr_loop_set_island_rejections(bool (*fn)(bool print),
|
|
2141
|
+
int (*drain_jobs)(void)) {
|
|
2082
2142
|
scr_island_rejections_fn = fn;
|
|
2143
|
+
scr_island_jobs_drain_fn = drain_jobs;
|
|
2083
2144
|
}
|
|
2084
2145
|
|
|
2085
2146
|
/* ── process.on('unhandledRejection') ─────────────────────────────────
|
|
2086
|
-
*
|
|
2087
|
-
*
|
|
2088
|
-
*
|
|
2089
|
-
* decided — the same values, later (SEMANTICS.md; mustCall-style
|
|
2090
|
-
* assertions and reason identity observe no difference, cross-turn
|
|
2091
|
-
* interleaving would). A registered listener suppresses the report and
|
|
2092
|
-
* the exit-1, exactly Node's handled-event contract. */
|
|
2093
|
-
|
|
2094
|
-
|
|
2147
|
+
* Dyn listeners are called per never-observed rejection at the completed
|
|
2148
|
+
* nextTick/microtask checkpoint. A registered listener suppresses the
|
|
2149
|
+
* default report and exit 1, exactly Node's handled-event contract. */
|
|
2095
2150
|
|
|
2096
2151
|
/* The unhandled-rejection LISTENER hook (scr_async_dyn.c installs it at
|
|
2097
2152
|
* registration — the loop-hook pattern, so listener-free binaries keep
|
|
@@ -2099,11 +2154,25 @@ void scr_loop_set_island_rejections(bool (*fn)(bool print)) {
|
|
|
2099
2154
|
* listener threw (the uncaught crash path). */
|
|
2100
2155
|
bool (*scr_urj_deliver_fn)(ScrPromise *p) = NULL;
|
|
2101
2156
|
|
|
2102
|
-
/* Unhandled rejections at
|
|
2157
|
+
/* Unhandled rejections at a completed nextTick/microtask checkpoint:
|
|
2158
|
+
* Node prints an error and exits 1 when no listener handles the event.
|
|
2159
|
+
* Snapshot the ledger: a listener can reject another promise, but that
|
|
2160
|
+
* new rejection belongs to the NEXT checkpoint rather than this report. */
|
|
2103
2161
|
bool scr_report_unhandled_rejections(void) {
|
|
2162
|
+
/* Static fibers drain before engine jobs in this runtime's documented
|
|
2163
|
+
* island ordering. Complete BOTH halves of that microtask checkpoint
|
|
2164
|
+
* before deciding either rejection ledger; engine reactions can attach
|
|
2165
|
+
* a handler to a rejection that would otherwise look unhandled here.
|
|
2166
|
+
* A host callback can wake a static fiber/tick, in which case the loop
|
|
2167
|
+
* must drain that work before reporting too. */
|
|
2168
|
+
if (scr_island_jobs_drain_fn != NULL) {
|
|
2169
|
+
scr_island_jobs_drain_fn();
|
|
2170
|
+
if (scr_ready_len > 0 || scr_nt_head != NULL) return false;
|
|
2171
|
+
}
|
|
2104
2172
|
bool any = false;
|
|
2105
2173
|
bool crashed = false;
|
|
2106
|
-
|
|
2174
|
+
size_t report_count = scr_nunhandled;
|
|
2175
|
+
for (size_t i = 0; i < report_count; i++) {
|
|
2107
2176
|
ScrPromise *p = scr_maybe_unhandled[i];
|
|
2108
2177
|
if (p->state == SCR_PROM_REJECTED && !p->rejection_observed && !crashed) {
|
|
2109
2178
|
if (scr_urj_deliver_fn != NULL) {
|
|
@@ -2145,7 +2214,12 @@ bool scr_report_unhandled_rejections(void) {
|
|
|
2145
2214
|
}
|
|
2146
2215
|
scr_promise_release(p);
|
|
2147
2216
|
}
|
|
2148
|
-
|
|
2217
|
+
size_t remaining = scr_nunhandled - report_count;
|
|
2218
|
+
if (remaining > 0) {
|
|
2219
|
+
memmove(scr_maybe_unhandled, scr_maybe_unhandled + report_count,
|
|
2220
|
+
remaining * sizeof *scr_maybe_unhandled);
|
|
2221
|
+
}
|
|
2222
|
+
scr_nunhandled = remaining;
|
|
2149
2223
|
if (crashed) {
|
|
2150
2224
|
scr_exc_print_uncaught();
|
|
2151
2225
|
scr_exit_code_note(1);
|
|
@@ -2155,20 +2229,22 @@ bool scr_report_unhandled_rejections(void) {
|
|
|
2155
2229
|
bool island = scr_island_rejections_fn(!any);
|
|
2156
2230
|
any = any || island;
|
|
2157
2231
|
}
|
|
2158
|
-
/* An 'unhandledRejection' listener can spawn fibers the exhausted loop
|
|
2159
|
-
* will never run (a .catch attach mints a reaction fiber): they are
|
|
2160
|
-
* abandoned by construction — re-note them so the RC audit's skip
|
|
2161
|
-
* covers their parked state, exactly the loop-teardown accounting. */
|
|
2162
|
-
if (scr_fibers_live > scr_fibers_abandoned) {
|
|
2163
|
-
scr_fibers_abandoned = scr_fibers_live;
|
|
2164
|
-
scr_note_abandoned_fibers(scr_fibers_abandoned);
|
|
2165
|
-
}
|
|
2166
2232
|
/* main returns 1 on a reported rejection — the 'exit' listeners (atexit)
|
|
2167
2233
|
* must see that code, like Node's. */
|
|
2168
2234
|
if (any) scr_exit_code_note(1);
|
|
2169
2235
|
return any;
|
|
2170
2236
|
}
|
|
2171
2237
|
|
|
2238
|
+
/* A fatal executable-module rejection suppresses unrelated rejections
|
|
2239
|
+
* created in the SAME checkpoint. Drop their retained ledger references
|
|
2240
|
+
* without delivering process events or a competing default report. */
|
|
2241
|
+
void scr_discard_unhandled_rejections(void) {
|
|
2242
|
+
for (size_t i = 0; i < scr_nunhandled; i++) {
|
|
2243
|
+
scr_promise_release(scr_maybe_unhandled[i]);
|
|
2244
|
+
}
|
|
2245
|
+
scr_nunhandled = 0;
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2172
2248
|
/* ── new Promise(executor) ────────────────────────────────────────────── */
|
|
2173
2249
|
|
|
2174
2250
|
/* resolve closures: caps[0] is a box whose slot holds the promise (+1). */
|
package/src/scr_async_dyn.c
CHANGED
|
@@ -403,7 +403,7 @@ ScrDyn *scr_dyn_promise_then(ScrPromise *src, ScrDyn *onf, ScrDyn *onr, ScrDyn *
|
|
|
403
403
|
/* A rejection HANDLER marks the source handled at attach (Node's
|
|
404
404
|
* moment; the reaction fiber's await re-marks harmlessly) — this is
|
|
405
405
|
* also what lets a .catch inside an 'unhandledRejection' listener fire
|
|
406
|
-
* 'rejectionHandled'
|
|
406
|
+
* 'rejectionHandled' immediately at the attach point. */
|
|
407
407
|
if (onr != NULL) scr_promise_mark_handled(src);
|
|
408
408
|
ScrDynThenPack *a = malloc(sizeof *a);
|
|
409
409
|
if (!a) scr_ad_oom();
|
|
@@ -720,4 +720,3 @@ ScrDyn *scr_dyn_new_promise_adapting(ScrPromise *src,
|
|
|
720
720
|
ScrPromise *scr_dyn_promise_of(const ScrDyn *d) {
|
|
721
721
|
return d->kind == SCR_DYN_PROMISE ? d->v.promise : NULL;
|
|
722
722
|
}
|
|
723
|
-
|
package/src/scr_bytes.c
CHANGED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#include "scr_runtime.h"
|
|
2
|
+
|
|
3
|
+
#include <math.h>
|
|
4
|
+
#include <stdio.h>
|
|
5
|
+
#include <stdint.h>
|
|
6
|
+
|
|
7
|
+
/* ES2023 Array/TypedArray copying methods and the typed-array-to-number[]
|
|
8
|
+
* bridge live in their own archive member. Programs that do not use this
|
|
9
|
+
* surface therefore pull in none of it — the static binary size contract. */
|
|
10
|
+
|
|
11
|
+
static uint64_t copying_slot_from_ptr(void *p) {
|
|
12
|
+
return (uint64_t)(uintptr_t)p;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
static void *copying_slot_to_ptr(uint64_t slot) {
|
|
16
|
+
return (void *)(uintptr_t)slot;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static bool copying_elem_is_ref(ScrElemKind kind) {
|
|
20
|
+
return kind == SCR_ELEM_STR || kind == SCR_ELEM_ARR ||
|
|
21
|
+
kind == SCR_ELEM_BYTES || kind == SCR_ELEM_REF;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static ScrArr *copying_arr_new_like(const ScrArr *a, size_t cap) {
|
|
25
|
+
return a->elem == SCR_ELEM_REF
|
|
26
|
+
? scr_arr_new_ref(a->elem_retain, a->elem_release,
|
|
27
|
+
a->elem_trace, cap ? cap : 1)
|
|
28
|
+
: scr_arr_new(a->elem, cap ? cap : 1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static uint64_t copying_arr_retain_slot(const ScrArr *a, uint64_t slot) {
|
|
32
|
+
if (!copying_elem_is_ref(a->elem)) return slot;
|
|
33
|
+
void *p = copying_slot_to_ptr(slot);
|
|
34
|
+
if (a->elem == SCR_ELEM_STR) p = scr_str_retain((ScrStr *)p);
|
|
35
|
+
else if (a->elem == SCR_ELEM_ARR) p = scr_arr_retain((ScrArr *)p);
|
|
36
|
+
else if (a->elem == SCR_ELEM_BYTES) p = scr_bytes_retain((ScrBytes *)p);
|
|
37
|
+
else p = a->elem_retain(p);
|
|
38
|
+
return copying_slot_from_ptr(p);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static void copying_arr_copy_slot(ScrArr *out, const ScrArr *src, size_t i) {
|
|
42
|
+
out->data[out->len++] = copying_arr_retain_slot(src, src->data[i]);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
ScrArr *scr_arr_to_reversed(const ScrArr *a) {
|
|
46
|
+
ScrArr *out = copying_arr_new_like(a, a->len);
|
|
47
|
+
for (size_t i = a->len; i > 0; i--) {
|
|
48
|
+
copying_arr_copy_slot(out, a, i - 1);
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
ScrArr *scr_arr_to_spliced(const ScrArr *a, double start,
|
|
54
|
+
double delete_count, const ScrArr *items) {
|
|
55
|
+
double len = (double)a->len;
|
|
56
|
+
double s0 = isnan(start) ? 0 : trunc(start);
|
|
57
|
+
if (s0 < 0) s0 += len;
|
|
58
|
+
size_t from = s0 <= 0 ? 0 : s0 >= len ? a->len : (size_t)s0;
|
|
59
|
+
double avail = len - (double)from;
|
|
60
|
+
double d0 = isnan(delete_count) ? 0 : trunc(delete_count);
|
|
61
|
+
size_t ndelete =
|
|
62
|
+
d0 <= 0 ? 0 : d0 >= avail ? (size_t)avail : (size_t)d0;
|
|
63
|
+
if (items->len > SIZE_MAX - (a->len - ndelete)) {
|
|
64
|
+
scr_trap("scriptc: out of memory\n");
|
|
65
|
+
}
|
|
66
|
+
size_t out_len = a->len - ndelete + items->len;
|
|
67
|
+
ScrArr *out = copying_arr_new_like(a, out_len);
|
|
68
|
+
for (size_t i = 0; i < from; i++) copying_arr_copy_slot(out, a, i);
|
|
69
|
+
for (size_t i = 0; i < items->len; i++) {
|
|
70
|
+
out->data[out->len++] =
|
|
71
|
+
copying_arr_retain_slot(a, items->data[i]);
|
|
72
|
+
}
|
|
73
|
+
for (size_t i = from + ndelete; i < a->len; i++) {
|
|
74
|
+
copying_arr_copy_slot(out, a, i);
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
static bool copying_arr_with_index(const ScrArr *a, double index,
|
|
80
|
+
size_t *out) {
|
|
81
|
+
double rel = isnan(index) ? 0 : trunc(index);
|
|
82
|
+
double actual = rel >= 0 ? rel : (double)a->len + rel;
|
|
83
|
+
if (!(actual >= 0) || actual >= (double)a->len) {
|
|
84
|
+
char num[32];
|
|
85
|
+
size_t numlen = scr_f64_to_str(index, num);
|
|
86
|
+
char msg[80];
|
|
87
|
+
int mlen = snprintf(msg, sizeof msg, "Invalid index : %.*s",
|
|
88
|
+
(int)numlen, num);
|
|
89
|
+
scr_throw_error_msg(SCR_ERR_RANGE, msg, (size_t)mlen);
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
*out = (size_t)actual;
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
ScrArr *scr_arr_with_f64(ScrArr *a, double index, double value) {
|
|
97
|
+
size_t i;
|
|
98
|
+
if (!copying_arr_with_index(a, index, &i)) return NULL;
|
|
99
|
+
ScrArr *out = scr_arr_slice(a, 0, INFINITY);
|
|
100
|
+
scr_arr_set_f64(out, (double)i, value);
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
ScrArr *scr_arr_with_bool(ScrArr *a, double index, bool value) {
|
|
105
|
+
size_t i;
|
|
106
|
+
if (!copying_arr_with_index(a, index, &i)) return NULL;
|
|
107
|
+
ScrArr *out = scr_arr_slice(a, 0, INFINITY);
|
|
108
|
+
scr_arr_set_bool(out, (double)i, value);
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
ScrArr *scr_arr_with_ref(ScrArr *a, double index, void *value) {
|
|
113
|
+
size_t i;
|
|
114
|
+
if (!copying_arr_with_index(a, index, &i)) return NULL;
|
|
115
|
+
ScrArr *out = scr_arr_slice(a, 0, INFINITY);
|
|
116
|
+
uint64_t retained =
|
|
117
|
+
copying_arr_retain_slot(a, copying_slot_from_ptr(value));
|
|
118
|
+
scr_arr_set_ref(out, (double)i, copying_slot_to_ptr(retained));
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
ScrBytes *scr_bytes_to_reversed(const ScrBytes *b) {
|
|
123
|
+
ScrBytes *out = scr_bytes_new(b->elem, (double)b->len);
|
|
124
|
+
for (size_t i = 0; i < b->len; i++) {
|
|
125
|
+
scr_bytes_set(out, (double)i,
|
|
126
|
+
scr_bytes_get(b, (double)(b->len - i - 1)));
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
ScrBytes *scr_bytes_with(const ScrBytes *b, double index, double value) {
|
|
132
|
+
double rel = isnan(index) ? 0 : trunc(index);
|
|
133
|
+
double actual = rel >= 0 ? rel : (double)b->len + rel;
|
|
134
|
+
if (!(actual >= 0) || actual >= (double)b->len) {
|
|
135
|
+
static const char msg[] = "Invalid typed array index";
|
|
136
|
+
scr_throw_error_msg(SCR_ERR_RANGE, msg, sizeof msg - 1);
|
|
137
|
+
return NULL;
|
|
138
|
+
}
|
|
139
|
+
ScrBytes *out = scr_bytes_copy(b);
|
|
140
|
+
scr_bytes_set(out, actual, value);
|
|
141
|
+
return out;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
ScrArr *scr_bytes_to_arr(const ScrBytes *b) {
|
|
145
|
+
ScrArr *out = scr_arr_new(SCR_ELEM_F64, b->len ? b->len : 1);
|
|
146
|
+
for (size_t i = 0; i < b->len; i++) {
|
|
147
|
+
scr_arr_push_f64(out, scr_bytes_get(b, (double)i));
|
|
148
|
+
}
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
ScrStr *scr_bytes_join(const ScrBytes *b, const ScrStr *separator) {
|
|
153
|
+
ScrArr *values = scr_bytes_to_arr(b);
|
|
154
|
+
ScrStr *out = scr_arr_join(values, (ScrStr *)separator);
|
|
155
|
+
scr_arr_release(values);
|
|
156
|
+
return out;
|
|
157
|
+
}
|
package/src/scr_island.c
CHANGED
|
@@ -267,14 +267,14 @@ void scr_island_set_netmod(void (*attach)(void *jsctx, void *host_obj), void (*t
|
|
|
267
267
|
* false when a promise rejects with no reaction attached (tracked here,
|
|
268
268
|
* promise and reason retained), is_handled == true when a handler is
|
|
269
269
|
* attached to it later (the rescission: unlinked and freed — a
|
|
270
|
-
* handled-later rejection never reports). At
|
|
271
|
-
*
|
|
272
|
-
* the FIRST never-observed rejection prints
|
|
273
|
-
* voice ("Unhandled promise rejection:
|
|
274
|
-
* — an Error reason renders "name:
|
|
275
|
-
* as the static ledger). Retaining
|
|
276
|
-
*
|
|
277
|
-
* the ledger holds it). */
|
|
270
|
+
* handled-later rejection never reports). At the completed microtask
|
|
271
|
+
* checkpoint the ledger joins scr_report_unhandled_rejections through
|
|
272
|
+
* the hook registered at boot: the FIRST never-observed rejection prints
|
|
273
|
+
* in the static runtime's exact voice ("Unhandled promise rejection:
|
|
274
|
+
* <String(reason)>", stderr, exit 1 — an Error reason renders "name:
|
|
275
|
+
* message" through its toString, same as the static ledger). Retaining
|
|
276
|
+
* the promise value keeps its identity stable for the rescission (the
|
|
277
|
+
* engine cannot recycle the object while the ledger holds it). */
|
|
278
278
|
typedef struct IslRejection {
|
|
279
279
|
JSValue promise; /* owned; identity for the rescission */
|
|
280
280
|
JSValue reason; /* owned */
|
|
@@ -555,10 +555,13 @@ static void isl_init(void) {
|
|
|
555
555
|
* progress exactly where Node runs its microtasks. */
|
|
556
556
|
scr_loop_set_io(isl_io_pending, isl_io_poll);
|
|
557
557
|
/* Unhandled island rejections: tracked as they happen (and rescinded
|
|
558
|
-
* when handled later), reported at
|
|
559
|
-
* static promise ledger
|
|
558
|
+
* when handled later), reported at the same completed microtask
|
|
559
|
+
* checkpoint as the static promise ledger — one voice, exit 1, like
|
|
560
|
+
* Node. The report hook drains engine jobs first so a same-checkpoint
|
|
561
|
+
* handler attachment gets its chance to rescind. */
|
|
560
562
|
JS_SetHostPromiseRejectionTracker(isl_rt, isl_rejection_tracker, NULL);
|
|
561
|
-
scr_loop_set_island_rejections(isl_report_rejections
|
|
563
|
+
scr_loop_set_island_rejections(isl_report_rejections,
|
|
564
|
+
scr_island_drain_jobs);
|
|
562
565
|
/* Armed island timers (AbortSignal.timeout) cap the loop's idle sleep
|
|
563
566
|
* so they fire on time while the poller waits on socket readiness —
|
|
564
567
|
* without keeping the loop alive by themselves (Node's unref'd timer).
|
|
@@ -2743,8 +2746,10 @@ static JSValue isl_host_exit(JSContext *ctx, JSValueConst this_val, int argc,
|
|
|
2743
2746
|
* mirrored here by the shim's setter, read by the emitted main after the
|
|
2744
2747
|
* loop drains — Node's a-program-that-sets-it-and-returns contract. */
|
|
2745
2748
|
static int isl_exit_code = 0;
|
|
2749
|
+
static size_t isl_exit_code_version = 0;
|
|
2746
2750
|
|
|
2747
2751
|
int scr_island_exit_code(void) { return isl_exit_code; }
|
|
2752
|
+
size_t scr_island_exit_code_version(void) { return isl_exit_code_version; }
|
|
2748
2753
|
|
|
2749
2754
|
static JSValue isl_host_set_exit_code(JSContext *ctx, JSValueConst this_val,
|
|
2750
2755
|
int argc, JSValueConst *argv) {
|
|
@@ -2753,6 +2758,7 @@ static JSValue isl_host_set_exit_code(JSContext *ctx, JSValueConst this_val,
|
|
|
2753
2758
|
int32_t code = 0;
|
|
2754
2759
|
JS_ToInt32(ctx, &code, argv[0]);
|
|
2755
2760
|
isl_exit_code = code;
|
|
2761
|
+
isl_exit_code_version++;
|
|
2756
2762
|
return JS_UNDEFINED;
|
|
2757
2763
|
}
|
|
2758
2764
|
|
package/src/scr_lib.c
CHANGED
|
@@ -3503,6 +3503,170 @@ ScrStr *scr_num_to_fixed0(double x) {
|
|
|
3503
3503
|
return r;
|
|
3504
3504
|
}
|
|
3505
3505
|
|
|
3506
|
+
/* The explicit-fraction-digits toFixed needs the exact binary value, not
|
|
3507
|
+
* the shortest decimal that round-trips to it: (1.005).toFixed(2) is
|
|
3508
|
+
* "1.00", for example. Represent abs(x) * 10^f as
|
|
3509
|
+
*
|
|
3510
|
+
* mantissa * 5^f * 2^(binary_exponent + f)
|
|
3511
|
+
*
|
|
3512
|
+
* in a tiny base-2^32 integer, then shift right with the spec's
|
|
3513
|
+
* round-half-up rule. The largest value handled here is below 1e21 with
|
|
3514
|
+
* f=100, so the rounded integer is below 1e121 (402 bits); sixteen limbs
|
|
3515
|
+
* leave comfortable headroom without heap allocation. */
|
|
3516
|
+
#define SCR_FIXED_LIMBS 16
|
|
3517
|
+
typedef struct {
|
|
3518
|
+
uint32_t limb[SCR_FIXED_LIMBS]; /* little-endian */
|
|
3519
|
+
int len;
|
|
3520
|
+
} ScrFixedInt;
|
|
3521
|
+
|
|
3522
|
+
static void scr_fixed_normalize(ScrFixedInt *v) {
|
|
3523
|
+
while (v->len > 1 && v->limb[v->len - 1] == 0) v->len--;
|
|
3524
|
+
}
|
|
3525
|
+
|
|
3526
|
+
static void scr_fixed_mul5(ScrFixedInt *v) {
|
|
3527
|
+
uint64_t carry = 0;
|
|
3528
|
+
for (int i = 0; i < v->len; i++) {
|
|
3529
|
+
uint64_t p = (uint64_t)v->limb[i] * 5 + carry;
|
|
3530
|
+
v->limb[i] = (uint32_t)p;
|
|
3531
|
+
carry = p >> 32;
|
|
3532
|
+
}
|
|
3533
|
+
if (carry != 0) v->limb[v->len++] = (uint32_t)carry;
|
|
3534
|
+
}
|
|
3535
|
+
|
|
3536
|
+
static bool scr_fixed_bit(const ScrFixedInt *v, int bit) {
|
|
3537
|
+
int word = bit / 32;
|
|
3538
|
+
return word < v->len && ((v->limb[word] >> (bit % 32)) & 1u) != 0;
|
|
3539
|
+
}
|
|
3540
|
+
|
|
3541
|
+
static void scr_fixed_shr(ScrFixedInt *v, int bits) {
|
|
3542
|
+
int words = bits / 32;
|
|
3543
|
+
int rem = bits % 32;
|
|
3544
|
+
if (words >= v->len) {
|
|
3545
|
+
v->limb[0] = 0;
|
|
3546
|
+
v->len = 1;
|
|
3547
|
+
return;
|
|
3548
|
+
}
|
|
3549
|
+
int n = v->len - words;
|
|
3550
|
+
for (int i = 0; i < n; i++) {
|
|
3551
|
+
uint32_t lo = v->limb[i + words] >> rem;
|
|
3552
|
+
uint32_t hi =
|
|
3553
|
+
rem != 0 && i + words + 1 < v->len
|
|
3554
|
+
? v->limb[i + words + 1] << (32 - rem)
|
|
3555
|
+
: 0;
|
|
3556
|
+
v->limb[i] = lo | hi;
|
|
3557
|
+
}
|
|
3558
|
+
v->len = n;
|
|
3559
|
+
scr_fixed_normalize(v);
|
|
3560
|
+
}
|
|
3561
|
+
|
|
3562
|
+
static void scr_fixed_shl(ScrFixedInt *v, int bits) {
|
|
3563
|
+
uint32_t out[SCR_FIXED_LIMBS] = {0};
|
|
3564
|
+
int words = bits / 32;
|
|
3565
|
+
int rem = bits % 32;
|
|
3566
|
+
for (int i = 0; i < v->len; i++) {
|
|
3567
|
+
int at = i + words;
|
|
3568
|
+
out[at] |= v->limb[i] << rem;
|
|
3569
|
+
if (rem != 0) out[at + 1] |= v->limb[i] >> (32 - rem);
|
|
3570
|
+
}
|
|
3571
|
+
int n = v->len + words + (rem != 0 ? 1 : 0);
|
|
3572
|
+
memcpy(v->limb, out, sizeof out);
|
|
3573
|
+
v->len = n;
|
|
3574
|
+
scr_fixed_normalize(v);
|
|
3575
|
+
}
|
|
3576
|
+
|
|
3577
|
+
static void scr_fixed_inc(ScrFixedInt *v) {
|
|
3578
|
+
uint64_t carry = 1;
|
|
3579
|
+
for (int i = 0; i < v->len && carry != 0; i++) {
|
|
3580
|
+
uint64_t s = (uint64_t)v->limb[i] + carry;
|
|
3581
|
+
v->limb[i] = (uint32_t)s;
|
|
3582
|
+
carry = s >> 32;
|
|
3583
|
+
}
|
|
3584
|
+
if (carry != 0) v->limb[v->len++] = (uint32_t)carry;
|
|
3585
|
+
}
|
|
3586
|
+
|
|
3587
|
+
/* Divide in place by 1e9; each quotient limb still fits uint32_t because
|
|
3588
|
+
* the carried remainder is below the divisor. Returns the remainder. */
|
|
3589
|
+
static uint32_t scr_fixed_div1e9(ScrFixedInt *v) {
|
|
3590
|
+
uint64_t rem = 0;
|
|
3591
|
+
for (int i = v->len - 1; i >= 0; i--) {
|
|
3592
|
+
uint64_t cur = (rem << 32) | v->limb[i];
|
|
3593
|
+
v->limb[i] = (uint32_t)(cur / 1000000000u);
|
|
3594
|
+
rem = cur % 1000000000u;
|
|
3595
|
+
}
|
|
3596
|
+
scr_fixed_normalize(v);
|
|
3597
|
+
return (uint32_t)rem;
|
|
3598
|
+
}
|
|
3599
|
+
|
|
3600
|
+
/* Number.prototype.toFixed(fractionDigits), with the argument already
|
|
3601
|
+
* number-typed by the frontend. ToIntegerOrInfinity validation precedes
|
|
3602
|
+
* the receiver's non-finite arm, as ECMA-262 requires. Invalid precision
|
|
3603
|
+
* raises V8's catchable RangeError text; otherwise the result is +1. */
|
|
3604
|
+
ScrStr *scr_num_to_fixed(double x, double fraction_digits) {
|
|
3605
|
+
double fd = isnan(fraction_digits) ? 0 : trunc(fraction_digits);
|
|
3606
|
+
if (!(fd >= 0 && fd <= 100)) {
|
|
3607
|
+
static const char msg[] =
|
|
3608
|
+
"toFixed() digits argument must be between 0 and 100";
|
|
3609
|
+
scr_throw_error_msg(SCR_ERR_RANGE, msg, sizeof msg - 1);
|
|
3610
|
+
return NULL;
|
|
3611
|
+
}
|
|
3612
|
+
int f = (int)fd;
|
|
3613
|
+
if (!isfinite(x) || fabs(x) >= 1e21) return scr_f64_to_scrstr(x);
|
|
3614
|
+
|
|
3615
|
+
bool neg = x < 0; /* false for -0, exactly like the spec's sign arm */
|
|
3616
|
+
double a = neg ? -x : x;
|
|
3617
|
+
uint64_t bits;
|
|
3618
|
+
memcpy(&bits, &a, sizeof bits);
|
|
3619
|
+
uint64_t mantissa = bits & ((1ull << 52) - 1);
|
|
3620
|
+
int ieee_exp = (int)((bits >> 52) & 0x7ffu);
|
|
3621
|
+
int binary_exp;
|
|
3622
|
+
if (ieee_exp == 0) {
|
|
3623
|
+
binary_exp = -1074;
|
|
3624
|
+
} else {
|
|
3625
|
+
mantissa |= 1ull << 52;
|
|
3626
|
+
binary_exp = ieee_exp - 1023 - 52;
|
|
3627
|
+
}
|
|
3628
|
+
|
|
3629
|
+
ScrFixedInt n = {{(uint32_t)mantissa, (uint32_t)(mantissa >> 32)}, 2};
|
|
3630
|
+
scr_fixed_normalize(&n);
|
|
3631
|
+
for (int i = 0; i < f; i++) scr_fixed_mul5(&n);
|
|
3632
|
+
int shift = binary_exp + f;
|
|
3633
|
+
if (shift >= 0) {
|
|
3634
|
+
scr_fixed_shl(&n, shift);
|
|
3635
|
+
} else {
|
|
3636
|
+
int right = -shift;
|
|
3637
|
+
bool round_up = scr_fixed_bit(&n, right - 1);
|
|
3638
|
+
scr_fixed_shr(&n, right);
|
|
3639
|
+
if (round_up) scr_fixed_inc(&n);
|
|
3640
|
+
}
|
|
3641
|
+
|
|
3642
|
+
/* Render the exact rounded integer through base-1e9 chunks, then place
|
|
3643
|
+
* the decimal point f digits from the right (padding through zero). */
|
|
3644
|
+
uint32_t chunks[SCR_FIXED_LIMBS];
|
|
3645
|
+
int chunk_count = 0;
|
|
3646
|
+
do {
|
|
3647
|
+
chunks[chunk_count++] = scr_fixed_div1e9(&n);
|
|
3648
|
+
} while (!(n.len == 1 && n.limb[0] == 0));
|
|
3649
|
+
|
|
3650
|
+
char digits[128];
|
|
3651
|
+
int dlen = snprintf(digits, sizeof digits, "%u", chunks[chunk_count - 1]);
|
|
3652
|
+
for (int i = chunk_count - 2; i >= 0; i--) {
|
|
3653
|
+
dlen += snprintf(digits + dlen, sizeof digits - (size_t)dlen,
|
|
3654
|
+
"%09u", chunks[i]);
|
|
3655
|
+
}
|
|
3656
|
+
|
|
3657
|
+
char out[128];
|
|
3658
|
+
int o = 0;
|
|
3659
|
+
if (neg) out[o++] = '-';
|
|
3660
|
+
int padded = dlen > f + 1 ? dlen : f + 1;
|
|
3661
|
+
int integer_digits = padded - f;
|
|
3662
|
+
int leading_zeros = padded - dlen;
|
|
3663
|
+
for (int i = 0; i < padded; i++) {
|
|
3664
|
+
if (f != 0 && i == integer_digits) out[o++] = '.';
|
|
3665
|
+
out[o++] = i < leading_zeros ? '0' : digits[i - leading_zeros];
|
|
3666
|
+
}
|
|
3667
|
+
return scr_str_new(out, (size_t)o);
|
|
3668
|
+
}
|
|
3669
|
+
|
|
3506
3670
|
/* Increment a decimal digit string in place. Returns true on overflow —
|
|
3507
3671
|
* the value becomes 1 followed by len zeros (the caller folds the zeros
|
|
3508
3672
|
* into its scale); an EMPTY string increments to "1" the same way (the
|
package/src/scr_runtime.h
CHANGED
|
@@ -317,6 +317,10 @@ bool scr_str_eq(ScrStr *a, ScrStr *b);
|
|
|
317
317
|
* <0, 0, >0. */
|
|
318
318
|
int scr_str_cmp(ScrStr *a, ScrStr *b);
|
|
319
319
|
|
|
320
|
+
/* ECMAScript string-list ordering: compare UTF-16 code units even though
|
|
321
|
+
* ScrStr stores well-formed UTF-8. Returns <0, 0, >0. */
|
|
322
|
+
int scr_str_cmp_u16(ScrStr *a, ScrStr *b);
|
|
323
|
+
|
|
320
324
|
/* ── class objects (classes as first-class values) ────────────────────
|
|
321
325
|
* The class STATIC side as a runtime value: one emitted IMMORTAL static
|
|
322
326
|
* per class the program takes as a value (`const X = C`, class
|
|
@@ -818,6 +822,16 @@ void scr_arr_set_ref(ScrArr *a, double i, void *v);
|
|
|
818
822
|
* elements retain into the copy. Borrows a. */
|
|
819
823
|
ScrArr *scr_arr_slice(ScrArr *a, double start, double end);
|
|
820
824
|
|
|
825
|
+
/* ES2023 copying methods. All borrow their inputs and return fresh +1
|
|
826
|
+
* shallow copies. with() raises Node's catchable RangeError for an invalid
|
|
827
|
+
* relative index; the ref variant retains the borrowed replacement. */
|
|
828
|
+
ScrArr *scr_arr_to_reversed(const ScrArr *a);
|
|
829
|
+
ScrArr *scr_arr_to_spliced(const ScrArr *a, double start,
|
|
830
|
+
double delete_count, const ScrArr *items);
|
|
831
|
+
ScrArr *scr_arr_with_f64(ScrArr *a, double index, double value);
|
|
832
|
+
ScrArr *scr_arr_with_bool(ScrArr *a, double index, bool value);
|
|
833
|
+
ScrArr *scr_arr_with_ref(ScrArr *a, double index, void *value);
|
|
834
|
+
|
|
821
835
|
/* push returns the new length (JS-exact); _ref takes ownership. */
|
|
822
836
|
double scr_arr_push_f64(ScrArr *a, double v);
|
|
823
837
|
double scr_arr_push_bool(ScrArr *a, bool v);
|
|
@@ -3338,6 +3352,18 @@ bool scr_await_bool(ScrPromise *p);
|
|
|
3338
3352
|
ScrStr *scr_await_str(ScrPromise *p); /* +1 */
|
|
3339
3353
|
void *scr_await_ref(ScrPromise *p); /* +1 via the stored retain */
|
|
3340
3354
|
void scr_await_void(ScrPromise *p);
|
|
3355
|
+
/* Internal ESM dependency wait: parks while pending but, unlike a
|
|
3356
|
+
* JavaScript await expression, does not hop when already settled. */
|
|
3357
|
+
void scr_module_await(ScrPromise *p);
|
|
3358
|
+
/* After the root-aware event loop returns, inspect the executable's async
|
|
3359
|
+
* module-root promise without another microtask hop: 0 = fulfilled,
|
|
3360
|
+
* 1 = rejected, 13 = still pending with no ref'd work capable of settling
|
|
3361
|
+
* it (Node's unsettled top-level-await exit status). A rejected root is
|
|
3362
|
+
* marked observed here but re-thrown separately; earlier-checkpoint
|
|
3363
|
+
* rejections were already decided by the loop and same-checkpoint
|
|
3364
|
+
* competitors are suppressed by the executable-module verdict. */
|
|
3365
|
+
int scr_promise_finish_top_level(ScrPromise *p);
|
|
3366
|
+
void scr_promise_rethrow_top_level(ScrPromise *p);
|
|
3341
3367
|
/* The checked-dynamic tree-crossing await (SCR_DYN_PROMISE's boundary contract): the
|
|
3342
3368
|
* payload as a dyn value (+1; void fulfillments answer the undefined
|
|
3343
3369
|
* value), or NULL with the rejection re-thrown into the awaiter. */
|
|
@@ -3388,23 +3414,19 @@ ScrDyn *scr_als_run(double id, ScrDyn *value, ScrDyn *fn, ScrDyn *args);
|
|
|
3388
3414
|
ScrDyn *scr_als_exit_run(double id, ScrDyn *fn, ScrDyn *args);
|
|
3389
3415
|
|
|
3390
3416
|
/* process.on/once('unhandledRejection', fn): dyn listeners dispatched
|
|
3391
|
-
* per never-observed rejection at
|
|
3392
|
-
* suppressing the default report and the
|
|
3393
|
-
*
|
|
3394
|
-
*
|
|
3395
|
-
* closure identity (the warning registry's story). Throws Node's
|
|
3417
|
+
* per never-observed rejection at the end of its nextTick/microtask
|
|
3418
|
+
* checkpoint — (reason, promise), suppressing the default report and the
|
|
3419
|
+
* exit-1. `once` auto-removes after one delivery; off removes by closure
|
|
3420
|
+
* identity (the warning registry's story). Throws Node's
|
|
3396
3421
|
* ERR_INVALID_ARG_TYPE on a non-function. */
|
|
3397
3422
|
void scr_process_on_unhandled_rejection(ScrDyn *fn, bool once);
|
|
3398
3423
|
void scr_process_off_unhandled_rejection(ScrDyn *fn);
|
|
3399
|
-
/* process.on/once/off('rejectionHandled', fn): the sibling registry.
|
|
3400
|
-
*
|
|
3401
|
-
*
|
|
3402
|
-
* handled any earlier never enters the report at all — the same
|
|
3403
|
-
* documented divergence); dispatch is synchronous at the attach, with
|
|
3404
|
-
* the promise, Node's payload. */
|
|
3424
|
+
/* process.on/once/off('rejectionHandled', fn): the sibling registry. A
|
|
3425
|
+
* promise handled after its unhandledRejection delivery fires once, with
|
|
3426
|
+
* the promise as Node's payload. */
|
|
3405
3427
|
void scr_process_on_rejection_handled(ScrDyn *fn, bool once);
|
|
3406
3428
|
void scr_process_off_rejection_handled(ScrDyn *fn);
|
|
3407
|
-
/* The
|
|
3429
|
+
/* The checkpoint delivery hook the registration above installs
|
|
3408
3430
|
* (scr_async_dyn.c → scr_report_unhandled_rejections; NULL = default
|
|
3409
3431
|
* report). Runtime-internal. */
|
|
3410
3432
|
extern bool (*scr_urj_deliver_fn)(ScrPromise *p);
|
|
@@ -3414,9 +3436,10 @@ extern bool (*scr_urj_deliver_fn)(ScrPromise *p);
|
|
|
3414
3436
|
* Runtime-internal. */
|
|
3415
3437
|
extern void (*scr_rjh_notify_fn)(ScrPromise *p);
|
|
3416
3438
|
/* The attach-time handled mark (a dyn then/catch carrying a rejection
|
|
3417
|
-
* handler
|
|
3418
|
-
*
|
|
3419
|
-
*
|
|
3439
|
+
* handler, or the module loader taking ownership of an evaluation
|
|
3440
|
+
* promise): marks pending and rejected sources observed at Node's attach
|
|
3441
|
+
* moment, firing the late-handled hook when the report already delivered
|
|
3442
|
+
* it. Runtime-internal. */
|
|
3420
3443
|
void scr_promise_mark_handled(ScrPromise *p);
|
|
3421
3444
|
/* A rejected promise's reason as a dyn value (identity-preserving for
|
|
3422
3445
|
* dyn payloads and %Error instances). +1. */
|
|
@@ -3646,7 +3669,13 @@ int scr_exit_code_hint_get(void);
|
|
|
3646
3669
|
* poll(2) sleep, or -1 when it can't wake for every pending child. */
|
|
3647
3670
|
int scr_children_wake_fd(void);
|
|
3648
3671
|
double scr_now_ms(void); /* the loop's monotonic clock, in ms */
|
|
3649
|
-
|
|
3672
|
+
/* Run to ordinary loop exhaustion, except that a non-NULL executable
|
|
3673
|
+
* module-root promise stops the loop as soon as it is rejected at a
|
|
3674
|
+
* microtask checkpoint. Fulfilled roots do not stop the loop: Node keeps
|
|
3675
|
+
* running ref'd work scheduled by a successfully evaluated module.
|
|
3676
|
+
* Returns true when a default/listener-crashing unhandled rejection
|
|
3677
|
+
* already selected and reported exit status 1. */
|
|
3678
|
+
bool scr_loop_run(ScrPromise *top_level);
|
|
3650
3679
|
/* External I/O hook, polled at loop quiescence like the child registry:
|
|
3651
3680
|
* `pending` keeps the loop alive; `poll` makes progress and may SLEEP up
|
|
3652
3681
|
* to max_wait_ms (on real fds — socket readiness wakes it early), so the
|
|
@@ -3655,12 +3684,14 @@ void scr_loop_run(void);
|
|
|
3655
3684
|
* never set it. */
|
|
3656
3685
|
void scr_loop_set_io(bool (*pending)(void), void (*poll)(double max_wait_ms));
|
|
3657
3686
|
bool scr_report_unhandled_rejections(void); /* true = exit 1 */
|
|
3687
|
+
void scr_discard_unhandled_rejections(void);
|
|
3658
3688
|
/* The island's unhandled-rejection ledger joins the report above (one
|
|
3659
3689
|
* registrant, set at engine boot; static builds never set it): called with
|
|
3660
3690
|
* print=true when the static ledger reported nothing, prints its FIRST
|
|
3661
3691
|
* never-observed rejection in the same voice, frees the ledger either way,
|
|
3662
3692
|
* and returns whether it had any. */
|
|
3663
|
-
void scr_loop_set_island_rejections(bool (*fn)(bool print)
|
|
3693
|
+
void scr_loop_set_island_rejections(bool (*fn)(bool print),
|
|
3694
|
+
int (*drain_jobs)(void));
|
|
3664
3695
|
/* The island's earliest armed timer deadline (scr_island_timers_deadline;
|
|
3665
3696
|
* HUGE_VAL when none) joins the loop's sleep computation: an armed
|
|
3666
3697
|
* AbortSignal.timeout must fire on time while the loop sleeps on socket
|
|
@@ -3874,8 +3905,10 @@ bool scr_zlib_inflate_exact(const unsigned char *src, size_t src_len,
|
|
|
3874
3905
|
|
|
3875
3906
|
/* The island process shim's implicit exit status (process.exitCode):
|
|
3876
3907
|
* Node's set-it-and-return-normally contract. The emitted main returns
|
|
3877
|
-
* this after the loop drains; 0 when never set.
|
|
3908
|
+
* this after the loop drains; 0 when never set. The version advances on
|
|
3909
|
+
* every assignment so an exit listener can override a provisional status. */
|
|
3878
3910
|
int scr_island_exit_code(void);
|
|
3911
|
+
size_t scr_island_exit_code_version(void);
|
|
3879
3912
|
|
|
3880
3913
|
/* ── web-platform globals (scr_web.c) ─────────────────────────────────
|
|
3881
3914
|
* The pure-JS prelude defining the island's WHATWG subset (streams et
|
|
@@ -4243,12 +4276,14 @@ bool scr_num_is_finite(double x);
|
|
|
4243
4276
|
bool scr_num_is_nan(double x);
|
|
4244
4277
|
bool scr_num_is_integer(double x);
|
|
4245
4278
|
bool scr_num_is_safe_integer(double x);
|
|
4246
|
-
/*
|
|
4247
|
-
*
|
|
4248
|
-
*
|
|
4249
|
-
*
|
|
4279
|
+
/* Number.prototype formatters: toExponential() is the fraction-digits-free
|
|
4280
|
+
* shortest correctly-rounded mantissa; toFixed0 is the non-throwing omitted
|
|
4281
|
+
* argument form. toFixed implements an explicit 0..100 fractionDigits with
|
|
4282
|
+
* exact binary-value rounding and THROWS V8's catchable RangeError outside
|
|
4283
|
+
* that range. All successful results are +1. */
|
|
4250
4284
|
ScrStr *scr_num_to_exponential(double x);
|
|
4251
4285
|
ScrStr *scr_num_to_fixed0(double x);
|
|
4286
|
+
ScrStr *scr_num_to_fixed(double x, double fraction_digits);
|
|
4252
4287
|
|
|
4253
4288
|
/* Object.is over two numbers — the spec's SameValue on doubles: NaN
|
|
4254
4289
|
* equals NaN, +0 differs from -0, everything else is ==. Never throws. */
|
|
@@ -4439,6 +4474,17 @@ void scr_bytes_set(ScrBytes *b, double i, double v);
|
|
|
4439
4474
|
* result is a fresh same-kind copy. Never throws. */
|
|
4440
4475
|
ScrBytes *scr_bytes_slice(const ScrBytes *b, double start, double end); /* +1 */
|
|
4441
4476
|
|
|
4477
|
+
/* ES2023 typed-array copying methods. Both preserve the receiver's element
|
|
4478
|
+
* kind and return a fresh +1 owner. with() raises Node's catchable
|
|
4479
|
+
* "Invalid typed array index" RangeError for an invalid relative index. */
|
|
4480
|
+
ScrBytes *scr_bytes_to_reversed(const ScrBytes *b); /* +1 */
|
|
4481
|
+
ScrBytes *scr_bytes_with(const ScrBytes *b, double index, double value); /* +1 */
|
|
4482
|
+
|
|
4483
|
+
/* Numeric typed-array iteration drained into number[], and Uint8Array.join.
|
|
4484
|
+
* Inputs borrowed; results fresh +1. */
|
|
4485
|
+
ScrArr *scr_bytes_to_arr(const ScrBytes *b); /* +1 */
|
|
4486
|
+
ScrStr *scr_bytes_join(const ScrBytes *b, const ScrStr *separator); /* +1 */
|
|
4487
|
+
|
|
4442
4488
|
/* TypedArray.prototype.fill on non-u8 receivers: per-element fill with
|
|
4443
4489
|
* the element write's coercion, slice-clamped relative indices; answers
|
|
4444
4490
|
* the receiver +1 (chaining). Never throws. */
|
package/src/scr_string.c
CHANGED
|
@@ -198,6 +198,64 @@ int scr_str_cmp(ScrStr *a, ScrStr *b) {
|
|
|
198
198
|
return a->len < b->len ? -1 : (a->len > b->len ? 1 : 0);
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
+
/* UTF-16 code-unit comparison over well-formed UTF-8 (the default
|
|
202
|
+
* Array.sort/toSorted and URLSearchParams.sort order). Byte order ALMOST
|
|
203
|
+
* matches — the exception is U+E000..U+FFFF (3-byte UTF-8, single high code
|
|
204
|
+
* units) vs supplementary code points (4-byte UTF-8, surrogate pairs
|
|
205
|
+
* 0xD800..0xDFFF): bytes put the 4-byte form last, code units put it first.
|
|
206
|
+
* Decode code points and compare their leading UTF-16 units. */
|
|
207
|
+
static uint32_t scr_str_lead_u16(const unsigned char *s, size_t len,
|
|
208
|
+
size_t *adv) {
|
|
209
|
+
unsigned char b = s[0];
|
|
210
|
+
uint32_t cp;
|
|
211
|
+
size_t n;
|
|
212
|
+
if (b < 0x80) {
|
|
213
|
+
cp = b; n = 1;
|
|
214
|
+
} else if ((b & 0xe0) == 0xc0) {
|
|
215
|
+
cp = b & 0x1fu; n = 2;
|
|
216
|
+
} else if ((b & 0xf0) == 0xe0) {
|
|
217
|
+
cp = b & 0x0fu; n = 3;
|
|
218
|
+
} else {
|
|
219
|
+
cp = b & 0x07u; n = 4;
|
|
220
|
+
}
|
|
221
|
+
if (n > len) n = len; /* defensive: strings are well-formed by contract */
|
|
222
|
+
for (size_t i = 1; i < n; i++) cp = (cp << 6) | (s[i] & 0x3fu);
|
|
223
|
+
*adv = n;
|
|
224
|
+
if (cp >= 0x10000) return 0xd800 + ((cp - 0x10000) >> 10);
|
|
225
|
+
return cp;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
int scr_str_cmp_u16(ScrStr *a, ScrStr *b) {
|
|
229
|
+
size_t ia = 0, ib = 0;
|
|
230
|
+
while (ia < a->len && ib < b->len) {
|
|
231
|
+
size_t na, nb;
|
|
232
|
+
uint32_t ua = scr_str_lead_u16(
|
|
233
|
+
(const unsigned char *)a->data + ia, a->len - ia, &na);
|
|
234
|
+
uint32_t ub = scr_str_lead_u16(
|
|
235
|
+
(const unsigned char *)b->data + ib, b->len - ib, &nb);
|
|
236
|
+
if (ua != ub) return ua < ub ? -1 : 1;
|
|
237
|
+
/* Equal leading units: equal whole code points (both single units or
|
|
238
|
+
* both pairs with equal highs — lows only differ if cps differ). */
|
|
239
|
+
if (na == 4 && nb == 4) {
|
|
240
|
+
uint32_t cpa = 0, cpb = 0;
|
|
241
|
+
for (size_t i = 0; i < 4; i++) {
|
|
242
|
+
cpa = (cpa << 6) |
|
|
243
|
+
(i == 0 ? (unsigned char)a->data[ia] & 0x07u
|
|
244
|
+
: (unsigned char)a->data[ia + i] & 0x3fu);
|
|
245
|
+
cpb = (cpb << 6) |
|
|
246
|
+
(i == 0 ? (unsigned char)b->data[ib] & 0x07u
|
|
247
|
+
: (unsigned char)b->data[ib + i] & 0x3fu);
|
|
248
|
+
}
|
|
249
|
+
if (cpa != cpb) return cpa < cpb ? -1 : 1;
|
|
250
|
+
}
|
|
251
|
+
ia += na;
|
|
252
|
+
ib += nb;
|
|
253
|
+
}
|
|
254
|
+
if (ia < a->len) return 1;
|
|
255
|
+
if (ib < b->len) return -1;
|
|
256
|
+
return 0;
|
|
257
|
+
}
|
|
258
|
+
|
|
201
259
|
/* ── interned strings ─────────────────────────────────────────────────
|
|
202
260
|
* The empty string and every single-character ASCII string are immortal
|
|
203
261
|
* statics (same layout the emitter uses for literals): charAt/slice churn
|
|
@@ -1272,4 +1330,3 @@ ScrStr *scr_str_decode_uri_component(ScrStr *s) {
|
|
|
1272
1330
|
}
|
|
1273
1331
|
return out;
|
|
1274
1332
|
}
|
|
1275
|
-
|
package/src/scr_url_params.c
CHANGED
|
@@ -467,57 +467,6 @@ bool scr_sp_has_value(ScrSearchParams *sp, ScrStr *name, ScrStr *value) {
|
|
|
467
467
|
return false;
|
|
468
468
|
}
|
|
469
469
|
|
|
470
|
-
/* UTF-16 code-unit comparison over well-formed UTF-8 (the spec's sort
|
|
471
|
-
* order). Byte order ALMOST matches — the exception is U+E000..U+FFFF
|
|
472
|
-
* (3-byte UTF-8, single high code units) vs supplementary code points
|
|
473
|
-
* (4-byte UTF-8, surrogate pairs 0xD800..0xDFFF): bytes put the 4-byte
|
|
474
|
-
* form last, code units put it first. Decode code points and compare
|
|
475
|
-
* their leading UTF-16 units. */
|
|
476
|
-
static uint32_t sp_lead_unit(const unsigned char *s, size_t len, size_t *adv) {
|
|
477
|
-
unsigned char b = s[0];
|
|
478
|
-
uint32_t cp;
|
|
479
|
-
size_t n;
|
|
480
|
-
if (b < 0x80) {
|
|
481
|
-
cp = b; n = 1;
|
|
482
|
-
} else if ((b & 0xe0) == 0xc0) {
|
|
483
|
-
cp = b & 0x1fu; n = 2;
|
|
484
|
-
} else if ((b & 0xf0) == 0xe0) {
|
|
485
|
-
cp = b & 0x0fu; n = 3;
|
|
486
|
-
} else {
|
|
487
|
-
cp = b & 0x07u; n = 4;
|
|
488
|
-
}
|
|
489
|
-
if (n > len) n = len; /* defensive: strings are well-formed by contract */
|
|
490
|
-
for (size_t i = 1; i < n; i++) cp = (cp << 6) | (s[i] & 0x3fu);
|
|
491
|
-
*adv = n;
|
|
492
|
-
if (cp >= 0x10000) return 0xd800 + ((cp - 0x10000) >> 10); /* high surrogate leads */
|
|
493
|
-
return cp;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
static int sp_cmp_u16(const ScrStr *a, const ScrStr *b) {
|
|
497
|
-
size_t ia = 0, ib = 0;
|
|
498
|
-
while (ia < a->len && ib < b->len) {
|
|
499
|
-
size_t na, nb;
|
|
500
|
-
uint32_t ua = sp_lead_unit((const unsigned char *)a->data + ia, a->len - ia, &na);
|
|
501
|
-
uint32_t ub = sp_lead_unit((const unsigned char *)b->data + ib, b->len - ib, &nb);
|
|
502
|
-
if (ua != ub) return ua < ub ? -1 : 1;
|
|
503
|
-
/* Equal LEADING units: equal whole code points (both single units or
|
|
504
|
-
* both pairs with equal highs — lows only differ if cps differ). */
|
|
505
|
-
if (na == 4 && nb == 4) {
|
|
506
|
-
uint32_t cpa = 0, cpb = 0;
|
|
507
|
-
for (size_t i = 0; i < 4; i++) {
|
|
508
|
-
cpa = (cpa << 6) | ((i == 0 ? a->data[ia] & 0x07 : a->data[ia + i] & 0x3f));
|
|
509
|
-
cpb = (cpb << 6) | ((i == 0 ? b->data[ib] & 0x07 : b->data[ib + i] & 0x3f));
|
|
510
|
-
}
|
|
511
|
-
if (cpa != cpb) return cpa < cpb ? -1 : 1;
|
|
512
|
-
}
|
|
513
|
-
ia += na;
|
|
514
|
-
ib += nb;
|
|
515
|
-
}
|
|
516
|
-
if (ia < a->len) return 1;
|
|
517
|
-
if (ib < b->len) return -1;
|
|
518
|
-
return 0;
|
|
519
|
-
}
|
|
520
|
-
|
|
521
470
|
/* Stable insertion sort by name (lists are small; equal names keep their
|
|
522
471
|
* relative order — the spec's stability requirement). */
|
|
523
472
|
void scr_sp_sort(ScrSearchParams *sp) {
|
|
@@ -525,7 +474,7 @@ void scr_sp_sort(ScrSearchParams *sp) {
|
|
|
525
474
|
ScrStr *n = sp->names[i];
|
|
526
475
|
ScrStr *v = sp->vals[i];
|
|
527
476
|
size_t j = i;
|
|
528
|
-
while (j > 0 &&
|
|
477
|
+
while (j > 0 && scr_str_cmp_u16(sp->names[j - 1], n) > 0) {
|
|
529
478
|
sp->names[j] = sp->names[j - 1];
|
|
530
479
|
sp->vals[j] = sp->vals[j - 1];
|
|
531
480
|
j--;
|