8234 lines
325 KiB
Python
8234 lines
325 KiB
Python
"""The C++ language domain."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from typing import TYPE_CHECKING, Any, Callable, TypeVar
|
|
|
|
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.errors import NoUri
|
|
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
|
|
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 desc_signature, 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')
|
|
|
|
"""
|
|
Important note on ids
|
|
----------------------------------------------------------------------------
|
|
|
|
Multiple id generation schemes are used due to backwards compatibility.
|
|
- v1: 1.2.3 <= version < 1.3
|
|
The style used before the rewrite.
|
|
It is not the actual old code, but a replication of the behaviour.
|
|
- v2: 1.3 <= version < now
|
|
Standardised mangling scheme from
|
|
https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling
|
|
though not completely implemented.
|
|
All versions are generated and attached to elements. The newest is used for
|
|
the index. All of the versions should work as permalinks.
|
|
|
|
|
|
Signature Nodes and Tagnames
|
|
----------------------------------------------------------------------------
|
|
|
|
Each signature is in a desc_signature node, where all children are
|
|
desc_signature_line nodes. Each of these lines will have the attribute
|
|
'sphinx_line_type' set to one of the following (prioritized):
|
|
- 'declarator', if the line contains the name of the declared object.
|
|
- 'templateParams', if the line starts a template parameter list,
|
|
- 'templateParams', if the line has template parameters
|
|
Note: such lines might get a new tag in the future.
|
|
- 'templateIntroduction, if the line is on the form 'conceptName{...}'
|
|
No other desc_signature nodes should exist (so far).
|
|
|
|
|
|
Grammar
|
|
----------------------------------------------------------------------------
|
|
|
|
See https://www.nongnu.org/hcb/ for the grammar,
|
|
and https://github.com/cplusplus/draft/blob/master/source/grammar.tex,
|
|
and https://github.com/cplusplus/concepts-ts
|
|
for the newest grammar.
|
|
|
|
common grammar things:
|
|
template-declaration ->
|
|
"template" "<" template-parameter-list ">" declaration
|
|
template-parameter-list ->
|
|
template-parameter
|
|
| template-parameter-list "," template-parameter
|
|
template-parameter ->
|
|
type-parameter
|
|
| parameter-declaration # i.e., same as a function argument
|
|
|
|
type-parameter ->
|
|
"class" "..."[opt] identifier[opt]
|
|
| "class" identifier[opt] "=" type-id
|
|
| "typename" "..."[opt] identifier[opt]
|
|
| "typename" identifier[opt] "=" type-id
|
|
| "template" "<" template-parameter-list ">"
|
|
"class" "..."[opt] identifier[opt]
|
|
| "template" "<" template-parameter-list ">"
|
|
"class" identifier[opt] "=" id-expression
|
|
# also, from C++17 we can have "typename" in template templates
|
|
templateDeclPrefix ->
|
|
"template" "<" template-parameter-list ">"
|
|
|
|
simple-declaration ->
|
|
attribute-specifier-seq[opt] decl-specifier-seq[opt]
|
|
init-declarator-list[opt] ;
|
|
# Make the semicolon optional.
|
|
# For now: drop the attributes (TODO).
|
|
# Use at most 1 init-declarator.
|
|
-> decl-specifier-seq init-declarator
|
|
-> decl-specifier-seq declarator initializer
|
|
|
|
decl-specifier ->
|
|
storage-class-specifier ->
|
|
( "static" (only for member_object and function_object)
|
|
| "extern" (only for member_object and function_object)
|
|
| "register"
|
|
)
|
|
thread_local[opt] (only for member_object)
|
|
(it can also appear before the others)
|
|
|
|
| type-specifier -> trailing-type-specifier
|
|
| function-specifier -> "inline" | "virtual" | "explicit" (only
|
|
for function_object)
|
|
| "friend" (only for function_object)
|
|
| "constexpr" (only for member_object and function_object)
|
|
trailing-type-specifier ->
|
|
simple-type-specifier
|
|
| elaborated-type-specifier
|
|
| typename-specifier
|
|
| cv-qualifier -> "const" | "volatile"
|
|
stricter grammar for decl-specifier-seq (with everything, each object
|
|
uses a subset):
|
|
visibility storage-class-specifier function-specifier "friend"
|
|
"constexpr" "volatile" "const" trailing-type-specifier
|
|
# where trailing-type-specifier can no be cv-qualifier
|
|
# Inside e.g., template parameters a strict subset is used
|
|
# (see type-specifier-seq)
|
|
trailing-type-specifier ->
|
|
simple-type-specifier ->
|
|
::[opt] nested-name-specifier[opt] type-name
|
|
| ::[opt] nested-name-specifier "template" simple-template-id
|
|
| "char" | "bool" | etc.
|
|
| decltype-specifier
|
|
| elaborated-type-specifier ->
|
|
class-key attribute-specifier-seq[opt] ::[opt]
|
|
nested-name-specifier[opt] identifier
|
|
| class-key ::[opt] nested-name-specifier[opt] template[opt]
|
|
simple-template-id
|
|
| "enum" ::[opt] nested-name-specifier[opt] identifier
|
|
| typename-specifier ->
|
|
"typename" ::[opt] nested-name-specifier identifier
|
|
| "typename" ::[opt] nested-name-specifier template[opt]
|
|
simple-template-id
|
|
class-key -> "class" | "struct" | "union"
|
|
type-name ->* identifier | simple-template-id
|
|
# ignoring attributes and decltype, and then some left-factoring
|
|
trailing-type-specifier ->
|
|
rest-of-trailing
|
|
("class" | "struct" | "union" | "typename") rest-of-trailing
|
|
built-in -> "char" | "bool" | etc.
|
|
decltype-specifier
|
|
rest-of-trailing -> (with some simplification)
|
|
"::"[opt] list-of-elements-separated-by-::
|
|
element ->
|
|
"template"[opt] identifier ("<" template-argument-list ">")[opt]
|
|
template-argument-list ->
|
|
template-argument "..."[opt]
|
|
| template-argument-list "," template-argument "..."[opt]
|
|
template-argument ->
|
|
constant-expression
|
|
| type-specifier-seq abstract-declarator
|
|
| id-expression
|
|
|
|
|
|
declarator ->
|
|
ptr-declarator
|
|
| noptr-declarator parameters-and-qualifiers trailing-return-type
|
|
ptr-declarator ->
|
|
noptr-declarator
|
|
| ptr-operator ptr-declarator
|
|
noptr-declarator ->
|
|
declarator-id attribute-specifier-seq[opt] ->
|
|
"..."[opt] id-expression
|
|
| rest-of-trailing
|
|
| noptr-declarator parameters-and-qualifiers
|
|
| noptr-declarator "[" constant-expression[opt] "]"
|
|
attribute-specifier-seq[opt]
|
|
| "(" ptr-declarator ")"
|
|
ptr-operator ->
|
|
"*" attribute-specifier-seq[opt] cv-qualifier-seq[opt]
|
|
| "& attribute-specifier-seq[opt]
|
|
| "&&" attribute-specifier-seq[opt]
|
|
| "::"[opt] nested-name-specifier "*" attribute-specifier-seq[opt]
|
|
cv-qualifier-seq[opt]
|
|
# function_object must use a parameters-and-qualifiers, the others may
|
|
# use it (e.g., function pointers)
|
|
parameters-and-qualifiers ->
|
|
"(" parameter-clause ")" attribute-specifier-seq[opt]
|
|
cv-qualifier-seq[opt] ref-qualifier[opt]
|
|
exception-specification[opt]
|
|
ref-qualifier -> "&" | "&&"
|
|
exception-specification ->
|
|
"noexcept" ("(" constant-expression ")")[opt]
|
|
"throw" ("(" type-id-list ")")[opt]
|
|
# TODO: we don't implement attributes
|
|
# member functions can have initializers, but we fold them into here
|
|
memberFunctionInit -> "=" "0"
|
|
# (note: only "0" is allowed as the value, according to the standard,
|
|
# right?)
|
|
|
|
enum-head ->
|
|
enum-key attribute-specifier-seq[opt] nested-name-specifier[opt]
|
|
identifier enum-base[opt]
|
|
enum-key -> "enum" | "enum struct" | "enum class"
|
|
enum-base ->
|
|
":" type
|
|
enumerator-definition ->
|
|
identifier
|
|
| identifier "=" constant-expression
|
|
|
|
We additionally add the possibility for specifying the visibility as the
|
|
first thing.
|
|
|
|
concept_object:
|
|
goal:
|
|
just a declaration of the name (for now)
|
|
|
|
grammar: only a single template parameter list, and the nested name
|
|
may not have any template argument lists
|
|
|
|
"template" "<" template-parameter-list ">"
|
|
nested-name-specifier
|
|
|
|
type_object:
|
|
goal:
|
|
either a single type (e.g., "MyClass:Something_T" or a typedef-like
|
|
thing (e.g. "Something Something_T" or "int I_arr[]"
|
|
grammar, single type: based on a type in a function parameter, but
|
|
without a name:
|
|
parameter-declaration
|
|
-> attribute-specifier-seq[opt] decl-specifier-seq
|
|
abstract-declarator[opt]
|
|
# Drop the attributes
|
|
-> decl-specifier-seq abstract-declarator[opt]
|
|
grammar, typedef-like: no initilizer
|
|
decl-specifier-seq declarator
|
|
Can start with a templateDeclPrefix.
|
|
|
|
member_object:
|
|
goal: as a type_object which must have a declarator, and optionally
|
|
with a initializer
|
|
grammar:
|
|
decl-specifier-seq declarator initializer
|
|
Can start with a templateDeclPrefix.
|
|
|
|
function_object:
|
|
goal: a function declaration, TODO: what about templates? for now: skip
|
|
grammar: no initializer
|
|
decl-specifier-seq declarator
|
|
Can start with a templateDeclPrefix.
|
|
|
|
class_object:
|
|
goal: a class declaration, but with specification of a base class
|
|
grammar:
|
|
attribute-specifier-seq[opt]
|
|
nested-name "final"[opt] (":" base-specifier-list)[opt]
|
|
base-specifier-list ->
|
|
base-specifier "..."[opt]
|
|
| base-specifier-list, base-specifier "..."[opt]
|
|
base-specifier ->
|
|
base-type-specifier
|
|
| "virtual" access-spe"cifier[opt] base-type-specifier
|
|
| access-specifier[opt] "virtual"[opt] base-type-specifier
|
|
Can start with a templateDeclPrefix.
|
|
|
|
enum_object:
|
|
goal: an unscoped enum or a scoped enum, optionally with the underlying
|
|
type specified
|
|
grammar:
|
|
("class" | "struct")[opt] visibility[opt]
|
|
attribute-specifier-seq[opt] nested-name (":" type)[opt]
|
|
enumerator_object:
|
|
goal: an element in a scoped or unscoped enum. The name should be
|
|
injected according to the scopedness.
|
|
grammar:
|
|
nested-name ("=" constant-expression)
|
|
|
|
namespace_object:
|
|
goal: a directive to put all following declarations in a specific scope
|
|
grammar:
|
|
nested-name
|
|
"""
|
|
|
|
udl_identifier_re = re.compile(r'''
|
|
[a-zA-Z_][a-zA-Z0-9_]*\b # note, no word boundary in the beginning
|
|
''', re.VERBOSE)
|
|
_string_re = re.compile(r"[LuU8]?('([^'\\]*(?:\\.[^'\\]*)*)'"
|
|
r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S)
|
|
_visibility_re = re.compile(r'\b(public|private|protected)\b')
|
|
_operator_re = re.compile(r'''
|
|
\[\s*\]
|
|
| \(\s*\)
|
|
| \+\+ | --
|
|
| ->\*? | \,
|
|
| (<<|>>)=? | && | \|\|
|
|
| <=>
|
|
| [!<>=/*%+|&^~-]=?
|
|
| (\b(and|and_eq|bitand|bitor|compl|not|not_eq|or|or_eq|xor|xor_eq)\b)
|
|
''', re.VERBOSE)
|
|
_fold_operator_re = re.compile(r'''
|
|
->\* | \.\* | \,
|
|
| (<<|>>)=? | && | \|\|
|
|
| !=
|
|
| [<>=/*%+|&^~-]=?
|
|
''', re.VERBOSE)
|
|
# see https://en.cppreference.com/w/cpp/keyword
|
|
_keywords = [
|
|
'alignas', 'alignof', 'and', 'and_eq', 'asm', 'auto', 'bitand', 'bitor',
|
|
'bool', 'break', 'case', 'catch', 'char', 'char8_t', 'char16_t', 'char32_t',
|
|
'class', 'compl', 'concept', 'const', 'consteval', 'constexpr', 'constinit',
|
|
'const_cast', 'continue',
|
|
'decltype', 'default', 'delete', 'do', 'double', 'dynamic_cast', 'else',
|
|
'enum', 'explicit', 'export', 'extern', 'false', 'float', 'for', 'friend',
|
|
'goto', 'if', 'inline', 'int', 'long', 'mutable', 'namespace', 'new',
|
|
'noexcept', 'not', 'not_eq', 'nullptr', 'operator', 'or', 'or_eq',
|
|
'private', 'protected', 'public', 'register', 'reinterpret_cast',
|
|
'requires', 'return', 'short', 'signed', 'sizeof', 'static',
|
|
'static_assert', 'static_cast', 'struct', 'switch', 'template', 'this',
|
|
'thread_local', 'throw', 'true', 'try', 'typedef', 'typeid', 'typename',
|
|
'union', 'unsigned', 'using', 'virtual', 'void', 'volatile', 'wchar_t',
|
|
'while', 'xor', 'xor_eq',
|
|
]
|
|
|
|
|
|
_simple_type_specifiers_re = re.compile(r"""
|
|
\b(
|
|
auto|void|bool
|
|
|signed|unsigned
|
|
|short|long
|
|
|char|wchar_t|char(8|16|32)_t
|
|
|int
|
|
|__int(64|128) # extension
|
|
|float|double
|
|
|__float80|_Float64x|__float128|_Float128 # extension
|
|
|_Complex|_Imaginary # extension
|
|
)\b
|
|
""", re.VERBOSE)
|
|
|
|
_max_id = 4
|
|
_id_prefix = [None, '', '_CPPv2', '_CPPv3', '_CPPv4']
|
|
# Ids are used in lookup keys which are used across pickled files,
|
|
# so when _max_id changes, make sure to update the ENV_VERSION.
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Id v1 constants
|
|
# ------------------------------------------------------------------------------
|
|
|
|
_id_fundamental_v1 = {
|
|
'char': 'c',
|
|
'signed char': 'c',
|
|
'unsigned char': 'C',
|
|
'int': 'i',
|
|
'signed int': 'i',
|
|
'unsigned int': 'U',
|
|
'long': 'l',
|
|
'signed long': 'l',
|
|
'unsigned long': 'L',
|
|
'bool': 'b',
|
|
}
|
|
_id_shorthands_v1 = {
|
|
'std::string': 'ss',
|
|
'std::ostream': 'os',
|
|
'std::istream': 'is',
|
|
'std::iostream': 'ios',
|
|
'std::vector': 'v',
|
|
'std::map': 'm',
|
|
}
|
|
_id_operator_v1 = {
|
|
'new': 'new-operator',
|
|
'new[]': 'new-array-operator',
|
|
'delete': 'delete-operator',
|
|
'delete[]': 'delete-array-operator',
|
|
# the arguments will make the difference between unary and binary
|
|
# '+(unary)' : 'ps',
|
|
# '-(unary)' : 'ng',
|
|
# '&(unary)' : 'ad',
|
|
# '*(unary)' : 'de',
|
|
'~': 'inv-operator',
|
|
'+': 'add-operator',
|
|
'-': 'sub-operator',
|
|
'*': 'mul-operator',
|
|
'/': 'div-operator',
|
|
'%': 'mod-operator',
|
|
'&': 'and-operator',
|
|
'|': 'or-operator',
|
|
'^': 'xor-operator',
|
|
'=': 'assign-operator',
|
|
'+=': 'add-assign-operator',
|
|
'-=': 'sub-assign-operator',
|
|
'*=': 'mul-assign-operator',
|
|
'/=': 'div-assign-operator',
|
|
'%=': 'mod-assign-operator',
|
|
'&=': 'and-assign-operator',
|
|
'|=': 'or-assign-operator',
|
|
'^=': 'xor-assign-operator',
|
|
'<<': 'lshift-operator',
|
|
'>>': 'rshift-operator',
|
|
'<<=': 'lshift-assign-operator',
|
|
'>>=': 'rshift-assign-operator',
|
|
'==': 'eq-operator',
|
|
'!=': 'neq-operator',
|
|
'<': 'lt-operator',
|
|
'>': 'gt-operator',
|
|
'<=': 'lte-operator',
|
|
'>=': 'gte-operator',
|
|
'!': 'not-operator',
|
|
'&&': 'sand-operator',
|
|
'||': 'sor-operator',
|
|
'++': 'inc-operator',
|
|
'--': 'dec-operator',
|
|
',': 'comma-operator',
|
|
'->*': 'pointer-by-pointer-operator',
|
|
'->': 'pointer-operator',
|
|
'()': 'call-operator',
|
|
'[]': 'subscript-operator',
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Id v > 1 constants
|
|
# ------------------------------------------------------------------------------
|
|
|
|
_id_fundamental_v2 = {
|
|
# not all of these are actually parsed as fundamental types, TODO: do that
|
|
'void': 'v',
|
|
'bool': 'b',
|
|
'char': 'c',
|
|
'signed char': 'a',
|
|
'unsigned char': 'h',
|
|
'wchar_t': 'w',
|
|
'char32_t': 'Di',
|
|
'char16_t': 'Ds',
|
|
'char8_t': 'Du',
|
|
'short': 's',
|
|
'short int': 's',
|
|
'signed short': 's',
|
|
'signed short int': 's',
|
|
'unsigned short': 't',
|
|
'unsigned short int': 't',
|
|
'int': 'i',
|
|
'signed': 'i',
|
|
'signed int': 'i',
|
|
'unsigned': 'j',
|
|
'unsigned int': 'j',
|
|
'long': 'l',
|
|
'long int': 'l',
|
|
'signed long': 'l',
|
|
'signed long int': 'l',
|
|
'unsigned long': 'm',
|
|
'unsigned long int': 'm',
|
|
'long long': 'x',
|
|
'long long int': 'x',
|
|
'signed long long': 'x',
|
|
'signed long long int': 'x',
|
|
'__int64': 'x',
|
|
'unsigned long long': 'y',
|
|
'unsigned long long int': 'y',
|
|
'__int128': 'n',
|
|
'signed __int128': 'n',
|
|
'unsigned __int128': 'o',
|
|
'float': 'f',
|
|
'double': 'd',
|
|
'long double': 'e',
|
|
'__float80': 'e', '_Float64x': 'e',
|
|
'__float128': 'g', '_Float128': 'g',
|
|
'_Complex float': 'Cf',
|
|
'_Complex double': 'Cd',
|
|
'_Complex long double': 'Ce',
|
|
'_Imaginary float': 'f',
|
|
'_Imaginary double': 'd',
|
|
'_Imaginary long double': 'e',
|
|
'auto': 'Da',
|
|
'decltype(auto)': 'Dc',
|
|
'std::nullptr_t': 'Dn',
|
|
}
|
|
_id_operator_v2 = {
|
|
'new': 'nw',
|
|
'new[]': 'na',
|
|
'delete': 'dl',
|
|
'delete[]': 'da',
|
|
# the arguments will make the difference between unary and binary
|
|
# in operator definitions
|
|
# '+(unary)' : 'ps',
|
|
# '-(unary)' : 'ng',
|
|
# '&(unary)' : 'ad',
|
|
# '*(unary)' : 'de',
|
|
'~': 'co', 'compl': 'co',
|
|
'+': 'pl',
|
|
'-': 'mi',
|
|
'*': 'ml',
|
|
'/': 'dv',
|
|
'%': 'rm',
|
|
'&': 'an', 'bitand': 'an',
|
|
'|': 'or', 'bitor': 'or',
|
|
'^': 'eo', 'xor': 'eo',
|
|
'=': 'aS',
|
|
'+=': 'pL',
|
|
'-=': 'mI',
|
|
'*=': 'mL',
|
|
'/=': 'dV',
|
|
'%=': 'rM',
|
|
'&=': 'aN', 'and_eq': 'aN',
|
|
'|=': 'oR', 'or_eq': 'oR',
|
|
'^=': 'eO', 'xor_eq': 'eO',
|
|
'<<': 'ls',
|
|
'>>': 'rs',
|
|
'<<=': 'lS',
|
|
'>>=': 'rS',
|
|
'==': 'eq',
|
|
'!=': 'ne', 'not_eq': 'ne',
|
|
'<': 'lt',
|
|
'>': 'gt',
|
|
'<=': 'le',
|
|
'>=': 'ge',
|
|
'<=>': 'ss',
|
|
'!': 'nt', 'not': 'nt',
|
|
'&&': 'aa', 'and': 'aa',
|
|
'||': 'oo', 'or': 'oo',
|
|
'++': 'pp',
|
|
'--': 'mm',
|
|
',': 'cm',
|
|
'->*': 'pm',
|
|
'->': 'pt',
|
|
'()': 'cl',
|
|
'[]': 'ix',
|
|
'.*': 'ds', # this one is not overloadable, but we need it for expressions
|
|
'?': 'qu',
|
|
}
|
|
_id_operator_unary_v2 = {
|
|
'++': 'pp_',
|
|
'--': 'mm_',
|
|
'*': 'de',
|
|
'&': 'ad',
|
|
'+': 'ps',
|
|
'-': 'ng',
|
|
'!': 'nt', 'not': 'nt',
|
|
'~': 'co', 'compl': 'co',
|
|
}
|
|
_id_char_from_prefix: dict[str | None, str] = {
|
|
None: 'c', 'u8': 'c',
|
|
'u': 'Ds', 'U': 'Di', 'L': 'w',
|
|
}
|
|
# these are ordered by preceedence
|
|
_expression_bin_ops = [
|
|
['||', 'or'],
|
|
['&&', 'and'],
|
|
['|', 'bitor'],
|
|
['^', 'xor'],
|
|
['&', 'bitand'],
|
|
['==', '!=', 'not_eq'],
|
|
['<=>', '<=', '>=', '<', '>'],
|
|
['<<', '>>'],
|
|
['+', '-'],
|
|
['*', '/', '%'],
|
|
['.*', '->*'],
|
|
]
|
|
_expression_unary_ops = ["++", "--", "*", "&", "+", "-", "!", "not", "~", "compl"]
|
|
_expression_assignment_ops = ["=", "*=", "/=", "%=", "+=", "-=",
|
|
">>=", "<<=", "&=", "and_eq", "^=", "|=", "xor_eq", "or_eq"]
|
|
_id_explicit_cast = {
|
|
'dynamic_cast': 'dc',
|
|
'static_cast': 'sc',
|
|
'const_cast': 'cc',
|
|
'reinterpret_cast': 'rc',
|
|
}
|
|
|
|
|
|
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):
|
|
pass
|
|
|
|
|
|
# Names
|
|
################################################################################
|
|
|
|
class ASTIdentifier(ASTBase):
|
|
def __init__(self, identifier: str) -> None:
|
|
assert identifier is not None
|
|
assert len(identifier) != 0
|
|
self.identifier = identifier
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
return transform(self.identifier)
|
|
|
|
def is_anon(self) -> bool:
|
|
return self.identifier[0] == '@'
|
|
|
|
def get_id(self, version: int) -> str:
|
|
if self.is_anon() and version < 3:
|
|
raise NoOldIdError
|
|
if version == 1:
|
|
if self.identifier == 'size_t':
|
|
return 's'
|
|
else:
|
|
return self.identifier
|
|
if self.identifier == "std":
|
|
return 'St'
|
|
elif self.identifier[0] == "~":
|
|
# a destructor, just use an arbitrary version of dtors
|
|
return 'D0'
|
|
else:
|
|
if self.is_anon():
|
|
return 'Ut%d_%s' % (len(self.identifier) - 1, self.identifier[1:])
|
|
else:
|
|
return str(len(self.identifier)) + self.identifier
|
|
|
|
# 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, templateArgs: str, symbol: Symbol) -> None:
|
|
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 + templateArgs
|
|
pnode = addnodes.pending_xref('', refdomain='cpp',
|
|
reftype='identifier',
|
|
reftarget=targetText, modname=None,
|
|
classname=None)
|
|
pnode['cpp: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
|
|
elif mode == 'param':
|
|
node['classes'].append('sig-param')
|
|
signode += node
|
|
elif mode == 'udl':
|
|
# the target is 'operator""id' instead of just 'id'
|
|
assert len(prefix) == 0
|
|
assert len(templateArgs) == 0
|
|
assert not self.is_anon()
|
|
targetText = 'operator""' + self.identifier
|
|
pnode = addnodes.pending_xref('', refdomain='cpp',
|
|
reftype='identifier',
|
|
reftarget=targetText, modname=None,
|
|
classname=None)
|
|
pnode['cpp:parent_key'] = symbol.get_lookup_key()
|
|
pnode += node
|
|
signode += pnode
|
|
else:
|
|
raise Exception('Unknown description mode: %s' % mode)
|
|
|
|
|
|
class ASTNestedNameElement(ASTBase):
|
|
def __init__(self, identOrOp: ASTIdentifier | ASTOperator,
|
|
templateArgs: ASTTemplateArgs) -> None:
|
|
self.identOrOp = identOrOp
|
|
self.templateArgs = templateArgs
|
|
|
|
def is_operator(self) -> bool:
|
|
return False
|
|
|
|
def get_id(self, version: int) -> str:
|
|
res = self.identOrOp.get_id(version)
|
|
if self.templateArgs:
|
|
res += self.templateArgs.get_id(version)
|
|
return res
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
res = transform(self.identOrOp)
|
|
if self.templateArgs:
|
|
res += transform(self.templateArgs)
|
|
return res
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, prefix: str, symbol: Symbol) -> None:
|
|
tArgs = str(self.templateArgs) if self.templateArgs is not None else ''
|
|
self.identOrOp.describe_signature(signode, mode, env, prefix, tArgs, symbol)
|
|
if self.templateArgs is not None:
|
|
self.templateArgs.describe_signature(signode, 'markType', env, symbol)
|
|
|
|
|
|
class ASTNestedName(ASTBase):
|
|
def __init__(self, names: list[ASTNestedNameElement],
|
|
templates: list[bool], rooted: bool) -> None:
|
|
assert len(names) > 0
|
|
self.names = names
|
|
self.templates = templates
|
|
assert len(self.names) == len(self.templates)
|
|
self.rooted = rooted
|
|
|
|
@property
|
|
def name(self) -> ASTNestedName:
|
|
return self
|
|
|
|
def num_templates(self) -> int:
|
|
count = 0
|
|
for n in self.names:
|
|
if n.is_operator():
|
|
continue
|
|
if n.templateArgs:
|
|
count += 1
|
|
return count
|
|
|
|
def get_id(self, version: int, modifiers: str = '') -> str:
|
|
if version == 1:
|
|
tt = str(self)
|
|
if tt in _id_shorthands_v1:
|
|
return _id_shorthands_v1[tt]
|
|
else:
|
|
return '::'.join(n.get_id(version) for n in self.names)
|
|
|
|
res = []
|
|
if len(self.names) > 1 or len(modifiers) > 0:
|
|
res.append('N')
|
|
res.append(modifiers)
|
|
for n in self.names:
|
|
res.append(n.get_id(version))
|
|
if len(self.names) > 1 or len(modifiers) > 0:
|
|
res.append('E')
|
|
return ''.join(res)
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
res = []
|
|
if self.rooted:
|
|
res.append('')
|
|
for i in range(len(self.names)):
|
|
n = self.names[i]
|
|
if self.templates[i]:
|
|
res.append("template " + transform(n))
|
|
else:
|
|
res.append(transform(n))
|
|
return '::'.join(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('::blah')
|
|
n = self.names[i]
|
|
if self.templates[i]:
|
|
unreachable = "Can this happen?"
|
|
raise AssertionError(unreachable) # TODO
|
|
signode += nodes.Text("template")
|
|
signode += nodes.Text(" ")
|
|
n.describe_signature(signode, mode, env, '', symbol)
|
|
elif mode == 'param':
|
|
assert not self.rooted, str(self)
|
|
assert len(self.names) == 1
|
|
assert not self.templates[0]
|
|
self.names[0].describe_signature(signode, 'param', env, '', symbol)
|
|
elif mode in ('markType', 'lastIsName', 'markName'):
|
|
# Each element should be a pending xref targeting the complete
|
|
# prefix. however, only the identifier part should be a link, such
|
|
# that template args can be a link as well.
|
|
# For 'lastIsName' we should also prepend template parameter lists.
|
|
templateParams: list[Any] = []
|
|
if mode == 'lastIsName':
|
|
assert symbol is not None
|
|
if symbol.declaration.templatePrefix is not None:
|
|
templateParams = symbol.declaration.templatePrefix.templates
|
|
iTemplateParams = 0
|
|
templateParamsPrefix = ''
|
|
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.
|
|
# 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)):
|
|
nne = names[i]
|
|
template = self.templates[i]
|
|
if not first:
|
|
dest += addnodes.desc_sig_punctuation('::', '::')
|
|
prefix += '::'
|
|
if template:
|
|
dest += addnodes.desc_sig_keyword('template', 'template')
|
|
dest += addnodes.desc_sig_space()
|
|
first = False
|
|
txt_nne = str(nne)
|
|
if txt_nne != '':
|
|
if nne.templateArgs and iTemplateParams < len(templateParams):
|
|
templateParamsPrefix += str(templateParams[iTemplateParams])
|
|
iTemplateParams += 1
|
|
nne.describe_signature(dest, 'markType',
|
|
env, templateParamsPrefix + prefix, symbol)
|
|
prefix += txt_nne
|
|
if mode == 'lastIsName':
|
|
if len(self.names) > 1:
|
|
dest += addnodes.desc_sig_punctuation('::', '::')
|
|
signode += dest
|
|
if self.templates[-1]:
|
|
signode += addnodes.desc_sig_keyword('template', 'template')
|
|
signode += addnodes.desc_sig_space()
|
|
self.names[-1].describe_signature(signode, mode, env, '', symbol)
|
|
else:
|
|
raise Exception('Unknown description mode: %s' % mode)
|
|
|
|
|
|
################################################################################
|
|
# Expressions
|
|
################################################################################
|
|
|
|
class ASTExpression(ASTBase):
|
|
def get_id(self, version: int) -> str:
|
|
raise NotImplementedError(repr(self))
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
raise NotImplementedError(repr(self))
|
|
|
|
|
|
# Primary expressions
|
|
################################################################################
|
|
|
|
class ASTLiteral(ASTExpression):
|
|
pass
|
|
|
|
|
|
class ASTPointerLiteral(ASTLiteral):
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
return 'nullptr'
|
|
|
|
def get_id(self, version: int) -> str:
|
|
return 'LDnE'
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
signode += addnodes.desc_sig_keyword('nullptr', 'nullptr')
|
|
|
|
|
|
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 get_id(self, version: int) -> str:
|
|
if self.value:
|
|
return 'L1E'
|
|
else:
|
|
return 'L0E'
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
signode += addnodes.desc_sig_keyword(str(self), str(self))
|
|
|
|
|
|
class ASTNumberLiteral(ASTLiteral):
|
|
def __init__(self, data: str) -> None:
|
|
self.data = data
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
return self.data
|
|
|
|
def get_id(self, version: int) -> str:
|
|
# TODO: floats should be mangled by writing the hex of the binary representation
|
|
return "L%sE" % self.data.replace("'", "")
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
signode += addnodes.desc_sig_literal_number(self.data, self.data)
|
|
|
|
|
|
class ASTStringLiteral(ASTLiteral):
|
|
def __init__(self, data: str) -> None:
|
|
self.data = data
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
return self.data
|
|
|
|
def get_id(self, version: int) -> str:
|
|
# note: the length is not really correct with escaping
|
|
return "LA%d_KcE" % (len(self.data) - 2)
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
signode += addnodes.desc_sig_literal_string(self.data, self.data)
|
|
|
|
|
|
class ASTCharLiteral(ASTLiteral):
|
|
def __init__(self, prefix: str, data: str) -> None:
|
|
self.prefix = prefix # may be None when no prefix
|
|
self.data = data
|
|
assert prefix in _id_char_from_prefix
|
|
self.type = _id_char_from_prefix[prefix]
|
|
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 get_id(self, version: int) -> str:
|
|
# TODO: the ID should be have L E around it
|
|
return self.type + str(self.value)
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
if self.prefix is not None:
|
|
signode += addnodes.desc_sig_keyword(self.prefix, self.prefix)
|
|
txt = "'" + self.data + "'"
|
|
signode += addnodes.desc_sig_literal_char(txt, txt)
|
|
|
|
|
|
class ASTUserDefinedLiteral(ASTLiteral):
|
|
def __init__(self, literal: ASTLiteral, ident: ASTIdentifier):
|
|
self.literal = literal
|
|
self.ident = ident
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
return transform(self.literal) + transform(self.ident)
|
|
|
|
def get_id(self, version: int) -> str:
|
|
# mangle as if it was a function call: ident(literal)
|
|
return f'clL_Zli{self.ident.get_id(version)}E{self.literal.get_id(version)}E'
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
self.literal.describe_signature(signode, mode, env, symbol)
|
|
self.ident.describe_signature(signode, "udl", env, "", "", symbol)
|
|
|
|
|
|
################################################################################
|
|
|
|
class ASTThisLiteral(ASTExpression):
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
return "this"
|
|
|
|
def get_id(self, version: int) -> str:
|
|
return "fpT"
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
signode += addnodes.desc_sig_keyword('this', 'this')
|
|
|
|
|
|
class ASTFoldExpr(ASTExpression):
|
|
def __init__(self, leftExpr: ASTExpression,
|
|
op: str, rightExpr: ASTExpression) -> None:
|
|
assert leftExpr is not None or rightExpr is not None
|
|
self.leftExpr = leftExpr
|
|
self.op = op
|
|
self.rightExpr = rightExpr
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
res = ['(']
|
|
if self.leftExpr:
|
|
res.append(transform(self.leftExpr))
|
|
res.append(' ')
|
|
res.append(self.op)
|
|
res.append(' ')
|
|
res.append('...')
|
|
if self.rightExpr:
|
|
res.append(' ')
|
|
res.append(self.op)
|
|
res.append(' ')
|
|
res.append(transform(self.rightExpr))
|
|
res.append(')')
|
|
return ''.join(res)
|
|
|
|
def get_id(self, version: int) -> str:
|
|
assert version >= 3
|
|
if version == 3:
|
|
return str(self)
|
|
# https://github.com/itanium-cxx-abi/cxx-abi/pull/67
|
|
res = []
|
|
if self.leftExpr is None: # (... op expr)
|
|
res.append('fl')
|
|
elif self.rightExpr is None: # (expr op ...)
|
|
res.append('fr')
|
|
else: # (expr op ... op expr)
|
|
# we don't check where the parameter pack is,
|
|
# we just always call this a binary left fold
|
|
res.append('fL')
|
|
res.append(_id_operator_v2[self.op])
|
|
if self.leftExpr:
|
|
res.append(self.leftExpr.get_id(version))
|
|
if self.rightExpr:
|
|
res.append(self.rightExpr.get_id(version))
|
|
return ''.join(res)
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
signode += addnodes.desc_sig_punctuation('(', '(')
|
|
if self.leftExpr:
|
|
self.leftExpr.describe_signature(signode, mode, env, symbol)
|
|
signode += addnodes.desc_sig_space()
|
|
signode += addnodes.desc_sig_operator(self.op, self.op)
|
|
signode += addnodes.desc_sig_space()
|
|
signode += addnodes.desc_sig_punctuation('...', '...')
|
|
if self.rightExpr:
|
|
signode += addnodes.desc_sig_space()
|
|
signode += addnodes.desc_sig_operator(self.op, self.op)
|
|
signode += addnodes.desc_sig_space()
|
|
self.rightExpr.describe_signature(signode, mode, env, symbol)
|
|
signode += addnodes.desc_sig_punctuation(')', ')')
|
|
|
|
|
|
class ASTParenExpr(ASTExpression):
|
|
def __init__(self, expr: ASTExpression):
|
|
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(')', ')')
|
|
|
|
|
|
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)
|
|
|
|
|
|
# Postfix expressions
|
|
################################################################################
|
|
|
|
class ASTPostfixOp(ASTBase):
|
|
def get_id(self, idPrefix: str, version: int) -> str:
|
|
raise NotImplementedError(repr(self))
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
raise NotImplementedError(repr(self))
|
|
|
|
|
|
class ASTPostfixArray(ASTPostfixOp):
|
|
def __init__(self, expr: ASTExpression):
|
|
self.expr = expr
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
return '[' + transform(self.expr) + ']'
|
|
|
|
def get_id(self, idPrefix: str, version: int) -> str:
|
|
return 'ix' + idPrefix + 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(']', ']')
|
|
|
|
|
|
class ASTPostfixMember(ASTPostfixOp):
|
|
def __init__(self, name: ASTNestedName):
|
|
self.name = name
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
return '.' + transform(self.name)
|
|
|
|
def get_id(self, idPrefix: str, version: int) -> str:
|
|
return 'dt' + idPrefix + self.name.get_id(version)
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
signode += addnodes.desc_sig_punctuation('.', '.')
|
|
self.name.describe_signature(signode, 'noneIsName', env, symbol)
|
|
|
|
|
|
class ASTPostfixMemberOfPointer(ASTPostfixOp):
|
|
def __init__(self, name: ASTNestedName):
|
|
self.name = name
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
return '->' + transform(self.name)
|
|
|
|
def get_id(self, idPrefix: str, version: int) -> str:
|
|
return 'pt' + idPrefix + self.name.get_id(version)
|
|
|
|
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 ASTPostfixInc(ASTPostfixOp):
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
return '++'
|
|
|
|
def get_id(self, idPrefix: str, version: int) -> str:
|
|
return 'pp' + idPrefix
|
|
|
|
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 get_id(self, idPrefix: str, version: int) -> str:
|
|
return 'mm' + idPrefix
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
signode += addnodes.desc_sig_operator('--', '--')
|
|
|
|
|
|
class ASTPostfixCallExpr(ASTPostfixOp):
|
|
def __init__(self, lst: ASTParenExprList | ASTBracedInitList) -> None:
|
|
self.lst = lst
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
return transform(self.lst)
|
|
|
|
def get_id(self, idPrefix: str, version: int) -> str:
|
|
res = ['cl', idPrefix]
|
|
for e in self.lst.exprs:
|
|
res.append(e.get_id(version))
|
|
res.append('E')
|
|
return ''.join(res)
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
self.lst.describe_signature(signode, mode, env, symbol)
|
|
|
|
|
|
class ASTPostfixExpr(ASTExpression):
|
|
def __init__(self, prefix: ASTType, 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 get_id(self, version: int) -> str:
|
|
id = self.prefix.get_id(version)
|
|
for p in self.postFixes:
|
|
id = p.get_id(id, version)
|
|
return id
|
|
|
|
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)
|
|
|
|
|
|
class ASTExplicitCast(ASTExpression):
|
|
def __init__(self, cast: str, typ: ASTType, expr: ASTExpression):
|
|
assert cast in _id_explicit_cast
|
|
self.cast = cast
|
|
self.typ = typ
|
|
self.expr = expr
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
res = [self.cast]
|
|
res.append('<')
|
|
res.append(transform(self.typ))
|
|
res.append('>(')
|
|
res.append(transform(self.expr))
|
|
res.append(')')
|
|
return ''.join(res)
|
|
|
|
def get_id(self, version: int) -> str:
|
|
return (_id_explicit_cast[self.cast] +
|
|
self.typ.get_id(version) +
|
|
self.expr.get_id(version))
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
signode += addnodes.desc_sig_keyword(self.cast, self.cast)
|
|
signode += addnodes.desc_sig_punctuation('<', '<')
|
|
self.typ.describe_signature(signode, mode, env, symbol)
|
|
signode += addnodes.desc_sig_punctuation('>', '>')
|
|
signode += addnodes.desc_sig_punctuation('(', '(')
|
|
self.expr.describe_signature(signode, mode, env, symbol)
|
|
signode += addnodes.desc_sig_punctuation(')', ')')
|
|
|
|
|
|
class ASTTypeId(ASTExpression):
|
|
def __init__(self, typeOrExpr: ASTType | ASTExpression, isType: bool):
|
|
self.typeOrExpr = typeOrExpr
|
|
self.isType = isType
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
return 'typeid(' + transform(self.typeOrExpr) + ')'
|
|
|
|
def get_id(self, version: int) -> str:
|
|
prefix = 'ti' if self.isType else 'te'
|
|
return prefix + self.typeOrExpr.get_id(version)
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
signode += addnodes.desc_sig_keyword('typeid', 'typeid')
|
|
signode += addnodes.desc_sig_punctuation('(', '(')
|
|
self.typeOrExpr.describe_signature(signode, mode, env, symbol)
|
|
signode += addnodes.desc_sig_punctuation(')', ')')
|
|
|
|
|
|
# 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 get_id(self, version: int) -> str:
|
|
return _id_operator_unary_v2[self.op] + self.expr.get_id(version)
|
|
|
|
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 ASTSizeofParamPack(ASTExpression):
|
|
def __init__(self, identifier: ASTIdentifier):
|
|
self.identifier = identifier
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
return "sizeof...(" + transform(self.identifier) + ")"
|
|
|
|
def get_id(self, version: int) -> str:
|
|
return 'sZ' + self.identifier.get_id(version)
|
|
|
|
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('...', '...')
|
|
signode += addnodes.desc_sig_punctuation('(', '(')
|
|
self.identifier.describe_signature(signode, 'markType', env,
|
|
symbol=symbol, prefix="", templateArgs="")
|
|
signode += addnodes.desc_sig_punctuation(')', ')')
|
|
|
|
|
|
class ASTSizeofType(ASTExpression):
|
|
def __init__(self, typ: ASTType):
|
|
self.typ = typ
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
return "sizeof(" + transform(self.typ) + ")"
|
|
|
|
def get_id(self, version: int) -> str:
|
|
return 'st' + self.typ.get_id(version)
|
|
|
|
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 get_id(self, version: int) -> str:
|
|
return 'sz' + self.expr.get_id(version)
|
|
|
|
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 get_id(self, version: int) -> str:
|
|
return 'at' + self.typ.get_id(version)
|
|
|
|
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(')', ')')
|
|
|
|
|
|
class ASTNoexceptExpr(ASTExpression):
|
|
def __init__(self, expr: ASTExpression):
|
|
self.expr = expr
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
return 'noexcept(' + transform(self.expr) + ')'
|
|
|
|
def get_id(self, version: int) -> str:
|
|
return 'nx' + self.expr.get_id(version)
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
signode += addnodes.desc_sig_keyword('noexcept', 'noexcept')
|
|
signode += addnodes.desc_sig_punctuation('(', '(')
|
|
self.expr.describe_signature(signode, mode, env, symbol)
|
|
signode += addnodes.desc_sig_punctuation(')', ')')
|
|
|
|
|
|
class ASTNewExpr(ASTExpression):
|
|
def __init__(self, rooted: bool, isNewTypeId: bool, typ: ASTType,
|
|
initList: ASTParenExprList | ASTBracedInitList) -> None:
|
|
self.rooted = rooted
|
|
self.isNewTypeId = isNewTypeId
|
|
self.typ = typ
|
|
self.initList = initList
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
res = []
|
|
if self.rooted:
|
|
res.append('::')
|
|
res.append('new ')
|
|
# TODO: placement
|
|
if self.isNewTypeId:
|
|
res.append(transform(self.typ))
|
|
else:
|
|
raise AssertionError
|
|
if self.initList is not None:
|
|
res.append(transform(self.initList))
|
|
return ''.join(res)
|
|
|
|
def get_id(self, version: int) -> str:
|
|
# the array part will be in the type mangling, so na is not used
|
|
res = ['nw']
|
|
# TODO: placement
|
|
res.append('_')
|
|
res.append(self.typ.get_id(version))
|
|
if self.initList is not None:
|
|
res.append(self.initList.get_id(version))
|
|
else:
|
|
res.append('E')
|
|
return ''.join(res)
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
if self.rooted:
|
|
signode += addnodes.desc_sig_punctuation('::', '::')
|
|
signode += addnodes.desc_sig_keyword('new', 'new')
|
|
signode += addnodes.desc_sig_space()
|
|
# TODO: placement
|
|
if self.isNewTypeId:
|
|
self.typ.describe_signature(signode, mode, env, symbol)
|
|
else:
|
|
raise AssertionError
|
|
if self.initList is not None:
|
|
self.initList.describe_signature(signode, mode, env, symbol)
|
|
|
|
|
|
class ASTDeleteExpr(ASTExpression):
|
|
def __init__(self, rooted: bool, array: bool, expr: ASTExpression):
|
|
self.rooted = rooted
|
|
self.array = array
|
|
self.expr = expr
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
res = []
|
|
if self.rooted:
|
|
res.append('::')
|
|
res.append('delete ')
|
|
if self.array:
|
|
res.append('[] ')
|
|
res.append(transform(self.expr))
|
|
return ''.join(res)
|
|
|
|
def get_id(self, version: int) -> str:
|
|
if self.array:
|
|
id = "da"
|
|
else:
|
|
id = "dl"
|
|
return id + self.expr.get_id(version)
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
if self.rooted:
|
|
signode += addnodes.desc_sig_punctuation('::', '::')
|
|
signode += addnodes.desc_sig_keyword('delete', 'delete')
|
|
signode += addnodes.desc_sig_space()
|
|
if self.array:
|
|
signode += addnodes.desc_sig_punctuation('[]', '[]')
|
|
signode += addnodes.desc_sig_space()
|
|
self.expr.describe_signature(signode, mode, env, symbol)
|
|
|
|
|
|
# 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 get_id(self, version: int) -> str:
|
|
return 'cv' + self.typ.get_id(version) + self.expr.get_id(version)
|
|
|
|
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(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 get_id(self, version: int) -> str:
|
|
assert version >= 2
|
|
res = []
|
|
for i in range(len(self.ops)):
|
|
res.append(_id_operator_v2[self.ops[i]])
|
|
res.append(self.exprs[i].get_id(version))
|
|
res.append(self.exprs[-1].get_id(version))
|
|
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 ASTConditionalExpr(ASTExpression):
|
|
def __init__(self, ifExpr: ASTExpression, thenExpr: ASTExpression,
|
|
elseExpr: ASTExpression):
|
|
self.ifExpr = ifExpr
|
|
self.thenExpr = thenExpr
|
|
self.elseExpr = elseExpr
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
res = []
|
|
res.append(transform(self.ifExpr))
|
|
res.append(' ? ')
|
|
res.append(transform(self.thenExpr))
|
|
res.append(' : ')
|
|
res.append(transform(self.elseExpr))
|
|
return ''.join(res)
|
|
|
|
def get_id(self, version: int) -> str:
|
|
assert version >= 2
|
|
res = []
|
|
res.append(_id_operator_v2['?'])
|
|
res.append(self.ifExpr.get_id(version))
|
|
res.append(self.thenExpr.get_id(version))
|
|
res.append(self.elseExpr.get_id(version))
|
|
return ''.join(res)
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
self.ifExpr.describe_signature(signode, mode, env, symbol)
|
|
signode += addnodes.desc_sig_space()
|
|
signode += addnodes.desc_sig_operator('?', '?')
|
|
signode += addnodes.desc_sig_space()
|
|
self.thenExpr.describe_signature(signode, mode, env, symbol)
|
|
signode += addnodes.desc_sig_space()
|
|
signode += addnodes.desc_sig_operator(':', ':')
|
|
signode += addnodes.desc_sig_space()
|
|
self.elseExpr.describe_signature(signode, mode, env, symbol)
|
|
|
|
|
|
class ASTBracedInitList(ASTBase):
|
|
def __init__(self, exprs: list[ASTExpression | ASTBracedInitList],
|
|
trailingComma: bool) -> None:
|
|
self.exprs = exprs
|
|
self.trailingComma = trailingComma
|
|
|
|
def get_id(self, version: int) -> str:
|
|
return "il%sE" % ''.join(e.get_id(version) for e in self.exprs)
|
|
|
|
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 ASTAssignmentExpr(ASTExpression):
|
|
def __init__(self, leftExpr: ASTExpression, op: str,
|
|
rightExpr: ASTExpression | ASTBracedInitList):
|
|
self.leftExpr = leftExpr
|
|
self.op = op
|
|
self.rightExpr = rightExpr
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
res = []
|
|
res.append(transform(self.leftExpr))
|
|
res.append(' ')
|
|
res.append(self.op)
|
|
res.append(' ')
|
|
res.append(transform(self.rightExpr))
|
|
return ''.join(res)
|
|
|
|
def get_id(self, version: int) -> str:
|
|
# we end up generating the ID from left to right, instead of right to left
|
|
res = []
|
|
res.append(_id_operator_v2[self.op])
|
|
res.append(self.leftExpr.get_id(version))
|
|
res.append(self.rightExpr.get_id(version))
|
|
return ''.join(res)
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
self.leftExpr.describe_signature(signode, mode, env, symbol)
|
|
signode += addnodes.desc_sig_space()
|
|
if ord(self.op[0]) >= ord('a') and ord(self.op[0]) <= ord('z'):
|
|
signode += addnodes.desc_sig_keyword(self.op, self.op)
|
|
else:
|
|
signode += addnodes.desc_sig_operator(self.op, self.op)
|
|
signode += addnodes.desc_sig_space()
|
|
self.rightExpr.describe_signature(signode, mode, env, symbol)
|
|
|
|
|
|
class ASTCommaExpr(ASTExpression):
|
|
def __init__(self, exprs: list[ASTExpression]):
|
|
assert len(exprs) > 0
|
|
self.exprs = exprs
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
return ', '.join(transform(e) for e in self.exprs)
|
|
|
|
def get_id(self, version: int) -> str:
|
|
id_ = _id_operator_v2[',']
|
|
res = []
|
|
for i in range(len(self.exprs) - 1):
|
|
res.append(id_)
|
|
res.append(self.exprs[i].get_id(version))
|
|
res.append(self.exprs[-1].get_id(version))
|
|
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_punctuation(',', ',')
|
|
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
|
|
################################################################################
|
|
|
|
# Things for ASTNestedName
|
|
################################################################################
|
|
|
|
class ASTOperator(ASTBase):
|
|
def is_anon(self) -> bool:
|
|
return False
|
|
|
|
def is_operator(self) -> bool:
|
|
return True
|
|
|
|
def get_id(self, version: int) -> str:
|
|
raise NotImplementedError
|
|
|
|
def _describe_identifier(self, signode: TextElement, identnode: TextElement,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
"""Render the prefix into signode, and the last part into identnode."""
|
|
raise NotImplementedError
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, prefix: str, templateArgs: str,
|
|
symbol: Symbol) -> None:
|
|
verify_description_mode(mode)
|
|
if mode == 'lastIsName':
|
|
mainName = addnodes.desc_name()
|
|
self._describe_identifier(mainName, mainName, env, symbol)
|
|
signode += mainName
|
|
elif mode == 'markType':
|
|
targetText = prefix + str(self) + templateArgs
|
|
pnode = addnodes.pending_xref('', refdomain='cpp',
|
|
reftype='identifier',
|
|
reftarget=targetText, modname=None,
|
|
classname=None)
|
|
pnode['cpp:parent_key'] = symbol.get_lookup_key()
|
|
# Render the identifier part, but collapse it into a string
|
|
# and make that the a link to this operator.
|
|
# E.g., if it is 'operator SomeType', then 'SomeType' becomes
|
|
# a link to the operator, not to 'SomeType'.
|
|
container = nodes.literal()
|
|
self._describe_identifier(signode, container, env, symbol)
|
|
txt = container.astext()
|
|
pnode += addnodes.desc_name(txt, txt)
|
|
signode += pnode
|
|
else:
|
|
addName = addnodes.desc_addname()
|
|
self._describe_identifier(addName, addName, env, symbol)
|
|
signode += addName
|
|
|
|
|
|
class ASTOperatorBuildIn(ASTOperator):
|
|
def __init__(self, op: str) -> None:
|
|
self.op = op
|
|
|
|
def get_id(self, version: int) -> str:
|
|
if version == 1:
|
|
ids = _id_operator_v1
|
|
if self.op not in ids:
|
|
raise NoOldIdError
|
|
else:
|
|
ids = _id_operator_v2
|
|
if self.op not in ids:
|
|
raise Exception('Internal error: Built-in operator "%s" can not '
|
|
'be mapped to an id.' % self.op)
|
|
return ids[self.op]
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
if self.op in ('new', 'new[]', 'delete', 'delete[]') or self.op[0] in "abcnox":
|
|
return 'operator ' + self.op
|
|
else:
|
|
return 'operator' + self.op
|
|
|
|
def _describe_identifier(self, signode: TextElement, identnode: TextElement,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
signode += addnodes.desc_sig_keyword('operator', 'operator')
|
|
if self.op in ('new', 'new[]', 'delete', 'delete[]') or self.op[0] in "abcnox":
|
|
signode += addnodes.desc_sig_space()
|
|
identnode += addnodes.desc_sig_operator(self.op, self.op)
|
|
|
|
|
|
class ASTOperatorLiteral(ASTOperator):
|
|
def __init__(self, identifier: ASTIdentifier) -> None:
|
|
self.identifier = identifier
|
|
|
|
def get_id(self, version: int) -> str:
|
|
if version == 1:
|
|
raise NoOldIdError
|
|
return 'li' + self.identifier.get_id(version)
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
return 'operator""' + transform(self.identifier)
|
|
|
|
def _describe_identifier(self, signode: TextElement, identnode: TextElement,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
signode += addnodes.desc_sig_keyword('operator', 'operator')
|
|
signode += addnodes.desc_sig_literal_string('""', '""')
|
|
self.identifier.describe_signature(identnode, 'markType', env, '', '', symbol)
|
|
|
|
|
|
class ASTOperatorType(ASTOperator):
|
|
def __init__(self, type: ASTType) -> None:
|
|
self.type = type
|
|
|
|
def get_id(self, version: int) -> str:
|
|
if version == 1:
|
|
return 'castto-%s-operator' % self.type.get_id(version)
|
|
else:
|
|
return 'cv' + self.type.get_id(version)
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
return ''.join(['operator ', transform(self.type)])
|
|
|
|
def get_name_no_template(self) -> str:
|
|
return str(self)
|
|
|
|
def _describe_identifier(self, signode: TextElement, identnode: TextElement,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
signode += addnodes.desc_sig_keyword('operator', 'operator')
|
|
signode += addnodes.desc_sig_space()
|
|
self.type.describe_signature(identnode, 'markType', env, symbol)
|
|
|
|
|
|
class ASTTemplateArgConstant(ASTBase):
|
|
def __init__(self, value: ASTExpression) -> None:
|
|
self.value = value
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
return transform(self.value)
|
|
|
|
def get_id(self, version: int) -> str:
|
|
if version == 1:
|
|
return str(self).replace(' ', '-')
|
|
if version == 2:
|
|
return 'X' + str(self) + 'E'
|
|
return 'X' + self.value.get_id(version) + 'E'
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
verify_description_mode(mode)
|
|
self.value.describe_signature(signode, mode, env, symbol)
|
|
|
|
|
|
class ASTTemplateArgs(ASTBase):
|
|
def __init__(self, args: list[ASTType | ASTTemplateArgConstant],
|
|
packExpansion: bool) -> None:
|
|
assert args is not None
|
|
self.args = args
|
|
self.packExpansion = packExpansion
|
|
|
|
def get_id(self, version: int) -> str:
|
|
if version == 1:
|
|
res = []
|
|
res.append(':')
|
|
res.append('.'.join(a.get_id(version) for a in self.args))
|
|
res.append(':')
|
|
return ''.join(res)
|
|
|
|
res = []
|
|
res.append('I')
|
|
if len(self.args) > 0:
|
|
for a in self.args[:-1]:
|
|
res.append(a.get_id(version))
|
|
if self.packExpansion:
|
|
res.append('J')
|
|
res.append(self.args[-1].get_id(version))
|
|
if self.packExpansion:
|
|
res.append('E')
|
|
res.append('E')
|
|
return ''.join(res)
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
res = ', '.join(transform(a) for a in self.args)
|
|
if self.packExpansion:
|
|
res += '...'
|
|
return '<' + res + '>'
|
|
|
|
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 a in self.args:
|
|
if not first:
|
|
signode += addnodes.desc_sig_punctuation(',', ',')
|
|
signode += addnodes.desc_sig_space()
|
|
first = False
|
|
a.describe_signature(signode, 'markType', env, symbol=symbol)
|
|
if self.packExpansion:
|
|
signode += addnodes.desc_sig_punctuation('...', '...')
|
|
signode += addnodes.desc_sig_punctuation('>', '>')
|
|
|
|
|
|
# Main part of declarations
|
|
################################################################################
|
|
|
|
class ASTTrailingTypeSpec(ASTBase):
|
|
def get_id(self, version: int) -> str:
|
|
raise NotImplementedError(repr(self))
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
raise NotImplementedError(repr(self))
|
|
|
|
|
|
class ASTTrailingTypeSpecFundamental(ASTTrailingTypeSpec):
|
|
def __init__(self, names: list[str], canonNames: list[str]) -> None:
|
|
assert len(names) != 0
|
|
assert len(names) == len(canonNames), (names, canonNames)
|
|
self.names = names
|
|
# the canonical name list is for ID lookup
|
|
self.canonNames = canonNames
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
return ' '.join(self.names)
|
|
|
|
def get_id(self, version: int) -> str:
|
|
if version == 1:
|
|
res = []
|
|
for a in self.canonNames:
|
|
if a in _id_fundamental_v1:
|
|
res.append(_id_fundamental_v1[a])
|
|
else:
|
|
res.append(a)
|
|
return '-'.join(res)
|
|
|
|
txt = ' '.join(self.canonNames)
|
|
if txt not in _id_fundamental_v2:
|
|
raise Exception(
|
|
'Semi-internal error: Fundamental type "%s" can not be mapped '
|
|
'to an ID. Is it a true fundamental type? If not so, the '
|
|
'parser should have rejected it.' % txt)
|
|
return _id_fundamental_v2[txt]
|
|
|
|
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 ASTTrailingTypeSpecDecltypeAuto(ASTTrailingTypeSpec):
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
return 'decltype(auto)'
|
|
|
|
def get_id(self, version: int) -> str:
|
|
if version == 1:
|
|
raise NoOldIdError
|
|
return 'Dc'
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
signode += addnodes.desc_sig_keyword('decltype', 'decltype')
|
|
signode += addnodes.desc_sig_punctuation('(', '(')
|
|
signode += addnodes.desc_sig_keyword('auto', 'auto')
|
|
signode += addnodes.desc_sig_punctuation(')', ')')
|
|
|
|
|
|
class ASTTrailingTypeSpecDecltype(ASTTrailingTypeSpec):
|
|
def __init__(self, expr: ASTExpression):
|
|
self.expr = expr
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
return 'decltype(' + transform(self.expr) + ')'
|
|
|
|
def get_id(self, version: int) -> str:
|
|
if version == 1:
|
|
raise NoOldIdError
|
|
return 'DT' + self.expr.get_id(version) + "E"
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
signode += addnodes.desc_sig_keyword('decltype', 'decltype')
|
|
signode += addnodes.desc_sig_punctuation('(', '(')
|
|
self.expr.describe_signature(signode, mode, env, symbol)
|
|
signode += addnodes.desc_sig_punctuation(')', ')')
|
|
|
|
|
|
class ASTTrailingTypeSpecName(ASTTrailingTypeSpec):
|
|
def __init__(self, prefix: str, nestedName: ASTNestedName,
|
|
placeholderType: str | None) -> None:
|
|
self.prefix = prefix
|
|
self.nestedName = nestedName
|
|
self.placeholderType = placeholderType
|
|
|
|
@property
|
|
def name(self) -> ASTNestedName:
|
|
return self.nestedName
|
|
|
|
def get_id(self, version: int) -> str:
|
|
return self.nestedName.get_id(version)
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
res = []
|
|
if self.prefix:
|
|
res.append(self.prefix)
|
|
res.append(' ')
|
|
res.append(transform(self.nestedName))
|
|
if self.placeholderType is not None:
|
|
res.append(' ')
|
|
res.append(self.placeholderType)
|
|
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)
|
|
if self.placeholderType is not None:
|
|
signode += addnodes.desc_sig_space()
|
|
if self.placeholderType == 'auto':
|
|
signode += addnodes.desc_sig_keyword('auto', 'auto')
|
|
elif self.placeholderType == 'decltype(auto)':
|
|
signode += addnodes.desc_sig_keyword('decltype', 'decltype')
|
|
signode += addnodes.desc_sig_punctuation('(', '(')
|
|
signode += addnodes.desc_sig_keyword('auto', 'auto')
|
|
signode += addnodes.desc_sig_punctuation(')', ')')
|
|
else:
|
|
raise AssertionError(self.placeholderType)
|
|
|
|
|
|
class ASTFunctionParameter(ASTBase):
|
|
def __init__(self, arg: ASTTypeWithInit | ASTTemplateParamConstrainedTypeWithInit,
|
|
ellipsis: bool = False) -> None:
|
|
self.arg = arg
|
|
self.ellipsis = ellipsis
|
|
|
|
def get_id(
|
|
self, version: int, objectType: str | None = None, symbol: Symbol | None = None,
|
|
) -> str:
|
|
# this is not part of the normal name mangling in C++
|
|
if symbol:
|
|
# the anchor will be our parent
|
|
return symbol.parent.declaration.get_id(version, prefixed=False)
|
|
# else, do the usual
|
|
if self.ellipsis:
|
|
return 'z'
|
|
else:
|
|
return self.arg.get_id(version)
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
if self.ellipsis:
|
|
return '...'
|
|
else:
|
|
return transform(self.arg)
|
|
|
|
def describe_signature(self, signode: TextElement, 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 ASTNoexceptSpec(ASTBase):
|
|
def __init__(self, expr: ASTExpression | None):
|
|
self.expr = expr
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
if self.expr:
|
|
return 'noexcept(' + transform(self.expr) + ')'
|
|
return 'noexcept'
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
signode += addnodes.desc_sig_keyword('noexcept', 'noexcept')
|
|
if self.expr:
|
|
signode += addnodes.desc_sig_punctuation('(', '(')
|
|
self.expr.describe_signature(signode, 'markType', env, symbol)
|
|
signode += addnodes.desc_sig_punctuation(')', ')')
|
|
|
|
|
|
class ASTParametersQualifiers(ASTBase):
|
|
def __init__(self, args: list[ASTFunctionParameter], volatile: bool, const: bool,
|
|
refQual: str | None, exceptionSpec: ASTNoexceptSpec,
|
|
trailingReturn: ASTType,
|
|
override: bool, final: bool, attrs: ASTAttributeList,
|
|
initializer: str | None) -> None:
|
|
self.args = args
|
|
self.volatile = volatile
|
|
self.const = const
|
|
self.refQual = refQual
|
|
self.exceptionSpec = exceptionSpec
|
|
self.trailingReturn = trailingReturn
|
|
self.override = override
|
|
self.final = final
|
|
self.attrs = attrs
|
|
self.initializer = initializer
|
|
|
|
@property
|
|
def function_params(self) -> list[ASTFunctionParameter]:
|
|
return self.args
|
|
|
|
def get_modifiers_id(self, version: int) -> str:
|
|
res = []
|
|
if self.volatile:
|
|
res.append('V')
|
|
if self.const:
|
|
if version == 1:
|
|
res.append('C')
|
|
else:
|
|
res.append('K')
|
|
if self.refQual == '&&':
|
|
res.append('O')
|
|
elif self.refQual == '&':
|
|
res.append('R')
|
|
return ''.join(res)
|
|
|
|
def get_param_id(self, version: int) -> str:
|
|
if version == 1:
|
|
if len(self.args) == 0:
|
|
return ''
|
|
else:
|
|
return '__' + '.'.join(a.get_id(version) for a in self.args)
|
|
if len(self.args) == 0:
|
|
return 'v'
|
|
else:
|
|
return ''.join(a.get_id(version) for a in 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 self.volatile:
|
|
res.append(' volatile')
|
|
if self.const:
|
|
res.append(' const')
|
|
if self.refQual:
|
|
res.append(' ')
|
|
res.append(self.refQual)
|
|
if self.exceptionSpec:
|
|
res.append(' ')
|
|
res.append(transform(self.exceptionSpec))
|
|
if self.trailingReturn:
|
|
res.append(' -> ')
|
|
res.append(transform(self.trailingReturn))
|
|
if self.final:
|
|
res.append(' final')
|
|
if self.override:
|
|
res.append(' override')
|
|
if len(self.attrs) != 0:
|
|
res.append(' ')
|
|
res.append(transform(self.attrs))
|
|
if self.initializer:
|
|
res.append(' = ')
|
|
res.append(self.initializer)
|
|
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(')', ')')
|
|
|
|
def _add_anno(signode: TextElement, text: str) -> None:
|
|
signode += addnodes.desc_sig_space()
|
|
signode += addnodes.desc_sig_keyword(text, text)
|
|
|
|
if self.volatile:
|
|
_add_anno(signode, 'volatile')
|
|
if self.const:
|
|
_add_anno(signode, 'const')
|
|
if self.refQual:
|
|
signode += addnodes.desc_sig_space()
|
|
signode += addnodes.desc_sig_punctuation(self.refQual, self.refQual)
|
|
if self.exceptionSpec:
|
|
signode += addnodes.desc_sig_space()
|
|
self.exceptionSpec.describe_signature(signode, mode, env, symbol)
|
|
if self.trailingReturn:
|
|
signode += addnodes.desc_sig_space()
|
|
signode += addnodes.desc_sig_operator('->', '->')
|
|
signode += addnodes.desc_sig_space()
|
|
self.trailingReturn.describe_signature(signode, mode, env, symbol)
|
|
if self.final:
|
|
_add_anno(signode, 'final')
|
|
if self.override:
|
|
_add_anno(signode, 'override')
|
|
if len(self.attrs) != 0:
|
|
signode += addnodes.desc_sig_space()
|
|
self.attrs.describe_signature(signode)
|
|
if self.initializer:
|
|
signode += addnodes.desc_sig_space()
|
|
signode += addnodes.desc_sig_punctuation('=', '=')
|
|
signode += addnodes.desc_sig_space()
|
|
assert self.initializer in ('0', 'delete', 'default')
|
|
if self.initializer == '0':
|
|
signode += addnodes.desc_sig_literal_number('0', '0')
|
|
else:
|
|
signode += addnodes.desc_sig_keyword(self.initializer, self.initializer)
|
|
|
|
|
|
class ASTExplicitSpec(ASTBase):
|
|
def __init__(self, expr: ASTExpression | None) -> None:
|
|
self.expr = expr
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
res = ['explicit']
|
|
if self.expr is not None:
|
|
res.append('(')
|
|
res.append(transform(self.expr))
|
|
res.append(')')
|
|
return ''.join(res)
|
|
|
|
def describe_signature(self, signode: TextElement,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
signode += addnodes.desc_sig_keyword('explicit', 'explicit')
|
|
if self.expr is not None:
|
|
signode += addnodes.desc_sig_punctuation('(', '(')
|
|
self.expr.describe_signature(signode, 'markType', env, symbol)
|
|
signode += addnodes.desc_sig_punctuation(')', ')')
|
|
|
|
|
|
class ASTDeclSpecsSimple(ASTBase):
|
|
def __init__(self, storage: str, threadLocal: bool, inline: bool, virtual: bool,
|
|
explicitSpec: ASTExplicitSpec | None,
|
|
consteval: bool, constexpr: bool, constinit: bool,
|
|
volatile: bool, const: bool, friend: bool,
|
|
attrs: ASTAttributeList) -> None:
|
|
self.storage = storage
|
|
self.threadLocal = threadLocal
|
|
self.inline = inline
|
|
self.virtual = virtual
|
|
self.explicitSpec = explicitSpec
|
|
self.consteval = consteval
|
|
self.constexpr = constexpr
|
|
self.constinit = constinit
|
|
self.volatile = volatile
|
|
self.const = const
|
|
self.friend = friend
|
|
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.virtual or other.virtual,
|
|
self.explicitSpec or other.explicitSpec,
|
|
self.consteval or other.consteval,
|
|
self.constexpr or other.constexpr,
|
|
self.constinit or other.constinit,
|
|
self.volatile or other.volatile,
|
|
self.const or other.const,
|
|
self.friend or other.friend,
|
|
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('thread_local')
|
|
if self.inline:
|
|
res.append('inline')
|
|
if self.friend:
|
|
res.append('friend')
|
|
if self.virtual:
|
|
res.append('virtual')
|
|
if self.explicitSpec:
|
|
res.append(transform(self.explicitSpec))
|
|
if self.consteval:
|
|
res.append('consteval')
|
|
if self.constexpr:
|
|
res.append('constexpr')
|
|
if self.constinit:
|
|
res.append('constinit')
|
|
if self.volatile:
|
|
res.append('volatile')
|
|
if self.const:
|
|
res.append('const')
|
|
return ' '.join(res)
|
|
|
|
def describe_signature(self, signode: TextElement,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
self.attrs.describe_signature(signode)
|
|
addSpace = len(self.attrs) != 0
|
|
|
|
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.storage:
|
|
addSpace = _add(signode, self.storage)
|
|
if self.threadLocal:
|
|
addSpace = _add(signode, 'thread_local')
|
|
if self.inline:
|
|
addSpace = _add(signode, 'inline')
|
|
if self.friend:
|
|
addSpace = _add(signode, 'friend')
|
|
if self.virtual:
|
|
addSpace = _add(signode, 'virtual')
|
|
if self.explicitSpec:
|
|
if addSpace:
|
|
signode += addnodes.desc_sig_space()
|
|
self.explicitSpec.describe_signature(signode, env, symbol)
|
|
addSpace = True
|
|
if self.consteval:
|
|
addSpace = _add(signode, 'consteval')
|
|
if self.constexpr:
|
|
addSpace = _add(signode, 'constexpr')
|
|
if self.constinit:
|
|
addSpace = _add(signode, 'constinit')
|
|
if self.volatile:
|
|
addSpace = _add(signode, 'volatile')
|
|
if self.const:
|
|
addSpace = _add(signode, '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
|
|
self.outer = outer
|
|
self.leftSpecs = leftSpecs
|
|
self.rightSpecs = rightSpecs
|
|
self.allSpecs = self.leftSpecs.mergeWith(self.rightSpecs)
|
|
self.trailingTypeSpec = trailing
|
|
|
|
def get_id(self, version: int) -> str:
|
|
if version == 1:
|
|
res = []
|
|
res.append(self.trailingTypeSpec.get_id(version))
|
|
if self.allSpecs.volatile:
|
|
res.append('V')
|
|
if self.allSpecs.const:
|
|
res.append('C')
|
|
return ''.join(res)
|
|
res = []
|
|
if self.allSpecs.volatile:
|
|
res.append('V')
|
|
if self.allSpecs.const:
|
|
res.append('K')
|
|
if self.trailingTypeSpec is not None:
|
|
res.append(self.trailingTypeSpec.get_id(version))
|
|
return ''.join(res)
|
|
|
|
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)
|
|
numChildren = len(signode)
|
|
self.leftSpecs.describe_signature(signode, env, symbol)
|
|
addSpace = len(signode) != numChildren
|
|
|
|
if self.trailingTypeSpec:
|
|
if addSpace:
|
|
signode += addnodes.desc_sig_space()
|
|
numChildren = len(signode)
|
|
self.trailingTypeSpec.describe_signature(signode, mode, env,
|
|
symbol=symbol)
|
|
addSpace = len(signode) != numChildren
|
|
|
|
if len(str(self.rightSpecs)) > 0:
|
|
if addSpace:
|
|
signode += addnodes.desc_sig_space()
|
|
self.rightSpecs.describe_signature(signode, env, symbol)
|
|
|
|
|
|
# Declarator
|
|
################################################################################
|
|
|
|
class ASTArray(ASTBase):
|
|
def __init__(self, size: ASTExpression):
|
|
self.size = size
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
if self.size:
|
|
return '[' + transform(self.size) + ']'
|
|
else:
|
|
return '[]'
|
|
|
|
def get_id(self, version: int) -> str:
|
|
if version == 1:
|
|
return 'A'
|
|
if version == 2:
|
|
if self.size:
|
|
return 'A' + str(self.size) + '_'
|
|
else:
|
|
return 'A_'
|
|
if self.size:
|
|
return 'A' + self.size.get_id(version) + '_'
|
|
else:
|
|
return 'A_'
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
verify_description_mode(mode)
|
|
signode += addnodes.desc_sig_punctuation('[', '[')
|
|
if self.size:
|
|
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))
|
|
|
|
@name.setter
|
|
def name(self, name: ASTNestedName) -> None:
|
|
raise NotImplementedError(repr(self))
|
|
|
|
@property
|
|
def isPack(self) -> bool:
|
|
raise NotImplementedError(repr(self))
|
|
|
|
@property
|
|
def function_params(self) -> list[ASTFunctionParameter]:
|
|
raise NotImplementedError(repr(self))
|
|
|
|
@property
|
|
def trailingReturn(self) -> ASTType:
|
|
raise NotImplementedError(repr(self))
|
|
|
|
def require_space_after_declSpecs(self) -> bool:
|
|
raise NotImplementedError(repr(self))
|
|
|
|
def get_modifiers_id(self, version: int) -> str:
|
|
raise NotImplementedError(repr(self))
|
|
|
|
def get_param_id(self, version: int) -> str:
|
|
raise NotImplementedError(repr(self))
|
|
|
|
def get_ptr_suffix_id(self, version: int) -> str:
|
|
raise NotImplementedError(repr(self))
|
|
|
|
def get_type_id(self, version: int, returnTypeId: str) -> str:
|
|
raise NotImplementedError(repr(self))
|
|
|
|
def is_function_type(self) -> bool:
|
|
raise NotImplementedError(repr(self))
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
raise NotImplementedError(repr(self))
|
|
|
|
|
|
class ASTDeclaratorNameParamQual(ASTDeclarator):
|
|
def __init__(self, declId: ASTNestedName,
|
|
arrayOps: list[ASTArray],
|
|
paramQual: ASTParametersQualifiers) -> None:
|
|
self.declId = declId
|
|
self.arrayOps = arrayOps
|
|
self.paramQual = paramQual
|
|
|
|
@property
|
|
def name(self) -> ASTNestedName:
|
|
return self.declId
|
|
|
|
@name.setter
|
|
def name(self, name: ASTNestedName) -> None:
|
|
self.declId = name
|
|
|
|
@property
|
|
def isPack(self) -> bool:
|
|
return False
|
|
|
|
@property
|
|
def function_params(self) -> list[ASTFunctionParameter]:
|
|
return self.paramQual.function_params
|
|
|
|
@property
|
|
def trailingReturn(self) -> ASTType:
|
|
return self.paramQual.trailingReturn
|
|
|
|
# only the modifiers for a function, e.g.,
|
|
def get_modifiers_id(self, version: int) -> str:
|
|
# cv-qualifiers
|
|
if self.paramQual:
|
|
return self.paramQual.get_modifiers_id(version)
|
|
raise Exception("This should only be called on a function: %s" % self)
|
|
|
|
def get_param_id(self, version: int) -> str: # only the parameters (if any)
|
|
if self.paramQual:
|
|
return self.paramQual.get_param_id(version)
|
|
else:
|
|
return ''
|
|
|
|
def get_ptr_suffix_id(self, version: int) -> str: # only the array specifiers
|
|
return ''.join(a.get_id(version) for a in self.arrayOps)
|
|
|
|
def get_type_id(self, version: int, returnTypeId: str) -> str:
|
|
assert version >= 2
|
|
res = []
|
|
# TODO: can we actually have both array ops and paramQual?
|
|
res.append(self.get_ptr_suffix_id(version))
|
|
if self.paramQual:
|
|
res.append(self.get_modifiers_id(version))
|
|
res.append('F')
|
|
res.append(returnTypeId)
|
|
res.append(self.get_param_id(version))
|
|
res.append('E')
|
|
else:
|
|
res.append(returnTypeId)
|
|
return ''.join(res)
|
|
|
|
# ------------------------------------------------------------------------
|
|
|
|
def require_space_after_declSpecs(self) -> bool:
|
|
return self.declId is not None
|
|
|
|
def is_function_type(self) -> bool:
|
|
return self.paramQual 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.paramQual:
|
|
res.append(transform(self.paramQual))
|
|
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.paramQual:
|
|
self.paramQual.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
|
|
|
|
@name.setter
|
|
def name(self, name: ASTNestedName) -> None:
|
|
self.declId = name
|
|
|
|
def get_param_id(self, version: int) -> str: # only the parameters (if any)
|
|
return ''
|
|
|
|
def get_ptr_suffix_id(self, version: int) -> str: # only the array specifiers
|
|
return ''
|
|
|
|
# ------------------------------------------------------------------------
|
|
|
|
def require_space_after_declSpecs(self) -> bool:
|
|
return self.declId is not None
|
|
|
|
def is_function_type(self) -> bool:
|
|
return False
|
|
|
|
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, volatile: bool, const: bool,
|
|
attrs: ASTAttributeList) -> None:
|
|
assert next
|
|
self.next = next
|
|
self.volatile = volatile
|
|
self.const = const
|
|
self.attrs = attrs
|
|
|
|
@property
|
|
def name(self) -> ASTNestedName:
|
|
return self.next.name
|
|
|
|
@name.setter
|
|
def name(self, name: ASTNestedName) -> None:
|
|
self.next.name = name
|
|
|
|
@property
|
|
def isPack(self) -> bool:
|
|
return self.next.isPack
|
|
|
|
@property
|
|
def function_params(self) -> list[ASTFunctionParameter]:
|
|
return self.next.function_params
|
|
|
|
@property
|
|
def trailingReturn(self) -> ASTType:
|
|
return self.next.trailingReturn
|
|
|
|
def require_space_after_declSpecs(self) -> bool:
|
|
return 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.volatile or self.const):
|
|
res.append(' ')
|
|
if self.volatile:
|
|
res.append('volatile')
|
|
if self.const:
|
|
if self.volatile:
|
|
res.append(' ')
|
|
res.append('const')
|
|
if self.const or self.volatile or len(self.attrs) > 0:
|
|
if self.next.require_space_after_declSpecs():
|
|
res.append(' ')
|
|
res.append(transform(self.next))
|
|
return ''.join(res)
|
|
|
|
def get_modifiers_id(self, version: int) -> str:
|
|
return self.next.get_modifiers_id(version)
|
|
|
|
def get_param_id(self, version: int) -> str:
|
|
return self.next.get_param_id(version)
|
|
|
|
def get_ptr_suffix_id(self, version: int) -> str:
|
|
if version == 1:
|
|
res = ['P']
|
|
if self.volatile:
|
|
res.append('V')
|
|
if self.const:
|
|
res.append('C')
|
|
res.append(self.next.get_ptr_suffix_id(version))
|
|
return ''.join(res)
|
|
|
|
res = [self.next.get_ptr_suffix_id(version)]
|
|
res.append('P')
|
|
if self.volatile:
|
|
res.append('V')
|
|
if self.const:
|
|
res.append('C')
|
|
return ''.join(res)
|
|
|
|
def get_type_id(self, version: int, returnTypeId: str) -> str:
|
|
# ReturnType *next, so we are part of the return type of 'next
|
|
res = ['P']
|
|
if self.volatile:
|
|
res.append('V')
|
|
if self.const:
|
|
res.append('C')
|
|
res.append(returnTypeId)
|
|
return self.next.get_type_id(version, returnTypeId=''.join(res))
|
|
|
|
def is_function_type(self) -> bool:
|
|
return self.next.is_function_type()
|
|
|
|
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.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.volatile:
|
|
_add_anno(signode, 'volatile')
|
|
if self.const:
|
|
if self.volatile:
|
|
signode += addnodes.desc_sig_space()
|
|
_add_anno(signode, 'const')
|
|
if self.const or self.volatile 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 ASTDeclaratorRef(ASTDeclarator):
|
|
def __init__(self, next: ASTDeclarator, attrs: ASTAttributeList) -> None:
|
|
assert next
|
|
self.next = next
|
|
self.attrs = attrs
|
|
|
|
@property
|
|
def name(self) -> ASTNestedName:
|
|
return self.next.name
|
|
|
|
@name.setter
|
|
def name(self, name: ASTNestedName) -> None:
|
|
self.next.name = name
|
|
|
|
@property
|
|
def isPack(self) -> bool:
|
|
return self.next.isPack
|
|
|
|
@property
|
|
def function_params(self) -> list[ASTFunctionParameter]:
|
|
return self.next.function_params
|
|
|
|
@property
|
|
def trailingReturn(self) -> ASTType:
|
|
return self.next.trailingReturn
|
|
|
|
def require_space_after_declSpecs(self) -> bool:
|
|
return 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.next.require_space_after_declSpecs():
|
|
res.append(' ')
|
|
res.append(transform(self.next))
|
|
return ''.join(res)
|
|
|
|
def get_modifiers_id(self, version: int) -> str:
|
|
return self.next.get_modifiers_id(version)
|
|
|
|
def get_param_id(self, version: int) -> str: # only the parameters (if any)
|
|
return self.next.get_param_id(version)
|
|
|
|
def get_ptr_suffix_id(self, version: int) -> str:
|
|
if version == 1:
|
|
return 'R' + self.next.get_ptr_suffix_id(version)
|
|
else:
|
|
return self.next.get_ptr_suffix_id(version) + 'R'
|
|
|
|
def get_type_id(self, version: int, returnTypeId: str) -> str:
|
|
assert version >= 2
|
|
# ReturnType &next, so we are part of the return type of 'next
|
|
return self.next.get_type_id(version, returnTypeId='R' + returnTypeId)
|
|
|
|
def is_function_type(self) -> bool:
|
|
return self.next.is_function_type()
|
|
|
|
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.next.require_space_after_declSpecs():
|
|
signode += addnodes.desc_sig_space()
|
|
self.next.describe_signature(signode, mode, env, symbol)
|
|
|
|
|
|
class ASTDeclaratorParamPack(ASTDeclarator):
|
|
def __init__(self, next: ASTDeclarator) -> None:
|
|
assert next
|
|
self.next = next
|
|
|
|
@property
|
|
def name(self) -> ASTNestedName:
|
|
return self.next.name
|
|
|
|
@name.setter
|
|
def name(self, name: ASTNestedName) -> None:
|
|
self.next.name = name
|
|
|
|
@property
|
|
def function_params(self) -> list[ASTFunctionParameter]:
|
|
return self.next.function_params
|
|
|
|
@property
|
|
def trailingReturn(self) -> ASTType:
|
|
return self.next.trailingReturn
|
|
|
|
@property
|
|
def isPack(self) -> bool:
|
|
return True
|
|
|
|
def require_space_after_declSpecs(self) -> bool:
|
|
return False
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
res = transform(self.next)
|
|
if self.next.name:
|
|
res = ' ' + res
|
|
return '...' + res
|
|
|
|
def get_modifiers_id(self, version: int) -> str:
|
|
return self.next.get_modifiers_id(version)
|
|
|
|
def get_param_id(self, version: int) -> str: # only the parameters (if any)
|
|
return self.next.get_param_id(version)
|
|
|
|
def get_ptr_suffix_id(self, version: int) -> str:
|
|
if version == 1:
|
|
return 'Dp' + self.next.get_ptr_suffix_id(version)
|
|
else:
|
|
return self.next.get_ptr_suffix_id(version) + 'Dp'
|
|
|
|
def get_type_id(self, version: int, returnTypeId: str) -> str:
|
|
assert version >= 2
|
|
# ReturnType... next, so we are part of the return type of 'next
|
|
return self.next.get_type_id(version, returnTypeId='Dp' + returnTypeId)
|
|
|
|
def is_function_type(self) -> bool:
|
|
return self.next.is_function_type()
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
verify_description_mode(mode)
|
|
signode += addnodes.desc_sig_punctuation('...', '...')
|
|
if self.next.name:
|
|
signode += addnodes.desc_sig_space()
|
|
self.next.describe_signature(signode, mode, env, symbol)
|
|
|
|
|
|
class ASTDeclaratorMemPtr(ASTDeclarator):
|
|
def __init__(self, className: ASTNestedName,
|
|
const: bool, volatile: bool, next: ASTDeclarator) -> None:
|
|
assert className
|
|
assert next
|
|
self.className = className
|
|
self.const = const
|
|
self.volatile = volatile
|
|
self.next = next
|
|
|
|
@property
|
|
def name(self) -> ASTNestedName:
|
|
return self.next.name
|
|
|
|
@name.setter
|
|
def name(self, name: ASTNestedName) -> None:
|
|
self.next.name = name
|
|
|
|
@property
|
|
def isPack(self):
|
|
return self.next.isPack
|
|
|
|
@property
|
|
def function_params(self) -> list[ASTFunctionParameter]:
|
|
return self.next.function_params
|
|
|
|
@property
|
|
def trailingReturn(self) -> ASTType:
|
|
return self.next.trailingReturn
|
|
|
|
def require_space_after_declSpecs(self) -> bool:
|
|
return True
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
res = []
|
|
res.append(transform(self.className))
|
|
res.append('::*')
|
|
if self.volatile:
|
|
res.append('volatile')
|
|
if self.const:
|
|
if self.volatile:
|
|
res.append(' ')
|
|
res.append('const')
|
|
if self.next.require_space_after_declSpecs():
|
|
res.append(' ')
|
|
res.append(transform(self.next))
|
|
return ''.join(res)
|
|
|
|
def get_modifiers_id(self, version: int) -> str:
|
|
if version == 1:
|
|
raise NoOldIdError
|
|
return self.next.get_modifiers_id(version)
|
|
|
|
def get_param_id(self, version: int) -> str: # only the parameters (if any)
|
|
if version == 1:
|
|
raise NoOldIdError
|
|
return self.next.get_param_id(version)
|
|
|
|
def get_ptr_suffix_id(self, version: int) -> str:
|
|
if version == 1:
|
|
raise NoOldIdError
|
|
raise NotImplementedError
|
|
return self.next.get_ptr_suffix_id(version) + 'Dp'
|
|
|
|
def get_type_id(self, version: int, returnTypeId: str) -> str:
|
|
assert version >= 2
|
|
# ReturnType name::* next, so we are part of the return type of next
|
|
nextReturnTypeId = ''
|
|
if self.volatile:
|
|
nextReturnTypeId += 'V'
|
|
if self.const:
|
|
nextReturnTypeId += 'K'
|
|
nextReturnTypeId += 'M'
|
|
nextReturnTypeId += self.className.get_id(version)
|
|
nextReturnTypeId += returnTypeId
|
|
return self.next.get_type_id(version, nextReturnTypeId)
|
|
|
|
def is_function_type(self) -> bool:
|
|
return self.next.is_function_type()
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
verify_description_mode(mode)
|
|
self.className.describe_signature(signode, 'markType', env, symbol)
|
|
signode += addnodes.desc_sig_punctuation('::', '::')
|
|
signode += addnodes.desc_sig_punctuation('*', '*')
|
|
|
|
def _add_anno(signode: TextElement, text: str) -> None:
|
|
signode += addnodes.desc_sig_keyword(text, text)
|
|
if self.volatile:
|
|
_add_anno(signode, 'volatile')
|
|
if self.const:
|
|
if self.volatile:
|
|
signode += addnodes.desc_sig_space()
|
|
_add_anno(signode, 'const')
|
|
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, params, and qualifiers are in inner
|
|
|
|
@property
|
|
def name(self) -> ASTNestedName:
|
|
return self.inner.name
|
|
|
|
@name.setter
|
|
def name(self, name: ASTNestedName) -> None:
|
|
self.inner.name = name
|
|
|
|
@property
|
|
def isPack(self):
|
|
return self.inner.isPack or self.next.isPack
|
|
|
|
@property
|
|
def function_params(self) -> list[ASTFunctionParameter]:
|
|
return self.inner.function_params
|
|
|
|
@property
|
|
def trailingReturn(self) -> ASTType:
|
|
return self.inner.trailingReturn
|
|
|
|
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 get_modifiers_id(self, version: int) -> str:
|
|
return self.inner.get_modifiers_id(version)
|
|
|
|
def get_param_id(self, version: int) -> str: # only the parameters (if any)
|
|
return self.inner.get_param_id(version)
|
|
|
|
def get_ptr_suffix_id(self, version: int) -> str:
|
|
if version == 1:
|
|
raise NoOldIdError # TODO: was this implemented before?
|
|
return self.next.get_ptr_suffix_id(version) + \
|
|
self.inner.get_ptr_suffix_id(version)
|
|
return self.inner.get_ptr_suffix_id(version) + \
|
|
self.next.get_ptr_suffix_id(version)
|
|
|
|
def get_type_id(self, version: int, returnTypeId: str) -> str:
|
|
assert version >= 2
|
|
# ReturnType (inner)next, so 'inner' returns everything outside
|
|
nextId = self.next.get_type_id(version, returnTypeId)
|
|
return self.inner.get_type_id(version, returnTypeId=nextId)
|
|
|
|
def is_function_type(self) -> bool:
|
|
return self.inner.is_function_type()
|
|
|
|
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)
|
|
|
|
|
|
# Type and initializer stuff
|
|
##############################################################################################
|
|
|
|
class ASTPackExpansionExpr(ASTExpression):
|
|
def __init__(self, expr: ASTExpression | ASTBracedInitList):
|
|
self.expr = expr
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
return transform(self.expr) + '...'
|
|
|
|
def get_id(self, version: int) -> str:
|
|
id = self.expr.get_id(version)
|
|
return 'sp' + id
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
self.expr.describe_signature(signode, mode, env, symbol)
|
|
signode += addnodes.desc_sig_punctuation('...', '...')
|
|
|
|
|
|
class ASTParenExprList(ASTBaseParenExprList):
|
|
def __init__(self, exprs: list[ASTExpression | ASTBracedInitList]) -> None:
|
|
self.exprs = exprs
|
|
|
|
def get_id(self, version: int) -> str:
|
|
return "pi%sE" % ''.join(e.get_id(version) for e in self.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 ASTInitializer(ASTBase):
|
|
def __init__(self, value: ASTExpression | ASTBracedInitList,
|
|
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
|
|
|
|
@name.setter
|
|
def name(self, name: ASTNestedName) -> None:
|
|
self.decl.name = name
|
|
|
|
@property
|
|
def isPack(self) -> bool:
|
|
return self.decl.isPack
|
|
|
|
@property
|
|
def function_params(self) -> list[ASTFunctionParameter]:
|
|
return self.decl.function_params
|
|
|
|
@property
|
|
def trailingReturn(self) -> ASTType:
|
|
return self.decl.trailingReturn
|
|
|
|
def get_id(self, version: int, objectType: str | None = None,
|
|
symbol: Symbol | None = None) -> str:
|
|
if version == 1:
|
|
res = []
|
|
if objectType: # needs the name
|
|
if objectType == 'function': # also modifiers
|
|
res.append(symbol.get_full_nested_name().get_id(version))
|
|
res.append(self.decl.get_param_id(version))
|
|
res.append(self.decl.get_modifiers_id(version))
|
|
if (self.declSpecs.leftSpecs.constexpr or
|
|
(self.declSpecs.rightSpecs and
|
|
self.declSpecs.rightSpecs.constexpr)):
|
|
res.append('CE')
|
|
elif objectType == 'type': # just the name
|
|
res.append(symbol.get_full_nested_name().get_id(version))
|
|
else:
|
|
raise AssertionError(objectType)
|
|
else: # only type encoding
|
|
if self.decl.is_function_type():
|
|
raise NoOldIdError
|
|
res.append(self.declSpecs.get_id(version))
|
|
res.append(self.decl.get_ptr_suffix_id(version))
|
|
res.append(self.decl.get_param_id(version))
|
|
return ''.join(res)
|
|
# other versions
|
|
res = []
|
|
if objectType: # needs the name
|
|
if objectType == 'function': # also modifiers
|
|
modifiers = self.decl.get_modifiers_id(version)
|
|
res.append(symbol.get_full_nested_name().get_id(version, modifiers))
|
|
if version >= 4:
|
|
# with templates we need to mangle the return type in as well
|
|
templ = symbol.declaration.templatePrefix
|
|
if templ is not None:
|
|
typeId = self.decl.get_ptr_suffix_id(version)
|
|
if self.trailingReturn:
|
|
returnTypeId = self.trailingReturn.get_id(version)
|
|
else:
|
|
returnTypeId = self.declSpecs.get_id(version)
|
|
res.append(typeId)
|
|
res.append(returnTypeId)
|
|
res.append(self.decl.get_param_id(version))
|
|
elif objectType == 'type': # just the name
|
|
res.append(symbol.get_full_nested_name().get_id(version))
|
|
else:
|
|
raise AssertionError(objectType)
|
|
else: # only type encoding
|
|
# the 'returnType' of a non-function type is simply just the last
|
|
# type, i.e., for 'int*' it is 'int'
|
|
returnTypeId = self.declSpecs.get_id(version)
|
|
typeId = self.decl.get_type_id(version, returnTypeId)
|
|
res.append(typeId)
|
|
return ''.join(res)
|
|
|
|
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 ASTTemplateParamConstrainedTypeWithInit(ASTBase):
|
|
def __init__(self, type: ASTType, init: ASTType) -> None:
|
|
assert type
|
|
self.type = type
|
|
self.init = init
|
|
|
|
@property
|
|
def name(self) -> ASTNestedName:
|
|
return self.type.name
|
|
|
|
@property
|
|
def isPack(self) -> bool:
|
|
return self.type.isPack
|
|
|
|
def get_id(
|
|
self, version: int, objectType: str | None = None, symbol: Symbol | None = None,
|
|
) -> str:
|
|
# this is not part of the normal name mangling in C++
|
|
assert version >= 2
|
|
if symbol:
|
|
# the anchor will be our parent
|
|
return symbol.parent.declaration.get_id(version, prefixed=False)
|
|
else:
|
|
return self.type.get_id(version)
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
res = transform(self.type)
|
|
if self.init:
|
|
res += " = "
|
|
res += transform(self.init)
|
|
return res
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
self.type.describe_signature(signode, mode, env, symbol)
|
|
if self.init:
|
|
signode += addnodes.desc_sig_space()
|
|
signode += addnodes.desc_sig_punctuation('=', '=')
|
|
signode += addnodes.desc_sig_space()
|
|
self.init.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
|
|
|
|
@property
|
|
def isPack(self) -> bool:
|
|
return self.type.isPack
|
|
|
|
def get_id(self, version: int, objectType: str | None = None,
|
|
symbol: Symbol | None = None) -> str:
|
|
if objectType != 'member':
|
|
return self.type.get_id(version, objectType)
|
|
if version == 1:
|
|
return (symbol.get_full_nested_name().get_id(version) + '__' +
|
|
self.type.get_id(version))
|
|
return symbol.get_full_nested_name().get_id(version)
|
|
|
|
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 ASTTypeUsing(ASTBase):
|
|
def __init__(self, name: ASTNestedName, type: ASTType) -> None:
|
|
self.name = name
|
|
self.type = type
|
|
|
|
def get_id(self, version: int, objectType: str | None = None,
|
|
symbol: Symbol | None = None) -> str:
|
|
if version == 1:
|
|
raise NoOldIdError
|
|
return symbol.get_full_nested_name().get_id(version)
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
res = []
|
|
res.append(transform(self.name))
|
|
if self.type:
|
|
res.append(' = ')
|
|
res.append(transform(self.type))
|
|
return ''.join(res)
|
|
|
|
def get_type_declaration_prefix(self) -> str:
|
|
return 'using'
|
|
|
|
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)
|
|
if self.type:
|
|
signode += addnodes.desc_sig_space()
|
|
signode += addnodes.desc_sig_punctuation('=', '=')
|
|
signode += addnodes.desc_sig_space()
|
|
self.type.describe_signature(signode, 'markType', env, symbol=symbol)
|
|
|
|
|
|
# Other declarations
|
|
##############################################################################################
|
|
|
|
class ASTConcept(ASTBase):
|
|
def __init__(self, nestedName: ASTNestedName, initializer: ASTInitializer) -> None:
|
|
self.nestedName = nestedName
|
|
self.initializer = initializer
|
|
|
|
@property
|
|
def name(self) -> ASTNestedName:
|
|
return self.nestedName
|
|
|
|
def get_id(self, version: int, objectType: str | None = None,
|
|
symbol: Symbol | None = None) -> str:
|
|
if version == 1:
|
|
raise NoOldIdError
|
|
return symbol.get_full_nested_name().get_id(version)
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
res = transform(self.nestedName)
|
|
if self.initializer:
|
|
res += transform(self.initializer)
|
|
return res
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
self.nestedName.describe_signature(signode, mode, env, symbol)
|
|
if self.initializer:
|
|
self.initializer.describe_signature(signode, mode, env, symbol)
|
|
|
|
|
|
class ASTBaseClass(ASTBase):
|
|
def __init__(self, name: ASTNestedName, visibility: str,
|
|
virtual: bool, pack: bool) -> None:
|
|
self.name = name
|
|
self.visibility = visibility
|
|
self.virtual = virtual
|
|
self.pack = pack
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
res = []
|
|
if self.visibility is not None:
|
|
res.append(self.visibility)
|
|
res.append(' ')
|
|
if self.virtual:
|
|
res.append('virtual ')
|
|
res.append(transform(self.name))
|
|
if self.pack:
|
|
res.append('...')
|
|
return ''.join(res)
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
verify_description_mode(mode)
|
|
if self.visibility is not None:
|
|
signode += addnodes.desc_sig_keyword(self.visibility,
|
|
self.visibility)
|
|
signode += addnodes.desc_sig_space()
|
|
if self.virtual:
|
|
signode += addnodes.desc_sig_keyword('virtual', 'virtual')
|
|
signode += addnodes.desc_sig_space()
|
|
self.name.describe_signature(signode, 'markType', env, symbol=symbol)
|
|
if self.pack:
|
|
signode += addnodes.desc_sig_punctuation('...', '...')
|
|
|
|
|
|
class ASTClass(ASTBase):
|
|
def __init__(self, name: ASTNestedName, final: bool, bases: list[ASTBaseClass],
|
|
attrs: ASTAttributeList) -> None:
|
|
self.name = name
|
|
self.final = final
|
|
self.bases = bases
|
|
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.attrs))
|
|
if len(self.attrs) != 0:
|
|
res.append(' ')
|
|
res.append(transform(self.name))
|
|
if self.final:
|
|
res.append(' final')
|
|
if len(self.bases) > 0:
|
|
res.append(' : ')
|
|
first = True
|
|
for b in self.bases:
|
|
if not first:
|
|
res.append(', ')
|
|
first = False
|
|
res.append(transform(b))
|
|
return ''.join(res)
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
verify_description_mode(mode)
|
|
self.attrs.describe_signature(signode)
|
|
if len(self.attrs) != 0:
|
|
signode += addnodes.desc_sig_space()
|
|
self.name.describe_signature(signode, mode, env, symbol=symbol)
|
|
if self.final:
|
|
signode += addnodes.desc_sig_space()
|
|
signode += addnodes.desc_sig_keyword('final', 'final')
|
|
if len(self.bases) > 0:
|
|
signode += addnodes.desc_sig_space()
|
|
signode += addnodes.desc_sig_punctuation(':', ':')
|
|
signode += addnodes.desc_sig_space()
|
|
for b in self.bases:
|
|
b.describe_signature(signode, mode, env, symbol=symbol)
|
|
signode += addnodes.desc_sig_punctuation(',', ',')
|
|
signode += addnodes.desc_sig_space()
|
|
signode.pop()
|
|
signode.pop()
|
|
|
|
|
|
class ASTUnion(ASTBase):
|
|
def __init__(self, name: ASTNestedName, attrs: ASTAttributeList) -> None:
|
|
self.name = name
|
|
self.attrs = attrs
|
|
|
|
def get_id(self, version: int, objectType: str, symbol: Symbol) -> str:
|
|
if version == 1:
|
|
raise NoOldIdError
|
|
return symbol.get_full_nested_name().get_id(version)
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
res = []
|
|
res.append(transform(self.attrs))
|
|
if len(self.attrs) != 0:
|
|
res.append(' ')
|
|
res.append(transform(self.name))
|
|
return ''.join(res)
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
verify_description_mode(mode)
|
|
self.attrs.describe_signature(signode)
|
|
if len(self.attrs) != 0:
|
|
signode += addnodes.desc_sig_space()
|
|
self.name.describe_signature(signode, mode, env, symbol=symbol)
|
|
|
|
|
|
class ASTEnum(ASTBase):
|
|
def __init__(self, name: ASTNestedName, scoped: str, underlyingType: ASTType,
|
|
attrs: ASTAttributeList) -> None:
|
|
self.name = name
|
|
self.scoped = scoped
|
|
self.underlyingType = underlyingType
|
|
self.attrs = attrs
|
|
|
|
def get_id(self, version: int, objectType: str, symbol: Symbol) -> str:
|
|
if version == 1:
|
|
raise NoOldIdError
|
|
return symbol.get_full_nested_name().get_id(version)
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
res = []
|
|
if self.scoped:
|
|
res.append(self.scoped)
|
|
res.append(' ')
|
|
res.append(transform(self.attrs))
|
|
if len(self.attrs) != 0:
|
|
res.append(' ')
|
|
res.append(transform(self.name))
|
|
if self.underlyingType:
|
|
res.append(' : ')
|
|
res.append(transform(self.underlyingType))
|
|
return ''.join(res)
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
verify_description_mode(mode)
|
|
# self.scoped has been done by the CPPEnumObject
|
|
self.attrs.describe_signature(signode)
|
|
if len(self.attrs) != 0:
|
|
signode += addnodes.desc_sig_space()
|
|
self.name.describe_signature(signode, mode, env, symbol=symbol)
|
|
if self.underlyingType:
|
|
signode += addnodes.desc_sig_space()
|
|
signode += addnodes.desc_sig_punctuation(':', ':')
|
|
signode += addnodes.desc_sig_space()
|
|
self.underlyingType.describe_signature(signode, 'noneIsName',
|
|
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:
|
|
if version == 1:
|
|
raise NoOldIdError
|
|
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)
|
|
|
|
|
|
################################################################################
|
|
# Templates
|
|
################################################################################
|
|
|
|
# Parameters
|
|
################################################################################
|
|
|
|
class ASTTemplateParam(ASTBase):
|
|
def get_identifier(self) -> ASTIdentifier:
|
|
raise NotImplementedError(repr(self))
|
|
|
|
def get_id(self, version: int) -> str:
|
|
raise NotImplementedError(repr(self))
|
|
|
|
def describe_signature(self, parentNode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
raise NotImplementedError(repr(self))
|
|
|
|
@property
|
|
def isPack(self) -> bool:
|
|
raise NotImplementedError(repr(self))
|
|
|
|
@property
|
|
def name(self) -> ASTNestedName:
|
|
raise NotImplementedError(repr(self))
|
|
|
|
|
|
class ASTTemplateKeyParamPackIdDefault(ASTTemplateParam):
|
|
def __init__(self, key: str, identifier: ASTIdentifier,
|
|
parameterPack: bool, default: ASTType) -> None:
|
|
assert key
|
|
if parameterPack:
|
|
assert default is None
|
|
self.key = key
|
|
self.identifier = identifier
|
|
self.parameterPack = parameterPack
|
|
self.default = default
|
|
|
|
def get_identifier(self) -> ASTIdentifier:
|
|
return self.identifier
|
|
|
|
def get_id(self, version: int) -> str:
|
|
assert version >= 2
|
|
# this is not part of the normal name mangling in C++
|
|
res = []
|
|
if self.parameterPack:
|
|
res.append('Dp')
|
|
else:
|
|
res.append('0') # we need to put something
|
|
return ''.join(res)
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
res = [self.key]
|
|
if self.parameterPack:
|
|
if self.identifier:
|
|
res.append(' ')
|
|
res.append('...')
|
|
if self.identifier:
|
|
if not self.parameterPack:
|
|
res.append(' ')
|
|
res.append(transform(self.identifier))
|
|
if self.default:
|
|
res.append(' = ')
|
|
res.append(transform(self.default))
|
|
return ''.join(res)
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
signode += addnodes.desc_sig_keyword(self.key, self.key)
|
|
if self.parameterPack:
|
|
if self.identifier:
|
|
signode += addnodes.desc_sig_space()
|
|
signode += addnodes.desc_sig_punctuation('...', '...')
|
|
if self.identifier:
|
|
if not self.parameterPack:
|
|
signode += addnodes.desc_sig_space()
|
|
self.identifier.describe_signature(signode, mode, env, '', '', symbol)
|
|
if self.default:
|
|
signode += addnodes.desc_sig_space()
|
|
signode += addnodes.desc_sig_punctuation('=', '=')
|
|
signode += addnodes.desc_sig_space()
|
|
self.default.describe_signature(signode, 'markType', env, symbol)
|
|
|
|
|
|
class ASTTemplateParamType(ASTTemplateParam):
|
|
def __init__(self, data: ASTTemplateKeyParamPackIdDefault) -> None:
|
|
assert data
|
|
self.data = data
|
|
|
|
@property
|
|
def name(self) -> ASTNestedName:
|
|
id = self.get_identifier()
|
|
return ASTNestedName([ASTNestedNameElement(id, None)], [False], rooted=False)
|
|
|
|
@property
|
|
def isPack(self) -> bool:
|
|
return self.data.parameterPack
|
|
|
|
def get_identifier(self) -> ASTIdentifier:
|
|
return self.data.get_identifier()
|
|
|
|
def get_id(
|
|
self, version: int, objectType: str | None = None, symbol: Symbol | None = None,
|
|
) -> str:
|
|
# this is not part of the normal name mangling in C++
|
|
assert version >= 2
|
|
if symbol:
|
|
# the anchor will be our parent
|
|
return symbol.parent.declaration.get_id(version, prefixed=False)
|
|
else:
|
|
return self.data.get_id(version)
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
return transform(self.data)
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
self.data.describe_signature(signode, mode, env, symbol)
|
|
|
|
|
|
class ASTTemplateParamTemplateType(ASTTemplateParam):
|
|
def __init__(self, nestedParams: ASTTemplateParams,
|
|
data: ASTTemplateKeyParamPackIdDefault) -> None:
|
|
assert nestedParams
|
|
assert data
|
|
self.nestedParams = nestedParams
|
|
self.data = data
|
|
|
|
@property
|
|
def name(self) -> ASTNestedName:
|
|
id = self.get_identifier()
|
|
return ASTNestedName([ASTNestedNameElement(id, None)], [False], rooted=False)
|
|
|
|
@property
|
|
def isPack(self) -> bool:
|
|
return self.data.parameterPack
|
|
|
|
def get_identifier(self) -> ASTIdentifier:
|
|
return self.data.get_identifier()
|
|
|
|
def get_id(
|
|
self, version: int, objectType: str | None = None, symbol: Symbol | None = None,
|
|
) -> str:
|
|
assert version >= 2
|
|
# this is not part of the normal name mangling in C++
|
|
if symbol:
|
|
# the anchor will be our parent
|
|
return symbol.parent.declaration.get_id(version, prefixed=None)
|
|
else:
|
|
return self.nestedParams.get_id(version) + self.data.get_id(version)
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
return transform(self.nestedParams) + transform(self.data)
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
self.nestedParams.describe_signature(signode, 'noneIsName', env, symbol)
|
|
signode += addnodes.desc_sig_space()
|
|
self.data.describe_signature(signode, mode, env, symbol)
|
|
|
|
|
|
class ASTTemplateParamNonType(ASTTemplateParam):
|
|
def __init__(self,
|
|
param: ASTTypeWithInit | ASTTemplateParamConstrainedTypeWithInit,
|
|
parameterPack: bool = False) -> None:
|
|
assert param
|
|
self.param = param
|
|
self.parameterPack = parameterPack
|
|
|
|
@property
|
|
def name(self) -> ASTNestedName:
|
|
id = self.get_identifier()
|
|
return ASTNestedName([ASTNestedNameElement(id, None)], [False], rooted=False)
|
|
|
|
@property
|
|
def isPack(self) -> bool:
|
|
return self.param.isPack or self.parameterPack
|
|
|
|
def get_identifier(self) -> ASTIdentifier:
|
|
name = self.param.name
|
|
if name:
|
|
assert len(name.names) == 1
|
|
assert name.names[0].identOrOp
|
|
assert not name.names[0].templateArgs
|
|
res = name.names[0].identOrOp
|
|
assert isinstance(res, ASTIdentifier)
|
|
return res
|
|
else:
|
|
return None
|
|
|
|
def get_id(
|
|
self, version: int, objectType: str | None = None, symbol: Symbol | None = None,
|
|
) -> str:
|
|
assert version >= 2
|
|
# this is not part of the normal name mangling in C++
|
|
if symbol:
|
|
# the anchor will be our parent
|
|
return symbol.parent.declaration.get_id(version, prefixed=None)
|
|
else:
|
|
res = '_'
|
|
if self.parameterPack:
|
|
res += 'Dp'
|
|
return res + self.param.get_id(version)
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
res = transform(self.param)
|
|
if self.parameterPack:
|
|
res += '...'
|
|
return res
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
self.param.describe_signature(signode, mode, env, symbol)
|
|
if self.parameterPack:
|
|
signode += addnodes.desc_sig_punctuation('...', '...')
|
|
|
|
|
|
class ASTTemplateParams(ASTBase):
|
|
def __init__(self, params: list[ASTTemplateParam],
|
|
requiresClause: ASTRequiresClause | None) -> None:
|
|
assert params is not None
|
|
self.params = params
|
|
self.requiresClause = requiresClause
|
|
|
|
def get_id(self, version: int, excludeRequires: bool = False) -> str:
|
|
assert version >= 2
|
|
res = []
|
|
res.append("I")
|
|
for param in self.params:
|
|
res.append(param.get_id(version))
|
|
res.append("E")
|
|
if not excludeRequires and self.requiresClause:
|
|
res.append('IQ')
|
|
res.append(self.requiresClause.expr.get_id(version))
|
|
res.append('E')
|
|
return ''.join(res)
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
res = []
|
|
res.append("template<")
|
|
res.append(", ".join(transform(a) for a in self.params))
|
|
res.append("> ")
|
|
if self.requiresClause is not None:
|
|
res.append(transform(self.requiresClause))
|
|
res.append(" ")
|
|
return ''.join(res)
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
signode += addnodes.desc_sig_keyword('template', 'template')
|
|
signode += addnodes.desc_sig_punctuation('<', '<')
|
|
first = True
|
|
for param in self.params:
|
|
if not first:
|
|
signode += addnodes.desc_sig_punctuation(',', ',')
|
|
signode += addnodes.desc_sig_space()
|
|
first = False
|
|
param.describe_signature(signode, mode, env, symbol)
|
|
signode += addnodes.desc_sig_punctuation('>', '>')
|
|
if self.requiresClause is not None:
|
|
signode += addnodes.desc_sig_space()
|
|
self.requiresClause.describe_signature(signode, mode, env, symbol)
|
|
|
|
def describe_signature_as_introducer(
|
|
self, parentNode: desc_signature, mode: str, env: BuildEnvironment,
|
|
symbol: Symbol, lineSpec: bool) -> None:
|
|
def makeLine(parentNode: desc_signature) -> addnodes.desc_signature_line:
|
|
signode = addnodes.desc_signature_line()
|
|
parentNode += signode
|
|
signode.sphinx_line_type = 'templateParams'
|
|
return signode
|
|
lineNode = makeLine(parentNode)
|
|
lineNode += addnodes.desc_sig_keyword('template', 'template')
|
|
lineNode += addnodes.desc_sig_punctuation('<', '<')
|
|
first = True
|
|
for param in self.params:
|
|
if not first:
|
|
lineNode += addnodes.desc_sig_punctuation(',', ',')
|
|
lineNode += addnodes.desc_sig_space()
|
|
first = False
|
|
if lineSpec:
|
|
lineNode = makeLine(parentNode)
|
|
param.describe_signature(lineNode, mode, env, symbol)
|
|
if lineSpec and not first:
|
|
lineNode = makeLine(parentNode)
|
|
lineNode += addnodes.desc_sig_punctuation('>', '>')
|
|
if self.requiresClause:
|
|
reqNode = addnodes.desc_signature_line()
|
|
reqNode.sphinx_line_type = 'requiresClause'
|
|
parentNode += reqNode
|
|
self.requiresClause.describe_signature(reqNode, 'markType', env, symbol)
|
|
|
|
|
|
# Template introducers
|
|
################################################################################
|
|
|
|
class ASTTemplateIntroductionParameter(ASTBase):
|
|
def __init__(self, identifier: ASTIdentifier, parameterPack: bool) -> None:
|
|
self.identifier = identifier
|
|
self.parameterPack = parameterPack
|
|
|
|
@property
|
|
def name(self) -> ASTNestedName:
|
|
id = self.get_identifier()
|
|
return ASTNestedName([ASTNestedNameElement(id, None)], [False], rooted=False)
|
|
|
|
@property
|
|
def isPack(self) -> bool:
|
|
return self.parameterPack
|
|
|
|
def get_identifier(self) -> ASTIdentifier:
|
|
return self.identifier
|
|
|
|
def get_id(
|
|
self, version: int, objectType: str | None = None, symbol: Symbol | None = None,
|
|
) -> str:
|
|
assert version >= 2
|
|
# this is not part of the normal name mangling in C++
|
|
if symbol:
|
|
# the anchor will be our parent
|
|
return symbol.parent.declaration.get_id(version, prefixed=None)
|
|
else:
|
|
if self.parameterPack:
|
|
return 'Dp'
|
|
else:
|
|
return '0' # we need to put something
|
|
|
|
def get_id_as_arg(self, version: int) -> str:
|
|
assert version >= 2
|
|
# used for the implicit requires clause
|
|
res = self.identifier.get_id(version)
|
|
if self.parameterPack:
|
|
return 'sp' + res
|
|
else:
|
|
return res
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
res = []
|
|
if self.parameterPack:
|
|
res.append('...')
|
|
res.append(transform(self.identifier))
|
|
return ''.join(res)
|
|
|
|
def describe_signature(self, signode: TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
if self.parameterPack:
|
|
signode += addnodes.desc_sig_punctuation('...', '...')
|
|
self.identifier.describe_signature(signode, mode, env, '', '', symbol)
|
|
|
|
|
|
class ASTTemplateIntroduction(ASTBase):
|
|
def __init__(self, concept: ASTNestedName,
|
|
params: list[ASTTemplateIntroductionParameter]) -> None:
|
|
assert len(params) > 0
|
|
self.concept = concept
|
|
self.params = params
|
|
|
|
def get_id(self, version: int) -> str:
|
|
assert version >= 2
|
|
# first do the same as a normal template parameter list
|
|
res = []
|
|
res.append("I")
|
|
for param in self.params:
|
|
res.append(param.get_id(version))
|
|
res.append("E")
|
|
# let's use X expr E, which is otherwise for constant template args
|
|
res.append("X")
|
|
res.append(self.concept.get_id(version))
|
|
res.append("I")
|
|
for param in self.params:
|
|
res.append(param.get_id_as_arg(version))
|
|
res.append("E")
|
|
res.append("E")
|
|
return ''.join(res)
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
res = []
|
|
res.append(transform(self.concept))
|
|
res.append('{')
|
|
res.append(', '.join(transform(param) for param in self.params))
|
|
res.append('} ')
|
|
return ''.join(res)
|
|
|
|
def describe_signature_as_introducer(
|
|
self, parentNode: desc_signature, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol, lineSpec: bool) -> None:
|
|
# Note: 'lineSpec' has no effect on template introductions.
|
|
signode = addnodes.desc_signature_line()
|
|
parentNode += signode
|
|
signode.sphinx_line_type = 'templateIntroduction'
|
|
self.concept.describe_signature(signode, 'markType', env, symbol)
|
|
signode += addnodes.desc_sig_punctuation('{', '{')
|
|
first = True
|
|
for param in self.params:
|
|
if not first:
|
|
signode += addnodes.desc_sig_punctuation(',', ',')
|
|
signode += addnodes.desc_sig_space()
|
|
first = False
|
|
param.describe_signature(signode, mode, env, symbol)
|
|
signode += addnodes.desc_sig_punctuation('}', '}')
|
|
|
|
|
|
################################################################################
|
|
|
|
class ASTTemplateDeclarationPrefix(ASTBase):
|
|
def __init__(self,
|
|
templates: list[ASTTemplateParams | ASTTemplateIntroduction]) -> None:
|
|
# templates is None means it's an explicit instantiation of a variable
|
|
self.templates = templates
|
|
|
|
def get_requires_clause_in_last(self) -> ASTRequiresClause | None:
|
|
if self.templates is None:
|
|
return None
|
|
lastList = self.templates[-1]
|
|
if not isinstance(lastList, ASTTemplateParams):
|
|
return None
|
|
return lastList.requiresClause # which may be None
|
|
|
|
def get_id_except_requires_clause_in_last(self, version: int) -> str:
|
|
assert version >= 2
|
|
# This is not part of the Itanium ABI mangling system.
|
|
res = []
|
|
lastIndex = len(self.templates) - 1
|
|
for i, t in enumerate(self.templates):
|
|
if isinstance(t, ASTTemplateParams):
|
|
res.append(t.get_id(version, excludeRequires=(i == lastIndex)))
|
|
else:
|
|
res.append(t.get_id(version))
|
|
return ''.join(res)
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
res = []
|
|
for t in self.templates:
|
|
res.append(transform(t))
|
|
return ''.join(res)
|
|
|
|
def describe_signature(self, signode: desc_signature, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol, lineSpec: bool) -> None:
|
|
verify_description_mode(mode)
|
|
for t in self.templates:
|
|
t.describe_signature_as_introducer(signode, 'lastIsName', env, symbol, lineSpec)
|
|
|
|
|
|
class ASTRequiresClause(ASTBase):
|
|
def __init__(self, expr: ASTExpression) -> None:
|
|
self.expr = expr
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
return 'requires ' + transform(self.expr)
|
|
|
|
def describe_signature(self, signode: nodes.TextElement, mode: str,
|
|
env: BuildEnvironment, symbol: Symbol) -> None:
|
|
signode += addnodes.desc_sig_keyword('requires', 'requires')
|
|
signode += addnodes.desc_sig_space()
|
|
self.expr.describe_signature(signode, mode, env, symbol)
|
|
|
|
|
|
################################################################################
|
|
################################################################################
|
|
|
|
class ASTDeclaration(ASTBase):
|
|
def __init__(self, objectType: str, directiveType: str | None = None,
|
|
visibility: str | None = None,
|
|
templatePrefix: ASTTemplateDeclarationPrefix | None = None,
|
|
declaration: Any = None,
|
|
trailingRequiresClause: ASTRequiresClause | None = None,
|
|
semicolon: bool = False) -> None:
|
|
self.objectType = objectType
|
|
self.directiveType = directiveType
|
|
self.visibility = visibility
|
|
self.templatePrefix = templatePrefix
|
|
self.declaration = declaration
|
|
self.trailingRequiresClause = trailingRequiresClause
|
|
self.semicolon = semicolon
|
|
|
|
self.symbol: Symbol = None
|
|
# set by CPPObject._add_enumerator_to_parent
|
|
self.enumeratorScopedSymbol: Symbol = None
|
|
|
|
def clone(self) -> ASTDeclaration:
|
|
templatePrefixClone = self.templatePrefix.clone() if self.templatePrefix else None
|
|
trailingRequiresClasueClone = self.trailingRequiresClause.clone() \
|
|
if self.trailingRequiresClause else None
|
|
return ASTDeclaration(self.objectType, self.directiveType, self.visibility,
|
|
templatePrefixClone,
|
|
self.declaration.clone(), trailingRequiresClasueClone,
|
|
self.semicolon)
|
|
|
|
@property
|
|
def name(self) -> ASTNestedName:
|
|
return self.declaration.name
|
|
|
|
@property
|
|
def function_params(self) -> list[ASTFunctionParameter]:
|
|
if self.objectType != 'function':
|
|
return None
|
|
return self.declaration.function_params
|
|
|
|
def get_id(self, version: int, prefixed: bool = True) -> str:
|
|
if version == 1:
|
|
if self.templatePrefix or self.trailingRequiresClause:
|
|
raise NoOldIdError
|
|
if self.objectType == 'enumerator' and self.enumeratorScopedSymbol:
|
|
return self.enumeratorScopedSymbol.declaration.get_id(version)
|
|
return self.declaration.get_id(version, self.objectType, self.symbol)
|
|
# version >= 2
|
|
if self.objectType == 'enumerator' and self.enumeratorScopedSymbol:
|
|
return self.enumeratorScopedSymbol.declaration.get_id(version, prefixed)
|
|
if prefixed:
|
|
res = [_id_prefix[version]]
|
|
else:
|
|
res = []
|
|
# (See also https://github.com/sphinx-doc/sphinx/pull/10286#issuecomment-1168102147)
|
|
# The first implementation of requires clauses only supported a single clause after the
|
|
# template prefix, and no trailing clause. It put the ID after the template parameter
|
|
# list, i.e.,
|
|
# "I" + template_parameter_list_id + "E" + "IQ" + requires_clause_id + "E"
|
|
# but the second implementation associates the requires clause with each list, i.e.,
|
|
# "I" + template_parameter_list_id + "IQ" + requires_clause_id + "E" + "E"
|
|
# To avoid making a new ID version, we make an exception for the last requires clause
|
|
# in the template prefix, and still put it in the end.
|
|
# As we now support trailing requires clauses we add that as if it was a conjunction.
|
|
if self.templatePrefix is not None:
|
|
res.append(self.templatePrefix.get_id_except_requires_clause_in_last(version))
|
|
requiresClauseInLast = self.templatePrefix.get_requires_clause_in_last()
|
|
else:
|
|
requiresClauseInLast = None
|
|
|
|
if requiresClauseInLast or self.trailingRequiresClause:
|
|
if version < 4:
|
|
raise NoOldIdError
|
|
res.append('IQ')
|
|
if requiresClauseInLast and self.trailingRequiresClause:
|
|
# make a conjunction of them
|
|
res.append('aa')
|
|
if requiresClauseInLast:
|
|
res.append(requiresClauseInLast.expr.get_id(version))
|
|
if self.trailingRequiresClause:
|
|
res.append(self.trailingRequiresClause.expr.get_id(version))
|
|
res.append('E')
|
|
res.append(self.declaration.get_id(version, self.objectType, self.symbol))
|
|
return ''.join(res)
|
|
|
|
def get_newest_id(self) -> str:
|
|
return self.get_id(_max_id, True)
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
res = []
|
|
if self.visibility and self.visibility != "public":
|
|
res.append(self.visibility)
|
|
res.append(' ')
|
|
if self.templatePrefix:
|
|
res.append(transform(self.templatePrefix))
|
|
res.append(transform(self.declaration))
|
|
if self.trailingRequiresClause:
|
|
res.append(' ')
|
|
res.append(transform(self.trailingRequiresClause))
|
|
if self.semicolon:
|
|
res.append(';')
|
|
return ''.join(res)
|
|
|
|
def describe_signature(self, signode: desc_signature, 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
|
|
|
|
if self.templatePrefix:
|
|
self.templatePrefix.describe_signature(signode, mode, env,
|
|
symbol=self.symbol,
|
|
lineSpec=options.get('tparam-line-spec'))
|
|
signode += mainDeclNode
|
|
if self.visibility and self.visibility != "public":
|
|
mainDeclNode += addnodes.desc_sig_keyword(self.visibility, self.visibility)
|
|
mainDeclNode += addnodes.desc_sig_space()
|
|
if self.objectType == 'type':
|
|
prefix = self.declaration.get_type_declaration_prefix()
|
|
mainDeclNode += addnodes.desc_sig_keyword(prefix, prefix)
|
|
mainDeclNode += addnodes.desc_sig_space()
|
|
elif self.objectType == 'concept':
|
|
mainDeclNode += addnodes.desc_sig_keyword('concept', 'concept')
|
|
mainDeclNode += addnodes.desc_sig_space()
|
|
elif self.objectType in {'member', 'function'}:
|
|
pass
|
|
elif self.objectType == 'class':
|
|
assert self.directiveType in ('class', 'struct')
|
|
mainDeclNode += addnodes.desc_sig_keyword(self.directiveType, self.directiveType)
|
|
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()
|
|
if self.directiveType == 'enum-class':
|
|
mainDeclNode += addnodes.desc_sig_keyword('class', 'class')
|
|
mainDeclNode += addnodes.desc_sig_space()
|
|
elif self.directiveType == 'enum-struct':
|
|
mainDeclNode += addnodes.desc_sig_keyword('struct', 'struct')
|
|
mainDeclNode += addnodes.desc_sig_space()
|
|
else:
|
|
assert self.directiveType == 'enum', self.directiveType
|
|
elif self.objectType == 'enumerator':
|
|
mainDeclNode += addnodes.desc_sig_keyword('enumerator', 'enumerator')
|
|
mainDeclNode += addnodes.desc_sig_space()
|
|
else:
|
|
raise AssertionError(self.objectType)
|
|
self.declaration.describe_signature(mainDeclNode, mode, env, self.symbol)
|
|
lastDeclNode = mainDeclNode
|
|
if self.trailingRequiresClause:
|
|
trailingReqNode = addnodes.desc_signature_line()
|
|
trailingReqNode.sphinx_line_type = 'trailingRequiresClause'
|
|
signode.append(trailingReqNode)
|
|
lastDeclNode = trailingReqNode
|
|
self.trailingRequiresClause.describe_signature(
|
|
trailingReqNode, 'markType', env, self.symbol)
|
|
if self.semicolon:
|
|
lastDeclNode += addnodes.desc_sig_punctuation(';', ';')
|
|
|
|
|
|
class ASTNamespace(ASTBase):
|
|
def __init__(self, nestedName: ASTNestedName,
|
|
templatePrefix: ASTTemplateDeclarationPrefix) -> None:
|
|
self.nestedName = nestedName
|
|
self.templatePrefix = templatePrefix
|
|
|
|
def _stringify(self, transform: StringifyTransform) -> str:
|
|
res = []
|
|
if self.templatePrefix:
|
|
res.append(transform(self.templatePrefix))
|
|
res.append(transform(self.nestedName))
|
|
return ''.join(res)
|
|
|
|
|
|
class SymbolLookupResult:
|
|
def __init__(self, symbols: Iterator[Symbol], parentSymbol: Symbol,
|
|
identOrOp: ASTIdentifier | ASTOperator, templateParams: Any,
|
|
templateArgs: ASTTemplateArgs) -> None:
|
|
self.symbols = symbols
|
|
self.parentSymbol = parentSymbol
|
|
self.identOrOp = identOrOp
|
|
self.templateParams = templateParams
|
|
self.templateArgs = templateArgs
|
|
|
|
|
|
class LookupKey:
|
|
def __init__(self, data: list[tuple[ASTNestedNameElement,
|
|
ASTTemplateParams | ASTTemplateIntroduction,
|
|
str]]) -> None:
|
|
self.data = data
|
|
|
|
|
|
def _is_specialization(templateParams: ASTTemplateParams | ASTTemplateIntroduction,
|
|
templateArgs: ASTTemplateArgs) -> bool:
|
|
# Checks if `templateArgs` does not exactly match `templateParams`.
|
|
# the names of the template parameters must be given exactly as args
|
|
# and params that are packs must in the args be the name expanded
|
|
if len(templateParams.params) != len(templateArgs.args):
|
|
return True
|
|
# having no template params and no arguments is also a specialization
|
|
if len(templateParams.params) == 0:
|
|
return True
|
|
for i in range(len(templateParams.params)):
|
|
param = templateParams.params[i]
|
|
arg = templateArgs.args[i]
|
|
# TODO: doing this by string manipulation is probably not the most efficient
|
|
paramName = str(param.name)
|
|
argTxt = str(arg)
|
|
isArgPackExpansion = argTxt.endswith('...')
|
|
if param.isPack != isArgPackExpansion:
|
|
return True
|
|
argName = argTxt[:-3] if isArgPackExpansion else argTxt
|
|
if paramName != argName:
|
|
return True
|
|
return False
|
|
|
|
|
|
class Symbol:
|
|
debug_indent = 0
|
|
debug_indent_string = " "
|
|
debug_lookup = False # overridden by the corresponding config value
|
|
debug_show_tree = False # overridden by the corresponding config value
|
|
|
|
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, 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.identOrOp
|
|
assert not self.templateParams
|
|
assert not self.templateArgs
|
|
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 | None,
|
|
identOrOp: ASTIdentifier | ASTOperator | None,
|
|
templateParams: ASTTemplateParams | ASTTemplateIntroduction | None,
|
|
templateArgs: Any, 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 = None
|
|
self.siblingBelow: Symbol | None = None
|
|
self.identOrOp = identOrOp
|
|
# Ensure the same symbol for `A` is created for:
|
|
#
|
|
# .. cpp:class:: template <typename T> class A
|
|
#
|
|
# and
|
|
#
|
|
# .. cpp:function:: template <typename T> int A<T>::foo()
|
|
if (templateArgs is not None and
|
|
not _is_specialization(templateParams, templateArgs)):
|
|
templateArgs = None
|
|
self.templateParams = templateParams # template<templateParams>
|
|
self.templateArgs = templateArgs # identifier<templateArgs>
|
|
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_template_and_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_template_and_function_params()
|
|
|
|
def _add_template_and_function_params(self) -> None:
|
|
if Symbol.debug_lookup:
|
|
Symbol.debug_indent += 1
|
|
Symbol.debug_print("_add_template_and_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 the template params
|
|
if self.templateParams:
|
|
for tp in self.templateParams.params:
|
|
if not tp.get_identifier():
|
|
continue
|
|
# only add a declaration if we our self are from a declaration
|
|
if self.declaration:
|
|
decl = ASTDeclaration(objectType='templateParam', declaration=tp)
|
|
else:
|
|
decl = None
|
|
nne = ASTNestedNameElement(tp.get_identifier(), None)
|
|
nn = ASTNestedName([nne], [False], rooted=False)
|
|
self._add_symbols(nn, [], decl, self.docname, self.line)
|
|
# add symbols for function parameters, if any
|
|
if self.declaration is not None and self.declaration.function_params is not None:
|
|
for fp in self.declaration.function_params:
|
|
if fp.arg is None:
|
|
continue
|
|
nn = fp.arg.name
|
|
if nn is None:
|
|
continue
|
|
# (comparing to the template params: we have checked that we are a declaration)
|
|
decl = ASTDeclaration(objectType='functionParam', declaration=fp)
|
|
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:
|
|
newChildren: list[Symbol] = []
|
|
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
|
|
newChildren.append(sChild)
|
|
self._children = newChildren
|
|
|
|
def get_all_symbols(self) -> Iterator[Any]:
|
|
yield self
|
|
for sChild in self._children:
|
|
yield from sChild.get_all_symbols()
|
|
|
|
@property
|
|
def children_recurse_anon(self) -> Generator[Symbol, None, None]:
|
|
for c in self._children:
|
|
yield c
|
|
if not c.identOrOp.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:
|
|
nne = ASTNestedNameElement(s.identOrOp, s.templateArgs)
|
|
if s.declaration is not None:
|
|
key.append((nne, s.templateParams, s.declaration.get_newest_id()))
|
|
else:
|
|
key.append((nne, s.templateParams, 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 = []
|
|
templates = []
|
|
for s in symbols:
|
|
names.append(ASTNestedNameElement(s.identOrOp, s.templateArgs))
|
|
templates.append(False)
|
|
return ASTNestedName(names, templates, rooted=False)
|
|
|
|
def _find_first_named_symbol(self, identOrOp: ASTIdentifier | ASTOperator,
|
|
templateParams: Any, templateArgs: ASTTemplateArgs,
|
|
templateShorthand: bool, matchSelf: bool,
|
|
recurseInAnon: bool, correctPrimaryTemplateArgs: bool,
|
|
) -> Symbol:
|
|
if Symbol.debug_lookup:
|
|
Symbol.debug_print("_find_first_named_symbol ->")
|
|
res = self._find_named_symbols(identOrOp, templateParams, templateArgs,
|
|
templateShorthand, matchSelf, recurseInAnon,
|
|
correctPrimaryTemplateArgs,
|
|
searchInSiblings=False)
|
|
try:
|
|
return next(res)
|
|
except StopIteration:
|
|
return None
|
|
|
|
def _find_named_symbols(self, identOrOp: ASTIdentifier | ASTOperator,
|
|
templateParams: Any, templateArgs: ASTTemplateArgs,
|
|
templateShorthand: bool, matchSelf: bool,
|
|
recurseInAnon: bool, correctPrimaryTemplateArgs: bool,
|
|
searchInSiblings: bool) -> Iterator[Symbol]:
|
|
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("identOrOp: ", identOrOp)
|
|
Symbol.debug_print("templateParams: ", templateParams)
|
|
Symbol.debug_print("templateArgs: ", templateArgs)
|
|
Symbol.debug_print("templateShorthand: ", templateShorthand)
|
|
Symbol.debug_print("matchSelf: ", matchSelf)
|
|
Symbol.debug_print("recurseInAnon: ", recurseInAnon)
|
|
Symbol.debug_print("correctPrimaryTemplateAargs:", correctPrimaryTemplateArgs)
|
|
Symbol.debug_print("searchInSiblings: ", searchInSiblings)
|
|
|
|
if correctPrimaryTemplateArgs:
|
|
if templateParams is not None and templateArgs is not None:
|
|
# If both are given, but it's not a specialization, then do lookup as if
|
|
# there is no argument list.
|
|
# For example: template<typename T> int A<T>::var;
|
|
if not _is_specialization(templateParams, templateArgs):
|
|
templateArgs = None
|
|
|
|
def matches(s: Symbol) -> bool:
|
|
if s.identOrOp != identOrOp:
|
|
return False
|
|
if (s.templateParams is None) != (templateParams is None):
|
|
if templateParams is not None:
|
|
# we query with params, they must match params
|
|
return False
|
|
if not templateShorthand:
|
|
# we don't query with params, and we do care about them
|
|
return False
|
|
if templateParams:
|
|
# TODO: do better comparison
|
|
if str(s.templateParams) != str(templateParams):
|
|
return False
|
|
if (s.templateArgs is None) != (templateArgs is None):
|
|
return False
|
|
if s.templateArgs:
|
|
# TODO: do better comparison
|
|
if str(s.templateArgs) != str(templateArgs):
|
|
return False
|
|
return True
|
|
|
|
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 matches(s):
|
|
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,
|
|
templateDecls: list[Any],
|
|
onMissingQualifiedSymbol: Callable[
|
|
[Symbol, ASTIdentifier | ASTOperator, Any, ASTTemplateArgs], Symbol | None,
|
|
],
|
|
strictTemplateParamArgLists: bool, ancestorLookupType: str,
|
|
templateShorthand: bool, matchSelf: bool,
|
|
recurseInAnon: bool, correctPrimaryTemplateArgs: bool,
|
|
searchInSiblings: bool,
|
|
) -> SymbolLookupResult:
|
|
# 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("templateDecls: ", ",".join(str(t) for t in templateDecls))
|
|
Symbol.debug_print("strictTemplateParamArgLists:", strictTemplateParamArgLists)
|
|
Symbol.debug_print("ancestorLookupType:", ancestorLookupType)
|
|
Symbol.debug_print("templateShorthand: ", templateShorthand)
|
|
Symbol.debug_print("matchSelf: ", matchSelf)
|
|
Symbol.debug_print("recurseInAnon: ", recurseInAnon)
|
|
Symbol.debug_print("correctPrimaryTemplateArgs: ", correctPrimaryTemplateArgs)
|
|
Symbol.debug_print("searchInSiblings: ", searchInSiblings)
|
|
|
|
if strictTemplateParamArgLists:
|
|
# Each template argument list must have a template parameter list.
|
|
# But to declare a template there must be an additional template parameter list.
|
|
assert (nestedName.num_templates() == len(templateDecls) or
|
|
nestedName.num_templates() + 1 == len(templateDecls))
|
|
else:
|
|
assert len(templateDecls) <= nestedName.num_templates() + 1
|
|
|
|
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]
|
|
if not firstName.is_operator():
|
|
while parentSymbol.parent:
|
|
if parentSymbol.find_identifier(firstName.identOrOp,
|
|
matchSelf=matchSelf,
|
|
recurseInAnon=recurseInAnon,
|
|
searchInSiblings=searchInSiblings):
|
|
# if we are in the scope of a constructor but wants to
|
|
# reference the class we need to walk one extra up
|
|
if (len(names) == 1 and ancestorLookupType == 'class' and matchSelf and
|
|
parentSymbol.parent and
|
|
parentSymbol.parent.identOrOp == firstName.identOrOp):
|
|
pass
|
|
else:
|
|
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
|
|
iTemplateDecl = 0
|
|
for name in names[:-1]:
|
|
identOrOp = name.identOrOp
|
|
templateArgs = name.templateArgs
|
|
if strictTemplateParamArgLists:
|
|
# there must be a parameter list
|
|
if templateArgs:
|
|
assert iTemplateDecl < len(templateDecls)
|
|
templateParams = templateDecls[iTemplateDecl]
|
|
iTemplateDecl += 1
|
|
else:
|
|
templateParams = None
|
|
else:
|
|
# take the next template parameter list if there is one
|
|
# otherwise it's ok
|
|
if templateArgs and iTemplateDecl < len(templateDecls):
|
|
templateParams = templateDecls[iTemplateDecl]
|
|
iTemplateDecl += 1
|
|
else:
|
|
templateParams = None
|
|
|
|
symbol = parentSymbol._find_first_named_symbol(
|
|
identOrOp,
|
|
templateParams, templateArgs,
|
|
templateShorthand=templateShorthand,
|
|
matchSelf=matchSelf,
|
|
recurseInAnon=recurseInAnon,
|
|
correctPrimaryTemplateArgs=correctPrimaryTemplateArgs)
|
|
if symbol is None:
|
|
symbol = onMissingQualifiedSymbol(parentSymbol, identOrOp,
|
|
templateParams, templateArgs)
|
|
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
|
|
name = names[-1]
|
|
identOrOp = name.identOrOp
|
|
templateArgs = name.templateArgs
|
|
if iTemplateDecl < len(templateDecls):
|
|
assert iTemplateDecl + 1 == len(templateDecls)
|
|
templateParams = templateDecls[iTemplateDecl]
|
|
else:
|
|
assert iTemplateDecl == len(templateDecls)
|
|
templateParams = None
|
|
|
|
symbols = parentSymbol._find_named_symbols(
|
|
identOrOp, templateParams, templateArgs,
|
|
templateShorthand=templateShorthand, matchSelf=matchSelf,
|
|
recurseInAnon=recurseInAnon, correctPrimaryTemplateArgs=False,
|
|
searchInSiblings=searchInSiblings)
|
|
if Symbol.debug_lookup:
|
|
symbols = list(symbols) # type: ignore[assignment]
|
|
Symbol.debug_indent -= 2
|
|
return SymbolLookupResult(symbols, parentSymbol,
|
|
identOrOp, templateParams, templateArgs)
|
|
|
|
def _add_symbols(self, nestedName: ASTNestedName, templateDecls: list[Any],
|
|
declaration: ASTDeclaration, docname: str, line: int) -> Symbol:
|
|
# 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("tdecls:", ",".join(str(t) for t in templateDecls))
|
|
Symbol.debug_print("nn: ", nestedName)
|
|
Symbol.debug_print("decl: ", declaration)
|
|
Symbol.debug_print(f"location: {docname}:{line}")
|
|
|
|
def onMissingQualifiedSymbol(parentSymbol: Symbol,
|
|
identOrOp: ASTIdentifier | ASTOperator,
|
|
templateParams: Any, templateArgs: ASTTemplateArgs,
|
|
) -> Symbol | None:
|
|
if Symbol.debug_lookup:
|
|
Symbol.debug_indent += 1
|
|
Symbol.debug_print("_add_symbols, onMissingQualifiedSymbol:")
|
|
Symbol.debug_indent += 1
|
|
Symbol.debug_print("templateParams:", templateParams)
|
|
Symbol.debug_print("identOrOp: ", identOrOp)
|
|
Symbol.debug_print("templateARgs: ", templateArgs)
|
|
Symbol.debug_indent -= 2
|
|
return Symbol(parent=parentSymbol, identOrOp=identOrOp,
|
|
templateParams=templateParams,
|
|
templateArgs=templateArgs, declaration=None,
|
|
docname=None, line=None)
|
|
|
|
lookupResult = self._symbol_lookup(nestedName, templateDecls,
|
|
onMissingQualifiedSymbol,
|
|
strictTemplateParamArgLists=True,
|
|
ancestorLookupType=None,
|
|
templateShorthand=False,
|
|
matchSelf=False,
|
|
recurseInAnon=False,
|
|
correctPrimaryTemplateArgs=True,
|
|
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("templateParams:", lookupResult.templateParams)
|
|
Symbol.debug_print("identOrOp: ", lookupResult.identOrOp)
|
|
Symbol.debug_print("templateArgs: ", lookupResult.templateArgs)
|
|
Symbol.debug_print("declaration: ", declaration)
|
|
Symbol.debug_print(f"location: {docname}:{line}")
|
|
Symbol.debug_indent -= 1
|
|
symbol = Symbol(parent=lookupResult.parentSymbol,
|
|
identOrOp=lookupResult.identOrOp,
|
|
templateParams=lookupResult.templateParams,
|
|
templateArgs=lookupResult.templateArgs,
|
|
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,
|
|
identOrOp=lookupResult.identOrOp,
|
|
templateParams=lookupResult.templateParams,
|
|
templateArgs=lookupResult.templateArgs,
|
|
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:
|
|
# but all existing must be functions as well,
|
|
# otherwise we declare it to be a duplicate
|
|
if symbol.declaration.objectType != 'function':
|
|
handleDuplicateDeclaration(symbol, candSymbol)
|
|
# (not reachable)
|
|
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")
|
|
if candSymbol is not None:
|
|
Symbol.debug_print("result is already created candSymbol")
|
|
else:
|
|
Symbol.debug_print("result is makeCandSymbol()")
|
|
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
|
|
|
|
def unconditionalAdd(self, otherChild):
|
|
# TODO: hmm, should we prune by docnames?
|
|
self._children.append(otherChild)
|
|
otherChild.parent = self
|
|
otherChild._assert_invariants()
|
|
|
|
if Symbol.debug_lookup:
|
|
Symbol.debug_indent += 1
|
|
for otherChild in other._children:
|
|
if Symbol.debug_lookup:
|
|
Symbol.debug_print("otherChild:\n", otherChild.to_string(Symbol.debug_indent))
|
|
Symbol.debug_indent += 1
|
|
if otherChild.isRedeclaration:
|
|
unconditionalAdd(self, otherChild)
|
|
if Symbol.debug_lookup:
|
|
Symbol.debug_print("isRedeclaration")
|
|
Symbol.debug_indent -= 1
|
|
continue
|
|
candiateIter = self._find_named_symbols(
|
|
identOrOp=otherChild.identOrOp,
|
|
templateParams=otherChild.templateParams,
|
|
templateArgs=otherChild.templateArgs,
|
|
templateShorthand=False, matchSelf=False,
|
|
recurseInAnon=False, correctPrimaryTemplateArgs=False,
|
|
searchInSiblings=False)
|
|
candidates = list(candiateIter)
|
|
|
|
if Symbol.debug_lookup:
|
|
Symbol.debug_print("raw candidate symbols:", len(candidates))
|
|
symbols = [s for s in candidates if not s.isRedeclaration]
|
|
if Symbol.debug_lookup:
|
|
Symbol.debug_print("non-duplicate candidate symbols:", len(symbols))
|
|
|
|
if len(symbols) == 0:
|
|
unconditionalAdd(self, otherChild)
|
|
if Symbol.debug_lookup:
|
|
Symbol.debug_indent -= 1
|
|
continue
|
|
|
|
ourChild = None
|
|
if otherChild.declaration is None:
|
|
if Symbol.debug_lookup:
|
|
Symbol.debug_print("no declaration in other child")
|
|
ourChild = symbols[0]
|
|
else:
|
|
queryId = otherChild.declaration.get_newest_id()
|
|
if Symbol.debug_lookup:
|
|
Symbol.debug_print("queryId: ", queryId)
|
|
for symbol in symbols:
|
|
if symbol.declaration is None:
|
|
if Symbol.debug_lookup:
|
|
Symbol.debug_print("empty candidate")
|
|
# if in the end we have non-matching, but have an empty one,
|
|
# then just continue with that
|
|
ourChild = symbol
|
|
continue
|
|
candId = symbol.declaration.get_newest_id()
|
|
if Symbol.debug_lookup:
|
|
Symbol.debug_print("candidate:", candId)
|
|
if candId == queryId:
|
|
ourChild = symbol
|
|
break
|
|
if Symbol.debug_lookup:
|
|
Symbol.debug_indent -= 1
|
|
if ourChild is None:
|
|
unconditionalAdd(self, otherChild)
|
|
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 '.. cpp:%s:: %s'.")
|
|
msg = msg % (ourChild.docname, ourChild.line,
|
|
ourChild.declaration.directiveType, name)
|
|
logger.warning(msg, location=(otherChild.docname, otherChild.line))
|
|
else:
|
|
if (otherChild.declaration.objectType ==
|
|
ourChild.declaration.objectType and
|
|
otherChild.declaration.objectType in
|
|
('templateParam', 'functionParam') and
|
|
ourChild.parent.declaration == otherChild.parent.declaration):
|
|
# `ourChild` was just created during merging by the call
|
|
# to `_fill_empty` on the parent and can be ignored.
|
|
pass
|
|
else:
|
|
# Both have declarations, and in the same docname.
|
|
# This can apparently happen, it should be safe to
|
|
# just ignore it, right?
|
|
# Hmm, only on duplicate declarations, right?
|
|
msg = "Internal C++ domain error during symbol merging.\n"
|
|
msg += "ourChild:\n" + ourChild.to_string(1)
|
|
msg += "\notherChild:\n" + otherChild.to_string(1)
|
|
logger.warning(msg, location=otherChild.docname)
|
|
ourChild.merge_with(otherChild, docnames, env)
|
|
if Symbol.debug_lookup:
|
|
Symbol.debug_indent -= 2
|
|
|
|
def add_name(self, nestedName: ASTNestedName,
|
|
templatePrefix: ASTTemplateDeclarationPrefix | None = None) -> Symbol:
|
|
if Symbol.debug_lookup:
|
|
Symbol.debug_indent += 1
|
|
Symbol.debug_print("add_name:")
|
|
if templatePrefix:
|
|
templateDecls = templatePrefix.templates
|
|
else:
|
|
templateDecls = []
|
|
res = self._add_symbols(nestedName, templateDecls,
|
|
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
|
|
if declaration.templatePrefix:
|
|
templateDecls = declaration.templatePrefix.templates
|
|
else:
|
|
templateDecls = []
|
|
res = self._add_symbols(nestedName, templateDecls, declaration, docname, line)
|
|
if Symbol.debug_lookup:
|
|
Symbol.debug_indent -= 1
|
|
return res
|
|
|
|
def find_identifier(self, identOrOp: ASTIdentifier | ASTOperator,
|
|
matchSelf: bool, recurseInAnon: bool, searchInSiblings: bool,
|
|
) -> Symbol:
|
|
if Symbol.debug_lookup:
|
|
Symbol.debug_indent += 1
|
|
Symbol.debug_print("find_identifier:")
|
|
Symbol.debug_indent += 1
|
|
Symbol.debug_print("identOrOp: ", identOrOp)
|
|
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.identOrOp == identOrOp:
|
|
return current
|
|
children = current.children_recurse_anon if recurseInAnon else current._children
|
|
for s in children:
|
|
if s.identOrOp == identOrOp:
|
|
return s
|
|
if not searchInSiblings:
|
|
break
|
|
current = current.siblingAbove
|
|
return None
|
|
|
|
def direct_lookup(self, key: LookupKey) -> Symbol:
|
|
if Symbol.debug_lookup:
|
|
Symbol.debug_indent += 1
|
|
Symbol.debug_print("direct_lookup:")
|
|
Symbol.debug_indent += 1
|
|
s = self
|
|
for name, templateParams, id_ in key.data:
|
|
if id_ is not None:
|
|
res = None
|
|
for cand in s._children:
|
|
if cand.declaration is None:
|
|
continue
|
|
if cand.declaration.get_newest_id() == id_:
|
|
res = cand
|
|
break
|
|
s = res
|
|
else:
|
|
identOrOp = name.identOrOp
|
|
templateArgs = name.templateArgs
|
|
s = s._find_first_named_symbol(identOrOp,
|
|
templateParams, templateArgs,
|
|
templateShorthand=False,
|
|
matchSelf=False,
|
|
recurseInAnon=False,
|
|
correctPrimaryTemplateArgs=False)
|
|
if Symbol.debug_lookup:
|
|
Symbol.debug_print("name: ", name)
|
|
Symbol.debug_print("templateParams:", templateParams)
|
|
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_name(self, nestedName: ASTNestedName, templateDecls: list[Any],
|
|
typ: str, templateShorthand: bool, matchSelf: bool,
|
|
recurseInAnon: bool, searchInSiblings: bool) -> tuple[list[Symbol], str]:
|
|
# templateShorthand: missing template parameter lists for templates is ok
|
|
# If the first component is None,
|
|
# then the second component _may_ be a string explaining why.
|
|
if Symbol.debug_lookup:
|
|
Symbol.debug_indent += 1
|
|
Symbol.debug_print("find_name:")
|
|
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("templateDecls: ", templateDecls)
|
|
Symbol.debug_print("typ: ", typ)
|
|
Symbol.debug_print("templateShorthand:", templateShorthand)
|
|
Symbol.debug_print("matchSelf: ", matchSelf)
|
|
Symbol.debug_print("recurseInAnon: ", recurseInAnon)
|
|
Symbol.debug_print("searchInSiblings: ", searchInSiblings)
|
|
|
|
class QualifiedSymbolIsTemplateParam(Exception):
|
|
pass
|
|
|
|
def onMissingQualifiedSymbol(parentSymbol: Symbol,
|
|
identOrOp: ASTIdentifier | ASTOperator,
|
|
templateParams: Any,
|
|
templateArgs: ASTTemplateArgs) -> Symbol | None:
|
|
# TODO: Maybe search without template args?
|
|
# Though, the correctPrimaryTemplateArgs does
|
|
# that for primary templates.
|
|
# Is there another case where it would be good?
|
|
if parentSymbol.declaration is not None:
|
|
if parentSymbol.declaration.objectType == 'templateParam':
|
|
raise QualifiedSymbolIsTemplateParam
|
|
return None
|
|
|
|
try:
|
|
lookupResult = self._symbol_lookup(nestedName, templateDecls,
|
|
onMissingQualifiedSymbol,
|
|
strictTemplateParamArgLists=False,
|
|
ancestorLookupType=typ,
|
|
templateShorthand=templateShorthand,
|
|
matchSelf=matchSelf,
|
|
recurseInAnon=recurseInAnon,
|
|
correctPrimaryTemplateArgs=False,
|
|
searchInSiblings=searchInSiblings)
|
|
except QualifiedSymbolIsTemplateParam:
|
|
return None, "templateParamInQualified"
|
|
|
|
if lookupResult is None:
|
|
# if it was a part of the qualification that could not be found
|
|
if Symbol.debug_lookup:
|
|
Symbol.debug_indent -= 2
|
|
return None, None
|
|
|
|
res = list(lookupResult.symbols)
|
|
if len(res) != 0:
|
|
if Symbol.debug_lookup:
|
|
Symbol.debug_indent -= 2
|
|
return res, None
|
|
|
|
if lookupResult.parentSymbol.declaration is not None:
|
|
if lookupResult.parentSymbol.declaration.objectType == 'templateParam':
|
|
return None, "templateParamInQualified"
|
|
|
|
# try without template params and args
|
|
symbol = lookupResult.parentSymbol._find_first_named_symbol(
|
|
lookupResult.identOrOp, None, None,
|
|
templateShorthand=templateShorthand, matchSelf=matchSelf,
|
|
recurseInAnon=recurseInAnon, correctPrimaryTemplateArgs=False)
|
|
if Symbol.debug_lookup:
|
|
Symbol.debug_indent -= 2
|
|
if symbol is not None:
|
|
return [symbol], None
|
|
else:
|
|
return None, None
|
|
|
|
def find_declaration(self, declaration: ASTDeclaration, typ: str, templateShorthand: bool,
|
|
matchSelf: bool, recurseInAnon: bool) -> Symbol:
|
|
# templateShorthand: missing template parameter lists for templates is ok
|
|
if Symbol.debug_lookup:
|
|
Symbol.debug_indent += 1
|
|
Symbol.debug_print("find_declaration:")
|
|
nestedName = declaration.name
|
|
if declaration.templatePrefix:
|
|
templateDecls = declaration.templatePrefix.templates
|
|
else:
|
|
templateDecls = []
|
|
|
|
def onMissingQualifiedSymbol(parentSymbol: Symbol,
|
|
identOrOp: ASTIdentifier | ASTOperator,
|
|
templateParams: Any,
|
|
templateArgs: ASTTemplateArgs) -> Symbol | None:
|
|
return None
|
|
|
|
lookupResult = self._symbol_lookup(nestedName, templateDecls,
|
|
onMissingQualifiedSymbol,
|
|
strictTemplateParamArgLists=False,
|
|
ancestorLookupType=typ,
|
|
templateShorthand=templateShorthand,
|
|
matchSelf=matchSelf,
|
|
recurseInAnon=recurseInAnon,
|
|
correctPrimaryTemplateArgs=False,
|
|
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
|
|
|
|
querySymbol = Symbol(parent=lookupResult.parentSymbol,
|
|
identOrOp=lookupResult.identOrOp,
|
|
templateParams=lookupResult.templateParams,
|
|
templateArgs=lookupResult.templateArgs,
|
|
declaration=declaration,
|
|
docname='fakeDocnameForQuery',
|
|
line=42)
|
|
queryId = declaration.get_newest_id()
|
|
for symbol in symbols:
|
|
if symbol.declaration is None:
|
|
continue
|
|
candId = symbol.declaration.get_newest_id()
|
|
if candId == queryId:
|
|
querySymbol.remove()
|
|
return symbol
|
|
querySymbol.remove()
|
|
return None
|
|
|
|
def to_string(self, indent: int) -> str:
|
|
res = [Symbol.debug_indent_string * indent]
|
|
if not self.parent:
|
|
res.append('::')
|
|
else:
|
|
if self.templateParams:
|
|
res.append(str(self.templateParams))
|
|
res.append('\n')
|
|
res.append(Symbol.debug_indent_string * indent)
|
|
if self.identOrOp:
|
|
res.append(str(self.identOrOp))
|
|
else:
|
|
res.append(str(self.declaration))
|
|
if self.templateArgs:
|
|
res.append(str(self.templateArgs))
|
|
if self.declaration:
|
|
res.append(": ")
|
|
if self.isRedeclaration:
|
|
res.append('!!duplicate!! ')
|
|
res.append("{" + self.declaration.objectType + "} ")
|
|
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.cpp_id_attributes
|
|
|
|
@property
|
|
def paren_attributes(self):
|
|
return self.config.cpp_paren_attributes
|
|
|
|
def _parse_string(self) -> str:
|
|
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:
|
|
# -> integer-literal
|
|
# | character-literal
|
|
# | floating-literal
|
|
# | string-literal
|
|
# | boolean-literal -> "false" | "true"
|
|
# | pointer-literal -> "nullptr"
|
|
# | user-defined-literal
|
|
|
|
def _udl(literal: ASTLiteral) -> ASTLiteral:
|
|
if not self.match(udl_identifier_re):
|
|
return literal
|
|
# hmm, should we care if it's a keyword?
|
|
# it looks like GCC does not disallow keywords
|
|
ident = ASTIdentifier(self.matched_text)
|
|
return ASTUserDefinedLiteral(literal, ident)
|
|
|
|
self.skip_ws()
|
|
if self.skip_word('nullptr'):
|
|
return ASTPointerLiteral()
|
|
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):
|
|
hasSuffix = self.match(float_literal_suffix_re)
|
|
floatLit = ASTNumberLiteral(self.definition[pos:self.pos])
|
|
if hasSuffix:
|
|
return floatLit
|
|
else:
|
|
return _udl(floatLit)
|
|
for regex in [binary_literal_re, hex_literal_re,
|
|
integer_literal_re, octal_literal_re]:
|
|
if self.match(regex):
|
|
hasSuffix = self.match(integers_literal_suffix_re)
|
|
intLit = ASTNumberLiteral(self.definition[pos:self.pos])
|
|
if hasSuffix:
|
|
return intLit
|
|
else:
|
|
return _udl(intLit)
|
|
|
|
string = self._parse_string()
|
|
if string is not None:
|
|
return _udl(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:
|
|
charLit = 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 _udl(charLit)
|
|
return None
|
|
|
|
def _parse_fold_or_paren_expression(self) -> ASTExpression:
|
|
# "(" expression ")"
|
|
# fold-expression
|
|
# -> ( cast-expression fold-operator ... )
|
|
# | ( ... fold-operator cast-expression )
|
|
# | ( cast-expression fold-operator ... fold-operator cast-expression
|
|
if self.current_char != '(':
|
|
return None
|
|
self.pos += 1
|
|
self.skip_ws()
|
|
if self.skip_string_and_ws("..."):
|
|
# ( ... fold-operator cast-expression )
|
|
if not self.match(_fold_operator_re):
|
|
self.fail("Expected fold operator after '...' in fold expression.")
|
|
op = self.matched_text
|
|
rightExpr = self._parse_cast_expression()
|
|
if not self.skip_string(')'):
|
|
self.fail("Expected ')' in end of fold expression.")
|
|
return ASTFoldExpr(None, op, rightExpr)
|
|
# try first parsing a unary right fold, or a binary fold
|
|
pos = self.pos
|
|
try:
|
|
self.skip_ws()
|
|
leftExpr = self._parse_cast_expression()
|
|
self.skip_ws()
|
|
if not self.match(_fold_operator_re):
|
|
self.fail("Expected fold operator after left expression in fold expression.")
|
|
op = self.matched_text
|
|
self.skip_ws()
|
|
if not self.skip_string_and_ws('...'):
|
|
self.fail("Expected '...' after fold operator in fold expression.")
|
|
except DefinitionError as eFold:
|
|
self.pos = pos
|
|
# fall back to a paren expression
|
|
try:
|
|
res = self._parse_expression()
|
|
self.skip_ws()
|
|
if not self.skip_string(')'):
|
|
self.fail("Expected ')' in end of parenthesized expression.")
|
|
except DefinitionError as eExpr:
|
|
raise self._make_multi_error([
|
|
(eFold, "If fold expression"),
|
|
(eExpr, "If parenthesized expression"),
|
|
], "Error in fold expression or parenthesized expression.") from eExpr
|
|
return ASTParenExpr(res)
|
|
# now it definitely is a fold expression
|
|
if self.skip_string(')'):
|
|
return ASTFoldExpr(leftExpr, op, None)
|
|
if not self.match(_fold_operator_re):
|
|
self.fail("Expected fold operator or ')' after '...' in fold expression.")
|
|
if op != self.matched_text:
|
|
self.fail("Operators are different in binary fold: '%s' and '%s'."
|
|
% (op, self.matched_text))
|
|
rightExpr = self._parse_cast_expression()
|
|
self.skip_ws()
|
|
if not self.skip_string(')'):
|
|
self.fail("Expected ')' to end binary fold expression.")
|
|
return ASTFoldExpr(leftExpr, op, rightExpr)
|
|
|
|
def _parse_primary_expression(self) -> ASTExpression:
|
|
# literal
|
|
# "this"
|
|
# lambda-expression
|
|
# "(" expression ")"
|
|
# fold-expression
|
|
# id-expression -> we parse this with _parse_nested_name
|
|
self.skip_ws()
|
|
res: ASTExpression = self._parse_literal()
|
|
if res is not None:
|
|
return res
|
|
self.skip_ws()
|
|
if self.skip_word("this"):
|
|
return ASTThisLiteral()
|
|
# TODO: try lambda expression
|
|
res = self._parse_fold_or_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 | ASTBracedInitList],
|
|
bool]:
|
|
# Parse open and close with the actual initializer-list in between
|
|
# -> initializer-clause '...'[opt]
|
|
# | initializer-list ',' initializer-clause '...'[opt]
|
|
self.skip_ws()
|
|
if not self.skip_string_and_ws(open):
|
|
return None, None
|
|
if self.skip_string(close):
|
|
return [], False
|
|
|
|
exprs: list[ASTExpression | ASTBracedInitList] = []
|
|
trailingComma = False
|
|
while True:
|
|
self.skip_ws()
|
|
expr = self._parse_initializer_clause()
|
|
self.skip_ws()
|
|
if self.skip_string('...'):
|
|
exprs.append(ASTPackExpansionExpr(expr))
|
|
else:
|
|
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:
|
|
# -> '(' 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_initializer_clause(self) -> ASTExpression | ASTBracedInitList:
|
|
bracedInitList = self._parse_braced_init_list()
|
|
if bracedInitList is not None:
|
|
return bracedInitList
|
|
return self._parse_assignment_expression(inTemplate=False)
|
|
|
|
def _parse_braced_init_list(self) -> ASTBracedInitList:
|
|
# -> '{' initializer-list ','[opt] '}'
|
|
# | '{' '}'
|
|
exprs, trailingComma = self._parse_initializer_list("braced-init-list", '{', '}')
|
|
if exprs is None:
|
|
return None
|
|
return ASTBracedInitList(exprs, trailingComma)
|
|
|
|
def _parse_expression_list_or_braced_init_list(
|
|
self,
|
|
) -> ASTParenExprList | ASTBracedInitList:
|
|
paren = self._parse_paren_expression_list()
|
|
if paren is not None:
|
|
return paren
|
|
return self._parse_braced_init_list()
|
|
|
|
def _parse_postfix_expression(self) -> ASTPostfixExpr:
|
|
# -> primary
|
|
# | postfix "[" expression "]"
|
|
# | postfix "[" braced-init-list [opt] "]"
|
|
# | postfix "(" expression-list [opt] ")"
|
|
# | postfix "." "template" [opt] id-expression
|
|
# | postfix "->" "template" [opt] id-expression
|
|
# | postfix "." pseudo-destructor-name
|
|
# | postfix "->" pseudo-destructor-name
|
|
# | postfix "++"
|
|
# | postfix "--"
|
|
# | simple-type-specifier "(" expression-list [opt] ")"
|
|
# | simple-type-specifier braced-init-list
|
|
# | typename-specifier "(" expression-list [opt] ")"
|
|
# | typename-specifier braced-init-list
|
|
# | "dynamic_cast" "<" type-id ">" "(" expression ")"
|
|
# | "static_cast" "<" type-id ">" "(" expression ")"
|
|
# | "reinterpret_cast" "<" type-id ">" "(" expression ")"
|
|
# | "const_cast" "<" type-id ">" "(" expression ")"
|
|
# | "typeid" "(" expression ")"
|
|
# | "typeid" "(" type-id ")"
|
|
|
|
prefixType = None
|
|
prefix: Any = None
|
|
self.skip_ws()
|
|
|
|
cast = None
|
|
for c in _id_explicit_cast:
|
|
if self.skip_word_and_ws(c):
|
|
cast = c
|
|
break
|
|
if cast is not None:
|
|
prefixType = "cast"
|
|
if not self.skip_string("<"):
|
|
self.fail("Expected '<' after '%s'." % cast)
|
|
typ = self._parse_type(False)
|
|
self.skip_ws()
|
|
if not self.skip_string_and_ws(">"):
|
|
self.fail("Expected '>' after type in '%s'." % cast)
|
|
if not self.skip_string("("):
|
|
self.fail("Expected '(' in '%s'." % cast)
|
|
|
|
def parser() -> ASTExpression:
|
|
return self._parse_expression()
|
|
expr = self._parse_expression_fallback([')'], parser)
|
|
self.skip_ws()
|
|
if not self.skip_string(")"):
|
|
self.fail("Expected ')' to end '%s'." % cast)
|
|
prefix = ASTExplicitCast(cast, typ, expr)
|
|
elif self.skip_word_and_ws("typeid"):
|
|
prefixType = "typeid"
|
|
if not self.skip_string_and_ws('('):
|
|
self.fail("Expected '(' after 'typeid'.")
|
|
pos = self.pos
|
|
try:
|
|
typ = self._parse_type(False)
|
|
prefix = ASTTypeId(typ, isType=True)
|
|
if not self.skip_string(')'):
|
|
self.fail("Expected ')' to end 'typeid' of type.")
|
|
except DefinitionError as eType:
|
|
self.pos = pos
|
|
try:
|
|
|
|
def parser() -> ASTExpression:
|
|
return self._parse_expression()
|
|
expr = self._parse_expression_fallback([')'], parser)
|
|
prefix = ASTTypeId(expr, isType=False)
|
|
if not self.skip_string(')'):
|
|
self.fail("Expected ')' to end 'typeid' of expression.")
|
|
except DefinitionError as eExpr:
|
|
self.pos = pos
|
|
header = "Error in 'typeid(...)'."
|
|
header += " Expected type or expression."
|
|
errors = []
|
|
errors.append((eType, "If type"))
|
|
errors.append((eExpr, "If expression"))
|
|
raise self._make_multi_error(errors, header) from eExpr
|
|
else: # a primary expression or a type
|
|
pos = self.pos
|
|
try:
|
|
prefix = self._parse_primary_expression()
|
|
prefixType = 'expr'
|
|
except DefinitionError as eOuter:
|
|
self.pos = pos
|
|
try:
|
|
# we are potentially casting, so save parens for us
|
|
# TODO: hmm, would we need to try both with operatorCast and with None?
|
|
prefix = self._parse_type(False, 'operatorCast')
|
|
prefixType = 'typeOperatorCast'
|
|
# | simple-type-specifier "(" expression-list [opt] ")"
|
|
# | simple-type-specifier braced-init-list
|
|
# | typename-specifier "(" expression-list [opt] ")"
|
|
# | typename-specifier braced-init-list
|
|
self.skip_ws()
|
|
if self.current_char != '(' and self.current_char != '{':
|
|
self.fail("Expecting '(' or '{' after type in cast expression.")
|
|
except DefinitionError as eInner:
|
|
self.pos = pos
|
|
header = "Error in postfix expression,"
|
|
header += " expected primary expression or type."
|
|
errors = []
|
|
errors.append((eOuter, "If primary expression"))
|
|
errors.append((eInner, "If type"))
|
|
raise self._make_multi_error(errors, header) from eInner
|
|
|
|
# and now parse postfixes
|
|
postFixes: list[ASTPostfixOp] = []
|
|
while True:
|
|
self.skip_ws()
|
|
if prefixType in ('expr', 'cast', 'typeid'):
|
|
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 dot
|
|
self.pos -= 2
|
|
elif self.skip_string('..'):
|
|
# don't steal the dot
|
|
self.pos -= 3
|
|
else:
|
|
name = self._parse_nested_name()
|
|
postFixes.append(ASTPostfixMember(name))
|
|
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_expression_list_or_braced_init_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 ")"
|
|
# | "sizeof" "..." "(" identifier ")"
|
|
# | "alignof" "(" type-id ")"
|
|
# | noexcept-expression -> noexcept "(" expression ")"
|
|
# | new-expression
|
|
# | delete-expression
|
|
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('...'):
|
|
if not self.skip_string_and_ws('('):
|
|
self.fail("Expecting '(' after 'sizeof...'.")
|
|
if not self.match(identifier_re):
|
|
self.fail("Expecting identifier for 'sizeof...'.")
|
|
ident = ASTIdentifier(self.matched_text)
|
|
self.skip_ws()
|
|
if not self.skip_string(")"):
|
|
self.fail("Expecting ')' to end 'sizeof...'.")
|
|
return ASTSizeofParamPack(ident)
|
|
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)
|
|
if self.skip_word_and_ws('noexcept'):
|
|
if not self.skip_string_and_ws('('):
|
|
self.fail("Expecting '(' after 'noexcept'.")
|
|
expr = self._parse_expression()
|
|
self.skip_ws()
|
|
if not self.skip_string(')'):
|
|
self.fail("Expecting ')' to end 'noexcept'.")
|
|
return ASTNoexceptExpr(expr)
|
|
# new-expression
|
|
pos = self.pos
|
|
rooted = self.skip_string('::')
|
|
self.skip_ws()
|
|
if not self.skip_word_and_ws('new'):
|
|
self.pos = pos
|
|
else:
|
|
# new-placement[opt] new-type-id new-initializer[opt]
|
|
# new-placement[opt] ( type-id ) new-initializer[opt]
|
|
isNewTypeId = True
|
|
if self.skip_string_and_ws('('):
|
|
# either this is a new-placement or it's the second production
|
|
# without placement, and it's actually the ( type-id ) part
|
|
self.fail("Sorry, neither new-placement nor parenthesised type-id "
|
|
"in new-epression is supported yet.")
|
|
# set isNewTypeId = False if it's (type-id)
|
|
if isNewTypeId:
|
|
declSpecs = self._parse_decl_specs(outer=None)
|
|
decl = self._parse_declarator(named=False, paramMode="new")
|
|
else:
|
|
self.fail("Sorry, parenthesised type-id in new expression not yet supported.")
|
|
lst = self._parse_expression_list_or_braced_init_list()
|
|
return ASTNewExpr(rooted, isNewTypeId, ASTType(declSpecs, decl), lst)
|
|
# delete-expression
|
|
pos = self.pos
|
|
rooted = self.skip_string('::')
|
|
self.skip_ws()
|
|
if not self.skip_word_and_ws('delete'):
|
|
self.pos = pos
|
|
else:
|
|
array = self.skip_string_and_ws('[')
|
|
if array and not self.skip_string_and_ws(']'):
|
|
self.fail("Expected ']' in array delete-expression.")
|
|
expr = self._parse_cast_expression()
|
|
return ASTDeleteExpr(rooted, array, expr)
|
|
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, inTemplate: bool) -> ASTExpression:
|
|
# logical-or = logical-and ||
|
|
# logical-and = inclusive-or &&
|
|
# inclusive-or = exclusive-or |
|
|
# exclusive-or = and ^
|
|
# and = equality &
|
|
# equality = relational ==, !=
|
|
# relational = shift <, >, <=, >=, <=>
|
|
# shift = additive <<, >>
|
|
# additive = multiplicative +, -
|
|
# multiplicative = pm *, /, %
|
|
# pm = cast .*, ->*
|
|
def _parse_bin_op_expr(self: DefinitionParser,
|
|
opId: int, inTemplate: bool) -> ASTExpression:
|
|
if opId + 1 == len(_expression_bin_ops):
|
|
def parser(inTemplate: bool) -> ASTExpression:
|
|
return self._parse_cast_expression()
|
|
else:
|
|
def parser(inTemplate: bool) -> ASTExpression:
|
|
return _parse_bin_op_expr(self, opId + 1, inTemplate=inTemplate)
|
|
exprs = []
|
|
ops = []
|
|
exprs.append(parser(inTemplate=inTemplate))
|
|
while True:
|
|
self.skip_ws()
|
|
if inTemplate and self.current_char == '>':
|
|
break
|
|
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(inTemplate=inTemplate)
|
|
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, inTemplate=inTemplate)
|
|
|
|
def _parse_conditional_expression_tail(self, orExprHead: ASTExpression,
|
|
inTemplate: bool) -> ASTConditionalExpr | None:
|
|
# Consumes the orExprHead on success.
|
|
|
|
# -> "?" expression ":" assignment-expression
|
|
self.skip_ws()
|
|
if not self.skip_string("?"):
|
|
return None
|
|
thenExpr = self._parse_expression()
|
|
self.skip_ws()
|
|
if not self.skip_string(":"):
|
|
self.fail('Expected ":" after then-expression in conditional expression.')
|
|
elseExpr = self._parse_assignment_expression(inTemplate)
|
|
return ASTConditionalExpr(orExprHead, thenExpr, elseExpr)
|
|
|
|
def _parse_assignment_expression(self, inTemplate: bool) -> ASTExpression:
|
|
# -> conditional-expression
|
|
# | logical-or-expression assignment-operator initializer-clause
|
|
# | yield-expression -> "co_yield" assignment-expression
|
|
# | "co_yield" braced-init-list
|
|
# | throw-expression -> "throw" assignment-expression[opt]
|
|
# TODO: yield-expression
|
|
# TODO: throw-expression
|
|
|
|
# Now we have (after expanding conditional-expression:
|
|
# logical-or-expression
|
|
# | logical-or-expression "?" expression ":" assignment-expression
|
|
# | logical-or-expression assignment-operator initializer-clause
|
|
leftExpr = self._parse_logical_or_expression(inTemplate=inTemplate)
|
|
# the ternary operator
|
|
condExpr = self._parse_conditional_expression_tail(leftExpr, inTemplate)
|
|
if condExpr is not None:
|
|
return condExpr
|
|
# and actual assignment
|
|
for op in _expression_assignment_ops:
|
|
if op[0] in 'anox':
|
|
if not self.skip_word(op):
|
|
continue
|
|
else:
|
|
if not self.skip_string(op):
|
|
continue
|
|
rightExpr = self._parse_initializer_clause()
|
|
return ASTAssignmentExpr(leftExpr, op, rightExpr)
|
|
# just a logical-or-expression
|
|
return leftExpr
|
|
|
|
def _parse_constant_expression(self, inTemplate: bool) -> ASTExpression:
|
|
# -> conditional-expression ->
|
|
# logical-or-expression
|
|
# | logical-or-expression "?" expression ":" assignment-expression
|
|
orExpr = self._parse_logical_or_expression(inTemplate=inTemplate)
|
|
condExpr = self._parse_conditional_expression_tail(orExpr, inTemplate)
|
|
if condExpr is not None:
|
|
return condExpr
|
|
return orExpr
|
|
|
|
def _parse_expression(self) -> ASTExpression:
|
|
# -> assignment-expression
|
|
# | expression "," assignment-expression
|
|
exprs = [self._parse_assignment_expression(inTemplate=False)]
|
|
while True:
|
|
self.skip_ws()
|
|
if not self.skip_string(','):
|
|
break
|
|
exprs.append(self._parse_assignment_expression(inTemplate=False))
|
|
if len(exprs) == 1:
|
|
return exprs[0]
|
|
else:
|
|
return ASTCommaExpr(exprs)
|
|
|
|
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_operator(self) -> ASTOperator:
|
|
self.skip_ws()
|
|
# adapted from the old code
|
|
# yay, a regular operator definition
|
|
if self.match(_operator_re):
|
|
return ASTOperatorBuildIn(self.matched_text)
|
|
|
|
# new/delete operator?
|
|
for op in 'new', 'delete':
|
|
if not self.skip_word(op):
|
|
continue
|
|
self.skip_ws()
|
|
if self.skip_string('['):
|
|
self.skip_ws()
|
|
if not self.skip_string(']'):
|
|
self.fail('Expected "]" after "operator ' + op + '["')
|
|
op += '[]'
|
|
return ASTOperatorBuildIn(op)
|
|
|
|
# user-defined literal?
|
|
if self.skip_string('""'):
|
|
self.skip_ws()
|
|
if not self.match(identifier_re):
|
|
self.fail("Expected user-defined literal suffix.")
|
|
identifier = ASTIdentifier(self.matched_text)
|
|
return ASTOperatorLiteral(identifier)
|
|
|
|
# oh well, looks like a cast operator definition.
|
|
# In that case, eat another type.
|
|
type = self._parse_type(named=False, outer="operatorCast")
|
|
return ASTOperatorType(type)
|
|
|
|
def _parse_template_argument_list(self) -> ASTTemplateArgs:
|
|
# template-argument-list: (but we include the < and > here
|
|
# template-argument ...[opt]
|
|
# template-argument-list, template-argument ...[opt]
|
|
# template-argument:
|
|
# constant-expression
|
|
# type-id
|
|
# id-expression
|
|
self.skip_ws()
|
|
if not self.skip_string_and_ws('<'):
|
|
return None
|
|
if self.skip_string('>'):
|
|
return ASTTemplateArgs([], False)
|
|
prevErrors = []
|
|
templateArgs: list[ASTType | ASTTemplateArgConstant] = []
|
|
packExpansion = False
|
|
while 1:
|
|
pos = self.pos
|
|
parsedComma = False
|
|
parsedEnd = False
|
|
try:
|
|
type = self._parse_type(named=False)
|
|
self.skip_ws()
|
|
if self.skip_string_and_ws('...'):
|
|
packExpansion = True
|
|
parsedEnd = True
|
|
if not self.skip_string('>'):
|
|
self.fail('Expected ">" after "..." in template argument list.')
|
|
elif self.skip_string('>'):
|
|
parsedEnd = True
|
|
elif self.skip_string(','):
|
|
parsedComma = True
|
|
else:
|
|
self.fail('Expected "...>", ">" or "," in template argument list.')
|
|
templateArgs.append(type)
|
|
except DefinitionError as e:
|
|
prevErrors.append((e, "If type argument"))
|
|
self.pos = pos
|
|
try:
|
|
value = self._parse_constant_expression(inTemplate=True)
|
|
self.skip_ws()
|
|
if self.skip_string_and_ws('...'):
|
|
packExpansion = True
|
|
parsedEnd = True
|
|
if not self.skip_string('>'):
|
|
self.fail('Expected ">" after "..." in template argument list.')
|
|
elif self.skip_string('>'):
|
|
parsedEnd = True
|
|
elif self.skip_string(','):
|
|
parsedComma = True
|
|
else:
|
|
self.fail('Expected "...>", ">" or "," in template argument list.')
|
|
templateArgs.append(ASTTemplateArgConstant(value))
|
|
except DefinitionError as e:
|
|
self.pos = pos
|
|
prevErrors.append((e, "If non-type argument"))
|
|
header = "Error in parsing template argument list."
|
|
raise self._make_multi_error(prevErrors, header) from e
|
|
if parsedEnd:
|
|
assert not parsedComma
|
|
break
|
|
assert not packExpansion
|
|
return ASTTemplateArgs(templateArgs, packExpansion)
|
|
|
|
def _parse_nested_name(self, memberPointer: bool = False) -> ASTNestedName:
|
|
names: list[ASTNestedNameElement] = []
|
|
templates: list[bool] = []
|
|
|
|
self.skip_ws()
|
|
rooted = False
|
|
if self.skip_string('::'):
|
|
rooted = True
|
|
while 1:
|
|
self.skip_ws()
|
|
if len(names) > 0:
|
|
template = self.skip_word_and_ws('template')
|
|
else:
|
|
template = False
|
|
templates.append(template)
|
|
identOrOp: ASTIdentifier | ASTOperator = None
|
|
if self.skip_word_and_ws('operator'):
|
|
identOrOp = self._parse_operator()
|
|
else:
|
|
if not self.match(identifier_re):
|
|
if memberPointer and len(names) > 0:
|
|
templates.pop()
|
|
break
|
|
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)
|
|
identOrOp = ASTIdentifier(identifier)
|
|
# try greedily to get template arguments,
|
|
# but otherwise a < might be because we are in an expression
|
|
pos = self.pos
|
|
try:
|
|
templateArgs = self._parse_template_argument_list()
|
|
except DefinitionError as ex:
|
|
self.pos = pos
|
|
templateArgs = None
|
|
self.otherErrors.append(ex)
|
|
names.append(ASTNestedNameElement(identOrOp, templateArgs))
|
|
|
|
self.skip_ws()
|
|
if not self.skip_string('::'):
|
|
if memberPointer:
|
|
self.fail("Expected '::' in pointer to member (function).")
|
|
break
|
|
return ASTNestedName(names, templates, rooted)
|
|
|
|
# ==========================================================================
|
|
|
|
def _parse_simple_type_specifiers(self) -> ASTTrailingTypeSpecFundamental:
|
|
modifier: str | None = None
|
|
signedness: str | None = None
|
|
width: list[str] = []
|
|
typ: str | None = None
|
|
names: list[str] = [] # the parsed sequence
|
|
|
|
self.skip_ws()
|
|
while self.match(_simple_type_specifiers_re):
|
|
t = self.matched_text
|
|
names.append(t)
|
|
if t in ('auto', 'void', 'bool',
|
|
'char', 'wchar_t', 'char8_t', 'char16_t', 'char32_t',
|
|
'int', '__int64', '__int128',
|
|
'float', 'double',
|
|
'__float80', '_Float64x', '__float128', '_Float128'):
|
|
if typ is not None:
|
|
self.fail(f"Can not have both {t} and {typ}.")
|
|
typ = t
|
|
elif t in ('signed', 'unsigned'):
|
|
if signedness is not None:
|
|
self.fail(f"Can not have both {t} and {signedness}.")
|
|
signedness = t
|
|
elif t == 'short':
|
|
if len(width) != 0:
|
|
self.fail(f"Can not have both {t} and {width[0]}.")
|
|
width.append(t)
|
|
elif t == 'long':
|
|
if len(width) != 0 and width[0] != 'long':
|
|
self.fail(f"Can not have both {t} and {width[0]}.")
|
|
width.append(t)
|
|
elif t in ('_Imaginary', '_Complex'):
|
|
if modifier is not None:
|
|
self.fail(f"Can not have both {t} and {modifier}.")
|
|
modifier = t
|
|
self.skip_ws()
|
|
if len(names) == 0:
|
|
return None
|
|
|
|
if typ in ('auto', 'void', 'bool',
|
|
'wchar_t', 'char8_t', 'char16_t', 'char32_t',
|
|
'__float80', '_Float64x', '__float128', '_Float128'):
|
|
if modifier is not None:
|
|
self.fail(f"Can not have both {typ} and {modifier}.")
|
|
if signedness is not None:
|
|
self.fail(f"Can not have both {typ} and {signedness}.")
|
|
if len(width) != 0:
|
|
self.fail(f"Can not have both {typ} and {' '.join(width)}.")
|
|
elif typ == 'char':
|
|
if modifier is not None:
|
|
self.fail(f"Can not have both {typ} and {modifier}.")
|
|
if len(width) != 0:
|
|
self.fail(f"Can not have both {typ} and {' '.join(width)}.")
|
|
elif typ == 'int':
|
|
if modifier is not None:
|
|
self.fail(f"Can not have both {typ} and {modifier}.")
|
|
elif typ in ('__int64', '__int128'):
|
|
if modifier is not None:
|
|
self.fail(f"Can not have both {typ} and {modifier}.")
|
|
if len(width) != 0:
|
|
self.fail(f"Can not have both {typ} and {' '.join(width)}.")
|
|
elif typ == 'float':
|
|
if signedness is not None:
|
|
self.fail(f"Can not have both {typ} and {signedness}.")
|
|
if len(width) != 0:
|
|
self.fail(f"Can not have both {typ} and {' '.join(width)}.")
|
|
elif typ == 'double':
|
|
if signedness is not None:
|
|
self.fail(f"Can not have both {typ} and {signedness}.")
|
|
if len(width) > 1:
|
|
self.fail(f"Can not have both {typ} and {' '.join(width)}.")
|
|
if len(width) == 1 and width[0] != 'long':
|
|
self.fail(f"Can not have both {typ} and {' '.join(width)}.")
|
|
elif typ is None:
|
|
if modifier is not None:
|
|
self.fail(f"Can not have {modifier} without a floating point type.")
|
|
else:
|
|
msg = f'Unhandled type {typ}'
|
|
raise AssertionError(msg)
|
|
|
|
canonNames: list[str] = []
|
|
if modifier is not None:
|
|
canonNames.append(modifier)
|
|
if signedness is not None:
|
|
canonNames.append(signedness)
|
|
canonNames.extend(width)
|
|
if typ is not None:
|
|
canonNames.append(typ)
|
|
return ASTTrailingTypeSpecFundamental(names, canonNames)
|
|
|
|
def _parse_trailing_type_spec(self) -> ASTTrailingTypeSpec:
|
|
# fundamental types, https://en.cppreference.com/w/cpp/language/type
|
|
# and extensions
|
|
self.skip_ws()
|
|
res = self._parse_simple_type_specifiers()
|
|
if res is not None:
|
|
return res
|
|
|
|
# decltype
|
|
self.skip_ws()
|
|
if self.skip_word_and_ws('decltype'):
|
|
if not self.skip_string_and_ws('('):
|
|
self.fail("Expected '(' after 'decltype'.")
|
|
if self.skip_word_and_ws('auto'):
|
|
if not self.skip_string(')'):
|
|
self.fail("Expected ')' after 'decltype(auto'.")
|
|
return ASTTrailingTypeSpecDecltypeAuto()
|
|
expr = self._parse_expression()
|
|
self.skip_ws()
|
|
if not self.skip_string(')'):
|
|
self.fail("Expected ')' after 'decltype(<expr>'.")
|
|
return ASTTrailingTypeSpecDecltype(expr)
|
|
|
|
# prefixed
|
|
prefix = None
|
|
self.skip_ws()
|
|
for k in ('class', 'struct', 'enum', 'union', 'typename'):
|
|
if self.skip_word_and_ws(k):
|
|
prefix = k
|
|
break
|
|
nestedName = self._parse_nested_name()
|
|
self.skip_ws()
|
|
placeholderType = None
|
|
if self.skip_word('auto'):
|
|
placeholderType = 'auto'
|
|
elif self.skip_word_and_ws('decltype'):
|
|
if not self.skip_string_and_ws('('):
|
|
self.fail("Expected '(' after 'decltype' in placeholder type specifier.")
|
|
if not self.skip_word_and_ws('auto'):
|
|
self.fail("Expected 'auto' after 'decltype(' in placeholder type specifier.")
|
|
if not self.skip_string_and_ws(')'):
|
|
self.fail("Expected ')' after 'decltype(auto' in placeholder type specifier.")
|
|
placeholderType = 'decltype(auto)'
|
|
return ASTTrailingTypeSpecName(prefix, nestedName, placeholderType)
|
|
|
|
def _parse_parameters_and_qualifiers(self, paramMode: str) -> ASTParametersQualifiers:
|
|
if paramMode == 'new':
|
|
return None
|
|
self.skip_ws()
|
|
if not self.skip_string('('):
|
|
if paramMode == 'function':
|
|
self.fail('Expecting "(" in parameters-and-qualifiers.')
|
|
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-and-qualifiers.')
|
|
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('Expecting "," or ")" in parameters-and-qualifiers, '
|
|
f'got "{self.current_char}".')
|
|
|
|
self.skip_ws()
|
|
const = self.skip_word_and_ws('const')
|
|
volatile = self.skip_word_and_ws('volatile')
|
|
if not const: # the can be permuted
|
|
const = self.skip_word_and_ws('const')
|
|
|
|
refQual = None
|
|
if self.skip_string('&&'):
|
|
refQual = '&&'
|
|
if not refQual and self.skip_string('&'):
|
|
refQual = '&'
|
|
|
|
exceptionSpec = None
|
|
self.skip_ws()
|
|
if self.skip_string('noexcept'):
|
|
if self.skip_string_and_ws('('):
|
|
expr = self._parse_constant_expression(False)
|
|
self.skip_ws()
|
|
if not self.skip_string(')'):
|
|
self.fail("Expecting ')' to end 'noexcept'.")
|
|
exceptionSpec = ASTNoexceptSpec(expr)
|
|
else:
|
|
exceptionSpec = ASTNoexceptSpec(None)
|
|
|
|
self.skip_ws()
|
|
if self.skip_string('->'):
|
|
trailingReturn = self._parse_type(named=False)
|
|
else:
|
|
trailingReturn = None
|
|
|
|
self.skip_ws()
|
|
override = self.skip_word_and_ws('override')
|
|
final = self.skip_word_and_ws('final')
|
|
if not override:
|
|
override = self.skip_word_and_ws(
|
|
'override') # they can be permuted
|
|
|
|
attrs = self._parse_attribute_list()
|
|
|
|
self.skip_ws()
|
|
initializer = None
|
|
# if this is a function pointer we should not swallow an initializer
|
|
if paramMode == 'function' and self.skip_string('='):
|
|
self.skip_ws()
|
|
valid = ('0', 'delete', 'default')
|
|
for w in valid:
|
|
if self.skip_word_and_ws(w):
|
|
initializer = w
|
|
break
|
|
if not initializer:
|
|
self.fail(
|
|
'Expected "%s" in initializer-specifier.'
|
|
% '" or "'.join(valid))
|
|
|
|
return ASTParametersQualifiers(
|
|
args, volatile, const, refQual, exceptionSpec, trailingReturn,
|
|
override, final, attrs, initializer)
|
|
|
|
def _parse_decl_specs_simple(self, outer: str, typed: bool) -> ASTDeclSpecsSimple:
|
|
"""Just parse the simple ones."""
|
|
storage = None
|
|
threadLocal = None
|
|
inline = None
|
|
virtual = None
|
|
explicitSpec = None
|
|
consteval = None
|
|
constexpr = None
|
|
constinit = None
|
|
volatile = None
|
|
const = None
|
|
friend = None
|
|
attrs = []
|
|
while 1: # accept any permutation of a subset of some decl-specs
|
|
self.skip_ws()
|
|
if not const and typed:
|
|
const = self.skip_word('const')
|
|
if const:
|
|
continue
|
|
if not volatile and typed:
|
|
volatile = self.skip_word('volatile')
|
|
if volatile:
|
|
continue
|
|
if not storage:
|
|
if outer in ('member', 'function'):
|
|
if self.skip_word('static'):
|
|
storage = 'static'
|
|
continue
|
|
if self.skip_word('extern'):
|
|
storage = 'extern'
|
|
continue
|
|
if outer == 'member':
|
|
if self.skip_word('mutable'):
|
|
storage = 'mutable'
|
|
continue
|
|
if self.skip_word('register'):
|
|
storage = 'register'
|
|
continue
|
|
if not inline and outer in ('function', 'member'):
|
|
inline = self.skip_word('inline')
|
|
if inline:
|
|
continue
|
|
if not constexpr and outer in ('member', 'function'):
|
|
constexpr = self.skip_word("constexpr")
|
|
if constexpr:
|
|
continue
|
|
|
|
if outer == 'member':
|
|
if not constinit:
|
|
constinit = self.skip_word('constinit')
|
|
if constinit:
|
|
continue
|
|
if not threadLocal:
|
|
threadLocal = self.skip_word('thread_local')
|
|
if threadLocal:
|
|
continue
|
|
if outer == 'function':
|
|
if not consteval:
|
|
consteval = self.skip_word('consteval')
|
|
if consteval:
|
|
continue
|
|
if not friend:
|
|
friend = self.skip_word('friend')
|
|
if friend:
|
|
continue
|
|
if not virtual:
|
|
virtual = self.skip_word('virtual')
|
|
if virtual:
|
|
continue
|
|
if not explicitSpec:
|
|
explicit = self.skip_word_and_ws('explicit')
|
|
if explicit:
|
|
expr: ASTExpression = None
|
|
if self.skip_string('('):
|
|
expr = self._parse_constant_expression(inTemplate=False)
|
|
if not expr:
|
|
self.fail("Expected constant expression after '('" +
|
|
" in explicit specifier.")
|
|
self.skip_ws()
|
|
if not self.skip_string(')'):
|
|
self.fail("Expected ')' to end explicit specifier.")
|
|
explicitSpec = ASTExplicitSpec(expr)
|
|
continue
|
|
attr = self._parse_attribute()
|
|
if attr:
|
|
attrs.append(attr)
|
|
continue
|
|
break
|
|
return ASTDeclSpecsSimple(storage, threadLocal, inline, virtual,
|
|
explicitSpec, consteval, constexpr, constinit,
|
|
volatile, const, friend, ASTAttributeList(attrs))
|
|
|
|
def _parse_decl_specs(self, outer: str, typed: bool = True) -> ASTDeclSpecs:
|
|
if outer:
|
|
if outer not in ('type', 'member', 'function', 'templateParam'):
|
|
raise Exception('Internal error, unknown outer "%s".' % outer)
|
|
"""
|
|
storage-class-specifier function-specifier "constexpr"
|
|
"volatile" "const" trailing-type-specifier
|
|
|
|
storage-class-specifier ->
|
|
"static" (only for member_object and function_object)
|
|
| "register"
|
|
|
|
function-specifier -> "inline" | "virtual" | "explicit" (only for
|
|
function_object)
|
|
|
|
"constexpr" (only for member_object and function_object)
|
|
"""
|
|
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,
|
|
) -> ASTDeclaratorNameParamQual | ASTDeclaratorNameBitField:
|
|
# now we should parse the name, and then suffixes
|
|
if named == 'maybe':
|
|
pos = self.pos
|
|
try:
|
|
declId = self._parse_nested_name()
|
|
except DefinitionError:
|
|
self.pos = pos
|
|
declId = None
|
|
elif named == 'single':
|
|
if self.match(identifier_re):
|
|
identifier = ASTIdentifier(self.matched_text)
|
|
nne = ASTNestedNameElement(identifier, None)
|
|
declId = ASTNestedName([nne], [False], rooted=False)
|
|
# if it's a member pointer, we may have '::', which should be an error
|
|
self.skip_ws()
|
|
if self.current_char == ':':
|
|
self.fail("Unexpected ':' after identifier.")
|
|
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()
|
|
if self.skip_string(']'):
|
|
arrayOps.append(ASTArray(None))
|
|
continue
|
|
|
|
def parser() -> ASTExpression:
|
|
return self._parse_expression()
|
|
value = self._parse_expression_fallback([']'], parser)
|
|
if not self.skip_string(']'):
|
|
self.fail("Expected ']' in end of array operator.")
|
|
arrayOps.append(ASTArray(value))
|
|
continue
|
|
break
|
|
paramQual = self._parse_parameters_and_qualifiers(paramMode)
|
|
if paramQual 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(inTemplate=False)
|
|
return ASTDeclaratorNameBitField(declId=declId, size=size)
|
|
return ASTDeclaratorNameParamQual(declId=declId, arrayOps=arrayOps,
|
|
paramQual=paramQual)
|
|
|
|
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', 'operatorCast', 'new'):
|
|
raise Exception(
|
|
"Internal error, unknown paramMode '%s'." % paramMode)
|
|
prevErrors = []
|
|
self.skip_ws()
|
|
if typed and self.skip_string('*'):
|
|
self.skip_ws()
|
|
volatile = False
|
|
const = False
|
|
attrList = []
|
|
while 1:
|
|
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:
|
|
attrList.append(attr)
|
|
continue
|
|
break
|
|
next = self._parse_declarator(named, paramMode, typed)
|
|
return ASTDeclaratorPtr(next=next, volatile=volatile, const=const,
|
|
attrs=ASTAttributeList(attrList))
|
|
# TODO: shouldn't we parse an R-value ref here first?
|
|
if typed and self.skip_string("&"):
|
|
attrs = self._parse_attribute_list()
|
|
next = self._parse_declarator(named, paramMode, typed)
|
|
return ASTDeclaratorRef(next=next, attrs=attrs)
|
|
if typed and self.skip_string("..."):
|
|
next = self._parse_declarator(named, paramMode, False)
|
|
return ASTDeclaratorParamPack(next=next)
|
|
if typed and self.current_char == '(': # note: peeking, not skipping
|
|
if paramMode == "operatorCast":
|
|
# TODO: we should be able to parse cast operators which return
|
|
# function pointers. For now, just hax it and ignore.
|
|
return ASTDeclaratorNameParamQual(declId=None, arrayOps=[],
|
|
paramQual=None)
|
|
# maybe this is the beginning of params and quals,try that first,
|
|
# otherwise assume it's noptr->declarator > ( ptr-declarator )
|
|
pos = self.pos
|
|
try:
|
|
# assume this is params and quals
|
|
res = self._parse_declarator_name_suffix(named, paramMode,
|
|
typed)
|
|
return res
|
|
except DefinitionError as exParamQual:
|
|
prevErrors.append((exParamQual,
|
|
"If declarator-id with parameters-and-qualifiers"))
|
|
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 be
|
|
# 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
|
|
prevErrors.append((exNoPtrParen, "If parenthesis in noptr-declarator"))
|
|
header = "Error in declarator"
|
|
raise self._make_multi_error(prevErrors, header) from exNoPtrParen
|
|
if typed: # pointer to member
|
|
pos = self.pos
|
|
try:
|
|
name = self._parse_nested_name(memberPointer=True)
|
|
self.skip_ws()
|
|
if not self.skip_string('*'):
|
|
self.fail("Expected '*' in pointer to member declarator.")
|
|
self.skip_ws()
|
|
except DefinitionError as e:
|
|
self.pos = pos
|
|
prevErrors.append((e, "If pointer to member declarator"))
|
|
else:
|
|
volatile = False
|
|
const = False
|
|
while 1:
|
|
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
|
|
break
|
|
next = self._parse_declarator(named, paramMode, typed)
|
|
return ASTDeclaratorMemPtr(name, const, volatile, next=next)
|
|
pos = self.pos
|
|
try:
|
|
res = self._parse_declarator_name_suffix(named, paramMode, typed)
|
|
# this is a heuristic for error messages, for when there is a < after a
|
|
# nested name, but it was not a successful template argument list
|
|
if self.current_char == '<':
|
|
self.otherErrors.append(self._make_multi_error(prevErrors, ""))
|
|
return res
|
|
except DefinitionError as e:
|
|
self.pos = pos
|
|
prevErrors.append((e, "If declarator-id"))
|
|
header = "Error in declarator or parameters-and-qualifiers"
|
|
raise self._make_multi_error(prevErrors, header) from e
|
|
|
|
def _parse_initializer(self, outer: str | None = None, allowFallback: bool = True,
|
|
) -> ASTInitializer:
|
|
# initializer # global vars
|
|
# -> brace-or-equal-initializer
|
|
# | '(' expression-list ')'
|
|
#
|
|
# brace-or-equal-initializer # member vars
|
|
# -> '=' initializer-clause
|
|
# | braced-init-list
|
|
#
|
|
# initializer-clause # function params, non-type template params (with '=' in front)
|
|
# -> assignment-expression
|
|
# | braced-init-list
|
|
#
|
|
# we don't distinguish between global and member vars, so disallow paren:
|
|
#
|
|
# -> braced-init-list # var only
|
|
# | '=' assignment-expression
|
|
# | '=' braced-init-list
|
|
self.skip_ws()
|
|
if outer == 'member':
|
|
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 == 'templateParam':
|
|
fallbackEnd = [',', '>']
|
|
elif outer is None: # function parameter
|
|
fallbackEnd = [',', ')']
|
|
else:
|
|
self.fail("Internal error, initializer for outer '%s' not "
|
|
"implemented." % outer)
|
|
|
|
inTemplate = outer == 'templateParam'
|
|
|
|
def parser() -> ASTExpression:
|
|
return self._parse_assignment_expression(inTemplate=inTemplate)
|
|
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|'maybe'|True: 'maybe' is e.g., for function objects which
|
|
doesn't need to name the arguments
|
|
|
|
outer == operatorCast: annoying case, we should not take the params
|
|
"""
|
|
if outer: # always named
|
|
if outer not in ('type', 'member', 'function',
|
|
'operatorCast', 'templateParam'):
|
|
raise Exception('Internal error, unknown outer "%s".' % outer)
|
|
if outer != 'operatorCast':
|
|
assert named
|
|
if outer in ('type', 'function'):
|
|
# We allow type objects to just be a name.
|
|
# Some functions don't have normal return types: constructors,
|
|
# destructors, cast operators
|
|
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)
|
|
mustEnd = True
|
|
if outer == 'function':
|
|
# Allow trailing requires on functions.
|
|
self.skip_ws()
|
|
if re.compile(r'requires\b').match(self.definition, self.pos):
|
|
mustEnd = False
|
|
if mustEnd:
|
|
self.assert_end(allowSemicolon=True)
|
|
except DefinitionError as exUntyped:
|
|
if outer == 'type':
|
|
desc = "If just a name"
|
|
elif outer == 'function':
|
|
desc = "If the function has no return type"
|
|
else:
|
|
raise AssertionError from exUntyped
|
|
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
|
|
if outer == 'type':
|
|
desc = "If typedef-like declaration"
|
|
elif outer == 'function':
|
|
desc = "If the function has a return type"
|
|
else:
|
|
raise AssertionError from exUntyped
|
|
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:
|
|
if outer == 'type':
|
|
header = "Type must be either just a name or a "
|
|
header += "typedef-like declaration."
|
|
elif outer == 'function':
|
|
header = "Error when parsing function declaration."
|
|
else:
|
|
raise AssertionError from exUntyped
|
|
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)
|
|
else:
|
|
paramMode = 'type'
|
|
if outer == 'member':
|
|
named = True
|
|
elif outer == 'operatorCast':
|
|
paramMode = 'operatorCast'
|
|
outer = None
|
|
elif outer == 'templateParam':
|
|
named = 'single'
|
|
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) -> ASTTypeWithInit | ASTTemplateParamConstrainedTypeWithInit:
|
|
if outer:
|
|
assert outer in ('type', 'member', 'function', 'templateParam')
|
|
type = self._parse_type(outer=outer, named=named)
|
|
if outer != 'templateParam':
|
|
init = self._parse_initializer(outer=outer)
|
|
return ASTTypeWithInit(type, init)
|
|
# it could also be a constrained type parameter, e.g., C T = int&
|
|
pos = self.pos
|
|
eExpr = None
|
|
try:
|
|
init = self._parse_initializer(outer=outer, allowFallback=False)
|
|
# note: init may be None if there is no =
|
|
if init is None:
|
|
return ASTTypeWithInit(type, None)
|
|
# we parsed an expression, so we must have a , or a >,
|
|
# otherwise the expression didn't get everything
|
|
self.skip_ws()
|
|
if self.current_char != ',' and self.current_char != '>':
|
|
# pretend it didn't happen
|
|
self.pos = pos
|
|
init = None
|
|
else:
|
|
# we assume that it was indeed an expression
|
|
return ASTTypeWithInit(type, init)
|
|
except DefinitionError as e:
|
|
self.pos = pos
|
|
eExpr = e
|
|
if not self.skip_string("="):
|
|
return ASTTypeWithInit(type, None)
|
|
try:
|
|
typeInit = self._parse_type(named=False, outer=None)
|
|
return ASTTemplateParamConstrainedTypeWithInit(type, typeInit)
|
|
except DefinitionError as eType:
|
|
if eExpr is None:
|
|
raise
|
|
errs = []
|
|
errs.append((eExpr, "If default template argument is an expression"))
|
|
errs.append((eType, "If default template argument is a type"))
|
|
msg = "Error in non-type template parameter"
|
|
msg += " or constrained template parameter."
|
|
raise self._make_multi_error(errs, msg) from eType
|
|
|
|
def _parse_type_using(self) -> ASTTypeUsing:
|
|
name = self._parse_nested_name()
|
|
self.skip_ws()
|
|
if not self.skip_string('='):
|
|
return ASTTypeUsing(name, None)
|
|
type = self._parse_type(False, None)
|
|
return ASTTypeUsing(name, type)
|
|
|
|
def _parse_concept(self) -> ASTConcept:
|
|
nestedName = self._parse_nested_name()
|
|
self.skip_ws()
|
|
initializer = self._parse_initializer('member')
|
|
return ASTConcept(nestedName, initializer)
|
|
|
|
def _parse_class(self) -> ASTClass:
|
|
attrs = self._parse_attribute_list()
|
|
name = self._parse_nested_name()
|
|
self.skip_ws()
|
|
final = self.skip_word_and_ws('final')
|
|
bases = []
|
|
self.skip_ws()
|
|
if self.skip_string(':'):
|
|
while 1:
|
|
self.skip_ws()
|
|
visibility = None
|
|
virtual = False
|
|
pack = False
|
|
if self.skip_word_and_ws('virtual'):
|
|
virtual = True
|
|
if self.match(_visibility_re):
|
|
visibility = self.matched_text
|
|
self.skip_ws()
|
|
if not virtual and self.skip_word_and_ws('virtual'):
|
|
virtual = True
|
|
baseName = self._parse_nested_name()
|
|
self.skip_ws()
|
|
pack = self.skip_string('...')
|
|
bases.append(ASTBaseClass(baseName, visibility, virtual, pack))
|
|
self.skip_ws()
|
|
if self.skip_string(','):
|
|
continue
|
|
break
|
|
return ASTClass(name, final, bases, attrs)
|
|
|
|
def _parse_union(self) -> ASTUnion:
|
|
attrs = self._parse_attribute_list()
|
|
name = self._parse_nested_name()
|
|
return ASTUnion(name, attrs)
|
|
|
|
def _parse_enum(self) -> ASTEnum:
|
|
scoped = None # is set by CPPEnumObject
|
|
attrs = self._parse_attribute_list()
|
|
name = self._parse_nested_name()
|
|
self.skip_ws()
|
|
underlyingType = None
|
|
if self.skip_string(':'):
|
|
underlyingType = self._parse_type(named=False)
|
|
return ASTEnum(name, scoped, underlyingType, attrs)
|
|
|
|
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(inTemplate=False)
|
|
initVal = self._parse_expression_fallback([], parser)
|
|
init = ASTInitializer(initVal)
|
|
return ASTEnumerator(name, init, attrs)
|
|
|
|
# ==========================================================================
|
|
|
|
def _parse_template_parameter(self) -> ASTTemplateParam:
|
|
self.skip_ws()
|
|
if self.skip_word('template'):
|
|
# declare a template template parameter
|
|
nestedParams = self._parse_template_parameter_list()
|
|
else:
|
|
nestedParams = None
|
|
|
|
pos = self.pos
|
|
try:
|
|
# Unconstrained type parameter or template type parameter
|
|
key = None
|
|
self.skip_ws()
|
|
if self.skip_word_and_ws('typename'):
|
|
key = 'typename'
|
|
elif self.skip_word_and_ws('class'):
|
|
key = 'class'
|
|
elif nestedParams:
|
|
self.fail("Expected 'typename' or 'class' after "
|
|
"template template parameter list.")
|
|
else:
|
|
self.fail("Expected 'typename' or 'class' in the "
|
|
"beginning of template type parameter.")
|
|
self.skip_ws()
|
|
parameterPack = self.skip_string('...')
|
|
self.skip_ws()
|
|
if self.match(identifier_re):
|
|
identifier = ASTIdentifier(self.matched_text)
|
|
else:
|
|
identifier = None
|
|
self.skip_ws()
|
|
if not parameterPack and self.skip_string('='):
|
|
default = self._parse_type(named=False, outer=None)
|
|
else:
|
|
default = None
|
|
if self.current_char not in ',>':
|
|
self.fail('Expected "," or ">" after (template) type parameter.')
|
|
data = ASTTemplateKeyParamPackIdDefault(key, identifier,
|
|
parameterPack, default)
|
|
if nestedParams:
|
|
return ASTTemplateParamTemplateType(nestedParams, data)
|
|
else:
|
|
return ASTTemplateParamType(data)
|
|
except DefinitionError as eType:
|
|
if nestedParams:
|
|
raise
|
|
try:
|
|
# non-type parameter or constrained type parameter
|
|
self.pos = pos
|
|
param = self._parse_type_with_init('maybe', 'templateParam')
|
|
self.skip_ws()
|
|
parameterPack = self.skip_string('...')
|
|
return ASTTemplateParamNonType(param, parameterPack)
|
|
except DefinitionError as eNonType:
|
|
self.pos = pos
|
|
header = "Error when parsing template parameter."
|
|
errs = []
|
|
errs.append(
|
|
(eType, "If unconstrained type parameter or template type parameter"))
|
|
errs.append(
|
|
(eNonType, "If constrained type parameter or non-type parameter"))
|
|
raise self._make_multi_error(errs, header) from None
|
|
|
|
def _parse_template_parameter_list(self) -> ASTTemplateParams:
|
|
# only: '<' parameter-list '>'
|
|
# we assume that 'template' has just been parsed
|
|
templateParams: list[ASTTemplateParam] = []
|
|
self.skip_ws()
|
|
if not self.skip_string("<"):
|
|
self.fail("Expected '<' after 'template'")
|
|
while 1:
|
|
pos = self.pos
|
|
err = None
|
|
try:
|
|
param = self._parse_template_parameter()
|
|
templateParams.append(param)
|
|
except DefinitionError as eParam:
|
|
self.pos = pos
|
|
err = eParam
|
|
self.skip_ws()
|
|
if self.skip_string('>'):
|
|
requiresClause = self._parse_requires_clause()
|
|
return ASTTemplateParams(templateParams, requiresClause)
|
|
elif self.skip_string(','):
|
|
continue
|
|
else:
|
|
header = "Error in template parameter list."
|
|
errs = []
|
|
if err:
|
|
errs.append((err, "If parameter"))
|
|
try:
|
|
self.fail('Expected "," or ">".')
|
|
except DefinitionError as e:
|
|
errs.append((e, "If no parameter"))
|
|
logger.debug(errs)
|
|
raise self._make_multi_error(errs, header)
|
|
|
|
def _parse_template_introduction(self) -> ASTTemplateIntroduction:
|
|
pos = self.pos
|
|
try:
|
|
concept = self._parse_nested_name()
|
|
except Exception:
|
|
self.pos = pos
|
|
return None
|
|
self.skip_ws()
|
|
if not self.skip_string('{'):
|
|
self.pos = pos
|
|
return None
|
|
|
|
# for sure it must be a template introduction now
|
|
params = []
|
|
while 1:
|
|
self.skip_ws()
|
|
parameterPack = self.skip_string('...')
|
|
self.skip_ws()
|
|
if not self.match(identifier_re):
|
|
self.fail("Expected identifier in template introduction list.")
|
|
txt_identifier = self.matched_text
|
|
# make sure there isn't a keyword
|
|
if txt_identifier in _keywords:
|
|
self.fail("Expected identifier in template introduction list, "
|
|
"got keyword: %s" % txt_identifier)
|
|
identifier = ASTIdentifier(txt_identifier)
|
|
params.append(ASTTemplateIntroductionParameter(identifier, parameterPack))
|
|
|
|
self.skip_ws()
|
|
if self.skip_string('}'):
|
|
break
|
|
if self.skip_string(','):
|
|
continue
|
|
self.fail('Error in template introduction list. Expected ",", or "}".')
|
|
return ASTTemplateIntroduction(concept, params)
|
|
|
|
def _parse_requires_clause(self) -> ASTRequiresClause | None:
|
|
# requires-clause -> 'requires' constraint-logical-or-expression
|
|
# constraint-logical-or-expression
|
|
# -> constraint-logical-and-expression
|
|
# | constraint-logical-or-expression '||' constraint-logical-and-expression
|
|
# constraint-logical-and-expression
|
|
# -> primary-expression
|
|
# | constraint-logical-and-expression '&&' primary-expression
|
|
self.skip_ws()
|
|
if not self.skip_word('requires'):
|
|
return None
|
|
|
|
def parse_and_expr(self: DefinitionParser) -> ASTExpression:
|
|
andExprs = []
|
|
ops = []
|
|
andExprs.append(self._parse_primary_expression())
|
|
while True:
|
|
self.skip_ws()
|
|
oneMore = False
|
|
if self.skip_string('&&'):
|
|
oneMore = True
|
|
ops.append('&&')
|
|
elif self.skip_word('and'):
|
|
oneMore = True
|
|
ops.append('and')
|
|
if not oneMore:
|
|
break
|
|
andExprs.append(self._parse_primary_expression())
|
|
if len(andExprs) == 1:
|
|
return andExprs[0]
|
|
else:
|
|
return ASTBinOpExpr(andExprs, ops)
|
|
|
|
orExprs = []
|
|
ops = []
|
|
orExprs.append(parse_and_expr(self))
|
|
while True:
|
|
self.skip_ws()
|
|
oneMore = False
|
|
if self.skip_string('||'):
|
|
oneMore = True
|
|
ops.append('||')
|
|
elif self.skip_word('or'):
|
|
oneMore = True
|
|
ops.append('or')
|
|
if not oneMore:
|
|
break
|
|
orExprs.append(parse_and_expr(self))
|
|
if len(orExprs) == 1:
|
|
return ASTRequiresClause(orExprs[0])
|
|
else:
|
|
return ASTRequiresClause(ASTBinOpExpr(orExprs, ops))
|
|
|
|
def _parse_template_declaration_prefix(self, objectType: str,
|
|
) -> ASTTemplateDeclarationPrefix | None:
|
|
templates: list[ASTTemplateParams | ASTTemplateIntroduction] = []
|
|
while 1:
|
|
self.skip_ws()
|
|
# the saved position is only used to provide a better error message
|
|
params: ASTTemplateParams | ASTTemplateIntroduction = None
|
|
pos = self.pos
|
|
if self.skip_word("template"):
|
|
try:
|
|
params = self._parse_template_parameter_list()
|
|
except DefinitionError as e:
|
|
if objectType == 'member' and len(templates) == 0:
|
|
return ASTTemplateDeclarationPrefix(None)
|
|
else:
|
|
raise e
|
|
if objectType == 'concept' and params.requiresClause is not None:
|
|
self.fail('requires-clause not allowed for concept')
|
|
else:
|
|
params = self._parse_template_introduction()
|
|
if not params:
|
|
break
|
|
if objectType == 'concept' and len(templates) > 0:
|
|
self.pos = pos
|
|
self.fail("More than 1 template parameter list for concept.")
|
|
templates.append(params)
|
|
if len(templates) == 0 and objectType == 'concept':
|
|
self.fail('Missing template parameter list for concept.')
|
|
if len(templates) == 0:
|
|
return None
|
|
else:
|
|
return ASTTemplateDeclarationPrefix(templates)
|
|
|
|
def _check_template_consistency(self, nestedName: ASTNestedName,
|
|
templatePrefix: ASTTemplateDeclarationPrefix,
|
|
fullSpecShorthand: bool, isMember: bool = False,
|
|
) -> ASTTemplateDeclarationPrefix:
|
|
numArgs = nestedName.num_templates()
|
|
isMemberInstantiation = False
|
|
if not templatePrefix:
|
|
numParams = 0
|
|
else:
|
|
if isMember and templatePrefix.templates is None:
|
|
numParams = 0
|
|
isMemberInstantiation = True
|
|
else:
|
|
numParams = len(templatePrefix.templates)
|
|
if numArgs + 1 < numParams:
|
|
self.fail("Too few template argument lists comapred to parameter"
|
|
" lists. Argument lists: %d, Parameter lists: %d."
|
|
% (numArgs, numParams))
|
|
if numArgs > numParams:
|
|
numExtra = numArgs - numParams
|
|
if not fullSpecShorthand and not isMemberInstantiation:
|
|
msg = "Too many template argument lists compared to parameter" \
|
|
" lists. Argument lists: %d, Parameter lists: %d," \
|
|
" Extra empty parameters lists prepended: %d." \
|
|
% (numArgs, numParams, numExtra)
|
|
msg += " Declaration:\n\t"
|
|
if templatePrefix:
|
|
msg += "%s\n\t" % templatePrefix
|
|
msg += str(nestedName)
|
|
self.warn(msg)
|
|
|
|
newTemplates: list[ASTTemplateParams | ASTTemplateIntroduction] = []
|
|
for _i in range(numExtra):
|
|
newTemplates.append(ASTTemplateParams([], requiresClause=None))
|
|
if templatePrefix and not isMemberInstantiation:
|
|
newTemplates.extend(templatePrefix.templates)
|
|
templatePrefix = ASTTemplateDeclarationPrefix(newTemplates)
|
|
return templatePrefix
|
|
|
|
def parse_declaration(self, objectType: str, directiveType: str) -> ASTDeclaration:
|
|
if objectType not in ('class', 'union', 'function', 'member', 'type',
|
|
'concept', 'enum', 'enumerator'):
|
|
raise Exception('Internal error, unknown objectType "%s".' % objectType)
|
|
if directiveType not in ('class', 'struct', 'union', 'function', 'member', 'var',
|
|
'type', 'concept',
|
|
'enum', 'enum-struct', 'enum-class', 'enumerator'):
|
|
raise Exception('Internal error, unknown directiveType "%s".' % directiveType)
|
|
visibility = None
|
|
templatePrefix = None
|
|
trailingRequiresClause = None
|
|
declaration: Any = None
|
|
|
|
self.skip_ws()
|
|
if self.match(_visibility_re):
|
|
visibility = self.matched_text
|
|
|
|
if objectType in ('type', 'concept', 'member', 'function', 'class', 'union'):
|
|
templatePrefix = self._parse_template_declaration_prefix(objectType)
|
|
|
|
if objectType == 'type':
|
|
prevErrors = []
|
|
pos = self.pos
|
|
try:
|
|
if not templatePrefix:
|
|
declaration = self._parse_type(named=True, outer='type')
|
|
except DefinitionError as e:
|
|
prevErrors.append((e, "If typedef-like declaration"))
|
|
self.pos = pos
|
|
pos = self.pos
|
|
try:
|
|
if not declaration:
|
|
declaration = self._parse_type_using()
|
|
except DefinitionError as e:
|
|
self.pos = pos
|
|
prevErrors.append((e, "If type alias or template alias"))
|
|
header = "Error in type declaration."
|
|
raise self._make_multi_error(prevErrors, header) from e
|
|
elif objectType == 'concept':
|
|
declaration = self._parse_concept()
|
|
elif objectType == 'member':
|
|
declaration = self._parse_type_with_init(named=True, outer='member')
|
|
elif objectType == 'function':
|
|
declaration = self._parse_type(named=True, outer='function')
|
|
trailingRequiresClause = self._parse_requires_clause()
|
|
elif objectType == 'class':
|
|
declaration = self._parse_class()
|
|
elif objectType == 'union':
|
|
declaration = self._parse_union()
|
|
elif objectType == 'enum':
|
|
declaration = self._parse_enum()
|
|
elif objectType == 'enumerator':
|
|
declaration = self._parse_enumerator()
|
|
else:
|
|
raise AssertionError
|
|
templatePrefix = self._check_template_consistency(declaration.name,
|
|
templatePrefix,
|
|
fullSpecShorthand=False,
|
|
isMember=objectType == 'member')
|
|
self.skip_ws()
|
|
semicolon = self.skip_string(';')
|
|
return ASTDeclaration(objectType, directiveType, visibility,
|
|
templatePrefix, declaration,
|
|
trailingRequiresClause, semicolon)
|
|
|
|
def parse_namespace_object(self) -> ASTNamespace:
|
|
templatePrefix = self._parse_template_declaration_prefix(objectType="namespace")
|
|
name = self._parse_nested_name()
|
|
templatePrefix = self._check_template_consistency(name, templatePrefix,
|
|
fullSpecShorthand=False)
|
|
res = ASTNamespace(name, templatePrefix)
|
|
res.objectType = 'namespace' # type: ignore[attr-defined]
|
|
return res
|
|
|
|
def parse_xref_object(self) -> tuple[ASTNamespace | ASTDeclaration, bool]:
|
|
pos = self.pos
|
|
try:
|
|
templatePrefix = self._parse_template_declaration_prefix(objectType="xref")
|
|
name = self._parse_nested_name()
|
|
# if there are '()' left, just skip them
|
|
self.skip_ws()
|
|
self.skip_string('()')
|
|
self.assert_end()
|
|
templatePrefix = self._check_template_consistency(name, templatePrefix,
|
|
fullSpecShorthand=True)
|
|
res1 = ASTNamespace(name, templatePrefix)
|
|
res1.objectType = 'xref' # type: ignore[attr-defined]
|
|
return res1, True
|
|
except DefinitionError as e1:
|
|
try:
|
|
self.pos = pos
|
|
res2 = self.parse_declaration('function', 'function')
|
|
# if there are '()' left, just skip them
|
|
self.skip_ws()
|
|
self.skip_string('()')
|
|
self.assert_end()
|
|
return res2, False
|
|
except DefinitionError as e2:
|
|
errs = []
|
|
errs.append((e1, "If shorthand ref"))
|
|
errs.append((e2, "If full function ref"))
|
|
msg = "Error in cross-reference."
|
|
raise self._make_multi_error(errs, msg) from e2
|
|
|
|
def parse_expression(self) -> ASTExpression | ASTType:
|
|
pos = self.pos
|
|
try:
|
|
expr = self._parse_expression()
|
|
self.skip_ws()
|
|
self.assert_end()
|
|
return expr
|
|
except DefinitionError as exExpr:
|
|
self.pos = pos
|
|
try:
|
|
typ = self._parse_type(False)
|
|
self.skip_ws()
|
|
self.assert_end()
|
|
return typ
|
|
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
|
|
|
|
|
|
def _make_phony_error_name() -> ASTNestedName:
|
|
nne = ASTNestedNameElement(ASTIdentifier("PhonyNameDueToError"), None)
|
|
return ASTNestedName([nne], [False], rooted=False)
|
|
|
|
|
|
class CPPObject(ObjectDescription[ASTDeclaration]):
|
|
"""Description of a C++ language object."""
|
|
|
|
doc_field_types: list[Field] = [
|
|
GroupedField('template parameter', label=_('Template Parameters'),
|
|
names=('tparam', 'template parameter'),
|
|
can_collapse=True),
|
|
]
|
|
|
|
option_spec: OptionSpec = {
|
|
'no-index-entry': directives.flag,
|
|
'no-contents-entry': directives.flag,
|
|
'no-typesetting': directives.flag,
|
|
'noindexentry': directives.flag,
|
|
'nocontentsentry': directives.flag,
|
|
'tparam-line-spec': 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
|
|
# && it's unscoped,
|
|
# then add the name to the parent scope
|
|
symbol = ast.symbol
|
|
assert symbol
|
|
assert symbol.identOrOp is not None
|
|
assert symbol.templateParams is None
|
|
assert symbol.templateArgs is None
|
|
parentSymbol = symbol.parent
|
|
assert parentSymbol
|
|
if parentSymbol.parent is None:
|
|
# TODO: we could warn, but it is somewhat equivalent to unscoped
|
|
# enums, without the enum
|
|
return # no parent
|
|
parentDecl = parentSymbol.declaration
|
|
if parentDecl is None:
|
|
# the parent is not explicitly declared
|
|
# TODO: we could warn, but it could be a style to just assume
|
|
# enumerator parents to be scoped
|
|
return
|
|
if parentDecl.objectType != 'enum':
|
|
# TODO: maybe issue a warning, enumerators in non-enums is weird,
|
|
# but it is somewhat equivalent to unscoped enums, without the enum
|
|
return
|
|
if parentDecl.directiveType != 'enum':
|
|
return
|
|
|
|
targetSymbol = parentSymbol.parent
|
|
s = targetSymbol.find_identifier(symbol.identOrOp, 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, identOrOp=symbol.identOrOp,
|
|
templateParams=None, templateArgs=None,
|
|
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:
|
|
# general note: name must be lstrip(':')'ed, to remove "::"
|
|
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
|
|
if not re.compile(r'^[a-zA-Z0-9_]*$').match(newestId):
|
|
logger.warning('Index id generation for C++ object "%s" failed, please '
|
|
'report as bug (id=%s).', ast, newestId,
|
|
location=self.get_location())
|
|
|
|
name = ast.symbol.get_full_nested_name().get_display_string().lstrip(':')
|
|
# Add index entry, but not if it's a declaration inside a concept
|
|
isInConcept = False
|
|
s = ast.symbol.parent
|
|
while s is not None:
|
|
decl = s.declaration
|
|
s = s.parent
|
|
if decl is None:
|
|
continue
|
|
if decl.objectType == 'concept':
|
|
isInConcept = True
|
|
break
|
|
if not isInConcept and 'no-index-entry' not in self.options:
|
|
strippedName = name
|
|
for prefix in self.env.config.cpp_index_common_prefix:
|
|
if name.startswith(prefix):
|
|
strippedName = strippedName[len(prefix):]
|
|
break
|
|
indexText = self.get_index_text(strippedName)
|
|
self.indexnode['entries'].append(('single', indexText, newestId, '', None))
|
|
|
|
if newestId not in self.state.document.ids:
|
|
# if the name is not unique, the first one will win
|
|
names = self.env.domaindata['cpp']['names']
|
|
if name not in names:
|
|
names[name] = ast.symbol.docname
|
|
# 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)
|
|
|
|
@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: desc_signature,
|
|
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 'cpp:parent_symbol' not in env.temp_data:
|
|
root = env.domaindata['cpp']['root_symbol']
|
|
env.temp_data['cpp:parent_symbol'] = root
|
|
env.ref_context['cpp:parent_key'] = root.get_lookup_key()
|
|
|
|
# The lookup keys assume that no nested scopes exists inside overloaded functions.
|
|
# (see also #5191)
|
|
# Example:
|
|
# .. cpp:function:: void f(int)
|
|
# .. cpp:function:: void f(double)
|
|
#
|
|
# .. cpp:function:: void g()
|
|
#
|
|
# :cpp:any:`boom`
|
|
#
|
|
# So we disallow any signatures inside functions.
|
|
parentSymbol = env.temp_data['cpp:parent_symbol']
|
|
parentDecl = parentSymbol.declaration
|
|
if parentDecl is not None and parentDecl.objectType == 'function':
|
|
msg = ("C++ declarations inside functions are not supported. "
|
|
f"Parent function: {parentSymbol.get_full_nested_name()}\n"
|
|
f"Directive name: {self.name}\nDirective arg: {self.arguments[0]}")
|
|
logger.warning(msg, location=self.get_location())
|
|
name = _make_phony_error_name()
|
|
symbol = parentSymbol.add_name(name)
|
|
env.temp_data['cpp:last_symbol'] = symbol
|
|
return []
|
|
# 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['cpp:last_symbol'] = None
|
|
return super().run()
|
|
|
|
def handle_signature(self, sig: str, signode: desc_signature) -> ASTDeclaration:
|
|
parentSymbol: Symbol = self.env.temp_data['cpp:parent_symbol']
|
|
|
|
max_len = (self.env.config.cpp_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['cpp: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['cpp:last_symbol']
|
|
if symbol.siblingAbove is not None:
|
|
assert symbol.siblingAbove.siblingBelow is None
|
|
symbol.siblingAbove.siblingBelow = symbol
|
|
self.env.temp_data['cpp: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['cpp:last_symbol'] = e.symbol
|
|
msg = __("Duplicate C++ declaration, also defined at %s:%s.\n"
|
|
"Declaration is '.. cpp:%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)
|
|
options['tparam-line-spec'] = 'tparam-line-spec' in self.options
|
|
self.describe_signature(signode, ast, options)
|
|
return ast
|
|
|
|
def before_content(self) -> None:
|
|
lastSymbol: Symbol = self.env.temp_data['cpp:last_symbol']
|
|
assert lastSymbol
|
|
self.oldParentSymbol = self.env.temp_data['cpp:parent_symbol']
|
|
self.oldParentKey: LookupKey = self.env.ref_context['cpp:parent_key']
|
|
self.env.temp_data['cpp:parent_symbol'] = lastSymbol
|
|
self.env.ref_context['cpp:parent_key'] = lastSymbol.get_lookup_key()
|
|
self.env.temp_data['cpp:domain_name'] = (
|
|
*self.env.temp_data.get('cpp:domain_name', ()),
|
|
lastSymbol.identOrOp._stringify(str),
|
|
)
|
|
|
|
def after_content(self) -> None:
|
|
self.env.temp_data['cpp:parent_symbol'] = self.oldParentSymbol
|
|
self.env.ref_context['cpp:parent_key'] = self.oldParentKey
|
|
self.env.temp_data['cpp:domain_name'] = self.env.temp_data['cpp:domain_name'][:-1]
|
|
|
|
def _object_hierarchy_parts(self, sig_node: desc_signature) -> tuple[str, ...]:
|
|
return tuple(s.identOrOp._stringify(str) for s in
|
|
self.env.temp_data['cpp:last_symbol'].get_full_nested_name().names)
|
|
|
|
def _toc_entry_name(self, sig_node: desc_signature) -> str:
|
|
if not sig_node.get('_toc_parts'):
|
|
return ''
|
|
|
|
config = self.env.app.config
|
|
objtype = sig_node.parent.get('objtype')
|
|
if config.add_function_parentheses and objtype in {'function', 'method'}:
|
|
parens = '()'
|
|
else:
|
|
parens = ''
|
|
*parents, name = sig_node['_toc_parts']
|
|
if config.toc_object_entries_show_parents == 'domain':
|
|
return '::'.join((*self.env.temp_data.get('cpp:domain_name', ()), name + parens))
|
|
if config.toc_object_entries_show_parents == 'hide':
|
|
return name + parens
|
|
if config.toc_object_entries_show_parents == 'all':
|
|
return '::'.join(parents + [name + parens])
|
|
return ''
|
|
|
|
|
|
class CPPTypeObject(CPPObject):
|
|
object_type = 'type'
|
|
|
|
|
|
class CPPConceptObject(CPPObject):
|
|
object_type = 'concept'
|
|
|
|
|
|
class CPPMemberObject(CPPObject):
|
|
object_type = 'member'
|
|
|
|
|
|
class CPPFunctionObject(CPPObject):
|
|
object_type = 'function'
|
|
|
|
doc_field_types = CPPObject.doc_field_types + [
|
|
GroupedField('parameter', label=_('Parameters'),
|
|
names=('param', 'parameter', 'arg', 'argument'),
|
|
can_collapse=True),
|
|
GroupedField('exceptions', label=_('Throws'), rolename='expr',
|
|
names=('throws', 'throw', 'exception'),
|
|
can_collapse=True),
|
|
GroupedField('retval', label=_('Return values'),
|
|
names=('retvals', 'retval'),
|
|
can_collapse=True),
|
|
Field('returnvalue', label=_('Returns'), has_arg=False,
|
|
names=('returns', 'return')),
|
|
]
|
|
|
|
|
|
class CPPClassObject(CPPObject):
|
|
object_type = 'class'
|
|
|
|
@property
|
|
def display_object_type(self) -> str:
|
|
# the distinction between class and struct is only cosmetic
|
|
assert self.objtype in ('class', 'struct')
|
|
return self.objtype
|
|
|
|
|
|
class CPPUnionObject(CPPObject):
|
|
object_type = 'union'
|
|
|
|
|
|
class CPPEnumObject(CPPObject):
|
|
object_type = 'enum'
|
|
|
|
|
|
class CPPEnumeratorObject(CPPObject):
|
|
object_type = 'enumerator'
|
|
|
|
|
|
class CPPNamespaceObject(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['cpp']['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.config)
|
|
try:
|
|
ast = parser.parse_namespace_object()
|
|
parser.assert_end()
|
|
except DefinitionError as e:
|
|
logger.warning(e, location=self.get_location())
|
|
name = _make_phony_error_name()
|
|
ast = ASTNamespace(name, None)
|
|
symbol = rootSymbol.add_name(ast.nestedName, ast.templatePrefix)
|
|
stack = [symbol]
|
|
self.env.temp_data['cpp:parent_symbol'] = symbol
|
|
self.env.temp_data['cpp:namespace_stack'] = stack
|
|
self.env.ref_context['cpp:parent_key'] = symbol.get_lookup_key()
|
|
return []
|
|
|
|
|
|
class CPPNamespacePushObject(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.config)
|
|
try:
|
|
ast = parser.parse_namespace_object()
|
|
parser.assert_end()
|
|
except DefinitionError as e:
|
|
logger.warning(e, location=self.get_location())
|
|
name = _make_phony_error_name()
|
|
ast = ASTNamespace(name, None)
|
|
oldParent = self.env.temp_data.get('cpp:parent_symbol', None)
|
|
if not oldParent:
|
|
oldParent = self.env.domaindata['cpp']['root_symbol']
|
|
symbol = oldParent.add_name(ast.nestedName, ast.templatePrefix)
|
|
stack = self.env.temp_data.get('cpp:namespace_stack', [])
|
|
stack.append(symbol)
|
|
self.env.temp_data['cpp:parent_symbol'] = symbol
|
|
self.env.temp_data['cpp:namespace_stack'] = stack
|
|
self.env.ref_context['cpp:parent_key'] = symbol.get_lookup_key()
|
|
return []
|
|
|
|
|
|
class CPPNamespacePopObject(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('cpp: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['cpp']['root_symbol']
|
|
self.env.temp_data['cpp:parent_symbol'] = symbol
|
|
self.env.temp_data['cpp:namespace_stack'] = stack
|
|
self.env.ref_context['cpp:parent_key'] = symbol.get_lookup_key()
|
|
return []
|
|
|
|
|
|
class AliasNode(nodes.Element):
|
|
def __init__(self, sig: str, aliasOptions: dict,
|
|
env: BuildEnvironment | None = None,
|
|
parentKey: LookupKey | None = None) -> None:
|
|
super().__init__()
|
|
self.sig = sig
|
|
self.aliasOptions = aliasOptions
|
|
if env is not None:
|
|
if 'cpp:parent_symbol' not in env.temp_data:
|
|
root = env.domaindata['cpp']['root_symbol']
|
|
env.temp_data['cpp:parent_symbol'] = root
|
|
env.ref_context['cpp:parent_key'] = root.get_lookup_key()
|
|
self.parentKey = env.ref_context['cpp:parent_key']
|
|
else:
|
|
assert parentKey is not None
|
|
self.parentKey = parentKey
|
|
|
|
def copy(self) -> AliasNode:
|
|
return self.__class__(self.sig, self.aliasOptions,
|
|
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'] = 'cpp'
|
|
# '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
|
|
if sChild.declaration.objectType in ("templateParam", "functionParam"):
|
|
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)
|
|
ast, isShorthand = parser.parse_xref_object()
|
|
parser.assert_end()
|
|
except DefinitionError as e:
|
|
logger.warning(e, location=node)
|
|
ast, isShorthand = None, None
|
|
|
|
if ast 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['cpp'].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
|
|
|
|
symbols: list[Symbol] = []
|
|
if isShorthand:
|
|
assert isinstance(ast, ASTNamespace)
|
|
ns = ast
|
|
name = ns.nestedName
|
|
if ns.templatePrefix:
|
|
templateDecls = ns.templatePrefix.templates
|
|
else:
|
|
templateDecls = []
|
|
symbols, failReason = parentSymbol.find_name(
|
|
nestedName=name,
|
|
templateDecls=templateDecls,
|
|
typ='any',
|
|
templateShorthand=True,
|
|
matchSelf=True, recurseInAnon=True,
|
|
searchInSiblings=False)
|
|
if symbols is None:
|
|
symbols = []
|
|
else:
|
|
assert isinstance(ast, ASTDeclaration)
|
|
decl = ast
|
|
name = decl.name
|
|
s = parentSymbol.find_declaration(decl, 'any',
|
|
templateShorthand=True,
|
|
matchSelf=True, recurseInAnon=True)
|
|
if s is not None:
|
|
symbols.append(s)
|
|
|
|
symbols = [s for s in symbols if s.declaration is not None]
|
|
|
|
if len(symbols) == 0:
|
|
signode = addnodes.desc_signature(sig, '')
|
|
node.append(signode)
|
|
signode.clear()
|
|
signode += addnodes.desc_name(sig, sig)
|
|
|
|
logger.warning("Can not find C++ declaration for alias '%s'." % ast,
|
|
location=node)
|
|
node.replace_self(signode)
|
|
else:
|
|
nodes = []
|
|
renderOptions = {
|
|
'tparam-line-spec': False,
|
|
}
|
|
for s in symbols:
|
|
assert s.declaration is not None
|
|
res = self._render_symbol(
|
|
s, maxdepth=node.aliasOptions['maxdepth'],
|
|
skipThis=node.aliasOptions['noroot'],
|
|
aliasOptions=node.aliasOptions,
|
|
renderOptions=renderOptions,
|
|
document=node.document)
|
|
nodes.extend(res)
|
|
node.replace_self(nodes)
|
|
|
|
|
|
class CPPAliasObject(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 (an overload set).
|
|
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
|
|
|
|
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())
|
|
signatures = self.get_signatures()
|
|
for sig in signatures:
|
|
node.append(AliasNode(sig, aliasOptions, env=self.env))
|
|
|
|
contentnode = addnodes.desc_content()
|
|
node.append(contentnode)
|
|
self.before_content()
|
|
self.state.nested_parse(self.content, self.content_offset, contentnode)
|
|
self.env.temp_data['object'] = None
|
|
self.after_content()
|
|
return [node]
|
|
|
|
|
|
class CPPXRefRole(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 refnode['reftype'] == 'any':
|
|
# Assume the removal part of fix_parens for :any: refs.
|
|
# The addition part is done with the reference is resolved.
|
|
if not has_explicit_title and title.endswith('()'):
|
|
title = title[:-2]
|
|
if target.endswith('()'):
|
|
target = target[:-2]
|
|
# TODO: should this really be here?
|
|
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[:1] == '~':
|
|
title = title[1:]
|
|
dcolon = title.rfind('::')
|
|
if dcolon != -1:
|
|
title = title[dcolon + 2:]
|
|
return title, target
|
|
|
|
|
|
class CPPExprRole(SphinxRole):
|
|
def __init__(self, asCode: bool) -> None:
|
|
super().__init__()
|
|
if asCode:
|
|
# render the expression as inline code
|
|
self.class_type = 'cpp-expr'
|
|
else:
|
|
# render the expression as inline text
|
|
self.class_type = 'cpp-texpr'
|
|
|
|
def run(self) -> tuple[list[Node], list[system_message]]:
|
|
text = self.text.replace('\n', ' ')
|
|
parser = DefinitionParser(text,
|
|
location=self.get_location(),
|
|
config=self.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('cpp', text, text, classes=[self.class_type])], []
|
|
parentSymbol = self.env.temp_data.get('cpp:parent_symbol', None)
|
|
if parentSymbol is None:
|
|
parentSymbol = self.env.domaindata['cpp']['root_symbol']
|
|
# ...most if not all of these classes should really apply to the individual references,
|
|
# not the container node
|
|
signode = addnodes.desc_inline('cpp', classes=[self.class_type])
|
|
ast.describe_signature(signode, 'markType', self.env, parentSymbol)
|
|
return [signode], []
|
|
|
|
|
|
class CPPDomain(Domain):
|
|
"""C++ language domain.
|
|
|
|
There are two 'object type' attributes being used::
|
|
|
|
- Each object created from directives gets an assigned .objtype from ObjectDescription.run.
|
|
This is simply the directive name.
|
|
- Each declaration (see the distinction in the directives dict below) has a nested .ast of
|
|
type ASTDeclaration. That object has .objectType which corresponds to the keys in the
|
|
object_types dict below. They are the core different types of declarations in C++ that
|
|
one can document.
|
|
"""
|
|
name = 'cpp'
|
|
label = 'C++'
|
|
object_types = {
|
|
'class': ObjType(_('class'), 'class', 'struct', 'identifier', 'type'),
|
|
'union': ObjType(_('union'), 'union', 'identifier', 'type'),
|
|
'function': ObjType(_('function'), 'func', 'identifier', 'type'),
|
|
'member': ObjType(_('member'), 'member', 'var', 'identifier'),
|
|
'type': ObjType(_('type'), 'identifier', 'type'),
|
|
'concept': ObjType(_('concept'), 'concept', 'identifier'),
|
|
'enum': ObjType(_('enum'), 'enum', 'identifier', 'type'),
|
|
'enumerator': ObjType(_('enumerator'), 'enumerator', 'identifier'),
|
|
# generated object types
|
|
'functionParam': ObjType(_('function parameter'), 'identifier', 'member', 'var'), # noqa: E501
|
|
'templateParam': ObjType(_('template parameter'),
|
|
'identifier', 'class', 'struct', 'union', 'member', 'var', 'type'), # noqa: E501
|
|
}
|
|
|
|
directives = {
|
|
# declarations
|
|
'class': CPPClassObject,
|
|
'struct': CPPClassObject,
|
|
'union': CPPUnionObject,
|
|
'function': CPPFunctionObject,
|
|
'member': CPPMemberObject,
|
|
'var': CPPMemberObject,
|
|
'type': CPPTypeObject,
|
|
'concept': CPPConceptObject,
|
|
'enum': CPPEnumObject,
|
|
'enum-struct': CPPEnumObject,
|
|
'enum-class': CPPEnumObject,
|
|
'enumerator': CPPEnumeratorObject,
|
|
# scope control
|
|
'namespace': CPPNamespaceObject,
|
|
'namespace-push': CPPNamespacePushObject,
|
|
'namespace-pop': CPPNamespacePopObject,
|
|
# other
|
|
'alias': CPPAliasObject,
|
|
}
|
|
roles = {
|
|
'any': CPPXRefRole(),
|
|
'class': CPPXRefRole(),
|
|
'struct': CPPXRefRole(),
|
|
'union': CPPXRefRole(),
|
|
'func': CPPXRefRole(fix_parens=True),
|
|
'member': CPPXRefRole(),
|
|
'var': CPPXRefRole(),
|
|
'type': CPPXRefRole(),
|
|
'concept': CPPXRefRole(),
|
|
'enum': CPPXRefRole(),
|
|
'enumerator': CPPXRefRole(),
|
|
'expr': CPPExprRole(asCode=True),
|
|
'texpr': CPPExprRole(asCode=False),
|
|
}
|
|
initial_data = {
|
|
'root_symbol': Symbol(None, None, None, None, None, None, None),
|
|
'names': {}, # full name for indexing -> docname
|
|
}
|
|
|
|
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)
|
|
for name, nDocname in list(self.data['names'].items()):
|
|
if nDocname == docname:
|
|
del self.data['names'][name]
|
|
|
|
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")
|
|
|
|
self.data['root_symbol'].merge_with(otherdata['root_symbol'],
|
|
docnames, self.env)
|
|
ourNames = self.data['names']
|
|
for name, docname in otherdata['names'].items():
|
|
if docname in docnames:
|
|
if name not in ourNames:
|
|
ourNames[name] = docname
|
|
# no need to warn on duplicates, the symbol merge already does that
|
|
if Symbol.debug_show_tree:
|
|
logger.debug("\tresult:")
|
|
logger.debug(self.data['root_symbol'].dump(1))
|
|
logger.debug("\tresult end")
|
|
logger.debug("merge_domaindata end")
|
|
|
|
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]:
|
|
# add parens again for those that could be functions
|
|
if typ in ('any', 'func'):
|
|
target += '()'
|
|
parser = DefinitionParser(target, location=node, config=env.config)
|
|
try:
|
|
ast, isShorthand = parser.parse_xref_object()
|
|
except DefinitionError as e:
|
|
# as arg to stop flake8 from complaining
|
|
def findWarning(e: Exception) -> tuple[str, Exception]:
|
|
if typ != 'any' and typ != 'func':
|
|
return target, e
|
|
# hax on top of the paren hax to try to get correct errors
|
|
parser2 = DefinitionParser(target[:-2],
|
|
location=node,
|
|
config=env.config)
|
|
try:
|
|
parser2.parse_xref_object()
|
|
except DefinitionError as e2:
|
|
return target[:-2], e2
|
|
# strange, that we don't get the error now, use the original
|
|
return target, e
|
|
t, ex = findWarning(e)
|
|
logger.warning('Unparseable C++ cross-reference: %r\n%s', t, ex,
|
|
location=node)
|
|
return None, None
|
|
parentKey: LookupKey = node.get("cpp: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.data)
|
|
logger.debug(rootSymbol.dump(1))
|
|
assert parentSymbol # should be there
|
|
else:
|
|
parentSymbol = rootSymbol
|
|
|
|
if isShorthand:
|
|
assert isinstance(ast, ASTNamespace)
|
|
ns = ast
|
|
name = ns.nestedName
|
|
if ns.templatePrefix:
|
|
templateDecls = ns.templatePrefix.templates
|
|
else:
|
|
templateDecls = []
|
|
# let's be conservative with the sibling lookup for now
|
|
searchInSiblings = (not name.rooted) and len(name.names) == 1
|
|
symbols, failReason = parentSymbol.find_name(
|
|
name, templateDecls, typ,
|
|
templateShorthand=True,
|
|
matchSelf=True, recurseInAnon=True,
|
|
searchInSiblings=searchInSiblings)
|
|
if symbols is None:
|
|
if typ == 'identifier':
|
|
if failReason == 'templateParamInQualified':
|
|
# this is an xref we created as part of a signature,
|
|
# so don't warn for names nested in template parameters
|
|
raise NoUri(str(name), typ)
|
|
s = None
|
|
else:
|
|
# just refer to the arbitrarily first symbol
|
|
s = symbols[0]
|
|
else:
|
|
assert isinstance(ast, ASTDeclaration)
|
|
decl = ast
|
|
name = decl.name
|
|
s = parentSymbol.find_declaration(decl, typ,
|
|
templateShorthand=True,
|
|
matchSelf=True, recurseInAnon=True)
|
|
if s is None or s.declaration is None:
|
|
txtName = str(name)
|
|
if txtName.startswith('std::') or txtName == 'std':
|
|
raise NoUri(txtName, typ)
|
|
return None, None
|
|
|
|
if typ.startswith('cpp:'):
|
|
typ = typ[4:]
|
|
declTyp = s.declaration.objectType
|
|
|
|
def checkType() -> bool:
|
|
if typ == 'any':
|
|
return True
|
|
objtypes = self.objtypes_for_role(typ)
|
|
if objtypes:
|
|
return declTyp in objtypes
|
|
logger.debug(f"Type is {typ}, declaration type is {declTyp}") # NoQA: G004
|
|
raise AssertionError
|
|
if not checkType():
|
|
logger.warning("cpp:%s targets a %s (%s).",
|
|
typ, s.declaration.objectType,
|
|
s.get_full_nested_name(),
|
|
location=node)
|
|
|
|
declaration = s.declaration
|
|
if isShorthand:
|
|
fullNestedName = s.get_full_nested_name()
|
|
displayName = fullNestedName.get_display_string().lstrip(':')
|
|
else:
|
|
displayName = decl.get_display_string()
|
|
docname = s.docname
|
|
assert docname
|
|
|
|
# the non-identifier refs are cross-references, which should be processed:
|
|
# - fix parenthesis due to operator() and add_function_parentheses
|
|
if typ != "identifier":
|
|
title = contnode.pop(0).astext()
|
|
# If it's operator(), we need to add '()' if explicit function parens
|
|
# are requested. Then the Sphinx machinery will add another pair.
|
|
# Also, if it's an 'any' ref that resolves to a function, we need to add
|
|
# parens as well.
|
|
# However, if it's a non-shorthand function ref, for a function that
|
|
# takes no arguments, then we may need to add parens again as well.
|
|
addParen = 0
|
|
if not node.get('refexplicit', False) and declaration.objectType == 'function':
|
|
if isShorthand:
|
|
# this is just the normal haxing for 'any' roles
|
|
if env.config.add_function_parentheses and typ == 'any':
|
|
addParen += 1
|
|
# and now this stuff for operator()
|
|
if (env.config.add_function_parentheses and typ == 'func' and
|
|
title.endswith('operator()')):
|
|
addParen += 1
|
|
if (typ in ('any', 'func') and
|
|
title.endswith('operator') and
|
|
displayName.endswith('operator()')):
|
|
addParen += 1
|
|
else:
|
|
# our job here is to essentially nullify add_function_parentheses
|
|
if env.config.add_function_parentheses:
|
|
if typ == 'any' and displayName.endswith('()'):
|
|
addParen += 1
|
|
elif typ == 'func':
|
|
if title.endswith('()') and not displayName.endswith('()'):
|
|
title = title[:-2]
|
|
else:
|
|
if displayName.endswith('()'):
|
|
addParen += 1
|
|
if addParen > 0:
|
|
title += '()' * addParen
|
|
# and reconstruct the title again
|
|
contnode += nodes.Text(title)
|
|
res = make_refnode(builder, fromdocname, docname,
|
|
declaration.get_newest_id(), contnode, displayName,
|
|
), declaration.objectType
|
|
return res
|
|
|
|
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:
|
|
if objtype == 'templateParam':
|
|
return [('cpp:templateParam', retnode)]
|
|
else:
|
|
return [('cpp:' + 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 get_full_qualified_name(self, node: Element) -> str:
|
|
target = node.get('reftarget', None)
|
|
if target is None:
|
|
return None
|
|
parentKey: LookupKey = node.get("cpp:parent_key", None)
|
|
if parentKey is None or len(parentKey.data) <= 0:
|
|
return None
|
|
|
|
rootSymbol = self.data['root_symbol']
|
|
parentSymbol = rootSymbol.direct_lookup(parentKey)
|
|
parentName = parentSymbol.get_full_nested_name()
|
|
return '::'.join([str(parentName), target])
|
|
|
|
|
|
def setup(app: Sphinx) -> dict[str, Any]:
|
|
app.add_domain(CPPDomain)
|
|
app.add_config_value("cpp_index_common_prefix", [], 'env')
|
|
app.add_config_value("cpp_id_attributes", [], 'env')
|
|
app.add_config_value("cpp_paren_attributes", [], 'env')
|
|
app.add_config_value("cpp_maximum_signature_line_length", None, 'env', types={int, None})
|
|
app.add_post_transform(AliasTransform)
|
|
|
|
# debug stuff
|
|
app.add_config_value("cpp_debug_lookup", False, '')
|
|
app.add_config_value("cpp_debug_show_tree", False, '')
|
|
|
|
def initStuff(app):
|
|
Symbol.debug_lookup = app.config.cpp_debug_lookup
|
|
Symbol.debug_show_tree = app.config.cpp_debug_show_tree
|
|
app.config.cpp_index_common_prefix.sort(reverse=True)
|
|
app.connect("builder-inited", initStuff)
|
|
|
|
return {
|
|
'version': 'builtin',
|
|
'env_version': 9,
|
|
'parallel_read_safe': True,
|
|
'parallel_write_safe': True,
|
|
}
|