mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-02 08:35:07 +08:00
feat(compiler): lower borrowed Array.prototype calls (#558)
This commit is contained in:
@@ -4449,6 +4449,8 @@ function emitDynamicLibCall(state: LibCallState): Temp {
|
||||
return finish(`scr_dyn_define_property(${arg(0)}, ${arg(1)}, ${arg(2)})`);
|
||||
case "dyn.getOwnPropertyDescriptor":
|
||||
return finish(`scr_dyn_get_own_property_descriptor(${arg(0)}, ${arg(1)})`);
|
||||
case "dyn.arrayProtoCall":
|
||||
return finish(`scr_dyn_array_proto_call(${arg(0)}, ${arg(1)}, ${arg(2)})`);
|
||||
case "dyn.hasKey":
|
||||
// `k in v` with a runtime key: the dyn presence answer (both
|
||||
// borrowed, no allocation, never throws).
|
||||
|
||||
@@ -1295,6 +1295,14 @@ export function jsonWriteHelper(emitter: CEmitter, t: IrType): string {
|
||||
break;
|
||||
}
|
||||
d.push(` if (d->kind != SCR_DYN_OBJ) { scr_dyn_check_fail(path, ${want}, d); return NULL; }`);
|
||||
if (shape.fields.length === 0 && !shape.indexValue) {
|
||||
const sourceAccessor = `${emitter.toDynHelper(t)}_source_access`;
|
||||
const rc = vAdapters(t);
|
||||
d.push(` {`);
|
||||
d.push(` ${cDecl(t, "sc_source")} = (${cType(t).trim()})scr_dyn_obj_source_cast(d, &${sourceAccessor}, &${rc.retain});`);
|
||||
d.push(` if (sc_source) return sc_source;`);
|
||||
d.push(` }`);
|
||||
}
|
||||
d.push(` ${cDecl(t, "r")} = ${mangleRecordNew(t.shapeId)}();`);
|
||||
for (const f of shape.fields) {
|
||||
const keyLit = cStringLiteral(Buffer.from(f.name, "utf8"));
|
||||
@@ -1591,14 +1599,14 @@ export function jsonWriteHelper(emitter: CEmitter, t: IrType): string {
|
||||
const carriesListenerIdentity = shape.fields.some(
|
||||
(f) => f.name === "handleEvent" && f.type.kind === "func",
|
||||
);
|
||||
if (carriesListenerIdentity) {
|
||||
if ((shape.fields.length === 0 && !shape.indexValue) || carriesListenerIdentity) {
|
||||
const rc = vAdapters(t);
|
||||
sourceAccessor = {
|
||||
name: `${name}_source_access`,
|
||||
release: rc.release,
|
||||
};
|
||||
emitter.walkerProtos.push(
|
||||
`static ScrDyn *${sourceAccessor.name}(void *v, bool materialize); /* live listener source ${key} */`,
|
||||
`static ScrDyn *${sourceAccessor.name}(void *v, bool materialize); /* record source ${key} */`,
|
||||
);
|
||||
d.push(
|
||||
` ScrDyn *d = scr_dyn_new_obj_with_identity(v, &${rc.retain}, &${sourceAccessor.name});`,
|
||||
|
||||
@@ -920,6 +920,21 @@ export class LlDyn {
|
||||
break;
|
||||
}
|
||||
requireKind(DYN_KIND.OBJ, "dcr");
|
||||
if (shape.fields.length === 0 && !shape.indexValue) {
|
||||
const sourceAccessor = `${this.toDynHelper(t)}_source_access`;
|
||||
const rc = vAdapters(host, t);
|
||||
host.declare(`declare ptr @scr_dyn_obj_source_cast(ptr, ptr, ptr)`);
|
||||
const source = B.tmp();
|
||||
const found = B.tmp();
|
||||
const lSource = B.newLabel("dcr.source");
|
||||
const lCopy = B.newLabel("dcr.copy");
|
||||
B.line(`${source} = call ptr @scr_dyn_obj_source_cast(ptr %d, ptr @${sourceAccessor}, ptr ${rc.retain})`);
|
||||
B.line(`${found} = icmp ne ptr ${source}, null`);
|
||||
B.condBr(found, lSource, lCopy);
|
||||
B.startBlock(lSource);
|
||||
B.terminate(`ret ptr ${source}`);
|
||||
B.startBlock(lCopy);
|
||||
}
|
||||
B.line(`%r0 = call ptr @${mangleRecordNew(t.shapeId)}()`);
|
||||
for (const f of shape.fields) {
|
||||
const fieldWant = host.cstr(dynDesc(f.type, this.host.recordsById, this.host.unionsById));
|
||||
@@ -1398,7 +1413,7 @@ export class LlDyn {
|
||||
const carriesListenerIdentity = shape.fields.some(
|
||||
(f) => f.name === "handleEvent" && f.type.kind === "func",
|
||||
);
|
||||
if (carriesListenerIdentity) {
|
||||
if ((shape.fields.length === 0 && !shape.indexValue) || carriesListenerIdentity) {
|
||||
const rc = vAdapters(host, t);
|
||||
sourceAccessor = {
|
||||
name: `${name}_source_access`,
|
||||
@@ -1664,7 +1679,7 @@ export class LlDyn {
|
||||
);
|
||||
if (sourceAccessor) {
|
||||
this.defs.push(
|
||||
`define internal ptr @${sourceAccessor.name}(ptr %v, i1 %materialize) ${FN_ATTRS} { ; live listener source ${key}`,
|
||||
`define internal ptr @${sourceAccessor.name}(ptr %v, i1 %materialize) ${FN_ATTRS} { ; record source ${key}`,
|
||||
`entry:`,
|
||||
` br i1 %materialize, label %snapshot, label %release`,
|
||||
`snapshot:`,
|
||||
|
||||
@@ -545,6 +545,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"dyn.defineProps": "scr_dyn_define_props",
|
||||
"dyn.defineProperty": "scr_dyn_define_property",
|
||||
"dyn.getOwnPropertyDescriptor": "scr_dyn_get_own_property_descriptor",
|
||||
"dyn.arrayProtoCall": "scr_dyn_array_proto_call",
|
||||
"dyn.typeof": "scr_dyn_typeof",
|
||||
"dyn.objectTag": "scr_dyn_object_tag",
|
||||
"dyn.toString": "scr_dyn_to_string_method",
|
||||
|
||||
@@ -4706,6 +4706,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr {
|
||||
// TestContext surface (t.test/t.skip/t.diagnostic), t.assert.*.
|
||||
lowerer.lowerTestMethodCall(expr, expr.expression) ??
|
||||
lowerer.lowerTimeoutMethodCall(expr, expr.expression) ??
|
||||
lowerArrayPrototypeBorrowCall(lowerer, expr, expr.expression) ??
|
||||
lowerObjectOwnPrototypeCall(lowerer, expr, expr.expression) ??
|
||||
lowerObjectPrototypeCall(lowerer, expr, expr.expression) ??
|
||||
lowerStringPrototypeCall(lowerer, expr, expr.expression) ??
|
||||
@@ -5330,6 +5331,40 @@ function immediateObjectTagReceiver(lowerer: Lowerer, node: ts.Expression): { ta
|
||||
return null;
|
||||
}
|
||||
|
||||
const ARRAY_BORROW_METHODS = new Set([
|
||||
"at", "every", "filter", "find", "findIndex", "forEach", "includes",
|
||||
"indexOf", "lastIndexOf", "map", "reduce", "reduceRight", "some",
|
||||
]);
|
||||
|
||||
function lowerArrayPrototypeBorrowCall(
|
||||
lowerer: Lowerer,
|
||||
call: ts.CallExpression,
|
||||
access: ts.PropertyAccessExpression,
|
||||
): IrExpr | null {
|
||||
if (lowerer.dynamic || !isJsSourceFile(call.getSourceFile()) || call.questionDotToken ||
|
||||
access.questionDotToken || access.name.text !== "call" ||
|
||||
!ts.isPropertyAccessExpression(access.expression) ||
|
||||
call.arguments.some(ts.isSpreadElement)) return null;
|
||||
const method = access.expression;
|
||||
if (method.questionDotToken || !ARRAY_BORROW_METHODS.has(method.name.text) ||
|
||||
!ts.isPropertyAccessExpression(method.expression) ||
|
||||
method.expression.questionDotToken || method.expression.name.text !== "prototype" ||
|
||||
!lowerer.isStdlibGlobal(method.expression.expression, "Array") ||
|
||||
!lowerer.isStdlibMember(method)) return null;
|
||||
const loc = locOf(call);
|
||||
const receiver = call.arguments[0]
|
||||
? lowerer.lowerExprExpecting(call.arguments[0]!, DYN)
|
||||
: { kind: "dynFrom", value: { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc }, type: DYN, loc } as IrExpr;
|
||||
const args = call.arguments.slice(1).map((arg) => lowerer.lowerExprExpecting(arg, DYN));
|
||||
return {
|
||||
kind: "libCall", fn: "dyn.arrayProtoCall", args: [
|
||||
receiver,
|
||||
{ kind: "strLit", value: method.name.text, type: STRING, loc },
|
||||
{ kind: "dynArrLit", elems: args, type: DYN, loc },
|
||||
], type: DYN, loc,
|
||||
};
|
||||
}
|
||||
|
||||
function lowerObjectOwnPrototypeCall(
|
||||
lowerer: Lowerer,
|
||||
call: ts.CallExpression,
|
||||
|
||||
@@ -6742,7 +6742,7 @@ export function lowerBinary(lowerer: Lowerer, expr: ts.BinaryExpression): IrExpr
|
||||
// dyn vs dyn (`context.actual !== context.exact` —
|
||||
// test/common's exit accounting): the runtime's whole-dyn
|
||||
// strict equality — scalars by value, units by kind,
|
||||
// reference kinds by node identity (scr_dyn_strict_eq).
|
||||
// reference kinds by identity (scr_dyn_strict_eq).
|
||||
scalarSide.type.kind === "dyn")
|
||||
) {
|
||||
return {
|
||||
|
||||
@@ -2099,6 +2099,7 @@ export type IrLibFn =
|
||||
| "dyn.defineProps"
|
||||
| "dyn.defineProperty"
|
||||
| "dyn.getOwnPropertyDescriptor"
|
||||
| "dyn.arrayProtoCall"
|
||||
/** Bare `typeof v` on a dyn value AS A STRING (arg: the dyn value,
|
||||
* borrowed; result: an owned string) — the dyn kind's JS answer:
|
||||
* undefined→"undefined", null/object/array/bytes→"object" (JS's oldest
|
||||
@@ -6820,7 +6821,8 @@ export function moduleUsesDynInvoke(mod: IrModule): boolean {
|
||||
return;
|
||||
}
|
||||
const node = v as { kind?: unknown; fn?: unknown };
|
||||
if (node.kind === "dynInvoke" || (node.kind === "libCall" && node.fn === "dyn.defineProps")) {
|
||||
if (node.kind === "dynInvoke" || (node.kind === "libCall" &&
|
||||
(node.fn === "dyn.defineProps" || node.fn === "dyn.arrayProtoCall"))) {
|
||||
found = true;
|
||||
return;
|
||||
}
|
||||
@@ -7896,6 +7898,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"dyn.defineProps",
|
||||
"dyn.defineProperty",
|
||||
"dyn.getOwnPropertyDescriptor",
|
||||
"dyn.arrayProtoCall",
|
||||
"process.chdir",
|
||||
"fs.realpathSync",
|
||||
"fs.realpathNativeSync",
|
||||
|
||||
@@ -127,6 +127,7 @@ export const LIB_FN_SIGS: Record<IrLibFn, { argTypes: (IrType | null)[]; result:
|
||||
"dyn.defineProps": { argTypes: [DYN, DYN], result: DYN },
|
||||
"dyn.defineProperty": { argTypes: [DYN, DYN, DYN], result: DYN },
|
||||
"dyn.getOwnPropertyDescriptor": { argTypes: [DYN, DYN], result: DYN },
|
||||
"dyn.arrayProtoCall": { argTypes: [DYN, STRING, DYN], result: DYN },
|
||||
"dyn.typeof": { argTypes: [DYN], result: STRING },
|
||||
"dyn.objectTag": { argTypes: [DYN], result: STRING },
|
||||
"module.registryInit": { argTypes: [F64], result: VOID },
|
||||
|
||||
@@ -214,6 +214,276 @@ static bool dyn_cb_check(ScrDyn *const *args, size_t argc) {
|
||||
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_JSVAL) return scr_dyn_isl_key_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)) return scr_dyn_retain(scr_dyn_undefined());
|
||||
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;
|
||||
}
|
||||
if (recv->kind == SCR_DYN_JSVAL) return scr_dyn_isl_fence(recv, "Array.prototype borrowed property presence");
|
||||
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[] = "Array.prototype method called on null or undefined";
|
||||
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;
|
||||
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());
|
||||
}
|
||||
|
||||
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 reduce = dyn_borrow_is(method, "reduce");
|
||||
bool reduce_right = dyn_borrow_is(method, "reduceRight");
|
||||
if (!(each || map || filter || some || every || find || find_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; }
|
||||
bool present = find || find_index || dyn_borrow_present(recv, i);
|
||||
if (scr_exc_pending()) { scr_dyn_release(out); return NULL; }
|
||||
if (!present) {
|
||||
if (map) {
|
||||
scr_dyn_release(out);
|
||||
static const char msg[] = "Array.prototype.map over sparse array-like values is not supported yet";
|
||||
scr_throw_error_msg(SCR_ERR_ERROR, msg, sizeof msg - 1);
|
||||
return NULL;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
ScrDyn *item = dyn_borrow_item(recv, i);
|
||||
if (!item) { scr_dyn_release(out); return NULL; }
|
||||
scr_dyn_this_push_dyn(this_arg);
|
||||
ScrDyn *result = dyn_call_cb(cb, item, i, 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 && truthy) return item;
|
||||
if (find_index && truthy) { scr_dyn_release(item); return scr_dyn_new_num((double)i); }
|
||||
}
|
||||
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) return scr_dyn_retain(scr_dyn_undefined());
|
||||
if (find_index) return scr_dyn_new_num(-1);
|
||||
return scr_dyn_retain(scr_dyn_undefined());
|
||||
}
|
||||
|
||||
static bool dyn_name_is(const char *m, const char *n) { return strcmp(m, n) == 0; }
|
||||
|
||||
/* Handle receivers: hand the whole call to the tag's ops (installed by
|
||||
@@ -484,6 +754,9 @@ static ScrDyn *scr_dyn_invoke_impl(
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ((recv->kind == SCR_DYN_NUM || recv->kind == SCR_DYN_BOOL || recv->kind == SCR_DYN_STR) &&
|
||||
dyn_name_is(method, "valueOf")) return scr_dyn_retain(recv);
|
||||
|
||||
if (recv->kind == SCR_DYN_STR) {
|
||||
ScrStr *s = recv->v.str;
|
||||
if (dyn_name_is(method, "slice")) {
|
||||
|
||||
@@ -737,6 +737,13 @@ bool scr_dyn_obj_same_source(const ScrDyn *a, const ScrDyn *b) {
|
||||
a->v.obj.source_identity == b->v.obj.source_identity;
|
||||
}
|
||||
|
||||
void *scr_dyn_obj_source_cast(const ScrDyn *d,
|
||||
ScrDyn *(*source_access)(void *, bool), void *(*source_retain)(void *)) {
|
||||
if (!d || d->kind != SCR_DYN_OBJ || !d->v.obj.source_identity ||
|
||||
d->v.obj.source_access != source_access) return NULL;
|
||||
return source_retain(d->v.obj.source_identity);
|
||||
}
|
||||
|
||||
ScrDyn *scr_dyn_new_obj_null_proto(void) {
|
||||
ScrDyn *d = scr_dyn_alloc(SCR_DYN_OBJ);
|
||||
d->null_proto = true;
|
||||
@@ -3870,10 +3877,16 @@ void *scr_dyn_retain_v(void *d) { return scr_dyn_retain((ScrDyn *)d); }
|
||||
void scr_dyn_release_v(void *d) { scr_dyn_release((ScrDyn *)d); }
|
||||
|
||||
/* JS === over two dyn values: scalars by value (NaN false, ±0 equal via
|
||||
* C ==; strings bytewise), units by kind, everything reference-shaped by
|
||||
* node IDENTITY (the checked-dynamic tree's object identity). Never throws. */
|
||||
* C ==; strings bytewise), units by kind, references by node identity or
|
||||
* by their retained static record source when one exists. Never throws. */
|
||||
bool scr_dyn_strict_eq(const ScrDyn *a, const ScrDyn *b) {
|
||||
if (a->kind != b->kind) return false;
|
||||
if (a->kind != b->kind) {
|
||||
if (a->kind == SCR_DYN_TYPED_REF && b->kind == SCR_DYN_OBJ)
|
||||
return b->v.obj.source_identity && a->v.typed_ref.ptr == b->v.obj.source_identity;
|
||||
if (a->kind == SCR_DYN_OBJ && b->kind == SCR_DYN_TYPED_REF)
|
||||
return a->v.obj.source_identity && a->v.obj.source_identity == b->v.typed_ref.ptr;
|
||||
return false;
|
||||
}
|
||||
switch (a->kind) {
|
||||
case SCR_DYN_UNDEF:
|
||||
case SCR_DYN_NULL: return true;
|
||||
@@ -3882,6 +3895,8 @@ bool scr_dyn_strict_eq(const ScrDyn *a, const ScrDyn *b) {
|
||||
case SCR_DYN_STR:
|
||||
return a->v.str->len == b->v.str->len &&
|
||||
memcmp(a->v.str->data, b->v.str->data, a->v.str->len) == 0;
|
||||
case SCR_DYN_OBJ:
|
||||
return a == b || scr_dyn_obj_same_source(a, b);
|
||||
case SCR_DYN_FUNC:
|
||||
/* The ScrDyn box is a boundary artifact — one closure crossing the
|
||||
* dyn boundary twice is still ONE JS function value, so identity
|
||||
|
||||
@@ -3445,9 +3445,8 @@ struct ScrDyn {
|
||||
size_t cap;
|
||||
ScrDynEntry *entries;
|
||||
/* Optional identity of the typed record that produced this ordinary
|
||||
* deep-copy snapshot. EventTarget uses it to recognize the same
|
||||
* EventListenerObject across repeated static→dyn crossings without
|
||||
* changing generic dyn-object `===` semantics. */
|
||||
* snapshot. Empty records and callback interfaces can retain their
|
||||
* source across repeated static-to-dynamic crossings. */
|
||||
/* Owned through source_access(source_identity, false). Callback
|
||||
* interfaces may request a fresh snapshot with the true arm. */
|
||||
void *source_identity;
|
||||
@@ -3576,14 +3575,15 @@ ScrDyn *scr_dyn_new_num(double n);
|
||||
ScrDyn *scr_dyn_new_str(ScrStr *s);
|
||||
ScrDyn *scr_dyn_new_arr(void);
|
||||
ScrDyn *scr_dyn_new_obj(void);
|
||||
/* The ordinary deep-copy object plus a retained typed source. source_access
|
||||
* releases that source when materialize=false and returns a fresh dyn snapshot
|
||||
* when true. Generic dyn member reads and strict equality remain snapshot/node
|
||||
* based; callback-interface consumers opt into the live arm explicitly. */
|
||||
/* The ordinary object snapshot plus a retained typed source. source_access
|
||||
* releases that source when materialize=false and returns a fresh snapshot
|
||||
* when true. */
|
||||
ScrDyn *scr_dyn_new_obj_with_identity(
|
||||
void *source, void *(*source_retain)(void *),
|
||||
ScrDyn *(*source_access)(void *, bool materialize));
|
||||
bool scr_dyn_obj_same_source(const ScrDyn *a, const ScrDyn *b);
|
||||
void *scr_dyn_obj_source_cast(const ScrDyn *d,
|
||||
ScrDyn *(*source_access)(void *, bool), void *(*source_retain)(void *));
|
||||
/* Object.create(null): the fresh null-prototype dictionary (see the
|
||||
* null_proto flavor flag above). */
|
||||
ScrDyn *scr_dyn_new_obj_null_proto(void);
|
||||
@@ -3650,6 +3650,7 @@ ScrDyn *scr_dyn_arr_at(const ScrDyn *d, double i);
|
||||
void scr_dyn_obj_set(ScrDyn *obj, const char *key, size_t key_len, ScrDyn *value);
|
||||
ScrDyn *scr_dyn_define_property(ScrDyn *target, ScrDyn *key, ScrDyn *descriptor);
|
||||
ScrDyn *scr_dyn_get_own_property_descriptor(ScrDyn *target, ScrDyn *key);
|
||||
ScrDyn *scr_dyn_array_proto_call(ScrDyn *target, ScrStr *method, ScrDyn *args);
|
||||
/* The checked-dynamic keyed WRITE (`h.k = v` on a dyn receiver): OBJ sets
|
||||
* the member (JS: later writes win, insertion order); undefined/null and
|
||||
* non-object kinds throw Node's catchable TypeErrors (strict-mode
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
const arrayLike = { 0: 'a', 1: 'b', 2: 'a', length: 3 };
|
||||
console.log(Array.prototype.indexOf.call(arrayLike, 'a'));
|
||||
console.log(Array.prototype.indexOf.call(arrayLike, 'a', 1));
|
||||
console.log(Array.prototype.lastIndexOf.call(arrayLike, 'a'));
|
||||
console.log(Array.prototype.lastIndexOf.call(arrayLike, 'a', -2));
|
||||
console.log(Array.prototype.includes.call(arrayLike, 'b'));
|
||||
console.log(Array.prototype.at.call(arrayLike, -1));
|
||||
console.log(Array.prototype.indexOf.call('aba', 'b'));
|
||||
console.log(Array.prototype.includes.call('aba', 'z'));
|
||||
try { Array.prototype.indexOf.call(null, 'a'); } catch (error) { console.log(error.name); }
|
||||
|
||||
const numbers = { 0: 2, 1: 3, 2: 2, length: 3 };
|
||||
console.log(Array.prototype.map.call(numbers, (value) => value * 2).join(','));
|
||||
console.log(Array.prototype.filter.call(numbers, (value) => value > 2).join(','));
|
||||
console.log(Array.prototype.some.call(numbers, (value) => value === 3));
|
||||
console.log(Array.prototype.every.call(numbers, (value) => value > 1));
|
||||
console.log(Array.prototype.find.call(numbers, (value) => value === 3));
|
||||
console.log(Array.prototype.findIndex.call(numbers, (value) => value === 3));
|
||||
let sum = 0;
|
||||
Array.prototype.forEach.call(numbers, (value) => { sum += value; });
|
||||
console.log(sum);
|
||||
console.log(Array.prototype.reduce.call(numbers, (acc, value) => acc + value, 0));
|
||||
console.log(Array.prototype.reduceRight.call(numbers, (acc, value) => acc * 10 + value, 0));
|
||||
|
||||
console.log(Array.prototype.indexOf.call({ 0: true, length: 'Infinity' }, true));
|
||||
console.log(Array.prototype.lastIndexOf.call({ 4294967295: 'last', length: 4294967296 }, 'last'));
|
||||
console.log(Array.prototype.some.call({ 0: 11, length: 'Infinity' }, (value) => value > 10));
|
||||
console.log(Array.prototype.every.call({ 0: 9, length: Infinity }, (value) => value > 10));
|
||||
console.log(Array.prototype.every.call({ 0: 11, length: 1 }, function () { return this.valueOf() === 5; }, 5));
|
||||
console.log(Array.prototype.some.call({ 0: 11, length: 1 }, function () { return this.valueOf() === 'yes'; }, 'yes'));
|
||||
let booleanReceiver;
|
||||
Array.prototype.forEach.call({ 0: 11, length: 1 }, function () { booleanReceiver = this.valueOf(); }, false);
|
||||
console.log(booleanReceiver);
|
||||
|
||||
const marker = {};
|
||||
const sparseBorrow = { 5: marker, length: 100 };
|
||||
console.log(Array.prototype.some.call(sparseBorrow, (value, index) => index === 5 && value === marker));
|
||||
const filteredBorrow = Array.prototype.filter.call(sparseBorrow, (value, index) => index === 5 && value === marker);
|
||||
console.log(filteredBorrow.length);
|
||||
console.log(filteredBorrow[0] === marker);
|
||||
console.log(Array.prototype.indexOf.call({ 0: marker, length: 1 }, marker));
|
||||
console.log(Array.prototype.indexOf.call({ 0: marker, length: 1 }, {}));
|
||||
console.log(Array.prototype.lastIndexOf.call({ 0: marker, 4294967295: marker, length: 4294967296 }, marker));
|
||||
|
||||
let fromIndexConversions = 0;
|
||||
const unusedFromIndex = { valueOf() { fromIndexConversions++; return 1; } };
|
||||
console.log(Array.prototype.indexOf.call({ length: 0 }, 'a', unusedFromIndex));
|
||||
console.log(Array.prototype.lastIndexOf.call({ length: 0 }, 'a', unusedFromIndex));
|
||||
console.log(Array.prototype.includes.call({ length: 0 }, 'a', unusedFromIndex));
|
||||
console.log(fromIndexConversions);
|
||||
|
||||
let visits = 0;
|
||||
console.log(Array.prototype.findIndex.call({ length: 2 }, (value, index) => { visits++; return index === 1 && value === undefined; }));
|
||||
console.log(visits);
|
||||
visits = 0;
|
||||
console.log(Array.prototype.find.call({ length: 2 }, (value, index) => { visits++; return index === 1 && value === undefined; }));
|
||||
console.log(visits);
|
||||
|
||||
let presenceChecks = 0;
|
||||
const proxiedArrayLike = new Proxy({ 0: 'proxy', length: 1 }, {
|
||||
has() { presenceChecks++; return true; },
|
||||
get(target, key) { return target[key]; },
|
||||
});
|
||||
console.log(Array.prototype.find.call(proxiedArrayLike, (value) => value === 'proxy'));
|
||||
console.log(Array.prototype.findIndex.call(proxiedArrayLike, (value) => value === 'proxy'));
|
||||
console.log(presenceChecks);
|
||||
console.log(Array.prototype.includes.call(proxiedArrayLike, 'proxy'));
|
||||
console.log(presenceChecks);
|
||||
console.log(Array.prototype.indexOf.call(proxiedArrayLike, 'proxy'));
|
||||
console.log(presenceChecks);
|
||||
Reference in New Issue
Block a user