diff --git a/source/quickjs.c b/source/quickjs.c index f7269997..d96f4ccd 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -28684,6 +28684,17 @@ static cJSON *ast_parse_statement (ASTParseState *s) { ast_node_end (s, node, s->buf_ptr); } break; + case TOK_GO: { + node = ast_node (s, "go", start); + ast_next_token (s); + if (s->token_val != ';' && s->token_val != '}' && !s->got_lf) { + cJSON *expr = ast_parse_expr (s); + cJSON_AddItemToObject (node, "expression", expr); + } + if (s->token_val == ';') ast_next_token (s); + ast_node_end (s, node, s->buf_ptr); + } break; + case TOK_THROW: { node = ast_node (s, "throw", start); ast_next_token (s); @@ -28961,6 +28972,7 @@ typedef struct MachGenState { const char *loop_continue; int is_arrow; + int has_inner_function; /* Set if function contains nested functions */ /* Error tracking */ cJSON *errors; @@ -29170,6 +29182,39 @@ static void mach_scan_vars (MachGenState *s, cJSON *node) { /* NOTE: Do NOT recurse into nested function nodes - they have their own scope */ } +/* Recursively scan for inner function definitions */ +static void mach_scan_inner_functions (MachGenState *s, cJSON *node) { + if (!node || s->has_inner_function) return; + + const char *kind = cJSON_GetStringValue (cJSON_GetObjectItem (node, "kind")); + if (!kind) return; + + /* Found an inner function */ + if (strcmp (kind, "function") == 0 || strcmp (kind, "=>") == 0) { + s->has_inner_function = 1; + return; + } + + /* Scan child nodes */ + cJSON *expr = cJSON_GetObjectItem (node, "expression"); + cJSON *left = cJSON_GetObjectItem (node, "left"); + cJSON *right = cJSON_GetObjectItem (node, "right"); + cJSON *list = cJSON_GetObjectItem (node, "list"); + cJSON *stmts = cJSON_GetObjectItem (node, "statements"); + cJSON *then_stmts = cJSON_GetObjectItem (node, "then"); + cJSON *else_stmts = cJSON_GetObjectItem (node, "else"); + + if (expr) mach_scan_inner_functions (s, expr); + if (left) mach_scan_inner_functions (s, left); + if (right) mach_scan_inner_functions (s, right); + + cJSON *item; + if (list) cJSON_ArrayForEach (item, list) mach_scan_inner_functions (s, item); + if (stmts) cJSON_ArrayForEach (item, stmts) mach_scan_inner_functions (s, item); + if (then_stmts) cJSON_ArrayForEach (item, then_stmts) mach_scan_inner_functions (s, item); + if (else_stmts) cJSON_ArrayForEach (item, else_stmts) mach_scan_inner_functions (s, item); +} + /* Resolve a variable - returns resolution type and slot/depth info */ static MachResolveResult mach_resolve_var (MachGenState *s, const char *name) { MachResolveResult result = {MACH_VAR_UNBOUND, -1, 0}; @@ -29449,6 +29494,46 @@ static void mach_emit_call_method (MachGenState *s, int dest, int obj, const cha mach_emit_2 (s, "invoke", frame_slot, dest); } +/* Emit tail call using goframe/goinvoke sequence */ +static void mach_emit_go_call (MachGenState *s, int func_slot, cJSON *args) { + int argc = cJSON_GetArraySize (args); + int frame_slot = mach_alloc_slot (s); + + mach_emit_3 (s, "goframe", frame_slot, func_slot, argc); + + int null_slot = mach_alloc_slot (s); + mach_emit_1 (s, "null", null_slot); + mach_emit_2 (s, "set_this", frame_slot, null_slot); + + int arg_idx = 1; + cJSON *arg; + cJSON_ArrayForEach (arg, args) { + mach_emit_3 (s, "arg", frame_slot, arg_idx++, arg->valueint); + } + + mach_emit_1 (s, "goinvoke", frame_slot); +} + +/* Emit method tail call using goframe/goinvoke sequence */ +static void mach_emit_go_call_method (MachGenState *s, int obj, const char *prop, cJSON *args) { + int func_slot = mach_alloc_slot (s); + mach_emit_get_prop (s, func_slot, obj, prop); + + int argc = cJSON_GetArraySize (args); + int frame_slot = mach_alloc_slot (s); + + mach_emit_3 (s, "goframe", frame_slot, func_slot, argc); + mach_emit_2 (s, "set_this", frame_slot, obj); + + int arg_idx = 1; + cJSON *arg; + cJSON_ArrayForEach (arg, args) { + mach_emit_3 (s, "arg", frame_slot, arg_idx++, arg->valueint); + } + + mach_emit_1 (s, "goinvoke", frame_slot); +} + /* Map JS operator to opcode string */ static const char *mach_binop_to_string (const char *kind) { if (strcmp (kind, "+") == 0) return "add"; @@ -30201,6 +30286,55 @@ static void mach_gen_statement (MachGenState *s, cJSON *stmt) { return; } + /* Go (tail call) statement */ + if (strcmp (kind, "go") == 0) { + if (s->has_inner_function) { + mach_error (s, stmt, "'go' cannot be used in functions containing inner functions"); + return; + } + + cJSON *call_expr = cJSON_GetObjectItem (stmt, "expression"); + if (!call_expr) { + mach_error (s, stmt, "'go' requires a function call expression"); + return; + } + + const char *call_kind = cJSON_GetStringValue (cJSON_GetObjectItem (call_expr, "kind")); + if (!call_kind || strcmp (call_kind, "(") != 0) { + mach_error (s, stmt, "'go' requires a function call expression"); + return; + } + + cJSON *callee = cJSON_GetObjectItem (call_expr, "expression"); + cJSON *args_list = cJSON_GetObjectItem (call_expr, "list"); + + /* Compile arguments first */ + cJSON *arg_slots = cJSON_CreateArray (); + cJSON *arg; + cJSON_ArrayForEach (arg, args_list) { + int arg_slot = mach_gen_expr (s, arg); + cJSON_AddItemToArray (arg_slots, cJSON_CreateNumber (arg_slot)); + } + + const char *callee_kind = cJSON_GetStringValue (cJSON_GetObjectItem (callee, "kind")); + + if (callee_kind && strcmp (callee_kind, ".") == 0) { + /* Method tail call: go obj.method(args) */ + cJSON *obj_node = cJSON_GetObjectItem (callee, "left"); + cJSON *prop_node = cJSON_GetObjectItem (callee, "right"); + const char *prop = cJSON_GetStringValue (prop_node); + int obj_slot = mach_gen_expr (s, obj_node); + mach_emit_go_call_method (s, obj_slot, prop, arg_slots); + } else { + /* Regular tail call: go func(args) */ + int func_slot = mach_gen_expr (s, callee); + mach_emit_go_call (s, func_slot, arg_slots); + } + + cJSON_Delete (arg_slots); + return; + } + /* Throw statement */ if (strcmp (kind, "throw") == 0) { cJSON *expr = cJSON_GetObjectItem (stmt, "expression"); @@ -30445,6 +30579,11 @@ static cJSON *mach_gen_function (MachGenState *parent, cJSON *func_node) { mach_scan_vars (&s, stmt); } + /* Scan for inner function definitions (for 'go' validation) */ + cJSON_ArrayForEach (stmt, stmts) { + mach_scan_inner_functions (&s, stmt); + } + /* Adjust temp slot start after all locals are allocated */ s.next_temp_slot = 1 + s.nr_args + s.nr_local_slots; if (s.next_temp_slot > s.max_slot) s.max_slot = s.next_temp_slot;