This commit is contained in:
2026-02-07 12:50:46 -06:00
parent 3621b1ef33
commit 45ce76aef7

View File

@@ -25481,9 +25481,10 @@ static JSValue js_cell_proto (JSContext *ctx, JSValue this_val, int argc, JSValu
JSValue obj = argv[0];
/* Intrinsic arrays return the Object prototype */
/* Arrays cannot have prototypes */
if (JS_IsArray (obj)) {
return ctx->class_proto[JS_CLASS_OBJECT];
JS_ThrowTypeError (ctx, "cannot get prototype of array");
return JS_EXCEPTION;
}
if (!JS_IsObject (obj)) return JS_NULL;
@@ -30272,6 +30273,21 @@ static int ast_sem_in_loop (ASTSemScope *scope) {
static void ast_sem_check_expr (ASTSemState *st, ASTSemScope *scope, cJSON *expr);
static void ast_sem_check_stmt (ASTSemState *st, ASTSemScope *scope, cJSON *stmt);
/* Pre-register function declarations in a statement list (hoisting).
This allows mutual recursion: function A can reference function B declared later. */
static void ast_sem_predeclare_vars (ASTSemScope *scope, cJSON *stmts) {
cJSON *stmt;
cJSON_ArrayForEach (stmt, stmts) {
const char *kind = cJSON_GetStringValue (cJSON_GetObjectItem (stmt, "kind"));
if (!kind) continue;
if (strcmp (kind, "function") == 0) {
const char *name = cJSON_GetStringValue (cJSON_GetObjectItem (stmt, "name"));
if (name && !ast_sem_find_var (scope, name))
ast_sem_add_var (scope, name, 0, "function", scope->function_nr);
}
}
}
/* Check whether an expression is being assigned to (=, +=, etc.) */
static void ast_sem_check_assign_target (ASTSemState *st, ASTSemScope *scope, cJSON *left) {
if (!left) return;
@@ -30452,6 +30468,9 @@ static void ast_sem_check_expr (ASTSemState *st, ASTSemScope *scope, cJSON *expr
if (def_val) ast_sem_check_expr (st, &fn_scope, def_val);
}
/* Pre-register all var/def/function declarations for mutual recursion */
ast_sem_predeclare_vars (&fn_scope, cJSON_GetObjectItem (expr, "statements"));
/* Check function body */
cJSON *stmt;
cJSON_ArrayForEach (stmt, cJSON_GetObjectItem (expr, "statements")) {
@@ -30731,6 +30750,9 @@ static void ast_sem_check_stmt (ASTSemState *st, ASTSemScope *scope, cJSON *stmt
if (def_val) ast_sem_check_expr (st, &fn_scope, def_val);
}
/* Pre-register all var/def/function declarations for mutual recursion */
ast_sem_predeclare_vars (&fn_scope, cJSON_GetObjectItem (stmt, "statements"));
cJSON *s2;
cJSON_ArrayForEach (s2, cJSON_GetObjectItem (stmt, "statements")) {
ast_sem_check_stmt (st, &fn_scope, s2);
@@ -34018,18 +34040,18 @@ static void mach_gen_emit_call (MachGenState *s, int dest, int func_slot, cJSON
}
static void mach_gen_emit_call_method (MachGenState *s, int dest, int obj, const char *prop, cJSON *args) {
int func_slot = mach_gen_alloc_slot (s);
mach_gen_emit_get_prop (s, func_slot, obj, prop);
int argc = cJSON_GetArraySize (args);
int frame_slot = mach_gen_alloc_slot (s);
mach_gen_emit_3 (s, "frame", frame_slot, func_slot, argc);
mach_gen_emit_3 (s, "setarg", frame_slot, 0, obj);
int arg_idx = 1;
/* Emit a single callmethod instruction:
["callmethod", dest, obj_reg, "method_name", arg_reg0, arg_reg1, ...] */
cJSON *instr = cJSON_CreateArray ();
cJSON_AddItemToArray (instr, cJSON_CreateString ("callmethod"));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (dest));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (obj));
cJSON_AddItemToArray (instr, cJSON_CreateString (prop));
cJSON *arg;
cJSON_ArrayForEach (arg, args) {
mach_gen_emit_3 (s, "setarg", frame_slot, arg_idx++, arg->valueint);
cJSON_AddItemToArray (instr, cJSON_CreateNumber (arg->valueint));
}
mach_gen_emit_2 (s, "invoke", frame_slot, dest);
mach_gen_add_instr (s, instr);
}
static void mach_gen_emit_go_call (MachGenState *s, int func_slot, cJSON *args) {
@@ -34315,6 +34337,62 @@ static int mach_gen_expr (MachGenState *s, cJSON *expr, int target) {
mach_gen_emit_const_str (s, slot, val ? val : "");
return slot;
}
/* Template literal with expressions: kind="text literal"
Format: value = "hello {0} world {1}", list = [expr0, expr1]
Compile as: format(fmt_string, [expr0, expr1, ...]) */
if (strcmp (kind, "text literal") == 0) {
cJSON *list = cJSON_GetObjectItem (expr, "list");
int nexpr = list ? cJSON_GetArraySize (list) : 0;
/* Evaluate each expression */
int *expr_slots = NULL;
if (nexpr > 0) {
expr_slots = alloca (nexpr * sizeof (int));
for (int i = 0; i < nexpr; i++) {
cJSON *item = cJSON_GetArrayItem (list, i);
expr_slots[i] = mach_gen_expr (s, item, -1);
}
}
/* Create array from expression results using the "array" opcode */
int arr_slot = mach_gen_alloc_slot (s);
{
cJSON *instr = cJSON_CreateArray ();
cJSON_AddItemToArray (instr, cJSON_CreateString ("array"));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (arr_slot));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (nexpr));
for (int i = 0; i < nexpr; i++)
cJSON_AddItemToArray (instr, cJSON_CreateNumber (expr_slots[i]));
mach_gen_add_instr (s, instr);
}
/* Load format intrinsic */
int fmt_func_slot = mach_gen_find_intrinsic (s, "format");
if (fmt_func_slot < 0) {
fmt_func_slot = mach_gen_alloc_slot (s);
cJSON *instr = cJSON_CreateArray ();
cJSON_AddItemToArray (instr, cJSON_CreateString ("access"));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (fmt_func_slot));
cJSON *lit = cJSON_CreateObject ();
cJSON_AddStringToObject (lit, "kind", "name");
cJSON_AddStringToObject (lit, "name", "format");
cJSON_AddStringToObject (lit, "make", "intrinsic");
cJSON_AddItemToArray (instr, lit);
mach_gen_add_instr (s, instr);
}
/* Load format string */
const char *fmt = cJSON_GetStringValue (cJSON_GetObjectItem (expr, "value"));
int fmt_str_slot = mach_gen_alloc_slot (s);
mach_gen_emit_const_str (s, fmt_str_slot, fmt ? fmt : "");
/* Call format(fmt_str, array) */
int result_slot = target >= 0 ? target : mach_gen_alloc_slot (s);
{
cJSON *call_args = cJSON_CreateArray ();
cJSON_AddItemToArray (call_args, cJSON_CreateNumber (fmt_str_slot));
cJSON_AddItemToArray (call_args, cJSON_CreateNumber (arr_slot));
mach_gen_emit_call (s, result_slot, fmt_func_slot, call_args);
cJSON_Delete (call_args);
}
return result_slot;
}
if (strcmp (kind, "regexp") == 0) {
int slot = target >= 0 ? target : mach_gen_alloc_slot (s);
const char *pattern = cJSON_GetStringValue (cJSON_GetObjectItem (expr, "pattern"));
@@ -36165,6 +36243,14 @@ static JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj,
int dest = (int)a1->valuedouble;
int obj_reg = (int)a2->valuedouble;
JSValue obj = frame->slots[obj_reg];
if (JS_IsFunction(obj)) {
JSFunction *fn_chk = JS_VALUE_GET_FUNCTION(obj);
if (fn_chk->length != 2) {
JS_ThrowTypeError(ctx, "cannot read property of non-proxy function");
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
goto disrupt;
}
}
JSValue val;
if (cJSON_IsString(a3)) {
JSValue key = JS_NewString(ctx, a3->valuestring);
@@ -36179,6 +36265,7 @@ static JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj,
val = JS_GetProperty(ctx, obj, idx);
}
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(val)) goto disrupt;
frame->slots[dest] = val;
}
else if (strcmp(op, "store") == 0) {
@@ -36186,20 +36273,29 @@ static JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj,
int val_reg = (int)a2->valuedouble;
JSValue obj = frame->slots[obj_reg];
JSValue val = frame->slots[val_reg];
if (JS_IsFunction(obj)) {
JS_ThrowTypeError(ctx, "cannot set property of function");
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
goto disrupt;
}
if (cJSON_IsString(a3)) {
JSValue key = JS_NewString(ctx, a3->valuestring);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
obj = frame->slots[obj_reg];
val = frame->slots[val_reg];
JS_SetProperty(ctx, obj, key, val);
int ret = JS_SetProperty(ctx, obj, key, val);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (ret < 0) goto disrupt;
} else {
JSValue idx = frame->slots[(int)a3->valuedouble];
int ret;
if (JS_IsInt(idx))
JS_SetPropertyUint32(ctx, obj, JS_VALUE_GET_INT(idx), val);
ret = JS_SetPropertyUint32(ctx, obj, JS_VALUE_GET_INT(idx), val);
else
JS_SetProperty(ctx, obj, idx, val);
ret = JS_SetProperty(ctx, obj, idx, val);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (ret < 0) goto disrupt;
}
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
}
else if (strcmp(op, "delete") == 0) {
int dest = (int)a1->valuedouble;
@@ -36215,6 +36311,7 @@ static JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj,
}
int ret = JS_DeleteProperty(ctx, obj, key);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (ret < 0) goto disrupt;
frame->slots[dest] = JS_NewBool(ctx, ret >= 0);
}
@@ -36326,6 +36423,99 @@ static JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj,
}
}
/* ---- Method call (handles function proxies) ---- */
else if (strcmp(op, "callmethod") == 0) {
/* ["callmethod", dest, obj_reg, "method_name", arg0_reg, arg1_reg, ...] */
int dest = (int)a1->valuedouble;
int obj_reg = (int)a2->valuedouble;
JSValue obj = frame->slots[obj_reg];
const char *method_name = a3->valuestring;
/* Count arg registers (items after a3) */
int nargs = 0;
for (cJSON *p = a3->next; p && !cJSON_IsString(p); p = p->next)
nargs++;
if (JS_IsFunction(obj)) {
/* Proxy call: obj(name, [args...]) */
JSValue key = JS_NewString(ctx, method_name);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
JSValue arr = JS_NewArray(ctx);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(arr)) goto disrupt;
frame->slots[dest] = arr; /* protect from GC */
cJSON *p = a3->next;
for (int i = 0; i < nargs; i++, p = p->next) {
if (cJSON_IsString(p)) break; /* hit line/col */
int areg = (int)p->valuedouble;
JS_SetPropertyUint32(ctx, frame->slots[dest], i, frame->slots[areg]);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
}
int vs_base = ctx->value_stack_top;
ctx->value_stack[vs_base] = key;
ctx->value_stack[vs_base + 1] = frame->slots[dest];
ctx->value_stack_top = vs_base + 2;
ctx->reg_current_frame = frame_ref.val;
ctx->rt->current_register_pc = pc > 0 ? pc - 1 : 0;
JSValue ret = JS_CallInternal(ctx, frame->slots[obj_reg], JS_NULL, 2, &ctx->value_stack[vs_base], 0);
ctx->value_stack_top = vs_base;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
ctx->reg_current_frame = JS_NULL;
if (JS_IsException(ret)) goto disrupt;
frame->slots[dest] = ret;
} else {
/* Record method call: get property, call with this=obj */
JSValue key = JS_NewString(ctx, method_name);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
JSValue method = JS_GetProperty(ctx, frame->slots[obj_reg], key);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(method)) goto disrupt;
if (!JS_IsFunction(method)) {
frame->slots[dest] = JS_NULL;
} else {
JSFunction *fn = JS_VALUE_GET_FUNCTION(method);
if (fn->kind == JS_FUNC_KIND_MCODE) {
/* mcode function — set up frame and jump */
JSFrameRegister *new_frame = alloc_frame_register(ctx, fn->u.mcode.code->nr_slots);
if (!new_frame) {
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
goto disrupt;
}
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
new_frame->function = method;
new_frame->slots[0] = frame->slots[obj_reg]; /* this */
cJSON *p = a3->next;
for (int i = 0; i < nargs && i < fn->u.mcode.code->nr_slots - 1; i++, p = p->next) {
if (cJSON_IsString(p)) break;
new_frame->slots[1 + i] = frame->slots[(int)p->valuedouble];
}
frame->address = JS_NewInt32(ctx, (pc << 16) | dest);
new_frame->caller = JS_MKPTR(frame);
frame = new_frame;
frame_ref.val = JS_MKPTR(frame);
code = fn->u.mcode.code;
pc = 0;
} else {
/* C or bytecode function */
int vs_base = ctx->value_stack_top;
cJSON *p = a3->next;
for (int i = 0; i < nargs; i++, p = p->next) {
if (cJSON_IsString(p)) break;
ctx->value_stack[vs_base + i] = frame->slots[(int)p->valuedouble];
}
ctx->value_stack_top = vs_base + nargs;
ctx->reg_current_frame = frame_ref.val;
ctx->rt->current_register_pc = pc > 0 ? pc - 1 : 0;
JSValue ret = JS_Call(ctx, method, frame->slots[obj_reg], nargs, &ctx->value_stack[vs_base]);
ctx->value_stack_top = vs_base;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
ctx->reg_current_frame = JS_NULL;
if (JS_IsException(ret)) goto disrupt;
frame->slots[dest] = ret;
}
}
}
}
/* ---- Tail calls ---- */
else if (strcmp(op, "goframe") == 0) {
int frame_reg = (int)a1->valuedouble;
@@ -36527,7 +36717,8 @@ static JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj,
/* ---- Push (append to array) ---- */
else if (strcmp(op, "push") == 0) {
int arr_slot = (int)a1->valuedouble;
JSValue val = frame->slots[(int)a2->valuedouble];
int val_slot = (int)a2->valuedouble;
JSValue val = frame->slots[val_slot];
if (!JS_IsArray(frame->slots[arr_slot])) { goto disrupt; }
JSGCRef arr_gc;
JS_PushGCRef(ctx, &arr_gc);
@@ -36536,7 +36727,15 @@ static JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj,
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
JS_PopGCRef(ctx, &arr_gc);
if (rc < 0) goto disrupt;
if (arr_gc.val != frame->slots[arr_slot]) frame->slots[arr_slot] = arr_gc.val;
if (arr_gc.val != frame->slots[arr_slot]) {
frame->slots[arr_slot] = arr_gc.val;
/* If we pushed the array onto itself and it was relocated,
fix the dangling reference in the last element */
if (val_slot == arr_slot) {
JSArray *a = JS_VALUE_GET_ARRAY(arr_gc.val);
a->values[a->len - 1] = arr_gc.val;
}
}
}
/* ---- Pop (remove last from array) ---- */