from __future__ import annotations

from typing import TYPE_CHECKING

from sphinx.domains.c._ast import (
    ASTAlignofExpr,
    ASTArray,
    ASTAssignmentExpr,
    ASTBinOpExpr,
    ASTBooleanLiteral,
    ASTBracedInitList,
    ASTCastExpr,
    ASTCharLiteral,
    ASTDeclaration,
    ASTDeclaratorNameBitField,
    ASTDeclaratorNameParam,
    ASTDeclaratorParen,
    ASTDeclaratorPtr,
    ASTDeclSpecs,
    ASTDeclSpecsSimple,
    ASTEnum,
    ASTEnumerator,
    ASTFallbackExpr,
    ASTFunctionParameter,
    ASTIdentifier,
    ASTIdExpression,
    ASTInitializer,
    ASTMacro,
    ASTMacroParameter,
    ASTNestedName,
    ASTNumberLiteral,
    ASTParameters,
    ASTParenExpr,
    ASTParenExprList,
    ASTPostfixArray,
    ASTPostfixCallExpr,
    ASTPostfixDec,
    ASTPostfixExpr,
    ASTPostfixInc,
    ASTPostfixMemberOfPointer,
    ASTSizeofExpr,
    ASTSizeofType,
    ASTStringLiteral,
    ASTStruct,
    ASTTrailingTypeSpecFundamental,
    ASTTrailingTypeSpecName,
    ASTType,
    ASTTypeWithInit,
    ASTUnaryOpExpr,
    ASTUnion,
)
from sphinx.domains.c._ids import (
    _expression_assignment_ops,
    _expression_bin_ops,
    _expression_unary_ops,
    _keywords,
    _simple_type_specifiers_re,
    _string_re,
)
from sphinx.util.cfamily import (
    ASTAttributeList,
    BaseParser,
    DefinitionError,
    UnsupportedMultiCharacterCharLiteral,
    binary_literal_re,
    char_literal_re,
    float_literal_re,
    float_literal_suffix_re,
    hex_literal_re,
    identifier_re,
    integer_literal_re,
    integers_literal_suffix_re,
    octal_literal_re,
)

if TYPE_CHECKING:
    from collections.abc import Callable, Sequence
    from typing import Any

    from sphinx.domains.c._ast import (
        ASTDeclarator,
        ASTExpression,
        ASTLiteral,
        ASTPostfixOp,
        ASTTrailingTypeSpec,
        DeclarationType,
    )


class DefinitionParser(BaseParser):
    @property
    def language(self) -> str:
        return 'C'

    @property
    def id_attributes(self) -> Sequence[str]:
        return self.config.c_id_attributes

    @property
    def paren_attributes(self) -> Sequence[str]:
        return self.config.c_paren_attributes

    def _parse_string(self) -> str | None:
        if self.current_char != '"':
            return None
        start_pos = self.pos
        self.pos += 1
        escape = False
        while True:
            if self.eof:
                self.fail('Unexpected end during inside string.')
            elif self.current_char == '"' and not escape:
                self.pos += 1
                break
            elif self.current_char == '\\':
                escape = True
            else:
                escape = False
            self.pos += 1
        return self.definition[start_pos : self.pos]

    def _parse_literal(self) -> ASTLiteral | None:
        # -> integer-literal
        #  | character-literal
        #  | floating-literal
        #  | string-literal
        #  | boolean-literal -> "false" | "true"
        self.skip_ws()
        if self.skip_word('true'):
            return ASTBooleanLiteral(True)
        if self.skip_word('false'):
            return ASTBooleanLiteral(False)
        pos = self.pos
        if self.match(float_literal_re):
            self.match(float_literal_suffix_re)
            return ASTNumberLiteral(self.definition[pos : self.pos])
        for regex in (
            binary_literal_re,
            hex_literal_re,
            integer_literal_re,
            octal_literal_re,
        ):
            if self.match(regex):
                self.match(integers_literal_suffix_re)
                return ASTNumberLiteral(self.definition[pos : self.pos])

        string = self._parse_string()
        if string is not None:
            return ASTStringLiteral(string)

        # character-literal
        if self.match(char_literal_re):
            prefix = self.last_match.group(1)  # may be None when no prefix
            data = self.last_match.group(2)
            try:
                return ASTCharLiteral(prefix, data)
            except UnicodeDecodeError as e:
                self.fail(
                    'Can not handle character literal. Internal error was: %s' % e
                )
            except UnsupportedMultiCharacterCharLiteral:
                self.fail(
                    'Can not handle character literal'
                    ' resulting in multiple decoded characters.'
                )
        return None

    def _parse_paren_expression(self) -> ASTExpression | None:
        # "(" expression ")"
        if self.current_char != '(':
            return None
        self.pos += 1
        res = self._parse_expression()
        self.skip_ws()
        if not self.skip_string(')'):
            self.fail("Expected ')' in end of parenthesized expression.")
        return ASTParenExpr(res)

    def _parse_primary_expression(self) -> ASTExpression | None:
        # literal
        # "(" expression ")"
        # id-expression -> we parse this with _parse_nested_name
        self.skip_ws()
        res: ASTExpression | None = self._parse_literal()
        if res is not None:
            return res
        res = self._parse_paren_expression()
        if res is not None:
            return res
        nn = self._parse_nested_name()
        if nn is not None:
            return ASTIdExpression(nn)
        return None

    def _parse_initializer_list(
        self, name: str, open: str, close: str
    ) -> tuple[list[ASTExpression] | None, bool | None]:
        # Parse open and close with the actual initializer-list in between
        # -> initializer-clause '...'[opt]
        #  | initializer-list ',' initializer-clause '...'[opt]
        # TODO: designators
        self.skip_ws()
        if not self.skip_string_and_ws(open):
            return None, None
        if self.skip_string(close):
            return [], False

        exprs = []
        trailing_comma = False
        while True:
            self.skip_ws()
            expr = self._parse_expression()
            self.skip_ws()
            exprs.append(expr)
            self.skip_ws()
            if self.skip_string(close):
                break
            if not self.skip_string_and_ws(','):
                self.fail(f"Error in {name}, expected ',' or '{close}'.")
            if self.current_char == close == '}':
                self.pos += 1
                trailing_comma = True
                break
        return exprs, trailing_comma

    def _parse_paren_expression_list(self) -> ASTParenExprList | None:
        # -> '(' expression-list ')'
        # though, we relax it to also allow empty parens
        # as it's needed in some cases
        #
        # expression-list
        # -> initializer-list
        exprs, _trailing_comma = self._parse_initializer_list(
            'parenthesized expression-list', '(', ')'
        )
        if exprs is None:
            return None
        return ASTParenExprList(exprs)

    def _parse_braced_init_list(self) -> ASTBracedInitList | None:
        # -> '{' initializer-list ','[opt] '}'
        #  | '{' '}'
        exprs, trailing_comma = self._parse_initializer_list(
            'braced-init-list', '{', '}'
        )
        if exprs is None:
            return None
        return ASTBracedInitList(exprs, trailing_comma)

    def _parse_postfix_expression(self) -> ASTPostfixExpr:
        # -> primary
        #  | postfix "[" expression "]"
        #  | postfix "[" braced-init-list [opt] "]"
        #  | postfix "(" expression-list [opt] ")"
        #  | postfix "." id-expression  // taken care of in primary by nested name
        #  | postfix "->" id-expression
        #  | postfix "++"
        #  | postfix "--"

        prefix = self._parse_primary_expression()

        # and now parse postfixes
        post_fixes: list[ASTPostfixOp] = []
        while True:
            self.skip_ws()
            if self.skip_string_and_ws('['):
                expr = self._parse_expression()
                self.skip_ws()
                if not self.skip_string(']'):
                    self.fail("Expected ']' in end of postfix expression.")
                post_fixes.append(ASTPostfixArray(expr))
                continue
            if self.skip_string('->'):
                if self.skip_string('*'):
                    # don't steal the arrow
                    self.pos -= 3
                else:
                    name = self._parse_nested_name()
                    post_fixes.append(ASTPostfixMemberOfPointer(name))
                    continue
            if self.skip_string('++'):
                post_fixes.append(ASTPostfixInc())
                continue
            if self.skip_string('--'):
                post_fixes.append(ASTPostfixDec())
                continue
            lst = self._parse_paren_expression_list()
            if lst is not None:
                post_fixes.append(ASTPostfixCallExpr(lst))
                continue
            break
        return ASTPostfixExpr(prefix, post_fixes)

    def _parse_unary_expression(self) -> ASTExpression:
        # -> postfix
        #  | "++" cast
        #  | "--" cast
        #  | unary-operator cast -> (* | & | + | - | ! | ~) cast
        # The rest:
        #  | "sizeof" unary
        #  | "sizeof" "(" type-id ")"
        #  | "alignof" "(" type-id ")"
        self.skip_ws()
        for op in _expression_unary_ops:
            # TODO: hmm, should we be able to backtrack here?
            if op[0] in 'cn':
                res = self.skip_word(op)
            else:
                res = self.skip_string(op)
            if res:
                expr = self._parse_cast_expression()
                return ASTUnaryOpExpr(op, expr)
        if self.skip_word_and_ws('sizeof'):
            if self.skip_string_and_ws('('):
                typ = self._parse_type(named=False)
                self.skip_ws()
                if not self.skip_string(')'):
                    self.fail("Expecting ')' to end 'sizeof'.")
                return ASTSizeofType(typ)
            expr = self._parse_unary_expression()
            return ASTSizeofExpr(expr)
        if self.skip_word_and_ws('alignof'):
            if not self.skip_string_and_ws('('):
                self.fail("Expecting '(' after 'alignof'.")
            typ = self._parse_type(named=False)
            self.skip_ws()
            if not self.skip_string(')'):
                self.fail("Expecting ')' to end 'alignof'.")
            return ASTAlignofExpr(typ)
        return self._parse_postfix_expression()

    def _parse_cast_expression(self) -> ASTExpression:
        # -> unary  | "(" type-id ")" cast
        pos = self.pos
        self.skip_ws()
        if self.skip_string('('):
            try:
                typ = self._parse_type(False)
                if not self.skip_string(')'):
                    self.fail("Expected ')' in cast expression.")
                expr = self._parse_cast_expression()
                return ASTCastExpr(typ, expr)
            except DefinitionError as ex_cast:
                self.pos = pos
                try:
                    return self._parse_unary_expression()
                except DefinitionError as ex_unary:
                    errs = [
                        (ex_cast, 'If type cast expression'),
                        (ex_unary, 'If unary expression'),
                    ]
                    raise self._make_multi_error(
                        errs, 'Error in cast expression.'
                    ) from ex_unary
        else:
            return self._parse_unary_expression()

    def _parse_logical_or_expression(self) -> ASTExpression:
        # logical-or     = logical-and      ||
        # logical-and    = inclusive-or     &&
        # inclusive-or   = exclusive-or     |
        # exclusive-or   = and              ^
        # and            = equality         &
        # equality       = relational       ==, !=
        # relational     = shift            <, >, <=, >=
        # shift          = additive         <<, >>
        # additive       = multiplicative   +, -
        # multiplicative = pm               *, /, %
        # pm             = cast             .*, ->*
        def _parse_bin_op_expr(self: DefinitionParser, op_id: int) -> ASTExpression:
            if op_id + 1 == len(_expression_bin_ops):
                parser = self._parse_cast_expression
            else:

                def parser() -> ASTExpression:
                    return _parse_bin_op_expr(self, op_id + 1)

            exprs = []
            ops = []
            exprs.append(parser())
            while True:
                self.skip_ws()
                pos = self.pos
                one_more = False
                for op in _expression_bin_ops[op_id]:
                    if op[0] in 'abcnox':
                        if not self.skip_word(op):
                            continue
                    else:
                        if not self.skip_string(op):
                            continue
                    if op == self.current_char == '&':
                        # don't split the && 'token'
                        self.pos -= 1
                        # and btw. && has lower precedence, so we are done
                        break
                    try:
                        expr = parser()
                        exprs.append(expr)
                        ops.append(op)
                        one_more = True
                        break
                    except DefinitionError:
                        self.pos = pos
                if not one_more:
                    break
            return ASTBinOpExpr(exprs, ops)  # type: ignore[return-value]

        return _parse_bin_op_expr(self, 0)

    def _parse_conditional_expression_tail(
        self, or_expr_head: Any
    ) -> ASTExpression | None:
        # -> "?" expression ":" assignment-expression
        return None

    def _parse_assignment_expression(self) -> ASTExpression:
        # -> conditional-expression
        #  | logical-or-expression assignment-operator initializer-clause
        # -> conditional-expression ->
        #     logical-or-expression
        #   | logical-or-expression "?" expression ":" assignment-expression
        #   | logical-or-expression assignment-operator initializer-clause
        exprs = []
        ops = []
        or_expr = self._parse_logical_or_expression()
        exprs.append(or_expr)
        # TODO: handle ternary with _parse_conditional_expression_tail
        while True:
            one_more = False
            self.skip_ws()
            for op in _expression_assignment_ops:
                if op[0] in 'abcnox':
                    if not self.skip_word(op):
                        continue
                else:
                    if not self.skip_string(op):
                        continue
                expr = self._parse_logical_or_expression()
                exprs.append(expr)
                ops.append(op)
                one_more = True
            if not one_more:
                break
        return ASTAssignmentExpr(exprs, ops)

    def _parse_constant_expression(self) -> ASTExpression:
        # -> conditional-expression
        or_expr = self._parse_logical_or_expression()
        # TODO: use _parse_conditional_expression_tail
        return or_expr

    def _parse_expression(self) -> ASTExpression:
        # -> assignment-expression
        #  | expression "," assignment-expression
        # TODO: actually parse the second production
        return self._parse_assignment_expression()

    def _parse_expression_fallback(
        self, end: list[str], parser: Callable[[], ASTExpression], allow: bool = True
    ) -> ASTExpression:
        # Stupidly "parse" an expression.
        # 'end' should be a list of characters which ends the expression.

        # first try to use the provided parser
        prev_pos = self.pos
        try:
            return parser()
        except DefinitionError as e:
            # some places (e.g., template parameters) we really don't want to use fallback,
            # and for testing we may want to globally disable it
            if not allow or not self.allowFallbackExpressionParsing:
                raise
            self.warn(
                'Parsing of expression failed. Using fallback parser.'
                ' Error was:\n%s' % e
            )
            self.pos = prev_pos
        # and then the fallback scanning
        assert end is not None
        self.skip_ws()
        start_pos = self.pos
        if self.match(_string_re):
            value = self.matched_text
        else:
            # TODO: add handling of more bracket-like things, and quote handling
            brackets = {'(': ')', '{': '}', '[': ']'}
            symbols: list[str] = []
            while not self.eof:
                if len(symbols) == 0 and self.current_char in end:
                    break
                if self.current_char in brackets:
                    symbols.append(brackets[self.current_char])
                elif len(symbols) > 0 and self.current_char == symbols[-1]:
                    symbols.pop()
                self.pos += 1
            if len(end) > 0 and self.eof:
                self.fail(
                    'Could not find end of expression starting at %d.' % start_pos
                )
            value = self.definition[start_pos : self.pos].strip()
        return ASTFallbackExpr(value.strip())

    def _parse_nested_name(self) -> ASTNestedName:
        names: list[Any] = []

        self.skip_ws()
        rooted = False
        if self.skip_string('.'):
            rooted = True
        while 1:
            self.skip_ws()
            if not self.match(identifier_re):
                self.fail('Expected identifier in nested name.')
            identifier = self.matched_text
            # make sure there isn't a keyword
            if identifier in _keywords:
                self.fail(
                    'Expected identifier in nested name, got keyword: %s' % identifier
                )
            if self.matched_text in self.config.c_extra_keywords:
                msg = (
                    'Expected identifier, got user-defined keyword: %s.'
                    ' Remove it from c_extra_keywords to allow it as identifier.\n'
                    'Currently c_extra_keywords is %s.'
                )
                self.fail(msg % (self.matched_text, str(self.config.c_extra_keywords)))
            ident = ASTIdentifier(identifier)
            names.append(ident)

            self.skip_ws()
            if not self.skip_string('.'):
                break
        return ASTNestedName(names, rooted)

    def _parse_simple_type_specifier(self) -> str | None:
        if self.match(_simple_type_specifiers_re):
            return self.matched_text
        for t in ('bool', 'complex', 'imaginary'):
            if t in self.config.c_extra_keywords:
                if self.skip_word(t):
                    return t
        return None

    def _parse_simple_type_specifiers(self) -> ASTTrailingTypeSpecFundamental | None:
        names: list[str] = []

        self.skip_ws()
        while True:
            t = self._parse_simple_type_specifier()
            if t is None:
                break
            names.append(t)
            self.skip_ws()
        if len(names) == 0:
            return None
        return ASTTrailingTypeSpecFundamental(names)

    def _parse_trailing_type_spec(self) -> ASTTrailingTypeSpec:
        # fundamental types, https://en.cppreference.com/w/c/language/type
        # and extensions
        self.skip_ws()
        res = self._parse_simple_type_specifiers()
        if res is not None:
            return res

        # prefixed
        prefix = None
        self.skip_ws()
        for k in ('struct', 'enum', 'union'):
            if self.skip_word_and_ws(k):
                prefix = k
                break

        nested_name = self._parse_nested_name()
        return ASTTrailingTypeSpecName(prefix, nested_name)

    def _parse_parameters(self, param_mode: str) -> ASTParameters | None:
        self.skip_ws()
        if not self.skip_string('('):
            if param_mode == 'function':
                self.fail('Expecting "(" in parameters.')
            else:
                return None

        args = []
        self.skip_ws()
        if not self.skip_string(')'):
            while 1:
                self.skip_ws()
                if self.skip_string('...'):
                    args.append(ASTFunctionParameter(None, True))
                    self.skip_ws()
                    if not self.skip_string(')'):
                        self.fail('Expected ")" after "..." in parameters.')
                    break
                # note: it seems that function arguments can always be named,
                # even in function pointers and similar.
                arg = self._parse_type_with_init(outer=None, named='single')
                # TODO: parse default parameters # TODO: didn't we just do that?
                args.append(ASTFunctionParameter(arg))

                self.skip_ws()
                if self.skip_string(','):
                    continue
                if self.skip_string(')'):
                    break
                self.fail(
                    f'Expecting "," or ")" in parameters, got "{self.current_char}".'
                )

        attrs = self._parse_attribute_list()
        return ASTParameters(args, attrs)

    def _parse_decl_specs_simple(
        self, outer: str | None, typed: bool
    ) -> ASTDeclSpecsSimple:
        """Just parse the simple ones."""
        storage = None
        thread_local = None
        inline = None
        restrict = None
        volatile = None
        const = None
        attrs = []
        while 1:  # accept any permutation of a subset of some decl-specs
            self.skip_ws()
            if not storage:
                if outer == 'member':
                    if self.skip_word('auto'):
                        storage = 'auto'
                        continue
                    if self.skip_word('register'):
                        storage = 'register'
                        continue
                if outer in {'member', 'function'}:
                    if self.skip_word('static'):
                        storage = 'static'
                        continue
                    if self.skip_word('extern'):
                        storage = 'extern'
                        continue
            if outer == 'member' and not thread_local:
                if self.skip_word('thread_local'):
                    thread_local = 'thread_local'
                    continue
                if self.skip_word('_Thread_local'):
                    thread_local = '_Thread_local'
                    continue
            if outer == 'function' and not inline:
                inline = self.skip_word('inline')
                if inline:
                    continue

            if not restrict and typed:
                restrict = self.skip_word('restrict')
                if restrict:
                    continue
            if not volatile and typed:
                volatile = self.skip_word('volatile')
                if volatile:
                    continue
            if not const and typed:
                const = self.skip_word('const')
                if const:
                    continue
            attr = self._parse_attribute()
            if attr:
                attrs.append(attr)
                continue
            break
        return ASTDeclSpecsSimple(
            storage,
            thread_local,
            inline,
            restrict,
            volatile,
            const,
            ASTAttributeList(attrs),
        )

    def _parse_decl_specs(self, outer: str | None, typed: bool = True) -> ASTDeclSpecs:
        if outer:
            if outer not in {'type', 'member', 'function'}:
                raise Exception('Internal error, unknown outer "%s".' % outer)
        left_specs = self._parse_decl_specs_simple(outer, typed)
        right_specs = None

        if typed:
            trailing = self._parse_trailing_type_spec()
            right_specs = self._parse_decl_specs_simple(outer, typed)
        else:
            trailing = None
        return ASTDeclSpecs(outer, left_specs, right_specs, trailing)

    def _parse_declarator_name_suffix(
        self, named: bool | str, param_mode: str, typed: bool
    ) -> ASTDeclarator:
        assert named in {True, False, 'single'}
        # now we should parse the name, and then suffixes
        if named == 'single':
            if self.match(identifier_re):
                if self.matched_text in _keywords:
                    self.fail(
                        'Expected identifier, got keyword: %s' % self.matched_text
                    )
                if self.matched_text in self.config.c_extra_keywords:
                    msg = (
                        'Expected identifier, got user-defined keyword: %s. '
                        'Remove it from c_extra_keywords to allow it as identifier.\n'
                        'Currently c_extra_keywords is %s.'
                    )
                    self.fail(
                        msg % (self.matched_text, str(self.config.c_extra_keywords))
                    )
                identifier = ASTIdentifier(self.matched_text)
                decl_id = ASTNestedName([identifier], rooted=False)
            else:
                decl_id = None
        elif named:
            decl_id = self._parse_nested_name()
        else:
            decl_id = None
        array_ops = []
        while 1:
            self.skip_ws()
            if typed and self.skip_string('['):
                self.skip_ws()
                static = False
                const = False
                volatile = False
                restrict = False
                while True:
                    if not static:
                        if self.skip_word_and_ws('static'):
                            static = True
                            continue
                    if not const:
                        if self.skip_word_and_ws('const'):
                            const = True
                            continue
                    if not volatile:
                        if self.skip_word_and_ws('volatile'):
                            volatile = True
                            continue
                    if not restrict:
                        if self.skip_word_and_ws('restrict'):
                            restrict = True
                            continue
                    break
                vla = False if static else self.skip_string_and_ws('*')
                if vla:
                    if not self.skip_string(']'):
                        self.fail("Expected ']' in end of array operator.")
                    size = None
                else:
                    if self.skip_string(']'):
                        size = None
                    else:
                        parser = self._parse_expression
                        size = self._parse_expression_fallback([']'], parser)
                        self.skip_ws()
                        if not self.skip_string(']'):
                            self.fail("Expected ']' in end of array operator.")
                array_ops.append(ASTArray(static, const, volatile, restrict, vla, size))
            else:
                break
        param = self._parse_parameters(param_mode)
        if param is None and len(array_ops) == 0:
            # perhaps a bit-field
            if named and param_mode == 'type' and typed:
                self.skip_ws()
                if self.skip_string(':'):
                    size = self._parse_constant_expression()
                    return ASTDeclaratorNameBitField(declId=decl_id, size=size)
        return ASTDeclaratorNameParam(declId=decl_id, arrayOps=array_ops, param=param)

    def _parse_declarator(
        self, named: bool | str, param_mode: str, typed: bool = True
    ) -> ASTDeclarator:
        # 'typed' here means 'parse return type stuff'
        if param_mode not in {'type', 'function'}:
            raise Exception("Internal error, unknown param_mode '%s'." % param_mode)
        prev_errors = []
        self.skip_ws()
        if typed and self.skip_string('*'):
            self.skip_ws()
            restrict = False
            volatile = False
            const = False
            attrs = []
            while 1:
                if not restrict:
                    restrict = self.skip_word_and_ws('restrict')
                    if restrict:
                        continue
                if not volatile:
                    volatile = self.skip_word_and_ws('volatile')
                    if volatile:
                        continue
                if not const:
                    const = self.skip_word_and_ws('const')
                    if const:
                        continue
                attr = self._parse_attribute()
                if attr is not None:
                    attrs.append(attr)
                    continue
                break
            next = self._parse_declarator(named, param_mode, typed)
            return ASTDeclaratorPtr(
                next=next,
                restrict=restrict,
                volatile=volatile,
                const=const,
                attrs=ASTAttributeList(attrs),
            )
        if typed and self.current_char == '(':  # note: peeking, not skipping
            # maybe this is the beginning of params, try that first,
            # otherwise assume it's noptr->declarator > ( ptr-declarator )
            pos = self.pos
            try:
                # assume this is params
                res = self._parse_declarator_name_suffix(named, param_mode, typed)
                return res
            except DefinitionError as ex_param_qual:
                msg = 'If declarator-id with parameters'
                if param_mode == 'function':
                    msg += " (e.g., 'void f(int arg)')"
                prev_errors.append((ex_param_qual, msg))
                self.pos = pos
                try:
                    assert self.current_char == '('
                    self.skip_string('(')
                    # TODO: hmm, if there is a name, it must be in inner, right?
                    # TODO: hmm, if there must be parameters, they must b
                    # inside, right?
                    inner = self._parse_declarator(named, param_mode, typed)
                    if not self.skip_string(')'):
                        self.fail('Expected \')\' in "( ptr-declarator )"')
                    next = self._parse_declarator(
                        named=False, param_mode='type', typed=typed
                    )
                    return ASTDeclaratorParen(inner=inner, next=next)
                except DefinitionError as ex_no_ptr_paren:
                    self.pos = pos
                    msg = 'If parenthesis in noptr-declarator'
                    if param_mode == 'function':
                        msg += " (e.g., 'void (*f(int arg))(double)')"
                    prev_errors.append((ex_no_ptr_paren, msg))
                    header = 'Error in declarator'
                    raise self._make_multi_error(
                        prev_errors, header
                    ) from ex_no_ptr_paren
        pos = self.pos
        try:
            return self._parse_declarator_name_suffix(named, param_mode, typed)
        except DefinitionError as e:
            self.pos = pos
            prev_errors.append((e, 'If declarator-id'))
            header = 'Error in declarator or parameters'
            raise self._make_multi_error(prev_errors, header) from e

    def _parse_initializer(
        self, outer: str | None = None, allow_fallback: bool = True
    ) -> ASTInitializer | None:
        self.skip_ws()
        if outer == 'member' and False:  # NoQA: SIM223,TD005  # TODO
            braced_init = self._parse_braced_init_list()
            if braced_init is not None:
                return ASTInitializer(braced_init, hasAssign=False)

        if not self.skip_string('='):
            return None

        braced_init = self._parse_braced_init_list()
        if braced_init is not None:
            return ASTInitializer(braced_init)

        if outer == 'member':
            fallback_end: list[str] = []
        elif outer is None:  # function parameter
            fallback_end = [',', ')']
        else:
            self.fail(
                "Internal error, initializer for outer '%s' not implemented." % outer
            )

        def parser() -> ASTExpression:
            return self._parse_assignment_expression()

        value = self._parse_expression_fallback(
            fallback_end, parser, allow=allow_fallback
        )
        return ASTInitializer(value)

    def _parse_type(self, named: bool | str, outer: str | None = None) -> ASTType:
        """named=False|'single'|True: 'single' is e.g., for function objects which
        doesn't need to name the arguments, but otherwise is a single name
        """
        if outer:  # always named
            if outer not in {'type', 'member', 'function'}:
                raise Exception('Internal error, unknown outer "%s".' % outer)
            assert named

        if outer == 'type':
            # We allow type objects to just be a name.
            prev_errors = []
            start_pos = self.pos
            # first try without the type
            try:
                decl_specs = self._parse_decl_specs(outer=outer, typed=False)
                decl = self._parse_declarator(named=True, param_mode=outer, typed=False)
                self.assert_end(allowSemicolon=True)
            except DefinitionError as ex_untyped:
                desc = 'If just a name'
                prev_errors.append((ex_untyped, desc))
                self.pos = start_pos
                try:
                    decl_specs = self._parse_decl_specs(outer=outer)
                    decl = self._parse_declarator(named=True, param_mode=outer)
                except DefinitionError as ex_typed:
                    self.pos = start_pos
                    desc = 'If typedef-like declaration'
                    prev_errors.append((ex_typed, desc))
                    # Retain the else branch for easier debugging.
                    # TODO: it would be nice to save the previous stacktrace
                    #       and output it here.
                    if True:
                        header = 'Type must be either just a name or a '
                        header += 'typedef-like declaration.'
                        raise self._make_multi_error(prev_errors, header) from ex_typed
                    else:
                        # For testing purposes.
                        # do it again to get the proper traceback (how do you
                        # reliably save a traceback when an exception is
                        # constructed?)
                        self.pos = start_pos
                        typed = True
                        decl_specs = self._parse_decl_specs(outer=outer, typed=typed)
                        decl = self._parse_declarator(
                            named=True, param_mode=outer, typed=typed
                        )
        elif outer == 'function':
            decl_specs = self._parse_decl_specs(outer=outer)
            decl = self._parse_declarator(named=True, param_mode=outer)
        else:
            param_mode = 'type'
            if outer == 'member':  # i.e., member
                named = True
            decl_specs = self._parse_decl_specs(outer=outer)
            decl = self._parse_declarator(named=named, param_mode=param_mode)
        return ASTType(decl_specs, decl)

    def _parse_type_with_init(
        self, named: bool | str, outer: str | None
    ) -> ASTTypeWithInit:
        if outer:
            assert outer in {'type', 'member', 'function'}
        type = self._parse_type(outer=outer, named=named)
        init = self._parse_initializer(outer=outer)
        return ASTTypeWithInit(type, init)

    def _parse_macro(self) -> ASTMacro:
        self.skip_ws()
        ident = self._parse_nested_name()
        if ident is None:
            self.fail('Expected identifier in macro definition.')
        self.skip_ws()
        if not self.skip_string_and_ws('('):
            return ASTMacro(ident, None)
        if self.skip_string(')'):
            return ASTMacro(ident, [])
        args = []
        while 1:
            self.skip_ws()
            if self.skip_string('...'):
                args.append(ASTMacroParameter(None, True))
                self.skip_ws()
                if not self.skip_string(')'):
                    self.fail('Expected ")" after "..." in macro parameters.')
                break
            if not self.match(identifier_re):
                self.fail('Expected identifier in macro parameters.')
            nn = ASTNestedName([ASTIdentifier(self.matched_text)], rooted=False)
            # Allow named variadic args:
            # https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html
            self.skip_ws()
            if self.skip_string_and_ws('...'):
                args.append(ASTMacroParameter(nn, False, True))
                self.skip_ws()
                if not self.skip_string(')'):
                    self.fail('Expected ")" after "..." in macro parameters.')
                break
            args.append(ASTMacroParameter(nn))
            if self.skip_string_and_ws(','):
                continue
            if self.skip_string_and_ws(')'):
                break
            self.fail("Expected identifier, ')', or ',' in macro parameter list.")
        return ASTMacro(ident, args)

    def _parse_struct(self) -> ASTStruct:
        name = self._parse_nested_name()
        return ASTStruct(name)

    def _parse_union(self) -> ASTUnion:
        name = self._parse_nested_name()
        return ASTUnion(name)

    def _parse_enum(self) -> ASTEnum:
        name = self._parse_nested_name()
        return ASTEnum(name)

    def _parse_enumerator(self) -> ASTEnumerator:
        name = self._parse_nested_name()
        attrs = self._parse_attribute_list()
        self.skip_ws()
        init = None
        if self.skip_string('='):
            self.skip_ws()
            parser = self._parse_constant_expression
            init_val = self._parse_expression_fallback([], parser)
            init = ASTInitializer(init_val)
        return ASTEnumerator(name, init, attrs)

    def parse_declaration(self, objectType: str, directiveType: str) -> ASTDeclaration:
        object_type = objectType
        directive_type = directiveType
        if object_type not in {
            'function',
            'member',
            'macro',
            'struct',
            'union',
            'enum',
            'enumerator',
            'type',
        }:
            raise Exception('Internal error, unknown objectType "%s".' % object_type)
        if directive_type not in {
            'function',
            'member',
            'var',
            'macro',
            'struct',
            'union',
            'enum',
            'enumerator',
            'type',
        }:
            raise Exception(
                'Internal error, unknown directiveType "%s".' % directive_type
            )

        declaration: DeclarationType | None = None
        if object_type == 'member':
            declaration = self._parse_type_with_init(named=True, outer='member')
        elif object_type == 'function':
            declaration = self._parse_type(named=True, outer='function')
        elif object_type == 'macro':
            declaration = self._parse_macro()
        elif object_type == 'struct':
            declaration = self._parse_struct()
        elif object_type == 'union':
            declaration = self._parse_union()
        elif object_type == 'enum':
            declaration = self._parse_enum()
        elif object_type == 'enumerator':
            declaration = self._parse_enumerator()
        elif object_type == 'type':
            declaration = self._parse_type(named=True, outer='type')
        else:
            raise AssertionError
        if object_type != 'macro':
            self.skip_ws()
            semicolon = self.skip_string(';')
        else:
            semicolon = False
        return ASTDeclaration(object_type, directive_type, declaration, semicolon)

    def parse_namespace_object(self) -> ASTNestedName:
        return self._parse_nested_name()

    def parse_xref_object(self) -> ASTNestedName:
        name = self._parse_nested_name()
        # if there are '()' left, just skip them
        self.skip_ws()
        self.skip_string('()')
        self.assert_end()
        return name

    def parse_expression(self) -> ASTExpression | ASTType:
        pos = self.pos
        res: ASTExpression | ASTType | None = None
        try:
            res = self._parse_expression()
            self.skip_ws()
            self.assert_end()
        except DefinitionError as ex_expr:
            self.pos = pos
            try:
                res = self._parse_type(False)
                self.skip_ws()
                self.assert_end()
            except DefinitionError as ex_type:
                header = 'Error when parsing (type) expression.'
                errs = [
                    (ex_expr, 'If expression'),
                    (ex_type, 'If type'),
                ]
                raise self._make_multi_error(errs, header) from ex_type
        return res
