diff --git a/source/quickjs.c b/source/quickjs.c index 50afcabb..3d435f54 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -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) ---- */