mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-31 07:23:06 +08:00
Support two gated dynamic-tier runtime units
The checked-dynamic tier needs two more optional units: Array.prototype's generic algorithms over array-like receivers (scr_dyn_array_like.c) and the Number/Boolean prototype objects as values (scr_dyn_builtin_prototype.c). Both are listed as optional runtime sources under a predicate of their own (all(<feature>, "dynInvoke")), so they link only into a program whose IR uses the matching libCall, and neither is an always-linked unit. Their symbol prefixes are in DYNAMIC_TIER_PREFIXES, so a fully typed executable may not carry them either. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
0faade48ae
commit
e19fa7a6b1
@@ -44,6 +44,8 @@ export function executableLinkFeatures(mod: IrModule, largePages = false): Nativ
|
||||
...(features.utilTiming ? { utilTiming: true } : {}),
|
||||
...(features.dynBuffer ? { dynBuffer: true } : {}),
|
||||
...(features.builtinValues ? { builtinValues: true } : {}),
|
||||
...(features.arrayLikeDispatch ? { arrayLikeDispatch: true } : {}),
|
||||
...(features.builtinPrototypeValue ? { builtinPrototypeValue: true } : {}),
|
||||
...(features.computedRequire ? { computedRequire: true } : {}),
|
||||
// Island code's signal and exit listeners join the events unit's
|
||||
// registries (scope-B NI-3).
|
||||
|
||||
@@ -1146,6 +1146,8 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"dyn.defineProperty": "scr_dyn_define_property",
|
||||
"dyn.getOwnPropertyDescriptor": "scr_dyn_get_own_property_descriptor",
|
||||
"dyn.arrayProtoCall": "scr_dyn_array_proto_call",
|
||||
"dyn.arrayLikeProtoCall": "scr_dyn_array_like_call",
|
||||
"dyn.builtinPrototype": "scr_dyn_builtin_prototype",
|
||||
"dyn.promiseAll": "scr_dyn_promise_all",
|
||||
"dyn.promiseAllSettled": "scr_dyn_promise_all_settled",
|
||||
"dyn.promiseAny": "scr_dyn_promise_any",
|
||||
|
||||
@@ -106,6 +106,13 @@ export interface NativeLinkFeatures {
|
||||
utilPromisify: boolean;
|
||||
dynBuiltinClass: boolean;
|
||||
dynBuiltinStream: boolean;
|
||||
/** An Array.prototype method runs over an array-like receiver:
|
||||
* scr_dyn_array_like.c. Absent (false) otherwise, so programs that never
|
||||
* call one over an array-like keep their exact link line. */
|
||||
arrayLikeDispatch?: boolean;
|
||||
/** Number.prototype or Boolean.prototype read as a value:
|
||||
* scr_dyn_builtin_prototype.c. Absent (false) otherwise. */
|
||||
builtinPrototypeValue?: boolean;
|
||||
parseArgs: boolean;
|
||||
stream: boolean;
|
||||
net: boolean;
|
||||
|
||||
@@ -150,6 +150,13 @@ export interface CcOptions {
|
||||
computedRequire?: boolean;
|
||||
/** Builtin functions read as values: compiles scr_builtin_values.c. */
|
||||
builtinValues?: boolean;
|
||||
/** An Array.prototype method runs over an array-like receiver:
|
||||
* compiles scr_dyn_array_like.c, which the dyn-invoke unit's array
|
||||
* dispatch calls for receivers that are not arrays. */
|
||||
arrayLikeDispatch?: boolean;
|
||||
/** Number.prototype or Boolean.prototype read as a value in a JavaScript
|
||||
* source: compiles scr_dyn_builtin_prototype.c. */
|
||||
builtinPrototypeValue?: boolean;
|
||||
/** The program constructs EventTarget/Event/CustomEvent values or
|
||||
* calls events.addAbortListener (the event.* libCalls), or uses the
|
||||
* native fetch AbortSignal built on the same engine: compiles
|
||||
|
||||
@@ -199,6 +199,14 @@ const RUNTIME_UNITS: readonly RuntimeUnit[] = [
|
||||
unit((u) => u.opts.dynBuffer === true && u.opts.dynInvoke, "scr_bytes_dyn.c"),
|
||||
// Builtin functions read as values in JavaScript dependencies.
|
||||
unit((u) => u.opts.builtinValues === true, "scr_builtin_values.c"),
|
||||
// Array.prototype's generic algorithms over array-like receivers, and the
|
||||
// Number/Boolean prototype objects as values: the dynamic tier's gated
|
||||
// units, whose work is JavaScript-only.
|
||||
unit((u) => u.opts.arrayLikeDispatch === true && u.opts.dynInvoke, "scr_dyn_array_like.c"),
|
||||
unit(
|
||||
(u) => u.opts.builtinPrototypeValue === true && u.opts.dynInvoke,
|
||||
"scr_dyn_builtin_prototype.c",
|
||||
),
|
||||
// createRequire's computed require and require.resolve.
|
||||
unit((u) => u.opts.computedRequire === true, "scr_require.c"),
|
||||
unit((u) => u.opts.dc, "scr_dc.c"),
|
||||
|
||||
@@ -98,6 +98,12 @@ export interface RuntimeFeatureSet extends NativeLinkFeatures {
|
||||
utilTiming: boolean;
|
||||
dynBuffer: boolean;
|
||||
builtinValues: boolean;
|
||||
/** Array.prototype's generic algorithms over array-likes
|
||||
* (scr_dyn_array_like.c). */
|
||||
arrayLikeDispatch?: boolean;
|
||||
/** Number.prototype and Boolean.prototype as values
|
||||
* (scr_dyn_builtin_prototype.c). */
|
||||
builtinPrototypeValue?: boolean;
|
||||
computedRequire: boolean;
|
||||
warningReplace: boolean;
|
||||
stdioExtended: boolean;
|
||||
@@ -309,6 +315,8 @@ export function effectiveRuntimeFeatures(
|
||||
utilTiming: features.utilTiming === true,
|
||||
dynBuffer: features.dynBuffer === true,
|
||||
builtinValues: features.builtinValues === true,
|
||||
arrayLikeDispatch: features.arrayLikeDispatch === true,
|
||||
builtinPrototypeValue: features.builtinPrototypeValue === true,
|
||||
computedRequire: features.computedRequire === true,
|
||||
warningReplace: features.warningReplace === true,
|
||||
stdioExtended: features.stdioExtended === true,
|
||||
|
||||
@@ -101,6 +101,11 @@ export interface EarlyExecutableNativeFeatures {
|
||||
utilPromisify: boolean;
|
||||
dynBuiltinClass: boolean;
|
||||
dynBuiltinStream: boolean;
|
||||
/** Array.prototype methods over array-like receivers: scr_dyn_array_like.c. */
|
||||
arrayLikeDispatch?: boolean;
|
||||
/** Number.prototype and Boolean.prototype as values:
|
||||
* scr_dyn_builtin_prototype.c. */
|
||||
builtinPrototypeValue?: boolean;
|
||||
parseArgs: boolean;
|
||||
stream: boolean;
|
||||
net: boolean;
|
||||
|
||||
@@ -725,6 +725,8 @@ async function compileExecutableNative(
|
||||
...(features.utilTiming ? { utilTiming: true } : {}),
|
||||
...(features.dynBuffer ? { dynBuffer: true } : {}),
|
||||
...(features.builtinValues ? { builtinValues: true } : {}),
|
||||
...(features.arrayLikeDispatch ? { arrayLikeDispatch: true } : {}),
|
||||
...(features.builtinPrototypeValue ? { builtinPrototypeValue: true } : {}),
|
||||
...(features.computedRequire ? { computedRequire: true } : {}),
|
||||
events: features.events,
|
||||
emitter: features.emitter,
|
||||
|
||||
@@ -765,6 +765,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"child.propsDescriptor",
|
||||
"child.propsOnDyn",
|
||||
"dyn.arrayProtoCall",
|
||||
"dyn.arrayLikeProtoCall",
|
||||
"process.chdir",
|
||||
"os.getPriority",
|
||||
"os.setPriority",
|
||||
|
||||
@@ -402,6 +402,13 @@ function buildLibFnSignatures() {
|
||||
"dyn.defineProperty": { argTypes: [DYN, DYN, DYN], result: DYN },
|
||||
"dyn.getOwnPropertyDescriptor": { argTypes: [DYN, DYN], result: DYN },
|
||||
"dyn.arrayProtoCall": { argTypes: [DYN, STRING, DYN], result: DYN },
|
||||
/** The same call through the array-like unit (JavaScript sources):
|
||||
* Array.prototype's generic algorithms over a receiver that is not a
|
||||
* native array, and the invoke unit's dispatch for everything else. */
|
||||
"dyn.arrayLikeProtoCall": { argTypes: [DYN, STRING, DYN], result: DYN },
|
||||
/** Number.prototype / Boolean.prototype as JavaScript values: the
|
||||
* prototype object (dyn.builtinPrototype unit). */
|
||||
"dyn.builtinPrototype": { argTypes: [STRING], result: DYN },
|
||||
/** Promise.all / Promise.allSettled / Promise.any over a spread
|
||||
* checked-dynamic array: the values, the settled-record array, or the
|
||||
* first fulfillment (AggregateError once every entry rejects).
|
||||
|
||||
@@ -330,6 +330,8 @@ const READABLE_DYN_ASYNC_PREFIXES: readonly string[] = ["readable.fromGen", "rea
|
||||
/** Entry points that link the dyn-invoke unit. */
|
||||
const DYN_INVOKE_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"dyn.arrayProtoCall",
|
||||
"dyn.arrayLikeProtoCall",
|
||||
"dyn.builtinPrototype",
|
||||
"dyn.arrayPrototype",
|
||||
"dyn.builtinFunction",
|
||||
"dyn.builtinMethod",
|
||||
@@ -963,6 +965,13 @@ const RUNTIME_FEATURE_RULES = {
|
||||
* half (scr_dyn_builtin_stream.c): native stream state for constructor
|
||||
* functions, over the stream unit. */
|
||||
dynBuiltinStream: rule({ fns: ["dyn.builtinStreamClass"] }),
|
||||
/** Array.prototype's generic algorithms over array-like receivers
|
||||
* (scr_dyn_array_like.c), which delegates every other name to the
|
||||
* dyn-invoke unit's array dispatch. */
|
||||
arrayLikeDispatch: rule({ fns: ["dyn.arrayLikeProtoCall"] }),
|
||||
/** Number.prototype and Boolean.prototype as values
|
||||
* (scr_dyn_builtin_prototype.c). */
|
||||
builtinPrototypeValue: rule({ fns: ["dyn.builtinPrototype"] }),
|
||||
dynInvoke: rule({
|
||||
initial: (mod) => mod.workers === true,
|
||||
fns: [
|
||||
@@ -1283,6 +1292,8 @@ function noRuntimeFeatures(): RuntimeFeatures {
|
||||
dynBuiltinClass: false,
|
||||
dynBuiltinStream: false,
|
||||
dynInvoke: false,
|
||||
arrayLikeDispatch: false,
|
||||
builtinPrototypeValue: false,
|
||||
dynTier: false,
|
||||
ecdh: false,
|
||||
x509: false,
|
||||
|
||||
@@ -74,6 +74,11 @@ export const OPTIONAL_RUNTIME_SOURCES = [
|
||||
// Island libCalls on native values typed by island declarations invoke
|
||||
// through the checked-dynamic path (scr_node_island_handle.c).
|
||||
["scr_dyn_invoke.c", any("dynInvoke", "nativeFetch", "nodeIsland")],
|
||||
// Array.prototype generic algorithms over array-like receivers
|
||||
// (scr_dyn_array_like.c); it calls the invoke unit's dispatch.
|
||||
["scr_dyn_array_like.c", all("arrayLikeDispatch", "dynInvoke")],
|
||||
// Number.prototype and Boolean.prototype read as values in JavaScript.
|
||||
["scr_dyn_builtin_prototype.c", all("builtinPrototypeValue", "dynInvoke")],
|
||||
// Buffer.prototype methods called on checked-dynamic values.
|
||||
["scr_bytes_dyn.c", all("dynBuffer", "dynInvoke")],
|
||||
// Builtin functions read as values in JavaScript dependencies.
|
||||
|
||||
@@ -0,0 +1,691 @@
|
||||
/* Array.prototype's generic algorithms over array-like receivers.
|
||||
*
|
||||
* `Array.prototype.<method>.call(obj, ...)` (and a stored method value
|
||||
* called on an object) reaches scr_dyn_array_proto_call, which runs
|
||||
* at/slice/indexOf/lastIndexOf/includes and the higher-order methods over
|
||||
* any receiver. Every other method answered "not supported yet", so a call
|
||||
* on an array-like — an ordinary object with a length, or a primitive's
|
||||
* ToObject wrapper — threw instead of running the algorithm.
|
||||
*
|
||||
* This unit runs those methods the way the specification writes them, as
|
||||
* Get/HasProperty/Set/DeletePropertyOrThrow steps bounded by ToLength, and
|
||||
* hands every other name back to scr_dyn_array_proto_call so the two units
|
||||
* keep one implementation each. It links only into programs that use the
|
||||
* dynamic array-like path (the frontend marks dyn.arrayLikeProtoCall for
|
||||
* JavaScript sources), so typed programs keep their exact executables and
|
||||
* scr_dyn_invoke.c is not touched: a program that only uses the older
|
||||
* borrow methods links exactly what it linked before.
|
||||
*
|
||||
* The algorithms only use the checked-dynamic tree's public surface; the
|
||||
* few helpers they share (an index key, Get/Has/Set at an index, the
|
||||
* array-like length, a callback gate) are private to this unit. */
|
||||
#include "scr_runtime.h"
|
||||
#include "scr_dyn_methods.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
/* One million indexed steps per call: past it the iteration reports
|
||||
* SC2020 rather than running an unbounded loop over a length property the
|
||||
* program chose. */
|
||||
static bool al_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 al_is(const ScrStr *method, const char *name) {
|
||||
size_t len = strlen(name);
|
||||
return method->len == len && memcmp(method->data, name, len) == 0;
|
||||
}
|
||||
|
||||
/* The canonical array index a property key spells, if it does. */
|
||||
static bool al_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;
|
||||
}
|
||||
|
||||
/* Get(receiver, key) through the receiver's checked-dynamic kind. A
|
||||
* primitive answers an ordinary property read on its ToObject wrapper,
|
||||
* which is fresh and unobservable: an absent member is undefined. The
|
||||
* result is owned (+1). */
|
||||
static ScrDyn *al_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 = al_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 (!al_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());
|
||||
}
|
||||
|
||||
/* HasProperty(receiver, key). */
|
||||
static bool al_has(ScrDyn *recv, const ScrStr *key) {
|
||||
if (recv->kind == SCR_DYN_STR || recv->kind == SCR_DYN_BYTES) {
|
||||
size_t index;
|
||||
return al_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 *al_key(size_t index) {
|
||||
char text[32];
|
||||
int n = snprintf(text, sizeof text, "%zu", index);
|
||||
return scr_str_new(text, (size_t)n);
|
||||
}
|
||||
|
||||
/* ToLength(Get(receiver, "length")). */
|
||||
static bool al_length(ScrDyn *recv, size_t *out) {
|
||||
ScrStr *key = scr_str_new("length", 6);
|
||||
ScrDyn *value = al_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;
|
||||
}
|
||||
|
||||
/* ToIntegerOrInfinity of an argument, truncated. */
|
||||
static bool al_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 al_present(ScrDyn *recv, size_t index) {
|
||||
ScrStr *key = al_key(index);
|
||||
bool found = al_has(recv, key);
|
||||
scr_str_release(key);
|
||||
return found;
|
||||
}
|
||||
|
||||
static ScrDyn *al_item(ScrDyn *recv, size_t index) {
|
||||
ScrStr *key = al_key(index);
|
||||
ScrDyn *value = al_get(recv, key);
|
||||
scr_str_release(key);
|
||||
return value;
|
||||
}
|
||||
|
||||
/* A primitive receiver's ToObject wrapper is fresh and unobservable: the
|
||||
* generic array algorithms read through it and discard their writes, which
|
||||
* is exactly what a wrapper nobody holds observes. */
|
||||
static bool al_primitive(const ScrDyn *recv) {
|
||||
switch (recv->kind) {
|
||||
case SCR_DYN_BOOL:
|
||||
case SCR_DYN_NUM:
|
||||
case SCR_DYN_STR:
|
||||
case SCR_DYN_BIGINT:
|
||||
case SCR_DYN_SYMBOL:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Set(O, index, value, true): a failing write throws from scr_dyn_key_set
|
||||
* and the caller checks scr_exc_pending(). */
|
||||
static void al_set(ScrDyn *recv, size_t index, ScrDyn *value) {
|
||||
if (al_primitive(recv)) return;
|
||||
ScrStr *key = al_key(index);
|
||||
scr_dyn_key_set(recv, key, value);
|
||||
scr_str_release(key);
|
||||
}
|
||||
|
||||
/* DeletePropertyOrThrow(O, index). */
|
||||
static void al_delete(ScrDyn *recv, size_t index) {
|
||||
if (al_primitive(recv)) return;
|
||||
ScrStr *key = al_key(index);
|
||||
scr_dyn_key_delete(recv, key, true);
|
||||
scr_str_release(key);
|
||||
}
|
||||
|
||||
/* Set(O, "length", length, true). */
|
||||
static void al_set_length(ScrDyn *recv, size_t length) {
|
||||
if (al_primitive(recv)) return;
|
||||
ScrStr *key = scr_str_new("length", 6);
|
||||
ScrDyn *value = scr_dyn_new_num((double)length);
|
||||
scr_dyn_key_set(recv, key, value);
|
||||
scr_dyn_release(value);
|
||||
scr_str_release(key);
|
||||
}
|
||||
|
||||
/* The receiver's elements in index order as a fresh dyn array. Every index
|
||||
* the receiver does not carry becomes undefined, as toReversed, toSorted
|
||||
* and with create their result with CreateDataProperty for every index.
|
||||
* Owned (+1), NULL with the exception pending. */
|
||||
static ScrDyn *al_snapshot(ScrDyn *recv, size_t len) {
|
||||
ScrDyn *out = scr_dyn_new_arr();
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (len > 1000000 && !al_iteration(i)) { scr_dyn_release(out); return NULL; }
|
||||
if (!al_present(recv, i)) {
|
||||
if (scr_exc_pending()) { scr_dyn_release(out); return NULL; }
|
||||
scr_dyn_arr_push(out, scr_dyn_retain(scr_dyn_undefined()));
|
||||
continue;
|
||||
}
|
||||
ScrDyn *item = al_item(recv, i);
|
||||
if (!item) { scr_dyn_release(out); return NULL; }
|
||||
scr_dyn_arr_push(out, item);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/* CreateDataProperty(out, index, Get(receiver, index)) — an ordinary
|
||||
* property read, with an absent index kept as a hole. */
|
||||
static bool al_copy_item(ScrDyn *out, ScrDyn *recv, size_t index) {
|
||||
if (!al_present(recv, index)) {
|
||||
if (scr_exc_pending()) return false;
|
||||
scr_dyn_arr_push_hole(out);
|
||||
return true;
|
||||
}
|
||||
ScrDyn *item = al_item(recv, index);
|
||||
if (!item) return false;
|
||||
scr_dyn_arr_push(out, item);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* JS relative-index normalization (slice's rule). */
|
||||
static size_t al_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;
|
||||
}
|
||||
|
||||
/* The callable-callback gate: JS's "<String(cb)> is not a function". */
|
||||
static bool al_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);
|
||||
ScrStr *text = scr_dyn_string_coerce_js(cb);
|
||||
if (text) {
|
||||
scr_jb_write(&b, text->data, text->len);
|
||||
scr_str_release(text);
|
||||
}
|
||||
scr_jb_puts(&b, " is not a function");
|
||||
scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&b));
|
||||
return false;
|
||||
}
|
||||
|
||||
/* One packed argument list, borrowed: the array dispatch hands the
|
||||
* algorithm its arguments as a dyn array. */
|
||||
ScrDyn *scr_dyn_array_like_call(ScrDyn *recv, ScrStr *method, ScrDyn *args) {
|
||||
if (recv->kind == SCR_DYN_ARR ||
|
||||
(recv->kind == SCR_DYN_TYPED_REF && scr_dyn_typed_ref_array_ops(recv)))
|
||||
return scr_dyn_array_proto_call(recv, method, args);
|
||||
/* Step 1 of each of these algorithms: `? ToObject(this value)`. The
|
||||
* array dispatch answers it for its own receivers; a nullish one never
|
||||
* reaches a length read. */
|
||||
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 (!al_length(recv, &len)) return NULL;
|
||||
|
||||
if (al_is(method, "pop")) {
|
||||
if (len == 0) {
|
||||
al_set_length(recv, 0);
|
||||
return scr_exc_pending() ? NULL : scr_dyn_retain(scr_dyn_undefined());
|
||||
}
|
||||
ScrDyn *item = al_item(recv, len - 1);
|
||||
if (!item) return NULL;
|
||||
al_delete(recv, len - 1);
|
||||
if (!scr_exc_pending()) al_set_length(recv, len - 1);
|
||||
if (scr_exc_pending()) { scr_dyn_release(item); return NULL; }
|
||||
return item;
|
||||
}
|
||||
if (al_is(method, "push") || al_is(method, "unshift")) {
|
||||
if (al_is(method, "push")) {
|
||||
for (size_t i = 0; i < argc; i++) {
|
||||
if (len > 1000000 && !al_iteration(i)) return NULL;
|
||||
al_set(recv, len + i, argv[i]);
|
||||
if (scr_exc_pending()) return NULL;
|
||||
}
|
||||
al_set_length(recv, len + argc);
|
||||
if (scr_exc_pending()) return NULL;
|
||||
return scr_dyn_new_num((double)(len + argc));
|
||||
}
|
||||
if (argc > 0) {
|
||||
/* Right to left, so each element lands one `argc` slot further. */
|
||||
for (size_t k = len; k > 0; k--) {
|
||||
size_t from = k - 1;
|
||||
size_t to = from + argc;
|
||||
if (len > 1000000 && !al_iteration(len - k)) return NULL;
|
||||
if (scr_exc_pending()) return NULL;
|
||||
if (!al_present(recv, from)) { al_delete(recv, to); }
|
||||
else {
|
||||
ScrDyn *item = al_item(recv, from);
|
||||
if (!item) return NULL;
|
||||
al_set(recv, to, item);
|
||||
scr_dyn_release(item);
|
||||
}
|
||||
if (scr_exc_pending()) return NULL;
|
||||
}
|
||||
for (size_t j = 0; j < argc; j++) {
|
||||
al_set(recv, j, argv[j]);
|
||||
if (scr_exc_pending()) return NULL;
|
||||
}
|
||||
}
|
||||
al_set_length(recv, len + argc);
|
||||
if (scr_exc_pending()) return NULL;
|
||||
return scr_dyn_new_num((double)(len + argc));
|
||||
}
|
||||
if (al_is(method, "shift")) {
|
||||
if (len == 0) {
|
||||
al_set_length(recv, 0);
|
||||
return scr_exc_pending() ? NULL : scr_dyn_retain(scr_dyn_undefined());
|
||||
}
|
||||
ScrDyn *first = al_item(recv, 0);
|
||||
if (!first) return NULL;
|
||||
for (size_t k = 1; k < len; k++) {
|
||||
if (len > 1000000 && !al_iteration(k - 1)) { scr_dyn_release(first); return NULL; }
|
||||
size_t to = k - 1;
|
||||
if (scr_exc_pending()) { scr_dyn_release(first); return NULL; }
|
||||
if (!al_present(recv, k)) { al_delete(recv, to); }
|
||||
else {
|
||||
ScrDyn *item = al_item(recv, k);
|
||||
if (!item) { scr_dyn_release(first); return NULL; }
|
||||
al_set(recv, to, item);
|
||||
scr_dyn_release(item);
|
||||
}
|
||||
if (scr_exc_pending()) { scr_dyn_release(first); return NULL; }
|
||||
}
|
||||
al_delete(recv, len - 1);
|
||||
if (!scr_exc_pending()) al_set_length(recv, len - 1);
|
||||
if (scr_exc_pending()) { scr_dyn_release(first); return NULL; }
|
||||
return first;
|
||||
}
|
||||
if (al_is(method, "reverse")) {
|
||||
size_t middle = len / 2;
|
||||
for (size_t lower = 0; lower < middle; lower++) {
|
||||
if (len > 1000000 && !al_iteration(lower)) return NULL;
|
||||
size_t upper = len - lower - 1;
|
||||
bool has_lower = al_present(recv, lower);
|
||||
if (scr_exc_pending()) return NULL;
|
||||
ScrDyn *lower_value = has_lower ? al_item(recv, lower) : NULL;
|
||||
if (has_lower && !lower_value) return NULL;
|
||||
bool has_upper = al_present(recv, upper);
|
||||
ScrDyn *upper_value = has_upper && !scr_exc_pending() ? al_item(recv, upper) : NULL;
|
||||
if (scr_exc_pending() || (has_upper && !upper_value)) {
|
||||
scr_dyn_release(lower_value);
|
||||
return NULL;
|
||||
}
|
||||
if (has_lower && has_upper) {
|
||||
al_set(recv, lower, upper_value);
|
||||
if (!scr_exc_pending()) al_set(recv, upper, lower_value);
|
||||
} else if (has_upper) {
|
||||
al_set(recv, lower, upper_value);
|
||||
if (!scr_exc_pending()) al_delete(recv, upper);
|
||||
} else if (has_lower) {
|
||||
al_delete(recv, lower);
|
||||
if (!scr_exc_pending()) al_set(recv, upper, lower_value);
|
||||
}
|
||||
scr_dyn_release(lower_value);
|
||||
scr_dyn_release(upper_value);
|
||||
if (scr_exc_pending()) return NULL;
|
||||
}
|
||||
return scr_dyn_retain(recv);
|
||||
}
|
||||
if (al_is(method, "join")) {
|
||||
ScrStr *sep = argc > 0 && argv[0]->kind != SCR_DYN_UNDEF
|
||||
? scr_dyn_string_coerce_js(argv[0])
|
||||
: scr_str_new(",", 1);
|
||||
if (!sep) return NULL;
|
||||
ScrJsonBuf buffer;
|
||||
scr_jb_init(&buffer);
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (i) scr_jb_write(&buffer, sep->data, sep->len);
|
||||
if (len > 1000000 && !al_iteration(i)) { scr_str_release(sep); return NULL; }
|
||||
if (!al_present(recv, i)) {
|
||||
if (scr_exc_pending()) { scr_str_release(sep); return NULL; }
|
||||
continue;
|
||||
}
|
||||
ScrDyn *item = al_item(recv, i);
|
||||
if (!item) { scr_str_release(sep); return NULL; }
|
||||
if (item->kind != SCR_DYN_UNDEF && item->kind != SCR_DYN_NULL) {
|
||||
ScrStr *text = scr_dyn_string_coerce_js(item);
|
||||
if (text) {
|
||||
scr_jb_write(&buffer, text->data, text->len);
|
||||
scr_str_release(text);
|
||||
}
|
||||
}
|
||||
scr_dyn_release(item);
|
||||
if (scr_exc_pending()) { scr_str_release(sep); return NULL; }
|
||||
}
|
||||
scr_str_release(sep);
|
||||
ScrStr *text = scr_jb_finish(&buffer);
|
||||
ScrDyn *answer = scr_dyn_new_str(text);
|
||||
scr_str_release(text);
|
||||
return answer;
|
||||
}
|
||||
if (al_is(method, "fill")) {
|
||||
ScrDyn *value = argc > 0 ? argv[0] : scr_dyn_undefined();
|
||||
double start_d = 0;
|
||||
double end_d = (double)len;
|
||||
if (argc > 1 && !al_number(argv[1], &start_d)) return NULL;
|
||||
if (argc > 2 && argv[2]->kind != SCR_DYN_UNDEF && !al_number(argv[2], &end_d)) return NULL;
|
||||
size_t start = al_rel_index(trunc(start_d), len);
|
||||
size_t end = al_rel_index(trunc(end_d), len);
|
||||
for (size_t i = start; i < end; i++) {
|
||||
if (len > 1000000 && !al_iteration(i - start)) return NULL;
|
||||
al_set(recv, i, value);
|
||||
if (scr_exc_pending()) return NULL;
|
||||
}
|
||||
return scr_dyn_retain(recv);
|
||||
}
|
||||
if (al_is(method, "copyWithin")) {
|
||||
double target_d = 0;
|
||||
double start_d = 0;
|
||||
double end_d = (double)len;
|
||||
if (argc > 0 && !al_number(argv[0], &target_d)) return NULL;
|
||||
if (argc > 1 && !al_number(argv[1], &start_d)) return NULL;
|
||||
if (argc > 2 && argv[2]->kind != SCR_DYN_UNDEF && !al_number(argv[2], &end_d)) return NULL;
|
||||
size_t to = al_rel_index(trunc(target_d), len);
|
||||
size_t from = al_rel_index(trunc(start_d), len);
|
||||
size_t end = al_rel_index(trunc(end_d), len);
|
||||
if (from >= end || to >= len) return scr_dyn_retain(recv);
|
||||
size_t count = end - from < len - to ? end - from : len - to;
|
||||
bool backwards = from < to && to < from + count;
|
||||
for (size_t step = 0; step < count; step++) {
|
||||
if (len > 1000000 && !al_iteration(step)) return NULL;
|
||||
size_t i = backwards ? count - 1 - step : step;
|
||||
size_t source = from + i;
|
||||
size_t target = to + i;
|
||||
if (!al_present(recv, source)) { al_delete(recv, target); }
|
||||
else {
|
||||
ScrDyn *item = al_item(recv, source);
|
||||
if (!item) return NULL;
|
||||
al_set(recv, target, item);
|
||||
scr_dyn_release(item);
|
||||
}
|
||||
if (scr_exc_pending()) return NULL;
|
||||
}
|
||||
return scr_dyn_retain(recv);
|
||||
}
|
||||
if (al_is(method, "concat")) {
|
||||
/* The receiver's own elements spread one level when it is a native
|
||||
* array; an array-like receiver is appended as a single value, as
|
||||
* Array.prototype.concat treats a non-array `this`. */
|
||||
ScrDyn *out = scr_dyn_new_arr();
|
||||
if (recv->kind == SCR_DYN_ARR) {
|
||||
for (size_t i = 0; i < len; i++)
|
||||
if (!al_copy_item(out, recv, i)) { scr_dyn_release(out); return NULL; }
|
||||
} else {
|
||||
scr_dyn_arr_push(out, scr_dyn_retain(recv));
|
||||
}
|
||||
/* An array argument spreads one level (holes included); every other
|
||||
* value is appended as itself. */
|
||||
for (size_t a = 0; a < argc; a++) {
|
||||
ScrDyn *arg = argv[a];
|
||||
if (arg->kind != SCR_DYN_ARR) {
|
||||
scr_dyn_arr_push(out, scr_dyn_retain(arg));
|
||||
continue;
|
||||
}
|
||||
for (size_t i = 0; i < arg->v.arr.len; i++)
|
||||
if (!al_copy_item(out, arg, i)) { scr_dyn_release(out); return NULL; }
|
||||
}
|
||||
return out;
|
||||
}
|
||||
if (al_is(method, "splice")) {
|
||||
/* No arguments is the one form whose actualStart is len, not 0: it
|
||||
* deletes nothing and only rewrites the same length. Starting at 0
|
||||
* would delete — and copy out — the whole receiver. */
|
||||
size_t start = len;
|
||||
size_t removed = 0;
|
||||
if (argc > 0) {
|
||||
double start_d = 0;
|
||||
if (!al_number(argv[0], &start_d)) return NULL;
|
||||
start = al_rel_index(trunc(start_d), len);
|
||||
removed = len - start;
|
||||
if (argc > 1) {
|
||||
double count_d = 0;
|
||||
if (!al_number(argv[1], &count_d)) return NULL;
|
||||
double clamped = count_d < 0 ? 0 : count_d;
|
||||
removed = clamped > (double)removed ? removed : (size_t)clamped;
|
||||
}
|
||||
}
|
||||
size_t items = argc > 2 ? argc - 2 : 0;
|
||||
/* ArraySpeciesCreate(O, actualDeleteCount) runs before any index is
|
||||
* read or written, and ArrayCreate refuses a length past 2^32-1. */
|
||||
if (removed > 4294967295u) {
|
||||
static const char msg[] = "Invalid array length";
|
||||
scr_throw_error_msg(SCR_ERR_RANGE, msg, sizeof msg - 1);
|
||||
return NULL;
|
||||
}
|
||||
ScrDyn *out = scr_dyn_new_arr();
|
||||
for (size_t i = 0; i < removed; i++) {
|
||||
if (len > 1000000 && !al_iteration(i)) { scr_dyn_release(out); return NULL; }
|
||||
if (!al_copy_item(out, recv, start + i)) { scr_dyn_release(out); return NULL; }
|
||||
}
|
||||
if (items < removed) {
|
||||
for (size_t k = start; k < len - removed; k++) {
|
||||
if (len > 1000000 && !al_iteration(k - start)) { scr_dyn_release(out); return NULL; }
|
||||
size_t target = k + items;
|
||||
if (!al_present(recv, k + removed)) { al_delete(recv, target); }
|
||||
else {
|
||||
ScrDyn *item = al_item(recv, k + removed);
|
||||
if (!item) { scr_dyn_release(out); return NULL; }
|
||||
al_set(recv, target, item);
|
||||
scr_dyn_release(item);
|
||||
}
|
||||
if (scr_exc_pending()) { scr_dyn_release(out); return NULL; }
|
||||
}
|
||||
for (size_t k = len; k > len - removed + items; k--) {
|
||||
al_delete(recv, k - 1);
|
||||
if (scr_exc_pending()) { scr_dyn_release(out); return NULL; }
|
||||
}
|
||||
} else if (items > removed) {
|
||||
for (size_t k = len - removed; k > start; k--) {
|
||||
if (len > 1000000 && !al_iteration(len - k)) { scr_dyn_release(out); return NULL; }
|
||||
size_t source = k + removed - 1;
|
||||
size_t target = k + items - 1;
|
||||
if (!al_present(recv, source)) { al_delete(recv, target); }
|
||||
else {
|
||||
ScrDyn *item = al_item(recv, source);
|
||||
if (!item) { scr_dyn_release(out); return NULL; }
|
||||
al_set(recv, target, item);
|
||||
scr_dyn_release(item);
|
||||
}
|
||||
if (scr_exc_pending()) { scr_dyn_release(out); return NULL; }
|
||||
}
|
||||
}
|
||||
for (size_t j = 0; j < items; j++) {
|
||||
al_set(recv, start + j, argv[2 + j]);
|
||||
if (scr_exc_pending()) { scr_dyn_release(out); return NULL; }
|
||||
}
|
||||
al_set_length(recv, len - removed + items);
|
||||
if (scr_exc_pending()) { scr_dyn_release(out); return NULL; }
|
||||
return out;
|
||||
}
|
||||
if (al_is(method, "with")) {
|
||||
double index_d = 0;
|
||||
if (!al_number(argc > 0 ? argv[0] : scr_dyn_undefined(), &index_d)) return NULL;
|
||||
double index = index_d < 0 ? (double)len + index_d : index_d;
|
||||
if (index < 0 || index >= (double)len) {
|
||||
static const char msg[] = "Invalid index";
|
||||
scr_throw_error_msg(SCR_ERR_RANGE, msg, sizeof msg - 1);
|
||||
return NULL;
|
||||
}
|
||||
ScrDyn *out = al_snapshot(recv, len);
|
||||
if (!out) return NULL;
|
||||
al_set(out, (size_t)index, argc > 1 ? argv[1] : scr_dyn_undefined());
|
||||
if (scr_exc_pending()) { scr_dyn_release(out); return NULL; }
|
||||
return out;
|
||||
}
|
||||
if (al_is(method, "toReversed")) {
|
||||
ScrDyn *out = al_snapshot(recv, len);
|
||||
if (!out) return NULL;
|
||||
ScrDyn *reversed = scr_dyn_invoke(out, "reverse", NULL, 0, "toReversed");
|
||||
scr_dyn_release(out);
|
||||
return reversed;
|
||||
}
|
||||
if (al_is(method, "toSorted")) {
|
||||
ScrDyn *cmp = argc > 0 ? argv[0] : scr_dyn_undefined();
|
||||
if (cmp->kind != SCR_DYN_UNDEF && !scr_dyn_is_callable(cmp)) {
|
||||
static const char msg[] = "The comparison function must be either a function or undefined";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
|
||||
return NULL;
|
||||
}
|
||||
ScrDyn *out = al_snapshot(recv, len);
|
||||
if (!out) return NULL;
|
||||
if (out->v.arr.len < 2) return out;
|
||||
ScrDyn *sorted = scr_dyn_invoke(out, "sort", argv, argc, "toSorted");
|
||||
scr_dyn_release(out);
|
||||
return sorted;
|
||||
}
|
||||
if (al_is(method, "toSpliced")) {
|
||||
double start_d = 0;
|
||||
if (argc > 0 && !al_number(argv[0], &start_d)) return NULL;
|
||||
size_t start = al_rel_index(trunc(start_d), len);
|
||||
size_t removed = len - start;
|
||||
if (argc > 1) {
|
||||
double count_d = 0;
|
||||
if (!al_number(argv[1], &count_d)) return NULL;
|
||||
double clamped = count_d < 0 ? 0 : count_d;
|
||||
removed = clamped > (double)removed ? removed : (size_t)clamped;
|
||||
}
|
||||
size_t items = argc > 2 ? argc - 2 : 0;
|
||||
/* The result array is created up front: a length past 2^53-1 is a
|
||||
* TypeError of its own, and ArrayCreate refuses anything past
|
||||
* 2^32-1 with a RangeError — both before any index is read. */
|
||||
double new_len = (double)len - (double)removed + (double)items;
|
||||
if (new_len > 9007199254740991.0) {
|
||||
static const char msg[] = "Invalid array length";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
|
||||
return NULL;
|
||||
}
|
||||
if (new_len > 4294967295.0) {
|
||||
static const char msg[] = "Invalid array length";
|
||||
scr_throw_error_msg(SCR_ERR_RANGE, msg, sizeof msg - 1);
|
||||
return NULL;
|
||||
}
|
||||
ScrDyn *out = scr_dyn_new_arr();
|
||||
for (size_t i = 0; i < start; i++) {
|
||||
if (len > 1000000 && !al_iteration(i)) { scr_dyn_release(out); return NULL; }
|
||||
if (!al_copy_item(out, recv, i)) { scr_dyn_release(out); return NULL; }
|
||||
}
|
||||
for (size_t j = 2; j < argc; j++) scr_dyn_arr_push(out, scr_dyn_retain(argv[j]));
|
||||
for (size_t i = start + removed; i < len; i++) {
|
||||
if (len > 1000000 && !al_iteration(i)) { scr_dyn_release(out); return NULL; }
|
||||
if (!al_copy_item(out, recv, i)) { scr_dyn_release(out); return NULL; }
|
||||
}
|
||||
return out;
|
||||
}
|
||||
if (al_is(method, "flat") || al_is(method, "flatMap")) {
|
||||
/* Snapshot the receiver into a native array the array dispatch
|
||||
* flattens: the flattening rules (depth counting, nested arrays and
|
||||
* wrapped array views) live in one implementation. flatMap maps first,
|
||||
* then flattens the mapped array at depth 1. */
|
||||
bool mapping = al_is(method, "flatMap");
|
||||
if (mapping && !al_cb_check(argv, argc)) return NULL;
|
||||
ScrDyn *mapped = scr_dyn_new_arr();
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (len > 1000000 && !al_iteration(i)) { scr_dyn_release(mapped); return NULL; }
|
||||
if (!al_present(recv, i)) {
|
||||
if (scr_exc_pending()) { scr_dyn_release(mapped); return NULL; }
|
||||
scr_dyn_arr_push_hole(mapped);
|
||||
continue;
|
||||
}
|
||||
ScrDyn *item = al_item(recv, i);
|
||||
if (!item) { scr_dyn_release(mapped); return NULL; }
|
||||
if (mapping) {
|
||||
ScrDyn *index = scr_dyn_new_num((double)i);
|
||||
ScrDyn *cbargs[3] = { item, index, recv };
|
||||
ScrDyn *result = scr_dyn_call(argv[0], cbargs, 3, "callback");
|
||||
scr_dyn_release(index);
|
||||
scr_dyn_release(item);
|
||||
if (!result) { scr_dyn_release(mapped); return NULL; }
|
||||
item = result;
|
||||
}
|
||||
scr_dyn_arr_push(mapped, item);
|
||||
}
|
||||
ScrDyn *one = scr_dyn_new_num(1);
|
||||
ScrDyn *flatargs[1] = { one };
|
||||
ScrDyn *out;
|
||||
if (mapping) {
|
||||
out = scr_dyn_invoke(mapped, "flat", flatargs, 1, "flatMap");
|
||||
} else {
|
||||
/* flat's own depth argument: absent, undefined or a number. */
|
||||
ScrDyn *given[1] = { argc > 0 ? argv[0] : scr_dyn_undefined() };
|
||||
size_t count = argc > 0 ? 1 : 0;
|
||||
out = scr_dyn_invoke(mapped, "flat", given, count, "flat");
|
||||
}
|
||||
scr_dyn_release(one);
|
||||
scr_dyn_release(mapped);
|
||||
return out;
|
||||
}
|
||||
/* Every other name keeps the one implementation in the invoke unit
|
||||
* (at, slice, indexOf, the higher-order methods, and the "not supported
|
||||
* yet" answers). */
|
||||
return scr_dyn_array_proto_call(recv, method, args);
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/* Number.prototype and Boolean.prototype as values.
|
||||
*
|
||||
* A JavaScript source reading `Number.prototype` or `Boolean.prototype`
|
||||
* used to fall through to an ordinary member read of the constructor
|
||||
* value, which answered undefined: there was no prototype object to read.
|
||||
* This unit builds the two prototype objects the way the language defines
|
||||
* them — an ordinary object whose prototype is Object.prototype, carrying
|
||||
* the kind's methods as non-enumerable, writable, configurable data
|
||||
* properties — and answers them from one box per thread, so every read is
|
||||
* the same object (`Number.prototype === Number.prototype`).
|
||||
*
|
||||
* A method called on the prototype object itself runs on the kind's empty
|
||||
* primitive (`Number.prototype.toString(2)` is "0", `Boolean.prototype
|
||||
* .valueOf()` is false), which is what Node's Number and Boolean prototype
|
||||
* objects are: wrappers around +0 and false. The methods are not generic,
|
||||
* so any other receiver — a plain object, null, a primitive of the other
|
||||
* kind — is the TypeError Node answers.
|
||||
*
|
||||
* The unit links only into programs that read those values in a JavaScript
|
||||
* source (the frontend marks dyn.builtinPrototype there), so typed programs
|
||||
* keep their exact executables and no always-linked unit changes.
|
||||
*
|
||||
* Wrapper objects (`new Number(1)`, the primitiveWrapper unit) build a
|
||||
* prototype object of their own for their instances' chain. A program that
|
||||
* does both should see ONE object: when that unit lands, this builder
|
||||
* should answer its prototype for a program that links it rather than
|
||||
* building a second one. */
|
||||
#include "scr_runtime.h"
|
||||
#include "scr_dyn_methods.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
/* The methods each prototype declares, with Node's `length` values. */
|
||||
static const struct { const char *name; uint32_t length; } bp_number_methods[] = {
|
||||
{"toString", 1},
|
||||
{"toFixed", 1},
|
||||
{"toExponential", 1},
|
||||
{"toPrecision", 1},
|
||||
{"valueOf", 0},
|
||||
};
|
||||
static const struct { const char *name; uint32_t length; } bp_boolean_methods[] = {
|
||||
{"toString", 0},
|
||||
{"valueOf", 0},
|
||||
};
|
||||
|
||||
static SCR_TL ScrDyn *bp_number_prototype;
|
||||
static SCR_TL ScrDyn *bp_boolean_prototype;
|
||||
|
||||
static void bp_prototypes_cleanup(void) {
|
||||
scr_dyn_release(bp_number_prototype);
|
||||
scr_dyn_release(bp_boolean_prototype);
|
||||
bp_number_prototype = NULL;
|
||||
bp_boolean_prototype = NULL;
|
||||
}
|
||||
|
||||
/** A prototype method of the kind: caps[0] is the kind's name ("Number" or
|
||||
* "Boolean") and caps[1] the method name. The method runs on the receiver's
|
||||
* primitive value as `this`: the receiver itself when the call passed a
|
||||
* primitive of the kind (`Number.prototype.toString.call(255)`), else the
|
||||
* empty primitive (+0 / false) the prototype object wraps, which is its
|
||||
* [[PrimitiveValue]] in Node too. Boolean's two methods are answered here,
|
||||
* since the checked-dynamic dispatch keeps no Boolean prototype table. */
|
||||
/** The prototype methods are not generic: each one reads the receiver's
|
||||
* [[NumberData]] / [[BooleanData]], so only a primitive of the kind or its
|
||||
* prototype object — the wrapper around +0 / false — is a valid `this`.
|
||||
* Anything else, another primitive included, is a TypeError in Node. */
|
||||
static void bp_bad_this(const ScrStr *kind, const ScrStr *method) {
|
||||
char message[128];
|
||||
snprintf(message, sizeof message, "%s.prototype.%s requires that 'this' be a %s",
|
||||
kind->data, method->data, kind->data);
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, strlen(message));
|
||||
}
|
||||
|
||||
static ScrDyn *bp_method_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
/* Both reads are +1, so the two strings are released below. */
|
||||
ScrStr *kind = 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(kind->data, "Boolean")) {
|
||||
if (receiver->kind != SCR_DYN_BOOL && receiver != bp_boolean_prototype) {
|
||||
bp_bad_this(kind, method);
|
||||
scr_dyn_release(receiver);
|
||||
scr_str_release(kind);
|
||||
scr_str_release(method);
|
||||
return NULL;
|
||||
}
|
||||
bool value = receiver->kind == SCR_DYN_BOOL ? receiver->v.b : false;
|
||||
if (!strcmp(method->data, "valueOf")) {
|
||||
result = scr_dyn_new_bool(value);
|
||||
} else {
|
||||
const char *text = value ? "true" : "false";
|
||||
ScrStr *string = scr_str_new(text, strlen(text));
|
||||
result = scr_dyn_new_str(string);
|
||||
scr_str_release(string);
|
||||
}
|
||||
} else if (receiver->kind != SCR_DYN_NUM && receiver != bp_number_prototype) {
|
||||
bp_bad_this(kind, method);
|
||||
scr_dyn_release(receiver);
|
||||
scr_str_release(kind);
|
||||
scr_str_release(method);
|
||||
return NULL;
|
||||
} else {
|
||||
ScrDyn *self = receiver->kind == SCR_DYN_NUM ? scr_dyn_retain(receiver) : scr_dyn_new_num(0);
|
||||
if (!strcmp(method->data, "toString")) {
|
||||
/* Number.prototype.toString is the radix form, which the number
|
||||
* dispatch answers through its own helper rather than as a method. */
|
||||
ScrStr *value = scr_dyn_to_string_argument(self, argc ? args[0] : scr_dyn_undefined(), method);
|
||||
if (value) {
|
||||
result = scr_dyn_new_str(value);
|
||||
scr_str_release(value);
|
||||
}
|
||||
} else {
|
||||
result = scr_dyn_invoke(self, method->data, args, argc, method->data);
|
||||
}
|
||||
scr_dyn_release(self);
|
||||
}
|
||||
scr_dyn_release(receiver);
|
||||
scr_str_release(kind);
|
||||
scr_str_release(method);
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Defines one non-enumerable data property on `object`: the attributes
|
||||
* every built-in method has ({ writable, configurable }, not enumerable). */
|
||||
static void bp_define(ScrDyn *object, const char *name, size_t name_len, ScrDyn *value) {
|
||||
ScrDyn *descriptor = scr_dyn_new_obj();
|
||||
scr_dyn_obj_set(descriptor, "value", 5, value);
|
||||
scr_dyn_obj_set(descriptor, "writable", 8, scr_dyn_new_bool(true));
|
||||
scr_dyn_obj_set(descriptor, "configurable", 12, scr_dyn_new_bool(true));
|
||||
ScrStr *key = scr_str_new(name, name_len);
|
||||
ScrDyn *key_value = scr_dyn_new_str(key);
|
||||
ScrDyn *defined = scr_dyn_define_property(object, key_value, descriptor);
|
||||
scr_dyn_release(defined);
|
||||
scr_dyn_release(key_value);
|
||||
scr_dyn_release(descriptor);
|
||||
scr_str_release(key);
|
||||
}
|
||||
|
||||
/** The kind's prototype object, built on first use; +1, one per thread (the
|
||||
* slot keeps its own reference, as `scr_dyn_array_prototype_base` does).
|
||||
* `kind` is "Number" or "Boolean" (the only two the frontend marks). */
|
||||
ScrDyn *scr_dyn_builtin_prototype(const ScrStr *kind) {
|
||||
bool number = !strcmp(kind->data, "Number");
|
||||
ScrDyn **slot = number ? &bp_number_prototype : &bp_boolean_prototype;
|
||||
if (*slot) return scr_dyn_retain(*slot);
|
||||
ScrDyn *prototype = scr_dyn_new_obj();
|
||||
*slot = prototype;
|
||||
scr_atexit(bp_prototypes_cleanup);
|
||||
ScrStr *kind_str = scr_str_new(kind->data, strlen(kind->data));
|
||||
const struct { const char *name; uint32_t length; } *methods =
|
||||
number ? bp_number_methods : bp_boolean_methods;
|
||||
size_t count = number ? sizeof bp_number_methods / sizeof bp_number_methods[0]
|
||||
: sizeof bp_boolean_methods / sizeof bp_boolean_methods[0];
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
ScrStr *name = scr_str_new(methods[i].name, strlen(methods[i].name));
|
||||
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(kind_str));
|
||||
scr_box_set_ref(closure->caps[1], scr_str_retain(name));
|
||||
ScrDyn *method = scr_dyn_new_func(closure, bp_method_call, methods[i].length,
|
||||
number ? "native:Number.prototype" : "native:Boolean.prototype", methods[i].name);
|
||||
bp_define(prototype, methods[i].name, strlen(methods[i].name), method);
|
||||
scr_str_release(name);
|
||||
}
|
||||
scr_str_release(kind_str);
|
||||
return scr_dyn_retain(prototype);
|
||||
}
|
||||
@@ -357,6 +357,8 @@ export const DYNAMIC_TIER_PREFIXES = [
|
||||
"scr_dyn_call",
|
||||
"scr_dyn_apply",
|
||||
"scr_dyn_invoke",
|
||||
"scr_dyn_array_like",
|
||||
"scr_dyn_builtin_prototype",
|
||||
"scr_dyn_construct",
|
||||
"scr_dyn_function_apply",
|
||||
"scr_dyn_fn_get",
|
||||
|
||||
Reference in New Issue
Block a user