diff options
Diffstat (limited to 'sphinx/domains/cpp.py')
-rw-r--r-- | sphinx/domains/cpp.py | 2983 |
1 files changed, 2297 insertions, 686 deletions
diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 8e74abc96..fcd352e71 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -9,6 +9,22 @@ :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. @@ -22,20 +38,45 @@ All versions are generated and attached to elements. The newest is used for the index. All of the versions should work as permalinks. - See http://www.nongnu.org/hcb/ for the grammar. + 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: - simple-declaration - -> attribute-specifier-seq[opt] decl-specifier-seq[opt] - init-declarator-list[opt] ; + 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) + storage-class-specifier -> + "static" (only for member_object and function_object) + | "register" | type-specifier -> trailing-type-specifier | function-specifier -> "inline" | "virtual" | "explicit" (only for function_object) @@ -93,7 +134,7 @@ declerator -> ptr-declerator | noptr-declarator parameters-and-qualifiers trailing-return-type - (TODO: for now we don't support it) + (TODO: for now we don't support trailing-eturn-type) ptr-declerator -> noptr-declerator | ptr-operator ptr-declarator @@ -104,7 +145,13 @@ | noptr-declerator parameters-and-qualifiers | noptr-declarator "[" constant-expression[opt] "]" attribute-specifier-seq[opt] - | "(" ptr-declarator ")" # TODO: not implemented yet + | "(" 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 -> @@ -147,25 +194,33 @@ -> 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 - TODO: what about templates? for now: skip grammar: - nested-name - | nested-name ":" - 'comma-separated list of nested-name optionally with visibility' + 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 @@ -184,30 +239,11 @@ nested-name """ -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 - - _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') -_array_def_re = re.compile(r'\[\s*([^\]]+?)?\s*\]') -_template_arg_re = re.compile(r'(%s)|([^,>]+)' % _string_re.pattern, re.S) _operator_re = re.compile(r'''(?x) \[\s*\] | \(\s*\) @@ -216,10 +252,26 @@ _operator_re = re.compile(r'''(?x) | (<<|>>)=? | && | \|\| | [!<>=/*%+|&^~-]=? ''') - -#------------------------------------------------------------------------------- +# 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', @@ -291,9 +343,9 @@ _id_operator_v1 = { '[]': 'subscript-operator' } -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ # Id v2 constants -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ _id_prefix_v2 = '_CPPv2' _id_fundamental_v2 = { @@ -387,6 +439,15 @@ _id_operator_v2 = { } +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 @@ -448,10 +509,263 @@ def _verify_description_mode(mode): raise Exception("Description mode '%s' is invalid." % mode) +class ASTIdentifier(ASTBase): + def __init__(self, identifier): + assert identifier is not None + self.identifier = identifier + + def get_id_v1(self): + if self.identifier == 'size_t': + return 's' + else: + return self.identifier + + def get_id_v2(self): + if self.identifier == "std": + return 'St' + elif self.identifier[0] == "~": + # a destructor, just use an arbitrary version of dtors + return 'D0' + else: + return text_type(len(self.identifier)) + self.identifier + + def __unicode__(self): + return self.identifier + + def describe_signature(self, signode, mode, env, prefix, symbol): + _verify_description_mode(mode) + if mode == 'markType': + targetText = prefix + self.identifier + pnode = addnodes.pending_xref('', refdomain='cpp', reftype='type', + reftarget=targetText, modname=None, + classname=None) + key = symbol.get_lookup_key() + assert key + pnode['cpp:parentKey'] = key + pnode += nodes.Text(self.identifier) + signode += pnode + elif mode == 'lastIsName': + signode += addnodes.desc_name(self.identifier, self.identifier) + elif mode == 'noneIsName': + signode += nodes.Text(self.identifier) + else: + raise Exception('Unknown description mode: %s' % mode) + + +class ASTTemplateKeyParamPackIdDefault(ASTBase): + def __init__(self, key, identifier, parameterPack, default): + assert key + if parameterPack: + assert default is None + self.key = key + self.identifier = identifier + self.parameterPack = parameterPack + self.default = default + + def get_identifier(self): + return self.identifier + + def get_id_v2(self): + # this is not part of the normal name mangling in C++ + res = [] + if self.parameterPack: + res.append('Dp') + else: + res.append('0') # we need to put something + return ''.join(res) + + def __unicode__(self): + res = [self.key] + if self.parameterPack: + if self.identifier: + res.append(' ') + res.append('...') + if self.identifier: + if not self.parameterPack: + res.append(' ') + res.append(text_type(self.identifier)) + if self.default: + res.append(' = ') + res.append(text_type(self.default)) + return ''.join(res) + + def describe_signature(self, signode, mode, env, symbol): + signode += nodes.Text(self.key) + if self.parameterPack: + if self.identifier: + signode += nodes.Text(' ') + signode += nodes.Text('...') + if self.identifier: + if not self.parameterPack: + signode += nodes.Text(' ') + self.identifier.describe_signature(signode, mode, env, '', symbol) + if self.default: + signode += nodes.Text(' = ') + self.default.describe_signature(signode, 'markType', env, symbol) + + +class ASTTemplateParamType(ASTBase): + def __init__(self, data): + assert data + self.data = data + + @property + def name(self): + id = self.get_identifier() + return ASTNestedName([ASTNestedNameElement(id, None)], rooted=False) + + def get_identifier(self): + return self.data.get_identifier() + + def get_id_v2(self, objectType=None, symbol=None): + # this is not part of the normal name mangling in C++ + if symbol: + # the anchor will be our parent + return symbol.parent.declaration.get_id_v2(prefixed=None) + else: + return self.data.get_id_v2() + + def __unicode__(self): + return text_type(self.data) + + def describe_signature(self, signode, mode, env, symbol): + self.data.describe_signature(signode, mode, env, symbol) + + +class ASTTemplateParamTemplateType(ASTBase): + def __init__(self, nestedParams, data): + assert nestedParams + assert data + self.nestedParams = nestedParams + self.data = data + + @property + def name(self): + id = self.get_identifier() + return ASTNestedName([ASTNestedNameElement(id, None)], rooted=False) + + def get_identifier(self): + return self.data.get_identifier() + + def get_id_v2(self, objectType=None, symbol=None): + # this is not part of the normal name mangling in C++ + if symbol: + # the anchor will be our parent + return symbol.parent.declaration.get_id_v2(prefixed=None) + else: + return self.nestedParams.get_id_v2() + self.data.get_id_v2() + + def __unicode__(self): + return text_type(self.nestedParams) + text_type(self.data) + + def describe_signature(self, signode, mode, env, symbol): + self.nestedParams.describe_signature(signode, 'noneIsName', env, symbol) + signode += nodes.Text(' ') + self.data.describe_signature(signode, mode, env, symbol) + + +class ASTTemplateParamNonType(ASTBase): + def __init__(self, param): + assert param + self.param = param + + @property + def name(self): + id = self.get_identifier() + return ASTNestedName([ASTNestedNameElement(id, None)], rooted=False) + + def get_identifier(self): + name = self.param.name + if name: + assert len(name.names) == 1 + assert name.names[0].identifier + assert not name.names[0].templateArgs + return name.names[0].identifier + else: + return None + + def get_id_v2(self, objectType=None, symbol=None): + # this is not part of the normal name mangling in C++ + if symbol: + # the anchor will be our parent + return symbol.parent.declaration.get_id_v2(prefixed=None) + else: + return '_' + self.param.get_id_v2() + + def __unicode__(self): + return text_type(self.param) + + def describe_signature(self, signode, mode, env, symbol): + self.param.describe_signature(signode, mode, env, symbol) + + +class ASTTemplateParams(ASTBase): + def __init__(self, params): + assert params is not None + self.params = params + + def get_id_v2(self): + res = [] + res.append("I") + for param in self.params: + res.append(param.get_id_v2()) + res.append("E") + return ''.join(res) + + def __unicode__(self): + res = [] + res.append(u"template<") + res.append(u", ".join(text_type(a) for a in self.params)) + res.append(u"> ") + return ''.join(res) + + def describe_signature(self, signode, mode, env, symbol): + signode += nodes.Text("template<") + first = True + for param in self.params: + if not first: + signode += nodes.Text(", ") + first = False + param.describe_signature(signode, mode, env, symbol) + signode += nodes.Text(">") + + +class ASTTemplateDeclarationPrefix(ASTBase): + def __init__(self, templates): + assert templates is not None + assert len(templates) > 0 + self.templates = templates + + # id_v1 does not exist + + def get_id_v2(self): + # this is not part of a normal name mangling system + res = [] + for t in self.templates: + res.append(t.get_id_v2()) + return u''.join(res) + + def __unicode__(self): + res = [] + for t in self.templates: + res.append(text_type(t)) + return u''.join(res) + + def describe_signature(self, signode, mode, env, symbol): + _verify_description_mode(mode) + for t in self.templates: + templateNode = addnodes.desc_signature() + t.describe_signature(templateNode, 'lastIsName', env, symbol) + signode += templateNode + + class ASTOperatorBuildIn(ASTBase): def __init__(self, op): self.op = op + def is_operator(self): + return True + def get_id_v1(self): if self.op not in _id_operator_v1: raise Exception('Internal error: Build-in operator "%s" can not ' @@ -470,10 +784,7 @@ class ASTOperatorBuildIn(ASTBase): else: return u'operator' + self.op - def get_name_no_template(self): - return text_type(self) - - def describe_signature(self, signode, mode, env, prefix): + def describe_signature(self, signode, mode, env, prefix, symbol): _verify_description_mode(mode) identifier = text_type(self) if mode == 'lastIsName': @@ -486,8 +797,8 @@ class ASTOperatorType(ASTBase): def __init__(self, type): self.type = type - def __unicode__(self): - return u''.join(['operator ', text_type(self.type)]) + def is_operator(self): + return True def get_id_v1(self): return u'castto-%s-operator' % self.type.get_id_v1() @@ -495,10 +806,38 @@ class ASTOperatorType(ASTBase): 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): + 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': @@ -522,25 +861,46 @@ class ASTTemplateArgConstant(ASTBase): # juse it verbatim for now return u'X' + text_type(self) + u'E' - def describe_signature(self, signode, mode, env): + def describe_signature(self, signode, mode, env, symbol): _verify_description_mode(mode) signode += nodes.Text(text_type(self)) -class ASTNestedNameElementEmpty(ASTBase): - """Used if a nested name starts with ::""" +class ASTTemplateArgs(ASTBase): + def __init__(self, args): + assert args is not None + assert len(args) > 0 + self.args = args def get_id_v1(self): - return u'' + 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): - return u'' + 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): - return u'' + res = ', '.join(text_type(a) for a in self.args) + return '<' + res + '>' - def describe_signature(self, signode, mode, env, prefix): - pass + 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): @@ -548,99 +908,58 @@ class ASTNestedNameElement(ASTBase): self.identifier = identifier self.templateArgs = templateArgs + def is_operator(self): + return False + def get_id_v1(self): - res = [] - if self.identifier == 'size_t': - res.append('s') - else: - res.append(self.identifier) + res = self.identifier.get_id_v1() if self.templateArgs: - res.append(':') - res.append(u'.'.join(a.get_id_v1() for a in self.templateArgs)) - res.append(':') - return u''.join(res) + res += self.templateArgs.get_id_v1() + return res def get_id_v2(self): - res = [] - if self.identifier == "std": - res.append(u'St') - elif self.identifier[0] == "~": - # a destructor, just use an arbitrary version of dtors - res.append("D0") - else: - res.append(text_type(len(self.identifier))) - res.append(self.identifier) + res = self.identifier.get_id_v2() if self.templateArgs: - res.append('I') - for a in self.templateArgs: - res.append(a.get_id_v2()) - res.append('E') - return u''.join(res) + res += self.templateArgs.get_id_v2() + return res def __unicode__(self): - res = [] - res.append(self.identifier) + res = text_type(self.identifier) if self.templateArgs: - res.append('<') - first = True - for a in self.templateArgs: - if not first: - res.append(', ') - first = False - res.append(text_type(a)) - res.append('>') - return u''.join(res) - - def get_name_no_template(self): - return text_type(self.identifier) + res += text_type(self.templateArgs) + return res - def describe_signature(self, signode, mode, env, prefix): - _verify_description_mode(mode) - if mode == 'markType': - targetText = prefix + text_type(self) - pnode = addnodes.pending_xref( - '', refdomain='cpp', reftype='type', - reftarget=targetText, modname=None, classname=None) - if env: # during testing we don't have an env, do we? - pnode['cpp:parent'] = env.ref_context.get('cpp:parent') - pnode += nodes.Text(text_type(self.identifier)) - signode += pnode - elif mode == 'lastIsName': - name = text_type(self.identifier) - signode += addnodes.desc_name(name, name) - else: - raise Exception('Unknown description mode: %s' % mode) + def describe_signature(self, signode, mode, env, prefix, symbol): + self.identifier.describe_signature(signode, mode, env, prefix, symbol) if self.templateArgs: - signode += nodes.Text('<') - first = True - for a in self.templateArgs: - if not first: - signode += nodes.Text(', ') - first = False - a.describe_signature(signode, 'markType', env) - signode += nodes.Text('>') + self.templateArgs.describe_signature(signode, mode, env, symbol) class ASTNestedName(ASTBase): - def __init__(self, names): + 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: - res = [] - id = self.names[0].get_id_v1() - if len(id) > 0: - res.append(id) - for n in self.names[1:]: - res.append(n.get_id_v1()) - return u'::'.join(res) + return u'::'.join(n.get_id_v1() for n in self.names) def get_id_v2(self, modifiers=""): res = [] @@ -653,36 +972,30 @@ class ASTNestedName(ASTBase): res.append('E') return u''.join(res) - def get_name_no_last_template(self): - res = u'::'.join([text_type(n) for n in self.names[:-1]]) - if len(self.names) > 1: - res += '::' - res += self.names[-1].get_name_no_template() - return res - - def prefix_nested_name(self, prefix): - if self.names[0] == '': - return self # it's defined at global namespace, don't tuch it - assert isinstance(prefix, ASTNestedName) - names = prefix.names[:] - names.extend(self.names) - return ASTNestedName(names) - def __unicode__(self): - return u'::'.join([text_type(n) for n in self.names]) + 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): + 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 = u'::'.join([text_type(n) for n in self.names[:-1]]) + 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 += u'::' - name = text_type(self.names[-1]) + addname += '::' signode += addnodes.desc_addname(addname, addname) - self.names[-1].describe_signature(signode, mode, env, '') + self.names[-1].describe_signature(signode, mode, env, '', symbol) elif mode == 'noneIsName': - name = text_type(self) - signode += nodes.Text(name) + signode += nodes.Text(text_type(self)) elif mode == 'param': name = text_type(self) signode += nodes.emphasis(name, name) @@ -698,7 +1011,7 @@ class ASTNestedName(ASTBase): prefix += '::' first = False if name != '': - name.describe_signature(signode, mode, env, prefix) + name.describe_signature(signode, mode, env, prefix, symbol) prefix += text_type(name) else: raise Exception('Unknown description mode: %s' % mode) @@ -728,7 +1041,7 @@ class ASTTrailingTypeSpecFundamental(ASTBase): 'parser should have rejected it.' % self.name) return _id_fundamental_v2[self.name] - def describe_signature(self, signode, mode, env): + def describe_signature(self, signode, mode, env, symbol): signode += nodes.Text(text_type(self.name)) @@ -755,11 +1068,11 @@ class ASTTrailingTypeSpecName(ASTBase): res.append(text_type(self.nestedName)) return u''.join(res) - def describe_signature(self, signode, mode, env): + 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) + self.nestedName.describe_signature(signode, mode, env, symbol=symbol) class ASTFunctinoParameter(ASTBase): @@ -785,12 +1098,12 @@ class ASTFunctinoParameter(ASTBase): else: return text_type(self.arg) - def describe_signature(self, signode, mode, env): + 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) + self.arg.describe_signature(signode, mode, env, symbol=symbol) class ASTParametersQualifiers(ASTBase): @@ -874,15 +1187,15 @@ class ASTParametersQualifiers(ASTBase): res.append(self.initializer) return u''.join(res) - def describe_signature(self, signode, mode, env): + 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) + arg.describe_signature(param, 'param', env, symbol=symbol) else: - arg.describe_signature(param, 'markType', env) + arg.describe_signature(param, 'markType', env, symbol=symbol) paramlist += param signode += paramlist @@ -910,7 +1223,8 @@ class ASTParametersQualifiers(ASTBase): class ASTDeclSpecsSimple(ASTBase): - def __init__(self, storage, inline, virtual, explicit, constexpr, volatile, const): + def __init__(self, storage, inline, virtual, explicit, + constexpr, volatile, const, friend): self.storage = storage self.inline = inline self.virtual = virtual @@ -918,6 +1232,7 @@ class ASTDeclSpecsSimple(ASTBase): self.constexpr = constexpr self.volatile = volatile self.const = const + self.friend = friend def mergeWith(self, other): if not other: @@ -928,7 +1243,8 @@ class ASTDeclSpecsSimple(ASTBase): self.explicit or other.explicit, self.constexpr or other.constexpr, self.volatile or other.volatile, - self.const or other.const) + self.const or other.const, + self.friend or other.friend) def __unicode__(self): res = [] @@ -936,6 +1252,8 @@ class ASTDeclSpecsSimple(ASTBase): res.append(self.storage) if self.inline: res.append('inline') + if self.friend: + res.append('friend') if self.virtual: res.append('virtual') if self.explicit: @@ -957,6 +1275,8 @@ class ASTDeclSpecsSimple(ASTBase): _add(modifiers, self.storage) if self.inline: _add(modifiers, 'inline') + if self.friend: + _add(modifiers, 'friend') if self.virtual: _add(modifiers, 'virtual') if self.explicit: @@ -970,11 +1290,10 @@ class ASTDeclSpecsSimple(ASTBase): class ASTDeclSpecs(ASTBase): - def __init__(self, outer, visibility, leftSpecs, rightSpecs, trailing): + def __init__(self, outer, leftSpecs, rightSpecs, trailing): # leftSpecs and rightSpecs are used for output # allSpecs are used for id generation self.outer = outer - self.visibility = visibility self.leftSpecs = leftSpecs self.rightSpecs = rightSpecs self.allSpecs = self.leftSpecs.mergeWith(self.rightSpecs) @@ -1002,15 +1321,8 @@ class ASTDeclSpecs(ASTBase): res.append(self.trailingTypeSpec.get_id_v2()) return u''.join(res) - def _print_visibility(self): - return (self.visibility and - not (self.outer in ('type', 'member', 'function') and - self.visibility == 'public')) - def __unicode__(self): res = [] - if self._print_visibility(): - res.append(self.visibility) l = text_type(self.leftSpecs) if len(l) > 0: if len(res) > 0: @@ -1027,7 +1339,7 @@ class ASTDeclSpecs(ASTBase): res.append(r) return "".join(res) - def describe_signature(self, signode, mode, env): + def describe_signature(self, signode, mode, env, symbol): _verify_description_mode(mode) modifiers = [] @@ -1036,8 +1348,6 @@ class ASTDeclSpecs(ASTBase): modifiers.append(nodes.Text(' ')) modifiers.append(addnodes.desc_annotation(text, text)) - if self._print_visibility(): - _add(modifiers, self.visibility) self.leftSpecs.describe_signature(modifiers) for m in modifiers: @@ -1045,7 +1355,8 @@ class ASTDeclSpecs(ASTBase): if self.trailingTypeSpec: if len(modifiers) > 0: signode += nodes.Text(' ') - self.trailingTypeSpec.describe_signature(signode, mode, env) + self.trailingTypeSpec.describe_signature(signode, mode, env, + symbol=symbol) modifiers = [] self.rightSpecs.describe_signature(modifiers) if len(modifiers) > 0: @@ -1054,82 +1365,387 @@ class ASTDeclSpecs(ASTBase): signode += m -class ASTPtrOpPtr(ASTBase): - def __init__(self, volatile, const): +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 ') + res.append('volatile') if self.const: - res.append('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) - def get_id_v1(self): - res = ['P'] + # 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_id_v2(self): + 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') - return u''.join(res) + 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() -class ASTPtrOpRef(ASTBase): def __unicode__(self): - return '&' + return '&' + text_type(self.next) - def get_id_v1(self): - return 'R' + # Id v1 ------------------------------------------------------------------ - def get_id_v2(self): - return 'R' + 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 -class ASTPtrOpParamPack(ASTBase): def __unicode__(self): - return '...' + res = text_type(self.next) + if self.next.name: + res = ' ' + res + return '...' + res - def get_id_v1(self): - return 'Dp' + # Id v1 ------------------------------------------------------------------ - def get_id_v2(self): - return 'Dp' + 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() -class ASTArray(ASTBase): - def __init__(self, size): - self.size = size + 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): - return u''.join(['[', text_type(self.size), ']']) + 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) - def get_id_v1(self): - return u'A' + # Id v1 ------------------------------------------------------------------ - def get_id_v2(self): - # TODO: this should maybe be done differently - return u'A' + text_type(self.size) + u'_' + def get_modifiers_id_v1(self): + raise NoOldIdError() - def describe_signature(self, signode, mode, env): + 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) - signode += nodes.Text(text_type(self)) + 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() -class ASTDeclerator(ASTBase): - def __init__(self, ptrOps, declId, suffixOps): - self.ptrOps = ptrOps + 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.suffixOps = suffixOps + self.arrayOps = arrayOps + self.paramQual = paramQual @property def name(self): @@ -1139,75 +1755,78 @@ class ASTDeclerator(ASTBase): def get_modifiers_id_v1(self): # only the modifiers for a function, e.g., # cv-qualifiers - for op in self.suffixOps: - if isinstance(op, ASTParametersQualifiers): - return op.get_modifiers_id_v1() + 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) - for op in self.suffixOps: - if isinstance(op, ASTParametersQualifiers): - return op.get_param_id_v1() - return '' + if self.paramQual: + return self.paramQual.get_param_id_v1() + else: + return '' - def get_ptr_suffix_id_v1(self): # only the ptr ops and array specifiers - return u''.join( - a.get_id_v1() - for a in self.ptrOps + self.suffixOps - if not isinstance(a, ASTParametersQualifiers)) + 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 - for op in self.suffixOps: - if isinstance(op, ASTParametersQualifiers): - return op.get_modifiers_id_v2() + 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) - for op in self.suffixOps: - if isinstance(op, ASTParametersQualifiers): - return op.get_param_id_v2() - return '' - - def get_ptr_suffix_id_v2(self): # only the ptr ops and array specifiers - return u''.join( - a.get_id_v2() - for a in self.ptrOps + self.suffixOps - if not isinstance(a, ASTParametersQualifiers)) - - def require_start_space(self): - if (len(self.ptrOps) > 0 and - isinstance(self.ptrOps[-1], ASTPtrOpParamPack)): - return False + if self.paramQual: + return self.paramQual.get_param_id_v2() else: - return self.declId is not None + 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 = [] - for op in self.ptrOps: - res.append(text_type(op)) - if isinstance(op, ASTPtrOpParamPack) and self.declId: - res.append(' ') if self.declId: res.append(text_type(self.declId)) - for op in self.suffixOps: + 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): + def describe_signature(self, signode, mode, env, symbol): _verify_description_mode(mode) - for op in self.ptrOps: - signode += nodes.Text(text_type(op)) - if isinstance(op, ASTPtrOpParamPack) and self.declId: - signode += nodes.Text(' ') if self.declId: - self.declId.describe_signature(signode, mode, env) - for op in self.suffixOps: + 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): @@ -1224,79 +1843,80 @@ class ASTInitializer(ASTBase): class ASTType(ASTBase): def __init__(self, declSpecs, decl): + assert declSpecs + assert decl self.declSpecs = declSpecs self.decl = decl - self.objectType = None @property def name(self): name = self.decl.name - if not name: - name = self.declSpecs.name return name - def get_id_v1(self): + def get_id_v1(self, objectType=None, symbol=None): res = [] - if self.objectType: # needs the name - if self.objectType == 'function': # also modifiers - res.append(self.name.get_id_v1()) + 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)): + if (self.declSpecs.leftSpecs.constexpr or + (self.declSpecs.rightSpecs and + self.declSpecs.rightSpecs.constexpr)): res.append('CE') - elif self.objectType == 'type': # just the name - res.append(self.name.get_id_v1()) + elif objectType == 'type': # just the name + res.append(symbol.get_full_nested_name().get_id_v1()) else: - print(self.objectType) + 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): + def get_id_v2(self, objectType=None, symbol=None): res = [] - if self.objectType: # needs the name - res.append(_id_prefix_v2) - if self.objectType == 'function': # also modifiers + if objectType: # needs the name + if objectType == 'function': # also modifiers modifiers = self.decl.get_modifiers_id_v2() - res.append(self.prefixedName.get_id_v2(modifiers)) + res.append(symbol.get_full_nested_name().get_id_v2(modifiers)) res.append(self.decl.get_param_id_v2()) - elif self.objectType == 'type': # just the name - res.append(self.prefixedName.get_id_v2()) + elif objectType == 'type': # just the name + res.append(symbol.get_full_nested_name().get_id_v2()) else: - print(self.objectType) + print(objectType) assert False else: # only type encoding - res.append(self.decl.get_ptr_suffix_id_v2()) - res.append(self.declSpecs.get_id_v2()) - res.append(self.decl.get_param_id_v2()) + # 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_start_space() and len(declSpecs) > 0: + if self.decl.require_space_after_declSpecs() and len(declSpecs) > 0: res.append(u' ') res.append(text_type(self.decl)) return u''.join(res) - def describe_signature(self, signode, mode, env): + def describe_signature(self, signode, mode, env, symbol): _verify_description_mode(mode) - self.declSpecs.describe_signature(signode, 'markType', env) - if (self.decl.require_start_space() and + 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) + self.decl.describe_signature(signode, mode, env, symbol) class ASTTypeWithInit(ASTBase): def __init__(self, type, init): - self.objectType = None self.type = type self.init = init @@ -1304,15 +1924,16 @@ class ASTTypeWithInit(ASTBase): def name(self): return self.type.name - def get_id_v1(self): - if self.objectType == 'member': - return self.name.get_id_v1() + u'__' + self.type.get_id_v1() + 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() + return self.type.get_id_v1(objectType) - def get_id_v2(self): - if self.objectType == 'member': - return _id_prefix_v2 + self.prefixedName.get_id_v2() + 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() @@ -1323,57 +1944,90 @@ class ASTTypeWithInit(ASTBase): res.append(text_type(self.init)) return u''.join(res) - def describe_signature(self, signode, mode, env): + def describe_signature(self, signode, mode, env, symbol): _verify_description_mode(mode) - self.type.describe_signature(signode, mode, env) + self.type.describe_signature(signode, mode, env, symbol=symbol) if self.init: self.init.describe_signature(signode, mode) +class ASTTypeUsing(ASTBase): + def __init__(self, name, type): + self.name = name + self.type = type + + def get_id_v1(self, objectType=None, symbol=None): + raise NoOldIdError() + + def get_id_v2(self, objectType=None, symbol=None): + return symbol.get_full_nested_name().get_id_v2() + + def __unicode__(self): + res = [] + res.append(text_type(self.name)) + if self.type: + res.append(' = ') + res.append(text_type(self.type)) + return u''.join(res) + + def describe_signature(self, signode, mode, env, symbol): + _verify_description_mode(mode) + self.name.describe_signature(signode, mode, env, symbol=symbol) + if self.type: + signode += nodes.Text(' = ') + self.type.describe_signature(signode, 'markType', env, symbol=symbol) + + class ASTBaseClass(ASTBase): - def __init__(self, name, visibility): + 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): + 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 += 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, mode, env) + self.name.describe_signature(signode, 'markType', env, symbol=symbol) + if self.pack: + signode += nodes.Text('...') class ASTClass(ASTBase): - def __init__(self, name, visibility, bases): + def __init__(self, name, final, bases): self.name = name - self.visibility = visibility + self.final = final self.bases = bases - def get_id_v1(self): - return self.name.get_id_v1() - #name = _id_shortwords.get(self.name) - #if name is not None: - # return name - #return self.name.replace(u' ', u'-') + def get_id_v1(self, objectType, symbol): + return symbol.get_full_nested_name().get_id_v1() - def get_id_v2(self): - return _id_prefix_v2 + self.prefixedName.get_id_v2() + def get_id_v2(self, objectType, symbol): + return symbol.get_full_nested_name().get_id_v2() def __unicode__(self): res = [] - if self.visibility != 'public': - res.append(self.visibility) - res.append(' ') res.append(text_type(self.name)) + if self.final: + res.append(' final') if len(self.bases) > 0: res.append(' : ') first = True @@ -1384,69 +2038,63 @@ class ASTClass(ASTBase): res.append(text_type(b)) return u''.join(res) - def describe_signature(self, signode, mode, env): + def describe_signature(self, signode, mode, env, symbol): _verify_description_mode(mode) - if self.visibility != 'public': - signode += addnodes.desc_annotation( - self.visibility, self.visibility) + self.name.describe_signature(signode, mode, env, symbol=symbol) + if self.final: signode += nodes.Text(' ') - self.name.describe_signature(signode, mode, env) + 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) + b.describe_signature(signode, mode, env, symbol=symbol) signode += nodes.Text(', ') signode.pop() + class ASTEnum(ASTBase): - def __init__(self, name, visibility, scoped, underlyingType): + def __init__(self, name, scoped, underlyingType): self.name = name - self.visibility = visibility self.scoped = scoped self.underlyingType = underlyingType - def get_id_v1(self): - return None # did not exist at that time + def get_id_v1(self, objectType, symbol): + raise NoOldIdError() - def get_id_v2(self): - return _id_prefix_v2 + self.prefixedName.get_id_v2() + 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(' ') - if self.visibility != 'public': - res.append(self.visibility) - 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): + def describe_signature(self, signode, mode, env, symbol): _verify_description_mode(mode) # self.scoped has been done by the CPPEnumObject - if self.visibility != 'public': - signode += addnodes.desc_annotation( - self.visibility, self.visibility) - signode += nodes.Text(' ') - self.name.describe_signature(signode, mode, env) + self.name.describe_signature(signode, mode, env, symbol=symbol) if self.underlyingType: signode += nodes.Text(' : ') - self.underlyingType.describe_signature(signode, 'noneIsName', env) + 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): - return None # did not exist at that time + def get_id_v1(self, objectType, symbol): + raise NoOldIdError() - def get_id_v2(self): - return _id_prefix_v2 + self.prefixedName.get_id_v2() + def get_id_v2(self, objectType, symbol): + return symbol.get_full_nested_name().get_id_v2() def __unicode__(self): res = [] @@ -1455,13 +2103,530 @@ class ASTEnumerator(ASTBase): res.append(text_type(self.init)) return u''.join(res) - def describe_signature(self, signode, mode, env): + def describe_signature(self, signode, mode, env, symbol): _verify_description_mode(mode) - self.name.describe_signature(signode, mode, env) + self.name.describe_signature(signode, mode, env, symbol=symbol) if self.init: self.init.describe_signature(signode, 'noneIsName') +class ASTDeclaration(ASTBase): + def __init__(self, objectType, visibility, templatePrefix, declaration): + self.objectType = objectType + self.visibility = visibility + self.templatePrefix = templatePrefix + self.declaration = declaration + + self.symbol = None + # set by CPPObject._add_enumerator_to_parent + self.enumeratorScopedSymbol = None + + def clone(self): + if self.templatePrefix: + templatePrefixClone = self.templatePrefix.clone() + else: + templatePrefixClone = None + return ASTDeclaration(self.objectType, self.visibility, + templatePrefixClone, + self.declaration.clone()) + + @property + def name(self): + return self.declaration.name + + def get_id_v1(self): + if self.templatePrefix: + raise NoOldIdError() + if self.objectType == 'enumerator' and self.enumeratorScopedSymbol: + return self.enumeratorScopedSymbol.declaration.get_id_v1() + return self.declaration.get_id_v1(self.objectType, self.symbol) + + def get_id_v2(self, prefixed=True): + if self.objectType == 'enumerator' and self.enumeratorScopedSymbol: + return self.enumeratorScopedSymbol.declaration.get_id_v2(prefixed) + if prefixed: + res = [_id_prefix_v2] + else: + res = [] + if self.templatePrefix: + res.append(self.templatePrefix.get_id_v2()) + res.append(self.declaration.get_id_v2(self.objectType, self.symbol)) + return u''.join(res) + + def get_newest_id(self): + return self.get_id_v2() + + def __unicode__(self): + res = [] + if self.visibility and self.visibility != "public": + res.append(self.visibility) + res.append(u' ') + if self.templatePrefix: + res.append(text_type(self.templatePrefix)) + res.append(text_type(self.declaration)) + return u''.join(res) + + def describe_signature(self, signode, mode, env): + _verify_description_mode(mode) + # the caller of the domain added a desc_signature node + # let's pop it so we can add templates before that + parentNode = signode.parent + mainDeclNode = signode + parentNode.pop() + + assert self.symbol + if self.templatePrefix: + self.templatePrefix.describe_signature(parentNode, mode, env, + symbol=self.symbol) + if self.visibility and self.visibility != "public": + mainDeclNode += addnodes.desc_annotation(self.visibility + " ", + self.visibility + " ") + if self.objectType == 'type': + mainDeclNode += addnodes.desc_annotation('type ', 'type ') + elif self.objectType == 'member': + pass + elif self.objectType == 'function': + pass + elif self.objectType == 'class': + mainDeclNode += addnodes.desc_annotation('class ', 'class ') + elif self.objectType == 'enum': + prefix = 'enum ' + if self.scoped: + prefix += self.scoped + prefix += ' ' + mainDeclNode += addnodes.desc_annotation(prefix, prefix) + elif self.objectType == 'enumerator': + mainDeclNode += addnodes.desc_annotation('enumerator ', 'enumerator ') + else: + assert False + self.declaration.describe_signature(mainDeclNode, mode, env, + symbol=self.symbol) + parentNode += mainDeclNode + + +class ASTNamespace(ASTBase): + def __init__(self, nestedName, templatePrefix): + self.nestedName = nestedName + self.templatePrefix = templatePrefix + + +class Symbol(object): + def _assert_invariants(self): + if not self.parent: + # parent == None means global scope, so declaration means a parent + assert not self.identifier + assert not self.templateParams + assert not self.templateArgs + assert not self.declaration + assert not self.docname + else: + if not self.identifier: + # in case it's an operator + assert self.declaration + if self.declaration: + assert self.docname + + def __init__(self, parent, identifier, + templateParams, templateArgs, declaration, docname): + self.parent = parent + self.identifier = identifier + self.templateParams = templateParams # template<templateParams> + self.templateArgs = templateArgs # identifier<templateArgs> + 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): + for sChild in self.children: + sChild.clear_doc(docname) + if sChild.declaration and sChild.docname == docname: + sChild.declaration = None + sChild.docname = None + + def get_all_symbols(self): + yield self + for sChild in self.children: + for s in sChild.get_all_symbols(): + yield s + + def get_lookup_key(self): + if not self.parent: + # specialise for the root + return None + symbols = [] + s = self + while s.parent: + symbols.append(s) + s = s.parent + symbols.reverse() + key = [] + for s in symbols: + if s.identifier: + nne = ASTNestedNameElement(s.identifier, s.templateArgs) + else: + assert s.declaration + nne = s.declaration.name.names[-1] + key.append((nne, s.templateParams)) + return key + + def get_full_nested_name(self): + names = [] + for nne, templateParams in self.get_lookup_key(): + names.append(nne) + return ASTNestedName(names, rooted=False) + + def _find_named_symbol(self, identifier, templateParams, + templateArgs, operator, + templateShorthand, matchSelf): + assert (identifier is None) != (operator is None) + + def matches(s): + if s.identifier != identifier: + return False + if not s.identifier: + if not s.declaration: + return False + assert operator + name = s.declaration.name.names[-1] + if not name.is_operator(): + return False + if text_type(name) != text_type(operator): + return False + if (s.templateParams is None) != (templateParams is None): + if templateParams is not None: + # we query with params, they must match params + return False + if not templateShorthand: + # we don't query with params, and we do care about them + return False + if templateParams: + # TODO: do better comparison + if text_type(s.templateParams) != text_type(templateParams): + return False + if (s.templateArgs is None) != (templateArgs is None): + return False + if s.templateArgs: + # TODO: do better comparison + if text_type(s.templateArgs) != text_type(templateArgs): + return False + return True + if matchSelf and matches(self): + return self + for s in self.children: + if matches(s): + return s + return None + + def _add_symbols(self, nestedName, templateDecls, declaration, docname): + # This condition should be checked at the parser level. + # Each template argument list must have a template parameter list. + # But to declare a template there must be an additional template parameter list. + assert(nestedName.num_templates() == len(templateDecls) or + nestedName.num_templates() + 1 == len(templateDecls)) + + parentSymbol = self + if nestedName.rooted: + while parentSymbol.parent: + parentSymbol = parentSymbol.parent + names = nestedName.names + iTemplateDecl = 0 + for name in names[:-1]: + # there shouldn't be anything inside an operator + # (other than template parameters, which are not added this way, right?) + assert not name.is_operator() + identifier = name.identifier + templateArgs = name.templateArgs + if templateArgs: + assert iTemplateDecl < len(templateDecls) + templateParams = templateDecls[iTemplateDecl] + iTemplateDecl += 1 + else: + templateParams = None + symbol = parentSymbol._find_named_symbol(identifier, + templateParams, + templateArgs, + operator=None, + templateShorthand=False, + matchSelf=False) + if symbol is None: + symbol = Symbol(parent=parentSymbol, identifier=identifier, + templateParams=templateParams, + templateArgs=templateArgs, declaration=None, + docname=None) + parentSymbol = symbol + name = names[-1] + if name.is_operator(): + identifier = None + templateArgs = None + operator = name + else: + identifier = name.identifier + templateArgs = name.templateArgs + operator = None + if iTemplateDecl < len(templateDecls): + if iTemplateDecl + 1 != len(templateDecls): + print(text_type(templateDecls)) + print(text_type(nestedName)) + assert iTemplateDecl + 1 == len(templateDecls) + templateParams = templateDecls[iTemplateDecl] + else: + assert iTemplateDecl == len(templateDecls) + templateParams = None + symbol = parentSymbol._find_named_symbol(identifier, + templateParams, + templateArgs, + operator, + templateShorthand=False, + matchSelf=False) + if symbol: + if not declaration: + # good, just a scope creation + return symbol + if not symbol.declaration: + # If someone first opened the scope, and then later + # declares it, e.g, + # .. namespace:: Test + # .. namespace:: nullptr + # .. class:: Test + symbol._fill_empty(declaration, docname) + return symbol + # it may simply be a functin overload + # TODO: it could be a duplicate but let's just insert anyway + # the id generation will warn about it + symbol = Symbol(parent=parentSymbol, identifier=identifier, + templateParams=templateParams, + templateArgs=templateArgs, + declaration=declaration, + docname=docname) + else: + symbol = Symbol(parent=parentSymbol, identifier=identifier, + templateParams=templateParams, + templateArgs=templateArgs, + declaration=declaration, + docname=docname) + return symbol + + def merge_with(self, other, docnames, env): + assert other is not None + for otherChild in other.children: + if not otherChild.identifier: + if not otherChild.declaration: + print("WTF?") + print(otherChild.dump(0)) + 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): + self._assert_invariants() + 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 @@ -1470,21 +2635,53 @@ class DefinitionParser(object): 'float', 'double', 'auto' ) - _prefix_keys = ('class', 'struct', 'union', 'typename') + _prefix_keys = ('class', 'struct', 'enum', 'union', 'typename') - def __init__(self, definition): + 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: @@ -1540,8 +2737,33 @@ class DefinitionParser(object): def assert_end(self): self.skip_ws() if not self.eof: - self.fail('expected end of definition, got %r' % - self.definition[self.pos:]) + 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() @@ -1562,74 +2784,96 @@ class DefinitionParser(object): 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() + type = self._parse_type(named=False, outer="operatorCast") return ASTOperatorType(type) - def _parse_nested_name(self): + 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('::'): - names.append(ASTNestedNameElementEmpty()) + rooted = True while 1: self.skip_ws() - # TODO: parse the "template" keyword - if not self.match(_identifier_re): - self.fail("expected identifier") - identifier = self.matched_text - if identifier == 'operator': + 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: - templateArgs = None - self.skip_ws() - if self.skip_string('<'): - templateArgs = [] - while 1: - pos = self.pos - try: - type = self._parse_type(allowParams=True) - templateArgs.append(type) - except DefinitionError: - self.pos = pos - symbols = [] - startPos = self.pos - self.skip_ws() - if self.match(_string_re): - value = self.matched_text - else: - while not self.eof: - if (len(symbols) == 0 and - self.current_char in ( - ',', '>')): - break - # TODO: actually implement nice handling - # of quotes, braces, brackets, parens, and - # whatever - self.pos += 1 - if self.eof: - self.pos = startPos - self.fail( - 'Could not find end of constant ' - 'template argument.') - value = self.definition[startPos:self.pos].strip() - templateArgs.append(ASTTemplateArgConstant(value)) - self.skip_ws() - if self.skip_string('>'): - break - elif self.skip_string(','): - continue - else: - self.fail('Expected ">" or "," in template ' - 'argument list.') + 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) + return ASTNestedName(names, rooted) def _parse_trailing_type_spec(self): # fundemental types @@ -1640,7 +2884,6 @@ class DefinitionParser(object): # TODO: this could/should be more strict elements = [] - self.skip_ws() if self.skip_word_and_ws('signed'): elements.append('signed') elif self.skip_word_and_ws('unsigned'): @@ -1652,7 +2895,9 @@ class DefinitionParser(object): elements.append('long') else: break - if self.skip_word_and_ws('int'): + 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') @@ -1695,10 +2940,10 @@ class DefinitionParser(object): 'parameters_and_qualifiers.') break if paramMode == 'function': - arg = self._parse_type_with_init(named='maybe') + arg = self._parse_type_with_init(outer=None, named='single') else: - arg = self._parse_type() - # TODO: parse default parameters + arg = self._parse_type(named=False) + # TODO: parse default parameters # TODO: didn't we just do that? args.append(ASTFunctinoParameter(arg)) self.skip_ws() @@ -1711,7 +2956,10 @@ class DefinitionParser(object): 'Expecting "," or ")" in parameters_and_qualifiers, ' 'got "%s".' % self.current_char) - if paramMode != 'function': + # 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) @@ -1771,6 +3019,7 @@ class DefinitionParser(object): 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: @@ -1782,11 +3031,9 @@ class DefinitionParser(object): if self.skip_word('mutable'): storage = 'mutable' continue - if outer == 'fuction': - # TODO: maybe in more contexts, missing test cases - if self.skip_word('register'): - storage = 'register' - continue + if self.skip_word('register'): + storage = 'register' + continue if outer == 'function': # function-specifiers @@ -1794,6 +3041,10 @@ class DefinitionParser(object): 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: @@ -1817,100 +3068,173 @@ class DefinitionParser(object): continue break return ASTDeclSpecsSimple(storage, inline, virtual, explicit, constexpr, - volatile, const) + 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) """ - visibility storage-class-specifier function-specifier "constexpr" + storage-class-specifier function-specifier "constexpr" "volatile" "const" trailing-type-specifier - storage-class-specifier -> "static" (only for member_object and - function_object) + 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) """ - visibility = None - leftSepcs = None - rightSpecs = None - if outer: - self.skip_ws() - if self.match(_visibility_re): - visibility = self.matched_text 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, visibility, leftSpecs, rightSpecs, trailing) - - def _parse_declerator(self, named, paramMode=None, typed=True): - if paramMode: - if paramMode not in ('type', 'function'): - raise Exception( - "Internal error, unknown paramMode '%s'." % paramMode) - ptrOps = [] - while 1: - if not typed: - break - self.skip_ws() - if self.skip_string('*'): - self.skip_ws() - volatile = self.skip_word_and_ws('volatile') - const = self.skip_word_and_ws('const') - ptrOps.append(ASTPtrOpPtr(volatile=volatile, const=const)) - elif self.skip_string('&'): - ptrOps.append(ASTPtrOpRef()) - elif self.skip_string('...'): - ptrOps.append(ASTPtrOpParamPack()) - break - else: - break + 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 - - suffixOpts = [] + arrayOps = [] while 1: self.skip_ws() if typed and self.skip_string('['): - startPos = self.pos - 1 - openCount = 1 - while not self.eof: - c = self.current_char - if c == '[': - openCount += 1 - elif c == ']': - openCount -= 1 - if openCount == 0: - break - self.pos += 1 - if self.eof: - self.pos = startPos - self.fail( - "Could not find closing square bracket for array.") - self.pos += 1 - suffixOpts.append(ASTArray( - self.definition[startPos + 1:self.pos - 1].strip())) + value = self._parse_expression(end=[']']) + res = self.skip_string(']') + assert res + arrayOps.append(ASTArray(value)) continue - if paramMode: - paramQual = self._parse_parameters_and_qualifiers(paramMode) - if paramQual: - suffixOpts.append(paramQual) - break + else: + break + paramQual = self._parse_parameters_and_qualifiers(paramMode) + return ASTDecleratorNameParamQual(declId=declId, arrayOps=arrayOps, + paramQual=paramQual) - return ASTDeclerator(ptrOps, declId, suffixOpts) + 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() @@ -1920,50 +3244,34 @@ class DefinitionParser(object): else: if outer == 'member': value = self.read_rest().strip() - return ASTInitializer(value) + elif outer == 'templateParam': + value = self._parse_expression(end=[',', '>']) elif outer is None: # function parameter - symbols = [] - startPos = self.pos - self.skip_ws() - if self.match(_string_re): - value = self.matched_text - return ASTInitializer(value) - while not self.eof: - if len(symbols) == 0 and self.current_char in (',', ')'): - break - elif len(symbols) > 0 and self.current_char == symbols[-1]: - symbols.pop() - elif self.current_char == '(': - symbols.append(')') - # TODO: actually implement nice handling of quotes, braces, - # brackets, parens, and whatever - self.pos += 1 - if self.eof: - self.pos = startPos - self.fail( - 'Could not find end of default value for function ' - 'parameter.') - value = self.definition[startPos:self.pos].strip() - return ASTInitializer(value) + value = self._parse_expression(end=[',', ')']) else: - self.fail( - "Internal error, initializer for outer '%s' not " - "implemented." % outer) + self.fail("Internal error, initializer for outer '%s' not " + "implemented." % outer) + return ASTInitializer(value) - def _parse_type(self, outer=None, named=False, allowParams=False): + 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'): + if outer not in ('type', 'member', 'function', + 'operatorCast', 'templateParam'): raise Exception('Internal error, unknown outer "%s".' % outer) - assert not named + 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: @@ -1972,79 +3280,116 @@ class DefinitionParser(object): 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': - raise DefinitionError( - 'Type must be either just a name or a ' - 'typedef-like declaration.\nJust a name error: ' - '%s\nTypedef-like expression error: %s' - % (exUntyped.description, exTyped.description)) + 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 - declSpecs = self._parse_decl_specs(outer=outer) - decl = self._parse_declerator(named=True, - paramMode=outer) + typed = True + declSpecs = self._parse_decl_specs(outer=outer, typed=typed) + decl = self._parse_declerator(named=True, paramMode=outer, + typed=typed) else: - if outer: + paramMode = 'type' + if outer == 'member': # i.e., member named = True - allowParams = True - if allowParams: - paramMode = 'type' - else: - paramMode = None + 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, outer=None, named=False): + def _parse_type_with_init(self, named, outer): if outer: - assert outer in ('type', 'member', 'function') + 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): - classVisibility = 'public' - if self.match(_visibility_re): - classVisibility = self.matched_text 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() - bases.append(ASTBaseClass(baseName, visibility)) + 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, classVisibility, bases) + return ASTClass(name, final, bases) def _parse_enum(self): - scoped = None # is set by CPPEnumObject - self.skip_ws() - visibility = 'public' - if self.match(_visibility_re): - visibility = self.matched_text + 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() - return ASTEnum(name, visibility, scoped, underlyingType) + underlyingType = self._parse_type(named=False) + return ASTEnum(name, scoped, underlyingType) def _parse_enumerator(self): name = self._parse_nested_name() @@ -2055,47 +3400,195 @@ class DefinitionParser(object): init = ASTInitializer(self.read_rest()) return ASTEnumerator(name, init) - def parse_type_object(self): - res = self._parse_type(outer='type') - res.objectType = 'type' - return res + 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_member_object(self): - res = self._parse_type_with_init(outer='member') - res.objectType = 'member' - return res + 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 parse_function_object(self): - res = self._parse_type(outer='function') - res.objectType = 'function' - return res + 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 - def parse_class_object(self): - res = self._parse_class() - res.objectType = 'class' - return res + self.skip_ws() + if self.match(_visibility_re): + visibility = self.matched_text - def parse_enum_object(self): - res = self._parse_enum() - res.objectType = 'enum' - return res + if objectType in ('type', 'member', 'function', 'class'): + templatePrefix = self._parse_template_declaration_prefix() - def parse_enumerator_object(self): - res = self._parse_enumerator() - res.objectType = 'enumerator' - return res + 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): - res = self._parse_nested_name() + 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): - res = self._parse_nested_name() + 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.""" @@ -2103,6 +3596,9 @@ class CPPObject(ObjectDescription): 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), @@ -2110,97 +3606,116 @@ class CPPObject(ObjectDescription): 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): - ids = [ # the newest should be first - ast.get_id_v2(), - ast.get_id_v1() - ] - theid = ids[0] - ast.newestId = theid - assert theid # shouldn't be None - name = text_type(ast.prefixedName) - if theid not in self.state.document.ids: + # 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, '')) + + if newestId not in self.state.document.ids: # if the name is not unique, the first one will win - objects = self.env.domaindata['cpp']['objects'] - if name not in objects: + 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 - #print("[CPP] non-unique name:", name) for id in ids: - if id: # is None when the element didn't exist in that version + if id: # is None when the element didn't exist in that version signode['ids'].append(id) - signode['first'] = (not self.names) + signode['first'] = (not self.names) # hmm, what is this abound? self.state.document.note_explicit_target(signode) - if not name in objects: - objects.setdefault(name, (self.env.docname, ast)) - if ast.objectType == 'enumerator': - # find the parent, if it exists && is an enum - # && it's unscoped, - # then add the name to the parent scope - assert len(ast.prefixedName.names) > 0 - parentPrefixedAstName = ASTNestedName(ast.prefixedName.names[:-1]) - parentPrefixedName = text_type(parentPrefixedAstName) - if parentPrefixedName in objects: - docname, parentAst = objects[parentPrefixedName] - if parentAst.objectType == 'enum' and not parentAst.scoped: - enumeratorName = ASTNestedName([ast.prefixedName.names[-1]]) - assert len(parentAst.prefixedName.names) > 0 - enumScope = ASTNestedName(parentAst.prefixedName.names[:-1]) - unscopedName = enumeratorName.prefix_nested_name(enumScope) - txtUnscopedName = text_type(unscopedName) - if not txtUnscopedName in objects: - objects.setdefault(txtUnscopedName, - (self.env.docname, ast)) - # add the uninstantiated template if it doesn't exist - uninstantiated = ast.prefixedName.get_name_no_last_template() - if uninstantiated != name and uninstantiated not in objects: - signode['names'].append(uninstantiated) - objects.setdefault(uninstantiated, (self.env.docname, ast)) - - indextext = self.get_index_text(name) - if not re.compile(r'^[a-zA-Z0-9_]*$').match(theid): - self.state_machine.reporter.warning( - 'Index id generation for C++ object "%s" failed, please ' - 'report as bug (id=%s).' % (text_type(ast), theid), - line=self.lineno) - self.indexnode['entries'].append(('single', indextext, theid, '')) def parse_definition(self, parser): raise NotImplementedError() - def describe_signature(self, signode, ast): + def describe_signature(self, signode, ast, parentScope): raise NotImplementedError() def handle_signature(self, sig, signode): - def set_lastname(name): - parent = self.env.ref_context.get('cpp:parent') - if parent and len(parent) > 0: - res = name.prefix_nested_name(parent[-1]) - else: - res = name - assert res - self.env.ref_context['cpp:lastname'] = res - return res + 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) + parser = DefinitionParser(sig, self) try: ast = self.parse_definition(parser) parser.assert_end() except DefinitionError as e: - self.state_machine.reporter.warning(e.description, - line=self.lineno) + self.warn(e.description) # It is easier to assume some phony name than handling the error in # the possibly inner declarations. - name = ASTNestedName([ - ASTNestedNameElement("PhonyNameDueToError", None) - ]) - set_lastname(name) + name = _make_phony_error_name() + symbol = parentSymbol.add_name(name) + self.env.ref_context['cpp:lastSymbol'] = symbol raise ValueError - self.describe_signature(signode, ast) + symbol = parentSymbol.add_declaration(ast, docname=self.env.docname) + self.env.ref_context['cpp:lastSymbol'] = symbol + + if ast.objectType == 'enumerator': + self._add_enumerator_to_parent(ast) - ast.prefixedName = set_lastname(ast.name) - assert ast.prefixedName + self.describe_signature(signode, ast) return ast @@ -2209,10 +3724,9 @@ class CPPTypeObject(CPPObject): return _('%s (C++ type)') % name def parse_definition(self, parser): - return parser.parse_type_object() + return parser.parse_declaration("type") def describe_signature(self, signode, ast): - signode += addnodes.desc_annotation('type ', 'type ') ast.describe_signature(signode, 'lastIsName', self.env) @@ -2221,7 +3735,7 @@ class CPPMemberObject(CPPObject): return _('%s (C++ member)') % name def parse_definition(self, parser): - return parser.parse_member_object() + return parser.parse_declaration("member") def describe_signature(self, signode, ast): ast.describe_signature(signode, 'lastIsName', self.env) @@ -2232,7 +3746,7 @@ class CPPFunctionObject(CPPObject): return _('%s (C++ function)') % name def parse_definition(self, parser): - return parser.parse_function_object() + return parser.parse_declaration("function") def describe_signature(self, signode, ast): ast.describe_signature(signode, 'lastIsName', self.env) @@ -2243,21 +3757,18 @@ class CPPClassObject(CPPObject): return _('%s (C++ class)') % name def before_content(self): - lastname = self.env.ref_context['cpp:lastname'] - assert lastname - if 'cpp:parent' in self.env.ref_context: - self.env.ref_context['cpp:parent'].append(lastname) - else: - self.env.ref_context['cpp:parent'] = [lastname] + 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:parent'].pop() + self.env.ref_context['cpp:parentSymbol'] = self.oldParentSymbol def parse_definition(self, parser): - return parser.parse_class_object() + return parser.parse_declaration("class") def describe_signature(self, signode, ast): - signode += addnodes.desc_annotation('class ', 'class ') ast.describe_signature(signode, 'lastIsName', self.env) @@ -2266,18 +3777,16 @@ class CPPEnumObject(CPPObject): return _('%s (C++ enum)') % name def before_content(self): - lastname = self.env.ref_context['cpp:lastname'] - assert lastname - if 'cpp:parent' in self.env.ref_context: - self.env.ref_context['cpp:parent'].append(lastname) - else: - self.env.ref_context['cpp:parent'] = [lastname] + 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:parent'].pop() + self.env.ref_context['cpp:parentSymbol'] = self.oldParentSymbol def parse_definition(self, parser): - ast = parser.parse_enum_object() + ast = parser.parse_declaration("enum") # self.objtype is set by ObjectDescription in run() if self.objtype == "enum": ast.scoped = None @@ -2290,11 +3799,6 @@ class CPPEnumObject(CPPObject): return ast def describe_signature(self, signode, ast): - prefix = 'enum ' - if ast.scoped: - prefix += ast.scoped - prefix += ' ' - signode += addnodes.desc_annotation(prefix, prefix) ast.describe_signature(signode, 'lastIsName', self.env) @@ -2303,10 +3807,9 @@ class CPPEnumeratorObject(CPPObject): return _('%s (C++ enumerator)') % name def parse_definition(self, parser): - return parser.parse_enumerator_object() + return parser.parse_declaration("enumerator") def describe_signature(self, signode, ast): - signode += addnodes.desc_annotation('enumerator ', 'enumerator ') ast.describe_signature(signode, 'lastIsName', self.env) @@ -2322,28 +3825,97 @@ class CPPNamespaceObject(Directive): 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'): - env.ref_context['cpp:parent'] = [] + symbol = rootSymbol + stack = [] else: - parser = DefinitionParser(self.arguments[0]) + parser = DefinitionParser(self.arguments[0], self) try: - prefix = parser.parse_namespace_object() + ast = parser.parse_namespace_object() parser.assert_end() except DefinitionError as e: - self.state_machine.reporter.warning(e.description, - line=self.lineno) - else: - env.ref_context['cpp:parent'] = [prefix] + 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:parent') + parent = env.ref_context.get('cpp:parentSymbol', None) if parent: - refnode['cpp:parent'] = parent[:] + refnode['cpp:parentKey'] = parent.get_lookup_key() + # 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 @@ -2355,6 +3927,7 @@ class CPPXRefRole(XRefRole): title = title[dcolon + 2:] return title, target + class CPPDomain(Domain): """C++ language domain.""" name = 'cpp' @@ -2378,7 +3951,9 @@ class CPPDomain(Domain): 'enum-struct': CPPEnumObject, 'enum-class': CPPEnumObject, 'enumerator': CPPEnumeratorObject, - 'namespace': CPPNamespaceObject + 'namespace': CPPNamespaceObject, + 'namespace-push': CPPNamespacePushObject, + 'namespace-pop': CPPNamespacePopObject } roles = { 'any': CPPXRefRole(), @@ -2391,69 +3966,105 @@ class CPPDomain(Domain): 'enumerator': CPPXRefRole() } initial_data = { - 'objects': {}, # prefixedName -> (docname, ast) + 'rootSymbol': Symbol(None, None, None, None, None, None), + 'names': {} # full name for indexing -> docname } def clear_doc(self, docname): - for fullname, data in list(self.data['objects'].items()): - if data[0] == docname: - del self.data['objects'][fullname] + 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): - # XXX check duplicates - for fullname, data in otherdata['objects'].items(): - if data[0] in docnames: - self.data['objects'][fullname] = data + 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, - target, node, contnode, warn=True): - def _create_refnode(nameAst): - name = text_type(nameAst) - if name not in self.data['objects']: - # try dropping the last template - name = nameAst.get_name_no_last_template() - if name not in self.data['objects']: - return None, None - docname, ast = self.data['objects'][name] - return make_refnode(builder, fromdocname, docname, ast.newestId, - contnode, name), ast.objectType - - parser = DefinitionParser(target) + 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: - nameAst = parser.parse_xref_object().name + ast = parser.parse_xref_object() parser.skip_ws() - if not parser.eof: - raise DefinitionError('') - except DefinitionError: - if warn: - env.warn_node('unparseable C++ definition: %r' % target, node) + 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 - # try as is the name is fully qualified - res = _create_refnode(nameAst) - if res[0]: - return res - - # try qualifying it with the parent - parent = node.get('cpp:parent', None) - if parent and len(parent) > 0: - return _create_refnode(nameAst.prefix_nested_name(parent[-1])) + 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 + 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, target, node, - contnode)[0] + return self._resolve_xref_inner(env, fromdocname, builder, target, + node, contnode)[0] def resolve_any_xref(self, env, fromdocname, builder, target, node, contnode): node, objtype = self._resolve_xref_inner(env, fromdocname, builder, - target, node, contnode, warn=False) + target, node, contnode, + emitWarnings=False) if node: return [('cpp:' + self.role_for_objtype(objtype), node)] return [] def get_objects(self): - for refname, (docname, ast) in iteritems(self.data['objects']): - yield (refname, refname, ast.objectType, docname, ast.newestId, 1) + 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) |