summaryrefslogtreecommitdiff
path: root/src/parser/parser_expr.c
diff options
context:
space:
mode:
authorZuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian>2026-01-11 15:11:00 +0000
committerZuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian>2026-01-11 15:11:00 +0000
commit55247a3f12a9eee7ba3fd7ca6d8fcea7a82c20f3 (patch)
treea2a71e2eb8ca0b2c483518c1902d89d18709c9ab /src/parser/parser_expr.c
parent2e7abed7cfe84a2c0df371cde35f8f68cfdca16c (diff)
Added src/ folder. Now I will add the rest.
Diffstat (limited to 'src/parser/parser_expr.c')
-rw-r--r--src/parser/parser_expr.c3627
1 files changed, 3627 insertions, 0 deletions
diff --git a/src/parser/parser_expr.c b/src/parser/parser_expr.c
new file mode 100644
index 0000000..e12c837
--- /dev/null
+++ b/src/parser/parser_expr.c
@@ -0,0 +1,3627 @@
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include "parser.h"
+#include "../zen/zen_facts.h"
+
+Type *get_field_type(ParserContext *ctx, Type *struct_type, const char *field_name);
+
+static int type_is_unsigned(Type *t)
+{
+ if (!t)
+ {
+ return 0;
+ }
+
+ return (t->kind == TYPE_U8 || t->kind == TYPE_U16 || t->kind == TYPE_U32 ||
+ t->kind == TYPE_U64 || t->kind == TYPE_USIZE || t->kind == TYPE_BYTE ||
+ t->kind == TYPE_U128 || t->kind == TYPE_UINT ||
+ (t->kind == TYPE_STRUCT && t->name &&
+ (0 == strcmp(t->name, "uint8_t") || 0 == strcmp(t->name, "uint16_t") ||
+ 0 == strcmp(t->name, "uint32_t") || 0 == strcmp(t->name, "uint64_t") ||
+ 0 == strcmp(t->name, "size_t"))));
+}
+
+static void check_format_string(ASTNode *call, Token t)
+{
+ if (call->type != NODE_EXPR_CALL)
+ {
+ return;
+ }
+ ASTNode *callee = call->call.callee;
+ if (callee->type != NODE_EXPR_VAR)
+ {
+ return;
+ }
+
+ char *fname = callee->var_ref.name;
+ if (!fname)
+ {
+ return;
+ }
+
+ if (strcmp(fname, "printf") != 0 && strcmp(fname, "sprintf") != 0 &&
+ strcmp(fname, "fprintf") != 0 && strcmp(fname, "dprintf") != 0)
+ {
+ return;
+ }
+
+ int fmt_idx = 0;
+ if (strcmp(fname, "fprintf") == 0 || strcmp(fname, "sprintf") == 0 ||
+ strcmp(fname, "dprintf") == 0)
+ {
+ fmt_idx = 1;
+ }
+
+ ASTNode *args = call->call.args;
+ ASTNode *fmt_arg = args;
+ for (int i = 0; i < fmt_idx; i++)
+ {
+ if (!fmt_arg)
+ {
+ return;
+ }
+ fmt_arg = fmt_arg->next;
+ }
+ if (!fmt_arg)
+ {
+ return;
+ }
+
+ if (fmt_arg->type != NODE_EXPR_LITERAL || fmt_arg->literal.type_kind != TOK_STRING)
+ {
+ return;
+ }
+
+ const char *fmt = fmt_arg->literal.string_val;
+
+ ASTNode *curr_arg = fmt_arg->next;
+ int arg_num = fmt_idx + 2;
+
+ for (int i = 0; fmt[i]; i++)
+ {
+ if (fmt[i] == '%')
+ {
+ i++;
+ if (fmt[i] == 0)
+ {
+ break;
+ }
+ if (fmt[i] == '%')
+ {
+ continue;
+ }
+
+ // Flags.
+ while (fmt[i] == '-' || fmt[i] == '+' || fmt[i] == ' ' || fmt[i] == '#' ||
+ fmt[i] == '0')
+ {
+ i++;
+ }
+
+ // Width.
+ while (isdigit(fmt[i]))
+ {
+ i++;
+ }
+
+ if (fmt[i] == '*')
+ {
+ i++;
+ if (!curr_arg)
+ {
+ warn_format_string(t, arg_num, "width(int)", "missing");
+ }
+ else
+ {
+ /* check int */
+ curr_arg = curr_arg->next;
+ arg_num++;
+ }
+ }
+
+ // Precision.
+ if (fmt[i] == '.')
+ {
+ i++;
+ while (isdigit(fmt[i]))
+ {
+ i++;
+ }
+
+ if (fmt[i] == '*')
+ {
+ i++;
+ if (!curr_arg)
+ {
+ warn_format_string(t, arg_num, "precision(int)", "missing");
+ }
+ else
+ {
+ /* check int */
+ curr_arg = curr_arg->next;
+ arg_num++;
+ }
+ }
+ }
+
+ // Length.
+ if (fmt[i] == 'h' || fmt[i] == 'l' || fmt[i] == 'L' || fmt[i] == 'z' || fmt[i] == 'j' ||
+ fmt[i] == 't')
+ {
+ if (fmt[i] == 'h' && fmt[i + 1] == 'h')
+ {
+ i++;
+ }
+ else if (fmt[i] == 'l' && fmt[i + 1] == 'l')
+ {
+ i++;
+ }
+ i++;
+ }
+
+ char spec = fmt[i];
+
+ if (!curr_arg)
+ {
+ warn_format_string(t, arg_num, "argument", "missing");
+ continue;
+ }
+
+ Type *vt = curr_arg->type_info;
+ char *got_type = vt ? type_to_string(vt) : "?";
+
+ if (spec == 'd' || spec == 'i' || spec == 'u' || spec == 'x' || spec == 'X' ||
+ spec == 'o')
+ {
+ if (vt && vt->kind != TYPE_INT && vt->kind != TYPE_I64 && !type_is_unsigned(vt) &&
+ vt->kind != TYPE_CHAR)
+ {
+ warn_format_string(t, arg_num, "integer", got_type);
+ }
+ }
+ else if (spec == 's')
+ {
+ if (vt && vt->kind != TYPE_STRING && vt->kind != TYPE_POINTER &&
+ vt->kind != TYPE_ARRAY)
+ {
+ warn_format_string(t, arg_num, "string", got_type);
+ }
+ }
+ else if (spec == 'f' || spec == 'F' || spec == 'e' || spec == 'E' || spec == 'g' ||
+ spec == 'G')
+ {
+ if (vt && vt->kind != TYPE_FLOAT && vt->kind != TYPE_F64)
+ {
+ warn_format_string(t, arg_num, "float", got_type);
+ }
+ }
+ else if (spec == 'p')
+ {
+ if (vt && vt->kind != TYPE_POINTER && vt->kind != TYPE_ARRAY)
+ {
+ warn_format_string(t, arg_num, "pointer", got_type);
+ }
+ }
+
+ curr_arg = curr_arg->next;
+ arg_num++;
+ }
+ }
+}
+
+ASTNode *parse_expression(ParserContext *ctx, Lexer *l)
+{
+ return parse_expr_prec(ctx, l, PREC_NONE);
+}
+
+Precedence get_token_precedence(Token t)
+{
+ if (t.type == TOK_INT || t.type == TOK_FLOAT || t.type == TOK_STRING || t.type == TOK_IDENT ||
+ t.type == TOK_FSTRING)
+ {
+ return PREC_NONE;
+ }
+
+ if (t.type == TOK_QUESTION)
+ {
+ return PREC_CALL;
+ }
+
+ if (t.type == TOK_ARROW && t.start[0] == '-')
+ {
+ return PREC_CALL;
+ }
+
+ if (t.type == TOK_Q_DOT)
+ {
+ return PREC_CALL;
+ }
+
+ if (t.type == TOK_QQ)
+ {
+ return PREC_OR;
+ }
+
+ if (t.type == TOK_QQ_EQ)
+ {
+ return PREC_ASSIGNMENT;
+ }
+
+ if (t.type == TOK_PIPE)
+ {
+ return PREC_TERM;
+ }
+
+ if (t.type == TOK_LANGLE || t.type == TOK_RANGLE)
+ {
+ return PREC_COMPARISON;
+ }
+
+ if (t.type == TOK_OP)
+ {
+ if (is_token(t, "=") || is_token(t, "+=") || is_token(t, "-=") || is_token(t, "*=") ||
+ is_token(t, "/=") || is_token(t, "%=") || is_token(t, "|=") || is_token(t, "&=") ||
+ is_token(t, "^=") || is_token(t, "<<=") || is_token(t, ">>="))
+ {
+ return PREC_ASSIGNMENT;
+ }
+
+ if (is_token(t, "||") || is_token(t, "or"))
+ {
+ return PREC_OR;
+ }
+
+ if (is_token(t, "&&") || is_token(t, "and"))
+ {
+ return PREC_AND;
+ }
+
+ if (is_token(t, "|"))
+ {
+ return PREC_TERM;
+ }
+
+ if (is_token(t, "^"))
+ {
+ return PREC_TERM;
+ }
+
+ if (is_token(t, "&"))
+ {
+ return PREC_TERM;
+ }
+
+ if (is_token(t, "<<") || is_token(t, ">>"))
+ {
+ return PREC_TERM;
+ }
+
+ if (is_token(t, "==") || is_token(t, "!="))
+ {
+ return PREC_EQUALITY;
+ }
+
+ if (is_token(t, "<") || is_token(t, ">") || is_token(t, "<=") || is_token(t, ">="))
+ {
+ return PREC_COMPARISON;
+ }
+
+ if (is_token(t, "+") || is_token(t, "-"))
+ {
+ return PREC_TERM;
+ }
+
+ if (is_token(t, "*") || is_token(t, "/") || is_token(t, "%"))
+ {
+ return PREC_FACTOR;
+ }
+
+ if (is_token(t, "."))
+ {
+ return PREC_CALL;
+ }
+
+ if (is_token(t, "|>"))
+ {
+ return PREC_TERM;
+ }
+ }
+
+ if (t.type == TOK_LBRACKET || t.type == TOK_LPAREN)
+ {
+ return PREC_CALL;
+ }
+
+ if (is_token(t, "??"))
+ {
+ return PREC_OR;
+ }
+
+ if (is_token(t, "\?\?="))
+ {
+ return PREC_ASSIGNMENT;
+ }
+
+ return PREC_NONE;
+}
+
+// Helper to check if a variable name is in a list.
+static int is_in_list(const char *name, char **list, int count)
+{
+ for (int i = 0; i < count; i++)
+ {
+ if (0 == strcmp(name, list[i]))
+ {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+// Recursively find all variable references in an expression/statement.
+static void find_var_refs(ASTNode *node, char ***refs, int *ref_count)
+{
+ if (!node)
+ {
+ return;
+ }
+
+ if (node->type == NODE_EXPR_VAR)
+ {
+ *refs = xrealloc(*refs, sizeof(char *) * (*ref_count + 1));
+ (*refs)[*ref_count] = xstrdup(node->var_ref.name);
+ (*ref_count)++;
+ }
+
+ switch (node->type)
+ {
+ case NODE_EXPR_BINARY:
+ find_var_refs(node->binary.left, refs, ref_count);
+ find_var_refs(node->binary.right, refs, ref_count);
+ break;
+ case NODE_EXPR_UNARY:
+ find_var_refs(node->unary.operand, refs, ref_count);
+ break;
+ case NODE_EXPR_CALL:
+ find_var_refs(node->call.callee, refs, ref_count);
+ for (ASTNode *arg = node->call.args; arg; arg = arg->next)
+ {
+ find_var_refs(arg, refs, ref_count);
+ }
+ break;
+ case NODE_EXPR_MEMBER:
+ find_var_refs(node->member.target, refs, ref_count);
+ break;
+ case NODE_EXPR_INDEX:
+ find_var_refs(node->index.array, refs, ref_count);
+ find_var_refs(node->index.index, refs, ref_count);
+ break;
+ case NODE_EXPR_SLICE:
+ find_var_refs(node->slice.array, refs, ref_count);
+ find_var_refs(node->slice.start, refs, ref_count);
+ find_var_refs(node->slice.end, refs, ref_count);
+ break;
+ case NODE_BLOCK:
+ for (ASTNode *stmt = node->block.statements; stmt; stmt = stmt->next)
+ {
+ find_var_refs(stmt, refs, ref_count);
+ }
+ break;
+ case NODE_RETURN:
+ find_var_refs(node->ret.value, refs, ref_count);
+ break;
+ case NODE_VAR_DECL:
+ case NODE_CONST:
+ find_var_refs(node->var_decl.init_expr, refs, ref_count);
+ break;
+ case NODE_IF:
+ find_var_refs(node->if_stmt.condition, refs, ref_count);
+ find_var_refs(node->if_stmt.then_body, refs, ref_count);
+ if (node->if_stmt.else_body)
+ {
+ find_var_refs(node->if_stmt.else_body, refs, ref_count);
+ }
+ break;
+ case NODE_WHILE:
+ find_var_refs(node->while_stmt.condition, refs, ref_count);
+ find_var_refs(node->while_stmt.body, refs, ref_count);
+ break;
+ case NODE_FOR:
+ find_var_refs(node->for_stmt.init, refs, ref_count);
+ find_var_refs(node->for_stmt.condition, refs, ref_count);
+ find_var_refs(node->for_stmt.step, refs, ref_count);
+ find_var_refs(node->for_stmt.body, refs, ref_count);
+ break;
+ case NODE_MATCH:
+ find_var_refs(node->match_stmt.expr, refs, ref_count);
+ for (ASTNode *c = node->match_stmt.cases; c; c = c->next)
+ {
+ find_var_refs(c->match_case.body, refs, ref_count);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+// Helper to find variable declarations in a subtree
+static void find_declared_vars(ASTNode *node, char ***decls, int *count)
+{
+ if (!node)
+ {
+ return;
+ }
+
+ if (node->type == NODE_VAR_DECL)
+ {
+ *decls = xrealloc(*decls, sizeof(char *) * (*count + 1));
+ (*decls)[*count] = xstrdup(node->var_decl.name);
+ (*count)++;
+ }
+
+ if (node->type == NODE_MATCH_CASE && node->match_case.binding_name)
+ {
+ *decls = xrealloc(*decls, sizeof(char *) * (*count + 1));
+ (*decls)[*count] = xstrdup(node->match_case.binding_name);
+ (*count)++;
+ }
+
+ switch (node->type)
+ {
+ case NODE_BLOCK:
+ for (ASTNode *stmt = node->block.statements; stmt; stmt = stmt->next)
+ {
+ find_declared_vars(stmt, decls, count);
+ }
+ break;
+ case NODE_IF:
+ find_declared_vars(node->if_stmt.then_body, decls, count);
+ find_declared_vars(node->if_stmt.else_body, decls, count);
+ break;
+ case NODE_WHILE:
+ find_declared_vars(node->while_stmt.body, decls, count);
+ break;
+ case NODE_FOR:
+ find_declared_vars(node->for_stmt.init, decls, count);
+ find_declared_vars(node->for_stmt.body, decls, count);
+ break;
+ case NODE_MATCH:
+ for (ASTNode *c = node->match_stmt.cases; c; c = c->next)
+ {
+ find_declared_vars(c, decls, count);
+ find_declared_vars(c->match_case.body, decls, count);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+// Analyze lambda body to find captured variables.
+void analyze_lambda_captures(ParserContext *ctx, ASTNode *lambda)
+{
+ if (!lambda || lambda->type != NODE_LAMBDA)
+ {
+ return;
+ }
+
+ char **all_refs = NULL;
+ int num_refs = 0;
+ find_var_refs(lambda->lambda.body, &all_refs, &num_refs);
+
+ char **local_decls = NULL;
+ int num_local_decls = 0;
+ find_declared_vars(lambda->lambda.body, &local_decls, &num_local_decls);
+
+ char **captures = xmalloc(sizeof(char *) * 16);
+ char **capture_types = xmalloc(sizeof(char *) * 16);
+ int num_captures = 0;
+
+ for (int i = 0; i < num_refs; i++)
+ {
+ const char *var_name = all_refs[i];
+
+ if (is_in_list(var_name, lambda->lambda.param_names, lambda->lambda.num_params))
+ {
+ continue;
+ }
+
+ if (is_in_list(var_name, local_decls, num_local_decls))
+ {
+ continue;
+ }
+
+ if (is_in_list(var_name, captures, num_captures))
+ {
+ continue;
+ }
+
+ if (strcmp(var_name, "printf") == 0 || strcmp(var_name, "malloc") == 0 ||
+ strcmp(var_name, "strcmp") == 0 || strcmp(var_name, "free") == 0 ||
+ strcmp(var_name, "Vec_new") == 0 || strcmp(var_name, "Vec_push") == 0)
+ {
+ continue;
+ }
+
+ FuncSig *fs = ctx->func_registry;
+ int is_func = 0;
+ while (fs)
+ {
+ if (0 == strcmp(fs->name, var_name))
+ {
+ is_func = 1;
+ break;
+ }
+ fs = fs->next;
+ }
+ if (is_func)
+ {
+ continue;
+ }
+
+ Scope *s = ctx->current_scope;
+ int is_local = 0;
+ int is_found = 0;
+ while (s)
+ {
+ Symbol *cur = s->symbols;
+ while (cur)
+ {
+ if (0 == strcmp(cur->name, var_name))
+ {
+ is_found = 1;
+ if (s->parent != NULL)
+ {
+ is_local = 1;
+ }
+ break;
+ }
+ cur = cur->next;
+ }
+ if (is_found)
+ {
+ break;
+ }
+ s = s->parent;
+ }
+
+ if (is_found && !is_local)
+ {
+ continue;
+ }
+
+ captures[num_captures] = xstrdup(var_name);
+
+ Type *t = find_symbol_type_info(ctx, var_name);
+ if (t)
+ {
+ capture_types[num_captures] = type_to_string(t);
+ }
+ else
+ {
+ // Fallback for global/unknown
+ // If looks like a function, use "void*" (for closure ctx)
+ // else default to "int" or "void*"
+ capture_types[num_captures] = xstrdup("int");
+ }
+ num_captures++;
+ }
+
+ lambda->lambda.captured_vars = captures;
+ lambda->lambda.captured_types = capture_types;
+ lambda->lambda.num_captures = num_captures;
+
+ if (local_decls)
+ {
+ for (int i = 0; i < num_local_decls; i++)
+ {
+ free(local_decls[i]);
+ }
+ free(local_decls);
+ }
+ for (int i = 0; i < num_refs; i++)
+ {
+ free(all_refs[i]);
+ }
+ if (all_refs)
+ {
+ free(all_refs);
+ }
+}
+
+ASTNode *parse_lambda(ParserContext *ctx, Lexer *l)
+{
+ lexer_next(l);
+
+ if (lexer_peek(l).type != TOK_LPAREN)
+ {
+ zpanic("Expected '(' after 'fn' in lambda");
+ }
+
+ lexer_next(l);
+
+ char **param_names = xmalloc(sizeof(char *) * 16);
+ char **param_types = xmalloc(sizeof(char *) * 16);
+ int num_params = 0;
+
+ while (lexer_peek(l).type != TOK_RPAREN)
+ {
+ if (num_params > 0)
+ {
+ if (lexer_peek(l).type != TOK_COMMA)
+ {
+ zpanic("Expected ',' between parameters");
+ }
+
+ lexer_next(l);
+ }
+
+ Token name_tok = lexer_next(l);
+ if (name_tok.type != TOK_IDENT)
+ {
+ zpanic("Expected parameter name");
+ }
+
+ param_names[num_params] = token_strdup(name_tok);
+
+ if (lexer_peek(l).type != TOK_COLON)
+ {
+ zpanic("Expected ':' after parameter name");
+ }
+
+ lexer_next(l);
+
+ char *param_type_str = parse_type(ctx, l);
+ param_types[num_params] = param_type_str;
+ num_params++;
+ }
+ lexer_next(l);
+
+ char *return_type = xstrdup("void");
+ if (lexer_peek(l).type == TOK_ARROW)
+ {
+ lexer_next(l);
+ return_type = parse_type(ctx, l);
+ }
+
+ ASTNode *body = NULL;
+ if (lexer_peek(l).type == TOK_LBRACE)
+ {
+ body = parse_block(ctx, l);
+ }
+ else
+ {
+ zpanic("Expected '{' for lambda body");
+ }
+
+ ASTNode *lambda = ast_create(NODE_LAMBDA);
+ lambda->lambda.param_names = param_names;
+ lambda->lambda.param_types = param_types;
+ lambda->lambda.return_type = return_type;
+ lambda->lambda.body = body;
+ lambda->lambda.num_params = num_params;
+ lambda->lambda.lambda_id = ctx->lambda_counter++;
+ lambda->lambda.is_expression = 0;
+ register_lambda(ctx, lambda);
+ analyze_lambda_captures(ctx, lambda);
+
+ return lambda;
+}
+
+// Helper to create AST for f-string content.
+static ASTNode *create_fstring_block(ParserContext *ctx, const char *content)
+{
+ ASTNode *block = ast_create(NODE_BLOCK);
+ block->type_info = type_new(TYPE_STRING);
+ block->resolved_type = xstrdup("string");
+
+ ASTNode *head = NULL, *tail = NULL;
+
+ ASTNode *decl_b = ast_create(NODE_RAW_STMT);
+ decl_b->raw_stmt.content = xstrdup("static char _b[4096]; _b[0]=0;");
+ if (!head)
+ {
+ head = decl_b;
+ }
+ else
+ {
+ tail->next = decl_b;
+ }
+ tail = decl_b;
+
+ ASTNode *decl_t = ast_create(NODE_RAW_STMT);
+ decl_t->raw_stmt.content = xstrdup("char _t[128];");
+ tail->next = decl_t;
+ tail = decl_t;
+
+ const char *cur = content;
+ while (*cur)
+ {
+ char *brace = strchr(cur, '{');
+ if (!brace)
+ {
+ if (strlen(cur) > 0)
+ {
+ ASTNode *cat = ast_create(NODE_RAW_STMT);
+ cat->raw_stmt.content = xmalloc(strlen(cur) + 20);
+ sprintf(cat->raw_stmt.content, "strcat(_b, \"%s\");", cur);
+ tail->next = cat;
+ tail = cat;
+ }
+ break;
+ }
+
+ if (brace > cur)
+ {
+ int len = brace - cur;
+ char *txt = xmalloc(len + 1);
+ strncpy(txt, cur, len);
+ txt[len] = 0;
+ ASTNode *cat = ast_create(NODE_RAW_STMT);
+ cat->raw_stmt.content = xmalloc(len + 20);
+ sprintf(cat->raw_stmt.content, "strcat(_b, \"%s\");", txt);
+ tail->next = cat;
+ tail = cat;
+ free(txt);
+ }
+
+ char *end_brace = strchr(brace, '}');
+ if (!end_brace)
+ {
+ break;
+ }
+
+ char *colon = NULL;
+ char *p = brace + 1;
+ int depth = 1;
+ while (p < end_brace)
+ {
+ if (*p == '{')
+ {
+ depth++;
+ }
+ if (*p == '}')
+ {
+ depth--;
+ }
+ if (depth == 1 && *p == ':' && !colon)
+ {
+ colon = p;
+ }
+ p++;
+ }
+
+ char *expr_str;
+ char *fmt = NULL;
+
+ if (colon && colon < end_brace)
+ {
+ int expr_len = colon - (brace + 1);
+ expr_str = xmalloc(expr_len + 1);
+ strncpy(expr_str, brace + 1, expr_len);
+ expr_str[expr_len] = 0;
+
+ int fmt_len = end_brace - (colon + 1);
+ fmt = xmalloc(fmt_len + 1);
+ strncpy(fmt, colon + 1, fmt_len);
+ fmt[fmt_len] = 0;
+ }
+ else
+ {
+ int expr_len = end_brace - (brace + 1);
+ expr_str = xmalloc(expr_len + 1);
+ strncpy(expr_str, brace + 1, expr_len);
+ expr_str[expr_len] = 0;
+ }
+
+ Lexer sub_l;
+ lexer_init(&sub_l, expr_str);
+ ASTNode *expr_node = parse_expression(ctx, &sub_l);
+
+ if (expr_node && expr_node->type == NODE_EXPR_VAR)
+ {
+ Symbol *sym = find_symbol_entry(ctx, expr_node->var_ref.name);
+ if (sym)
+ {
+ sym->is_used = 1;
+ }
+ }
+
+ ASTNode *call_sprintf = ast_create(NODE_EXPR_CALL);
+ ASTNode *callee = ast_create(NODE_EXPR_VAR);
+ callee->var_ref.name = xstrdup("sprintf");
+ call_sprintf->call.callee = callee;
+
+ ASTNode *arg_t = ast_create(NODE_EXPR_VAR);
+ arg_t->var_ref.name = xstrdup("_t");
+
+ ASTNode *arg_fmt = NULL;
+ if (fmt)
+ {
+ char *fmt_str = xmalloc(strlen(fmt) + 3);
+ sprintf(fmt_str, "%%%s", fmt);
+ arg_fmt = ast_create(NODE_EXPR_LITERAL);
+ arg_fmt->literal.type_kind = 2;
+ arg_fmt->literal.string_val = fmt_str;
+ arg_fmt->type_info = type_new(TYPE_STRING);
+ }
+ else
+ {
+ // _z_str(expr)
+ ASTNode *call_macro = ast_create(NODE_EXPR_CALL);
+ ASTNode *macro_callee = ast_create(NODE_EXPR_VAR);
+ macro_callee->var_ref.name = xstrdup("_z_str");
+ call_macro->call.callee = macro_callee;
+ Lexer l2;
+ lexer_init(&l2, expr_str);
+ ASTNode *expr_copy = parse_expression(ctx, &l2);
+
+ call_macro->call.args = expr_copy;
+ arg_fmt = call_macro;
+ }
+
+ call_sprintf->call.args = arg_t;
+ arg_t->next = arg_fmt;
+ arg_fmt->next = expr_node;
+
+ tail->next = call_sprintf;
+ tail = call_sprintf;
+
+ // strcat(_b, _t)
+ ASTNode *cat_t = ast_create(NODE_RAW_STMT);
+ cat_t->raw_stmt.content = xstrdup("strcat(_b, _t);");
+ tail->next = cat_t;
+ tail = cat_t;
+
+ cur = end_brace + 1;
+ free(expr_str);
+ if (fmt)
+ {
+ free(fmt);
+ }
+ }
+
+ // Return _b
+ ASTNode *ret_b = ast_create(NODE_RAW_STMT);
+ ret_b->raw_stmt.content = xstrdup("_b;");
+ tail->next = ret_b;
+ tail = ret_b;
+
+ block->block.statements = head;
+ return block;
+}
+
+ASTNode *parse_primary(ParserContext *ctx, Lexer *l)
+{
+ ASTNode *node = NULL;
+ Token t = lexer_next(l);
+
+ // ** Prefixes **
+
+ // Literals
+ if (t.type == TOK_INT)
+ {
+ node = ast_create(NODE_EXPR_LITERAL);
+ node->literal.type_kind = 0;
+ node->type_info = type_new(TYPE_INT);
+ char *s = token_strdup(t);
+ long long val;
+ if (t.len > 2 && s[0] == '0' && s[1] == 'b')
+ {
+ val = strtoll(s + 2, NULL, 2);
+ }
+ else
+ {
+ val = strtoll(s, NULL, 0);
+ }
+
+ if (val > 2147483647LL || val < -2147483648LL)
+ {
+ warn_integer_overflow(t, "int", val);
+ }
+
+ node->literal.int_val = (int)val;
+ free(s);
+ }
+ else if (t.type == TOK_FLOAT)
+ {
+ node = ast_create(NODE_EXPR_LITERAL);
+ node->literal.type_kind = 1;
+ node->literal.float_val = atof(t.start);
+ node->type_info = type_new(TYPE_F64);
+ }
+ else if (t.type == TOK_STRING)
+ {
+ node = ast_create(NODE_EXPR_LITERAL);
+ node->literal.type_kind = TOK_STRING;
+ node->literal.string_val = xmalloc(t.len);
+ strncpy(node->literal.string_val, t.start + 1, t.len - 2);
+ node->literal.string_val[t.len - 2] = 0;
+ node->type_info = type_new(TYPE_STRING);
+ }
+ else if (t.type == TOK_FSTRING)
+ {
+ char *inner = xmalloc(t.len);
+ strncpy(inner, t.start + 2, t.len - 3);
+ inner[t.len - 3] = 0;
+ node = create_fstring_block(ctx, inner);
+ free(inner);
+ }
+ else if (t.type == TOK_CHAR)
+ {
+ node = ast_create(NODE_EXPR_LITERAL);
+ node->literal.type_kind = TOK_CHAR;
+ node->literal.string_val = token_strdup(t);
+ node->type_info = type_new(TYPE_I8);
+ }
+
+ else if (t.type == TOK_SIZEOF)
+ {
+ if (lexer_peek(l).type != TOK_LPAREN)
+ {
+ zpanic("Expected ( after sizeof");
+ }
+ lexer_next(l);
+
+ int pos = l->pos;
+ int col = l->col;
+ int line = l->line;
+ Type *ty = parse_type_formal(ctx, l);
+
+ if (ty->kind != TYPE_UNKNOWN && lexer_peek(l).type == TOK_RPAREN)
+ {
+ lexer_next(l);
+ char *ts = type_to_string(ty);
+ node = ast_create(NODE_EXPR_SIZEOF);
+ node->size_of.target_type = ts;
+ node->size_of.expr = NULL;
+ node->type_info = type_new(TYPE_USIZE);
+ }
+ else
+ {
+ l->pos = pos;
+ l->col = col;
+ l->line = line;
+ ASTNode *ex = parse_expression(ctx, l);
+ if (lexer_next(l).type != TOK_RPAREN)
+ {
+ zpanic("Expected ) after sizeof identifier");
+ }
+ node = ast_create(NODE_EXPR_SIZEOF);
+ node->size_of.target_type = NULL;
+ node->size_of.expr = ex;
+ node->type_info = type_new(TYPE_USIZE);
+ }
+ }
+
+ else if (t.type == TOK_IDENT && strncmp(t.start, "typeof", 6) == 0 && t.len == 6)
+ {
+ if (lexer_peek(l).type != TOK_LPAREN)
+ {
+ zpanic("Expected ( after typeof");
+ }
+ lexer_next(l);
+
+ int pos = l->pos;
+ int col = l->col;
+ int line = l->line;
+ Type *ty = parse_type_formal(ctx, l);
+
+ if (ty->kind != TYPE_UNKNOWN && lexer_peek(l).type == TOK_RPAREN)
+ {
+ lexer_next(l);
+ char *ts = type_to_string(ty);
+ node = ast_create(NODE_TYPEOF);
+ node->size_of.target_type = ts;
+ node->size_of.expr = NULL;
+ }
+ else
+ {
+ l->pos = pos;
+ l->col = col;
+ l->line = line;
+ ASTNode *ex = parse_expression(ctx, l);
+ if (lexer_next(l).type != TOK_RPAREN)
+ {
+ zpanic("Expected ) after typeof expression");
+ }
+ node = ast_create(NODE_TYPEOF);
+ node->size_of.target_type = NULL;
+ node->size_of.expr = ex;
+ }
+ }
+
+ else if (t.type == TOK_AT)
+ {
+ Token ident = lexer_next(l);
+ if (ident.type != TOK_IDENT)
+ {
+ zpanic("Expected intrinsic name after @");
+ }
+
+ int kind = -1;
+ if (strncmp(ident.start, "type_name", 9) == 0 && ident.len == 9)
+ {
+ kind = 0;
+ }
+ else if (strncmp(ident.start, "fields", 6) == 0 && ident.len == 6)
+ {
+ kind = 1;
+ }
+ else
+ {
+ zpanic("Unknown intrinsic @%.*s", ident.len, ident.start);
+ }
+
+ if (lexer_next(l).type != TOK_LPAREN)
+ {
+ zpanic("Expected ( after intrinsic");
+ }
+
+ Type *target = parse_type_formal(ctx, l);
+
+ if (lexer_next(l).type != TOK_RPAREN)
+ {
+ zpanic("Expected ) after intrinsic type");
+ }
+
+ node = ast_create(NODE_REFLECTION);
+ node->reflection.kind = kind;
+ node->reflection.target_type = target;
+ node->type_info = (kind == 0) ? type_new(TYPE_STRING) : type_new_ptr(type_new(TYPE_VOID));
+ }
+
+ else if (t.type == TOK_IDENT && strncmp(t.start, "match", 5) == 0 && t.len == 5)
+ {
+ ASTNode *expr = parse_expression(ctx, l);
+ skip_comments(l);
+ if (lexer_next(l).type != TOK_LBRACE)
+ {
+ zpanic("Expected { after match expression");
+ }
+
+ ASTNode *h = 0, *tl = 0;
+ while (1)
+ {
+ skip_comments(l);
+ if (lexer_peek(l).type == TOK_RBRACE)
+ {
+ break;
+ }
+ if (lexer_peek(l).type == TOK_COMMA)
+ {
+ lexer_next(l);
+ }
+
+ skip_comments(l);
+ if (lexer_peek(l).type == TOK_RBRACE)
+ {
+ break;
+ }
+
+ Token p = lexer_next(l);
+ char *pattern = token_strdup(p);
+ int is_default = (strcmp(pattern, "_") == 0);
+
+ char *binding = NULL;
+ int is_destructure = 0;
+ skip_comments(l);
+ if (!is_default && lexer_peek(l).type == TOK_LPAREN)
+ {
+ lexer_next(l);
+ Token b = lexer_next(l);
+ if (b.type != TOK_IDENT)
+ {
+ zpanic("Expected binding name");
+ }
+ binding = token_strdup(b);
+ if (lexer_next(l).type != TOK_RPAREN)
+ {
+ zpanic("Expected )");
+ }
+ is_destructure = 1;
+ }
+
+ ASTNode *guard = NULL;
+ skip_comments(l);
+ if (lexer_peek(l).type == TOK_IDENT && strncmp(lexer_peek(l).start, "if", 2) == 0)
+ {
+ lexer_next(l);
+ guard = parse_expression(ctx, l);
+ }
+
+ skip_comments(l);
+ if (lexer_next(l).type != TOK_ARROW)
+ {
+ zpanic("Expected '=>'");
+ }
+
+ ASTNode *body;
+ skip_comments(l);
+ Token pk = lexer_peek(l);
+ if (pk.type == TOK_LBRACE)
+ {
+ body = parse_block(ctx, l);
+ }
+ else if (pk.type == TOK_ASSERT ||
+ (pk.type == TOK_IDENT && strncmp(pk.start, "assert", 6) == 0))
+ {
+ body = parse_assert(ctx, l);
+ }
+ else if (pk.type == TOK_IDENT && strncmp(pk.start, "return", 6) == 0)
+ {
+ body = parse_return(ctx, l);
+ }
+ else
+ {
+ body = parse_expression(ctx, l);
+ }
+
+ ASTNode *c = ast_create(NODE_MATCH_CASE);
+ c->match_case.pattern = pattern;
+ c->match_case.binding_name = binding;
+ c->match_case.is_destructuring = is_destructure;
+ c->match_case.guard = guard;
+ c->match_case.body = body;
+ c->match_case.is_default = is_default;
+ if (!h)
+ {
+ h = c;
+ }
+ else
+ {
+ tl->next = c;
+ }
+ tl = c;
+ }
+ lexer_next(l);
+ node = ast_create(NODE_MATCH);
+ node->match_stmt.expr = expr;
+ node->match_stmt.cases = h;
+ }
+
+ else if (t.type == TOK_IDENT)
+ {
+ if (t.len == 2 && strncmp(t.start, "fn", 2) == 0 && lexer_peek(l).type == TOK_LPAREN)
+ {
+ l->pos -= t.len;
+ l->col -= t.len;
+ return parse_lambda(ctx, l);
+ }
+
+ char *ident = token_strdup(t);
+
+ if (lexer_peek(l).type == TOK_OP && lexer_peek(l).start[0] == '!' && lexer_peek(l).len == 1)
+ {
+ node = parse_macro_call(ctx, l, ident);
+ if (node)
+ {
+ free(ident);
+ return node;
+ }
+ }
+
+ if (lexer_peek(l).type == TOK_ARROW)
+ {
+ lexer_next(l);
+ return parse_arrow_lambda_single(ctx, l, ident);
+ }
+
+ char *acc = ident;
+ while (1)
+ {
+ int changed = 0;
+ if (lexer_peek(l).type == TOK_DCOLON)
+ {
+ lexer_next(l);
+ Token suffix = lexer_next(l);
+ if (suffix.type != TOK_IDENT)
+ {
+ zpanic("Expected identifier after ::");
+ }
+
+ SelectiveImport *si =
+ (!ctx->current_module_prefix) ? find_selective_import(ctx, acc) : NULL;
+ if (si)
+ {
+ char *tmp =
+ xmalloc(strlen(si->source_module) + strlen(si->symbol) + suffix.len + 3);
+ sprintf(tmp, "%s_%s_", si->source_module, si->symbol);
+ strncat(tmp, suffix.start, suffix.len);
+ free(acc);
+ acc = tmp;
+ }
+ else
+ {
+ Module *mod = find_module(ctx, acc);
+ if (mod)
+ {
+ if (mod->is_c_header)
+ {
+ char *tmp = xmalloc(suffix.len + 1);
+ strncpy(tmp, suffix.start, suffix.len);
+ tmp[suffix.len] = 0;
+ free(acc);
+ acc = tmp;
+ }
+ else
+ {
+ char *tmp = xmalloc(strlen(mod->base_name) + suffix.len + 2);
+ sprintf(tmp, "%s_", mod->base_name);
+ strncat(tmp, suffix.start, suffix.len);
+ free(acc);
+ acc = tmp;
+ }
+ }
+ else
+ {
+ char *tmp = xmalloc(strlen(acc) + suffix.len + 2);
+ sprintf(tmp, "%s_", acc);
+ strncat(tmp, suffix.start, suffix.len);
+ free(acc);
+ acc = tmp;
+ }
+ }
+ changed = 1;
+ }
+
+ if (lexer_peek(l).type == TOK_LANGLE)
+ {
+ Lexer lookahead = *l;
+ lexer_next(&lookahead);
+ parse_type(ctx, &lookahead);
+ if (lexer_peek(&lookahead).type == TOK_RANGLE)
+ {
+ lexer_next(l);
+ char *concrete_type = parse_type(ctx, l);
+ lexer_next(l);
+
+ int is_struct = 0;
+ GenericTemplate *st = ctx->templates;
+ while (st)
+ {
+ if (strcmp(st->name, acc) == 0)
+ {
+ is_struct = 1;
+ break;
+ }
+ st = st->next;
+ }
+ if (!is_struct && (strcmp(acc, "Result") == 0 || strcmp(acc, "Option") == 0))
+ {
+ is_struct = 1;
+ }
+
+ if (is_struct)
+ {
+ instantiate_generic(ctx, acc, concrete_type);
+
+ char *clean_type = sanitize_mangled_name(concrete_type);
+
+ char *m = xmalloc(strlen(acc) + strlen(clean_type) + 2);
+ sprintf(m, "%s_%s", acc, clean_type);
+ free(clean_type);
+
+ free(acc);
+ acc = m;
+ }
+ else
+ {
+ char *m = instantiate_function_template(ctx, acc, concrete_type);
+ if (m)
+ {
+ free(acc);
+ acc = m;
+ }
+ else
+ {
+ zpanic("Unknown generic %s", acc);
+ }
+ }
+ changed = 1;
+ }
+ }
+ if (!changed)
+ {
+ break;
+ }
+ }
+
+ if (lexer_peek(l).type == TOK_LBRACE)
+ {
+ int is_struct_init = 0;
+ Lexer pl = *l;
+ lexer_next(&pl);
+ Token fi = lexer_peek(&pl);
+
+ if (fi.type == TOK_RBRACE)
+ {
+ is_struct_init = 1;
+ }
+ else if (fi.type == TOK_IDENT)
+ {
+ lexer_next(&pl);
+ if (lexer_peek(&pl).type == TOK_COLON)
+ {
+ is_struct_init = 1;
+ }
+ }
+ if (is_struct_init)
+ {
+ char *struct_name = acc;
+ if (!ctx->current_module_prefix)
+ {
+ SelectiveImport *si = find_selective_import(ctx, acc);
+ if (si)
+ {
+ struct_name = xmalloc(strlen(si->source_module) + strlen(si->symbol) + 2);
+ sprintf(struct_name, "%s_%s", si->source_module, si->symbol);
+ }
+ }
+ if (struct_name == acc && ctx->current_module_prefix && !is_known_generic(ctx, acc))
+ {
+ char *prefixed = xmalloc(strlen(ctx->current_module_prefix) + strlen(acc) + 2);
+ sprintf(prefixed, "%s_%s", ctx->current_module_prefix, acc);
+ struct_name = prefixed;
+ }
+ lexer_next(l);
+ node = ast_create(NODE_EXPR_STRUCT_INIT);
+ node->struct_init.struct_name = struct_name;
+ Type *init_type = type_new(TYPE_STRUCT);
+ init_type->name = xstrdup(struct_name);
+ node->type_info = init_type;
+
+ ASTNode *head = NULL, *tail = NULL;
+ int first = 1;
+ while (lexer_peek(l).type != TOK_RBRACE)
+ {
+ if (!first && lexer_peek(l).type == TOK_COMMA)
+ {
+ lexer_next(l);
+ }
+ if (lexer_peek(l).type == TOK_RBRACE)
+ {
+ break;
+ }
+ Token fn = lexer_next(l);
+ if (lexer_next(l).type != TOK_COLON)
+ {
+ zpanic("Expected :");
+ }
+ ASTNode *val = parse_expression(ctx, l);
+ ASTNode *assign = ast_create(NODE_VAR_DECL);
+ assign->var_decl.name = token_strdup(fn);
+ assign->var_decl.init_expr = val;
+ if (!head)
+ {
+ head = assign;
+ }
+ else
+ {
+ tail->next = assign;
+ }
+ tail = assign;
+ first = 0;
+ }
+ lexer_next(l);
+ node->struct_init.fields = head;
+ Type *st = type_new(TYPE_STRUCT);
+ st->name = xstrdup(struct_name);
+ node->type_info = st;
+ return node; // Struct init cannot be called/indexed usually, return early
+ }
+ }
+
+ // E. readln(args...) Magic
+ FuncSig *sig = find_func(ctx, acc);
+ if (strcmp(acc, "readln") == 0 && lexer_peek(l).type == TOK_LPAREN)
+ {
+ lexer_next(l); // eat (
+
+ // Parse args
+ ASTNode *args[16];
+ int ac = 0;
+ if (lexer_peek(l).type != TOK_RPAREN)
+ {
+ while (1)
+ {
+ args[ac++] = parse_expression(ctx, l);
+ if (lexer_peek(l).type == TOK_COMMA)
+ {
+ lexer_next(l);
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ if (lexer_next(l).type != TOK_RPAREN)
+ {
+ zpanic("Expected )");
+ }
+
+ if (ac == 0)
+ {
+ // readln() -> _z_readln_raw()
+ node = ast_create(NODE_EXPR_CALL);
+ ASTNode *callee = ast_create(NODE_EXPR_VAR);
+ callee->var_ref.name = xstrdup("_z_readln_raw");
+ node->call.callee = callee;
+ node->type_info = type_new(TYPE_STRING);
+ }
+ else
+ {
+ // readln(vars...) -> _z_scan_helper("fmt", &vars...)
+ // 1. Build Format String
+ char fmt[256];
+ fmt[0] = 0;
+ for (int i = 0; i < ac; i++)
+ {
+ Type *t = args[i]->type_info;
+ if (!t && args[i]->type == NODE_EXPR_VAR)
+ {
+ t = find_symbol_type_info(ctx, args[i]->var_ref.name);
+ }
+
+ if (!t)
+ {
+ strcat(fmt, "%d"); // Fallback
+ }
+ else
+ {
+ if (t->kind == TYPE_INT || t->kind == TYPE_I32 || t->kind == TYPE_BOOL)
+ {
+ strcat(fmt, "%d");
+ }
+ else if (t->kind == TYPE_F64)
+ {
+ strcat(fmt, "%lf");
+ }
+ else if (t->kind == TYPE_F32 || t->kind == TYPE_FLOAT)
+ {
+ strcat(fmt, "%f");
+ }
+ else if (t->kind == TYPE_STRING)
+ {
+ strcat(fmt, "%s");
+ }
+ else if (t->kind == TYPE_CHAR || t->kind == TYPE_I8 || t->kind == TYPE_U8 ||
+ t->kind == TYPE_BYTE)
+ {
+ strcat(fmt, " %c"); // Space skip whitespace
+ }
+ else
+ {
+ strcat(fmt, "%d");
+ }
+ }
+ if (i < ac - 1)
+ {
+ strcat(fmt, " ");
+ }
+ }
+
+ // 2. Build Call Node
+ node = ast_create(NODE_EXPR_CALL);
+ ASTNode *callee = ast_create(NODE_EXPR_VAR);
+ callee->var_ref.name = xstrdup("_z_scan_helper");
+ node->call.callee = callee;
+ node->type_info = type_new(TYPE_INT); // Returns count
+
+ // 3. Build Args List: "fmt" then &arg1, &arg2...
+ ASTNode *fmt_node = ast_create(NODE_EXPR_LITERAL);
+ fmt_node->literal.type_kind = 2; // string
+ fmt_node->literal.string_val = xstrdup(fmt);
+
+ ASTNode *head = fmt_node, *tail = fmt_node;
+
+ for (int i = 0; i < ac; i++)
+ {
+ // Create Unary & (AddressOf) node wrapping the arg
+ ASTNode *addr = ast_create(NODE_EXPR_UNARY);
+ addr->unary.op = xstrdup("&");
+ addr->unary.operand = args[i];
+ // Link
+ tail->next = addr;
+ tail = addr;
+ }
+ node->call.args = head;
+ }
+ free(acc);
+
+ }
+ else
+ if (sig && lexer_peek(l).type == TOK_LPAREN)
+ {
+ lexer_next(l);
+ ASTNode *head = NULL, *tail = NULL;
+ int args_provided = 0;
+ char **arg_names = NULL;
+ int has_named = 0;
+
+ if (lexer_peek(l).type != TOK_RPAREN)
+ {
+ while (1)
+ {
+ char *arg_name = NULL;
+
+ Token t1 = lexer_peek(l);
+ if (t1.type == TOK_IDENT)
+ {
+ Token t2 = lexer_peek2(l);
+ if (t2.type == TOK_COLON)
+ {
+ arg_name = token_strdup(t1);
+ has_named = 1;
+ lexer_next(l);
+ lexer_next(l);
+ }
+ }
+
+ ASTNode *arg = parse_expression(ctx, l);
+ if (!head)
+ {
+ head = arg;
+ }
+ else
+ {
+ tail->next = arg;
+ }
+ tail = arg;
+ args_provided++;
+
+ arg_names = xrealloc(arg_names, args_provided * sizeof(char *));
+ arg_names[args_provided - 1] = arg_name;
+
+ arg_names = xrealloc(arg_names, args_provided * sizeof(char *));
+ arg_names[args_provided - 1] = arg_name;
+
+ if (lexer_peek(l).type == TOK_COMMA)
+ {
+ lexer_next(l);
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ if (lexer_next(l).type != TOK_RPAREN)
+ {
+ zpanic("Expected )");
+ }
+ for (int i = args_provided; i < sig->total_args; i++)
+ {
+ if (sig->defaults[i])
+ {
+ ASTNode *def = ast_create(NODE_RAW_STMT);
+ def->raw_stmt.content = xstrdup(sig->defaults[i]);
+ if (!head)
+ {
+ head = def;
+ }
+ else
+ {
+ tail->next = def;
+ }
+ tail = def;
+ }
+ }
+ node = ast_create(NODE_EXPR_CALL);
+ node->token = t; // Set source token
+ ASTNode *callee = ast_create(NODE_EXPR_VAR);
+ callee->var_ref.name = acc;
+ node->call.callee = callee;
+ node->call.args = head;
+ node->call.arg_names = has_named ? arg_names : NULL;
+ node->call.arg_count = args_provided;
+ if (sig)
+ {
+ node->definition_token = sig->decl_token;
+ }
+ if (sig->is_async)
+ {
+ Type *async_type = type_new(TYPE_STRUCT);
+ async_type->name = xstrdup("Async");
+ node->type_info = async_type;
+ node->resolved_type = xstrdup("Async");
+ }
+ else if (sig->ret_type)
+ {
+ node->type_info = sig->ret_type;
+ node->resolved_type = type_to_string(sig->ret_type);
+ }
+ else
+ {
+ node->resolved_type = xstrdup("void");
+ }
+ }
+ else if (!sig && !find_symbol_entry(ctx, acc) && lexer_peek(l).type == TOK_LPAREN)
+ {
+ lexer_next(l); // eat (
+ ASTNode *head = NULL, *tail = NULL;
+ char **arg_names = NULL;
+ int args_provided = 0;
+ int has_named = 0;
+
+ if (lexer_peek(l).type != TOK_RPAREN)
+ {
+ while (1)
+ {
+ char *arg_name = NULL;
+
+ // Check for named argument: name: value
+ Token t1 = lexer_peek(l);
+ if (t1.type == TOK_IDENT)
+ {
+ Token t2 = lexer_peek2(l);
+ if (t2.type == TOK_COLON)
+ {
+ arg_name = token_strdup(t1);
+ has_named = 1;
+ lexer_next(l);
+ lexer_next(l);
+ }
+ }
+
+ ASTNode *arg = parse_expression(ctx, l);
+ if (!head)
+ {
+ head = arg;
+ }
+ else
+ {
+ tail->next = arg;
+ }
+ tail = arg;
+ args_provided++;
+
+ arg_names = xrealloc(arg_names, args_provided * sizeof(char *));
+ arg_names[args_provided - 1] = arg_name;
+
+ if (lexer_peek(l).type == TOK_COMMA)
+ {
+ lexer_next(l);
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ if (lexer_next(l).type != TOK_RPAREN)
+ {
+ zpanic("Expected )");
+ }
+
+ node = ast_create(NODE_EXPR_CALL);
+ node->token = t;
+ ASTNode *callee = ast_create(NODE_EXPR_VAR);
+ callee->var_ref.name = acc;
+ node->call.callee = callee;
+ node->call.args = head;
+ node->call.arg_names = has_named ? arg_names : NULL;
+ node->call.arg_count = args_provided;
+ // Unknown return type - let codegen infer it
+ node->resolved_type = xstrdup("unknown");
+ // Fall through to Postfix
+ }
+ else
+ {
+ node = ast_create(NODE_EXPR_VAR);
+ node->token = t; // Set source token
+ node->var_ref.name = acc;
+ node->type_info = find_symbol_type_info(ctx, acc);
+
+ Symbol *sym = find_symbol_entry(ctx, acc);
+ if (sym)
+ {
+ sym->is_used = 1;
+ node->definition_token = sym->decl_token;
+ }
+
+ char *type_str = find_symbol_type(ctx, acc);
+
+ if (type_str)
+ {
+ node->resolved_type = type_str;
+ node->var_ref.suggestion = NULL;
+ }
+ else
+ {
+ node->resolved_type = xstrdup("unknown");
+ if (should_suppress_undef_warning(ctx, acc))
+ {
+ node->var_ref.suggestion = NULL;
+ }
+ else
+ {
+ node->var_ref.suggestion = find_similar_symbol(ctx, acc);
+ }
+ }
+ }
+ }
+
+ else if (t.type == TOK_LPAREN)
+ {
+
+ Lexer lookahead = *l;
+ int is_lambda = 0;
+ char **params = xmalloc(sizeof(char *) * 16);
+ int nparams = 0;
+
+ while (1)
+ {
+ if (lexer_peek(&lookahead).type != TOK_IDENT)
+ {
+ break;
+ }
+ params[nparams++] = token_strdup(lexer_next(&lookahead));
+ Token sep = lexer_peek(&lookahead);
+ if (sep.type == TOK_COMMA)
+ {
+ lexer_next(&lookahead);
+ continue;
+ }
+ else if (sep.type == TOK_RPAREN)
+ {
+ lexer_next(&lookahead);
+ if (lexer_peek(&lookahead).type == TOK_ARROW)
+ {
+ lexer_next(&lookahead);
+ is_lambda = 1;
+ }
+ break;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ if (is_lambda && nparams > 0)
+ {
+ *l = lookahead; // Commit
+ return parse_arrow_lambda_multi(ctx, l, params, nparams);
+ }
+
+ int saved = l->pos;
+ if (lexer_peek(l).type == TOK_IDENT)
+ {
+ Lexer cast_look = *l;
+ lexer_next(&cast_look); // eat ident
+ while (lexer_peek(&cast_look).type == TOK_DCOLON)
+ { // handle A::B
+ lexer_next(&cast_look);
+ if (lexer_peek(&cast_look).type == TOK_IDENT)
+ {
+ lexer_next(&cast_look);
+ }
+ else
+ {
+ break;
+ }
+ }
+ while (lexer_peek(&cast_look).type == TOK_OP && is_token(lexer_peek(&cast_look), "*"))
+ {
+ lexer_next(&cast_look);
+ }
+
+ if (lexer_peek(&cast_look).type == TOK_RPAREN)
+ {
+ lexer_next(&cast_look); // eat )
+ Token next = lexer_peek(&cast_look);
+ // Heuristic: It's a cast if followed by literal, ident, paren, or &/*
+ if (next.type == TOK_STRING || next.type == TOK_INT || next.type == TOK_FLOAT ||
+ (next.type == TOK_OP &&
+ (is_token(next, "&") || is_token(next, "*") || is_token(next, "!"))) ||
+ next.type == TOK_IDENT || next.type == TOK_LPAREN)
+ {
+
+ Type *cast_type_obj = parse_type_formal(ctx, l);
+ char *cast_type = type_to_string(cast_type_obj);
+ if (lexer_next(l).type != TOK_RPAREN)
+ {
+ zpanic("Expected ) after cast");
+ }
+ ASTNode *target = parse_expr_prec(ctx, l, PREC_UNARY);
+
+ node = ast_create(NODE_EXPR_CAST);
+ node->cast.target_type = cast_type;
+ node->cast.expr = target;
+ node->type_info = cast_type_obj;
+ return node; // Casts are usually unary, handled here.
+ }
+ }
+ }
+ l->pos = saved; // Reset if not a cast
+
+ ASTNode *expr = parse_expression(ctx, l);
+ if (lexer_next(l).type != TOK_RPAREN)
+ {
+ zpanic("Expected )");
+ }
+ node = expr;
+ }
+
+ else if (t.type == TOK_LBRACKET)
+ {
+ ASTNode *head = NULL, *tail = NULL;
+ int count = 0;
+ while (lexer_peek(l).type != TOK_RBRACKET)
+ {
+ ASTNode *elem = parse_expression(ctx, l);
+ count++;
+ if (!head)
+ {
+ head = elem;
+ tail = elem;
+ }
+ else
+ {
+ tail->next = elem;
+ tail = elem;
+ }
+ if (lexer_peek(l).type == TOK_COMMA)
+ {
+ lexer_next(l);
+ }
+ else
+ {
+ break;
+ }
+ }
+ if (lexer_next(l).type != TOK_RBRACKET)
+ {
+ zpanic("Expected ] after array literal");
+ }
+ node = ast_create(NODE_EXPR_ARRAY_LITERAL);
+ node->array_literal.elements = head;
+ node->array_literal.count = count;
+ if (head && head->type_info)
+ {
+ Type *elem_type = head->type_info;
+ Type *arr_type = type_new(TYPE_ARRAY);
+ arr_type->inner = elem_type;
+ arr_type->array_size = count;
+ node->type_info = arr_type;
+ }
+ }
+ else
+ {
+ zpanic_at(t, "Unexpected token in parse_primary: %.*s", t.len, t.start);
+ }
+
+ while (1)
+ {
+ if (lexer_peek(l).type == TOK_LPAREN)
+ {
+ Token op = lexer_next(l); // consume '('
+ ASTNode *head = NULL, *tail = NULL;
+ char **arg_names = NULL;
+ int arg_count = 0;
+ int has_named = 0;
+
+ if (lexer_peek(l).type != TOK_RPAREN)
+ {
+ while (1)
+ {
+ char *arg_name = NULL;
+
+ // Check for named argument: IDENT : expr
+ Token t1 = lexer_peek(l);
+ if (t1.type == TOK_IDENT)
+ {
+ Token t2 = lexer_peek2(l);
+ if (t2.type == TOK_COLON)
+ {
+ arg_name = token_strdup(t1);
+ has_named = 1;
+ lexer_next(l); // eat IDENT
+ lexer_next(l); // eat :
+ }
+ }
+
+ ASTNode *arg = parse_expression(ctx, l);
+ if (!head)
+ {
+ head = arg;
+ }
+ else
+ {
+ tail->next = arg;
+ }
+ tail = arg;
+
+ arg_names = xrealloc(arg_names, (arg_count + 1) * sizeof(char *));
+ arg_names[arg_count] = arg_name;
+ arg_count++;
+
+ if (lexer_peek(l).type == TOK_COMMA)
+ {
+ lexer_next(l);
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ if (lexer_next(l).type != TOK_RPAREN)
+ {
+ zpanic("Expected ) after call arguments");
+ }
+
+ ASTNode *call = ast_create(NODE_EXPR_CALL);
+ call->call.callee = node;
+ call->call.args = head;
+ call->call.arg_names = has_named ? arg_names : NULL;
+ call->call.arg_count = arg_count;
+ check_format_string(call, op);
+
+ // Try to infer type if callee has function type info
+ call->resolved_type = xstrdup("unknown"); // Default (was int)
+ if (node->type_info && node->type_info->kind == TYPE_FUNCTION && node->type_info->inner)
+ {
+ call->type_info = node->type_info->inner;
+
+ // Update resolved_type based on real return
+ // (Optional: type_to_string(call->type_info))
+ }
+ node = call;
+ }
+
+ else if (lexer_peek(l).type == TOK_LBRACKET)
+ {
+ Token bracket = lexer_next(l); // consume '['
+ ASTNode *index = parse_expression(ctx, l);
+ if (lexer_next(l).type != TOK_RBRACKET)
+ {
+ zpanic("Expected ] after index");
+ }
+
+ // Static Array Bounds Check
+ if (node->type_info && node->type_info->kind == TYPE_ARRAY &&
+ node->type_info->array_size > 0)
+ {
+ if (index->type == NODE_EXPR_LITERAL && index->literal.type_kind == 0)
+ {
+ int idx = index->literal.int_val;
+ if (idx < 0 || idx >= node->type_info->array_size)
+ {
+ warn_array_bounds(bracket, idx, node->type_info->array_size);
+ }
+ }
+ }
+
+ int overloaded_get = 0;
+ if (node->type_info && node->type_info->kind != TYPE_ARRAY &&
+ (node->type_info->kind == TYPE_STRUCT ||
+ (node->type_info->kind == TYPE_POINTER && node->type_info->inner &&
+ node->type_info->inner->kind == TYPE_STRUCT)))
+ {
+ Type *st = node->type_info;
+ char *struct_name = (st->kind == TYPE_STRUCT) ? st->name : st->inner->name;
+ int is_ptr = (st->kind == TYPE_POINTER);
+
+ char mangled[256];
+ sprintf(mangled, "%s_get", struct_name);
+ FuncSig *sig = find_func(ctx, mangled);
+ if (sig)
+ {
+ // Rewrite to Call: node.get(index)
+ ASTNode *call = ast_create(NODE_EXPR_CALL);
+ ASTNode *callee = ast_create(NODE_EXPR_VAR);
+ callee->var_ref.name = xstrdup(mangled);
+ call->call.callee = callee;
+
+ // Arg 1: Self
+ ASTNode *arg1 = node;
+ if (sig->total_args > 0 && sig->arg_types[0]->kind == TYPE_POINTER && !is_ptr)
+ {
+ // Needs ptr, have value -> &node
+ ASTNode *addr = ast_create(NODE_EXPR_UNARY);
+ addr->unary.op = xstrdup("&");
+ addr->unary.operand = node;
+ addr->type_info = type_new_ptr(st);
+ arg1 = addr;
+ }
+ else if (is_ptr && sig->arg_types[0]->kind != TYPE_POINTER)
+ {
+ // Needs value, have ptr -> *node
+ ASTNode *deref = ast_create(NODE_EXPR_UNARY);
+ deref->unary.op = xstrdup("*");
+ deref->unary.operand = node;
+ arg1 = deref;
+ }
+
+ // Arg 2: Index
+ arg1->next = index;
+ index->next = NULL;
+ call->call.args = arg1;
+
+ call->type_info = sig->ret_type;
+ call->resolved_type = type_to_string(sig->ret_type);
+
+ node = call;
+ overloaded_get = 1;
+ }
+ }
+
+ if (!overloaded_get)
+ {
+ ASTNode *idx_node = ast_create(NODE_EXPR_INDEX);
+ idx_node->index.array = node;
+ idx_node->index.index = index;
+ idx_node->type_info = (node->type_info && node->type_info->inner)
+ ? node->type_info->inner
+ : type_new(TYPE_INT);
+ node = idx_node;
+ }
+ }
+
+ else
+ {
+ break;
+ }
+ }
+
+ return node;
+}
+
+int is_comparison_op(const char *op)
+{
+ return (strcmp(op, "==") == 0 || strcmp(op, "!=") == 0 || strcmp(op, "<") == 0 ||
+ strcmp(op, ">") == 0 || strcmp(op, "<=") == 0 || strcmp(op, ">=") == 0);
+}
+
+Type *get_field_type(ParserContext *ctx, Type *struct_type, const char *field_name)
+{
+ if (!struct_type)
+ {
+ return NULL;
+ }
+
+ // Built-in Fields for Arrays/Slices
+ if (struct_type->kind == TYPE_ARRAY)
+ {
+ if (strcmp(field_name, "len") == 0)
+ {
+ return type_new(TYPE_INT);
+ }
+ if (struct_type->array_size == 0)
+ { // Slice
+ if (strcmp(field_name, "cap") == 0)
+ {
+ return type_new(TYPE_INT);
+ }
+ if (strcmp(field_name, "data") == 0)
+ {
+ return type_new_ptr(struct_type->inner);
+ }
+ }
+ }
+
+ char *sname = struct_type->name;
+ // Handle Pointers (User* -> User)
+ if (struct_type->kind == TYPE_POINTER && struct_type->inner)
+ {
+ sname = struct_type->inner->name;
+ }
+ if (!sname)
+ {
+ return NULL;
+ }
+
+ ASTNode *def = find_struct_def(ctx, sname);
+ if (!def)
+ {
+ return NULL;
+ }
+
+ ASTNode *f = def->strct.fields;
+ while (f)
+ {
+ if (strcmp(f->field.name, field_name) == 0)
+ {
+ return f->type_info;
+ }
+ f = f->next;
+ }
+ return NULL;
+}
+
+const char *get_operator_method(const char *op)
+{
+ // Arithmetic
+ if (strcmp(op, "+") == 0)
+ {
+ return "add";
+ }
+ if (strcmp(op, "-") == 0)
+ {
+ return "sub";
+ }
+ if (strcmp(op, "*") == 0)
+ {
+ return "mul";
+ }
+ if (strcmp(op, "/") == 0)
+ {
+ return "div";
+ }
+ if (strcmp(op, "%") == 0)
+ {
+ return "rem";
+ }
+
+ // Comparison
+ if (strcmp(op, "==") == 0)
+ {
+ return "eq";
+ }
+ if (strcmp(op, "!=") == 0)
+ {
+ return "neq";
+ }
+ if (strcmp(op, "<") == 0)
+ {
+ return "lt";
+ }
+ if (strcmp(op, ">") == 0)
+ {
+ return "gt";
+ }
+ if (strcmp(op, "<=") == 0)
+ {
+ return "le";
+ }
+ if (strcmp(op, ">=") == 0)
+ {
+ return "ge";
+ }
+
+ // --- NEW: Bitwise ---
+ if (strcmp(op, "&") == 0)
+ {
+ return "bitand";
+ }
+ if (strcmp(op, "|") == 0)
+ {
+ return "bitor";
+ }
+ if (strcmp(op, "^") == 0)
+ {
+ return "bitxor";
+ }
+ if (strcmp(op, "<<") == 0)
+ {
+ return "shl";
+ }
+ if (strcmp(op, ">>") == 0)
+ {
+ return "shr";
+ }
+
+ return NULL;
+}
+
+ASTNode *parse_expr_prec(ParserContext *ctx, Lexer *l, Precedence min_prec)
+{
+ Token t = lexer_peek(l);
+ ASTNode *lhs = NULL;
+
+ if (t.type == TOK_QUESTION)
+ {
+ Lexer lookahead = *l;
+ lexer_next(&lookahead);
+ Token next = lexer_peek(&lookahead);
+
+ if (next.type == TOK_STRING || next.type == TOK_FSTRING)
+ {
+ lexer_next(l); // consume '?'
+ Token t_str = lexer_next(l);
+
+ char *inner = xmalloc(t_str.len);
+ if (t_str.type == TOK_FSTRING)
+ {
+ strncpy(inner, t_str.start + 2, t_str.len - 3);
+ inner[t_str.len - 3] = 0;
+ }
+ else
+ {
+ strncpy(inner, t_str.start + 1, t_str.len - 2);
+ inner[t_str.len - 2] = 0;
+ }
+
+ // Reuse printf sugar to generate the prompt print
+ char *print_code = process_printf_sugar(ctx, inner, 0, "stdout");
+ free(inner);
+
+ // Checks for (args...) suffix for SCAN mode
+ if (lexer_peek(l).type == TOK_LPAREN)
+ {
+ lexer_next(l); // consume (
+
+ // Parse args
+ ASTNode *args[16];
+ int ac = 0;
+ if (lexer_peek(l).type != TOK_RPAREN)
+ {
+ while (1)
+ {
+ args[ac++] = parse_expression(ctx, l);
+ if (lexer_peek(l).type == TOK_COMMA)
+ {
+ lexer_next(l);
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ if (lexer_next(l).type != TOK_RPAREN)
+ {
+ zpanic("Expected )");
+ }
+
+ char fmt[256];
+ fmt[0] = 0;
+ for (int i = 0; i < ac; i++)
+ {
+ Type *t = args[i]->type_info;
+ if (!t && args[i]->type == NODE_EXPR_VAR)
+ {
+ t = find_symbol_type_info(ctx, args[i]->var_ref.name);
+ }
+
+ if (!t)
+ {
+ strcat(fmt, "%d");
+ }
+ else
+ {
+ if (t->kind == TYPE_INT || t->kind == TYPE_I32 || t->kind == TYPE_BOOL)
+ {
+ strcat(fmt, "%d");
+ }
+ else if (t->kind == TYPE_F64)
+ {
+ strcat(fmt, "%lf");
+ }
+ else if (t->kind == TYPE_F32 || t->kind == TYPE_FLOAT)
+ {
+ strcat(fmt, "%f");
+ }
+ else if (t->kind == TYPE_STRING)
+ {
+ strcat(fmt, "%s");
+ }
+ else if (t->kind == TYPE_CHAR || t->kind == TYPE_I8 || t->kind == TYPE_U8 ||
+ t->kind == TYPE_BYTE)
+ {
+ strcat(fmt, " %c");
+ }
+ else
+ {
+ strcat(fmt, "%d");
+ }
+ }
+ if (i < ac - 1)
+ {
+ strcat(fmt, " ");
+ }
+ }
+
+ ASTNode *block = ast_create(NODE_BLOCK);
+
+ ASTNode *s1 = ast_create(NODE_RAW_STMT);
+ // Append semicolon to ensure it's a valid statement
+ char *s1_code = xmalloc(strlen(print_code) + 2);
+ sprintf(s1_code, "%s;", print_code);
+ s1->raw_stmt.content = s1_code;
+ free(print_code);
+
+ ASTNode *call = ast_create(NODE_EXPR_CALL);
+ ASTNode *callee = ast_create(NODE_EXPR_VAR);
+ callee->var_ref.name = xstrdup("_z_scan_helper");
+ call->call.callee = callee;
+ call->type_info = type_new(TYPE_INT);
+
+ ASTNode *fmt_node = ast_create(NODE_EXPR_LITERAL);
+ fmt_node->literal.type_kind = 2;
+ fmt_node->literal.string_val = xstrdup(fmt);
+ ASTNode *head = fmt_node, *tail = fmt_node;
+
+ for (int i = 0; i < ac; i++)
+ {
+ ASTNode *addr = ast_create(NODE_EXPR_UNARY);
+ addr->unary.op = xstrdup("&");
+ addr->unary.operand = args[i];
+ tail->next = addr;
+ tail = addr;
+ }
+ call->call.args = head;
+
+ // Link Statements
+ s1->next = call;
+ block->block.statements = s1;
+
+ return block;
+ }
+ else
+ {
+ // String Mode (Original)
+ size_t len = strlen(print_code);
+ if (len > 5)
+ {
+ print_code[len - 5] = 0; // Strip "0; })"
+ }
+
+ char *final_code = xmalloc(strlen(print_code) + 64);
+ sprintf(final_code, "%s readln(); })", print_code);
+ free(print_code);
+
+ ASTNode *n = ast_create(NODE_RAW_STMT);
+ n->raw_stmt.content = final_code;
+ return n;
+ }
+ }
+ }
+ if (t.type == TOK_OP && is_token(t, "!"))
+ {
+ Lexer lookahead = *l;
+ lexer_next(&lookahead);
+ Token next = lexer_peek(&lookahead);
+
+ if (next.type == TOK_STRING || next.type == TOK_FSTRING)
+ {
+ lexer_next(l); // consume '!'
+ Token t_str = lexer_next(l);
+
+ char *inner = xmalloc(t_str.len);
+ if (t_str.type == TOK_FSTRING)
+ {
+ strncpy(inner, t_str.start + 2, t_str.len - 3);
+ inner[t_str.len - 3] = 0;
+ }
+ else
+ {
+ strncpy(inner, t_str.start + 1, t_str.len - 2);
+ inner[t_str.len - 2] = 0;
+ }
+
+ // Check for .. suffix (.. suppresses newline)
+ int newline = 1;
+ if (lexer_peek(l).type == TOK_DOTDOT)
+ {
+ lexer_next(l); // consume ..
+ newline = 0;
+ }
+
+ char *code = process_printf_sugar(ctx, inner, newline, "stderr");
+ free(inner);
+
+ ASTNode *n = ast_create(NODE_RAW_STMT);
+ n->raw_stmt.content = code;
+ return n;
+ }
+ }
+
+ if (t.type == TOK_AWAIT)
+ {
+ lexer_next(l); // consume await
+ ASTNode *operand = parse_expr_prec(ctx, l, PREC_UNARY);
+
+ lhs = ast_create(NODE_AWAIT);
+ lhs->unary.operand = operand;
+ // Type inference: await Async<T> yields T
+ // If operand is a call to an async function, look up its ret_type (not Async)
+ if (operand->type == NODE_EXPR_CALL && operand->call.callee->type == NODE_EXPR_VAR)
+ {
+ FuncSig *sig = find_func(ctx, operand->call.callee->var_ref.name);
+ if (sig && sig->is_async && sig->ret_type)
+ {
+ lhs->type_info = sig->ret_type;
+ lhs->resolved_type = type_to_string(sig->ret_type);
+ }
+ else if (sig && !sig->is_async)
+ {
+ // Not an async function - shouldn't await it
+ lhs->type_info = type_new(TYPE_VOID);
+ lhs->resolved_type = xstrdup("void");
+ }
+ else
+ {
+ lhs->type_info = type_new_ptr(type_new(TYPE_VOID));
+ lhs->resolved_type = xstrdup("void*");
+ }
+ }
+ else
+ {
+ // Awaiting a variable - harder to determine underlying type
+ // Fallback to void* for now (could be improved with metadata)
+ lhs->type_info = type_new_ptr(type_new(TYPE_VOID));
+ lhs->resolved_type = xstrdup("void*");
+ }
+
+ goto after_unary;
+ }
+
+ if (t.type == TOK_OP &&
+ (is_token(t, "-") || is_token(t, "!") || is_token(t, "*") || is_token(t, "&") ||
+ is_token(t, "~") || is_token(t, "&&") || is_token(t, "++") || is_token(t, "--")))
+ {
+ lexer_next(l); // consume op
+ ASTNode *operand = parse_expr_prec(ctx, l, PREC_UNARY);
+
+ char *method = NULL;
+ if (is_token(t, "-"))
+ {
+ method = "neg";
+ }
+ if (is_token(t, "!"))
+ {
+ method = "not";
+ }
+ if (is_token(t, "~"))
+ {
+ method = "bitnot";
+ }
+
+ if (method && operand->type_info)
+ {
+ Type *ot = operand->type_info;
+ char *struct_name = NULL;
+ int is_ptr = 0;
+ // Unwrap pointer if needed (Struct* -> Struct) to find the method
+ if (ot->kind == TYPE_STRUCT)
+ {
+ struct_name = ot->name;
+ is_ptr = 0;
+ }
+ else if (ot->kind == TYPE_POINTER && ot->inner->kind == TYPE_STRUCT)
+ {
+ struct_name = ot->inner->name;
+ is_ptr = 1;
+ }
+
+ if (struct_name)
+ {
+ char mangled[256];
+ sprintf(mangled, "%s_%s", struct_name, method);
+
+ if (find_func(ctx, mangled))
+ {
+ // Rewrite: ~x -> Struct_bitnot(x)
+ ASTNode *call = ast_create(NODE_EXPR_CALL);
+ ASTNode *callee = ast_create(NODE_EXPR_VAR);
+ callee->var_ref.name = xstrdup(mangled);
+ call->call.callee = callee;
+
+ // Handle 'self' argument adjustment (Pointer vs Value)
+ ASTNode *arg = operand;
+ FuncSig *sig = find_func(ctx, mangled);
+
+ if (sig->total_args > 0 && sig->arg_types[0]->kind == TYPE_POINTER && !is_ptr)
+ {
+ int is_rvalue =
+ (operand->type == NODE_EXPR_CALL || operand->type == NODE_EXPR_BINARY ||
+ operand->type == NODE_MATCH);
+ ASTNode *addr = ast_create(NODE_EXPR_UNARY);
+ addr->unary.op = is_rvalue ? xstrdup("&_rval") : xstrdup("&");
+ addr->unary.operand = operand;
+ addr->type_info = type_new_ptr(ot);
+ arg = addr;
+ }
+ else if (is_ptr && sig->arg_types[0]->kind != TYPE_POINTER)
+ {
+ // Function wants Value, we have Pointer -> Dereference (*)
+ ASTNode *deref = ast_create(NODE_EXPR_UNARY);
+ deref->unary.op = xstrdup("*");
+ deref->unary.operand = operand;
+ deref->type_info = ot->inner;
+ arg = deref;
+ }
+
+ call->call.args = arg;
+ call->type_info = sig->ret_type;
+ call->resolved_type = type_to_string(sig->ret_type);
+ lhs = call;
+
+ // Skip standard unary node creation
+ goto after_unary;
+ }
+ }
+ }
+
+ // Standard Unary Node (for primitives or if no overload found)
+ lhs = ast_create(NODE_EXPR_UNARY);
+ lhs->unary.op = token_strdup(t);
+ lhs->unary.operand = operand;
+
+ if (operand->type_info)
+ {
+ if (is_token(t, "&"))
+ {
+ lhs->type_info = type_new_ptr(operand->type_info);
+ }
+ else if (is_token(t, "*"))
+ {
+ if (operand->type_info->kind == TYPE_POINTER)
+ {
+ lhs->type_info = operand->type_info->inner;
+ }
+ }
+ else
+ {
+ lhs->type_info = operand->type_info;
+ }
+ }
+
+ after_unary:; // Label to skip standard creation if overloaded
+ }
+
+ else if (is_token(t, "sizeof"))
+ {
+ lexer_next(l);
+ if (lexer_peek(l).type == TOK_LPAREN)
+ {
+ const char *start = l->src + l->pos;
+ int depth = 0;
+ while (1)
+ {
+ Token tk = lexer_peek(l);
+ if (tk.type == TOK_EOF)
+ {
+ zpanic("Unterminated sizeof");
+ }
+ if (tk.type == TOK_LPAREN)
+ {
+ depth++;
+ }
+ if (tk.type == TOK_RPAREN)
+ {
+ depth--;
+ if (depth == 0)
+ {
+ lexer_next(l);
+ break;
+ }
+ }
+ lexer_next(l);
+ }
+ int len = (l->src + l->pos) - start;
+ char *content = xmalloc(len + 8);
+ sprintf(content, "sizeof%.*s", len, start);
+ lhs = ast_create(NODE_RAW_STMT);
+ lhs->raw_stmt.content = content;
+ lhs->type_info = type_new(TYPE_INT);
+ }
+ else
+ {
+ zpanic("sizeof must be followed by (");
+ }
+ }
+ else
+ {
+ lhs = parse_primary(ctx, l);
+ }
+
+ while (1)
+ {
+ Token op = lexer_peek(l);
+ Precedence prec = get_token_precedence(op);
+
+ // Handle postfix ++ and -- (highest postfix precedence)
+ if (op.type == TOK_OP && op.len == 2 &&
+ ((op.start[0] == '+' && op.start[1] == '+') ||
+ (op.start[0] == '-' && op.start[1] == '-')))
+ {
+ lexer_next(l); // consume ++ or --
+ ASTNode *node = ast_create(NODE_EXPR_UNARY);
+ node->unary.op = (op.start[0] == '+') ? xstrdup("_post++") : xstrdup("_post--");
+ node->unary.operand = lhs;
+ node->type_info = lhs->type_info;
+ lhs = node;
+ continue;
+ }
+
+ if (prec == PREC_NONE || prec < min_prec)
+ {
+ break;
+ }
+
+ // Pointer access: ->
+ if (op.type == TOK_ARROW && op.start[0] == '-')
+ {
+ lexer_next(l);
+ Token field = lexer_next(l);
+ if (field.type != TOK_IDENT)
+ {
+ zpanic_at(field, "Expected field name after ->");
+ break;
+ }
+ ASTNode *node = ast_create(NODE_EXPR_MEMBER);
+ node->member.target = lhs;
+ node->member.field = token_strdup(field);
+ node->member.is_pointer_access = 1;
+
+ node->type_info = get_field_type(ctx, lhs->type_info, node->member.field);
+ if (node->type_info)
+ {
+ node->resolved_type = type_to_string(node->type_info);
+ }
+ else
+ {
+ node->resolved_type = xstrdup("unknown");
+ }
+
+ lhs = node;
+ continue;
+ }
+
+ // Null-safe access: ?.
+ if (op.type == TOK_Q_DOT)
+ {
+ lexer_next(l);
+ Token field = lexer_next(l);
+ if (field.type != TOK_IDENT)
+ {
+ zpanic_at(field, "Expected field name after ?.");
+ break;
+ }
+ ASTNode *node = ast_create(NODE_EXPR_MEMBER);
+ node->member.target = lhs;
+ node->member.field = token_strdup(field);
+ node->member.is_pointer_access = 2;
+
+ node->type_info = get_field_type(ctx, lhs->type_info, node->member.field);
+ if (node->type_info)
+ {
+ node->resolved_type = type_to_string(node->type_info);
+ }
+
+ lhs = node;
+ continue;
+ }
+
+ // Postfix ? (Result Unwrap OR Ternary)
+ if (op.type == TOK_QUESTION)
+ {
+ // Disambiguate
+ Lexer lookahead = *l;
+ lexer_next(&lookahead); // skip ?
+ Token next = lexer_peek(&lookahead);
+
+ // Heuristic: If next token starts an expression => Ternary
+ // (Ident, Number, String, (, {, -, !, *, etc)
+ int is_ternary = 0;
+ if (next.type == TOK_INT || next.type == TOK_FLOAT || next.type == TOK_STRING ||
+ next.type == TOK_IDENT || next.type == TOK_LPAREN || next.type == TOK_LBRACE ||
+ next.type == TOK_SIZEOF || next.type == TOK_DEFER || next.type == TOK_AUTOFREE ||
+ next.type == TOK_FSTRING || next.type == TOK_CHAR)
+ {
+ is_ternary = 1;
+ }
+ // Check unary ops
+ if (next.type == TOK_OP)
+ {
+ if (is_token(next, "-") || is_token(next, "!") || is_token(next, "*") ||
+ is_token(next, "&") || is_token(next, "~"))
+ {
+ is_ternary = 1;
+ }
+ }
+
+ if (is_ternary)
+ {
+ if (PREC_TERNARY < min_prec)
+ {
+ break; // Return to caller to handle precedence
+ }
+
+ lexer_next(l); // consume ?
+ ASTNode *true_expr = parse_expression(ctx, l);
+ expect(l, TOK_COLON, "Expected : in ternary");
+ ASTNode *false_expr = parse_expr_prec(ctx, l, PREC_TERNARY); // Right associative
+
+ ASTNode *tern = ast_create(NODE_TERNARY);
+ zen_trigger_at(TRIGGER_TERNARY, lhs->token);
+
+ tern->ternary.cond = lhs;
+ tern->ternary.true_expr = true_expr;
+ tern->ternary.false_expr = false_expr;
+
+ // Type inference hint: Both branches should match?
+ // Logic later in codegen/semant.
+ lhs = tern;
+ continue;
+ }
+
+ // Otherwise: Unwrap (High Precedence)
+ if (PREC_CALL < min_prec)
+ {
+ break;
+ }
+
+ lexer_next(l);
+ ASTNode *n = ast_create(NODE_TRY);
+ n->try_stmt.expr = lhs;
+ lhs = n;
+ continue;
+ }
+
+ // Pipe: |>
+ if (op.type == TOK_PIPE || (op.type == TOK_OP && is_token(op, "|>")))
+ {
+ lexer_next(l);
+ ASTNode *rhs = parse_expr_prec(ctx, l, prec + 1);
+ if (rhs->type == NODE_EXPR_CALL)
+ {
+ ASTNode *old_args = rhs->call.args;
+ lhs->next = old_args;
+ rhs->call.args = lhs;
+ lhs = rhs;
+ }
+ else
+ {
+ ASTNode *call = ast_create(NODE_EXPR_CALL);
+ call->call.callee = rhs;
+ call->call.args = lhs;
+ lhs->next = NULL;
+ lhs = call;
+ }
+ continue;
+ }
+
+ lexer_next(l); // Consume operator/paren/bracket
+
+ // Call: (...)
+ if (op.type == TOK_LPAREN)
+ {
+ ASTNode *call = ast_create(NODE_EXPR_CALL);
+ call->call.callee = lhs;
+ ASTNode *head = NULL, *tail = NULL;
+ char **arg_names = NULL;
+ int arg_count = 0;
+ int has_named = 0;
+
+ if (lexer_peek(l).type != TOK_RPAREN)
+ {
+ while (1)
+ {
+ char *arg_name = NULL;
+
+ // Check for named argument: IDENT : expr
+ Token t1 = lexer_peek(l);
+ if (t1.type == TOK_IDENT)
+ {
+ // Lookahead for colon
+ Token t2 = lexer_peek2(l);
+ if (t2.type == TOK_COLON)
+ {
+ arg_name = token_strdup(t1);
+ has_named = 1;
+ lexer_next(l); // eat IDENT
+ lexer_next(l); // eat :
+ }
+ }
+
+ ASTNode *arg = parse_expression(ctx, l);
+ if (!head)
+ {
+ head = arg;
+ }
+ else
+ {
+ tail->next = arg;
+ }
+ tail = arg;
+
+ // Store arg name
+ arg_names = xrealloc(arg_names, (arg_count + 1) * sizeof(char *));
+ arg_names[arg_count] = arg_name;
+ arg_count++;
+
+ if (lexer_peek(l).type == TOK_COMMA)
+ {
+ lexer_next(l);
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ if (lexer_next(l).type != TOK_RPAREN)
+ {
+ zpanic("Expected )");
+ }
+ call->call.args = head;
+ call->call.arg_names = has_named ? arg_names : NULL;
+ call->call.arg_count = arg_count;
+
+ // FIX: Propagate return type from function type info
+ call->resolved_type = xstrdup("unknown");
+ if (lhs->type_info && lhs->type_info->kind == TYPE_FUNCTION && lhs->type_info->inner)
+ {
+ call->type_info = lhs->type_info->inner;
+ }
+
+ lhs = call;
+ continue;
+ }
+
+ // Index: [...] or Slice: [start..end]
+ if (op.type == TOK_LBRACKET)
+ {
+ ASTNode *start = NULL;
+ ASTNode *end = NULL;
+ int is_slice = 0;
+
+ // Case: [..] or [..end]
+ if (lexer_peek(l).type == TOK_DOTDOT)
+ {
+ is_slice = 1;
+ lexer_next(l); // consume ..
+ if (lexer_peek(l).type != TOK_RBRACKET)
+ {
+ end = parse_expression(ctx, l);
+ }
+ }
+ else
+ {
+ // Case: [start] or [start..] or [start..end]
+ start = parse_expression(ctx, l);
+ if (lexer_peek(l).type == TOK_DOTDOT)
+ {
+ is_slice = 1;
+ lexer_next(l); // consume ..
+ if (lexer_peek(l).type != TOK_RBRACKET)
+ {
+ end = parse_expression(ctx, l);
+ }
+ }
+ }
+
+ if (lexer_next(l).type != TOK_RBRACKET)
+ {
+ zpanic("Expected ]");
+ }
+
+ if (is_slice)
+ {
+ ASTNode *node = ast_create(NODE_EXPR_SLICE);
+ node->slice.array = lhs;
+ node->slice.start = start;
+ node->slice.end = end;
+
+ // Type Inference & Registration
+ if (lhs->type_info)
+ {
+ Type *inner = NULL;
+ if (lhs->type_info->kind == TYPE_ARRAY)
+ {
+ inner = lhs->type_info->inner;
+ }
+ else if (lhs->type_info->kind == TYPE_POINTER)
+ {
+ inner = lhs->type_info->inner;
+ }
+
+ if (inner)
+ {
+ node->type_info = type_new(TYPE_ARRAY);
+ node->type_info->inner = inner;
+ node->type_info->array_size = 0; // Slice
+
+ // Clean up string for registration (e.g. "int" from "int*")
+ char *inner_str = type_to_string(inner);
+
+ // Strip * if it somehow managed to keep one, though parse_type_formal
+ // should handle it For now assume type_to_string gives base type
+ register_slice(ctx, inner_str);
+ }
+ }
+
+ lhs = node;
+ }
+ else
+ {
+ ASTNode *node = ast_create(NODE_EXPR_INDEX);
+ node->index.array = lhs;
+ node->index.index = start;
+
+ // Static Array Bounds Check
+ if (lhs->type_info && lhs->type_info->kind == TYPE_ARRAY &&
+ lhs->type_info->array_size > 0)
+ {
+ if (start->type == NODE_EXPR_LITERAL && start->literal.type_kind == 0)
+ {
+ int idx = start->literal.int_val;
+ if (idx < 0 || idx >= lhs->type_info->array_size)
+ {
+ warn_array_bounds(op, idx, lhs->type_info->array_size);
+ }
+ }
+ }
+
+ lhs = node;
+ }
+ continue;
+ }
+
+ // Member: .
+ if (op.type == TOK_OP && is_token(op, "."))
+ {
+ Token field = lexer_next(l);
+ if (field.type != TOK_IDENT)
+ {
+ zpanic_at(field, "Expected field name after .");
+ break;
+ }
+ ASTNode *node = ast_create(NODE_EXPR_MEMBER);
+ node->member.target = lhs;
+ node->member.field = token_strdup(field);
+ node->member.is_pointer_access = 0;
+
+ if (lhs->type_info && lhs->type_info->kind == TYPE_POINTER)
+ {
+ node->member.is_pointer_access = 1;
+
+ // Special case: .val() on pointer = dereference
+ if (strcmp(node->member.field, "val") == 0 && lexer_peek(l).type == TOK_LPAREN)
+ {
+ lexer_next(l); // consume (
+ if (lexer_peek(l).type == TOK_RPAREN)
+ {
+ lexer_next(l); // consume )
+ // Rewrite to dereference: *ptr
+ ASTNode *deref = ast_create(NODE_EXPR_UNARY);
+ deref->unary.op = xstrdup("*");
+ deref->unary.operand = lhs;
+ deref->type_info = lhs->type_info->inner;
+ lhs = deref;
+ continue;
+ }
+ }
+ }
+ else if (lhs->type == NODE_EXPR_VAR)
+ {
+ char *type = find_symbol_type(ctx, lhs->var_ref.name);
+ if (type && strchr(type, '*'))
+ {
+ node->member.is_pointer_access = 1;
+
+ // Special case: .val() on pointer = dereference
+ if (strcmp(node->member.field, "val") == 0 && lexer_peek(l).type == TOK_LPAREN)
+ {
+ lexer_next(l); // consume (
+ if (lexer_peek(l).type == TOK_RPAREN)
+ {
+ lexer_next(l); // consume )
+ // Rewrite to dereference: *ptr
+ ASTNode *deref = ast_create(NODE_EXPR_UNARY);
+ deref->unary.op = xstrdup("*");
+ deref->unary.operand = lhs;
+ // Try to get inner type
+ if (lhs->type_info && lhs->type_info->kind == TYPE_POINTER)
+ {
+ deref->type_info = lhs->type_info->inner;
+ }
+ lhs = deref;
+ continue;
+ }
+ }
+ }
+ if (strcmp(lhs->var_ref.name, "self") == 0 && !node->member.is_pointer_access)
+ {
+ node->member.is_pointer_access = 1;
+ }
+ }
+
+ node->type_info = get_field_type(ctx, lhs->type_info, node->member.field);
+
+ // FIX: If not a field, check if it is a method
+ if (!node->type_info && lhs->type_info)
+ {
+ char *struct_name = NULL;
+ Type *st = lhs->type_info;
+ if (st->kind == TYPE_STRUCT)
+ {
+ struct_name = st->name;
+ }
+ else if (st->kind == TYPE_POINTER && st->inner && st->inner->kind == TYPE_STRUCT)
+ {
+ struct_name = st->inner->name;
+ }
+
+ if (struct_name)
+ {
+ char mangled[256];
+ sprintf(mangled, "%s_%s", struct_name, node->member.field);
+
+ FuncSig *sig = find_func(ctx, mangled);
+ if (sig)
+ {
+ // It is a method! Create a Function Type Info to carry the return type
+ Type *ft = type_new(TYPE_FUNCTION);
+ ft->name = xstrdup(mangled);
+ ft->inner = sig->ret_type; // Return type
+ node->type_info = ft;
+ }
+ }
+ }
+
+ if (node->type_info)
+ {
+ node->resolved_type = type_to_string(node->type_info);
+ }
+ else
+ {
+ node->resolved_type = xstrdup("unknown");
+ }
+
+ lhs = node;
+ continue;
+ }
+
+ ASTNode *rhs = parse_expr_prec(ctx, l, prec + 1);
+ ASTNode *bin = ast_create(NODE_EXPR_BINARY);
+ bin->token = op;
+ if (op.type == TOK_OP)
+ {
+ if (is_token(op, "&") || is_token(op, "|") || is_token(op, "^"))
+ {
+ zen_trigger_at(TRIGGER_BITWISE, op);
+ }
+ else if (is_token(op, "<<") || is_token(op, ">>"))
+ {
+ zen_trigger_at(TRIGGER_BITWISE, op);
+ }
+ }
+ bin->binary.left = lhs;
+ bin->binary.right = rhs;
+
+ if (op.type == TOK_LANGLE)
+ {
+ bin->binary.op = xstrdup("<");
+ }
+ else if (op.type == TOK_RANGLE)
+ {
+ bin->binary.op = xstrdup(">");
+ }
+ else
+ {
+ bin->binary.op = token_strdup(op);
+ }
+
+ if (strcmp(bin->binary.op, "/") == 0 || strcmp(bin->binary.op, "%") == 0)
+ {
+ if (rhs->type == NODE_EXPR_LITERAL && rhs->literal.type_kind == 0 &&
+ rhs->literal.int_val == 0)
+ {
+ warn_division_by_zero(op);
+ }
+ }
+
+ if (is_comparison_op(bin->binary.op))
+ {
+ // Check for identical operands (x == x)
+ if (lhs->type == NODE_EXPR_VAR && rhs->type == NODE_EXPR_VAR)
+ {
+ if (strcmp(lhs->var_ref.name, rhs->var_ref.name) == 0)
+ {
+ if (strcmp(bin->binary.op, "==") == 0 || strcmp(bin->binary.op, ">=") == 0 ||
+ strcmp(bin->binary.op, "<=") == 0)
+ {
+ warn_comparison_always_true(op, "Comparing a variable to itself");
+ }
+ else if (strcmp(bin->binary.op, "!=") == 0 ||
+ strcmp(bin->binary.op, ">") == 0 || strcmp(bin->binary.op, "<") == 0)
+ {
+ warn_comparison_always_false(op, "Comparing a variable to itself");
+ }
+ }
+ }
+ else if (lhs->type == NODE_EXPR_LITERAL && lhs->literal.type_kind == 0 &&
+ rhs->type == NODE_EXPR_LITERAL && rhs->literal.type_kind == 0)
+ {
+ // Check if literals make sense (e.g. 5 > 5)
+ if (lhs->literal.int_val == rhs->literal.int_val)
+ {
+ if (strcmp(bin->binary.op, "==") == 0 || strcmp(bin->binary.op, ">=") == 0 ||
+ strcmp(bin->binary.op, "<=") == 0)
+ {
+ warn_comparison_always_true(op, "Comparing identical literals");
+ }
+ else
+ {
+ warn_comparison_always_false(op, "Comparing identical literals");
+ }
+ }
+ }
+
+ if (lhs->type_info && type_is_unsigned(lhs->type_info))
+ {
+ if (rhs->type == NODE_EXPR_LITERAL && rhs->literal.type_kind == 0 &&
+ rhs->literal.int_val == 0)
+ {
+ if (strcmp(bin->binary.op, ">=") == 0)
+ {
+ warn_comparison_always_true(op, "Unsigned value is always >= 0");
+ }
+ else if (strcmp(bin->binary.op, "<") == 0)
+ {
+ warn_comparison_always_false(op, "Unsigned value is never < 0");
+ }
+ }
+ }
+ }
+
+ if (strcmp(bin->binary.op, "=") == 0 || strcmp(bin->binary.op, "+=") == 0 ||
+ strcmp(bin->binary.op, "-=") == 0 || strcmp(bin->binary.op, "*=") == 0 ||
+ strcmp(bin->binary.op, "/=") == 0)
+ {
+
+ if (lhs->type == NODE_EXPR_VAR)
+ {
+ // Check if the variable is const
+ Type *t = find_symbol_type_info(ctx, lhs->var_ref.name);
+ if (t && t->is_const)
+ {
+ zpanic_at(op, "Cannot assign to const variable '%s'", lhs->var_ref.name);
+ }
+
+ // Check if the variable is immutable
+ if (!is_var_mutable(ctx, lhs->var_ref.name))
+ {
+ zpanic_at(op,
+ "Cannot assign to immutable variable '%s' (use 'var mut' to make it "
+ "mutable)",
+ lhs->var_ref.name);
+ }
+ }
+ }
+
+ int is_compound = 0;
+ size_t op_len = strlen(bin->binary.op);
+
+ // Check if operator ends with '=' but is not ==, !=, <=, >=
+ if (op_len > 1 && bin->binary.op[op_len - 1] == '=')
+ {
+ char c = bin->binary.op[0];
+ if (c != '=' && c != '!' && c != '<' && c != '>')
+ {
+ is_compound = 1;
+ }
+ // Special handle for <<= and >>=
+ if (strcmp(bin->binary.op, "<<=") == 0 || strcmp(bin->binary.op, ">>=") == 0)
+ {
+ is_compound = 1;
+ }
+ }
+
+ if (is_compound)
+ {
+ ASTNode *op_node = ast_create(NODE_EXPR_BINARY);
+ op_node->binary.left = lhs;
+ op_node->binary.right = rhs;
+
+ // Extract the base operator (remove last char '=')
+ char *inner_op = xmalloc(op_len);
+ strncpy(inner_op, bin->binary.op, op_len - 1);
+ inner_op[op_len - 1] = '\0';
+ op_node->binary.op = inner_op;
+
+ // Inherit type info temporarily
+ if (lhs->type_info && rhs->type_info && type_eq(lhs->type_info, rhs->type_info))
+ {
+ op_node->type_info = lhs->type_info;
+ }
+
+ const char *inner_method = get_operator_method(inner_op);
+ if (inner_method)
+ {
+ Type *lt = lhs->type_info;
+ char *struct_name = NULL;
+ int is_lhs_ptr = 0;
+
+ if (lt)
+ {
+ if (lt->kind == TYPE_STRUCT)
+ {
+ struct_name = lt->name;
+ is_lhs_ptr = 0;
+ }
+ else if (lt->kind == TYPE_POINTER && lt->inner->kind == TYPE_STRUCT)
+ {
+ struct_name = lt->inner->name;
+ is_lhs_ptr = 1;
+ }
+ }
+
+ if (struct_name)
+ {
+ char mangled[256];
+ sprintf(mangled, "%s_%s", struct_name, inner_method);
+ FuncSig *sig = find_func(ctx, mangled);
+ if (sig)
+ {
+ // Rewrite op_node from BINARY -> CALL
+ ASTNode *call = ast_create(NODE_EXPR_CALL);
+ ASTNode *callee = ast_create(NODE_EXPR_VAR);
+ callee->var_ref.name = xstrdup(mangled);
+ call->call.callee = callee;
+
+ // Handle 'self' argument
+ ASTNode *arg1 = lhs;
+ if (sig->total_args > 0 && sig->arg_types[0]->kind == TYPE_POINTER &&
+ !is_lhs_ptr)
+ {
+ ASTNode *addr = ast_create(NODE_EXPR_UNARY);
+ addr->unary.op = xstrdup("&");
+ addr->unary.operand = lhs;
+ addr->type_info = type_new_ptr(lt);
+ arg1 = addr;
+ }
+ else if (is_lhs_ptr && sig->arg_types[0]->kind != TYPE_POINTER)
+ {
+ ASTNode *deref = ast_create(NODE_EXPR_UNARY);
+ deref->unary.op = xstrdup("*");
+ deref->unary.operand = lhs;
+ arg1 = deref;
+ }
+
+ call->call.args = arg1;
+ arg1->next = rhs;
+ rhs->next = NULL;
+ call->type_info = sig->ret_type;
+
+ // Replace op_node with the call
+ op_node = call;
+ }
+ }
+ }
+
+ free(bin->binary.op);
+ bin->binary.op = xstrdup("=");
+ bin->binary.right = op_node;
+ }
+
+ // Index Set Overload: Call(get, idx) = val --> Call(set, idx, val)
+ if (strcmp(bin->binary.op, "=") == 0 && lhs->type == NODE_EXPR_CALL)
+ {
+ if (lhs->call.callee->type == NODE_EXPR_VAR)
+ {
+ char *name = lhs->call.callee->var_ref.name;
+ // Check if it ends in "_get"
+ size_t len = strlen(name);
+ if (len > 4 && strcmp(name + len - 4, "_get") == 0)
+ {
+ char *set_name = xstrdup(name);
+ set_name[len - 3] = 's'; // Replace 'g' with 's' -> _set
+ set_name[len - 2] = 'e';
+ set_name[len - 1] = 't';
+
+ if (find_func(ctx, set_name))
+ {
+ // Create NEW Call Node for Set
+ ASTNode *set_call = ast_create(NODE_EXPR_CALL);
+ ASTNode *set_callee = ast_create(NODE_EXPR_VAR);
+ set_callee->var_ref.name = set_name;
+ set_call->call.callee = set_callee;
+
+ // Clone argument list (Shallow copy of arg nodes to preserve chain for get)
+ ASTNode *lhs_args = lhs->call.args;
+ ASTNode *new_head = NULL;
+ ASTNode *new_tail = NULL;
+
+ while (lhs_args)
+ {
+ ASTNode *arg_copy = xmalloc(sizeof(ASTNode));
+ memcpy(arg_copy, lhs_args, sizeof(ASTNode));
+ arg_copy->next = NULL;
+
+ if (!new_head)
+ {
+ new_head = arg_copy;
+ }
+ else
+ {
+ new_tail->next = arg_copy;
+ }
+ new_tail = arg_copy;
+
+ lhs_args = lhs_args->next;
+ }
+
+ // Append RHS to new args
+ ASTNode *val_expr = bin->binary.right;
+ if (new_tail)
+ {
+ new_tail->next = val_expr;
+ }
+ else
+ {
+ new_head = val_expr;
+ }
+
+ set_call->call.args = new_head;
+ set_call->type_info = type_new(TYPE_VOID);
+
+ lhs = set_call; // Use the new Set call as the result
+ continue;
+ }
+ else
+ {
+ free(set_name);
+ }
+ }
+ }
+ }
+
+ const char *method = get_operator_method(bin->binary.op);
+
+ if (method)
+ {
+ Type *lt = lhs->type_info;
+ char *struct_name = NULL;
+ int is_lhs_ptr = 0;
+
+ if (lt)
+ {
+ if (lt->kind == TYPE_STRUCT)
+ {
+ struct_name = lt->name;
+ is_lhs_ptr = 0;
+ }
+ else if (lt->kind == TYPE_POINTER && lt->inner->kind == TYPE_STRUCT)
+ {
+ struct_name = lt->inner->name;
+ is_lhs_ptr = 1;
+ }
+ }
+
+ if (struct_name)
+ {
+ char mangled[256];
+ sprintf(mangled, "%s_%s", struct_name, method);
+
+ FuncSig *sig = find_func(ctx, mangled);
+
+ if (sig)
+ {
+ ASTNode *call = ast_create(NODE_EXPR_CALL);
+ ASTNode *callee = ast_create(NODE_EXPR_VAR);
+ callee->var_ref.name = xstrdup(mangled);
+ call->call.callee = callee;
+
+ ASTNode *arg1 = lhs;
+
+ // Check if function expects a pointer for 'self'
+ if (sig->total_args > 0 && sig->arg_types[0] &&
+ sig->arg_types[0]->kind == TYPE_POINTER)
+ {
+ if (!is_lhs_ptr)
+ {
+ // Value -> Pointer.
+ int is_rvalue =
+ (lhs->type == NODE_EXPR_CALL || lhs->type == NODE_EXPR_BINARY ||
+ lhs->type == NODE_EXPR_STRUCT_INIT ||
+ lhs->type == NODE_EXPR_CAST || lhs->type == NODE_MATCH);
+
+ ASTNode *addr = ast_create(NODE_EXPR_UNARY);
+ addr->unary.op = is_rvalue ? xstrdup("&_rval") : xstrdup("&");
+ addr->unary.operand = lhs;
+ addr->type_info = type_new_ptr(lt);
+ arg1 = addr;
+ }
+ }
+ else
+ {
+ // Function expects value
+ if (is_lhs_ptr)
+ {
+ // Have pointer, need value -> *lhs
+ ASTNode *deref = ast_create(NODE_EXPR_UNARY);
+ deref->unary.op = xstrdup("*");
+ deref->unary.operand = lhs;
+ if (lt && lt->kind == TYPE_POINTER)
+ {
+ deref->type_info = lt->inner;
+ }
+ arg1 = deref;
+ }
+ }
+
+ call->call.args = arg1;
+ arg1->next = rhs;
+ rhs->next = NULL;
+
+ call->type_info = sig->ret_type;
+ call->resolved_type = type_to_string(sig->ret_type);
+
+ lhs = call;
+ continue; // Loop again with result as new lhs
+ }
+ }
+ }
+
+ // Standard Type Checking (if no overload found)
+ if (lhs->type_info && rhs->type_info)
+ {
+ if (is_comparison_op(bin->binary.op))
+ {
+ bin->type_info = type_new(TYPE_INT); // bool
+ char *t1 = type_to_string(lhs->type_info);
+ char *t2 = type_to_string(rhs->type_info);
+ // Skip type check if either operand is void* (escape hatch type)
+ int skip_check = (strcmp(t1, "void*") == 0 || strcmp(t2, "void*") == 0);
+ if (!skip_check && !type_eq(lhs->type_info, rhs->type_info))
+ {
+ char msg[256];
+ sprintf(msg, "Type mismatch in comparison: cannot compare '%s' and '%s'", t1,
+ t2);
+
+ char suggestion[256];
+ sprintf(suggestion, "Both operands must have compatible types for comparison");
+
+ zpanic_with_suggestion(op, msg, suggestion);
+ }
+ }
+ else
+ {
+ if (type_eq(lhs->type_info, rhs->type_info))
+ {
+ bin->type_info = lhs->type_info;
+ }
+ else
+ {
+ char *t1 = type_to_string(lhs->type_info);
+ char *t2 = type_to_string(rhs->type_info);
+
+ // Allow pointer arithmetic: ptr + int, ptr - int, int + ptr
+ int is_ptr_arith = 0;
+ if (strcmp(bin->binary.op, "+") == 0 || strcmp(bin->binary.op, "-") == 0)
+ {
+ int lhs_is_ptr = (lhs->type_info->kind == TYPE_POINTER ||
+ (t1 && strstr(t1, "*") != NULL));
+ int rhs_is_ptr = (rhs->type_info->kind == TYPE_POINTER ||
+ (t2 && strstr(t2, "*") != NULL));
+ int lhs_is_int =
+ (lhs->type_info->kind == TYPE_INT || lhs->type_info->kind == TYPE_I32 ||
+ lhs->type_info->kind == TYPE_I64 ||
+ lhs->type_info->kind == TYPE_ISIZE ||
+ lhs->type_info->kind == TYPE_USIZE ||
+ (t1 && (strcmp(t1, "int") == 0 || strcmp(t1, "isize") == 0 ||
+ strcmp(t1, "usize") == 0 || strcmp(t1, "size_t") == 0 ||
+ strcmp(t1, "ptrdiff_t") == 0)));
+ int rhs_is_int =
+ (rhs->type_info->kind == TYPE_INT || rhs->type_info->kind == TYPE_I32 ||
+ rhs->type_info->kind == TYPE_I64 ||
+ rhs->type_info->kind == TYPE_ISIZE ||
+ rhs->type_info->kind == TYPE_USIZE ||
+ (t2 && (strcmp(t2, "int") == 0 || strcmp(t2, "isize") == 0 ||
+ strcmp(t2, "usize") == 0 || strcmp(t2, "size_t") == 0 ||
+ strcmp(t2, "ptrdiff_t") == 0)));
+
+ if ((lhs_is_ptr && rhs_is_int) || (lhs_is_int && rhs_is_ptr))
+ {
+ is_ptr_arith = 1;
+ bin->type_info = lhs_is_ptr ? lhs->type_info : rhs->type_info;
+ }
+ }
+
+ if (!is_ptr_arith)
+ {
+ char msg[256];
+ sprintf(msg, "Type mismatch in binary operation '%s'", bin->binary.op);
+
+ char suggestion[512];
+ sprintf(suggestion,
+ "Left operand has type '%s', right operand has type '%s'\n = "
+ "note: Consider casting one operand to match the other",
+ t1, t2);
+
+ zpanic_with_suggestion(op, msg, suggestion);
+ }
+ }
+ }
+ }
+
+ lhs = bin;
+ }
+ return lhs;
+}
+
+ASTNode *parse_arrow_lambda_single(ParserContext *ctx, Lexer *l, char *param_name)
+{
+ ASTNode *lambda = ast_create(NODE_LAMBDA);
+ lambda->lambda.param_names = xmalloc(sizeof(char *));
+ lambda->lambda.param_names[0] = param_name;
+ lambda->lambda.num_params = 1;
+
+ // Default param type: int
+ lambda->lambda.param_types = xmalloc(sizeof(char *));
+ lambda->lambda.param_types[0] = xstrdup("int");
+
+ // Create Type Info: int -> int
+ Type *t = type_new(TYPE_FUNCTION);
+ t->inner = type_new(TYPE_INT); // Return
+ t->args = xmalloc(sizeof(Type *));
+ t->args[0] = type_new(TYPE_INT); // Arg
+ t->arg_count = 1;
+ lambda->type_info = t;
+
+ // Body parsing...
+ ASTNode *body_block = NULL;
+ if (lexer_peek(l).type == TOK_LBRACE)
+ {
+ body_block = parse_block(ctx, l);
+ }
+ else
+ {
+ ASTNode *expr = parse_expression(ctx, l);
+ ASTNode *ret = ast_create(NODE_RETURN);
+ ret->ret.value = expr;
+ body_block = ast_create(NODE_BLOCK);
+ body_block->block.statements = ret;
+ }
+ lambda->lambda.body = body_block;
+ lambda->lambda.return_type = xstrdup("int");
+ lambda->lambda.lambda_id = ctx->lambda_counter++;
+ lambda->lambda.is_expression = 1;
+ register_lambda(ctx, lambda);
+ analyze_lambda_captures(ctx, lambda);
+ return lambda;
+}
+
+ASTNode *parse_arrow_lambda_multi(ParserContext *ctx, Lexer *l, char **param_names, int num_params)
+{
+ ASTNode *lambda = ast_create(NODE_LAMBDA);
+ lambda->lambda.param_names = param_names;
+ lambda->lambda.num_params = num_params;
+
+ // Type Info construction
+ Type *t = type_new(TYPE_FUNCTION);
+ t->inner = type_new(TYPE_INT);
+ t->args = xmalloc(sizeof(Type *) * num_params);
+ t->arg_count = num_params;
+
+ lambda->lambda.param_types = xmalloc(sizeof(char *) * num_params);
+ for (int i = 0; i < num_params; i++)
+ {
+ lambda->lambda.param_types[i] = xstrdup("int");
+ t->args[i] = type_new(TYPE_INT);
+ }
+ lambda->type_info = t;
+
+ // Body parsing...
+ ASTNode *body_block = NULL;
+ if (lexer_peek(l).type == TOK_LBRACE)
+ {
+ body_block = parse_block(ctx, l);
+ }
+ else
+ {
+ ASTNode *expr = parse_expression(ctx, l);
+ ASTNode *ret = ast_create(NODE_RETURN);
+ ret->ret.value = expr;
+ body_block = ast_create(NODE_BLOCK);
+ body_block->block.statements = ret;
+ }
+ lambda->lambda.body = body_block;
+ lambda->lambda.return_type = xstrdup("int");
+ lambda->lambda.lambda_id = ctx->lambda_counter++;
+ lambda->lambda.is_expression = 1;
+ register_lambda(ctx, lambda);
+ analyze_lambda_captures(ctx, lambda);
+ return lambda;
+}