import t, c
from stdint import *
import memhub
import string
from .tokens import (Token, TokenType, Keyword, TokOp)
from .base import (
    AST, ASTCtx, ASTKind, OpKind, ASTFlag,
    _set_pos, _binop_from_op, _augop_from_op, _cmpop_from_op, _copy_str, _inherit_pos,
    _init_ast,
    CONST_INT, CONST_FLOAT, CONST_STR, CONST_BOOL, CONST_NONE,
    FLAG_IS_ASYNC,
)
from .exprs import (
    BoolOp, BinOp, UnaryOp, Lambda, IfExp, Dict, Set,
    ListComp, SetComp, DictComp, GeneratorExp,
    Await, Yield, YieldFrom, FormattedValue, JoinedStr,
    Constant, NamedExpr, Attribute, Subscript, Starred, Name,
    List, Tuple, Slice, Call, Compare, OpNode,
)
from .stmts import (
    Module, Expression, Interactive, FunctionType,
    FunctionDef, ClassDef, Return, Delete, Assign, AugAssign, AnnAssign,
    For, While, If, With, Raise, Try, Assert, Global, Nonlocal,
    Pass, Break, Continue, Expr, Import, ImportFrom, Match,
)
from .astaux import (
    ExceptHandler, Arguments, Arg, Keyword as KwNode, Alias, WithItem, Comprehension,
)
from .match import (
    MatchCase, MatchValue, MatchSingleton, MatchSequence,
    MatchMapping, MatchClass, MatchStar, MatchAs, MatchOr,
)
from .lexer import Lexer, _lexer_init, tokenize, new_lexer


# ============================================================
# Parser 状态
# ============================================================
class Parser:
    tokens: Token | t.CPtr              # token 链表头
    cur: Token | t.CPtr                 # 当前 token
    pool: memhub.MemManager | t.CPtr    # 内存池
    error_count: t.CInt                 # 错误计数
    pending_decorators: list[AST | t.CPtr] | t.CPtr    # 待处理的装饰器 list

    def __new__(self, pool: memhub.MemManager | t.CPtr):
        ptr: Parser | t.CPtr = pool.alloc(Parser.__sizeof__())
        if ptr:
            string.memset(ptr, 0, Parser.__sizeof__())
        return ptr


def new_parser(pool: memhub.MemManager | t.CPtr) -> Parser | t.CPtr:
    """从 pool 分配并返回 Parser（跨模块安全包装）"""
    return Parser(pool)


def _parser_init(ps: Parser | t.CPtr, tokens: Token | t.CPtr,
                 pool: memhub.MemManager | t.CPtr):
    if ps is None: return
    ps.tokens = tokens
    ps.cur = tokens
    ps.pool = pool
    ps.error_count = 0
    ps.pending_decorators = None


# ============================================================
# Token 导航辅助
# ============================================================

def _advance(ps: Parser | t.CPtr):
    """前进到下一个 token"""
    if ps.cur is None: return
    if ps.cur.next is not None:
        ps.cur = ps.cur.next


def _cur_type(ps: Parser | t.CPtr) -> t.CInt:
    if ps.cur is None: return TokenType.EndMarker
    return ps.cur.type


def _cur_op(ps: Parser | t.CPtr) -> t.CInt:
    if ps.cur is None: return 0
    return ps.cur.op_subtype


def _cur_kw(ps: Parser | t.CPtr) -> t.CInt:
    if ps.cur is None: return 0
    return ps.cur.kw_subtype


def _cur_str(ps: Parser | t.CPtr) -> str:
    if ps.cur is None: return None
    return ps.cur.str_val


def _cur_lineno(ps: Parser | t.CPtr) -> t.CInt:
    if ps.cur is None: return 0
    return ps.cur.lineno


def _cur_col(ps: Parser | t.CPtr) -> t.CInt:
    if ps.cur is None: return 0
    return ps.cur.col_offset


def _skip_nl(ps: Parser | t.CPtr):
    """跳过 TokenType.Nl token"""
    while ps.cur is not None and ps.cur.type == TokenType.Nl:
        ps.cur = ps.cur.next


def _skip_nls_and_newlines(ps: Parser | t.CPtr):
    """跳过 TokenType.Nl 和 TokenType.NewLine（用于续行）"""
    while ps.cur is not None:
        if ps.cur.type == TokenType.Nl or ps.cur.type == TokenType.NewLine:
            ps.cur = ps.cur.next
        else:
            break


def _expect_type(ps: Parser | t.CPtr, ttype: t.CInt) -> t.CInt:
    """期望当前 token 是指定类型，是则前进并返回 1，否则返回 0"""
    if _cur_type(ps) == ttype:
        _advance(ps)
        return 1
    ps.error_count += 1
    return 0


def _expect_op(ps: Parser | t.CPtr, op_id: t.CInt) -> t.CInt:
    """期望当前是某个运算符"""
    if _cur_type(ps) == TokenType.Op and _cur_op(ps) == op_id:
        _advance(ps)
        return 1
    ps.error_count += 1
    return 0


def _match_op(ps: Parser | t.CPtr, op_id: t.CInt) -> t.CInt:
    """检查但不消耗"""
    if _cur_type(ps) == TokenType.Op and _cur_op(ps) == op_id:
        return 1
    return 0


def _accept_op(ps: Parser | t.CPtr, op_id: t.CInt) -> t.CInt:
    """如果匹配则消耗并返回 1"""
    if _match_op(ps, op_id):
        _advance(ps)
        return 1
    return 0


# ============================================================
# 常量构造辅助（新 API：Constant 直接接受 lineno/col）
# ============================================================

def _new_const_int(pool: memhub.MemManager | t.CPtr, val: t.CInt64T,
                   lineno: t.CInt, col: t.CInt) -> AST | t.CPtr:
    return Constant(pool, CONST_INT, val, 0.0, None, lineno, col)


def _new_const_float(pool: memhub.MemManager | t.CPtr, val: t.CDouble,
                     lineno: t.CInt, col: t.CInt) -> AST | t.CPtr:
    return Constant(pool, CONST_FLOAT, 0, val, None, lineno, col)


def _new_const_str(pool: memhub.MemManager | t.CPtr, val: str,
                   lineno: t.CInt, col: t.CInt) -> AST | t.CPtr:
    return Constant(pool, CONST_STR, 0, 0.0, val, lineno, col)


def _new_const_bool(pool: memhub.MemManager | t.CPtr, val: t.CInt,
                    lineno: t.CInt, col: t.CInt) -> AST | t.CPtr:
    return Constant(pool, CONST_BOOL, val, 0.0, None, lineno, col)


def _new_const_none(pool: memhub.MemManager | t.CPtr,
                    lineno: t.CInt, col: t.CInt) -> AST | t.CPtr:
    return Constant(pool, CONST_NONE, 0, 0.0, None, lineno, col)


# ============================================================
# ctx 辅助（递归修正赋值目标的 ctx）
# ============================================================

def _set_ctx(node: AST | t.CPtr, ctx: t.CInt):
    """设置节点的 ctx 字段（按 kind 转型后赋值）"""
    if node is None: return
    k: t.CInt = node.kind()
    if k == ASTKind.Name:
        nm: Name | t.CPtr = (Name | t.CPtr)(node)
        nm.ctx = ctx
    elif k == ASTKind.Attribute:
        at: Attribute | t.CPtr = (Attribute | t.CPtr)(node)
        at.ctx = ctx
    elif k == ASTKind.Subscript:
        sb: Subscript | t.CPtr = (Subscript | t.CPtr)(node)
        sb.ctx = ctx
    elif k == ASTKind.Tuple:
        tp: Tuple | t.CPtr = (Tuple | t.CPtr)(node)
        tp.ctx = ctx
    elif k == ASTKind.List:
        ls: List | t.CPtr = (List | t.CPtr)(node)
        ls.ctx = ctx
    elif k == ASTKind.Starred:
        st: Starred | t.CPtr = (Starred | t.CPtr)(node)
        st.ctx = ctx


def _fix_store_ctx(node: AST | t.CPtr):
    """递归修正赋值目标的 ctx 为 Store"""
    if node is None: return
    _set_ctx(node, ASTCtx.Store)
    k: t.CInt = node.kind()
    if k == ASTKind.Tuple or k == ASTKind.List:
        tp_elts: list[AST | t.CPtr] | t.CPtr = None
        if k == ASTKind.Tuple:
            tp: Tuple | t.CPtr = (Tuple | t.CPtr)(node)
            tp_elts = tp.elts
        else:
            ls: List | t.CPtr = (List | t.CPtr)(node)
            tp_elts = ls.elts
        if tp_elts is not None:
            n: t.CSizeT = tp_elts.__len__()
            i: t.CSizeT = 0
            while i < n:
                cur: AST | t.CPtr = tp_elts.get(i)
                _fix_store_ctx(cur)
                i += 1
    elif k == ASTKind.Starred:
        st2: Starred | t.CPtr = (Starred | t.CPtr)(node)
        _fix_store_ctx(st2.value)


# ============================================================
# 表达式解析
# ============================================================

def _parse_expr(ps: Parser | t.CPtr) -> AST | t.CPtr:
    """expr: yield_expr | namedexpr_test"""
    if _cur_kw(ps) == Keyword.Yield:
        return _parse_yield(ps)
    return _parse_namedexpr(ps)


def _parse_namedexpr(ps: Parser | t.CPtr) -> AST | t.CPtr:
    """namedexpr_test: test [':=' test]"""
    left: AST | t.CPtr = _parse_test(ps)
    if left is None: return None
    if _match_op(ps, TokOp.ColonEq):
        _advance(ps)
        right: AST | t.CPtr = _parse_test(ps)
        node: AST | t.CPtr = NamedExpr(ps.pool, left, right)
        _set_pos(node, left.lineno, left.col_offset, 0, 0)
        return node
    return left


def _parse_test(ps: Parser | t.CPtr) -> AST | t.CPtr:
    """test: or_test ['if' or_test 'else' test] | lambdef"""
    if _cur_kw(ps) == Keyword.Lambda:
        return _parse_lambda(ps)
    left: AST | t.CPtr = _parse_or_test(ps)
    if left is None: return None
    if _cur_kw(ps) == Keyword.If:
        _advance(ps)
        test: AST | t.CPtr = _parse_or_test(ps)
        if _cur_kw(ps) == Keyword.Else:
            _advance(ps)
            orelse: AST | t.CPtr = _parse_test(ps)
            node: AST | t.CPtr = IfExp(ps.pool, test, left, orelse)
            _set_pos(node, left.lineno, left.col_offset, 0, 0)
            return node
        ps.error_count += 1
        return left
    return left


def _parse_lambda(ps: Parser | t.CPtr) -> AST | t.CPtr:
    """lambdef: 'lambda' [varargslist] ':' test"""
    lineno: t.CInt = _cur_lineno(ps)
    col: t.CInt = _cur_col(ps)
    _advance(ps)  # lambda
    args: AST | t.CPtr = None
    if not _match_op(ps, TokOp.Colon):
        args = _parse_varargslist(ps, 1)
    if not _expect_op(ps, TokOp.Colon):
        return None
    body: AST | t.CPtr = _parse_test(ps)
    node: AST | t.CPtr = Lambda(ps.pool, args, body)
    _set_pos(node, lineno, col, 0, 0)
    return node


def _parse_or_test(ps: Parser | t.CPtr) -> AST | t.CPtr:
    """or_test: and_test ('or' and_test)*"""
    left: AST | t.CPtr = _parse_and_test(ps)
    if left is None: return None
    if _cur_kw(ps) == Keyword.Or:
        values: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
        values.append(left)
        while _cur_kw(ps) == Keyword.Or:
            _advance(ps)
            right: AST | t.CPtr = _parse_and_test(ps)
            if right is None: break
            values.append(right)
        node: AST | t.CPtr = BoolOp(ps.pool, OpKind.Or, values)
        _set_pos(node, left.lineno, left.col_offset, 0, 0)
        return node
    return left


def _parse_and_test(ps: Parser | t.CPtr) -> AST | t.CPtr:
    """and_test: not_test ('and' not_test)*"""
    left: AST | t.CPtr = _parse_not_test(ps)
    if left is None: return None
    if _cur_kw(ps) == Keyword.And:
        values: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
        values.append(left)
        while _cur_kw(ps) == Keyword.And:
            _advance(ps)
            right: AST | t.CPtr = _parse_not_test(ps)
            if right is None: break
            values.append(right)
        node: AST | t.CPtr = BoolOp(ps.pool, OpKind.And, values)
        _set_pos(node, left.lineno, left.col_offset, 0, 0)
        return node
    return left


def _parse_not_test(ps: Parser | t.CPtr) -> AST | t.CPtr:
    """not_test: 'not' not_test | comparison"""
    if _cur_kw(ps) == Keyword.Not:
        lineno: t.CInt = _cur_lineno(ps)
        col: t.CInt = _cur_col(ps)
        _advance(ps)
        operand: AST | t.CPtr = _parse_not_test(ps)
        node: AST | t.CPtr = UnaryOp(ps.pool, OpKind.Not, operand)
        _set_pos(node, lineno, col, 0, 0)
        return node
    return _parse_comparison(ps)


def _parse_comparison(ps: Parser | t.CPtr) -> AST | t.CPtr:
    """comparison: bitor_expr (comp_op bitor_expr)*

    单比较设计：a < b → Compare(left=a, op=Lt, right=b)
    链式比较 a < b < c → BoolOp(And, [Compare(a,<,b), Compare(b,<,c)])
    """
    left: AST | t.CPtr = _parse_bitor(ps)
    if left is None: return None

    op_id: t.CInt = _match_comp_op(ps)
    if op_id == OpKind.NoneOp:
        return left
    _advance_comp_op(ps)
    right: AST | t.CPtr = _parse_bitor(ps)
    # 单比较：构造 ops 和 comparators list
    ops: list[t.CInt] | t.CPtr = list[t.CInt](ps.pool, 8)
    ops.append(op_id)
    comparators: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
    comparators.append(right)
    first_cmp: AST | t.CPtr = Compare(ps.pool, left, ops, comparators)
    _set_pos(first_cmp, left.lineno, left.col_offset, 0, 0)

    # 检查链式比较
    next_op: t.CInt = _match_comp_op(ps)
    if next_op == OpKind.NoneOp:
        return first_cmp

    # 链式：a < b < c → BoolOp(And, [Compare(a,<,b), Compare(b,<,c), ...])
    values: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
    values.append(first_cmp)
    cur_left: AST | t.CPtr = right
    while next_op != OpKind.NoneOp:
        _advance_comp_op(ps)
        cur_right: AST | t.CPtr = _parse_bitor(ps)
        ops2: list[t.CInt] | t.CPtr = list[t.CInt](ps.pool, 8)
        ops2.append(next_op)
        comparators2: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
        comparators2.append(cur_right)
        cmp: AST | t.CPtr = Compare(ps.pool, cur_left, ops2, comparators2)
        _set_pos(cmp, cur_left.lineno, cur_left.col_offset, 0, 0)
        values.append(cmp)
        cur_left = cur_right
        next_op = _match_comp_op(ps)
    node: AST | t.CPtr = BoolOp(ps.pool, OpKind.And, values)
    _set_pos(node, left.lineno, left.col_offset, 0, 0)
    return node


def _match_comp_op(ps: Parser | t.CPtr) -> t.CInt:
    """检查当前是否是比较运算符，返回 OP_* 或 OpKind.NoneOp"""
    if _cur_type(ps) == TokenType.Op:
        op_id: t.CInt = _cur_op(ps)
        if op_id == TokOp.EqEq: return OpKind.Eq
        if op_id == TokOp.ExclaimEq: return OpKind.Ne
        if op_id == TokOp.Less: return OpKind.Lt
        if op_id == TokOp.LessEq: return OpKind.Le
        if op_id == TokOp.Greater: return OpKind.Gt
        if op_id == TokOp.GreaterEq: return OpKind.Ge
    if _cur_kw(ps) == Keyword.Is:
        if ps.cur.next is not None and ps.cur.next.type == TokenType.Name and ps.cur.next.kw_subtype == Keyword.Not:
            return OpKind.IsNot
        return OpKind.Is
    if _cur_kw(ps) == Keyword.Not:
        if ps.cur.next is not None and ps.cur.next.kw_subtype == Keyword.In:
            return OpKind.NotIn
    if _cur_kw(ps) == Keyword.In:
        return OpKind.In
    return OpKind.NoneOp


def _advance_comp_op(ps: Parser | t.CPtr):
    """消耗比较运算符 token"""
    if _cur_type(ps) == TokenType.Op:
        _advance(ps)
        return
    if _cur_kw(ps) == Keyword.Is:
        _advance(ps)
        if _cur_kw(ps) == Keyword.Not:
            _advance(ps)
        return
    if _cur_kw(ps) == Keyword.Not:
        _advance(ps)
        if _cur_kw(ps) == Keyword.In:
            _advance(ps)
        return
    if _cur_kw(ps) == Keyword.In:
        _advance(ps)


def _parse_bitor(ps: Parser | t.CPtr) -> AST | t.CPtr:
    left: AST | t.CPtr = _parse_bitxor(ps)
    if left is None: return None
    while _match_op(ps, TokOp.VBar):
        _advance(ps)
        right: AST | t.CPtr = _parse_bitxor(ps)
        node: AST | t.CPtr = BinOp(ps.pool, left, OpKind.BitOr, right)
        _set_pos(node, left.lineno, left.col_offset, 0, 0)
        left = node
    return left


def _parse_bitxor(ps: Parser | t.CPtr) -> AST | t.CPtr:
    left: AST | t.CPtr = _parse_bitand(ps)
    if left is None: return None
    while _match_op(ps, TokOp.Caret):
        _advance(ps)
        right: AST | t.CPtr = _parse_bitand(ps)
        node: AST | t.CPtr = BinOp(ps.pool, left, OpKind.BitXor, right)
        _set_pos(node, left.lineno, left.col_offset, 0, 0)
        left = node
    return left


def _parse_bitand(ps: Parser | t.CPtr) -> AST | t.CPtr:
    left: AST | t.CPtr = _parse_shift(ps)
    if left is None: return None
    while _match_op(ps, TokOp.Amp):
        _advance(ps)
        right: AST | t.CPtr = _parse_shift(ps)
        node: AST | t.CPtr = BinOp(ps.pool, left, OpKind.BitAnd, right)
        _set_pos(node, left.lineno, left.col_offset, 0, 0)
        left = node
    return left


def _parse_shift(ps: Parser | t.CPtr) -> AST | t.CPtr:
    left: AST | t.CPtr = _parse_arith(ps)
    if left is None: return None
    while _match_op(ps, TokOp.LtLt) or _match_op(ps, TokOp.GtGt):
        op_token: t.CInt = _cur_op(ps)
        _advance(ps)
        right: AST | t.CPtr = _parse_arith(ps)
        ast_op: t.CInt = OpKind.LShift
        if op_token == TokOp.GtGt:
            ast_op = OpKind.RShift
        node: AST | t.CPtr = BinOp(ps.pool, left, ast_op, right)
        _set_pos(node, left.lineno, left.col_offset, 0, 0)
        left = node
    return left


def _parse_arith(ps: Parser | t.CPtr) -> AST | t.CPtr:
    left: AST | t.CPtr = _parse_term(ps)
    if left is None: return None
    while _match_op(ps, TokOp.Plus) or _match_op(ps, TokOp.Minus):
        op_token: t.CInt = _cur_op(ps)
        _advance(ps)
        right: AST | t.CPtr = _parse_term(ps)
        ast_op: t.CInt = OpKind.Add
        if op_token == TokOp.Minus:
            ast_op = OpKind.Sub
        node: AST | t.CPtr = BinOp(ps.pool, left, ast_op, right)
        _set_pos(node, left.lineno, left.col_offset, 0, 0)
        left = node
    return left


def _parse_term(ps: Parser | t.CPtr) -> AST | t.CPtr:
    left: AST | t.CPtr = _parse_factor(ps)
    if left is None: return None
    while True:
        op_token: t.CInt = 0
        if _match_op(ps, TokOp.Star): op_token = TokOp.Star
        elif _match_op(ps, TokOp.Slash): op_token = TokOp.Slash
        elif _match_op(ps, TokOp.DSlash): op_token = TokOp.DSlash
        elif _match_op(ps, TokOp.Percent): op_token = TokOp.Percent
        elif _match_op(ps, TokOp.At): op_token = TokOp.At
        if op_token == 0: break
        _advance(ps)
        right: AST | t.CPtr = _parse_factor(ps)
        ast_op: t.CInt = _binop_from_op(op_token)
        node: AST | t.CPtr = BinOp(ps.pool, left, ast_op, right)
        _set_pos(node, left.lineno, left.col_offset, 0, 0)
        left = node
    return left


def _parse_factor(ps: Parser | t.CPtr) -> AST | t.CPtr:
    """factor: '+' factor | '-' factor | '~' factor | power"""
    if _match_op(ps, TokOp.Plus) or _match_op(ps, TokOp.Minus) or _match_op(ps, TokOp.Tilde):
        op_token: t.CInt = _cur_op(ps)
        lineno: t.CInt = _cur_lineno(ps)
        col: t.CInt = _cur_col(ps)
        _advance(ps)
        operand: AST | t.CPtr = _parse_factor(ps)
        ast_op: t.CInt = OpKind.UAdd
        if op_token == TokOp.Minus:
            ast_op = OpKind.USub
        elif op_token == TokOp.Tilde:
            ast_op = OpKind.Invert
        node: AST | t.CPtr = UnaryOp(ps.pool, ast_op, operand)
        _set_pos(node, lineno, col, 0, 0)
        return node
    return _parse_power(ps)


def _parse_power(ps: Parser | t.CPtr) -> AST | t.CPtr:
    """power: await_expr ['**' factor] | atom_expr ['**' factor]"""
    base: AST | t.CPtr = _parse_atom_expr(ps)
    if base is None: return None
    if _match_op(ps, TokOp.StarStar):
        _advance(ps)
        exp: AST | t.CPtr = _parse_factor(ps)
        node: AST | t.CPtr = BinOp(ps.pool, base, OpKind.Pow, exp)
        _set_pos(node, base.lineno, base.col_offset, 0, 0)
        return node
    return base


def _parse_atom_expr(ps: Parser | t.CPtr) -> AST | t.CPtr:
    """atom_expr: ['await'] atom trailer*"""
    if _cur_kw(ps) == Keyword.Await:
        lineno: t.CInt = _cur_lineno(ps)
        col: t.CInt = _cur_col(ps)
        _advance(ps)
        inner: AST | t.CPtr = _parse_atom_expr(ps)
        node: AST | t.CPtr = Await(ps.pool, inner)
        _set_pos(node, lineno, col, 0, 0)
        return node
    base: AST | t.CPtr = _parse_atom(ps)
    if base is None: return None
    # trailer 循环
    while True:
        if _match_op(ps, TokOp.Dot):
            _advance(ps)
            if _cur_type(ps) != TokenType.Name:
                ps.error_count += 1
                break
            attr_name: str = _cur_str(ps)
            attr_lineno: t.CInt = _cur_lineno(ps)
            attr_col: t.CInt = _cur_col(ps)
            _advance(ps)
            ctx: t.CInt = ASTCtx.Load
            if _match_op(ps, TokOp.Equal):
                ctx = ASTCtx.Store
            elif _match_op(ps, TokOp.PlusEq) or _match_op(ps, TokOp.MinusEq):
                ctx = ASTCtx.Store
            base = Attribute(ps.pool, base, attr_name, ctx)
            _set_pos(base, attr_lineno, attr_col, 0, 0)
        elif _match_op(ps, TokOp.LPar):
            base = _parse_call(ps, base)
        elif _match_op(ps, TokOp.Lsqb):
            base = _parse_subscript(ps, base)
        else:
            break
    return base


def _parse_call(ps: Parser | t.CPtr, func: AST | t.CPtr) -> AST | t.CPtr:
    """解析函数调用 trailer。args 和 keywords 均为 list。"""
    lineno: t.CInt = _cur_lineno(ps)
    col: t.CInt = _cur_col(ps)
    _advance(ps)  # (
    _skip_nls_and_newlines(ps)  # 跳过 ( 后的换行
    args: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
    keywords: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)

    if not _match_op(ps, TokOp.RPar):
        while True:
            # **expr
            if _match_op(ps, TokOp.StarStar):
                _advance(ps)
                _skip_nls_and_newlines(ps)
                val: AST | t.CPtr = _parse_expr(ps)
                kw: AST | t.CPtr = KwNode(ps.pool, None, val)
                _set_pos(kw, _cur_lineno(ps), _cur_col(ps), 0, 0)
                keywords.append(kw)
            # *expr
            elif _match_op(ps, TokOp.Star):
                _advance(ps)
                _skip_nls_and_newlines(ps)
                val = _parse_expr(ps)
                star: AST | t.CPtr = Starred(ps.pool, val, ASTCtx.Load)
                _set_pos(star, val.lineno, val.col_offset, 0, 0)
                args.append(star)
            # keyword = expr
            elif _cur_type(ps) == TokenType.Name:
                if ps.cur.next is not None and ps.cur.next.type == TokenType.Op and ps.cur.next.op_subtype == TokOp.Equal:
                    kw_name: str = _cur_str(ps)
                    kw_lineno: t.CInt = _cur_lineno(ps)
                    kw_col: t.CInt = _cur_col(ps)
                    _advance(ps)  # name
                    _advance(ps)  # =
                    _skip_nls_and_newlines(ps)
                    val = _parse_expr(ps)
                    kw = KwNode(ps.pool, kw_name, val)
                    _set_pos(kw, kw_lineno, kw_col, 0, 0)
                    keywords.append(kw)
                else:
                    val = _parse_expr(ps)
                    args.append(val)
            else:
                val = _parse_expr(ps)
                args.append(val)
            _skip_nls_and_newlines(ps)  # 跳过参数后的换行
            if not _accept_op(ps, TokOp.Comma):
                break
            _skip_nls_and_newlines(ps)  # 跳过逗号后的换行
    _skip_nls_and_newlines(ps)  # 跳过 ) 前的换行
    _expect_op(ps, TokOp.RPar)
    node: AST | t.CPtr = Call(ps.pool, func, args, keywords)
    _set_pos(node, lineno, col, 0, 0)
    return node


def _parse_subscript(ps: Parser | t.CPtr, value: AST | t.CPtr) -> AST | t.CPtr:
    """解析下标 trailer"""
    lineno: t.CInt = _cur_lineno(ps)
    col: t.CInt = _cur_col(ps)
    _advance(ps)  # [
    _skip_nl(ps)
    slice_node: AST | t.CPtr = _parse_slice(ps)
    _expect_op(ps, TokOp.Rsqb)
    ctx: t.CInt = ASTCtx.Load
    if _match_op(ps, TokOp.Equal):
        ctx = ASTCtx.Store
    node: AST | t.CPtr = Subscript(ps.pool, value, slice_node, ctx)
    _set_pos(node, lineno, col, 0, 0)
    return node


def _parse_slice(ps: Parser | t.CPtr) -> AST | t.CPtr:
    """解析 slice：[expr] ':' [expr] [':' [expr]] | expr | expr, expr, ..."""
    lower: AST | t.CPtr = None
    if not _match_op(ps, TokOp.Colon):
        lower = _parse_expr(ps)

    # 检查是否有逗号（元组语法: a[x, y, ...]）
    if _match_op(ps, TokOp.Comma):
        elts: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
        if lower is not None:
            elts.append(lower)
        while _accept_op(ps, TokOp.Comma):
            if _match_op(ps, TokOp.Rsqb):
                break  # 尾随逗号
            elem: AST | t.CPtr = _parse_expr(ps)
            if elem is not None:
                elts.append(elem)
        lineno: t.CInt = 0
        if lower is not None:
            lineno = lower.lineno
        node: AST | t.CPtr = Tuple(ps.pool, elts, ASTCtx.Load)
        _set_pos(node, lineno, 0, 0, 0)
        return node

    if _match_op(ps, TokOp.Colon):
        _advance(ps)
        upper: AST | t.CPtr = None
        if not _match_op(ps, TokOp.Colon) and not _match_op(ps, TokOp.Rsqb):
            upper = _parse_expr(ps)
        step: AST | t.CPtr = None
        if _accept_op(ps, TokOp.Colon):
            if not _match_op(ps, TokOp.Rsqb):
                step = _parse_expr(ps)
        lineno: t.CInt = 0
        if lower is not None:
            lineno = lower.lineno
        elif upper is not None:
            lineno = upper.lineno
        else:
            lineno = _cur_lineno(ps)
        node: AST | t.CPtr = Slice(ps.pool, lower, upper, step)
        _set_pos(node, lineno, 0, 0, 0)
        return node
    return lower


def _parse_atom(ps: Parser | t.CPtr) -> AST | t.CPtr:
    """atom: 最小表达式单元"""
    ttype: t.CInt = _cur_type(ps)
    lineno: t.CInt = _cur_lineno(ps)
    col: t.CInt = _cur_col(ps)

    if ttype == TokenType.Name:
        kw: t.CInt = _cur_kw(ps)
        if kw == Keyword.True_:
            _advance(ps)
            return _new_const_bool(ps.pool, 1, lineno, col)
        if kw == Keyword.False_:
            _advance(ps)
            return _new_const_bool(ps.pool, 0, lineno, col)
        if kw == Keyword.None_:
            _advance(ps)
            return _new_const_none(ps.pool, lineno, col)
        name_str: str = _cur_str(ps)
        _advance(ps)
        node: AST | t.CPtr = Name(ps.pool, name_str, ASTCtx.Load)
        _set_pos(node, lineno, col, 0, 0)
        return node

    if ttype == TokenType.Number:
        tok: Token | t.CPtr = ps.cur
        _advance(ps)
        if tok.is_float:
            return _new_const_float(ps.pool, tok.float_val, lineno, col)
        return _new_const_int(ps.pool, tok.int_val, lineno, col)

    if ttype == TokenType.String:
        sval: str = _cur_str(ps)
        is_fstr: t.CInt = ps.cur.kw_subtype & 4
        _advance(ps)
        if is_fstr:
            return _parse_fstring(ps, sval, lineno, col)
        return _new_const_str(ps.pool, sval, lineno, col)

    if _match_op(ps, TokOp.Ellipsis):
        _advance(ps)
        return _new_const_none(ps.pool, lineno, col)

    if _match_op(ps, TokOp.LPar):
        return _parse_paren(ps)
    if _match_op(ps, TokOp.Lsqb):
        return _parse_list(ps)
    if _match_op(ps, TokOp.LBrace):
        return _parse_brace(ps)

    ps.error_count += 1
    return None


def _parse_paren(ps: Parser | t.CPtr) -> AST | t.CPtr:
    """括号表达式: () | (expr) | (expr,) | (expr, ...) | (yield)"""
    lineno: t.CInt = _cur_lineno(ps)
    col: t.CInt = _cur_col(ps)
    _advance(ps)  # (
    if _match_op(ps, TokOp.RPar):
        _advance(ps)
        # 空元组
        empty: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
        node: AST | t.CPtr = Tuple(ps.pool, empty, ASTCtx.Load)
        _set_pos(node, lineno, col, 0, 0)
        return node
    # yield
    if _cur_kw(ps) == Keyword.Yield:
        y: AST | t.CPtr = _parse_yield(ps)
        _expect_op(ps, TokOp.RPar)
        return y
    first: AST | t.CPtr = _parse_expr(ps)
    if _match_op(ps, TokOp.Comma):
        # 元组
        elts: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
        elts.append(first)
        while _accept_op(ps, TokOp.Comma):
            if _match_op(ps, TokOp.RPar):
                break
            e: AST | t.CPtr = _parse_expr(ps)
            elts.append(e)
        _expect_op(ps, TokOp.RPar)
        node = Tuple(ps.pool, elts, ASTCtx.Load)
        _set_pos(node, lineno, col, 0, 0)
        return node
    _expect_op(ps, TokOp.RPar)
    # 检查推导式
    if _cur_kw(ps) == Keyword.For:
        return _parse_comprehension(ps, first, ASTKind.GeneratorExp, lineno, col)
    return first


def _parse_list(ps: Parser | t.CPtr) -> AST | t.CPtr:
    """列表: [] | [expr_list] | [expr_list comp_for]"""
    lineno: t.CInt = _cur_lineno(ps)
    col: t.CInt = _cur_col(ps)
    _advance(ps)  # [
    if _match_op(ps, TokOp.Rsqb):
        _advance(ps)
        empty: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
        node: AST | t.CPtr = List(ps.pool, empty, ASTCtx.Load)
        _set_pos(node, lineno, col, 0, 0)
        return node
    first: AST | t.CPtr = _parse_expr(ps)
    if _cur_kw(ps) == Keyword.For:
        node = _parse_comprehension(ps, first, ASTKind.ListComp, lineno, col)
        _expect_op(ps, TokOp.Rsqb)
        return node
    elts: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
    elts.append(first)
    while _accept_op(ps, TokOp.Comma):
        _skip_nl(ps)
        if _match_op(ps, TokOp.Rsqb):
            break
        e: AST | t.CPtr = _parse_expr(ps)
        elts.append(e)
    _expect_op(ps, TokOp.Rsqb)
    node = List(ps.pool, elts, ASTCtx.Load)
    _set_pos(node, lineno, col, 0, 0)
    return node


def _parse_brace(ps: Parser | t.CPtr) -> AST | t.CPtr:
    """花括号: {} | {set_items} | {dict_items} | {comp_for}"""
    lineno: t.CInt = _cur_lineno(ps)
    col: t.CInt = _cur_col(ps)
    _advance(ps)  # {
    _skip_nl(ps)
    if _match_op(ps, TokOp.RBrace):
        _advance(ps)
        empty_k: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
        empty_v: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
        node: AST | t.CPtr = Dict(ps.pool, empty_k, empty_v)
        _set_pos(node, lineno, col, 0, 0)
        return node
    first: AST | t.CPtr = _parse_expr(ps)
    # dict: key: value
    if _match_op(ps, TokOp.Colon):
        _advance(ps)
        val: AST | t.CPtr = _parse_expr(ps)
        if _cur_kw(ps) == Keyword.For:
            # dict comprehension
            gens: list[AST | t.CPtr] | t.CPtr = _parse_comp_for(ps)
            node = DictComp(ps.pool, first, val, gens)
            _set_pos(node, lineno, col, 0, 0)
            _expect_op(ps, TokOp.RBrace)
            return node
        keys: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
        values: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
        keys.append(first)
        values.append(val)
        while _accept_op(ps, TokOp.Comma):
            _skip_nl(ps)
            if _match_op(ps, TokOp.RBrace):
                break
            k: AST | t.CPtr = _parse_expr(ps)
            _expect_op(ps, TokOp.Colon)
            v: AST | t.CPtr = _parse_expr(ps)
            keys.append(k)
            values.append(v)
        _expect_op(ps, TokOp.RBrace)
        node = Dict(ps.pool, keys, values)
        _set_pos(node, lineno, col, 0, 0)
        return node
    # set
    if _cur_kw(ps) == Keyword.For:
        node = _parse_comprehension(ps, first, ASTKind.SetComp, lineno, col)
        _expect_op(ps, TokOp.RBrace)
        return node
    elts: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
    elts.append(first)
    while _accept_op(ps, TokOp.Comma):
        if _match_op(ps, TokOp.RBrace):
            break
        e: AST | t.CPtr = _parse_expr(ps)
        elts.append(e)
    _expect_op(ps, TokOp.RBrace)
    node = Set(ps.pool, elts)
    _set_pos(node, lineno, col, 0, 0)
    return node


# ============================================================
# f-string 解析
# ============================================================

def _parse_sub_expr(ps: Parser | t.CPtr, src: str) -> AST | t.CPtr:
    """创建子 lexer + parser 解析表达式（用于 f-string 内的 {expr}）"""
    pool: memhub.MemManager | t.CPtr = ps.pool
    lx: Lexer | t.CPtr = new_lexer(pool)
    if lx is None: return None
    _lexer_init(lx, src, pool)
    tokens: Token | t.CPtr = tokenize(lx)
    if tokens is None: return None
    sub_ps: Parser | t.CPtr = new_parser(pool)
    if sub_ps is None: return None
    _parser_init(sub_ps, tokens, pool)
    return _parse_test(sub_ps)


def _parse_fstring(ps: Parser | t.CPtr, content: str,
                   lineno: t.CInt, col: t.CInt) -> AST | t.CPtr:
    """解析 f-string 内容，构建 JoinedStr 或 FormattedValue。"""
    pool: memhub.MemManager | t.CPtr = ps.pool
    values: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](pool, 8)
    i: t.CSizeT = 0
    content_len: t.CSizeT = string.strlen(content)
    # 字面量文本缓冲区
    lit_buf: str = pool.alloc(content_len + 1)
    if lit_buf is None: return None
    lit_pos: t.CSizeT = 0

    while i < content_len:
        ch: t.CChar = content[i]
        if ch == '{':
            # {{ → 字面量 {
            if i + 1 < content_len and content[i + 1] == '{':
                lit_buf[lit_pos] = '{'
                lit_pos += 1
                i += 2
                continue
            # 刷新字面量缓冲区
            if lit_pos > 0:
                lit_buf[lit_pos] = '\0'
                lit_node: AST | t.CPtr = _new_const_str(pool, lit_buf, lineno, col)
                values.append(lit_node)
                lit_buf = pool.alloc(content_len + 1)
                if lit_buf is None: break
                lit_pos = 0
            # 查找匹配的 }，同时跟踪嵌套 {} 和字符串字面量
            j: t.CSizeT = i + 1
            depth: t.CInt = 1
            conv_pos: t.CSizeT = 0
            fmt_pos: t.CSizeT = 0
            while j < content_len and depth > 0:
                c: t.CChar = content[j]
                if c == '{':
                    depth += 1
                elif c == '}':
                    depth -= 1
                    if depth == 0:
                        break
                elif depth == 1:
                    if c == '!' and conv_pos == 0 and fmt_pos == 0:
                        if j + 1 < content_len:
                            nc: t.CChar = content[j + 1]
                            if nc == 's' or nc == 'r' or nc == 'a':
                                if j + 2 < content_len:
                                    after: t.CChar = content[j + 2]
                                    if after == ':' or after == '}':
                                        conv_pos = j
                                elif j + 2 == content_len:
                                    conv_pos = j
                    elif c == ':' and fmt_pos == 0:
                        fmt_pos = j
                j += 1
            if depth != 0:
                ps.error_count += 1
                break
            # 提取表达式文本
            expr_end: t.CSizeT = j
            if conv_pos > 0:
                expr_end = conv_pos
            elif fmt_pos > 0:
                expr_end = fmt_pos
            expr_len: t.CSizeT = expr_end - i - 1
            if expr_len == 0:
                ps.error_count += 1
                i = j + 1
                continue
            expr_buf: str = pool.alloc(expr_len + 1)
            if expr_buf is None: break
            string.memcpy(expr_buf, content + i + 1, expr_len)
            expr_buf[expr_len] = '\0'
            # 解析表达式
            expr_node: AST | t.CPtr = _parse_sub_expr(ps, expr_buf)
            # 转换符
            conversion: t.CInt = -1
            if conv_pos > 0:
                conv_char: t.CChar = content[conv_pos + 1]
                if conv_char == 's':
                    conversion = 115
                elif conv_char == 'r':
                    conversion = 114
                elif conv_char == 'a':
                    conversion = 97
            # 格式说明符
            format_spec: AST | t.CPtr = None
            if fmt_pos > 0:
                spec_len: t.CSizeT = j - fmt_pos - 1
                if spec_len > 0:
                    spec_buf: str = pool.alloc(spec_len + 1)
                    if spec_buf is not None:
                        string.memcpy(spec_buf, content + fmt_pos + 1, spec_len)
                        spec_buf[spec_len] = '\0'
                        format_spec = _new_const_str(pool, spec_buf, lineno, col)
            fv: AST | t.CPtr = FormattedValue(pool, expr_node, conversion, format_spec)
            _set_pos(fv, lineno, col, 0, 0)
            values.append(fv)
            i = j + 1
        elif ch == '}':
            if i + 1 < content_len and content[i + 1] == '}':
                lit_buf[lit_pos] = '}'
                lit_pos += 1
                i += 2
            else:
                ps.error_count += 1
                i += 1
        else:
            lit_buf[lit_pos] = ch
            lit_pos += 1
            i += 1

    # 刷新剩余字面量
    if lit_pos > 0:
        lit_buf[lit_pos] = '\0'
        lit_node2: AST | t.CPtr = _new_const_str(pool, lit_buf, lineno, col)
        values.append(lit_node2)

    # 如果没有内容，返回空字符串
    if values.__len__() == 0:
        return _new_const_str(pool, "", lineno, col)
    # 如果只有一个 FormattedValue 且无字面量，返回 FormattedValue
    if values.__len__() == 1:
        first: AST | t.CPtr = values.get(0)
        if first.kind() == ASTKind.FormattedValue:
            return first
    node: AST | t.CPtr = JoinedStr(pool, values)
    _set_pos(node, lineno, col, 0, 0)
    return node


def _parse_comprehension(ps: Parser | t.CPtr, elt: AST | t.CPtr,
                         vtype: t.CInt, lineno: t.CInt, col: t.CInt) -> AST | t.CPtr:
    """通用推导式：elt comp_for"""
    gens: list[AST | t.CPtr] | t.CPtr = _parse_comp_for(ps)
    node: AST | t.CPtr = None
    if vtype == ASTKind.ListComp:
        node = ListComp(ps.pool, elt, gens)
    elif vtype == ASTKind.SetComp:
        node = SetComp(ps.pool, elt, gens)
    else:
        node = GeneratorExp(ps.pool, elt, gens)
    _set_pos(node, lineno, col, 0, 0)
    return node


def _parse_comp_for(ps: Parser | t.CPtr) -> list[AST | t.CPtr] | t.CPtr:
    """comp_for: ['async'] 'for' exprlist 'in' or_test [comp_if] (comp_for)*
    返回 generators list。"""
    gens: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
    while True:
        is_async: t.CInt = 0
        if _cur_kw(ps) == Keyword.Async:
            is_async = 1
            _advance(ps)
        if _cur_kw(ps) != Keyword.For:
            break
        lineno: t.CInt = _cur_lineno(ps)
        col: t.CInt = _cur_col(ps)
        _advance(ps)
        target: AST | t.CPtr = _parse_target_list(ps)
        if _cur_kw(ps) != Keyword.In:
            ps.error_count += 1
            break
        _advance(ps)
        iter_: AST | t.CPtr = _parse_or_test(ps)
        # 收集 ifs 到 list
        ifs: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
        while _cur_kw(ps) == Keyword.If:
            _advance(ps)
            cond: AST | t.CPtr = _parse_or_test(ps)
            ifs.append(cond)
        comp: AST | t.CPtr = Comprehension(ps.pool, target, iter_, ifs, is_async)
        _set_pos(comp, lineno, col, 0, 0)
        gens.append(comp)
    return gens


def _parse_target_list(ps: Parser | t.CPtr) -> AST | t.CPtr:
    """解析赋值目标列表（用逗号分隔）"""
    first: AST | t.CPtr = _parse_target(ps)
    if _match_op(ps, TokOp.Comma):
        elts: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
        elts.append(first)
        while _accept_op(ps, TokOp.Comma):
            if _cur_kw(ps) == Keyword.In:
                break
            e: AST | t.CPtr = _parse_target(ps)
            elts.append(e)
        node: AST | t.CPtr = Tuple(ps.pool, elts, ASTCtx.Store)
        _set_pos(node, first.lineno, first.col_offset, 0, 0)
        return node
    return first


def _parse_target(ps: Parser | t.CPtr) -> AST | t.CPtr:
    """解析单个赋值目标"""
    if _match_op(ps, TokOp.Star):
        _advance(ps)
        inner: AST | t.CPtr = _parse_atom_expr(ps)
        node: AST | t.CPtr = Starred(ps.pool, inner, ASTCtx.Store)
        _set_pos(node, inner.lineno, inner.col_offset, 0, 0)
        return node
    if _match_op(ps, TokOp.LPar):
        _advance(ps)
        _skip_nl(ps)
        t_: AST | t.CPtr = _parse_target_list(ps)
        _skip_nl(ps)
        _expect_op(ps, TokOp.RPar)
        return t_
    if _match_op(ps, TokOp.Lsqb):
        _advance(ps)
        _skip_nl(ps)
        first: AST | t.CPtr = _parse_target(ps)
        elts: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
        elts.append(first)
        while _accept_op(ps, TokOp.Comma):
            _skip_nl(ps)
            if _match_op(ps, TokOp.Rsqb):
                break
            e: AST | t.CPtr = _parse_target(ps)
            elts.append(e)
        _expect_op(ps, TokOp.Rsqb)
        node = List(ps.pool, elts, ASTCtx.Store)
        _set_pos(node, first.lineno, first.col_offset, 0, 0)
        return node
    # 普通 atom_expr，但 ctx=Store
    node = _parse_atom_expr(ps)
    if node is not None:
        _set_ctx(node, ASTCtx.Store)
    return node


def _parse_yield(ps: Parser | t.CPtr) -> AST | t.CPtr:
    lineno: t.CInt = _cur_lineno(ps)
    col: t.CInt = _cur_col(ps)
    _advance(ps)  # yield
    if _cur_kw(ps) == Keyword.From:
        _advance(ps)
        val: AST | t.CPtr = _parse_expr(ps)
        node: AST | t.CPtr = YieldFrom(ps.pool, val)
        _set_pos(node, lineno, col, 0, 0)
        return node
    val2: AST | t.CPtr = None
    if not _match_op(ps, TokOp.RPar) and _cur_type(ps) != TokenType.NewLine and _cur_type(ps) != TokenType.EndMarker:
        if _cur_type(ps) == TokenType.Op and (_cur_op(ps) == TokOp.Comma or _cur_op(ps) == TokOp.Rsqb):
            pass
        else:
            val2 = _parse_test(ps)
    node = Yield(ps.pool, val2)
    _set_pos(node, lineno, col, 0, 0)
    return node


def _parse_varargslist(ps: Parser | t.CPtr, is_lambda: t.CInt = 0) -> AST | t.CPtr:
    """函数参数列表解析（lambda 和 def 共用）
    新 API：args/defaults/kw_defaults 为 list，vararg/kwarg 为字段。"""
    args: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
    vararg: AST | t.CPtr = None
    kwarg: AST | t.CPtr = None
    defaults: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
    kw_defaults: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)

    while True:
        _skip_nl(ps)
        if _match_op(ps, TokOp.RPar):
            break
        # *args 或 * (kwonly 分隔)
        if _match_op(ps, TokOp.Star):
            _advance(ps)
            _skip_nl(ps)
            if _cur_type(ps) == TokenType.Name:
                vararg = _parse_arg_def(ps, is_lambda)
            if not _accept_op(ps, TokOp.Comma):
                break
            _skip_nl(ps)
            continue
        # **kwargs
        if _match_op(ps, TokOp.StarStar):
            _advance(ps)
            _skip_nl(ps)
            kwarg = _parse_arg_def(ps, is_lambda)
            if not _accept_op(ps, TokOp.Comma):
                break
            _skip_nl(ps)
            continue
        # / 分隔符（posonly 结束）— 新 API 中 posonly/regular 不区分
        if _match_op(ps, TokOp.Slash):
            _advance(ps)
            if not _accept_op(ps, TokOp.Comma):
                break
            _skip_nl(ps)
            continue
        # 普通参数
        a: AST | t.CPtr = _parse_arg_def(ps, is_lambda)
        if a is None: break
        args.append(a)
        if _match_op(ps, TokOp.Equal):
            _advance(ps)
            d: AST | t.CPtr = _parse_test(ps)
            defaults.append(d)
        if not _accept_op(ps, TokOp.Comma):
            break
        _skip_nl(ps)
    return Arguments(ps.pool, args, vararg, kwarg, defaults, kw_defaults)


def _parse_arg_def(ps: Parser | t.CPtr, is_lambda: t.CInt = 0) -> AST | t.CPtr:
    """arg: TokenType.Name [':' annotation]"""
    if _cur_type(ps) != TokenType.Name:
        ps.error_count += 1
        return None
    name_str: str = _cur_str(ps)
    lineno: t.CInt = _cur_lineno(ps)
    col: t.CInt = _cur_col(ps)
    _advance(ps)
    annotation: AST | t.CPtr = None
    if is_lambda == 0:
        if _accept_op(ps, TokOp.Colon):
            annotation = _parse_test(ps)
    result: AST | t.CPtr = Arg(ps.pool, name_str, annotation)
    _set_pos(result, lineno, col, 0, 0)
    return result


# ============================================================
# 语句解析（设计：直接 append 到 parent，不返回 list）
# ============================================================

def _parse_suite_into_children(ps: Parser | t.CPtr, parent: AST | t.CPtr):
    """解析语句块，直接 append 为 parent 的子节点。
    suite: TokenType.NewLine TokenType.Indent stmt+ TokenType.Dedent | simple_stmt"""
    _skip_nl(ps)
    if _cur_type(ps) == TokenType.NewLine:
        _advance(ps)
        _skip_nl(ps)
        if _cur_type(ps) != TokenType.Indent:
            ps.error_count += 1
            return
        _advance(ps)
        sidx: t.CInt = 0
        while _cur_type(ps) != TokenType.Dedent and _cur_type(ps) != TokenType.EndMarker:
            sidx += 1
            # 装饰器（类体内方法也可以有装饰器）
            if _match_op(ps, TokOp.At):
                ps.pending_decorators = _parse_decorators(ps)
            before_cur: Token | t.CPtr = ps.cur
            _parse_statement_into(ps, parent)
            # 防御：若解析未前进 token，强制前进以避免死循环
            if ps.cur is before_cur:
                ps.error_count += 1
                _advance(ps)
            _skip_nl(ps)
        _expect_type(ps, TokenType.Dedent)
        return
    # 单行 suite
    _parse_simple_stmt_into(ps, parent)


def _parse_statement_into(ps: Parser | t.CPtr, parent: AST | t.CPtr):
    """解析一条语句（复合或简单），append 到 parent。"""
    ttype: t.CInt = _cur_type(ps)
    kw: t.CInt = _cur_kw(ps)
    if ttype == TokenType.Name:
        if kw == Keyword.If or kw == Keyword.While or kw == Keyword.For or kw == Keyword.Try:
            stmt: AST | t.CPtr = _parse_compound(ps)
            if stmt is not None:
                parent.append(stmt)
            return
        if kw == Keyword.With or kw == Keyword.Def or kw == Keyword.Class:
            stmt = _parse_compound(ps)
            if stmt is not None:
                parent.append(stmt)
            return
        if kw == Keyword.Match or kw == Keyword.Async:
            stmt = _parse_compound(ps)
            if stmt is not None:
                parent.append(stmt)
            return
    _parse_simple_stmt_into(ps, parent)


def _parse_simple_stmt_into(ps: Parser | t.CPtr, parent: AST | t.CPtr):
    """simple_stmt: small_stmt (';' small_stmt)* [';'] TokenType.NewLine"""
    first: AST | t.CPtr = _parse_small_stmt(ps)
    if first is not None:
        parent.append(first)
    while _accept_op(ps, TokOp.Semi):
        if _cur_type(ps) == TokenType.NewLine or _cur_type(ps) == TokenType.EndMarker:
            break
        s: AST | t.CPtr = _parse_small_stmt(ps)
        if s is None: break
        parent.append(s)
    _skip_nl(ps)
    if _cur_type(ps) == TokenType.NewLine:
        _advance(ps)


def _parse_small_stmt(ps: Parser | t.CPtr) -> AST | t.CPtr:
    """small_stmt: 各种简单语句"""
    kw: t.CInt = _cur_kw(ps)
    if kw == Keyword.Pass:
        lineno: t.CInt = _cur_lineno(ps)
        col: t.CInt = _cur_col(ps)
        _advance(ps)
        node: AST | t.CPtr = Pass(ps.pool)
        _set_pos(node, lineno, col, 0, 0)
        return node
    if kw == Keyword.Break:
        lineno = _cur_lineno(ps)
        col = _cur_col(ps)
        _advance(ps)
        node = Break(ps.pool)
        _set_pos(node, lineno, col, 0, 0)
        return node
    if kw == Keyword.Continue:
        lineno = _cur_lineno(ps)
        col = _cur_col(ps)
        _advance(ps)
        node = Continue(ps.pool)
        _set_pos(node, lineno, col, 0, 0)
        return node
    if kw == Keyword.Return:
        return _parse_return(ps)
    if kw == Keyword.Raise:
        return _parse_raise(ps)
    if kw == Keyword.Global:
        return _parse_global(ps)
    if kw == Keyword.Nonlocal:
        return _parse_nonlocal(ps)
    if kw == Keyword.Import:
        return _parse_import(ps)
    if kw == Keyword.From:
        return _parse_from_import(ps)
    if kw == Keyword.Del:
        return _parse_del(ps)
    if kw == Keyword.Assert:
        return _parse_assert(ps)
    if kw == Keyword.Yield:
        return _parse_expr_stmt(ps, _parse_yield(ps))
    # 表达式语句 / 赋值 / augassign / annassign
    return _parse_expr_stmt(ps, None)


def _maybe_tuple(ps: Parser | t.CPtr, first: AST | t.CPtr) -> AST | t.CPtr:
    """如果 first 后面有逗号，收集逗号分隔的表达式为 Tuple。"""
    if first is None: return None
    if not _match_op(ps, TokOp.Comma):
        return first
    lineno: t.CInt = first.lineno
    col: t.CInt = first.col_offset
    elts: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
    elts.append(first)
    _accept_op(ps, TokOp.Comma)
    while True:
        tt: t.CInt = _cur_type(ps)
        if tt == TokenType.NewLine or tt == TokenType.EndMarker:
            break
        if tt == TokenType.Op:
            op: t.CInt = _cur_op(ps)
            if op == TokOp.Equal or op == TokOp.Semi:
                break
        e: AST | t.CPtr = _parse_expr(ps)
        if e is None: break
        elts.append(e)
        if not _accept_op(ps, TokOp.Comma):
            break
    node: AST | t.CPtr = Tuple(ps.pool, elts, ASTCtx.Load)
    _set_pos(node, lineno, col, 0, 0)
    return node


def _parse_testlist(ps: Parser | t.CPtr) -> AST | t.CPtr:
    """解析逗号分隔的表达式列表，多于1个则形成 Tuple。"""
    first: AST | t.CPtr = _parse_expr(ps)
    return _maybe_tuple(ps, first)


def _parse_expr_stmt(ps: Parser | t.CPtr, first: AST | t.CPtr) -> AST | t.CPtr:
    """表达式语句，处理赋值/augassign/annassign"""
    if first is None:
        first = _parse_testlist(ps)
    else:
        first = _maybe_tuple(ps, first)
    if first is None:
        return None
    lineno: t.CInt = first.lineno
    col: t.CInt = first.col_offset
    # 赋值
    if _match_op(ps, TokOp.Equal):
        targets: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
        targets.append(first)
        _fix_store_ctx(first)
        value: AST | t.CPtr = None
        accept_result: t.CInt = _accept_op(ps, TokOp.Equal)
        while accept_result:
            v: AST | t.CPtr = _parse_testlist(ps)
            if v is None: break
            if value is not None:
                targets.append(value)
                _fix_store_ctx(value)
            value = v
            accept_result = _accept_op(ps, TokOp.Equal)
        node: AST | t.CPtr = Assign(ps.pool, targets, value)
        _set_pos(node, lineno, col, 0, 0)
        return node
    # augassign
    if _cur_type(ps) == TokenType.Op:
        op_id: t.CInt = _cur_op(ps)
        aug_op: t.CInt = _augop_from_op(op_id)
        if aug_op != OpKind.NoneOp:
            _advance(ps)
            value2: AST | t.CPtr = _parse_testlist(ps)
            _fix_store_ctx(first)
            node2: AST | t.CPtr = AugAssign(ps.pool, first, aug_op, value2)
            _set_pos(node2, lineno, col, 0, 0)
            return node2
    # annassign: target ':' annotation [= value]
    if _match_op(ps, TokOp.Colon):
        _advance(ps)
        annotation: AST | t.CPtr = _parse_test(ps)
        value3: AST | t.CPtr = None
        if _accept_op(ps, TokOp.Equal):
            value3 = _parse_testlist(ps)
        if ps.pool is None:
            return None
        _fix_store_ctx(first)
        if ps.pool is None:
            return None
        node3: AST | t.CPtr = AnnAssign(ps.pool, first, annotation, value3, 1)
        _set_pos(node3, lineno, col, 0, 0)
        return node3
    # 表达式语句
    node4: AST | t.CPtr = Expr(ps.pool, first)
    _set_pos(node4, lineno, col, 0, 0)
    return node4


def _parse_return(ps: Parser | t.CPtr) -> AST | t.CPtr:
    lineno: t.CInt = _cur_lineno(ps)
    col: t.CInt = _cur_col(ps)
    _advance(ps)
    val: AST | t.CPtr = None
    if _cur_type(ps) != TokenType.NewLine and _cur_type(ps) != TokenType.EndMarker and _cur_type(ps) != TokenType.Op:
        val = _parse_testlist(ps)
    elif _cur_type(ps) == TokenType.Op and _cur_op(ps) != TokOp.Semi:
        val = _parse_testlist(ps)
    node: AST | t.CPtr = Return(ps.pool, val)
    _set_pos(node, lineno, col, 0, 0)
    return node


def _parse_raise(ps: Parser | t.CPtr) -> AST | t.CPtr:
    lineno: t.CInt = _cur_lineno(ps)
    col: t.CInt = _cur_col(ps)
    _advance(ps)
    exc: AST | t.CPtr = None
    cause: AST | t.CPtr = None
    if _cur_type(ps) != TokenType.NewLine and _cur_type(ps) != TokenType.EndMarker:
        exc = _parse_test(ps)
        if _cur_kw(ps) == Keyword.From:
            _advance(ps)
            cause = _parse_test(ps)
    node: AST | t.CPtr = Raise(ps.pool, exc, cause)
    _set_pos(node, lineno, col, 0, 0)
    return node


def _parse_global(ps: Parser | t.CPtr) -> AST | t.CPtr:
    lineno: t.CInt = _cur_lineno(ps)
    col: t.CInt = _cur_col(ps)
    _advance(ps)
    names: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
    if _cur_type(ps) == TokenType.Name:
        n: AST | t.CPtr = Name(ps.pool, _cur_str(ps), ASTCtx.Store)
        _set_pos(n, _cur_lineno(ps), _cur_col(ps), 0, 0)
        names.append(n)
        _advance(ps)
        while _accept_op(ps, TokOp.Comma):
            if _cur_type(ps) != TokenType.Name: break
            n = Name(ps.pool, _cur_str(ps), ASTCtx.Store)
            _set_pos(n, _cur_lineno(ps), _cur_col(ps), 0, 0)
            names.append(n)
            _advance(ps)
    node: AST | t.CPtr = Global(ps.pool, names)
    _set_pos(node, lineno, col, 0, 0)
    return node


def _parse_nonlocal(ps: Parser | t.CPtr) -> AST | t.CPtr:
    lineno: t.CInt = _cur_lineno(ps)
    col: t.CInt = _cur_col(ps)
    _advance(ps)
    names: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
    if _cur_type(ps) == TokenType.Name:
        n: AST | t.CPtr = Name(ps.pool, _cur_str(ps), ASTCtx.Store)
        _set_pos(n, _cur_lineno(ps), _cur_col(ps), 0, 0)
        names.append(n)
        _advance(ps)
        while _accept_op(ps, TokOp.Comma):
            if _cur_type(ps) != TokenType.Name: break
            n = Name(ps.pool, _cur_str(ps), ASTCtx.Store)
            _set_pos(n, _cur_lineno(ps), _cur_col(ps), 0, 0)
            names.append(n)
            _advance(ps)
    node: AST | t.CPtr = Nonlocal(ps.pool, names)
    _set_pos(node, lineno, col, 0, 0)
    return node


def _parse_import(ps: Parser | t.CPtr) -> AST | t.CPtr:
    lineno: t.CInt = _cur_lineno(ps)
    col: t.CInt = _cur_col(ps)
    _advance(ps)
    names: list[AST | t.CPtr] | t.CPtr = _parse_alias_list(ps)
    node: AST | t.CPtr = Import(ps.pool, names)
    _set_pos(node, lineno, col, 0, 0)
    return node


def _parse_from_import(ps: Parser | t.CPtr) -> AST | t.CPtr:
    lineno: t.CInt = _cur_lineno(ps)
    col: t.CInt = _cur_col(ps)
    _advance(ps)
    # 解析 module（可能带点号）
    level: t.CInt = 0
    while _match_op(ps, TokOp.Dot):
        level += 1
        _advance(ps)
    while _match_op(ps, TokOp.Ellipsis):
        level += 3
        _advance(ps)
    module_name: str = None
    if _cur_type(ps) == TokenType.Name:
        module_name = _cur_str(ps)
        _advance(ps)
        while _match_op(ps, TokOp.Dot):
            _advance(ps)
            if _cur_type(ps) == TokenType.Name:
                next_name: str = _cur_str(ps)
                mlen: t.CInt = string.strlen(module_name)
                nlen: t.CInt = string.strlen(next_name)
                new_mod: str = ps.pool.alloc(mlen + 1 + nlen + 1)
                if new_mod is not None:
                    string.strcpy(new_mod, module_name)
                    new_mod[mlen] = '.'
                    string.strcpy(new_mod + mlen + 1, next_name)
                    module_name = new_mod
                _advance(ps)
    if _cur_kw(ps) != Keyword.Import:
        ps.error_count += 1
        return None
    _advance(ps)
    # import *
    if _match_op(ps, TokOp.Star):
        _advance(ps)
        star_alias: AST | t.CPtr = Alias(ps.pool, "*", None)
        _set_pos(star_alias, lineno, col, 0, 0)
        star_names: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
        star_names.append(star_alias)
        node: AST | t.CPtr = ImportFrom(ps.pool, module_name, star_names, level)
        _set_pos(node, lineno, col, 0, 0)
        return node
    # ( names ) or names
    if _match_op(ps, TokOp.LPar):
        _advance(ps)
        _skip_nl(ps)
        names: list[AST | t.CPtr] | t.CPtr = _parse_alias_list(ps)
        _skip_nl(ps)
        _expect_op(ps, TokOp.RPar)
    else:
        names = _parse_alias_list(ps)
    node: AST | t.CPtr = ImportFrom(ps.pool, module_name, names, level)
    _set_pos(node, lineno, col, 0, 0)
    return node


def _parse_alias_list(ps: Parser | t.CPtr) -> list[AST | t.CPtr] | t.CPtr:
    """返回 alias list。"""
    names: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
    while True:
        if _cur_type(ps) != TokenType.Name:
            break
        name_str: str = _cur_str(ps)
        lineno: t.CInt = _cur_lineno(ps)
        col: t.CInt = _cur_col(ps)
        _advance(ps)
        # dotted name
        while _match_op(ps, TokOp.Dot):
            _advance(ps)
            if _cur_type(ps) == TokenType.Name:
                next_n: str = _cur_str(ps)
                nmlen: t.CInt = string.strlen(name_str)
                nxtlen: t.CInt = string.strlen(next_n)
                new_name: str = ps.pool.alloc(nmlen + 1 + nxtlen + 1)
                if new_name is not None:
                    string.strcpy(new_name, name_str)
                    new_name[nmlen] = '.'
                    string.strcpy(new_name + nmlen + 1, next_n)
                    name_str = new_name
                _advance(ps)
        asname: str = None
        if _cur_kw(ps) == Keyword.As:
            _advance(ps)
            if _cur_type(ps) == TokenType.Name:
                asname = _cur_str(ps)
                _advance(ps)
        a: AST | t.CPtr = Alias(ps.pool, name_str, asname)
        _set_pos(a, lineno, col, 0, 0)
        names.append(a)
        if not _accept_op(ps, TokOp.Comma):
            break
    return names


def _parse_del(ps: Parser | t.CPtr) -> AST | t.CPtr:
    lineno: t.CInt = _cur_lineno(ps)
    col: t.CInt = _cur_col(ps)
    _advance(ps)
    targets: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
    while True:
        t_: AST | t.CPtr = _parse_atom_expr(ps)
        if t_ is None: break
        _set_ctx(t_, ASTCtx.Del)
        targets.append(t_)
        if not _accept_op(ps, TokOp.Comma):
            break
    node: AST | t.CPtr = Delete(ps.pool, targets)
    _set_pos(node, lineno, col, 0, 0)
    return node


def _parse_assert(ps: Parser | t.CPtr) -> AST | t.CPtr:
    lineno: t.CInt = _cur_lineno(ps)
    col: t.CInt = _cur_col(ps)
    _advance(ps)
    test: AST | t.CPtr = _parse_test(ps)
    msg: AST | t.CPtr = None
    if _accept_op(ps, TokOp.Comma):
        msg = _parse_test(ps)
    node: AST | t.CPtr = Assert(ps.pool, test, msg)
    _set_pos(node, lineno, col, 0, 0)
    return node


# ============================================================
# 复合语句
# ============================================================

def _parse_compound(ps: Parser | t.CPtr) -> AST | t.CPtr:
    kw: t.CInt = _cur_kw(ps)
    if kw == Keyword.If:
        return _parse_if(ps)
    if kw == Keyword.While:
        return _parse_while(ps)
    if kw == Keyword.For:
        return _parse_for(ps, 0)
    if kw == Keyword.Try:
        return _parse_try(ps)
    if kw == Keyword.With:
        return _parse_with(ps, 0)
    if kw == Keyword.Def:
        return _parse_funcdef(ps, 0)
    if kw == Keyword.Class:
        return _parse_classdef(ps)
    if kw == Keyword.Async:
        _advance(ps)
        if _cur_kw(ps) == Keyword.Def:
            return _parse_funcdef(ps, 1)
        if _cur_kw(ps) == Keyword.For:
            return _parse_for(ps, 1)
        if _cur_kw(ps) == Keyword.With:
            return _parse_with(ps, 1)
        ps.error_count += 1
        return None
    if kw == Keyword.Match:
        return _parse_match(ps)
    ps.error_count += 1
    return None


def _parse_if(ps: Parser | t.CPtr) -> AST | t.CPtr:
    lineno: t.CInt = _cur_lineno(ps)
    col: t.CInt = _cur_col(ps)
    _advance(ps)  # if
    test: AST | t.CPtr = _parse_test(ps)
    _expect_op(ps, TokOp.Colon)
    # 先用空 orelse 构造，后面填充
    empty: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
    node: AST | t.CPtr = If(ps.pool, test, empty)
    _parse_suite_into_children(ps, node)
    orelse: list[AST | t.CPtr] | t.CPtr = _parse_if_tail(ps)
    node.orelse = orelse
    _set_pos(node, lineno, col, 0, 0)
    return node


def _parse_if_tail(ps: Parser | t.CPtr) -> list[AST | t.CPtr] | t.CPtr:
    """Parse elif/else chain and return orelse list.

    elif:  orelse = list containing single If node (nested)
    else:  orelse = statement list
    """
    if _cur_kw(ps) == Keyword.Elif:
        lineno: t.CInt = _cur_lineno(ps)
        col: t.CInt = _cur_col(ps)
        _advance(ps)
        elif_test: AST | t.CPtr = _parse_test(ps)
        _expect_op(ps, TokOp.Colon)
        empty: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
        elif_node: AST | t.CPtr = If(ps.pool, elif_test, empty)
        _parse_suite_into_children(ps, elif_node)
        elif_orelse: list[AST | t.CPtr] | t.CPtr = _parse_if_tail(ps)
        elif_node.orelse = elif_orelse
        _set_pos(elif_node, lineno, col, 0, 0)
        orelse: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
        orelse.append(elif_node)
        return orelse
    if _cur_kw(ps) == Keyword.Else:
        _advance(ps)
        _expect_op(ps, TokOp.Colon)
        scratch: Module | t.CPtr = Module(ps.pool)
        _parse_suite_into_children(ps, scratch)
        return scratch.children
    return list[AST | t.CPtr](ps.pool, 8)  # 空 list


# ============================================================
# 复合语句解析（续）
# ============================================================

def _parse_while(ps: Parser | t.CPtr) -> AST | t.CPtr:
    lineno: t.CInt = _cur_lineno(ps)
    col: t.CInt = _cur_col(ps)
    _advance(ps)  # while
    test: AST | t.CPtr = _parse_test(ps)
    _expect_op(ps, TokOp.Colon)
    empty: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
    node: AST | t.CPtr = While(ps.pool, test, empty)
    _parse_suite_into_children(ps, node)
    if _cur_kw(ps) == Keyword.Else:
        _advance(ps)
        _expect_op(ps, TokOp.Colon)
        scratch: Module | t.CPtr = Module(ps.pool)
        _parse_suite_into_children(ps, scratch)
        node.orelse = scratch.children
    _set_pos(node, lineno, col, 0, 0)
    return node


def _parse_for(ps: Parser | t.CPtr, is_async: t.CInt) -> AST | t.CPtr:
    lineno: t.CInt = _cur_lineno(ps)
    col: t.CInt = _cur_col(ps)
    _advance(ps)  # for
    target: AST | t.CPtr = _parse_target_list(ps)
    if _cur_kw(ps) != Keyword.In:
        ps.error_count += 1
        return None
    _advance(ps)
    iter_: AST | t.CPtr = _parse_or_test(ps)
    _expect_op(ps, TokOp.Colon)
    empty: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
    node: AST | t.CPtr = For(ps.pool, target, iter_, empty, is_async)
    _fix_store_ctx(target)
    _parse_suite_into_children(ps, node)
    if _cur_kw(ps) == Keyword.Else:
        _advance(ps)
        _expect_op(ps, TokOp.Colon)
        scratch: Module | t.CPtr = Module(ps.pool)
        _parse_suite_into_children(ps, scratch)
        node.orelse = scratch.children
    _set_pos(node, lineno, col, 0, 0)
    return node


def _parse_try(ps: Parser | t.CPtr) -> AST | t.CPtr:
    lineno: t.CInt = _cur_lineno(ps)
    col: t.CInt = _cur_col(ps)
    _advance(ps)  # try
    _expect_op(ps, TokOp.Colon)
    empty: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
    node: AST | t.CPtr = Try(ps.pool, empty, empty, empty)
    _parse_suite_into_children(ps, node)
    # handlers (except)
    handlers: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
    while _cur_kw(ps) == Keyword.Except:
        h: AST | t.CPtr = _parse_except_handler(ps)
        if h is not None:
            handlers.append(h)
    node.handlers = handlers
    # else
    if _cur_kw(ps) == Keyword.Else:
        _advance(ps)
        _expect_op(ps, TokOp.Colon)
        scratch: Module | t.CPtr = Module(ps.pool)
        _parse_suite_into_children(ps, scratch)
        node.orelse = scratch.children
    # finally
    if _cur_kw(ps) == Keyword.Finally:
        _advance(ps)
        _expect_op(ps, TokOp.Colon)
        scratch2: Module | t.CPtr = Module(ps.pool)
        _parse_suite_into_children(ps, scratch2)
        node.finalbody = scratch2.children
    _set_pos(node, lineno, col, 0, 0)
    return node


def _parse_except_handler(ps: Parser | t.CPtr) -> AST | t.CPtr:
    lineno: t.CInt = _cur_lineno(ps)
    col: t.CInt = _cur_col(ps)
    _advance(ps)  # except
    type_: AST | t.CPtr = None
    name: str = None
    if _cur_op(ps) != TokOp.Colon:
        type_ = _parse_test(ps)
        if _cur_kw(ps) == Keyword.As:
            _advance(ps)
            if _cur_type(ps) == TokenType.Name:
                name = _cur_str(ps)
                _advance(ps)
    _expect_op(ps, TokOp.Colon)
    node: AST | t.CPtr = ExceptHandler(ps.pool, type_, name)
    _parse_suite_into_children(ps, node)
    _set_pos(node, lineno, col, 0, 0)
    return node


def _parse_with(ps: Parser | t.CPtr, is_async: t.CInt) -> AST | t.CPtr:
    lineno: t.CInt = _cur_lineno(ps)
    col: t.CInt = _cur_col(ps)
    _advance(ps)  # with
    items: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
    # items
    while True:
        item: AST | t.CPtr = _parse_with_item(ps)
        if item is None: break
        items.append(item)
        if not _accept_op(ps, TokOp.Comma):
            break
        # 检查是否可以续行（with (...): 形式）
        if _cur_type(ps) == TokenType.NewLine or _cur_type(ps) == TokenType.Nl:
            break
    _expect_op(ps, TokOp.Colon)
    node: AST | t.CPtr = With(ps.pool, items, is_async)
    _parse_suite_into_children(ps, node)
    _set_pos(node, lineno, col, 0, 0)
    return node


def _parse_with_item(ps: Parser | t.CPtr) -> AST | t.CPtr:
    """with_item: test ['as' expr]"""
    lineno: t.CInt = _cur_lineno(ps)
    col: t.CInt = _cur_col(ps)
    context_expr: AST | t.CPtr = _parse_or_test(ps)
    if context_expr is None: return None
    optional_vars: AST | t.CPtr = None
    if _cur_kw(ps) == Keyword.As:
        _advance(ps)
        optional_vars = _parse_target(ps)
        _fix_store_ctx(optional_vars)
    node: AST | t.CPtr = WithItem(ps.pool, context_expr, optional_vars)
    _set_pos(node, lineno, col, 0, 0)
    return node


def _parse_funcdef(ps: Parser | t.CPtr, is_async: t.CInt) -> AST | t.CPtr:
    lineno: t.CInt = _cur_lineno(ps)
    col: t.CInt = _cur_col(ps)
    _advance(ps)  # def
    if _cur_type(ps) != TokenType.Name:
        ps.error_count += 1
        return None
    name: str = _cur_str(ps)
    _advance(ps)
    args: AST | t.CPtr = None
    if _accept_op(ps, TokOp.LPar):
        args = _parse_varargslist(ps, 0)
        _expect_op(ps, TokOp.RPar)
    returns: AST | t.CPtr = None
    if _accept_op(ps, TokOp.RArrow):
        returns = _parse_test(ps)
    _expect_op(ps, TokOp.Colon)
    dec_list: list[AST | t.CPtr] | t.CPtr = ps.pending_decorators
    ps.pending_decorators = None
    node: AST | t.CPtr = FunctionDef(ps.pool, name, args, dec_list, returns, is_async)
    _parse_suite_into_children(ps, node)
    _set_pos(node, lineno, col, 0, 0)
    return node


def _parse_classdef(ps: Parser | t.CPtr) -> AST | t.CPtr:
    lineno: t.CInt = _cur_lineno(ps)
    col: t.CInt = _cur_col(ps)
    _advance(ps)  # class
    if _cur_type(ps) != TokenType.Name:
        ps.error_count += 1
        return None
    name: str = _cur_str(ps)
    _advance(ps)
    # 解析类型参数 [T] 或 [T, U]（PEP 695 泛型类语法）
    type_params: list[str] | t.CPtr = None
    if _accept_op(ps, TokOp.Lsqb):
        type_params = list[str](ps.pool, 8)
        while not _match_op(ps, TokOp.Rsqb):
            if _cur_type(ps) == TokenType.EndMarker: break
            if _cur_type(ps) == TokenType.Name:
                tp_name: str = _cur_str(ps)
                _advance(ps)
                # 跳过可选的 bound/constraint: [T: bound] 或 [T: (A, B)]
                if _match_op(ps, TokOp.Colon):
                    _advance(ps)
                    _parse_test(ps)
                type_params.append(tp_name)
            if not _accept_op(ps, TokOp.Comma):
                break
        _expect_op(ps, TokOp.Rsqb)
    bases: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
    keywords: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
    # bases / keywords
    if _accept_op(ps, TokOp.LPar):
        while not _match_op(ps, TokOp.RPar):
            if _cur_type(ps) == TokenType.EndMarker: break
            # base 或 keyword=value
            if _cur_type(ps) == TokenType.Name:
                # 解析为表达式，若后跟 = 则作为 keyword
                e: AST | t.CPtr = _parse_or_test(ps)
                if _match_op(ps, TokOp.Equal):
                    _advance(ps)
                    v: AST | t.CPtr = _parse_test(ps)
                    kw_node: AST | t.CPtr = KwNode(ps.pool, e, v)
                    keywords.append(kw_node)
                else:
                    bases.append(e)
            if not _accept_op(ps, TokOp.Comma):
                break
        _expect_op(ps, TokOp.RPar)
    _expect_op(ps, TokOp.Colon)
    dec_list: list[AST | t.CPtr] | t.CPtr = ps.pending_decorators
    ps.pending_decorators = None
    node: AST | t.CPtr = ClassDef(ps.pool, name, bases, keywords, dec_list, type_params)
    _parse_suite_into_children(ps, node)
    _set_pos(node, lineno, col, 0, 0)
    return node


def _parse_match(ps: Parser | t.CPtr) -> AST | t.CPtr:
    """match 语句：match subject ':' NewLine Indent case+ Dedent"""
    lineno: t.CInt = _cur_lineno(ps)
    col: t.CInt = _cur_col(ps)
    _advance(ps)  # match
    subject: AST | t.CPtr = _parse_test(ps)
    _expect_op(ps, TokOp.Colon)
    cases: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
    node: AST | t.CPtr = Match(ps.pool, subject, cases)
    # 解析 body：与 _parse_suite_into_children 相同的 NewLine/Indent/Dedent 模式，
    # 但解析 case 子句到 cases 列表而非普通语句到 children
    _skip_nl(ps)
    if _cur_type(ps) == TokenType.NewLine:
        _advance(ps)
        _skip_nl(ps)
        if _cur_type(ps) == TokenType.Indent:
            _advance(ps)
            while _cur_type(ps) != TokenType.Dedent and _cur_type(ps) != TokenType.EndMarker:
                if _cur_kw(ps) == Keyword.Case:
                    case_node: AST | t.CPtr = _parse_match_case(ps)
                    if case_node is not None:
                        cases.append(case_node)
                else:
                    _advance(ps)  # 跳过非 case token，避免死循环
            _expect_type(ps, TokenType.Dedent)
    _set_pos(node, lineno, col, 0, 0)
    return node


# ============================================================
# Match pattern 解析
# ============================================================

def _make_name_ast(pool: memhub.MemManager | t.CPtr, node: Name | t.CPtr) -> AST | t.CPtr:
    """将 Name* 转换为 AST*（解决 TransPyC 类型推断问题）"""
    return node


def _parse_pattern(ps: Parser | t.CPtr) -> AST | t.CPtr:
    """pattern: or_pattern ['as' NAME]
    or_pattern: closed_pattern ('|' closed_pattern)*"""
    pat: AST | t.CPtr = _parse_closed_pattern(ps)
    if pat is None: return None
    # OR pattern
    if _match_op(ps, TokOp.VBar):
        patterns: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
        patterns.append(pat)
        while _accept_op(ps, TokOp.VBar):
            p: AST | t.CPtr = _parse_closed_pattern(ps)
            if p is not None:
                patterns.append(p)
        or_node: AST | t.CPtr = MatchOr(ps.pool, patterns)
        _set_pos(or_node, pat.lineno, pat.col_offset, 0, 0)
        pat = or_node
    # AS pattern
    if _cur_kw(ps) == Keyword.As:
        _advance(ps)
        if _cur_type(ps) != TokenType.Name:
            ps.error_count += 1
            return pat
        name_str: str = _cur_str(ps)
        _advance(ps)
        as_node: AST | t.CPtr = MatchAs(ps.pool, pat, name_str)
        _set_pos(as_node, pat.lineno, pat.col_offset, 0, 0)
        return as_node
    return pat


def _parse_closed_pattern(ps: Parser | t.CPtr) -> AST | t.CPtr:
    """closed_pattern: literal | capture | wildcard | value | group | sequence | mapping | class"""
    lineno: t.CInt = _cur_lineno(ps)
    col: t.CInt = _cur_col(ps)
    ttype: t.CInt = _cur_type(ps)
    val: AST | t.CPtr
    node: AST | t.CPtr
    tok: Token | t.CPtr
    sval: str

    # 数字字面量 pattern
    if ttype == TokenType.Number:
        tok = ps.cur
        _advance(ps)
        if tok.is_float:
            val = _new_const_float(ps.pool, tok.float_val, lineno, col)
        else:
            val = _new_const_int(ps.pool, tok.int_val, lineno, col)
        node = MatchValue(ps.pool, val)
        _set_pos(node, lineno, col, 0, 0)
        return node

    # 字符串字面量 pattern
    if ttype == TokenType.String:
        sval = _cur_str(ps)
        _advance(ps)
        val = _new_const_str(ps.pool, sval, lineno, col)
        node = MatchValue(ps.pool, val)
        _set_pos(node, lineno, col, 0, 0)
        return node

    # 负数字面量 pattern: -1, -1.5
    if _match_op(ps, TokOp.Minus):
        _advance(ps)
        if _cur_type(ps) == TokenType.Number:
            tok = ps.cur
            _advance(ps)
            if tok.is_float:
                val = _new_const_float(ps.pool, -tok.float_val, lineno, col)
            else:
                val = _new_const_int(ps.pool, -tok.int_val, lineno, col)
            node = MatchValue(ps.pool, val)
            _set_pos(node, lineno, col, 0, 0)
            return node
        ps.error_count += 1
        return None

    # Name: None/True/False/_ /capture/value/class
    if ttype == TokenType.Name:
        kw: t.CInt = _cur_kw(ps)
        # None/True/False → MatchSingleton
        if kw == Keyword.None_ or kw == Keyword.True_ or kw == Keyword.False_:
            _advance(ps)
            if kw == Keyword.None_:
                val = _new_const_none(ps.pool, lineno, col)
            elif kw == Keyword.True_:
                val = _new_const_bool(ps.pool, 1, lineno, col)
            else:
                val = _new_const_bool(ps.pool, 0, lineno, col)
            node = MatchSingleton(ps.pool, val)
            _set_pos(node, lineno, col, 0, 0)
            return node
        name_str: str = _cur_str(ps)
        # 通配符 _
        if string.strcmp(name_str, "_") == 0:
            _advance(ps)
            node = MatchAs(ps.pool, None, None)
            _set_pos(node, lineno, col, 0, 0)
            return node
        _advance(ps)
        # 检查 dotted name（value pattern 或 class pattern）
        if _match_op(ps, TokOp.Dot):
            name_node: Name | t.CPtr = Name(ps.pool, name_str, ASTCtx.Load)
            _set_pos(name_node, lineno, col, 0, 0)
            base: AST | t.CPtr = _make_name_ast(ps.pool, name_node)
            while _match_op(ps, TokOp.Dot):
                _advance(ps)
                if _cur_type(ps) != TokenType.Name:
                    ps.error_count += 1
                    break
                attr_name: str = _cur_str(ps)
                attr_lineno: t.CInt = _cur_lineno(ps)
                attr_col: t.CInt = _cur_col(ps)
                _advance(ps)
                base = Attribute(ps.pool, base, attr_name, ASTCtx.Load)
                _set_pos(base, attr_lineno, attr_col, 0, 0)
            # class pattern: dotted.name(...)
            if _match_op(ps, TokOp.LPar):
                return _parse_class_pattern(ps, base, lineno, col)
            # value pattern: dotted.name
            node = MatchValue(ps.pool, base)
            _set_pos(node, lineno, col, 0, 0)
            return node
        # class pattern: Name(...)
        if _match_op(ps, TokOp.LPar):
            cls_node: Name | t.CPtr = Name(ps.pool, name_str, ASTCtx.Load)
            _set_pos(cls_node, lineno, col, 0, 0)
            cls: AST | t.CPtr = _make_name_ast(ps.pool, cls_node)
            return _parse_class_pattern(ps, cls, lineno, col)
        # capture pattern: NAME
        node = MatchAs(ps.pool, None, name_str)
        _set_pos(node, lineno, col, 0, 0)
        return node

    # group pattern / sequence pattern (tuple): (...)
    if _match_op(ps, TokOp.LPar):
        _advance(ps)
        inner: AST | t.CPtr = _parse_pattern(ps)
        if _accept_op(ps, TokOp.Comma):
            # sequence pattern (tuple)
            patterns: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
            if inner is not None:
                patterns.append(inner)
            while not _match_op(ps, TokOp.RPar) and _cur_type(ps) != TokenType.EndMarker:
                p: AST | t.CPtr = _parse_pattern(ps)
                if p is not None:
                    patterns.append(p)
                if not _accept_op(ps, TokOp.Comma):
                    break
            _expect_op(ps, TokOp.RPar)
            node = MatchSequence(ps.pool, patterns)
            _set_pos(node, lineno, col, 0, 0)
            return node
        _expect_op(ps, TokOp.RPar)
        return inner  # group pattern

    # sequence pattern (list): [...]
    if _match_op(ps, TokOp.Lsqb):
        return _parse_sequence_pattern(ps, TokOp.Rsqb, lineno, col)

    # mapping pattern: {...}
    if _match_op(ps, TokOp.LBrace):
        return _parse_mapping_pattern(ps, lineno, col)

    ps.error_count += 1
    return None


def _parse_sequence_pattern(ps: Parser | t.CPtr, close_op: t.CInt,
                             lineno: t.CInt, col: t.CInt) -> AST | t.CPtr:
    """sequence_pattern: '[' [pattern_list] ']'"""
    patterns: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
    _advance(ps)  # [
    while not _match_op(ps, close_op) and _cur_type(ps) != TokenType.EndMarker:
        # *name (star pattern)
        if _match_op(ps, TokOp.Star):
            _advance(ps)
            if _cur_type(ps) == TokenType.Name:
                star_name: str = _cur_str(ps)
                _advance(ps)
                star_node: AST | t.CPtr
                if string.strcmp(star_name, "_") == 0:
                    star_node = MatchStar(ps.pool, None)
                else:
                    star_node = MatchStar(ps.pool, star_name)
                _set_pos(star_node, lineno, col, 0, 0)
                patterns.append(star_node)
        else:
            p: AST | t.CPtr = _parse_pattern(ps)
            if p is not None:
                patterns.append(p)
        if not _accept_op(ps, TokOp.Comma):
            break
    _expect_op(ps, close_op)
    node: AST | t.CPtr = MatchSequence(ps.pool, patterns)
    _set_pos(node, lineno, col, 0, 0)
    return node


def _parse_mapping_pattern(ps: Parser | t.CPtr,
                            lineno: t.CInt, col: t.CInt) -> AST | t.CPtr:
    """mapping_pattern: '{' [items] '}'
    items: key ':' pattern (',' key ':' pattern)* [',' '**' NAME] | '**' NAME"""
    keys: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
    patterns: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
    rest: str = None
    node: AST | t.CPtr
    key: AST | t.CPtr
    val_pat: AST | t.CPtr
    _advance(ps)  # {
    if _match_op(ps, TokOp.RBrace):
        _advance(ps)
        node = MatchMapping(ps.pool, keys, patterns, rest)
        _set_pos(node, lineno, col, 0, 0)
        return node
    while _cur_type(ps) != TokenType.EndMarker:
        if _match_op(ps, TokOp.StarStar):
            _advance(ps)
            if _cur_type(ps) == TokenType.Name:
                rest = _cur_str(ps)
                _advance(ps)
            break
        key = _parse_expr(ps)
        _expect_op(ps, TokOp.Colon)
        val_pat = _parse_pattern(ps)
        if key is not None:
            keys.append(key)
        if val_pat is not None:
            patterns.append(val_pat)
        if not _accept_op(ps, TokOp.Comma):
            break
    _expect_op(ps, TokOp.RBrace)
    node = MatchMapping(ps.pool, keys, patterns, rest)
    _set_pos(node, lineno, col, 0, 0)
    return node


def _parse_class_pattern(ps: Parser | t.CPtr, cls: AST | t.CPtr,
                          lineno: t.CInt, col: t.CInt) -> AST | t.CPtr:
    """class_pattern: cls '(' [pattern_list] ')'
    pattern_list: positional_pattern (',' positional_pattern)*
                  [',' keyword_pattern]*  | keyword_pattern (',' keyword_pattern)*
    keyword_pattern: NAME '=' pattern"""
    patterns: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
    kwd_attrs: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
    kwd_patterns: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
    p: AST | t.CPtr
    _advance(ps)  # (
    if not _match_op(ps, TokOp.RPar):
        while _cur_type(ps) != TokenType.EndMarker:
            # keyword pattern: NAME '=' pattern
            if _cur_type(ps) == TokenType.Name and ps.cur is not None:
                nxt: Token | t.CPtr = ps.cur.next
                if nxt is not None and nxt.type == TokenType.Op and nxt.op_subtype == TokOp.Equal:
                    attr_name: str = _cur_str(ps)
                    _advance(ps)  # NAME
                    _advance(ps)  # =
                    kwd_pat: AST | t.CPtr = _parse_pattern(ps)
                    attr_n: Name | t.CPtr = Name(ps.pool, attr_name, ASTCtx.Load)
                    _set_pos(attr_n, lineno, col, 0, 0)
                    attr_node: AST | t.CPtr = _make_name_ast(ps.pool, attr_n)
                    kwd_attrs.append(attr_node)
                    if kwd_pat is not None:
                        kwd_patterns.append(kwd_pat)
                else:
                    p = _parse_pattern(ps)
                    if p is not None:
                        patterns.append(p)
            else:
                p = _parse_pattern(ps)
                if p is not None:
                    patterns.append(p)
            if not _accept_op(ps, TokOp.Comma):
                break
    _expect_op(ps, TokOp.RPar)
    node: AST | t.CPtr = MatchClass(ps.pool, cls, patterns, kwd_attrs, kwd_patterns)
    _set_pos(node, lineno, col, 0, 0)
    return node


def _parse_match_case(ps: Parser | t.CPtr) -> AST | t.CPtr:
    """case pattern [if guard] ':' suite"""
    lineno: t.CInt = _cur_lineno(ps)
    col: t.CInt = _cur_col(ps)
    _advance(ps)  # case
    guard: AST | t.CPtr = None
    pattern: AST | t.CPtr = _parse_pattern(ps)
    if _cur_kw(ps) == Keyword.If:
        _advance(ps)
        guard = _parse_or_test(ps)
    _expect_op(ps, TokOp.Colon)
    node: AST | t.CPtr = MatchCase(ps.pool, pattern, guard)
    _parse_suite_into_children(ps, node)
    _set_pos(node, lineno, col, 0, 0)
    return node


def _parse_decorators(ps: Parser | t.CPtr) -> list[AST | t.CPtr] | t.CPtr:
    """解析装饰器链，返回 list。"""
    decs: list[AST | t.CPtr] | t.CPtr = list[AST | t.CPtr](ps.pool, 8)
    while _match_op(ps, TokOp.At):
        lineno: t.CInt = _cur_lineno(ps)
        col: t.CInt = _cur_col(ps)
        _advance(ps)  # @
        expr: AST | t.CPtr = _parse_namedexpr(ps)
        _skip_nl(ps)
        if _cur_type(ps) == TokenType.NewLine:
            _advance(ps)
        decs.append(expr)
    return decs


# ============================================================
# 模块入口
# ============================================================

def _parse_module_body(ps: Parser | t.CPtr, module_node: AST | t.CPtr):
    """解析模块体，append 到 module_node。"""
    _skip_nl(ps)
    stmt_idx: t.CInt = 0
    while _cur_type(ps) != TokenType.EndMarker:
        stmt_idx += 1
        # 装饰器
        if _match_op(ps, TokOp.At):
            ps.pending_decorators = _parse_decorators(ps)
        # 记录解析前的 token 指针，用于检测未前进的死循环
        before_cur: Token | t.CPtr = ps.cur
        _parse_statement_into(ps, module_node)
        # 防御：若解析未前进 token（如 _parse_atom 遇到无法识别的 token 返回 None），
        # 强制前进以避免主循环死循环；累计 error_count 供调用方诊断
        if ps.cur is before_cur:
            ps.error_count += 1
            _advance(ps)
        _skip_nl(ps)


def parse_tokens(pool: memhub.MemManager | t.CPtr,
                 tokens: Token | t.CPtr) -> AST | t.CPtr:
    """从 token 链表解析 AST Module。"""
    if pool is None or tokens is None:
        return None
    ps: Parser | t.CPtr = new_parser(pool)
    if ps is None: return None
    _parser_init(ps, tokens, pool)
    module_node: AST | t.CPtr = Module(pool)
    _parse_module_body(ps, module_node)
    return module_node
