# -*- coding: utf-8 -*- """ sphinx.domains.cpp ~~~~~~~~~~~~~~~~~~ The C++ language domain. :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import re from copy import deepcopy from six import iteritems, text_type from docutils import nodes from sphinx import addnodes from sphinx.roles import XRefRole from sphinx.locale import l_, _ from sphinx.domains import Domain, ObjType from sphinx.directives import ObjectDescription from sphinx.util.nodes import make_refnode from sphinx.util.compat import Directive from sphinx.util.pycompat import UnicodeMixin from sphinx.util.docfields import Field, GroupedField """ 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 http://mentorembedded.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. See http://www.nongnu.org/hcb/ for the grammar, or https://github.com/cplusplus/draft/blob/master/source/grammar.tex 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 "typname" in template templates templateDeclPrefix -> "template" "<" template-parameter-list ">" simple-declaration -> attribute-specifier-seq[opt] decl-specifier-seq[opt] init-declarator-list[opt] ; # Drop the semi-colon. For now: drop the attributes (TODO). # Use at most 1 init-declerator. -> decl-specifier-seq init-declerator -> decl-specifier-seq declerator initializer decl-specifier -> storage-class-specifier -> "static" (only for member_object and function_object) | "register" | 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 paramters 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" | ect. | 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 build-in -> "char" | "bool" | ect. 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-declerator | id-expression declerator -> ptr-declerator | noptr-declarator parameters-and-qualifiers trailing-return-type (TODO: for now we don't support trailing-eturn-type) ptr-declerator -> noptr-declerator | ptr-operator ptr-declarator noptr-declerator -> declarator-id attribute-specifier-seq[opt] -> "..."[opt] id-expression | rest-of-trailing | noptr-declerator 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 poitners) 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. 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 declerator Can start with a templateDeclPrefix. member_object: goal: as a type_object which must have a declerator, and optionally with a initializer grammar: decl-specifier-seq declerator 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 declerator Can start with a templateDeclPrefix. class_object: goal: a class declaration, but with specification of a base class grammar: 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] 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 """ _identifier_re = re.compile(r'(~?\b[a-zA-Z_][a-zA-Z0-9_]*)\b') _whitespace_re = re.compile(r'\s+(?u)') _string_re = re.compile(r"[LuU8]?('([^'\\]*(?:\\.[^'\\]*)*)'" r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S) _visibility_re = re.compile(r'\b(public|private|protected)\b') _operator_re = re.compile(r'''(?x) \[\s*\] | \(\s*\) | \+\+ | -- | ->\*? | \, | (<<|>>)=? | && | \|\| | [!<>=/*%+|&^~-]=? ''') # see http://en.cppreference.com/w/cpp/keyword _keywords = [ 'alignas', 'alignof', 'and', 'and_eq', 'asm', 'auto', 'bitand', 'bitor', 'bool', 'break', 'case', 'catch', 'char', 'char16_t', 'char32_t', 'class', 'compl', 'concept', 'const', 'constexpr', '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' ] # ------------------------------------------------------------------------------ # 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 v2 constants # ------------------------------------------------------------------------------ _id_prefix_v2 = '_CPPv2' _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', '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', 'unsigned long long': 'y', 'unsigned long long int': 'y', 'float': 'f', 'double': 'd', '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 # '+(unary)' : 'ps', # '-(unary)' : 'ng', # '&(unary)' : 'ad', # '*(unary)' : 'de', '~': 'co', '+': 'pl', '-': 'mi', '*': 'ml', '/': 'dv', '%': 'rm', '&': 'an', '|': 'or', '^': 'eo', '=': 'aS', '+=': 'pL', '-=': 'mI', '*=': 'mL', '/=': 'dV', '%=': 'rM', '&=': 'aN', '|=': 'oR', '^=': 'eO', '<<': 'ls', '>>': 'rs', '<<=': 'lS', '>>=': 'rS', '==': 'eq', '!=': 'ne', '<': 'lt', '>': 'gt', '<=': 'le', '>=': 'ge', '!': 'nt', '&&': 'aa', '||': 'oo', '++': 'pp', '--': 'mm', ',': 'cm', '->*': 'pm', '->': 'pt', '()': 'cl', '[]': 'ix' } class NoOldIdError(UnicodeMixin, Exception): # Used to avoid implementing unneeded id generation for old id schmes. def __init__(self, description=""): self.description = description def __unicode__(self): return self.description class DefinitionError(UnicodeMixin, Exception): def __init__(self, description): self.description = description def __unicode__(self): return self.description class ASTBase(UnicodeMixin): def __eq__(self, other): if type(self) is not type(other): return False try: for key, value in iteritems(self.__dict__): if value != getattr(other, key): return False except AttributeError: return False return True def __ne__(self, other): return not self.__eq__(other) __hash__ = None def clone(self): """Clone a definition expression node.""" return deepcopy(self) def get_id_v1(self): """Return the v1 id for the node.""" raise NotImplementedError(repr(self)) def get_id_v2(self): """Return the v2 id for the node.""" raise NotImplementedError(repr(self)) def get_name(self): """Return the name. Returns either `None` or a node with a name you might call :meth:`split_owner` on. """ raise NotImplementedError(repr(self)) def prefix_nested_name(self, prefix): """Prefix a name node (a node returned by :meth:`get_name`).""" raise NotImplementedError(repr(self)) def __unicode__(self): raise NotImplementedError(repr(self)) def __repr__(self): return '<%s %s>' % (self.__class__.__name__, self) def _verify_description_mode(mode): if mode not in ('lastIsName', 'noneIsName', 'markType', 'param'): raise Exception("Description mode '%s' is invalid." % mode) class ASTIdentifier(ASTBase): def __init__(self, identifier): assert identifier is not None self.identifier = identifier def get_id_v1(self): if self.identifier == 'size_t': return 's' else: return self.identifier def get_id_v2(self): if self.identifier == "std": return 'St' elif self.identifier[0] == "~": # a destructor, just use an arbitrary version of dtors return 'D0' else: return text_type(len(self.identifier)) + self.identifier def __unicode__(self): return self.identifier def describe_signature(self, signode, mode, env, prefix, symbol): _verify_description_mode(mode) if mode == 'markType': targetText = prefix + self.identifier pnode = addnodes.pending_xref('', refdomain='cpp', reftype='type', reftarget=targetText, modname=None, classname=None) key = symbol.get_lookup_key() assert key pnode['cpp:parentKey'] = key pnode += nodes.Text(self.identifier) signode += pnode elif mode == 'lastIsName': signode += addnodes.desc_name(self.identifier, self.identifier) elif mode == 'noneIsName': signode += nodes.Text(self.identifier) else: raise Exception('Unknown description mode: %s' % mode) class ASTTemplateKeyParamPackIdDefault(ASTBase): def __init__(self, key, identifier, parameterPack, default): assert key if parameterPack: assert default is None self.key = key self.identifier = identifier self.parameterPack = parameterPack self.default = default def get_identifier(self): return self.identifier def get_id_v2(self): # 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 __unicode__(self): res = [self.key] if self.parameterPack: if self.identifier: res.append(' ') res.append('...') if self.identifier: if not self.parameterPack: res.append(' ') res.append(text_type(self.identifier)) if self.default: res.append(' = ') res.append(text_type(self.default)) return ''.join(res) def describe_signature(self, signode, mode, env, symbol): signode += nodes.Text(self.key) if self.parameterPack: if self.identifier: signode += nodes.Text(' ') signode += nodes.Text('...') if self.identifier: if not self.parameterPack: signode += nodes.Text(' ') self.identifier.describe_signature(signode, mode, env, '', symbol) if self.default: signode += nodes.Text(' = ') self.default.describe_signature(signode, 'markType', env, symbol) class ASTTemplateParamType(ASTBase): def __init__(self, data): assert data self.data = data @property def name(self): id = self.get_identifier() return ASTNestedName([ASTNestedNameElement(id, None)], rooted=False) def get_identifier(self): return self.data.get_identifier() def get_id_v2(self, objectType=None, symbol=None): # 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_v2(prefixed=None) else: return self.data.get_id_v2() def __unicode__(self): return text_type(self.data) def describe_signature(self, signode, mode, env, symbol): self.data.describe_signature(signode, mode, env, symbol) class ASTTemplateParamTemplateType(ASTBase): def __init__(self, nestedParams, data): assert nestedParams assert data self.nestedParams = nestedParams self.data = data @property def name(self): id = self.get_identifier() return ASTNestedName([ASTNestedNameElement(id, None)], rooted=False) def get_identifier(self): return self.data.get_identifier() def get_id_v2(self, objectType=None, symbol=None): # 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_v2(prefixed=None) else: return self.nestedParams.get_id_v2() + self.data.get_id_v2() def __unicode__(self): return text_type(self.nestedParams) + text_type(self.data) def describe_signature(self, signode, mode, env, symbol): self.nestedParams.describe_signature(signode, 'noneIsName', env, symbol) signode += nodes.Text(' ') self.data.describe_signature(signode, mode, env, symbol) class ASTTemplateParamNonType(ASTBase): def __init__(self, param): assert param self.param = param @property def name(self): id = self.get_identifier() return ASTNestedName([ASTNestedNameElement(id, None)], rooted=False) def get_identifier(self): name = self.param.name if name: assert len(name.names) == 1 assert name.names[0].identifier assert not name.names[0].templateArgs return name.names[0].identifier else: return None def get_id_v2(self, objectType=None, symbol=None): # 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_v2(prefixed=None) else: return '_' + self.param.get_id_v2() def __unicode__(self): return text_type(self.param) def describe_signature(self, signode, mode, env, symbol): self.param.describe_signature(signode, mode, env, symbol) class ASTTemplateParams(ASTBase): def __init__(self, params): assert params is not None self.params = params def get_id_v2(self): res = [] res.append("I") for param in self.params: res.append(param.get_id_v2()) res.append("E") return ''.join(res) def __unicode__(self): res = [] res.append(u"template<") res.append(u", ".join(text_type(a) for a in self.params)) res.append(u"> ") return ''.join(res) def describe_signature(self, signode, mode, env, symbol): signode += nodes.Text("template<") first = True for param in self.params: if not first: signode += nodes.Text(", ") first = False param.describe_signature(signode, mode, env, symbol) signode += nodes.Text(">") class ASTTemplateDeclarationPrefix(ASTBase): def __init__(self, templates): assert templates is not None assert len(templates) > 0 self.templates = templates # id_v1 does not exist def get_id_v2(self): # this is not part of a normal name mangling system res = [] for t in self.templates: res.append(t.get_id_v2()) return u''.join(res) def __unicode__(self): res = [] for t in self.templates: res.append(text_type(t)) return u''.join(res) def describe_signature(self, signode, mode, env, symbol): _verify_description_mode(mode) for t in self.templates: templateNode = addnodes.desc_signature() t.describe_signature(templateNode, 'lastIsName', env, symbol) signode += templateNode class ASTOperatorBuildIn(ASTBase): def __init__(self, op): self.op = op def is_operator(self): return True def get_id_v1(self): if self.op not in _id_operator_v1: raise Exception('Internal error: Build-in operator "%s" can not ' 'be mapped to an id.' % self.op) return _id_operator_v1[self.op] def get_id_v2(self): if self.op not in _id_operator_v2: raise Exception('Internal error: Build-in operator "%s" can not ' 'be mapped to an id.' % self.op) return _id_operator_v2[self.op] def __unicode__(self): if self.op in ('new', 'new[]', 'delete', 'delete[]'): return u'operator ' + self.op else: return u'operator' + self.op def describe_signature(self, signode, mode, env, prefix, symbol): _verify_description_mode(mode) identifier = text_type(self) if mode == 'lastIsName': signode += addnodes.desc_name(identifier, identifier) else: signode += addnodes.desc_addname(identifier, identifier) class ASTOperatorType(ASTBase): def __init__(self, type): self.type = type def is_operator(self): return True def get_id_v1(self): return u'castto-%s-operator' % self.type.get_id_v1() def get_id_v2(self): return u'cv' + self.type.get_id_v2() def __unicode__(self): return u''.join(['operator ', text_type(self.type)]) def get_name_no_template(self): return text_type(self) def describe_signature(self, signode, mode, env, prefix, symbol): _verify_description_mode(mode) identifier = text_type(self) if mode == 'lastIsName': signode += addnodes.desc_name(identifier, identifier) else: signode += addnodes.desc_addname(identifier, identifier) class ASTOperatorLiteral(ASTBase): def __init__(self, identifier): self.identifier = identifier def is_operator(self): return True def get_id_v1(self): raise NoOldIdError() def get_id_v2(self): return u'li' + self.identifier.get_id_v2() def __unicode__(self): return u'operator""' + text_type(self.identifier) def describe_signature(self, signode, mode, env, prefix, symbol): _verify_description_mode(mode) identifier = text_type(self) if mode == 'lastIsName': signode += addnodes.desc_name(identifier, identifier) else: signode += addnodes.desc_addname(identifier, identifier) class ASTTemplateArgConstant(ASTBase): def __init__(self, value): self.value = value def __unicode__(self): return text_type(self.value) def get_id_v1(self): return text_type(self).replace(u' ', u'-') def get_id_v2(self): # TODO: doing this properly needs parsing of expressions, let's just # juse it verbatim for now return u'X' + text_type(self) + u'E' def describe_signature(self, signode, mode, env, symbol): _verify_description_mode(mode) signode += nodes.Text(text_type(self)) class ASTTemplateArgs(ASTBase): def __init__(self, args): assert args is not None assert len(args) > 0 self.args = args def get_id_v1(self): res = [] res.append(':') res.append(u'.'.join(a.get_id_v1() for a in self.args)) res.append(':') return u''.join(res) def get_id_v2(self): res = [] res.append('I') for a in self.args: res.append(a.get_id_v2()) res.append('E') return u''.join(res) def __unicode__(self): res = ', '.join(text_type(a) for a in self.args) return '<' + res + '>' def describe_signature(self, signode, mode, env, symbol): _verify_description_mode(mode) signode += nodes.Text('<') first = True for a in self.args: if not first: signode += nodes.Text(', ') first = False a.describe_signature(signode, 'markType', env, symbol=symbol) signode += nodes.Text('>') class ASTNestedNameElement(ASTBase): def __init__(self, identifier, templateArgs): self.identifier = identifier self.templateArgs = templateArgs def is_operator(self): return False def get_id_v1(self): res = self.identifier.get_id_v1() if self.templateArgs: res += self.templateArgs.get_id_v1() return res def get_id_v2(self): res = self.identifier.get_id_v2() if self.templateArgs: res += self.templateArgs.get_id_v2() return res def __unicode__(self): res = text_type(self.identifier) if self.templateArgs: res += text_type(self.templateArgs) return res def describe_signature(self, signode, mode, env, prefix, symbol): self.identifier.describe_signature(signode, mode, env, prefix, symbol) if self.templateArgs: self.templateArgs.describe_signature(signode, mode, env, symbol) class ASTNestedName(ASTBase): def __init__(self, names, rooted): assert len(names) > 0 self.names = names self.rooted = rooted @property def name(self): return self def num_templates(self): count = 0 for n in self.names: if n.is_operator(): continue if n.templateArgs: count += 1 return count def get_id_v1(self): tt = text_type(self) if tt in _id_shorthands_v1: return _id_shorthands_v1[tt] else: return u'::'.join(n.get_id_v1() for n in self.names) def get_id_v2(self, modifiers=""): 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_v2()) if len(self.names) > 1 or len(modifiers) > 0: res.append('E') return u''.join(res) def __unicode__(self): res = [] if self.rooted: res.append('') for n in self.names: res.append(text_type(n)) return '::'.join(res) def describe_signature(self, signode, mode, env, symbol): _verify_description_mode(mode) # just print the name part, with template args, not template params if mode == 'lastIsName': addname = [] if self.rooted: addname.append('') for n in self.names[:-1]: addname.append(text_type(n)) addname = '::'.join(addname) if len(self.names) > 1: addname += '::' signode += addnodes.desc_addname(addname, addname) self.names[-1].describe_signature(signode, mode, env, '', symbol) elif mode == 'noneIsName': signode += nodes.Text(text_type(self)) elif mode == 'param': name = text_type(self) signode += nodes.emphasis(name, name) elif mode == 'markType': # 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. prefix = '' first = True for name in self.names: if not first: signode += nodes.Text('::') prefix += '::' first = False if name != '': name.describe_signature(signode, mode, env, prefix, symbol) prefix += text_type(name) else: raise Exception('Unknown description mode: %s' % mode) class ASTTrailingTypeSpecFundamental(ASTBase): def __init__(self, name): self.name = name def __unicode__(self): return self.name def get_id_v1(self): res = [] for a in self.name.split(' '): if a in _id_fundamental_v1: res.append(_id_fundamental_v1[a]) else: res.append(a) return u'-'.join(res) def get_id_v2(self): if self.name 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.' % self.name) return _id_fundamental_v2[self.name] def describe_signature(self, signode, mode, env, symbol): signode += nodes.Text(text_type(self.name)) class ASTTrailingTypeSpecName(ASTBase): def __init__(self, prefix, nestedName): self.prefix = prefix self.nestedName = nestedName @property def name(self): return self.nestedName def get_id_v1(self): return self.nestedName.get_id_v1() def get_id_v2(self): return self.nestedName.get_id_v2() def __unicode__(self): res = [] if self.prefix: res.append(self.prefix) res.append(' ') res.append(text_type(self.nestedName)) return u''.join(res) def describe_signature(self, signode, mode, env, symbol): if self.prefix: signode += addnodes.desc_annotation(self.prefix, self.prefix) signode += nodes.Text(' ') self.nestedName.describe_signature(signode, mode, env, symbol=symbol) class ASTFunctinoParameter(ASTBase): def __init__(self, arg, ellipsis=False): self.arg = arg self.ellipsis = ellipsis def get_id_v1(self): if self.ellipsis: return 'z' else: return self.arg.get_id_v1() def get_id_v2(self): if self.ellipsis: return 'z' else: return self.arg.get_id_v2() def __unicode__(self): if self.ellipsis: return '...' else: return text_type(self.arg) def describe_signature(self, signode, mode, env, symbol): _verify_description_mode(mode) if self.ellipsis: signode += nodes.Text('...') else: self.arg.describe_signature(signode, mode, env, symbol=symbol) class ASTParametersQualifiers(ASTBase): def __init__(self, args, volatile, const, refQual, exceptionSpec, override, final, initializer): self.args = args self.volatile = volatile self.const = const self.refQual = refQual self.exceptionSpec = exceptionSpec self.override = override self.final = final self.initializer = initializer # Id v1 ------------------------------------------------------------------ def get_modifiers_id_v1(self): res = [] if self.volatile: res.append('V') if self.const: res.append('C') if self.refQual == '&&': res.append('O') elif self.refQual == '&': res.append('R') return u''.join(res) def get_param_id_v1(self): if len(self.args) == 0: return '' else: return u'__' + u'.'.join(a.get_id_v1() for a in self.args) # Id v2 ------------------------------------------------------------------ def get_modifiers_id_v2(self): res = [] if self.volatile: res.append('V') if self.const: res.append('K') if self.refQual == '&&': res.append('O') elif self.refQual == '&': res.append('R') return u''.join(res) def get_param_id_v2(self): if len(self.args) == 0: return 'v' else: return u''.join(a.get_id_v2() for a in self.args) def __unicode__(self): res = [] res.append('(') first = True for a in self.args: if not first: res.append(', ') first = False res.append(text_type(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(text_type(self.exceptionSpec)) if self.final: res.append(' final') if self.override: res.append(' override') if self.initializer: res.append(' = ') res.append(self.initializer) return u''.join(res) def describe_signature(self, signode, mode, env, symbol): _verify_description_mode(mode) paramlist = addnodes.desc_parameterlist() for arg in self.args: param = addnodes.desc_parameter('', '', noemph=True) if mode == 'lastIsName': # i.e., outer-function params arg.describe_signature(param, 'param', env, symbol=symbol) else: arg.describe_signature(param, 'markType', env, symbol=symbol) paramlist += param signode += paramlist def _add_anno(signode, text): signode += nodes.Text(' ') signode += addnodes.desc_annotation(text, text) def _add_text(signode, text): signode += nodes.Text(' ' + text) if self.volatile: _add_anno(signode, 'volatile') if self.const: _add_anno(signode, 'const') if self.refQual: _add_text(signode, self.refQual) if self.exceptionSpec: _add_anno(signode, text_type(self.exceptionSpec)) if self.final: _add_anno(signode, 'final') if self.override: _add_anno(signode, 'override') if self.initializer: _add_text(signode, '= ' + text_type(self.initializer)) class ASTDeclSpecsSimple(ASTBase): def __init__(self, storage, inline, virtual, explicit, constexpr, volatile, const, friend): self.storage = storage self.inline = inline self.virtual = virtual self.explicit = explicit self.constexpr = constexpr self.volatile = volatile self.const = const self.friend = friend def mergeWith(self, other): if not other: return self return ASTDeclSpecsSimple(self.storage or other.storage, self.inline or other.inline, self.virtual or other.virtual, self.explicit or other.explicit, self.constexpr or other.constexpr, self.volatile or other.volatile, self.const or other.const, self.friend or other.friend) def __unicode__(self): res = [] if self.storage: res.append(self.storage) if self.inline: res.append('inline') if self.friend: res.append('friend') if self.virtual: res.append('virtual') if self.explicit: res.append('explicit') if self.constexpr: res.append('constexpr') if self.volatile: res.append('volatile') if self.const: res.append('const') return u' '.join(res) def describe_signature(self, modifiers): def _add(modifiers, text): if len(modifiers) > 0: modifiers.append(nodes.Text(' ')) modifiers.append(addnodes.desc_annotation(text, text)) if self.storage: _add(modifiers, self.storage) if self.inline: _add(modifiers, 'inline') if self.friend: _add(modifiers, 'friend') if self.virtual: _add(modifiers, 'virtual') if self.explicit: _add(modifiers, 'explicit') if self.constexpr: _add(modifiers, 'constexpr') if self.volatile: _add(modifiers, 'volatile') if self.const: _add(modifiers, 'const') class ASTDeclSpecs(ASTBase): def __init__(self, outer, leftSpecs, rightSpecs, trailing): # 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 @property def name(self): return self.trailingTypeSpec.name def get_id_v1(self): res = [] res.append(self.trailingTypeSpec.get_id_v1()) if self.allSpecs.volatile: res.append('V') if self.allSpecs.const: res.append('C') return u''.join(res) def get_id_v2(self): res = [] if self.leftSpecs.volatile or self.rightSpecs.volatile: res.append('V') if self.leftSpecs.const or self.rightSpecs.volatile: res.append('K') res.append(self.trailingTypeSpec.get_id_v2()) return u''.join(res) def __unicode__(self): res = [] l = text_type(self.leftSpecs) if len(l) > 0: if len(res) > 0: res.append(" ") res.append(l) if self.trailingTypeSpec: if len(res) > 0: res.append(" ") res.append(text_type(self.trailingTypeSpec)) r = text_type(self.rightSpecs) if len(r) > 0: if len(res) > 0: res.append(" ") res.append(r) return "".join(res) def describe_signature(self, signode, mode, env, symbol): _verify_description_mode(mode) modifiers = [] def _add(modifiers, text): if len(modifiers) > 0: modifiers.append(nodes.Text(' ')) modifiers.append(addnodes.desc_annotation(text, text)) self.leftSpecs.describe_signature(modifiers) for m in modifiers: signode += m if self.trailingTypeSpec: if len(modifiers) > 0: signode += nodes.Text(' ') self.trailingTypeSpec.describe_signature(signode, mode, env, symbol=symbol) modifiers = [] self.rightSpecs.describe_signature(modifiers) if len(modifiers) > 0: signode += nodes.Text(' ') for m in modifiers: signode += m class ASTArray(ASTBase): def __init__(self, size): self.size = size def __unicode__(self): return u''.join(['[', text_type(self.size), ']']) def get_id_v1(self): return u'A' def get_id_v2(self): # TODO: this should maybe be done differently return u'A' + text_type(self.size) + u'_' def describe_signature(self, signode, mode, env): _verify_description_mode(mode) signode += nodes.Text(text_type(self)) class ASTDeclaratorPtr(ASTBase): def __init__(self, next, volatile, const): assert next self.next = next self.volatile = volatile self.const = const @property def name(self): return self.next.name def require_space_after_declSpecs(self): # TODO: if has paramPack, then False ? return True def __unicode__(self): res = ['*'] if self.volatile: res.append('volatile') if self.const: if self.volatile: res.append(' ') res.append('const') if self.const or self.volatile: if self.next.require_space_after_declSpecs: res.append(' ') res.append(text_type(self.next)) return u''.join(res) # Id v1 ------------------------------------------------------------------ def get_modifiers_id_v1(self): return self.next.get_modifiers_id_v1() def get_param_id_v1(self): return self.next.get_param_id_v1() def get_ptr_suffix_id_v1(self): res = 'P' if self.volatile: res += 'V' if self.const: res += 'C' return res + self.next.get_ptr_suffix_id_v1() # Id v2 ------------------------------------------------------------------ def get_modifiers_id_v2(self): return self.next.get_modifiers_id_v2() def get_param_id_v2(self): return self.next.get_param_id_v2() def get_ptr_suffix_id_v2(self): res = [self.next.get_ptr_suffix_id_v2()] res.append('P') if self.volatile: res.append('V') if self.const: res.append('C') return u''.join(res) def get_type_id_v2(self, returnTypeId): # 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_v2(returnTypeId=u''.join(res)) # ------------------------------------------------------------------------ def is_function_type(self): return self.next.is_function_type() def describe_signature(self, signode, mode, env, symbol): _verify_description_mode(mode) signode += nodes.Text("*") def _add_anno(signode, text): signode += addnodes.desc_annotation(text, text) if self.volatile: _add_anno(signode, 'volatile') if self.const: if self.volatile: signode += nodes.Text(' ') _add_anno(signode, 'const') if self.const or self.volatile: if self.next.require_space_after_declSpecs: signode += nodes.Text(' ') self.next.describe_signature(signode, mode, env, symbol) class ASTDeclaratorRef(ASTBase): def __init__(self, next): assert next self.next = next @property def name(self): return self.next.name def require_space_after_declSpecs(self): return self.next.require_space_after_declSpecs() def __unicode__(self): return '&' + text_type(self.next) # Id v1 ------------------------------------------------------------------ def get_modifiers_id_v1(self): return self.next.get_modifiers_id_v1() def get_param_id_v1(self): # only the parameters (if any) return self.next.get_param_id_v1() def get_ptr_suffix_id_v1(self): return u'R' + self.next.get_ptr_suffix_id_v1() # Id v2 ------------------------------------------------------------------ def get_modifiers_id_v2(self): return self.next.get_modifiers_id_v2() def get_param_id_v2(self): # only the parameters (if any) return self.next.get_param_id_v2() def get_ptr_suffix_id_v2(self): return self.next.get_ptr_suffix_id_v2() + u'R' def get_type_id_v2(self, returnTypeId): # ReturnType &next, so we are part of the return type of 'next return self.next.get_type_id_v2(returnTypeId=u'R' + returnTypeId) # ------------------------------------------------------------------------ def is_function_type(self): return self.next.is_function_type() def describe_signature(self, signode, mode, env, symbol): _verify_description_mode(mode) signode += nodes.Text("&") self.next.describe_signature(signode, mode, env, symbol) class ASTDeclaratorParamPack(ASTBase): def __init__(self, next): assert next self.next = next @property def name(self): return self.next.name def require_space_after_declSpecs(self): return False def __unicode__(self): res = text_type(self.next) if self.next.name: res = ' ' + res return '...' + res # Id v1 ------------------------------------------------------------------ def get_modifiers_id_v1(self): return self.next.get_modifiers_id_v1() def get_param_id_v1(self): # only the parameters (if any) return self.next.get_param_id_v1() def get_ptr_suffix_id_v1(self): return 'Dp' + self.next.get_ptr_suffix_id_v2() # Id v2 ------------------------------------------------------------------ def get_modifiers_id_v2(self): return self.next.get_modifiers_id_v2() def get_param_id_v2(self): # only the parameters (if any) return self.next.get_param_id_v2() def get_ptr_suffix_id_v2(self): return self.next.get_ptr_suffix_id_v2() + u'Dp' def get_type_id_v2(self, returnTypeId): # ReturnType... next, so we are part of the return type of 'next return self.next.get_type_id_v2(returnTypeId=u'Dp' + returnTypeId) # ------------------------------------------------------------------------ def is_function_type(self): return self.next.is_function_type() def describe_signature(self, signode, mode, env, symbol): _verify_description_mode(mode) signode += nodes.Text("...") if self.next.name: signode += nodes.Text(' ') self.next.describe_signature(signode, mode, env, symbol) class ASTDeclaratorMemPtr(ASTBase): def __init__(self, className, const, volatile, next): assert className assert next self.className = className self.const = const self.volatile = volatile self.next = next @property def name(self): return self.next.name def require_space_after_declSpecs(self): return True def __unicode__(self): res = [] res.append(text_type(self.className)) res.append('::*') if self.volatile: res.append(' volatile') if self.const: res.append(' const') if self.next.require_space_after_declSpecs(): res.append(' ') res.append(text_type(self.next)) return ''.join(res) # Id v1 ------------------------------------------------------------------ def get_modifiers_id_v1(self): raise NoOldIdError() def get_param_id_v1(self): # only the parameters (if any) raise NoOldIdError() def get_ptr_suffix_id_v1(self): raise NoOldIdError() # Id v2 ------------------------------------------------------------------ def get_modifiers_id_v2(self): return self.next.get_modifiers_id_v2() def get_param_id_v2(self): # only the parameters (if any) return self.next.get_param_id_v2() def get_ptr_suffix_id_v2(self): raise NotImplementedError() return self.next.get_ptr_suffix_id_v2() + u'Dp' def get_type_id_v2(self, returnTypeId): # 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_v2() nextReturnTypeId += returnTypeId return self.next.get_type_id_v2(nextReturnTypeId) # ------------------------------------------------------------------------ def is_function_type(self): return self.next.is_function_type() def describe_signature(self, signode, mode, env, symbol): _verify_description_mode(mode) self.className.describe_signature(signode, mode, env, symbol) signode += nodes.Text('::*') def _add_anno(signode, text): signode += addnodes.desc_annotation(text, text) if self.volatile: _add_anno(signode, 'volatile') if self.const: if self.volatile: signode += nodes.Text(' ') _add_anno(signode, 'const') if self.next.require_space_after_declSpecs(): if self.volatile or self.const: signode += nodes.Text(' ') self.next.describe_signature(signode, mode, env, symbol) class ASTDeclaratorParen(ASTBase): def __init__(self, inner, next): 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): return self.inner.name def require_space_after_declSpecs(self): return True def __unicode__(self): res = ['('] res.append(text_type(self.inner)) res.append(')') res.append(text_type(self.next)) return ''.join(res) # Id v1 ------------------------------------------------------------------ def get_modifiers_id_v1(self): return self.inner.get_modifiers_id_v1() def get_param_id_v1(self): # only the parameters (if any) return self.inner.get_param_id_v1() def get_ptr_suffix_id_v1(self): raise NoOldIdError() # TODO: was this implemented before? return self.next.get_ptr_suffix_id_v2() + \ self.inner.get_ptr_suffix_id_v2() # Id v2 ------------------------------------------------------------------ def get_modifiers_id_v2(self): return self.inner.get_modifiers_id_v2() def get_param_id_v2(self): # only the parameters (if any) return self.inner.get_param_id_v2() def get_ptr_suffix_id_v2(self): return self.inner.get_ptr_suffix_id_v2() + \ self.next.get_ptr_suffix_id_v2() def get_type_id_v2(self, returnTypeId): # ReturnType (inner)next, so 'inner' returns everything outside nextId = self.next.get_type_id_v2(returnTypeId) return self.inner.get_type_id_v2(returnTypeId=nextId) # ------------------------------------------------------------------------ def is_function_type(self): return self.inner.is_function_type() def describe_signature(self, signode, mode, env, symbol): _verify_description_mode(mode) signode += nodes.Text('(') self.inner.describe_signature(signode, mode, env, symbol) signode += nodes.Text(')') self.next.describe_signature(signode, "noneIsName", env, symbol) class ASTDecleratorNameParamQual(ASTBase): def __init__(self, declId, arrayOps, paramQual): self.declId = declId self.arrayOps = arrayOps self.paramQual = paramQual @property def name(self): return self.declId # Id v1 ------------------------------------------------------------------ def get_modifiers_id_v1(self): # only the modifiers for a function, e.g., # cv-qualifiers if self.paramQual: return self.paramQual.get_modifiers_id_v1() raise Exception( "This should only be called on a function: %s" % text_type(self)) def get_param_id_v1(self): # only the parameters (if any) if self.paramQual: return self.paramQual.get_param_id_v1() else: return '' def get_ptr_suffix_id_v1(self): # only the array specifiers return u''.join(a.get_id_v1() for a in self.arrayOps) # Id v2 ------------------------------------------------------------------ def get_modifiers_id_v2(self): # only the modifiers for a function, e.g., # cv-qualifiers if self.paramQual: return self.paramQual.get_modifiers_id_v2() raise Exception( "This should only be called on a function: %s" % text_type(self)) def get_param_id_v2(self): # only the parameters (if any) if self.paramQual: return self.paramQual.get_param_id_v2() else: return '' def get_ptr_suffix_id_v2(self): # only the array specifiers return u''.join(a.get_id_v2() for a in self.arrayOps) def get_type_id_v2(self, returnTypeId): res = [] # TOOD: can we actually have both array ops and paramQual? res.append(self.get_ptr_suffix_id_v2()) if self.paramQual: res.append(self.get_modifiers_id_v2()) res.append('F') res.append(returnTypeId) res.append(self.get_param_id_v2()) res.append('E') else: res.append(returnTypeId) return u''.join(res) # ------------------------------------------------------------------------ def require_space_after_declSpecs(self): return self.declId is not None def is_function_type(self): return self.paramQual is not None def __unicode__(self): res = [] if self.declId: res.append(text_type(self.declId)) for op in self.arrayOps: res.append(text_type(op)) if self.paramQual: res.append(text_type(self.paramQual)) return u''.join(res) def describe_signature(self, signode, mode, env, symbol): _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) if self.paramQual: self.paramQual.describe_signature(signode, mode, env, symbol) class ASTInitializer(ASTBase): def __init__(self, value): self.value = value def __unicode__(self): return u''.join([' = ', text_type(self.value)]) def describe_signature(self, signode, mode): _verify_description_mode(mode) signode += nodes.Text(text_type(self)) class ASTType(ASTBase): def __init__(self, declSpecs, decl): assert declSpecs assert decl self.declSpecs = declSpecs self.decl = decl @property def name(self): name = self.decl.name return name def get_id_v1(self, objectType=None, symbol=None): res = [] if objectType: # needs the name if objectType == 'function': # also modifiers res.append(symbol.get_full_nested_name().get_id_v1()) res.append(self.decl.get_param_id_v1()) res.append(self.decl.get_modifiers_id_v1()) 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_v1()) else: print(objectType) assert False else: # only type encoding if self.decl.is_function_type(): raise NoOldIdError() res.append(self.declSpecs.get_id_v1()) res.append(self.decl.get_ptr_suffix_id_v1()) res.append(self.decl.get_param_id_v1()) return u''.join(res) def get_id_v2(self, objectType=None, symbol=None): res = [] if objectType: # needs the name if objectType == 'function': # also modifiers modifiers = self.decl.get_modifiers_id_v2() res.append(symbol.get_full_nested_name().get_id_v2(modifiers)) res.append(self.decl.get_param_id_v2()) elif objectType == 'type': # just the name res.append(symbol.get_full_nested_name().get_id_v2()) else: print(objectType) assert False 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_v2() typeId = self.decl.get_type_id_v2(returnTypeId) res.append(typeId) return u''.join(res) def __unicode__(self): res = [] declSpecs = text_type(self.declSpecs) res.append(declSpecs) if self.decl.require_space_after_declSpecs() and len(declSpecs) > 0: res.append(u' ') res.append(text_type(self.decl)) return u''.join(res) def describe_signature(self, signode, mode, env, symbol): _verify_description_mode(mode) self.declSpecs.describe_signature(signode, 'markType', env, symbol) if (self.decl.require_space_after_declSpecs() and len(text_type(self.declSpecs)) > 0): signode += nodes.Text(' ') self.decl.describe_signature(signode, mode, env, symbol) class ASTTypeWithInit(ASTBase): def __init__(self, type, init): self.type = type self.init = init @property def name(self): return self.type.name def get_id_v1(self, objectType=None, symbol=None): if objectType == 'member': return symbol.get_full_nested_name().get_id_v1() + u'__' \ + self.type.get_id_v1() else: return self.type.get_id_v1(objectType) def get_id_v2(self, objectType=None, symbol=None): if objectType == 'member': return symbol.declaration.name.get_id_v2() else: return self.type.get_id_v2() def __unicode__(self): res = [] res.append(text_type(self.type)) if self.init: res.append(text_type(self.init)) return u''.join(res) def describe_signature(self, signode, mode, env, symbol): _verify_description_mode(mode) self.type.describe_signature(signode, mode, env, symbol=symbol) if self.init: self.init.describe_signature(signode, mode) class ASTTypeUsing(ASTBase): def __init__(self, name, type): self.name = name self.type = type def get_id_v1(self, objectType=None, symbol=None): raise NoOldIdError() def get_id_v2(self, objectType=None, symbol=None): return symbol.get_full_nested_name().get_id_v2() def __unicode__(self): res = [] res.append(text_type(self.name)) if self.type: res.append(' = ') res.append(text_type(self.type)) return u''.join(res) def describe_signature(self, signode, mode, env, symbol): _verify_description_mode(mode) self.name.describe_signature(signode, mode, env, symbol=symbol) if self.type: signode += nodes.Text(' = ') self.type.describe_signature(signode, 'markType', env, symbol=symbol) class ASTBaseClass(ASTBase): def __init__(self, name, visibility, virtual, pack): self.name = name self.visibility = visibility self.virtual = virtual self.pack = pack def __unicode__(self): res = [] if self.visibility != 'private': res.append(self.visibility) res.append(' ') if self.virtual: res.append('virtual ') res.append(text_type(self.name)) if self.pack: res.append('...') return u''.join(res) def describe_signature(self, signode, mode, env, symbol): _verify_description_mode(mode) if self.visibility != 'private': signode += addnodes.desc_annotation(self.visibility, self.visibility) signode += nodes.Text(' ') if self.virtual: signode += addnodes.desc_annotation('virtual', 'virtual') signode += nodes.Text(' ') self.name.describe_signature(signode, 'markType', env, symbol=symbol) if self.pack: signode += nodes.Text('...') class ASTClass(ASTBase): def __init__(self, name, final, bases): self.name = name self.final = final self.bases = bases def get_id_v1(self, objectType, symbol): return symbol.get_full_nested_name().get_id_v1() def get_id_v2(self, objectType, symbol): return symbol.get_full_nested_name().get_id_v2() def __unicode__(self): res = [] res.append(text_type(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(text_type(b)) return u''.join(res) def describe_signature(self, signode, mode, env, symbol): _verify_description_mode(mode) self.name.describe_signature(signode, mode, env, symbol=symbol) if self.final: signode += nodes.Text(' ') signode += addnodes.desc_annotation('final', 'final') if len(self.bases) > 0: signode += nodes.Text(' : ') for b in self.bases: b.describe_signature(signode, mode, env, symbol=symbol) signode += nodes.Text(', ') signode.pop() class ASTEnum(ASTBase): def __init__(self, name, scoped, underlyingType): self.name = name self.scoped = scoped self.underlyingType = underlyingType def get_id_v1(self, objectType, symbol): raise NoOldIdError() def get_id_v2(self, objectType, symbol): return symbol.get_full_nested_name().get_id_v2() def __unicode__(self): res = [] if self.scoped: res.append(self.scoped) res.append(' ') res.append(text_type(self.name)) if self.underlyingType: res.append(' : ') res.append(text_type(self.underlyingType)) return u''.join(res) def describe_signature(self, signode, mode, env, symbol): _verify_description_mode(mode) # self.scoped has been done by the CPPEnumObject self.name.describe_signature(signode, mode, env, symbol=symbol) if self.underlyingType: signode += nodes.Text(' : ') self.underlyingType.describe_signature(signode, 'noneIsName', env, symbol=symbol) class ASTEnumerator(ASTBase): def __init__(self, name, init): self.name = name self.init = init def get_id_v1(self, objectType, symbol): raise NoOldIdError() def get_id_v2(self, objectType, symbol): return symbol.get_full_nested_name().get_id_v2() def __unicode__(self): res = [] res.append(text_type(self.name)) if self.init: res.append(text_type(self.init)) return u''.join(res) def describe_signature(self, signode, mode, env, symbol): _verify_description_mode(mode) self.name.describe_signature(signode, mode, env, symbol=symbol) if self.init: self.init.describe_signature(signode, 'noneIsName') class ASTDeclaration(ASTBase): def __init__(self, objectType, visibility, templatePrefix, declaration): self.objectType = objectType self.visibility = visibility self.templatePrefix = templatePrefix self.declaration = declaration self.symbol = None # set by CPPObject._add_enumerator_to_parent self.enumeratorScopedSymbol = None def clone(self): if self.templatePrefix: templatePrefixClone = self.templatePrefix.clone() else: templatePrefixClone = None return ASTDeclaration(self.objectType, self.visibility, templatePrefixClone, self.declaration.clone()) @property def name(self): return self.declaration.name def get_id_v1(self): if self.templatePrefix: raise NoOldIdError() if self.objectType == 'enumerator' and self.enumeratorScopedSymbol: return self.enumeratorScopedSymbol.declaration.get_id_v1() return self.declaration.get_id_v1(self.objectType, self.symbol) def get_id_v2(self, prefixed=True): if self.objectType == 'enumerator' and self.enumeratorScopedSymbol: return self.enumeratorScopedSymbol.declaration.get_id_v2(prefixed) if prefixed: res = [_id_prefix_v2] else: res = [] if self.templatePrefix: res.append(self.templatePrefix.get_id_v2()) res.append(self.declaration.get_id_v2(self.objectType, self.symbol)) return u''.join(res) def get_newest_id(self): return self.get_id_v2() def __unicode__(self): res = [] if self.visibility and self.visibility != "public": res.append(self.visibility) res.append(u' ') if self.templatePrefix: res.append(text_type(self.templatePrefix)) res.append(text_type(self.declaration)) return u''.join(res) def describe_signature(self, signode, mode, env): _verify_description_mode(mode) # the caller of the domain added a desc_signature node # let's pop it so we can add templates before that parentNode = signode.parent mainDeclNode = signode parentNode.pop() assert self.symbol if self.templatePrefix: self.templatePrefix.describe_signature(parentNode, mode, env, symbol=self.symbol) if self.visibility and self.visibility != "public": mainDeclNode += addnodes.desc_annotation(self.visibility + " ", self.visibility + " ") if self.objectType == 'type': mainDeclNode += addnodes.desc_annotation('type ', 'type ') elif self.objectType == 'member': pass elif self.objectType == 'function': pass elif self.objectType == 'class': mainDeclNode += addnodes.desc_annotation('class ', 'class ') elif self.objectType == 'enum': prefix = 'enum ' if self.scoped: prefix += self.scoped prefix += ' ' mainDeclNode += addnodes.desc_annotation(prefix, prefix) elif self.objectType == 'enumerator': mainDeclNode += addnodes.desc_annotation('enumerator ', 'enumerator ') else: assert False self.declaration.describe_signature(mainDeclNode, mode, env, symbol=self.symbol) parentNode += mainDeclNode class ASTNamespace(ASTBase): def __init__(self, nestedName, templatePrefix): self.nestedName = nestedName self.templatePrefix = templatePrefix class Symbol(object): def _assert_invariants(self): if not self.parent: # parent == None means global scope, so declaration means a parent assert not self.identifier assert not self.templateParams assert not self.templateArgs assert not self.declaration assert not self.docname else: if not self.identifier: # in case it's an operator assert self.declaration if self.declaration: assert self.docname def __init__(self, parent, identifier, templateParams, templateArgs, declaration, docname): self.parent = parent self.identifier = identifier self.templateParams = templateParams # template self.templateArgs = templateArgs # identifier self.declaration = declaration self.docname = docname self._assert_invariants() self.children = [] if self.parent: self.parent.children.append(self) if self.declaration: self.declaration.symbol = self # add symbols for the template params # (do it after self.children has been initialised if self.templateParams: for p in self.templateParams.params: if not p.get_identifier(): continue # only add a declaration if we our selfs from a declaration if declaration: decl = ASTDeclaration('templateParam', None, None, p) else: decl = None nne = ASTNestedNameElement(p.get_identifier(), None) nn = ASTNestedName([nne], rooted=False) self._add_symbols(nn, [], decl, docname) def _fill_empty(self, declaration, docname): self._assert_invariants() assert not self.declaration assert not self.docname assert declaration assert docname self.declaration = declaration self.declaration.symbol = self self.docname = docname self._assert_invariants() def clear_doc(self, docname): newChildren = [] for sChild in self.children: sChild.clear_doc(docname) if sChild.declaration and sChild.docname == docname: sChild.declaration = None sChild.docname = None # Just remove operators, because there is no identification if # they got removed. # Don't remove other symbols because they may be used in namespace # directives. if sChild.identifier or sChild.declaration: newChildren.append(sChild) self.children = newChildren def get_all_symbols(self): yield self for sChild in self.children: for s in sChild.get_all_symbols(): yield s def get_lookup_key(self): if not self.parent: # specialise for the root return None symbols = [] s = self while s.parent: symbols.append(s) s = s.parent symbols.reverse() key = [] for s in symbols: if s.identifier: nne = ASTNestedNameElement(s.identifier, s.templateArgs) else: assert s.declaration nne = s.declaration.name.names[-1] key.append((nne, s.templateParams)) return key def get_full_nested_name(self): names = [] for nne, templateParams in self.get_lookup_key(): names.append(nne) return ASTNestedName(names, rooted=False) def _find_named_symbol(self, identifier, templateParams, templateArgs, operator, templateShorthand, matchSelf): assert (identifier is None) != (operator is None) def matches(s): if s.identifier != identifier: return False if not s.identifier: if not s.declaration: return False assert operator name = s.declaration.name.names[-1] if not name.is_operator(): return False if text_type(name) != text_type(operator): 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 text_type(s.templateParams) != text_type(templateParams): return False if (s.templateArgs is None) != (templateArgs is None): return False if s.templateArgs: # TODO: do better comparison if text_type(s.templateArgs) != text_type(templateArgs): return False return True if matchSelf and matches(self): return self for s in self.children: if matches(s): return s return None def _add_symbols(self, nestedName, templateDecls, declaration, docname): # This condition should be checked at the parser level. # 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)) parentSymbol = self if nestedName.rooted: while parentSymbol.parent: parentSymbol = parentSymbol.parent names = nestedName.names iTemplateDecl = 0 for name in names[:-1]: # there shouldn't be anything inside an operator # (other than template parameters, which are not added this way, right?) assert not name.is_operator() identifier = name.identifier templateArgs = name.templateArgs if templateArgs: assert iTemplateDecl < len(templateDecls) templateParams = templateDecls[iTemplateDecl] iTemplateDecl += 1 else: templateParams = None symbol = parentSymbol._find_named_symbol(identifier, templateParams, templateArgs, operator=None, templateShorthand=False, matchSelf=False) if symbol is None: symbol = Symbol(parent=parentSymbol, identifier=identifier, templateParams=templateParams, templateArgs=templateArgs, declaration=None, docname=None) parentSymbol = symbol name = names[-1] if name.is_operator(): identifier = None templateArgs = None operator = name else: identifier = name.identifier templateArgs = name.templateArgs operator = None if iTemplateDecl < len(templateDecls): if iTemplateDecl + 1 != len(templateDecls): print(text_type(templateDecls)) print(text_type(nestedName)) assert iTemplateDecl + 1 == len(templateDecls) templateParams = templateDecls[iTemplateDecl] else: assert iTemplateDecl == len(templateDecls) templateParams = None symbol = parentSymbol._find_named_symbol(identifier, templateParams, templateArgs, operator, templateShorthand=False, matchSelf=False) if symbol: if not declaration: # good, just a scope creation return symbol if not symbol.declaration: # If someone first opened the scope, and then later # declares it, e.g, # .. namespace:: Test # .. namespace:: nullptr # .. class:: Test symbol._fill_empty(declaration, docname) return symbol # it may simply be a functin overload # TODO: it could be a duplicate but let's just insert anyway # the id generation will warn about it symbol = Symbol(parent=parentSymbol, identifier=identifier, templateParams=templateParams, templateArgs=templateArgs, declaration=declaration, docname=docname) else: symbol = Symbol(parent=parentSymbol, identifier=identifier, templateParams=templateParams, templateArgs=templateArgs, declaration=declaration, docname=docname) return symbol def merge_with(self, other, docnames, env): assert other is not None for otherChild in other.children: if not otherChild.identifier: if not otherChild.declaration: print("Problem in symbol tree merging") print("OtherChild.dump:") print(otherChild.dump(0)) print("Other.dump:") print(other.dump(0)) assert otherChild.declaration operator = otherChild.declaration.name.names[-1] assert operator.is_operator() else: operator = None ourChild = self._find_named_symbol(otherChild.identifier, otherChild.templateParams, otherChild.templateArgs, operator, templateShorthand=False, matchSelf=False) if ourChild is None: # TODO: hmm, should we prune by docnames? self.children.append(otherChild) otherChild.parent = self otherChild._assert_invariants() continue if otherChild.declaration and otherChild.docname in docnames: if not ourChild.declaration: ourChild._fill_empty(otherChild.declaration, otherChild.docname) elif ourChild.docname != otherChild.docname: name = text_type(ourChild.declaration) msg = "Duplicate declaration, also defined in '%s'.\n" msg += "Declaration is '%s'." msg = msg % (ourChild.docname, name) env.warn(otherChild.docname, msg) else: # Both have declarations, and in the same docname. # This can apparently happen, it should be safe to # just ignore it, right? pass ourChild.merge_with(otherChild, docnames, env) def add_name(self, nestedName, templatePrefix=None): if templatePrefix: templateDecls = templatePrefix.templates else: templateDecls = [] return self._add_symbols(nestedName, templateDecls, declaration=None, docname=None) def add_declaration(self, declaration, docname): assert declaration assert docname nestedName = declaration.name if declaration.templatePrefix: templateDecls = declaration.templatePrefix.templates else: templateDecls = [] return self._add_symbols(nestedName, templateDecls, declaration, docname) def find_identifier(self, identifier, matchSelf): if matchSelf and self.identifier and self.identifier == identifier: return self for s in self.children: if s.identifier and s.identifier == identifier: return s return None def direct_lookup(self, key): s = self for name, templateParams in key: if name.is_operator(): identifier = None templateArgs = None operator = name else: identifier = name.identifier templateArgs = name.templateArgs operator = None s = s._find_named_symbol(identifier, templateParams, templateArgs, operator, templateShorthand=False, matchSelf=True) if not s: return None return s def find_name(self, nestedName, templateDecls, templateShorthand, matchSelf): # templateShorthand: missing template parameter lists for templates is ok # TODO: unify this with the _add_symbols # This condition should be checked at the parser level. assert len(templateDecls) <= nestedName.num_templates() + 1 parentSymbol = self if nestedName.rooted: while parentSymbol.parent: parentSymbol = parentSymbol.parent names = nestedName.names # walk up until we find the first identifier firstName = names[0] if not firstName.is_operator(): while parentSymbol.parent: if parentSymbol.find_identifier(firstName.identifier, matchSelf=matchSelf): break parentSymbol = parentSymbol.parent iTemplateDecl = 0 for iName in range(len(names)): name = names[iName] if iName + 1 == len(names): if name.is_operator(): identifier = None templateArgs = None operator = name else: identifier = name.identifier templateArgs = name.templateArgs operator = None if iTemplateDecl < len(templateDecls): assert iTemplateDecl + 1 == len(templateDecls) templateParams = templateDecls[iTemplateDecl] else: assert iTemplateDecl == len(templateDecls) templateParams = None symbol = parentSymbol._find_named_symbol(identifier, templateParams, templateArgs, operator, templateShorthand=templateShorthand, matchSelf=matchSelf) if symbol: return symbol else: return None else: # there shouldn't be anything inside an operator assert not name.is_operator() identifier = name.identifier templateArgs = name.templateArgs if templateArgs and iTemplateDecl < len(templateDecls): templateParams = templateDecls[iTemplateDecl] iTemplateDecl += 1 else: templateParams = None symbol = parentSymbol._find_named_symbol(identifier, templateParams, templateArgs, operator=None, templateShorthand=templateShorthand, matchSelf=matchSelf) if symbol is None: # TODO: maybe search without template args return None parentSymbol = symbol assert False # should have returned in the loop def to_string(self, indent): res = ['\t'*indent] if not self.parent: res.append('::') else: if self.templateParams: res.append(text_type(self.templateParams)) res.append('\n') res.append('\t'*indent) if self.identifier: res.append(text_type(self.identifier)) else: res.append(text_type(self.declaration)) if self.templateArgs: res.append(text_type(self.templateArgs)) if self.declaration: res.append(": ") res.append(text_type(self.declaration)) if self.docname: res.append('\t(') res.append(self.docname) res.append(')') res.append('\n') return ''.join(res) def dump(self, indent): res = [self.to_string(indent)] for c in self.children: res.append(c.dump(indent + 1)) return ''.join(res) class DefinitionParser(object): # those without signedness and size modifiers # see http://en.cppreference.com/w/cpp/language/types _simple_fundemental_types = ( 'void', 'bool', 'char', 'wchar_t', 'char16_t', 'char32_t', 'int', 'float', 'double', 'auto' ) _prefix_keys = ('class', 'struct', 'enum', 'union', 'typename') def __init__(self, definition, warnEnv): self.definition = definition.strip() self.pos = 0 self.end = len(self.definition) self.last_match = None self._previous_state = (0, None) self.warnEnv = warnEnv def _make_multi_error(self, errors, header): if len(errors) == 1: return DefinitionError(header + '\n' + errors[0][0].description) result = [header, '\n'] for e in errors: if len(e[1]) > 0: ident = ' ' result.append(e[1]) result.append(':\n') for line in e[0].description.split('\n'): if len(line) == 0: continue result.append(ident) result.append(line) result.append('\n') else: result.append(e[0].description) return DefinitionError(''.join(result)) def status(self, msg): # for debugging indicator = '-' * self.pos + '^' print("%s\n%s\n%s" % (msg, self.definition, indicator)) def fail(self, msg): indicator = '-' * self.pos + '^' raise DefinitionError( 'Invalid definition: %s [error at %d]\n %s\n %s' % (msg, self.pos, self.definition, indicator)) def warn(self, msg): if self.warnEnv: self.warnEnv.warn(msg) else: print("Warning: %s" % msg) def match(self, regex): match = regex.match(self.definition, self.pos) if match is not None: self._previous_state = (self.pos, self.last_match) self.pos = match.end() self.last_match = match return True return False def backout(self): self.pos, self.last_match = self._previous_state def skip_string(self, string): strlen = len(string) if self.definition[self.pos:self.pos + strlen] == string: self.pos += strlen return True return False def skip_word(self, word): return self.match(re.compile(r'\b%s\b' % re.escape(word))) def skip_ws(self): return self.match(_whitespace_re) def skip_word_and_ws(self, word): if self.skip_word(word): self.skip_ws() return True return False @property def eof(self): return self.pos >= self.end @property def current_char(self): try: return self.definition[self.pos] except IndexError: return 'EOF' @property def matched_text(self): if self.last_match is not None: return self.last_match.group() def read_rest(self): rv = self.definition[self.pos:] self.pos = self.end return rv def assert_end(self): self.skip_ws() if not self.eof: self.fail('Expected end of definition.') def _parse_expression(self, end): # Stupidly "parse" an expression. # 'end' should be a list of characters which ends the expression. assert end 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 = [] while not self.eof: if (len(symbols) == 0 and self.current_char in end): break if self.current_char in brackets.keys(): symbols.append(brackets[self.current_char]) elif len(symbols) > 0 and self.current_char == symbols[-1]: symbols.pop() self.pos += 1 if self.eof: self.fail("Could not find end of expression starting at %d." % startPos) value = self.definition[startPos:self.pos].strip() return value.strip() def _parse_operator(self): self.skip_ws() # adapted from the old code # thank god, 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): self.skip_ws() if not self.skip_string('<'): return None prevErrors = [] templateArgs = [] while 1: pos = self.pos parsedComma = False parsedEnd = False try: type = self._parse_type(named=False) self.skip_ws() if 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_expression(end=[',', '>']) self.skip_ws() if 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) if parsedEnd: assert not parsedComma break return ASTTemplateArgs(templateArgs) def _parse_nested_name(self, memberPointer=False): names = [] self.skip_ws() rooted = False if self.skip_string('::'): rooted = True while 1: self.skip_ws() if self.skip_word_and_ws('template'): self.fail("'template' in nested name not implemented.") elif self.skip_word_and_ws('operator'): op = self._parse_operator() names.append(op) else: if not self.match(_identifier_re): if memberPointer and len(names) > 0: 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) templateArgs = self._parse_template_argument_list() identifier = ASTIdentifier(identifier) names.append(ASTNestedNameElement(identifier, templateArgs)) self.skip_ws() if not self.skip_string('::'): if memberPointer: self.fail("Expected '::' in pointer to member (function).") break return ASTNestedName(names, rooted) def _parse_trailing_type_spec(self): # fundemental types self.skip_ws() for t in self._simple_fundemental_types: if self.skip_word(t): return ASTTrailingTypeSpecFundamental(t) # TODO: this could/should be more strict elements = [] if self.skip_word_and_ws('signed'): elements.append('signed') elif self.skip_word_and_ws('unsigned'): elements.append('unsigned') while 1: if self.skip_word_and_ws('short'): elements.append('short') elif self.skip_word_and_ws('long'): elements.append('long') else: break if self.skip_word_and_ws('char'): elements.append('char') elif self.skip_word_and_ws('int'): elements.append('int') elif self.skip_word_and_ws('double'): elements.append('double') if len(elements) > 0: return ASTTrailingTypeSpecFundamental(u' '.join(elements)) # decltype self.skip_ws() if self.skip_word_and_ws('decltype'): self.fail('"decltype(.)" in trailing_type_spec not implemented') # prefixed prefix = None self.skip_ws() for k in self._prefix_keys: if self.skip_word_and_ws(k): prefix = k break nestedName = self._parse_nested_name() return ASTTrailingTypeSpecName(prefix, nestedName) def _parse_parameters_and_qualifiers(self, paramMode): 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(ASTFunctinoParameter(None, True)) self.skip_ws() if not self.skip_string(')'): self.fail('Expected ")" after "..." in ' 'parameters_and_qualifiers.') break if paramMode == 'function': arg = self._parse_type_with_init(outer=None, named='single') else: arg = self._parse_type(named=False) # TODO: parse default parameters # TODO: didn't we just do that? args.append(ASTFunctinoParameter(arg)) self.skip_ws() if self.skip_string(','): continue elif self.skip_string(')'): break else: self.fail( 'Expecting "," or ")" in parameters_and_qualifiers, ' 'got "%s".' % self.current_char) # TODO: why did we have this bail-out? # does it hurt to parse the extra stuff? # it's needed for pointer to member functions if paramMode != 'function' and False: return ASTParametersQualifiers( args, None, None, None, None, None, None, None) 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 override = None final = None initializer = None self.skip_ws() if self.skip_string('noexcept'): exceptionSpec = 'noexcept' self.skip_ws() if self.skip_string('('): self.fail('Parameterised "noexcept" not implemented.') 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 self.skip_ws() if 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.' % u'" or "'.join(valid)) return ASTParametersQualifiers( args, volatile, const, refQual, exceptionSpec, override, final, initializer) def _parse_decl_specs_simple(self, outer, typed): """Just parse the simple ones.""" storage = None inline = None virtual = None explicit = None constexpr = None volatile = None const = None friend = None while 1: # accept any permutation of a subset of some decl-specs self.skip_ws() if not storage: if outer in ('member', 'function'): if self.skip_word('static'): storage = 'static' continue if outer == 'member': if self.skip_word('mutable'): storage = 'mutable' continue if self.skip_word('register'): storage = 'register' continue if outer == 'function': # function-specifiers if not inline: inline = self.skip_word('inline') if inline: 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 explicit: explicit = self.skip_word('explicit') if explicit: continue if not constexpr and outer in ('member', 'function'): constexpr = self.skip_word("constexpr") if constexpr: continue if not volatile and typed: volatile = self.skip_word('volatile') if volatile: continue if not const and typed: const = self.skip_word('const') if const: continue break return ASTDeclSpecsSimple(storage, inline, virtual, explicit, constexpr, volatile, const, friend) def _parse_decl_specs(self, outer, typed=True): 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_param_qual(self, named, paramMode, typed): # 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], 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('['): value = self._parse_expression(end=[']']) res = self.skip_string(']') assert res arrayOps.append(ASTArray(value)) continue else: break paramQual = self._parse_parameters_and_qualifiers(paramMode) return ASTDecleratorNameParamQual(declId=declId, arrayOps=arrayOps, paramQual=paramQual) def _parse_declerator(self, named, paramMode, typed=True): # 'typed' here means 'parse return type stuff' if paramMode not in ('type', 'function', 'operatorCast'): 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 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_declerator(named, paramMode, typed) return ASTDeclaratorPtr(next=next, volatile=volatile, const=const) # TODO: shouldn't we parse an R-value ref here first? if typed and self.skip_string("&"): next = self._parse_declerator(named, paramMode, typed) return ASTDeclaratorRef(next=next) if typed and self.skip_string("..."): next = self._parse_declerator(named, paramMode, False) return ASTDeclaratorParamPack(next=next) 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_declerator(named, paramMode, typed) return ASTDeclaratorMemPtr(name, const, volatile, 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 ASTDecleratorNameParamQual(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_param_qual(named, paramMode, typed) return res except DefinitionError as exParamQual: prevErrors.append((exParamQual, "If declId, 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 b # inside, right? inner = self._parse_declerator(named, paramMode, typed) if not self.skip_string(')'): self.fail("Expected ')' in \"( ptr-declarator )\"") next = self._parse_declerator(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) pos = self.pos try: return self._parse_declarator_name_param_qual(named, paramMode, typed) 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) def _parse_initializer(self, outer=None): self.skip_ws() # TODO: support paren and brace initialization for memberObject if not self.skip_string('='): return None else: if outer == 'member': value = self.read_rest().strip() elif outer == 'templateParam': value = self._parse_expression(end=[',', '>']) elif outer is None: # function parameter value = self._parse_expression(end=[',', ')']) else: self.fail("Internal error, initializer for outer '%s' not " "implemented." % outer) return ASTInitializer(value) def _parse_type(self, named, outer=None): """ 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, # destrutors, cast operators prevErrors = [] startPos = self.pos # first try without the type try: declSpecs = self._parse_decl_specs(outer=outer, typed=False) decl = self._parse_declerator(named=True, paramMode=outer, typed=False) self.assert_end() except DefinitionError as exUntyped: if outer == 'type': desc = "If just a name" elif outer == 'function': desc = "If the function has no return type" else: assert False prevErrors.append((exUntyped, desc)) self.pos = startPos try: declSpecs = self._parse_decl_specs(outer=outer) decl = self._parse_declerator(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: assert False 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: assert False raise self._make_multi_error(prevErrors, header) else: # For testing purposes. # do it again to get the proper traceback (how do you # relieable save a traceback when an exception is # constructed?) pass self.pos = startPos typed = True declSpecs = self._parse_decl_specs(outer=outer, typed=typed) decl = self._parse_declerator(named=True, paramMode=outer, typed=typed) else: paramMode = 'type' if outer == 'member': # i.e., 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_declerator(named=named, paramMode=paramMode) return ASTType(declSpecs, decl) def _parse_type_with_init(self, named, outer): if outer: assert outer in ('type', 'member', 'function', 'templateParam') type = self._parse_type(outer=outer, named=named) init = self._parse_initializer(outer=outer) return ASTTypeWithInit(type, init) def _parse_type_using(self): 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_class(self): 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 = 'private' 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 else: break return ASTClass(name, final, bases) def _parse_enum(self): scoped = None # is set by CPPEnumObject self.skip_ws() 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) def _parse_enumerator(self): name = self._parse_nested_name() self.skip_ws() init = None if self.skip_string('='): self.skip_ws() init = ASTInitializer(self.read_rest()) return ASTEnumerator(name, init) def _parse_template_parameter_list(self): # only: '<' parameter-list '>' # we assume that 'template' has just been parsed templateParams = [] self.skip_ws() if not self.skip_string("<"): self.fail("Expected '<' after 'template'") while 1: prevErrors = [] self.skip_ws() if self.skip_word('template'): # declare a tenplate template parameter nestedParams = self._parse_template_parameter_list() else: nestedParams = None self.skip_ws() key = None 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.") if key: # declare a type or 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 data = ASTTemplateKeyParamPackIdDefault(key, identifier, parameterPack, default) if nestedParams: # template type param = ASTTemplateParamTemplateType(nestedParams, data) else: # type param = ASTTemplateParamType(data) templateParams.append(param) else: # declare a non-type parameter pos = self.pos try: param = self._parse_type_with_init('maybe', 'templateParam') templateParams.append(ASTTemplateParamNonType(param)) except DefinitionError as e: prevErrors.append((e, "If non-type template parameter")) self.pos = pos self.skip_ws() if self.skip_string('>'): return ASTTemplateParams(templateParams) elif self.skip_string(','): continue else: header = "Error in template parameter list." try: self.fail('Expected "=", ",", or ">".') except DefinitionError as e: prevErrors.append((e, "")) raise self._make_multi_error(prevErrors, header) def _parse_template_declaration_prefix(self): templates = [] while 1: self.skip_ws() if not self.skip_word("template"): break params = self._parse_template_parameter_list() templates.append(params) if len(templates) == 0: return None else: return ASTTemplateDeclarationPrefix(templates) def _check_template_consistency(self, nestedName, templatePrefix, fullSpecShorthand): numArgs = nestedName.num_templates() if not templatePrefix: numParams = 0 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: 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" % text_type(templatePrefix) msg += text_type(nestedName) self.warn(msg) newTemplates = [] for i in range(numExtra): newTemplates.append(ASTTemplateParams([])) if templatePrefix: newTemplates.extend(templatePrefix.templates) templatePrefix = ASTTemplateDeclarationPrefix(newTemplates) return templatePrefix def parse_declaration(self, objectType): if objectType not in ('type', 'member', 'function', 'class', 'enum', 'enumerator'): raise Exception('Internal error, unknown objectType "%s".' % objectType) visibility = None templatePrefix = None declaration = None self.skip_ws() if self.match(_visibility_re): visibility = self.matched_text if objectType in ('type', 'member', 'function', 'class'): templatePrefix = self._parse_template_declaration_prefix() 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) elif objectType == 'member': declaration = self._parse_type_with_init(named=True, outer='member') elif objectType == 'function': declaration = self._parse_type(named=True, outer='function') elif objectType == 'class': declaration = self._parse_class() elif objectType == 'enum': declaration = self._parse_enum() elif objectType == 'enumerator': declaration = self._parse_enumerator() else: assert False templatePrefix = self._check_template_consistency(declaration.name, templatePrefix, fullSpecShorthand=False) return ASTDeclaration(objectType, visibility, templatePrefix, declaration) def parse_namespace_object(self): templatePrefix = self._parse_template_declaration_prefix() name = self._parse_nested_name() templatePrefix = self._check_template_consistency(name, templatePrefix, fullSpecShorthand=False) res = ASTNamespace(name, templatePrefix) res.objectType = 'namespace' return res def parse_xref_object(self): templatePrefix = self._parse_template_declaration_prefix() name = self._parse_nested_name() templatePrefix = self._check_template_consistency(name, templatePrefix, fullSpecShorthand=True) res = ASTNamespace(name, templatePrefix) res.objectType = 'xref' return res def _make_phony_error_name(): nne = ASTNestedNameElement(ASTIdentifier("PhonyNameDueToError"), None) return ASTNestedName([nne], rooted=False) class CPPObject(ObjectDescription): """Description of a C++ language object.""" doc_field_types = [ GroupedField('parameter', label=l_('Parameters'), names=('param', 'parameter', 'arg', 'argument'), can_collapse=True), GroupedField('template parameter', label=l_('Template Parameters'), names=('tparam', 'template parameter'), can_collapse=True), GroupedField('exceptions', label=l_('Throws'), rolename='cpp:class', names=('throws', 'throw', 'exception'), can_collapse=True), Field('returnvalue', label=l_('Returns'), has_arg=False, names=('returns', 'return')), ] def warn(self, msg): self.state_machine.reporter.warning(msg, line=self.lineno) def _add_enumerator_to_parent(self, ast): 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.identifier 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 parnets 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.scoped: return targetSymbol = parentSymbol.parent s = targetSymbol.find_identifier(symbol.identifier, matchSelf=False) if s is not None: # something is already declared with that name return declClone = symbol.declaration.clone() declClone.enumeratorScopedSymbol = symbol Symbol(parent=targetSymbol, identifier=symbol.identifier, templateParams=None, templateArgs=None, declaration=declClone, docname=self.env.docname) def add_target_and_index(self, ast, sig, signode): # general note: name must be lstrip(':')'ed, to remove "::" try: id_v1 = ast.get_id_v1() except NoOldIdError: id_v1 = None id_v2 = ast.get_id_v2() # store them in reverse order, so the newest is first ids = [id_v2, id_v1] newestId = ids[0] assert newestId # shouldn't be None if not re.compile(r'^[a-zA-Z0-9_]*$').match(newestId): self.warn('Index id generation for C++ object "%s" failed, please ' 'report as bug (id=%s).' % (text_type(ast), newestId)) name = text_type(ast.symbol.get_full_nested_name()).lstrip(':') indexText = self.get_index_text(name) 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 signode['names'].append(name) else: # print("[CPP] non-unique name:", name) pass for id in ids: if id: # is None when the element didn't exist in that version signode['ids'].append(id) signode['first'] = (not self.names) # hmm, what is this abound? self.state.document.note_explicit_target(signode) def parse_definition(self, parser): raise NotImplementedError() def describe_signature(self, signode, ast, parentScope): raise NotImplementedError() def handle_signature(self, sig, signode): if 'cpp:parentSymbol' not in self.env.ref_context: root = self.env.domaindata['cpp']['rootSymbol'] self.env.ref_context['cpp:parentSymbol'] = root parentSymbol = self.env.ref_context['cpp:parentSymbol'] parser = DefinitionParser(sig, self) try: ast = self.parse_definition(parser) parser.assert_end() except DefinitionError as e: self.warn(e.description) # 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.ref_context['cpp:lastSymbol'] = symbol raise ValueError symbol = parentSymbol.add_declaration(ast, docname=self.env.docname) self.env.ref_context['cpp:lastSymbol'] = symbol if ast.objectType == 'enumerator': self._add_enumerator_to_parent(ast) self.describe_signature(signode, ast) return ast class CPPTypeObject(CPPObject): def get_index_text(self, name): return _('%s (C++ type)') % name def parse_definition(self, parser): return parser.parse_declaration("type") def describe_signature(self, signode, ast): ast.describe_signature(signode, 'lastIsName', self.env) class CPPMemberObject(CPPObject): def get_index_text(self, name): return _('%s (C++ member)') % name def parse_definition(self, parser): return parser.parse_declaration("member") def describe_signature(self, signode, ast): ast.describe_signature(signode, 'lastIsName', self.env) class CPPFunctionObject(CPPObject): def get_index_text(self, name): return _('%s (C++ function)') % name def parse_definition(self, parser): return parser.parse_declaration("function") def describe_signature(self, signode, ast): ast.describe_signature(signode, 'lastIsName', self.env) class CPPClassObject(CPPObject): def get_index_text(self, name): return _('%s (C++ class)') % name def before_content(self): lastSymbol = self.env.ref_context['cpp:lastSymbol'] assert lastSymbol self.oldParentSymbol = self.env.ref_context['cpp:parentSymbol'] self.env.ref_context['cpp:parentSymbol'] = lastSymbol def after_content(self): self.env.ref_context['cpp:parentSymbol'] = self.oldParentSymbol def parse_definition(self, parser): return parser.parse_declaration("class") def describe_signature(self, signode, ast): ast.describe_signature(signode, 'lastIsName', self.env) class CPPEnumObject(CPPObject): def get_index_text(self, name): return _('%s (C++ enum)') % name def before_content(self): lastSymbol = self.env.ref_context['cpp:lastSymbol'] assert lastSymbol self.oldParentSymbol = self.env.ref_context['cpp:parentSymbol'] self.env.ref_context['cpp:parentSymbol'] = lastSymbol def after_content(self): self.env.ref_context['cpp:parentSymbol'] = self.oldParentSymbol def parse_definition(self, parser): ast = parser.parse_declaration("enum") # self.objtype is set by ObjectDescription in run() if self.objtype == "enum": ast.scoped = None elif self.objtype == "enum-struct": ast.scoped = "struct" elif self.objtype == "enum-class": ast.scoped = "class" else: assert False return ast def describe_signature(self, signode, ast): ast.describe_signature(signode, 'lastIsName', self.env) class CPPEnumeratorObject(CPPObject): def get_index_text(self, name): return _('%s (C++ enumerator)') % name def parse_definition(self, parser): return parser.parse_declaration("enumerator") def describe_signature(self, signode, ast): ast.describe_signature(signode, 'lastIsName', self.env) class CPPNamespaceObject(Directive): """ 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 = {} def warn(self, msg): self.state_machine.reporter.warning(msg, line=self.lineno) def run(self): env = self.state.document.settings.env rootSymbol = env.domaindata['cpp']['rootSymbol'] if self.arguments[0].strip() in ('NULL', '0', 'nullptr'): symbol = rootSymbol stack = [] else: parser = DefinitionParser(self.arguments[0], self) try: ast = parser.parse_namespace_object() parser.assert_end() except DefinitionError as e: self.warn(e.description) name = _make_phony_error_name() ast = ASTNamespace(name, None) symbol = rootSymbol.add_name(ast.nestedName, ast.templatePrefix) stack = [symbol] env.ref_context['cpp:parentSymbol'] = symbol env.temp_data['cpp:namespaceStack'] = stack return [] class CPPNamespacePushObject(Directive): has_content = False required_arguments = 1 optional_arguments = 0 final_argument_whitespace = True option_spec = {} def warn(self, msg): self.state_machine.reporter.warning(msg, line=self.lineno) def run(self): env = self.state.document.settings.env if self.arguments[0].strip() in ('NULL', '0', 'nullptr'): return parser = DefinitionParser(self.arguments[0], self) try: ast = parser.parse_namespace_object() parser.assert_end() except DefinitionError as e: self.warn(e.description) name = _make_phony_error_name() ast = ASTNamespace(name, None) oldParent = env.ref_context.get('cpp:parentSymbol', None) if not oldParent: oldParent = env.domaindata['cpp']['rootSymbol'] symbol = oldParent.add_name(ast.nestedName, ast.templatePrefix) stack = env.temp_data.get('cpp:namespaceStack', []) stack.append(symbol) env.ref_context['cpp:parentSymbol'] = symbol env.temp_data['cpp:namespaceStack'] = stack return [] class CPPNamespacePopObject(Directive): has_content = False required_arguments = 0 optional_arguments = 0 final_argument_whitespace = True option_spec = {} def warn(self, msg): self.state_machine.reporter.warning(msg, line=self.lineno) def run(self): env = self.state.document.settings.env stack = env.temp_data.get('cpp:namespaceStack', None) if not stack or len(stack) == 0: self.warn("C++ namespace pop on empty stack. Defaulting to gobal scope.") stack = [] else: stack.pop() if len(stack) > 0: symbol = stack[-1] else: symbol = env.domaindata['cpp']['rootSymbol'] env.ref_context['cpp:parentSymbol'] = symbol env.temp_data['cpp:namespaceStack'] = stack return [] class CPPXRefRole(XRefRole): def process_link(self, env, refnode, has_explicit_title, title, target): parent = env.ref_context.get('cpp:parentSymbol', None) if parent: refnode['cpp:parentKey'] = parent.get_lookup_key() 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 CPPDomain(Domain): """C++ language domain.""" name = 'cpp' label = 'C++' object_types = { 'class': ObjType(l_('class'), 'class'), 'function': ObjType(l_('function'), 'func'), 'member': ObjType(l_('member'), 'member'), 'type': ObjType(l_('type'), 'type'), 'enum': ObjType(l_('enum'), 'enum'), 'enumerator': ObjType(l_('enumerator'), 'enumerator') } directives = { 'class': CPPClassObject, 'function': CPPFunctionObject, 'member': CPPMemberObject, 'var': CPPMemberObject, 'type': CPPTypeObject, 'enum': CPPEnumObject, 'enum-struct': CPPEnumObject, 'enum-class': CPPEnumObject, 'enumerator': CPPEnumeratorObject, 'namespace': CPPNamespaceObject, 'namespace-push': CPPNamespacePushObject, 'namespace-pop': CPPNamespacePopObject } roles = { 'any': CPPXRefRole(), 'class': CPPXRefRole(), 'func': CPPXRefRole(fix_parens=True), 'member': CPPXRefRole(), 'var': CPPXRefRole(), 'type': CPPXRefRole(), 'enum': CPPXRefRole(), 'enumerator': CPPXRefRole() } initial_data = { 'rootSymbol': Symbol(None, None, None, None, None, None), 'names': {} # full name for indexing -> docname } def clear_doc(self, docname): rootSymbol = self.data['rootSymbol'] rootSymbol.clear_doc(docname) for name, nDocname in list(self.data['names'].items()): if nDocname == docname: del self.data['names'][name] def process_doc(self, env, docname, document): # just for debugging # print(docname) # print(self.data['rootSymbol'].dump(0)) pass def merge_domaindata(self, docnames, otherdata): self.data['rootSymbol'].merge_with(otherdata['rootSymbol'], docnames, self.env) ourNames = self.data['names'] for name, docname in otherdata['names'].items(): if docname in docnames: if name in ourNames: msg = "Duplicate declaration, also defined in '%s'.\n" msg += "Name of declaration is '%s'." msg = msg % (ourNames[name], name) self.env.warn(docname, msg) else: ourNames[name] = docname def _resolve_xref_inner(self, env, fromdocname, builder, typ, target, node, contnode, emitWarnings=True): class Warner(object): def warn(self, msg): if emitWarnings: env.warn_node(msg, node) warner = Warner() parser = DefinitionParser(target, warner) try: ast = parser.parse_xref_object() parser.skip_ws() parser.assert_end() except DefinitionError as e: warner.warn('Unparseable C++ cross-reference: %r\n%s' % (target, str(e.description))) return None, None parentKey = node.get("cpp:parentKey", None) rootSymbol = self.data['rootSymbol'] if parentKey: parentSymbol = rootSymbol.direct_lookup(parentKey) if not parentSymbol: print("Target: ", target) print("ParentKey: ", parentKey) assert parentSymbol # should be there else: parentSymbol = rootSymbol name = ast.nestedName if ast.templatePrefix: templateDecls = ast.templatePrefix.templates else: templateDecls = [] s = parentSymbol.find_name(name, templateDecls, templateShorthand=True, matchSelf=True) if s is None or s.declaration is None: return None, None declaration = s.declaration fullNestedName = s.get_full_nested_name() name = text_type(fullNestedName).lstrip(':') docname = s.docname assert docname if typ == 'any' and declaration.objectType == 'function': if env.config.add_function_parentheses: if not node['refexplicit']: title = contnode.pop(0).astext() contnode += nodes.Text(title + '()') return make_refnode(builder, fromdocname, docname, declaration.get_newest_id(), contnode, name ), declaration.objectType def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): return self._resolve_xref_inner(env, fromdocname, builder, typ, target, node, contnode)[0] def resolve_any_xref(self, env, fromdocname, builder, target, node, contnode): node, objtype = self._resolve_xref_inner(env, fromdocname, builder, 'any', target, node, contnode, emitWarnings=False) if node: return [('cpp:' + self.role_for_objtype(objtype), node)] return [] def get_objects(self): rootSymbol = self.data['rootSymbol'] for symbol in rootSymbol.get_all_symbols(): if symbol.declaration is None: continue assert symbol.docname name = text_type(symbol.get_full_nested_name()).lstrip(':') objectType = symbol.declaration.objectType docname = symbol.docname newestId = symbol.declaration.get_newest_id() yield (name, name, objectType, docname, newestId, 1)