@scriptc/runtime 0.0.21 → 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/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
- /* fs.readSync(fd, buffer, offset, length) — the 4-argument buffer form.
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. Returns the byte count read(2) reports;
1653
- * errors carry the errno name like the other fd operations. */
1654
- double scr_fs_read_sync(double fd, ScrBytes *buf, double offset, double length) {
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 < 0 || offset > (double)bytelen) {
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
- ssize_t n = read((int)fd, buf->data + off, want);
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 composed slice ──────────────────────────────────────────
3218
- * Date values have no representation the compiled surface is exactly
3219
- * Date.now() and the composed new Date(ms?).toISOString(). */
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 scr_date_ms_of(long long y, int mo, int d, int hh, int mi, int ss, int ms) {
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
- double t = scr_date_ms_of(yy, mo, d, hh, mi, ss, ms);
3403
- return isnan(t) ? t : t - off;
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 (kMaxYear/kMinYear,
3416
- * date.h) before TimeClip can see the result, and Node answers NaN past
3417
- * it matched here, and it keeps days_from_civil's long long exact.
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