Files
scriptc/packages/runtime/src/scr_dyn_invoke.c
T
Chris Tate 928c30e908 Bind thisArg in the array callback methods of dynamic arrays
forEach, map, filter, some, every, find, findIndex, findLast,
findLastIndex and flatMap on a dynamic array, and through
Array.prototype.<method>.call on a native array, called the callback
without its thisArg, so `this` in the callback was not the second
argument. These methods now bind `this` to the thisArg, or undefined
when there is none, once per call, around all of the method's callbacks.
2026-10-26 10:19:57 -05:00

2552 lines
111 KiB
C

/* Prototype-method dispatch on checked-dynamic receivers (scr_dyn_invoke)
* and its companions: JS String() over the checked-dynamic tree (scr_dyn_display — join
* and the error texts need it standalone) and Object.defineProperties
* over dyn values (scr_dyn_define_props). Linked only when the IR
* carries dynInvoke nodes or dyn.defineProps calls (native-toolchain.ts gates on
* moduleUsesDynInvoke — the scr_assert.c precedent), so dispatch-free
* binaries keep their exact size class. The checked-dynamic tree itself lives in
* scr_json.c; this unit uses only its public surface plus ScrJsonBuf.
*/
#include "scr_runtime.h"
#include "scr_dyn_methods.h"
#include <math.h>
#include <stdio.h>
#include <stdlib.h> /* malloc/free in the sort snapshot */
#include <string.h>
/* ── checked-dynamic METHOD DISPATCH (scr_dyn_invoke) ──────────────────
*
* `recv.m(args)` where recv is a dyn value and `m` is a name a
* dyn-representable prototype declares (Array/String/Function shared
* names — push, slice, forEach, apply, ...): a stored-member read would
* return wrong results for real methods, so the dispatch runs HERE, over the
* receiver's runtime kind. test/common's mustCall internals are the
* canonical caller (mustCallChecks.push/filter/forEach, fn.apply).
*
* Honesty ladder, per (kind, name):
* - implemented: JS-exact semantics below;
* - the name exists on that kind's JS prototype but has no
* implementation here: a "not supported yet" Error instead of a
* wrong result;
* - the name does not exist on that kind's prototype: Node's own
* TypeError ("<spelling> is not a function"), because that IS the
* JS answer;
* - OBJ receivers: the own member calls (own properties shadow the
* prototype in JS too), otherwise "<spelling> is not a function";
* - undefined/null receivers: Node's "Cannot read properties of ...".
*
* recv/args are BORROWED; the result is owned (+1). MAY THROW (returns
* NULL with the exception pending). */
/* JS String() over a dyn value, runtime-side (join needs it standalone —
* the emitted sc_ds walker exists only in programs that spell
* String(unknown) themselves). Same rules: arrays join with "," and
* null/undefined elements print empty, the checked-dynamic tree's error encoding renders
* "name: message", functions render the native-code form. With
* `to_string` (join and the default sort order, which run ToString), other
* objects convert with ToPrimitive's string hint, calling a toString or
* valueOf of their own or of their prototypes, and an exception it throws
* stays pending; without it (error messages), they are [object Object]. */
static void scr_dyn_display_buf(ScrJsonBuf *b, const ScrDyn *d, bool to_string) {
switch (d->kind) {
case SCR_DYN_SYMBOL: {
ScrStr *s = scr_dyn_string_coerce(d);
scr_str_release(s);
return;
}
case SCR_DYN_UNDEF: scr_jb_puts(b, "undefined"); return;
case SCR_DYN_NULL: scr_jb_puts(b, "null"); return;
case SCR_DYN_BOOL: scr_jb_puts(b, d->v.b ? "true" : "false"); return;
case SCR_DYN_BIGINT: {
ScrStr *s = scr_bigint_to_string(d->v.bigint, 10);
scr_jb_write(b, s->data, s->len);
scr_str_release(s);
return;
}
case SCR_DYN_NUM: {
ScrStr *s = scr_f64_to_scrstr(d->v.num);
scr_jb_write(b, s->data, s->len);
scr_str_release(s);
return;
}
case SCR_DYN_STR:
scr_jb_write(b, d->v.str->data, d->v.str->len);
return;
case SCR_DYN_ARR: {
/* The length is read once, as join reads it; an element's toString can
* append to or shrink the array (scr_dyn_arr_at answers undefined past
* its current end). */
size_t len = d->v.arr.len;
for (size_t i = 0; i < len; i++) {
if (i > 0) scr_jb_putc(b, ',');
ScrDyn *e = scr_dyn_arr_at(d, (double)i);
if (!e) return;
if (e->kind != SCR_DYN_UNDEF && e->kind != SCR_DYN_NULL) scr_dyn_display_buf(b, e, to_string);
scr_dyn_release(e);
if (scr_exc_pending()) return;
}
return;
}
case SCR_DYN_OBJ: {
const ScrDyn *marker = scr_dyn_obj_get(d, "%error", 6);
if (marker) {
ScrStr *text = scr_dyn_error_string(d);
if (!text) return;
scr_jb_write(b, text->data, text->len);
scr_str_release(text);
return;
}
if (to_string) {
ScrStr *text = scr_dyn_string_coerce_js(d);
if (!text) return;
scr_jb_write(b, text->data, text->len);
scr_str_release(text);
return;
}
scr_jb_puts(b, "[object Object]");
return;
}
case SCR_DYN_BYTES:
if (d->buffer) {
/* Buffer-flavored values (stream chunks) coerce utf8 (Node's
* Buffer.toString); plain Uint8Array joins its elements. */
ScrStr *enc = scr_str_new("utf8", 4);
ScrStr *txt = scr_bytes_to_str(d->v.bytes, enc);
scr_str_release(enc);
scr_jb_write(b, txt->data, txt->len);
scr_str_release(txt);
return;
}
for (size_t i = 0; i < d->v.bytes->len; i++) {
if (i > 0) scr_jb_putc(b, ',');
ScrStr *n = scr_f64_to_scrstr(scr_bytes_get(d->v.bytes, (double)i));
scr_jb_write(b, n->data, n->len);
scr_str_release(n);
}
return;
case SCR_DYN_FUNC:
scr_jb_puts(b, "function ");
if (d->v.fn.name) scr_jb_puts(b, d->v.fn.name);
scr_jb_puts(b, "() { [native code] }");
return;
case SCR_DYN_HANDLE: {
ScrStr *s = scr_dyn_to_string(d, NULL);
scr_jb_write(b, s->data, s->len);
scr_str_release(s);
return;
}
case SCR_DYN_PROMISE:
/* Object.prototype.toString — promises carry no own toString, and
* their @@toStringTag is not modeled here; Node's String() answer
* for a bare promise is "[object Promise]". */
scr_jb_puts(b, "[object Promise]");
return;
case SCR_DYN_TYPED_REF: {
/* ToString of a class instance that converts through
* Object.prototype.toString shows its Symbol.toStringTag getter's
* string. */
if (to_string && scr_dyn_typed_ref_put_tag(b, d)) return;
ScrDyn *materialized = scr_dyn_typed_ref_materialize(d);
scr_dyn_display_buf(b, materialized, to_string);
scr_dyn_release(materialized);
return;
}
case SCR_DYN_PROXY:
scr_dyn_proxy_unsupported("string conversion");
return;
}
}
void scr_jb_join_dyn(ScrJsonBuf *b, const ScrDyn *d) {
if (d->kind != SCR_DYN_UNDEF && d->kind != SCR_DYN_NULL) scr_dyn_display_buf(b, d, true);
}
ScrStr *scr_dyn_display(const ScrDyn *d) {
ScrJsonBuf b;
scr_jb_init(&b);
scr_dyn_display_buf(&b, d, false);
return scr_jb_finish(&b);
}
static void dyn_throw_not_fn(const char *what) {
ScrJsonBuf b;
scr_jb_init(&b);
scr_jb_puts(&b, what);
scr_jb_puts(&b, " is not a function");
scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&b));
}
static void dyn_throw_unsupported(const char *proto, const char *method) {
ScrJsonBuf b;
scr_jb_init(&b);
scr_jb_putc(&b, '\'');
scr_jb_puts(&b, proto);
scr_jb_puts(&b, ".prototype.");
scr_jb_puts(&b, method);
scr_jb_puts(&b, "' on a dynamic value is not supported yet");
scr_throw_error(SCR_ERR_ERROR, scr_jb_finish(&b));
}
/* ToIntegerOrInfinity over an optional index argument. Missing and
* undefined use the operation's default; present values run ToNumber once. */
static double dyn_index_arg(ScrDyn *const *args, size_t argc, size_t i, double dflt, const char *what) {
(void)what;
if (i >= argc || args[i]->kind == SCR_DYN_UNDEF) return dflt;
double number;
if (!scr_dyn_number_coerce_js(args[i], &number)) return 0;
return isnan(number) ? 0 : trunc(number);
}
/* JS relative-index normalization (slice's rule). */
static size_t dyn_rel_index(double rel, size_t len) {
if (rel < 0) {
double r = (double)len + rel;
return r < 0 ? 0 : (size_t)r;
}
return rel > (double)len ? len : (size_t)rel;
}
static bool dyn_arr_length_unchanged(const ScrDyn *recv, size_t length) {
if (recv->v.arr.len == length) return true;
static const char message[] = "Array method after an argument changes the receiver length is not supported yet";
scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020");
return false;
}
/* The array callback runner (forEach/map/filter/some/every/find/
* findIndex): calls cb(item, i, recv) per element through the boxed
* thunk. Returns the owned result or NULL with the exception pending. */
static ScrDyn *dyn_call_cb(ScrDyn *cb, ScrDyn *item, size_t i, ScrDyn *recv) {
ScrDyn *idx = scr_dyn_new_num((double)i);
ScrDyn *cbargs[3] = { item, idx, recv };
ScrDyn *r = scr_dyn_call(cb, cbargs, 3, "callback");
scr_dyn_release(idx);
return r;
}
/* The callable-callback gate: JS's "<String(cb)> is not a function". */
static bool dyn_cb_check(ScrDyn *const *args, size_t argc) {
ScrDyn *cb = argc > 0 ? args[0] : scr_dyn_undefined();
if (scr_dyn_is_callable(cb)) return true;
ScrJsonBuf b;
scr_jb_init(&b);
scr_dyn_display_buf(&b, cb, false);
scr_jb_puts(&b, " is not a function");
scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&b));
return false;
}
static bool dyn_borrow_is(const ScrStr *method, const char *name) {
size_t len = strlen(name);
return method->len == len && memcmp(method->data, name, len) == 0;
}
static bool dyn_borrow_index(const ScrStr *key, size_t *out) {
if (!key->len || (key->len > 1 && key->data[0] == '0')) return false;
size_t n = 0;
for (size_t i = 0; i < key->len; i++) {
unsigned digit = (unsigned)(key->data[i] - '0');
if (digit > 9 || n > (SIZE_MAX - digit) / 10) return false;
n = n * 10 + digit;
}
*out = n;
return true;
}
static ScrDyn *dyn_borrow_get(ScrDyn *recv, const ScrStr *key) {
if (recv->kind == SCR_DYN_TYPED_REF) {
ScrDyn *snapshot = scr_dyn_typed_ref_materialize(recv);
if (!snapshot) return NULL;
ScrDyn *result = dyn_borrow_get(snapshot, key);
scr_dyn_release(snapshot);
return result;
}
if (recv->kind == SCR_DYN_OBJ) return scr_dyn_obj_read(recv, key->data, key->len);
if (recv->kind == SCR_DYN_PROXY) return scr_dyn_proxy_get(recv, key);
if (recv->kind == SCR_DYN_FUNC) {
ScrDyn *value = scr_dyn_fn_get(recv, key->data, key->len);
return value ? value : scr_dyn_retain(scr_dyn_undefined());
}
if (recv->kind == SCR_DYN_ARR || recv->kind == SCR_DYN_STR || recv->kind == SCR_DYN_BYTES) {
if (key->len == 6 && memcmp(key->data, "length", 6) == 0) {
double length = recv->kind == SCR_DYN_ARR ? (double)recv->v.arr.len
: recv->kind == SCR_DYN_STR ? scr_str_utf16_len(recv->v.str)
: (double)recv->v.bytes->len;
return scr_dyn_new_num(length);
}
size_t index;
if (!dyn_borrow_index(key, &index)) {
if (recv->kind == SCR_DYN_ARR && recv->v.arr.properties)
return scr_dyn_arr_named_get(recv, key);
return scr_dyn_retain(scr_dyn_undefined());
}
if (recv->kind == SCR_DYN_ARR && index >= recv->v.arr.len && recv->v.arr.properties)
return scr_dyn_arr_named_get(recv, key);
if (recv->kind == SCR_DYN_ARR) return scr_dyn_arr_at(recv, (double)index);
if (recv->kind == SCR_DYN_BYTES) {
return index < recv->v.bytes->len
? scr_dyn_new_num(scr_bytes_get(recv->v.bytes, (double)index))
: scr_dyn_retain(scr_dyn_undefined());
}
if ((double)index >= scr_str_utf16_len(recv->v.str)) return scr_dyn_retain(scr_dyn_undefined());
ScrStr *unit = scr_str_slice(recv->v.str, (double)index, (double)index + 1);
ScrDyn *value = scr_dyn_new_str(unit);
scr_str_release(unit);
return value;
}
return scr_dyn_retain(scr_dyn_undefined());
}
static bool dyn_borrow_has(ScrDyn *recv, const ScrStr *key) {
if (recv->kind == SCR_DYN_STR || recv->kind == SCR_DYN_BYTES) {
size_t index;
return dyn_borrow_index(key, &index) &&
(recv->kind == SCR_DYN_STR
? (double)index < scr_str_utf16_len(recv->v.str)
: index < recv->v.bytes->len);
}
if (recv->kind == SCR_DYN_FUNC) {
ScrDyn *value = scr_dyn_fn_get(recv, key->data, key->len);
bool present = value != NULL;
scr_dyn_release(value);
return present;
}
return scr_dyn_has_key(recv, key);
}
static ScrStr *dyn_borrow_key(size_t index) {
char text[32];
int n = snprintf(text, sizeof text, "%zu", index);
return scr_str_new(text, (size_t)n);
}
static bool dyn_borrow_length(ScrDyn *recv, size_t *out) {
ScrStr *key = scr_str_new("length", 6);
ScrDyn *value = dyn_borrow_get(recv, key);
scr_str_release(key);
if (!value) return false;
double number;
bool ok = scr_dyn_number_coerce_js(value, &number);
scr_dyn_release(value);
if (!ok) return false;
if (isnan(number) || number <= 0) { *out = 0; return true; }
number = fmin(trunc(number), 9007199254740991.0);
if (number > (double)SIZE_MAX) {
static const char msg[] = "array-like length exceeds the native index range";
scr_throw_error_msg(SCR_ERR_ERROR, msg, sizeof msg - 1);
return false;
}
*out = (size_t)number;
return true;
}
static bool dyn_borrow_iteration(size_t step) {
if (step < 1000000) return true;
static const char msg[] = "array-like iteration over one million entries is not supported yet";
scr_throw_error_msg(SCR_ERR_ERROR, msg, sizeof msg - 1);
return false;
}
static bool dyn_borrow_number(const ScrDyn *value, double *out) {
if (!scr_dyn_number_coerce_js(value, out)) return false;
if (isnan(*out)) *out = 0;
else if (isfinite(*out)) *out = trunc(*out);
return true;
}
static bool dyn_borrow_present(ScrDyn *recv, size_t index) {
ScrStr *key = dyn_borrow_key(index);
bool found = dyn_borrow_has(recv, key);
scr_str_release(key);
return found;
}
static ScrDyn *dyn_borrow_item(ScrDyn *recv, size_t index) {
ScrStr *key = dyn_borrow_key(index);
ScrDyn *value = dyn_borrow_get(recv, key);
scr_str_release(key);
return value;
}
ScrDyn *scr_dyn_array_proto_call(ScrDyn *recv, ScrStr *method, ScrDyn *args) {
if (recv->kind == SCR_DYN_NULL || recv->kind == SCR_DYN_UNDEF) {
static const char msg[] = "Cannot convert undefined or null to object";
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
return NULL;
}
if (!args || args->kind != SCR_DYN_ARR) scr_trap("scriptc: invalid Array.prototype arguments\n");
ScrDyn *const *argv = args->v.arr.items;
size_t argc = args->v.arr.len;
size_t len;
if (!dyn_borrow_length(recv, &len)) return NULL;
bool index_of = dyn_borrow_is(method, "indexOf");
bool last_index_of = dyn_borrow_is(method, "lastIndexOf");
bool includes = dyn_borrow_is(method, "includes");
if (index_of || last_index_of || includes) {
if (len == 0) return includes ? scr_dyn_new_bool(false) : scr_dyn_new_num(-1);
ScrDyn *needle = argc ? argv[0] : scr_dyn_undefined();
double from = last_index_of ? (double)len - 1 : 0;
if (argc > 1 && !dyn_borrow_number(argv[1], &from)) return NULL;
if (isnan(from)) from = 0;
else from = trunc(from);
double first = from < 0 ? (double)len + from : from;
if (last_index_of) {
if (from >= 0) first = fmin(from, (double)len - 1);
if (first >= (double)len) first = (double)len - 1;
for (size_t step = 0; first >= 0 && step <= (size_t)first; step++) {
if (len > 1000000 && !dyn_borrow_iteration(step)) return NULL;
size_t i = (size_t)first - step;
if (!dyn_borrow_present(recv, i)) { if (scr_exc_pending()) return NULL; continue; }
ScrDyn *item = dyn_borrow_item(recv, i);
if (!item) return NULL;
bool found = scr_dyn_strict_eq(item, needle);
scr_dyn_release(item);
if (found) return scr_dyn_new_num((double)i);
}
return scr_dyn_new_num(-1);
}
if (first < 0) first = 0;
if (first >= (double)len) return includes ? scr_dyn_new_bool(false) : scr_dyn_new_num(-1);
for (size_t i = (size_t)first; i < len; i++) {
if (len > 1000000 && !dyn_borrow_iteration(i - (size_t)first)) return NULL;
if (!includes && !dyn_borrow_present(recv, i)) { if (scr_exc_pending()) return NULL; continue; }
ScrDyn *item = dyn_borrow_item(recv, i);
if (!item) return NULL;
bool found = scr_dyn_strict_eq(item, needle) ||
(includes && item->kind == SCR_DYN_NUM && needle->kind == SCR_DYN_NUM &&
isnan(item->v.num) && isnan(needle->v.num));
scr_dyn_release(item);
if (found) return includes ? scr_dyn_new_bool(true) : scr_dyn_new_num((double)i);
}
return includes ? scr_dyn_new_bool(false) : scr_dyn_new_num(-1);
}
if (dyn_borrow_is(method, "at")) {
double offset = 0;
if (argc && !dyn_borrow_number(argv[0], &offset)) return NULL;
double index = offset < 0 ? (double)len + offset : offset;
return index >= 0 && index < (double)len
? dyn_borrow_item(recv, (size_t)index)
: scr_dyn_retain(scr_dyn_undefined());
}
if (dyn_borrow_is(method, "slice")) {
if (recv->kind == SCR_DYN_PROXY) {
scr_dyn_proxy_unsupported("Array.prototype.slice");
return NULL;
}
double startD = 0;
double endD = (double)len;
if (argc && !dyn_borrow_number(argv[0], &startD)) return NULL;
if (argc > 1 && argv[1]->kind != SCR_DYN_UNDEF && !dyn_borrow_number(argv[1], &endD)) return NULL;
if (isnan(startD)) startD = 0;
if (isnan(endD)) endD = 0;
size_t start = dyn_rel_index(trunc(startD), len);
size_t end = dyn_rel_index(trunc(endD), len);
if (end > start && end - start > 1000000) {
static const char message[] = "Array.prototype.slice exceeds the native iteration limit";
scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020");
return NULL;
}
ScrDyn *out = scr_dyn_new_arr();
for (size_t i = start; i < end; i++) {
if (!dyn_borrow_iteration(i - start)) { scr_dyn_release(out); return NULL; }
bool present = dyn_borrow_present(recv, i);
if (scr_exc_pending()) { scr_dyn_release(out); return NULL; }
if (!present) {
scr_dyn_arr_push_hole(out);
continue;
}
ScrDyn *item = dyn_borrow_item(recv, i);
if (!item) { scr_dyn_release(out); return NULL; }
scr_dyn_arr_push(out, item);
}
return out;
}
bool each = dyn_borrow_is(method, "forEach");
bool map = dyn_borrow_is(method, "map");
bool filter = dyn_borrow_is(method, "filter");
bool some = dyn_borrow_is(method, "some");
bool every = dyn_borrow_is(method, "every");
bool find = dyn_borrow_is(method, "find");
bool find_index = dyn_borrow_is(method, "findIndex");
bool find_last = dyn_borrow_is(method, "findLast");
bool find_last_index = dyn_borrow_is(method, "findLastIndex");
bool reduce = dyn_borrow_is(method, "reduce");
bool reduce_right = dyn_borrow_is(method, "reduceRight");
if (!(each || map || filter || some || every || find || find_index || find_last || find_last_index || reduce || reduce_right)) {
static const char msg[] = "Array.prototype method is not supported yet";
scr_throw_error_msg(SCR_ERR_ERROR, msg, sizeof msg - 1);
return NULL;
}
if (!dyn_cb_check(argv, argc)) return NULL;
ScrDyn *cb = argv[0];
if (reduce || reduce_right) {
ScrDyn *acc = argc > 1 ? scr_dyn_retain(argv[1]) : NULL;
for (size_t step = 0; step < len; step++) {
if (len > 1000000 && !dyn_borrow_iteration(step)) { scr_dyn_release(acc); return NULL; }
size_t i = reduce_right ? len - 1 - step : step;
if (!dyn_borrow_present(recv, i)) { if (scr_exc_pending()) { scr_dyn_release(acc); return NULL; } continue; }
ScrDyn *item = dyn_borrow_item(recv, i);
if (!item) { scr_dyn_release(acc); return NULL; }
if (!acc) { acc = item; continue; }
ScrDyn *idx = scr_dyn_new_num((double)i);
ScrDyn *cbargs[4] = { acc, item, idx, recv };
ScrDyn *next = scr_dyn_call(cb, cbargs, 4, "callback");
scr_dyn_release(idx);
scr_dyn_release(item);
scr_dyn_release(acc);
if (!next) return NULL;
acc = next;
}
if (acc) return acc;
static const char msg[] = "Reduce of empty array with no initial value";
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
return NULL;
}
ScrDyn *out = (map || filter) ? scr_dyn_new_arr() : NULL;
ScrDyn *this_arg = argc > 1 ? argv[1] : scr_dyn_undefined();
for (size_t i = 0; i < len; i++) {
if (len > 1000000 && !dyn_borrow_iteration(i)) { scr_dyn_release(out); return NULL; }
size_t index = find_last || find_last_index ? len - 1 - i : i;
bool present = find || find_index || find_last || find_last_index || dyn_borrow_present(recv, index);
if (scr_exc_pending()) { scr_dyn_release(out); return NULL; }
if (!present) {
if (map) {
scr_dyn_arr_push_hole(out);
}
continue;
}
ScrDyn *item = dyn_borrow_item(recv, index);
if (!item) { scr_dyn_release(out); return NULL; }
scr_dyn_this_push_dyn(this_arg);
ScrDyn *result = dyn_call_cb(cb, item, index, recv);
scr_dyn_this_pop();
if (!result) { scr_dyn_release(item); scr_dyn_release(out); return NULL; }
if (map) {
scr_dyn_arr_push(out, result);
} else {
bool truthy = scr_dyn_truthy(result);
scr_dyn_release(result);
if (filter && truthy) scr_dyn_arr_push(out, scr_dyn_retain(item));
if (some && truthy) { scr_dyn_release(item); return scr_dyn_new_bool(true); }
if (every && !truthy) { scr_dyn_release(item); return scr_dyn_new_bool(false); }
if ((find || find_last) && truthy) return item;
if ((find_index || find_last_index) && truthy) { scr_dyn_release(item); return scr_dyn_new_num((double)index); }
}
scr_dyn_release(item);
}
if (out) return out;
if (some) return scr_dyn_new_bool(false);
if (every) return scr_dyn_new_bool(true);
if (find || find_last) return scr_dyn_retain(scr_dyn_undefined());
if (find_index || find_last_index) return scr_dyn_new_num(-1);
return scr_dyn_retain(scr_dyn_undefined());
}
/* The dispatch below maps a call's method name to an id once
* (dyn_method_of, scr_dyn_methods.h) and its branches compare ids. Names
* compare by content, and any name the branches do not test is DYN_M_NONE.
* scripts/gen-dyn-methods.mjs writes the header from the DYN_M_ ids used
* here. */
/* Handle receivers: hand the whole call to the tag's ops (installed by
* the owning unit at main() — a missing install is an internal error the
* accessor reports). */
static ScrDyn *scr_dynh_dispatch(ScrDyn *recv, const char *method, ScrDyn *const *args, size_t argc, const char *what) {
const ScrDynHandleOps *ops = scr_dyn_handle_ops_of(recv);
if (ops->invoke) return ops->invoke(recv->v.handle.ptr, recv, method, args, argc, what);
ScrDyn *callable = ops->get ? ops->get(recv->v.handle.ptr, method, strlen(method)) : NULL;
if (scr_exc_pending()) { scr_dyn_release(callable); return NULL; }
if (!callable) callable = scr_dyn_retain(scr_dyn_undefined());
scr_dyn_this_push_dyn(recv);
ScrDyn *result = scr_dyn_call(callable, args, argc, what);
scr_dyn_this_pop();
scr_dyn_release(callable);
return result;
}
/* JS Array.prototype.sort over a dyn array: the spec's snapshot-sort, as
* V8 runs it. Elements other than undefined copy (retained) into a work
* list, V8's TimSort orders it (scr_sort_slots_with: Node's comparator
* calls in Node's order), and the undefined elements follow it without
* reaching the comparator. The ordered list writes back index by index, so
* a comparator that mutates the receiver mid-sort never dangles the items
* being ordered. The default comparator compares ToString images
* (scr_dyn_display_buf — join's conversion) in UTF-16 code-unit order. A
* comparator result converts with ToNumber; NaN counts as 0. */
static ScrStr *dyn_sort_str(const ScrDyn *e) {
ScrJsonBuf b;
scr_jb_init(&b);
scr_dyn_display_buf(&b, e, true);
return scr_jb_finish(&b);
}
static bool dyn_sort_less(void *ctx, uint64_t xs, uint64_t ys, bool *failed) {
ScrDyn *cmp = ctx;
ScrDyn *x = (ScrDyn *)(uintptr_t)xs, *y = (ScrDyn *)(uintptr_t)ys;
if (cmp) {
ScrDyn *argv[2] = { x, y };
ScrDyn *r = scr_dyn_call(cmp, argv, 2, "comparefn");
if (!r) { *failed = true; return false; }
double v = 0;
bool ok = scr_dyn_number_coerce_js(r, &v);
scr_dyn_release(r);
if (!ok || scr_exc_pending()) { *failed = true; return false; }
return v < 0;
}
ScrStr *xstr = dyn_sort_str(x);
ScrStr *ystr = scr_exc_pending() ? NULL : dyn_sort_str(y);
if (scr_exc_pending()) {
scr_str_release(xstr);
if (ystr) scr_str_release(ystr);
*failed = true;
return false;
}
int c = scr_str_cmp_u16(xstr, ystr);
scr_str_release(xstr);
scr_str_release(ystr);
return c < 0;
}
static bool dyn_arr_sort(ScrDyn *recv, ScrDyn *cmp) {
size_t len = recv->v.arr.len;
uint64_t *work = (uint64_t *)malloc((len ? len : 1) * sizeof(uint64_t));
if (!work) return true; /* OOM: answer the array unsorted over crashing */
size_t count = 0, undefined = 0;
for (size_t i = 0; i < len; i++) {
ScrDyn *item = recv->v.arr.items[i];
if (item->kind == SCR_DYN_UNDEF) undefined++;
else work[count++] = (uint64_t)(uintptr_t)scr_dyn_retain(item);
}
bool ok = scr_sort_slots_with(work, count, dyn_sort_less, cmp);
if (ok) {
/* Write back into whatever the array holds NOW (a mutating comparator
* may have replaced entries): the work list's +1 moves in, then the
* undefined elements, and the displaced entry releases. Elements beyond
* the current length (a shrinking comparator) just release. */
for (size_t i = 0; i < count + undefined; i++) {
ScrDyn *next = i < count ? (ScrDyn *)(uintptr_t)work[i] : scr_dyn_retain(scr_dyn_undefined());
if (i < recv->v.arr.len) {
ScrDyn *old = recv->v.arr.items[i];
recv->v.arr.items[i] = next;
scr_dyn_release(old);
} else {
scr_dyn_release(next);
}
}
} else {
for (size_t i = 0; i < count; i++) scr_dyn_release((ScrDyn *)(uintptr_t)work[i]);
}
free(work);
return ok;
}
static bool dyn_arr_flatten(ScrDyn *out, const ScrDyn *source, double depth, size_t *visits) {
for (size_t i = 0; i < source->v.arr.len; i++) {
if (++*visits > 1000000) {
static const char message[] = "Array.prototype.flat exceeds the native iteration limit";
scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020");
return false;
}
if (!dyn_borrow_present((ScrDyn *)source, i)) { if (scr_exc_pending()) return false; continue; }
ScrDyn *item = dyn_borrow_item((ScrDyn *)source, i);
if (!item) return false;
if (depth > 0 && item->kind == SCR_DYN_TYPED_REF && scr_dyn_ref_is_array(item)) {
ScrDyn *view = scr_dyn_typed_ref_materialize(item);
if (!view || scr_exc_pending()) { scr_dyn_release(view); scr_dyn_release(item); return false; }
bool ok = dyn_arr_flatten(out, view, depth - 1, visits);
scr_dyn_release(view);
scr_dyn_release(item);
if (!ok) return false;
continue;
}
if (depth > 0 && item->kind == SCR_DYN_ARR) {
bool ok = dyn_arr_flatten(out, item, depth - 1, visits);
scr_dyn_release(item);
if (!ok) return false;
} else {
scr_dyn_arr_push(out, item);
}
}
return true;
}
static bool dyn_arr_copy_item(ScrDyn *out, const ScrDyn *source, size_t index, bool preserve_hole) {
if (preserve_hole && !dyn_borrow_present((ScrDyn *)source, index)) {
if (scr_exc_pending()) return false;
scr_dyn_arr_push_hole(out);
return true;
}
ScrDyn *item = scr_dyn_arr_at(source, (double)index);
if (!item) return false;
scr_dyn_arr_push(out, item);
return true;
}
static bool dyn_arr_reversed_copy(ScrDyn *out, const ScrDyn *source) {
for (size_t i = source->v.arr.len; i > 0; i--)
if (!dyn_arr_copy_item(out, source, i - 1, false)) return false;
return true;
}
/* Names each prototype declares BEYOND what's implemented here — these
* throw the "not supported yet" Error instead of mis-answering "is not a
* function". */
static bool dyn_arr_proto_unimpl(int m) {
return m == DYN_M_toString || m == DYN_M_toLocaleString;
}
static bool dyn_arr_proto_mutates(int m) {
switch (m) {
case DYN_M_push: case DYN_M_pop: case DYN_M_shift: case DYN_M_unshift: case DYN_M_reverse:
case DYN_M_sort: case DYN_M_fill: case DYN_M_copyWithin: case DYN_M_splice:
return true;
default:
return false;
}
}
static bool dyn_bytes_proto_real(int m) {
switch (m) {
case DYN_M_slice: case DYN_M_at: case DYN_M_indexOf: case DYN_M_lastIndexOf: case DYN_M_includes:
case DYN_M_join: case DYN_M_forEach: case DYN_M_map: case DYN_M_filter: case DYN_M_some:
case DYN_M_every: case DYN_M_find: case DYN_M_findIndex: case DYN_M_reverse: case DYN_M_fill:
case DYN_M_set: case DYN_M_subarray: case DYN_M_sort: case DYN_M_keys: case DYN_M_values:
case DYN_M_entries: case DYN_M_reduce: case DYN_M_reduceRight: case DYN_M_copyWithin:
case DYN_M_toString: case DYN_M_toLocaleString: case DYN_M_findLast: case DYN_M_findLastIndex:
case DYN_M_toReversed: case DYN_M_toSorted: case DYN_M_with:
return true;
default:
return false;
}
}
static ScrDyn *scr_dyn_invoke_impl(
ScrDyn *recv, const char *method, int mid, ScrDyn *const *args, size_t argc,
const char *what, ScrDyn *callback_recv);
/* Live native arrays: the constant-time methods that loops call once per
* element. Each mirrors the checked-array dispatch below (which consults
* neither own properties nor the prototype for these builtins) but touches
* one native element instead of materializing and recommitting the whole
* array. Anything that could observe more — a hole, argument coercion, a
* multi-value push, a value the element type rejects — sets *handled false
* and takes the snapshot path. */
static ScrDyn *dyn_live_array_method(ScrDyn *recv, int mid, ScrDyn *const *args,
size_t argc, const char *what, bool *handled) {
const ScrDynTypedArrayOps *ops = scr_dyn_typed_ref_array_ops(recv);
if (!ops) return NULL;
ScrArr *arr = recv->v.typed_ref.ptr;
size_t len = arr->len;
/* A frozen array's view refuses the write. */
if (scr_arr_frozen(arr) && ((mid == DYN_M_push) || (mid == DYN_M_pop))) return NULL;
if ((mid == DYN_M_push) && argc == 1 && ops->set) {
if (!ops->set(arr, (double)len, args[0])) return NULL;
*handled = true;
return scr_exc_pending() ? NULL : scr_dyn_new_num((double)arr->len);
}
if ((mid == DYN_M_pop)) {
if (len == 0) {
*handled = true;
return scr_dyn_retain(scr_dyn_undefined());
}
ScrDyn *last = scr_dyn_typed_ref_element(recv, len - 1);
if (!last) return NULL;
scr_arr_set_len(arr, (double)(len - 1));
*handled = true;
return last;
}
if ((mid == DYN_M_at) && (argc == 0 || args[0]->kind == SCR_DYN_NUM || args[0]->kind == SCR_DYN_UNDEF)) {
double index = dyn_index_arg(args, argc, 0, 0, what);
if (index < 0) index += (double)len;
if (index < 0 || index >= (double)len) {
*handled = true;
return scr_dyn_retain(scr_dyn_undefined());
}
ScrDyn *value = scr_dyn_typed_ref_element(recv, (size_t)index);
if (value) *handled = true;
return value;
}
return NULL;
}
ScrDyn *scr_dyn_prepare_method(ScrDyn *recv, const char *method) {
if (recv->kind == SCR_DYN_UNDEF || recv->kind == SCR_DYN_NULL)
return scr_dyn_invoke(recv, method, NULL, 0, method);
if (recv->kind == SCR_DYN_OBJ)
return scr_dyn_obj_read(recv, method, strlen(method));
if (recv->kind == SCR_DYN_ARR && (recv->v.arr.properties || recv->prototype))
return scr_dyn_arr_custom_member(recv, method, strlen(method));
if (recv->kind == SCR_DYN_PROXY) {
ScrStr *key = scr_str_new(method, strlen(method));
ScrDyn *result = scr_dyn_proxy_get(recv, key);
scr_str_release(key);
return result;
}
return NULL;
}
ScrDyn *scr_dyn_invoke_prepared(ScrDyn *recv, ScrDyn *callee, const char *method,
ScrDyn *const *args, size_t argc, const char *what) {
if (!callee) return scr_dyn_invoke(recv, method, args, argc, what);
scr_dyn_this_push_dyn(recv);
ScrDyn *result = scr_dyn_call(callee, args, argc, what);
scr_dyn_this_pop();
return result;
}
ScrDyn *scr_dyn_invoke(ScrDyn *recv, const char *method,
ScrDyn *const *args, size_t argc,
const char *what) {
return scr_dyn_invoke_impl(recv, method, dyn_method_of(method), args, argc, what, NULL);
}
/* A dense array without holes, named properties or a prototype of its
* own, whose length and elements are writable: its common methods below
* run without the dispatch ladder, which answers every other receiver and
* method as before. */
static bool dyn_arr_plain(const ScrDyn *recv) {
return recv->kind == SCR_DYN_ARR && !recv->v.arr.presence && !recv->v.arr.properties &&
!recv->prototype && !recv->null_proto && !recv->non_extensible &&
!recv->v.arr.sealed && !recv->v.arr.frozen && !scr_dyn_is_array_prototype(recv);
}
/* scr_dyn_strict_eq, with the number and string cases inline: an element
* search compares every element. */
static inline bool dyn_elem_strict_eq(const ScrDyn *a, const ScrDyn *b) {
if (a->kind == SCR_DYN_STR && b->kind == SCR_DYN_STR) {
const ScrStr *x = a->v.str, *y = b->v.str;
return x == y || (x->len == y->len && memcmp(x->data, y->data, x->len) == 0);
}
if (a->kind == SCR_DYN_NUM && b->kind == SCR_DYN_NUM) return a->v.num == b->v.num;
return scr_dyn_strict_eq(a, b);
}
/* push, pop, shift, at, includes and indexOf of a plain array, with the
* ladder's results. An argument that would run user code (a non-number
* index, a fromIndex) leaves the call to the ladder. True when answered. */
static bool dyn_arr_fast(ScrDyn *recv, int mid, ScrDyn *const *args, size_t argc,
ScrDyn **result) {
size_t len = recv->v.arr.len;
switch (mid) {
case DYN_M_push:
for (size_t i = 0; i < argc; i++) scr_dyn_arr_push(recv, scr_dyn_retain(args[i]));
*result = scr_dyn_new_num((double)recv->v.arr.len);
return true;
case DYN_M_pop:
if (len == 0) {
*result = scr_dyn_retain(scr_dyn_undefined());
return true;
}
recv->v.arr.len = len - 1;
*result = recv->v.arr.items[len - 1]; /* ownership moves out */
return true;
case DYN_M_shift:
if (len == 0) {
*result = scr_dyn_retain(scr_dyn_undefined());
return true;
}
*result = recv->v.arr.items[0]; /* ownership moves out */
memmove(recv->v.arr.items, recv->v.arr.items + 1, (len - 1) * sizeof(ScrDyn *));
recv->v.arr.len = len - 1;
return true;
case DYN_M_at: {
if (argc == 0 || args[0]->kind != SCR_DYN_NUM) return false;
double index = args[0]->v.num;
index = isnan(index) ? 0 : trunc(index);
if (index < 0) index += (double)len;
*result = index < 0 || index >= (double)len ? scr_dyn_retain(scr_dyn_undefined())
: scr_dyn_retain(recv->v.arr.items[(size_t)index]);
return true;
}
case DYN_M_includes:
case DYN_M_indexOf: {
if (argc > 1) return false;
ScrDyn *needle = argc > 0 ? args[0] : scr_dyn_undefined();
bool nan = mid == DYN_M_includes && needle->kind == SCR_DYN_NUM && needle->v.num != needle->v.num;
for (size_t i = 0; i < len; i++) {
const ScrDyn *item = recv->v.arr.items[i];
if (nan ? item->kind == SCR_DYN_NUM && item->v.num != item->v.num
: dyn_elem_strict_eq(item, needle)) {
*result = mid == DYN_M_includes ? scr_dyn_new_bool(true) : scr_dyn_new_num((double)i);
return true;
}
}
*result = mid == DYN_M_includes ? scr_dyn_new_bool(false) : scr_dyn_new_num(-1);
return true;
}
default:
return false;
}
}
/* The call site's method id. The slot is the site's, shared by every
* thread: each writes the same id. */
static int32_t dyn_site_method(const char *method, int32_t *mid_slot) {
int32_t mid = __atomic_load_n(mid_slot, __ATOMIC_RELAXED);
if (mid < 0) {
mid = dyn_method_of(method);
__atomic_store_n(mid_slot, mid, __ATOMIC_RELAXED);
}
return mid;
}
ScrDyn *scr_dyn_invoke_prepared_id(ScrDyn *recv, ScrDyn *callee, const char *method,
int32_t *mid_slot, ScrDyn *const *args, size_t argc,
const char *what) {
if (callee) return scr_dyn_invoke_prepared(recv, callee, method, args, argc, what);
int32_t mid = dyn_site_method(method, mid_slot);
ScrDyn *result;
if (dyn_arr_plain(recv) && dyn_arr_fast(recv, mid, args, argc, &result)) return result;
return scr_dyn_invoke_impl(recv, method, mid, args, argc, what, NULL);
}
void scr_dyn_invoke_discard_id(ScrDyn *recv, ScrDyn *callee, const char *method,
int32_t *mid_slot, ScrDyn *const *args, size_t argc,
const char *what) {
/* A plain array's push whose new length nobody reads. */
if (!callee && dyn_arr_plain(recv) && dyn_site_method(method, mid_slot) == DYN_M_push) {
for (size_t i = 0; i < argc; i++) scr_dyn_arr_push(recv, scr_dyn_retain(args[i]));
return;
}
scr_dyn_release(scr_dyn_invoke_prepared_id(recv, callee, method, mid_slot, args, argc, what));
}
/* The callback methods of a dense dyn array that take a thisArg:
* forEach, map, filter, some, every, find, findIndex, findLast,
* findLastIndex and flatMap. The caller binds `this` for the callbacks.
* `len` is the length before the first callback. */
static ScrDyn *dyn_arr_callback_method(ScrDyn *recv, int mid, ScrDyn *const *args, size_t argc,
size_t len, ScrDyn *callback_recv) {
if ((mid == DYN_M_findLast) || (mid == DYN_M_findLastIndex)) {
if (!dyn_cb_check(args, argc)) return NULL;
ScrDyn *visible_recv = callback_recv ? callback_recv : recv;
for (size_t i = len; i > 0; i--) {
size_t index = i - 1;
ScrDyn *item = index < recv->v.arr.len
? scr_dyn_retain(recv->v.arr.items[index])
: scr_dyn_retain(scr_dyn_undefined());
ScrDyn *result = dyn_call_cb(args[0], item, index, visible_recv);
if (!result) { scr_dyn_release(item); return NULL; }
bool matched = scr_dyn_truthy(result);
scr_dyn_release(result);
if (matched) {
if ((mid == DYN_M_findLast)) return item;
scr_dyn_release(item);
return scr_dyn_new_num((double)index);
}
scr_dyn_release(item);
}
return (mid == DYN_M_findLast)
? scr_dyn_retain(scr_dyn_undefined()) : scr_dyn_new_num(-1);
}
if ((mid == DYN_M_forEach) || (mid == DYN_M_map) ||
(mid == DYN_M_filter) || (mid == DYN_M_some) ||
(mid == DYN_M_every) || (mid == DYN_M_find) ||
(mid == DYN_M_findIndex)) {
if (!dyn_cb_check(args, argc)) return NULL;
ScrDyn *cb = args[0];
ScrDyn *out = ((mid == DYN_M_map) || (mid == DYN_M_filter)) ? scr_dyn_new_arr() : NULL;
/* Array iteration methods snapshot length before the first callback.
* A shrinking callback can remove a later dense entry; an expanding
* callback must not make the new tail observable to this pass. Live
* Web-boundary capsules are the externally visible receiver, so pass
* that capsule as callback arg 3: mutations through the callback's
* array argument then commit directly to the original static array. */
size_t n = len;
ScrDyn *visible_recv = callback_recv ? callback_recv : recv;
for (size_t step = 0; step < n; step++) {
size_t i = step;
if (i >= recv->v.arr.len) continue;
ScrDyn *item = scr_dyn_retain(recv->v.arr.items[i]);
ScrDyn *r = dyn_call_cb(cb, item, i, visible_recv);
if (!r) { scr_dyn_release(item); scr_dyn_release(out); return NULL; }
if ((mid == DYN_M_map)) {
scr_dyn_arr_push(out, r); /* ownership moves in */
r = NULL;
} else {
bool truthy = scr_dyn_truthy(r);
scr_dyn_release(r);
if ((mid == DYN_M_filter) && truthy) scr_dyn_arr_push(out, scr_dyn_retain(item));
if ((mid == DYN_M_some) && truthy) { scr_dyn_release(item); return scr_dyn_new_bool(true); }
if ((mid == DYN_M_every) && !truthy) { scr_dyn_release(item); return scr_dyn_new_bool(false); }
if ((mid == DYN_M_find) && truthy) return item; /* +1 moves out */
if ((mid == DYN_M_findIndex) && truthy) { scr_dyn_release(item); return scr_dyn_new_num((double)i); }
}
scr_dyn_release(item);
}
if (out) return out;
if ((mid == DYN_M_some)) return scr_dyn_new_bool(false);
if ((mid == DYN_M_every)) return scr_dyn_new_bool(true);
if ((mid == DYN_M_find)) return scr_dyn_retain(scr_dyn_undefined());
if ((mid == DYN_M_findIndex)) return scr_dyn_new_num(-1);
return scr_dyn_retain(scr_dyn_undefined()); /* forEach */
}
if ((mid == DYN_M_flatMap)) {
/* JS Array.prototype.flatMap over a dyn array: map + a depth-1
* flatten. Native dyn-array results flatten element-by-element; a
* WRAPPED engine array flattens through the routed keyed reads
* (elements wrap back scalar-normalized); everything else pushes
* as a single element (JS keeps non-array callback results whole).
* JS's spec snapshots the length up front (elements appended by
* the callback are not visited). */
if (!dyn_cb_check(args, argc)) return NULL;
ScrDyn *cb = args[0];
ScrDyn *out = scr_dyn_new_arr();
size_t n = recv->v.arr.len;
for (size_t i = 0; i < n && i < recv->v.arr.len; i++) {
if (!dyn_borrow_present(recv, i)) { if (scr_exc_pending()) { scr_dyn_release(out); return NULL; } continue; }
ScrDyn *item = dyn_borrow_item(recv, i);
if (!item) { scr_dyn_release(out); return NULL; }
ScrDyn *r = dyn_call_cb(
cb, item, i, callback_recv ? callback_recv : recv);
scr_dyn_release(item);
if (!r) { scr_dyn_release(out); return NULL; }
if (r->kind == SCR_DYN_TYPED_REF && scr_dyn_ref_is_array(r)) {
ScrDyn *view = scr_dyn_typed_ref_materialize(r);
scr_dyn_release(r);
r = view;
if (!r) { scr_dyn_release(out); return NULL; }
}
if (r->kind == SCR_DYN_ARR) {
for (size_t j = 0; j < r->v.arr.len; j++) {
if (dyn_borrow_present(r, j)) scr_dyn_arr_push(out, dyn_borrow_item(r, j));
if (scr_exc_pending()) { scr_dyn_release(r); scr_dyn_release(out); return NULL; }
}
scr_dyn_release(r);
} else {
scr_dyn_arr_push(out, r); /* ownership moves in */
}
}
return out;
}
return NULL;
}
static ScrDyn *scr_dyn_invoke_impl(
ScrDyn *recv, const char *method, int mid, ScrDyn *const *args, size_t argc,
const char *what, ScrDyn *callback_recv) {
if (recv->kind == SCR_DYN_UNDEF || recv->kind == SCR_DYN_NULL) {
ScrJsonBuf b;
scr_jb_init(&b);
scr_jb_puts(&b, "Cannot read properties of ");
scr_jb_puts(&b, recv->kind == SCR_DYN_UNDEF ? "undefined" : "null");
scr_jb_puts(&b, " (reading '");
scr_jb_puts(&b, method);
scr_jb_puts(&b, "')");
scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&b));
return NULL;
}
/* Native handles dispatch through the tag's ops onto the same entry
* points the static lowerings use (scr_http/scr_net register them at
* main()); the ladder inside throws the "not supported yet" Error for
* real-but-unimplemented members. */
if (recv->kind == SCR_DYN_HANDLE) {
return scr_dynh_dispatch(recv, method, args, argc, what);
}
if (scr_dyn_is_array_prototype(recv) && dyn_arr_proto_mutates(mid)) {
scr_dyn_array_prototype_element_fence(recv);
return NULL;
}
if (recv->kind == SCR_DYN_PROXY) {
ScrStr *key = scr_str_new(method, strlen(method));
ScrDyn *callable = scr_dyn_proxy_get(recv, key);
scr_str_release(key);
if (!callable) return NULL;
scr_dyn_this_push_dyn(recv);
ScrDyn *result = scr_dyn_call(callable, args, argc, what);
scr_dyn_this_pop();
scr_dyn_release(callable);
return result;
}
/* Live Web-boundary capsules expose the prototype of their materialized
* value. Run the ordinary dispatch against the stable snapshot, then
* commit successful mutations back into the original static value.
* Mutators such as reverse/sort return their receiver, so translate that
* snapshot identity back to the externally visible capsule. */
if (recv->kind == SCR_DYN_TYPED_REF) {
bool handled = false;
ScrDyn *fast = dyn_live_array_method(recv, mid, args, argc, what, &handled);
if (handled) return fast;
/* Iterator helpers on a compiled generator. */
if (scr_dyn_generator_helper_hook &&
scr_dyn_generator_helper_hook(recv, method, args, argc, &fast))
return fast;
if (scr_event_ref_hooks &&
scr_event_ref_hooks->invoke(recv, method, args, argc, what, &fast))
return fast;
/* An inherited Date method of an instance of a class declared `extends
* Date`. */
ScrDyn *date_method = scr_dyn_date_class_member(recv, method, strlen(method));
if (date_method) {
scr_dyn_this_push_dyn(recv);
ScrDyn *result = scr_dyn_call(date_method, args, argc, what);
scr_dyn_this_pop();
scr_dyn_release(date_method);
return result;
}
/* A method of a bag-tracked class instance that its view holds as an
* ordinary data property, own or inherited: the call the view's
* dispatch below makes, without building the view. */
size_t method_len = strlen(method);
ScrDyn *inherited = scr_dyn_typed_ref_bag_method(recv, method, method_len);
if (scr_exc_pending()) return NULL;
if (inherited && scr_dyn_is_callable(inherited)) {
scr_dyn_this_push_dyn(recv);
ScrDyn *result = scr_dyn_call(inherited, args, argc, what);
scr_dyn_this_pop();
scr_dyn_release(inherited);
return result;
}
scr_dyn_release(inherited);
ScrDyn *materialized = scr_dyn_typed_ref_materialize(recv);
if (scr_exc_pending()) {
scr_dyn_release(materialized);
return NULL;
}
bool mutates = materialized->kind == SCR_DYN_ARR &&
dyn_arr_proto_mutates(mid);
ScrDyn *result = scr_dyn_invoke_impl(
materialized, method, mid, args, argc, what, recv);
if (mutates && !scr_exc_pending()) scr_dyn_typed_ref_commit(recv);
if (result == materialized) {
scr_dyn_release(result);
result = scr_dyn_retain(recv);
}
scr_dyn_release(materialized);
return result;
}
/* OBJ method calls follow the native prototype chain while binding the
* original receiver as `this`. */
if (recv->kind == SCR_DYN_OBJ || recv->kind == SCR_DYN_PROXY) {
ScrDyn *m = scr_dyn_obj_read(recv, method, strlen(method));
if (m && (scr_dyn_is_callable(m))) {
/* JS binds the receiver for the call (`obj.method()` — this === obj):
* the ambient-receiver window (scr_runtime.h). */
scr_dyn_this_push_dyn(callback_recv ? callback_recv : recv);
ScrDyn *r = scr_dyn_call(m, args, argc, what);
scr_dyn_this_pop();
scr_dyn_release(m);
return r;
}
scr_dyn_release(m);
dyn_throw_not_fn(what);
return NULL;
}
if (recv->kind == SCR_DYN_FUNC) {
if ((mid == DYN_M_apply)) {
return scr_dyn_apply_array_like(recv, argc ? args[0] : scr_dyn_undefined(),
argc > 1 ? args[1] : scr_dyn_undefined(), what);
}
if ((mid == DYN_M_call)) {
scr_dyn_this_push_dyn(argc >= 1 ? args[0] : scr_dyn_undefined());
ScrDyn *r = scr_dyn_call(recv, argc > 1 ? args + 1 : NULL, argc > 1 ? argc - 1 : 0, what);
scr_dyn_this_pop();
return r;
}
if ((mid == DYN_M_bind)) return scr_dyn_bind(recv, args, argc);
if ((mid == DYN_M_toString)) {
ScrDyn *member = scr_dyn_fn_get(recv, method, strlen(method));
if (member && member->kind != SCR_DYN_UNDEF) {
scr_dyn_this_push_dyn(recv);
ScrDyn *result = scr_dyn_call(member, args, argc, what);
scr_dyn_this_pop();
scr_dyn_release(member);
return result;
}
scr_dyn_release(member);
dyn_throw_unsupported("Function", method);
return NULL;
}
/* An OWN property on the FUNC box (defineProperties writes — the
* mustCall-wrapper expando family): a callable member runs with the
* box bound (JS's o.m() receiver), everything else keeps Node's
* is-not-a-function. */
{
ScrDyn *own = scr_dyn_fn_get(recv, method, strlen(method));
if (own) {
if (own->kind == SCR_DYN_FUNC) {
scr_dyn_this_push_dyn(recv);
ScrDyn *r = scr_dyn_call(own, args, argc, what);
scr_dyn_this_pop();
scr_dyn_release(own);
return r;
}
scr_dyn_release(own);
}
}
dyn_throw_not_fn(what);
return NULL;
}
if ((recv->kind == SCR_DYN_SYMBOL || recv->kind == SCR_DYN_BIGINT || recv->kind == SCR_DYN_NUM || recv->kind == SCR_DYN_BOOL || recv->kind == SCR_DYN_STR) &&
(mid == DYN_M_valueOf)) return scr_dyn_retain(recv);
if (recv->kind == SCR_DYN_NUM && (mid == DYN_M_toFixed)) {
double digits = dyn_index_arg(args, argc, 0, 0, what);
if (scr_exc_pending()) return NULL;
ScrStr *text = scr_num_to_fixed(recv->v.num, digits);
if (scr_exc_pending()) { scr_str_release(text); return NULL; }
ScrDyn *result = scr_dyn_new_str(text);
scr_str_release(text);
return result;
}
if (recv->kind == SCR_DYN_NUM && (mid == DYN_M_toExponential)) {
if (argc && args[0]->kind != SCR_DYN_UNDEF) {
dyn_throw_unsupported("Number", method);
return NULL;
}
ScrStr *text = scr_num_to_exponential(recv->v.num);
ScrDyn *result = scr_dyn_new_str(text);
scr_str_release(text);
return result;
}
if (recv->kind == SCR_DYN_STR) {
ScrStr *s = recv->v.str;
if ((mid == DYN_M_slice)) {
double start = dyn_index_arg(args, argc, 0, 0, what);
if (scr_exc_pending()) return NULL;
double end = dyn_index_arg(args, argc, 1, scr_str_utf16_len(s), what);
if (scr_exc_pending()) return NULL;
ScrStr *piece = scr_str_slice(s, start, end);
ScrDyn *r = scr_dyn_new_str(piece); /* retains */
scr_str_release(piece);
return r;
}
if (mid == DYN_M_at) {
/* String.prototype.at: one UTF-16 code unit, counted from the end
* for a negative index; undefined outside the string. */
double index = dyn_index_arg(args, argc, 0, 0, what);
if (scr_exc_pending()) return NULL;
double len = (double)scr_str_utf16_len(s);
double k = index < 0 ? len + index : index;
if (k < 0 || k >= len) return scr_dyn_retain(scr_dyn_undefined());
ScrStr *unit = scr_str_slice(s, k, k + 1);
ScrDyn *r = scr_dyn_new_str(unit); /* retains */
scr_str_release(unit);
return r;
}
if ((mid == DYN_M_concat) ||
(mid == DYN_M_indexOf) || (mid == DYN_M_lastIndexOf) ||
(mid == DYN_M_includes)) {
if (((mid == DYN_M_indexOf) || (mid == DYN_M_lastIndexOf) ||
(mid == DYN_M_includes)) &&
argc >= 1 && args[0]->kind == SCR_DYN_STR) {
double position = dyn_index_arg(args, argc, 1, (mid == DYN_M_lastIndexOf) ? INFINITY : 0, what);
if (scr_exc_pending()) return NULL;
if ((mid == DYN_M_includes)) return scr_dyn_new_bool(scr_str_index_of(s, args[0]->v.str, position) >= 0);
if ((mid == DYN_M_indexOf)) return scr_dyn_new_num(scr_str_index_of(s, args[0]->v.str, position));
return scr_dyn_new_num(scr_str_last_index_of_from(s, args[0]->v.str, position));
}
dyn_throw_unsupported("String", method);
return NULL;
}
dyn_throw_not_fn(what);
return NULL;
}
if (recv->kind == SCR_DYN_ARR) {
if ((mid == DYN_M_keys) || (mid == DYN_M_values) || (mid == DYN_M_entries))
return scr_dyn_native_handle_iterator(callback_recv ? callback_recv : recv,
(mid == DYN_M_keys) ? 1 : (mid == DYN_M_entries) ? 2 : 0);
size_t len = recv->v.arr.len;
if (recv->v.arr.presence) {
bool sparse = false;
for (size_t i = 0; i < len; i++) if (!recv->v.arr.presence[i]) { sparse = true; break; }
if (!sparse) { free(recv->v.arr.presence); recv->v.arr.presence = NULL; }
else if ((mid == DYN_M_forEach) || (mid == DYN_M_map) || (mid == DYN_M_filter) ||
(mid == DYN_M_some) || (mid == DYN_M_every) || (mid == DYN_M_find) ||
(mid == DYN_M_findIndex) || (mid == DYN_M_findLast) || (mid == DYN_M_findLastIndex) ||
(mid == DYN_M_reduce) || (mid == DYN_M_reduceRight) || (mid == DYN_M_slice) ||
(mid == DYN_M_indexOf) || (mid == DYN_M_lastIndexOf) || (mid == DYN_M_includes)) {
ScrDyn *pack = scr_dyn_new_arr();
for (size_t i = 0; i < argc; i++) scr_dyn_arr_push(pack, scr_dyn_retain(args[i]));
ScrStr *name = scr_str_new(method, strlen(method));
ScrDyn *result = scr_dyn_array_proto_call(callback_recv ? callback_recv : recv, name, pack);
scr_str_release(name);
scr_dyn_release(pack);
return result;
} else if (dyn_arr_proto_mutates(mid) && !(mid == DYN_M_fill) && !(mid == DYN_M_push) && !(mid == DYN_M_pop) && !(mid == DYN_M_shift)) {
static const char message[] = "This checked array mutation over holes is not supported yet";
scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020");
return NULL;
}
}
size_t splice_start = 0;
size_t splice_removed = 0;
if (dyn_arr_proto_mutates(mid)) {
bool grows = (mid == DYN_M_push) && argc > 0;
grows = grows || ((mid == DYN_M_unshift) && argc > 0);
bool shrinks = ((mid == DYN_M_pop) || (mid == DYN_M_shift)) && len > 0;
if ((mid == DYN_M_splice)) {
double startD = dyn_index_arg(args, argc, 0, 0, what);
if (scr_exc_pending()) return NULL;
splice_start = dyn_rel_index(startD, len);
splice_removed = len - splice_start;
if (argc > 1) {
double countD = dyn_index_arg(args, argc, 1, 0, what);
if (scr_exc_pending()) return NULL;
splice_removed = countD <= 0 ? 0 : countD >= (double)splice_removed ? splice_removed : (size_t)countD;
}
if (recv->v.arr.len != len) {
static const char message[] = "Array.prototype.splice after an argument changes the receiver length is not supported yet";
scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020");
return NULL;
}
size_t inserted = argc > 2 ? argc - 2 : 0;
if (inserted > splice_removed && inserted - splice_removed > 1000000) {
static const char message[] = "Array.prototype.splice exceeds the native array growth limit";
scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020");
return NULL;
}
grows = grows || inserted > splice_removed;
shrinks = shrinks || splice_removed > inserted;
if (splice_removed > inserted && recv->v.arr.sealed && !recv->v.arr.frozen) {
for (size_t i = splice_start; i < len - splice_removed + inserted; i++) {
size_t source = i < splice_start + inserted ? i : i + splice_removed - inserted;
ScrDyn *replacement = i < splice_start + inserted ? args[i - splice_start + 2] : recv->v.arr.items[source];
if (i < splice_start + inserted) scr_dyn_region_store(recv, replacement);
ScrDyn *old = recv->v.arr.items[i];
recv->v.arr.items[i] = scr_dyn_retain(replacement);
scr_dyn_release(old);
}
static const char message[] = "Cannot delete non-configurable property";
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
return NULL;
}
}
/* A frozen array throws at the operation's first write, as in Node. */
int frozen_op = !recv->v.arr.frozen ? -1
: (mid == DYN_M_push) ? 1
: (mid == DYN_M_pop) ? 2
: (mid == DYN_M_shift) ? 6
: (mid == DYN_M_unshift) ? 4
: (mid == DYN_M_splice) ? 10
: ((mid == DYN_M_sort) || (mid == DYN_M_reverse)) ? 7
: -1;
double items = (mid == DYN_M_splice) ? (argc > 2 ? (double)(argc - 2) : 0) : (double)argc;
if (frozen_op >= 0 &&
scr_array_frozen_error(frozen_op, len, (double)splice_start, frozen_op == 10 ? (double)splice_removed : items, items))
return NULL;
if ((mid == DYN_M_push) && grows && recv->non_extensible) {
scr_array_not_extensible_error((double)len);
return NULL;
}
if ((mid == DYN_M_pop) && shrinks && recv->v.arr.sealed) {
scr_array_undeletable_error((double)(len - 1));
return NULL;
}
if ((grows && recv->non_extensible) || (shrinks && recv->v.arr.sealed && !(mid == DYN_M_shift))) {
static const char message[] = "Cannot modify a sealed or frozen array";
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
return NULL;
}
}
if ((mid == DYN_M_push)) {
for (size_t i = 0; i < argc; i++) scr_dyn_arr_push(recv, scr_dyn_retain(args[i]));
return scr_dyn_new_num((double)recv->v.arr.len);
}
if ((mid == DYN_M_pop)) {
if (len == 0) return scr_dyn_retain(scr_dyn_undefined());
ScrDyn *result = scr_dyn_arr_at(recv, (double)(len - 1));
if (!result) return NULL;
scr_dyn_release(recv->v.arr.items[--recv->v.arr.len]);
return result;
}
if ((mid == DYN_M_shift)) {
if (len == 0) return scr_dyn_retain(scr_dyn_undefined());
if (recv->v.arr.sealed) {
if (recv->v.arr.frozen) {
static const char message[] = "Cannot assign to read only property";
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
return NULL;
}
for (size_t i = 1; i < len; i++) {
scr_dyn_release(recv->v.arr.items[i - 1]);
recv->v.arr.items[i - 1] = scr_dyn_retain(recv->v.arr.items[i]);
}
scr_array_undeletable_error((double)(len - 1));
return NULL;
}
ScrDyn *first = recv->v.arr.items[0];
memmove(recv->v.arr.items, recv->v.arr.items + 1, (len - 1) * sizeof(ScrDyn *));
if (recv->v.arr.presence) memmove(recv->v.arr.presence, recv->v.arr.presence + 1, len - 1);
recv->v.arr.len = len - 1;
return first; /* ownership moves out */
}
if ((mid == DYN_M_unshift)) {
/* Grow the backing store before shifting the old elements. The
* appended placeholders are immortal, so overwriting them does not
* abandon retained argument references. */
for (size_t i = 0; i < argc; i++) scr_dyn_arr_push(recv, scr_dyn_retain(scr_dyn_undefined()));
memmove(recv->v.arr.items + argc, recv->v.arr.items, len * sizeof(ScrDyn *));
for (size_t i = 0; i < argc; i++) {
scr_dyn_region_store(recv, args[i]);
recv->v.arr.items[i] = scr_dyn_retain(args[i]);
}
return scr_dyn_new_num((double)recv->v.arr.len);
}
if ((mid == DYN_M_splice)) {
size_t start = splice_start;
size_t removed = splice_removed;
size_t inserted = argc > 2 ? argc - 2 : 0;
ScrDyn *out = scr_dyn_new_arr();
for (size_t i = start; i < start + removed; i++)
scr_dyn_arr_push(out, scr_dyn_retain(recv->v.arr.items[i]));
size_t next_len = len - removed + inserted;
if (next_len > recv->v.arr.cap) {
size_t capacity = recv->v.arr.cap ? recv->v.arr.cap : 4;
while (capacity < next_len) {
if (capacity > SIZE_MAX / 2) scr_trap("scriptc: array capacity overflow\n");
capacity *= 2;
}
ScrDyn **items = scr_mem_realloc(recv->v.arr.items, capacity * sizeof *items);
if (!items) scr_trap("scriptc: out of memory\n");
recv->v.arr.items = items;
recv->v.arr.cap = capacity;
}
for (size_t i = start; i < start + removed; i++) scr_dyn_release(recv->v.arr.items[i]);
memmove(recv->v.arr.items + start + inserted,
recv->v.arr.items + start + removed,
(len - start - removed) * sizeof(ScrDyn *));
for (size_t i = 0; i < inserted; i++) {
scr_dyn_region_store(recv, args[i + 2]);
recv->v.arr.items[start + i] = scr_dyn_retain(args[i + 2]);
}
recv->v.arr.len = next_len;
return out;
}
if ((mid == DYN_M_slice)) {
double startD = dyn_index_arg(args, argc, 0, 0, what);
if (scr_exc_pending()) return NULL;
double endD = dyn_index_arg(args, argc, 1, (double)len, what);
if (scr_exc_pending()) return NULL;
if (!dyn_arr_length_unchanged(recv, len)) return NULL;
size_t start = dyn_rel_index(startD, len);
size_t end = dyn_rel_index(endD, len);
ScrDyn *out = scr_dyn_new_arr();
for (size_t i = start; i < end; i++) scr_dyn_arr_push(out, scr_dyn_retain(recv->v.arr.items[i]));
return out;
}
if ((mid == DYN_M_at)) {
double iD = dyn_index_arg(args, argc, 0, 0, what);
if (scr_exc_pending()) return NULL;
if (!dyn_arr_length_unchanged(recv, len)) return NULL;
double idx = iD < 0 ? (double)len + iD : iD;
if (idx < 0 || idx >= (double)len) return scr_dyn_retain(scr_dyn_undefined());
return scr_dyn_arr_at(recv, idx);
}
if ((mid == DYN_M_indexOf) || (mid == DYN_M_lastIndexOf) ||
(mid == DYN_M_includes)) {
if (len == 0) return (mid == DYN_M_includes) ? scr_dyn_new_bool(false) : scr_dyn_new_num(-1);
ScrDyn *needle = argc > 0 ? args[0] : scr_dyn_undefined();
double from = (mid == DYN_M_lastIndexOf) ? (double)len - 1 : 0;
if (argc > 1) {
from = dyn_index_arg(args, argc, 1, 0, what);
if (scr_exc_pending()) return NULL;
}
if (!dyn_arr_length_unchanged(recv, len)) return NULL;
if ((mid == DYN_M_lastIndexOf)) {
double first = from < 0 ? (double)len + from : from;
if (first >= (double)len) first = (double)len - 1;
for (size_t i = first < 0 ? 0 : (size_t)first + 1; i > 0; i--) {
if (scr_dyn_strict_eq(recv->v.arr.items[i - 1], needle)) return scr_dyn_new_num((double)(i - 1));
}
return scr_dyn_new_num(-1);
}
double first = from < 0 ? (double)len + from : from;
if (first < 0) first = 0;
for (size_t i = first >= (double)len ? len : (size_t)first; i < len; i++) {
const ScrDyn *item = recv->v.arr.items[i];
bool nan_match = (mid == DYN_M_includes) &&
item->kind == SCR_DYN_NUM && needle->kind == SCR_DYN_NUM &&
item->v.num != item->v.num && needle->v.num != needle->v.num;
if (nan_match || scr_dyn_strict_eq(item, needle)) {
return (mid == DYN_M_includes) ? scr_dyn_new_bool(true) : scr_dyn_new_num((double)i);
}
}
return (mid == DYN_M_includes) ? scr_dyn_new_bool(false) : scr_dyn_new_num(-1);
}
if ((mid == DYN_M_forEach) || (mid == DYN_M_map) || (mid == DYN_M_filter) ||
(mid == DYN_M_some) || (mid == DYN_M_every) || (mid == DYN_M_find) ||
(mid == DYN_M_findIndex) || (mid == DYN_M_findLast) || (mid == DYN_M_findLastIndex) ||
(mid == DYN_M_flatMap)) {
/* Every callback runs with `this` bound to the thisArg, or undefined
* when there is none. */
scr_dyn_this_push_dyn(argc > 1 ? args[1] : scr_dyn_undefined());
ScrDyn *result = dyn_arr_callback_method(recv, mid, args, argc, len, callback_recv);
scr_dyn_this_pop();
return result;
}
if ((mid == DYN_M_join)) {
ScrJsonBuf b;
scr_jb_init(&b);
ScrStr *separator = argc > 0 && args[0]->kind != SCR_DYN_UNDEF
? scr_dyn_string_coerce_js(args[0]) : scr_str_new(",", 1);
if (!separator) return NULL; /* nothing written yet */
for (size_t i = 0; i < len; i++) {
if (i > 0) scr_jb_write(&b, separator->data, separator->len);
ScrDyn *e = scr_dyn_arr_at(recv, (double)i);
if (e) {
if (e->kind != SCR_DYN_UNDEF && e->kind != SCR_DYN_NULL) scr_dyn_display_buf(&b, e, true);
scr_dyn_release(e);
}
if (!e || scr_exc_pending()) {
/* The buffer's bytes live in a string allocation: discard it as one. */
scr_str_release(scr_jb_finish(&b));
scr_str_release(separator);
return NULL;
}
}
scr_str_release(separator);
ScrStr *joined = scr_jb_finish(&b);
ScrDyn *r = scr_dyn_new_str(joined); /* retains */
scr_str_release(joined);
return r;
}
if ((mid == DYN_M_concat)) {
ScrDyn *out = scr_dyn_new_arr();
for (size_t i = 0; i < len; i++) {
if (!dyn_arr_copy_item(out, recv, i, true)) { scr_dyn_release(out); return NULL; }
}
for (size_t a = 0; a < argc; a++) {
ScrDyn *view = args[a]->kind == SCR_DYN_TYPED_REF && scr_dyn_ref_is_array(args[a])
? scr_dyn_typed_ref_materialize(args[a]) : NULL;
if (scr_exc_pending()) { scr_dyn_release(view); scr_dyn_release(out); return NULL; }
const ScrDyn *source = view ? view : args[a];
if (source->kind == SCR_DYN_ARR) {
for (size_t i = 0; i < source->v.arr.len; i++) {
if (!dyn_arr_copy_item(out, source, i, true)) { scr_dyn_release(view); scr_dyn_release(out); return NULL; }
}
} else {
scr_dyn_arr_push(out, scr_dyn_retain(args[a]));
}
scr_dyn_release(view);
}
return out;
}
if ((mid == DYN_M_flat)) {
double depth = dyn_index_arg(args, argc, 0, 1, what);
if (scr_exc_pending()) return NULL;
if (!dyn_arr_length_unchanged(recv, len)) return NULL;
ScrDyn *out = scr_dyn_new_arr();
size_t visits = 0;
if (!dyn_arr_flatten(out, recv, depth < 0 ? 0 : depth, &visits)) {
scr_dyn_release(out);
return NULL;
}
return out;
}
if ((mid == DYN_M_reverse)) {
for (size_t i = 0; i < len / 2; i++) {
ScrDyn *tmp = recv->v.arr.items[i];
recv->v.arr.items[i] = recv->v.arr.items[len - 1 - i];
recv->v.arr.items[len - 1 - i] = tmp;
}
return scr_dyn_retain(recv);
}
if ((mid == DYN_M_toReversed)) {
ScrDyn *out = scr_dyn_new_arr();
if (!dyn_arr_reversed_copy(out, recv)) { scr_dyn_release(out); return NULL; }
return out;
}
if ((mid == DYN_M_with)) {
double indexD = dyn_index_arg(args, argc, 0, 0, what);
if (scr_exc_pending()) return NULL;
if (!dyn_arr_length_unchanged(recv, len)) return NULL;
double index = indexD < 0 ? (double)len + indexD : indexD;
if (index < 0 || index >= (double)len) {
static const char message[] = "Invalid index";
scr_throw_error_msg(SCR_ERR_RANGE, message, sizeof message - 1);
return NULL;
}
ScrDyn *replacement = argc > 1 ? args[1] : scr_dyn_undefined();
ScrDyn *out = scr_dyn_new_arr();
for (size_t i = 0; i < len; i++) {
if ((size_t)index == i) scr_dyn_arr_push(out, scr_dyn_retain(replacement));
else if (!dyn_arr_copy_item(out, recv, i, false)) { scr_dyn_release(out); return NULL; }
}
return out;
}
if ((mid == DYN_M_toSpliced)) {
double startD = dyn_index_arg(args, argc, 0, 0, what);
if (scr_exc_pending()) return NULL;
size_t start = dyn_rel_index(startD, len);
size_t delete_count = argc == 0 ? 0 : len - start;
if (argc > 1) {
double countD = dyn_index_arg(args, argc, 1, 0, what);
if (scr_exc_pending()) return NULL;
delete_count = countD <= 0 ? 0 : countD >= (double)delete_count ? delete_count : (size_t)countD;
}
if (!dyn_arr_length_unchanged(recv, len)) return NULL;
ScrDyn *out = scr_dyn_new_arr();
for (size_t i = 0; i < start; i++) if (!dyn_arr_copy_item(out, recv, i, false)) { scr_dyn_release(out); return NULL; }
for (size_t i = 2; i < argc; i++) scr_dyn_arr_push(out, scr_dyn_retain(args[i]));
for (size_t i = start + delete_count; i < len; i++) if (!dyn_arr_copy_item(out, recv, i, false)) { scr_dyn_release(out); return NULL; }
return out;
}
if ((mid == DYN_M_fill)) {
double startD = dyn_index_arg(args, argc, 1, 0, what);
if (scr_exc_pending()) return NULL;
double endD = dyn_index_arg(args, argc, 2, (double)len, what);
if (scr_exc_pending()) return NULL;
if (!dyn_arr_length_unchanged(recv, len)) return NULL;
size_t start = dyn_rel_index(startD, len);
size_t end = dyn_rel_index(endD, len);
if (recv->v.arr.frozen && scr_array_frozen_error(8, len, (double)start, (double)end, 0)) return NULL;
ScrDyn *fill = argc ? args[0] : scr_dyn_undefined();
if (start < end) scr_dyn_region_store(recv, fill);
for (size_t i = start; i < end; i++) {
if (recv->non_extensible && !scr_dyn_arr_has_index(recv, i)) {
static const char message[] = "Cannot add property, object is not extensible";
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
return NULL;
}
ScrDyn *old = recv->v.arr.items[i];
recv->v.arr.items[i] = scr_dyn_retain(fill);
if (recv->v.arr.presence) recv->v.arr.presence[i] = 1;
scr_dyn_release(old);
}
return scr_dyn_retain(recv);
}
if ((mid == DYN_M_copyWithin)) {
double targetD = dyn_index_arg(args, argc, 0, 0, what);
if (scr_exc_pending()) return NULL;
double startD = dyn_index_arg(args, argc, 1, 0, what);
if (scr_exc_pending()) return NULL;
double endD = dyn_index_arg(args, argc, 2, (double)len, what);
if (scr_exc_pending()) return NULL;
if (!dyn_arr_length_unchanged(recv, len)) return NULL;
size_t target = dyn_rel_index(targetD, len);
size_t start = dyn_rel_index(startD, len);
size_t end = dyn_rel_index(endD, len);
size_t count = end > start ? end - start : 0;
if (count > len - target) count = len - target;
if (recv->v.arr.frozen && scr_array_frozen_error(9, len, (double)target, (double)start, (double)end))
return NULL;
if (start < target && target < start + count) {
for (size_t i = count; i > 0; i--) {
ScrDyn *old = recv->v.arr.items[target + i - 1];
recv->v.arr.items[target + i - 1] = scr_dyn_retain(recv->v.arr.items[start + i - 1]);
scr_dyn_release(old);
}
} else {
for (size_t i = 0; i < count; i++) {
ScrDyn *old = recv->v.arr.items[target + i];
recv->v.arr.items[target + i] = scr_dyn_retain(recv->v.arr.items[start + i]);
scr_dyn_release(old);
}
}
return scr_dyn_retain(recv);
}
if ((mid == DYN_M_reduce) || (mid == DYN_M_reduceRight)) {
if (!dyn_cb_check(args, argc)) return NULL;
bool backward = (mid == DYN_M_reduceRight);
ScrDyn *acc = argc > 1 ? scr_dyn_retain(args[1]) : NULL;
ScrDyn *visible_recv = callback_recv ? callback_recv : recv;
for (size_t step = 0; step < len; step++) {
size_t i = backward ? len - 1 - step : step;
if (i >= recv->v.arr.len) continue;
ScrDyn *item = scr_dyn_retain(recv->v.arr.items[i]);
if (!acc) { acc = item; continue; }
ScrDyn *index = scr_dyn_new_num((double)i);
ScrDyn *call_args[] = { acc, item, index, visible_recv };
ScrDyn *next = scr_dyn_call(args[0], call_args, 4, "callback");
scr_dyn_release(index);
scr_dyn_release(item);
scr_dyn_release(acc);
if (!next) return NULL;
acc = next;
}
if (acc) return acc;
static const char message[] = "Reduce of empty array with no initial value";
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
return NULL;
}
if ((mid == DYN_M_sort)) {
ScrDyn *cmp = argc > 0 ? args[0] : scr_dyn_undefined();
if (cmp->kind != SCR_DYN_UNDEF && cmp->kind != SCR_DYN_FUNC) {
/* V8 appends the received value's string image. */
ScrJsonBuf b;
scr_jb_init(&b);
scr_jb_puts(&b, "The comparison function must be either a function or undefined: ");
scr_dyn_display_buf(&b, cmp, false);
scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&b));
return NULL;
}
if (len > 1 && !dyn_arr_sort(recv, cmp->kind == SCR_DYN_FUNC ? cmp : NULL)) return NULL;
return scr_dyn_retain(recv);
}
if ((mid == DYN_M_toSorted)) {
ScrDyn *cmp = argc > 0 ? args[0] : scr_dyn_undefined();
if (cmp->kind != SCR_DYN_UNDEF && cmp->kind != SCR_DYN_FUNC) {
static const char message[] = "The comparison function must be either a function or undefined";
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
return NULL;
}
ScrDyn *out = scr_dyn_new_arr();
for (size_t i = 0; i < len; i++) if (!dyn_arr_copy_item(out, recv, i, false)) { scr_dyn_release(out); return NULL; }
if (len > 1 && !dyn_arr_sort(out, cmp->kind == SCR_DYN_FUNC ? cmp : NULL)) {
scr_dyn_release(out);
return NULL;
}
return out;
}
if (dyn_arr_proto_unimpl(mid)) {
dyn_throw_unsupported("Array", method);
return NULL;
}
dyn_throw_not_fn(what);
return NULL;
}
/* PROMISE receivers: the then/catch/finally reactions ride the fiber
* machinery (scr_dyn_promise_then — microtask-exact ordering); every
* other Promise.prototype name is `then`-adjacent sugar JS doesn't
* have, so the not-a-function answer IS the JS answer. */
if (recv->kind == SCR_DYN_PROMISE) {
#ifdef SCR_LIB
scr_trap("scriptc: promise dispatch is unavailable in library mode\n");
#else
if ((mid == DYN_M_then)) {
return scr_dyn_promise_then(recv->v.promise, argc >= 1 ? args[0] : NULL,
argc >= 2 ? args[1] : NULL, NULL);
}
if ((mid == DYN_M_catch)) {
return scr_dyn_promise_then(recv->v.promise, NULL, argc >= 1 ? args[0] : NULL, NULL);
}
if ((mid == DYN_M_finally)) {
return scr_dyn_promise_then(recv->v.promise, NULL, NULL, argc >= 1 ? args[0] : NULL);
}
dyn_throw_not_fn(what);
return NULL;
#endif
}
if (recv->kind == SCR_DYN_BYTES) {
ScrBytes *bytes = recv->v.bytes;
/* An own property of the view shadows the prototype's method. */
ScrDyn *member = bytes->properties ? scr_bytes_named_get(recv, method, strlen(method)) : NULL;
if (!member && scr_exc_pending()) return NULL;
if (member) {
ScrDyn *result = NULL;
if (!scr_dyn_is_callable(member)) dyn_throw_not_fn(what);
else {
scr_dyn_this_push_dyn(recv);
result = scr_dyn_call(member, args, argc, what);
scr_dyn_this_pop();
}
scr_dyn_release(member);
return result;
}
if (bytes->is_data_view) {
static const char *const kinds[] = { "Uint8", "Int8", "Uint16", "Int16", "Uint32", "Int32", "Float32", "Float64", "BigUint64", "BigInt64", "Float16" };
const bool get = !strncmp(method, "get", 3);
const bool set = !strncmp(method, "set", 3);
for (size_t kind = 0; (get || set) && kind < sizeof kinds / sizeof kinds[0]; kind++) {
if (strcmp(method + 3, kinds[kind])) continue;
double offset = 0;
if (argc && !scr_dyn_number_coerce_js(args[0], &offset)) return NULL;
/* ToIndex precedes value coercion; the view bounds check follows
* it. Keep these stages distinct for observable valueOf calls. */
offset = isnan(offset) ? 0 : trunc(offset);
if (!(offset >= 0) || offset > 9007199254740991.0) {
static const char message[] = "Offset is outside the bounds of the DataView";
scr_throw_error_msg(SCR_ERR_RANGE, message, sizeof message - 1);
return NULL;
}
const bool le = argc > (get ? 1 : 2) && scr_dyn_truthy(args[get ? 1 : 2]);
if (get) {
if (kind == SCR_DV_BIGU64 || kind == SCR_DV_BIGI64) {
ScrBigInt *value = scr_bigint_dataview_get(bytes, offset, kind == SCR_DV_BIGI64, le);
if (scr_exc_pending()) return NULL;
ScrDyn *result = scr_dyn_new_bigint(value);
scr_bigint_release(value);
return result;
}
double value = scr_dataview_get(bytes, offset, (ScrDataViewGet)kind, le);
return scr_exc_pending() ? NULL : scr_dyn_new_num(value);
}
if (kind == SCR_DV_BIGU64 || kind == SCR_DV_BIGI64) {
ScrBigInt *value = scr_dyn_bigint_coerce(argc > 1 ? args[1] : scr_dyn_undefined());
if (scr_exc_pending()) return NULL;
scr_bigint_dataview_set(bytes, offset, value, le);
scr_bigint_release(value);
} else {
double value;
if (!scr_dyn_number_coerce_js(argc > 1 ? args[1] : scr_dyn_undefined(), &value)) return NULL;
scr_dataview_set(bytes, offset, value, (ScrDataViewGet)kind, le);
}
return scr_exc_pending() ? NULL : scr_dyn_retain(scr_dyn_undefined());
}
/* The typed array methods below are not DataView's; it inherits
* Object.prototype's. */
if ((mid == DYN_M_toString) || (mid == DYN_M_toLocaleString)) {
ScrStr *tag = scr_str_new("[object DataView]", 17);
ScrDyn *out = scr_dyn_new_str(tag);
scr_str_release(tag);
return out;
}
if ((mid == DYN_M_valueOf)) return scr_dyn_retain(recv);
dyn_throw_not_fn(what);
return NULL;
}
bool handled;
ScrDyn *answer = scr_dyn_bytes_method(recv, method, args, argc, &handled);
if (handled) return answer;
if (dyn_bytes_proto_real(mid)) {
dyn_throw_unsupported(scr_bytes_elem_name(bytes->elem), method);
return NULL;
}
}
/* NUM/BOOL/BYTES-remainder: the name is no method of this kind — JS's
* own answer. */
dyn_throw_not_fn(what);
return NULL;
}
static ScrDyn *dyn_array_method_value_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
ScrStr *method = scr_box_get_ref(closure->caps[0]);
ScrDyn *receiver = scr_dyn_this_get();
ScrDyn *result;
/* An array, or a live view of a native array, runs the method its own
* dispatch has; other receivers take the generic array-like algorithms. */
if (receiver->kind == SCR_DYN_ARR ||
(receiver->kind == SCR_DYN_TYPED_REF && scr_dyn_typed_ref_array_ops(receiver))) {
result = scr_dyn_invoke(receiver, method->data, args, argc, method->data);
} else {
ScrDyn *pack = scr_dyn_new_arr();
for (size_t i = 0; i < argc; i++) scr_dyn_arr_push(pack, scr_dyn_retain(args[i]));
result = scr_dyn_array_proto_call(receiver, method, pack);
scr_dyn_release(pack);
}
scr_dyn_release(receiver);
scr_str_release(method);
return result;
}
static SCR_TL ScrDyn *dyn_builtin_methods;
static void dyn_builtin_methods_cleanup(void) {
scr_dyn_release(dyn_builtin_methods);
dyn_builtin_methods = NULL;
}
static ScrDyn *dyn_builtin_method_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
ScrStr *prototype = scr_box_get_ref(closure->caps[0]);
ScrStr *method = scr_box_get_ref(closure->caps[1]);
ScrDyn *receiver = scr_dyn_this_get();
ScrDyn *result = NULL;
if (!strcmp(prototype->data, "Object")) {
ScrDyn *key = argc ? args[0] : scr_dyn_undefined();
if (!strcmp(method->data, "toString")) {
ScrStr *tag = scr_dyn_object_tag_of(receiver);
if (tag) { result = scr_dyn_new_str(tag); scr_str_release(tag); }
} else if (!strcmp(method->data, "hasOwnProperty")) {
bool own = scr_dyn_has_own_computed(receiver, key);
if (!scr_exc_pending()) result = scr_dyn_new_bool(own);
} else if (!strcmp(method->data, "propertyIsEnumerable")) {
bool enumerable = scr_dyn_property_is_enumerable_computed(receiver, key);
if (!scr_exc_pending()) result = scr_dyn_new_bool(enumerable);
} else if (receiver->kind == SCR_DYN_NULL || receiver->kind == SCR_DYN_UNDEF) {
static const char message[] = "Cannot convert undefined or null to object";
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
} else if (receiver->kind == SCR_DYN_OBJ || receiver->kind == SCR_DYN_ARR ||
receiver->kind == SCR_DYN_FUNC || receiver->kind == SCR_DYN_HANDLE ||
receiver->kind == SCR_DYN_TYPED_REF || receiver->kind == SCR_DYN_PROXY ||
receiver->kind == SCR_DYN_PROMISE || receiver->kind == SCR_DYN_BYTES) result = scr_dyn_retain(receiver);
else dyn_throw_unsupported("Object", method->data);
} else {
bool compatible = false;
if (!strcmp(prototype->data, "String")) compatible = receiver->kind != SCR_DYN_UNDEF && receiver->kind != SCR_DYN_NULL;
else if (!strcmp(prototype->data, "Number")) compatible = receiver->kind == SCR_DYN_NUM;
else if (receiver->kind == SCR_DYN_HANDLE) {
const ScrDynHandleOps *ops = scr_dyn_handle_ops_of(receiver);
compatible = !strcmp(ops->cls, prototype->data);
}
if (!compatible) {
static const char message[] = "Builtin prototype method called on incompatible receiver";
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
} else if (!strcmp(method->data, "toString") && receiver->kind == SCR_DYN_NUM) {
ScrStr *value = scr_dyn_to_string_argument(receiver, argc ? args[0] : scr_dyn_undefined(), method);
if (value) { result = scr_dyn_new_str(value); scr_str_release(value); }
} else if (!strcmp(prototype->data, "String")) {
bool strict = !strcmp(method->data, "toString") || !strcmp(method->data, "valueOf");
if (strict && receiver->kind != SCR_DYN_STR) {
static const char message[] = "String.prototype method requires a string receiver";
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
} else if (strict) result = scr_dyn_retain(receiver);
else {
ScrStr *text = scr_dyn_string_coerce_js(receiver);
if (text) {
ScrDyn *string = scr_dyn_new_str(text);
scr_str_release(text);
if (!strcmp(method->data, "slice")) result = scr_dyn_invoke(string, method->data, args, argc, method->data);
else dyn_throw_unsupported("String", method->data);
scr_dyn_release(string);
}
}
} else result = scr_dyn_invoke(receiver, method->data, args, argc, method->data);
}
scr_dyn_release(receiver);
scr_str_release(prototype);
scr_str_release(method);
return result;
}
ScrDyn *scr_dyn_builtin_method(const ScrStr *prototype, const ScrStr *method) {
if (!dyn_builtin_methods) {
dyn_builtin_methods = scr_dyn_new_obj_null_proto();
scr_atexit(dyn_builtin_methods_cleanup);
}
ScrJsonBuf buffer;
scr_jb_init(&buffer);
scr_jb_puts(&buffer, prototype->data);
scr_jb_putc(&buffer, '.');
scr_jb_puts(&buffer, method->data);
ScrStr *key = scr_jb_finish(&buffer);
ScrDyn *value = scr_dyn_obj_get(dyn_builtin_methods, key->data, key->len);
if (!value) {
ScrClosure *closure = scr_closure_new(NULL, 2);
closure->caps[0] = scr_box_new(SCR_BOX_STR);
closure->caps[1] = scr_box_new(SCR_BOX_STR);
scr_box_set_ref(closure->caps[0], scr_str_retain((ScrStr *)prototype));
scr_box_set_ref(closure->caps[1], scr_str_retain((ScrStr *)method));
size_t arity = (!strcmp(method->data, "set") || !strcmp(method->data, "substring") ||
!strcmp(method->data, "slice") ||
!strcmp(method->data, "replace") || !strcmp(method->data, "split")) ? 2 :
(!strcmp(method->data, "get") || !strcmp(method->data, "add") || !strcmp(method->data, "has") ||
!strcmp(method->data, "delete") || !strcmp(method->data, "forEach") || !strcmp(method->data, "startsWith") ||
!strcmp(method->data, "endsWith") || !strcmp(method->data, "padStart") || !strcmp(method->data, "toJSON") ||
!strcmp(method->data, "charCodeAt") || !strcmp(method->data, "hasOwnProperty") ||
!strcmp(method->data, "propertyIsEnumerable") || (!strcmp(prototype->data, "Number") && !strcmp(method->data, "toString"))) ? 1 : 0;
value = scr_dyn_new_func(closure, dyn_builtin_method_call, arity, "native:prototype", method->data);
scr_dyn_obj_set(dyn_builtin_methods, key->data, key->len, value);
}
scr_str_release(key);
return scr_dyn_retain(value);
}
/* ── Builtin constructors, inherited Object.prototype members and builtin
* functions as dynamic values ──────────────────────────────────────────
* This unit links only into programs that use them (the frontend marks
* dyn.globalConstructor, dyn.builtinFunction and constant-key reads of the
* inherited names), so programs that never read them keep their exact
* executables.
*
* JavaScript reads `Object`, `Date`, `RegExp`, `Map`, `Set`, `Promise`,
* `WeakMap` and `WeakSet` as interned functions, and the same functions
* answer `.constructor` on the matching dynamic values, so
* `o.constructor === Object` and `a.constructor === b.constructor` are
* exact identity tests. `Object(v)` works through the stored value; calling
* the others through it is fenced. */
/* String constants live in named arrays in their own section rather than
* in the unit's merged string pool (the explicit section also keeps the
* compiler from folding the unit's existing literals into them), so
* programs that link this unit for other reasons keep their exact bytes. */
#if defined(__APPLE__)
#define SCR_DYN_COMPAT_STR __attribute__((section("__TEXT,__scr_dyn_compat")))
#elif defined(_WIN32)
#define SCR_DYN_COMPAT_STR
#else
#define SCR_DYN_COMPAT_STR __attribute__((section(".rodata.scr_dyn_compat")))
#endif
static const char dyn_s_error_tag[] SCR_DYN_COMPAT_STR = "[object Error]";
static const char dyn_s0[] SCR_DYN_COMPAT_STR = "calling the %s constructor%s through a stored value is not supported yet";
static const char dyn_s1[] SCR_DYN_COMPAT_STR = "calling '%s' through a stored value is not supported yet";
static const char dyn_s3[] SCR_DYN_COMPAT_STR = "scriptc: invalid global constructor name\n";
static const char dyn_s4[] SCR_DYN_COMPAT_STR = "propertyIsEnumerable";
static const char dyn_s5[] SCR_DYN_COMPAT_STR = "ERR_INVALID_ARG_TYPE";
static const char dyn_s6[] SCR_DYN_COMPAT_STR = "native:constructor";
static const char dyn_s7[] SCR_DYN_COMPAT_STR = "process.nextTick";
static const char dyn_s8[] SCR_DYN_COMPAT_STR = " on a primitive";
static const char dyn_s9[] SCR_DYN_COMPAT_STR = "hasOwnProperty";
static const char dyn_s10[] SCR_DYN_COMPAT_STR = "native:builtin";
static const char dyn_s11[] SCR_DYN_COMPAT_STR = "queueMicrotask";
static const char dyn_s12[] SCR_DYN_COMPAT_STR = "clearImmediate";
static const char dyn_s13[] SCR_DYN_COMPAT_STR = "setImmediate";
static const char dyn_s14[] SCR_DYN_COMPAT_STR = "constructor";
static const char dyn_s15[] SCR_DYN_COMPAT_STR = "setInterval";
static const char dyn_s16[] SCR_DYN_COMPAT_STR = "setTimeout";
static const char dyn_s17[] SCR_DYN_COMPAT_STR = "callback";
static const char dyn_s18[] SCR_DYN_COMPAT_STR = "toString";
static const char dyn_s20[] SCR_DYN_COMPAT_STR = "Function";
static const char dyn_s21[] SCR_DYN_COMPAT_STR = "Promise";
static const char dyn_s22[] SCR_DYN_COMPAT_STR = "WeakMap";
static const char dyn_s23[] SCR_DYN_COMPAT_STR = "valueOf";
static const char dyn_s24[] SCR_DYN_COMPAT_STR = "WeakSet";
static const char dyn_s25[] SCR_DYN_COMPAT_STR = "RegExp";
static const char dyn_s26[] SCR_DYN_COMPAT_STR = "Object";
static const char dyn_s27[] SCR_DYN_COMPAT_STR = "SC2020";
static const char dyn_s28[] SCR_DYN_COMPAT_STR = "clear";
static const char dyn_s29[] SCR_DYN_COMPAT_STR = "Date";
static const char dyn_s30[] SCR_DYN_COMPAT_STR = "Map";
static const char dyn_s31[] SCR_DYN_COMPAT_STR = "Set";
static const char dyn_s32[] SCR_DYN_COMPAT_STR = "";
static const char dyn_s33[] SCR_DYN_COMPAT_STR = "constructing %s%s through a stored value is not supported yet";
static const char dyn_s34[] SCR_DYN_COMPAT_STR = " from a primitive";
static const char dyn_s35[] SCR_DYN_COMPAT_STR = "FinalizationRegistry";
static const char dyn_s36[] SCR_DYN_COMPAT_STR = "atob";
static const char dyn_s37[] SCR_DYN_COMPAT_STR = "btoa";
static const char dyn_s38[] SCR_DYN_COMPAT_STR = "structuredClone";
static const char dyn_s39[] SCR_DYN_COMPAT_STR = "The \"input\" argument must be specified";
static const char dyn_s40[] SCR_DYN_COMPAT_STR = "The \"The value argument must be specified\" argument must be specified";
static const char dyn_s41[] SCR_DYN_COMPAT_STR = "ERR_MISSING_ARGS";
static const char *const dyn_global_ctor_names[] = {
dyn_s26, dyn_s29, dyn_s25, dyn_s30, dyn_s31, dyn_s21, dyn_s22, dyn_s24, dyn_s35,
};
#define DYN_GLOBAL_CTORS (sizeof dyn_global_ctor_names / sizeof dyn_global_ctor_names[0])
static SCR_TL ScrDyn *dyn_global_ctors[DYN_GLOBAL_CTORS];
static SCR_TL bool dyn_global_ctors_registered;
static void dyn_global_ctors_cleanup(void) {
for (size_t i = 0; i < DYN_GLOBAL_CTORS; i++) {
scr_dyn_release(dyn_global_ctors[i]);
dyn_global_ctors[i] = NULL;
}
}
static ScrDyn *dyn_global_ctor_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
const char *name = closure->fn;
bool object = strcmp(name, dyn_s26) == 0;
if (object) {
const ScrDyn *value = argc ? args[0] : scr_dyn_undefined();
if (value->kind == SCR_DYN_UNDEF || value->kind == SCR_DYN_NULL) return scr_dyn_new_obj();
if (value->kind != SCR_DYN_NUM && value->kind != SCR_DYN_STR && value->kind != SCR_DYN_BOOL &&
value->kind != SCR_DYN_BIGINT && value->kind != SCR_DYN_SYMBOL) {
return scr_dyn_retain((ScrDyn *)value);
}
}
char message[128];
int length = snprintf(message, sizeof message, dyn_s0,
name, object ? dyn_s8 : dyn_s32);
scr_throw_error_msg_code(SCR_ERR_ERROR, message, (size_t)length, dyn_s27);
return NULL;
}
/* `new C(...args)` through a stored Object, Date, Map, Set, WeakMap or
* WeakSet builds what the `new` expression builds. RegExp, Promise, and
* Object over a primitive (a wrapper object) are fenced. */
static bool dyn_global_construct(const ScrDyn *callee, const ScrDyn *args, ScrDyn **result) {
if (callee->kind != SCR_DYN_FUNC || callee->v.fn.thunk != dyn_global_ctor_call) return false;
const char *name = callee->v.fn.clo->fn;
ScrDyn *const *items = args->v.arr.items;
size_t argc = args->v.arr.len;
const ScrDyn *first = argc ? items[0] : scr_dyn_undefined();
bool primitive = first->kind == SCR_DYN_NUM || first->kind == SCR_DYN_STR || first->kind == SCR_DYN_BOOL ||
first->kind == SCR_DYN_BIGINT || first->kind == SCR_DYN_SYMBOL;
if (strcmp(name, dyn_s26) == 0 && !primitive) *result = dyn_global_ctor_call(callee->v.fn.clo, items, argc);
else if (strcmp(name, dyn_s29) == 0)
*result = argc == 0 ? scr_dyn_native_date_new(args)
: argc == 1 ? scr_dyn_native_date_new_value(args)
: scr_dyn_native_date_new_fields(args);
else if (strcmp(name, dyn_s30) == 0) *result = scr_dyn_native_map_new(first);
else if (strcmp(name, dyn_s31) == 0) *result = scr_dyn_native_set_new(first);
else if (strcmp(name, dyn_s22) == 0) *result = scr_weak_map_new((ScrDyn *)first);
else if (strcmp(name, dyn_s24) == 0) *result = scr_weak_set_new((ScrDyn *)first);
else {
char message[128];
int length = snprintf(message, sizeof message, dyn_s33, name, strcmp(name, dyn_s26) == 0 ? dyn_s34 : dyn_s32);
scr_throw_error_msg_code(SCR_ERR_ERROR, message, (size_t)length, dyn_s27);
*result = NULL;
}
return true;
}
static ScrDyn *dyn_global_ctor(const char *name) {
for (size_t i = 0; i < DYN_GLOBAL_CTORS; i++) {
if (dyn_global_ctor_names[i][0] != name[0] || strcmp(dyn_global_ctor_names[i], name) != 0) continue;
if (!dyn_global_ctors_registered) {
dyn_global_ctors_registered = true;
scr_atexit(dyn_global_ctors_cleanup);
scr_dyn_global_construct_fn = dyn_global_construct;
}
if (!dyn_global_ctors[i]) {
/* Each constructor's own length: Object 1, Date 7, RegExp 2, Promise 1,
* the collections 0, FinalizationRegistry 1. */
uint32_t length = i == 0 || i == 5 || i == 8 ? 1 : i == 1 ? 7 : i == 2 ? 2 : 0;
dyn_global_ctors[i] = scr_dyn_new_func(scr_closure_new((void *)dyn_global_ctor_names[i], 0),
dyn_global_ctor_call, length, dyn_s6,
dyn_global_ctor_names[i]);
/* `o.constructor.prototype` of a plain object is Object.prototype
* (zod's isPlainObject). The other prototypes are not modeled. */
if (i == 0) scr_dyn_fn_seed_prototype(dyn_global_ctors[i], scr_dyn_object_prototype());
}
return scr_dyn_retain(dyn_global_ctors[i]);
}
return NULL;
}
static ScrDyn *dyn_function_probe_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
(void)closure; (void)args; (void)argc;
return scr_dyn_retain(scr_dyn_undefined());
}
/* The Function constructor: the value any ordinary function answers for
* `.constructor` (scr_json.c keeps its singletons). */
static ScrDyn *dyn_function_constructor(void) {
ScrDyn *probe = scr_dyn_new_func(scr_closure_new(NULL, 0), dyn_function_probe_call, 0, dyn_s10, dyn_s20);
ScrDyn *ctor = scr_dyn_fn_get(probe, dyn_s14, 11);
scr_dyn_release(probe);
return ctor;
}
ScrDyn *scr_dyn_global_constructor(const ScrStr *name) {
if (strcmp(name->data, dyn_s20) == 0) return dyn_function_constructor();
ScrDyn *error = scr_dyn_error_constructor(name->data);
if (error) return error;
ScrDyn *ctor = dyn_global_ctor(name->data);
if (!ctor) scr_trap(dyn_s3);
return ctor;
}
/* Object.prototype.toString's tag: errors answer "[object Error]" (the
* shared tag walk in scr_json.c answers "[object Object]" for them). */
ScrStr *scr_dyn_object_tag_of(const ScrDyn *d) {
if (d->kind == SCR_DYN_OBJ && scr_dyn_err_instanceof(d, 0)) return scr_str_new(dyn_s_error_tag, sizeof dyn_s_error_tag - 1);
return scr_dyn_object_tag(d);
}
/* Which Object.prototype members a value inherits when no nearer prototype
* defines them: types with their own toString or valueOf keep theirs. */
enum { DYN_INHERIT_VALUE_OF = 1, DYN_INHERIT_TO_STRING = 2, DYN_INHERIT_OWN = 4, DYN_INHERIT_ALL = 7 };
static ScrDyn *dyn_inherited_from(const char *ctor, int methods, const ScrStr *key) {
if (strcmp(key->data, dyn_s14) == 0) return ctor ? dyn_global_ctor(ctor) : NULL;
bool applies = (strcmp(key->data, dyn_s23) == 0 && (methods & DYN_INHERIT_VALUE_OF)) ||
(strcmp(key->data, dyn_s18) == 0 && (methods & DYN_INHERIT_TO_STRING)) ||
((strcmp(key->data, dyn_s9) == 0 || strcmp(key->data, dyn_s4) == 0) &&
(methods & DYN_INHERIT_OWN));
if (!applies) return NULL;
ScrStr *prototype = scr_str_new(dyn_s26, 6);
ScrDyn *method = scr_dyn_builtin_method(prototype, key);
scr_str_release(prototype);
return method;
}
/* The inherited member `key` of `d` when its own chain does not define it,
* or NULL (the read's undefined stands). */
static ScrDyn *dyn_inherited_member(const ScrDyn *d, const ScrStr *key) {
switch (d->kind) {
case SCR_DYN_TYPED_REF: {
ScrDyn *view = scr_dyn_typed_ref_materialize(d);
ScrDyn *member = view && !scr_exc_pending() ? dyn_inherited_member(view, key) : NULL;
scr_dyn_release(view);
return member;
}
case SCR_DYN_OBJ: {
/* A plain object, or an Object.create chain ending in Object.prototype;
* a member present anywhere on the chain (even as undefined) stands. */
ScrDyn *object_prototype = scr_dyn_object_prototype();
const ScrDyn *tail = d;
bool present = false;
for (const ScrDyn *link = d; link; link = link->prototype) {
tail = link;
if (link == object_prototype) break;
if (link->kind != SCR_DYN_OBJ || scr_dyn_has_own(link, key)) {
present = true;
break;
}
}
scr_dyn_release(object_prototype);
if (present || (tail != object_prototype && tail->null_proto)) return NULL;
/* Errors keep Error.prototype.toString and their constructor values. */
if (scr_dyn_err_instanceof(d, 0)) return dyn_inherited_from(NULL, DYN_INHERIT_VALUE_OF | DYN_INHERIT_OWN, key);
return dyn_inherited_from(dyn_s26, DYN_INHERIT_ALL, key);
}
case SCR_DYN_ARR:
/* Array.prototype defines toString and answers constructor itself. */
if (d->null_proto || d->prototype || scr_dyn_has_own(d, key)) return NULL;
return dyn_inherited_from(NULL, DYN_INHERIT_VALUE_OF | DYN_INHERIT_OWN, key);
case SCR_DYN_FUNC:
/* Function.prototype defines toString and answers constructor itself. */
if (scr_dyn_has_own(d, key)) return NULL;
return dyn_inherited_from(NULL, DYN_INHERIT_VALUE_OF | DYN_INHERIT_OWN, key);
case SCR_DYN_PROMISE:
return dyn_inherited_from(dyn_s21, DYN_INHERIT_ALL, key);
case SCR_DYN_HANDLE:
switch (d->v.handle.tag) {
case SCR_DYNH_DATE: return dyn_inherited_from(dyn_s29, DYN_INHERIT_OWN, key);
case SCR_DYNH_REGEXP: return dyn_inherited_from(dyn_s25, DYN_INHERIT_VALUE_OF | DYN_INHERIT_OWN, key);
case SCR_DYNH_MAP: return dyn_inherited_from(dyn_s30, DYN_INHERIT_ALL, key);
case SCR_DYNH_SET: return dyn_inherited_from(dyn_s31, DYN_INHERIT_ALL, key);
case SCR_DYNH_WEAK_MAP: return dyn_inherited_from(dyn_s22, DYN_INHERIT_ALL, key);
case SCR_DYNH_WEAK_SET: return dyn_inherited_from(dyn_s24, DYN_INHERIT_ALL, key);
default: return NULL;
}
default:
return NULL;
}
}
/* Object.prototype's own constructor and shared methods, read on the
* prototype itself (`const { hasOwnProperty } = Object.prototype`, the
* `proto.constructor` of an isPlainObject check): the member, or NULL when
* `d` is another value and the ordinary read applies. */
ScrDyn *scr_dyn_object_prototype_member(const ScrDyn *d, const ScrStr *key) {
ScrDyn *object_prototype = scr_dyn_object_prototype();
bool prototype = d == object_prototype;
scr_dyn_release(object_prototype);
return prototype ? dyn_inherited_from(dyn_s26, DYN_INHERIT_ALL, key) : NULL;
}
/* A constant-key read of constructor/valueOf/toString/hasOwnProperty/
* propertyIsEnumerable: `read` is the ordinary read's +1 result (NULL when
* it threw). Takes it; answers the inherited member in place of an
* undefined read, the read otherwise. */
ScrDyn *scr_dyn_inherited_read(const ScrDyn *d, const ScrStr *key, ScrDyn *read) {
if (!read || read->kind != SCR_DYN_UNDEF) return read;
ScrDyn *member = dyn_inherited_member(d, key);
if (scr_exc_pending()) {
scr_dyn_release(member);
scr_dyn_release(read);
return NULL;
}
if (!member) return read;
scr_dyn_release(read);
return member;
}
/* Builtin global functions stored in JavaScript (the `var nextTick = typeof
* setImmediate === "function" ? setImmediate : setTimeout` probe): one
* interned function per name, so typeof answers "function" and `===` is
* identity. Calls run the timer and tick APIs with Node's argument
* contract — a function callback (else ERR_INVALID_ARG_TYPE), the delay
* through ToNumber, the remaining arguments passed to the callback — and
* timer functions answer the numeric handles the direct calls answer,
* which the clear functions accept. */
static SCR_TL ScrDyn *dyn_builtin_fns;
static void dyn_builtin_fns_cleanup(void) {
scr_dyn_release(dyn_builtin_fns);
dyn_builtin_fns = NULL;
}
/* Library builds have no event loop (a library that schedules timers is
* refused at SC4005 before emission), so the calls that schedule are left
* out of them. */
#ifndef SCR_LIB
static bool dyn_builtin_fn_callback(ScrDyn *const *args, size_t argc) {
if (argc > 0 && args[0]->kind == SCR_DYN_FUNC) return true;
static const char message[] = "The \"callback\" argument must be of type function.";
scr_throw_error_msg_code(SCR_ERR_TYPE, message, sizeof message - 1, dyn_s5);
return false;
}
static void dyn_builtin_fn_fire(ScrClosure *c) {
ScrDyn *fn = (ScrDyn *)scr_box_get_ref(c->caps[0]);
ScrDyn *args = (ScrDyn *)scr_box_get_ref(c->caps[1]);
ScrDyn *r = scr_dyn_call(fn, args->v.arr.items, args->v.arr.len, dyn_s17);
if (r != NULL) scr_dyn_release(r);
scr_dyn_release(fn);
scr_dyn_release(args);
}
/* The closure that calls args[0] with args[first..] when the loop runs it. */
static ScrClosure *dyn_builtin_fn_deferred(ScrDyn *const *args, size_t argc, size_t first) {
ScrDyn *rest = scr_dyn_new_arr();
for (size_t i = first; i < argc; i++) scr_dyn_arr_push(rest, scr_dyn_retain(args[i]));
ScrClosure *c = scr_closure_new((void *)dyn_builtin_fn_fire, 2);
c->caps[0] = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, NULL);
scr_box_set_ref(c->caps[0], scr_dyn_retain(args[0]));
c->caps[1] = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, NULL);
scr_box_set_ref(c->caps[1], rest); /* moves */
return c;
}
#endif
/* atob, btoa and structuredClone as values: the calls the direct
* spellings make, with Node's error for a missing argument. NULL with the
* exception pending, or the result (+1); `*handled` reports whether `name`
* is one of them. */
static ScrDyn *dyn_builtin_data_fn_call(const ScrStr *name, ScrDyn *const *args, size_t argc, bool *handled) {
bool atob = strcmp(name->data, dyn_s36) == 0, btoa = strcmp(name->data, dyn_s37) == 0;
bool clone = strcmp(name->data, dyn_s38) == 0;
*handled = atob || btoa || clone;
if (!*handled) return NULL;
if (argc == 0) {
const char *message = clone ? dyn_s40 : dyn_s39;
scr_throw_error_msg_code(SCR_ERR_TYPE, message, strlen(message), dyn_s41);
return NULL;
}
if (clone) return scr_structured_clone(args[0], argc > 1 ? args[1] : scr_dyn_undefined());
ScrStr *text = atob ? scr_atob(args[0]) : scr_btoa(args[0]);
if (!text) return NULL;
ScrDyn *result = scr_dyn_new_str(text); /* retains */
scr_str_release(text);
return result;
}
static ScrDyn *dyn_builtin_fn_call(ScrClosure *clo, ScrDyn *const *args, size_t argc) {
{
ScrStr *name = scr_box_get_ref(clo->caps[0]);
bool handled;
ScrDyn *result = dyn_builtin_data_fn_call(name, args, argc, &handled);
scr_str_release(name);
if (handled) return result;
}
#ifdef SCR_LIB
(void)clo;
(void)args;
(void)argc;
static const char message[] = "Builtin timer functions require an executable event loop";
scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC4005");
return NULL;
#else
ScrStr *name = scr_box_get_ref(clo->caps[0]);
ScrDyn *result = NULL;
bool timeout = strcmp(name->data, dyn_s16) == 0;
if (timeout || strcmp(name->data, dyn_s15) == 0) {
double ms = 0;
if (dyn_builtin_fn_callback(args, argc) && (argc < 2 || scr_dyn_number_coerce_js(args[1], &ms))) {
ScrClosure *c = dyn_builtin_fn_deferred(args, argc, 2);
result = scr_dyn_new_num(timeout ? scr_set_timeout_handle(c, ms) : scr_set_interval(c, ms));
}
} else if (strcmp(name->data, dyn_s13) == 0) {
if (dyn_builtin_fn_callback(args, argc))
result = scr_dyn_new_num(scr_set_immediate(dyn_builtin_fn_deferred(args, argc, 1)));
} else if (strcmp(name->data, dyn_s7) == 0) {
if (dyn_builtin_fn_callback(args, argc)) {
scr_next_tick(dyn_builtin_fn_deferred(args, argc, 1));
result = scr_dyn_retain(scr_dyn_undefined());
}
} else if (strcmp(name->data, dyn_s11) == 0) {
if (dyn_builtin_fn_callback(args, argc)) {
scr_queue_microtask_dyn(args[0]);
result = scr_dyn_retain(scr_dyn_undefined());
}
} else if (strncmp(name->data, dyn_s28, 5) == 0) {
/* Node ignores anything that is not a live handle. */
if (argc > 0 && args[0]->kind == SCR_DYN_NUM) {
if (strcmp(name->data, dyn_s12) == 0) scr_clear_immediate(args[0]->v.num);
else scr_clear_interval(args[0]->v.num);
}
result = scr_dyn_retain(scr_dyn_undefined());
} else {
char message[160];
int length = snprintf(message, sizeof message, dyn_s1, name->data);
scr_throw_error_msg_code(SCR_ERR_ERROR, message, (size_t)length, dyn_s27);
}
scr_str_release(name);
return result;
#endif
}
ScrDyn *scr_dyn_builtin_function(const ScrStr *name) {
if (!dyn_builtin_fns) {
dyn_builtin_fns = scr_dyn_new_obj_null_proto();
scr_atexit(dyn_builtin_fns_cleanup);
}
ScrDyn *value = scr_dyn_obj_get(dyn_builtin_fns, name->data, name->len);
if (!value) {
ScrClosure *closure = scr_closure_new(NULL, 1);
closure->caps[0] = scr_box_new(SCR_BOX_STR);
scr_box_set_ref(closure->caps[0], scr_str_retain((ScrStr *)name));
const char *dot = memchr(name->data, '.', name->len);
bool two = strcmp(name->data, dyn_s16) == 0 || strcmp(name->data, dyn_s15) == 0 ||
strcmp(name->data, dyn_s38) == 0;
value = scr_dyn_new_func(closure, dyn_builtin_fn_call, two ? 2 : 1, dyn_s10, dot ? dot + 1 : name->data);
scr_dyn_obj_set(dyn_builtin_fns, name->data, name->len, value); /* the table owns it */
}
return scr_dyn_retain(value);
}
/* An own data property of a namespace object. Attribute bits: 1 writable,
* 2 enumerable, 4 configurable. */
static void dyn_namespace_put(ScrDyn *object, ScrDyn *key, ScrDyn *value, unsigned attributes) {
ScrDyn *descriptor = scr_dyn_new_obj();
scr_dyn_obj_set(descriptor, "value", 5, scr_dyn_retain(value));
scr_dyn_obj_set(descriptor, "writable", 8, scr_dyn_new_bool((attributes & 1) != 0));
scr_dyn_obj_set(descriptor, "enumerable", 10, scr_dyn_new_bool((attributes & 2) != 0));
scr_dyn_obj_set(descriptor, "configurable", 12, scr_dyn_new_bool((attributes & 4) != 0));
scr_dyn_release(scr_dyn_define_property(object, key, descriptor));
scr_dyn_release(descriptor);
}
ScrDyn *scr_dyn_namespace_new(const ScrStr *tag, ScrDyn *tag_key) {
ScrDyn *object = scr_dyn_new_obj();
ScrDyn *value = scr_dyn_new_str((ScrStr *)tag);
dyn_namespace_put(object, tag_key, value, 4);
scr_dyn_release(value);
return object;
}
void scr_dyn_namespace_define(ScrDyn *object, const ScrStr *key, ScrDyn *value, double attributes,
double length) {
if (length >= 0 && value->kind == SCR_DYN_FUNC) value->v.fn.arity = (uint32_t)length;
ScrDyn *name = scr_dyn_new_str((ScrStr *)key);
dyn_namespace_put(object, name, value, (unsigned)attributes);
scr_dyn_release(name);
if (length >= 0) scr_dyn_fence_member(object, key);
}
ScrDyn *scr_dyn_array_prototype(void) {
static SCR_TL bool initialized;
ScrDyn *prototype = scr_dyn_array_prototype_base();
if (initialized) return prototype;
initialized = true;
static const struct { const char *name; size_t arity; } methods[] = {
{"at", 1}, {"concat", 1}, {"copyWithin", 2}, {"fill", 1}, {"find", 1}, {"findIndex", 1},
{"findLast", 1}, {"findLastIndex", 1}, {"lastIndexOf", 1}, {"pop", 0}, {"push", 1},
{"reverse", 0}, {"shift", 0}, {"unshift", 1}, {"slice", 2}, {"sort", 1}, {"splice", 2},
{"includes", 1}, {"indexOf", 1}, {"join", 1}, {"keys", 0}, {"entries", 0}, {"values", 0},
{"forEach", 1}, {"filter", 1}, {"flat", 0}, {"flatMap", 1}, {"map", 1}, {"every", 1},
{"some", 1}, {"reduce", 1}, {"reduceRight", 1}, {"toReversed", 0}, {"toSorted", 1},
{"toSpliced", 2}, {"with", 2}, {"toLocaleString", 0}, {"toString", 0},
};
for (size_t i = 0; i < sizeof methods / sizeof methods[0]; i++) {
ScrStr *name = scr_str_new(methods[i].name, strlen(methods[i].name));
ScrDyn *method;
if (!strcmp(methods[i].name, "values")) method = scr_dyn_array_values_function();
else {
ScrClosure *closure = scr_closure_new(NULL, 1);
closure->caps[0] = scr_box_new(SCR_BOX_STR);
scr_box_set_ref(closure->caps[0], scr_str_retain(name));
method = scr_dyn_new_func(closure, dyn_array_method_value_call, methods[i].arity,
"native:Array.prototype", methods[i].name);
}
ScrDyn *descriptor = scr_dyn_new_obj();
scr_dyn_obj_set(descriptor, "value", 5, method);
scr_dyn_obj_set(descriptor, "writable", 8, scr_dyn_new_bool(true));
scr_dyn_obj_set(descriptor, "configurable", 12, scr_dyn_new_bool(true));
ScrDyn *key = scr_dyn_new_str(name);
ScrDyn *defined = scr_dyn_define_property(prototype, key, descriptor);
scr_dyn_release(defined);
scr_dyn_release(key);
scr_dyn_release(descriptor);
scr_str_release(name);
}
return prototype;
}
/* Object.defineProperties over dyn values (see scr_runtime.h). */
static ScrDyn *scr_dyn_define_props_internal(ScrDyn *target, ScrDyn *descs, ScrDyn *protected_keys) {
/* Island-held operands ARE objects to Node — the non-object TypeError
* below would be a wrong claim. They are fenced with a "not supported
* yet" error instead (lane dyn-routing-ops). */
if (scr_exc_pending()) return NULL;
if (target->kind != SCR_DYN_OBJ && target->kind != SCR_DYN_FUNC && target->kind != SCR_DYN_ARR) {
scr_throw_error_msg(SCR_ERR_TYPE, "Object.defineProperties called on non-object",
strlen("Object.defineProperties called on non-object"));
return NULL;
}
if (descs->kind != SCR_DYN_OBJ) {
scr_throw_error_msg(SCR_ERR_TYPE, "Object.defineProperties called on non-object",
strlen("Object.defineProperties called on non-object"));
return NULL;
}
ScrDyn *keys = scr_dyn_obj_own_keys(descs);
ScrDyn *pending = scr_dyn_new_arr();
static const char *const names[] = {"enumerable", "configurable", "value", "writable", "get", "set"};
static const size_t lengths[] = {10, 12, 5, 8, 3, 3};
for (size_t i = 0; i < keys->v.arr.len; i++) {
ScrDyn *key = keys->v.arr.items[i];
ScrStr *name = key->v.str;
if (!scr_dyn_obj_enumerable(descs, name->data, name->len)) continue;
ScrDyn *descriptor = scr_dyn_obj_read(descs, name->data, name->len);
if (!descriptor) goto fail;
if (descriptor->kind != SCR_DYN_OBJ) {
ScrJsonBuf b;
scr_jb_init(&b);
scr_jb_puts(&b, "Property description must be an object: ");
scr_dyn_display_buf(&b, descriptor, false);
scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&b));
scr_dyn_release(descriptor);
goto fail;
}
ScrDyn *snapshot = scr_dyn_new_obj();
for (size_t field = 0; field < 6; field++) {
if (!scr_dyn_obj_get(descriptor, names[field], lengths[field])) continue;
ScrDyn *value = scr_dyn_obj_read(descriptor, names[field], lengths[field]);
if (!value) {
scr_dyn_release(snapshot);
scr_dyn_release(descriptor);
goto fail;
}
scr_dyn_obj_set(snapshot, names[field], lengths[field], value);
}
scr_dyn_release(descriptor);
ScrDyn *get = scr_dyn_obj_get(snapshot, "get", 3);
ScrDyn *set = scr_dyn_obj_get(snapshot, "set", 3);
if ((get || set) && (scr_dyn_obj_get(snapshot, "value", 5) ||
scr_dyn_obj_get(snapshot, "writable", 8))) {
static const char msg[] = "Invalid property descriptor. Cannot both specify accessors and a value or writable attribute";
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
scr_dyn_release(snapshot);
goto fail;
}
if ((get && get->kind != SCR_DYN_UNDEF && get->kind != SCR_DYN_FUNC) ||
(set && set->kind != SCR_DYN_UNDEF && set->kind != SCR_DYN_FUNC)) {
static const char msg[] = "Getter and setter must be functions";
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
scr_dyn_release(snapshot);
goto fail;
}
ScrDyn *pair = scr_dyn_new_arr();
scr_dyn_arr_push(pair, scr_dyn_retain(key));
scr_dyn_arr_push(pair, snapshot);
scr_dyn_arr_push(pending, pair);
}
scr_dyn_release(keys);
for (size_t i = 0; i < pending->v.arr.len; i++) {
ScrDyn *pair = pending->v.arr.items[i];
ScrDyn *key = pair->v.arr.items[0];
ScrDyn *descriptor = pair->v.arr.items[1];
if (protected_keys) {
for (size_t k = 0; k < protected_keys->v.arr.len; k++) {
if (!scr_dyn_strict_eq(key, protected_keys->v.arr.items[k])) continue;
static const char message[] = "Replacing compiled class prototype members has no native lowering [SC1090]";
scr_throw_error_msg(SCR_ERR_ERROR, message, sizeof message - 1);
scr_dyn_release(pending);
return NULL;
}
}
ScrDyn *defined = scr_dyn_define_property(target, key, descriptor);
if (!defined) {
scr_dyn_release(pending);
return NULL;
}
scr_dyn_release(defined);
}
scr_dyn_release(pending);
return scr_dyn_retain(target);
fail:
scr_dyn_release(keys);
scr_dyn_release(pending);
return NULL;
}
ScrDyn *scr_dyn_define_props(ScrDyn *target, ScrDyn *descs) {
return scr_dyn_define_props_internal(target, descs, NULL);
}
ScrDyn *scr_dyn_define_prototype_props(ScrDyn *target, ScrDyn *descs, ScrDyn *protected_keys) {
return scr_dyn_define_props_internal(target, descs, protected_keys);
}
ScrDyn *scr_dyn_obj_create_with_properties(ScrDyn *prototype, ScrDyn *descriptors) {
ScrDyn *object = scr_dyn_obj_create(prototype);
if (!object) return NULL;
if (descriptors->kind == SCR_DYN_UNDEF) return object;
ScrDyn *defined = scr_dyn_define_props(object, descriptors);
if (!defined) { scr_dyn_release(object); return NULL; }
scr_dyn_release(defined);
return object;
}