diff --git a/source/quickjs.c b/source/quickjs.c index 39ff7d99..d76ecbf9 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -4921,15 +4921,8 @@ int JS_SetPropertyStr (JSContext *ctx, JSValue this_obj, const char *prop, JSVal obj_ref.val = this_obj; val_ref.val = val; - /* Create JSValue key from string - try immediate ASCII first */ - int len = strlen (prop); - JSValue key; - if (len <= MIST_ASCII_MAX_LEN) { - key = MIST_TryNewImmediateASCII (prop, len); - if (JS_IsNull (key)) { key = js_new_string8_len (ctx, prop, len); } - } else { - key = js_new_string8_len (ctx, prop, len); - } + /* Create JSValue key from string - use js_key_new for interned stone keys */ + JSValue key = js_key_new (ctx, prop); if (JS_IsException (key)) { JS_DeleteGCRef (ctx, &val_ref); JS_DeleteGCRef (ctx, &obj_ref); @@ -23441,7 +23434,7 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu } /* array(object) - keys */ - if (JS_IsObject (arg) && !JS_IsArray (arg)) { + if (JS_IsRecord (arg)) { /* Return object keys */ return JS_GetOwnPropertyNames (ctx, arg); } @@ -25481,9 +25474,9 @@ static JSValue js_cell_proto (JSContext *ctx, JSValue this_val, int argc, JSValu JSValue obj = argv[0]; - /* Intrinsic arrays return the Object prototype */ + /* Intrinsic arrays do not have prototypes - disrupt */ if (JS_IsArray (obj)) { - return ctx->class_proto[JS_CLASS_OBJECT]; + return JS_ThrowTypeError (ctx, "arrays do not have prototypes"); } if (!JS_IsObject (obj)) return JS_NULL; @@ -30272,6 +30265,33 @@ 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-scan statements to add var/def names to scope before processing bodies. + This allows forward references (e.g., mutual recursion). */ +static void ast_sem_prescan_vars (ASTSemState *st, 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, "var") == 0) { + cJSON *left = cJSON_GetObjectItem (stmt, "left"); + const char *name = cJSON_GetStringValue (cJSON_GetObjectItem (left, "name")); + if (name && !ast_sem_find_var (scope, name)) + ast_sem_add_var (scope, name, 0, kind, scope->function_nr); + } else if (strcmp (kind, "var_list") == 0) { + cJSON *item; + cJSON_ArrayForEach (item, cJSON_GetObjectItem (stmt, "list")) { + const char *ik = cJSON_GetStringValue (cJSON_GetObjectItem (item, "kind")); + if (ik && strcmp (ik, "var") == 0) { + cJSON *left = cJSON_GetObjectItem (item, "left"); + const char *name = cJSON_GetStringValue (cJSON_GetObjectItem (left, "name")); + if (name && !ast_sem_find_var (scope, name)) + ast_sem_add_var (scope, name, 0, ik, 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; @@ -30450,9 +30470,13 @@ 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-scan for forward references (e.g., mutual recursion) */ + cJSON *fn_stmts = cJSON_GetObjectItem (expr, "statements"); + ast_sem_prescan_vars (st, &fn_scope, fn_stmts); + /* Check function body */ cJSON *stmt; - cJSON_ArrayForEach (stmt, cJSON_GetObjectItem (expr, "statements")) { + cJSON_ArrayForEach (stmt, fn_stmts) { ast_sem_check_stmt (st, &fn_scope, stmt); } @@ -30528,7 +30552,9 @@ static void ast_sem_check_stmt (ASTSemState *st, ASTSemScope *scope, cJSON *stmt if (existing && existing->is_const) { ast_sem_error (st, left, "cannot redeclare constant '%s'", name); } - ast_sem_add_var (scope, name, 0, "var", scope->function_nr); + if (!existing || existing->function_nr != scope->function_nr + || scope->block_depth > 0) + ast_sem_add_var (scope, name, 0, "var", scope->function_nr); if (scope->block_depth > 0) { char buf[128]; snprintf (buf, sizeof (buf), "_%s_%d", name, st->block_var_counter++); @@ -30550,17 +30576,20 @@ static void ast_sem_check_stmt (ASTSemState *st, ASTSemScope *scope, cJSON *stmt ASTSemVar *existing = ast_sem_find_var (scope, name); if (existing && existing->is_const) { ast_sem_error (st, left, "cannot redeclare constant '%s'", name); - } else if (existing) { - ast_sem_error (st, left, "cannot redeclare '%s' as constant", name); - } - ast_sem_add_var (scope, name, 1, "def", scope->function_nr); - if (scope->block_depth > 0) { - char buf[128]; - snprintf (buf, sizeof (buf), "_%s_%d", name, st->block_var_counter++); - char *sn = sys_malloc (strlen (buf) + 1); - strcpy (sn, buf); - scope->vars[scope->var_count - 1].scope_name = sn; - cJSON_AddStringToObject (left, "scope_name", sn); + } else if (existing && !existing->is_const && existing->function_nr == scope->function_nr) { + /* Pre-scanned as var, now upgrading to const */ + existing->is_const = 1; + existing->make = "def"; + } else { + ast_sem_add_var (scope, name, 1, "def", scope->function_nr); + if (scope->block_depth > 0) { + char buf[128]; + snprintf (buf, sizeof (buf), "_%s_%d", name, st->block_var_counter++); + char *sn = sys_malloc (strlen (buf) + 1); + strcpy (sn, buf); + scope->vars[scope->var_count - 1].scope_name = sn; + cJSON_AddStringToObject (left, "scope_name", sn); + } } } ast_sem_check_expr (st, scope, cJSON_GetObjectItem (stmt, "right")); @@ -31474,6 +31503,46 @@ static int mach_compile_expr(MachCompState *cs, cJSON *node, int dest) { return dest; } + if (fn_kind && strcmp(fn_kind, "[") == 0) { + /* Method call with bracket notation: obj[expr](args) */ + int save_freereg = cs->freereg; + int base = mach_reserve_reg(cs); /* R(base) = obj */ + if (dest < 0) dest = base; + int key_reg = mach_reserve_reg(cs); /* R(base+1) = key */ + + /* Compile obj into base */ + cJSON *obj_expr = cJSON_GetObjectItem(fn_expr, "expression"); + if (!obj_expr) obj_expr = cJSON_GetObjectItem(fn_expr, "left"); + int obj_r = mach_compile_expr(cs, obj_expr, base); + if (obj_r != base) + mach_emit(cs, MACH_ABC(MACH_MOVE, base, obj_r, 0)); + + /* Compile key expr into R(base+1) */ + cJSON *idx_expr = cJSON_GetObjectItem(fn_expr, "index"); + if (!idx_expr) idx_expr = cJSON_GetObjectItem(fn_expr, "right"); + int kr = mach_compile_expr(cs, idx_expr, key_reg); + if (kr != key_reg) + mach_emit(cs, MACH_ABC(MACH_MOVE, key_reg, kr, 0)); + + /* Compile args into R(base+2)..R(base+1+nargs) */ + for (int i = 0; i < nargs; i++) { + int arg_reg = mach_reserve_reg(cs); + cJSON *arg = cJSON_GetArrayItem(args, i); + int r = mach_compile_expr(cs, arg, arg_reg); + if (r != arg_reg) + mach_emit(cs, MACH_ABC(MACH_MOVE, arg_reg, r, 0)); + } + + /* C=0xFF signals key is in R(base+1) */ + mach_emit(cs, MACH_ABC(MACH_CALLMETHOD, base, nargs, 0xFF)); + mach_free_reg_to(cs, save_freereg); + if (dest >= 0 && dest != base) + mach_emit(cs, MACH_ABC(MACH_MOVE, dest, base, 0)); + else + dest = base; + return dest; + } + /* Save freereg so we can allocate consecutive regs for call */ int save_freereg = cs->freereg; @@ -33436,15 +33505,14 @@ static JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, case MACH_CALLMETHOD: { /* Method call: R(A)=obj, B=nargs in R(A+2)..R(A+1+B), C=cpool key index - Result stored in R(A). + Result stored in R(A). C=0xFF means key is in R(A+1). If obj is a function (proxy): call obj(key_str, [args...]) Else (record): get property, call property(obj_as_this, args...) */ int base = a; int nargs = b; - JSValue obj = frame->slots[base]; - JSValue key = code->cpool[c]; + JSValue key = (c == 0xFF) ? frame->slots[base + 1] : code->cpool[c]; - if (JS_IsFunction(obj)) { + if (JS_IsFunction(frame->slots[base]) && JS_IsText(key)) { /* Proxy call: obj(name, [args...]) */ JSValue arr = JS_NewArray(ctx); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); @@ -33467,6 +33535,10 @@ static JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, ctx->reg_current_frame = JS_NULL; if (JS_IsException(ret)) goto disrupt; frame->slots[base] = ret; + } else if (JS_IsFunction(frame->slots[base])) { + /* Non-proxy function with non-text key: disrupt */ + JS_ThrowTypeError(ctx, "cannot use bracket notation on non-proxy function"); + goto disrupt; } else { /* Record method call: get property, call with this=obj */ JSValue method = JS_GetProperty(ctx, frame->slots[base], key); @@ -33693,7 +33765,7 @@ static JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, break; } if (JS_IsNull(frame->caller)) { - result = JS_NULL; + result = JS_EXCEPTION; goto done; } /* Unwind one frame — read caller's saved pc from its address field */