@scriptc/runtime 0.0.22 → 0.0.23
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 +5 -2
- package/src/scr_cycle.c +403 -62
- package/src/scr_fetch.c +652 -77
- package/src/scr_json.c +86 -2
- package/src/scr_lib.c +277 -18
- package/src/scr_runtime.h +87 -10
package/src/scr_json.c
CHANGED
|
@@ -1637,8 +1637,17 @@ ScrStr *scr_dyn_to_string(const ScrDyn *d, const ScrStr *enc) {
|
|
|
1637
1637
|
case SCR_DYN_OBJ:
|
|
1638
1638
|
return scr_str_new("[object Object]", 15);
|
|
1639
1639
|
case SCR_DYN_HANDLE:
|
|
1640
|
-
|
|
1641
|
-
|
|
1640
|
+
if (d->v.handle.tag >= SCR_DYNH_ABORT_SIGNAL &&
|
|
1641
|
+
d->v.handle.tag <= SCR_DYNH_ABORT_CONTROLLER) {
|
|
1642
|
+
ScrJsonBuf b;
|
|
1643
|
+
scr_jb_init(&b);
|
|
1644
|
+
scr_jb_puts(&b, "[object ");
|
|
1645
|
+
scr_jb_puts(&b, scr_dyn_handle_cls(d));
|
|
1646
|
+
scr_jb_putc(&b, ']');
|
|
1647
|
+
return scr_jb_finish(&b);
|
|
1648
|
+
}
|
|
1649
|
+
/* IncomingMessage/ServerResponse/Socket and the other Node handles
|
|
1650
|
+
* inherit Object.prototype.toString without a @@toStringTag. */
|
|
1642
1651
|
return scr_str_new("[object Object]", 15);
|
|
1643
1652
|
case SCR_DYN_PROMISE:
|
|
1644
1653
|
/* Object.prototype.toString with the Promise @@toStringTag. */
|
|
@@ -1789,6 +1798,81 @@ ScrStr *scr_dyn_string_coerce_js(const ScrDyn *d) {
|
|
|
1789
1798
|
return scr_dyn_string_coerce(d);
|
|
1790
1799
|
}
|
|
1791
1800
|
|
|
1801
|
+
/* JS ToNumber over a checked-dynamic value, including OrdinaryToPrimitive's
|
|
1802
|
+
* NUMBER hint for object snapshots. This is the numeric twin of
|
|
1803
|
+
* scr_dyn_string_coerce_js above: valueOf precedes toString, inherited
|
|
1804
|
+
* Object.prototype.valueOf returns the receiver (so conversion continues),
|
|
1805
|
+
* and the inherited toString fallback supplies "[object Object]". Borrows d;
|
|
1806
|
+
* false means an object hook threw or no primitive value could be produced. */
|
|
1807
|
+
bool scr_dyn_number_coerce_js(const ScrDyn *d, double *out) {
|
|
1808
|
+
if (d->kind == SCR_DYN_TYPED_REF) {
|
|
1809
|
+
ScrDyn *materialized = scr_dyn_typed_ref_materialize(d);
|
|
1810
|
+
bool ok = scr_dyn_number_coerce_js(materialized, out);
|
|
1811
|
+
scr_dyn_release(materialized);
|
|
1812
|
+
return ok;
|
|
1813
|
+
}
|
|
1814
|
+
switch (d->kind) {
|
|
1815
|
+
case SCR_DYN_NULL:
|
|
1816
|
+
*out = 0.0;
|
|
1817
|
+
return true;
|
|
1818
|
+
case SCR_DYN_BOOL:
|
|
1819
|
+
*out = d->v.b ? 1.0 : 0.0;
|
|
1820
|
+
return true;
|
|
1821
|
+
case SCR_DYN_NUM:
|
|
1822
|
+
*out = d->v.num;
|
|
1823
|
+
return true;
|
|
1824
|
+
case SCR_DYN_STR:
|
|
1825
|
+
*out = scr_string_to_number(d->v.str);
|
|
1826
|
+
return true;
|
|
1827
|
+
case SCR_DYN_UNDEF:
|
|
1828
|
+
*out = NAN;
|
|
1829
|
+
return true;
|
|
1830
|
+
case SCR_DYN_OBJ: {
|
|
1831
|
+
static const char *const hint[2] = { "valueOf", "toString" };
|
|
1832
|
+
for (int i = 0; i < 2; i++) {
|
|
1833
|
+
ScrDyn *m = scr_dyn_obj_get(d, hint[i], strlen(hint[i])); /* borrowed */
|
|
1834
|
+
if (!m) {
|
|
1835
|
+
if (!d->null_proto && i == 0) {
|
|
1836
|
+
/* Inherited Object.prototype.valueOf returns the object, so the
|
|
1837
|
+
* number-hint protocol advances to toString. */
|
|
1838
|
+
continue;
|
|
1839
|
+
}
|
|
1840
|
+
if (!d->null_proto && i == 1) {
|
|
1841
|
+
*out = NAN; /* Number("[object Object]") */
|
|
1842
|
+
return true;
|
|
1843
|
+
}
|
|
1844
|
+
continue;
|
|
1845
|
+
}
|
|
1846
|
+
if (m->kind != SCR_DYN_FUNC) continue;
|
|
1847
|
+
scr_dyn_this_push_dyn(d);
|
|
1848
|
+
ScrDyn *r = scr_dyn_call(m, NULL, 0, hint[i]);
|
|
1849
|
+
scr_dyn_this_pop();
|
|
1850
|
+
if (!r) return false;
|
|
1851
|
+
if (scr_dyn_to_primitive_result_is_object(r)) {
|
|
1852
|
+
scr_dyn_release(r);
|
|
1853
|
+
continue;
|
|
1854
|
+
}
|
|
1855
|
+
bool ok = scr_dyn_number_coerce_js(r, out);
|
|
1856
|
+
scr_dyn_release(r);
|
|
1857
|
+
return ok;
|
|
1858
|
+
}
|
|
1859
|
+
static const char msg[] = "Cannot convert object to primitive value";
|
|
1860
|
+
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
|
|
1861
|
+
return false;
|
|
1862
|
+
}
|
|
1863
|
+
default: {
|
|
1864
|
+
/* Arrays, byte views, functions, promises, and native handles inherit a
|
|
1865
|
+
* valueOf that returns the receiver, then use their existing JS-exact
|
|
1866
|
+
* string rendering for the numeric conversion. */
|
|
1867
|
+
ScrStr *text = scr_dyn_string_coerce(d);
|
|
1868
|
+
if (!text) return false;
|
|
1869
|
+
*out = scr_string_to_number(text);
|
|
1870
|
+
scr_str_release(text);
|
|
1871
|
+
return true;
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1792
1876
|
/* The checked-dynamic keyed WRITE (`h.k = v` on a dyn receiver): OBJ sets
|
|
1793
1877
|
* the member (later writes win, insertion order — JS); undefined/null
|
|
1794
1878
|
* throws Node's "Cannot set properties of ..."; every other kind throws
|
package/src/scr_lib.c
CHANGED
|
@@ -1646,12 +1646,77 @@ double scr_fs_open(ScrStr *path, ScrStr *flags) {
|
|
|
1646
1646
|
|
|
1647
1647
|
/* fs.closeSync(fd) — close(2); failure throws Node's path-less fs error
|
|
1648
1648
|
* shape ("EBADF: bad file descriptor, close"). */
|
|
1649
|
-
|
|
1649
|
+
|
|
1650
|
+
/* Offset-preserving read for fs.readSync's numeric-position form. POSIX
|
|
1651
|
+
* supplies pread(2). Windows follows libuv's synchronous-handle recipe:
|
|
1652
|
+
* ReadFile with an OVERLAPPED byte offset, then restore the handle position
|
|
1653
|
+
* because synchronous ReadFile updates it even when OVERLAPPED is present. */
|
|
1654
|
+
static ssize_t scr_fs_pread(int fd, void *data, size_t length, double position) {
|
|
1655
|
+
#ifdef _WIN32
|
|
1656
|
+
HANDLE handle = (HANDLE)_get_osfhandle(fd);
|
|
1657
|
+
if (handle == INVALID_HANDLE_VALUE) {
|
|
1658
|
+
errno = EBADF;
|
|
1659
|
+
return -1;
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
OVERLAPPED overlapped;
|
|
1663
|
+
memset(&overlapped, 0, sizeof overlapped);
|
|
1664
|
+
LARGE_INTEGER at;
|
|
1665
|
+
at.QuadPart = (LONGLONG)position;
|
|
1666
|
+
overlapped.Offset = at.LowPart;
|
|
1667
|
+
overlapped.OffsetHigh = at.HighPart;
|
|
1668
|
+
|
|
1669
|
+
LARGE_INTEGER zero;
|
|
1670
|
+
LARGE_INTEGER original;
|
|
1671
|
+
zero.QuadPart = 0;
|
|
1672
|
+
BOOL restore = SetFilePointerEx(handle, zero, &original, FILE_CURRENT);
|
|
1673
|
+
DWORD got = 0;
|
|
1674
|
+
DWORD want = length > (size_t)UINT32_MAX ? UINT32_MAX : (DWORD)length;
|
|
1675
|
+
BOOL ok = ReadFile(handle, data, want, &got, &overlapped);
|
|
1676
|
+
DWORD error = ok ? ERROR_SUCCESS : GetLastError();
|
|
1677
|
+
if (restore) (void)SetFilePointerEx(handle, original, NULL, FILE_BEGIN);
|
|
1678
|
+
/* ReadFile may report a terminal status after still filling part of the
|
|
1679
|
+
* caller's buffer (notably ERROR_MORE_DATA for a message-mode pipe).
|
|
1680
|
+
* libuv/Node return those bytes and surface the status on the next read. */
|
|
1681
|
+
if (ok || got > 0 || error == ERROR_HANDLE_EOF || error == ERROR_BROKEN_PIPE) {
|
|
1682
|
+
return (ssize_t)got;
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
/* The read-specific subset of libuv's Win32-to-errno translation. */
|
|
1686
|
+
switch (error) {
|
|
1687
|
+
case ERROR_INVALID_HANDLE:
|
|
1688
|
+
case ERROR_ACCESS_DENIED:
|
|
1689
|
+
errno = EBADF;
|
|
1690
|
+
break;
|
|
1691
|
+
case ERROR_INVALID_FUNCTION:
|
|
1692
|
+
case ERROR_INVALID_PARAMETER:
|
|
1693
|
+
errno = EINVAL;
|
|
1694
|
+
break;
|
|
1695
|
+
case ERROR_NOT_ENOUGH_MEMORY:
|
|
1696
|
+
case ERROR_OUTOFMEMORY:
|
|
1697
|
+
errno = ENOMEM;
|
|
1698
|
+
break;
|
|
1699
|
+
case ERROR_OPERATION_ABORTED:
|
|
1700
|
+
errno = EINTR;
|
|
1701
|
+
break;
|
|
1702
|
+
default:
|
|
1703
|
+
errno = EIO;
|
|
1704
|
+
break;
|
|
1705
|
+
}
|
|
1706
|
+
return -1;
|
|
1707
|
+
#else
|
|
1708
|
+
return pread(fd, data, length, (off_t)position);
|
|
1709
|
+
#endif
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
/* fs.readSync(fd, buffer, offset, length[, position]) — the buffer form.
|
|
1650
1713
|
* Node validates offset/length against the buffer before reading and
|
|
1651
1714
|
* throws ERR_OUT_OF_RANGE; here the checks clamp to the same contract and
|
|
1652
|
-
* throw the RangeError shape.
|
|
1653
|
-
*
|
|
1654
|
-
|
|
1715
|
+
* throw the RangeError shape. Position -1 means the fd's current offset;
|
|
1716
|
+
* nonnegative positions do not advance it. Returns the byte count the OS
|
|
1717
|
+
* reports; errors carry the errno name like the other fd operations. */
|
|
1718
|
+
double scr_fs_read_sync(double fd, ScrBytes *buf, double offset, double length,
|
|
1719
|
+
double position) {
|
|
1655
1720
|
size_t bytelen = buf->len; /* u8 buffers: elem count == byte count */
|
|
1656
1721
|
char msg[160];
|
|
1657
1722
|
int mlen;
|
|
@@ -1659,7 +1724,28 @@ double scr_fs_read_sync(double fd, ScrBytes *buf, double offset, double length)
|
|
|
1659
1724
|
* form, length against the remaining window (validateOffset vs the
|
|
1660
1725
|
* buffer-bounds check in fs.readSync). */
|
|
1661
1726
|
char numbuf[40];
|
|
1662
|
-
if (offset
|
|
1727
|
+
if (!(isfinite(offset) && trunc(offset) == offset)) {
|
|
1728
|
+
char recv[48];
|
|
1729
|
+
scr_num_received(offset, recv);
|
|
1730
|
+
mlen = snprintf(msg, sizeof msg,
|
|
1731
|
+
"The value of \"offset\" is out of range. It must be an integer. Received %s",
|
|
1732
|
+
recv);
|
|
1733
|
+
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)mlen, "ERR_OUT_OF_RANGE");
|
|
1734
|
+
return 0;
|
|
1735
|
+
}
|
|
1736
|
+
if (offset < 0 || offset > 9007199254740991.0) {
|
|
1737
|
+
numbuf[scr_f64_to_str(offset, numbuf)] = 0;
|
|
1738
|
+
mlen = snprintf(msg, sizeof msg,
|
|
1739
|
+
"The value of \"offset\" is out of range. It must be >= 0 && <= 9007199254740991. Received %s",
|
|
1740
|
+
numbuf);
|
|
1741
|
+
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)mlen, "ERR_OUT_OF_RANGE");
|
|
1742
|
+
return 0;
|
|
1743
|
+
}
|
|
1744
|
+
/* Node returns after offset's intrinsic validation when the requested
|
|
1745
|
+
* length normalizes to zero: buffer-window bounds, position, and fd are
|
|
1746
|
+
* not consulted. Preserve the existing size_t coercion's [0, 1) case. */
|
|
1747
|
+
if (length >= 0 && length < 1) return 0;
|
|
1748
|
+
if (offset > (double)bytelen) {
|
|
1663
1749
|
numbuf[scr_f64_to_str(offset, numbuf)] = 0;
|
|
1664
1750
|
mlen = snprintf(msg, sizeof msg,
|
|
1665
1751
|
"The value of \"offset\" is out of range. It must be >= 0 && <= 9007199254740991. Received %s",
|
|
@@ -1677,7 +1763,27 @@ double scr_fs_read_sync(double fd, ScrBytes *buf, double offset, double length)
|
|
|
1677
1763
|
return 0;
|
|
1678
1764
|
}
|
|
1679
1765
|
size_t want = (size_t)length;
|
|
1680
|
-
|
|
1766
|
+
if (!(isfinite(position) && trunc(position) == position)) {
|
|
1767
|
+
char recv[48];
|
|
1768
|
+
scr_num_received(position, recv);
|
|
1769
|
+
mlen = snprintf(msg, sizeof msg,
|
|
1770
|
+
"The value of \"position\" is out of range. It must be an integer. Received %s",
|
|
1771
|
+
recv);
|
|
1772
|
+
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)mlen, "ERR_OUT_OF_RANGE");
|
|
1773
|
+
return 0;
|
|
1774
|
+
}
|
|
1775
|
+
if (position < -1 || position > 9007199254740991.0) {
|
|
1776
|
+
char recv[48];
|
|
1777
|
+
scr_num_received(position, recv);
|
|
1778
|
+
mlen = snprintf(msg, sizeof msg,
|
|
1779
|
+
"The value of \"position\" is out of range. It must be >= -1 && <= 9007199254740991. Received %s",
|
|
1780
|
+
recv);
|
|
1781
|
+
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)mlen, "ERR_OUT_OF_RANGE");
|
|
1782
|
+
return 0;
|
|
1783
|
+
}
|
|
1784
|
+
ssize_t n = position == -1
|
|
1785
|
+
? read((int)fd, buf->data + off, want)
|
|
1786
|
+
: scr_fs_pread((int)fd, buf->data + off, want, position);
|
|
1681
1787
|
if (n < 0) {
|
|
1682
1788
|
int e = errno;
|
|
1683
1789
|
char namebuf[16];
|
|
@@ -3214,9 +3320,10 @@ double scr_str_last_index_of(ScrStr *s, ScrStr *needle) {
|
|
|
3214
3320
|
return -1.0;
|
|
3215
3321
|
}
|
|
3216
3322
|
|
|
3217
|
-
/* ── Date, the
|
|
3218
|
-
*
|
|
3219
|
-
*
|
|
3323
|
+
/* ── Date, the read-only value slice ───────────────────────────────────
|
|
3324
|
+
* Values are TimeClip'd epoch-millisecond scalars. Identity/mutation are
|
|
3325
|
+
* frontend-fenced; construction, storage, getters, and ISO formatting
|
|
3326
|
+
* observe exactly this payload. */
|
|
3220
3327
|
|
|
3221
3328
|
double scr_date_now(void) {
|
|
3222
3329
|
struct timespec ts;
|
|
@@ -3225,6 +3332,16 @@ double scr_date_now(void) {
|
|
|
3225
3332
|
return floor((double)ts.tv_sec * 1000.0 + (double)ts.tv_nsec / 1e6);
|
|
3226
3333
|
}
|
|
3227
3334
|
|
|
3335
|
+
/* Date's TimeClip: non-finite/out-of-range values become Invalid Date,
|
|
3336
|
+
* finite values truncate toward zero, and -0 normalizes to +0. */
|
|
3337
|
+
double scr_date_new_ms(double ms) {
|
|
3338
|
+
if (!isfinite(ms) || fabs(ms) > 8640000000000000.0) return NAN;
|
|
3339
|
+
double clipped = trunc(ms);
|
|
3340
|
+
return clipped == 0 ? 0 : clipped;
|
|
3341
|
+
}
|
|
3342
|
+
|
|
3343
|
+
double scr_date_get_time(double ms) { return ms; }
|
|
3344
|
+
|
|
3228
3345
|
/* Node's Date.prototype.toISOString over a millisecond time value:
|
|
3229
3346
|
* TimeClip's ToInteger truncation, proleptic Gregorian civil-from-days
|
|
3230
3347
|
* (Howard Hinnant's algorithm), YYYY-MM-DDTHH:mm:ss.sssZ with expanded
|
|
@@ -3291,7 +3408,7 @@ static double scr_days_from_civil(long long y, int m, int d) {
|
|
|
3291
3408
|
return (double)(era * 146097 + (long long)doe - 719468);
|
|
3292
3409
|
}
|
|
3293
3410
|
|
|
3294
|
-
static double
|
|
3411
|
+
static double scr_date_make_ms(long long y, int mo, int d, int hh, int mi, int ss, int ms) {
|
|
3295
3412
|
/* V8 accepts days 1..31 in every month and ROLLS OVER past the month's
|
|
3296
3413
|
* end (Feb 30 → Mar 2) — days_from_civil extrapolates linearly, so the
|
|
3297
3414
|
* rollover falls out; day 0 and 32+ are NaN, like V8. */
|
|
@@ -3299,10 +3416,13 @@ static double scr_date_ms_of(long long y, int mo, int d, int hh, int mi, int ss,
|
|
|
3299
3416
|
if (hh > 24 || mi > 59 || ss > 59 || (hh == 24 && (mi || ss || ms))) return NAN;
|
|
3300
3417
|
double t = scr_days_from_civil(y, mo, d) * 86400000.0 +
|
|
3301
3418
|
hh * 3600000.0 + mi * 60000.0 + ss * 1000.0 + ms;
|
|
3302
|
-
if (fabs(t) > 8640000000000000.0) return NAN;
|
|
3303
3419
|
return t;
|
|
3304
3420
|
}
|
|
3305
3421
|
|
|
3422
|
+
static double scr_date_ms_of(long long y, int mo, int d, int hh, int mi, int ss, int ms) {
|
|
3423
|
+
return scr_date_new_ms(scr_date_make_ms(y, mo, d, hh, mi, ss, ms));
|
|
3424
|
+
}
|
|
3425
|
+
|
|
3306
3426
|
static bool scr_date_digits(const char **p, const char *end, int n, int *out) {
|
|
3307
3427
|
int v = 0;
|
|
3308
3428
|
if (end - *p < n) return false;
|
|
@@ -3358,6 +3478,7 @@ double scr_date_parse_get_time(ScrStr *s) {
|
|
|
3358
3478
|
bool neg = *p == '-';
|
|
3359
3479
|
p++;
|
|
3360
3480
|
if (!scr_date_digits(&p, end, 6, &y6)) return NAN;
|
|
3481
|
+
if (neg && y6 == 0) return NAN; /* ECMA forbids expanded -000000 */
|
|
3361
3482
|
yy = neg ? -(long long)y6 : y6;
|
|
3362
3483
|
} else {
|
|
3363
3484
|
if (!scr_date_digits(&p, end, 4, &y)) return NAN;
|
|
@@ -3393,14 +3514,18 @@ double scr_date_parse_get_time(ScrStr *s) {
|
|
|
3393
3514
|
p++;
|
|
3394
3515
|
if (!scr_date_digits(&p, end, 2, &oh) || p >= end || *p++ != ':') return NAN;
|
|
3395
3516
|
if (!scr_date_digits(&p, end, 2, &om)) return NAN;
|
|
3517
|
+
if (oh > 23 || om > 59) return NAN;
|
|
3396
3518
|
off = (oh * 60 + om) * 60000.0;
|
|
3397
3519
|
if (neg) off = -off;
|
|
3398
3520
|
} else {
|
|
3399
3521
|
return NAN;
|
|
3400
3522
|
}
|
|
3401
3523
|
if (p != end) return NAN;
|
|
3402
|
-
|
|
3403
|
-
|
|
3524
|
+
/* MakeDate can lie just beyond the TimeClip boundary while the
|
|
3525
|
+
* explicit offset brings the final UTC instant back into range. The
|
|
3526
|
+
* spec clips only after that offset has been applied. */
|
|
3527
|
+
double t = scr_date_make_ms(yy, mo, d, hh, mi, ss, ms);
|
|
3528
|
+
return scr_date_new_ms(t - off);
|
|
3404
3529
|
}
|
|
3405
3530
|
}
|
|
3406
3531
|
|
|
@@ -3412,10 +3537,10 @@ double scr_date_parse_get_time(ScrStr *s) {
|
|
|
3412
3537
|
* 1900+year (the spec's MakeFullYear), out-of-range months ROLL into the
|
|
3413
3538
|
* year (Date.UTC(2017, 13) is Feb 2018) and any integer date offsets from
|
|
3414
3539
|
* day 1 of that month — days_from_civil extrapolates linearly, so both
|
|
3415
|
-
* rollovers fall out. V8 bounds MakeDay's year to ±1e6
|
|
3416
|
-
*
|
|
3417
|
-
*
|
|
3418
|
-
* Never throws. */
|
|
3540
|
+
* rollovers fall out. V8 bounds MakeDay's input year to ±1e6 and input
|
|
3541
|
+
* month to ±1e7 before normalizing the month (kMaxYear/kMinYear and
|
|
3542
|
+
* kMaxMonth/kMinMonth, date.h); Node answers NaN past either bound even
|
|
3543
|
+
* when the two inputs would normalize back into range. Never throws. */
|
|
3419
3544
|
double scr_date_utc(double y, double mo, double d,
|
|
3420
3545
|
double h, double mi, double s, double ms) {
|
|
3421
3546
|
if (!isfinite(y) || !isfinite(mo) || !isfinite(d) || !isfinite(h) ||
|
|
@@ -3429,16 +3554,150 @@ double scr_date_utc(double y, double mo, double d,
|
|
|
3429
3554
|
mi = trunc(mi);
|
|
3430
3555
|
s = trunc(s);
|
|
3431
3556
|
ms = trunc(ms);
|
|
3557
|
+
if (fabs(y) > 1000000.0 || fabs(mo) > 10000000.0) return NAN;
|
|
3432
3558
|
if (y >= 0 && y <= 99) y += 1900;
|
|
3433
3559
|
double ym = y + floor(mo / 12.0);
|
|
3434
3560
|
int mn = (int)(mo - floor(mo / 12.0) * 12.0); /* 0..11 */
|
|
3435
|
-
if (fabs(ym) > 1000000.0) return NAN; /* V8's MakeDay year bound */
|
|
3436
3561
|
double days = scr_days_from_civil((long long)ym, mn + 1, 1) + (d - 1.0);
|
|
3437
3562
|
double t = days * 86400000.0 + h * 3600000.0 + mi * 60000.0 + s * 1000.0 + ms;
|
|
3438
3563
|
if (fabs(t) > 8640000000000000.0) return NAN; /* TimeClip */
|
|
3439
3564
|
return t == 0 ? 0 : t; /* normalize -0 (TimeClip's +0) */
|
|
3440
3565
|
}
|
|
3441
3566
|
|
|
3567
|
+
/* ── Date calendar getters ────────────────────────────────────────────
|
|
3568
|
+
* UTC fields use the same proleptic-Gregorian walk as toISOString, so the
|
|
3569
|
+
* whole Date range is portable. Local fields use the host timezone via
|
|
3570
|
+
* localtime, exactly the environment the sibling Node process observes;
|
|
3571
|
+
* a libc that cannot represent an extreme instant answers NaN rather than
|
|
3572
|
+
* inventing a zone. */
|
|
3573
|
+
|
|
3574
|
+
typedef struct ScrDateParts {
|
|
3575
|
+
long long year;
|
|
3576
|
+
int month, date, day, hours, minutes, seconds, milliseconds;
|
|
3577
|
+
double timezone_offset;
|
|
3578
|
+
} ScrDateParts;
|
|
3579
|
+
|
|
3580
|
+
/* Calendar decomposition itself also serves LocalTime(t), which can lie
|
|
3581
|
+
* just outside TimeClip's UTC interval after applying a zone offset at an
|
|
3582
|
+
* endpoint. The checked wrapper is the public UTC-getter gate; the inner
|
|
3583
|
+
* walk accepts those bounded local-time values too. */
|
|
3584
|
+
static void scr_date_utc_parts_unchecked(double t, ScrDateParts *out) {
|
|
3585
|
+
double dayd = floor(t / 86400000.0);
|
|
3586
|
+
long long msday = (long long)(t - dayd * 86400000.0);
|
|
3587
|
+
long long z = (long long)dayd + 719468;
|
|
3588
|
+
long long era = (z >= 0 ? z : z - 146096) / 146097;
|
|
3589
|
+
unsigned long long doe = (unsigned long long)(z - era * 146097);
|
|
3590
|
+
unsigned long long yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
|
|
3591
|
+
long long y = (long long)yoe + era * 400;
|
|
3592
|
+
unsigned long long doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
|
|
3593
|
+
unsigned long long mp = (5 * doy + 2) / 153;
|
|
3594
|
+
unsigned long long d = doy - (153 * mp + 2) / 5 + 1;
|
|
3595
|
+
unsigned long long m = mp < 10 ? mp + 3 : mp - 9;
|
|
3596
|
+
if (m <= 2) y++;
|
|
3597
|
+
long long wday = ((long long)dayd + 4) % 7;
|
|
3598
|
+
if (wday < 0) wday += 7;
|
|
3599
|
+
out->year = y;
|
|
3600
|
+
out->month = (int)m - 1;
|
|
3601
|
+
out->date = (int)d;
|
|
3602
|
+
out->day = (int)wday;
|
|
3603
|
+
out->hours = (int)(msday / 3600000);
|
|
3604
|
+
out->minutes = (int)(msday / 60000 % 60);
|
|
3605
|
+
out->seconds = (int)(msday / 1000 % 60);
|
|
3606
|
+
out->milliseconds = (int)(msday % 1000);
|
|
3607
|
+
out->timezone_offset = 0;
|
|
3608
|
+
}
|
|
3609
|
+
|
|
3610
|
+
static bool scr_date_utc_parts(double ms, ScrDateParts *out) {
|
|
3611
|
+
if (!isfinite(ms) || fabs(ms) > 8640000000000000.0) return false;
|
|
3612
|
+
scr_date_utc_parts_unchecked(trunc(ms), out);
|
|
3613
|
+
return true;
|
|
3614
|
+
}
|
|
3615
|
+
|
|
3616
|
+
static bool scr_date_localtime(double secd, struct tm *out) {
|
|
3617
|
+
time_t sec = (time_t)secd;
|
|
3618
|
+
if ((double)sec != secd) return false;
|
|
3619
|
+
#ifdef _WIN32
|
|
3620
|
+
return localtime_s(out, &sec) == 0;
|
|
3621
|
+
#else
|
|
3622
|
+
return localtime_r(&sec, out) != NULL;
|
|
3623
|
+
#endif
|
|
3624
|
+
}
|
|
3625
|
+
|
|
3626
|
+
static bool scr_date_local_parts(double ms, ScrDateParts *out) {
|
|
3627
|
+
if (!isfinite(ms) || fabs(ms) > 8640000000000000.0) return false;
|
|
3628
|
+
double clipped = trunc(ms);
|
|
3629
|
+
double secd = floor(clipped / 1000.0);
|
|
3630
|
+
struct tm tmv;
|
|
3631
|
+
double basis_secd = secd;
|
|
3632
|
+
if (!scr_date_localtime(basis_secd, &tmv)) {
|
|
3633
|
+
/* Windows' _localtime64_s rejects pre-epoch instants and years after
|
|
3634
|
+
* 3001 even though both are valid ECMAScript Dates. Query the host's
|
|
3635
|
+
* zone rule at a calendar-equivalent surrogate year in 2000..2399:
|
|
3636
|
+
* Gregorian weekdays/leap years repeat every 400 years. This keeps
|
|
3637
|
+
* every valid Date finite; the OS-vs-Node historical-rule difference
|
|
3638
|
+
* remains the documented timezone-data divergence. */
|
|
3639
|
+
ScrDateParts utc;
|
|
3640
|
+
scr_date_utc_parts_unchecked(clipped, &utc);
|
|
3641
|
+
long long cycle_year = (utc.year - 2000) % 400;
|
|
3642
|
+
if (cycle_year < 0) cycle_year += 400;
|
|
3643
|
+
long long surrogate_year = 2000 + cycle_year;
|
|
3644
|
+
basis_secd =
|
|
3645
|
+
scr_days_from_civil(surrogate_year, utc.month + 1, utc.date) * 86400.0 +
|
|
3646
|
+
utc.hours * 3600.0 + utc.minutes * 60.0 + utc.seconds;
|
|
3647
|
+
if (!scr_date_localtime(basis_secd, &tmv)) return false;
|
|
3648
|
+
}
|
|
3649
|
+
/* Treat the local broken-down fields as UTC. Its distance from the real
|
|
3650
|
+
* (or surrogate) epoch second is the zone offset. Apply that offset to
|
|
3651
|
+
* the original instant and use the portable Gregorian walk for its
|
|
3652
|
+
* fields; Date#getTimezoneOffset uses the
|
|
3653
|
+
* opposite sign (UTC - local), in whole minutes. Historical local-mean
|
|
3654
|
+
* offsets can contain seconds, which JavaScript truncates toward zero. */
|
|
3655
|
+
double local_as_utc =
|
|
3656
|
+
scr_days_from_civil((long long)tmv.tm_year + 1900, tmv.tm_mon + 1, tmv.tm_mday) * 86400.0 +
|
|
3657
|
+
tmv.tm_hour * 3600.0 + tmv.tm_min * 60.0 + tmv.tm_sec;
|
|
3658
|
+
double local_offset = local_as_utc - basis_secd;
|
|
3659
|
+
scr_date_utc_parts_unchecked(clipped + local_offset * 1000.0, out);
|
|
3660
|
+
double timezone_offset = trunc(-local_offset / 60.0);
|
|
3661
|
+
out->timezone_offset = timezone_offset == 0 ? 0 : timezone_offset;
|
|
3662
|
+
return true;
|
|
3663
|
+
}
|
|
3664
|
+
|
|
3665
|
+
static bool scr_date_parts(double ms, bool utc, ScrDateParts *out) {
|
|
3666
|
+
return utc ? scr_date_utc_parts(ms, out) : scr_date_local_parts(ms, out);
|
|
3667
|
+
}
|
|
3668
|
+
|
|
3669
|
+
#define SCR_DATE_PART_GETTER(name, field) \
|
|
3670
|
+
double scr_date_get_##name(double ms, bool utc) { \
|
|
3671
|
+
ScrDateParts p; \
|
|
3672
|
+
return scr_date_parts(ms, utc, &p) ? (double)p.field : NAN; \
|
|
3673
|
+
} \
|
|
3674
|
+
double scr_date_get_##name##_local(double ms) { \
|
|
3675
|
+
return scr_date_get_##name(ms, false); \
|
|
3676
|
+
} \
|
|
3677
|
+
double scr_date_get_##name##_utc(double ms) { \
|
|
3678
|
+
return scr_date_get_##name(ms, true); \
|
|
3679
|
+
}
|
|
3680
|
+
|
|
3681
|
+
SCR_DATE_PART_GETTER(full_year, year)
|
|
3682
|
+
SCR_DATE_PART_GETTER(month, month)
|
|
3683
|
+
SCR_DATE_PART_GETTER(date, date)
|
|
3684
|
+
SCR_DATE_PART_GETTER(day, day)
|
|
3685
|
+
SCR_DATE_PART_GETTER(hours, hours)
|
|
3686
|
+
SCR_DATE_PART_GETTER(minutes, minutes)
|
|
3687
|
+
SCR_DATE_PART_GETTER(seconds, seconds)
|
|
3688
|
+
|
|
3689
|
+
#undef SCR_DATE_PART_GETTER
|
|
3690
|
+
|
|
3691
|
+
double scr_date_get_milliseconds(double ms) {
|
|
3692
|
+
ScrDateParts p;
|
|
3693
|
+
return scr_date_utc_parts(ms, &p) ? (double)p.milliseconds : NAN;
|
|
3694
|
+
}
|
|
3695
|
+
|
|
3696
|
+
double scr_date_get_timezone_offset(double ms) {
|
|
3697
|
+
ScrDateParts p;
|
|
3698
|
+
return scr_date_local_parts(ms, &p) ? p.timezone_offset : NAN;
|
|
3699
|
+
}
|
|
3700
|
+
|
|
3442
3701
|
/* ── Number statics ────────────────────────────────────────────────────
|
|
3443
3702
|
* JS-exact: the ES2015 Number statics never coerce (unlike the global
|
|
3444
3703
|
* isNaN/isFinite), and the compiler routes only number-typed arguments
|
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
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
/*
|
|
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
|
|
@@ -2416,7 +2459,10 @@ ScrStr *scr_path_win32_to_namespaced_path(ScrStr *path);
|
|
|
2416
2459
|
* (failure throws the path-less "EBADF: bad file descriptor, close").
|
|
2417
2460
|
* The pair behind spawn's fd-stdio form. */
|
|
2418
2461
|
double scr_fs_open(ScrStr *path, ScrStr *flags);
|
|
2419
|
-
|
|
2462
|
+
/* position == -1 reads from and advances the descriptor's current offset;
|
|
2463
|
+
* nonnegative positions leave that offset unchanged (pread/ReadFile seam). */
|
|
2464
|
+
double scr_fs_read_sync(double fd, ScrBytes *buf, double offset, double length,
|
|
2465
|
+
double position);
|
|
2420
2466
|
void scr_fs_close(double fd);
|
|
2421
2467
|
|
|
2422
2468
|
/* ── WHATWG URL (scr_url.c) ──────────────────────────────────────────
|
|
@@ -2714,6 +2760,7 @@ typedef enum {
|
|
|
2714
2760
|
SCR_DYNH_FETCH_RESPONSE, /* native static-fetch Response */
|
|
2715
2761
|
SCR_DYNH_FETCH_HEADERS, /* native static-fetch response Headers */
|
|
2716
2762
|
SCR_DYNH_EVENT, /* native static-fetch abort Event */
|
|
2763
|
+
SCR_DYNH_ABORT_CONTROLLER, /* native static-fetch AbortController */
|
|
2717
2764
|
SCR_DYNH_COUNT,
|
|
2718
2765
|
} ScrDynHandleTag;
|
|
2719
2766
|
|
|
@@ -2980,6 +3027,7 @@ ScrStr *scr_dyn_string_coerce(const ScrDyn *d);
|
|
|
2980
3027
|
* called, their throws propagating) — the WHATWG USVString conversions.
|
|
2981
3028
|
* Borrows; +1 or NULL with the exception pending. */
|
|
2982
3029
|
ScrStr *scr_dyn_string_coerce_js(const ScrDyn *d);
|
|
3030
|
+
bool scr_dyn_number_coerce_js(const ScrDyn *d, double *out);
|
|
2983
3031
|
|
|
2984
3032
|
/* `d instanceof TypeError` (and the other builtin error classes) on a
|
|
2985
3033
|
* checked-dynamic value: the from_error cache resolves the dyn encoding
|
|
@@ -3907,9 +3955,11 @@ long scr_promise_live_count(void);
|
|
|
3907
3955
|
* emitted main calls scr_fetch_install in either form. */
|
|
3908
3956
|
void scr_fetch_install(void);
|
|
3909
3957
|
ScrPromise *scr_fetch_static(ScrStr *url, ScrDyn *init); /* +1 promise<Response handle> */
|
|
3958
|
+
ScrDyn *scr_fetch_response_new(ScrDyn *body, ScrDyn *init); /* borrowed args; +1 Response handle or NULL pending */
|
|
3910
3959
|
ScrPromise *scr_fetch_response_json(ScrDyn *response); /* +1 promise<dyn> */
|
|
3911
3960
|
ScrPromise *scr_fetch_response_text(ScrDyn *response); /* +1 promise<dyn> */
|
|
3912
3961
|
ScrPromise *scr_fetch_response_bytes(ScrDyn *response); /* +1 promise<dyn> */
|
|
3962
|
+
ScrDyn *scr_fetch_abort_controller_new(void); /* +1 AbortController handle */
|
|
3913
3963
|
/* Borrowed number; +1 AbortSignal handle or NULL pending. */
|
|
3914
3964
|
ScrDyn *scr_fetch_abort_timeout(ScrDyn *delay);
|
|
3915
3965
|
ScrDyn *scr_fetch_abort_now(ScrDyn *reason); /* borrowed reason; +1 handle */
|
|
@@ -4398,10 +4448,13 @@ bool scr_num_same_value(double a, double b);
|
|
|
4398
4448
|
* en-US is the one embedded locale. Result +1; never throws. */
|
|
4399
4449
|
ScrStr *scr_intl_num_format_en_us(double x);
|
|
4400
4450
|
|
|
4401
|
-
/* ── Date, the
|
|
4402
|
-
* Date
|
|
4403
|
-
*
|
|
4451
|
+
/* ── Date, the read-only value slice (scr_lib.c) ──────────────────────
|
|
4452
|
+
* A Date value is its TimeClip'd epoch-millisecond scalar. Identity and
|
|
4453
|
+
* mutation are frontend-fenced; construction, storage, getters, and ISO
|
|
4454
|
+
* formatting are exact over this representation. */
|
|
4404
4455
|
double scr_date_now(void); /* integer ms since epoch, like Node */
|
|
4456
|
+
double scr_date_new_ms(double ms); /* TimeClip (NaN when invalid) */
|
|
4457
|
+
double scr_date_get_time(double ms);
|
|
4405
4458
|
/* Node's exact ISO 8601 UTC format (expanded ±YYYYYY years outside
|
|
4406
4459
|
* 0–9999). THROWS Node's "Invalid time value" RangeError on NaN or
|
|
4407
4460
|
* |ms| > 8.64e15 and returns NULL; +1 otherwise. */
|
|
@@ -4415,6 +4468,30 @@ double scr_date_parse_get_time(ScrStr *s);
|
|
|
4415
4468
|
* and years past V8's ±1e6 MakeDay bound. Never throws. */
|
|
4416
4469
|
double scr_date_utc(double y, double mo, double d,
|
|
4417
4470
|
double h, double mi, double s, double ms);
|
|
4471
|
+
double scr_date_get_full_year(double ms, bool utc);
|
|
4472
|
+
double scr_date_get_month(double ms, bool utc);
|
|
4473
|
+
double scr_date_get_date(double ms, bool utc);
|
|
4474
|
+
double scr_date_get_day(double ms, bool utc);
|
|
4475
|
+
double scr_date_get_hours(double ms, bool utc);
|
|
4476
|
+
double scr_date_get_minutes(double ms, bool utc);
|
|
4477
|
+
double scr_date_get_seconds(double ms, bool utc);
|
|
4478
|
+
double scr_date_get_milliseconds(double ms);
|
|
4479
|
+
double scr_date_get_timezone_offset(double ms);
|
|
4480
|
+
/* One-argument ABI wrappers used by the LLVM lib-call table. */
|
|
4481
|
+
double scr_date_get_full_year_local(double ms);
|
|
4482
|
+
double scr_date_get_full_year_utc(double ms);
|
|
4483
|
+
double scr_date_get_month_local(double ms);
|
|
4484
|
+
double scr_date_get_month_utc(double ms);
|
|
4485
|
+
double scr_date_get_date_local(double ms);
|
|
4486
|
+
double scr_date_get_date_utc(double ms);
|
|
4487
|
+
double scr_date_get_day_local(double ms);
|
|
4488
|
+
double scr_date_get_day_utc(double ms);
|
|
4489
|
+
double scr_date_get_hours_local(double ms);
|
|
4490
|
+
double scr_date_get_hours_utc(double ms);
|
|
4491
|
+
double scr_date_get_minutes_local(double ms);
|
|
4492
|
+
double scr_date_get_minutes_utc(double ms);
|
|
4493
|
+
double scr_date_get_seconds_local(double ms);
|
|
4494
|
+
double scr_date_get_seconds_utc(double ms);
|
|
4418
4495
|
|
|
4419
4496
|
/* ── bitwise operators (scr_lib.c) ────────────────────────────────────
|
|
4420
4497
|
* JS-exact ToInt32/ToUint32 semantics: NaN/±Infinity → 0, truncation
|