| """The C language domain.""" |
| |
| from __future__ import annotations |
| |
| import re |
| from typing import TYPE_CHECKING, Any, Callable, TypeVar, Union, cast |
| |
| from docutils import nodes |
| from docutils.parsers.rst import directives |
| |
| from sphinx import addnodes |
| from sphinx.directives import ObjectDescription |
| from sphinx.domains import Domain, ObjType |
| from sphinx.locale import _, __ |
| from sphinx.roles import SphinxRole, XRefRole |
| from sphinx.transforms import SphinxTransform |
| from sphinx.transforms.post_transforms import ReferencesResolver |
| from sphinx.util import logging |
| from sphinx.util.cfamily import ( |
| ASTAttributeList, |
| ASTBaseBase, |
| ASTBaseParenExprList, |
| BaseParser, |
| DefinitionError, |
| NoOldIdError, |
| StringifyTransform, |
| UnsupportedMultiCharacterCharLiteral, |
| anon_identifier_re, |
| 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, |
| verify_description_mode, |
| ) |
| from sphinx.util.docfields import Field, GroupedField, TypedField |
| from sphinx.util.docutils import SphinxDirective |
| from sphinx.util.nodes import make_refnode |
| |
| if TYPE_CHECKING: |
| from collections.abc import Generator, Iterator |
| |
| from docutils.nodes import Element, Node, TextElement, system_message |
| |
| from sphinx.addnodes import pending_xref |
| from sphinx.application import Sphinx |
| from sphinx.builders import Builder |
| from sphinx.environment import BuildEnvironment |
| from sphinx.util.typing import OptionSpec |
| |
| logger = logging.getLogger(__name__) |
| T = TypeVar('T') |
| |
| DeclarationType = Union[ |
| "ASTStruct", "ASTUnion", "ASTEnum", "ASTEnumerator", |
| "ASTType", "ASTTypeWithInit", "ASTMacro", |
| ] |
| |
| # https://en.cppreference.com/w/c/keyword |
| _keywords = [ |
| 'auto', 'break', 'case', 'char', 'const', 'continue', 'default', 'do', 'double', |
| 'else', 'enum', 'extern', 'float', 'for', 'goto', 'if', 'inline', 'int', 'long', |
| 'register', 'restrict', 'return', 'short', 'signed', 'sizeof', 'static', 'struct', |
| 'switch', 'typedef', 'union', 'unsigned', 'void', 'volatile', 'while', |
| '_Alignas', '_Alignof', '_Atomic', '_Bool', '_Complex', |
| '_Decimal32', '_Decimal64', '_Decimal128', |
| '_Generic', '_Imaginary', '_Noreturn', '_Static_assert', '_Thread_local', |
| ] |
| # These are only keyword'y when the corresponding headers are included. |
| # They are used as default value for c_extra_keywords. |
| _macroKeywords = [ |
| 'alignas', 'alignof', 'bool', 'complex', 'imaginary', 'noreturn', 'static_assert', |
| 'thread_local', |
| ] |
| |
| # these are ordered by precedence |
| _expression_bin_ops = [ |
| ['||', 'or'], |
| ['&&', 'and'], |
| ['|', 'bitor'], |
| ['^', 'xor'], |
| ['&', 'bitand'], |
| ['==', '!=', 'not_eq'], |
| ['<=', '>=', '<', '>'], |
| ['<<', '>>'], |
| ['+', '-'], |
| ['*', '/', '%'], |
| ['.*', '->*'], |
| ] |
| _expression_unary_ops = ["++", "--", "*", "&", "+", "-", "!", "not", "~", "compl"] |
| _expression_assignment_ops = ["=", "*=", "/=", "%=", "+=", "-=", |
| ">>=", "<<=", "&=", "and_eq", "^=", "xor_eq", "|=", "or_eq"] |
| |
| _max_id = 1 |
| _id_prefix = [None, 'c.', 'Cv2.'] |
| # Ids are used in lookup keys which are used across pickled files, |
| # so when _max_id changes, make sure to update the ENV_VERSION. |
| |
| _string_re = re.compile(r"[LuU8]?('([^'\\]*(?:\\.[^'\\]*)*)'" |
| r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S) |
| |
| # bool, complex, and imaginary are macro "keywords", so they are handled separately |
| _simple_type_specifiers_re = re.compile(r""" |
| \b( |
| void|_Bool |
| |signed|unsigned |
| |short|long |
| |char |
| |int |
| |__uint128|__int128 |
| |__int(8|16|32|64|128) # extension |
| |float|double |
| |_Decimal(32|64|128) |
| |_Complex|_Imaginary |
| |__float80|_Float64x|__float128|_Float128|__ibm128 # extension |
| |__fp16 # extension |
| |_Sat|_Fract|fract|_Accum|accum # extension |
| )\b |
| """, re.VERBOSE) |
| |
| |
| class _DuplicateSymbolError(Exception): |
| def __init__(self, symbol: Symbol, declaration: ASTDeclaration) -> None: |
| assert symbol |
| assert declaration |
| self.symbol = symbol |
| self.declaration = declaration |
| |
| def __str__(self) -> str: |
| return "Internal C duplicate symbol error:\n%s" % self.symbol.dump(0) |
| |
| |
| class ASTBase(ASTBaseBase): |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| raise NotImplementedError(repr(self)) |
| |
| |
| # Names |
| ################################################################################ |
| |
| class ASTIdentifier(ASTBaseBase): |
| def __init__(self, identifier: str) -> None: |
| assert identifier is not None |
| assert len(identifier) != 0 |
| self.identifier = identifier |
| |
| def __eq__(self, other: Any) -> bool: |
| return type(other) is ASTIdentifier and self.identifier == other.identifier |
| |
| def is_anon(self) -> bool: |
| return self.identifier[0] == '@' |
| |
| # and this is where we finally make a difference between __str__ and the display string |
| |
| def __str__(self) -> str: |
| return self.identifier |
| |
| def get_display_string(self) -> str: |
| return "[anonymous]" if self.is_anon() else self.identifier |
| |
| def describe_signature(self, signode: TextElement, mode: str, env: BuildEnvironment, |
| prefix: str, symbol: Symbol) -> None: |
| # note: slightly different signature of describe_signature due to the prefix |
| verify_description_mode(mode) |
| if self.is_anon(): |
| node = addnodes.desc_sig_name(text="[anonymous]") |
| else: |
| node = addnodes.desc_sig_name(self.identifier, self.identifier) |
| if mode == 'markType': |
| targetText = prefix + self.identifier |
| pnode = addnodes.pending_xref('', refdomain='c', |
| reftype='identifier', |
| reftarget=targetText, modname=None, |
| classname=None) |
| pnode['c:parent_key'] = symbol.get_lookup_key() |
| pnode += node |
| signode += pnode |
| elif mode == 'lastIsName': |
| nameNode = addnodes.desc_name() |
| nameNode += node |
| signode += nameNode |
| elif mode == 'noneIsName': |
| signode += node |
| else: |
| raise Exception('Unknown description mode: %s' % mode) |
| |
| |
| class ASTNestedName(ASTBase): |
| def __init__(self, names: list[ASTIdentifier], rooted: bool) -> None: |
| assert len(names) > 0 |
| self.names = names |
| self.rooted = rooted |
| |
| @property |
| def name(self) -> ASTNestedName: |
| return self |
| |
| def get_id(self, version: int) -> str: |
| return '.'.join(str(n) for n in self.names) |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| res = '.'.join(transform(n) for n in self.names) |
| if self.rooted: |
| return '.' + res |
| else: |
| return res |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| verify_description_mode(mode) |
| # just print the name part, with template args, not template params |
| if mode == 'noneIsName': |
| if self.rooted: |
| unreachable = "Can this happen?" |
| raise AssertionError(unreachable) # TODO |
| signode += nodes.Text('.') |
| for i in range(len(self.names)): |
| if i != 0: |
| unreachable = "Can this happen?" |
| raise AssertionError(unreachable) # TODO |
| signode += nodes.Text('.') |
| n = self.names[i] |
| n.describe_signature(signode, mode, env, '', symbol) |
| elif mode == 'param': |
| assert not self.rooted, str(self) |
| assert len(self.names) == 1 |
| self.names[0].describe_signature(signode, 'noneIsName', env, '', symbol) |
| elif mode in ('markType', 'lastIsName', 'markName'): |
| # Each element should be a pending xref targeting the complete |
| # prefix. |
| prefix = '' |
| first = True |
| names = self.names[:-1] if mode == 'lastIsName' else self.names |
| # If lastIsName, then wrap all of the prefix in a desc_addname, |
| # else append directly to signode. |
| # TODO: also for C? |
| # NOTE: Breathe previously relied on the prefix being in the desc_addname node, |
| # so it can remove it in inner declarations. |
| dest = signode |
| if mode == 'lastIsName': |
| dest = addnodes.desc_addname() |
| if self.rooted: |
| prefix += '.' |
| if mode == 'lastIsName' and len(names) == 0: |
| signode += addnodes.desc_sig_punctuation('.', '.') |
| else: |
| dest += addnodes.desc_sig_punctuation('.', '.') |
| for i in range(len(names)): |
| ident = names[i] |
| if not first: |
| dest += addnodes.desc_sig_punctuation('.', '.') |
| prefix += '.' |
| first = False |
| txt_ident = str(ident) |
| if txt_ident != '': |
| ident.describe_signature(dest, 'markType', env, prefix, symbol) |
| prefix += txt_ident |
| if mode == 'lastIsName': |
| if len(self.names) > 1: |
| dest += addnodes.desc_sig_punctuation('.', '.') |
| signode += dest |
| self.names[-1].describe_signature(signode, mode, env, '', symbol) |
| else: |
| raise Exception('Unknown description mode: %s' % mode) |
| |
| |
| ################################################################################ |
| # Expressions |
| ################################################################################ |
| |
| class ASTExpression(ASTBase): |
| pass |
| |
| |
| # Primary expressions |
| ################################################################################ |
| |
| class ASTLiteral(ASTExpression): |
| pass |
| |
| |
| class ASTBooleanLiteral(ASTLiteral): |
| def __init__(self, value: bool) -> None: |
| self.value = value |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| if self.value: |
| return 'true' |
| else: |
| return 'false' |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| txt = str(self) |
| signode += addnodes.desc_sig_keyword(txt, txt) |
| |
| |
| class ASTNumberLiteral(ASTLiteral): |
| def __init__(self, data: str) -> None: |
| self.data = data |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| return self.data |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| txt = str(self) |
| signode += addnodes.desc_sig_literal_number(txt, txt) |
| |
| |
| class ASTCharLiteral(ASTLiteral): |
| def __init__(self, prefix: str, data: str) -> None: |
| self.prefix = prefix # may be None when no prefix |
| self.data = data |
| decoded = data.encode().decode('unicode-escape') |
| if len(decoded) == 1: |
| self.value = ord(decoded) |
| else: |
| raise UnsupportedMultiCharacterCharLiteral(decoded) |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| if self.prefix is None: |
| return "'" + self.data + "'" |
| else: |
| return self.prefix + "'" + self.data + "'" |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| txt = str(self) |
| signode += addnodes.desc_sig_literal_char(txt, txt) |
| |
| |
| class ASTStringLiteral(ASTLiteral): |
| def __init__(self, data: str) -> None: |
| self.data = data |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| return self.data |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| txt = str(self) |
| signode += addnodes.desc_sig_literal_string(txt, txt) |
| |
| |
| class ASTIdExpression(ASTExpression): |
| def __init__(self, name: ASTNestedName): |
| # note: this class is basically to cast a nested name as an expression |
| self.name = name |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| return transform(self.name) |
| |
| def get_id(self, version: int) -> str: |
| return self.name.get_id(version) |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| self.name.describe_signature(signode, mode, env, symbol) |
| |
| |
| class ASTParenExpr(ASTExpression): |
| def __init__(self, expr): |
| self.expr = expr |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| return '(' + transform(self.expr) + ')' |
| |
| def get_id(self, version: int) -> str: |
| return self.expr.get_id(version) |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| signode += addnodes.desc_sig_punctuation('(', '(') |
| self.expr.describe_signature(signode, mode, env, symbol) |
| signode += addnodes.desc_sig_punctuation(')', ')') |
| |
| |
| # Postfix expressions |
| ################################################################################ |
| |
| class ASTPostfixOp(ASTBase): |
| pass |
| |
| |
| class ASTPostfixCallExpr(ASTPostfixOp): |
| def __init__(self, lst: ASTParenExprList | ASTBracedInitList) -> None: |
| self.lst = lst |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| return transform(self.lst) |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| self.lst.describe_signature(signode, mode, env, symbol) |
| |
| |
| class ASTPostfixArray(ASTPostfixOp): |
| def __init__(self, expr: ASTExpression) -> None: |
| self.expr = expr |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| return '[' + transform(self.expr) + ']' |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| signode += addnodes.desc_sig_punctuation('[', '[') |
| self.expr.describe_signature(signode, mode, env, symbol) |
| signode += addnodes.desc_sig_punctuation(']', ']') |
| |
| |
| class ASTPostfixInc(ASTPostfixOp): |
| def _stringify(self, transform: StringifyTransform) -> str: |
| return '++' |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| signode += addnodes.desc_sig_operator('++', '++') |
| |
| |
| class ASTPostfixDec(ASTPostfixOp): |
| def _stringify(self, transform: StringifyTransform) -> str: |
| return '--' |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| signode += addnodes.desc_sig_operator('--', '--') |
| |
| |
| class ASTPostfixMemberOfPointer(ASTPostfixOp): |
| def __init__(self, name): |
| self.name = name |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| return '->' + transform(self.name) |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| signode += addnodes.desc_sig_operator('->', '->') |
| self.name.describe_signature(signode, 'noneIsName', env, symbol) |
| |
| |
| class ASTPostfixExpr(ASTExpression): |
| def __init__(self, prefix: ASTExpression, postFixes: list[ASTPostfixOp]): |
| self.prefix = prefix |
| self.postFixes = postFixes |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| res = [transform(self.prefix)] |
| for p in self.postFixes: |
| res.append(transform(p)) |
| return ''.join(res) |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| self.prefix.describe_signature(signode, mode, env, symbol) |
| for p in self.postFixes: |
| p.describe_signature(signode, mode, env, symbol) |
| |
| |
| # Unary expressions |
| ################################################################################ |
| |
| class ASTUnaryOpExpr(ASTExpression): |
| def __init__(self, op: str, expr: ASTExpression): |
| self.op = op |
| self.expr = expr |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| if self.op[0] in 'cn': |
| return self.op + " " + transform(self.expr) |
| else: |
| return self.op + transform(self.expr) |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| if self.op[0] in 'cn': |
| signode += addnodes.desc_sig_keyword(self.op, self.op) |
| signode += addnodes.desc_sig_space() |
| else: |
| signode += addnodes.desc_sig_operator(self.op, self.op) |
| self.expr.describe_signature(signode, mode, env, symbol) |
| |
| |
| class ASTSizeofType(ASTExpression): |
| def __init__(self, typ): |
| self.typ = typ |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| return "sizeof(" + transform(self.typ) + ")" |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| signode += addnodes.desc_sig_keyword('sizeof', 'sizeof') |
| signode += addnodes.desc_sig_punctuation('(', '(') |
| self.typ.describe_signature(signode, mode, env, symbol) |
| signode += addnodes.desc_sig_punctuation(')', ')') |
| |
| |
| class ASTSizeofExpr(ASTExpression): |
| def __init__(self, expr: ASTExpression): |
| self.expr = expr |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| return "sizeof " + transform(self.expr) |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| signode += addnodes.desc_sig_keyword('sizeof', 'sizeof') |
| signode += addnodes.desc_sig_space() |
| self.expr.describe_signature(signode, mode, env, symbol) |
| |
| |
| class ASTAlignofExpr(ASTExpression): |
| def __init__(self, typ: ASTType): |
| self.typ = typ |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| return "alignof(" + transform(self.typ) + ")" |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| signode += addnodes.desc_sig_keyword('alignof', 'alignof') |
| signode += addnodes.desc_sig_punctuation('(', '(') |
| self.typ.describe_signature(signode, mode, env, symbol) |
| signode += addnodes.desc_sig_punctuation(')', ')') |
| |
| |
| # Other expressions |
| ################################################################################ |
| |
| class ASTCastExpr(ASTExpression): |
| def __init__(self, typ: ASTType, expr: ASTExpression): |
| self.typ = typ |
| self.expr = expr |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| res = ['('] |
| res.append(transform(self.typ)) |
| res.append(')') |
| res.append(transform(self.expr)) |
| return ''.join(res) |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| signode += addnodes.desc_sig_punctuation('(', '(') |
| self.typ.describe_signature(signode, mode, env, symbol) |
| signode += addnodes.desc_sig_punctuation(')', ')') |
| self.expr.describe_signature(signode, mode, env, symbol) |
| |
| |
| class ASTBinOpExpr(ASTBase): |
| def __init__(self, exprs: list[ASTExpression], ops: list[str]): |
| assert len(exprs) > 0 |
| assert len(exprs) == len(ops) + 1 |
| self.exprs = exprs |
| self.ops = ops |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| res = [] |
| res.append(transform(self.exprs[0])) |
| for i in range(1, len(self.exprs)): |
| res.append(' ') |
| res.append(self.ops[i - 1]) |
| res.append(' ') |
| res.append(transform(self.exprs[i])) |
| return ''.join(res) |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| self.exprs[0].describe_signature(signode, mode, env, symbol) |
| for i in range(1, len(self.exprs)): |
| signode += addnodes.desc_sig_space() |
| op = self.ops[i - 1] |
| if ord(op[0]) >= ord('a') and ord(op[0]) <= ord('z'): |
| signode += addnodes.desc_sig_keyword(op, op) |
| else: |
| signode += addnodes.desc_sig_operator(op, op) |
| signode += addnodes.desc_sig_space() |
| self.exprs[i].describe_signature(signode, mode, env, symbol) |
| |
| |
| class ASTAssignmentExpr(ASTExpression): |
| def __init__(self, exprs: list[ASTExpression], ops: list[str]): |
| assert len(exprs) > 0 |
| assert len(exprs) == len(ops) + 1 |
| self.exprs = exprs |
| self.ops = ops |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| res = [] |
| res.append(transform(self.exprs[0])) |
| for i in range(1, len(self.exprs)): |
| res.append(' ') |
| res.append(self.ops[i - 1]) |
| res.append(' ') |
| res.append(transform(self.exprs[i])) |
| return ''.join(res) |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| self.exprs[0].describe_signature(signode, mode, env, symbol) |
| for i in range(1, len(self.exprs)): |
| signode += addnodes.desc_sig_space() |
| op = self.ops[i - 1] |
| if ord(op[0]) >= ord('a') and ord(op[0]) <= ord('z'): |
| signode += addnodes.desc_sig_keyword(op, op) |
| else: |
| signode += addnodes.desc_sig_operator(op, op) |
| signode += addnodes.desc_sig_space() |
| self.exprs[i].describe_signature(signode, mode, env, symbol) |
| |
| |
| class ASTFallbackExpr(ASTExpression): |
| def __init__(self, expr: str): |
| self.expr = expr |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| return self.expr |
| |
| def get_id(self, version: int) -> str: |
| return str(self.expr) |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| signode += nodes.literal(self.expr, self.expr) |
| |
| |
| ################################################################################ |
| # Types |
| ################################################################################ |
| |
| class ASTTrailingTypeSpec(ASTBase): |
| pass |
| |
| |
| class ASTTrailingTypeSpecFundamental(ASTTrailingTypeSpec): |
| def __init__(self, names: list[str]) -> None: |
| assert len(names) != 0 |
| self.names = names |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| return ' '.join(self.names) |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| first = True |
| for n in self.names: |
| if not first: |
| signode += addnodes.desc_sig_space() |
| else: |
| first = False |
| signode += addnodes.desc_sig_keyword_type(n, n) |
| |
| |
| class ASTTrailingTypeSpecName(ASTTrailingTypeSpec): |
| def __init__(self, prefix: str, nestedName: ASTNestedName) -> None: |
| self.prefix = prefix |
| self.nestedName = nestedName |
| |
| @property |
| def name(self) -> ASTNestedName: |
| return self.nestedName |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| res = [] |
| if self.prefix: |
| res.append(self.prefix) |
| res.append(' ') |
| res.append(transform(self.nestedName)) |
| return ''.join(res) |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| if self.prefix: |
| signode += addnodes.desc_sig_keyword(self.prefix, self.prefix) |
| signode += addnodes.desc_sig_space() |
| self.nestedName.describe_signature(signode, mode, env, symbol=symbol) |
| |
| |
| class ASTFunctionParameter(ASTBase): |
| def __init__(self, arg: ASTTypeWithInit | None, ellipsis: bool = False) -> None: |
| self.arg = arg |
| self.ellipsis = ellipsis |
| |
| def get_id(self, version: int, objectType: str, symbol: Symbol) -> str: |
| # the anchor will be our parent |
| return symbol.parent.declaration.get_id(version, prefixed=False) |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| if self.ellipsis: |
| return '...' |
| else: |
| return transform(self.arg) |
| |
| def describe_signature(self, signode: Any, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| verify_description_mode(mode) |
| if self.ellipsis: |
| signode += addnodes.desc_sig_punctuation('...', '...') |
| else: |
| self.arg.describe_signature(signode, mode, env, symbol=symbol) |
| |
| |
| class ASTParameters(ASTBase): |
| def __init__(self, args: list[ASTFunctionParameter], attrs: ASTAttributeList) -> None: |
| self.args = args |
| self.attrs = attrs |
| |
| @property |
| def function_params(self) -> list[ASTFunctionParameter]: |
| return self.args |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| res = [] |
| res.append('(') |
| first = True |
| for a in self.args: |
| if not first: |
| res.append(', ') |
| first = False |
| res.append(str(a)) |
| res.append(')') |
| if len(self.attrs) != 0: |
| res.append(' ') |
| res.append(transform(self.attrs)) |
| return ''.join(res) |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| verify_description_mode(mode) |
| multi_line_parameter_list = False |
| test_node: Element = signode |
| while test_node.parent: |
| if not isinstance(test_node, addnodes.desc_signature): |
| test_node = test_node.parent |
| continue |
| multi_line_parameter_list = test_node.get('multi_line_parameter_list', False) |
| break |
| |
| # only use the desc_parameterlist for the outer list, not for inner lists |
| if mode == 'lastIsName': |
| paramlist = addnodes.desc_parameterlist() |
| paramlist['multi_line_parameter_list'] = multi_line_parameter_list |
| for arg in self.args: |
| param = addnodes.desc_parameter('', '', noemph=True) |
| arg.describe_signature(param, 'param', env, symbol=symbol) |
| paramlist += param |
| signode += paramlist |
| else: |
| signode += addnodes.desc_sig_punctuation('(', '(') |
| first = True |
| for arg in self.args: |
| if not first: |
| signode += addnodes.desc_sig_punctuation(',', ',') |
| signode += addnodes.desc_sig_space() |
| first = False |
| arg.describe_signature(signode, 'markType', env, symbol=symbol) |
| signode += addnodes.desc_sig_punctuation(')', ')') |
| |
| if len(self.attrs) != 0: |
| signode += addnodes.desc_sig_space() |
| self.attrs.describe_signature(signode) |
| |
| |
| class ASTDeclSpecsSimple(ASTBaseBase): |
| def __init__(self, storage: str, threadLocal: str, inline: bool, |
| restrict: bool, volatile: bool, const: bool, attrs: ASTAttributeList) -> None: |
| self.storage = storage |
| self.threadLocal = threadLocal |
| self.inline = inline |
| self.restrict = restrict |
| self.volatile = volatile |
| self.const = const |
| self.attrs = attrs |
| |
| def mergeWith(self, other: ASTDeclSpecsSimple) -> ASTDeclSpecsSimple: |
| if not other: |
| return self |
| return ASTDeclSpecsSimple(self.storage or other.storage, |
| self.threadLocal or other.threadLocal, |
| self.inline or other.inline, |
| self.volatile or other.volatile, |
| self.const or other.const, |
| self.restrict or other.restrict, |
| self.attrs + other.attrs) |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| res: list[str] = [] |
| if len(self.attrs) != 0: |
| res.append(transform(self.attrs)) |
| if self.storage: |
| res.append(self.storage) |
| if self.threadLocal: |
| res.append(self.threadLocal) |
| if self.inline: |
| res.append('inline') |
| if self.restrict: |
| res.append('restrict') |
| if self.volatile: |
| res.append('volatile') |
| if self.const: |
| res.append('const') |
| return ' '.join(res) |
| |
| def describe_signature(self, modifiers: list[Node]) -> None: |
| def _add(modifiers: list[Node], text: str) -> None: |
| if len(modifiers) != 0: |
| modifiers.append(addnodes.desc_sig_space()) |
| modifiers.append(addnodes.desc_sig_keyword(text, text)) |
| |
| if len(modifiers) != 0 and len(self.attrs) != 0: |
| modifiers.append(addnodes.desc_sig_space()) |
| tempNode = nodes.TextElement() |
| self.attrs.describe_signature(tempNode) |
| modifiers.extend(tempNode.children) |
| if self.storage: |
| _add(modifiers, self.storage) |
| if self.threadLocal: |
| _add(modifiers, self.threadLocal) |
| if self.inline: |
| _add(modifiers, 'inline') |
| if self.restrict: |
| _add(modifiers, 'restrict') |
| if self.volatile: |
| _add(modifiers, 'volatile') |
| if self.const: |
| _add(modifiers, 'const') |
| |
| |
| class ASTDeclSpecs(ASTBase): |
| def __init__(self, outer: str, |
| leftSpecs: ASTDeclSpecsSimple, |
| rightSpecs: ASTDeclSpecsSimple, |
| trailing: ASTTrailingTypeSpec) -> None: |
| # leftSpecs and rightSpecs are used for output |
| # allSpecs are used for id generation TODO: remove? |
| self.outer = outer |
| self.leftSpecs = leftSpecs |
| self.rightSpecs = rightSpecs |
| self.allSpecs = self.leftSpecs.mergeWith(self.rightSpecs) |
| self.trailingTypeSpec = trailing |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| res: list[str] = [] |
| l = transform(self.leftSpecs) |
| if len(l) > 0: |
| res.append(l) |
| if self.trailingTypeSpec: |
| if len(res) > 0: |
| res.append(" ") |
| res.append(transform(self.trailingTypeSpec)) |
| r = str(self.rightSpecs) |
| if len(r) > 0: |
| if len(res) > 0: |
| res.append(" ") |
| res.append(r) |
| return "".join(res) |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| verify_description_mode(mode) |
| modifiers: list[Node] = [] |
| |
| self.leftSpecs.describe_signature(modifiers) |
| |
| for m in modifiers: |
| signode += m |
| if self.trailingTypeSpec: |
| if len(modifiers) > 0: |
| signode += addnodes.desc_sig_space() |
| self.trailingTypeSpec.describe_signature(signode, mode, env, |
| symbol=symbol) |
| modifiers = [] |
| self.rightSpecs.describe_signature(modifiers) |
| if len(modifiers) > 0: |
| signode += addnodes.desc_sig_space() |
| for m in modifiers: |
| signode += m |
| |
| |
| # Declarator |
| ################################################################################ |
| |
| class ASTArray(ASTBase): |
| def __init__(self, static: bool, const: bool, volatile: bool, restrict: bool, |
| vla: bool, size: ASTExpression): |
| self.static = static |
| self.const = const |
| self.volatile = volatile |
| self.restrict = restrict |
| self.vla = vla |
| self.size = size |
| if vla: |
| assert size is None |
| if size is not None: |
| assert not vla |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| el = [] |
| if self.static: |
| el.append('static') |
| if self.restrict: |
| el.append('restrict') |
| if self.volatile: |
| el.append('volatile') |
| if self.const: |
| el.append('const') |
| if self.vla: |
| return '[' + ' '.join(el) + '*]' |
| elif self.size: |
| el.append(transform(self.size)) |
| return '[' + ' '.join(el) + ']' |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| verify_description_mode(mode) |
| signode += addnodes.desc_sig_punctuation('[', '[') |
| addSpace = False |
| |
| def _add(signode: TextElement, text: str) -> bool: |
| if addSpace: |
| signode += addnodes.desc_sig_space() |
| signode += addnodes.desc_sig_keyword(text, text) |
| return True |
| |
| if self.static: |
| addSpace = _add(signode, 'static') |
| if self.restrict: |
| addSpace = _add(signode, 'restrict') |
| if self.volatile: |
| addSpace = _add(signode, 'volatile') |
| if self.const: |
| addSpace = _add(signode, 'const') |
| if self.vla: |
| signode += addnodes.desc_sig_punctuation('*', '*') |
| elif self.size: |
| if addSpace: |
| signode += addnodes.desc_sig_space() |
| self.size.describe_signature(signode, 'markType', env, symbol) |
| signode += addnodes.desc_sig_punctuation(']', ']') |
| |
| |
| class ASTDeclarator(ASTBase): |
| @property |
| def name(self) -> ASTNestedName: |
| raise NotImplementedError(repr(self)) |
| |
| @property |
| def function_params(self) -> list[ASTFunctionParameter]: |
| raise NotImplementedError(repr(self)) |
| |
| def require_space_after_declSpecs(self) -> bool: |
| raise NotImplementedError(repr(self)) |
| |
| |
| class ASTDeclaratorNameParam(ASTDeclarator): |
| def __init__(self, declId: ASTNestedName, |
| arrayOps: list[ASTArray], param: ASTParameters) -> None: |
| self.declId = declId |
| self.arrayOps = arrayOps |
| self.param = param |
| |
| @property |
| def name(self) -> ASTNestedName: |
| return self.declId |
| |
| @property |
| def function_params(self) -> list[ASTFunctionParameter]: |
| return self.param.function_params |
| |
| # ------------------------------------------------------------------------ |
| |
| def require_space_after_declSpecs(self) -> bool: |
| return self.declId is not None |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| res = [] |
| if self.declId: |
| res.append(transform(self.declId)) |
| for op in self.arrayOps: |
| res.append(transform(op)) |
| if self.param: |
| res.append(transform(self.param)) |
| return ''.join(res) |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| verify_description_mode(mode) |
| if self.declId: |
| self.declId.describe_signature(signode, mode, env, symbol) |
| for op in self.arrayOps: |
| op.describe_signature(signode, mode, env, symbol) |
| if self.param: |
| self.param.describe_signature(signode, mode, env, symbol) |
| |
| |
| class ASTDeclaratorNameBitField(ASTDeclarator): |
| def __init__(self, declId: ASTNestedName, size: ASTExpression): |
| self.declId = declId |
| self.size = size |
| |
| @property |
| def name(self) -> ASTNestedName: |
| return self.declId |
| |
| # ------------------------------------------------------------------------ |
| |
| def require_space_after_declSpecs(self) -> bool: |
| return self.declId is not None |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| res = [] |
| if self.declId: |
| res.append(transform(self.declId)) |
| res.append(" : ") |
| res.append(transform(self.size)) |
| return ''.join(res) |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| verify_description_mode(mode) |
| if self.declId: |
| self.declId.describe_signature(signode, mode, env, symbol) |
| signode += addnodes.desc_sig_space() |
| signode += addnodes.desc_sig_punctuation(':', ':') |
| signode += addnodes.desc_sig_space() |
| self.size.describe_signature(signode, mode, env, symbol) |
| |
| |
| class ASTDeclaratorPtr(ASTDeclarator): |
| def __init__(self, next: ASTDeclarator, restrict: bool, volatile: bool, const: bool, |
| attrs: ASTAttributeList) -> None: |
| assert next |
| self.next = next |
| self.restrict = restrict |
| self.volatile = volatile |
| self.const = const |
| self.attrs = attrs |
| |
| @property |
| def name(self) -> ASTNestedName: |
| return self.next.name |
| |
| @property |
| def function_params(self) -> list[ASTFunctionParameter]: |
| return self.next.function_params |
| |
| def require_space_after_declSpecs(self) -> bool: |
| return self.const or self.volatile or self.restrict or \ |
| len(self.attrs) > 0 or \ |
| self.next.require_space_after_declSpecs() |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| res = ['*'] |
| res.append(transform(self.attrs)) |
| if len(self.attrs) != 0 and (self.restrict or self.volatile or self.const): |
| res.append(' ') |
| if self.restrict: |
| res.append('restrict') |
| if self.volatile: |
| if self.restrict: |
| res.append(' ') |
| res.append('volatile') |
| if self.const: |
| if self.restrict or self.volatile: |
| res.append(' ') |
| res.append('const') |
| if self.const or self.volatile or self.restrict or len(self.attrs) > 0: |
| if self.next.require_space_after_declSpecs(): |
| res.append(' ') |
| res.append(transform(self.next)) |
| return ''.join(res) |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| verify_description_mode(mode) |
| signode += addnodes.desc_sig_punctuation('*', '*') |
| self.attrs.describe_signature(signode) |
| if len(self.attrs) != 0 and (self.restrict or self.volatile or self.const): |
| signode += addnodes.desc_sig_space() |
| |
| def _add_anno(signode: TextElement, text: str) -> None: |
| signode += addnodes.desc_sig_keyword(text, text) |
| |
| if self.restrict: |
| _add_anno(signode, 'restrict') |
| if self.volatile: |
| if self.restrict: |
| signode += addnodes.desc_sig_space() |
| _add_anno(signode, 'volatile') |
| if self.const: |
| if self.restrict or self.volatile: |
| signode += addnodes.desc_sig_space() |
| _add_anno(signode, 'const') |
| if self.const or self.volatile or self.restrict or len(self.attrs) > 0: |
| if self.next.require_space_after_declSpecs(): |
| signode += addnodes.desc_sig_space() |
| self.next.describe_signature(signode, mode, env, symbol) |
| |
| |
| class ASTDeclaratorParen(ASTDeclarator): |
| def __init__(self, inner: ASTDeclarator, next: ASTDeclarator) -> None: |
| assert inner |
| assert next |
| self.inner = inner |
| self.next = next |
| # TODO: we assume the name and params are in inner |
| |
| @property |
| def name(self) -> ASTNestedName: |
| return self.inner.name |
| |
| @property |
| def function_params(self) -> list[ASTFunctionParameter]: |
| return self.inner.function_params |
| |
| def require_space_after_declSpecs(self) -> bool: |
| return True |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| res = ['('] |
| res.append(transform(self.inner)) |
| res.append(')') |
| res.append(transform(self.next)) |
| return ''.join(res) |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| verify_description_mode(mode) |
| signode += addnodes.desc_sig_punctuation('(', '(') |
| self.inner.describe_signature(signode, mode, env, symbol) |
| signode += addnodes.desc_sig_punctuation(')', ')') |
| self.next.describe_signature(signode, "noneIsName", env, symbol) |
| |
| |
| # Initializer |
| ################################################################################ |
| |
| class ASTParenExprList(ASTBaseParenExprList): |
| def __init__(self, exprs: list[ASTExpression]) -> None: |
| self.exprs = exprs |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| exprs = [transform(e) for e in self.exprs] |
| return '(%s)' % ', '.join(exprs) |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| verify_description_mode(mode) |
| signode += addnodes.desc_sig_punctuation('(', '(') |
| first = True |
| for e in self.exprs: |
| if not first: |
| signode += addnodes.desc_sig_punctuation(',', ',') |
| signode += addnodes.desc_sig_space() |
| else: |
| first = False |
| e.describe_signature(signode, mode, env, symbol) |
| signode += addnodes.desc_sig_punctuation(')', ')') |
| |
| |
| class ASTBracedInitList(ASTBase): |
| def __init__(self, exprs: list[ASTExpression], trailingComma: bool) -> None: |
| self.exprs = exprs |
| self.trailingComma = trailingComma |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| exprs = ', '.join(transform(e) for e in self.exprs) |
| trailingComma = ',' if self.trailingComma else '' |
| return f'{{{exprs}{trailingComma}}}' |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| verify_description_mode(mode) |
| signode += addnodes.desc_sig_punctuation('{', '{') |
| first = True |
| for e in self.exprs: |
| if not first: |
| signode += addnodes.desc_sig_punctuation(',', ',') |
| signode += addnodes.desc_sig_space() |
| else: |
| first = False |
| e.describe_signature(signode, mode, env, symbol) |
| if self.trailingComma: |
| signode += addnodes.desc_sig_punctuation(',', ',') |
| signode += addnodes.desc_sig_punctuation('}', '}') |
| |
| |
| class ASTInitializer(ASTBase): |
| def __init__(self, value: ASTBracedInitList | ASTExpression, |
| hasAssign: bool = True) -> None: |
| self.value = value |
| self.hasAssign = hasAssign |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| val = transform(self.value) |
| if self.hasAssign: |
| return ' = ' + val |
| else: |
| return val |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| verify_description_mode(mode) |
| if self.hasAssign: |
| signode += addnodes.desc_sig_space() |
| signode += addnodes.desc_sig_punctuation('=', '=') |
| signode += addnodes.desc_sig_space() |
| self.value.describe_signature(signode, 'markType', env, symbol) |
| |
| |
| class ASTType(ASTBase): |
| def __init__(self, declSpecs: ASTDeclSpecs, decl: ASTDeclarator) -> None: |
| assert declSpecs |
| assert decl |
| self.declSpecs = declSpecs |
| self.decl = decl |
| |
| @property |
| def name(self) -> ASTNestedName: |
| return self.decl.name |
| |
| def get_id(self, version: int, objectType: str, symbol: Symbol) -> str: |
| return symbol.get_full_nested_name().get_id(version) |
| |
| @property |
| def function_params(self) -> list[ASTFunctionParameter]: |
| return self.decl.function_params |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| res = [] |
| declSpecs = transform(self.declSpecs) |
| res.append(declSpecs) |
| if self.decl.require_space_after_declSpecs() and len(declSpecs) > 0: |
| res.append(' ') |
| res.append(transform(self.decl)) |
| return ''.join(res) |
| |
| def get_type_declaration_prefix(self) -> str: |
| if self.declSpecs.trailingTypeSpec: |
| return 'typedef' |
| else: |
| return 'type' |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| verify_description_mode(mode) |
| self.declSpecs.describe_signature(signode, 'markType', env, symbol) |
| if (self.decl.require_space_after_declSpecs() and |
| len(str(self.declSpecs)) > 0): |
| signode += addnodes.desc_sig_space() |
| # for parameters that don't really declare new names we get 'markType', |
| # this should not be propagated, but be 'noneIsName'. |
| if mode == 'markType': |
| mode = 'noneIsName' |
| self.decl.describe_signature(signode, mode, env, symbol) |
| |
| |
| class ASTTypeWithInit(ASTBase): |
| def __init__(self, type: ASTType, init: ASTInitializer) -> None: |
| self.type = type |
| self.init = init |
| |
| @property |
| def name(self) -> ASTNestedName: |
| return self.type.name |
| |
| def get_id(self, version: int, objectType: str, symbol: Symbol) -> str: |
| return self.type.get_id(version, objectType, symbol) |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| res = [] |
| res.append(transform(self.type)) |
| if self.init: |
| res.append(transform(self.init)) |
| return ''.join(res) |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| verify_description_mode(mode) |
| self.type.describe_signature(signode, mode, env, symbol) |
| if self.init: |
| self.init.describe_signature(signode, mode, env, symbol) |
| |
| |
| class ASTMacroParameter(ASTBase): |
| def __init__(self, arg: ASTNestedName | None, ellipsis: bool = False, |
| variadic: bool = False) -> None: |
| self.arg = arg |
| self.ellipsis = ellipsis |
| self.variadic = variadic |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| if self.ellipsis: |
| return '...' |
| elif self.variadic: |
| return transform(self.arg) + '...' |
| else: |
| return transform(self.arg) |
| |
| def describe_signature(self, signode: Any, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| verify_description_mode(mode) |
| if self.ellipsis: |
| signode += addnodes.desc_sig_punctuation('...', '...') |
| elif self.variadic: |
| name = str(self) |
| signode += addnodes.desc_sig_name(name, name) |
| else: |
| self.arg.describe_signature(signode, mode, env, symbol=symbol) |
| |
| |
| class ASTMacro(ASTBase): |
| def __init__(self, ident: ASTNestedName, args: list[ASTMacroParameter] | None) -> None: |
| self.ident = ident |
| self.args = args |
| |
| @property |
| def name(self) -> ASTNestedName: |
| return self.ident |
| |
| def get_id(self, version: int, objectType: str, symbol: Symbol) -> str: |
| return symbol.get_full_nested_name().get_id(version) |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| res = [] |
| res.append(transform(self.ident)) |
| if self.args is not None: |
| res.append('(') |
| first = True |
| for arg in self.args: |
| if not first: |
| res.append(', ') |
| first = False |
| res.append(transform(arg)) |
| res.append(')') |
| return ''.join(res) |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| verify_description_mode(mode) |
| self.ident.describe_signature(signode, mode, env, symbol) |
| if self.args is None: |
| return |
| paramlist = addnodes.desc_parameterlist() |
| for arg in self.args: |
| param = addnodes.desc_parameter('', '', noemph=True) |
| arg.describe_signature(param, 'param', env, symbol=symbol) |
| paramlist += param |
| signode += paramlist |
| |
| |
| class ASTStruct(ASTBase): |
| def __init__(self, name: ASTNestedName) -> None: |
| self.name = name |
| |
| def get_id(self, version: int, objectType: str, symbol: Symbol) -> str: |
| return symbol.get_full_nested_name().get_id(version) |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| return transform(self.name) |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| verify_description_mode(mode) |
| self.name.describe_signature(signode, mode, env, symbol=symbol) |
| |
| |
| class ASTUnion(ASTBase): |
| def __init__(self, name: ASTNestedName) -> None: |
| self.name = name |
| |
| def get_id(self, version: int, objectType: str, symbol: Symbol) -> str: |
| return symbol.get_full_nested_name().get_id(version) |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| return transform(self.name) |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| verify_description_mode(mode) |
| self.name.describe_signature(signode, mode, env, symbol=symbol) |
| |
| |
| class ASTEnum(ASTBase): |
| def __init__(self, name: ASTNestedName) -> None: |
| self.name = name |
| |
| def get_id(self, version: int, objectType: str, symbol: Symbol) -> str: |
| return symbol.get_full_nested_name().get_id(version) |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| return transform(self.name) |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| verify_description_mode(mode) |
| self.name.describe_signature(signode, mode, env, symbol=symbol) |
| |
| |
| class ASTEnumerator(ASTBase): |
| def __init__(self, name: ASTNestedName, init: ASTInitializer | None, |
| attrs: ASTAttributeList) -> None: |
| self.name = name |
| self.init = init |
| self.attrs = attrs |
| |
| def get_id(self, version: int, objectType: str, symbol: Symbol) -> str: |
| return symbol.get_full_nested_name().get_id(version) |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| res = [] |
| res.append(transform(self.name)) |
| if len(self.attrs) != 0: |
| res.append(' ') |
| res.append(transform(self.attrs)) |
| if self.init: |
| res.append(transform(self.init)) |
| return ''.join(res) |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, symbol: Symbol) -> None: |
| verify_description_mode(mode) |
| self.name.describe_signature(signode, mode, env, symbol) |
| if len(self.attrs) != 0: |
| signode += addnodes.desc_sig_space() |
| self.attrs.describe_signature(signode) |
| if self.init: |
| self.init.describe_signature(signode, 'markType', env, symbol) |
| |
| |
| class ASTDeclaration(ASTBaseBase): |
| def __init__(self, objectType: str, directiveType: str | None, |
| declaration: DeclarationType | ASTFunctionParameter, |
| semicolon: bool = False) -> None: |
| self.objectType = objectType |
| self.directiveType = directiveType |
| self.declaration = declaration |
| self.semicolon = semicolon |
| |
| self.symbol: Symbol = None |
| # set by CObject._add_enumerator_to_parent |
| self.enumeratorScopedSymbol: Symbol = None |
| |
| def clone(self) -> ASTDeclaration: |
| return ASTDeclaration(self.objectType, self.directiveType, |
| self.declaration.clone(), self.semicolon) |
| |
| @property |
| def name(self) -> ASTNestedName: |
| decl = cast(DeclarationType, self.declaration) |
| return decl.name |
| |
| @property |
| def function_params(self) -> list[ASTFunctionParameter] | None: |
| if self.objectType != 'function': |
| return None |
| decl = cast(ASTType, self.declaration) |
| return decl.function_params |
| |
| def get_id(self, version: int, prefixed: bool = True) -> str: |
| if self.objectType == 'enumerator' and self.enumeratorScopedSymbol: |
| return self.enumeratorScopedSymbol.declaration.get_id(version, prefixed) |
| id_ = self.declaration.get_id(version, self.objectType, self.symbol) |
| if prefixed: |
| return _id_prefix[version] + id_ |
| else: |
| return id_ |
| |
| def get_newest_id(self) -> str: |
| return self.get_id(_max_id, True) |
| |
| def _stringify(self, transform: StringifyTransform) -> str: |
| res = transform(self.declaration) |
| if self.semicolon: |
| res += ';' |
| return res |
| |
| def describe_signature(self, signode: TextElement, mode: str, |
| env: BuildEnvironment, options: dict) -> None: |
| verify_description_mode(mode) |
| assert self.symbol |
| # The caller of the domain added a desc_signature node. |
| # Always enable multiline: |
| signode['is_multiline'] = True |
| # Put each line in a desc_signature_line node. |
| mainDeclNode = addnodes.desc_signature_line() |
| mainDeclNode.sphinx_line_type = 'declarator' |
| mainDeclNode['add_permalink'] = not self.symbol.isRedeclaration |
| signode += mainDeclNode |
| |
| if self.objectType in {'member', 'function', 'macro'}: |
| pass |
| elif self.objectType == 'struct': |
| mainDeclNode += addnodes.desc_sig_keyword('struct', 'struct') |
| mainDeclNode += addnodes.desc_sig_space() |
| elif self.objectType == 'union': |
| mainDeclNode += addnodes.desc_sig_keyword('union', 'union') |
| mainDeclNode += addnodes.desc_sig_space() |
| elif self.objectType == 'enum': |
| mainDeclNode += addnodes.desc_sig_keyword('enum', 'enum') |
| mainDeclNode += addnodes.desc_sig_space() |
| elif self.objectType == 'enumerator': |
| mainDeclNode += addnodes.desc_sig_keyword('enumerator', 'enumerator') |
| mainDeclNode += addnodes.desc_sig_space() |
| elif self.objectType == 'type': |
| decl = cast(ASTType, self.declaration) |
| prefix = decl.get_type_declaration_prefix() |
| mainDeclNode += addnodes.desc_sig_keyword(prefix, prefix) |
| mainDeclNode += addnodes.desc_sig_space() |
| else: |
| raise AssertionError |
| self.declaration.describe_signature(mainDeclNode, mode, env, self.symbol) |
| if self.semicolon: |
| mainDeclNode += addnodes.desc_sig_punctuation(';', ';') |
| |
| |
| class SymbolLookupResult: |
| def __init__(self, symbols: Iterator[Symbol], parentSymbol: Symbol, |
| ident: ASTIdentifier) -> None: |
| self.symbols = symbols |
| self.parentSymbol = parentSymbol |
| self.ident = ident |
| |
| |
| class LookupKey: |
| def __init__(self, data: list[tuple[ASTIdentifier, str]]) -> None: |
| self.data = data |
| |
| def __str__(self) -> str: |
| inner = ', '.join(f"({ident}, {id_})" for ident, id_ in self.data) |
| return f'[{inner}]' |
| |
| |
| class Symbol: |
| debug_indent = 0 |
| debug_indent_string = " " |
| debug_lookup = False |
| debug_show_tree = False |
| |
| def __copy__(self): |
| raise AssertionError # shouldn't happen |
| |
| def __deepcopy__(self, memo): |
| if self.parent: |
| raise AssertionError # shouldn't happen |
| # the domain base class makes a copy of the initial data, which is fine |
| return Symbol(None, None, None, None, None) |
| |
| @staticmethod |
| def debug_print(*args: Any) -> None: |
| logger.debug(Symbol.debug_indent_string * Symbol.debug_indent, end="") |
| logger.debug(*args) |
| |
| def _assert_invariants(self) -> None: |
| if not self.parent: |
| # parent == None means global scope, so declaration means a parent |
| assert not self.declaration |
| assert not self.docname |
| else: |
| if self.declaration: |
| assert self.docname |
| |
| def __setattr__(self, key: str, value: Any) -> None: |
| if key == "children": |
| raise AssertionError |
| return super().__setattr__(key, value) |
| |
| def __init__( |
| self, |
| parent: Symbol, |
| ident: ASTIdentifier, |
| declaration: ASTDeclaration | None, |
| docname: str | None, |
| line: int | None, |
| ) -> None: |
| self.parent = parent |
| # declarations in a single directive are linked together |
| self.siblingAbove: Symbol = None |
| self.siblingBelow: Symbol = None |
| self.ident = ident |
| self.declaration = declaration |
| self.docname = docname |
| self.line = line |
| self.isRedeclaration = False |
| self._assert_invariants() |
| |
| # Remember to modify Symbol.remove if modifications to the parent change. |
| self._children: list[Symbol] = [] |
| self._anonChildren: list[Symbol] = [] |
| # note: _children includes _anonChildren |
| if self.parent: |
| self.parent._children.append(self) |
| if self.declaration: |
| self.declaration.symbol = self |
| |
| # Do symbol addition after self._children has been initialised. |
| self._add_function_params() |
| |
| def _fill_empty(self, declaration: ASTDeclaration, docname: str, line: int) -> None: |
| self._assert_invariants() |
| assert self.declaration is None |
| assert self.docname is None |
| assert self.line is None |
| assert declaration is not None |
| assert docname is not None |
| assert line is not None |
| self.declaration = declaration |
| self.declaration.symbol = self |
| self.docname = docname |
| self.line = line |
| self._assert_invariants() |
| # and symbol addition should be done as well |
| self._add_function_params() |
| |
| def _add_function_params(self) -> None: |
| if Symbol.debug_lookup: |
| Symbol.debug_indent += 1 |
| Symbol.debug_print("_add_function_params:") |
| # Note: we may be called from _fill_empty, so the symbols we want |
| # to add may actually already be present (as empty symbols). |
| |
| # add symbols for function parameters, if any |
| if self.declaration is not None and self.declaration.function_params is not None: |
| for p in self.declaration.function_params: |
| if p.arg is None: |
| continue |
| nn = p.arg.name |
| if nn is None: |
| continue |
| # (comparing to the template params: we have checked that we are a declaration) |
| decl = ASTDeclaration('functionParam', None, p) |
| assert not nn.rooted |
| assert len(nn.names) == 1 |
| self._add_symbols(nn, decl, self.docname, self.line) |
| if Symbol.debug_lookup: |
| Symbol.debug_indent -= 1 |
| |
| def remove(self) -> None: |
| if self.parent is None: |
| return |
| assert self in self.parent._children |
| self.parent._children.remove(self) |
| self.parent = None |
| |
| def clear_doc(self, docname: str) -> None: |
| for sChild in self._children: |
| sChild.clear_doc(docname) |
| if sChild.declaration and sChild.docname == docname: |
| sChild.declaration = None |
| sChild.docname = None |
| sChild.line = None |
| if sChild.siblingAbove is not None: |
| sChild.siblingAbove.siblingBelow = sChild.siblingBelow |
| if sChild.siblingBelow is not None: |
| sChild.siblingBelow.siblingAbove = sChild.siblingAbove |
| sChild.siblingAbove = None |
| sChild.siblingBelow = None |
| |
| def get_all_symbols(self) -> Iterator[Symbol]: |
| yield self |
| for sChild in self._children: |
| yield from sChild.get_all_symbols() |
| |
| @property |
| def children(self) -> Iterator[Symbol]: |
| yield from self._children |
| |
| @property |
| def children_recurse_anon(self) -> Iterator[Symbol]: |
| for c in self._children: |
| yield c |
| if not c.ident.is_anon(): |
| continue |
| yield from c.children_recurse_anon |
| |
| def get_lookup_key(self) -> LookupKey: |
| # The pickle files for the environment and for each document are distinct. |
| # The environment has all the symbols, but the documents has xrefs that |
| # must know their scope. A lookup key is essentially a specification of |
| # how to find a specific symbol. |
| symbols = [] |
| s = self |
| while s.parent: |
| symbols.append(s) |
| s = s.parent |
| symbols.reverse() |
| key = [] |
| for s in symbols: |
| if s.declaration is not None: |
| # TODO: do we need the ID? |
| key.append((s.ident, s.declaration.get_newest_id())) |
| else: |
| key.append((s.ident, None)) |
| return LookupKey(key) |
| |
| def get_full_nested_name(self) -> ASTNestedName: |
| symbols = [] |
| s = self |
| while s.parent: |
| symbols.append(s) |
| s = s.parent |
| symbols.reverse() |
| names = [] |
| for s in symbols: |
| names.append(s.ident) |
| return ASTNestedName(names, rooted=False) |
| |
| def _find_first_named_symbol(self, ident: ASTIdentifier, |
| matchSelf: bool, recurseInAnon: bool) -> Symbol | None: |
| # TODO: further simplification from C++ to C |
| if Symbol.debug_lookup: |
| Symbol.debug_print("_find_first_named_symbol ->") |
| res = self._find_named_symbols(ident, matchSelf, recurseInAnon, |
| searchInSiblings=False) |
| try: |
| return next(res) |
| except StopIteration: |
| return None |
| |
| def _find_named_symbols(self, ident: ASTIdentifier, |
| matchSelf: bool, recurseInAnon: bool, |
| searchInSiblings: bool) -> Iterator[Symbol]: |
| # TODO: further simplification from C++ to C |
| if Symbol.debug_lookup: |
| Symbol.debug_indent += 1 |
| Symbol.debug_print("_find_named_symbols:") |
| Symbol.debug_indent += 1 |
| Symbol.debug_print("self:") |
| logger.debug(self.to_string(Symbol.debug_indent + 1), end="") |
| Symbol.debug_print("ident: ", ident) |
| Symbol.debug_print("matchSelf: ", matchSelf) |
| Symbol.debug_print("recurseInAnon: ", recurseInAnon) |
| Symbol.debug_print("searchInSiblings: ", searchInSiblings) |
| |
| def candidates() -> Generator[Symbol, None, None]: |
| s = self |
| if Symbol.debug_lookup: |
| Symbol.debug_print("searching in self:") |
| logger.debug(s.to_string(Symbol.debug_indent + 1), end="") |
| while True: |
| if matchSelf: |
| yield s |
| if recurseInAnon: |
| yield from s.children_recurse_anon |
| else: |
| yield from s._children |
| |
| if s.siblingAbove is None: |
| break |
| s = s.siblingAbove |
| if Symbol.debug_lookup: |
| Symbol.debug_print("searching in sibling:") |
| logger.debug(s.to_string(Symbol.debug_indent + 1), end="") |
| |
| for s in candidates(): |
| if Symbol.debug_lookup: |
| Symbol.debug_print("candidate:") |
| logger.debug(s.to_string(Symbol.debug_indent + 1), end="") |
| if s.ident == ident: |
| if Symbol.debug_lookup: |
| Symbol.debug_indent += 1 |
| Symbol.debug_print("matches") |
| Symbol.debug_indent -= 3 |
| yield s |
| if Symbol.debug_lookup: |
| Symbol.debug_indent += 2 |
| if Symbol.debug_lookup: |
| Symbol.debug_indent -= 2 |
| |
| def _symbol_lookup( |
| self, |
| nestedName: ASTNestedName, |
| onMissingQualifiedSymbol: Callable[[Symbol, ASTIdentifier], Symbol | None], |
| ancestorLookupType: str | None, |
| matchSelf: bool, |
| recurseInAnon: bool, |
| searchInSiblings: bool, |
| ) -> SymbolLookupResult | None: |
| # TODO: further simplification from C++ to C |
| # ancestorLookupType: if not None, specifies the target type of the lookup |
| if Symbol.debug_lookup: |
| Symbol.debug_indent += 1 |
| Symbol.debug_print("_symbol_lookup:") |
| Symbol.debug_indent += 1 |
| Symbol.debug_print("self:") |
| logger.debug(self.to_string(Symbol.debug_indent + 1), end="") |
| Symbol.debug_print("nestedName: ", nestedName) |
| Symbol.debug_print("ancestorLookupType:", ancestorLookupType) |
| Symbol.debug_print("matchSelf: ", matchSelf) |
| Symbol.debug_print("recurseInAnon: ", recurseInAnon) |
| Symbol.debug_print("searchInSiblings: ", searchInSiblings) |
| |
| names = nestedName.names |
| |
| # find the right starting point for lookup |
| parentSymbol = self |
| if nestedName.rooted: |
| while parentSymbol.parent: |
| parentSymbol = parentSymbol.parent |
| if ancestorLookupType is not None: |
| # walk up until we find the first identifier |
| firstName = names[0] |
| while parentSymbol.parent: |
| if parentSymbol.find_identifier(firstName, |
| matchSelf=matchSelf, |
| recurseInAnon=recurseInAnon, |
| searchInSiblings=searchInSiblings): |
| break |
| parentSymbol = parentSymbol.parent |
| |
| if Symbol.debug_lookup: |
| Symbol.debug_print("starting point:") |
| logger.debug(parentSymbol.to_string(Symbol.debug_indent + 1), end="") |
| |
| # and now the actual lookup |
| for ident in names[:-1]: |
| symbol = parentSymbol._find_first_named_symbol( |
| ident, matchSelf=matchSelf, recurseInAnon=recurseInAnon) |
| if symbol is None: |
| symbol = onMissingQualifiedSymbol(parentSymbol, ident) |
| if symbol is None: |
| if Symbol.debug_lookup: |
| Symbol.debug_indent -= 2 |
| return None |
| # We have now matched part of a nested name, and need to match more |
| # so even if we should matchSelf before, we definitely shouldn't |
| # even more. (see also issue #2666) |
| matchSelf = False |
| parentSymbol = symbol |
| |
| if Symbol.debug_lookup: |
| Symbol.debug_print("handle last name from:") |
| logger.debug(parentSymbol.to_string(Symbol.debug_indent + 1), end="") |
| |
| # handle the last name |
| ident = names[-1] |
| |
| symbols = parentSymbol._find_named_symbols( |
| ident, matchSelf=matchSelf, |
| recurseInAnon=recurseInAnon, |
| searchInSiblings=searchInSiblings) |
| if Symbol.debug_lookup: |
| symbols = list(symbols) # type: ignore[assignment] |
| Symbol.debug_indent -= 2 |
| return SymbolLookupResult(symbols, parentSymbol, ident) |
| |
| def _add_symbols( |
| self, |
| nestedName: ASTNestedName, |
| declaration: ASTDeclaration | None, |
| docname: str | None, |
| line: int | None, |
| ) -> Symbol: |
| # TODO: further simplification from C++ to C |
| # Used for adding a whole path of symbols, where the last may or may not |
| # be an actual declaration. |
| |
| if Symbol.debug_lookup: |
| Symbol.debug_indent += 1 |
| Symbol.debug_print("_add_symbols:") |
| Symbol.debug_indent += 1 |
| Symbol.debug_print("nn: ", nestedName) |
| Symbol.debug_print("decl: ", declaration) |
| Symbol.debug_print(f"location: {docname}:{line}") |
| |
| def onMissingQualifiedSymbol(parentSymbol: Symbol, ident: ASTIdentifier) -> Symbol: |
| if Symbol.debug_lookup: |
| Symbol.debug_indent += 1 |
| Symbol.debug_print("_add_symbols, onMissingQualifiedSymbol:") |
| Symbol.debug_indent += 1 |
| Symbol.debug_print("ident: ", ident) |
| Symbol.debug_indent -= 2 |
| return Symbol(parent=parentSymbol, ident=ident, |
| declaration=None, docname=None, line=None) |
| |
| lookupResult = self._symbol_lookup(nestedName, |
| onMissingQualifiedSymbol, |
| ancestorLookupType=None, |
| matchSelf=False, |
| recurseInAnon=False, |
| searchInSiblings=False) |
| assert lookupResult is not None # we create symbols all the way, so that can't happen |
| symbols = list(lookupResult.symbols) |
| if len(symbols) == 0: |
| if Symbol.debug_lookup: |
| Symbol.debug_print("_add_symbols, result, no symbol:") |
| Symbol.debug_indent += 1 |
| Symbol.debug_print("ident: ", lookupResult.ident) |
| Symbol.debug_print("declaration: ", declaration) |
| Symbol.debug_print(f"location: {docname}:{line}") |
| Symbol.debug_indent -= 1 |
| symbol = Symbol(parent=lookupResult.parentSymbol, |
| ident=lookupResult.ident, |
| declaration=declaration, |
| docname=docname, line=line) |
| if Symbol.debug_lookup: |
| Symbol.debug_indent -= 2 |
| return symbol |
| |
| if Symbol.debug_lookup: |
| Symbol.debug_print("_add_symbols, result, symbols:") |
| Symbol.debug_indent += 1 |
| Symbol.debug_print("number symbols:", len(symbols)) |
| Symbol.debug_indent -= 1 |
| |
| if not declaration: |
| if Symbol.debug_lookup: |
| Symbol.debug_print("no declaration") |
| Symbol.debug_indent -= 2 |
| # good, just a scope creation |
| # TODO: what if we have more than one symbol? |
| return symbols[0] |
| |
| noDecl = [] |
| withDecl = [] |
| dupDecl = [] |
| for s in symbols: |
| if s.declaration is None: |
| noDecl.append(s) |
| elif s.isRedeclaration: |
| dupDecl.append(s) |
| else: |
| withDecl.append(s) |
| if Symbol.debug_lookup: |
| Symbol.debug_print("#noDecl: ", len(noDecl)) |
| Symbol.debug_print("#withDecl:", len(withDecl)) |
| Symbol.debug_print("#dupDecl: ", len(dupDecl)) |
| |
| # With partial builds we may start with a large symbol tree stripped of declarations. |
| # Essentially any combination of noDecl, withDecl, and dupDecls seems possible. |
| # TODO: make partial builds fully work. What should happen when the primary symbol gets |
| # deleted, and other duplicates exist? The full document should probably be rebuild. |
| |
| # First check if one of those with a declaration matches. |
| # If it's a function, we need to compare IDs, |
| # otherwise there should be only one symbol with a declaration. |
| def makeCandSymbol() -> Symbol: |
| if Symbol.debug_lookup: |
| Symbol.debug_print("begin: creating candidate symbol") |
| symbol = Symbol(parent=lookupResult.parentSymbol, |
| ident=lookupResult.ident, |
| declaration=declaration, |
| docname=docname, line=line) |
| if Symbol.debug_lookup: |
| Symbol.debug_print("end: creating candidate symbol") |
| return symbol |
| |
| if len(withDecl) == 0: |
| candSymbol = None |
| else: |
| candSymbol = makeCandSymbol() |
| |
| def handleDuplicateDeclaration(symbol: Symbol, candSymbol: Symbol) -> None: |
| if Symbol.debug_lookup: |
| Symbol.debug_indent += 1 |
| Symbol.debug_print("redeclaration") |
| Symbol.debug_indent -= 1 |
| Symbol.debug_indent -= 2 |
| # Redeclaration of the same symbol. |
| # Let the new one be there, but raise an error to the client |
| # so it can use the real symbol as subscope. |
| # This will probably result in a duplicate id warning. |
| candSymbol.isRedeclaration = True |
| raise _DuplicateSymbolError(symbol, declaration) |
| |
| if declaration.objectType != "function": |
| assert len(withDecl) <= 1 |
| handleDuplicateDeclaration(withDecl[0], candSymbol) |
| # (not reachable) |
| |
| # a function, so compare IDs |
| candId = declaration.get_newest_id() |
| if Symbol.debug_lookup: |
| Symbol.debug_print("candId:", candId) |
| for symbol in withDecl: |
| oldId = symbol.declaration.get_newest_id() |
| if Symbol.debug_lookup: |
| Symbol.debug_print("oldId: ", oldId) |
| if candId == oldId: |
| handleDuplicateDeclaration(symbol, candSymbol) |
| # (not reachable) |
| # no candidate symbol found with matching ID |
| # if there is an empty symbol, fill that one |
| if len(noDecl) == 0: |
| if Symbol.debug_lookup: |
| Symbol.debug_print( |
| "no match, no empty, candSybmol is not None?:", candSymbol is not None, |
| ) |
| Symbol.debug_indent -= 2 |
| if candSymbol is not None: |
| return candSymbol |
| else: |
| return makeCandSymbol() |
| else: |
| if Symbol.debug_lookup: |
| Symbol.debug_print( |
| "no match, but fill an empty declaration, candSybmol is not None?:", |
| candSymbol is not None) |
| Symbol.debug_indent -= 2 |
| if candSymbol is not None: |
| candSymbol.remove() |
| # assert len(noDecl) == 1 |
| # TODO: enable assertion when we at some point find out how to do cleanup |
| # for now, just take the first one, it should work fine ... right? |
| symbol = noDecl[0] |
| # If someone first opened the scope, and then later |
| # declares it, e.g, |
| # .. namespace:: Test |
| # .. namespace:: nullptr |
| # .. class:: Test |
| symbol._fill_empty(declaration, docname, line) |
| return symbol |
| |
| def merge_with(self, other: Symbol, docnames: list[str], |
| env: BuildEnvironment) -> None: |
| if Symbol.debug_lookup: |
| Symbol.debug_indent += 1 |
| Symbol.debug_print("merge_with:") |
| assert other is not None |
| for otherChild in other._children: |
| ourChild = self._find_first_named_symbol( |
| ident=otherChild.ident, matchSelf=False, |
| recurseInAnon=False) |
| if ourChild is None: |
| # TODO: hmm, should we prune by docnames? |
| self._children.append(otherChild) |
| otherChild.parent = self |
| otherChild._assert_invariants() |
| continue |
| if otherChild.declaration and otherChild.docname in docnames: |
| if not ourChild.declaration: |
| ourChild._fill_empty(otherChild.declaration, |
| otherChild.docname, otherChild.line) |
| elif ourChild.docname != otherChild.docname: |
| name = str(ourChild.declaration) |
| msg = __("Duplicate C declaration, also defined at %s:%s.\n" |
| "Declaration is '.. c:%s:: %s'.") |
| msg = msg % (ourChild.docname, ourChild.line, |
| ourChild.declaration.directiveType, name) |
| logger.warning(msg, location=(otherChild.docname, otherChild.line)) |
| else: |
| # Both have declarations, and in the same docname. |
| # This can apparently happen, it should be safe to |
| # just ignore it, right? |
| pass |
| ourChild.merge_with(otherChild, docnames, env) |
| if Symbol.debug_lookup: |
| Symbol.debug_indent -= 1 |
| |
| def add_name(self, nestedName: ASTNestedName) -> Symbol: |
| if Symbol.debug_lookup: |
| Symbol.debug_indent += 1 |
| Symbol.debug_print("add_name:") |
| res = self._add_symbols(nestedName, declaration=None, docname=None, line=None) |
| if Symbol.debug_lookup: |
| Symbol.debug_indent -= 1 |
| return res |
| |
| def add_declaration(self, declaration: ASTDeclaration, |
| docname: str, line: int) -> Symbol: |
| if Symbol.debug_lookup: |
| Symbol.debug_indent += 1 |
| Symbol.debug_print("add_declaration:") |
| assert declaration is not None |
| assert docname is not None |
| assert line is not None |
| nestedName = declaration.name |
| res = self._add_symbols(nestedName, declaration, docname, line) |
| if Symbol.debug_lookup: |
| Symbol.debug_indent -= 1 |
| return res |
| |
| def find_identifier(self, ident: ASTIdentifier, |
| matchSelf: bool, recurseInAnon: bool, searchInSiblings: bool, |
| ) -> Symbol | None: |
| if Symbol.debug_lookup: |
| Symbol.debug_indent += 1 |
| Symbol.debug_print("find_identifier:") |
| Symbol.debug_indent += 1 |
| Symbol.debug_print("ident: ", ident) |
| Symbol.debug_print("matchSelf: ", matchSelf) |
| Symbol.debug_print("recurseInAnon: ", recurseInAnon) |
| Symbol.debug_print("searchInSiblings:", searchInSiblings) |
| logger.debug(self.to_string(Symbol.debug_indent + 1), end="") |
| Symbol.debug_indent -= 2 |
| current = self |
| while current is not None: |
| if Symbol.debug_lookup: |
| Symbol.debug_indent += 2 |
| Symbol.debug_print("trying:") |
| logger.debug(current.to_string(Symbol.debug_indent + 1), end="") |
| Symbol.debug_indent -= 2 |
| if matchSelf and current.ident == ident: |
| return current |
| children = current.children_recurse_anon if recurseInAnon else current._children |
| for s in children: |
| if s.ident == ident: |
| return s |
| if not searchInSiblings: |
| break |
| current = current.siblingAbove |
| return None |
| |
| def direct_lookup(self, key: LookupKey) -> Symbol | None: |
| if Symbol.debug_lookup: |
| Symbol.debug_indent += 1 |
| Symbol.debug_print("direct_lookup:") |
| Symbol.debug_indent += 1 |
| s = self |
| for name, id_ in key.data: |
| res = None |
| for cand in s._children: |
| if cand.ident == name: |
| res = cand |
| break |
| s = res |
| if Symbol.debug_lookup: |
| Symbol.debug_print("name: ", name) |
| Symbol.debug_print("id: ", id_) |
| if s is not None: |
| logger.debug(s.to_string(Symbol.debug_indent + 1), end="") |
| else: |
| Symbol.debug_print("not found") |
| if s is None: |
| if Symbol.debug_lookup: |
| Symbol.debug_indent -= 2 |
| return None |
| if Symbol.debug_lookup: |
| Symbol.debug_indent -= 2 |
| return s |
| |
| def find_declaration(self, nestedName: ASTNestedName, typ: str, |
| matchSelf: bool, recurseInAnon: bool) -> Symbol | None: |
| # templateShorthand: missing template parameter lists for templates is ok |
| if Symbol.debug_lookup: |
| Symbol.debug_indent += 1 |
| Symbol.debug_print("find_declaration:") |
| |
| def onMissingQualifiedSymbol( |
| parentSymbol: Symbol, |
| ident: ASTIdentifier, |
| ) -> Symbol | None: |
| return None |
| |
| lookupResult = self._symbol_lookup(nestedName, |
| onMissingQualifiedSymbol, |
| ancestorLookupType=typ, |
| matchSelf=matchSelf, |
| recurseInAnon=recurseInAnon, |
| searchInSiblings=False) |
| if Symbol.debug_lookup: |
| Symbol.debug_indent -= 1 |
| if lookupResult is None: |
| return None |
| |
| symbols = list(lookupResult.symbols) |
| if len(symbols) == 0: |
| return None |
| return symbols[0] |
| |
| def to_string(self, indent: int) -> str: |
| res = [Symbol.debug_indent_string * indent] |
| if not self.parent: |
| res.append('::') |
| else: |
| if self.ident: |
| res.append(str(self.ident)) |
| else: |
| res.append(str(self.declaration)) |
| if self.declaration: |
| res.append(": ") |
| if self.isRedeclaration: |
| res.append('!!duplicate!! ') |
| res.append(str(self.declaration)) |
| if self.docname: |
| res.append('\t(') |
| res.append(self.docname) |
| res.append(')') |
| res.append('\n') |
| return ''.join(res) |
| |
| def dump(self, indent: int) -> str: |
| res = [self.to_string(indent)] |
| for c in self._children: |
| res.append(c.dump(indent + 1)) |
| return ''.join(res) |
| |
| |
| class DefinitionParser(BaseParser): |
| @property |
| def language(self) -> str: |
| return 'C' |
| |
| @property |
| def id_attributes(self): |
| return self.config.c_id_attributes |
| |
| @property |
| def paren_attributes(self): |
| return self.config.c_paren_attributes |
| |
| def _parse_string(self) -> str | None: |
| if self.current_char != '"': |
| return None |
| startPos = 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[startPos: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], bool]: |
| # 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 = [] |
| trailingComma = 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 and close == '}': |
| self.pos += 1 |
| trailingComma = True |
| break |
| return exprs, trailingComma |
| |
| 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, trailingComma = 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, trailingComma = self._parse_initializer_list("braced-init-list", '{', '}') |
| if exprs is None: |
| return None |
| return ASTBracedInitList(exprs, trailingComma) |
| |
| 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 |
| postFixes: 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.") |
| postFixes.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() |
| postFixes.append(ASTPostfixMemberOfPointer(name)) |
| continue |
| if self.skip_string('++'): |
| postFixes.append(ASTPostfixInc()) |
| continue |
| if self.skip_string('--'): |
| postFixes.append(ASTPostfixDec()) |
| continue |
| lst = self._parse_paren_expression_list() |
| if lst is not None: |
| postFixes.append(ASTPostfixCallExpr(lst)) |
| continue |
| break |
| return ASTPostfixExpr(prefix, postFixes) |
| |
| 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 exCast: |
| self.pos = pos |
| try: |
| return self._parse_unary_expression() |
| except DefinitionError as exUnary: |
| errs = [] |
| errs.append((exCast, "If type cast expression")) |
| errs.append((exUnary, "If unary expression")) |
| raise self._make_multi_error(errs, |
| "Error in cast expression.") from exUnary |
| 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, opId): |
| if opId + 1 == len(_expression_bin_ops): |
| def parser() -> ASTExpression: |
| return self._parse_cast_expression() |
| else: |
| def parser() -> ASTExpression: |
| return _parse_bin_op_expr(self, opId + 1) |
| exprs = [] |
| ops = [] |
| exprs.append(parser()) |
| while True: |
| self.skip_ws() |
| pos = self.pos |
| oneMore = False |
| for op in _expression_bin_ops[opId]: |
| if op[0] in 'abcnox': |
| if not self.skip_word(op): |
| continue |
| else: |
| if not self.skip_string(op): |
| continue |
| if op == '&' and 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) |
| oneMore = True |
| break |
| except DefinitionError: |
| self.pos = pos |
| if not oneMore: |
| break |
| return ASTBinOpExpr(exprs, ops) |
| return _parse_bin_op_expr(self, 0) |
| |
| def _parse_conditional_expression_tail(self, orExprHead: 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 = [] |
| orExpr = self._parse_logical_or_expression() |
| exprs.append(orExpr) |
| # TODO: handle ternary with _parse_conditional_expression_tail |
| while True: |
| oneMore = 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) |
| oneMore = True |
| if not oneMore: |
| break |
| return ASTAssignmentExpr(exprs, ops) |
| |
| def _parse_constant_expression(self) -> ASTExpression: |
| # -> conditional-expression |
| orExpr = self._parse_logical_or_expression() |
| # TODO: use _parse_conditional_expression_tail |
| return orExpr |
| |
| 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 |
| prevPos = 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 = prevPos |
| # and then the fallback scanning |
| assert end is not None |
| self.skip_ws() |
| startPos = 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." |
| % startPos) |
| value = self.definition[startPos: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 |
| |
| nestedName = self._parse_nested_name() |
| return ASTTrailingTypeSpecName(prefix, nestedName) |
| |
| def _parse_parameters(self, paramMode: str) -> ASTParameters | None: |
| self.skip_ws() |
| if not self.skip_string('('): |
| if paramMode == '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 |
| threadLocal = 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 threadLocal: |
| if self.skip_word('thread_local'): |
| threadLocal = 'thread_local' |
| continue |
| if self.skip_word('_Thread_local'): |
| threadLocal = '_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, threadLocal, 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) |
| leftSpecs = self._parse_decl_specs_simple(outer, typed) |
| rightSpecs = None |
| |
| if typed: |
| trailing = self._parse_trailing_type_spec() |
| rightSpecs = self._parse_decl_specs_simple(outer, typed) |
| else: |
| trailing = None |
| return ASTDeclSpecs(outer, leftSpecs, rightSpecs, trailing) |
| |
| def _parse_declarator_name_suffix( |
| self, named: bool | str, paramMode: 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) |
| declId = ASTNestedName([identifier], rooted=False) |
| else: |
| declId = None |
| elif named: |
| declId = self._parse_nested_name() |
| else: |
| declId = None |
| arrayOps = [] |
| 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: |
| |
| def parser(): |
| return 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.") |
| arrayOps.append(ASTArray(static, const, volatile, restrict, vla, size)) |
| else: |
| break |
| param = self._parse_parameters(paramMode) |
| if param is None and len(arrayOps) == 0: |
| # perhaps a bit-field |
| if named and paramMode == 'type' and typed: |
| self.skip_ws() |
| if self.skip_string(':'): |
| size = self._parse_constant_expression() |
| return ASTDeclaratorNameBitField(declId=declId, size=size) |
| return ASTDeclaratorNameParam(declId=declId, arrayOps=arrayOps, |
| param=param) |
| |
| def _parse_declarator(self, named: bool | str, paramMode: str, |
| typed: bool = True) -> ASTDeclarator: |
| # 'typed' here means 'parse return type stuff' |
| if paramMode not in ('type', 'function'): |
| raise Exception( |
| "Internal error, unknown paramMode '%s'." % paramMode) |
| prevErrors = [] |
| 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, paramMode, 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, paramMode, |
| typed) |
| return res |
| except DefinitionError as exParamQual: |
| msg = "If declarator-id with parameters" |
| if paramMode == 'function': |
| msg += " (e.g., 'void f(int arg)')" |
| prevErrors.append((exParamQual, 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, paramMode, typed) |
| if not self.skip_string(')'): |
| self.fail("Expected ')' in \"( ptr-declarator )\"") |
| next = self._parse_declarator(named=False, |
| paramMode="type", |
| typed=typed) |
| return ASTDeclaratorParen(inner=inner, next=next) |
| except DefinitionError as exNoPtrParen: |
| self.pos = pos |
| msg = "If parenthesis in noptr-declarator" |
| if paramMode == 'function': |
| msg += " (e.g., 'void (*f(int arg))(double)')" |
| prevErrors.append((exNoPtrParen, msg)) |
| header = "Error in declarator" |
| raise self._make_multi_error(prevErrors, header) from exNoPtrParen |
| pos = self.pos |
| try: |
| return self._parse_declarator_name_suffix(named, paramMode, typed) |
| except DefinitionError as e: |
| self.pos = pos |
| prevErrors.append((e, "If declarator-id")) |
| header = "Error in declarator or parameters" |
| raise self._make_multi_error(prevErrors, header) from e |
| |
| def _parse_initializer(self, outer: str | None = None, allowFallback: bool = True, |
| ) -> ASTInitializer | None: |
| self.skip_ws() |
| if outer == 'member' and False: # NoQA: SIM223 # TODO |
| bracedInit = self._parse_braced_init_list() |
| if bracedInit is not None: |
| return ASTInitializer(bracedInit, hasAssign=False) |
| |
| if not self.skip_string('='): |
| return None |
| |
| bracedInit = self._parse_braced_init_list() |
| if bracedInit is not None: |
| return ASTInitializer(bracedInit) |
| |
| if outer == 'member': |
| fallbackEnd: list[str] = [] |
| elif outer is None: # function parameter |
| fallbackEnd = [',', ')'] |
| else: |
| self.fail("Internal error, initializer for outer '%s' not " |
| "implemented." % outer) |
| |
| def parser(): |
| return self._parse_assignment_expression() |
| |
| value = self._parse_expression_fallback(fallbackEnd, parser, allow=allowFallback) |
| 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. |
| prevErrors = [] |
| startPos = self.pos |
| # first try without the type |
| try: |
| declSpecs = self._parse_decl_specs(outer=outer, typed=False) |
| decl = self._parse_declarator(named=True, paramMode=outer, |
| typed=False) |
| self.assert_end(allowSemicolon=True) |
| except DefinitionError as exUntyped: |
| desc = "If just a name" |
| prevErrors.append((exUntyped, desc)) |
| self.pos = startPos |
| try: |
| declSpecs = self._parse_decl_specs(outer=outer) |
| decl = self._parse_declarator(named=True, paramMode=outer) |
| except DefinitionError as exTyped: |
| self.pos = startPos |
| desc = "If typedef-like declaration" |
| prevErrors.append((exTyped, 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(prevErrors, header) from exTyped |
| else: # NoQA: RET506 |
| # 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 = startPos |
| typed = True |
| declSpecs = self._parse_decl_specs(outer=outer, typed=typed) |
| decl = self._parse_declarator(named=True, paramMode=outer, |
| typed=typed) |
| elif outer == 'function': |
| declSpecs = self._parse_decl_specs(outer=outer) |
| decl = self._parse_declarator(named=True, paramMode=outer) |
| else: |
| paramMode = 'type' |
| if outer == 'member': # i.e., member |
| named = True |
| declSpecs = self._parse_decl_specs(outer=outer) |
| decl = self._parse_declarator(named=named, paramMode=paramMode) |
| return ASTType(declSpecs, 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() |
| |
| def parser() -> ASTExpression: |
| return self._parse_constant_expression() |
| |
| initVal = self._parse_expression_fallback([], parser) |
| init = ASTInitializer(initVal) |
| return ASTEnumerator(name, init, attrs) |
| |
| def parse_declaration(self, objectType: str, directiveType: str) -> ASTDeclaration: |
| if objectType not in ('function', 'member', |
| 'macro', 'struct', 'union', 'enum', 'enumerator', 'type'): |
| raise Exception('Internal error, unknown objectType "%s".' % objectType) |
| if directiveType not in ('function', 'member', 'var', |
| 'macro', 'struct', 'union', 'enum', 'enumerator', 'type'): |
| raise Exception('Internal error, unknown directiveType "%s".' % directiveType) |
| |
| declaration: DeclarationType = None |
| if objectType == 'member': |
| declaration = self._parse_type_with_init(named=True, outer='member') |
| elif objectType == 'function': |
| declaration = self._parse_type(named=True, outer='function') |
| elif objectType == 'macro': |
| declaration = self._parse_macro() |
| elif objectType == 'struct': |
| declaration = self._parse_struct() |
| elif objectType == 'union': |
| declaration = self._parse_union() |
| elif objectType == 'enum': |
| declaration = self._parse_enum() |
| elif objectType == 'enumerator': |
| declaration = self._parse_enumerator() |
| elif objectType == 'type': |
| declaration = self._parse_type(named=True, outer='type') |
| else: |
| raise AssertionError |
| if objectType != 'macro': |
| self.skip_ws() |
| semicolon = self.skip_string(';') |
| else: |
| semicolon = False |
| return ASTDeclaration(objectType, directiveType, 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 |
| try: |
| res = self._parse_expression() |
| self.skip_ws() |
| self.assert_end() |
| except DefinitionError as exExpr: |
| self.pos = pos |
| try: |
| res = self._parse_type(False) |
| self.skip_ws() |
| self.assert_end() |
| except DefinitionError as exType: |
| header = "Error when parsing (type) expression." |
| errs = [] |
| errs.append((exExpr, "If expression")) |
| errs.append((exType, "If type")) |
| raise self._make_multi_error(errs, header) from exType |
| return res |
| |
| |
| def _make_phony_error_name() -> ASTNestedName: |
| return ASTNestedName([ASTIdentifier("PhonyNameDueToError")], rooted=False) |
| |
| |
| class CObject(ObjectDescription[ASTDeclaration]): |
| """ |
| Description of a C language object. |
| """ |
| |
| option_spec: OptionSpec = { |
| 'no-index-entry': directives.flag, |
| 'no-contents-entry': directives.flag, |
| 'no-typesetting': directives.flag, |
| 'noindexentry': directives.flag, |
| 'nocontentsentry': directives.flag, |
| 'single-line-parameter-list': directives.flag, |
| } |
| |
| def _add_enumerator_to_parent(self, ast: ASTDeclaration) -> None: |
| assert ast.objectType == 'enumerator' |
| # find the parent, if it exists && is an enum |
| # then add the name to the parent scope |
| symbol = ast.symbol |
| assert symbol |
| assert symbol.ident is not None |
| parentSymbol = symbol.parent |
| assert parentSymbol |
| if parentSymbol.parent is None: |
| # TODO: we could warn, but it is somewhat equivalent to |
| # enumeratorss, without the enum |
| return # no parent |
| parentDecl = parentSymbol.declaration |
| if parentDecl is None: |
| # the parent is not explicitly declared |
| # TODO: we could warn, but? |
| return |
| if parentDecl.objectType != 'enum': |
| # TODO: maybe issue a warning, enumerators in non-enums is weird, |
| # but it is somewhat equivalent to enumeratorss, without the enum |
| return |
| if parentDecl.directiveType != 'enum': |
| return |
| |
| targetSymbol = parentSymbol.parent |
| s = targetSymbol.find_identifier(symbol.ident, matchSelf=False, recurseInAnon=True, |
| searchInSiblings=False) |
| if s is not None: |
| # something is already declared with that name |
| return |
| declClone = symbol.declaration.clone() |
| declClone.enumeratorScopedSymbol = symbol |
| Symbol(parent=targetSymbol, ident=symbol.ident, |
| declaration=declClone, |
| docname=self.env.docname, line=self.get_source_info()[1]) |
| |
| def add_target_and_index(self, ast: ASTDeclaration, sig: str, |
| signode: TextElement) -> None: |
| ids = [] |
| for i in range(1, _max_id + 1): |
| try: |
| id = ast.get_id(version=i) |
| ids.append(id) |
| except NoOldIdError: |
| assert i < _max_id |
| # let's keep the newest first |
| ids = list(reversed(ids)) |
| newestId = ids[0] |
| assert newestId # shouldn't be None |
| |
| name = ast.symbol.get_full_nested_name().get_display_string().lstrip('.') |
| if newestId not in self.state.document.ids: |
| # always add the newest id |
| assert newestId |
| signode['ids'].append(newestId) |
| # only add compatibility ids when there are no conflicts |
| for id in ids[1:]: |
| if not id: # is None when the element didn't exist in that version |
| continue |
| if id not in self.state.document.ids: |
| signode['ids'].append(id) |
| |
| self.state.document.note_explicit_target(signode) |
| |
| if 'no-index-entry' not in self.options: |
| indexText = self.get_index_text(name) |
| self.indexnode['entries'].append(('single', indexText, newestId, '', None)) |
| |
| @property |
| def object_type(self) -> str: |
| raise NotImplementedError |
| |
| @property |
| def display_object_type(self) -> str: |
| return self.object_type |
| |
| def get_index_text(self, name: str) -> str: |
| return _('%s (C %s)') % (name, self.display_object_type) |
| |
| def parse_definition(self, parser: DefinitionParser) -> ASTDeclaration: |
| return parser.parse_declaration(self.object_type, self.objtype) |
| |
| def describe_signature(self, signode: TextElement, ast: ASTDeclaration, |
| options: dict) -> None: |
| ast.describe_signature(signode, 'lastIsName', self.env, options) |
| |
| def run(self) -> list[Node]: |
| env = self.state.document.settings.env # from ObjectDescription.run |
| if 'c:parent_symbol' not in env.temp_data: |
| root = env.domaindata['c']['root_symbol'] |
| env.temp_data['c:parent_symbol'] = root |
| env.ref_context['c:parent_key'] = root.get_lookup_key() |
| |
| # When multiple declarations are made in the same directive |
| # they need to know about each other to provide symbol lookup for function parameters. |
| # We use last_symbol to store the latest added declaration in a directive. |
| env.temp_data['c:last_symbol'] = None |
| return super().run() |
| |
| def handle_signature(self, sig: str, signode: TextElement) -> ASTDeclaration: |
| parentSymbol: Symbol = self.env.temp_data['c:parent_symbol'] |
| |
| max_len = (self.env.config.c_maximum_signature_line_length |
| or self.env.config.maximum_signature_line_length |
| or 0) |
| signode['multi_line_parameter_list'] = ( |
| 'single-line-parameter-list' not in self.options |
| and (len(sig) > max_len > 0) |
| ) |
| |
| parser = DefinitionParser(sig, location=signode, config=self.env.config) |
| try: |
| ast = self.parse_definition(parser) |
| parser.assert_end() |
| except DefinitionError as e: |
| logger.warning(e, location=signode) |
| # It is easier to assume some phony name than handling the error in |
| # the possibly inner declarations. |
| name = _make_phony_error_name() |
| symbol = parentSymbol.add_name(name) |
| self.env.temp_data['c:last_symbol'] = symbol |
| raise ValueError from e |
| |
| try: |
| symbol = parentSymbol.add_declaration( |
| ast, docname=self.env.docname, line=self.get_source_info()[1]) |
| # append the new declaration to the sibling list |
| assert symbol.siblingAbove is None |
| assert symbol.siblingBelow is None |
| symbol.siblingAbove = self.env.temp_data['c:last_symbol'] |
| if symbol.siblingAbove is not None: |
| assert symbol.siblingAbove.siblingBelow is None |
| symbol.siblingAbove.siblingBelow = symbol |
| self.env.temp_data['c:last_symbol'] = symbol |
| except _DuplicateSymbolError as e: |
| # Assume we are actually in the old symbol, |
| # instead of the newly created duplicate. |
| self.env.temp_data['c:last_symbol'] = e.symbol |
| msg = __("Duplicate C declaration, also defined at %s:%s.\n" |
| "Declaration is '.. c:%s:: %s'.") |
| msg = msg % (e.symbol.docname, e.symbol.line, self.display_object_type, sig) |
| logger.warning(msg, location=signode) |
| |
| if ast.objectType == 'enumerator': |
| self._add_enumerator_to_parent(ast) |
| |
| # note: handle_signature may be called multiple time per directive, |
| # if it has multiple signatures, so don't mess with the original options. |
| options = dict(self.options) |
| self.describe_signature(signode, ast, options) |
| return ast |
| |
| def before_content(self) -> None: |
| lastSymbol: Symbol = self.env.temp_data['c:last_symbol'] |
| assert lastSymbol |
| self.oldParentSymbol = self.env.temp_data['c:parent_symbol'] |
| self.oldParentKey: LookupKey = self.env.ref_context['c:parent_key'] |
| self.env.temp_data['c:parent_symbol'] = lastSymbol |
| self.env.ref_context['c:parent_key'] = lastSymbol.get_lookup_key() |
| |
| def after_content(self) -> None: |
| self.env.temp_data['c:parent_symbol'] = self.oldParentSymbol |
| self.env.ref_context['c:parent_key'] = self.oldParentKey |
| |
| |
| class CMemberObject(CObject): |
| object_type = 'member' |
| |
| @property |
| def display_object_type(self) -> str: |
| # the distinction between var and member is only cosmetic |
| assert self.objtype in ('member', 'var') |
| return self.objtype |
| |
| |
| _function_doc_field_types = [ |
| TypedField('parameter', label=_('Parameters'), |
| names=('param', 'parameter', 'arg', 'argument'), |
| typerolename='expr', typenames=('type',)), |
| GroupedField('retval', label=_('Return values'), |
| names=('retvals', 'retval'), |
| can_collapse=True), |
| Field('returnvalue', label=_('Returns'), has_arg=False, |
| names=('returns', 'return')), |
| Field('returntype', label=_('Return type'), has_arg=False, |
| names=('rtype',)), |
| ] |
| |
| |
| class CFunctionObject(CObject): |
| object_type = 'function' |
| |
| doc_field_types = _function_doc_field_types.copy() |
| |
| |
| class CMacroObject(CObject): |
| object_type = 'macro' |
| |
| doc_field_types = _function_doc_field_types.copy() |
| |
| |
| class CStructObject(CObject): |
| object_type = 'struct' |
| |
| |
| class CUnionObject(CObject): |
| object_type = 'union' |
| |
| |
| class CEnumObject(CObject): |
| object_type = 'enum' |
| |
| |
| class CEnumeratorObject(CObject): |
| object_type = 'enumerator' |
| |
| |
| class CTypeObject(CObject): |
| object_type = 'type' |
| |
| |
| class CNamespaceObject(SphinxDirective): |
| """ |
| This directive is just to tell Sphinx that we're documenting stuff in |
| namespace foo. |
| """ |
| |
| has_content = False |
| required_arguments = 1 |
| optional_arguments = 0 |
| final_argument_whitespace = True |
| option_spec: OptionSpec = {} |
| |
| def run(self) -> list[Node]: |
| rootSymbol = self.env.domaindata['c']['root_symbol'] |
| if self.arguments[0].strip() in ('NULL', '0', 'nullptr'): |
| symbol = rootSymbol |
| stack: list[Symbol] = [] |
| else: |
| parser = DefinitionParser(self.arguments[0], |
| location=self.get_location(), |
| config=self.env.config) |
| try: |
| name = parser.parse_namespace_object() |
| parser.assert_end() |
| except DefinitionError as e: |
| logger.warning(e, location=self.get_location()) |
| name = _make_phony_error_name() |
| symbol = rootSymbol.add_name(name) |
| stack = [symbol] |
| self.env.temp_data['c:parent_symbol'] = symbol |
| self.env.temp_data['c:namespace_stack'] = stack |
| self.env.ref_context['c:parent_key'] = symbol.get_lookup_key() |
| return [] |
| |
| |
| class CNamespacePushObject(SphinxDirective): |
| has_content = False |
| required_arguments = 1 |
| optional_arguments = 0 |
| final_argument_whitespace = True |
| option_spec: OptionSpec = {} |
| |
| def run(self) -> list[Node]: |
| if self.arguments[0].strip() in ('NULL', '0', 'nullptr'): |
| return [] |
| parser = DefinitionParser(self.arguments[0], |
| location=self.get_location(), |
| config=self.env.config) |
| try: |
| name = parser.parse_namespace_object() |
| parser.assert_end() |
| except DefinitionError as e: |
| logger.warning(e, location=self.get_location()) |
| name = _make_phony_error_name() |
| oldParent = self.env.temp_data.get('c:parent_symbol', None) |
| if not oldParent: |
| oldParent = self.env.domaindata['c']['root_symbol'] |
| symbol = oldParent.add_name(name) |
| stack = self.env.temp_data.get('c:namespace_stack', []) |
| stack.append(symbol) |
| self.env.temp_data['c:parent_symbol'] = symbol |
| self.env.temp_data['c:namespace_stack'] = stack |
| self.env.ref_context['c:parent_key'] = symbol.get_lookup_key() |
| return [] |
| |
| |
| class CNamespacePopObject(SphinxDirective): |
| has_content = False |
| required_arguments = 0 |
| optional_arguments = 0 |
| final_argument_whitespace = True |
| option_spec: OptionSpec = {} |
| |
| def run(self) -> list[Node]: |
| stack = self.env.temp_data.get('c:namespace_stack', None) |
| if not stack or len(stack) == 0: |
| logger.warning("C namespace pop on empty stack. Defaulting to global scope.", |
| location=self.get_location()) |
| stack = [] |
| else: |
| stack.pop() |
| if len(stack) > 0: |
| symbol = stack[-1] |
| else: |
| symbol = self.env.domaindata['c']['root_symbol'] |
| self.env.temp_data['c:parent_symbol'] = symbol |
| self.env.temp_data['c:namespace_stack'] = stack |
| self.env.ref_context['cp:parent_key'] = symbol.get_lookup_key() |
| return [] |
| |
| |
| class AliasNode(nodes.Element): |
| def __init__( |
| self, |
| sig: str, |
| aliasOptions: dict, |
| document: Any, |
| env: BuildEnvironment | None = None, |
| parentKey: LookupKey | None = None, |
| ) -> None: |
| super().__init__() |
| self.sig = sig |
| self.aliasOptions = aliasOptions |
| self.document = document |
| if env is not None: |
| if 'c:parent_symbol' not in env.temp_data: |
| root = env.domaindata['c']['root_symbol'] |
| env.temp_data['c:parent_symbol'] = root |
| env.ref_context['c:parent_key'] = root.get_lookup_key() |
| self.parentKey = env.ref_context['c:parent_key'] |
| else: |
| assert parentKey is not None |
| self.parentKey = parentKey |
| |
| def copy(self) -> AliasNode: |
| return self.__class__(self.sig, self.aliasOptions, self.document, |
| env=None, parentKey=self.parentKey) |
| |
| |
| class AliasTransform(SphinxTransform): |
| default_priority = ReferencesResolver.default_priority - 1 |
| |
| def _render_symbol(self, s: Symbol, maxdepth: int, skipThis: bool, |
| aliasOptions: dict, renderOptions: dict, |
| document: Any) -> list[Node]: |
| if maxdepth == 0: |
| recurse = True |
| elif maxdepth == 1: |
| recurse = False |
| else: |
| maxdepth -= 1 |
| recurse = True |
| |
| nodes: list[Node] = [] |
| if not skipThis: |
| signode = addnodes.desc_signature('', '') |
| nodes.append(signode) |
| s.declaration.describe_signature(signode, 'markName', self.env, renderOptions) |
| |
| if recurse: |
| if skipThis: |
| childContainer: list[Node] | addnodes.desc = nodes |
| else: |
| content = addnodes.desc_content() |
| desc = addnodes.desc() |
| content.append(desc) |
| desc.document = document |
| desc['domain'] = 'c' |
| # 'desctype' is a backwards compatible attribute |
| desc['objtype'] = desc['desctype'] = 'alias' |
| desc['no-index'] = True |
| childContainer = desc |
| |
| for sChild in s.children: |
| if sChild.declaration is None: |
| continue |
| childNodes = self._render_symbol( |
| sChild, maxdepth=maxdepth, skipThis=False, |
| aliasOptions=aliasOptions, renderOptions=renderOptions, |
| document=document) |
| childContainer.extend(childNodes) |
| |
| if not skipThis and len(desc.children) != 0: |
| nodes.append(content) |
| return nodes |
| |
| def apply(self, **kwargs: Any) -> None: |
| for node in self.document.findall(AliasNode): |
| sig = node.sig |
| parentKey = node.parentKey |
| try: |
| parser = DefinitionParser(sig, location=node, |
| config=self.env.config) |
| name = parser.parse_xref_object() |
| except DefinitionError as e: |
| logger.warning(e, location=node) |
| name = None |
| |
| if name is None: |
| # could not be parsed, so stop here |
| signode = addnodes.desc_signature(sig, '') |
| signode.clear() |
| signode += addnodes.desc_name(sig, sig) |
| node.replace_self(signode) |
| continue |
| |
| rootSymbol: Symbol = self.env.domains['c'].data['root_symbol'] |
| parentSymbol: Symbol = rootSymbol.direct_lookup(parentKey) |
| if not parentSymbol: |
| logger.debug("Target: %s", sig) |
| logger.debug("ParentKey: %s", parentKey) |
| logger.debug(rootSymbol.dump(1)) |
| assert parentSymbol # should be there |
| |
| s = parentSymbol.find_declaration( |
| name, 'any', |
| matchSelf=True, recurseInAnon=True) |
| if s is None: |
| signode = addnodes.desc_signature(sig, '') |
| node.append(signode) |
| signode.clear() |
| signode += addnodes.desc_name(sig, sig) |
| |
| logger.warning("Could not find C declaration for alias '%s'." % name, |
| location=node) |
| node.replace_self(signode) |
| continue |
| # Declarations like .. var:: int Missing::var |
| # may introduce symbols without declarations. |
| # But if we skip the root then it is ok to start recursion from it. |
| if not node.aliasOptions['noroot'] and s.declaration is None: |
| signode = addnodes.desc_signature(sig, '') |
| node.append(signode) |
| signode.clear() |
| signode += addnodes.desc_name(sig, sig) |
| |
| logger.warning( |
| "Can not render C declaration for alias '%s'. No such declaration." % name, |
| location=node) |
| node.replace_self(signode) |
| continue |
| |
| nodes = self._render_symbol(s, maxdepth=node.aliasOptions['maxdepth'], |
| skipThis=node.aliasOptions['noroot'], |
| aliasOptions=node.aliasOptions, |
| renderOptions={}, document=node.document) |
| node.replace_self(nodes) |
| |
| |
| class CAliasObject(ObjectDescription): |
| option_spec: OptionSpec = { |
| 'maxdepth': directives.nonnegative_int, |
| 'noroot': directives.flag, |
| } |
| |
| def run(self) -> list[Node]: |
| """ |
| On purpose this doesn't call the ObjectDescription version, but is based on it. |
| Each alias signature may expand into multiple real signatures if 'noroot'. |
| The code is therefore based on the ObjectDescription version. |
| """ |
| if ':' in self.name: |
| self.domain, self.objtype = self.name.split(':', 1) |
| else: |
| self.domain, self.objtype = '', self.name |
| |
| node = addnodes.desc() |
| node.document = self.state.document |
| node['domain'] = self.domain |
| # 'desctype' is a backwards compatible attribute |
| node['objtype'] = node['desctype'] = self.objtype |
| node['no-index'] = True |
| |
| self.names: list[str] = [] |
| aliasOptions = { |
| 'maxdepth': self.options.get('maxdepth', 1), |
| 'noroot': 'noroot' in self.options, |
| } |
| if aliasOptions['noroot'] and aliasOptions['maxdepth'] == 1: |
| logger.warning("Error in C alias declaration." |
| " Requested 'noroot' but 'maxdepth' 1." |
| " When skipping the root declaration," |
| " need 'maxdepth' 0 for infinite or at least 2.", |
| location=self.get_location()) |
| for sig in self.get_signatures(): |
| node.append(AliasNode(sig, aliasOptions, self.state.document, env=self.env)) |
| return [node] |
| |
| |
| class CXRefRole(XRefRole): |
| def process_link(self, env: BuildEnvironment, refnode: Element, |
| has_explicit_title: bool, title: str, target: str) -> tuple[str, str]: |
| refnode.attributes.update(env.ref_context) |
| |
| if not has_explicit_title: |
| # major hax: replace anon names via simple string manipulation. |
| # Can this actually fail? |
| title = anon_identifier_re.sub("[anonymous]", str(title)) |
| |
| if not has_explicit_title: |
| target = target.lstrip('~') # only has a meaning for the title |
| # if the first character is a tilde, don't display the module/class |
| # parts of the contents |
| if title[0:1] == '~': |
| title = title[1:] |
| dot = title.rfind('.') |
| if dot != -1: |
| title = title[dot + 1:] |
| return title, target |
| |
| |
| class CExprRole(SphinxRole): |
| def __init__(self, asCode: bool) -> None: |
| super().__init__() |
| if asCode: |
| # render the expression as inline code |
| self.class_type = 'c-expr' |
| else: |
| # render the expression as inline text |
| self.class_type = 'c-texpr' |
| |
| def run(self) -> tuple[list[Node], list[system_message]]: |
| text = self.text.replace('\n', ' ') |
| parser = DefinitionParser(text, location=self.get_location(), |
| config=self.env.config) |
| # attempt to mimic XRefRole classes, except that... |
| try: |
| ast = parser.parse_expression() |
| except DefinitionError as ex: |
| logger.warning('Unparseable C expression: %r\n%s', text, ex, |
| location=self.get_location()) |
| # see below |
| return [addnodes.desc_inline('c', text, text, classes=[self.class_type])], [] |
| parentSymbol = self.env.temp_data.get('c:parent_symbol', None) |
| if parentSymbol is None: |
| parentSymbol = self.env.domaindata['c']['root_symbol'] |
| # ...most if not all of these classes should really apply to the individual references, |
| # not the container node |
| signode = addnodes.desc_inline('c', classes=[self.class_type]) |
| ast.describe_signature(signode, 'markType', self.env, parentSymbol) |
| return [signode], [] |
| |
| |
| class CDomain(Domain): |
| """C language domain.""" |
| name = 'c' |
| label = 'C' |
| object_types = { |
| # 'identifier' is the one used for xrefs generated in signatures, not in roles |
| 'member': ObjType(_('member'), 'var', 'member', 'data', 'identifier'), |
| 'var': ObjType(_('variable'), 'var', 'member', 'data', 'identifier'), |
| 'function': ObjType(_('function'), 'func', 'identifier', 'type'), |
| 'macro': ObjType(_('macro'), 'macro', 'identifier'), |
| 'struct': ObjType(_('struct'), 'struct', 'identifier', 'type'), |
| 'union': ObjType(_('union'), 'union', 'identifier', 'type'), |
| 'enum': ObjType(_('enum'), 'enum', 'identifier', 'type'), |
| 'enumerator': ObjType(_('enumerator'), 'enumerator', 'identifier'), |
| 'type': ObjType(_('type'), 'identifier', 'type'), |
| # generated object types |
| 'functionParam': ObjType(_('function parameter'), 'identifier', 'var', 'member', 'data'), # noqa: E501 |
| } |
| |
| directives = { |
| 'member': CMemberObject, |
| 'var': CMemberObject, |
| 'function': CFunctionObject, |
| 'macro': CMacroObject, |
| 'struct': CStructObject, |
| 'union': CUnionObject, |
| 'enum': CEnumObject, |
| 'enumerator': CEnumeratorObject, |
| 'type': CTypeObject, |
| # scope control |
| 'namespace': CNamespaceObject, |
| 'namespace-push': CNamespacePushObject, |
| 'namespace-pop': CNamespacePopObject, |
| # other |
| 'alias': CAliasObject, |
| } |
| roles = { |
| 'member': CXRefRole(), |
| 'data': CXRefRole(), |
| 'var': CXRefRole(), |
| 'func': CXRefRole(fix_parens=True), |
| 'macro': CXRefRole(), |
| 'struct': CXRefRole(), |
| 'union': CXRefRole(), |
| 'enum': CXRefRole(), |
| 'enumerator': CXRefRole(), |
| 'type': CXRefRole(), |
| 'expr': CExprRole(asCode=True), |
| 'texpr': CExprRole(asCode=False), |
| } |
| initial_data: dict[str, Symbol | dict[str, tuple[str, str, str]]] = { |
| 'root_symbol': Symbol(None, None, None, None, None), |
| 'objects': {}, # fullname -> docname, node_id, objtype |
| } |
| |
| def clear_doc(self, docname: str) -> None: |
| if Symbol.debug_show_tree: |
| logger.debug("clear_doc: %s", docname) |
| logger.debug("\tbefore:") |
| logger.debug(self.data['root_symbol'].dump(1)) |
| logger.debug("\tbefore end") |
| |
| rootSymbol = self.data['root_symbol'] |
| rootSymbol.clear_doc(docname) |
| |
| if Symbol.debug_show_tree: |
| logger.debug("\tafter:") |
| logger.debug(self.data['root_symbol'].dump(1)) |
| logger.debug("\tafter end") |
| logger.debug("clear_doc end: %s", docname) |
| |
| def process_doc(self, env: BuildEnvironment, docname: str, |
| document: nodes.document) -> None: |
| if Symbol.debug_show_tree: |
| logger.debug("process_doc: %s", docname) |
| logger.debug(self.data['root_symbol'].dump(0)) |
| logger.debug("process_doc end: %s", docname) |
| |
| def process_field_xref(self, pnode: pending_xref) -> None: |
| pnode.attributes.update(self.env.ref_context) |
| |
| def merge_domaindata(self, docnames: list[str], otherdata: dict) -> None: |
| if Symbol.debug_show_tree: |
| logger.debug("merge_domaindata:") |
| logger.debug("\tself:") |
| logger.debug(self.data['root_symbol'].dump(1)) |
| logger.debug("\tself end") |
| logger.debug("\tother:") |
| logger.debug(otherdata['root_symbol'].dump(1)) |
| logger.debug("\tother end") |
| logger.debug("merge_domaindata end") |
| |
| self.data['root_symbol'].merge_with(otherdata['root_symbol'], |
| docnames, self.env) |
| ourObjects = self.data['objects'] |
| for fullname, (fn, id_, objtype) in otherdata['objects'].items(): |
| if fn in docnames: |
| if fullname not in ourObjects: |
| ourObjects[fullname] = (fn, id_, objtype) |
| # no need to warn on duplicates, the symbol merge already does that |
| |
| def _resolve_xref_inner(self, env: BuildEnvironment, fromdocname: str, builder: Builder, |
| typ: str, target: str, node: pending_xref, |
| contnode: Element) -> tuple[Element | None, str | None]: |
| parser = DefinitionParser(target, location=node, config=env.config) |
| try: |
| name = parser.parse_xref_object() |
| except DefinitionError as e: |
| logger.warning('Unparseable C cross-reference: %r\n%s', target, e, |
| location=node) |
| return None, None |
| parentKey: LookupKey = node.get("c:parent_key", None) |
| rootSymbol = self.data['root_symbol'] |
| if parentKey: |
| parentSymbol: Symbol = rootSymbol.direct_lookup(parentKey) |
| if not parentSymbol: |
| logger.debug("Target: %s", target) |
| logger.debug("ParentKey: %s", parentKey) |
| logger.debug(rootSymbol.dump(1)) |
| assert parentSymbol # should be there |
| else: |
| parentSymbol = rootSymbol |
| s = parentSymbol.find_declaration(name, typ, |
| matchSelf=True, recurseInAnon=True) |
| if s is None or s.declaration is None: |
| return None, None |
| |
| # TODO: check role type vs. object type |
| |
| declaration = s.declaration |
| displayName = name.get_display_string() |
| docname = s.docname |
| assert docname |
| |
| return make_refnode(builder, fromdocname, docname, |
| declaration.get_newest_id(), contnode, displayName, |
| ), declaration.objectType |
| |
| def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, |
| typ: str, target: str, node: pending_xref, |
| contnode: Element) -> Element | None: |
| return self._resolve_xref_inner(env, fromdocname, builder, typ, |
| target, node, contnode)[0] |
| |
| def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, |
| target: str, node: pending_xref, contnode: Element, |
| ) -> list[tuple[str, Element]]: |
| with logging.suppress_logging(): |
| retnode, objtype = self._resolve_xref_inner(env, fromdocname, builder, |
| 'any', target, node, contnode) |
| if retnode: |
| return [('c:' + self.role_for_objtype(objtype), retnode)] |
| return [] |
| |
| def get_objects(self) -> Iterator[tuple[str, str, str, str, str, int]]: |
| rootSymbol = self.data['root_symbol'] |
| for symbol in rootSymbol.get_all_symbols(): |
| if symbol.declaration is None: |
| continue |
| assert symbol.docname |
| fullNestedName = symbol.get_full_nested_name() |
| name = str(fullNestedName).lstrip('.') |
| dispname = fullNestedName.get_display_string().lstrip('.') |
| objectType = symbol.declaration.objectType |
| docname = symbol.docname |
| newestId = symbol.declaration.get_newest_id() |
| yield (name, dispname, objectType, docname, newestId, 1) |
| |
| |
| def setup(app: Sphinx) -> dict[str, Any]: |
| app.add_domain(CDomain) |
| app.add_config_value("c_id_attributes", [], 'env') |
| app.add_config_value("c_paren_attributes", [], 'env') |
| app.add_config_value("c_extra_keywords", _macroKeywords, 'env') |
| app.add_config_value("c_maximum_signature_line_length", None, 'env', types={int, None}) |
| app.add_post_transform(AliasTransform) |
| |
| return { |
| 'version': 'builtin', |
| 'env_version': 3, |
| 'parallel_read_safe': True, |
| 'parallel_write_safe': True, |
| } |