// parser.bux — Recursive descent parser (ported from parser.nim)
// Parses Bux source tokens into an AST.
module Parser {

extern func bux_strlen(s: String) -> uint;
extern func bux_str_to_int(s: String) -> int64;

// Forward declarations for mutual recursion
func parserParseExpr(p: *Parser) -> *Expr;
func parserParseStmt(p: *Parser) -> *Stmt;
func parserParseBlock(p: *Parser) -> *Block;
func parserParsePrimary(p: *Parser) -> *Expr;
func parserParsePostfixExpr(p: *Parser) -> *Expr;
func parserParseUnary(p: *Parser) -> *Expr;
func parserParseBinaryPrec(p: *Parser, minPrec: int) -> *Expr;

// ---------------------------------------------------------------------------
// Parser state
// ---------------------------------------------------------------------------
struct Parser {
    tokens: *LexToken,
    tokenCount: int,
    pos: int,
    diagCount: int,
    diags: *ParserDiag,
    structInitAllowed: bool,
}

struct ParserDiag {
    line: uint32,
    column: uint32,
    message: String,
}

// ---------------------------------------------------------------------------
// Token helpers
// ---------------------------------------------------------------------------

func parserCurToken(p: *Parser) -> LexToken {
    if p.pos < p.tokenCount {
        return p.tokens[p.pos];
    }
    var eof: LexToken;
    eof.kind = tkEndOfFile;
    eof.text = "";
    return eof;
}

func parserPeek(p: *Parser, ahead: int) -> int {
    let i: int = p.pos + ahead;
    if i >= 0 && i < p.tokenCount {
        return p.tokens[i].kind;
    }
    return tkEndOfFile;
}

// Lookahead to determine if '<' starts a type argument list.
func parserIsTypeArgListAhead(p: *Parser) -> bool {
    if !parserCheck(p, tkLt) { return false; }
    var depth: int = 0;
    var ahead: int = 0;
    while true {
        let kind: int = parserPeek(p, ahead);
        if kind == tkEndOfFile || kind == tkLBrace || kind == tkSemicolon {
            return false;
        }
        if kind == tkLt {
            depth = depth + 1;
        } else if kind == tkGt {
            depth = depth - 1;
            if depth == 0 {
                return true;
            }
        }
        ahead = ahead + 1;
    }
    return false;
}

func parserAdvance(p: *Parser) -> LexToken {
    let tok: LexToken = parserCurToken(p);
    if p.pos < p.tokenCount {
        p.pos = p.pos + 1;
    }
    return tok;
}

func parserCheck(p: *Parser, kind: int) -> bool {
    return parserPeek(p, 0) == kind;
}

func parserMatch(p: *Parser, kind: int) -> bool {
    if parserCheck(p, kind) {
        discard parserAdvance(p);
        return true;
    }
    return false;
}

func parserExpect(p: *Parser, kind: int, msg: String) -> LexToken {
    if parserCheck(p, kind) {
        return parserAdvance(p);
    }
    let tok: LexToken = parserCurToken(p);
    if p.diagCount < 256 {
        p.diags[p.diagCount] = ParserDiag {
            line: tok.line, column: tok.column, message: msg
        };
        p.diagCount = p.diagCount + 1;
    }
    return tok;
}

func parserEmitDiag(p: *Parser, line: uint32, col: uint32, msg: String) {
    if p.diagCount < 256 {
        p.diags[p.diagCount] = ParserDiag {
            line: line, column: col, message: msg
        };
        p.diagCount = p.diagCount + 1;
    }
}

func parserIsKeyword(kind: int) -> bool {
    if kind >= tkIf && kind <= tkSuper { return true; }
    if kind == tkSizeOf { return true; }
    return false;
}

func parserExpectIdentOrKeyword(p: *Parser, msg: String) -> LexToken {
    let tok: LexToken = parserCurToken(p);
    if tok.kind == tkIdent || parserIsKeyword(tok.kind) {
        return parserAdvance(p);
    }
    if p.diagCount < 256 {
        p.diags[p.diagCount] = ParserDiag {
            line: tok.line, column: tok.column, message: msg
        };
        p.diagCount = p.diagCount + 1;
    }
    return tok;
}

// ---------------------------------------------------------------------------
// Type parsing
// ---------------------------------------------------------------------------

func parserParseType(p: *Parser) -> *TypeExpr {
    let line: uint32 = parserCurToken(p).line;
    let col: uint32 = parserCurToken(p).column;
    let kindTok: int = parserPeek(p, 0);

    // &T (shared reference) and &mut T (mutable reference)
    if kindTok == tkAmp {
        discard parserAdvance(p); // &
        var isMut: bool = false;
        // Check for "mut" keyword
        if parserCheck(p, tkIdent) {
            let tok: LexToken = parserCurToken(p);
            if String_Eq(tok.text, "mut") {
                isMut = true;
                discard parserAdvance(p); // mut
            }
        }
        let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
        if isMut {
            te.kind = tekMutRef;
        } else {
            te.kind = tekRef;
        }
        te.line = line;
        te.column = col;
        te.pointerPointee = parserParseType(p);
        if te.pointerPointee != null as *TypeExpr {
            te.typeName = String_Concat(te.pointerPointee.typeName, "*");
        }
        return te;
    }

    // *T (pointer)
    if kindTok == tkStar {
        discard parserAdvance(p);
        let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
        te.kind = tekPointer;
        te.line = line;
        te.column = col;
        te.pointerPointee = parserParseType(p);
        // Set typeName to "Pointee*"
        if te.pointerPointee != null as *TypeExpr {
            te.typeName = String_Concat(te.pointerPointee.typeName, "*");
        }
        return te;
    }

    // func(Params) -> Ret
    if kindTok == tkFunc {
        discard parserAdvance(p);
        discard parserExpect(p, tkLParen, "expected '(' after 'func'");
        var params: *TypeExprList = null as *TypeExprList;
        var paramsTail: *TypeExprList = null as *TypeExprList;
        var count: int = 0;
        while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile {
            let paramTe: *TypeExpr = parserParseType(p);
            let node: *TypeExprList = bux_alloc(sizeof(TypeExprList)) as *TypeExprList;
            node.te = paramTe;
            node.next = null as *TypeExprList;
            if params == null as *TypeExprList {
                params = node;
            } else {
                paramsTail.next = node;
            }
            paramsTail = node;
            count = count + 1;
            if parserCheck(p, tkComma) {
                discard parserAdvance(p);
            }
        }
        discard parserExpect(p, tkRParen, "expected ')' after func params");
        var ret: *TypeExpr = null as *TypeExpr;
        if parserCheck(p, tkArrow) {
            discard parserAdvance(p);
            ret = parserParseType(p);
        }
        let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
        te.kind = tekFunc;
        te.line = line;
        te.column = col;
        te.funcParams = params;
        te.funcRet = ret;
        te.funcParamCount = count;
        return te;
    }

    // name
    let nameTok: LexToken = parserExpect(p, tkIdent, "expected type name");
    // self / Self -> tekSelf
    if String_Eq(nameTok.text, "self") || String_Eq(nameTok.text, "Self") {
        let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
        te.kind = tekSelf;
        te.line = nameTok.line;
        te.column = nameTok.column;
        te.typeName = "Self";
        return te;
    }
    let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
    te.kind = tekNamed;
    te.line = nameTok.line;
    te.column = nameTok.column;
    te.typeName = nameTok.text;

    // Optional <T, U> type args
    if parserCheck(p, tkLt) {
        discard parserAdvance(p); // <
        let arg0: LexToken = parserExpect(p, tkIdent, "expected type argument");
        te.typeArgName0 = arg0.text;
        te.typeArgCount = 1;
        if parserMatch(p, tkComma) {
            let arg1: LexToken = parserExpect(p, tkIdent, "expected type argument");
            te.typeArgName1 = arg1.text;
            te.typeArgCount = 2;
        }
        discard parserExpect(p, tkGt, "expected '>' to close type arguments");
    }
    return te;
}

// ---------------------------------------------------------------------------
// Forward declarations and helpers
// ---------------------------------------------------------------------------

func parserParseExpr(p: *Parser) -> *Expr;
func parserParseStmt(p: *Parser) -> *Stmt;
func parserParseBlock(p: *Parser) -> *Block;

func parserMakeExpr(kind: int, line: uint32, col: uint32) -> *Expr {
    let e: *Expr = bux_alloc(sizeof(Expr)) as *Expr;
    e.kind = kind;
    e.line = line;
    e.column = col;
    e.strValue = "";
    e.intValue = 0;
    e.boolValue = false;
    e.tokKind = 0;
    e.tokText = "";
    e.child1 = null as *Expr;
    e.child2 = null as *Expr;
    e.child3 = null as *Expr;
    e.refType = null as *TypeExpr;
    e.refBlock = null as *Block;
    e.genericCallee = "";
    e.genericTypeArg0 = "";
    e.genericTypeArg1 = "";
    e.genericTypeArgCount = 0;
    e.structName = "";
    e.structFieldCount = 0;
    e.callArgs = null as *ExprList;
    e.callArgCount = 0;
    return e;
}

// ---------------------------------------------------------------------------
// Primary expressions
// ---------------------------------------------------------------------------

func parserParsePrimary(p: *Parser) -> *Expr {
    while parserCheck(p, tkNewLine) {
        discard parserAdvance(p);
    }
    let tok: LexToken = parserCurToken(p);
    let line: uint32 = tok.line;
    let col: uint32 = tok.column;
    let kind: int = tok.kind;

    // Literals
    if kind == tkIntLiteral || kind == tkFloatLiteral || kind == tkStringLiteral
        || kind == tkCharLiteral || kind == tkBoolLiteral {
        discard parserAdvance(p);
        let e: *Expr = parserMakeExpr(ekLiteral, line, col);
        e.tokKind = kind;
        e.tokText = tok.text;
        if kind == tkIntLiteral {
            e.intValue = bux_str_to_int(tok.text) as int;
        }
        return e;
    }

    // Identifier
    if kind == tkIdent {
        discard parserAdvance(p);
        let e: *Expr = parserMakeExpr(ekIdent, line, col);
        e.strValue = tok.text;
        return e;
    }

    // self
    if kind == tkSelf {
        discard parserAdvance(p);
        return parserMakeExpr(ekSelf, line, col);
    }

    // null
    if kind == tkNull {
        discard parserAdvance(p);
        let e: *Expr = parserMakeExpr(ekLiteral, line, col);
        e.tokKind = tkNull;
        return e;
    }

    // sizeof(Type)
    if kind == tkSizeOf {
        discard parserAdvance(p);
        discard parserExpect(p, tkLParen, "expected '(' after sizeof");
        let e: *Expr = parserMakeExpr(ekSizeOf, line, col);
        e.refType = parserParseType(p);
        discard parserExpect(p, tkRParen, "expected ')' after sizeof type");
        return e;
    }

    // spawn Callee(args)
    if kind == tkSpawn {
        discard parserAdvance(p);
        let e: *Expr = parserMakeExpr(ekSpawn, line, col);
        e.child1 = parserParsePrimary(p);
        // Optional call arguments
        if parserCheck(p, tkLParen) {
            discard parserAdvance(p);
            if !parserCheck(p, tkRParen) {
                e.child2 = parserParseExpr(p);
                if parserMatch(p, tkComma) {
                    e.child3 = parserParseExpr(p);
                    while parserMatch(p, tkComma) {
                        discard parserParseExpr(p);
                    }
                }
            }
            discard parserExpect(p, tkRParen, "expected ')' after spawn arguments");
        }
        return e;
    }

    // #intrinsics
    if kind >= tkHashLine && kind <= tkHashModule {
        discard parserAdvance(p);
        let e: *Expr = parserMakeExpr(ekLiteral, line, col);
        e.intValue = kind;
        return e;
    }

    // ( expr )
    if kind == tkLParen {
        discard parserAdvance(p);
        let e: *Expr = parserParseExpr(p);
        discard parserExpect(p, tkRParen, "expected ')'");
        return e;
    }

    // Closure: |params| -> Ret { body }
    if kind == tkPipe {
        return parserParseClosure(p);
    }

    parserEmitDiag(p, line, col, "expected expression");
    return parserMakeExpr(ekLiteral, line, col);
}

// ---------------------------------------------------------------------------
// Closure: |params| -> Ret { body }
// ---------------------------------------------------------------------------

func parserParseClosure(p: *Parser) -> *Expr {
    let line: uint32 = parserCurToken(p).line;
    let col: uint32 = parserCurToken(p).column;
    discard parserExpect(p, tkPipe, "expected '|' to start closure params");

    let e: *Expr = parserMakeExpr(ekClosure, line, col);
    let params: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
    params.kind = dkFunc;
    params.paramCount = 0;

    // Parse params: name: Type
    while !parserCheck(p, tkPipe) && parserPeek(p, 0) != tkEndOfFile {
        while parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) {
            discard parserAdvance(p);
        }
        if parserCheck(p, tkPipe) || parserPeek(p, 0) == tkEndOfFile {
            break;
        }
        if params.paramCount >= 9 { break; }
        let nameTok: LexToken = parserExpectIdentOrKeyword(p, "expected parameter name in closure");
        discard parserExpect(p, tkColon, "expected ':' in closure parameter");
        let ptype: *TypeExpr = parserParseType(p);

        let idx: int = params.paramCount;
        if idx == 0 {
            params.param0.name = nameTok.text;
            params.param0.refParamType = ptype;
        } else if idx == 1 {
            params.param1.name = nameTok.text;
            params.param1.refParamType = ptype;
        } else if idx == 2 {
            params.param2.name = nameTok.text;
            params.param2.refParamType = ptype;
        } else if idx == 3 {
            params.param3.name = nameTok.text;
            params.param3.refParamType = ptype;
        } else if idx == 4 {
            params.param4.name = nameTok.text;
            params.param4.refParamType = ptype;
        } else if idx == 5 {
            params.param5.name = nameTok.text;
            params.param5.refParamType = ptype;
        } else if idx == 6 {
            params.param6.name = nameTok.text;
            params.param6.refParamType = ptype;
        } else if idx == 7 {
            params.param7.name = nameTok.text;
            params.param7.refParamType = ptype;
        } else if idx == 8 {
            params.param8.name = nameTok.text;
            params.param8.refParamType = ptype;
        }
        params.paramCount = params.paramCount + 1;
        if parserMatch(p, tkComma) { continue; }
        break;
    }

    discard parserExpect(p, tkPipe, "expected '|' to close closure params");
    e.closureParams = params;

    // Optional return type: -> Type
    if parserMatch(p, tkArrow) {
        e.refType = parserParseType(p);
    }

    // Body: { ... }
    e.refBlock = parserParseBlock(p);
    return e;
}

// ---------------------------------------------------------------------------
// Postfix: call, index, field access, as, is, ?
// ---------------------------------------------------------------------------

func parserParsePostfixExpr(p: *Parser) -> *Expr {
    var left: *Expr = parserParsePrimary(p);
    while true {
        let kind: int = parserPeek(p, 0);

        // Call: expr(args)
        if kind == tkLParen {
            discard parserAdvance(p);
            let line: uint32 = parserCurToken(p).line;
            let col: uint32 = parserCurToken(p).column;
            let e: *Expr = parserMakeExpr(ekCall, line, col);
            e.child1 = left;
            // Parse all arguments into linked list
            var argCount: int = 0;
            var firstArg: *ExprList = null as *ExprList;
            var lastArg: *ExprList = null as *ExprList;
            while !parserCheck(p, tkRParen) {
                var argExpr: *Expr = null as *Expr;
                var argName: String = "";
                // Named argument: name: value
                if parserPeek(p, 0) == tkIdent && parserPeek(p, 1) == tkColon {
                    let nameTok: LexToken = parserCurToken(p);
                    argName = nameTok.text;
                    discard parserAdvance(p); // ident
                    discard parserAdvance(p); // :
                    argExpr = parserParseExpr(p);
                } else {
                    argExpr = parserParseExpr(p);
                }
                let argNode: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList;
                argNode.expr = argExpr;
                argNode.next = null as *ExprList;
                argNode.argName = argName;
                if firstArg == null as *ExprList {
                    firstArg = argNode;
                    lastArg = argNode;
                } else {
                    lastArg.next = argNode;
                    lastArg = argNode;
                }
                argCount = argCount + 1;
                if !parserMatch(p, tkComma) { break; }
            }
            e.callArgs = firstArg;
            e.callArgCount = argCount;
            discard parserExpect(p, tkRParen, "expected ')'");
            left = e;
            continue;
        }

        // Generic call: Func<Type>(args)  or  Type<T> { ... }
        if kind == tkLt {
            if left.kind == ekIdent && parserIsTypeArgListAhead(p) {
                discard parserAdvance(p); // <
                let ta0: LexToken = parserExpect(p, tkIdent, "expected type argument");
                left.genericCallee = left.strValue;
                left.genericTypeArg0 = ta0.text;
                left.genericTypeArgCount = 1;
                if parserMatch(p, tkComma) {
                    let ta1: LexToken = parserExpect(p, tkIdent, "expected type argument");
                    left.genericTypeArg1 = ta1.text;
                    left.genericTypeArgCount = 2;
                }
                discard parserExpect(p, tkGt, "expected '>' to close type arguments");
                // After generic args, continue loop to handle call or struct init
                continue;
            }
        }

        // Index: expr[expr]
        if kind == tkLBracket {
            discard parserAdvance(p);
            let line: uint32 = parserCurToken(p).line;
            let col: uint32 = parserCurToken(p).column;
            let e: *Expr = parserMakeExpr(ekIndex, line, col);
            e.child1 = left;
            e.child2 = parserParseExpr(p);
            discard parserExpect(p, tkRBracket, "expected ']'");
            left = e;
            continue;
        }

        // .await
        if kind == tkDot {
            if parserPeek(p, 1) == tkAwait {
                discard parserAdvance(p); // .
                discard parserAdvance(p); // await
                let line: uint32 = parserCurToken(p).line;
                let col: uint32 = parserCurToken(p).column;
                let e: *Expr = parserMakeExpr(ekAwait, line, col);
                e.child1 = left;
                left = e;
                continue;
            }
        }

        // Field: expr.name
        if kind == tkDot {
            discard parserAdvance(p);
            let line: uint32 = parserCurToken(p).line;
            let col: uint32 = parserCurToken(p).column;
            let name: LexToken = parserExpectIdentOrKeyword(p, "expected field name");
            let e: *Expr = parserMakeExpr(ekField, line, col);
            e.child1 = left;
            e.strValue = name.text;
            left = e;
            continue;
        }

        // Path: expr::name
        if kind == tkColonColon {
            discard parserAdvance(p);
            let line: uint32 = parserCurToken(p).line;
            let col: uint32 = parserCurToken(p).column;
            let name: LexToken = parserExpectIdentOrKeyword(p, "expected path segment");
            let e: *Expr = parserMakeExpr(ekField, line, col);
            e.child1 = left;
            e.strValue = name.text;
            left = e;
            continue;
        }

        // as, is
        if kind == tkAs || kind == tkIs {
            discard parserAdvance(p);
            let line: uint32 = parserCurToken(p).line;
            let col: uint32 = parserCurToken(p).column;
            let ek: int = 0;
            if kind == tkAs { ek = ekCast; } else { ek = ekIs; }
            let e: *Expr = parserMakeExpr(ek, line, col);
            e.child1 = left;
            e.refType = parserParseType(p);
            left = e;
            continue;
        }

        // ? (try operator)
        if kind == tkQuestion {
            discard parserAdvance(p);
            let line: uint32 = parserCurToken(p).line;
            let col: uint32 = parserCurToken(p).column;
            let e: *Expr = parserMakeExpr(ekTry, line, col);
            e.child1 = left;
            left = e;
            continue;
        }

        // ! (unwrap operator)
        if kind == tkBang {
            discard parserAdvance(p);
            let line: uint32 = parserCurToken(p).line;
            let col: uint32 = parserCurToken(p).column;
            let e: *Expr = parserMakeExpr(ekUnwrap, line, col);
            e.child1 = left;
            left = e;
            continue;
        }

        // ++, --
        if kind == tkPlusPlus || kind == tkMinusMinus {
            discard parserAdvance(p);
            let line: uint32 = parserCurToken(p).line;
            let col: uint32 = parserCurToken(p).column;
            let e: *Expr = parserMakeExpr(ekPostfix, line, col);
            e.child1 = left;
            e.intValue = kind;
            left = e;
            continue;
        }

        // Struct init: TypeName { field: value, ... }
        if kind == tkLBrace {
            if p.structInitAllowed && left.kind == ekIdent {
                discard parserAdvance(p); // consume {
                let siLine: uint32 = parserCurToken(p).line;
                let siCol: uint32 = parserCurToken(p).column;
                let typeName: String = left.strValue;
                var fieldCount: int = 0;
                var firstField: *Expr = null as *Expr;
                var lastField: *Expr = null as *Expr;
                while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
                    while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
                    if parserCheck(p, tkRBrace) || parserPeek(p, 0) == tkEndOfFile { break; }
                    let sip: int = p.pos;
                    let fName: LexToken = parserExpectIdentOrKeyword(p, "expected field name");
                    discard parserExpect(p, tkColon, "expected ':'");
                    let fValue: *Expr = parserParseExpr(p);
                    let fExpr: *Expr = parserMakeExpr(ekField, fName.line, fName.column);
                    fExpr.strValue = fName.text;
                    fExpr.child1 = fValue;
                    if firstField == null as *Expr {
                        firstField = fExpr;
                        lastField = fExpr;
                    } else {
                        lastField.child3 = fExpr;
                        lastField = fExpr;
                    }
                    fieldCount = fieldCount + 1;
                    parserMatch(p, tkComma);
                    if p.pos == sip { discard parserAdvance(p); }
                }
                discard parserExpect(p, tkRBrace, "expected '}'");
                let e: *Expr = parserMakeExpr(ekStructInit, siLine, siCol);
                e.structName = typeName;
                e.structFieldCount = fieldCount;
                e.child1 = firstField;
                // Propagate generic type args from the identifier
                e.genericTypeArg0 = left.genericTypeArg0;
                e.genericTypeArg1 = left.genericTypeArg1;
                e.genericTypeArgCount = left.genericTypeArgCount;
                left = e;
                continue;
            } else {
                break;
            }
        }

        break;
    }
    return left;
}

// ---------------------------------------------------------------------------
// Binary expression (precedence climbing)
// All binary operators: arithmetic, comparison, logical, bitwise, assignment
// ---------------------------------------------------------------------------

func parserPrecedence(op: int) -> int {
    // Assignment operators are parsed by parserParseAssign, not here
    if op == tkPipePipe { return 2; }
    if op == tkAmpAmp { return 3; }
    if op == tkPipe { return 4; }
    if op == tkCaret { return 5; }
    if op == tkAmp { return 6; }
    if op == tkEq || op == tkNe { return 7; }
    if op == tkLt || op == tkLe || op == tkGt || op == tkGe { return 8; }
    if op == tkShl || op == tkShr { return 9; }
    if op == tkPlus || op == tkMinus { return 10; }
    if op == tkStar || op == tkSlash || op == tkPercent { return 11; }
    if op == tkStarStar { return 12; }
    return 0;
}

func parserParseUnary(p: *Parser) -> *Expr {
    while parserCheck(p, tkNewLine) {
        discard parserAdvance(p);
    }
    let tok: LexToken = parserCurToken(p);
    let line: uint32 = tok.line;
    let col: uint32 = tok.column;
    let kind: int = tok.kind;

    // -expr, !expr, *expr, &expr
    if kind == tkMinus || kind == tkBang || kind == tkStar || kind == tkAmp {
        discard parserAdvance(p);
        let e: *Expr = parserMakeExpr(ekUnary, line, col);
        e.intValue = kind;
        e.child1 = parserParseUnary(p);
        return e;
    }

    return parserParsePostfixExpr(p);
}

func parserParseBinaryPrec(p: *Parser, minPrec: int) -> *Expr {
    var left: *Expr = parserParseUnary(p);
    while true {
        while parserCheck(p, tkNewLine) {
            discard parserAdvance(p);
        }
        let op: int = parserPeek(p, 0);
        let prec: int = parserPrecedence(op);
        if prec < minPrec { break; }
        let opTok: LexToken = parserAdvance(p);
        let line: uint32 = opTok.line;
        let col: uint32 = opTok.column;
        let nextMinPrec: int = prec + 1;
        let right: *Expr = parserParseBinaryPrec(p, nextMinPrec);
        let e: *Expr = parserMakeExpr(ekBinary, line, col);
        e.intValue = opTok.kind;
        e.child1 = left;
        e.child2 = right;
        left = e;
    }
    return left;
}

func parserParseBinary(p: *Parser) -> *Expr {
    return parserParseBinaryPrec(p, 1);
}

// ---------------------------------------------------------------------------
// Range: lo .. hi  or  lo ..= hi
// ---------------------------------------------------------------------------

func parserParseRange(p: *Parser) -> *Expr {
    var left: *Expr = parserParseBinary(p);
    if parserCheck(p, tkDotDot) || parserCheck(p, tkDotDotEqual) {
        let inclusive: bool = parserCheck(p, tkDotDotEqual);
        let opTok: LexToken = parserAdvance(p);
        let right: *Expr = parserParseBinary(p);
        let e: *Expr = parserMakeExpr(ekRange, opTok.line, opTok.column);
        e.child1 = left;
        e.child2 = right;
        e.boolValue = inclusive;
        return e;
    }
    return left;
}

// ---------------------------------------------------------------------------
// Ternary: cond ? then : else
// ---------------------------------------------------------------------------

func parserParseTernary(p: *Parser) -> *Expr {
    var left: *Expr = parserParseRange(p);
    if parserMatch(p, tkQuestion) {
        let thenExpr: *Expr = parserParseExpr(p);
        discard parserExpect(p, tkColon, "expected ':' in ternary");
        let elseExpr: *Expr = parserParseExpr(p);
        let e: *Expr = parserMakeExpr(ekTernary, left.line, left.column);
        e.child1 = left;
        e.child2 = thenExpr;
        e.child3 = elseExpr;
        return e;
    }
    return left;
}

// ---------------------------------------------------------------------------
// Assignment: target = value (right-associative)
// ---------------------------------------------------------------------------

func parserParseAssign(p: *Parser) -> *Expr {
    let left: *Expr = parserParseTernary(p);
    let op: int = parserPeek(p, 0);
    if op == tkAssign || op == tkPlusAssign || op == tkMinusAssign || op == tkStarAssign || op == tkSlashAssign || op == tkPercentAssign || op == tkAmpAssign || op == tkPipeAssign || op == tkCaretAssign || op == tkShlAssign || op == tkShrAssign {
        let opTok: LexToken = parserAdvance(p);
        let right: *Expr = parserParseAssign(p);
        let e: *Expr = parserMakeExpr(ekAssign, opTok.line, opTok.column);
        e.intValue = opTok.kind;
        e.child1 = left;
        e.child2 = right;
        return e;
    }
    return left;
}

// ---------------------------------------------------------------------------
// Top-level expression
// ---------------------------------------------------------------------------

func parserParseExpr(p: *Parser) -> *Expr {
    return parserParseAssign(p);
}

// ---------------------------------------------------------------------------
// Statements
// ---------------------------------------------------------------------------

func parserParseStmt(p: *Parser) -> *Stmt {
    let tok: LexToken = parserCurToken(p);
    let line: uint32 = tok.line;
    let col: uint32 = tok.column;
    let kind: int = tok.kind;

    // let / var
    if kind == tkLet || kind == tkVar {
        let isVar: bool = (kind == tkVar);
        discard parserAdvance(p);
        let nameTok: LexToken = parserExpectIdentOrKeyword(p, "expected variable name");
        var typeExpr: *TypeExpr = null as *TypeExpr;
        if parserMatch(p, tkColon) {
            typeExpr = parserParseType(p);
        }
        var init: *Expr = null as *Expr;
        if parserMatch(p, tkAssign) {
            init = parserParseExpr(p);
        } else if !isVar {
            discard parserExpect(p, tkAssign, "expected '=' in let statement");
            init = parserParseExpr(p);
        }
        parserMatch(p, tkSemicolon); // optional ;

        let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
        s.kind = skLet;
        s.line = line;
        s.column = col;
        s.strValue = nameTok.text;
        s.boolValue = isVar;
        s.child1 = init;
        s.refStmtType = typeExpr;
        return s;
    }

    // discard expr
    if kind == tkDiscard {
        discard parserAdvance(p);
        var val: *Expr = null as *Expr;
        if !parserCheck(p, tkSemicolon) && !parserCheck(p, tkRBrace) && !parserCheck(p, tkNewLine) {
            val = parserParseExpr(p);
        }
        parserMatch(p, tkSemicolon);
        let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
        s.kind = skExpr;
        s.line = line;
        s.column = col;
        s.child1 = val;
        return s;
    }

    // defer expr
    if kind == tkDefer {
        discard parserAdvance(p);
        let val: *Expr = parserParseExpr(p);
        parserMatch(p, tkSemicolon);
        let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
        s.kind = skDefer;
        s.line = line;
        s.column = col;
        s.child1 = val;
        return s;
    }

    // if
    if kind == tkIf {
        discard parserAdvance(p);
        p.structInitAllowed = false;
        let cond: *Expr = parserParseExpr(p);
        p.structInitAllowed = true;
        let thenBlock: *Block = parserParseBlock(p);
        var elseBlock: *Block = null as *Block;
        while parserCheck(p, tkNewLine) {
            discard parserAdvance(p);
        }
        if parserMatch(p, tkElse) {
            if parserCheck(p, tkIf) {
                // else if → parse the if statement, wrap in a synthetic block
                let innerIf: *Stmt = parserParseStmt(p);
                elseBlock = bux_alloc(sizeof(Block)) as *Block;
                elseBlock.line = innerIf.line;
                elseBlock.column = innerIf.column;
                elseBlock.stmtCount = 1;
                elseBlock.firstStmt = innerIf;
                elseBlock.lastStmt = innerIf;
                innerIf.nextStmt = null as *Stmt;
            } else {
                elseBlock = parserParseBlock(p);
            }
        }
        let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
        s.kind = skIf;
        s.line = line;
        s.column = col;
        s.child1 = cond;
        s.refStmtBlock = thenBlock;
        s.refStmtElse = elseBlock;
        return s;
    }

    // while
    if kind == tkWhile {
        discard parserAdvance(p);
        p.structInitAllowed = false;
        let cond: *Expr = parserParseExpr(p);
        p.structInitAllowed = true;
        let body: *Block = parserParseBlock(p);
        let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
        s.kind = skWhile;
        s.line = line;
        s.column = col;
        s.child1 = cond;
        s.refStmtBlock = body;
        return s;
    }

    // loop
    if kind == tkLoop {
        discard parserAdvance(p);
        let body: *Block = parserParseBlock(p);
        let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
        s.kind = skLoop;
        s.line = line;
        s.column = col;
        s.refStmtBlock = body;
        return s;
    }

    // for
    if kind == tkFor {
        discard parserAdvance(p);
        let varName: LexToken = parserExpect(p, tkIdent, "expected loop variable");
        discard parserExpect(p, tkIn, "expected 'in'");
        p.structInitAllowed = false;
        let iter: *Expr = parserParseExpr(p);
        p.structInitAllowed = true;
        let body: *Block = parserParseBlock(p);
        let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
        s.kind = skFor;
        s.line = line;
        s.column = col;
        s.strValue = varName.text;
        s.child1 = iter;
        s.refStmtBlock = body;
        return s;
    }

    // return
    if kind == tkReturn {
        discard parserAdvance(p);
        var value: *Expr = null as *Expr;
        if !parserCheck(p, tkSemicolon) && !parserCheck(p, tkNewLine) && !parserCheck(p, tkRBrace) {
            value = parserParseExpr(p);
        }
        parserMatch(p, tkSemicolon);
        let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
        s.kind = skReturn;
        s.line = line;
        s.column = col;
        s.child1 = value;
        return s;
    }

    // break / continue
    if kind == tkBreak || kind == tkContinue {
        let sk: int = 0;
        if kind == tkBreak { sk = skBreak; } else { sk = skContinue; }
        discard parserAdvance(p);
        parserMatch(p, tkSemicolon);
        let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
        s.kind = sk;
        s.line = line;
        s.column = col;
        return s;
    }

    // match
    if kind == tkMatch {
        discard parserAdvance(p);
        p.structInitAllowed = false;
        let subject: *Expr = parserParseExpr(p);
        p.structInitAllowed = true;
        discard parserExpect(p, tkLBrace, "expected '{' to start match body");
        // Skip match body (simplified — just parse arms as empty)
        while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
            if parserCheck(p, tkNewLine) { discard parserAdvance(p); continue; }
            let mp: int = p.pos;
            discard parserParseExpr(p);  // pattern
            if parserMatch(p, tkFatArrow) {
                discard parserParseExpr(p);  // body
            }
            if p.pos == mp { discard parserAdvance(p); }
        }
        discard parserExpect(p, tkRBrace, "expected '}' to close match");
        let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
        s.kind = skMatch;
        s.line = line;
        s.column = col;
        s.child1 = subject;
        return s;
    }

    // switch
    if kind == tkSwitch {
        discard parserAdvance(p);
        p.structInitAllowed = false;
        let subject: *Expr = parserParseExpr(p);
        p.structInitAllowed = true;
        while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
        discard parserExpect(p, tkLBrace, "expected '{' to start switch body");
        var caseBlock: *Block = bux_alloc(sizeof(Block)) as *Block;
        caseBlock.line = line;
        caseBlock.column = col;
        caseBlock.stmtCount = 0;
        caseBlock.firstStmt = null as *Stmt;
        caseBlock.lastStmt = null as *Stmt;
        var defaultBody: *Block = null as *Block;
        while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
            while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
            if parserCheck(p, tkRBrace) || parserPeek(p, 0) == tkEndOfFile { break; }
            if parserCheck(p, tkDefault) {
                discard parserAdvance(p);
                discard parserExpect(p, tkColon, "expected ':' after default");
                let defaultStmt: *Stmt = parserParseStmt(p);
                defaultBody = bux_alloc(sizeof(Block)) as *Block;
                defaultBody.line = defaultStmt.line;
                defaultBody.column = defaultStmt.column;
                defaultBody.stmtCount = 1;
                defaultBody.firstStmt = defaultStmt;
                defaultBody.lastStmt = defaultStmt;
                defaultStmt.nextStmt = null as *Stmt;
                continue;
            }
            if parserCheck(p, tkCase) {
                discard parserAdvance(p);
                let caseVal: *Expr = parserParseExpr(p);
                discard parserExpect(p, tkColon, "expected ':' after case value");
                let caseStmt: *Stmt = parserParseStmt(p);
                let caseBody: *Block = bux_alloc(sizeof(Block)) as *Block;
                caseBody.line = caseStmt.line;
                caseBody.column = caseStmt.column;
                caseBody.stmtCount = 1;
                caseBody.firstStmt = caseStmt;
                caseBody.lastStmt = caseStmt;
                caseStmt.nextStmt = null as *Stmt;
                let c: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
                c.kind = skExpr;
                c.line = caseVal.line;
                c.column = caseVal.column;
                c.child1 = caseVal;
                c.refStmtBlock = caseBody;
                c.nextStmt = null as *Stmt;
                if caseBlock.firstStmt == null as *Stmt {
                    caseBlock.firstStmt = c;
                    caseBlock.lastStmt = c;
                } else {
                    caseBlock.lastStmt.nextStmt = c;
                    caseBlock.lastStmt = c;
                }
                caseBlock.stmtCount = caseBlock.stmtCount + 1;
                continue;
            }
            discard parserAdvance(p);
        }
        discard parserExpect(p, tkRBrace, "expected '}' to close switch");
        let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
        s.kind = skSwitch;
        s.line = line;
        s.column = col;
        s.child1 = subject;
        s.refStmtBlock = caseBlock;
        s.refStmtElse = defaultBody;
        return s;
    }

    // Expression statement
    if kind == tkNewLine || kind == tkSemicolon {
        discard parserAdvance(p);
        return null as *Stmt;
    }

    let expr: *Expr = parserParseExpr(p);
    parserMatch(p, tkSemicolon);
    let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
    s.kind = skExpr;
    s.line = line;
    s.column = col;
    s.child1 = expr;
    return s;
}

// ---------------------------------------------------------------------------
// Block: { stmt* }
// ---------------------------------------------------------------------------

func parserParseBlock(p: *Parser) -> *Block {
    discard parserExpect(p, tkLBrace, "expected '{'");
    let b: *Block = bux_alloc(sizeof(Block)) as *Block;
    b.line = parserCurToken(p).line;
    b.column = parserCurToken(p).column;
    b.stmtCount = 0;
    b.firstStmt = null as *Stmt;
    b.lastStmt = null as *Stmt;

    // Build AST with parserParseStmt (may not consume all tokens)
    var blockPos: int = p.pos;
    while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
        while parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) {
            discard parserAdvance(p);
        }
        if parserCheck(p, tkRBrace) || parserPeek(p, 0) == tkEndOfFile {
            break;
        }
        let beforePos: int = p.pos;
        let s: *Stmt = parserParseStmt(p);
        if p.pos == beforePos {
            discard parserAdvance(p);
            continue;
        }
        if s != null as *Stmt {
            s.nextStmt = null as *Stmt;
            if b.firstStmt == null as *Stmt {
                b.firstStmt = s;
                b.lastStmt = s;
            } else {
                b.lastStmt.nextStmt = s;
                b.lastStmt = s;
            }
            b.stmtCount = b.stmtCount + 1;
        }
    }
    // Reliable token consumption: reset to after { and use depth counter
    p.pos = blockPos;
    var depth: int = 1;
    while depth > 0 && parserPeek(p, 0) != tkEndOfFile {
        if parserCheck(p, tkLBrace) { depth = depth + 1; }
        else if parserCheck(p, tkRBrace) { depth = depth - 1; }
        discard parserAdvance(p);
    }
    return b;
}

// ---------------------------------------------------------------------------
// Function parameters
// ---------------------------------------------------------------------------

func parserParseParamList(p: *Parser) -> *Decl {
    let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
    d.kind = dkFunc;
    d.paramCount = 0;

    discard parserExpect(p, tkLParen, "expected '('");
    while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile {
        while parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) {
            discard parserAdvance(p);
        }
        if parserCheck(p, tkRParen) || parserPeek(p, 0) == tkEndOfFile {
            break;
        }
        if d.paramCount >= 9 { break; }
        let nameTok: LexToken = parserExpectIdentOrKeyword(p, "expected parameter name");
        discard parserExpect(p, tkColon, "expected ':' in parameter");
        let ptype: *TypeExpr = parserParseType(p);
        var defExpr: *Expr = null as *Expr;
        if parserMatch(p, tkAssign) {
            defExpr = parserParseExpr(p);
        }

        if d.paramCount == 0 {
            d.param0.name = nameTok.text;
            d.param0.refParamType = ptype;
            d.param0.defaultExpr = defExpr;
        } else if d.paramCount == 1 {
            d.param1.name = nameTok.text;
            d.param1.refParamType = ptype;
            d.param1.defaultExpr = defExpr;
        } else if d.paramCount == 2 {
            d.param2.name = nameTok.text;
            d.param2.refParamType = ptype;
            d.param2.defaultExpr = defExpr;
        } else if d.paramCount == 3 {
            d.param3.name = nameTok.text;
            d.param3.refParamType = ptype;
            d.param3.defaultExpr = defExpr;
        } else if d.paramCount == 4 {
            d.param4.name = nameTok.text;
            d.param4.refParamType = ptype;
            d.param4.defaultExpr = defExpr;
        } else if d.paramCount == 5 {
            d.param5.name = nameTok.text;
            d.param5.refParamType = ptype;
            d.param5.defaultExpr = defExpr;
        } else if d.paramCount == 6 {
            d.param6.name = nameTok.text;
            d.param6.refParamType = ptype;
            d.param6.defaultExpr = defExpr;
        } else if d.paramCount == 7 {
            d.param7.name = nameTok.text;
            d.param7.refParamType = ptype;
            d.param7.defaultExpr = defExpr;
        } else if d.paramCount == 8 {
            d.param8.name = nameTok.text;
            d.param8.refParamType = ptype;
            d.param8.defaultExpr = defExpr;
        }
        d.paramCount = d.paramCount + 1;

        if parserMatch(p, tkComma) { continue; }
        break;
    }
    discard parserExpect(p, tkRParen, "expected ')'");
    return d;
}

// ---------------------------------------------------------------------------
// Type parameters: <T: Bound, U: Bound2>
// ---------------------------------------------------------------------------

func parserParseTypeParams(p: *Parser, d: *Decl) {
    if !parserCheck(p, tkLt) { return; }
    discard parserAdvance(p);
    let tp0: LexToken = parserExpect(p, tkIdent, "expected type param");
    d.typeParam0 = tp0.text;
    d.typeParamCount = 1;
    if parserMatch(p, tkColon) {
        let bound0: LexToken = parserExpect(p, tkIdent, "expected trait bound name");
        d.typeParam0Bound = bound0.text;
    }
    if parserMatch(p, tkComma) {
        let tp1: LexToken = parserExpect(p, tkIdent, "expected type param");
        d.typeParam1 = tp1.text;
        d.typeParamCount = 2;
        if parserMatch(p, tkColon) {
            let bound1: LexToken = parserExpect(p, tkIdent, "expected trait bound name");
            d.typeParam1Bound = bound1.text;
        }
    }
    discard parserExpect(p, tkGt, "expected '>'");
}

// ---------------------------------------------------------------------------
// Declarations
// ---------------------------------------------------------------------------

func parserParseFuncDecl(p: *Parser, isPublic: bool, isExtern: bool, isAsync: bool) -> *Decl {
    let line: uint32 = parserCurToken(p).line;
    let col: uint32 = parserCurToken(p).column;
    discard parserExpect(p, tkFunc, "expected 'func'");

    let nameTok: LexToken = parserExpectIdentOrKeyword(p, "expected function name");
    let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
    d.kind = dkFunc;
    d.line = line;
    d.column = col;
    d.isPublic = isPublic;
    d.isAsync = isAsync;
    d.strValue = nameTok.text;

    // Type params <T: Bound, U: Bound2>
    parserParseTypeParams(p, d);

    // Params
    let params: *Decl = parserParseParamList(p);
    d.paramCount = params.paramCount;
    d.param0 = params.param0;
    d.param1 = params.param1;
    d.param2 = params.param2;
    d.param3 = params.param3;
    d.param4 = params.param4;
    d.param5 = params.param5;
    d.param6 = params.param6;
    d.param7 = params.param7;
    d.param8 = params.param8;

    // Return type
    if parserMatch(p, tkArrow) {
        d.retType = parserParseType(p);
    }

    // Body
    if !isExtern && parserCheck(p, tkLBrace) {
        d.refBody = parserParseBlock(p);
    } else {
        d.refBody = null as *Block;
    }

    return d;
}

func parserParseStructDecl(p: *Parser, isPublic: bool) -> *Decl {
    let line: uint32 = parserCurToken(p).line;
    let col: uint32 = parserCurToken(p).column;
    discard parserExpect(p, tkStruct, "expected 'struct'");
    let nameTok: LexToken = parserExpectIdentOrKeyword(p, "expected struct name");

    let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
    d.fields = bux_alloc(256 as uint * sizeof(StructField)) as *StructField;
    if d.fields == null as *StructField {
        PrintLine("ERROR: bux_alloc returned null for fields");
    }
    d.kind = dkStruct;
    d.line = line;
    d.column = col;
    d.isPublic = isPublic;
    d.strValue = nameTok.text;
    var fieldCount: int = 0;

    // Type params <T: Bound, U: Bound2>
    parserParseTypeParams(p, d);

    discard parserExpect(p, tkLBrace, "expected '{'");
    while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
        if fieldCount >= 256 { break; }
        if parserCheck(p, tkNewLine) { discard parserAdvance(p); continue; }
        if parserCheck(p, tkSemicolon) { discard parserAdvance(p); continue; }
        let beforePos: int = p.pos;
        let fName: LexToken = parserExpectIdentOrKeyword(p, "expected field name");
        if fieldCount >= 250 && fieldCount <= 256 {
            PrintLine(String_Concat("RAW fieldCount=", bux_int_to_str(fieldCount as int64)));
            PrintLine(String_Concat("RAW fName=", fName.text));
            PrintLine(String_Concat("RAW pos=", bux_int_to_str(p.pos as int64)));
        }
        discard parserExpect(p, tkColon, "expected ':' in struct field");
        let fType: *TypeExpr = parserParseType(p);
        parserMatch(p, tkSemicolon);
        // Infinite-loop safeguard
        if p.pos == beforePos {
            discard parserAdvance(p);
            continue;
        }

        d.fields[fieldCount].name = fName.text;
        d.fields[fieldCount].refFieldType = fType;
        fieldCount = fieldCount + 1;
    }
    d.fieldCount = fieldCount;
    discard parserExpect(p, tkRBrace, "expected '}'");
    return d;
}

func parserParseEnumDecl(p: *Parser, isPublic: bool) -> *Decl {
    let line: uint32 = parserCurToken(p).line;
    let col: uint32 = parserCurToken(p).column;
    discard parserExpect(p, tkEnum, "expected 'enum'");
    let nameTok: LexToken = parserExpect(p, tkIdent, "expected enum name");

    let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
    d.kind = dkEnum;
    d.line = line;
    d.column = col;
    d.isPublic = isPublic;
    d.strValue = nameTok.text;

    discard parserExpect(p, tkLBrace, "expected '{'");
    while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
        if d.variantCount >= 9 { break; }
        if parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) { discard parserAdvance(p); continue; }
        let vName: LexToken = parserExpect(p, tkIdent, "expected variant name");

        var v: EnumVariant;
        v.name = vName.text;
        v.fieldCount = 0;
        v.fieldTypeName0 = "";
        v.fieldTypeName1 = "";

        // Optional (Type, Type) data
        if parserMatch(p, tkLParen) {
            let t0: LexToken = parserExpect(p, tkIdent, "expected data type");
            v.fieldTypeName0 = t0.text;
            v.fieldCount = 1;
            if parserMatch(p, tkComma) {
                let t1: LexToken = parserExpect(p, tkIdent, "expected data type");
                v.fieldTypeName1 = t1.text;
                v.fieldCount = 2;
            }
            discard parserExpect(p, tkRParen, "expected ')'");
        }

        if d.variantCount == 0 { d.variant0 = v; }
        else if d.variantCount == 1 { d.variant1 = v; }
        else if d.variantCount == 2 { d.variant2 = v; }
        else if d.variantCount == 3 { d.variant3 = v; }
        else if d.variantCount == 4 { d.variant4 = v; }
        else if d.variantCount == 5 { d.variant5 = v; }
        else if d.variantCount == 6 { d.variant6 = v; }
        else if d.variantCount == 7 { d.variant7 = v; }
        else if d.variantCount == 8 { d.variant8 = v; }
        d.variantCount = d.variantCount + 1;

        parserMatch(p, tkComma);
        if parserCheck(p, tkNewLine) { discard parserAdvance(p); }
    }
    discard parserExpect(p, tkRBrace, "expected '}'");
    return d;
}

func parserParseImportDecl(p: *Parser, isPublic: bool) -> *Decl {
    let line: uint32 = parserCurToken(p).line;
    let col: uint32 = parserCurToken(p).column;
    discard parserExpect(p, tkImport, "expected 'import'");

    let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
    d.kind = dkUse;
    d.line = line;
    d.column = col;
    d.isPublic = isPublic;

    // Parse path: Std::Io::PrintLine
    var pathStr: String = "";
    var segCount: int = 0;
    while parserCheck(p, tkIdent) || (segCount > 0 && parserCheck(p, tkColonColon) && parserPeek(p, 1) != tkLBrace) {
        if segCount > 0 {
            discard parserAdvance(p); // ::
            if String_Len(pathStr) > 0 {
                let tmp: *char8 = bux_alloc(256) as *char8;
                // Append to path string (simplified)
                pathStr = String_Concat(pathStr, "::");
            }
        }
        let seg: LexToken = parserExpect(p, tkIdent, "expected module path segment");
        pathStr = String_Concat(pathStr, seg.text);
        segCount = segCount + 1;
    }
    d.usePath = pathStr;

    // Optional ::{name1, name2} or ::*
    if parserMatch(p, tkColonColon) {
        if parserCheck(p, tkLBrace) {
            discard parserAdvance(p); // {
            var names: String = "";
            while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
                while parserCheck(p, tkNewLine) {
                    discard parserAdvance(p);
                }
                if parserCheck(p, tkRBrace) || parserPeek(p, 0) == tkEndOfFile {
                    break;
                }
                let n: LexToken = parserExpect(p, tkIdent, "expected import name");
                names = String_Concat(names, n.text);
                names = String_Concat(names, ",");
                if !parserMatch(p, tkComma) { break; }
            }
            discard parserExpect(p, tkRBrace, "expected '}'");
            d.useNames = names;
            d.useKind = 2; // ukMulti
        } else if parserCheck(p, tkStar) {
            discard parserAdvance(p); // *
            d.useKind = 1; // ukGlob
        } else {
            d.useKind = 0; // ukSingle
        }
    } else {
        d.useKind = 0; // ukSingle
    }

    parserMatch(p, tkSemicolon);
    return d;
}

func parserParseExternDecl(p: *Parser, isPublic: bool) -> *Decl {
    let line: uint32 = parserCurToken(p).line;
    let col: uint32 = parserCurToken(p).column;
    discard parserExpect(p, tkExtern, "expected 'extern'");

    if parserCheck(p, tkFunc) {
        let d: *Decl = parserParseFuncDecl(p, isPublic, true, false);
        d.kind = dkExternFunc;
        return d;
    }

    let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
    d.kind = dkExternFunc;
    d.line = line;
    d.column = col;
    d.isPublic = isPublic;
    return d;
}

// ---------------------------------------------------------------------------
// Interface declaration
// ---------------------------------------------------------------------------

func parserParseInterfaceDecl(p: *Parser, isPublic: bool) -> *Decl {
    let line: uint32 = parserCurToken(p).line;
    let col: uint32 = parserCurToken(p).column;
    discard parserExpect(p, tkInterface, "expected 'interface'");
    let nameTok: LexToken = parserExpect(p, tkIdent, "expected interface name");
    discard parserExpect(p, tkLBrace, "expected '{' to start interface body");

    let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
    d.kind = dkInterface;
    d.line = line;
    d.column = col;
    d.isPublic = isPublic;
    d.strValue = nameTok.text;

    var methods: *Decl = null as *Decl;
    var lastMethod: *Decl = null as *Decl;
    while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
        if parserCheck(p, tkNewLine) { discard parserAdvance(p); continue; }
        if parserCheck(p, tkFunc) {
            let m: *Decl = parserParseFuncDecl(p, false, false, false);
            if methods == null as *Decl {
                methods = m;
                lastMethod = m;
            } else {
                lastMethod.childDecl2 = m;
                lastMethod = m;
            }
            d.methodCount = d.methodCount + 1;
        } else {
            break;
        }
    }
    d.childDecl1 = methods;
    discard parserExpect(p, tkRBrace, "expected '}' to close interface");
    return d;
}

// ---------------------------------------------------------------------------
// Top-level declaration
// ---------------------------------------------------------------------------

func parserParseDecl(p: *Parser) -> *Decl {
    // Skip newlines before declaration (matching bootstrap behavior)
    while parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) {
        discard parserAdvance(p);
    }
    let isPublic: bool = parserMatch(p, tkPub);

    // Parse @[Checked] attribute
    var isChecked: int = 0;
    if parserCheck(p, tkAt) {
        discard parserAdvance(p); // @
        if parserCheck(p, tkLBracket) {
            discard parserAdvance(p); // [
            if parserCheck(p, tkIdent) {
                let attrName: LexToken = parserCurToken(p);
                if String_Eq(attrName.text, "Checked") {
                    isChecked = 1;
                }
                discard parserAdvance(p); // attribute name
            }
            if parserCheck(p, tkRBracket) {
                discard parserAdvance(p); // ]
            }
        }
        // Skip newlines after attribute before the declaration
        while parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) {
            discard parserAdvance(p);
        }
    }

    let kind: int = parserPeek(p, 0);

    if kind == tkAsync && parserPeek(p, 1) == tkFunc {
        discard parserAdvance(p); // async
        let d: *Decl = parserParseFuncDecl(p, isPublic, false, true);
        d.isChecked = isChecked;
        return d;
    }
    if kind == tkFunc {
        let d: *Decl = parserParseFuncDecl(p, isPublic, false, false);
        d.isChecked = isChecked;
        return d;
    }
    if kind == tkStruct { return parserParseStructDecl(p, isPublic); }
    if kind == tkEnum { return parserParseEnumDecl(p, isPublic); }
    if kind == tkImport { return parserParseImportDecl(p, isPublic); }
    if kind == tkExtern { return parserParseExternDecl(p, isPublic); }
    if kind == tkInterface { return parserParseInterfaceDecl(p, isPublic); }

    if kind == tkExtend {
        discard parserAdvance(p);
        let line: uint32 = parserCurToken(p).line;
        let col: uint32 = parserCurToken(p).column;
        let typeName: LexToken = parserExpect(p, tkIdent, "expected type name");

        let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
        d.kind = dkImpl;
        d.line = line;
        d.column = col;
        d.isPublic = isPublic;
        d.strValue = typeName.text;

        // Optional <T: Bound>
        parserParseTypeParams(p, d);

        // Optional 'for InterfaceName'
        if parserMatch(p, tkFor) {
            let ifaceName: LexToken = parserExpect(p, tkIdent, "expected interface name");
            d.strValue2 = ifaceName.text;
        }

        discard parserExpect(p, tkLBrace, "expected '{'");
        while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
            if parserCheck(p, tkNewLine) { discard parserAdvance(p); continue; }
            if parserCheck(p, tkFunc) {
                let m: *Decl = parserParseFuncDecl(p, false, false, false);
                // Store method in linked list
                if d.methodCount == 0 {
                    d.childDecl1 = m;
                } else if d.methodCount == 1 {
                    d.childDecl1.childDecl2 = m;
                }
                d.methodCount = d.methodCount + 1;
            } else {
                break;
            }
        }
        discard parserExpect(p, tkRBrace, "expected '}'");
        return d;
    }

    if kind == tkModule {
        discard parserAdvance(p);
        let name: LexToken = parserExpect(p, tkIdent, "expected module name");
        // Parse optional path segments (::Name)
        while parserCheck(p, tkColonColon) {
            discard parserAdvance(p);
            discard parserExpect(p, tkIdent, "expected module path segment");
        }
        let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
        d.kind = dkModule;
        d.strValue = name.text;
        // Parse module body with braces
        if parserCheck(p, tkLBrace) {
            discard parserAdvance(p); // consume {
            var items: *Decl = null as *Decl;
            var lastItem: *Decl = null as *Decl;
            while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
                if parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) {
                    discard parserAdvance(p);
                    continue;
                }
                let beforePos: int = p.pos;
                let item: *Decl = parserParseDecl(p);
                if item != null as *Decl {
                    item.childDecl2 = null as *Decl;
                    if items == null as *Decl {
                        items = item;
                        lastItem = item;
                    } else {
                        lastItem.childDecl2 = item;
                        lastItem = item;
                    }
                }
                // Infinite-loop safeguard: if no progress, skip token
                if p.pos == beforePos {
                    discard parserAdvance(p);
                }
            }
            discard parserExpect(p, tkRBrace, "expected '}' to close module");
            d.childDecl1 = items; // first item of module
        } else {
            parserMatch(p, tkSemicolon);
        }
        return d;
    }

    if kind == tkConst {
        discard parserAdvance(p);
        let name: LexToken = parserExpect(p, tkIdent, "expected const name");
        discard parserExpect(p, tkColon, "expected ':'");
        let ct: *TypeExpr = parserParseType(p);
        discard parserExpect(p, tkAssign, "expected '='");
        let val: *Expr = parserParseExpr(p);
        parserMatch(p, tkSemicolon);
        let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
        d.kind = dkConst;
        d.strValue = name.text;
        d.constType = ct;
        d.constValue = val;
        return d;
    }

    // Unknown declaration — skip one token and return null
    // (was: skip to newline/}, which was destructive)
    parserEmitDiag(p, parserCurToken(p).line, parserCurToken(p).column, "skipping unknown declaration");
    discard parserAdvance(p);
    return null as *Decl;
}

// ---------------------------------------------------------------------------
// Module parsing
// ---------------------------------------------------------------------------

func Parser_Parse(tokens: *LexToken, tokenCount: int) -> *Module {
    let p: *Parser = bux_alloc(sizeof(Parser)) as *Parser;
    p.tokens = tokens;
    p.tokenCount = tokenCount;
    p.pos = 0;
    p.structInitAllowed = true;
    let diagBuf: *ParserDiag = bux_alloc(256 as uint * sizeof(ParserDiag)) as *ParserDiag;
    p.diags = diagBuf;
    p.diagCount = 0;

    let mod: *Module = bux_alloc(sizeof(Module)) as *Module;
    mod.name = "";
    mod.itemCount = 0;
    mod.firstItem = null as *Decl;

    // Parse declarations until EOF
    while parserPeek(p, 0) != tkEndOfFile {
        if parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) {
            discard parserAdvance(p);
            continue;
        }
        let beforePos: int = p.pos;
        let decl: *Decl = parserParseDecl(p);
        if decl != null as *Decl {
            decl.childDecl2 = mod.firstItem; // push front
            mod.firstItem = decl;
            mod.itemCount = mod.itemCount + 1;
        }
        // Infinite-loop safeguard: if no progress, skip token
        if p.pos == beforePos {
            discard parserAdvance(p);
        }
    }

    return mod;
}

}
