From 55247a3f12a9eee7ba3fd7ca6d8fcea7a82c20f3 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Sun, 11 Jan 2026 15:11:00 +0000 Subject: Added src/ folder. Now I will add the rest. --- src/parser/parser.h | 433 +++++ src/parser/parser_core.c | 618 +++++++ src/parser/parser_expr.c | 3627 +++++++++++++++++++++++++++++++++++++++++ src/parser/parser_stmt.c | 3900 +++++++++++++++++++++++++++++++++++++++++++++ src/parser/parser_type.c | 706 ++++++++ src/parser/parser_utils.c | 2459 ++++++++++++++++++++++++++++ 6 files changed, 11743 insertions(+) create mode 100644 src/parser/parser.h create mode 100644 src/parser/parser_core.c create mode 100644 src/parser/parser_expr.c create mode 100644 src/parser/parser_stmt.c create mode 100644 src/parser/parser_type.c create mode 100644 src/parser/parser_utils.c (limited to 'src/parser') 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 +#include +#include +#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 +#include +#include +#include +#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 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 +#include +#include +#include +#include +#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, ¶m_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, ¶m_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 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) + + // 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 + 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 + 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) + 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 + 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 + 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 \n#include \n#include \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 \n#include \n#include \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 +#include +#include +#include +#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 + if (lexer_peek(l).type == TOK_LANGLE) + { + lexer_next(l); // eat < + Type *arg = parse_type_formal(ctx, l); + + // Handle nested generics like Vec> 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, [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 +#include +#include +#include +#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 +} -- cgit v1.2.3