@scriptc/runtime 0.0.7 → 0.0.9
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_bytes.c +58 -12
- package/src/scr_bytes_io.c +97 -1
- package/src/scr_dyn_handle.c +8 -66
- package/src/scr_error.c +15 -0
- package/src/scr_events_emitter.c +34 -0
- package/src/scr_exception.c +24 -1
- package/src/scr_island.c +109 -6
- package/src/scr_json.c +218 -0
- package/src/scr_lib.c +101 -3
- package/src/scr_library.c +119 -5
- package/src/scr_number.c +29 -19
- package/src/scr_object.c +18 -0
- package/src/scr_runtime.h +173 -7
- package/src/scr_stream.c +49 -2
package/src/scr_json.c
CHANGED
|
@@ -424,6 +424,52 @@ void scr_dyn_arr_push(ScrDyn *arr, ScrDyn *item) {
|
|
|
424
424
|
arr->v.arr.items[arr->v.arr.len++] = item; /* ownership moves in */
|
|
425
425
|
}
|
|
426
426
|
|
|
427
|
+
/* Spread completion for a runtime-arity argument list (`f(...xs)` in the
|
|
428
|
+
* checked-dynamic tier): JS's spread over the DOM's iterable kinds —
|
|
429
|
+
* arrays element-by-element (retained), strings by code POINT (the string
|
|
430
|
+
* iterator; astral chars arrive unsplit), bytes by byte; every other kind
|
|
431
|
+
* throws V8's exact SPREAD-CALL TypeError (catchable, pending — callers
|
|
432
|
+
* check): nullish sources spell the spread expression (`what`) — "v is
|
|
433
|
+
* not iterable (cannot read property undefined)" — and everything else is
|
|
434
|
+
* the generic "Spread syntax requires ...iterable[Symbol.iterator] to be
|
|
435
|
+
* a function". Borrows src. */
|
|
436
|
+
void scr_dyn_arr_push_spread(ScrDyn *arr, const ScrDyn *src, const char *what) {
|
|
437
|
+
if (src->kind == SCR_DYN_ARR) {
|
|
438
|
+
for (size_t i = 0; i < src->v.arr.len; i++) {
|
|
439
|
+
scr_dyn_arr_push(arr, scr_dyn_retain(src->v.arr.items[i]));
|
|
440
|
+
}
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
if (src->kind == SCR_DYN_BYTES) {
|
|
444
|
+
for (size_t i = 0; i < src->v.bytes->len; i++) {
|
|
445
|
+
scr_dyn_arr_push(arr, scr_dyn_new_num((double)src->v.bytes->data[i]));
|
|
446
|
+
}
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
if (src->kind == SCR_DYN_STR) {
|
|
450
|
+
double len = scr_str_utf16_len(src->v.str);
|
|
451
|
+
for (double at = 0; at < len;) {
|
|
452
|
+
ScrStr *cp = scr_str_cp_at(src->v.str, at);
|
|
453
|
+
at += scr_str_utf16_len(cp);
|
|
454
|
+
scr_dyn_arr_push(arr, scr_dyn_new_str(cp));
|
|
455
|
+
scr_str_release(cp);
|
|
456
|
+
}
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
if (src->kind == SCR_DYN_UNDEF || src->kind == SCR_DYN_NULL) {
|
|
460
|
+
ScrJsonBuf b;
|
|
461
|
+
scr_jb_init(&b);
|
|
462
|
+
scr_jb_puts(&b, what);
|
|
463
|
+
scr_jb_puts(&b, " is not iterable (cannot read property ");
|
|
464
|
+
scr_jb_puts(&b, src->kind == SCR_DYN_UNDEF ? "undefined" : "null");
|
|
465
|
+
scr_jb_puts(&b, ")");
|
|
466
|
+
scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&b));
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
static const char msg[] = "Spread syntax requires ...iterable[Symbol.iterator] to be a function";
|
|
470
|
+
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
|
|
471
|
+
}
|
|
472
|
+
|
|
427
473
|
/* Takes ownership of key (malloc'd) and value. Duplicate keys: the LATER
|
|
428
474
|
* value wins (like JS JSON.parse) — the old value is released and the new
|
|
429
475
|
* key buffer freed (the surviving entry keeps its original, equal key). */
|
|
@@ -551,6 +597,13 @@ ScrDyn *scr_dyn_call(const ScrDyn *d, ScrDyn *const *args, size_t argc, const ch
|
|
|
551
597
|
return d->v.fn.thunk(d->v.fn.clo, args, argc);
|
|
552
598
|
}
|
|
553
599
|
|
|
600
|
+
/* scr_dyn_call over a DOM ARRAY's elements — the spread-application form
|
|
601
|
+
* (`f(...args)` after the emitted argument array is built). Borrows both;
|
|
602
|
+
* result owned (+1), or NULL with the exception pending. */
|
|
603
|
+
ScrDyn *scr_dyn_apply(const ScrDyn *d, const ScrDyn *args, const char *what) {
|
|
604
|
+
return scr_dyn_call(d, args->v.arr.items, args->v.arr.len, what);
|
|
605
|
+
}
|
|
606
|
+
|
|
554
607
|
/* ── native handles in the DOM (SCR_DYN_HANDLE) ───────────────────────
|
|
555
608
|
* Per-tag ops stamped by the owning units at main() (scr_http_dyn_install
|
|
556
609
|
* / scr_net_dyn_install — the scr_net_install hook story), so this
|
|
@@ -582,6 +635,76 @@ const ScrDynHandleOps *scr_dyn_handle_ops_of(const ScrDyn *d) {
|
|
|
582
635
|
return scr_dyn_handle_ops(d->v.handle.tag);
|
|
583
636
|
}
|
|
584
637
|
|
|
638
|
+
/* errors.js's determineSpecificType over a DOM value — the "Received
|
|
639
|
+
* ..." tail of Node's ERR_INVALID_ARG_TYPE messages. Renders into buf
|
|
640
|
+
* when the shape needs a payload; returns the text either way. Lives
|
|
641
|
+
* beside the DOM core (not the gated handle unit) because the always-
|
|
642
|
+
* linked argument validators (bytes, fs) render through it too. */
|
|
643
|
+
const char *scr_dyn_specific_type(const ScrDyn *cb, char *detail, size_t cap) {
|
|
644
|
+
const char *d = detail;
|
|
645
|
+
switch (cb->kind) {
|
|
646
|
+
case SCR_DYN_NULL: d = "null"; break;
|
|
647
|
+
case SCR_DYN_UNDEF: d = "undefined"; break;
|
|
648
|
+
case SCR_DYN_OBJ: d = "an instance of Object"; break;
|
|
649
|
+
case SCR_DYN_ARR: d = "an instance of Array"; break;
|
|
650
|
+
case SCR_DYN_BYTES: d = "an instance of Uint8Array"; break;
|
|
651
|
+
case SCR_DYN_FUNC: d = "function"; break; /* callers usually return before this */
|
|
652
|
+
case SCR_DYN_HANDLE:
|
|
653
|
+
snprintf(detail, cap, "an instance of %s", scr_dyn_handle_cls(cb));
|
|
654
|
+
break;
|
|
655
|
+
case SCR_DYN_PROMISE: d = "an instance of Promise"; break;
|
|
656
|
+
case SCR_DYN_BOOL:
|
|
657
|
+
snprintf(detail, cap, "type boolean (%s)", cb->v.b ? "true" : "false");
|
|
658
|
+
break;
|
|
659
|
+
case SCR_DYN_NUM: {
|
|
660
|
+
char num[32];
|
|
661
|
+
size_t n = scr_f64_to_str(cb->v.num, num);
|
|
662
|
+
snprintf(detail, cap, "type number (%.*s)", (int)n, num);
|
|
663
|
+
break;
|
|
664
|
+
}
|
|
665
|
+
case SCR_DYN_STR: {
|
|
666
|
+
const ScrStr *sv = cb->v.str;
|
|
667
|
+
char insp[32];
|
|
668
|
+
size_t n = 0;
|
|
669
|
+
insp[n++] = '\'';
|
|
670
|
+
for (size_t i = 0; i < sv->len && n < 28; i++) insp[n++] = sv->data[i];
|
|
671
|
+
if (sv->len + 2 > 28) {
|
|
672
|
+
n = 25;
|
|
673
|
+
memcpy(insp + n, "...", 3);
|
|
674
|
+
n += 3;
|
|
675
|
+
} else {
|
|
676
|
+
insp[n++] = '\'';
|
|
677
|
+
}
|
|
678
|
+
snprintf(detail, cap, "type string (%.*s)", (int)n, insp);
|
|
679
|
+
break;
|
|
680
|
+
}
|
|
681
|
+
default: d = "an instance of Object"; break;
|
|
682
|
+
}
|
|
683
|
+
return d;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/* Node's ERR_INVALID_ARG_TYPE thrower ("The \"chunk\" argument must be
|
|
687
|
+
* of type string or an instance of Buffer or Uint8Array. Received type
|
|
688
|
+
* number (5)") — the handle dispatchers' and argument validators'
|
|
689
|
+
* per-arg gates. `expected` is the full "of type ..."/"an instance of
|
|
690
|
+
* ..." clause. */
|
|
691
|
+
/* The compiler-resolved ERR_INVALID_ARG_TYPE throw with a RUNTIME-
|
|
692
|
+
* rendered Received tail (error.argTypeThrow — the always-throwing
|
|
693
|
+
* lowered arms whose offending value is not a literal). Borrows all
|
|
694
|
+
* three; always throws catchably. */
|
|
695
|
+
void scr_throw_arg_type(const ScrStr *argname, const ScrStr *expected, const ScrDyn *got) {
|
|
696
|
+
scr_dyn_arg_type_fail(argname->data, expected->data, got);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
void scr_dyn_arg_type_fail(const char *argname, const char *expected, const ScrDyn *got) {
|
|
700
|
+
char detail[64];
|
|
701
|
+
const char *d = scr_dyn_specific_type(got, detail, sizeof detail);
|
|
702
|
+
char msg[224];
|
|
703
|
+
int len = snprintf(msg, sizeof msg,
|
|
704
|
+
"The \"%s\" argument must be %s. Received %s", argname, expected, d);
|
|
705
|
+
scr_throw_error_msg_code(SCR_ERR_TYPE, msg, (size_t)len, "ERR_INVALID_ARG_TYPE");
|
|
706
|
+
}
|
|
707
|
+
|
|
585
708
|
static void scr_dyn_handle_release(void *h, ScrDynHandleTag tag) {
|
|
586
709
|
scr_dyn_handle_ops(tag)->release(h);
|
|
587
710
|
}
|
|
@@ -1066,6 +1189,39 @@ ScrStr *scr_dyn_string_coerce(const ScrDyn *d) {
|
|
|
1066
1189
|
return scr_dyn_to_string(d, NULL);
|
|
1067
1190
|
}
|
|
1068
1191
|
|
|
1192
|
+
/* JS ToString over a DOM value WITH the object protocol (the WHATWG
|
|
1193
|
+
* USVString conversions — URLSearchParams names/values): an OBJ whose
|
|
1194
|
+
* own 'toString' member is callable is invoked with zero arguments (its
|
|
1195
|
+
* throw propagates, catchably); a non-primitive answer falls through to
|
|
1196
|
+
* 'valueOf' (ToPrimitive's string hint); exhaustion is the spec's
|
|
1197
|
+
* "Cannot convert object to primitive value" TypeError. Every other
|
|
1198
|
+
* kind matches scr_dyn_string_coerce (units RENDER — ToString(null) is
|
|
1199
|
+
* "null"). Borrows; +1, or NULL with the exception pending. */
|
|
1200
|
+
ScrStr *scr_dyn_string_coerce_js(const ScrDyn *d) {
|
|
1201
|
+
if (d->kind == SCR_DYN_OBJ) {
|
|
1202
|
+
static const char *const hint[2] = { "toString", "valueOf" };
|
|
1203
|
+
for (int i = 0; i < 2; i++) {
|
|
1204
|
+
ScrDyn *m = scr_dyn_obj_get(d, hint[i], strlen(hint[i])); /* borrowed */
|
|
1205
|
+
if (!m || m->kind != SCR_DYN_FUNC) continue;
|
|
1206
|
+
ScrDyn *r = scr_dyn_call(m, NULL, 0, hint[i]);
|
|
1207
|
+
if (!r) return NULL; /* the method threw — pending */
|
|
1208
|
+
if (r->kind == SCR_DYN_OBJ || r->kind == SCR_DYN_ARR ||
|
|
1209
|
+
r->kind == SCR_DYN_FUNC || r->kind == SCR_DYN_HANDLE ||
|
|
1210
|
+
r->kind == SCR_DYN_PROMISE) {
|
|
1211
|
+
scr_dyn_release(r); /* non-primitive answer: try the next method */
|
|
1212
|
+
continue;
|
|
1213
|
+
}
|
|
1214
|
+
ScrStr *s = scr_dyn_string_coerce(r);
|
|
1215
|
+
scr_dyn_release(r);
|
|
1216
|
+
return s;
|
|
1217
|
+
}
|
|
1218
|
+
static const char msg[] = "Cannot convert object to primitive value";
|
|
1219
|
+
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
|
|
1220
|
+
return NULL;
|
|
1221
|
+
}
|
|
1222
|
+
return scr_dyn_string_coerce(d);
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1069
1225
|
/* The checked-dynamic keyed WRITE (`h.k = v` on a dyn receiver): OBJ sets
|
|
1070
1226
|
* the member (later writes win, insertion order — JS); undefined/null
|
|
1071
1227
|
* throws Node's "Cannot set properties of ..."; every other kind throws
|
|
@@ -1078,6 +1234,26 @@ void scr_dyn_key_set(ScrDyn *recv, ScrStr *key, ScrDyn *value) {
|
|
|
1078
1234
|
scr_dyn_obj_set(recv, key->data, key->len, scr_dyn_retain(value));
|
|
1079
1235
|
return;
|
|
1080
1236
|
}
|
|
1237
|
+
if (recv->kind == SCR_DYN_ARR) {
|
|
1238
|
+
/* An INDEX write on a DOM array (`args[i] = v` — the variadic-rest
|
|
1239
|
+
* rebuild): a canonical numeric key sets/extends the element, holes
|
|
1240
|
+
* padding with undefined exactly like JS length growth. Non-index
|
|
1241
|
+
* keys keep the throw below (DOM arrays carry no expando table). */
|
|
1242
|
+
size_t idx = 0;
|
|
1243
|
+
int is_index = key->len > 0 && !(key->len > 1 && key->data[0] == '0');
|
|
1244
|
+
for (size_t i = 0; is_index && i < key->len; i++) {
|
|
1245
|
+
if (key->data[i] < '0' || key->data[i] > '9') is_index = 0;
|
|
1246
|
+
else idx = idx * 10 + (size_t)(key->data[i] - '0');
|
|
1247
|
+
}
|
|
1248
|
+
if (is_index) {
|
|
1249
|
+
while (recv->v.arr.len <= idx) {
|
|
1250
|
+
scr_dyn_arr_push(recv, scr_dyn_retain(scr_dyn_undefined()));
|
|
1251
|
+
}
|
|
1252
|
+
scr_dyn_release(recv->v.arr.items[idx]);
|
|
1253
|
+
recv->v.arr.items[idx] = scr_dyn_retain(value);
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1081
1257
|
if (recv->kind == SCR_DYN_HANDLE) {
|
|
1082
1258
|
scr_dyn_handle_key_set(recv, key, value);
|
|
1083
1259
|
return;
|
|
@@ -2089,6 +2265,48 @@ static ScrDyn *scr_dyn_objwalk(const ScrDyn *v, ScrObjWalk mode) {
|
|
|
2089
2265
|
}
|
|
2090
2266
|
|
|
2091
2267
|
ScrDyn *scr_dyn_obj_keys(const ScrDyn *v) { return scr_dyn_objwalk(v, SCR_OBJWALK_KEYS); }
|
|
2268
|
+
|
|
2269
|
+
/* Object.assign over DOM values: copies `src`'s own members onto `target`
|
|
2270
|
+
* (last write wins) and answers the target retained (+1). Nullish
|
|
2271
|
+
* receivers throw Node's ToObject TypeError; nullish sources copy
|
|
2272
|
+
* nothing; non-object sources copy nothing DOM-representable. */
|
|
2273
|
+
ScrDyn *scr_dyn_assign(ScrDyn *target, const ScrDyn *src) {
|
|
2274
|
+
if (target->kind == SCR_DYN_UNDEF || target->kind == SCR_DYN_NULL) {
|
|
2275
|
+
const char *m = "Cannot convert undefined or null to object";
|
|
2276
|
+
scr_throw_error_msg(SCR_ERR_TYPE, m, strlen(m));
|
|
2277
|
+
return NULL;
|
|
2278
|
+
}
|
|
2279
|
+
if (target->kind == SCR_DYN_OBJ && src->kind == SCR_DYN_OBJ) {
|
|
2280
|
+
for (size_t i = 0; i < src->v.obj.len; i++) {
|
|
2281
|
+
scr_dyn_obj_set(target, src->v.obj.entries[i].key,
|
|
2282
|
+
src->v.obj.entries[i].key_len,
|
|
2283
|
+
scr_dyn_retain(src->v.obj.entries[i].value));
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
return scr_dyn_retain(target);
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
bool scr_dyn_has_own(const ScrDyn *v, const ScrStr *key) {
|
|
2290
|
+
if (v->kind == SCR_DYN_UNDEF || v->kind == SCR_DYN_NULL) {
|
|
2291
|
+
const char *m = "Cannot convert undefined or null to object";
|
|
2292
|
+
scr_throw_error_msg(SCR_ERR_TYPE, m, strlen(m));
|
|
2293
|
+
return false;
|
|
2294
|
+
}
|
|
2295
|
+
if (v->kind == SCR_DYN_OBJ) {
|
|
2296
|
+
return scr_dyn_obj_get(v, key->data, key->len) != NULL;
|
|
2297
|
+
}
|
|
2298
|
+
if (v->kind == SCR_DYN_ARR) {
|
|
2299
|
+
if (key->len == 6 && memcmp(key->data, "length", 6) == 0) return true;
|
|
2300
|
+
size_t idx = 0;
|
|
2301
|
+
int is_index = key->len > 0 && !(key->len > 1 && key->data[0] == '0');
|
|
2302
|
+
for (size_t i = 0; is_index && i < key->len; i++) {
|
|
2303
|
+
if (key->data[i] < '0' || key->data[i] > '9') is_index = 0;
|
|
2304
|
+
else idx = idx * 10 + (size_t)(key->data[i] - '0');
|
|
2305
|
+
}
|
|
2306
|
+
return is_index != 0 && idx < v->v.arr.len;
|
|
2307
|
+
}
|
|
2308
|
+
return false;
|
|
2309
|
+
}
|
|
2092
2310
|
ScrDyn *scr_dyn_obj_values(const ScrDyn *v) { return scr_dyn_objwalk(v, SCR_OBJWALK_VALUES); }
|
|
2093
2311
|
ScrDyn *scr_dyn_obj_entries(const ScrDyn *v) { return scr_dyn_objwalk(v, SCR_OBJWALK_ENTRIES); }
|
|
2094
2312
|
|
package/src/scr_lib.c
CHANGED
|
@@ -1647,7 +1647,7 @@ double scr_fs_read_sync(double fd, ScrBytes *buf, double offset, double length)
|
|
|
1647
1647
|
mlen = snprintf(msg, sizeof msg,
|
|
1648
1648
|
"The value of \"offset\" is out of range. It must be >= 0 && <= 9007199254740991. Received %s",
|
|
1649
1649
|
numbuf);
|
|
1650
|
-
|
|
1650
|
+
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)mlen, "ERR_OUT_OF_RANGE");
|
|
1651
1651
|
return 0;
|
|
1652
1652
|
}
|
|
1653
1653
|
size_t off = (size_t)offset;
|
|
@@ -1656,7 +1656,7 @@ double scr_fs_read_sync(double fd, ScrBytes *buf, double offset, double length)
|
|
|
1656
1656
|
mlen = snprintf(msg, sizeof msg,
|
|
1657
1657
|
"The value of \"length\" is out of range. It must be <= %zu. Received %s",
|
|
1658
1658
|
bytelen - off, numbuf);
|
|
1659
|
-
|
|
1659
|
+
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)mlen, "ERR_OUT_OF_RANGE");
|
|
1660
1660
|
return 0;
|
|
1661
1661
|
}
|
|
1662
1662
|
size_t want = (size_t)length;
|
|
@@ -2528,7 +2528,7 @@ ScrStr *scr_crypto_random_string(double n, ScrStr *enc) {
|
|
|
2528
2528
|
int mlen = snprintf(msg, sizeof msg,
|
|
2529
2529
|
"The value of \"size\" is out of range. It must be >= 0 && <= 2147483647. Received %.*s",
|
|
2530
2530
|
(int)numlen, num);
|
|
2531
|
-
|
|
2531
|
+
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)mlen, "ERR_OUT_OF_RANGE");
|
|
2532
2532
|
return NULL;
|
|
2533
2533
|
}
|
|
2534
2534
|
size_t size = (size_t)n;
|
|
@@ -3486,6 +3486,104 @@ ScrStr *scr_num_to_fixed0(double x) {
|
|
|
3486
3486
|
return r;
|
|
3487
3487
|
}
|
|
3488
3488
|
|
|
3489
|
+
/* Increment a decimal digit string in place. Returns true on overflow —
|
|
3490
|
+
* the value becomes 1 followed by len zeros (the caller folds the zeros
|
|
3491
|
+
* into its scale); an EMPTY string increments to "1" the same way (the
|
|
3492
|
+
* round-up-from-nothing case: 0.0005 at 3 fraction digits). */
|
|
3493
|
+
static bool scr_dec_inc(char *d, int len) {
|
|
3494
|
+
for (int i = len - 1; i >= 0; i--) {
|
|
3495
|
+
if (d[i] != '9') {
|
|
3496
|
+
d[i]++;
|
|
3497
|
+
return false;
|
|
3498
|
+
}
|
|
3499
|
+
d[i] = '0';
|
|
3500
|
+
}
|
|
3501
|
+
d[0] = '1';
|
|
3502
|
+
return true;
|
|
3503
|
+
}
|
|
3504
|
+
|
|
3505
|
+
/* Intl.NumberFormat("en-US").format(x) / x.toLocaleString("en-US") with
|
|
3506
|
+
* DEFAULT options: decimal notation, minimum 0 / maximum 3 fraction
|
|
3507
|
+
* digits, "," grouping every three integer digits, "∞"/"NaN" texts, and
|
|
3508
|
+
* "-0" whenever the input is negative or negative zero even after
|
|
3509
|
+
* rounding to zero. Rounding is half-up ON THE SHORTEST ROUND-TRIPPING
|
|
3510
|
+
* DECIMAL — ICU's rounding input, probed against Node: format(1.0005) is
|
|
3511
|
+
* "1.001" although the double is 1.000499... and toFixed(3) answers
|
|
3512
|
+
* "1.000"; format(1e23) prints the shortest form's trailing zeros, not
|
|
3513
|
+
* the double's exact expansion. The en-US/latn symbols (",", ".", "∞",
|
|
3514
|
+
* "NaN", group size 3) are the whole embedded locale surface. Verified
|
|
3515
|
+
* differentially against Node. Result +1; never throws. */
|
|
3516
|
+
ScrStr *scr_intl_num_format_en_us(double x) {
|
|
3517
|
+
if (isnan(x)) return scr_str_new("NaN", 3);
|
|
3518
|
+
if (isinf(x)) {
|
|
3519
|
+
return x < 0 ? scr_str_new("-\xE2\x88\x9E", 4) : scr_str_new("\xE2\x88\x9E", 3);
|
|
3520
|
+
}
|
|
3521
|
+
bool neg = signbit(x) != 0;
|
|
3522
|
+
if (x == 0) return neg ? scr_str_new("-0", 2) : scr_str_new("0", 1);
|
|
3523
|
+
double a = neg ? -x : x;
|
|
3524
|
+
|
|
3525
|
+
/* Shortest digits: value = 0.d × 10^n (no trailing zeros, k ≤ 17). */
|
|
3526
|
+
char d[18];
|
|
3527
|
+
int n;
|
|
3528
|
+
int k = scr_f64_digits(a, d, &n);
|
|
3529
|
+
|
|
3530
|
+
/* Round at 3 fraction digits: fraction position p is digit index
|
|
3531
|
+
* n+p-1, so index n+3 is the first DROPPED digit. Half-up on the
|
|
3532
|
+
* decimal digits — the shortest string ends right after them, so
|
|
3533
|
+
* "first dropped digit ≥ 5" IS the whole decision. */
|
|
3534
|
+
int keep = n + 3;
|
|
3535
|
+
if (keep < k) {
|
|
3536
|
+
bool up = keep >= 0 && d[keep] >= '5';
|
|
3537
|
+
k = keep < 0 ? 0 : keep;
|
|
3538
|
+
if (up && scr_dec_inc(d, k)) {
|
|
3539
|
+
/* Carried out (all nines, or the round-up-from-nothing 0.0005
|
|
3540
|
+
* case): one leading 1, the dropped nines fold into the scale. */
|
|
3541
|
+
k = 1;
|
|
3542
|
+
n += 1;
|
|
3543
|
+
} else if (k == 0) {
|
|
3544
|
+
/* Everything rounded away: ±0 with the sign preserved. */
|
|
3545
|
+
return neg ? scr_str_new("-0", 2) : scr_str_new("0", 1);
|
|
3546
|
+
}
|
|
3547
|
+
}
|
|
3548
|
+
|
|
3549
|
+
/* Assemble: integer digits (indices [0, n)), zero-padded past k, then
|
|
3550
|
+
* the ≤ 3 fraction digits (indices n..n+2, '0' outside [0, k)) with
|
|
3551
|
+
* trailing zeros trimmed, then commas every three integer digits. */
|
|
3552
|
+
char frac[3];
|
|
3553
|
+
int flen = 0;
|
|
3554
|
+
for (int p = 1; p <= 3; p++) {
|
|
3555
|
+
int idx = n + p - 1;
|
|
3556
|
+
frac[flen++] = (idx >= 0 && idx < k) ? d[idx] : '0';
|
|
3557
|
+
}
|
|
3558
|
+
while (flen > 0 && frac[flen - 1] == '0') flen--;
|
|
3559
|
+
|
|
3560
|
+
char out[512];
|
|
3561
|
+
int o = 0;
|
|
3562
|
+
if (neg) out[o++] = '-';
|
|
3563
|
+
if (n <= 0) {
|
|
3564
|
+
out[o++] = '0';
|
|
3565
|
+
} else {
|
|
3566
|
+
for (int i = 0; i < n; i++) {
|
|
3567
|
+
if (i > 0 && (n - i) % 3 == 0) out[o++] = ',';
|
|
3568
|
+
out[o++] = (i < k) ? d[i] : '0';
|
|
3569
|
+
}
|
|
3570
|
+
}
|
|
3571
|
+
if (flen > 0) {
|
|
3572
|
+
out[o++] = '.';
|
|
3573
|
+
memcpy(out + o, frac, (size_t)flen);
|
|
3574
|
+
o += flen;
|
|
3575
|
+
}
|
|
3576
|
+
return scr_str_new(out, (size_t)o);
|
|
3577
|
+
}
|
|
3578
|
+
|
|
3579
|
+
/* Object.is over two numbers — the spec's SameValue on doubles: NaN
|
|
3580
|
+
* equals NaN, +0 differs from -0, everything else is ==. */
|
|
3581
|
+
bool scr_num_same_value(double a, double b) {
|
|
3582
|
+
if (a != a) return b != b;
|
|
3583
|
+
if (a == 0 && b == 0) return signbit(a) == signbit(b);
|
|
3584
|
+
return a == b;
|
|
3585
|
+
}
|
|
3586
|
+
|
|
3489
3587
|
bool scr_num_is_nan(double x) { return isnan(x) != 0; }
|
|
3490
3588
|
|
|
3491
3589
|
bool scr_num_is_integer(double x) { return isfinite(x) && trunc(x) == x; }
|
package/src/scr_library.c
CHANGED
|
@@ -40,7 +40,19 @@ void scr_library_set_sink(ScrLibSinkFn fn, void *ctx) {
|
|
|
40
40
|
* conforming survival pattern), then deliver exactly once, then abort:
|
|
41
41
|
* before registration, or if the sink returns (the ruled host-contract
|
|
42
42
|
* violation). The address is the funnel frame's return address — the trap
|
|
43
|
-
* site — 0 where the toolchain cannot supply one.
|
|
43
|
+
* site — 0 where the toolchain cannot supply one.
|
|
44
|
+
*
|
|
45
|
+
* Delivery shape (the ratified structured trap-teaching encoding): a
|
|
46
|
+
* message that already begins with the 0x01 marker — a facade-authored
|
|
47
|
+
* structured throw riding the verbatim rule, or the wrapper's compile-
|
|
48
|
+
* time-assembled SC4012 contract trap — is delivered byte-for-byte. Every
|
|
49
|
+
* OTHER message is a trap the runtime DETECTED, and the funnel assembles
|
|
50
|
+
* it here into 0x01 text 0x1F code 0x1F symbol [0x1F remediation]: the
|
|
51
|
+
* baseline human line becomes field 0 unchanged (so plain-text hosts read
|
|
52
|
+
* exactly what they always read), the code classifies the trap kind, the
|
|
53
|
+
* symbol is the entry the trapping call came through (recorded by the
|
|
54
|
+
* entry prologue below), and the remediation is the profile's for that
|
|
55
|
+
* code when the program TU's overlay table declares one. */
|
|
44
56
|
|
|
45
57
|
#if defined(__GNUC__) || defined(__clang__)
|
|
46
58
|
#define SCR_TRAP_ADDR() ((uint64_t)(uintptr_t)__builtin_return_address(0))
|
|
@@ -48,8 +60,95 @@ void scr_library_set_sink(ScrLibSinkFn fn, void *ctx) {
|
|
|
48
60
|
#define SCR_TRAP_ADDR() ((uint64_t)0)
|
|
49
61
|
#endif
|
|
50
62
|
|
|
63
|
+
/* The current-entry slot: every generated entry's prologue records its
|
|
64
|
+
* external symbol before dispatching into core code. A single static slot
|
|
65
|
+
* is sound — exactly one core is live per process (the one-live-core rule),
|
|
66
|
+
* entries never nest, and a trap can only fire while an entry is on the
|
|
67
|
+
* stack. NULL (never entered) renders as the empty symbol field. */
|
|
68
|
+
static const char *scr_library_entry_symbol = NULL;
|
|
69
|
+
|
|
70
|
+
/* Detected-trap classification: the runtime's trap sites self-classify
|
|
71
|
+
* through their message conventions (the exact bytes the executable lane
|
|
72
|
+
* prints), so the kind → code mapping keys on those prefixes. The codes are
|
|
73
|
+
* the compiler registry's runtime family (diagnostics/diagnostic.ts —
|
|
74
|
+
* documented beside SC4012); SC4019 is the family's residual for detected
|
|
75
|
+
* traps outside the named kinds (environment failures, unsupported
|
|
76
|
+
* operations, the RC audit). */
|
|
77
|
+
static const struct {
|
|
78
|
+
const char *prefix;
|
|
79
|
+
const char *code;
|
|
80
|
+
} scr_library_trap_kinds[] = {
|
|
81
|
+
{"Uncaught ", "SC4013"}, /* escaped exception at an entry */
|
|
82
|
+
{"scriptc: RangeError: ", "SC4014"}, /* range trap */
|
|
83
|
+
{"scriptc: TypeError: ", "SC4015"}, /* type trap */
|
|
84
|
+
{"scriptc: SyntaxError: ", "SC4016"}, /* syntax trap (regex compile) */
|
|
85
|
+
{"scriptc: out of memory", "SC4017"}, /* allocation failure */
|
|
86
|
+
{"scriptc: internal error: ", "SC4018"}, /* internal invariant failure */
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
static const char *scr_library_trap_code(const char *msg, size_t len) {
|
|
90
|
+
for (size_t i = 0; i < sizeof scr_library_trap_kinds / sizeof scr_library_trap_kinds[0]; i++) {
|
|
91
|
+
size_t plen = strlen(scr_library_trap_kinds[i].prefix);
|
|
92
|
+
if (len >= plen && memcmp(msg, scr_library_trap_kinds[i].prefix, plen) == 0) {
|
|
93
|
+
return scr_library_trap_kinds[i].code;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return "SC4019"; /* other detected trap */
|
|
97
|
+
}
|
|
98
|
+
|
|
51
99
|
static _Noreturn void scr_library_trap_deliver(const char *msg, size_t len, uint64_t addr) {
|
|
52
100
|
scr_library_poisoned = true;
|
|
101
|
+
if (!(len > 0 && (uint8_t)msg[0] == 0x01)) {
|
|
102
|
+
/* A detected trap: assemble the structured message. Static buffer —
|
|
103
|
+
* no malloc on the failure path; text truncates before structure ever
|
|
104
|
+
* would (codes and symbols are short; an oversized remediation drops
|
|
105
|
+
* whole, never split). */
|
|
106
|
+
static char buf[2048];
|
|
107
|
+
const char *code = scr_library_trap_code(msg, len);
|
|
108
|
+
const char *text = msg;
|
|
109
|
+
size_t text_len = len;
|
|
110
|
+
const char *rem = NULL;
|
|
111
|
+
for (size_t i = 0; i < scr_library_trap_overlays_len; i++) {
|
|
112
|
+
const char *const *t = &scr_library_trap_overlays[3 * i];
|
|
113
|
+
if (strcmp(t[0], code) == 0) {
|
|
114
|
+
if (t[1] != NULL) {
|
|
115
|
+
text = t[1];
|
|
116
|
+
text_len = strlen(t[1]);
|
|
117
|
+
}
|
|
118
|
+
rem = t[2];
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const char *sym = scr_library_entry_symbol != NULL ? scr_library_entry_symbol : "";
|
|
123
|
+
size_t tail = 2 + strlen(code) + strlen(sym);
|
|
124
|
+
if (rem != NULL) {
|
|
125
|
+
if (tail + 1 + strlen(rem) > sizeof buf - 1) rem = NULL; /* drop whole, keep structure */
|
|
126
|
+
else tail += 1 + strlen(rem);
|
|
127
|
+
}
|
|
128
|
+
size_t n = 0;
|
|
129
|
+
buf[n++] = '\x01';
|
|
130
|
+
size_t cap = sizeof buf - 1 - tail;
|
|
131
|
+
if (text_len > cap) text_len = cap;
|
|
132
|
+
for (size_t i = 0; i < text_len; i++) {
|
|
133
|
+
/* The encoding reserves 0x01/0x1F; runtime messages never contain
|
|
134
|
+
* them, but an escaped exception's rendered text embeds user bytes. */
|
|
135
|
+
char c = text[i];
|
|
136
|
+
buf[n++] = (c == '\x01' || c == '\x1f') ? ' ' : c;
|
|
137
|
+
}
|
|
138
|
+
buf[n++] = '\x1f';
|
|
139
|
+
memcpy(buf + n, code, strlen(code));
|
|
140
|
+
n += strlen(code);
|
|
141
|
+
buf[n++] = '\x1f';
|
|
142
|
+
memcpy(buf + n, sym, strlen(sym));
|
|
143
|
+
n += strlen(sym);
|
|
144
|
+
if (rem != NULL) {
|
|
145
|
+
buf[n++] = '\x1f';
|
|
146
|
+
memcpy(buf + n, rem, strlen(rem));
|
|
147
|
+
n += strlen(rem);
|
|
148
|
+
}
|
|
149
|
+
msg = buf;
|
|
150
|
+
len = n;
|
|
151
|
+
}
|
|
53
152
|
if (scr_library_sink != NULL) {
|
|
54
153
|
scr_library_sink(scr_library_sink_ctx, (const uint8_t *)msg, len, addr);
|
|
55
154
|
}
|
|
@@ -60,6 +159,14 @@ __attribute__((noinline)) _Noreturn void scr_trap(const char *msg) {
|
|
|
60
159
|
scr_library_trap_deliver(msg, strlen(msg), SCR_TRAP_ADDR());
|
|
61
160
|
}
|
|
62
161
|
|
|
162
|
+
__attribute__((noinline)) _Noreturn void scr_trap_len(const char *msg, size_t len) {
|
|
163
|
+
/* The length-delimited funnel entry: structured trap-teaching messages
|
|
164
|
+
* and verbatim 0x01-led thrown messages are byte-counted, never
|
|
165
|
+
* NUL-scanned (a thrown JS string may embed NUL). Same poison-deliver-
|
|
166
|
+
* abort discipline as scr_trap. */
|
|
167
|
+
scr_library_trap_deliver(msg, len, SCR_TRAP_ADDR());
|
|
168
|
+
}
|
|
169
|
+
|
|
63
170
|
__attribute__((noinline)) _Noreturn void scr_trap_fmt(const char *fmt, ...) {
|
|
64
171
|
static char buf[512]; /* no malloc on the invariant-failure path */
|
|
65
172
|
va_list ap;
|
|
@@ -72,7 +179,11 @@ __attribute__((noinline)) _Noreturn void scr_trap_fmt(const char *fmt, ...) {
|
|
|
72
179
|
|
|
73
180
|
/* ── entry prologues ──────────────────────────────────────────────────── */
|
|
74
181
|
|
|
75
|
-
void scr_library_entry(bool reset_arena) {
|
|
182
|
+
void scr_library_entry(bool reset_arena, const char *entry_symbol) {
|
|
183
|
+
/* Record the entry symbol FIRST so even a poisoned-abort's core dump
|
|
184
|
+
* names the entry; a trap anywhere below (the arena reset's OOM
|
|
185
|
+
* included) then reports the right symbol. */
|
|
186
|
+
scr_library_entry_symbol = entry_symbol;
|
|
76
187
|
/* A poisoned library's entries abort deterministically — never through the
|
|
77
188
|
* sink again (it received its exactly-once message when the trap fired),
|
|
78
189
|
* never into a heap whose invariants already failed. */
|
|
@@ -125,13 +236,16 @@ ScrStr *scr_library_str_in(const uint8_t *p, size_t len) {
|
|
|
125
236
|
return scr_str_new(len == 0 ? "" : (const char *)p, len);
|
|
126
237
|
}
|
|
127
238
|
|
|
128
|
-
ScrBytes *scr_library_bytes_in(const uint8_t *p, size_t len) {
|
|
239
|
+
ScrBytes *scr_library_bytes_in(const uint8_t *p, size_t len, const char *trap_msg) {
|
|
129
240
|
ScrBytes *b = scr_bytes_new(SCR_BYTES_U8, (double)len);
|
|
130
241
|
if (b == NULL) {
|
|
131
242
|
/* scr_bytes_new throws only for lengths past 2^53-1 — an impossible
|
|
132
|
-
* host buffer; funnel the contract violation instead of a NULL deref.
|
|
243
|
+
* host buffer; funnel the contract violation instead of a NULL deref.
|
|
244
|
+
* The message is the wrapper's compiler-assembled structured
|
|
245
|
+
* trap-teaching form (code SC4012, the trapping export's symbol, the
|
|
246
|
+
* profile's teaching and remediation) — opaque bytes here. */
|
|
133
247
|
scr_exc_clear();
|
|
134
|
-
scr_trap(
|
|
248
|
+
scr_trap(trap_msg);
|
|
135
249
|
}
|
|
136
250
|
if (len > 0 && p != NULL) memcpy(b->data, p, len);
|
|
137
251
|
return b;
|
package/src/scr_number.c
CHANGED
|
@@ -24,27 +24,19 @@
|
|
|
24
24
|
* unchanged. Provides d2d(), d2d_small_int(), decimalLength17(), div10(). */
|
|
25
25
|
#include "../vendor/ryu/d2s.c"
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (x < 0) {
|
|
36
|
-
*out++ = '-';
|
|
37
|
-
x = -x;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/* Shortest round-tripping digits via Ryū. Mirrors d2s_buffered_n's
|
|
41
|
-
* dispatch: the exact small-integer fast path first (trailing decimal
|
|
42
|
-
* zeros folded into the exponent), the full algorithm otherwise. */
|
|
27
|
+
/* The Ryū digit core, shared by the ECMA placement below and the Intl
|
|
28
|
+
* en-US number formatter (scr_lib.c): the shortest round-tripping digit
|
|
29
|
+
* string for a positive finite double — value = 0.digits × 10^n with no
|
|
30
|
+
* trailing zeros. Returns k (the digit count, ≤ 17); digits is
|
|
31
|
+
* NUL-terminated. Mirrors d2s_buffered_n's dispatch: the exact
|
|
32
|
+
* small-integer fast path first (trailing decimal zeros folded into the
|
|
33
|
+
* exponent), the full algorithm otherwise. */
|
|
34
|
+
int scr_f64_digits(double x, char digits[18], int *n_out) {
|
|
43
35
|
uint64_t bits;
|
|
44
36
|
memcpy(&bits, &x, sizeof bits);
|
|
45
37
|
const uint64_t ieeeMantissa = bits & ((1ull << DOUBLE_MANTISSA_BITS) - 1);
|
|
46
38
|
const uint32_t ieeeExponent =
|
|
47
|
-
(uint32_t)(bits >> DOUBLE_MANTISSA_BITS); /* sign
|
|
39
|
+
(uint32_t)(bits >> DOUBLE_MANTISSA_BITS); /* sign must be stripped */
|
|
48
40
|
floating_decimal_64 v;
|
|
49
41
|
if (d2d_small_int(ieeeMantissa, ieeeExponent, &v)) {
|
|
50
42
|
for (;;) {
|
|
@@ -60,9 +52,8 @@ size_t scr_f64_to_str(double x, char *buf) {
|
|
|
60
52
|
|
|
61
53
|
/* Ryū's (mantissa, exponent) → ECMA's (digits, k, n): the k mantissa
|
|
62
54
|
* digits have no trailing zeros, and value = 0.digits * 10^n. */
|
|
63
|
-
char digits[18];
|
|
64
55
|
int k = (int)decimalLength17(v.mantissa);
|
|
65
|
-
|
|
56
|
+
*n_out = v.exponent + k;
|
|
66
57
|
digits[k] = '\0';
|
|
67
58
|
uint64_t m = v.mantissa;
|
|
68
59
|
for (int i = k - 1; i >= 0; i--) {
|
|
@@ -70,6 +61,25 @@ size_t scr_f64_to_str(double x, char *buf) {
|
|
|
70
61
|
digits[i] = (char)('0' + (uint32_t)m - 10 * (uint32_t)q);
|
|
71
62
|
m = q;
|
|
72
63
|
}
|
|
64
|
+
return k;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
size_t scr_f64_to_str(double x, char *buf) {
|
|
68
|
+
if (isnan(x)) return (size_t)(stpcpy(buf, "NaN") - buf);
|
|
69
|
+
if (x == 0) return (size_t)(stpcpy(buf, "0") - buf); /* covers -0 */
|
|
70
|
+
if (isinf(x)) {
|
|
71
|
+
return (size_t)(stpcpy(buf, x < 0 ? "-Infinity" : "Infinity") - buf);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
char *out = buf;
|
|
75
|
+
if (x < 0) {
|
|
76
|
+
*out++ = '-';
|
|
77
|
+
x = -x;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
char digits[18];
|
|
81
|
+
int n;
|
|
82
|
+
int k = scr_f64_digits(x, digits, &n);
|
|
73
83
|
|
|
74
84
|
if (k <= n && n <= 21) {
|
|
75
85
|
/* Integer: digits followed by n-k zeros. */
|
package/src/scr_object.c
CHANGED
|
@@ -14,6 +14,24 @@ ScrStr *scr_classobj_name(ScrClassObj *c) {
|
|
|
14
14
|
return scr_str_retain((ScrStr *)c->name);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
/* The keyed-write MISS on a fixed-shape (signature-free) record: JS would
|
|
18
|
+
* ADD the property, which a monomorphic struct cannot — the write throws
|
|
19
|
+
* the catchable TypeError naming the key instead (the documented
|
|
20
|
+
* divergence; the emitted per-shape keyed-write helpers call this after
|
|
21
|
+
* the declared-field chain misses). */
|
|
22
|
+
void scr_record_key_miss(ScrStr *k) {
|
|
23
|
+
const char *base = "Cannot add property '";
|
|
24
|
+
ScrStr *head = scr_str_new(base, strlen(base));
|
|
25
|
+
ScrStr *with_key = scr_str_concat(head, k);
|
|
26
|
+
scr_str_release(head);
|
|
27
|
+
const char *tail_c = "' to a fixed-shape object";
|
|
28
|
+
ScrStr *tail = scr_str_new(tail_c, strlen(tail_c));
|
|
29
|
+
ScrStr *msg = scr_str_concat(with_key, tail);
|
|
30
|
+
scr_str_release(with_key);
|
|
31
|
+
scr_str_release(tail);
|
|
32
|
+
scr_throw_error(SCR_ERR_TYPE, msg); /* takes ownership */
|
|
33
|
+
}
|
|
34
|
+
|
|
17
35
|
#ifdef SCR_RC_AUDIT
|
|
18
36
|
static long scr_live_objects = 0;
|
|
19
37
|
long scr_obj_live_count(void) { return scr_live_objects; }
|