@scriptc/runtime 0.0.8 → 0.0.10
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_assert.c +43 -4
- package/src/scr_bytes.c +65 -18
- package/src/scr_bytes_io.c +507 -1
- package/src/scr_dc.c +6 -0
- package/src/scr_dgram.c +176 -1
- package/src/scr_dyn_handle.c +8 -66
- package/src/scr_dyn_invoke.c +96 -2
- package/src/scr_error.c +15 -0
- package/src/scr_events_emitter.c +34 -0
- package/src/scr_exception.c +4 -1
- package/src/scr_inspect.c +27 -3
- package/src/scr_island.c +417 -3
- package/src/scr_json.c +643 -6
- package/src/scr_lib.c +122 -4
- package/src/scr_library.c +105 -2
- package/src/scr_net.c +112 -0
- package/src/scr_number.c +29 -19
- package/src/scr_runtime.h +358 -9
- package/src/scr_stream.c +49 -2
- package/src/scr_tls.c +172 -0
package/src/scr_json.c
CHANGED
|
@@ -328,6 +328,7 @@ static ScrDyn *scr_dyn_alloc(ScrDynKind kind) {
|
|
|
328
328
|
d->rc = 1;
|
|
329
329
|
d->kind = kind;
|
|
330
330
|
d->buffer = false;
|
|
331
|
+
d->null_proto = false;
|
|
331
332
|
if (kind == SCR_DYN_ARR) {
|
|
332
333
|
d->v.arr.len = 0; /* cap/items preserved from the node's last life */
|
|
333
334
|
} else if (kind == SCR_DYN_OBJ) {
|
|
@@ -381,6 +382,11 @@ void scr_dyn_release(ScrDyn *d) {
|
|
|
381
382
|
* scr_json.c without the fiber machinery) never references it. */
|
|
382
383
|
scr_dyn_promise_release_fn(d->v.promise);
|
|
383
384
|
break;
|
|
385
|
+
case SCR_DYN_JSVAL:
|
|
386
|
+
/* Installed by scr_dyn_alloc_jsval (the gated constructor is the
|
|
387
|
+
* only producer) — same story as the promise arm. */
|
|
388
|
+
scr_dyn_jsval_ops()->release(d->v.jsval.cell);
|
|
389
|
+
break;
|
|
384
390
|
default:
|
|
385
391
|
break; /* null/bool/num have no children */
|
|
386
392
|
}
|
|
@@ -424,6 +430,107 @@ void scr_dyn_arr_push(ScrDyn *arr, ScrDyn *item) {
|
|
|
424
430
|
arr->v.arr.items[arr->v.arr.len++] = item; /* ownership moves in */
|
|
425
431
|
}
|
|
426
432
|
|
|
433
|
+
/* Spread completion for a runtime-arity argument list (`f(...xs)` in the
|
|
434
|
+
* checked-dynamic tier): JS's spread over the DOM's iterable kinds —
|
|
435
|
+
* arrays element-by-element (retained), strings by code POINT (the string
|
|
436
|
+
* iterator; astral chars arrive unsplit), bytes by byte; every other kind
|
|
437
|
+
* throws V8's exact SPREAD-CALL TypeError (catchable, pending — callers
|
|
438
|
+
* check): nullish sources spell the spread expression (`what`) — "v is
|
|
439
|
+
* not iterable (cannot read property undefined)" — and everything else is
|
|
440
|
+
* the generic "Spread syntax requires ...iterable[Symbol.iterator] to be
|
|
441
|
+
* a function". Borrows src. */
|
|
442
|
+
void scr_dyn_arr_push_spread(ScrDyn *arr, const ScrDyn *src, const char *what) {
|
|
443
|
+
if (src->kind == SCR_DYN_ARR) {
|
|
444
|
+
for (size_t i = 0; i < src->v.arr.len; i++) {
|
|
445
|
+
scr_dyn_arr_push(arr, scr_dyn_retain(src->v.arr.items[i]));
|
|
446
|
+
}
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
if (src->kind == SCR_DYN_BYTES) {
|
|
450
|
+
for (size_t i = 0; i < src->v.bytes->len; i++) {
|
|
451
|
+
scr_dyn_arr_push(arr, scr_dyn_new_num((double)src->v.bytes->data[i]));
|
|
452
|
+
}
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
if (src->kind == SCR_DYN_STR) {
|
|
456
|
+
double len = scr_str_utf16_len(src->v.str);
|
|
457
|
+
for (double at = 0; at < len;) {
|
|
458
|
+
ScrStr *cp = scr_str_cp_at(src->v.str, at);
|
|
459
|
+
at += scr_str_utf16_len(cp);
|
|
460
|
+
scr_dyn_arr_push(arr, scr_dyn_new_str(cp));
|
|
461
|
+
scr_str_release(cp);
|
|
462
|
+
}
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
if (src->kind == SCR_DYN_UNDEF || src->kind == SCR_DYN_NULL) {
|
|
466
|
+
ScrJsonBuf b;
|
|
467
|
+
scr_jb_init(&b);
|
|
468
|
+
scr_jb_puts(&b, what);
|
|
469
|
+
scr_jb_puts(&b, " is not iterable (cannot read property ");
|
|
470
|
+
scr_jb_puts(&b, src->kind == SCR_DYN_UNDEF ? "undefined" : "null");
|
|
471
|
+
scr_jb_puts(&b, ")");
|
|
472
|
+
scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&b));
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
if (src->kind == SCR_DYN_JSVAL) {
|
|
476
|
+
/* An engine array IS iterable — the generic not-iterable TypeError
|
|
477
|
+
* would be a wrong claim. Loud fence (lane dyn-routing-ops). */
|
|
478
|
+
scr_dyn_isl_fence(src, "spread");
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
static const char msg[] = "Spread syntax requires ...iterable[Symbol.iterator] to be a function";
|
|
482
|
+
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/* Destructuring pack over a DOM source (`const [a, b] = d`, a destructured
|
|
486
|
+
* dyn callback param): the spread walk's iterable kinds collect into a
|
|
487
|
+
* FRESH array — arrays element-by-element (retained), strings by code
|
|
488
|
+
* point, bytes by byte — and every other kind throws V8's DESTRUCTURING
|
|
489
|
+
* TypeError: `msg` verbatim when non-empty (the compile-time spelling —
|
|
490
|
+
* "v is not iterable" for identifier sources, "f is not a function or its
|
|
491
|
+
* return value is not iterable" for identifier-callee calls), else the
|
|
492
|
+
* runtime kind wording ("number 5 is not iterable (cannot read property
|
|
493
|
+
* Symbol(Symbol.iterator))"; objects and functions carry no value text,
|
|
494
|
+
* undefined no kind prefix, null V8's "object null"). Borrows both; +1 or
|
|
495
|
+
* NULL with the TypeError pending. */
|
|
496
|
+
ScrDyn *scr_dyn_iter_pack(const ScrDyn *src, const ScrStr *msg) {
|
|
497
|
+
if (src->kind == SCR_DYN_ARR || src->kind == SCR_DYN_BYTES || src->kind == SCR_DYN_STR) {
|
|
498
|
+
ScrDyn *out = scr_dyn_new_arr();
|
|
499
|
+
scr_dyn_arr_push_spread(out, src, ""); /* iterable kinds never consult `what` */
|
|
500
|
+
return out;
|
|
501
|
+
}
|
|
502
|
+
/* An engine-held value may well BE iterable — claiming "not iterable"
|
|
503
|
+
* would be a wrong answer. The routing lane owns iteration; until it
|
|
504
|
+
* arms this, the island fence names the operation loudly. */
|
|
505
|
+
if (src->kind == SCR_DYN_JSVAL) {
|
|
506
|
+
scr_dyn_isl_fence(src, "destructuring");
|
|
507
|
+
return NULL;
|
|
508
|
+
}
|
|
509
|
+
if (msg != NULL && msg->len > 0) {
|
|
510
|
+
scr_throw_error(SCR_ERR_TYPE, scr_str_new(msg->data, msg->len));
|
|
511
|
+
return NULL;
|
|
512
|
+
}
|
|
513
|
+
ScrJsonBuf b;
|
|
514
|
+
scr_jb_init(&b);
|
|
515
|
+
switch (src->kind) {
|
|
516
|
+
case SCR_DYN_UNDEF: scr_jb_puts(&b, "undefined"); break;
|
|
517
|
+
case SCR_DYN_NULL: scr_jb_puts(&b, "object null"); break;
|
|
518
|
+
case SCR_DYN_BOOL: scr_jb_puts(&b, src->v.b ? "boolean true" : "boolean false"); break;
|
|
519
|
+
case SCR_DYN_NUM: {
|
|
520
|
+
char buf[32];
|
|
521
|
+
scr_jb_puts(&b, "number ");
|
|
522
|
+
size_t n = scr_f64_to_str(src->v.num, buf);
|
|
523
|
+
scr_jb_write(&b, buf, n);
|
|
524
|
+
break;
|
|
525
|
+
}
|
|
526
|
+
case SCR_DYN_FUNC: scr_jb_puts(&b, "function"); break;
|
|
527
|
+
default: scr_jb_puts(&b, "object"); break;
|
|
528
|
+
}
|
|
529
|
+
scr_jb_puts(&b, " is not iterable (cannot read property Symbol(Symbol.iterator))");
|
|
530
|
+
scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&b));
|
|
531
|
+
return NULL;
|
|
532
|
+
}
|
|
533
|
+
|
|
427
534
|
/* Takes ownership of key (malloc'd) and value. Duplicate keys: the LATER
|
|
428
535
|
* value wins (like JS JSON.parse) — the old value is released and the new
|
|
429
536
|
* key buffer freed (the surviving entry keeps its original, equal key). */
|
|
@@ -481,6 +588,11 @@ ScrDyn *scr_dyn_new_str(ScrStr *s) {
|
|
|
481
588
|
|
|
482
589
|
ScrDyn *scr_dyn_new_arr(void) { return scr_dyn_alloc(SCR_DYN_ARR); }
|
|
483
590
|
ScrDyn *scr_dyn_new_obj(void) { return scr_dyn_alloc(SCR_DYN_OBJ); }
|
|
591
|
+
ScrDyn *scr_dyn_new_obj_null_proto(void) {
|
|
592
|
+
ScrDyn *d = scr_dyn_alloc(SCR_DYN_OBJ);
|
|
593
|
+
d->null_proto = true;
|
|
594
|
+
return d;
|
|
595
|
+
}
|
|
484
596
|
|
|
485
597
|
ScrDyn *scr_dyn_new_bytes_copy(const ScrBytes *b) {
|
|
486
598
|
ScrDyn *d = scr_dyn_alloc(SCR_DYN_BYTES);
|
|
@@ -540,6 +652,15 @@ ScrDyn *scr_dyn_new_func(ScrClosure *clo, ScrDynThunk thunk, uint32_t arity, con
|
|
|
540
652
|
* compiled per signature). Args borrowed; result owned (+1) or NULL with
|
|
541
653
|
* the exception pending. */
|
|
542
654
|
ScrDyn *scr_dyn_call(const ScrDyn *d, ScrDyn *const *args, size_t argc, const char *what) {
|
|
655
|
+
if (d->kind == SCR_DYN_JSVAL) {
|
|
656
|
+
/* An ENGINE callee: the call routes through scr_jsval_call with the
|
|
657
|
+
* uniform argument conversion (wrapped cells by reference, DOM data
|
|
658
|
+
* deep-copied, FUNC boxes through the host shim); a non-callable
|
|
659
|
+
* engine value throws the ENGINE's own TypeError, bridged catchably.
|
|
660
|
+
* `what` is unused — the engine's message names the failure. */
|
|
661
|
+
(void)what;
|
|
662
|
+
return scr_dyn_jsval_ops()->call(d->v.jsval.cell, args, argc);
|
|
663
|
+
}
|
|
543
664
|
if (d->kind != SCR_DYN_FUNC) {
|
|
544
665
|
ScrJsonBuf b;
|
|
545
666
|
scr_jb_init(&b);
|
|
@@ -551,6 +672,13 @@ ScrDyn *scr_dyn_call(const ScrDyn *d, ScrDyn *const *args, size_t argc, const ch
|
|
|
551
672
|
return d->v.fn.thunk(d->v.fn.clo, args, argc);
|
|
552
673
|
}
|
|
553
674
|
|
|
675
|
+
/* scr_dyn_call over a DOM ARRAY's elements — the spread-application form
|
|
676
|
+
* (`f(...args)` after the emitted argument array is built). Borrows both;
|
|
677
|
+
* result owned (+1), or NULL with the exception pending. */
|
|
678
|
+
ScrDyn *scr_dyn_apply(const ScrDyn *d, const ScrDyn *args, const char *what) {
|
|
679
|
+
return scr_dyn_call(d, args->v.arr.items, args->v.arr.len, what);
|
|
680
|
+
}
|
|
681
|
+
|
|
554
682
|
/* ── native handles in the DOM (SCR_DYN_HANDLE) ───────────────────────
|
|
555
683
|
* Per-tag ops stamped by the owning units at main() (scr_http_dyn_install
|
|
556
684
|
* / scr_net_dyn_install — the scr_net_install hook story), so this
|
|
@@ -582,6 +710,160 @@ const ScrDynHandleOps *scr_dyn_handle_ops_of(const ScrDyn *d) {
|
|
|
582
710
|
return scr_dyn_handle_ops(d->v.handle.tag);
|
|
583
711
|
}
|
|
584
712
|
|
|
713
|
+
/* errors.js's determineSpecificType over a DOM value — the "Received
|
|
714
|
+
* ..." tail of Node's ERR_INVALID_ARG_TYPE messages. Renders into buf
|
|
715
|
+
* when the shape needs a payload; returns the text either way. Lives
|
|
716
|
+
* beside the DOM core (not the gated handle unit) because the always-
|
|
717
|
+
* linked argument validators (bytes, fs) render through it too. */
|
|
718
|
+
const char *scr_dyn_specific_type(const ScrDyn *cb, char *detail, size_t cap) {
|
|
719
|
+
const char *d = detail;
|
|
720
|
+
switch (cb->kind) {
|
|
721
|
+
case SCR_DYN_NULL: d = "null"; break;
|
|
722
|
+
case SCR_DYN_UNDEF: d = "undefined"; break;
|
|
723
|
+
case SCR_DYN_OBJ: d = "an instance of Object"; break;
|
|
724
|
+
case SCR_DYN_ARR: d = "an instance of Array"; break;
|
|
725
|
+
case SCR_DYN_BYTES: d = "an instance of Uint8Array"; break;
|
|
726
|
+
case SCR_DYN_FUNC:
|
|
727
|
+
/* determineSpecificType: `function ${value.name}` — anonymous
|
|
728
|
+
* functions keep Node's trailing space. */
|
|
729
|
+
snprintf(detail, cap, "function %s", cb->v.fn.name != NULL ? cb->v.fn.name : "");
|
|
730
|
+
break;
|
|
731
|
+
case SCR_DYN_HANDLE:
|
|
732
|
+
snprintf(detail, cap, "an instance of %s", scr_dyn_handle_cls(cb));
|
|
733
|
+
break;
|
|
734
|
+
case SCR_DYN_PROMISE: d = "an instance of Promise"; break;
|
|
735
|
+
case SCR_DYN_JSVAL:
|
|
736
|
+
/* Engine-held: only objects/arrays/functions survive wrap-time scalar
|
|
737
|
+
* normalization — pick by the engine's typeof. */
|
|
738
|
+
d = scr_dyn_isl_typeof_is(cb, "function") ? "function" : "an instance of Object";
|
|
739
|
+
break;
|
|
740
|
+
case SCR_DYN_BOOL:
|
|
741
|
+
snprintf(detail, cap, "type boolean (%s)", cb->v.b ? "true" : "false");
|
|
742
|
+
break;
|
|
743
|
+
case SCR_DYN_NUM: {
|
|
744
|
+
char num[32];
|
|
745
|
+
size_t n = scr_f64_to_str(cb->v.num, num);
|
|
746
|
+
snprintf(detail, cap, "type number (%.*s)", (int)n, num);
|
|
747
|
+
break;
|
|
748
|
+
}
|
|
749
|
+
case SCR_DYN_STR: {
|
|
750
|
+
const ScrStr *sv = cb->v.str;
|
|
751
|
+
char insp[32];
|
|
752
|
+
size_t n = 0;
|
|
753
|
+
insp[n++] = '\'';
|
|
754
|
+
for (size_t i = 0; i < sv->len && n < 28; i++) insp[n++] = sv->data[i];
|
|
755
|
+
if (sv->len + 2 > 28) {
|
|
756
|
+
n = 25;
|
|
757
|
+
memcpy(insp + n, "...", 3);
|
|
758
|
+
n += 3;
|
|
759
|
+
} else {
|
|
760
|
+
insp[n++] = '\'';
|
|
761
|
+
}
|
|
762
|
+
snprintf(detail, cap, "type string (%.*s)", (int)n, insp);
|
|
763
|
+
break;
|
|
764
|
+
}
|
|
765
|
+
default: d = "an instance of Object"; break;
|
|
766
|
+
}
|
|
767
|
+
return d;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/* Node's ERR_INVALID_ARG_TYPE thrower ("The \"chunk\" argument must be
|
|
771
|
+
* of type string or an instance of Buffer or Uint8Array. Received type
|
|
772
|
+
* number (5)") — the handle dispatchers' and argument validators'
|
|
773
|
+
* per-arg gates. `expected` is the full "of type ..."/"an instance of
|
|
774
|
+
* ..." clause. */
|
|
775
|
+
/* The compiler-resolved ERR_INVALID_ARG_TYPE throw with a RUNTIME-
|
|
776
|
+
* rendered Received tail (error.argTypeThrow — the always-throwing
|
|
777
|
+
* lowered arms whose offending value is not a literal). Borrows all
|
|
778
|
+
* three; always throws catchably. */
|
|
779
|
+
void scr_throw_arg_type(const ScrStr *argname, const ScrStr *expected, const ScrDyn *got) {
|
|
780
|
+
scr_dyn_arg_type_fail(argname->data, expected->data, got);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
void scr_dyn_arg_type_fail(const char *argname, const char *expected, const ScrDyn *got) {
|
|
784
|
+
char detail[64];
|
|
785
|
+
const char *d = scr_dyn_specific_type(got, detail, sizeof detail);
|
|
786
|
+
char msg[224];
|
|
787
|
+
int len = snprintf(msg, sizeof msg,
|
|
788
|
+
"The \"%s\" argument must be %s. Received %s", argname, expected, d);
|
|
789
|
+
scr_throw_error_msg_code(SCR_ERR_TYPE, msg, (size_t)len, "ERR_INVALID_ARG_TYPE");
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/* The property flavor of the same ladder — Node renders option-bag
|
|
793
|
+
* members as "The \"options.x\" property must be ..." (errors.js keys the
|
|
794
|
+
* wording on the name, but every property-path caller here knows it is
|
|
795
|
+
* one). Same runtime-rendered Received tail; always throws catchably. */
|
|
796
|
+
void scr_dyn_prop_type_fail(const char *name, const char *expected, const ScrDyn *got) {
|
|
797
|
+
char detail[64];
|
|
798
|
+
const char *d = scr_dyn_specific_type(got, detail, sizeof detail);
|
|
799
|
+
char msg[224];
|
|
800
|
+
int len = snprintf(msg, sizeof msg,
|
|
801
|
+
"The \"%s\" property must be %s. Received %s", name, expected, d);
|
|
802
|
+
scr_throw_error_msg_code(SCR_ERR_TYPE, msg, (size_t)len, "ERR_INVALID_ARG_TYPE");
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/* The compiler-resolved property-typed throw (error.propTypeThrow —
|
|
806
|
+
* argTypeThrow's option-bag sibling). Borrows all three; always throws. */
|
|
807
|
+
void scr_throw_prop_type(const ScrStr *name, const ScrStr *expected, const ScrDyn *got) {
|
|
808
|
+
scr_dyn_prop_type_fail(name->data, expected->data, got);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/* ERR_INVALID_ARG_VALUE's "Received" tail — util.inspect where ARG_TYPE
|
|
812
|
+
* renders determineSpecificType: strings quote, scalars print plain.
|
|
813
|
+
* Deep shapes render their bracket sketch (enough for the validators'
|
|
814
|
+
* ladders; nothing observable pins the deep forms). */
|
|
815
|
+
const char *scr_dyn_inspect_lite(const ScrDyn *v, char *buf, size_t cap) {
|
|
816
|
+
switch (v->kind) {
|
|
817
|
+
case SCR_DYN_NULL: return "null";
|
|
818
|
+
case SCR_DYN_UNDEF: return "undefined";
|
|
819
|
+
case SCR_DYN_BOOL: return v->v.b ? "true" : "false";
|
|
820
|
+
case SCR_DYN_NUM: {
|
|
821
|
+
scr_f64_to_str(v->v.num, buf);
|
|
822
|
+
return buf;
|
|
823
|
+
}
|
|
824
|
+
case SCR_DYN_STR: {
|
|
825
|
+
const ScrStr *s = v->v.str;
|
|
826
|
+
size_t n = 0;
|
|
827
|
+
buf[n++] = '\'';
|
|
828
|
+
for (size_t i = 0; i < s->len && n + 5 < cap; i++) buf[n++] = s->data[i];
|
|
829
|
+
if (s->len + 2 + 5 > cap) {
|
|
830
|
+
memcpy(buf + n, "...", 3);
|
|
831
|
+
n += 3;
|
|
832
|
+
}
|
|
833
|
+
buf[n++] = '\'';
|
|
834
|
+
buf[n] = 0;
|
|
835
|
+
return buf;
|
|
836
|
+
}
|
|
837
|
+
case SCR_DYN_ARR: return "[ ... ]";
|
|
838
|
+
case SCR_DYN_OBJ: return "{ ... }";
|
|
839
|
+
case SCR_DYN_BYTES: return "<Buffer ...>";
|
|
840
|
+
default: return "[object]";
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
/* Node's ERR_INVALID_ARG_VALUE thrower: "The <argument|property> '<name>'
|
|
845
|
+
* <reason>. Received <inspected>" — the argument/property choice follows
|
|
846
|
+
* errors.js (a dotted name is a property path). `reason` defaults to
|
|
847
|
+
* "is invalid" when NULL. TypeError, like Node's default. */
|
|
848
|
+
void scr_dyn_arg_value_fail(const char *name, const char *reason, const ScrDyn *got) {
|
|
849
|
+
char insp[64];
|
|
850
|
+
const char *d = scr_dyn_inspect_lite(got, insp, sizeof insp);
|
|
851
|
+
char msg[256];
|
|
852
|
+
int len = snprintf(msg, sizeof msg, "The %s '%s' %s. Received %s",
|
|
853
|
+
strchr(name, '.') != NULL ? "property" : "argument", name,
|
|
854
|
+
reason != NULL ? reason : "is invalid", d);
|
|
855
|
+
scr_throw_error_msg_code(SCR_ERR_TYPE, msg, (size_t)len, "ERR_INVALID_ARG_VALUE");
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
/* The deferred JS lowering fence, thrown from a ladder's post-validation
|
|
859
|
+
* tail: the compiler renders the message (the statement fence's own text,
|
|
860
|
+
* "[SC2020 at file:line]" included) and the ladder throws it verbatim
|
|
861
|
+
* AFTER its Node-order validations pass — Node's validation errors come
|
|
862
|
+
* first, the honest refuse second. Borrowed; always throws catchably. */
|
|
863
|
+
void scr_throw_lowering_fence(const ScrStr *msg) {
|
|
864
|
+
scr_throw_error_msg_code(SCR_ERR_ERROR, msg->data, msg->len, "SC2020");
|
|
865
|
+
}
|
|
866
|
+
|
|
585
867
|
static void scr_dyn_handle_release(void *h, ScrDynHandleTag tag) {
|
|
586
868
|
scr_dyn_handle_ops(tag)->release(h);
|
|
587
869
|
}
|
|
@@ -604,6 +886,78 @@ ScrDyn *scr_dyn_alloc_promise(void (*release_fn)(ScrPromise *p)) {
|
|
|
604
886
|
return scr_dyn_alloc(SCR_DYN_PROMISE);
|
|
605
887
|
}
|
|
606
888
|
|
|
889
|
+
/* ── island values in the DOM (SCR_DYN_JSVAL) ─────────────────────────
|
|
890
|
+
* The gated constructor (scr_dyn_from_jsval, scr_island.c) builds through
|
|
891
|
+
* this allocator view and installs the engine-routing ops — the
|
|
892
|
+
* scr_dyn_alloc_promise story: a dynamic-free link never references
|
|
893
|
+
* engine symbols, and JSVAL nodes exist only after an install. */
|
|
894
|
+
static const ScrDynJsvalOps *scr_dynjs_ops = NULL;
|
|
895
|
+
|
|
896
|
+
ScrDyn *scr_dyn_alloc_jsval(ScrJsval *cell, const ScrDynJsvalOps *ops) {
|
|
897
|
+
scr_dynjs_ops = ops;
|
|
898
|
+
ScrDyn *d = scr_dyn_alloc(SCR_DYN_JSVAL);
|
|
899
|
+
d->v.jsval.cell = cell; /* ownership moves in */
|
|
900
|
+
return d;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
const ScrDynJsvalOps *scr_dyn_jsval_ops(void) {
|
|
904
|
+
if (!scr_dynjs_ops) {
|
|
905
|
+
scr_trap("scriptc: internal error: dyn jsval ops not installed\n");
|
|
906
|
+
}
|
|
907
|
+
return scr_dynjs_ops;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
bool scr_dyn_isl_typeof_is(const ScrDyn *d, const char *name) {
|
|
911
|
+
if (d->kind != SCR_DYN_JSVAL) return false;
|
|
912
|
+
ScrStr *t = scr_dyn_jsval_ops()->type_of(d->v.jsval.cell);
|
|
913
|
+
size_t n = strlen(name);
|
|
914
|
+
bool r = t->len == n && memcmp(t->data, name, n) == 0;
|
|
915
|
+
scr_str_release(t);
|
|
916
|
+
return r;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
bool scr_dyn_isl_is_array(const ScrDyn *d) {
|
|
920
|
+
return d->kind == SCR_DYN_JSVAL && scr_dyn_jsval_ops()->is_array(d->v.jsval.cell);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
bool scr_dyn_isl_is_error(const ScrDyn *d) {
|
|
924
|
+
return d->kind == SCR_DYN_JSVAL && scr_dyn_jsval_ops()->is_error(d->v.jsval.cell);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
bool scr_dyn_isl_fence(const ScrDyn *d, const char *what) {
|
|
928
|
+
if (d->kind != SCR_DYN_JSVAL) return false;
|
|
929
|
+
ScrJsonBuf b;
|
|
930
|
+
scr_jb_init(&b);
|
|
931
|
+
scr_jb_puts(&b, what);
|
|
932
|
+
scr_jb_puts(&b, " on an island value held in 'unknown' is not supported yet");
|
|
933
|
+
scr_throw_error(SCR_ERR_ERROR, scr_jb_finish(&b));
|
|
934
|
+
return false;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
ScrDyn *scr_dyn_isl_key_get(const ScrDyn *d, const ScrStr *k) {
|
|
938
|
+
/* The emitted keyed read's JSVAL arm: o[k] runs in the ENGINE (getters
|
|
939
|
+
* included, their throws bridged) and the result wraps back scalar-
|
|
940
|
+
* normalized — the retired `.length -> fence` row (and before that,
|
|
941
|
+
* the fence box's silent `.length -> 0`). */
|
|
942
|
+
return scr_dyn_jsval_ops()->key_get(d->v.jsval.cell, k);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
bool scr_dyn_is_nullish(const ScrDyn *d) {
|
|
946
|
+
if (d->kind == SCR_DYN_UNDEF || d->kind == SCR_DYN_NULL) return true;
|
|
947
|
+
/* JSVAL defensively routes to the engine's own test — the wrap
|
|
948
|
+
* constructor scalar-normalizes engine null/undefined away, so this
|
|
949
|
+
* arm answers false unless a producer bypassed it. */
|
|
950
|
+
if (d->kind == SCR_DYN_JSVAL) return scr_dyn_jsval_ops()->is_nullish(d->v.jsval.cell);
|
|
951
|
+
return false;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
void scr_dyn_isl_tostr_buf(ScrJsonBuf *b, const ScrDyn *d) {
|
|
955
|
+
ScrStr *s = scr_dyn_jsval_ops()->to_str(d->v.jsval.cell);
|
|
956
|
+
if (!s) return; /* bridged — the exception is pending, append nothing */
|
|
957
|
+
for (size_t i = 0; i < s->len; i++) scr_jb_putc(b, s->data[i]);
|
|
958
|
+
scr_str_release(s);
|
|
959
|
+
}
|
|
960
|
+
|
|
607
961
|
void *scr_dyn_handle_unbox(const ScrDyn *d, ScrDynHandleTag tag, const ScrDynPath *path, const char *want) {
|
|
608
962
|
if (d->kind != SCR_DYN_HANDLE || d->v.handle.tag != tag) {
|
|
609
963
|
scr_dyn_check_fail(path, want, d);
|
|
@@ -727,6 +1081,10 @@ bool scr_dyn_truthy(const ScrDyn *d) {
|
|
|
727
1081
|
case SCR_DYN_FUNC:
|
|
728
1082
|
case SCR_DYN_HANDLE:
|
|
729
1083
|
case SCR_DYN_PROMISE: return true;
|
|
1084
|
+
case SCR_DYN_JSVAL:
|
|
1085
|
+
/* Route to the engine's ToBoolean: objects/arrays/functions are
|
|
1086
|
+
* true, but the symbol/bigint edge (0n is falsy) needs the engine. */
|
|
1087
|
+
return scr_dyn_jsval_ops()->truthy(d->v.jsval.cell);
|
|
730
1088
|
default: return false; /* undefined, null */
|
|
731
1089
|
}
|
|
732
1090
|
}
|
|
@@ -735,6 +1093,10 @@ bool scr_dyn_truthy(const ScrDyn *d) {
|
|
|
735
1093
|
* null answers "object" — JS's oldest wart, preserved. */
|
|
736
1094
|
ScrStr *scr_dyn_typeof(const ScrDyn *d) {
|
|
737
1095
|
const char *s;
|
|
1096
|
+
/* An island value answers the ENGINE's typeof — "object" for the
|
|
1097
|
+
* wrapped objects/arrays, "function" for engine functions (row 1 of
|
|
1098
|
+
* the jsval→DOM op table; scalars normalized away at wrap time). */
|
|
1099
|
+
if (d->kind == SCR_DYN_JSVAL) return scr_dyn_jsval_ops()->type_of(d->v.jsval.cell);
|
|
738
1100
|
switch (d->kind) {
|
|
739
1101
|
case SCR_DYN_UNDEF: s = "undefined"; break;
|
|
740
1102
|
case SCR_DYN_NULL:
|
|
@@ -826,6 +1188,17 @@ static bool scr_dyn_json_write(ScrJsonBuf *b, const ScrDyn *d) {
|
|
|
826
1188
|
/* No own enumerable properties — Node stringifies a promise as {}. */
|
|
827
1189
|
scr_jb_puts(b, "{}");
|
|
828
1190
|
return true;
|
|
1191
|
+
case SCR_DYN_JSVAL: {
|
|
1192
|
+
/* The ENGINE's own JSON.stringify text splices in (toJSON protocols,
|
|
1193
|
+
* cycle TypeErrors — the engine's, bridged catchably). An engine
|
|
1194
|
+
* FUNCTION is absent under stringify, like the DOM's FUNC kind. */
|
|
1195
|
+
if (scr_dyn_isl_typeof_is(d, "function")) return false;
|
|
1196
|
+
ScrStr *j = scr_dyn_jsval_ops()->to_json(d->v.jsval.cell);
|
|
1197
|
+
if (!j) return true; /* pending exception; caller checks */
|
|
1198
|
+
scr_jb_write(b, j->data, j->len);
|
|
1199
|
+
scr_str_release(j);
|
|
1200
|
+
return true;
|
|
1201
|
+
}
|
|
829
1202
|
case SCR_DYN_HANDLE:
|
|
830
1203
|
default: {
|
|
831
1204
|
const char *msg = "JSON.stringify of a runtime handle is not supported yet";
|
|
@@ -1043,6 +1416,14 @@ ScrStr *scr_dyn_to_string(const ScrDyn *d, const ScrStr *enc) {
|
|
|
1043
1416
|
static const char f[] = "function () { [native code] }";
|
|
1044
1417
|
return scr_str_new(f, sizeof f - 1);
|
|
1045
1418
|
}
|
|
1419
|
+
case SCR_DYN_JSVAL: {
|
|
1420
|
+
/* The engine's own ToString (row 2 of the jsval→DOM op table): the
|
|
1421
|
+
* real prototype chain runs — user toString included, its throw
|
|
1422
|
+
* bridging. A bridged failure follows this function's existing
|
|
1423
|
+
* throw shape (pending exception + the empty-string dummy). */
|
|
1424
|
+
ScrStr *s = scr_dyn_jsval_ops()->to_str(d->v.jsval.cell);
|
|
1425
|
+
return s ? s : scr_str_new("", 0);
|
|
1426
|
+
}
|
|
1046
1427
|
case SCR_DYN_UNDEF:
|
|
1047
1428
|
case SCR_DYN_NULL:
|
|
1048
1429
|
default: {
|
|
@@ -1055,6 +1436,23 @@ ScrStr *scr_dyn_to_string(const ScrDyn *d, const ScrStr *enc) {
|
|
|
1055
1436
|
}
|
|
1056
1437
|
}
|
|
1057
1438
|
|
|
1439
|
+
/* The METHOD-CALL spelling `d.toString(enc?)` — scr_dyn_to_string with
|
|
1440
|
+
* the one receiver whose prototype LACKS the method carved out: a
|
|
1441
|
+
* null-prototype dictionary (Object.create(null)) has no toString at
|
|
1442
|
+
* all, so Node throws "<spelling> is not a function" where every other
|
|
1443
|
+
* OBJ answers "[object Object]". `what` carries the source spelling. */
|
|
1444
|
+
ScrStr *scr_dyn_to_string_method(const ScrDyn *d, const ScrStr *enc, const ScrStr *what) {
|
|
1445
|
+
if (d->kind == SCR_DYN_OBJ && d->null_proto) {
|
|
1446
|
+
ScrJsonBuf b;
|
|
1447
|
+
scr_jb_init(&b);
|
|
1448
|
+
for (size_t i = 0; i < what->len; i++) scr_jb_putc(&b, what->data[i]);
|
|
1449
|
+
scr_jb_puts(&b, " is not a function");
|
|
1450
|
+
scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&b));
|
|
1451
|
+
return scr_str_new("", 0);
|
|
1452
|
+
}
|
|
1453
|
+
return scr_dyn_to_string(d, enc);
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1058
1456
|
/* JS String() over the DOM kind — the WebIDL ToString the web globals
|
|
1059
1457
|
* (atob/btoa, DOMException's name resolution) run on their arguments:
|
|
1060
1458
|
* the unit kinds RENDER ("null"/"undefined") where the .toString() twin
|
|
@@ -1066,6 +1464,39 @@ ScrStr *scr_dyn_string_coerce(const ScrDyn *d) {
|
|
|
1066
1464
|
return scr_dyn_to_string(d, NULL);
|
|
1067
1465
|
}
|
|
1068
1466
|
|
|
1467
|
+
/* JS ToString over a DOM value WITH the object protocol (the WHATWG
|
|
1468
|
+
* USVString conversions — URLSearchParams names/values): an OBJ whose
|
|
1469
|
+
* own 'toString' member is callable is invoked with zero arguments (its
|
|
1470
|
+
* throw propagates, catchably); a non-primitive answer falls through to
|
|
1471
|
+
* 'valueOf' (ToPrimitive's string hint); exhaustion is the spec's
|
|
1472
|
+
* "Cannot convert object to primitive value" TypeError. Every other
|
|
1473
|
+
* kind matches scr_dyn_string_coerce (units RENDER — ToString(null) is
|
|
1474
|
+
* "null"). Borrows; +1, or NULL with the exception pending. */
|
|
1475
|
+
ScrStr *scr_dyn_string_coerce_js(const ScrDyn *d) {
|
|
1476
|
+
if (d->kind == SCR_DYN_OBJ) {
|
|
1477
|
+
static const char *const hint[2] = { "toString", "valueOf" };
|
|
1478
|
+
for (int i = 0; i < 2; i++) {
|
|
1479
|
+
ScrDyn *m = scr_dyn_obj_get(d, hint[i], strlen(hint[i])); /* borrowed */
|
|
1480
|
+
if (!m || m->kind != SCR_DYN_FUNC) continue;
|
|
1481
|
+
ScrDyn *r = scr_dyn_call(m, NULL, 0, hint[i]);
|
|
1482
|
+
if (!r) return NULL; /* the method threw — pending */
|
|
1483
|
+
if (r->kind == SCR_DYN_OBJ || r->kind == SCR_DYN_ARR ||
|
|
1484
|
+
r->kind == SCR_DYN_FUNC || r->kind == SCR_DYN_HANDLE ||
|
|
1485
|
+
r->kind == SCR_DYN_PROMISE) {
|
|
1486
|
+
scr_dyn_release(r); /* non-primitive answer: try the next method */
|
|
1487
|
+
continue;
|
|
1488
|
+
}
|
|
1489
|
+
ScrStr *s = scr_dyn_string_coerce(r);
|
|
1490
|
+
scr_dyn_release(r);
|
|
1491
|
+
return s;
|
|
1492
|
+
}
|
|
1493
|
+
static const char msg[] = "Cannot convert object to primitive value";
|
|
1494
|
+
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
|
|
1495
|
+
return NULL;
|
|
1496
|
+
}
|
|
1497
|
+
return scr_dyn_string_coerce(d);
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1069
1500
|
/* The checked-dynamic keyed WRITE (`h.k = v` on a dyn receiver): OBJ sets
|
|
1070
1501
|
* the member (later writes win, insertion order — JS); undefined/null
|
|
1071
1502
|
* throws Node's "Cannot set properties of ..."; every other kind throws
|
|
@@ -1102,6 +1533,13 @@ void scr_dyn_key_set(ScrDyn *recv, ScrStr *key, ScrDyn *value) {
|
|
|
1102
1533
|
scr_dyn_handle_key_set(recv, key, value);
|
|
1103
1534
|
return;
|
|
1104
1535
|
}
|
|
1536
|
+
if (recv->kind == SCR_DYN_JSVAL) {
|
|
1537
|
+
/* The write lands on the REAL engine object (aliasing preserved —
|
|
1538
|
+
* island-side readers see it); the value crosses through the uniform
|
|
1539
|
+
* from_dyn conversion, and engine refusals bridge catchably. */
|
|
1540
|
+
scr_dyn_jsval_ops()->key_set(recv->v.jsval.cell, key, value);
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1105
1543
|
ScrJsonBuf b;
|
|
1106
1544
|
scr_jb_init(&b);
|
|
1107
1545
|
if (recv->kind == SCR_DYN_UNDEF || recv->kind == SCR_DYN_NULL) {
|
|
@@ -1115,6 +1553,22 @@ void scr_dyn_key_set(ScrDyn *recv, ScrStr *key, ScrDyn *value) {
|
|
|
1115
1553
|
for (size_t i = 0; i < key->len; i++) scr_jb_putc(&b, key->data[i]);
|
|
1116
1554
|
scr_jb_puts(&b, "' on ");
|
|
1117
1555
|
scr_jb_puts(&b, scr_dyn_kind_name(recv));
|
|
1556
|
+
/* V8 quotes the primitive's own rendering after the kind — "on number
|
|
1557
|
+
* '5'", "on string 'abc'", "on boolean 'true'". Other kinds stop at
|
|
1558
|
+
* the kind word. */
|
|
1559
|
+
if (recv->kind == SCR_DYN_NUM) {
|
|
1560
|
+
char buf[32];
|
|
1561
|
+
size_t n = scr_f64_to_str(recv->v.num, buf);
|
|
1562
|
+
scr_jb_puts(&b, " '");
|
|
1563
|
+
scr_jb_write(&b, buf, n);
|
|
1564
|
+
scr_jb_putc(&b, '\'');
|
|
1565
|
+
} else if (recv->kind == SCR_DYN_STR) {
|
|
1566
|
+
scr_jb_puts(&b, " '");
|
|
1567
|
+
scr_jb_write(&b, recv->v.str->data, recv->v.str->len);
|
|
1568
|
+
scr_jb_putc(&b, '\'');
|
|
1569
|
+
} else if (recv->kind == SCR_DYN_BOOL) {
|
|
1570
|
+
scr_jb_puts(&b, recv->v.b ? " 'true'" : " 'false'");
|
|
1571
|
+
}
|
|
1118
1572
|
}
|
|
1119
1573
|
scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&b));
|
|
1120
1574
|
}
|
|
@@ -1176,12 +1630,28 @@ void scr_jb_put_dyn(ScrJsonBuf *b, const ScrDyn *d) {
|
|
|
1176
1630
|
scr_jb_puts(b, "null"); /* the buffer never surfaces: the pending throw wins */
|
|
1177
1631
|
return;
|
|
1178
1632
|
}
|
|
1633
|
+
case SCR_DYN_JSVAL: {
|
|
1634
|
+
/* The ENGINE's own JSON.stringify text splices in (toJSON protocols,
|
|
1635
|
+
* cycle TypeErrors — all the engine's, bridged catchably). An engine
|
|
1636
|
+
* FUNCTION serializes like the DOM's FUNC kind (dropped from objects
|
|
1637
|
+
* by the member loop below; null defensively elsewhere). */
|
|
1638
|
+
if (scr_dyn_isl_typeof_is(d, "function")) {
|
|
1639
|
+
scr_jb_puts(b, "null");
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1642
|
+
ScrStr *j = scr_dyn_jsval_ops()->to_json(d->v.jsval.cell);
|
|
1643
|
+
if (!j) return; /* bridged — the pending throw wins */
|
|
1644
|
+
for (size_t i = 0; i < j->len; i++) scr_jb_putc(b, j->data[i]);
|
|
1645
|
+
scr_str_release(j);
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1179
1648
|
case SCR_DYN_OBJ: {
|
|
1180
1649
|
scr_jb_putc(b, '{');
|
|
1181
1650
|
bool first = true;
|
|
1182
1651
|
for (size_t i = 0; i < d->v.obj.len; i++) {
|
|
1183
1652
|
const ScrDynEntry *e = &d->v.obj.entries[i];
|
|
1184
1653
|
if (e->value->kind == SCR_DYN_UNDEF || e->value->kind == SCR_DYN_FUNC) continue; /* dropped, like Node */
|
|
1654
|
+
if (e->value->kind == SCR_DYN_JSVAL && scr_dyn_isl_typeof_is(e->value, "function")) continue; /* engine functions drop too */
|
|
1185
1655
|
if (!first) scr_jb_putc(b, ',');
|
|
1186
1656
|
first = false;
|
|
1187
1657
|
/* Keys escape exactly like string values (put_json_str quotes). */
|
|
@@ -1230,6 +1700,9 @@ static const char *scr_dyn_kind_name(const ScrDyn *d) {
|
|
|
1230
1700
|
case SCR_DYN_FUNC: return "function";
|
|
1231
1701
|
case SCR_DYN_HANDLE: return scr_dyn_handle_cls(d); /* "got IncomingMessage" */
|
|
1232
1702
|
case SCR_DYN_PROMISE: return "Promise"; /* "got Promise" */
|
|
1703
|
+
case SCR_DYN_JSVAL: return "an island value"; /* "got an island value" — validated
|
|
1704
|
+
* extraction of engine-held values has no armed route yet (lane
|
|
1705
|
+
* dom-jsval-long-tail); the failure names the world honestly. */
|
|
1233
1706
|
}
|
|
1234
1707
|
return "unknown";
|
|
1235
1708
|
}
|
|
@@ -1784,6 +2257,12 @@ bool scr_dyn_strict_eq(const ScrDyn *a, const ScrDyn *b) {
|
|
|
1784
2257
|
case SCR_DYN_PROMISE:
|
|
1785
2258
|
/* And the PROMISE: one promise crossing twice is one JS value. */
|
|
1786
2259
|
return a->v.promise == b->v.promise;
|
|
2260
|
+
case SCR_DYN_JSVAL:
|
|
2261
|
+
/* Identity is the ENGINE VALUE, not the box or even the cell: two
|
|
2262
|
+
* wraps of one engine value compare ===-equal (the engine's own
|
|
2263
|
+
* strict equality answers). Mixed kinds already answered false above
|
|
2264
|
+
* — a DOM copy is a different object, which is Node's answer too. */
|
|
2265
|
+
return a == b || scr_dyn_jsval_ops()->strict_eq(a->v.jsval.cell, b->v.jsval.cell);
|
|
1787
2266
|
default: return a == b;
|
|
1788
2267
|
}
|
|
1789
2268
|
}
|
|
@@ -1820,6 +2299,12 @@ ScrDyn *scr_dyn_fn_get(const ScrDyn *d, const char *key, size_t key_len) {
|
|
|
1820
2299
|
|
|
1821
2300
|
void scr_sc_validate_options(const ScrDyn *options) {
|
|
1822
2301
|
if (options == NULL || options->kind == SCR_DYN_UNDEF || options->kind == SCR_DYN_NULL) return;
|
|
2302
|
+
/* An engine-held options bag IS a dictionary to Node — the "cannot be
|
|
2303
|
+
* converted" TypeError would be a wrong claim. Loud fence. */
|
|
2304
|
+
if (options->kind == SCR_DYN_JSVAL) {
|
|
2305
|
+
scr_dyn_isl_fence(options, "structuredClone options");
|
|
2306
|
+
return;
|
|
2307
|
+
}
|
|
1823
2308
|
if (options->kind != SCR_DYN_OBJ) {
|
|
1824
2309
|
static const char msg[] =
|
|
1825
2310
|
"Failed to execute 'structuredClone': Options cannot be converted to a dictionary";
|
|
@@ -1898,6 +2383,12 @@ static ScrDyn *scr_sc_clone(const ScrDyn *v, const ScrScParent *up) {
|
|
|
1898
2383
|
}
|
|
1899
2384
|
return out;
|
|
1900
2385
|
}
|
|
2386
|
+
case SCR_DYN_JSVAL:
|
|
2387
|
+
/* Node CLONES a plain engine object — the DataCloneError default
|
|
2388
|
+
* below would be a wrong claim, and fabricating a shape would be a
|
|
2389
|
+
* silent wrong answer. Loud fence (lane dom-jsval-long-tail). */
|
|
2390
|
+
scr_dyn_isl_fence(v, "structuredClone");
|
|
2391
|
+
return NULL;
|
|
1901
2392
|
case SCR_DYN_FUNC:
|
|
1902
2393
|
case SCR_DYN_HANDLE:
|
|
1903
2394
|
default: {
|
|
@@ -1942,6 +2433,11 @@ ScrDyn *scr_structured_clone_missing(void) {
|
|
|
1942
2433
|
}
|
|
1943
2434
|
|
|
1944
2435
|
bool scr_dyn_err_instanceof(const ScrDyn *d, double kind) {
|
|
2436
|
+
/* A JSVAL node never came from a runtime ScrError, so the cache miss
|
|
2437
|
+
* below answers false — the documented contract ("a DOM value that
|
|
2438
|
+
* never came from an error answers false"). An ENGINE TypeError held
|
|
2439
|
+
* in 'unknown' thus answers false where Node answers true: covered by
|
|
2440
|
+
* lane dom-jsval-long-tail (needs the engine's class instanceof). */
|
|
1945
2441
|
for (size_t i = 0; i < scr_errdyn_n; i++) {
|
|
1946
2442
|
if (scr_errdyn_cache[i].dyn == d) {
|
|
1947
2443
|
const ScrVt *vt = scr_errdyn_cache[i].err->vt;
|
|
@@ -2009,6 +2505,12 @@ static ScrDyn *scr_dyn_objwalk(const ScrDyn *v, ScrObjWalk mode) {
|
|
|
2009
2505
|
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
|
|
2010
2506
|
return NULL;
|
|
2011
2507
|
}
|
|
2508
|
+
if (v->kind == SCR_DYN_JSVAL) {
|
|
2509
|
+
/* The ENGINE walks its own object (own-key order, getters running,
|
|
2510
|
+
* Object.entries' pairs) and the results come back as a NATIVE DOM
|
|
2511
|
+
* array — keys are DOM strings, values wrap per element. */
|
|
2512
|
+
return scr_dyn_jsval_ops()->obj_walk(v->v.jsval.cell, (int)mode);
|
|
2513
|
+
}
|
|
2012
2514
|
ScrDyn *out = scr_dyn_new_arr();
|
|
2013
2515
|
if (v->kind == SCR_DYN_OBJ) {
|
|
2014
2516
|
size_t n = v->v.obj.len;
|
|
@@ -2110,21 +2612,150 @@ static ScrDyn *scr_dyn_objwalk(const ScrDyn *v, ScrObjWalk mode) {
|
|
|
2110
2612
|
|
|
2111
2613
|
ScrDyn *scr_dyn_obj_keys(const ScrDyn *v) { return scr_dyn_objwalk(v, SCR_OBJWALK_KEYS); }
|
|
2112
2614
|
|
|
2615
|
+
/* One source's own enumerable members onto an OBJ target — the
|
|
2616
|
+
* CopyDataProperties walk Object.assign runs per source. OBJ sources copy
|
|
2617
|
+
* their members directly (last write wins); the index-keyed kinds
|
|
2618
|
+
* (arrays, strings, bytes) ride the entries walk, so the copied key set
|
|
2619
|
+
* is EXACTLY what Object.keys answers for that kind (string sources per
|
|
2620
|
+
* UTF-16 code unit, like Node's String exotic object); nullish sources
|
|
2621
|
+
* copy nothing (Node skips them) and the scalar/function/handle kinds
|
|
2622
|
+
* have no own enumerable string keys. Non-OBJ targets copy nothing (the
|
|
2623
|
+
* existing two-arg stance — a DOM array target has no property table). */
|
|
2624
|
+
static void scr_dyn_assign_from(ScrDyn *target, const ScrDyn *src) {
|
|
2625
|
+
if (target->kind != SCR_DYN_OBJ) return;
|
|
2626
|
+
if (src->kind == SCR_DYN_UNDEF || src->kind == SCR_DYN_NULL) return;
|
|
2627
|
+
if (src->kind == SCR_DYN_OBJ) {
|
|
2628
|
+
for (size_t i = 0; i < src->v.obj.len; i++) {
|
|
2629
|
+
scr_dyn_obj_set(target, src->v.obj.entries[i].key,
|
|
2630
|
+
src->v.obj.entries[i].key_len,
|
|
2631
|
+
scr_dyn_retain(src->v.obj.entries[i].value));
|
|
2632
|
+
}
|
|
2633
|
+
return;
|
|
2634
|
+
}
|
|
2635
|
+
if (src->kind != SCR_DYN_ARR && src->kind != SCR_DYN_STR &&
|
|
2636
|
+
src->kind != SCR_DYN_BYTES) {
|
|
2637
|
+
return;
|
|
2638
|
+
}
|
|
2639
|
+
ScrDyn *pairs = scr_dyn_obj_entries(src); /* +1; never throws here */
|
|
2640
|
+
if (pairs == NULL) return;
|
|
2641
|
+
if (pairs->kind == SCR_DYN_ARR) {
|
|
2642
|
+
for (size_t i = 0; i < pairs->v.arr.len; i++) {
|
|
2643
|
+
const ScrDyn *pair = pairs->v.arr.items[i];
|
|
2644
|
+
if (pair->kind != SCR_DYN_ARR || pair->v.arr.len != 2) continue;
|
|
2645
|
+
const ScrDyn *k = pair->v.arr.items[0];
|
|
2646
|
+
if (k->kind != SCR_DYN_STR) continue;
|
|
2647
|
+
scr_dyn_obj_set(target, k->v.str->data, k->v.str->len,
|
|
2648
|
+
scr_dyn_retain(pair->v.arr.items[1]));
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
scr_dyn_release(pairs);
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2113
2654
|
/* Object.assign over DOM values: copies `src`'s own members onto `target`
|
|
2114
2655
|
* (last write wins) and answers the target retained (+1). Nullish
|
|
2115
2656
|
* receivers throw Node's ToObject TypeError; nullish sources copy
|
|
2116
|
-
* nothing;
|
|
2657
|
+
* nothing; index-keyed sources (arrays/strings/bytes) copy their index
|
|
2658
|
+
* keys like Node; the remaining kinds have no own enumerable keys. */
|
|
2117
2659
|
ScrDyn *scr_dyn_assign(ScrDyn *target, const ScrDyn *src) {
|
|
2118
2660
|
if (target->kind == SCR_DYN_UNDEF || target->kind == SCR_DYN_NULL) {
|
|
2119
2661
|
const char *m = "Cannot convert undefined or null to object";
|
|
2120
2662
|
scr_throw_error_msg(SCR_ERR_TYPE, m, strlen(m));
|
|
2121
2663
|
return NULL;
|
|
2122
2664
|
}
|
|
2123
|
-
if (target->kind ==
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2665
|
+
if (target->kind == SCR_DYN_JSVAL) {
|
|
2666
|
+
/* An ENGINE target: the copy runs in the engine (Object.assign's own
|
|
2667
|
+
* semantics — setters fire, own-enumerable order); the source enters
|
|
2668
|
+
* per the uniform conversion (a wrapped source spreads by reference,
|
|
2669
|
+
* DOM data as the usual member deep copy). */
|
|
2670
|
+
if (!scr_dyn_jsval_ops()->assign(target->v.jsval.cell, src)) return NULL;
|
|
2671
|
+
return scr_dyn_retain(target);
|
|
2672
|
+
}
|
|
2673
|
+
if (src->kind == SCR_DYN_JSVAL) {
|
|
2674
|
+
/* A wrapped SOURCE onto a DOM target: the engine lists its own
|
|
2675
|
+
* [key, value] pairs (getters running) and each lands as a DOM
|
|
2676
|
+
* member — values wrap per element, scalars normalized. */
|
|
2677
|
+
if (target->kind == SCR_DYN_OBJ) {
|
|
2678
|
+
ScrDyn *entries = scr_dyn_jsval_ops()->obj_walk(src->v.jsval.cell, 2);
|
|
2679
|
+
if (!entries) return NULL;
|
|
2680
|
+
for (size_t i = 0; i < entries->v.arr.len; i++) {
|
|
2681
|
+
const ScrDyn *pair = entries->v.arr.items[i];
|
|
2682
|
+
const ScrDyn *k = pair->v.arr.items[0];
|
|
2683
|
+
scr_dyn_obj_set(target, k->v.str->data, k->v.str->len,
|
|
2684
|
+
scr_dyn_retain(pair->v.arr.items[1]));
|
|
2685
|
+
}
|
|
2686
|
+
scr_dyn_release(entries);
|
|
2687
|
+
}
|
|
2688
|
+
return scr_dyn_retain(target);
|
|
2689
|
+
}
|
|
2690
|
+
scr_dyn_assign_from(target, src);
|
|
2691
|
+
return scr_dyn_retain(target);
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2694
|
+
/* Variadic Object.assign's argument pack (the `Object.assign({},
|
|
2695
|
+
* ...arr.map(f), tail)` shape): the compiler builds one fresh DOM array
|
|
2696
|
+
* of sources — plain arguments push borrowed (+1 in), spread arguments
|
|
2697
|
+
* flatten through the spread-call walk (scr_dyn_arr_push_spread's V8
|
|
2698
|
+
* TypeError texts, `what` spelling the spread expression for the nullish
|
|
2699
|
+
* form) — so every source evaluates and flattens BEFORE any copying,
|
|
2700
|
+
* exactly JS's ArgumentListEvaluation. */
|
|
2701
|
+
void scr_dyn_pack_push(ScrDyn *pack, ScrDyn *v) {
|
|
2702
|
+
scr_dyn_arr_push(pack, scr_dyn_retain(v));
|
|
2703
|
+
}
|
|
2704
|
+
|
|
2705
|
+
void scr_dyn_pack_push_spread(ScrDyn *pack, const ScrDyn *src, const ScrStr *what) {
|
|
2706
|
+
scr_dyn_arr_push_spread(pack, src, what->data);
|
|
2707
|
+
}
|
|
2708
|
+
|
|
2709
|
+
/* The ITERATED-path spread completion: V8 only takes the optimized
|
|
2710
|
+
* apply-path texts (scr_dyn_arr_push_spread's — the expression spelled
|
|
2711
|
+
* for nullish sources) when the spread is the SINGLE LAST argument; a spread
|
|
2712
|
+
* followed by more arguments, or one of several spreads, drives the real
|
|
2713
|
+
* iterator protocol, whose failure text describes the VALUE instead —
|
|
2714
|
+
* "undefined", "object null", "number 5", "boolean true", "function",
|
|
2715
|
+
* bare "object" — + " is not iterable (cannot read property
|
|
2716
|
+
* Symbol(Symbol.iterator))". The compiler picks the variant by the
|
|
2717
|
+
* spread's syntactic position. MAY THROW (pending). Borrows src. */
|
|
2718
|
+
void scr_dyn_pack_push_spread_iter(ScrDyn *pack, const ScrDyn *src) {
|
|
2719
|
+
if (src->kind == SCR_DYN_ARR || src->kind == SCR_DYN_BYTES ||
|
|
2720
|
+
src->kind == SCR_DYN_STR) {
|
|
2721
|
+
scr_dyn_arr_push_spread(pack, src, ""); /* iterable kinds never throw */
|
|
2722
|
+
return;
|
|
2723
|
+
}
|
|
2724
|
+
ScrJsonBuf b;
|
|
2725
|
+
scr_jb_init(&b);
|
|
2726
|
+
switch (src->kind) {
|
|
2727
|
+
case SCR_DYN_UNDEF: scr_jb_puts(&b, "undefined"); break;
|
|
2728
|
+
case SCR_DYN_NULL: scr_jb_puts(&b, "object null"); break;
|
|
2729
|
+
case SCR_DYN_NUM: {
|
|
2730
|
+
scr_jb_puts(&b, "number ");
|
|
2731
|
+
ScrStr *s = scr_f64_to_scrstr(src->v.num);
|
|
2732
|
+
for (size_t i = 0; i < s->len; i++) scr_jb_putc(&b, s->data[i]);
|
|
2733
|
+
scr_str_release(s);
|
|
2734
|
+
break;
|
|
2735
|
+
}
|
|
2736
|
+
case SCR_DYN_BOOL: scr_jb_puts(&b, src->v.b ? "boolean true" : "boolean false"); break;
|
|
2737
|
+
case SCR_DYN_FUNC: scr_jb_puts(&b, "function"); break;
|
|
2738
|
+
default: scr_jb_puts(&b, "object"); break; /* OBJ/HANDLE/PROMISE */
|
|
2739
|
+
}
|
|
2740
|
+
scr_jb_puts(&b, " is not iterable (cannot read property Symbol(Symbol.iterator))");
|
|
2741
|
+
scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&b));
|
|
2742
|
+
}
|
|
2743
|
+
|
|
2744
|
+
/* Object.assign(target, ...sources) over the flattened pack: the nullish
|
|
2745
|
+
* ToObject TypeError first (Node throws before looking at sources), then
|
|
2746
|
+
* each source's own-member copy left to right, answering the target
|
|
2747
|
+
* retained (+1) — identity, like JS. */
|
|
2748
|
+
ScrDyn *scr_dyn_assign_all(ScrDyn *target, const ScrDyn *sources) {
|
|
2749
|
+
if (target->kind == SCR_DYN_UNDEF || target->kind == SCR_DYN_NULL) {
|
|
2750
|
+
const char *m = "Cannot convert undefined or null to object";
|
|
2751
|
+
scr_throw_error_msg(SCR_ERR_TYPE, m, strlen(m));
|
|
2752
|
+
return NULL;
|
|
2753
|
+
}
|
|
2754
|
+
if (sources->kind == SCR_DYN_ARR) {
|
|
2755
|
+
for (size_t i = 0; i < sources->v.arr.len; i++) {
|
|
2756
|
+
ScrDyn *r = scr_dyn_assign(target, sources->v.arr.items[i]);
|
|
2757
|
+
if (!r) return NULL;
|
|
2758
|
+
scr_dyn_release(r);
|
|
2128
2759
|
}
|
|
2129
2760
|
}
|
|
2130
2761
|
return scr_dyn_retain(target);
|
|
@@ -2136,6 +2767,12 @@ bool scr_dyn_has_own(const ScrDyn *v, const ScrStr *key) {
|
|
|
2136
2767
|
scr_throw_error_msg(SCR_ERR_TYPE, m, strlen(m));
|
|
2137
2768
|
return false;
|
|
2138
2769
|
}
|
|
2770
|
+
/* Engine-held: the ENGINE's own Object.hasOwn answers (a bridged
|
|
2771
|
+
* surprise leaves the exception pending and answers false — callers
|
|
2772
|
+
* check pending like every fallible dyn op). */
|
|
2773
|
+
if (v->kind == SCR_DYN_JSVAL) {
|
|
2774
|
+
return scr_dyn_jsval_ops()->has_own(v->v.jsval.cell, key) == 1;
|
|
2775
|
+
}
|
|
2139
2776
|
if (v->kind == SCR_DYN_OBJ) {
|
|
2140
2777
|
return scr_dyn_obj_get(v, key->data, key->len) != NULL;
|
|
2141
2778
|
}
|