mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-10 01:56:15 +08:00
DataView setters, fresh-ArrayBuffer erasure, Object.is, and the en-US Intl slice
- The DataView setter family lands end-to-end; fresh ArrayBuffers erase into zero-filled typed-array views; resizable/shared surfaces fence by name - Object.is compiles with the spec's SameValue across every kind, including union operands - Composed en-US Intl.NumberFormat and toLocaleString print byte-exact through the Ryu digit core's shortest-round-trip rounding, 30k-double fuzz-verified; the rest of Intl fences by name with reasons - DurationFormat deliberately fences: Node throws V8-internal Temporal errors with embedded source paths on plain inputs # Conflicts: # packages/compiler/test/ts7/baselines/order-parity.json
This commit is contained in:
@@ -312,6 +312,49 @@ double scr_dataview_get(const ScrBytes *b, double byte_off, ScrDataViewGet kind,
|
||||
return 0; /* unreachable */
|
||||
}
|
||||
|
||||
void scr_dataview_set(ScrBytes *b, double byte_off, double value, ScrDataViewGet kind, bool le) {
|
||||
size_t width = scr_dataview_get_size(kind);
|
||||
/* ToIndex + the view-relative bounds check — the getters' ONE message. */
|
||||
double off = (byte_off != byte_off) ? 0 : trunc(byte_off);
|
||||
if (!(off >= 0) || off > 9007199254740991.0 || off + (double)width > (double)b->len) {
|
||||
static const char msg[] = "Offset is outside the bounds of the DataView";
|
||||
scr_throw_error_msg(SCR_ERR_RANGE, msg, sizeof msg - 1);
|
||||
return;
|
||||
}
|
||||
/* Coerce to the stored bit pattern. The integer kinds share ToUint32's
|
||||
* 2^32 residue (signed/unsigned store the same bits; narrower widths
|
||||
* take the low bytes — 2^width divides 2^32, so the residues agree);
|
||||
* F32 rounds double→float to nearest even, exactly the spec. */
|
||||
uint64_t u = 0;
|
||||
switch (kind) {
|
||||
case SCR_DV_U8:
|
||||
case SCR_DV_I8:
|
||||
case SCR_DV_U16:
|
||||
case SCR_DV_I16:
|
||||
case SCR_DV_U32:
|
||||
case SCR_DV_I32:
|
||||
u = (uint64_t)scr_bytes_to_u32(value);
|
||||
break;
|
||||
case SCR_DV_F32: {
|
||||
float f = (float)value;
|
||||
uint32_t bits;
|
||||
memcpy(&bits, &f, 4);
|
||||
u = bits;
|
||||
break;
|
||||
}
|
||||
case SCR_DV_F64:
|
||||
memcpy(&u, &value, 8);
|
||||
break;
|
||||
default:
|
||||
return; /* BIG kinds never lower (bigint arguments) */
|
||||
}
|
||||
/* Scatter host-independently; le false is the JS big-endian default. */
|
||||
uint8_t *p = b->data + (size_t)off;
|
||||
for (size_t i = 0; i < width; i++) {
|
||||
p[le ? i : width - 1 - i] = (uint8_t)(u >> (8 * i));
|
||||
}
|
||||
}
|
||||
|
||||
/* ── encodings (u8 only — the compiler routes only u8 receivers here) ──── */
|
||||
|
||||
static const char scr_hex_digits[] = "0123456789abcdef";
|
||||
|
||||
@@ -3486,6 +3486,104 @@ ScrStr *scr_num_to_fixed0(double x) {
|
||||
return r;
|
||||
}
|
||||
|
||||
/* Increment a decimal digit string in place. Returns true on overflow —
|
||||
* the value becomes 1 followed by len zeros (the caller folds the zeros
|
||||
* into its scale); an EMPTY string increments to "1" the same way (the
|
||||
* round-up-from-nothing case: 0.0005 at 3 fraction digits). */
|
||||
static bool scr_dec_inc(char *d, int len) {
|
||||
for (int i = len - 1; i >= 0; i--) {
|
||||
if (d[i] != '9') {
|
||||
d[i]++;
|
||||
return false;
|
||||
}
|
||||
d[i] = '0';
|
||||
}
|
||||
d[0] = '1';
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Intl.NumberFormat("en-US").format(x) / x.toLocaleString("en-US") with
|
||||
* DEFAULT options: decimal notation, minimum 0 / maximum 3 fraction
|
||||
* digits, "," grouping every three integer digits, "∞"/"NaN" texts, and
|
||||
* "-0" whenever the input is negative or negative zero even after
|
||||
* rounding to zero. Rounding is half-up ON THE SHORTEST ROUND-TRIPPING
|
||||
* DECIMAL — ICU's rounding input, probed against Node: format(1.0005) is
|
||||
* "1.001" although the double is 1.000499... and toFixed(3) answers
|
||||
* "1.000"; format(1e23) prints the shortest form's trailing zeros, not
|
||||
* the double's exact expansion. The en-US/latn symbols (",", ".", "∞",
|
||||
* "NaN", group size 3) are the whole embedded locale surface. Verified
|
||||
* differentially against Node. Result +1; never throws. */
|
||||
ScrStr *scr_intl_num_format_en_us(double x) {
|
||||
if (isnan(x)) return scr_str_new("NaN", 3);
|
||||
if (isinf(x)) {
|
||||
return x < 0 ? scr_str_new("-\xE2\x88\x9E", 4) : scr_str_new("\xE2\x88\x9E", 3);
|
||||
}
|
||||
bool neg = signbit(x) != 0;
|
||||
if (x == 0) return neg ? scr_str_new("-0", 2) : scr_str_new("0", 1);
|
||||
double a = neg ? -x : x;
|
||||
|
||||
/* Shortest digits: value = 0.d × 10^n (no trailing zeros, k ≤ 17). */
|
||||
char d[18];
|
||||
int n;
|
||||
int k = scr_f64_digits(a, d, &n);
|
||||
|
||||
/* Round at 3 fraction digits: fraction position p is digit index
|
||||
* n+p-1, so index n+3 is the first DROPPED digit. Half-up on the
|
||||
* decimal digits — the shortest string ends right after them, so
|
||||
* "first dropped digit ≥ 5" IS the whole decision. */
|
||||
int keep = n + 3;
|
||||
if (keep < k) {
|
||||
bool up = keep >= 0 && d[keep] >= '5';
|
||||
k = keep < 0 ? 0 : keep;
|
||||
if (up && scr_dec_inc(d, k)) {
|
||||
/* Carried out (all nines, or the round-up-from-nothing 0.0005
|
||||
* case): one leading 1, the dropped nines fold into the scale. */
|
||||
k = 1;
|
||||
n += 1;
|
||||
} else if (k == 0) {
|
||||
/* Everything rounded away: ±0 with the sign preserved. */
|
||||
return neg ? scr_str_new("-0", 2) : scr_str_new("0", 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Assemble: integer digits (indices [0, n)), zero-padded past k, then
|
||||
* the ≤ 3 fraction digits (indices n..n+2, '0' outside [0, k)) with
|
||||
* trailing zeros trimmed, then commas every three integer digits. */
|
||||
char frac[3];
|
||||
int flen = 0;
|
||||
for (int p = 1; p <= 3; p++) {
|
||||
int idx = n + p - 1;
|
||||
frac[flen++] = (idx >= 0 && idx < k) ? d[idx] : '0';
|
||||
}
|
||||
while (flen > 0 && frac[flen - 1] == '0') flen--;
|
||||
|
||||
char out[512];
|
||||
int o = 0;
|
||||
if (neg) out[o++] = '-';
|
||||
if (n <= 0) {
|
||||
out[o++] = '0';
|
||||
} else {
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (i > 0 && (n - i) % 3 == 0) out[o++] = ',';
|
||||
out[o++] = (i < k) ? d[i] : '0';
|
||||
}
|
||||
}
|
||||
if (flen > 0) {
|
||||
out[o++] = '.';
|
||||
memcpy(out + o, frac, (size_t)flen);
|
||||
o += flen;
|
||||
}
|
||||
return scr_str_new(out, (size_t)o);
|
||||
}
|
||||
|
||||
/* Object.is over two numbers — the spec's SameValue on doubles: NaN
|
||||
* equals NaN, +0 differs from -0, everything else is ==. */
|
||||
bool scr_num_same_value(double a, double b) {
|
||||
if (a != a) return b != b;
|
||||
if (a == 0 && b == 0) return signbit(a) == signbit(b);
|
||||
return a == b;
|
||||
}
|
||||
|
||||
bool scr_num_is_nan(double x) { return isnan(x) != 0; }
|
||||
|
||||
bool scr_num_is_integer(double x) { return isfinite(x) && trunc(x) == x; }
|
||||
|
||||
@@ -24,27 +24,19 @@
|
||||
* unchanged. Provides d2d(), d2d_small_int(), decimalLength17(), div10(). */
|
||||
#include "../vendor/ryu/d2s.c"
|
||||
|
||||
size_t scr_f64_to_str(double x, char *buf) {
|
||||
if (isnan(x)) return (size_t)(stpcpy(buf, "NaN") - buf);
|
||||
if (x == 0) return (size_t)(stpcpy(buf, "0") - buf); /* covers -0 */
|
||||
if (isinf(x)) {
|
||||
return (size_t)(stpcpy(buf, x < 0 ? "-Infinity" : "Infinity") - buf);
|
||||
}
|
||||
|
||||
char *out = buf;
|
||||
if (x < 0) {
|
||||
*out++ = '-';
|
||||
x = -x;
|
||||
}
|
||||
|
||||
/* Shortest round-tripping digits via Ryū. Mirrors d2s_buffered_n's
|
||||
* dispatch: the exact small-integer fast path first (trailing decimal
|
||||
* zeros folded into the exponent), the full algorithm otherwise. */
|
||||
/* The Ryū digit core, shared by the ECMA placement below and the Intl
|
||||
* en-US number formatter (scr_lib.c): the shortest round-tripping digit
|
||||
* string for a positive finite double — value = 0.digits × 10^n with no
|
||||
* trailing zeros. Returns k (the digit count, ≤ 17); digits is
|
||||
* NUL-terminated. Mirrors d2s_buffered_n's dispatch: the exact
|
||||
* small-integer fast path first (trailing decimal zeros folded into the
|
||||
* exponent), the full algorithm otherwise. */
|
||||
int scr_f64_digits(double x, char digits[18], int *n_out) {
|
||||
uint64_t bits;
|
||||
memcpy(&bits, &x, sizeof bits);
|
||||
const uint64_t ieeeMantissa = bits & ((1ull << DOUBLE_MANTISSA_BITS) - 1);
|
||||
const uint32_t ieeeExponent =
|
||||
(uint32_t)(bits >> DOUBLE_MANTISSA_BITS); /* sign already stripped */
|
||||
(uint32_t)(bits >> DOUBLE_MANTISSA_BITS); /* sign must be stripped */
|
||||
floating_decimal_64 v;
|
||||
if (d2d_small_int(ieeeMantissa, ieeeExponent, &v)) {
|
||||
for (;;) {
|
||||
@@ -60,9 +52,8 @@ size_t scr_f64_to_str(double x, char *buf) {
|
||||
|
||||
/* Ryū's (mantissa, exponent) → ECMA's (digits, k, n): the k mantissa
|
||||
* digits have no trailing zeros, and value = 0.digits * 10^n. */
|
||||
char digits[18];
|
||||
int k = (int)decimalLength17(v.mantissa);
|
||||
int n = v.exponent + k;
|
||||
*n_out = v.exponent + k;
|
||||
digits[k] = '\0';
|
||||
uint64_t m = v.mantissa;
|
||||
for (int i = k - 1; i >= 0; i--) {
|
||||
@@ -70,6 +61,25 @@ size_t scr_f64_to_str(double x, char *buf) {
|
||||
digits[i] = (char)('0' + (uint32_t)m - 10 * (uint32_t)q);
|
||||
m = q;
|
||||
}
|
||||
return k;
|
||||
}
|
||||
|
||||
size_t scr_f64_to_str(double x, char *buf) {
|
||||
if (isnan(x)) return (size_t)(stpcpy(buf, "NaN") - buf);
|
||||
if (x == 0) return (size_t)(stpcpy(buf, "0") - buf); /* covers -0 */
|
||||
if (isinf(x)) {
|
||||
return (size_t)(stpcpy(buf, x < 0 ? "-Infinity" : "Infinity") - buf);
|
||||
}
|
||||
|
||||
char *out = buf;
|
||||
if (x < 0) {
|
||||
*out++ = '-';
|
||||
x = -x;
|
||||
}
|
||||
|
||||
char digits[18];
|
||||
int n;
|
||||
int k = scr_f64_digits(x, digits, &n);
|
||||
|
||||
if (k <= n && n <= 21) {
|
||||
/* Integer: digits followed by n-k zeros. */
|
||||
|
||||
@@ -3896,6 +3896,12 @@ void scr_jsval_cast_fail(ScrJsval *v, const ScrStr *target);
|
||||
*/
|
||||
size_t scr_f64_to_str(double x, char *buf);
|
||||
|
||||
/* The Ryū digit core (scr_number.c), shared with the Intl en-US number
|
||||
* formatter: the shortest round-tripping digits of a POSITIVE finite
|
||||
* double — value = 0.digits × 10^n, no trailing zeros, NUL-terminated.
|
||||
* Returns k, the digit count (≤ 17). */
|
||||
int scr_f64_digits(double x, char digits[18], int *n_out);
|
||||
|
||||
/* ToString for template literals / string coercion. Returns +1. */
|
||||
ScrStr *scr_f64_to_scrstr(double x);
|
||||
ScrStr *scr_bool_to_scrstr(bool b); /* interned "true"/"false" */
|
||||
@@ -3927,6 +3933,18 @@ bool scr_num_is_safe_integer(double x);
|
||||
ScrStr *scr_num_to_exponential(double x);
|
||||
ScrStr *scr_num_to_fixed0(double x);
|
||||
|
||||
/* Object.is over two numbers — the spec's SameValue on doubles: NaN
|
||||
* equals NaN, +0 differs from -0, everything else is ==. Never throws. */
|
||||
bool scr_num_same_value(double a, double b);
|
||||
|
||||
/* Intl.NumberFormat("en-US").format(x) / x.toLocaleString("en-US") with
|
||||
* default options: decimal notation, 0–3 fraction digits rounded half-up
|
||||
* on the shortest round-tripping decimal (ICU's rounding input — NOT
|
||||
* toFixed's exact-value rounding), "," grouping every three integer
|
||||
* digits, "∞"/"NaN" texts, "-0" for negative inputs rounding to zero.
|
||||
* en-US is the one embedded locale. Result +1; never throws. */
|
||||
ScrStr *scr_intl_num_format_en_us(double x);
|
||||
|
||||
/* ── Date, the composed slice (scr_lib.c) ─────────────────────────────
|
||||
* Date values have no representation; the runtime surface is exactly
|
||||
* Date.now() and toISOString over a millisecond time value. */
|
||||
@@ -4081,6 +4099,14 @@ typedef enum ScrDataViewGet {
|
||||
} ScrDataViewGet;
|
||||
double scr_dataview_get(const ScrBytes *b, double byte_off, ScrDataViewGet kind, bool le);
|
||||
|
||||
/* DataView setters (the integer/float kinds only — the BIG kinds never
|
||||
* lower: bigint arguments have no representation). Values coerce
|
||||
* JS-exactly: the integer kinds by modular truncation (ToUint32's residue
|
||||
* — the narrower widths store its low bytes, the same 2^width residue),
|
||||
* F32 by double→float round-to-nearest-even. Offsets go through ToIndex
|
||||
* with the getters' one RangeError. */
|
||||
void scr_dataview_set(ScrBytes *b, double byte_off, double value, ScrDataViewGet kind, bool le);
|
||||
|
||||
/* Element read/write. Any invalid index — negative, fractional, NaN, or
|
||||
* out of bounds — TRAPS like the array runtime (SEMANTICS.md documents
|
||||
* the divergence from JS's undefined-read/ignored-write). Writes coerce
|
||||
|
||||
Reference in New Issue
Block a user