# -*- 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. Tagnames ---------------------------------------------------------------------------- Each desc_signature node will have the attribute 'sphinx_cpp_tagname' set to - 'templateParams', if the line is on the form 'template<...>', - 'declarator', if the line contains the name of the declared object. No other desc_signature nodes should exist (so far). Grammar ---------------------------------------------------------------------------- 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) | "extern" (only for member_object and function_object) | "register" ) thread_local[opt] (only for member_object) (it can also appear before the others) | type-specifier -> trailing-type-specifier | function-specifier -> "inline" | "virtual" | "explicit" (only for function_object) | "friend" (only for function_object) | "constexpr" (only for member_object and function_object) trailing-type-specifier -> simple-type-specifier | elaborated-type-specifier | typename-specifier | cv-qualifier -> "const" | "volatile" stricter grammar for decl-specifier-seq (with everything, each object uses a subset): visibility storage-class-specifier function-specifier "friend" "constexpr" "volatile" "const" trailing-type-specifier # where trailing-type-specifier can no be cv-qualifier # Inside e.g., template 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 _DuplicateSymbolError(UnicodeMixin, Exception): def __init__(self, symbol, candSymbol): assert symbol assert candSymbol self.symbol = symbol self.candSymbol = candSymbol def __unicode__(self): return "Internal C++ duplicate symbol error:\n%s" % self.symbol.dump(0) 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() templateNode.sphinx_cpp_tagname = 'templateParams' 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, threadLocal, inline, virtual, explicit, constexpr, volatile, const, friend): self.storage = storage self.threadLocal = threadLocal 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.threadLocal or other.threadLocal, 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.threadLocal: res.append('thread_local') if self.inline: res.append('inline') if self.friend: res.append('friend') if self.virtual: res.append('virtual') if self.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.threadLocal: _add(modifiers, 'thread_local') 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 get_type_declaration_prefix(self): if self.declSpecs.trailingTypeSpec: return 'typedef' else: return 'type' 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 get_type_declaration_prefix(self): return 'using' 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 mainDeclNode.sphinx_cpp_tagname = 'declarator' 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': prefix = self.declaration.get_type_declaration_prefix() prefix += ' ' mainDeclNode += addnodes.desc_annotation(prefix, prefix) 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, so let's compare ids. candSymbol = Symbol(parent=parentSymbol, identifier=identifier, templateParams=templateParams, templateArgs=templateArgs, declaration=declaration, docname=docname) newId = declaration.get_newest_id() oldId = symbol.declaration.get_newest_id() if newId != oldId: # we already inserted the symbol, so return the new one symbol = candSymbol else: # Redeclaration of the same symbol. # Let the new one be there, but raise an error to the client # so it can use the real symbol as subscope. # This will probably result in a duplicate id warning. raise _DuplicateSymbolError(symbol, candSymbol) 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 threadLocal = 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 self.skip_word('extern'): storage = 'extern' continue if outer == 'member': if self.skip_word('mutable'): storage = 'mutable' continue if self.skip_word('register'): storage = 'register' continue if not threadLocal and outer == 'member': threadLocal = self.skip_word('thread_local') if threadLocal: 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, threadLocal, 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 try: symbol = parentSymbol.add_declaration(ast, docname=self.env.docname) self.env.ref_context['cpp:lastSymbol'] = symbol except _DuplicateSymbolError as e: # Assume we are actually in the old symbol, # instead of the newly created duplicate. self.env.ref_context['cpp:lastSymbol'] = e.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)