summaryrefslogtreecommitdiff
path: root/src/parser
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
parent2e7abed7cfe84a2c0df371cde35f8f68cfdca16c (diff)
Added src/ folder. Now I will add the rest.
Diffstat (limited to 'src/parser')
-rw-r--r--src/parser/parser.h433
-rw-r--r--src/parser/parser_core.c618
-rw-r--r--src/parser/parser_expr.c3627
-rw-r--r--src/parser/parser_stmt.c3900
-rw-r--r--src/parser/parser_type.c706
-rw-r--r--src/parser/parser_utils.c2459
6 files changed, 11743 insertions, 0 deletions
diff --git a/src/parser/parser.h b/src/parser/parser.h
new file mode 100644
index 0000000..b3213c9
--- /dev/null
+++ b/src/parser/parser.h
@@ -0,0 +1,433 @@
+
+#ifndef PARSER_H
+#define PARSER_H
+
+#include "ast.h"
+#include "zprep.h"
+
+// Operator precedence for expression parsing
+typedef enum
+{
+ PREC_NONE,
+ PREC_ASSIGNMENT,
+ PREC_TERNARY,
+ PREC_OR,
+ PREC_AND,
+ PREC_EQUALITY,
+ PREC_COMPARISON,
+ PREC_TERM,
+ PREC_FACTOR,
+ PREC_UNARY,
+ PREC_CALL,
+ PREC_PRIMARY
+} Precedence;
+
+// Main entry points
+// Forward declarations
+struct ParserContext;
+typedef struct ParserContext ParserContext;
+
+ASTNode *parse_program(ParserContext *ctx, Lexer *l);
+
+extern ParserContext *g_parser_ctx;
+
+// Symbol table
+typedef struct Symbol
+{
+ char *name;
+ char *type_name;
+ Type *type_info;
+ int is_mutable;
+ int is_used;
+ int is_autofree;
+ Token decl_token;
+ int is_const_value;
+ int const_int_val;
+ struct Symbol *next;
+} Symbol;
+
+typedef struct Scope
+{
+ Symbol *symbols;
+ struct Scope *parent;
+} Scope;
+
+// Function registry
+typedef struct FuncSig
+{
+ char *name;
+ Token decl_token; // For LSP
+ int total_args;
+ char **defaults;
+ Type **arg_types;
+ Type *ret_type;
+ int is_varargs;
+ int is_async; // Async function flag
+ int must_use; // Attribute: warn if return value discarded
+ struct FuncSig *next;
+} FuncSig;
+
+// Lambda tracking
+typedef struct LambdaRef
+{
+ ASTNode *node;
+ struct LambdaRef *next;
+} LambdaRef;
+
+typedef struct GenericTemplate
+{
+ char *name;
+ ASTNode *struct_node;
+ struct GenericTemplate *next;
+} GenericTemplate;
+
+typedef struct GenericFuncTemplate
+{
+ char *name;
+ char *generic_param;
+ ASTNode *func_node;
+ struct GenericFuncTemplate *next;
+} GenericFuncTemplate;
+
+typedef struct GenericImplTemplate
+{
+ char *struct_name;
+ char *generic_param;
+ ASTNode *impl_node;
+ struct GenericImplTemplate *next;
+} GenericImplTemplate;
+
+typedef struct ImportedFile
+{
+ char *path;
+ struct ImportedFile *next;
+} ImportedFile;
+
+typedef struct VarMutability
+{
+ char *name;
+ int is_mutable;
+ struct VarMutability *next;
+} VarMutability;
+
+// Instantiation tracking
+typedef struct Instantiation
+{
+ char *name;
+ ASTNode *struct_node;
+ struct Instantiation *next;
+} Instantiation;
+
+typedef struct StructRef
+{
+ ASTNode *node;
+ struct StructRef *next;
+} StructRef;
+
+typedef struct StructDef
+{
+ char *name;
+ ASTNode *node;
+ struct StructDef *next;
+} StructDef;
+
+// Type tracking
+typedef struct SliceType
+{
+ char *name;
+ struct SliceType *next;
+} SliceType;
+
+typedef struct TupleType
+{
+ char *sig;
+ struct TupleType *next;
+} TupleType;
+
+// Enum tracking
+typedef struct EnumVariantReg
+{
+ char *enum_name;
+ char *variant_name;
+ int tag_id;
+ struct EnumVariantReg *next;
+} EnumVariantReg;
+
+// Deprecated function tracking
+typedef struct DeprecatedFunc
+{
+ char *name;
+ char *reason; // Optional reason message
+ struct DeprecatedFunc *next;
+} DeprecatedFunc;
+
+// Module system
+typedef struct Module
+{
+ char *alias;
+ char *path;
+ char *base_name;
+ int is_c_header;
+ struct Module *next;
+} Module;
+
+typedef struct SelectiveImport
+{
+ char *symbol;
+ char *alias;
+ char *source_module;
+ struct SelectiveImport *next;
+} SelectiveImport;
+
+// Impl cache
+typedef struct ImplReg
+{
+ char *trait;
+ char *strct;
+ struct ImplReg *next;
+} ImplReg;
+
+// Plugin tracking
+typedef struct ImportedPlugin
+{
+ char *name; // Original plugin name (for example, "brainfuck")
+ char *alias; // Optional alias (for example, "bf"), NULL if no alias
+ struct ImportedPlugin *next;
+} ImportedPlugin;
+
+struct ParserContext
+{
+ Scope *current_scope;
+ FuncSig *func_registry;
+
+ // Lambdas
+ LambdaRef *global_lambdas;
+ int lambda_counter;
+
+// Generics
+#define MAX_KNOWN_GENERICS 1024
+ char *known_generics[MAX_KNOWN_GENERICS];
+ int known_generics_count;
+ GenericTemplate *templates;
+ GenericFuncTemplate *func_templates;
+ GenericImplTemplate *impl_templates;
+
+ // Instantiations
+ Instantiation *instantiations;
+ ASTNode *instantiated_structs;
+ ASTNode *instantiated_funcs;
+
+ // Structs/Enums
+ StructRef *parsed_structs_list;
+ StructRef *parsed_enums_list;
+ StructRef *parsed_funcs_list;
+ StructRef *parsed_impls_list;
+ StructRef *parsed_globals_list;
+ StructDef *struct_defs;
+ EnumVariantReg *enum_variants;
+ ImplReg *registered_impls;
+
+ // Types
+ SliceType *used_slices;
+ TupleType *used_tuples;
+
+ // Modules/Imports
+ Module *modules;
+ SelectiveImport *selective_imports;
+ char *current_module_prefix;
+ ImportedFile *imported_files;
+ ImportedPlugin *imported_plugins; // Plugin imports
+
+ // Config/State
+ int immutable_by_default;
+ char *current_impl_struct;
+
+ // Internal tracking
+ VarMutability *var_mutability_table;
+ DeprecatedFunc *deprecated_funcs;
+
+ // LSP / Fault Tolerance
+ int is_fault_tolerant;
+ void *error_callback_data;
+ void (*on_error)(void *data, Token t, const char *msg);
+
+ // LSP: Flat symbol list (persists after parsing for LSP queries)
+ Symbol *all_symbols;
+
+ // External C interop: suppress undefined warnings for external symbols
+ int has_external_includes; // Set when include <...> is used
+ char **extern_symbols; // Explicitly declared extern symbols
+ int extern_symbol_count;
+
+ // Codegen state:
+ FILE *hoist_out; // For plugins to hoist code to file scope
+ int skip_preamble; // If 1, codegen_node(NODE_ROOT) won't emit preamble
+ int is_repl; // REPL mode flag
+};
+
+// Token helpers
+char *token_strdup(Token t);
+int is_token(Token t, const char *s);
+Token expect(Lexer *l, TokenType type, const char *msg);
+void skip_comments(Lexer *l);
+char *consume_until_semicolon(Lexer *l);
+char *consume_and_rewrite(ParserContext *ctx, Lexer *l);
+
+// C reserved word warnings
+int is_c_reserved_word(const char *name);
+void warn_c_reserved_word(Token t, const char *name);
+
+// Symbol table
+void enter_scope(ParserContext *ctx);
+void exit_scope(ParserContext *ctx);
+void add_symbol(ParserContext *ctx, const char *n, const char *t, Type *type_info);
+void add_symbol_with_token(ParserContext *ctx, const char *n, const char *t, Type *type_info,
+ Token tok);
+Type *find_symbol_type_info(ParserContext *ctx, const char *n);
+char *find_symbol_type(ParserContext *ctx, const char *n);
+Symbol *find_symbol_entry(ParserContext *ctx, const char *n);
+Symbol *find_symbol_in_all(ParserContext *ctx, const char *n); // LSP flat lookup
+char *find_similar_symbol(ParserContext *ctx, const char *name);
+
+// Function registry
+void register_func(ParserContext *ctx, const char *name, int count, char **defaults,
+ Type **arg_types, Type *ret_type, int is_varargs, int is_async,
+ Token decl_token);
+void register_func_template(ParserContext *ctx, const char *name, const char *param, ASTNode *node);
+GenericFuncTemplate *find_func_template(ParserContext *ctx, const char *name);
+
+// Generic/template helpers
+void register_generic(ParserContext *ctx, char *name);
+int is_known_generic(ParserContext *ctx, char *name);
+void register_impl_template(ParserContext *ctx, const char *sname, const char *param,
+ ASTNode *node);
+void add_to_struct_list(ParserContext *ctx, ASTNode *node);
+void add_to_enum_list(ParserContext *ctx, ASTNode *node);
+void add_to_func_list(ParserContext *ctx, ASTNode *node);
+void add_to_impl_list(ParserContext *ctx, ASTNode *node);
+void add_to_global_list(ParserContext *ctx, ASTNode *node);
+void register_builtins(ParserContext *ctx);
+void add_instantiated_func(ParserContext *ctx, ASTNode *fn);
+void instantiate_generic(ParserContext *ctx, const char *name, const char *concrete_type);
+char *sanitize_mangled_name(const char *s);
+void register_impl(ParserContext *ctx, const char *trait, const char *strct);
+int check_impl(ParserContext *ctx, const char *trait, const char *strct);
+void register_template(ParserContext *ctx, const char *name, ASTNode *node);
+void register_deprecated_func(ParserContext *ctx, const char *name, const char *reason);
+DeprecatedFunc *find_deprecated_func(ParserContext *ctx, const char *name);
+ASTNode *parse_arrow_lambda_single(ParserContext *ctx, Lexer *l, char *param_name);
+ASTNode *parse_arrow_lambda_multi(ParserContext *ctx, Lexer *l, char **param_names, int num_params);
+
+// Utils
+char *parse_and_convert_args(ParserContext *ctx, Lexer *l, char ***defaults_out, int *count_out,
+ Type ***types_out, char ***names_out, int *is_varargs_out);
+int is_file_imported(ParserContext *ctx, const char *path);
+void mark_file_imported(ParserContext *ctx, const char *path);
+void register_plugin(ParserContext *ctx, const char *name, const char *alias);
+const char *resolve_plugin(ParserContext *ctx, const char *name_or_alias);
+void print_type_defs(ParserContext *ctx, FILE *out, ASTNode *nodes);
+
+// String manipulation
+char *replace_in_string(const char *src, const char *old_w, const char *new_w);
+char *replace_type_str(const char *src, const char *param, const char *concrete,
+ const char *old_struct, const char *new_struct);
+Type *replace_type_formal(Type *t, const char *p, const char *c, const char *os, const char *ns);
+ASTNode *copy_ast_replacing(ASTNode *n, const char *p, const char *c, const char *os,
+ const char *ns);
+char *extract_module_name(const char *path);
+
+// Enum helpers
+void register_enum_variant(ParserContext *ctx, const char *ename, const char *vname, int tag);
+EnumVariantReg *find_enum_variant(ParserContext *ctx, const char *vname);
+
+// Lambda helpers
+void register_lambda(ParserContext *ctx, ASTNode *node);
+void analyze_lambda_captures(ParserContext *ctx, ASTNode *lambda);
+
+// Type registration
+void register_slice(ParserContext *ctx, const char *type);
+void register_tuple(ParserContext *ctx, const char *sig);
+
+// Struct lookup
+ASTNode *find_struct_def(ParserContext *ctx, const char *name);
+void register_struct_def(ParserContext *ctx, const char *name, ASTNode *node);
+
+// Module system
+Module *find_module(ParserContext *ctx, const char *alias);
+void register_module(ParserContext *ctx, const char *alias, const char *path);
+void register_selective_import(ParserContext *ctx, const char *symbol, const char *alias,
+ const char *source_module);
+SelectiveImport *find_selective_import(ParserContext *ctx, const char *name);
+
+// Mutability tracking
+void register_var_mutability(ParserContext *ctx, const char *name, int is_mutable);
+int is_var_mutable(ParserContext *ctx, const char *name);
+
+// External symbol tracking (C interop)
+void register_extern_symbol(ParserContext *ctx, const char *name);
+int is_extern_symbol(ParserContext *ctx, const char *name);
+int should_suppress_undef_warning(ParserContext *ctx, const char *name);
+
+// Initialization
+void init_builtins();
+
+// Expression rewriting
+char *rewrite_expr_methods(ParserContext *ctx, char *raw);
+char *process_fstring(ParserContext *ctx, const char *content);
+char *instantiate_function_template(ParserContext *ctx, const char *name,
+ const char *concrete_type);
+FuncSig *find_func(ParserContext *ctx, const char *name);
+
+Type *parse_type_formal(ParserContext *ctx, Lexer *l);
+char *parse_type(ParserContext *ctx, Lexer *l);
+Type *parse_type_base(ParserContext *ctx, Lexer *l);
+
+ASTNode *parse_expression(ParserContext *ctx, Lexer *l);
+ASTNode *parse_expr_prec(ParserContext *ctx, Lexer *l, Precedence min_prec);
+ASTNode *parse_primary(ParserContext *ctx, Lexer *l);
+ASTNode *parse_lambda(ParserContext *ctx, Lexer *l);
+// parse_arrow_lambda_single/multi already declared above
+char *parse_condition_raw(ParserContext *ctx, Lexer *l);
+char *parse_array_literal(ParserContext *ctx, Lexer *l, const char *st);
+char *parse_tuple_literal(ParserContext *ctx, Lexer *l, const char *tn);
+char *parse_embed(ParserContext *ctx, Lexer *l);
+
+ASTNode *parse_macro_call(ParserContext *ctx, Lexer *l, char *name);
+ASTNode *parse_statement(ParserContext *ctx, Lexer *l);
+ASTNode *parse_block(ParserContext *ctx, Lexer *l);
+ASTNode *parse_if(ParserContext *ctx, Lexer *l);
+ASTNode *parse_while(ParserContext *ctx, Lexer *l);
+ASTNode *parse_for(ParserContext *ctx, Lexer *l);
+ASTNode *parse_loop(ParserContext *ctx, Lexer *l);
+ASTNode *parse_repeat(ParserContext *ctx, Lexer *l);
+ASTNode *parse_unless(ParserContext *ctx, Lexer *l);
+ASTNode *parse_guard(ParserContext *ctx, Lexer *l);
+ASTNode *parse_match(ParserContext *ctx, Lexer *l);
+ASTNode *parse_return(ParserContext *ctx, Lexer *l);
+
+char *process_printf_sugar(ParserContext *ctx, const char *content, int newline,
+ const char *target);
+ASTNode *parse_assert(ParserContext *ctx, Lexer *l);
+ASTNode *parse_defer(ParserContext *ctx, Lexer *l);
+ASTNode *parse_asm(ParserContext *ctx, Lexer *l);
+ASTNode *parse_plugin(ParserContext *ctx, Lexer *l);
+ASTNode *parse_var_decl(ParserContext *ctx, Lexer *l);
+ASTNode *parse_const(ParserContext *ctx, Lexer *l);
+ASTNode *parse_type_alias(ParserContext *ctx, Lexer *l);
+
+ASTNode *parse_function(ParserContext *ctx, Lexer *l, int is_async);
+ASTNode *parse_struct(ParserContext *ctx, Lexer *l, int is_union);
+ASTNode *parse_enum(ParserContext *ctx, Lexer *l);
+ASTNode *parse_trait(ParserContext *ctx, Lexer *l);
+ASTNode *parse_impl(ParserContext *ctx, Lexer *l);
+ASTNode *parse_impl_trait(ParserContext *ctx, Lexer *l);
+ASTNode *parse_test(ParserContext *ctx, Lexer *l);
+ASTNode *parse_include(ParserContext *ctx, Lexer *l);
+ASTNode *parse_import(ParserContext *ctx, Lexer *l);
+ASTNode *parse_comptime(ParserContext *ctx, Lexer *l);
+
+char *patch_self_args(const char *args, const char *struct_name);
+
+ASTNode *parse_program_nodes(ParserContext *ctx, Lexer *l);
+
+#endif // PARSER_H
diff --git a/src/parser/parser_core.c b/src/parser/parser_core.c
new file mode 100644
index 0000000..1b40cf4
--- /dev/null
+++ b/src/parser/parser_core.c
@@ -0,0 +1,618 @@
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "parser.h"
+#include "zprep.h"
+
+static ASTNode *generate_derive_impls(ParserContext *ctx, ASTNode *strct, char **traits, int count);
+
+// Main parsing entry point
+ASTNode *parse_program_nodes(ParserContext *ctx, Lexer *l)
+{
+ ASTNode *h = 0, *tl = 0;
+ while (1)
+ {
+ skip_comments(l);
+ Token t = lexer_peek(l);
+
+ if (t.type == TOK_EOF)
+ {
+ break;
+ }
+
+ if (t.type == TOK_COMPTIME)
+ {
+ ASTNode *gen = parse_comptime(ctx, l);
+ if (gen)
+ {
+ if (!h)
+ {
+ h = gen;
+ }
+ else
+ {
+ tl->next = gen;
+ }
+ if (!tl)
+ {
+ tl = gen;
+ }
+ while (tl->next)
+ {
+ tl = tl->next;
+ }
+ }
+ continue;
+ }
+
+ ASTNode *s = 0;
+
+ int attr_must_use = 0;
+ int attr_deprecated = 0;
+ int attr_inline = 0;
+ int attr_pure = 0;
+ int attr_noreturn = 0;
+ int attr_cold = 0;
+ int attr_hot = 0;
+ int attr_packed = 0;
+ int attr_align = 0;
+ int attr_noinline = 0;
+ int attr_constructor = 0;
+ int attr_destructor = 0;
+ int attr_unused = 0;
+ int attr_weak = 0;
+ int attr_export = 0;
+ int attr_comptime = 0;
+ char *deprecated_msg = NULL;
+ char *attr_section = NULL;
+
+ char *derived_traits[32];
+ int derived_count = 0;
+
+ while (t.type == TOK_AT)
+ {
+ lexer_next(l);
+ Token attr = lexer_next(l);
+ if (attr.type != TOK_IDENT && attr.type != TOK_COMPTIME)
+ {
+ zpanic("Expected attribute name after @");
+ }
+
+ if (0 == strncmp(attr.start, "must_use", 8) && 8 == attr.len)
+ {
+ attr_must_use = 1;
+ }
+ else if (0 == strncmp(attr.start, "deprecated", 10) && 10 == attr.len)
+ {
+ attr_deprecated = 1;
+ if (lexer_peek(l).type == TOK_LPAREN)
+ {
+ lexer_next(l);
+ Token msg = lexer_next(l);
+ if (msg.type == TOK_STRING)
+ {
+ deprecated_msg = xmalloc(msg.len - 1);
+ strncpy(deprecated_msg, msg.start + 1, msg.len - 2);
+ deprecated_msg[msg.len - 2] = 0;
+ }
+ if (lexer_next(l).type != TOK_RPAREN)
+ {
+ zpanic("Expected ) after deprecated message");
+ }
+ }
+ }
+ else if (0 == strncmp(attr.start, "inline", 6) && 6 == attr.len)
+ {
+ attr_inline = 1;
+ }
+ else if (0 == strncmp(attr.start, "noinline", 8) && 8 == attr.len)
+ {
+ attr_noinline = 1;
+ }
+ else if (0 == strncmp(attr.start, "pure", 4) && 4 == attr.len)
+ {
+ attr_pure = 1;
+ }
+ else if (0 == strncmp(attr.start, "noreturn", 8) && 8 == attr.len)
+ {
+ attr_noreturn = 1;
+ }
+ else if (0 == strncmp(attr.start, "cold", 4) && 4 == attr.len)
+ {
+ attr_cold = 1;
+ }
+ else if (0 == strncmp(attr.start, "hot", 3) && 3 == attr.len)
+ {
+ attr_hot = 1;
+ }
+ else if (0 == strncmp(attr.start, "constructor", 11) && 11 == attr.len)
+ {
+ attr_constructor = 1;
+ }
+ else if (0 == strncmp(attr.start, "destructor", 10) && 10 == attr.len)
+ {
+ attr_destructor = 1;
+ }
+ else if (0 == strncmp(attr.start, "unused", 6) && 6 == attr.len)
+ {
+ attr_unused = 1;
+ }
+ else if (0 == strncmp(attr.start, "weak", 4) && 4 == attr.len)
+ {
+ attr_weak = 1;
+ }
+ else if (0 == strncmp(attr.start, "export", 6) && 6 == attr.len)
+ {
+ attr_export = 1;
+ }
+ else if (0 == strncmp(attr.start, "comptime", 8) && 8 == attr.len)
+ {
+ attr_comptime = 1;
+ }
+ else if (0 == strncmp(attr.start, "section", 7) && 7 == attr.len)
+ {
+ if (lexer_peek(l).type == TOK_LPAREN)
+ {
+ lexer_next(l);
+ Token sec = lexer_next(l);
+ if (sec.type == TOK_STRING)
+ {
+ attr_section = xmalloc(sec.len - 1);
+ strncpy(attr_section, sec.start + 1, sec.len - 2);
+ attr_section[sec.len - 2] = 0;
+ }
+ if (lexer_next(l).type != TOK_RPAREN)
+ {
+ zpanic("Expected ) after section name");
+ }
+ }
+ else
+ {
+ zpanic("@section requires a name: @section(\"name\")");
+ }
+ }
+ else if (0 == strncmp(attr.start, "packed", 6) && 6 == attr.len)
+ {
+ attr_packed = 1;
+ }
+ else if (0 == strncmp(attr.start, "align", 5) && 5 == attr.len)
+ {
+ if (lexer_peek(l).type == TOK_LPAREN)
+ {
+ lexer_next(l);
+ Token num = lexer_next(l);
+ if (num.type == TOK_INT)
+ {
+ attr_align = atoi(num.start);
+ }
+ if (lexer_next(l).type != TOK_RPAREN)
+ {
+ zpanic("Expected ) after align value");
+ }
+ }
+ else
+ {
+ zpanic("@align requires a value: @align(N)");
+ }
+ }
+ else if (0 == strncmp(attr.start, "derive", 6) && 6 == attr.len)
+ {
+ if (lexer_peek(l).type == TOK_LPAREN)
+ {
+ lexer_next(l);
+ while (1)
+ {
+ Token t = lexer_next(l);
+ if (t.type != TOK_IDENT)
+ {
+ zpanic("Expected trait name in @derive");
+ }
+ if (derived_count < 32)
+ {
+ derived_traits[derived_count++] = token_strdup(t);
+ }
+ if (lexer_peek(l).type == TOK_COMMA)
+ {
+ lexer_next(l);
+ }
+ else
+ {
+ break;
+ }
+ }
+ if (lexer_next(l).type != TOK_RPAREN)
+ {
+ zpanic("Expected ) after derive traits");
+ }
+ }
+ else
+ {
+ zpanic("@derive requires traits: @derive(Debug, Clone)");
+ }
+ }
+ else
+ {
+ zwarn_at(attr, "Unknown attribute: %.*s", attr.len, attr.start);
+ }
+
+ t = lexer_peek(l);
+ }
+
+ if (t.type == TOK_PREPROC)
+ {
+ lexer_next(l);
+ char *content = xmalloc(t.len + 2);
+ strncpy(content, t.start, t.len);
+ content[t.len] = '\n';
+ content[t.len + 1] = 0;
+ s = ast_create(NODE_RAW_STMT);
+ s->raw_stmt.content = content;
+ }
+ else if (t.type == TOK_IDENT)
+ {
+ // Inline function: inline fn name(...) { }
+ if (0 == strncmp(t.start, "inline", 6) && 6 == t.len)
+ {
+ lexer_next(l);
+ Token next = lexer_peek(l);
+ if (next.type == TOK_IDENT && 2 == next.len && 0 == strncmp(next.start, "fn", 2))
+ {
+ s = parse_function(ctx, l, 0);
+ attr_inline = 1;
+ }
+ else
+ {
+ zpanic_at(next, "Expected 'fn' after 'inline'");
+ }
+ }
+ else if (0 == strncmp(t.start, "fn", 2) && 2 == t.len)
+ {
+ s = parse_function(ctx, l, 0);
+ }
+ else if (0 == strncmp(t.start, "struct", 6) && 6 == t.len)
+ {
+ s = parse_struct(ctx, l, 0);
+ if (s && s->type == NODE_STRUCT)
+ {
+ s->strct.is_packed = attr_packed;
+ s->strct.align = attr_align;
+
+ if (derived_count > 0)
+ {
+ ASTNode *impls =
+ generate_derive_impls(ctx, s, derived_traits, derived_count);
+ s->next = impls;
+ }
+ }
+ }
+ else if (0 == strncmp(t.start, "enum", 4) && 4 == t.len)
+ {
+ s = parse_enum(ctx, l);
+ if (s && s->type == NODE_ENUM)
+ {
+ if (derived_count > 0)
+ {
+ ASTNode *impls =
+ generate_derive_impls(ctx, s, derived_traits, derived_count);
+ s->next = impls;
+ }
+ }
+ }
+ else if (t.len == 4 && strncmp(t.start, "impl", 4) == 0)
+ {
+ s = parse_impl(ctx, l);
+ }
+ else if (t.len == 5 && strncmp(t.start, "trait", 5) == 0)
+ {
+ s = parse_trait(ctx, l);
+ }
+ else if (t.len == 7 && strncmp(t.start, "include", 7) == 0)
+ {
+ s = parse_include(ctx, l);
+ }
+ else if (t.len == 6 && strncmp(t.start, "import", 6) == 0)
+ {
+ s = parse_import(ctx, l);
+ }
+ else if (t.len == 3 && strncmp(t.start, "var", 3) == 0)
+ {
+ s = parse_var_decl(ctx, l);
+ }
+ else if (t.len == 5 && strncmp(t.start, "const", 5) == 0)
+ {
+ s = parse_const(ctx, l);
+ }
+ else if (t.len == 6 && strncmp(t.start, "extern", 6) == 0)
+ {
+ lexer_next(l);
+
+ Token peek = lexer_peek(l);
+ if (peek.type == TOK_IDENT && peek.len == 2 && strncmp(peek.start, "fn", 2) == 0)
+ {
+ s = parse_function(ctx, l, 0);
+ }
+ else
+ {
+ while (1)
+ {
+ Token sym = lexer_next(l);
+ if (sym.type != TOK_IDENT)
+ {
+ break;
+ }
+
+ char *name = token_strdup(sym);
+ register_extern_symbol(ctx, name);
+
+ Token next = lexer_peek(l);
+ if (next.type == TOK_COMMA)
+ {
+ lexer_next(l);
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ if (lexer_peek(l).type == TOK_SEMICOLON)
+ {
+ lexer_next(l);
+ }
+ continue;
+ }
+ }
+ else if (0 == strncmp(t.start, "type", 4) && 4 == t.len)
+ {
+ s = parse_type_alias(ctx, l);
+ }
+ else if (0 == strncmp(t.start, "raw", 3) && 3 == t.len)
+ {
+ lexer_next(l);
+ if (lexer_peek(l).type != TOK_LBRACE)
+ {
+ zpanic("Expected { after raw");
+ }
+ lexer_next(l);
+
+ const char *start = l->src + l->pos;
+
+ int depth = 1;
+ while (depth > 0)
+ {
+ Token t = lexer_next(l);
+ if (t.type == TOK_EOF)
+ {
+ zpanic("Unexpected EOF in raw block");
+ }
+ if (t.type == TOK_LBRACE)
+ {
+ depth++;
+ }
+ if (t.type == TOK_RBRACE)
+ {
+ depth--;
+ }
+ }
+
+ const char *end = l->src + l->pos - 1;
+ size_t len = end - start;
+
+ char *content = xmalloc(len + 1);
+ memcpy(content, start, len);
+ content[len] = 0;
+
+ s = ast_create(NODE_RAW_STMT);
+ s->raw_stmt.content = content;
+ }
+ else
+ {
+ lexer_next(l);
+ }
+ }
+ else if (t.type == TOK_ASYNC)
+ {
+ lexer_next(l);
+ Token next = lexer_peek(l);
+ if (0 == strncmp(next.start, "fn", 2) && 2 == next.len)
+ {
+ s = parse_function(ctx, l, 1);
+ if (s)
+ {
+ s->func.is_async = 1;
+ }
+ }
+ else
+ {
+ zpanic_at(next, "Expected 'fn' after 'async'");
+ }
+ }
+
+ else if (t.type == TOK_UNION)
+ {
+ s = parse_struct(ctx, l, 1);
+ }
+ else if (t.type == TOK_TRAIT)
+ {
+ s = parse_trait(ctx, l);
+ }
+ else if (t.type == TOK_IMPL)
+ {
+ s = parse_impl(ctx, l);
+ }
+ else if (t.type == TOK_TEST)
+ {
+ s = parse_test(ctx, l);
+ }
+ else
+ {
+ lexer_next(l);
+ }
+
+ if (s && s->type == NODE_FUNCTION)
+ {
+ s->func.must_use = attr_must_use;
+ s->func.is_inline = attr_inline || s->func.is_inline;
+ s->func.noinline = attr_noinline;
+ s->func.constructor = attr_constructor;
+ s->func.destructor = attr_destructor;
+ s->func.unused = attr_unused;
+ s->func.weak = attr_weak;
+ s->func.is_export = attr_export;
+ s->func.cold = attr_cold;
+ s->func.hot = attr_hot;
+ s->func.noreturn = attr_noreturn;
+ s->func.pure = attr_pure;
+ s->func.section = attr_section;
+ s->func.is_comptime = attr_comptime;
+
+ if (attr_deprecated && s->func.name)
+ {
+ register_deprecated_func(ctx, s->func.name, deprecated_msg);
+ }
+
+ if (attr_must_use && s->func.name)
+ {
+ FuncSig *sig = find_func(ctx, s->func.name);
+ if (sig)
+ {
+ sig->must_use = 1;
+ }
+ }
+ }
+
+ if (s)
+ {
+ if (!h)
+ {
+ h = s;
+ }
+ else
+ {
+ tl->next = s;
+ }
+ tl = s;
+ while (tl->next)
+ {
+ tl = tl->next;
+ }
+ }
+ }
+ return h;
+}
+
+ASTNode *parse_program(ParserContext *ctx, Lexer *l)
+{
+ g_parser_ctx = ctx;
+ enter_scope(ctx);
+ register_builtins(ctx);
+
+ ASTNode *r = ast_create(NODE_ROOT);
+ r->root.children = parse_program_nodes(ctx, l);
+ return r;
+}
+
+static ASTNode *generate_derive_impls(ParserContext *ctx, ASTNode *strct, char **traits, int count)
+{
+ ASTNode *head = NULL, *tail = NULL;
+ char *name = strct->strct.name;
+
+ for (int i = 0; i < count; i++)
+ {
+ char *trait = traits[i];
+ char *code = NULL;
+
+ if (0 == strcmp(trait, "Clone"))
+ {
+ code = xmalloc(1024);
+ sprintf(code, "impl %s { fn clone(self) -> %s { return *self; } }", name, name);
+ }
+ else if (0 == strcmp(trait, "Eq"))
+ {
+ char body[4096];
+ body[0] = 0;
+
+ if (strct->type == NODE_ENUM)
+ {
+ // Simple Enum equality (tag comparison)
+ // Generate Eq impl for Enum
+
+ sprintf(body, "return self.tag == other.tag;");
+ }
+ else
+ {
+ ASTNode *f = strct->strct.fields;
+ int first = 1;
+ strcat(body, "return ");
+ while (f)
+ {
+ if (f->type == NODE_FIELD)
+ {
+ char *fn = f->field.name;
+ char *ft = f->field.type;
+ if (!first)
+ {
+ strcat(body, " && ");
+ }
+ char cmp[256];
+
+ ASTNode *fdef = find_struct_def(ctx, ft);
+ if (fdef && fdef->type == NODE_ENUM)
+ {
+ // Enum field: compare tags
+ sprintf(cmp, "self.%s.tag == other.%s.tag", fn, fn);
+ }
+ else if (fdef && fdef->type == NODE_STRUCT)
+ {
+ // Struct field: use _eq function
+ sprintf(cmp, "%s_eq(&self.%s, other.%s)", ft, fn, fn);
+ }
+ else
+ {
+ // Primitive or unknown: use ==
+ sprintf(cmp, "self.%s == other.%s", fn, fn);
+ }
+ strcat(body, cmp);
+ first = 0;
+ }
+ f = f->next;
+ }
+ if (first)
+ {
+ strcat(body, "true");
+ }
+ strcat(body, ";");
+ }
+ code = xmalloc(4096 + 1024);
+ sprintf(code, "impl %s { fn eq(self, other: %s) -> bool { %s } }", name, name, body);
+ }
+ else if (0 == strcmp(trait, "Debug"))
+ {
+ // Simplistic Debug for now, I know.
+ code = xmalloc(1024);
+ sprintf(code, "impl %s { fn to_string(self) -> char* { return \"%s { ... }\"; } }",
+ name, name);
+ }
+
+ if (code)
+ {
+ Lexer tmp;
+ lexer_init(&tmp, code);
+ ASTNode *impl = parse_impl(ctx, &tmp);
+ if (impl)
+ {
+ if (!head)
+ {
+ head = impl;
+ }
+ else
+ {
+ tail->next = impl;
+ }
+ tail = impl;
+ }
+ }
+ }
+ return head;
+}
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;
+}
diff --git a/src/parser/parser_stmt.c b/src/parser/parser_stmt.c
new file mode 100644
index 0000000..619caaf
--- /dev/null
+++ b/src/parser/parser_stmt.c
@@ -0,0 +1,3900 @@
+// parser_stmt.c - Statement and Declaration parsing
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include "parser.h"
+
+#include "../plugins/plugin_manager.h"
+#include "../ast/ast.h"
+#include "../zen/zen_facts.h"
+#include "zprep_plugin.h"
+
+static char *curr_func_ret = NULL;
+
+static void check_assignment_condition(ASTNode *cond)
+{
+ if (!cond)
+ {
+ return;
+ }
+ if (cond->type == NODE_EXPR_BINARY)
+ {
+ if (cond->binary.op && strcmp(cond->binary.op, "=") == 0)
+ {
+ zwarn_at(cond->token, "Assignment in condition");
+ fprintf(stderr, COLOR_CYAN " = note: " COLOR_RESET "Did you mean '=='?\n");
+ }
+ }
+}
+
+ASTNode *parse_function(ParserContext *ctx, Lexer *l, int is_async)
+{
+ lexer_next(l); // eat 'fn'
+ Token name_tok = lexer_next(l);
+ char *name = token_strdup(name_tok);
+
+ // Check for C reserved word conflict
+ if (is_c_reserved_word(name))
+ {
+ warn_c_reserved_word(name_tok, name);
+ }
+
+ char *gen_param = NULL;
+ if (lexer_peek(l).type == TOK_LANGLE)
+ {
+ lexer_next(l);
+ Token gt = lexer_next(l);
+ gen_param = token_strdup(gt);
+ if (lexer_next(l).type != TOK_RANGLE)
+ {
+ zpanic("Expected >");
+ }
+ }
+
+ enter_scope(ctx);
+ char **defaults;
+ int count;
+ Type **arg_types;
+ char **param_names;
+ int is_varargs = 0;
+
+ char *args =
+ parse_and_convert_args(ctx, l, &defaults, &count, &arg_types, &param_names, &is_varargs);
+
+ char *ret = "void";
+ Type *ret_type_obj = type_new(TYPE_VOID);
+
+ if (strcmp(name, "main") == 0)
+ {
+ ret = "int";
+ ret_type_obj = type_new(TYPE_INT);
+ }
+
+ if (lexer_peek(l).type == TOK_ARROW)
+ {
+ lexer_next(l);
+ ret_type_obj = parse_type_formal(ctx, l);
+ ret = type_to_string(ret_type_obj);
+ }
+
+ extern char *curr_func_ret;
+ curr_func_ret = ret;
+
+ // Auto-prefix function name if in module context
+ // Don't prefix generic templates or functions inside impl blocks (already mangled)
+ if (ctx->current_module_prefix && !gen_param && !ctx->current_impl_struct)
+ {
+ char *prefixed_name = xmalloc(strlen(ctx->current_module_prefix) + strlen(name) + 2);
+ sprintf(prefixed_name, "%s_%s", ctx->current_module_prefix, name);
+ free(name);
+ name = prefixed_name;
+ }
+
+ // Register if concrete (Global functions only)
+ if (!gen_param && !ctx->current_impl_struct)
+ {
+ register_func(ctx, name, count, defaults, arg_types, ret_type_obj, is_varargs, is_async,
+ name_tok);
+ // Note: must_use is set after return by caller (parser_core.c)
+ }
+
+ ASTNode *body = NULL;
+ if (lexer_peek(l).type == TOK_SEMICOLON)
+ {
+ lexer_next(l); // consume ;
+ }
+ else
+ {
+ body = parse_block(ctx, l);
+ }
+
+ // Check for unused parameters
+ // The current scope contains arguments (since parse_block creates a new child scope for body)
+ // Only check if we parsed a body (not a prototype) function
+ if (body && ctx->current_scope)
+ {
+ Symbol *sym = ctx->current_scope->symbols;
+ while (sym)
+ {
+ // Check if unused and not prefixed with '_' (conventional ignore)
+ // also ignore 'self' as it is often mandated by traits
+ if (!sym->is_used && sym->name[0] != '_' && strcmp(sym->name, "self") != 0 &&
+ strcmp(name, "main") != 0)
+ {
+ warn_unused_parameter(sym->decl_token, sym->name, name);
+ }
+ sym = sym->next;
+ }
+ }
+
+ exit_scope(ctx);
+ curr_func_ret = NULL;
+
+ ASTNode *node = ast_create(NODE_FUNCTION);
+ node->token = name_tok; // Save definition location
+ node->func.name = name;
+ node->func.args = args;
+ node->func.ret_type = ret;
+ node->func.body = body;
+
+ node->func.arg_types = arg_types;
+ node->func.param_names = param_names;
+ node->func.arg_count = count;
+ node->func.defaults = defaults;
+ node->func.ret_type_info = ret_type_obj;
+ node->func.is_varargs = is_varargs;
+
+ if (gen_param)
+ {
+ register_func_template(ctx, name, gen_param, node);
+ return NULL;
+ }
+ if (!ctx->current_impl_struct)
+ {
+ add_to_func_list(ctx, node);
+ }
+ return node;
+}
+
+char *patch_self_args(const char *args, const char *struct_name)
+{
+ if (!args)
+ {
+ return NULL;
+ }
+ char *new_args = xmalloc(strlen(args) + strlen(struct_name) + 10);
+
+ // Check if it starts with "void* self"
+ if (strncmp(args, "void* self", 10) == 0)
+ {
+ sprintf(new_args, "%s* self%s", struct_name, args + 10);
+ }
+ else
+ {
+ strcpy(new_args, args);
+ }
+ return new_args;
+}
+
+ASTNode *parse_match(ParserContext *ctx, Lexer *l)
+{
+ init_builtins();
+ Token start_token = lexer_peek(l);
+ lexer_next(l); // eat 'match'
+ ASTNode *expr = parse_expression(ctx, l);
+
+ if (lexer_next(l).type != TOK_LBRACE)
+ {
+ zpanic("Expected { in match");
+ }
+
+ ASTNode *h = 0, *tl = 0;
+ while (lexer_peek(l).type != TOK_RBRACE)
+ {
+ 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;
+ }
+
+ // --- 1. Parse Comma-Separated Patterns ---
+ char patterns_buf[1024];
+ patterns_buf[0] = 0;
+ int pattern_count = 0;
+
+ while (1)
+ {
+ Token p = lexer_next(l);
+ char *p_str = token_strdup(p);
+
+ // === [FIX] Handle Namespacing (Enum::Variant) ===
+ while (lexer_peek(l).type == TOK_DCOLON)
+ {
+ lexer_next(l); // eat ::
+ Token suffix = lexer_next(l);
+ char *tmp = xmalloc(strlen(p_str) + suffix.len + 2);
+ // Join with underscore: Result::Ok -> Result_Ok
+ sprintf(tmp, "%s_%.*s", p_str, suffix.len, suffix.start);
+ free(p_str);
+ p_str = tmp;
+ }
+ // ================================================
+
+ if (pattern_count > 0)
+ {
+ strcat(patterns_buf, ",");
+ }
+ strcat(patterns_buf, p_str);
+ free(p_str);
+ pattern_count++;
+
+ Lexer lookahead = *l;
+ skip_comments(&lookahead);
+ if (lexer_peek(&lookahead).type == TOK_COMMA)
+ {
+ lexer_next(l); // eat comma
+ skip_comments(l);
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ char *pattern = xstrdup(patterns_buf);
+ int is_default = (strcmp(pattern, "_") == 0);
+
+ char *binding = NULL;
+ int is_destructure = 0;
+
+ // --- 2. Handle Destructuring: Ok(v) ---
+ // (Only allowed if we matched a single pattern, e.g. "Result::Ok(val)")
+ if (!is_default && pattern_count == 1 && lexer_peek(l).type == TOK_LPAREN)
+ {
+ lexer_next(l); // eat (
+ Token b = lexer_next(l);
+ if (b.type != TOK_IDENT)
+ {
+ zpanic("Expected variable name in pattern");
+ }
+ binding = token_strdup(b);
+ if (lexer_next(l).type != TOK_RPAREN)
+ {
+ zpanic("Expected )");
+ }
+ is_destructure = 1;
+ }
+
+ // --- 3. Parse Guard (if condition) ---
+ ASTNode *guard = NULL;
+ if (lexer_peek(l).type == TOK_IDENT && strncmp(lexer_peek(l).start, "if", 2) == 0)
+ {
+ lexer_next(l);
+ guard = parse_expression(ctx, l);
+ check_assignment_condition(guard);
+ }
+
+ if (lexer_next(l).type != TOK_ARROW)
+ {
+ zpanic("Expected =>");
+ }
+
+ ASTNode *body;
+ 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); // eat }
+
+ ASTNode *n = ast_create(NODE_MATCH);
+ n->line = start_token.line;
+ n->token = start_token; // Capture token for rich warning
+ n->match_stmt.expr = expr;
+ n->match_stmt.cases = h;
+ return n;
+}
+
+ASTNode *parse_loop(ParserContext *ctx, Lexer *l)
+{
+ lexer_next(l);
+ ASTNode *b = parse_block(ctx, l);
+ ASTNode *n = ast_create(NODE_LOOP);
+ n->loop_stmt.body = b;
+ return n;
+}
+
+ASTNode *parse_repeat(ParserContext *ctx, Lexer *l)
+{
+ lexer_next(l);
+ char *c = rewrite_expr_methods(ctx, parse_condition_raw(ctx, l));
+ ASTNode *b = parse_block(ctx, l);
+ ASTNode *n = ast_create(NODE_REPEAT);
+ n->repeat_stmt.count = c;
+ n->repeat_stmt.body = b;
+ return n;
+}
+
+ASTNode *parse_unless(ParserContext *ctx, Lexer *l)
+{
+ lexer_next(l);
+ ASTNode *cond = parse_expression(ctx, l);
+ ASTNode *body = parse_block(ctx, l);
+ ASTNode *n = ast_create(NODE_UNLESS);
+ n->unless_stmt.condition = cond;
+ n->unless_stmt.body = body;
+ return n;
+}
+
+ASTNode *parse_guard(ParserContext *ctx, Lexer *l)
+{
+ lexer_next(l); // consume 'guard'
+
+ // Parse the condition as an AST
+ ASTNode *cond = parse_expression(ctx, l);
+
+ // Check for 'else'
+ Token t = lexer_peek(l);
+ if (t.type != TOK_IDENT || strncmp(t.start, "else", 4) != 0)
+ {
+ zpanic("Expected 'else' after guard condition");
+ }
+ lexer_next(l); // consume 'else'
+
+ // Parse the body - either a block or a single statement
+ ASTNode *body;
+ if (lexer_peek(l).type == TOK_LBRACE)
+ {
+ body = parse_block(ctx, l);
+ }
+ else
+ {
+ // Single statement (e.g., guard x != NULL else return;)
+ body = parse_statement(ctx, l);
+ }
+
+ // Create the node
+ ASTNode *n = ast_create(NODE_GUARD);
+ n->guard_stmt.condition = cond;
+ n->guard_stmt.body = body;
+ return n;
+}
+
+ASTNode *parse_defer(ParserContext *ctx, Lexer *l)
+{
+ lexer_next(l); // defer
+ ASTNode *s;
+ if (lexer_peek(l).type == TOK_LBRACE)
+ {
+ s = parse_block(ctx, l);
+ }
+ else
+ {
+ s = ast_create(NODE_RAW_STMT);
+ s->raw_stmt.content = consume_and_rewrite(ctx, l);
+ }
+ ASTNode *n = ast_create(NODE_DEFER);
+ n->defer_stmt.stmt = s;
+ return n;
+}
+
+ASTNode *parse_asm(ParserContext *ctx, Lexer *l)
+{
+ (void)ctx; // suppress unused parameter warning
+ Token t = lexer_peek(l);
+ zen_trigger_at(TRIGGER_ASM, t);
+ lexer_next(l); // eat 'asm'
+
+ // Check for 'volatile'
+ int is_volatile = 0;
+ if (lexer_peek(l).type == TOK_VOLATILE)
+ {
+ is_volatile = 1;
+ lexer_next(l);
+ }
+
+ // Expect {
+ if (lexer_peek(l).type != TOK_LBRACE)
+ {
+ zpanic("Expected { after asm");
+ }
+ lexer_next(l);
+
+ // Parse assembly template strings
+ char *code = xmalloc(4096); // Buffer for assembly code
+ code[0] = 0;
+
+ while (1)
+ {
+ Token t = lexer_peek(l);
+
+ // Check for end of asm block or start of operands
+ if (t.type == TOK_RBRACE)
+ {
+ break;
+ }
+ if (t.type == TOK_COLON)
+ {
+ break;
+ }
+
+ // Support string literals for assembly instructions
+ if (t.type == TOK_STRING)
+ {
+ lexer_next(l);
+ // Extract string content (strip quotes)
+ int str_len = t.len - 2;
+ if (strlen(code) > 0)
+ {
+ strcat(code, "\n");
+ }
+ strncat(code, t.start + 1, str_len);
+ }
+ // Also support bare identifiers for simple instructions like 'nop', 'pause'
+ else if (t.type == TOK_IDENT)
+ {
+ lexer_next(l);
+ if (strlen(code) > 0)
+ {
+ strcat(code, "\n");
+ }
+ strncat(code, t.start, t.len);
+
+ // Check for instruction arguments
+ while (lexer_peek(l).type != TOK_RBRACE && lexer_peek(l).type != TOK_COLON)
+ {
+ Token arg = lexer_peek(l);
+
+ if (arg.type == TOK_SEMICOLON)
+ {
+ lexer_next(l);
+ break;
+ }
+
+ // Handle substitution {var}
+ if (arg.type == TOK_LBRACE)
+ {
+ lexer_next(l);
+ strcat(code, "{");
+ // Consume until }
+ while (lexer_peek(l).type != TOK_RBRACE && lexer_peek(l).type != TOK_EOF)
+ {
+ Token sub = lexer_next(l);
+ strncat(code, sub.start, sub.len);
+ }
+ if (lexer_peek(l).type == TOK_RBRACE)
+ {
+ lexer_next(l);
+ strcat(code, "}");
+ }
+ continue;
+ }
+
+ if (arg.type == TOK_IDENT)
+ {
+ // Check prev char for % or $
+ char last_char = 0;
+ size_t clen = strlen(code);
+ if (clen > 0)
+ {
+ if (code[clen - 1] == ' ' && clen > 1)
+ {
+ last_char = code[clen - 2];
+ }
+ else
+ {
+ last_char = code[clen - 1];
+ }
+ }
+ if (last_char != '%' && last_char != '$' && last_char != ',')
+ {
+ break;
+ }
+ }
+
+ lexer_next(l);
+
+ // No space logic
+ int no_space = 0;
+ size_t clen = strlen(code);
+ if (clen > 0)
+ {
+ char lc = code[clen - 1];
+ if (lc == '%' || lc == '$')
+ {
+ no_space = 1;
+ }
+ }
+
+ if (!no_space)
+ {
+ strcat(code, " ");
+ }
+ strncat(code, arg.start, arg.len);
+ }
+ }
+ else
+ {
+ zpanic("Expected assembly string, instruction, or ':' in asm block");
+ }
+ }
+
+ // Parse outputs (: out(x), inout(y))
+ char **outputs = NULL;
+ char **output_modes = NULL;
+ int num_outputs = 0;
+
+ if (lexer_peek(l).type == TOK_COLON)
+ {
+ lexer_next(l); // eat :
+
+ outputs = xmalloc(sizeof(char *) * 16);
+ output_modes = xmalloc(sizeof(char *) * 16);
+
+ while (1)
+ {
+ Token t = lexer_peek(l);
+ if (t.type == TOK_COLON || t.type == TOK_RBRACE)
+ {
+ break;
+ }
+ if (t.type == TOK_COMMA)
+ {
+ lexer_next(l);
+ continue;
+ }
+
+ // Parse out(var) or inout(var)
+ if (t.type == TOK_IDENT)
+ {
+ char *mode = token_strdup(t);
+ lexer_next(l);
+
+ if (lexer_peek(l).type != TOK_LPAREN)
+ {
+ zpanic("Expected ( after output mode");
+ }
+ lexer_next(l);
+
+ Token var = lexer_next(l);
+ if (var.type != TOK_IDENT)
+ {
+ zpanic("Expected variable name");
+ }
+
+ if (lexer_peek(l).type != TOK_RPAREN)
+ {
+ zpanic("Expected ) after variable");
+ }
+ lexer_next(l);
+
+ outputs[num_outputs] = token_strdup(var);
+ output_modes[num_outputs] = mode;
+ num_outputs++;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
+ // Parse inputs (: in(a), in(b))
+ char **inputs = NULL;
+ int num_inputs = 0;
+
+ if (lexer_peek(l).type == TOK_COLON)
+ {
+ lexer_next(l); // eat :
+
+ inputs = xmalloc(sizeof(char *) * 16);
+
+ while (1)
+ {
+ Token t = lexer_peek(l);
+ if (t.type == TOK_COLON || t.type == TOK_RBRACE)
+ {
+ break;
+ }
+ if (t.type == TOK_COMMA)
+ {
+ lexer_next(l);
+ continue;
+ }
+
+ // Parse in(var)
+ if (t.type == TOK_IDENT && strncmp(t.start, "in", 2) == 0)
+ {
+ lexer_next(l);
+
+ if (lexer_peek(l).type != TOK_LPAREN)
+ {
+ zpanic("Expected ( after in");
+ }
+ lexer_next(l);
+
+ Token var = lexer_next(l);
+ if (var.type != TOK_IDENT)
+ {
+ zpanic("Expected variable name");
+ }
+
+ if (lexer_peek(l).type != TOK_RPAREN)
+ {
+ zpanic("Expected ) after variable");
+ }
+ lexer_next(l);
+
+ inputs[num_inputs] = token_strdup(var);
+ num_inputs++;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
+ // Parse clobbers (: "eax", "memory")
+ char **clobbers = NULL;
+ int num_clobbers = 0;
+
+ if (lexer_peek(l).type == TOK_COLON)
+ {
+ lexer_next(l); // eat :
+
+ clobbers = xmalloc(sizeof(char *) * 16);
+
+ while (1)
+ {
+ Token t = lexer_peek(l);
+ if (t.type == TOK_RBRACE)
+ {
+ break;
+ }
+ if (t.type == TOK_COMMA)
+ {
+ lexer_next(l);
+ continue;
+ }
+
+ if (t.type == TOK_STRING)
+ {
+ lexer_next(l);
+ // Extract string content
+ char *clob = xmalloc(t.len);
+ strncpy(clob, t.start + 1, t.len - 2);
+ clob[t.len - 2] = 0;
+ clobbers[num_clobbers++] = clob;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
+ // Expect closing }
+ if (lexer_peek(l).type != TOK_RBRACE)
+ {
+ zpanic("Expected } at end of asm block");
+ }
+ lexer_next(l);
+
+ // Create AST node
+ ASTNode *n = ast_create(NODE_ASM);
+ n->asm_stmt.code = code;
+ n->asm_stmt.is_volatile = is_volatile;
+ n->asm_stmt.outputs = outputs;
+ n->asm_stmt.output_modes = output_modes;
+ n->asm_stmt.inputs = inputs;
+ n->asm_stmt.clobbers = clobbers;
+ n->asm_stmt.num_outputs = num_outputs;
+ n->asm_stmt.num_inputs = num_inputs;
+ n->asm_stmt.num_clobbers = num_clobbers;
+
+ return n;
+}
+
+ASTNode *parse_test(ParserContext *ctx, Lexer *l)
+{
+ lexer_next(l); // eat 'test'
+ Token t = lexer_next(l);
+ if (t.type != TOK_STRING)
+ {
+ zpanic("Test name must be a string literal");
+ }
+
+ // Strip quotes for AST storage
+ char *name = xmalloc(t.len);
+ strncpy(name, t.start + 1, t.len - 2);
+ name[t.len - 2] = 0;
+
+ ASTNode *body = parse_block(ctx, l);
+
+ ASTNode *n = ast_create(NODE_TEST);
+ n->test_stmt.name = name;
+ n->test_stmt.body = body;
+ return n;
+}
+
+ASTNode *parse_assert(ParserContext *ctx, Lexer *l)
+{
+ lexer_next(l); // assert
+ if (lexer_peek(l).type == TOK_LPAREN)
+ {
+ lexer_next(l); // optional paren? usually yes
+ }
+
+ ASTNode *cond = parse_expression(ctx, l);
+
+ char *msg = NULL;
+ if (lexer_peek(l).type == TOK_COMMA)
+ {
+ lexer_next(l);
+ Token st = lexer_next(l);
+ if (st.type != TOK_STRING)
+ {
+ zpanic("Expected message string");
+ }
+ msg = xmalloc(st.len + 1);
+ strncpy(msg, st.start, st.len);
+ msg[st.len] = 0;
+ }
+
+ if (lexer_peek(l).type == TOK_RPAREN)
+ {
+ lexer_next(l);
+ }
+ if (lexer_peek(l).type == TOK_SEMICOLON)
+ {
+ lexer_next(l);
+ }
+
+ ASTNode *n = ast_create(NODE_ASSERT);
+ n->assert_stmt.condition = cond;
+ n->assert_stmt.message = msg;
+ return n;
+}
+
+// Helper for Value-Returning Defer
+static void replace_it_with_var(ASTNode *node, char *var_name)
+{
+ if (!node)
+ {
+ return;
+ }
+ if (node->type == NODE_EXPR_VAR)
+ {
+ if (strcmp(node->var_ref.name, "it") == 0)
+ {
+ // Replace 'it' with var_name
+ node->var_ref.name = xstrdup(var_name);
+ }
+ }
+ else if (node->type == NODE_EXPR_CALL)
+ {
+ replace_it_with_var(node->call.callee, var_name);
+ ASTNode *arg = node->call.args;
+ while (arg)
+ {
+ replace_it_with_var(arg, var_name);
+ arg = arg->next;
+ }
+ }
+ else if (node->type == NODE_EXPR_MEMBER)
+ {
+ replace_it_with_var(node->member.target, var_name);
+ }
+ else if (node->type == NODE_EXPR_BINARY)
+ {
+ replace_it_with_var(node->binary.left, var_name);
+ replace_it_with_var(node->binary.right, var_name);
+ }
+ else if (node->type == NODE_EXPR_UNARY)
+ {
+ replace_it_with_var(node->unary.operand, var_name);
+ }
+ else if (node->type == NODE_BLOCK)
+ {
+ ASTNode *s = node->block.statements;
+ while (s)
+ {
+ replace_it_with_var(s, var_name);
+ s = s->next;
+ }
+ }
+}
+
+ASTNode *parse_var_decl(ParserContext *ctx, Lexer *l)
+{
+ lexer_next(l); // eat 'var'
+
+ // Check for 'mut' keyword
+ int is_mutable = 0;
+ if (lexer_peek(l).type == TOK_MUT)
+ {
+ is_mutable = 1;
+ lexer_next(l);
+ }
+ else
+ {
+ // Default mutability depends on directive
+ is_mutable = !ctx->immutable_by_default;
+ }
+
+ // Destructuring: var {x, y} = ...
+ if (lexer_peek(l).type == TOK_LBRACE || lexer_peek(l).type == TOK_LPAREN)
+ {
+ int is_struct = (lexer_peek(l).type == TOK_LBRACE);
+ lexer_next(l);
+ char **names = xmalloc(16 * sizeof(char *));
+ int count = 0;
+ while (1)
+ {
+ Token t = lexer_next(l);
+ char *nm = token_strdup(t);
+ // UPDATE: Pass NULL to add_symbol
+ names[count++] = nm;
+ add_symbol(ctx, nm, "unknown", NULL);
+ // Register mutability for each destructured variable
+ register_var_mutability(ctx, nm, is_mutable);
+ Token next = lexer_next(l);
+ if (next.type == (is_struct ? TOK_RBRACE : TOK_RPAREN))
+ {
+ break;
+ }
+ if (next.type != TOK_COMMA)
+ {
+ zpanic("Expected comma");
+ }
+ }
+ if (lexer_next(l).type != TOK_OP)
+ {
+ zpanic("Expected =");
+ }
+ ASTNode *init = parse_expression(ctx, l);
+ if (lexer_peek(l).type == TOK_SEMICOLON)
+ {
+ lexer_next(l);
+ }
+ ASTNode *n = ast_create(NODE_DESTRUCT_VAR);
+ n->destruct.names = names;
+ n->destruct.count = count;
+ n->destruct.init_expr = init;
+ n->destruct.is_struct_destruct = is_struct;
+ return n;
+ }
+
+ // Normal Declaration OR Named Struct Destructuring
+ Token name_tok = lexer_next(l);
+ char *name = token_strdup(name_tok);
+
+ // Check for Struct Destructuring: var Point { x, y }
+ if (lexer_peek(l).type == TOK_LBRACE)
+ {
+ lexer_next(l); // eat {
+ char **names = xmalloc(16 * sizeof(char *));
+ char **fields = xmalloc(16 * sizeof(char *));
+ int count = 0;
+
+ while (1)
+ {
+ // Parse field:name or just name
+ Token t = lexer_next(l);
+ char *ident = token_strdup(t);
+
+ if (lexer_peek(l).type == TOK_COLON)
+ {
+ // field: var_name
+ lexer_next(l); // eat :
+ Token v = lexer_next(l);
+ fields[count] = ident;
+ names[count] = token_strdup(v);
+ }
+ else
+ {
+ // Shorthand: field (implies var name = field)
+ fields[count] = ident;
+ names[count] = ident; // Share pointer or duplicate? duplicate safer if we free
+ }
+ // Register symbol for variable
+ add_symbol(ctx, names[count], "unknown", NULL);
+ register_var_mutability(ctx, names[count], is_mutable);
+
+ count++;
+
+ Token next = lexer_next(l);
+ if (next.type == TOK_RBRACE)
+ {
+ break;
+ }
+ if (next.type != TOK_COMMA)
+ {
+ zpanic("Expected comma in struct pattern");
+ }
+ }
+
+ if (lexer_next(l).type != TOK_OP)
+ {
+ zpanic("Expected =");
+ }
+ ASTNode *init = parse_expression(ctx, l);
+ if (lexer_peek(l).type == TOK_SEMICOLON)
+ {
+ lexer_next(l);
+ }
+
+ ASTNode *n = ast_create(NODE_DESTRUCT_VAR);
+ n->destruct.names = names;
+ n->destruct.field_names = fields;
+ n->destruct.count = count;
+ n->destruct.init_expr = init;
+ n->destruct.is_struct_destruct = 1;
+ n->destruct.struct_name = name; // "Point"
+ return n;
+ }
+
+ // Check for Guard Pattern: var Some(val) = opt else { ... }
+ if (lexer_peek(l).type == TOK_LPAREN)
+ {
+ lexer_next(l); // eat (
+ Token val_tok = lexer_next(l);
+ char *val_name = token_strdup(val_tok);
+
+ if (lexer_next(l).type != TOK_RPAREN)
+ {
+ zpanic("Expected ')' in guard pattern");
+ }
+
+ if (lexer_next(l).type != TOK_OP)
+ {
+ zpanic("Expected '=' after guard pattern");
+ }
+
+ ASTNode *init = parse_expression(ctx, l);
+
+ Token t = lexer_next(l);
+ if (t.type != TOK_IDENT || strncmp(t.start, "else", 4) != 0)
+ {
+ zpanic("Expected 'else' in guard statement");
+ }
+
+ ASTNode *else_blk;
+ if (lexer_peek(l).type == TOK_LBRACE)
+ {
+ else_blk = parse_block(ctx, l);
+ }
+ else
+ {
+ else_blk = ast_create(NODE_BLOCK);
+ else_blk->block.statements = parse_statement(ctx, l);
+ }
+
+ if (lexer_peek(l).type == TOK_SEMICOLON)
+ {
+ lexer_next(l);
+ }
+
+ ASTNode *n = ast_create(NODE_DESTRUCT_VAR);
+ n->destruct.names = xmalloc(sizeof(char *));
+ n->destruct.names[0] = val_name;
+ n->destruct.count = 1;
+ n->destruct.init_expr = init;
+ n->destruct.is_guard = 1;
+ n->destruct.guard_variant = name;
+ n->destruct.else_block = else_blk;
+
+ add_symbol(ctx, val_name, "unknown", NULL);
+ register_var_mutability(ctx, val_name, is_mutable);
+
+ return n;
+ }
+
+ char *type = NULL;
+ Type *type_obj = NULL; // --- NEW: Formal Type Object ---
+
+ if (lexer_peek(l).type == TOK_COLON)
+ {
+ lexer_next(l);
+ // Hybrid Parse: Get Object AND String
+ type_obj = parse_type_formal(ctx, l);
+ type = type_to_string(type_obj);
+ }
+
+ ASTNode *init = NULL;
+ if (lexer_peek(l).type == TOK_OP && is_token(lexer_peek(l), "="))
+ {
+ lexer_next(l);
+
+ // Peek for special initializers
+ Token next = lexer_peek(l);
+ if (next.type == TOK_IDENT && strncmp(next.start, "embed", 5) == 0)
+ {
+ char *e = parse_embed(ctx, l);
+ init = ast_create(NODE_RAW_STMT);
+ init->raw_stmt.content = e;
+ if (!type)
+ {
+ register_slice(ctx, "char");
+ type = xstrdup("Slice_char");
+ }
+ if (lexer_peek(l).type == TOK_SEMICOLON)
+ {
+ lexer_next(l);
+ }
+ }
+ else if (next.type == TOK_LBRACKET && type && strncmp(type, "Slice_", 6) == 0)
+ {
+ char *code = parse_array_literal(ctx, l, type);
+ init = ast_create(NODE_RAW_STMT);
+ init->raw_stmt.content = code;
+ if (lexer_peek(l).type == TOK_SEMICOLON)
+ {
+ lexer_next(l);
+ }
+ }
+ else if (next.type == TOK_LPAREN && type && strncmp(type, "Tuple_", 6) == 0)
+ {
+ char *code = parse_tuple_literal(ctx, l, type);
+ init = ast_create(NODE_RAW_STMT);
+ init->raw_stmt.content = code;
+ if (lexer_peek(l).type == TOK_SEMICOLON)
+ {
+ lexer_next(l);
+ }
+ }
+ else
+ {
+ init = parse_expression(ctx, l);
+ }
+
+ if (init && type)
+ {
+ // 1. Get RHS Type
+ char *rhs_type = init->resolved_type;
+ if (!rhs_type && init->type_info)
+ {
+ rhs_type = type_to_string(init->type_info);
+ }
+
+ // 2. Check for Pointer Mismatch (Target* = Source*)
+ if (rhs_type && strchr(type, '*') && strchr(rhs_type, '*'))
+ {
+ // Strip stars to get struct names
+ char target_struct[256];
+ strcpy(target_struct, type);
+ target_struct[strlen(target_struct) - 1] = 0;
+ char source_struct[256];
+ strcpy(source_struct, rhs_type);
+ source_struct[strlen(source_struct) - 1] = 0;
+
+ // 3. Look up Source definition to find its Parent
+ ASTNode *def = find_struct_def(ctx, source_struct);
+
+ // 4. If Source's parent matches Target, Inject Cast!
+ if (def && def->strct.parent && strcmp(def->strct.parent, target_struct) == 0)
+ {
+ // Create Cast Node
+ ASTNode *cast = ast_create(NODE_EXPR_CAST);
+ cast->cast.target_type = xstrdup(type);
+ cast->cast.expr = init;
+ cast->type_info = type_obj; // Inherit formal type
+
+ init = cast; // Replace init with cast
+ }
+ }
+ }
+
+ // --- Type Inference Logic ---
+ if (!type && init)
+ {
+ // FIX: Trust the AST type info if available (handles Calls, Binary Ops)
+ if (init->type_info)
+ {
+ type_obj = init->type_info;
+ type = type_to_string(type_obj);
+ }
+ else if (init->type == NODE_EXPR_SLICE)
+ {
+ zpanic("Slice Node has NO Type Info!");
+ }
+ // Fallbacks for literals
+ else if (init->type == NODE_EXPR_LITERAL)
+ {
+ if (init->literal.type_kind == 0)
+ {
+ type = xstrdup("int");
+ type_obj = type_new(TYPE_INT);
+ }
+ else if (init->literal.type_kind == 1)
+ {
+ type = xstrdup("float");
+ type_obj = type_new(TYPE_FLOAT);
+ }
+ else if (init->literal.type_kind == 2)
+ {
+ type = xstrdup("string");
+ type_obj = type_new(TYPE_STRING);
+ }
+ }
+ else if (init->type == NODE_EXPR_STRUCT_INIT)
+ {
+ type = xstrdup(init->struct_init.struct_name);
+ type_obj = type_new(TYPE_STRUCT);
+ type_obj->name = xstrdup(type);
+ }
+
+ // fprintf(stderr, "DEBUG PVarDecl: Var '%s' inferred type '%s' (init->type_info
+ // present: %d)\n", name, type, init && init->type_info ? 1 : 0);
+ }
+ }
+
+ if (!type && !init)
+ {
+ zpanic_at(name_tok, "Variable '%s' requires a type or initializer", name);
+ }
+
+ // Register in symbol table with actual token
+ add_symbol_with_token(ctx, name, type, type_obj, name_tok);
+ register_var_mutability(ctx, name, is_mutable);
+
+ // NEW: Capture Const Integer Values
+ if (!is_mutable && init && init->type == NODE_EXPR_LITERAL && init->literal.type_kind == 0)
+ {
+ Symbol *s = find_symbol_entry(ctx, name); // Helper to find the struct
+ if (s)
+ {
+ s->is_const_value = 1;
+ s->const_int_val = init->literal.int_val;
+ }
+ }
+
+ if (lexer_peek(l).type == TOK_SEMICOLON)
+ {
+ lexer_next(l);
+ }
+
+ ASTNode *n = ast_create(NODE_VAR_DECL);
+ n->token = name_tok; // Save location
+ n->var_decl.name = name;
+ n->var_decl.type_str = type;
+ n->var_decl.is_mutable = is_mutable;
+ n->type_info = type_obj;
+
+ // Auto-construct Trait Object
+ if (type && is_trait(type) && init && init->type == NODE_EXPR_UNARY &&
+ strcmp(init->unary.op, "&") == 0 && init->unary.operand->type == NODE_EXPR_VAR)
+ {
+ char *var_ref_name = init->unary.operand->var_ref.name;
+ char *struct_type = find_symbol_type(ctx, var_ref_name);
+ if (struct_type)
+ {
+ char *code = xmalloc(512);
+ sprintf(code, "(%s){.self=&%s, .vtable=&%s_%s_VTable}", type, var_ref_name, struct_type,
+ type);
+ ASTNode *wrapper = ast_create(NODE_RAW_STMT);
+ wrapper->raw_stmt.content = code;
+ init = wrapper;
+ }
+ }
+
+ n->var_decl.init_expr = init;
+
+ // Global detection: Either no scope (yet) OR root scope (no parent)
+ if (!ctx->current_scope || !ctx->current_scope->parent)
+ {
+ add_to_global_list(ctx, n);
+ }
+
+ // Check for 'defer' (Value-Returning Defer)
+ if (lexer_peek(l).type == TOK_DEFER)
+ {
+ lexer_next(l); // eat defer
+ // Parse the defer expression/statement
+ // Usually defer close(it);
+ // We parse expression.
+ ASTNode *expr = parse_expression(ctx, l);
+
+ // Handle "it" substitution
+ replace_it_with_var(expr, name);
+
+ if (lexer_peek(l).type == TOK_SEMICOLON)
+ {
+ lexer_next(l);
+ }
+
+ ASTNode *d = ast_create(NODE_DEFER);
+ d->defer_stmt.stmt = expr;
+
+ // Chain it: var_decl -> defer
+ n->next = d;
+ }
+
+ return n;
+}
+
+ASTNode *parse_const(ParserContext *ctx, Lexer *l)
+{
+ lexer_next(l); // eat const
+ Token n = lexer_next(l);
+
+ char *type_str = NULL;
+ Type *type_obj = NULL;
+
+ if (lexer_peek(l).type == TOK_COLON)
+ {
+ lexer_next(l);
+ // Hybrid Parse
+ type_obj = parse_type_formal(ctx, l);
+ type_str = type_to_string(type_obj);
+ }
+
+ char *ns = token_strdup(n);
+ if (!type_obj)
+ {
+ type_obj = type_new(TYPE_UNKNOWN); // Ensure we have an object
+ }
+ type_obj->is_const = 1;
+ add_symbol(ctx, ns, type_str ? type_str : "unknown", type_obj);
+
+ ASTNode *i = 0;
+ if (lexer_peek(l).type == TOK_OP && is_token(lexer_peek(l), "="))
+ {
+ lexer_next(l);
+
+ // Check for constant integer literal
+ if (lexer_peek(l).type == TOK_INT)
+ {
+ Token val_tok = lexer_peek(l);
+ int val = atoi(token_strdup(val_tok)); // quick check
+
+ Symbol *s = find_symbol_entry(ctx, ns);
+ if (s)
+ {
+ s->is_const_value = 1;
+ s->const_int_val = val;
+
+ // FIX: Infer type 'int' if unknown
+ if (!s->type_name || strcmp(s->type_name, "unknown") == 0)
+ {
+ if (s->type_name)
+ {
+ free(s->type_name);
+ }
+ s->type_name = xstrdup("int");
+ if (s->type_info)
+ {
+ free(s->type_info);
+ }
+ s->type_info = type_new(TYPE_INT);
+ }
+ }
+ }
+
+ if (lexer_peek(l).type == TOK_LPAREN && type_str && strncmp(type_str, "Tuple_", 6) == 0)
+ {
+ char *code = parse_tuple_literal(ctx, l, type_str);
+ i = ast_create(NODE_RAW_STMT);
+ i->raw_stmt.content = code;
+ }
+ else
+ {
+ i = parse_expression(ctx, l);
+ }
+ }
+ else
+ {
+ lexer_next(l);
+ }
+
+ if (lexer_peek(l).type == TOK_SEMICOLON)
+ {
+ lexer_next(l);
+ }
+
+ ASTNode *o = ast_create(NODE_CONST);
+ o->var_decl.name = ns;
+ o->var_decl.type_str = type_str;
+ o->var_decl.init_expr = i;
+
+ if (!ctx->current_scope || !ctx->current_scope->parent)
+ {
+ add_to_global_list(ctx, o);
+ }
+
+ return o;
+}
+
+ASTNode *parse_type_alias(ParserContext *ctx, Lexer *l)
+{
+ lexer_next(l);
+ Token n = lexer_next(l);
+ lexer_next(l);
+ char *o = parse_type(ctx, l);
+ lexer_next(l);
+ ASTNode *node = ast_create(NODE_TYPE_ALIAS);
+ node->type_alias.alias = xmalloc(n.len + 1);
+ strncpy(node->type_alias.alias, n.start, n.len);
+ node->type_alias.alias[n.len] = 0;
+ node->type_alias.original_type = o;
+ return node;
+}
+
+ASTNode *parse_return(ParserContext *ctx, Lexer *l)
+{
+ lexer_next(l); // eat 'return'
+ ASTNode *n = ast_create(NODE_RETURN);
+
+ int handled = 0;
+
+ // 1. Check for Tuple Literal Return: return (a, b);
+ // Condition: Function returns Tuple_..., starts with '(', and contains ',' at top level
+ if (curr_func_ret && strncmp(curr_func_ret, "Tuple_", 6) == 0 &&
+ lexer_peek(l).type == TOK_LPAREN)
+ {
+
+ // Peek ahead to distinguish "(expr)" from "(a, b)"
+ int is_tuple_lit = 0;
+ int depth = 0;
+
+ // Just scan tokens manually using a temp lexer to be safe
+ Lexer temp_l = *l;
+
+ while (1)
+ {
+ Token t = lexer_next(&temp_l);
+ if (t.type == TOK_EOF)
+ {
+ break;
+ }
+ if (t.type == TOK_SEMICOLON)
+ {
+ break; // Safety break
+ }
+
+ if (t.type == TOK_LPAREN)
+ {
+ depth++;
+ }
+ if (t.type == TOK_RPAREN)
+ {
+ depth--;
+ if (depth == 0)
+ {
+ break; // End of potential tuple
+ }
+ }
+
+ // If we find a comma at depth 1 (inside the first parens), it's a tuple literal!
+ if (depth == 1 && t.type == TOK_COMMA)
+ {
+ is_tuple_lit = 1;
+ break;
+ }
+ }
+
+ if (is_tuple_lit)
+ {
+ char *code = parse_tuple_literal(ctx, l, curr_func_ret);
+ ASTNode *raw = ast_create(NODE_RAW_STMT);
+ raw->raw_stmt.content = code;
+ n->ret.value = raw;
+ handled = 1;
+ }
+ }
+ // 2. Check for Array Literal Return: return [a, b];
+ else if (curr_func_ret && strncmp(curr_func_ret, "Slice_", 6) == 0 &&
+ lexer_peek(l).type == TOK_LBRACKET)
+ {
+ char *code = parse_array_literal(ctx, l, curr_func_ret);
+ ASTNode *raw = ast_create(NODE_RAW_STMT);
+ raw->raw_stmt.content = code;
+ n->ret.value = raw;
+ handled = 1;
+ }
+
+ // 3. Standard Expression Return
+ if (!handled)
+ {
+ if (lexer_peek(l).type == TOK_SEMICOLON)
+ {
+ n->ret.value = NULL;
+ }
+ else
+ {
+ n->ret.value = parse_expression(ctx, l);
+ }
+ }
+
+ if (lexer_peek(l).type == TOK_SEMICOLON)
+ {
+ lexer_next(l);
+ }
+ return n;
+}
+
+ASTNode *parse_if(ParserContext *ctx, Lexer *l)
+{
+ lexer_next(l); // eat if
+ ASTNode *cond = parse_expression(ctx, l);
+ check_assignment_condition(cond);
+
+ ASTNode *then_b = NULL;
+ if (lexer_peek(l).type == TOK_LBRACE)
+ {
+ then_b = parse_block(ctx, l);
+ }
+ else
+ {
+ // Single statement: Wrap in scope + block
+ enter_scope(ctx);
+ ASTNode *s = parse_statement(ctx, l);
+ exit_scope(ctx);
+ then_b = ast_create(NODE_BLOCK);
+ then_b->block.statements = s;
+ }
+
+ ASTNode *else_b = NULL;
+ skip_comments(l);
+ if (lexer_peek(l).type == TOK_IDENT && strncmp(lexer_peek(l).start, "else", 4) == 0)
+ {
+ lexer_next(l);
+ if (lexer_peek(l).type == TOK_IDENT && strncmp(lexer_peek(l).start, "if", 2) == 0)
+ {
+ else_b = parse_if(ctx, l);
+ }
+ else if (lexer_peek(l).type == TOK_LBRACE)
+ {
+ else_b = parse_block(ctx, l);
+ }
+ else
+ {
+ // Single statement else
+ enter_scope(ctx);
+ ASTNode *s = parse_statement(ctx, l);
+ exit_scope(ctx);
+ else_b = ast_create(NODE_BLOCK);
+ else_b->block.statements = s;
+ }
+ }
+ ASTNode *n = ast_create(NODE_IF);
+ n->if_stmt.condition = cond;
+ n->if_stmt.then_body = then_b;
+ n->if_stmt.else_body = else_b;
+ return n;
+}
+
+ASTNode *parse_while(ParserContext *ctx, Lexer *l)
+{
+ lexer_next(l);
+ ASTNode *cond = parse_expression(ctx, l);
+ check_assignment_condition(cond);
+
+ // Zen: While(true)
+ if ((cond->type == NODE_EXPR_LITERAL && cond->literal.type_kind == TOK_INT &&
+ strcmp(cond->literal.string_val, "1") == 0) ||
+ (cond->type == NODE_EXPR_VAR && strcmp(cond->var_ref.name, "true") == 0))
+ {
+ zen_trigger_at(TRIGGER_WHILE_TRUE, cond->token);
+ }
+ ASTNode *body;
+ if (lexer_peek(l).type == TOK_LBRACE)
+ {
+ body = parse_block(ctx, l);
+ }
+ else
+ {
+ body = parse_statement(ctx, l);
+ }
+ ASTNode *n = ast_create(NODE_WHILE);
+ n->while_stmt.condition = cond;
+ n->while_stmt.body = body;
+ return n;
+}
+
+ASTNode *parse_for(ParserContext *ctx, Lexer *l)
+{
+ lexer_next(l);
+
+ // Range Loop: for i in 0..10
+ if (lexer_peek(l).type == TOK_IDENT)
+ {
+ int saved_pos = l->pos;
+ Token var = lexer_next(l);
+ Token in_tok = lexer_next(l);
+
+ if (in_tok.type == TOK_IDENT && strncmp(in_tok.start, "in", 2) == 0)
+ {
+ Token start_tok = lexer_next(l);
+ if (lexer_next(l).type == TOK_DOTDOT)
+ {
+ Token end_tok = lexer_next(l);
+
+ ASTNode *n = ast_create(NODE_FOR_RANGE);
+ n->for_range.var_name = xmalloc(var.len + 1);
+ strncpy(n->for_range.var_name, var.start, var.len);
+ n->for_range.var_name[var.len] = 0;
+ n->for_range.start = xmalloc(start_tok.len + 1);
+ strncpy(n->for_range.start, start_tok.start, start_tok.len);
+ n->for_range.start[start_tok.len] = 0;
+ n->for_range.end = xmalloc(end_tok.len + 1);
+ strncpy(n->for_range.end, end_tok.start, end_tok.len);
+ n->for_range.end[end_tok.len] = 0;
+
+ if (lexer_peek(l).type == TOK_IDENT && strncmp(lexer_peek(l).start, "step", 4) == 0)
+ {
+ lexer_next(l);
+ Token s_tok = lexer_next(l);
+ char *sval = xmalloc(s_tok.len + 1);
+ strncpy(sval, s_tok.start, s_tok.len);
+ sval[s_tok.len] = 0;
+ n->for_range.step = sval;
+ }
+ else
+ {
+ n->for_range.step = NULL;
+ }
+
+ // Fix: Enter scope to register loop variable
+ enter_scope(ctx);
+ // Register loop variable so body can see it
+ add_symbol(ctx, n->for_range.var_name, "int", type_new(TYPE_INT));
+
+ // Handle body (brace or single stmt)
+ if (lexer_peek(l).type == TOK_LBRACE)
+ {
+ n->for_range.body = parse_block(ctx, l);
+ }
+ else
+ {
+ n->for_range.body = parse_statement(ctx, l);
+ }
+ exit_scope(ctx);
+
+ return n;
+ }
+ }
+ l->pos = saved_pos; // Restore
+ }
+
+ // C-Style For Loop
+ enter_scope(ctx);
+ if (lexer_peek(l).type == TOK_LPAREN)
+ {
+ lexer_next(l);
+ }
+
+ ASTNode *init = NULL;
+ if (lexer_peek(l).type != TOK_SEMICOLON)
+ {
+ if (lexer_peek(l).type == TOK_IDENT && strncmp(lexer_peek(l).start, "var", 3) == 0)
+ {
+ init = parse_var_decl(ctx, l);
+ }
+ else
+ {
+ init = parse_expression(ctx, l);
+ if (lexer_peek(l).type == TOK_SEMICOLON)
+ {
+ lexer_next(l);
+ }
+ }
+ }
+ else
+ {
+ lexer_next(l);
+ }
+
+ ASTNode *cond = NULL;
+ if (lexer_peek(l).type != TOK_SEMICOLON)
+ {
+ cond = parse_expression(ctx, l);
+ }
+ else
+ {
+ // Empty condition = true
+ ASTNode *true_lit = ast_create(NODE_EXPR_LITERAL);
+ true_lit->literal.type_kind = 0;
+ true_lit->literal.int_val = 1;
+ cond = true_lit;
+ }
+ if (lexer_peek(l).type == TOK_SEMICOLON)
+ {
+ lexer_next(l);
+ }
+
+ ASTNode *step = NULL;
+ if (lexer_peek(l).type != TOK_RPAREN && lexer_peek(l).type != TOK_LBRACE)
+ {
+ step = parse_expression(ctx, l);
+ }
+
+ if (lexer_peek(l).type == TOK_RPAREN)
+ {
+ lexer_next(l);
+ }
+
+ ASTNode *body;
+ if (lexer_peek(l).type == TOK_LBRACE)
+ {
+ body = parse_block(ctx, l);
+ }
+ else
+ {
+ body = parse_statement(ctx, l);
+ }
+ exit_scope(ctx);
+
+ ASTNode *n = ast_create(NODE_FOR);
+ n->for_stmt.init = init;
+ n->for_stmt.condition = cond;
+ n->for_stmt.step = step;
+ n->for_stmt.body = body;
+ return n;
+}
+
+char *process_printf_sugar(ParserContext *ctx, const char *content, int newline, const char *target)
+{
+ char *gen = xmalloc(8192);
+ strcpy(gen, "({ ");
+
+ char *s = xstrdup(content);
+ char *cur = s;
+
+ while (*cur)
+ {
+ // 1. Find text before the next '{'
+ char *brace = cur;
+ while (*brace && *brace != '{')
+ {
+ brace++;
+ }
+
+ if (brace > cur)
+ {
+ // Append text literal
+ char buf[256];
+ sprintf(buf, "fprintf(%s, \"%%s\", \"", target);
+ strcat(gen, buf);
+ strncat(gen, cur, brace - cur);
+ strcat(gen, "\"); ");
+ }
+
+ if (*brace == 0)
+ {
+ break;
+ }
+
+ // 2. Handle {expression}
+ char *p = brace + 1;
+ char *colon = NULL;
+ int depth = 1;
+ while (*p && depth > 0)
+ {
+ if (*p == '{')
+ {
+ depth++;
+ }
+ if (*p == '}')
+ {
+ depth--;
+ }
+ if (depth == 1 && *p == ':' && !colon)
+ {
+ colon = p;
+ }
+ if (depth == 0)
+ {
+ break;
+ }
+ p++;
+ }
+
+ *p = 0; // Terminate expression
+ char *expr = brace + 1;
+
+ // Unescape \" to " in the expression code to ensure correct parsing
+ char *read = expr;
+ char *write = expr;
+ while (*read)
+ {
+ if (*read == '\\' && *(read + 1) == '"')
+ {
+ *write = '"';
+ read += 2;
+ write++;
+ }
+ else
+ {
+ *write = *read;
+ read++;
+ write++;
+ }
+ }
+ *write = 0;
+ char *fmt = NULL;
+ if (colon)
+ {
+ *colon = 0;
+ fmt = colon + 1;
+ }
+
+ // ============================================================
+ // === [NEW] Auto-detect to_string() overload ===
+ // ============================================================
+ char *clean_expr = expr;
+ while (*clean_expr == ' ')
+ {
+ clean_expr++; // Skip leading spaces
+ }
+
+ // Mark the variable as used (fixes "Unused variable" warnings for f-string interpolation)
+ Symbol *sym = find_symbol_entry(ctx, clean_expr);
+ if (sym)
+ {
+ sym->is_used = 1;
+ }
+
+ char *type = find_symbol_type(ctx, clean_expr);
+ char *allocated_expr = NULL;
+
+ if (type)
+ {
+ char func_name[512];
+ snprintf(func_name, sizeof(func_name), "%s_to_string", type);
+
+ // If type is a pointer (Struct*), check if base Struct has to_string
+ if (!find_func(ctx, func_name) && strchr(type, '*'))
+ {
+ char base[256];
+ strcpy(base, type);
+ base[strlen(base) - 1] = 0; // remove '*'
+ snprintf(func_name, sizeof(func_name), "%s_to_string", base);
+ }
+
+ // If a _to_string method exists, rewrite 'x' -> 'x.to_string()'
+ if (find_func(ctx, func_name))
+ {
+ allocated_expr = xmalloc(strlen(clean_expr) + 20);
+ sprintf(allocated_expr, "%s.to_string()", clean_expr);
+ expr = allocated_expr;
+ }
+ }
+ // ============================================================
+
+ // Rewrite the expression to handle pointer access (header_ptr.magic -> header_ptr->magic)
+ char *wrapped_expr = xmalloc(strlen(expr) + 5);
+ sprintf(wrapped_expr, "#{%s}", expr);
+ char *rw_expr = rewrite_expr_methods(ctx, wrapped_expr);
+ free(wrapped_expr);
+
+ if (fmt)
+ {
+ // Explicit format: {x:%.2f}
+ char buf[128];
+ sprintf(buf, "fprintf(%s, \"%%", target);
+ strcat(gen, buf);
+ strcat(gen, fmt);
+ strcat(gen, "\", ");
+ strcat(gen, rw_expr); // Use rewritten expr
+ strcat(gen, "); ");
+ }
+ else
+ {
+ // Auto-detect format based on type if possible
+ const char *format_spec = NULL;
+ char *inferred_type = find_symbol_type(ctx, clean_expr); // Simple variable lookup
+
+ // Basic Type Mappings
+ if (inferred_type)
+ {
+ if (strcmp(inferred_type, "int") == 0 || strcmp(inferred_type, "i32") == 0 ||
+ strcmp(inferred_type, "bool") == 0)
+ {
+ format_spec = "%d";
+ }
+ else if (strcmp(inferred_type, "long") == 0 || strcmp(inferred_type, "i64") == 0 ||
+ strcmp(inferred_type, "isize") == 0)
+ {
+ format_spec = "%ld";
+ }
+ else if (strcmp(inferred_type, "usize") == 0 || strcmp(inferred_type, "u64") == 0)
+ {
+ format_spec = "%lu";
+ }
+ else if (strcmp(inferred_type, "float") == 0 || strcmp(inferred_type, "f32") == 0 ||
+ strcmp(inferred_type, "double") == 0)
+ {
+ format_spec = "%f";
+ }
+ else if (strcmp(inferred_type, "char") == 0 || strcmp(inferred_type, "byte") == 0)
+ {
+ format_spec = "%c";
+ }
+ else if (strcmp(inferred_type, "string") == 0 ||
+ strcmp(inferred_type, "str") == 0 ||
+ (inferred_type[strlen(inferred_type) - 1] == '*' &&
+ strstr(inferred_type, "char")))
+ {
+ format_spec = "%s";
+ }
+ else if (strstr(inferred_type, "*"))
+ {
+ format_spec = "%p"; // Pointer
+ }
+ }
+
+ // Check for Literals if variable lookup failed
+ if (!format_spec)
+ {
+ if (isdigit(clean_expr[0]) || clean_expr[0] == '-')
+ {
+ format_spec = "%d"; // Naive integer guess (could be float)
+ }
+ else if (clean_expr[0] == '"')
+ {
+ format_spec = "%s";
+ }
+ else if (clean_expr[0] == '\'')
+ {
+ format_spec = "%c";
+ }
+ }
+
+ if (format_spec)
+ {
+ char buf[128];
+ sprintf(buf, "fprintf(%s, \"", target);
+ strcat(gen, buf);
+ strcat(gen, format_spec);
+ strcat(gen, "\", ");
+ strcat(gen, rw_expr);
+ strcat(gen, "); ");
+ }
+ else
+ {
+ // Fallback to runtime macro
+ char buf[128];
+ sprintf(buf, "fprintf(%s, _z_str(", target);
+ strcat(gen, buf);
+ strcat(gen, rw_expr);
+ strcat(gen, "), ");
+ strcat(gen, rw_expr);
+ strcat(gen, "); ");
+ }
+ }
+
+ free(rw_expr); // Don't forget to free!
+ if (allocated_expr)
+ {
+ free(allocated_expr); // Don't forget to free the auto-generated call!
+ }
+
+ cur = p + 1;
+ }
+
+ if (newline)
+ {
+ char buf[128];
+ sprintf(buf, "fprintf(%s, \"\\n\"); ", target);
+ strcat(gen, buf);
+ }
+ else
+ {
+ strcat(gen, "fflush(stdout); ");
+ }
+
+ strcat(gen, "0; })");
+
+ free(s);
+ return gen;
+}
+
+ASTNode *parse_macro_call(ParserContext *ctx, Lexer *l, char *macro_name)
+{
+ Token start_tok = lexer_peek(l);
+ if (lexer_peek(l).type != TOK_OP || lexer_peek(l).start[0] != '!')
+ {
+ return NULL;
+ }
+ lexer_next(l); // consume !
+
+ // Expect {
+ if (lexer_peek(l).type != TOK_LBRACE)
+ {
+ zpanic("Expected { after macro invocation");
+ }
+ lexer_next(l); // consume {
+
+ // Collect body until }
+ char *body = xmalloc(8192);
+ body[0] = '\0';
+ int body_len = 0;
+ int depth = 1;
+ int last_line = start_tok.line;
+
+ while (depth > 0)
+ {
+ Token t = lexer_peek(l);
+ if (t.type == TOK_EOF)
+ {
+ zpanic("Unexpected EOF in macro block");
+ }
+
+ if (t.type == TOK_LBRACE)
+ {
+ depth++;
+ }
+ if (t.type == TOK_RBRACE)
+ {
+ depth--;
+ }
+
+ if (depth > 0)
+ {
+ if (body_len + t.len + 2 < 8192)
+ {
+ // Preserve newlines
+ if (t.line > last_line)
+ {
+ body[body_len] = '\n';
+ body[body_len + 1] = 0;
+ body_len++;
+ }
+ else
+ {
+ body[body_len] = ' ';
+ body[body_len + 1] = 0;
+ body_len++;
+ }
+
+ strncat(body, t.start, t.len);
+ body_len += t.len;
+ }
+ }
+
+ last_line = t.line;
+ lexer_next(l);
+ }
+
+ // Resolve plugin name
+ const char *plugin_name = resolve_plugin(ctx, macro_name);
+ if (!plugin_name)
+ {
+ char err[256];
+ snprintf(err, sizeof(err), "Unknown plugin: %s (did you forget 'import plugin \"%s\"'?)",
+ macro_name, macro_name);
+ zpanic(err);
+ }
+
+ // Find Plugin Definition
+ // Verify plugin exists
+ ZPlugin *found = zptr_find_plugin(plugin_name);
+
+ if (!found)
+ {
+ char err[256];
+ snprintf(err, sizeof(err), "Plugin implementation not found: %s", plugin_name);
+ zpanic(err);
+ }
+
+ // Execute Plugin Immediately (Expansion)
+ FILE *capture = tmpfile();
+ if (!capture)
+ {
+ zpanic("Failed to create capture buffer for plugin expansion");
+ }
+
+ ZApi api = {.filename = g_current_filename ? g_current_filename : "input.zc",
+ .current_line = start_tok.line,
+ .out = capture,
+ .hoist_out = ctx->hoist_out};
+
+ found->fn(body, &api);
+
+ // Read captured output
+ long len = ftell(capture);
+ rewind(capture);
+ char *expanded_code = xmalloc(len + 1);
+ fread(expanded_code, 1, len, capture);
+ expanded_code[len] = 0;
+ fclose(capture);
+ free(body);
+
+ // Create Raw Statement/Expression Node
+ ASTNode *n = ast_create(NODE_RAW_STMT);
+ n->line = start_tok.line;
+ n->raw_stmt.content = expanded_code;
+
+ return n;
+}
+
+ASTNode *parse_statement(ParserContext *ctx, Lexer *l)
+{
+ Token tk = lexer_peek(l);
+ ASTNode *s = NULL;
+
+ if (tk.type == TOK_SEMICOLON)
+ {
+ lexer_next(l);
+ ASTNode *nop = ast_create(NODE_BLOCK); // Empty block as NOP
+ nop->block.statements = NULL;
+ return nop;
+ }
+
+ if (tk.type == TOK_PREPROC)
+ {
+ lexer_next(l); // consume token
+ char *content = xmalloc(tk.len + 2);
+ strncpy(content, tk.start, tk.len);
+ content[tk.len] = '\n'; // Ensure newline
+ content[tk.len + 1] = 0;
+ ASTNode *s = ast_create(NODE_RAW_STMT);
+ s->raw_stmt.content = content;
+ return s;
+ }
+
+ if (tk.type == TOK_STRING || tk.type == TOK_FSTRING)
+ {
+ Lexer lookahead = *l;
+ lexer_next(&lookahead);
+ TokenType next_type = lexer_peek(&lookahead).type;
+
+ if (next_type == TOK_SEMICOLON || next_type == TOK_DOTDOT)
+ {
+ Token t = lexer_next(l); // consume string
+
+ char *inner = xmalloc(t.len);
+ // Strip quotes
+ if (t.type == TOK_FSTRING)
+ {
+ strncpy(inner, t.start + 2, t.len - 3);
+ inner[t.len - 3] = 0;
+ }
+ else
+ {
+ strncpy(inner, t.start + 1, t.len - 2);
+ inner[t.len - 2] = 0;
+ }
+
+ // ; means println (end of line), .. means print (continuation)
+ int is_ln = (next_type == TOK_SEMICOLON);
+ char *code = process_printf_sugar(ctx, inner, is_ln, "stdout");
+
+ if (next_type == TOK_SEMICOLON)
+ {
+ lexer_next(l); // consume ;
+ }
+ else if (next_type == TOK_DOTDOT)
+ {
+ lexer_next(l); // consume ..
+ }
+
+ ASTNode *n = ast_create(NODE_RAW_STMT);
+ n->raw_stmt.content = code;
+ free(inner);
+ return n;
+ }
+ }
+
+ // Block
+ if (tk.type == TOK_LBRACE)
+ {
+ return parse_block(ctx, l);
+ }
+
+ // Keywords / Special
+ if (tk.type == TOK_TRAIT)
+ {
+ return parse_trait(ctx, l);
+ }
+ if (tk.type == TOK_IMPL)
+ {
+ return parse_impl(ctx, l);
+ }
+ if (tk.type == TOK_AUTOFREE)
+ {
+ lexer_next(l);
+ if (lexer_peek(l).type != TOK_IDENT || strncmp(lexer_peek(l).start, "var", 3) != 0)
+ {
+ zpanic("Expected 'var' after autofree");
+ }
+ s = parse_var_decl(ctx, l);
+ s->var_decl.is_autofree = 1;
+ // Mark symbol as autofree to suppress unused variable warning
+ Symbol *sym = find_symbol_entry(ctx, s->var_decl.name);
+ if (sym)
+ {
+ sym->is_autofree = 1;
+ }
+ return s;
+ }
+ if (tk.type == TOK_TEST)
+ {
+ return parse_test(ctx, l);
+ }
+ if (tk.type == TOK_ASSERT)
+ {
+ return parse_assert(ctx, l);
+ }
+ if (tk.type == TOK_DEFER)
+ {
+ return parse_defer(ctx, l);
+ }
+ if (tk.type == TOK_ASM)
+ {
+ return parse_asm(ctx, l);
+ }
+
+ // Identifiers (Keywords or Expressions)
+ if (tk.type == TOK_IDENT)
+ {
+ // Check for macro invocation: identifier! { code }
+ Lexer lookahead = *l;
+ lexer_next(&lookahead);
+ Token exclaim = lexer_peek(&lookahead);
+ lexer_next(&lookahead);
+ Token lbrace = lexer_peek(&lookahead);
+ if (exclaim.type == TOK_OP && exclaim.len == 1 && exclaim.start[0] == '!' &&
+ lbrace.type == TOK_LBRACE)
+ {
+ // This is a macro invocation
+ char *macro_name = token_strdup(tk);
+ lexer_next(l); // consume identifier
+
+ ASTNode *n = parse_macro_call(ctx, l, macro_name);
+ free(macro_name);
+ return n;
+ }
+
+ // Check for raw blocks
+ if (strncmp(tk.start, "raw", 3) == 0 && tk.len == 3)
+ {
+ lexer_next(l); // eat raw
+ if (lexer_peek(l).type != TOK_LBRACE)
+ {
+ zpanic("Expected { after raw");
+ }
+ lexer_next(l); // eat {
+
+ const char *start = l->src + l->pos;
+ int depth = 1;
+ while (depth > 0)
+ {
+ Token t = lexer_next(l);
+ if (t.type == TOK_EOF)
+ {
+ zpanic("Unexpected EOF in raw block");
+ }
+ if (t.type == TOK_LBRACE)
+ {
+ depth++;
+ }
+ if (t.type == TOK_RBRACE)
+ {
+ depth--;
+ }
+ }
+ const char *end = l->src + l->pos - 1;
+ size_t len = end - start;
+
+ char *content = xmalloc(len + 1);
+ memcpy(content, start, len);
+ content[len] = 0;
+
+ ASTNode *s = ast_create(NODE_RAW_STMT);
+ s->raw_stmt.content = content;
+ return s;
+ }
+
+ // Check for plugin blocks
+ if (strncmp(tk.start, "plugin", 6) == 0 && tk.len == 6)
+ {
+ lexer_next(l); // consume 'plugin'
+ return parse_plugin(ctx, l);
+ }
+
+ if (strncmp(tk.start, "var", 3) == 0 && tk.len == 3)
+ {
+ return parse_var_decl(ctx, l);
+ }
+
+ // Static local variable: static var x = 0;
+ if (strncmp(tk.start, "static", 6) == 0 && tk.len == 6)
+ {
+ lexer_next(l); // eat 'static'
+ Token next = lexer_peek(l);
+ if (strncmp(next.start, "var", 3) == 0 && next.len == 3)
+ {
+ ASTNode *v = parse_var_decl(ctx, l);
+ v->var_decl.is_static = 1;
+ return v;
+ }
+ zpanic_at(next, "Expected 'var' after 'static'");
+ }
+ if (strncmp(tk.start, "const", 5) == 0 && tk.len == 5)
+ {
+ return parse_const(ctx, l);
+ }
+ if (strncmp(tk.start, "return", 6) == 0 && tk.len == 6)
+ {
+ return parse_return(ctx, l);
+ }
+ if (strncmp(tk.start, "if", 2) == 0 && tk.len == 2)
+ {
+ return parse_if(ctx, l);
+ }
+ if (strncmp(tk.start, "while", 5) == 0 && tk.len == 5)
+ {
+ return parse_while(ctx, l);
+ }
+ if (strncmp(tk.start, "for", 3) == 0 && tk.len == 3)
+ {
+ return parse_for(ctx, l);
+ }
+ if (strncmp(tk.start, "match", 5) == 0 && tk.len == 5)
+ {
+ return parse_match(ctx, l);
+ }
+
+ // Break with optional label: break; or break 'outer;
+ if (strncmp(tk.start, "break", 5) == 0 && tk.len == 5)
+ {
+ lexer_next(l);
+ ASTNode *n = ast_create(NODE_BREAK);
+ n->break_stmt.target_label = NULL;
+ // Check for 'label
+ if (lexer_peek(l).type == TOK_CHAR)
+ {
+ Token label_tok = lexer_next(l);
+ // Extract label name (strip quotes)
+ char *label = xmalloc(label_tok.len);
+ strncpy(label, label_tok.start + 1, label_tok.len - 2);
+ label[label_tok.len - 2] = 0;
+ n->break_stmt.target_label = label;
+ }
+ if (lexer_peek(l).type == TOK_SEMICOLON)
+ {
+ lexer_next(l);
+ }
+ return n;
+ }
+
+ // Continue with optional label
+ if (strncmp(tk.start, "continue", 8) == 0 && tk.len == 8)
+ {
+ lexer_next(l);
+ ASTNode *n = ast_create(NODE_CONTINUE);
+ n->continue_stmt.target_label = NULL;
+ if (lexer_peek(l).type == TOK_CHAR)
+ {
+ Token label_tok = lexer_next(l);
+ char *label = xmalloc(label_tok.len);
+ strncpy(label, label_tok.start + 1, label_tok.len - 2);
+ label[label_tok.len - 2] = 0;
+ n->continue_stmt.target_label = label;
+ }
+ if (lexer_peek(l).type == TOK_SEMICOLON)
+ {
+ lexer_next(l);
+ }
+ return n;
+ }
+
+ if (strncmp(tk.start, "loop", 4) == 0 && tk.len == 4)
+ {
+ return parse_loop(ctx, l);
+ }
+ if (strncmp(tk.start, "repeat", 6) == 0 && tk.len == 6)
+ {
+ return parse_repeat(ctx, l);
+ }
+ if (strncmp(tk.start, "unless", 6) == 0 && tk.len == 6)
+ {
+ return parse_unless(ctx, l);
+ }
+ if (strncmp(tk.start, "guard", 5) == 0 && tk.len == 5)
+ {
+ return parse_guard(ctx, l);
+ }
+
+ // Do-while loop: do { body } while condition;
+ if (strncmp(tk.start, "do", 2) == 0 && tk.len == 2)
+ {
+ lexer_next(l); // eat 'do'
+ ASTNode *body = parse_block(ctx, l);
+
+ // Expect 'while'
+ Token while_tok = lexer_peek(l);
+ if (while_tok.type != TOK_IDENT || strncmp(while_tok.start, "while", 5) != 0 ||
+ while_tok.len != 5)
+ {
+ zpanic_at(while_tok, "Expected 'while' after do block");
+ }
+ lexer_next(l); // eat 'while'
+
+ ASTNode *cond = parse_expression(ctx, l);
+ if (lexer_peek(l).type == TOK_SEMICOLON)
+ {
+ lexer_next(l);
+ }
+
+ ASTNode *n = ast_create(NODE_DO_WHILE);
+ n->do_while_stmt.body = body;
+ n->do_while_stmt.condition = cond;
+ n->do_while_stmt.loop_label = NULL;
+ return n;
+ }
+
+ if (strncmp(tk.start, "defer", 5) == 0 && tk.len == 5)
+ {
+ return parse_defer(ctx, l);
+ }
+
+ // Goto statement: goto label_name; OR goto *expr; (computed goto)
+ if (strncmp(tk.start, "goto", 4) == 0 && tk.len == 4)
+ {
+ Token goto_tok = lexer_next(l); // eat 'goto'
+ Token next = lexer_peek(l);
+
+ // Computed goto: goto *ptr;
+ if (next.type == TOK_OP && next.start[0] == '*')
+ {
+ lexer_next(l); // eat '*'
+ ASTNode *target = parse_expression(ctx, l);
+ if (lexer_peek(l).type == TOK_SEMICOLON)
+ {
+ lexer_next(l);
+ }
+
+ ASTNode *n = ast_create(NODE_GOTO);
+ n->goto_stmt.label_name = NULL;
+ n->goto_stmt.goto_expr = target;
+ n->token = goto_tok;
+ return n;
+ }
+
+ // Regular goto
+ Token label = lexer_next(l);
+ if (label.type != TOK_IDENT)
+ {
+ zpanic_at(label, "Expected label name after goto");
+ }
+ if (lexer_peek(l).type == TOK_SEMICOLON)
+ {
+ lexer_next(l);
+ }
+ ASTNode *n = ast_create(NODE_GOTO);
+ n->goto_stmt.label_name = token_strdup(label);
+ n->token = goto_tok;
+ zen_trigger_at(TRIGGER_GOTO, goto_tok);
+ return n;
+ }
+
+ // Label detection: identifier followed by : (but not ::)
+ {
+ Lexer lookahead = *l;
+ Token ident = lexer_next(&lookahead);
+ Token maybe_colon = lexer_peek(&lookahead);
+ if (maybe_colon.type == TOK_COLON)
+ {
+ // Check it's not :: (double colon for namespaces)
+ lexer_next(&lookahead);
+ Token after_colon = lexer_peek(&lookahead);
+ if (after_colon.type != TOK_COLON)
+ {
+ // This is a label!
+ lexer_next(l); // eat identifier
+ lexer_next(l); // eat :
+ ASTNode *n = ast_create(NODE_LABEL);
+ n->label_stmt.label_name = token_strdup(ident);
+ n->token = ident;
+ return n;
+ }
+ }
+ }
+
+ if ((strncmp(tk.start, "print", 5) == 0 && tk.len == 5) ||
+ (strncmp(tk.start, "println", 7) == 0 && tk.len == 7) ||
+ (strncmp(tk.start, "eprint", 6) == 0 && tk.len == 6) ||
+ (strncmp(tk.start, "eprintln", 8) == 0 && tk.len == 8))
+ {
+
+ // Revert: User requested print without newline
+ int is_ln = (tk.len == 7 || tk.len == 8);
+ // int is_ln = (tk.len == 7 || tk.len == 8);
+ int is_err = (tk.start[0] == 'e');
+ char *target = is_err ? "stderr" : "stdout";
+
+ lexer_next(l); // eat keyword
+
+ Token t = lexer_next(l);
+ if (t.type != TOK_STRING && t.type != TOK_FSTRING)
+ {
+ zpanic("Expected string literal after print/eprint");
+ }
+
+ char *inner = xmalloc(t.len);
+ if (t.type == TOK_FSTRING)
+ {
+ strncpy(inner, t.start + 2, t.len - 3);
+ inner[t.len - 3] = 0;
+ }
+ else
+ {
+ strncpy(inner, t.start + 1, t.len - 2);
+ inner[t.len - 2] = 0;
+ }
+
+ char *code = process_printf_sugar(ctx, inner, is_ln, target);
+ free(inner);
+
+ if (lexer_peek(l).type == TOK_SEMICOLON)
+ {
+ lexer_next(l);
+ }
+
+ ASTNode *n = ast_create(NODE_RAW_STMT);
+ n->raw_stmt.content = code;
+ return n;
+ }
+ }
+
+ // Default: Expression Statement
+ s = parse_expression(ctx, l);
+
+ int has_semi = 0;
+ if (lexer_peek(l).type == TOK_SEMICOLON)
+ {
+ lexer_next(l);
+ has_semi = 1;
+ }
+
+ // Auto-print in REPL: If no semicolon (implicit expr at block end)
+ // and not an assignment, print it.
+ if (ctx->is_repl && s && !has_semi)
+ {
+ int is_assign = 0;
+ if (s->type == NODE_EXPR_BINARY)
+ {
+ char *op = s->binary.op;
+ if (strcmp(op, "=") == 0 ||
+ (strlen(op) > 1 && op[strlen(op) - 1] == '=' && strcmp(op, "==") != 0 &&
+ strcmp(op, "!=") != 0 && strcmp(op, "<=") != 0 && strcmp(op, ">=") != 0))
+ {
+ is_assign = 1;
+ }
+ }
+
+ if (!is_assign)
+ {
+ ASTNode *print_node = ast_create(NODE_REPL_PRINT);
+ print_node->repl_print.expr = s;
+ // Preserve line info
+ print_node->line = s->line;
+ print_node->token = s->token;
+ return print_node;
+ }
+ }
+
+ if (s)
+ {
+ s->line = tk.line;
+ }
+
+ // Check for discarded must_use result
+ if (s && s->type == NODE_EXPR_CALL)
+ {
+ ASTNode *callee = s->call.callee;
+ if (callee && callee->type == NODE_EXPR_VAR)
+ {
+ FuncSig *sig = find_func(ctx, callee->var_ref.name);
+ if (sig && sig->must_use)
+ {
+ zwarn_at(tk, "Ignoring return value of function marked @must_use");
+ fprintf(stderr, COLOR_CYAN " = note: " COLOR_RESET
+ "Use the result or explicitly discard with `_ = ...`\n");
+ }
+ }
+ }
+
+ return s;
+}
+
+ASTNode *parse_block(ParserContext *ctx, Lexer *l)
+{
+ lexer_next(l); // eat '{'
+ enter_scope(ctx);
+ ASTNode *head = 0, *tail = 0;
+
+ int unreachable = 0;
+
+ while (1)
+ {
+ skip_comments(l);
+ Token tk = lexer_peek(l);
+ if (tk.type == TOK_RBRACE)
+ {
+ lexer_next(l);
+ break;
+ }
+ if (tk.type == TOK_EOF)
+ {
+ break;
+ }
+
+ if (unreachable == 1)
+ {
+ warn_unreachable_code(tk);
+ unreachable = 2; // Warned once, don't spam
+ }
+
+ ASTNode *s = parse_statement(ctx, l);
+ if (s)
+ {
+ if (!head)
+ {
+ head = s;
+ }
+ else
+ {
+ tail->next = s;
+ }
+ tail = s;
+ while (tail->next)
+ {
+ tail = tail->next; // Handle chains (e.g. var decl + defer)
+ }
+
+ // Check for control flow interruption
+ if (s->type == NODE_RETURN || s->type == NODE_BREAK || s->type == NODE_CONTINUE)
+ {
+ if (unreachable == 0)
+ {
+ unreachable = 1;
+ }
+ }
+ }
+ }
+
+ // Check for unused variables in this block scope
+ if (ctx->current_scope && !ctx->is_repl)
+ {
+ Symbol *sym = ctx->current_scope->symbols;
+ while (sym)
+ {
+ // Skip special names and already warned
+ if (!sym->is_used && sym->name[0] != '_' && strcmp(sym->name, "it") != 0 &&
+ strcmp(sym->name, "self") != 0)
+ {
+ // Skip autofree variables (used implicitly for cleanup)
+ if (sym->is_autofree)
+ {
+ sym = sym->next;
+ continue;
+ }
+
+ // RAII: Don't warn if type implements Drop (it is used implicitly)
+ int has_drop = (sym->type_info && sym->type_info->has_drop);
+ if (!has_drop && sym->type_info && sym->type_info->name)
+ {
+ ASTNode *def = find_struct_def(ctx, sym->type_info->name);
+ if (def && def->type_info && def->type_info->has_drop)
+ {
+ has_drop = 1;
+ }
+ }
+
+ if (!has_drop)
+ {
+ warn_unused_variable(sym->decl_token, sym->name);
+ }
+ }
+ sym = sym->next;
+ }
+ }
+
+ exit_scope(ctx);
+ ASTNode *b = ast_create(NODE_BLOCK);
+ b->block.statements = head;
+ return b;
+}
+
+// FIX: Robust struct field parsing
+// Trait Parsing
+ASTNode *parse_trait(ParserContext *ctx, Lexer *l)
+{
+ lexer_next(l); // eat trait
+ Token n = lexer_next(l);
+ if (n.type != TOK_IDENT)
+ {
+ zpanic("Expected trait name");
+ }
+ char *name = xmalloc(n.len + 1);
+ strncpy(name, n.start, n.len);
+ name[n.len] = 0;
+
+ lexer_next(l); // eat {
+
+ ASTNode *methods = NULL, *tail = NULL;
+ while (1)
+ {
+ skip_comments(l);
+ if (lexer_peek(l).type == TOK_RBRACE)
+ {
+ lexer_next(l);
+ break;
+ }
+
+ // Parse method signature: fn name(args...) -> ret;
+ // Re-use parse_function but stop at semicolon?
+ // Actually trait methods might have default impls later, but for now just signatures.
+ // Let's parse full function but body might be empty/null?
+ // Or simpler: just parse signature manually.
+
+ Token ft = lexer_next(l);
+ if (ft.type != TOK_IDENT || strncmp(ft.start, "fn", 2) != 0)
+ {
+ zpanic("Expected fn in trait");
+ }
+
+ Token mn = lexer_next(l);
+ char *mname = xmalloc(mn.len + 1);
+ strncpy(mname, mn.start, mn.len);
+ mname[mn.len] = 0;
+
+ char **defaults = NULL;
+ int arg_count = 0;
+ Type **arg_types = NULL;
+ char **param_names = NULL;
+ int is_varargs = 0;
+ char *args = parse_and_convert_args(ctx, l, &defaults, &arg_count, &arg_types, &param_names,
+ &is_varargs);
+
+ char *ret = xstrdup("void");
+ if (lexer_peek(l).type == TOK_ARROW)
+ {
+ lexer_next(l);
+ char *rt = parse_type(ctx, l);
+ free(ret);
+ ret = rt;
+ }
+
+ if (lexer_peek(l).type == TOK_SEMICOLON)
+ {
+ lexer_next(l);
+ ASTNode *m = ast_create(NODE_FUNCTION);
+ m->func.param_names = param_names;
+ m->func.name = mname;
+ m->func.args = args;
+ m->func.ret_type = ret;
+ m->func.body = NULL;
+ if (!methods)
+ {
+ methods = m;
+ }
+ else
+ {
+ tail->next = m;
+ }
+ tail = m;
+ }
+ else
+ {
+ // Default implementation? Not supported yet.
+ zpanic("Trait methods must end with ; for now");
+ }
+ }
+
+ ASTNode *n_node = ast_create(NODE_TRAIT);
+ n_node->trait.name = name;
+ n_node->trait.methods = methods;
+ register_trait(name);
+ return n_node;
+}
+
+ASTNode *parse_impl(ParserContext *ctx, Lexer *l)
+{
+
+ lexer_next(l); // eat impl
+ Token t1 = lexer_next(l);
+ char *name1 = token_strdup(t1);
+
+ char *gen_param = NULL;
+ // Check for <T> on the struct name
+ if (lexer_peek(l).type == TOK_LANGLE)
+ {
+ lexer_next(l); // eat <
+ Token gt = lexer_next(l);
+ gen_param = token_strdup(gt);
+ if (lexer_next(l).type != TOK_RANGLE)
+ {
+ zpanic("Expected >");
+ }
+ }
+
+ // Check for "for" (Trait impl)
+ Token pk = lexer_peek(l);
+ if (pk.type == TOK_FOR ||
+ (pk.type == TOK_IDENT && strncmp(pk.start, "for", 3) == 0 && pk.len == 3))
+ {
+ if (pk.type != TOK_FOR)
+ {
+ lexer_next(l);
+ }
+ else
+ {
+ lexer_next(l); // eat for
+ }
+ Token t2 = lexer_next(l);
+ char *name2 = token_strdup(t2);
+
+ register_impl(ctx, name1, name2);
+
+ // RAII: Check for "Drop" trait implementation
+ if (strcmp(name1, "Drop") == 0)
+ {
+ Symbol *s = find_symbol_entry(ctx, name2);
+ if (s && s->type_info)
+ {
+ s->type_info->has_drop = 1;
+ }
+ else
+ {
+ // Try finding struct definition
+ ASTNode *def = find_struct_def(ctx, name2);
+ if (def && def->type_info)
+ {
+ def->type_info->has_drop = 1;
+ }
+ }
+ }
+
+ ctx->current_impl_struct = name2; // Set context to prevent duplicate emission and prefixing
+
+ lexer_next(l); // eat {
+ ASTNode *h = 0, *tl = 0;
+ while (1)
+ {
+ skip_comments(l);
+ if (lexer_peek(l).type == TOK_RBRACE)
+ {
+ lexer_next(l);
+ break;
+ }
+ if (lexer_peek(l).type == TOK_IDENT && strncmp(lexer_peek(l).start, "fn", 2) == 0)
+ {
+ ASTNode *f = parse_function(ctx, l, 0);
+ // Mangle: Type_Trait_Method
+ char *mangled = xmalloc(strlen(name2) + strlen(name1) + strlen(f->func.name) + 4);
+ sprintf(mangled, "%s_%s_%s", name2, name1, f->func.name);
+ free(f->func.name);
+ f->func.name = mangled;
+ char *na = patch_self_args(f->func.args, name2);
+ free(f->func.args);
+ f->func.args = na;
+ if (!h)
+ {
+ h = f;
+ }
+ else
+ {
+ tl->next = f;
+ }
+ tl = f;
+ }
+ else if (lexer_peek(l).type == TOK_ASYNC)
+ {
+ lexer_next(l); // eat async
+ if (lexer_peek(l).type == TOK_IDENT && strncmp(lexer_peek(l).start, "fn", 2) == 0)
+ {
+ ASTNode *f = parse_function(ctx, l, 1);
+ f->func.is_async = 1;
+ // Mangle: Type_Trait_Method
+ char *mangled =
+ xmalloc(strlen(name2) + strlen(name1) + strlen(f->func.name) + 4);
+ sprintf(mangled, "%s_%s_%s", name2, name1, f->func.name);
+ free(f->func.name);
+ f->func.name = mangled;
+ char *na = patch_self_args(f->func.args, name2);
+ free(f->func.args);
+ f->func.args = na;
+ if (!h)
+ {
+ h = f;
+ }
+ else
+ {
+ tl->next = f;
+ }
+ tl = f;
+ }
+ else
+ {
+ zpanic("Expected 'fn' after 'async'");
+ }
+ }
+ else
+ {
+ lexer_next(l);
+ }
+ }
+ ctx->current_impl_struct = NULL; // Restore context
+ ASTNode *n = ast_create(NODE_IMPL_TRAIT);
+ n->impl_trait.trait_name = name1;
+ n->impl_trait.target_type = name2;
+ n->impl_trait.methods = h;
+ add_to_impl_list(ctx, n);
+ return n;
+ }
+ else
+ {
+ // Regular impl Struct (impl Box or impl Box<T>)
+
+ // Auto-prefix struct name if in module context
+ if (ctx->current_module_prefix && !gen_param)
+ {
+ char *prefixed_name = xmalloc(strlen(ctx->current_module_prefix) + strlen(name1) + 2);
+ sprintf(prefixed_name, "%s_%s", ctx->current_module_prefix, name1);
+ free(name1);
+ name1 = prefixed_name;
+ }
+
+ ctx->current_impl_struct = name1; // For patch_self_args inside parse_function
+
+ if (gen_param)
+ {
+ // GENERIC IMPL TEMPLATE: impl Box<T>
+ if (lexer_next(l).type != TOK_LBRACE)
+ {
+ zpanic("Expected {");
+ }
+ ASTNode *h = 0, *tl = 0;
+ while (1)
+ {
+ skip_comments(l);
+ if (lexer_peek(l).type == TOK_RBRACE)
+ {
+ lexer_next(l);
+ break;
+ }
+ if (lexer_peek(l).type == TOK_IDENT && strncmp(lexer_peek(l).start, "fn", 2) == 0)
+ {
+ ASTNode *f = parse_function(ctx, l, 0);
+ // Standard Mangle for template: Box_method
+ char *mangled = xmalloc(strlen(name1) + strlen(f->func.name) + 2);
+ sprintf(mangled, "%s_%s", name1, f->func.name);
+ free(f->func.name);
+ f->func.name = mangled;
+
+ char *na = patch_self_args(f->func.args, name1);
+ free(f->func.args);
+ f->func.args = na;
+
+ if (!h)
+ {
+ h = f;
+ }
+ else
+ {
+ tl->next = f;
+ }
+ tl = f;
+ }
+ else if (lexer_peek(l).type == TOK_ASYNC)
+ {
+ lexer_next(l); // eat async
+ if (lexer_peek(l).type == TOK_IDENT &&
+ strncmp(lexer_peek(l).start, "fn", 2) == 0)
+ {
+ ASTNode *f = parse_function(ctx, l, 1);
+ f->func.is_async = 1;
+ char *mangled = xmalloc(strlen(name1) + strlen(f->func.name) + 2);
+ sprintf(mangled, "%s_%s", name1, f->func.name);
+ free(f->func.name);
+ f->func.name = mangled;
+ char *na = patch_self_args(f->func.args, name1);
+ free(f->func.args);
+ f->func.args = na;
+ if (!h)
+ {
+ h = f;
+ }
+ else
+ {
+ tl->next = f;
+ }
+ tl = f;
+ }
+ else
+ {
+ zpanic("Expected 'fn' after 'async'");
+ }
+ }
+ else
+ {
+ lexer_next(l);
+ }
+ }
+ // Register Template
+ ASTNode *n = ast_create(NODE_IMPL);
+ n->impl.struct_name = name1;
+ n->impl.methods = h;
+ register_impl_template(ctx, name1, gen_param, n);
+ ctx->current_impl_struct = NULL;
+ return NULL; // Do not emit generic template
+ }
+ else
+ {
+ // REGULAR IMPL
+ lexer_next(l); // eat {
+ ASTNode *h = 0, *tl = 0;
+ while (1)
+ {
+ skip_comments(l);
+ if (lexer_peek(l).type == TOK_RBRACE)
+ {
+ lexer_next(l);
+ break;
+ }
+ if (lexer_peek(l).type == TOK_IDENT && strncmp(lexer_peek(l).start, "fn", 2) == 0)
+ {
+ ASTNode *f = parse_function(ctx, l, 0);
+
+ // Standard Mangle: Struct_method
+ char *mangled = xmalloc(strlen(name1) + strlen(f->func.name) + 2);
+ sprintf(mangled, "%s_%s", name1, f->func.name);
+ free(f->func.name);
+ f->func.name = mangled;
+
+ char *na = patch_self_args(f->func.args, name1);
+ free(f->func.args);
+ f->func.args = na;
+
+ // FIX: Register the MANGLED name so calls like String_from(...) find the return
+ // type
+ register_func(ctx, mangled, f->func.arg_count, f->func.defaults,
+ f->func.arg_types, f->func.ret_type_info, f->func.is_varargs, 0,
+ f->token);
+
+ if (!h)
+ {
+ h = f;
+ }
+ else
+ {
+ tl->next = f;
+ }
+ tl = f;
+ }
+ else if (lexer_peek(l).type == TOK_ASYNC)
+ {
+ lexer_next(l);
+ if (lexer_peek(l).type == TOK_IDENT &&
+ strncmp(lexer_peek(l).start, "fn", 2) == 0)
+ {
+ ASTNode *f = parse_function(ctx, l, 1);
+ f->func.is_async = 1;
+ char *mangled = xmalloc(strlen(name1) + strlen(f->func.name) + 2);
+ sprintf(mangled, "%s_%s", name1, f->func.name);
+ free(f->func.name);
+ f->func.name = mangled;
+ char *na = patch_self_args(f->func.args, name1);
+ free(f->func.args);
+ f->func.args = na;
+ register_func(ctx, mangled, f->func.arg_count, f->func.defaults,
+ f->func.arg_types, f->func.ret_type_info, f->func.is_varargs,
+ 1, f->token);
+ if (!h)
+ {
+ h = f;
+ }
+ else
+ {
+ tl->next = f;
+ }
+ tl = f;
+ }
+ else
+ {
+ zpanic("Expected 'fn' after 'async'");
+ }
+ }
+ else
+ {
+ lexer_next(l);
+ }
+ }
+ ctx->current_impl_struct = NULL;
+ ASTNode *n = ast_create(NODE_IMPL);
+ n->impl.struct_name = name1;
+ n->impl.methods = h;
+ add_to_impl_list(ctx, n);
+ return n;
+ }
+ }
+}
+
+ASTNode *parse_struct(ParserContext *ctx, Lexer *l, int is_union)
+{
+
+ lexer_next(l); // eat struct or union
+ Token n = lexer_next(l);
+ char *name = token_strdup(n);
+
+ // Generic Param <T>
+ char *gp = NULL;
+ if (lexer_peek(l).type == TOK_LANGLE)
+ {
+ lexer_next(l);
+ Token g = lexer_next(l);
+ gp = token_strdup(g);
+ lexer_next(l);
+ register_generic(ctx, name);
+ }
+
+ lexer_next(l); // eat {
+ ASTNode *h = 0, *tl = 0;
+
+ while (1)
+ {
+ skip_comments(l);
+ Token t = lexer_peek(l);
+ if (t.type == TOK_RBRACE)
+ {
+ lexer_next(l);
+ break;
+ }
+ if (t.type == TOK_SEMICOLON || t.type == TOK_COMMA)
+ {
+ lexer_next(l);
+ continue;
+ }
+
+ // --- HANDLE 'use' (Struct Embedding) ---
+ if (t.type == TOK_USE)
+ {
+ lexer_next(l); // eat use
+ // Parse the type (e.g. Header<I32>)
+ Type *use_type = parse_type_formal(ctx, l);
+ char *use_name = type_to_string(use_type);
+
+ expect(l, TOK_SEMICOLON, "Expected ; after use");
+
+ // Find the definition and COPY fields
+ ASTNode *def = find_struct_def(ctx, use_name);
+ if (!def && is_known_generic(ctx, use_type->name))
+ {
+ // Try to force instantiation if not found?
+ // For now, rely on parse_type having triggered instantiation.
+ char *mangled =
+ type_to_string(use_type); // This works if type_to_string returns mangled name
+ def = find_struct_def(ctx, mangled);
+ free(mangled);
+ }
+
+ if (def && def->type == NODE_STRUCT)
+ {
+ ASTNode *f = def->strct.fields;
+ while (f)
+ {
+ ASTNode *nf = ast_create(NODE_FIELD);
+ nf->field.name = xstrdup(f->field.name);
+ nf->field.type = xstrdup(f->field.type);
+ if (!h)
+ {
+ h = nf;
+ }
+ else
+ {
+ tl->next = nf;
+ }
+ tl = nf;
+ f = f->next;
+ }
+ }
+ else
+ {
+ // If definition not found (e.g. user struct defined later), we can't embed fields
+ // yet. Compiler limitation: 'use' requires struct to be defined before. Fallback:
+ // Emit a placeholder field so compilation doesn't crash, but layout will be wrong.
+ // printf("Warning: Could not find struct '%s' for embedding.\n", use_name);
+ }
+ free(use_name);
+ continue;
+ }
+ // ---------------------------------------
+
+ if (t.type == TOK_IDENT)
+ {
+ Token f_name = lexer_next(l);
+ expect(l, TOK_COLON, "Expected :");
+ char *f_type = parse_type(ctx, l);
+
+ ASTNode *f = ast_create(NODE_FIELD);
+ f->field.name = token_strdup(f_name);
+ f->field.type = f_type;
+ f->field.bit_width = 0;
+
+ // Optional bit width: name: type : 3
+ if (lexer_peek(l).type == TOK_COLON)
+ {
+ lexer_next(l); // eat :
+ Token width_tok = lexer_next(l);
+ if (width_tok.type != TOK_INT)
+ {
+ zpanic("Expected bit width integer");
+ }
+ f->field.bit_width = atoi(token_strdup(width_tok));
+ }
+
+ if (!h)
+ {
+ h = f;
+ }
+ else
+ {
+ tl->next = f;
+ }
+ tl = f;
+
+ if (lexer_peek(l).type == TOK_SEMICOLON || lexer_peek(l).type == TOK_COMMA)
+ {
+ lexer_next(l);
+ }
+ }
+ else
+ {
+ lexer_next(l);
+ }
+ }
+
+ ASTNode *node = ast_create(NODE_STRUCT);
+ add_to_struct_list(ctx, node);
+
+ // Auto-prefix struct name if in module context
+ if (ctx->current_module_prefix && !gp)
+ { // Don't prefix generic templates
+ char *prefixed_name = xmalloc(strlen(ctx->current_module_prefix) + strlen(name) + 2);
+ sprintf(prefixed_name, "%s_%s", ctx->current_module_prefix, name);
+ free(name);
+ name = prefixed_name;
+ }
+
+ node->strct.name = name;
+
+ // Initialize Type Info so we can track traits (like Drop)
+ node->type_info = type_new(TYPE_STRUCT);
+ node->type_info->name = xstrdup(name);
+ if (gp)
+ {
+ node->type_info->kind = TYPE_GENERIC;
+ // TODO: track generic params
+ }
+
+ node->strct.fields = h;
+ node->strct.generic_param = gp;
+ node->strct.is_union = is_union;
+
+ if (gp)
+ {
+ node->strct.is_template = 1;
+ register_template(ctx, name, node);
+ }
+
+ // Register definition for 'use' lookups and LSP
+ if (!gp)
+ {
+ register_struct_def(ctx, name, node);
+ }
+
+ return node;
+}
+
+Type *parse_type_obj(ParserContext *ctx, Lexer *l)
+{
+ // 1. Parse the base type (int, U32, MyStruct, etc.)
+ Type *t = parse_type_base(ctx, l);
+
+ // 2. Handle Pointers (e.g. int***)
+ while (lexer_peek(l).type == TOK_OP && lexer_peek(l).start[0] == '*')
+ {
+ lexer_next(l); // eat *
+ // Wrap the current type in a Pointer type
+ Type *ptr = type_new(TYPE_POINTER);
+ ptr->inner = t;
+ t = ptr;
+ }
+
+ // (Optional: You can add array parsing here later if needed)
+
+ return t;
+}
+
+ASTNode *parse_enum(ParserContext *ctx, Lexer *l)
+{
+ lexer_next(l);
+ Token n = lexer_next(l);
+
+ // 1. Check for Generic <T>
+ char *gp = NULL;
+ if (lexer_peek(l).type == TOK_LANGLE)
+ {
+ lexer_next(l); // eat <
+ Token g = lexer_next(l);
+ gp = token_strdup(g);
+ lexer_next(l); // eat >
+ register_generic(ctx, n.start ? token_strdup(n) : "anon");
+ }
+
+ lexer_next(l); // eat {
+
+ ASTNode *h = 0, *tl = 0;
+ int v = 0;
+ char *ename = token_strdup(n); // Store enum name
+
+ while (1)
+ {
+ skip_comments(l);
+ Token t = lexer_peek(l);
+ if (t.type == TOK_RBRACE)
+ {
+ lexer_next(l);
+ break;
+ }
+ if (t.type == TOK_COMMA)
+ {
+ lexer_next(l);
+ continue;
+ }
+
+ if (t.type == TOK_IDENT)
+ {
+ Token vt = lexer_next(l);
+ char *vname = token_strdup(vt);
+
+ // 2. Parse Payload Type (Ok(int))
+ Type *payload = NULL;
+ if (lexer_peek(l).type == TOK_LPAREN)
+ {
+ lexer_next(l);
+ payload = parse_type_obj(ctx, l);
+ if (lexer_next(l).type != TOK_RPAREN)
+ {
+ zpanic("Expected )");
+ }
+ }
+
+ ASTNode *va = ast_create(NODE_ENUM_VARIANT);
+ va->variant.name = vname;
+ va->variant.tag_id = v++; // Use tag_id instead of value
+ va->variant.payload = payload; // Store Type*
+
+ // Register Variant (Mangled name to avoid collisions: Result_Ok)
+ char mangled[256];
+ sprintf(mangled, "%s_%s", ename, vname);
+ register_enum_variant(ctx, ename, mangled, va->variant.tag_id);
+
+ // Handle explicit assignment: Ok = 5
+ if (lexer_peek(l).type == TOK_OP && *lexer_peek(l).start == '=')
+ {
+ lexer_next(l);
+ va->variant.tag_id = atoi(lexer_next(l).start);
+ v = va->variant.tag_id + 1;
+ }
+
+ if (!h)
+ {
+ h = va;
+ }
+ else
+ {
+ tl->next = va;
+ }
+ tl = va;
+ }
+ else
+ {
+ lexer_next(l);
+ }
+ }
+
+ // Auto-prefix enum name if in module context
+ if (ctx->current_module_prefix && !gp)
+ { // Don't prefix generic templates
+ char *prefixed_name = xmalloc(strlen(ctx->current_module_prefix) + strlen(ename) + 2);
+ sprintf(prefixed_name, "%s_%s", ctx->current_module_prefix, ename);
+ free(ename);
+ ename = prefixed_name;
+ }
+
+ ASTNode *node = ast_create(NODE_ENUM);
+ node->enm.name = ename;
+
+ node->enm.variants = h;
+ node->enm.generic_param = gp; // 3. Store generic param
+
+ if (gp)
+ {
+ node->enm.is_template = 1;
+ register_template(ctx, node->enm.name, node);
+ }
+
+ add_to_enum_list(ctx, node); // Register globally
+
+ return node;
+}
+ASTNode *parse_include(ParserContext *ctx, Lexer *l)
+{
+ lexer_next(l); // eat 'include'
+ Token t = lexer_next(l);
+ char *path = NULL;
+ int is_system = 0;
+
+ if (t.type == TOK_LANGLE)
+ {
+ // System include: include <raylib.h>
+ is_system = 1;
+ char buf[256];
+ buf[0] = 0;
+ while (1)
+ {
+ Token i = lexer_next(l);
+ if (i.type == TOK_RANGLE)
+ {
+ break;
+ }
+ strncat(buf, i.start, i.len);
+ }
+ path = xstrdup(buf);
+
+ // Mark that this file has external includes (suppress undefined warnings)
+ ctx->has_external_includes = 1;
+ }
+ else
+ {
+ // Local include: include "file.h"
+ is_system = 0;
+ int len = t.len - 2;
+ path = xmalloc(len + 1);
+ strncpy(path, t.start + 1, len);
+ path[len] = 0;
+ }
+
+ ASTNode *n = ast_create(NODE_INCLUDE);
+ n->include.path = path;
+ n->include.is_system = is_system;
+ return n;
+}
+ASTNode *parse_import(ParserContext *ctx, Lexer *l)
+{
+ lexer_next(l); // eat 'import'
+
+ // Check for 'plugin' keyword
+ Token next = lexer_peek(l);
+ if (next.type == TOK_IDENT && next.len == 6 && strncmp(next.start, "plugin", 6) == 0)
+ {
+ lexer_next(l); // consume "plugin"
+
+ // Expect string literal with plugin name
+ Token plugin_tok = lexer_next(l);
+ if (plugin_tok.type != TOK_STRING)
+ {
+ zpanic("Expected string literal after 'import plugin'");
+ }
+
+ // Extract plugin name (strip quotes)
+ int name_len = plugin_tok.len - 2;
+ char *plugin_name = xmalloc(name_len + 1);
+ strncpy(plugin_name, plugin_tok.start + 1, name_len);
+ plugin_name[name_len] = '\0';
+
+ // Check for optional "as alias"
+ char *alias = NULL;
+ Token as_tok = lexer_peek(l);
+ if (as_tok.type == TOK_IDENT && as_tok.len == 2 && strncmp(as_tok.start, "as", 2) == 0)
+ {
+ lexer_next(l); // consume "as"
+ Token alias_tok = lexer_next(l);
+ if (alias_tok.type != TOK_IDENT)
+ {
+ zpanic("Expected identifier after 'as'");
+ }
+ alias = token_strdup(alias_tok);
+ }
+
+ // Register the plugin
+ register_plugin(ctx, plugin_name, alias);
+
+ // Consume optional semicolon
+ if (lexer_peek(l).type == TOK_SEMICOLON)
+ {
+ lexer_next(l);
+ }
+
+ // Return NULL - no AST node needed for imports
+ return NULL;
+ }
+
+ // Regular module import handling follows...
+ // Check if this is selective import: import { ... } from "file"
+ int is_selective = 0;
+ char *symbols[32]; // Max 32 selective imports
+ char *aliases[32];
+ int symbol_count = 0;
+
+ if (lexer_peek(l).type == TOK_LBRACE)
+ {
+ is_selective = 1;
+ lexer_next(l); // eat {
+
+ // Parse symbol list
+ while (lexer_peek(l).type != TOK_RBRACE)
+ {
+ if (symbol_count > 0 && lexer_peek(l).type == TOK_COMMA)
+ {
+ lexer_next(l); // eat comma
+ }
+
+ Token sym_tok = lexer_next(l);
+ if (sym_tok.type != TOK_IDENT)
+ {
+ zpanic("Expected identifier in selective import");
+ }
+
+ symbols[symbol_count] = xmalloc(sym_tok.len + 1);
+ strncpy(symbols[symbol_count], sym_tok.start, sym_tok.len);
+ symbols[symbol_count][sym_tok.len] = 0;
+
+ // Check for 'as alias'
+ Token next = lexer_peek(l);
+ if (next.type == TOK_IDENT && next.len == 2 && strncmp(next.start, "as", 2) == 0)
+ {
+ lexer_next(l); // eat 'as'
+ Token alias_tok = lexer_next(l);
+ if (alias_tok.type != TOK_IDENT)
+ {
+ zpanic("Expected identifier after 'as'");
+ }
+
+ aliases[symbol_count] = xmalloc(alias_tok.len + 1);
+ strncpy(aliases[symbol_count], alias_tok.start, alias_tok.len);
+ aliases[symbol_count][alias_tok.len] = 0;
+ }
+ else
+ {
+ aliases[symbol_count] = NULL; // No alias
+ }
+
+ symbol_count++;
+ }
+
+ lexer_next(l); // eat }
+
+ // Expect 'from'
+ Token from_tok = lexer_next(l);
+ if (from_tok.type != TOK_IDENT || from_tok.len != 4 ||
+ strncmp(from_tok.start, "from", 4) != 0)
+ {
+ zpanic("Expected 'from' after selective import list, got type=%d", from_tok.type);
+ }
+ }
+
+ // Parse filename
+ Token t = lexer_next(l);
+ if (t.type != TOK_STRING)
+ {
+ zpanic("Expected string (filename) after 'from' in selective import, got type %d", t.type);
+ }
+ int ln = t.len - 2; // Remove quotes
+ char *fn = xmalloc(ln + 1);
+ strncpy(fn, t.start + 1, ln);
+ fn[ln] = 0;
+
+ // Resolve relative paths (if starts with ./ or ../
+ char resolved_path[1024];
+ if (fn[0] == '.' && (fn[1] == '/' || (fn[1] == '.' && fn[2] == '/')))
+ {
+ // Relative import - resolve relative to current file
+ char *current_dir = xstrdup(g_current_filename);
+ char *last_slash = strrchr(current_dir, '/');
+ if (last_slash)
+ {
+ *last_slash = 0; // Truncate to directory
+ const char *leaf = fn;
+ if (leaf[0] == '.' && leaf[1] == '/')
+ {
+ leaf += 2;
+ }
+ snprintf(resolved_path, sizeof(resolved_path), "%s/%s", current_dir, leaf);
+ }
+ else
+ {
+ snprintf(resolved_path, sizeof(resolved_path), "%s", fn);
+ }
+ free(current_dir);
+ free(fn);
+ fn = xstrdup(resolved_path);
+ }
+
+ // Check if file exists, if not try system-wide paths
+ if (access(fn, R_OK) != 0)
+ {
+ // Try system-wide standard library location
+ static const char *system_paths[] = {"/usr/local/share/zenc", "/usr/share/zenc", NULL};
+
+ char system_path[1024];
+ int found = 0;
+
+ for (int i = 0; system_paths[i] && !found; i++)
+ {
+ snprintf(system_path, sizeof(system_path), "%s/%s", system_paths[i], fn);
+ if (access(system_path, R_OK) == 0)
+ {
+ free(fn);
+ fn = xstrdup(system_path);
+ found = 1;
+ }
+ }
+
+ if (!found)
+ {
+ // File not found anywhere - will error later when trying to open
+ }
+ }
+
+ // Check if file already imported
+ if (is_file_imported(ctx, fn))
+ {
+ free(fn);
+ return NULL;
+ }
+ mark_file_imported(ctx, fn);
+
+ // For selective imports, register them BEFORE parsing the file
+ char *module_base_name = NULL;
+ if (is_selective)
+ {
+ module_base_name = extract_module_name(fn);
+ for (int i = 0; i < symbol_count; i++)
+ {
+ register_selective_import(ctx, symbols[i], aliases[i], module_base_name);
+ }
+ }
+
+ // Check for 'as alias' syntax (for namespaced imports)
+ char *alias = NULL;
+ if (!is_selective)
+ {
+ Token next_tok = lexer_peek(l);
+ if (next_tok.type == TOK_IDENT && next_tok.len == 2 &&
+ strncmp(next_tok.start, "as", 2) == 0)
+ {
+ lexer_next(l); // eat 'as'
+ Token alias_tok = lexer_next(l);
+ if (alias_tok.type != TOK_IDENT)
+ {
+ zpanic("Expected identifier after 'as'");
+ }
+
+ alias = xmalloc(alias_tok.len + 1);
+ strncpy(alias, alias_tok.start, alias_tok.len);
+ alias[alias_tok.len] = 0;
+
+ // Register the module
+
+ // Check if C header
+ int is_header = 0;
+ if (strlen(fn) > 2 && strcmp(fn + strlen(fn) - 2, ".h") == 0)
+ {
+ is_header = 1;
+ }
+
+ // Register the module
+ Module *m = xmalloc(sizeof(Module));
+ m->alias = xstrdup(alias);
+ m->path = xstrdup(fn);
+ m->base_name = extract_module_name(fn);
+ m->is_c_header = is_header;
+ m->next = ctx->modules;
+ ctx->modules = m;
+ }
+ }
+
+ // C Header: Emit include and return (don't parse)
+ if (strlen(fn) > 2 && strcmp(fn + strlen(fn) - 2, ".h") == 0)
+ {
+ // We can iterate over registered modules to check if we missed setting is_c_header?
+ // But we handled 'as' above. If no 'as', we need to check if we should register it?
+ // Usually 'import "foo.h" as f' is required for namespacing.
+
+ // Emit #include
+ // TODO: Where to emit? parser doesn't emit code usually.
+ // Actually, we can just return a NODE_INCLUDE AST!
+ // But wait, the user wants 'import as alias'.
+
+ // If we return NULL, nothing happens.
+ // Use NODE_INCLUDE to ensure it gets emitted.
+
+ ASTNode *n = ast_create(NODE_INCLUDE);
+ n->include.path = xstrdup(fn); // Store exact path
+ n->include.is_system = 0; // Double quotes
+ return n;
+ }
+
+ // Load and parse the file
+ char *src = load_file(fn);
+ if (!src)
+ {
+ zpanic("Not found: %s", fn);
+ }
+
+ Lexer i;
+ lexer_init(&i, src);
+
+ // If this is a namespaced import or selective import, set the module prefix
+ char *prev_module_prefix = ctx->current_module_prefix;
+ char *temp_module_prefix = NULL;
+
+ if (alias)
+ { // For 'import "file" as alias'
+ temp_module_prefix = extract_module_name(fn);
+ ctx->current_module_prefix = temp_module_prefix;
+ }
+ else if (is_selective)
+ { // For 'import {sym} from "file"'
+ temp_module_prefix = extract_module_name(fn);
+ ctx->current_module_prefix = temp_module_prefix;
+ }
+
+ // Update global filename context for relative imports inside the new file
+ const char *saved_fn = g_current_filename;
+ g_current_filename = fn;
+
+ ASTNode *r = parse_program_nodes(ctx, &i);
+
+ // Restore filename context
+ g_current_filename = (char *)saved_fn;
+
+ // Restore previous module context
+ if (temp_module_prefix)
+ {
+ free(temp_module_prefix);
+ ctx->current_module_prefix = prev_module_prefix;
+ }
+
+ // Free selective import symbols and aliases
+ if (is_selective)
+ {
+ for (int k = 0; k < symbol_count; k++)
+ {
+ free(symbols[k]);
+ if (aliases[k])
+ {
+ free(aliases[k]);
+ }
+ }
+ }
+
+ if (alias)
+ {
+ free(alias);
+ }
+
+ if (module_base_name)
+ { // This was only used for selective import registration, not for ctx->current_module_prefix
+ free(module_base_name);
+ }
+
+ free(fn);
+ return r;
+}
+
+ASTNode *parse_comptime(ParserContext *ctx, Lexer *l)
+{
+ expect(l, TOK_COMPTIME, "comptime");
+ expect(l, TOK_LBRACE, "expected { after comptime");
+
+ // 1. Extract Raw Code
+ const char *start = l->src + l->pos;
+ int depth = 1;
+ while (depth > 0)
+ {
+ Token t = lexer_next(l);
+ if (t.type == TOK_EOF)
+ {
+ zpanic("Unexpected EOF in comptime block");
+ }
+ if (t.type == TOK_LBRACE)
+ {
+ depth++;
+ }
+ if (t.type == TOK_RBRACE)
+ {
+ depth--;
+ }
+ }
+ // End is passed the closing brace, so pos points after it.
+ // The code block is between start and (current pos - 1)
+ int len = (l->src + l->pos - 1) - start;
+ char *code = xmalloc(len + 1);
+ strncpy(code, start, len);
+ code[len] = 0;
+
+ // 2. Wrap and Write
+ char filename[64];
+ sprintf(filename, "_tmp_comptime_%d.c", rand());
+ FILE *f = fopen(filename, "w");
+ if (!f)
+ {
+ zpanic("Could not create temp file %s", filename);
+ }
+
+ // Stdout capture wrapper
+ // We assume the user writes C code that fits in main(), or includes headers.
+ // For simplicity V1: We provide standard headers and wrap content in main if it doesn't look
+ // like definitions? Actually failure mode: User defines functions. Better: User provides body
+ // of main() or definitions. Heuristic: If we wrap in main, we can't define functions inside
+ // main (standard C). Proposal: User code runs AS IS. User must provide main(). Wait, user
+ // example: printf("..."); If I just paste printf("..."), it needs a main. Let's wrap in main()
+ // by default.
+ fprintf(f, "#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n");
+ fprintf(f, "int main() {\n%s\nreturn 0;\n}\n", code);
+ fclose(f);
+
+ // 3. Compile
+ char cmd[4096];
+ char bin[1024];
+ sprintf(bin, "%s.bin", filename);
+ // Suppress GCC output
+ sprintf(cmd, "gcc %s -o %s > /dev/null 2>&1", filename, bin);
+ int res = system(cmd);
+ if (res != 0)
+ {
+ // Retry without wrapper (maybe user provided main)
+ f = fopen(filename, "w");
+ fprintf(f, "#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n");
+ fprintf(f, "%s\n", code);
+ fclose(f);
+ res = system(cmd);
+ if (res != 0)
+ {
+ zpanic("Comptime compilation failed for:\n%s", code);
+ }
+ }
+
+ // 4. Run and Capture
+ char out_file[1024];
+ sprintf(out_file, "%s.out", filename);
+ sprintf(cmd, "./%s > %s", bin, out_file);
+ if (system(cmd) != 0)
+ {
+ zpanic("Comptime execution failed");
+ }
+
+ // 5. Read Output
+ char *output_src = load_file(out_file);
+ if (!output_src)
+ {
+ output_src = xstrdup(""); // Empty output is valid
+ }
+
+ // Cleanup
+ remove(filename);
+ remove(bin);
+ remove(out_file);
+ free(code); // bin and out_file are strings on stack
+
+ // 6. Parse Output (Recursively)
+ Lexer new_l;
+ lexer_init(&new_l, output_src);
+ // Note: Recursive call. We leak output_src intentionally so AST tokens remain valid.
+ // In a long running process we would manage this in an Arena.
+ return parse_program_nodes(ctx, &new_l);
+}
+
+// Parse plugin block: plugin name ... end
+ASTNode *parse_plugin(ParserContext *ctx, Lexer *l)
+{
+ (void)ctx;
+
+ // Expect 'plugin' keyword (already consumed by caller)
+ // Next should be plugin name
+ Token tk = lexer_next(l);
+ if (tk.type != TOK_IDENT)
+ {
+ zpanic("Expected plugin name after 'plugin' keyword");
+ }
+
+ // Extract plugin name
+ char *plugin_name = xmalloc(tk.len + 1);
+ strncpy(plugin_name, tk.start, tk.len);
+ plugin_name[tk.len] = '\0';
+
+ // Collect everything until 'end'
+ char *body = xmalloc(8192);
+ body[0] = '\0';
+ int body_len = 0;
+
+ while (1)
+ {
+ Token t = lexer_peek(l);
+ if (t.type == TOK_EOF)
+ {
+ zpanic("Unexpected EOF in plugin block, expected 'end'");
+ }
+
+ // Check for 'end'
+ if (t.type == TOK_IDENT && t.len == 3 && strncmp(t.start, "end", 3) == 0)
+ {
+ lexer_next(l); // consume 'end'
+ break;
+ }
+
+ // Append token to body
+ if (body_len + t.len + 2 < 8192)
+ {
+ strncat(body, t.start, t.len);
+ body[body_len + t.len] = ' ';
+ body[body_len + t.len + 1] = '\0';
+ body_len += t.len + 1;
+ }
+
+ lexer_next(l);
+ }
+
+ // Create plugin node
+ ASTNode *n = ast_create(NODE_PLUGIN);
+ n->plugin_stmt.plugin_name = plugin_name;
+ n->plugin_stmt.body = body;
+
+ if (lexer_peek(l).type == TOK_SEMICOLON)
+ {
+ lexer_next(l);
+ }
+ return n;
+}
diff --git a/src/parser/parser_type.c b/src/parser/parser_type.c
new file mode 100644
index 0000000..a5f8d86
--- /dev/null
+++ b/src/parser/parser_type.c
@@ -0,0 +1,706 @@
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include "parser.h"
+#include "../ast/ast.h"
+
+Type *parse_type_base(ParserContext *ctx, Lexer *l)
+{
+ Token t = lexer_peek(l);
+
+ if (t.type == TOK_IDENT)
+ {
+ // Handle "struct Name" or "enum Name"
+ if ((t.len == 6 && strncmp(t.start, "struct", 6) == 0) ||
+ (t.len == 4 && strncmp(t.start, "enum", 4) == 0))
+ {
+ lexer_next(l); // consume keyword
+ t = lexer_peek(l);
+ if (t.type != TOK_IDENT)
+ {
+ zpanic_at(t, "Expected identifier after struct/enum");
+ }
+ }
+
+ lexer_next(l);
+ char *name = token_strdup(t);
+
+ // Self type alias: Replace "Self" with current impl struct type
+ if (strcmp(name, "Self") == 0 && ctx->current_impl_struct)
+ {
+ name = xstrdup(ctx->current_impl_struct);
+ }
+
+ // Handle Namespace :: (A::B -> A_B)
+ while (lexer_peek(l).type == TOK_DCOLON)
+ {
+ lexer_next(l); // eat ::
+ Token next = lexer_next(l);
+ if (next.type != TOK_IDENT)
+ {
+ zpanic_at(t, "Expected identifier after ::");
+ }
+
+ char *suffix = token_strdup(next);
+ char *resolved_suffix = suffix;
+
+ // Map Zen Primitive suffixes to C types to match Generic Instantiation
+ if (strcmp(suffix, "I32") == 0)
+ {
+ resolved_suffix = "int32_t";
+ }
+ else if (strcmp(suffix, "U32") == 0)
+ {
+ resolved_suffix = "uint32_t";
+ }
+ else if (strcmp(suffix, "I8") == 0)
+ {
+ resolved_suffix = "int8_t";
+ }
+ else if (strcmp(suffix, "U8") == 0)
+ {
+ resolved_suffix = "uint8_t";
+ }
+ else if (strcmp(suffix, "I16") == 0)
+ {
+ resolved_suffix = "int16_t";
+ }
+ else if (strcmp(suffix, "U16") == 0)
+ {
+ resolved_suffix = "uint16_t";
+ }
+ else if (strcmp(suffix, "I64") == 0)
+ {
+ resolved_suffix = "int64_t";
+ }
+ else if (strcmp(suffix, "U64") == 0)
+ {
+ resolved_suffix = "uint64_t";
+ }
+ else if (strcmp(suffix, "usize") == 0)
+ {
+ resolved_suffix = "size_t";
+ }
+ else if (strcmp(suffix, "string") == 0)
+ {
+ resolved_suffix = "char*";
+ }
+
+ // Check if 'name' is a module alias (e.g., m::Vector)
+ Module *mod = find_module(ctx, name);
+ char *merged;
+ if (mod)
+ {
+ // Module-qualified type: Use module base name
+ merged = xmalloc(strlen(mod->base_name) + strlen(resolved_suffix) + 2);
+ sprintf(merged, "%s_%s", mod->base_name, resolved_suffix);
+ }
+ else
+ {
+ // Regular namespace or enum variant
+ merged = xmalloc(strlen(name) + strlen(resolved_suffix) + 2);
+ sprintf(merged, "%s_%s", name, resolved_suffix);
+ }
+
+ free(name);
+ if (suffix != resolved_suffix)
+ {
+ free(suffix); // Only free if we didn't remap
+ }
+ else
+ {
+ free(suffix);
+ }
+
+ name = merged;
+ }
+
+ // Check for Primitives (Base types)
+ if (strcmp(name, "U0") == 0)
+ {
+ free(name);
+ return type_new(TYPE_VOID);
+ }
+ if (strcmp(name, "I8") == 0)
+ {
+ free(name);
+ return type_new(TYPE_I8);
+ }
+ if (strcmp(name, "U8") == 0)
+ {
+ free(name);
+ return type_new(TYPE_U8);
+ }
+ if (strcmp(name, "I16") == 0)
+ {
+ free(name);
+ return type_new(TYPE_I16);
+ }
+ if (strcmp(name, "U16") == 0)
+ {
+ free(name);
+ return type_new(TYPE_U16);
+ }
+ if (strcmp(name, "I32") == 0)
+ {
+ free(name);
+ return type_new(TYPE_I32);
+ }
+ if (strcmp(name, "U32") == 0)
+ {
+ free(name);
+ return type_new(TYPE_U32);
+ }
+ if (strcmp(name, "I64") == 0)
+ {
+ free(name);
+ return type_new(TYPE_I64);
+ }
+ if (strcmp(name, "U64") == 0)
+ {
+ free(name);
+ return type_new(TYPE_U64);
+ }
+ if (strcmp(name, "F32") == 0)
+ {
+ free(name);
+ return type_new(TYPE_F32);
+ }
+ if (strcmp(name, "F64") == 0)
+ {
+ free(name);
+ return type_new(TYPE_F64);
+ }
+ if (strcmp(name, "usize") == 0)
+ {
+ free(name);
+ return type_new(TYPE_USIZE);
+ }
+ if (strcmp(name, "isize") == 0)
+ {
+ free(name);
+ return type_new(TYPE_ISIZE);
+ }
+ if (strcmp(name, "byte") == 0)
+ {
+ free(name);
+ return type_new(TYPE_BYTE);
+ }
+ if (strcmp(name, "I128") == 0)
+ {
+ free(name);
+ return type_new(TYPE_I128);
+ }
+ if (strcmp(name, "U128") == 0)
+ {
+ free(name);
+ return type_new(TYPE_U128);
+ }
+ if (strcmp(name, "rune") == 0)
+ {
+ free(name);
+ return type_new(TYPE_RUNE);
+ }
+ if (strcmp(name, "uint") == 0)
+ {
+ free(name);
+ return type_new(TYPE_UINT);
+ }
+
+ if (strcmp(name, "int") == 0)
+ {
+ free(name);
+ return type_new(TYPE_INT);
+ }
+ if (strcmp(name, "float") == 0)
+ {
+ free(name);
+ return type_new(TYPE_F32);
+ }
+ if (strcmp(name, "double") == 0)
+ {
+ free(name);
+ return type_new(TYPE_F64);
+ }
+ if (strcmp(name, "void") == 0)
+ {
+ free(name);
+ return type_new(TYPE_VOID);
+ }
+ if (strcmp(name, "string") == 0)
+ {
+ free(name);
+ return type_new(TYPE_STRING);
+ }
+ if (strcmp(name, "bool") == 0)
+ {
+ free(name);
+ return type_new(TYPE_BOOL);
+ }
+ if (strcmp(name, "char") == 0)
+ {
+ free(name);
+ return type_new(TYPE_CHAR);
+ }
+
+ // Selective imports ONLY apply when we're NOT in a module context
+ if (!ctx->current_module_prefix)
+ {
+ SelectiveImport *si = find_selective_import(ctx, name);
+ if (si)
+ {
+ // This is a selectively imported symbol
+ // Resolve to the actual struct name which was prefixed during module parsing
+ free(name);
+ name = xmalloc(strlen(si->source_module) + strlen(si->symbol) + 2);
+ sprintf(name, "%s_%s", si->source_module, si->symbol);
+ }
+ }
+
+ // If we're IN a module and no selective import matched, apply module prefix
+ if (ctx->current_module_prefix && !is_known_generic(ctx, name))
+ {
+ // Auto-prefix struct name if in module context (unless it's a known primitive/generic)
+ char *prefixed_name = xmalloc(strlen(ctx->current_module_prefix) + strlen(name) + 2);
+ sprintf(prefixed_name, "%s_%s", ctx->current_module_prefix, name);
+ free(name);
+ name = prefixed_name;
+ }
+
+ Type *ty = type_new(TYPE_STRUCT);
+ ty->name = name;
+
+ // Handle Generics <T>
+ if (lexer_peek(l).type == TOK_LANGLE)
+ {
+ lexer_next(l); // eat <
+ Type *arg = parse_type_formal(ctx, l);
+
+ // Handle nested generics like Vec<Vec<int>> where >> is tokenized as one op
+ Token next_tok = lexer_peek(l);
+ if (next_tok.type == TOK_RANGLE)
+ {
+ lexer_next(l); // Consume >
+ }
+ else if (next_tok.type == TOK_OP && next_tok.len == 2 &&
+ strncmp(next_tok.start, ">>", 2) == 0)
+ {
+ // Split >> into two > tokens
+ // Consume the first > by advancing lexer manually
+ l->pos += 1;
+ l->col += 1;
+ }
+ else
+ {
+ zpanic_at(t, "Expected > after generic");
+ }
+
+ // --- INSTANTIATION TRIGGER ---
+ char *arg_str = type_to_string(arg);
+ instantiate_generic(ctx, name, arg_str);
+
+ char *clean_arg = sanitize_mangled_name(arg_str);
+ char mangled[256];
+ sprintf(mangled, "%s_%s", name, clean_arg);
+ free(clean_arg);
+
+ free(ty->name);
+ ty->name = xstrdup(mangled);
+ free(arg_str);
+
+ ty->kind = TYPE_STRUCT;
+ ty->args = NULL;
+ ty->arg_count = 0;
+ }
+ return ty;
+ }
+
+ if (t.type == TOK_LBRACKET)
+ {
+ lexer_next(l); // eat [
+ Type *inner = parse_type_formal(ctx, l);
+
+ // Check for fixed-size array [T; N]
+ if (lexer_peek(l).type == TOK_SEMICOLON)
+ {
+ lexer_next(l); // eat ;
+ Token size_tok = lexer_next(l);
+ int size = 0;
+ if (size_tok.type == TOK_INT)
+ {
+ size = atoi(size_tok.start);
+ }
+ else if (size_tok.type == TOK_IDENT)
+ {
+ // Look up in symbol table for constant propagation
+ char *name = token_strdup(size_tok);
+ Symbol *sym = find_symbol_entry(ctx, name);
+ if (sym && sym->is_const_value)
+ {
+ size = sym->const_int_val;
+ sym->is_used = 1; // MARK AS USED
+ }
+ else
+ {
+ zpanic_at(size_tok,
+ "Array size must be a compile-time constant or integer literal");
+ }
+ free(name);
+ }
+ else
+ {
+ zpanic_at(size_tok, "Expected integer for array size");
+ }
+ if (lexer_next(l).type != TOK_RBRACKET)
+ {
+ zpanic("Expected ] after array size");
+ }
+
+ Type *arr = type_new(TYPE_ARRAY);
+ arr->inner = inner;
+ arr->array_size = size;
+ return arr;
+ }
+
+ // Otherwise it's a slice [T]
+ if (lexer_next(l).type != TOK_RBRACKET)
+ {
+ zpanic("Expected ] in type");
+ }
+
+ // Register Slice
+ char *inner_str = type_to_string(inner);
+ register_slice(ctx, inner_str);
+
+ Type *arr = type_new(TYPE_ARRAY);
+ arr->inner = inner;
+ arr->array_size = 0; // 0 means slice, not fixed-size
+ return arr;
+ }
+
+ if (t.type == TOK_LPAREN)
+ {
+ lexer_next(l); // eat (
+ char sig[256];
+ sig[0] = 0;
+
+ while (1)
+ {
+ Type *sub = parse_type_formal(ctx, l);
+ char *s = type_to_string(sub);
+ strcat(sig, s);
+ free(s);
+
+ if (lexer_peek(l).type == TOK_COMMA)
+ {
+ lexer_next(l);
+ strcat(sig, "_");
+ }
+ else
+ {
+ break;
+ }
+ }
+ if (lexer_next(l).type != TOK_RPAREN)
+ {
+ zpanic("Expected ) in tuple");
+ }
+
+ register_tuple(ctx, sig);
+
+ char *tuple_name = xmalloc(strlen(sig) + 7);
+ sprintf(tuple_name, "Tuple_%s", sig);
+
+ Type *ty = type_new(TYPE_STRUCT);
+ ty->name = tuple_name;
+ return ty;
+ }
+
+ return type_new(TYPE_UNKNOWN);
+}
+
+Type *parse_type_formal(ParserContext *ctx, Lexer *l)
+{
+ int is_restrict = 0;
+ if (lexer_peek(l).type == TOK_IDENT && lexer_peek(l).len == 8 &&
+ strncmp(lexer_peek(l).start, "restrict", 8) == 0)
+ {
+ lexer_next(l); // eat restrict
+ is_restrict = 1;
+ }
+
+ // Example: fn(int, int) -> int
+ if (lexer_peek(l).type == TOK_IDENT && strncmp(lexer_peek(l).start, "fn", 2) == 0 &&
+ lexer_peek(l).len == 2)
+ {
+
+ lexer_next(l); // eat 'fn'
+ Type *fn_type = type_new(TYPE_FUNCTION);
+ fn_type->is_varargs = 0;
+
+ expect(l, TOK_LPAREN, "Expected '(' for function type");
+
+ // Parse Arguments
+ fn_type->arg_count = 0;
+ fn_type->args = NULL;
+
+ while (lexer_peek(l).type != TOK_RPAREN)
+ {
+ Type *arg = parse_type_formal(ctx, l);
+ fn_type->arg_count++;
+ fn_type->args = xrealloc(fn_type->args, sizeof(Type *) * fn_type->arg_count);
+ fn_type->args[fn_type->arg_count - 1] = arg;
+
+ if (lexer_peek(l).type == TOK_COMMA)
+ {
+ lexer_next(l);
+ }
+ else
+ {
+ break;
+ }
+ }
+ expect(l, TOK_RPAREN, "Expected ')' after function args");
+
+ // Parse Return Type (-> Type)
+ if (lexer_peek(l).type == TOK_ARROW)
+ {
+ lexer_next(l); // eat ->
+ fn_type->inner = parse_type_formal(ctx, l); // Return type stored in inner
+ }
+ else
+ {
+ fn_type->inner = type_new(TYPE_VOID);
+ }
+
+ return fn_type;
+ }
+
+ // Handles: int, Struct, Generic<T>, [Slice], (Tuple)
+ Type *t = parse_type_base(ctx, l);
+
+ // Handles: T*, T**, etc.
+ while (lexer_peek(l).type == TOK_OP && *lexer_peek(l).start == '*')
+ {
+ lexer_next(l); // consume '*'
+ t = type_new_ptr(t);
+ }
+
+ // 4. Handle Array Suffixes (e.g. int[10])
+ while (lexer_peek(l).type == TOK_LBRACKET)
+ {
+ lexer_next(l); // consume '['
+
+ int size = 0;
+ if (lexer_peek(l).type == TOK_INT)
+ {
+ Token t = lexer_peek(l);
+ char buffer[64];
+ int len = t.len < 63 ? t.len : 63;
+ strncpy(buffer, t.start, len);
+ buffer[len] = 0;
+ size = atoi(buffer);
+ lexer_next(l);
+ }
+
+ expect(l, TOK_RBRACKET, "Expected ']' in array type");
+
+ Type *arr = type_new(TYPE_ARRAY);
+ arr->inner = t;
+ arr->array_size = size;
+ t = arr;
+ }
+
+ if (is_restrict)
+ {
+ t->is_restrict = 1;
+ }
+ return t;
+}
+
+char *parse_type(ParserContext *ctx, Lexer *l)
+{
+ Type *t = parse_type_formal(ctx, l);
+
+ return type_to_string(t);
+}
+
+char *parse_array_literal(ParserContext *ctx, Lexer *l, const char *st)
+{
+ (void)ctx; // suppress unused parameter warning
+ lexer_next(l);
+ size_t cap = 128;
+ char *c = xmalloc(cap);
+ c[0] = 0;
+ int n = 0;
+
+ while (1)
+ {
+ Token t = lexer_peek(l);
+ if (t.type == TOK_RBRACKET)
+ {
+ lexer_next(l);
+ break;
+ }
+ if (t.type == TOK_COMMA)
+ {
+ lexer_next(l);
+ continue;
+ }
+
+ const char *s = l->src + l->pos;
+ int d = 0;
+ while (1)
+ {
+ Token it = lexer_peek(l);
+ if (it.type == TOK_EOF)
+ {
+ break;
+ }
+ if (d == 0 && (it.type == TOK_COMMA || it.type == TOK_RBRACKET))
+ {
+ break;
+ }
+ if (it.type == TOK_LBRACKET || it.type == TOK_LPAREN)
+ {
+ d++;
+ }
+ if (it.type == TOK_RBRACKET || it.type == TOK_RPAREN)
+ {
+ d--;
+ }
+ lexer_next(l);
+ }
+
+ int len = (l->src + l->pos) - s;
+ if (strlen(c) + len + 5 > cap)
+ {
+ cap *= 2;
+ c = xrealloc(c, cap);
+ }
+ if (n > 0)
+ {
+ strcat(c, ", ");
+ }
+ strncat(c, s, len);
+ n++;
+ }
+
+ char rt[64];
+ if (strncmp(st, "Slice_", 6) == 0)
+ {
+ strcpy(rt, st + 6);
+ }
+ else
+ {
+ strcpy(rt, "int");
+ }
+
+ char *o = xmalloc(strlen(c) + 128);
+ sprintf(o, "(%s){.data=(%s[]){%s},.len=%d,.cap=%d}", st, rt, c, n, n);
+ free(c);
+ return o;
+}
+char *parse_tuple_literal(ParserContext *ctx, Lexer *l, const char *tn)
+{
+ (void)ctx; // suppress unused parameter warning
+ lexer_next(l);
+ size_t cap = 128;
+ char *c = xmalloc(cap);
+ c[0] = 0;
+
+ while (1)
+ {
+ Token t = lexer_peek(l);
+ if (t.type == TOK_RPAREN)
+ {
+ lexer_next(l);
+ break;
+ }
+ if (t.type == TOK_COMMA)
+ {
+ lexer_next(l);
+ continue;
+ }
+
+ const char *s = l->src + l->pos;
+ int d = 0;
+ while (1)
+ {
+ Token it = lexer_peek(l);
+ if (it.type == TOK_EOF)
+ {
+ break;
+ }
+ if (d == 0 && (it.type == TOK_COMMA || it.type == TOK_RPAREN))
+ {
+ break;
+ }
+ if (it.type == TOK_LPAREN)
+ {
+ d++;
+ }
+ if (it.type == TOK_RPAREN)
+ {
+ d--;
+ }
+ lexer_next(l);
+ }
+
+ int len = (l->src + l->pos) - s;
+ if (strlen(c) + len + 5 > cap)
+ {
+ cap *= 2;
+ c = xrealloc(c, cap);
+ }
+ if (strlen(c) > 0)
+ {
+ strcat(c, ", ");
+ }
+ strncat(c, s, len);
+ }
+
+ char *o = xmalloc(strlen(c) + 128);
+ sprintf(o, "(%s){%s}", tn, c);
+ free(c);
+ return o;
+}
+char *parse_embed(ParserContext *ctx, Lexer *l)
+{
+ lexer_next(l);
+ Token t = lexer_next(l);
+ if (t.type != TOK_STRING)
+ {
+ zpanic("String required");
+ }
+ char fn[256];
+ strncpy(fn, t.start + 1, t.len - 2);
+ fn[t.len - 2] = 0;
+
+ FILE *f = fopen(fn, "rb");
+ if (!f)
+ {
+ zpanic("404: %s", fn);
+ }
+ fseek(f, 0, SEEK_END);
+ long len = ftell(f);
+ rewind(f);
+ unsigned char *b = xmalloc(len);
+ fread(b, 1, len, f);
+ fclose(f);
+
+ register_slice(ctx, "char");
+ size_t oc = len * 6 + 128;
+ char *o = xmalloc(oc);
+ sprintf(o, "(Slice_char){.data=(char[]){");
+ char *p = o + strlen(o);
+ for (int i = 0; i < len; i++)
+ {
+ p += sprintf(p, "0x%02X,", b[i]);
+ }
+ sprintf(p, "},.len=%ld,.cap=%ld}", len, len);
+ free(b);
+ return o;
+}
diff --git a/src/parser/parser_utils.c b/src/parser/parser_utils.c
new file mode 100644
index 0000000..88823de
--- /dev/null
+++ b/src/parser/parser_utils.c
@@ -0,0 +1,2459 @@
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include "parser.h"
+#include "../codegen/codegen.h"
+
+Token expect(Lexer *l, TokenType type, const char *msg)
+{
+ Token t = lexer_next(l);
+ if (t.type != type)
+ {
+ zpanic_at(t, "Expected %s, but got '%.*s'", msg, t.len, t.start);
+ return (Token){type, t.start, 0, t.line, t.col};
+ }
+ return t;
+}
+
+int is_token(Token t, const char *s)
+{
+ int len = strlen(s);
+ return (t.len == len && strncmp(t.start, s, len) == 0);
+}
+
+char *token_strdup(Token t)
+{
+ char *s = xmalloc(t.len + 1);
+ strncpy(s, t.start, t.len);
+ s[t.len] = 0;
+ return s;
+}
+
+void skip_comments(Lexer *l)
+{
+ while (lexer_peek(l).type == TOK_COMMENT)
+ {
+ lexer_next(l);
+ }
+}
+
+// C reserved words that conflict with C when used as identifiers.
+// TODO: We gotta work on these.
+static const char *C_RESERVED_WORDS[] = {
+ // C types that could be used as names
+ "double", "float", "signed", "unsigned", "short", "long", "auto", "register",
+ // C keywords
+ "switch", "case", "default", "do", "goto", "typedef", "static", "extern", "volatile", "inline",
+ "restrict", "sizeof", "const",
+ // C11+ keywords
+ "_Alignas", "_Alignof", "_Atomic", "_Bool", "_Complex", "_Generic", "_Imaginary", "_Noreturn",
+ "_Static_assert", "_Thread_local", NULL};
+
+int is_c_reserved_word(const char *name)
+{
+ for (int i = 0; C_RESERVED_WORDS[i] != NULL; i++)
+ {
+ if (strcmp(name, C_RESERVED_WORDS[i]) == 0)
+ {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+void warn_c_reserved_word(Token t, const char *name)
+{
+ zwarn_at(t, "Identifier '%s' conflicts with C reserved word", name);
+ fprintf(stderr, COLOR_CYAN " = note: " COLOR_RESET
+ "This will cause compilation errors in the generated C code\n");
+}
+
+char *consume_until_semicolon(Lexer *l)
+{
+ const char *s = l->src + l->pos;
+ int d = 0;
+ while (1)
+ {
+ Token t = lexer_peek(l);
+ if (t.type == TOK_EOF)
+ {
+ break;
+ }
+ if (t.type == TOK_LBRACE || t.type == TOK_LPAREN || t.type == TOK_LBRACKET)
+ {
+ d++;
+ }
+ if (t.type == TOK_RBRACE || t.type == TOK_RPAREN || t.type == TOK_RBRACKET)
+ {
+ d--;
+ }
+
+ if (d == 0 && t.type == TOK_SEMICOLON)
+ {
+ int len = t.start - s;
+ char *r = xmalloc(len + 1);
+ strncpy(r, s, len);
+ r[len] = 0;
+ lexer_next(l);
+ return r;
+ }
+ lexer_next(l);
+ }
+ return xstrdup("");
+}
+
+void enter_scope(ParserContext *ctx)
+{
+ Scope *s = xmalloc(sizeof(Scope));
+ s->symbols = 0;
+ s->parent = ctx->current_scope;
+ ctx->current_scope = s;
+}
+
+void exit_scope(ParserContext *ctx)
+{
+ if (!ctx->current_scope)
+ {
+ return;
+ }
+
+ // Check for unused variables
+ Symbol *sym = ctx->current_scope->symbols;
+ while (sym)
+ {
+ if (!sym->is_used && strcmp(sym->name, "self") != 0 && sym->name[0] != '_')
+ {
+ // Could emit warning here
+ }
+ sym = sym->next;
+ }
+
+ ctx->current_scope = ctx->current_scope->parent;
+}
+
+void add_symbol(ParserContext *ctx, const char *n, const char *t, Type *type_info)
+{
+ add_symbol_with_token(ctx, n, t, type_info, (Token){0});
+}
+
+void add_symbol_with_token(ParserContext *ctx, const char *n, const char *t, Type *type_info,
+ Token tok)
+{
+ if (!ctx->current_scope)
+ {
+ enter_scope(ctx);
+ }
+
+ if (n[0] != '_' && ctx->current_scope->parent && strcmp(n, "it") != 0 && strcmp(n, "self") != 0)
+ {
+ Scope *p = ctx->current_scope->parent;
+ while (p)
+ {
+ Symbol *sh = p->symbols;
+ while (sh)
+ {
+ if (strcmp(sh->name, n) == 0)
+ {
+ warn_shadowing(tok, n);
+ break;
+ }
+ sh = sh->next;
+ }
+ if (sh)
+ {
+ break; // found it
+ }
+ p = p->parent;
+ }
+ }
+ Symbol *s = xmalloc(sizeof(Symbol));
+ s->name = xstrdup(n);
+ s->type_name = t ? xstrdup(t) : NULL;
+ s->type_info = type_info;
+ s->is_mutable = 1;
+ s->is_used = 0;
+ s->decl_token = tok;
+ s->is_const_value = 0;
+ s->next = ctx->current_scope->symbols;
+ ctx->current_scope->symbols = s;
+
+ // LSP: Also add to flat list (for persistent access after scope exit)
+ Symbol *lsp_copy = xmalloc(sizeof(Symbol));
+ *lsp_copy = *s;
+ lsp_copy->next = ctx->all_symbols;
+ ctx->all_symbols = lsp_copy;
+}
+
+Type *find_symbol_type_info(ParserContext *ctx, const char *n)
+{
+ if (!ctx->current_scope)
+ {
+ return NULL;
+ }
+ Scope *s = ctx->current_scope;
+ while (s)
+ {
+ Symbol *sym = s->symbols;
+ while (sym)
+ {
+ if (strcmp(sym->name, n) == 0)
+ {
+ return sym->type_info;
+ }
+ sym = sym->next;
+ }
+ s = s->parent;
+ }
+ return NULL;
+}
+
+char *find_symbol_type(ParserContext *ctx, const char *n)
+{
+ if (!ctx->current_scope)
+ {
+ return NULL;
+ }
+ Scope *s = ctx->current_scope;
+ while (s)
+ {
+ Symbol *sym = s->symbols;
+ while (sym)
+ {
+ if (strcmp(sym->name, n) == 0)
+ {
+ return sym->type_name;
+ }
+ sym = sym->next;
+ }
+ s = s->parent;
+ }
+ return NULL;
+}
+
+Symbol *find_symbol_entry(ParserContext *ctx, const char *n)
+{
+ if (!ctx->current_scope)
+ {
+ return NULL;
+ }
+ Scope *s = ctx->current_scope;
+ while (s)
+ {
+ Symbol *sym = s->symbols;
+ while (sym)
+ {
+ if (strcmp(sym->name, n) == 0)
+ {
+ return sym;
+ }
+ sym = sym->next;
+ }
+ s = s->parent;
+ }
+ return NULL;
+}
+
+// LSP: Search flat symbol list (works after scopes are destroyed).
+Symbol *find_symbol_in_all(ParserContext *ctx, const char *n)
+{
+ Symbol *sym = ctx->all_symbols;
+ while (sym)
+ {
+ if (strcmp(sym->name, n) == 0)
+ {
+ return sym;
+ }
+ sym = sym->next;
+ }
+ return NULL;
+}
+
+void init_builtins()
+{
+ static int init = 0;
+ if (init)
+ {
+ return;
+ }
+ init = 1;
+}
+
+void register_func(ParserContext *ctx, const char *name, int count, char **defaults,
+ Type **arg_types, Type *ret_type, int is_varargs, int is_async, Token decl_token)
+{
+ FuncSig *f = xmalloc(sizeof(FuncSig));
+ f->name = xstrdup(name);
+ f->decl_token = decl_token;
+ f->total_args = count;
+ f->defaults = defaults;
+ f->arg_types = arg_types;
+ f->ret_type = ret_type;
+ f->is_varargs = is_varargs;
+ f->is_async = is_async;
+ f->must_use = 0; // Default: can discard result
+ f->next = ctx->func_registry;
+ ctx->func_registry = f;
+}
+
+void register_func_template(ParserContext *ctx, const char *name, const char *param, ASTNode *node)
+{
+ GenericFuncTemplate *t = xmalloc(sizeof(GenericFuncTemplate));
+ t->name = xstrdup(name);
+ t->generic_param = xstrdup(param);
+ t->func_node = node;
+ t->next = ctx->func_templates;
+ ctx->func_templates = t;
+}
+
+void register_deprecated_func(ParserContext *ctx, const char *name, const char *reason)
+{
+ DeprecatedFunc *d = xmalloc(sizeof(DeprecatedFunc));
+ d->name = xstrdup(name);
+ d->reason = reason ? xstrdup(reason) : NULL;
+ d->next = ctx->deprecated_funcs;
+ ctx->deprecated_funcs = d;
+}
+
+DeprecatedFunc *find_deprecated_func(ParserContext *ctx, const char *name)
+{
+ DeprecatedFunc *d = ctx->deprecated_funcs;
+ while (d)
+ {
+ if (strcmp(d->name, name) == 0)
+ {
+ return d;
+ }
+ d = d->next;
+ }
+ return NULL;
+}
+
+GenericFuncTemplate *find_func_template(ParserContext *ctx, const char *name)
+{
+ GenericFuncTemplate *t = ctx->func_templates;
+ while (t)
+ {
+ if (strcmp(t->name, name) == 0)
+ {
+ return t;
+ }
+ t = t->next;
+ }
+ return NULL;
+}
+
+void register_generic(ParserContext *ctx, char *name)
+{
+ for (int i = 0; i < ctx->known_generics_count; i++)
+ {
+ if (strcmp(ctx->known_generics[i], name) == 0)
+ {
+ return;
+ }
+ }
+ ctx->known_generics[ctx->known_generics_count++] = strdup(name);
+}
+
+int is_known_generic(ParserContext *ctx, char *name)
+{
+ for (int i = 0; i < ctx->known_generics_count; i++)
+ {
+ if (strcmp(ctx->known_generics[i], name) == 0)
+ {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+void register_impl_template(ParserContext *ctx, const char *sname, const char *param, ASTNode *node)
+{
+ GenericImplTemplate *t = xmalloc(sizeof(GenericImplTemplate));
+ t->struct_name = xstrdup(sname);
+ t->generic_param = xstrdup(param);
+ t->impl_node = node;
+ t->next = ctx->impl_templates;
+ ctx->impl_templates = t;
+}
+
+void add_to_struct_list(ParserContext *ctx, ASTNode *node)
+{
+ StructRef *r = xmalloc(sizeof(StructRef));
+ r->node = node;
+ r->next = ctx->parsed_structs_list;
+ ctx->parsed_structs_list = r;
+}
+
+void add_to_enum_list(ParserContext *ctx, ASTNode *node)
+{
+ StructRef *r = xmalloc(sizeof(StructRef));
+ r->node = node;
+ r->next = ctx->parsed_enums_list;
+ ctx->parsed_enums_list = r;
+}
+
+void add_to_func_list(ParserContext *ctx, ASTNode *node)
+{
+ StructRef *r = xmalloc(sizeof(StructRef));
+ r->node = node;
+ r->next = ctx->parsed_funcs_list;
+ ctx->parsed_funcs_list = r;
+}
+
+void add_to_impl_list(ParserContext *ctx, ASTNode *node)
+{
+ StructRef *r = xmalloc(sizeof(StructRef));
+ r->node = node;
+ r->next = ctx->parsed_impls_list;
+ ctx->parsed_impls_list = r;
+}
+
+void add_to_global_list(ParserContext *ctx, ASTNode *node)
+{
+ StructRef *r = xmalloc(sizeof(StructRef));
+ r->node = node;
+ r->next = ctx->parsed_globals_list;
+ ctx->parsed_globals_list = r;
+}
+
+void register_builtins(ParserContext *ctx)
+{
+ Type *t = type_new(TYPE_BOOL);
+ t->is_const = 1;
+ add_symbol(ctx, "true", "bool", t);
+
+ t = type_new(TYPE_BOOL);
+ t->is_const = 1;
+ add_symbol(ctx, "false", "bool", t);
+
+ // Register 'free'
+ Type *void_t = type_new(TYPE_VOID);
+ add_symbol(ctx, "free", "void", void_t);
+
+ // Register common libc functions to avoid warnings
+ add_symbol(ctx, "strdup", "string", type_new(TYPE_STRING));
+ add_symbol(ctx, "malloc", "void*", type_new_ptr(void_t));
+ add_symbol(ctx, "realloc", "void*", type_new_ptr(void_t));
+ add_symbol(ctx, "calloc", "void*", type_new_ptr(void_t));
+ add_symbol(ctx, "puts", "int", type_new(TYPE_INT));
+ add_symbol(ctx, "printf", "int", type_new(TYPE_INT));
+ add_symbol(ctx, "strcmp", "int", type_new(TYPE_INT));
+ add_symbol(ctx, "strlen", "int", type_new(TYPE_INT));
+ add_symbol(ctx, "strcpy", "string", type_new(TYPE_STRING));
+ add_symbol(ctx, "strcat", "string", type_new(TYPE_STRING));
+ add_symbol(ctx, "exit", "void", void_t);
+
+ // File I/O
+ add_symbol(ctx, "fopen", "void*", type_new_ptr(void_t));
+ add_symbol(ctx, "fclose", "int", type_new(TYPE_INT));
+ add_symbol(ctx, "fread", "usize", type_new(TYPE_USIZE));
+ add_symbol(ctx, "fwrite", "usize", type_new(TYPE_USIZE));
+ add_symbol(ctx, "fseek", "int", type_new(TYPE_INT));
+ add_symbol(ctx, "ftell", "long", type_new(TYPE_I64));
+ add_symbol(ctx, "rewind", "void", void_t);
+ add_symbol(ctx, "fprintf", "int", type_new(TYPE_INT));
+ add_symbol(ctx, "sprintf", "int", type_new(TYPE_INT));
+ add_symbol(ctx, "feof", "int", type_new(TYPE_INT));
+ add_symbol(ctx, "ferror", "int", type_new(TYPE_INT));
+ add_symbol(ctx, "usleep", "int", type_new(TYPE_INT));
+}
+
+void add_instantiated_func(ParserContext *ctx, ASTNode *fn)
+{
+ fn->next = ctx->instantiated_funcs;
+ ctx->instantiated_funcs = fn;
+}
+
+void register_enum_variant(ParserContext *ctx, const char *ename, const char *vname, int tag)
+{
+ EnumVariantReg *r = xmalloc(sizeof(EnumVariantReg));
+ r->enum_name = xstrdup(ename);
+ r->variant_name = xstrdup(vname);
+ r->tag_id = tag;
+ r->next = ctx->enum_variants;
+ ctx->enum_variants = r;
+}
+
+EnumVariantReg *find_enum_variant(ParserContext *ctx, const char *vname)
+{
+ EnumVariantReg *r = ctx->enum_variants;
+ while (r)
+ {
+ if (strcmp(r->variant_name, vname) == 0)
+ {
+ return r;
+ }
+ r = r->next;
+ }
+ return NULL;
+}
+
+void register_lambda(ParserContext *ctx, ASTNode *node)
+{
+ LambdaRef *ref = xmalloc(sizeof(LambdaRef));
+ ref->node = node;
+ ref->next = ctx->global_lambdas;
+ ctx->global_lambdas = ref;
+}
+
+void register_var_mutability(ParserContext *ctx, const char *name, int is_mutable)
+{
+ VarMutability *v = xmalloc(sizeof(VarMutability));
+ v->name = xstrdup(name);
+ v->is_mutable = is_mutable;
+ v->next = ctx->var_mutability_table;
+ ctx->var_mutability_table = v;
+}
+
+int is_var_mutable(ParserContext *ctx, const char *name)
+{
+ for (VarMutability *v = ctx->var_mutability_table; v; v = v->next)
+ {
+ if (strcmp(v->name, name) == 0)
+ {
+ return v->is_mutable;
+ }
+ }
+ return 1;
+}
+
+void register_extern_symbol(ParserContext *ctx, const char *name)
+{
+ // Check for duplicates
+ for (int i = 0; i < ctx->extern_symbol_count; i++)
+ {
+ if (strcmp(ctx->extern_symbols[i], name) == 0)
+ {
+ return;
+ }
+ }
+
+ // Grow array if needed
+ if (ctx->extern_symbol_count == 0)
+ {
+ ctx->extern_symbols = xmalloc(sizeof(char *) * 64);
+ }
+ else if (ctx->extern_symbol_count % 64 == 0)
+ {
+ ctx->extern_symbols =
+ xrealloc(ctx->extern_symbols, sizeof(char *) * (ctx->extern_symbol_count + 64));
+ }
+
+ ctx->extern_symbols[ctx->extern_symbol_count++] = xstrdup(name);
+}
+
+int is_extern_symbol(ParserContext *ctx, const char *name)
+{
+ for (int i = 0; i < ctx->extern_symbol_count; i++)
+ {
+ if (strcmp(ctx->extern_symbols[i], name) == 0)
+ {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+// Unified check: should we suppress "undefined variable" warning for this name?
+int should_suppress_undef_warning(ParserContext *ctx, const char *name)
+{
+ if (strcmp(name, "struct") == 0 || strcmp(name, "tv") == 0)
+ {
+ return 1;
+ }
+
+ if (is_extern_symbol(ctx, name))
+ {
+ return 1;
+ }
+
+ int is_all_caps = 1;
+ for (const char *p = name; *p; p++)
+ {
+ if (islower((unsigned char)*p))
+ {
+ is_all_caps = 0;
+ break;
+ }
+ }
+ if (is_all_caps && name[0] != '\0')
+ {
+ return 1;
+ }
+
+ if (ctx->has_external_includes)
+ {
+ return 1;
+ }
+
+ return 0;
+}
+
+void register_slice(ParserContext *ctx, const char *type)
+{
+ SliceType *c = ctx->used_slices;
+ while (c)
+ {
+ if (strcmp(c->name, type) == 0)
+ {
+ return;
+ }
+ c = c->next;
+ }
+ SliceType *n = xmalloc(sizeof(SliceType));
+ n->name = xstrdup(type);
+ n->next = ctx->used_slices;
+ ctx->used_slices = n;
+
+ // Register Struct Def for Reflection
+ char slice_name[256];
+ sprintf(slice_name, "Slice_%s", type);
+
+ ASTNode *len_f = ast_create(NODE_FIELD);
+ len_f->field.name = xstrdup("len");
+ len_f->field.type = xstrdup("int");
+ ASTNode *cap_f = ast_create(NODE_FIELD);
+ cap_f->field.name = xstrdup("cap");
+ cap_f->field.type = xstrdup("int");
+ ASTNode *data_f = ast_create(NODE_FIELD);
+ data_f->field.name = xstrdup("data");
+ char ptr_type[256];
+ sprintf(ptr_type, "%s*", type);
+ data_f->field.type = xstrdup(ptr_type);
+
+ data_f->next = len_f;
+ len_f->next = cap_f;
+
+ ASTNode *def = ast_create(NODE_STRUCT);
+ def->strct.name = xstrdup(slice_name);
+ def->strct.fields = data_f;
+
+ register_struct_def(ctx, slice_name, def);
+}
+
+void register_tuple(ParserContext *ctx, const char *sig)
+{
+ TupleType *c = ctx->used_tuples;
+ while (c)
+ {
+ if (strcmp(c->sig, sig) == 0)
+ {
+ return;
+ }
+ c = c->next;
+ }
+ TupleType *n = xmalloc(sizeof(TupleType));
+ n->sig = xstrdup(sig);
+ n->next = ctx->used_tuples;
+ ctx->used_tuples = n;
+}
+
+void register_struct_def(ParserContext *ctx, const char *name, ASTNode *node)
+{
+ StructDef *d = xmalloc(sizeof(StructDef));
+ d->name = xstrdup(name);
+ d->node = node;
+ d->next = ctx->struct_defs;
+ ctx->struct_defs = d;
+}
+
+ASTNode *find_struct_def(ParserContext *ctx, const char *name)
+{
+ Instantiation *i = ctx->instantiations;
+ while (i)
+ {
+ if (strcmp(i->name, name) == 0)
+ {
+ return i->struct_node;
+ }
+ i = i->next;
+ }
+
+ ASTNode *s = ctx->instantiated_structs;
+ while (s)
+ {
+ if (s->type == NODE_STRUCT && strcmp(s->strct.name, name) == 0)
+ {
+ return s;
+ }
+ s = s->next;
+ }
+
+ StructRef *r = ctx->parsed_structs_list;
+ while (r)
+ {
+ if (strcmp(r->node->strct.name, name) == 0)
+ {
+ return r->node;
+ }
+ r = r->next;
+ }
+
+ // Check manually registered definitions (e.g. Slices)
+ StructDef *d = ctx->struct_defs;
+ while (d)
+ {
+ if (strcmp(d->name, name) == 0)
+ {
+ return d->node;
+ }
+ d = d->next;
+ }
+
+ // Check enums list (for @derive(Eq) and field type lookups)
+ StructRef *e = ctx->parsed_enums_list;
+ while (e)
+ {
+ if (e->node->type == NODE_ENUM && strcmp(e->node->enm.name, name) == 0)
+ {
+ return e->node;
+ }
+ e = e->next;
+ }
+
+ return NULL;
+}
+
+Module *find_module(ParserContext *ctx, const char *alias)
+{
+ Module *m = ctx->modules;
+ while (m)
+ {
+ if (strcmp(m->alias, alias) == 0)
+ {
+ return m;
+ }
+ m = m->next;
+ }
+ return NULL;
+}
+
+void register_module(ParserContext *ctx, const char *alias, const char *path)
+{
+ Module *m = xmalloc(sizeof(Module));
+ m->alias = xstrdup(alias);
+ m->path = xstrdup(path);
+ m->base_name = extract_module_name(path);
+ m->next = ctx->modules;
+ ctx->modules = m;
+}
+
+void register_selective_import(ParserContext *ctx, const char *symbol, const char *alias,
+ const char *source_module)
+{
+ SelectiveImport *si = xmalloc(sizeof(SelectiveImport));
+ si->symbol = xstrdup(symbol);
+ si->alias = alias ? xstrdup(alias) : NULL;
+ si->source_module = xstrdup(source_module);
+ si->next = ctx->selective_imports;
+ ctx->selective_imports = si;
+}
+
+SelectiveImport *find_selective_import(ParserContext *ctx, const char *name)
+{
+ SelectiveImport *si = ctx->selective_imports;
+ while (si)
+ {
+ if (si->alias && strcmp(si->alias, name) == 0)
+ {
+ return si;
+ }
+ if (!si->alias && strcmp(si->symbol, name) == 0)
+ {
+ return si;
+ }
+ si = si->next;
+ }
+ return NULL;
+}
+
+char *extract_module_name(const char *path)
+{
+ const char *slash = strrchr(path, '/');
+ const char *base = slash ? slash + 1 : path;
+ const char *dot = strrchr(base, '.');
+ int len = dot ? (int)(dot - base) : (int)strlen(base);
+ char *name = xmalloc(len + 1);
+ strncpy(name, base, len);
+ name[len] = 0;
+ return name;
+}
+
+int is_ident_char(char c)
+{
+ return isalnum(c) || c == '_';
+}
+
+ASTNode *copy_fields(ASTNode *fields)
+{
+ if (!fields)
+ {
+ return NULL;
+ }
+ ASTNode *n = ast_create(NODE_FIELD);
+ n->field.name = xstrdup(fields->field.name);
+ n->field.type = xstrdup(fields->field.type);
+ n->next = copy_fields(fields->next);
+ return n;
+}
+char *replace_in_string(const char *src, const char *old_w, const char *new_w)
+{
+ if (!src || !old_w || !new_w)
+ {
+ return src ? xstrdup(src) : NULL;
+ }
+
+ char *result;
+ int i, cnt = 0;
+ int newWlen = strlen(new_w);
+ int oldWlen = strlen(old_w);
+
+ for (i = 0; src[i] != '\0'; i++)
+ {
+ if (strstr(&src[i], old_w) == &src[i])
+ {
+ // Check boundaries to ensure we match whole words only
+ int valid = 1;
+
+ // Check preceding character
+ if (i > 0 && is_ident_char(src[i - 1]))
+ {
+ valid = 0;
+ }
+
+ // Check following character
+ if (valid && is_ident_char(src[i + oldWlen]))
+ {
+ valid = 0;
+ }
+
+ if (valid)
+ {
+ cnt++;
+ i += oldWlen - 1;
+ }
+ }
+ }
+
+ // Allocate result buffer
+ result = (char *)xmalloc(i + cnt * (newWlen - oldWlen) + 1);
+
+ i = 0;
+ while (*src)
+ {
+ if (strstr(src, old_w) == src)
+ {
+ int valid = 1;
+
+ // Check boundary relative to the *new* result buffer built so far
+ if (i > 0 && is_ident_char(result[i - 1]))
+ {
+ valid = 0;
+ }
+
+ // Check boundary relative to the *original* source string
+ if (valid && is_ident_char(src[oldWlen]))
+ {
+ valid = 0;
+ }
+
+ if (valid)
+ {
+ strcpy(&result[i], new_w);
+ i += newWlen;
+ src += oldWlen;
+ }
+ else
+ {
+ result[i++] = *src++;
+ }
+ }
+ else
+ {
+ result[i++] = *src++;
+ }
+ }
+ result[i] = '\0';
+ return result;
+}
+
+char *replace_type_str(const char *src, const char *param, const char *concrete,
+ const char *old_struct, const char *new_struct)
+{
+ if (!src)
+ {
+ return NULL;
+ }
+
+ if (strcmp(src, param) == 0)
+ {
+ return xstrdup(concrete);
+ }
+
+ if (old_struct && new_struct && strcmp(src, old_struct) == 0)
+ {
+ return xstrdup(new_struct);
+ }
+
+ if (old_struct && new_struct && param)
+ {
+ char *mangled = xmalloc(strlen(old_struct) + strlen(param) + 2);
+ sprintf(mangled, "%s_%s", old_struct, param);
+ if (strcmp(src, mangled) == 0)
+ {
+ free(mangled);
+ return xstrdup(new_struct);
+ }
+ free(mangled);
+ }
+
+ if (param && concrete && src)
+ {
+ char suffix[256];
+ sprintf(suffix, "_%s", param);
+ size_t slen = strlen(src);
+ size_t plen = strlen(suffix);
+ if (slen > plen && strcmp(src + slen - plen, suffix) == 0)
+ {
+ // Ends with _T -> Replace suffix with _int (sanitize for pointers like JsonValue*)
+ char *clean_concrete = sanitize_mangled_name(concrete);
+ char *ret = xmalloc(slen - plen + strlen(clean_concrete) + 2);
+ strncpy(ret, src, slen - plen);
+ ret[slen - plen] = 0;
+ strcat(ret, "_");
+ strcat(ret, clean_concrete);
+ free(clean_concrete);
+ return ret;
+ }
+ }
+
+ size_t len = strlen(src);
+ if (len > 1 && src[len - 1] == '*')
+ {
+ char *base = xmalloc(len);
+ strncpy(base, src, len - 1);
+ base[len - 1] = 0;
+
+ char *new_base = replace_type_str(base, param, concrete, old_struct, new_struct);
+ free(base);
+
+ if (strcmp(new_base, base) != 0)
+ {
+ char *ret = xmalloc(strlen(new_base) + 2);
+ sprintf(ret, "%s*", new_base);
+ free(new_base);
+ return ret;
+ }
+ free(new_base);
+ }
+
+ if (strncmp(src, "Slice_", 6) == 0)
+ {
+ char *base = xstrdup(src + 6);
+ char *new_base = replace_type_str(base, param, concrete, old_struct, new_struct);
+ free(base);
+
+ if (strcmp(new_base, base) != 0)
+ {
+ char *ret = xmalloc(strlen(new_base) + 7);
+ sprintf(ret, "Slice_%s", new_base);
+ free(new_base);
+ return ret;
+ }
+ free(new_base);
+ }
+
+ return xstrdup(src);
+}
+
+ASTNode *copy_ast_replacing(ASTNode *n, const char *p, const char *c, const char *os,
+ const char *ns);
+
+Type *replace_type_formal(Type *t, const char *p, const char *c, const char *os, const char *ns)
+{
+ if (!t)
+ {
+ return NULL;
+ }
+
+ if ((t->kind == TYPE_STRUCT || t->kind == TYPE_GENERIC) && t->name && strcmp(t->name, p) == 0)
+ {
+ if (strcmp(c, "int") == 0)
+ {
+ return type_new(TYPE_INT);
+ }
+ if (strcmp(c, "float") == 0)
+ {
+ return type_new(TYPE_FLOAT);
+ }
+ if (strcmp(c, "void") == 0)
+ {
+ return type_new(TYPE_VOID);
+ }
+ if (strcmp(c, "string") == 0)
+ {
+ return type_new(TYPE_STRING);
+ }
+ if (strcmp(c, "bool") == 0)
+ {
+ return type_new(TYPE_BOOL);
+ }
+ if (strcmp(c, "char") == 0)
+ {
+ return type_new(TYPE_CHAR);
+ }
+
+ if (strcmp(c, "I8") == 0)
+ {
+ return type_new(TYPE_I8);
+ }
+ if (strcmp(c, "U8") == 0)
+ {
+ return type_new(TYPE_U8);
+ }
+ if (strcmp(c, "I16") == 0)
+ {
+ return type_new(TYPE_I16);
+ }
+ if (strcmp(c, "U16") == 0)
+ {
+ return type_new(TYPE_U16);
+ }
+ if (strcmp(c, "I32") == 0)
+ {
+ return type_new(TYPE_I32);
+ }
+ if (strcmp(c, "U32") == 0)
+ {
+ return type_new(TYPE_U32);
+ }
+ if (strcmp(c, "I64") == 0)
+ {
+ return type_new(TYPE_I64);
+ }
+ if (strcmp(c, "U64") == 0)
+ {
+ return type_new(TYPE_U64);
+ }
+ if (strcmp(c, "F32") == 0)
+ {
+ return type_new(TYPE_F32);
+ }
+ if (strcmp(c, "F64") == 0)
+ {
+ return type_new(TYPE_F64);
+ }
+
+ if (strcmp(c, "usize") == 0)
+ {
+ return type_new(TYPE_USIZE);
+ }
+ if (strcmp(c, "isize") == 0)
+ {
+ return type_new(TYPE_ISIZE);
+ }
+ if (strcmp(c, "byte") == 0)
+ {
+ return type_new(TYPE_BYTE);
+ }
+ if (strcmp(c, "I128") == 0)
+ {
+ return type_new(TYPE_I128);
+ }
+ if (strcmp(c, "U128") == 0)
+ {
+ return type_new(TYPE_U128);
+ }
+
+ if (strcmp(c, "rune") == 0)
+ {
+ return type_new(TYPE_RUNE);
+ }
+ if (strcmp(c, "uint") == 0)
+ {
+ return type_new(TYPE_UINT);
+ }
+
+ Type *n = type_new(TYPE_STRUCT);
+ n->name = sanitize_mangled_name(c);
+ return n;
+ }
+
+ Type *n = xmalloc(sizeof(Type));
+ *n = *t;
+
+ if (t->name)
+ {
+ if (os && ns && strcmp(t->name, os) == 0)
+ {
+ n->name = xstrdup(ns);
+ n->kind = TYPE_STRUCT;
+ n->arg_count = 0;
+ n->args = NULL;
+ }
+
+ else if (p && c)
+ {
+ char suffix[256];
+ sprintf(suffix, "_%s", p); // e.g. "_T"
+ size_t nlen = strlen(t->name);
+ size_t slen = strlen(suffix);
+
+ if (nlen > slen && strcmp(t->name + nlen - slen, suffix) == 0)
+ {
+ // It ends in _T. Replace with _int (c), sanitizing for pointers
+ char *clean_c = sanitize_mangled_name(c);
+ char *new_name = xmalloc(nlen - slen + strlen(clean_c) + 2);
+ strncpy(new_name, t->name, nlen - slen);
+ new_name[nlen - slen] = 0;
+ strcat(new_name, "_");
+ strcat(new_name, clean_c);
+ free(clean_c);
+ n->name = new_name;
+ // Ensure it's concrete to prevent double mangling later
+ n->kind = TYPE_STRUCT;
+ n->arg_count = 0;
+ n->args = NULL;
+ }
+ else
+ {
+ n->name = xstrdup(t->name);
+ }
+ }
+ else
+ {
+ n->name = xstrdup(t->name);
+ }
+ }
+
+ if (t->kind == TYPE_POINTER || t->kind == TYPE_ARRAY)
+ {
+ n->inner = replace_type_formal(t->inner, p, c, os, ns);
+ }
+
+ if (n->arg_count > 0 && t->args)
+ {
+ n->args = xmalloc(sizeof(Type *) * t->arg_count);
+ for (int i = 0; i < t->arg_count; i++)
+ {
+ n->args[i] = replace_type_formal(t->args[i], p, c, os, ns);
+ }
+ }
+
+ return n;
+}
+
+// Helper to replace generic params in mangled names (e.g. Option_V_None -> Option_int_None)
+char *replace_mangled_part(const char *src, const char *param, const char *concrete)
+{
+ if (!src || !param || !concrete)
+ {
+ return src ? xstrdup(src) : NULL;
+ }
+
+ char *result = xmalloc(4096); // Basic buffer for simplicity
+ result[0] = 0;
+
+ const char *curr = src;
+ char *out = result;
+ int plen = strlen(param);
+
+ while (*curr)
+ {
+ // Check if param matches here
+ if (strncmp(curr, param, plen) == 0)
+ {
+ // Check boundaries: Must be delimited by quoted boundaries, OR underscores, OR string
+ // ends
+ int valid = 1;
+
+ // Check Prev: Start of string OR Underscore
+ if (curr > src)
+ {
+ if (*(curr - 1) != '_' && is_ident_char(*(curr - 1)))
+ {
+ valid = 0;
+ }
+ }
+
+ // Check Next: End of string OR Underscore
+ if (valid && curr[plen] != 0 && curr[plen] != '_' && is_ident_char(curr[plen]))
+ {
+ valid = 0;
+ }
+
+ if (valid)
+ {
+ strcpy(out, concrete);
+ out += strlen(concrete);
+ curr += plen;
+ continue;
+ }
+ }
+ *out++ = *curr++;
+ }
+ *out = 0;
+ return xstrdup(result);
+}
+
+ASTNode *copy_ast_replacing(ASTNode *n, const char *p, const char *c, const char *os,
+ const char *ns)
+{
+ if (!n)
+ {
+ return NULL;
+ }
+
+ ASTNode *new_node = xmalloc(sizeof(ASTNode));
+ *new_node = *n;
+
+ if (n->resolved_type)
+ {
+ new_node->resolved_type = replace_type_str(n->resolved_type, p, c, os, ns);
+ }
+ new_node->type_info = replace_type_formal(n->type_info, p, c, os, ns);
+
+ new_node->next = copy_ast_replacing(n->next, p, c, os, ns);
+
+ switch (n->type)
+ {
+ case NODE_FUNCTION:
+ new_node->func.name = xstrdup(n->func.name);
+ new_node->func.ret_type = replace_type_str(n->func.ret_type, p, c, os, ns);
+
+ char *tmp_args = replace_in_string(n->func.args, p, c);
+ if (os && ns)
+ {
+ char *tmp2 = replace_in_string(tmp_args, os, ns);
+ free(tmp_args);
+ tmp_args = tmp2;
+ }
+ if (p && c)
+ {
+ char *clean_c = sanitize_mangled_name(c);
+ char *tmp3 = replace_mangled_part(tmp_args, p, clean_c);
+ free(clean_c);
+ free(tmp_args);
+ tmp_args = tmp3;
+ }
+ new_node->func.args = tmp_args;
+
+ new_node->func.ret_type_info = replace_type_formal(n->func.ret_type_info, p, c, os, ns);
+ if (n->func.arg_types)
+ {
+ new_node->func.arg_types = xmalloc(sizeof(Type *) * n->func.arg_count);
+ for (int i = 0; i < n->func.arg_count; i++)
+ {
+ new_node->func.arg_types[i] =
+ replace_type_formal(n->func.arg_types[i], p, c, os, ns);
+ }
+ }
+
+ new_node->func.body = copy_ast_replacing(n->func.body, p, c, os, ns);
+ break;
+ case NODE_BLOCK:
+ new_node->block.statements = copy_ast_replacing(n->block.statements, p, c, os, ns);
+ break;
+ case NODE_RAW_STMT:
+ {
+ char *s1 = replace_in_string(n->raw_stmt.content, p, c);
+ if (os && ns)
+ {
+ char *s2 = replace_in_string(s1, os, ns);
+ free(s1);
+ s1 = s2;
+ }
+
+ if (p && c)
+ {
+ char *clean_c = sanitize_mangled_name(c);
+ char *s3 = replace_mangled_part(s1, p, clean_c);
+ free(clean_c);
+ free(s1);
+ s1 = s3;
+ }
+
+ new_node->raw_stmt.content = s1;
+ }
+ break;
+ case NODE_VAR_DECL:
+ new_node->var_decl.name = xstrdup(n->var_decl.name);
+ new_node->var_decl.type_str = replace_type_str(n->var_decl.type_str, p, c, os, ns);
+ new_node->var_decl.init_expr = copy_ast_replacing(n->var_decl.init_expr, p, c, os, ns);
+ break;
+ case NODE_RETURN:
+ new_node->ret.value = copy_ast_replacing(n->ret.value, p, c, os, ns);
+ break;
+ case NODE_EXPR_BINARY:
+ new_node->binary.left = copy_ast_replacing(n->binary.left, p, c, os, ns);
+ new_node->binary.right = copy_ast_replacing(n->binary.right, p, c, os, ns);
+ new_node->binary.op = xstrdup(n->binary.op);
+ break;
+ case NODE_EXPR_UNARY:
+ new_node->unary.op = xstrdup(n->unary.op);
+ new_node->unary.operand = copy_ast_replacing(n->unary.operand, p, c, os, ns);
+ break;
+ case NODE_EXPR_CALL:
+ new_node->call.callee = copy_ast_replacing(n->call.callee, p, c, os, ns);
+ new_node->call.args = copy_ast_replacing(n->call.args, p, c, os, ns);
+ new_node->call.arg_names = n->call.arg_names; // Share pointer (shallow copy)
+ new_node->call.arg_count = n->call.arg_count;
+ break;
+ case NODE_EXPR_VAR:
+ {
+ char *n1 = xstrdup(n->var_ref.name);
+ if (p && c)
+ {
+ char *clean_c = sanitize_mangled_name(c);
+ char *n2 = replace_mangled_part(n1, p, clean_c);
+ free(clean_c);
+ free(n1);
+ n1 = n2;
+ }
+ new_node->var_ref.name = n1;
+ }
+ break;
+ case NODE_FIELD:
+ new_node->field.name = xstrdup(n->field.name);
+ new_node->field.type = replace_type_str(n->field.type, p, c, os, ns);
+ break;
+ case NODE_EXPR_LITERAL:
+ if (n->literal.type_kind == 2)
+ {
+ new_node->literal.string_val = xstrdup(n->literal.string_val);
+ }
+ break;
+ case NODE_EXPR_MEMBER:
+ new_node->member.target = copy_ast_replacing(n->member.target, p, c, os, ns);
+ new_node->member.field = xstrdup(n->member.field);
+ break;
+ case NODE_EXPR_INDEX:
+ new_node->index.array = copy_ast_replacing(n->index.array, p, c, os, ns);
+ new_node->index.index = copy_ast_replacing(n->index.index, p, c, os, ns);
+ break;
+ case NODE_EXPR_CAST:
+ new_node->cast.target_type = replace_type_str(n->cast.target_type, p, c, os, ns);
+ new_node->cast.expr = copy_ast_replacing(n->cast.expr, p, c, os, ns);
+ break;
+ case NODE_EXPR_STRUCT_INIT:
+ new_node->struct_init.struct_name =
+ replace_type_str(n->struct_init.struct_name, p, c, os, ns);
+ ASTNode *h = NULL, *t = NULL, *curr = n->struct_init.fields;
+ while (curr)
+ {
+ ASTNode *cp = copy_ast_replacing(curr, p, c, os, ns);
+ cp->next = NULL;
+ if (!h)
+ {
+ h = cp;
+ }
+ else
+ {
+ t->next = cp;
+ }
+ t = cp;
+ curr = curr->next;
+ }
+ new_node->struct_init.fields = h;
+ break;
+ case NODE_IF:
+ new_node->if_stmt.condition = copy_ast_replacing(n->if_stmt.condition, p, c, os, ns);
+ new_node->if_stmt.then_body = copy_ast_replacing(n->if_stmt.then_body, p, c, os, ns);
+ new_node->if_stmt.else_body = copy_ast_replacing(n->if_stmt.else_body, p, c, os, ns);
+ break;
+ case NODE_WHILE:
+ new_node->while_stmt.condition = copy_ast_replacing(n->while_stmt.condition, p, c, os, ns);
+ new_node->while_stmt.body = copy_ast_replacing(n->while_stmt.body, p, c, os, ns);
+ break;
+ case NODE_FOR:
+ new_node->for_stmt.init = copy_ast_replacing(n->for_stmt.init, p, c, os, ns);
+ new_node->for_stmt.condition = copy_ast_replacing(n->for_stmt.condition, p, c, os, ns);
+ new_node->for_stmt.step = copy_ast_replacing(n->for_stmt.step, p, c, os, ns);
+ new_node->for_stmt.body = copy_ast_replacing(n->for_stmt.body, p, c, os, ns);
+ break;
+
+ case NODE_MATCH_CASE:
+ if (n->match_case.pattern)
+ {
+ char *s1 = replace_in_string(n->match_case.pattern, p, c);
+ if (os && ns)
+ {
+ char *s2 = replace_in_string(s1, os, ns);
+ free(s1);
+ s1 = s2;
+ char *colons = strstr(s1, "::");
+ if (colons)
+ {
+ colons[0] = '_';
+ memmove(colons + 1, colons + 2, strlen(colons + 2) + 1);
+ }
+ }
+ new_node->match_case.pattern = s1;
+ }
+ new_node->match_case.body = copy_ast_replacing(n->match_case.body, p, c, os, ns);
+ if (n->match_case.guard)
+ {
+ new_node->match_case.guard = copy_ast_replacing(n->match_case.guard, p, c, os, ns);
+ }
+ break;
+
+ case NODE_IMPL:
+ new_node->impl.struct_name = replace_type_str(n->impl.struct_name, p, c, os, ns);
+ new_node->impl.methods = copy_ast_replacing(n->impl.methods, p, c, os, ns);
+ break;
+ default:
+ break;
+ }
+ return new_node;
+}
+
+// Helper to sanitize type names for mangling (e.g. "int*" -> "intPtr")
+char *sanitize_mangled_name(const char *s)
+{
+ char *buf = xmalloc(strlen(s) * 4 + 1);
+ char *p = buf;
+ while (*s)
+ {
+ if (*s == '*')
+ {
+ strcpy(p, "Ptr");
+ p += 3;
+ }
+ else if (*s == ' ')
+ {
+ *p++ = '_';
+ }
+ else if ((*s >= 'a' && *s <= 'z') || (*s >= 'A' && *s <= 'Z') || (*s >= '0' && *s <= '9') ||
+ *s == '_')
+ {
+ *p++ = *s;
+ }
+ else
+ {
+ *p++ = '_';
+ }
+ s++;
+ }
+ *p = 0;
+ return buf;
+}
+
+FuncSig *find_func(ParserContext *ctx, const char *name)
+{
+ FuncSig *c = ctx->func_registry;
+ while (c)
+ {
+ if (strcmp(c->name, name) == 0)
+ {
+ return c;
+ }
+ c = c->next;
+ }
+ return NULL;
+}
+
+char *instantiate_function_template(ParserContext *ctx, const char *name, const char *concrete_type)
+{
+ GenericFuncTemplate *tpl = find_func_template(ctx, name);
+ if (!tpl)
+ {
+ return NULL;
+ }
+
+ char *clean_type = sanitize_mangled_name(concrete_type);
+ char *mangled = xmalloc(strlen(name) + strlen(clean_type) + 2);
+ sprintf(mangled, "%s_%s", name, clean_type);
+ free(clean_type);
+
+ if (find_func(ctx, mangled))
+ {
+ return mangled;
+ }
+
+ ASTNode *new_fn =
+ copy_ast_replacing(tpl->func_node, tpl->generic_param, concrete_type, NULL, NULL);
+ if (!new_fn || new_fn->type != NODE_FUNCTION)
+ {
+ return NULL;
+ }
+ free(new_fn->func.name);
+ new_fn->func.name = xstrdup(mangled);
+
+ register_func(ctx, mangled, new_fn->func.arg_count, new_fn->func.defaults,
+ new_fn->func.arg_types, new_fn->func.ret_type_info, new_fn->func.is_varargs, 0,
+ new_fn->token);
+
+ add_instantiated_func(ctx, new_fn);
+ return mangled;
+}
+
+char *process_fstring(ParserContext *ctx, const char *content)
+{
+ (void)ctx; // suppress unused parameter warning
+ char *gen = xmalloc(4096);
+
+ strcpy(gen, "({ static char _b[1024]; _b[0]=0; char _t[128]; ");
+
+ char *s = xstrdup(content);
+ char *cur = s;
+
+ while (*cur)
+ {
+ char *brace = cur;
+ while (*brace && *brace != '{')
+ {
+ brace++;
+ }
+
+ if (brace > cur)
+ {
+ char tmp = *brace;
+ *brace = 0;
+ strcat(gen, "strcat(_b, \"");
+ strcat(gen, cur);
+ strcat(gen, "\"); ");
+ *brace = tmp;
+ }
+
+ if (*brace == 0)
+ {
+ break;
+ }
+
+ char *p = brace + 1;
+ char *colon = NULL;
+ int depth = 1;
+
+ while (*p && depth > 0)
+ {
+ if (*p == '{')
+ {
+ depth++;
+ }
+ if (*p == '}')
+ {
+ depth--;
+ }
+ if (depth == 1 && *p == ':' && !colon)
+ {
+ colon = p;
+ }
+ if (depth == 0)
+ {
+ break;
+ }
+ p++;
+ }
+
+ *p = 0;
+ char *expr = brace + 1;
+ char *fmt = NULL;
+ if (colon)
+ {
+ *colon = 0;
+ fmt = colon + 1;
+ }
+
+ if (fmt)
+ {
+ strcat(gen, "sprintf(_t, \"%");
+ strcat(gen, fmt);
+ strcat(gen, "\", ");
+ strcat(gen, expr);
+ strcat(gen, "); strcat(_b, _t); ");
+ }
+ else
+ {
+ strcat(gen, "sprintf(_t, _z_str(");
+ strcat(gen, expr);
+ strcat(gen, "), ");
+ strcat(gen, expr);
+ strcat(gen, "); strcat(_b, _t); ");
+ }
+
+ cur = p + 1;
+ }
+
+ strcat(gen, "_b; })");
+ free(s);
+ return gen;
+}
+
+void register_impl(ParserContext *ctx, const char *trait, const char *strct)
+{
+ ImplReg *r = xmalloc(sizeof(ImplReg));
+ r->trait = xstrdup(trait);
+ r->strct = xstrdup(strct);
+ r->next = ctx->registered_impls;
+ ctx->registered_impls = r;
+}
+
+int check_impl(ParserContext *ctx, const char *trait, const char *strct)
+{
+ ImplReg *r = ctx->registered_impls;
+ while (r)
+ {
+ if (strcmp(r->trait, trait) == 0 && strcmp(r->strct, strct) == 0)
+ {
+ return 1;
+ }
+ r = r->next;
+ }
+ return 0;
+}
+
+void register_template(ParserContext *ctx, const char *name, ASTNode *node)
+{
+ GenericTemplate *t = xmalloc(sizeof(GenericTemplate));
+ t->name = xstrdup(name);
+ t->struct_node = node;
+ t->next = ctx->templates;
+ ctx->templates = t;
+}
+
+ASTNode *copy_fields_replacing(ParserContext *ctx, ASTNode *fields, const char *param,
+ const char *concrete)
+{
+ if (!fields)
+ {
+ return NULL;
+ }
+ ASTNode *n = ast_create(NODE_FIELD);
+ n->field.name = xstrdup(fields->field.name);
+
+ // Replace strings
+ n->field.type = replace_type_str(fields->field.type, param, concrete, NULL, NULL);
+
+ // Replace formal types (Deep Copy)
+ n->type_info = replace_type_formal(fields->type_info, param, concrete, NULL, NULL);
+
+ if (n->field.type && strchr(n->field.type, '_'))
+ {
+ // Parse potential generic: e.g. "MapEntry_int" -> instantiate("MapEntry", "int")
+ char *underscore = strrchr(n->field.type, '_');
+ if (underscore && underscore > n->field.type)
+ {
+ // Remove trailing '*' if present
+ char *type_copy = xstrdup(n->field.type);
+ char *star = strchr(type_copy, '*');
+ if (star)
+ {
+ *star = '\0';
+ }
+
+ underscore = strrchr(type_copy, '_');
+ if (underscore)
+ {
+ *underscore = '\0';
+ char *template_name = type_copy;
+ char *concrete_arg = underscore + 1;
+
+ // Check if this is actually a known generic template
+ GenericTemplate *gt = ctx->templates;
+ int found = 0;
+ while (gt)
+ {
+ if (strcmp(gt->name, template_name) == 0)
+ {
+ found = 1;
+ break;
+ }
+ gt = gt->next;
+ }
+
+ if (found)
+ {
+ instantiate_generic(ctx, template_name, concrete_arg);
+ }
+ }
+ free(type_copy);
+ }
+ }
+
+ n->next = copy_fields_replacing(ctx, fields->next, param, concrete);
+ return n;
+}
+
+void instantiate_generic(ParserContext *ctx, const char *tpl, const char *arg)
+{
+ // Ignore generic placeholders
+ if (strlen(arg) == 1 && isupper(arg[0]))
+ {
+ return;
+ }
+ if (strcmp(arg, "T") == 0)
+ {
+ return;
+ }
+
+ char *clean_arg = sanitize_mangled_name(arg);
+ char m[256];
+ sprintf(m, "%s_%s", tpl, clean_arg);
+ free(clean_arg);
+
+ Instantiation *c = ctx->instantiations;
+ while (c)
+ {
+ if (strcmp(c->name, m) == 0)
+ {
+ return; // Already instantiated, DO NOTHING.
+ }
+ c = c->next;
+ }
+
+ GenericTemplate *t = ctx->templates;
+ while (t)
+ {
+ if (strcmp(t->name, tpl) == 0)
+ {
+ break;
+ }
+ t = t->next;
+ }
+ if (!t)
+ {
+ zpanic("Unknown generic: %s", tpl);
+ }
+
+ Instantiation *ni = xmalloc(sizeof(Instantiation));
+ ni->name = xstrdup(m);
+ ni->struct_node = NULL; // Placeholder to break cycles
+ ni->next = ctx->instantiations;
+ ctx->instantiations = ni;
+
+ ASTNode *struct_node_copy = NULL;
+
+ if (t->struct_node->type == NODE_STRUCT)
+ {
+ ASTNode *i = ast_create(NODE_STRUCT);
+ i->strct.name = xstrdup(m);
+ i->strct.is_template = 0;
+ i->strct.fields = copy_fields_replacing(ctx, t->struct_node->strct.fields,
+ t->struct_node->strct.generic_param, arg);
+ struct_node_copy = i;
+ register_struct_def(ctx, m, i);
+ }
+ else if (t->struct_node->type == NODE_ENUM)
+ {
+ ASTNode *i = ast_create(NODE_ENUM);
+ i->enm.name = xstrdup(m);
+ i->enm.is_template = 0;
+ ASTNode *h = 0, *tl = 0;
+ ASTNode *v = t->struct_node->enm.variants;
+ while (v)
+ {
+ ASTNode *nv = ast_create(NODE_ENUM_VARIANT);
+ nv->variant.name = xstrdup(v->variant.name);
+ nv->variant.tag_id = v->variant.tag_id;
+ nv->variant.payload = replace_type_formal(
+ v->variant.payload, t->struct_node->enm.generic_param, arg, NULL, NULL);
+ char mangled_var[512];
+ sprintf(mangled_var, "%s_%s", m, nv->variant.name);
+ register_enum_variant(ctx, m, mangled_var, nv->variant.tag_id);
+ if (!h)
+ {
+ h = nv;
+ }
+ else
+ {
+ tl->next = nv;
+ }
+ tl = nv;
+ v = v->next;
+ }
+ i->enm.variants = h;
+ struct_node_copy = i;
+ }
+
+ ni->struct_node = struct_node_copy;
+
+ if (struct_node_copy)
+ {
+ struct_node_copy->next = ctx->instantiated_structs;
+ ctx->instantiated_structs = struct_node_copy;
+ }
+
+ GenericImplTemplate *it = ctx->impl_templates;
+ while (it)
+ {
+ if (strcmp(it->struct_name, tpl) == 0)
+ {
+ ASTNode *backup_next = it->impl_node->next;
+ it->impl_node->next = NULL; // Break link to isolate node
+ ASTNode *new_impl = copy_ast_replacing(it->impl_node, it->generic_param, arg, tpl, m);
+ it->impl_node->next = backup_next; // Restore
+
+ new_impl->impl.struct_name = xstrdup(m);
+ ASTNode *meth = new_impl->impl.methods;
+ while (meth)
+ {
+ char *suffix = strchr(meth->func.name, '_');
+ if (suffix)
+ {
+ char *new_name = xmalloc(strlen(m) + strlen(suffix) + 1);
+ sprintf(new_name, "%s%s", m, suffix);
+ free(meth->func.name);
+ meth->func.name = new_name;
+ register_func(ctx, new_name, meth->func.arg_count, meth->func.defaults,
+ meth->func.arg_types, meth->func.ret_type_info,
+ meth->func.is_varargs, 0, meth->token);
+ }
+
+ if (meth->func.ret_type && strchr(meth->func.ret_type, '_'))
+ {
+ char *ret_copy = xstrdup(meth->func.ret_type);
+ char *underscore = strrchr(ret_copy, '_');
+ if (underscore && underscore > ret_copy)
+ {
+ *underscore = '\0';
+ char *template_name = ret_copy;
+
+ // Check if this looks like a generic (e.g., "Option_V" or "Result_V")
+ GenericTemplate *gt = ctx->templates;
+ while (gt)
+ {
+ if (strcmp(gt->name, template_name) == 0)
+ {
+ // Found matching template, instantiate it
+ instantiate_generic(ctx, template_name, arg);
+ break;
+ }
+ gt = gt->next;
+ }
+ }
+ free(ret_copy);
+ }
+
+ meth = meth->next;
+ }
+ add_instantiated_func(ctx, new_impl);
+ }
+ it = it->next;
+ }
+}
+
+int is_file_imported(ParserContext *ctx, const char *p)
+{
+ ImportedFile *c = ctx->imported_files;
+ while (c)
+ {
+ if (strcmp(c->path, p) == 0)
+ {
+ return 1;
+ }
+ c = c->next;
+ }
+ return 0;
+}
+
+void mark_file_imported(ParserContext *ctx, const char *p)
+{
+ ImportedFile *f = xmalloc(sizeof(ImportedFile));
+ f->path = xstrdup(p);
+ f->next = ctx->imported_files;
+ ctx->imported_files = f;
+}
+
+char *parse_condition_raw(ParserContext *ctx, Lexer *l)
+{
+ (void)ctx; // suppress unused parameter warning
+ Token t = lexer_peek(l);
+ if (t.type == TOK_LPAREN)
+ {
+ Token op = lexer_next(l);
+ const char *s = op.start;
+ int d = 1;
+ while (d > 0)
+ {
+ t = lexer_next(l);
+ if (t.type == TOK_EOF)
+ {
+ zpanic("Unterminated condition");
+ }
+ if (t.type == TOK_LPAREN)
+ {
+ d++;
+ }
+ if (t.type == TOK_RPAREN)
+ {
+ d--;
+ }
+ }
+ const char *cs = s + 1;
+ int len = t.start - cs;
+ char *c = xmalloc(len + 1);
+ strncpy(c, cs, len);
+ c[len] = 0;
+ return c;
+ }
+ else
+ {
+ const char *start = l->src + l->pos;
+ while (1)
+ {
+ t = lexer_peek(l);
+ if (t.type == TOK_LBRACE || t.type == TOK_EOF)
+ {
+ break;
+ }
+ lexer_next(l);
+ }
+ int len = (l->src + l->pos) - start;
+ if (len == 0)
+ {
+ zpanic("Empty condition or missing body");
+ }
+ char *c = xmalloc(len + 1);
+ strncpy(c, start, len);
+ c[len] = 0;
+ return c;
+ }
+}
+
+char *rewrite_expr_methods(ParserContext *ctx, char *raw)
+{
+ if (!raw)
+ {
+ return NULL;
+ }
+
+ int in_expr = 0;
+ char *result = xmalloc(strlen(raw) * 4 + 100);
+ char *dest = result;
+ char *src = raw;
+
+ while (*src)
+ {
+ if (strncmp(src, "#{", 2) == 0)
+ {
+ in_expr = 1;
+ src += 2;
+ *dest++ = '(';
+ continue;
+ }
+
+ if (in_expr && *src == '}')
+ {
+ in_expr = 0;
+ *dest++ = ')';
+ src++;
+ continue;
+ }
+
+ if (in_expr && *src == '.')
+ {
+ char acc[64];
+ int i = 0;
+ char *back = src - 1;
+ while (back >= raw && (isalnum(*back) || *back == '_'))
+ {
+ back--;
+ }
+ back++;
+ while (back < src && i < 63)
+ {
+ acc[i++] = *back++;
+ }
+ acc[i] = 0;
+
+ char *vtype = find_symbol_type(ctx, acc);
+ if (!vtype)
+ {
+ *dest++ = *src++;
+ continue;
+ }
+
+ char method[64];
+ i = 0;
+ src++;
+ while (isalnum(*src) || *src == '_')
+ {
+ method[i++] = *src++;
+ }
+ method[i] = 0;
+
+ // Check for field access
+ char *base_t = xstrdup(vtype);
+ char *pc = strchr(base_t, '*');
+ int is_ptr_type = (pc != NULL);
+ if (pc)
+ {
+ *pc = 0;
+ }
+
+ ASTNode *def = find_struct_def(ctx, base_t);
+ int is_field = 0;
+ if (def && (def->type == NODE_STRUCT))
+ {
+ ASTNode *f = def->strct.fields;
+ while (f)
+ {
+ if (strcmp(f->field.name, method) == 0)
+ {
+ is_field = 1;
+ break;
+ }
+ f = f->next;
+ }
+ }
+ free(base_t);
+
+ if (is_field)
+ {
+ dest -= strlen(acc);
+ if (is_ptr_type)
+ {
+ dest += sprintf(dest, "(%s)->%s", acc, method);
+ }
+ else
+ {
+ dest += sprintf(dest, "(%s).%s", acc, method);
+ }
+ continue;
+ }
+
+ if (*src == '(')
+ {
+ dest -= strlen(acc);
+ int paren_depth = 0;
+ src++;
+ paren_depth++;
+
+ char ptr_check[64];
+ strcpy(ptr_check, vtype);
+ int is_ptr = (strchr(ptr_check, '*') != NULL);
+ if (is_ptr)
+ {
+ char *p = strchr(ptr_check, '*');
+ if (p)
+ {
+ *p = 0;
+ }
+ }
+
+ dest += sprintf(dest, "%s_%s(%s%s", ptr_check, method, is_ptr ? "" : "&", acc);
+
+ int has_args = 0;
+ while (*src && paren_depth > 0)
+ {
+ if (!isspace(*src))
+ {
+ has_args = 1;
+ }
+ if (*src == '(')
+ {
+ paren_depth++;
+ }
+ if (*src == ')')
+ {
+ paren_depth--;
+ }
+ if (paren_depth == 0)
+ {
+ break;
+ }
+ *dest++ = *src++;
+ }
+
+ if (has_args)
+ {
+ *dest++ = ')';
+ }
+ else
+ {
+ *dest++ = ')';
+ }
+
+ src++;
+ continue;
+ }
+ else
+ {
+ dest -= strlen(acc);
+ char ptr_check[64];
+ strcpy(ptr_check, vtype);
+ int is_ptr = (strchr(ptr_check, '*') != NULL);
+ if (is_ptr)
+ {
+ char *p = strchr(ptr_check, '*');
+ if (p)
+ {
+ *p = 0;
+ }
+ }
+ dest += sprintf(dest, "%s_%s(%s%s)", ptr_check, method, is_ptr ? "" : "&", acc);
+ continue;
+ }
+ }
+
+ if (!in_expr && strncmp(src, "::", 2) == 0)
+ {
+ char acc[64];
+ int i = 0;
+ char *back = src - 1;
+ while (back >= raw && (isalnum(*back) || *back == '_'))
+ {
+ back--;
+ }
+ back++;
+ while (back < src && i < 63)
+ {
+ acc[i++] = *back++;
+ }
+ acc[i] = 0;
+
+ src += 2;
+ char field[64];
+ i = 0;
+ while (isalnum(*src) || *src == '_')
+ {
+ field[i++] = *src++;
+ }
+ field[i] = 0;
+
+ dest -= strlen(acc);
+
+ if (*src == '(')
+ {
+ dest += sprintf(dest, "%s_%s", acc, field);
+ }
+ else
+ {
+ dest += sprintf(dest, "%s_%s", acc, field);
+ }
+ continue;
+ }
+
+ if (in_expr && isalpha(*src))
+ {
+ char tok[128];
+ int i = 0;
+ while ((isalnum(*src) || *src == '_') && i < 127)
+ {
+ tok[i++] = *src++;
+ }
+ tok[i] = 0;
+
+ while (*src == ' ' || *src == '\t')
+ {
+ src++;
+ }
+
+ if (strncmp(src, "::", 2) == 0)
+ {
+ src += 2;
+ char func_name[128];
+ snprintf(func_name, sizeof(func_name), "%s", tok);
+ char method[64];
+ i = 0;
+ while (isalnum(*src) || *src == '_')
+ {
+ method[i++] = *src++;
+ }
+ method[i] = 0;
+
+ while (*src == ' ' || *src == '\t')
+ {
+ src++;
+ }
+
+ if (*src == '(')
+ {
+ src++;
+
+ char mangled[256];
+ snprintf(mangled, sizeof(mangled), "%s_%s", func_name, method);
+
+ if (*src == ')')
+ {
+ dest += sprintf(dest, "%s()", mangled);
+ src++;
+ }
+ else
+ {
+ FuncSig *sig = find_func(ctx, func_name);
+ if (sig)
+ {
+ dest += sprintf(dest, "%s(&(%s){0}", mangled, func_name);
+ while (*src && *src != ')')
+ {
+ *dest++ = *src++;
+ }
+ *dest++ = ')';
+ if (*src == ')')
+ {
+ src++;
+ }
+ }
+ else
+ {
+ dest += sprintf(dest, "%s(", mangled);
+ while (*src && *src != ')')
+ {
+ *dest++ = *src++;
+ }
+ *dest++ = ')';
+ if (*src == ')')
+ {
+ src++;
+ }
+ }
+ }
+ continue;
+ }
+ }
+
+ strcpy(dest, tok);
+ dest += strlen(tok);
+ continue;
+ }
+
+ *dest++ = *src++;
+ }
+
+ *dest = 0;
+ return result;
+}
+
+char *consume_and_rewrite(ParserContext *ctx, Lexer *l)
+{
+ char *r = consume_until_semicolon(l);
+ char *rw = rewrite_expr_methods(ctx, r);
+ free(r);
+ return rw;
+}
+
+char *parse_and_convert_args(ParserContext *ctx, Lexer *l, char ***defaults_out, int *count_out,
+ Type ***types_out, char ***names_out, int *is_varargs_out)
+{
+ if (lexer_next(l).type != TOK_LPAREN)
+ {
+ zpanic("Expected '(' in function args");
+ }
+
+ char *buf = xmalloc(1024);
+ buf[0] = 0;
+ int count = 0;
+ char **defaults = xmalloc(sizeof(char *) * 16);
+ Type **types = xmalloc(sizeof(Type *) * 16);
+ char **names = xmalloc(sizeof(char *) * 16);
+
+ for (int i = 0; i < 16; i++)
+ {
+ defaults[i] = NULL;
+ types[i] = NULL;
+ names[i] = NULL;
+ }
+
+ if (lexer_peek(l).type != TOK_RPAREN)
+ {
+ while (1)
+ {
+ Token t = lexer_next(l);
+ // Handle 'self'
+ if (t.type == TOK_IDENT && strncmp(t.start, "self", 4) == 0 && t.len == 4)
+ {
+ names[count] = xstrdup("self");
+ if (ctx->current_impl_struct)
+ {
+ Type *st = NULL;
+ // Check for primitives to avoid creating struct int*
+ if (strcmp(ctx->current_impl_struct, "int") == 0)
+ {
+ st = type_new(TYPE_INT);
+ }
+ else if (strcmp(ctx->current_impl_struct, "float") == 0)
+ {
+ st = type_new(TYPE_F32);
+ }
+ else if (strcmp(ctx->current_impl_struct, "char") == 0)
+ {
+ st = type_new(TYPE_CHAR);
+ }
+ else if (strcmp(ctx->current_impl_struct, "bool") == 0)
+ {
+ st = type_new(TYPE_BOOL);
+ }
+ else if (strcmp(ctx->current_impl_struct, "string") == 0)
+ {
+ st = type_new(TYPE_STRING);
+ }
+ // Add other primitives as needed
+ else
+ {
+ st = type_new(TYPE_STRUCT);
+ st->name = xstrdup(ctx->current_impl_struct);
+ }
+ Type *pt = type_new_ptr(st);
+
+ char buf_type[256];
+ sprintf(buf_type, "%s*", ctx->current_impl_struct);
+ // Register 'self' with actual type in symbol table
+ add_symbol(ctx, "self", buf_type, pt);
+
+ types[count] = pt;
+
+ strcat(buf, "void* self");
+ }
+ else
+ {
+ strcat(buf, "void* self");
+ types[count] = type_new_ptr(type_new(TYPE_VOID));
+ add_symbol(ctx, "self", "void*", types[count]);
+ }
+ count++;
+ }
+ else
+ {
+ if (t.type != TOK_IDENT)
+ {
+ zpanic("Expected arg name");
+ }
+ char *name = token_strdup(t);
+ names[count] = name; // Store name
+ if (lexer_next(l).type != TOK_COLON)
+ {
+ zpanic("Expected ':'");
+ }
+
+ Type *arg_type = parse_type_formal(ctx, l);
+ char *type_str = type_to_string(arg_type);
+
+ add_symbol(ctx, name, type_str, arg_type);
+ types[count] = arg_type;
+
+ if (strlen(buf) > 0)
+ {
+ strcat(buf, ", ");
+ }
+
+ char *fn_ptr = strstr(type_str, "(*)");
+ if (arg_type->kind == TYPE_FUNCTION)
+ {
+ strcat(buf, "z_closure_T ");
+ strcat(buf, name);
+ }
+ else if (fn_ptr)
+ {
+ // Inject name into function pointer: int (*)(int) -> int (*name)(int)
+ int prefix_len = fn_ptr - type_str;
+ strncat(buf, type_str, prefix_len);
+ strcat(buf, " (*");
+ strcat(buf, name);
+ strcat(buf, ")");
+ strcat(buf, fn_ptr + 3); // Skip "(*)"
+ }
+ else
+ {
+ strcat(buf, type_str);
+ strcat(buf, " ");
+ strcat(buf, name);
+ }
+
+ count++;
+
+ if (lexer_peek(l).type == TOK_OP && is_token(lexer_peek(l), "="))
+ {
+ lexer_next(l);
+ Token val = lexer_next(l);
+ defaults[count - 1] = token_strdup(val);
+ }
+ }
+ if (lexer_peek(l).type == TOK_COMMA)
+ {
+ lexer_next(l);
+ // Check if next is ...
+ if (lexer_peek(l).type == TOK_ELLIPSIS)
+ {
+ lexer_next(l);
+ if (is_varargs_out)
+ {
+ *is_varargs_out = 1;
+ }
+ if (strlen(buf) > 0)
+ {
+ strcat(buf, ", ");
+ }
+ strcat(buf, "...");
+ break; // Must be last
+ }
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ if (lexer_next(l).type != TOK_RPAREN)
+ {
+ zpanic("Expected ')' after args");
+ }
+
+ *defaults_out = defaults;
+ *count_out = count;
+ *types_out = types;
+ *names_out = names;
+ return buf;
+}
+
+// Helper to find similar symbol name in current scope
+char *find_similar_symbol(ParserContext *ctx, const char *name)
+{
+ if (!ctx->current_scope)
+ {
+ return NULL;
+ }
+
+ const char *best_match = NULL;
+ int best_dist = 999;
+
+ // Check local scopes
+ Scope *s = ctx->current_scope;
+ while (s)
+ {
+ Symbol *sym = s->symbols;
+ while (sym)
+ {
+ int dist = levenshtein(name, sym->name);
+ if (dist < best_dist && dist <= 3)
+ {
+ best_dist = dist;
+ best_match = sym->name;
+ }
+ sym = sym->next;
+ }
+ s = s->parent;
+ }
+
+ // Check builtins/globals if any (simplified)
+ return best_match ? xstrdup(best_match) : NULL;
+}
+
+void register_plugin(ParserContext *ctx, const char *name, const char *alias)
+{
+ ImportedPlugin *p = xmalloc(sizeof(ImportedPlugin));
+ p->name = xstrdup(name);
+ p->alias = alias ? xstrdup(alias) : NULL;
+ p->next = ctx->imported_plugins;
+ ctx->imported_plugins = p;
+}
+
+const char *resolve_plugin(ParserContext *ctx, const char *name_or_alias)
+{
+ for (ImportedPlugin *p = ctx->imported_plugins; p; p = p->next)
+ {
+ // Check if it matches the alias
+ if (p->alias && strcmp(p->alias, name_or_alias) == 0)
+ {
+ return p->name;
+ }
+ // Check if it matches the name
+ if (strcmp(p->name, name_or_alias) == 0)
+ {
+ return p->name;
+ }
+ }
+ return NULL; // Plugin not found
+}