diff options
Diffstat (limited to 'sphinx/domains/cpp.py')
-rw-r--r-- | sphinx/domains/cpp.py | 2275 |
1 files changed, 1418 insertions, 857 deletions
diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index cb64a60d..23bd469f 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -7,11 +7,144 @@ :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. + + See http://www.nongnu.org/hcb/ for the grammar. + See http://mentorembedded.github.io/cxx-abi/abi.html#mangling for the + inspiration for the id generation. + + common grammar things: + 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) + | type-specifier -> trailing-type-specifier + | function-specifier -> "inline" | "virtual" | "explicit" (only + for function_object) + | "friend" (only for function_object) + | "constexpr" (only for member_object and function_object) + trailing-type-specifier -> + simple-type-specifier + | elaborated-type-specifier + | typename-specifier + | cv-qualifier -> "const" | "volatile" + stricter grammar for decl-specifier-seq (with everything, each object + uses a subset): + visibility storage-class-specifier function-specifier "friend" + "constexpr" "volatile" "const" trailing-type-specifier + # where trailing-type-specifier can no be cv-qualifier + # Inside e.g., template paramters a strict subset is used + # (see type-specifier-seq) + trailing-type-specifier -> + simple-type-specifier -> + ::[opt] nested-name-specifier[opt] type-name + | ::[opt] nested-name-specifier "template" simple-template-id + | "char" | "bool" | ect. + | decltype-specifier + | elaborated-type-specifier -> + class-key attribute-specifier-seq[opt] ::[opt] + nested-name-specifier[opt] identifier + | class-key ::[opt] nested-name-specifier[opt] template[opt] + simple-template-id + | "enum" ::[opt] nested-name-specifier[opt] identifier + | typename-specifier -> + "typename" ::[opt] nested-name-specifier identifier + | "typename" ::[opt] nested-name-specifier template[opt] + simple-template-id + class-key -> "class" | "struct" | "union" + type-name ->* identifier | simple-template-id + # ignoring attributes and decltype, and then some left-factoring + trailing-type-specifier -> + rest-of-trailing + ("class" | "struct" | "union" | "typename") rest-of-trailing + build-in -> "char" | "bool" | ect. + decltype-specifier + rest-of-trailing -> (with some simplification) + "::"[opt] list-of-elements-separated-by-:: + element -> + "template"[opt] identifier ("<" template-argument-list ">")[opt] + template-argument-list -> + template-argument "..."[opt] + | template-argument-list "," template-argument "..."[opt] + template-argument -> + constant-expression + | type-specifier-seq abstract-declerator + | id-expression + + + declerator -> + ptr-declerator + | noptr-declarator parameters-and-qualifiers trailing-return-type + (TODO: for now we don't support it) + ptr-declerator -> + noptr-declerator + | ptr-operator ptr-declarator + noptr-declerator -> + declarator-id attribute-specifier-seq[opt] -> + "..."[opt] id-expression + | rest-of-trailing + | noptr-declerator parameters-and-qualifiers + | noptr-declarator "[" constant-expression[opt] "]" + attribute-specifier-seq[opt] + | "(" ptr-declarator ")" # TODO: not implemented yet + # function_object must use a parameters-and-qualifiers, the others may + # use it (e.g., function poitners) + parameters-and-qualifiers -> + "(" parameter-clause ")" attribute-specifier-seq[opt] + cv-qualifier-seq[opt] ref-qualifier[opt] + exception-specification[opt] + ref-qualifier -> "&" | "&&" + exception-specification -> + "noexcept" ("(" constant-expression ")")[opt] + "throw" ("(" type-id-list ")")[opt] + # TODO: we don't implement attributes + # member functions can have initializers, but we fold them into here + memberFunctionInit -> "=" "0" + # (note: only "0" is allowed as the value, according to the standard, + # right?) + + + We additionally add the possibility for specifying the visibility as the + first thing. + + type_object: + goal: + either a single type (e.g., "MyClass:Something_T" or a typedef-like + thing (e.g. "Something Something_T" or "int I_arr[]" + grammar, single type: based on a type in a function parameter, but + without a name: + parameter-declaration + -> attribute-specifier-seq[opt] decl-specifier-seq + abstract-declarator[opt] + # Drop the attributes + -> decl-specifier-seq abstract-declarator[opt] + grammar, typedef-like: no initilizer + decl-specifier-seq declerator + + + member_object: + goal: as a type_object which must have a declerator, and optionally + with a initializer + grammar: + decl-specifier-seq declerator initializer + + function_object: + goal: a function declaration, TODO: what about templates? for now: skip + grammar: no initializer + decl-specifier-seq declerator """ import re +import traceback from copy import deepcopy +from six import iteritems, text_type from docutils import nodes from sphinx import addnodes @@ -21,6 +154,7 @@ 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 @@ -40,88 +174,112 @@ _operator_re = re.compile(r'''(?x) | [!<>=/*%+|&^~-]=? ''') -_id_shortwords = { - 'char': 'c', - 'signed char': 'c', - 'unsigned char': 'C', - 'int': 'i', - 'signed int': 'i', - 'unsigned int': 'U', - 'long': 'l', - 'signed long': 'l', - 'unsigned long': 'L', - 'bool': 'b', - 'size_t': 's', - 'std::string': 'ss', - 'std::ostream': 'os', - 'std::istream': 'is', - 'std::iostream': 'ios', - 'std::vector': 'v', - 'std::map': 'm', - 'operator[]': 'subscript-operator', - 'operator()': 'call-operator', - 'operator!': 'not-operator', - 'operator<': 'lt-operator', - 'operator<=': 'lte-operator', - 'operator>': 'gt-operator', - 'operator>=': 'gte-operator', - 'operator=': 'assign-operator', - 'operator/': 'div-operator', - 'operator*': 'mul-operator', - 'operator%': 'mod-operator', - 'operator+': 'add-operator', - 'operator-': 'sub-operator', - 'operator|': 'or-operator', - 'operator&': 'and-operator', - 'operator^': 'xor-operator', - 'operator&&': 'sand-operator', - 'operator||': 'sor-operator', - 'operator==': 'eq-operator', - 'operator!=': 'neq-operator', - 'operator<<': 'lshift-operator', - 'operator>>': 'rshift-operator', - 'operator-=': 'sub-assign-operator', - 'operator+=': 'add-assign-operator', - 'operator*-': 'mul-assign-operator', - 'operator/=': 'div-assign-operator', - 'operator%=': 'mod-assign-operator', - 'operator&=': 'and-assign-operator', - 'operator|=': 'or-assign-operator', - 'operator<<=': 'lshift-assign-operator', - 'operator>>=': 'rshift-assign-operator', - 'operator^=': 'xor-assign-operator', - 'operator,': 'comma-operator', - 'operator->': 'pointer-operator', - 'operator->*': 'pointer-by-pointer-operator', - 'operator~': 'inv-operator', - 'operator++': 'inc-operator', - 'operator--': 'dec-operator', - 'operator new': 'new-operator', - 'operator new[]': 'new-array-operator', - 'operator delete': 'delete-operator', - 'operator delete[]': 'delete-array-operator' +_id_prefix = '_CPP' +_id_fundamental = { + # not all of these are actually parsed as fundamental types, TODO: do that + 'void': 'v', + 'bool': 'b', + 'char': 'c', + 'signed char': 'a', + 'unsigned char': 'h', + 'wchar_t': 'w', + 'char32_t': 'Di', + 'char16_t': 'Ds', + 'short': 's', + 'short int': 's', + 'signed short': 's', + 'signed short int': 's', + 'unsigned short': 't', + 'unsigned short int': 't', + 'int': 'i', + 'signed': 'i', + 'signed int': 'i', + 'unsigned': 'j', + 'unsigned int': 'j', + 'long': 'l', + 'long int': 'l', + 'signed long': 'l', + 'signed long int': 'l', + 'unsigned long': 'm', + 'unsigned long int': 'm', + 'long long': 'x', + 'long long int': 'x', + 'signed long long': 'x', + 'signed long long int': 'x', + 'unsigned long long': 'y', + 'unsigned long long int': 'y', + 'float': 'f', + 'double': 'd', + 'long double': 'e', + 'auto': 'Da', + 'decltype(auto)': 'Dc', + 'std::nullptr_t': 'Dn' +} +_id_operator = { + 'new': 'nw', + 'new[]': 'na', + 'delete': 'dl', + 'delete[]': 'da', + # the arguments will make the difference between unary and binary + # '+(unary)' : 'ps', + #'-(unary)' : 'ng', + #'&(unary)' : 'ad', + #'*(unary)' : 'de', + '~': 'co', + '+': 'pl', + '-': 'mi', + '*': 'ml', + '/': 'dv', + '%': 'rm', + '&': 'an', + '|': 'or', + '^': 'eo', + '=': 'aS', + '+=': 'pL', + '-=': 'mI', + '*=': 'mL', + '/=': 'dV', + '%=': 'rM', + '&=': 'aN', + '|=': 'oR', + '^=': 'eO', + '<<': 'ls', + '>>': 'rs', + '<<=': 'lS', + '>>=': 'rS', + '==': 'eq', + '!=': 'ne', + '<': 'lt', + '>': 'gt', + '<=': 'le', + '>=': 'ge', + '!': 'nt', + '&&': 'aa', + '||': 'oo', + '++': 'pp', + '--': 'mm', + ',': 'cm', + '->*': 'pm', + '->': 'pt', + '()': 'cl', + '[]': 'ix' } -class DefinitionError(Exception): - +class DefinitionError(UnicodeMixin, Exception): def __init__(self, description): self.description = description - def __str__(self): - return unicode(self).encode('utf-8') - def __unicode__(self): return self.description -class DefExpr(object): - +class ASTBase(UnicodeMixin): def __eq__(self, other): if type(self) is not type(other): return False try: - for key, value in self.__dict__.iteritems(): + for key, value in iteritems(self.__dict__): if value != getattr(other, key): return False except AttributeError: @@ -139,7 +297,7 @@ class DefExpr(object): def get_id(self): """Return the id for the node.""" - return u'' + raise NotImplementedError(repr(self)) def get_name(self): """Return the name. @@ -147,402 +305,748 @@ class DefExpr(object): Returns either `None` or a node with a name you might call :meth:`split_owner` on. """ - return None - - def split_owner(self): - """Nodes returned by :meth:`get_name` can split off their - owning parent. This function returns the owner and the - name as a tuple of two items. If a node does not support - it, it returns None as owner and self as name. - """ - return None, self + raise NotImplementedError(repr(self)) - def prefix(self, prefix): + def prefix_nested_name(self, prefix): """Prefix a name node (a node returned by :meth:`get_name`).""" - raise NotImplementedError() - - def __str__(self): - return unicode(self).encode('utf-8') + raise NotImplementedError(repr(self)) def __unicode__(self): - raise NotImplementedError() + raise NotImplementedError(repr(self)) def __repr__(self): return '<%s %s>' % (self.__class__.__name__, self) -class PrimaryDefExpr(DefExpr): - - def get_name(self): - return self - - def prefix(self, prefix): - if isinstance(prefix, PathDefExpr): - prefix = prefix.clone() - prefix.path.append(self) - return prefix - return PathDefExpr([prefix, self]) +def _verify_description_mode(mode): + if not mode in ('lastIsName', 'noneIsName', 'markType', 'param'): + raise Exception("Description mode '%s' is invalid." % mode) -class NameDefExpr(PrimaryDefExpr): - - def __init__(self, name): - self.name = name +class ASTOperatorBuildIn(ASTBase): + def __init__(self, op): + self.op = op def get_id(self): - name = _id_shortwords.get(self.name) - if name is not None: - return name - return self.name.replace(u' ', u'-') + if not self.op in _id_operator: + raise Exception('Internal error: Build-in operator "%s" can not ' + 'be mapped to an id.' % self.op) + return _id_operator[self.op] def __unicode__(self): - return unicode(self.name) - - -class PathDefExpr(PrimaryDefExpr): + if self.op in ('new', 'new[]', 'delete', 'delete[]'): + return u'operator ' + self.op + else: + return u'operator' + self.op - def __init__(self, parts): - self.path = parts + def get_name_no_template(self): + return text_type(self) - def get_id(self): - rv = u'::'.join(x.get_id() for x in self.path) - return _id_shortwords.get(rv, rv) + def describe_signature(self, signode, mode, env, prefix): + _verify_description_mode(mode) + identifier = text_type(self) + if mode == 'lastIsName': + signode += addnodes.desc_name(identifier, identifier) + else: + signode += addnodes.desc_addname(identifier, identifier) - def split_owner(self): - if len(self.path) > 1: - return PathDefExpr(self.path[:-1]), self.path[-1] - return None, self - def prefix(self, prefix): - if isinstance(prefix, PathDefExpr): - prefix = prefix.clone() - prefix.path.extend(self.path) - return prefix - return PathDefExpr([prefix] + self.path) +class ASTOperatorType(ASTBase): + def __init__(self, type): + self.type = type def __unicode__(self): - return u'::'.join(map(unicode, self.path)) + return u''.join(['operator ', text_type(self.type)]) + def get_id(self): + return u'cv' + self.type.get_id() -class ArrayTypeSuffixDefExpr(object): + def get_name_no_template(self): + return text_type(self) + + def describe_signature(self, signode, mode, env, prefix): + _verify_description_mode(mode) + identifier = text_type(self) + if mode == 'lastIsName': + signode += addnodes.desc_name(identifier, identifier) + else: + signode += addnodes.desc_addname(identifier, identifier) - def __init__(self, size_hint=None): - self.size_hint = size_hint - def get_id_suffix(self): - return 'A' +class ASTTemplateArgConstant(ASTBase): + def __init__(self, value): + self.value = value def __unicode__(self): - return u'[%s]' % ( - self.size_hint is not None and unicode(self.size_hint) or u'', - ) + return text_type(self.value) + def get_id(self): + # TODO: doing this properly needs parsing of expressions, let's just + # juse it verbatim for now + return u'X' + text_type(self) + u'E' -class TemplateDefExpr(PrimaryDefExpr): + def describe_signature(self, signode, mode, env): + _verify_description_mode(mode) + signode += nodes.Text(text_type(self)) - def __init__(self, typename, args): - self.typename = typename - self.args = args - def split_owner(self): - owner, typename = self.typename.split_owner() - return owner, TemplateDefExpr(typename, self.args) +class ASTNestedNameElement(ASTBase): + def __init__(self, identifier, templateArgs): + self.identifier = identifier + self.templateArgs = templateArgs def get_id(self): - return u'%s:%s:' % (self.typename.get_id(), - u'.'.join(x.get_id() for x in self.args)) + res = [] + if self.identifier == "std": + res.append(u'St') + else: + res.append(text_type(len(self.identifier))) + res.append(self.identifier) + if self.templateArgs: + res.append('I') + for a in self.templateArgs: + res.append(a.get_id()) + res.append('E') + return u''.join(res) def __unicode__(self): - return u'%s<%s>' % (self.typename, u', '.join(map(unicode, self.args))) - - -class ConstantTemplateArgExpr(PrimaryDefExpr): + res = [] + res.append(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) + + 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.temp_data.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) + 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('>') + + +class ASTNestedName(ASTBase): + def __init__(self, names): + """Use an empty string as the first name if it should start with '::' + """ + self.names = names - def __init__(self, arg): - self.arg = arg + @property + def name(self): + return self def get_id(self): - return self.arg.replace(u' ', u'-') + res = [] + if len(self.names) > 1: + res.append('N') + for n in self.names: + res.append(n.get_id()) + if len(self.names) > 1: + 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 unicode(self.arg) + return u'::'.join([text_type(n) for n in self.names]) + + def describe_signature(self, signode, mode, env): + _verify_description_mode(mode) + if mode == 'lastIsName': + addname = u'::'.join([text_type(n) for n in self.names[:-1]]) + if len(self.names) > 1: + addname += u'::' + name = text_type(self.names[-1]) + signode += addnodes.desc_addname(addname, addname) + self.names[-1].describe_signature(signode, mode, env, '') + elif mode == 'noneIsName': + name = text_type(self) + signode += nodes.Text(name) + elif mode == 'param': + name = text_type(self) + signode += nodes.emphasis(name, name) + elif mode == 'markType': + # each element should be a pending xref targeting the complete + # prefix. however, only the identifier part should be a link, such + # that template args can be a link as well. + prefix = '' + first = True + for name in self.names: + if not first: + signode += nodes.Text('::') + prefix += '::' + first = False + if name != '': + name.describe_signature(signode, mode, env, prefix) + prefix += text_type(name) + else: + raise Exception('Unknown description mode: %s' % mode) + +class ASTTrailingTypeSpecFundamental(ASTBase): + def __init__(self, name): + self.name = name -class WrappingDefExpr(DefExpr): + def __unicode__(self): + return self.name - def __init__(self, typename): - self.typename = typename + def get_id(self): + if not self.name in _id_fundamental: + raise Exception( + 'Semi-internal error: Fundamental type "%s" can not be mapped ' + 'to an id. Is it a true fundamental type? If not so, the ' + 'parser should have rejected it.' % self.name) + return _id_fundamental[self.name] - def get_name(self): - return self.typename.get_name() + def describe_signature(self, signode, mode, env): + signode += nodes.Text(text_type(self.name)) -class ModifierDefExpr(WrappingDefExpr): +class ASTTrailingTypeSpecName(ASTBase): + def __init__(self, prefix, nestedName): + self.prefix = prefix + self.nestedName = nestedName - def __init__(self, typename, modifiers): - WrappingDefExpr.__init__(self, typename) - self.modifiers = modifiers + @property + def name(self): + return self.nestedName def get_id(self): - pieces = [_id_shortwords.get(unicode(x), unicode(x)) - for x in self.modifiers] - pieces.append(self.typename.get_id()) - return u'-'.join(pieces) + return self.nestedName.get_id() def __unicode__(self): - return u' '.join(map(unicode, list(self.modifiers) + [self.typename])) + res = [] + if self.prefix: + res.append(self.prefix) + res.append(' ') + res.append(text_type(self.nestedName)) + return u''.join(res) + + def describe_signature(self, signode, mode, env): + if self.prefix: + signode += addnodes.desc_annotation(self.prefix, self.prefix) + signode += nodes.Text(' ') + self.nestedName.describe_signature(signode, mode, env) -class PtrDefExpr(WrappingDefExpr): +class ASTFunctinoParameter(ASTBase): + def __init__(self, arg, ellipsis=False): + self.arg = arg + self.ellipsis = ellipsis def get_id(self): - return self.typename.get_id() + u'P' + if self.ellipsis: + return 'z' + else: + return self.arg.get_id() def __unicode__(self): - return u'%s*' % self.typename + if self.ellipsis: + return '...' + else: + return text_type(self.arg) + def describe_signature(self, signode, mode, env): + _verify_description_mode(mode) + if self.ellipsis: + signode += nodes.Text('...') + else: + self.arg.describe_signature(signode, mode, env) -class LValRefDefExpr(WrappingDefExpr): - def get_id(self): - return self.typename.get_id() + u'R' +class ASTParametersQualifiers(ASTBase): + def __init__(self, args, volatile, const, refQual, exceptionSpec, override, + final, initializer): + self.args = args + self.volatile = volatile + self.const = const + self.refQual = refQual + self.exceptionSpec = exceptionSpec + self.override = override + self.final = final + self.initializer = initializer + + def get_modifiers_id(self): + res = [] + if self.volatile: + res.append('V') + if self.const: + res.append('K') + if self.refQual == '&&': + res.append('O') + elif self.refQual == '&': + res.append('R') + return u''.join(res) + + def get_param_id(self): + if len(self.args) == 0: + return 'v' + else: + return u''.join(a.get_id() for a in self.args) def __unicode__(self): - return u'%s&' % self.typename + res = [] + res.append('(') + first = True + for a in self.args: + if not first: + res.append(', ') + first = False + res.append(text_type(a)) + res.append(')') + if self.volatile: + res.append(' volatile') + if self.const: + res.append(' const') + if self.refQual: + res.append(' ') + res.append(self.refQual) + if self.exceptionSpec: + res.append(' ') + res.append(text_type(self.exceptionSpec)) + if self.final: + res.append(' final') + if self.override: + res.append(' override') + if self.initializer: + res.append(' = ') + res.append(self.initializer) + return u''.join(res) + + def describe_signature(self, signode, mode, env): + _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) + else: + arg.describe_signature(param, 'markType', env) + paramlist += param + signode += paramlist + + def _add_anno(signode, text): + signode += nodes.Text(' ') + signode += addnodes.desc_annotation(text, text) + + def _add_text(signode, text): + signode += nodes.Text(' ' + text) + + if self.volatile: + _add_anno(signode, 'volatile') + if self.const: + _add_anno(signode, 'const') + if self.refQual: + _add_text(signode, self.refQual) + if self.exceptionSpec: + _add_anno(signode, text_type(self.exceptionSpec)) + if self.final: + _add_anno(signode, 'final') + if self.override: + _add_anno(signode, 'override') + if self.initializer: + _add_text(signode, '= ' + text_type(self.initializer)) + +class ASTDeclSpecs(ASTBase): + def __init__(self, outer, visibility, storage, inline, virtual, explicit, + constexpr, volatile, const, trailing): + self.outer = outer + self.visibility = visibility + self.storage = storage + self.inline = inline + self.virtual = virtual + self.explicit = explicit + self.constexpr = constexpr + self.volatile = volatile + self.const = const + self.trailingTypeSpec = trailing -class RValRefDefExpr(WrappingDefExpr): + @property + def name(self): + return self.trailingTypeSpec.name def get_id(self): - return self.typename.get_id() + u'RR' + res = [] + if self.volatile: + res.append('V') + if self.const: + res.append('K') + res.append(self.trailingTypeSpec.get_id()) + 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): - return u'%s&&' % self.typename + res = [] + if self._print_visibility(): + res.append(self.visibility) + if self.storage: + res.append(self.storage) + if self.inline: + res.append('inline') + if self.virtual: + res.append('virtual') + if self.explicit: + res.append('explicit') + if self.constexpr: + res.append('constexpr') + if self.volatile: + res.append('volatile') + if self.const: + res.append('const') + if self.trailingTypeSpec: + res.append(text_type(self.trailingTypeSpec)) + return u' '.join(res) + def describe_signature(self, signode, mode, env): + _verify_description_mode(mode) + modifiers = [] -class ConstDefExpr(WrappingDefExpr): + def _add(modifiers, text): + if len(modifiers) > 0: + modifiers.append(nodes.Text(' ')) + modifiers.append(addnodes.desc_annotation(text, text)) + + if self._print_visibility(): + _add(modifiers, self.visibility) + if self.storage: + _add(modifiers, self.storage) + if self.inline: + _add(modifiers, 'inline') + if self.virtual: + _add(modifiers, 'virtual') + if self.explicit: + _add(modifiers, 'explicit') + if self.constexpr: + _add(modifiers, 'constexpr') + if self.volatile: + _add(modifiers, 'volatile') + if self.const: + _add(modifiers, 'const') + for m in modifiers: + signode += m + if self.trailingTypeSpec: + if len(modifiers) > 0: + signode += nodes.Text(' ') + self.trailingTypeSpec.describe_signature(signode, mode, env) - def __init__(self, typename, prefix=False): - WrappingDefExpr.__init__(self, typename) - self.prefix = prefix - def get_id(self): - return self.typename.get_id() + u'C' +class ASTPtrOpPtr(ASTBase): + def __init__(self, volatile, const): + self.volatile = volatile + self.const = const def __unicode__(self): - return (self.prefix and u'const %s' or u'%s const') % self.typename + res = ['*'] + if self.volatile: + res.append('volatile ') + if self.const: + res.append('const ') + return u''.join(res) + def get_id(self): + res = ['P'] + if self.volatile: + res.append('V') + if self.const: + res.append('C') + return u''.join(res) -class CastOpDefExpr(PrimaryDefExpr): - def __init__(self, typename): - self.typename = typename +class ASTPtrOpRef(ASTBase): + def __unicode__(self): + return '&' def get_id(self): - return u'castto-%s-operator' % self.typename.get_id() + return 'R' + +class ASTPtrOpParamPack(ASTBase): def __unicode__(self): - return u'operator %s' % self.typename + return '...' + def get_id(self): + return 'Dp' -class ArgumentDefExpr(DefExpr): - def __init__(self, type, name, type_suffixes, default=None): - self.name = name - self.type = type - self.type_suffixes = type_suffixes - self.default = default +class ASTArray(ASTBase): + def __init__(self, size): + self.size = size - def get_name(self): - return self.name.get_name() + def __unicode__(self): + return u''.join(['[', text_type(self.size), ']']) def get_id(self): - buf = [] - buf.append(self.type and self.type.get_id() or 'X') - for suffix in self.type_suffixes: - buf.append(suffix.get_id_suffix()) - return u''.join(buf) + # TODO: this should maybe be done differently + return u'A' + text_type(self.size) + u'_' - def __unicode__(self): - buf = [(u'%s %s' % (self.type or u'', self.name or u'')).strip()] - if self.default is not None: - buf.append('=%s' % self.default) - for suffix in self.type_suffixes: - buf.append(unicode(suffix)) - return u''.join(buf) + def describe_signature(self, signode, mode, env): + _verify_description_mode(mode) + signode += nodes.Text(text_type(self)) -class NamedDefExpr(DefExpr): +class ASTDeclerator(ASTBase): + def __init__(self, ptrOps, declId, suffixOps): + self.ptrOps = ptrOps + self.declId = declId + self.suffixOps = suffixOps - def __init__(self, name, visibility, static): - self.name = name - self.visibility = visibility - self.static = static + @property + def name(self): + return self.declId + + def get_modifiers_id(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() + raise Exception( + "This should only be called on a function: %s" % text_type(self)) + + def get_param_id(self): # only the parameters (if any) + for op in self.suffixOps: + if isinstance(op, ASTParametersQualifiers): + return op.get_param_id() + return '' - def get_name(self): - return self.name.get_name() - - def get_modifiers(self, visibility='public'): - rv = [] - if self.visibility != visibility: - rv.append(self.visibility) - if self.static: - rv.append(u'static') - return rv + def get_ptr_suffix_id(self): # only the ptr ops and array specifiers + return u''.join( + a.get_id() + 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 + else: + return self.declId != 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: + res.append(text_type(op)) + return u''.join(res) + + def describe_signature(self, signode, mode, env): + _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: + op.describe_signature(signode, mode, env) + + +class ASTInitializer(ASTBase): + def __init__(self, value): + self.value = value + + def __unicode__(self): + return u''.join([' = ', text_type(self.value)]) + + def describe_signature(self, signode, mode): + _verify_description_mode(mode) + signode += nodes.Text(text_type(self)) -class TypeObjDefExpr(NamedDefExpr): - def __init__(self, name, visibility, static, typename, type_suffixes): - NamedDefExpr.__init__(self, name, visibility, static) - self.typename = typename - self.type_suffixes = type_suffixes +class ASTType(ASTBase): + def __init__(self, declSpecs, 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(self): - if self.typename is None: - buf = [self.name.get_id()] - else: - buf = [u'%s__%s' % (self.name.get_id(), self.typename.get_id())] - for suffix in self.type_suffixes: - buf.append(suffix.get_id_suffix()) - return u''.join(buf) + res = [] + if self.objectType: # needs the name + res.append(_id_prefix) + if self.objectType == 'function': # also modifiers + res.append(self.decl.get_modifiers_id()) + res.append(self.prefixedName.get_id()) + res.append(self.decl.get_param_id()) + elif self.objectType == 'type': # just the name + res.append(self.prefixedName.get_id()) + else: + print(self.objectType) + assert False + else: # only type encoding + res.append(self.decl.get_ptr_suffix_id()) + res.append(self.declSpecs.get_id()) + res.append(self.decl.get_param_id()) + return u''.join(res) def __unicode__(self): - buf = self.get_modifiers() - if self.typename is None: - buf.append(unicode(self.name)) - else: - buf.extend(map(unicode, (self.typename, self.name))) - buf = [u' '.join(buf)] - for suffix in self.type_suffixes: - buf.append(unicode(suffix)) - return u''.join(buf) + res = [] + declSpecs = text_type(self.declSpecs) + res.append(declSpecs) + if self.decl.require_start_space() and len(declSpecs) > 0: + res.append(u' ') + res.append(text_type(self.decl)) + return u''.join(res) + + def describe_signature(self, signode, mode, env): + _verify_description_mode(mode) + self.declSpecs.describe_signature(signode, 'markType', env) + if (self.decl.require_start_space() and + len(text_type(self.declSpecs)) > 0): + signode += nodes.Text(' ') + self.decl.describe_signature(signode, mode, env) -class MemberObjDefExpr(NamedDefExpr): +class ASTTypeWithInit(ASTBase): + def __init__(self, type, init): + self.objectType = None + self.type = type + self.init = init - def __init__(self, name, visibility, static, typename, type_suffixes, - value): - NamedDefExpr.__init__(self, name, visibility, static) - self.typename = typename - self.type_suffixes = type_suffixes - self.value = value + @property + def name(self): + return self.type.name def get_id(self): - buf = [u'%s__%s' % (self.name.get_id(), self.typename.get_id())] - for suffix in self.type_suffixes: - buf.append(suffix.get_id_suffix()) - return u''.join(buf) + if self.objectType == 'member': + return _id_prefix + self.prefixedName.get_id() + else: + return self.type.get_id() def __unicode__(self): - buf = self.get_modifiers() - buf.extend((unicode(self.typename), unicode(self.name))) - buf = [u' '.join(buf)] - for suffix in self.type_suffixes: - buf.append(unicode(suffix)) - if self.value is not None: - buf.append(u' = %s' % self.value) - return u''.join(buf) - - -class FuncDefExpr(NamedDefExpr): - - def __init__(self, name, visibility, static, explicit, constexpr, rv, - signature, **kwargs): - NamedDefExpr.__init__(self, name, visibility, static) - self.rv = rv - self.signature = signature - self.explicit = explicit - self.constexpr = constexpr - self.const = kwargs.get('const', False) - self.volatile = kwargs.get('volatile', False) - self.noexcept = kwargs.get('noexcept', False) - self.override = kwargs.get('override', False) - self.rvalue_this = kwargs.get('rvalue_this', False) - self.lvalue_this = kwargs.get('lvalue_this', False) - self.pure_virtual = kwargs.get('pure_virtual', False) - self.delete = kwargs.get('delete', False) - self.default = kwargs.get('default', False) + res = [] + res.append(text_type(self.type)) + if self.init: + res.append(text_type(self.init)) + return u''.join(res) - def get_id(self): - return u'%s%s%s%s' % ( - self.name.get_id(), - self.signature and u'__' + - u'.'.join(x.get_id() for x in self.signature) or u'', - self.const and u'C' or u'', - self.constexpr and 'CE' or '' - ) + def describe_signature(self, signode, mode, env): + _verify_description_mode(mode) + self.type.describe_signature(signode, mode, env) + if self.init: + self.init.describe_signature(signode, mode) - def __unicode__(self): - buf = self.get_modifiers() - if self.explicit: - buf.append(u'explicit') - if self.constexpr: - buf.append(u'constexpr') - if self.rv is not None: - buf.append(unicode(self.rv)) - buf.append(u'%s(%s)' % (self.name, u', '.join( - map(unicode, self.signature)))) - if self.const: - buf.append(u'const') - if self.volatile: - buf.append(u'volatile') - if self.rvalue_this: - buf.append(u'&&') - if self.lvalue_this: - buf.append(u'&') - if self.noexcept: - buf.append(u'noexcept') - if self.override: - buf.append(u'override') - if self.pure_virtual: - buf.append(u'= 0') - if self.default: - buf.append(u'= default') - if self.delete: - buf.append(u'= delete') - return u' '.join(buf) +class ASTBaseClass(ASTBase): + def __init__(self, name, visibility): + self.name = name + self.visibility = visibility + + def __unicode__(self): + res = [] + if self.visibility != 'private': + res.append(self.visibility) + res.append(' ') + res.append(text_type(self.name)) + return u''.join(res) + + def describe_signature(self, signode, mode, env): + _verify_description_mode(mode) + if self.visibility != 'private': + signode += addnodes.desc_annotation( + self.visibility, self.visibility) + signode += nodes.Text(' ') + self.name.describe_signature(signode, mode, env) -class ClassDefExpr(NamedDefExpr): - def __init__(self, name, visibility, static, bases): - NamedDefExpr.__init__(self, name, visibility, static) +class ASTClass(ASTBase): + def __init__(self, name, bases): + self.name = name self.bases = bases def get_id(self): - return self.name.get_id() - - def _tostring(self, visibility='public'): - buf = self.get_modifiers(visibility) - buf.append(unicode(self.name)) - if self.bases: - buf.append(u':') - buf.append(u', '.join(base._tostring('private') - for base in self.bases)) - return u' '.join(buf) + return _id_prefix + self.prefixedName.get_id() def __unicode__(self): - return self._tostring('public') + res = [] + res.append(text_type(self.name)) + if len(self.bases) > 0: + res.append(' : ') + first = True + for b in self.bases: + if not first: + res.append(', ') + first = False + res.append(text_type(b)) + return u''.join(res) + + def describe_signature(self, signode, mode, env): + _verify_description_mode(mode) + self.name.describe_signature(signode, mode, env) + if len(self.bases) > 0: + signode += nodes.Text(' : ') + for b in self.bases: + b.describe_signature(signode, mode, env) + signode += nodes.Text(', ') + signode.pop() + class DefinitionParser(object): + # those without signedness and size modifiers + # see http://en.cppreference.com/w/cpp/language/types + _simple_fundemental_types = ( + 'void', 'bool', 'char', 'wchar_t', 'char16_t', 'char32_t', 'int', + 'float', 'double', 'auto' + ) - # mapping of valid type modifiers. if the set is None it means - # the modifier can prefix all types, otherwise only the types - # (actually more keywords) in the set. Also check - # _guess_typename when changing this. - _modifiers = { - 'volatile': None, - 'register': None, - 'mutable': None, - 'const': None, - 'typename': None, - 'struct': None, - 'unsigned': set(('char', 'short', 'int', 'long')), - 'signed': set(('char', 'short', 'int', 'long')), - 'short': set(('int',)), - 'long': set(('int', 'long', 'double')) - } + _prefix_keys = ('class', 'struct', 'union', 'typename') def __init__(self, definition): self.definition = definition.strip() @@ -552,8 +1056,10 @@ class DefinitionParser(object): self._previous_state = (0, None) def fail(self, msg): - raise DefinitionError('Invalid definition: %s [error at %d]\n %s' % - (msg, self.pos, self.definition)) + indicator = '-' * self.pos + '^' + raise DefinitionError( + 'Invalid definition: %s [error at %d]\n %s\n %s' % + (msg, self.pos, self.definition, indicator)) def match(self, regex): match = regex.match(self.definition, self.pos) @@ -602,379 +1108,528 @@ class DefinitionParser(object): if self.last_match is not None: return self.last_match.group() + def read_rest(self): + rv = self.definition[self.pos:] + self.pos = self.end + return rv + + def assert_end(self): + self.skip_ws() + if not self.eof: + self.fail('expected end of definition, got %r' % + self.definition[self.pos:]) + def _parse_operator(self): self.skip_ws() + # adapted from the old code # thank god, a regular operator definition if self.match(_operator_re): - return NameDefExpr('operator' + - _whitespace_re.sub('', self.matched_text)) + return ASTOperatorBuildIn(self.matched_text) # new/delete operator? - for allocop in 'new', 'delete': - if not self.skip_word(allocop): + for op in 'new', 'delete': + if not self.skip_word(op): continue self.skip_ws() if self.skip_string('['): self.skip_ws() if not self.skip_string(']'): - self.fail('expected "]" for ' + allocop) - allocop += '[]' - return NameDefExpr('operator ' + allocop) + self.fail('Expected "]" after "operator ' + op + '["') + op += '[]' + return ASTOperatorBuildIn(op) # oh well, looks like a cast operator definition. # In that case, eat another type. type = self._parse_type() - return CastOpDefExpr(type) - - def _parse_name(self): - return self._parse_name_or_template_arg(False) - - def _parse_name_or_template_arg(self, in_template): - if not self.match(_identifier_re): - if not in_template: - self.fail('expected name') - if not self.match(_template_arg_re): - self.fail('expected name or constant template argument') - return ConstantTemplateArgExpr(self.matched_text.strip()) - identifier = self.matched_text - - # strictly speaking, operators are not regular identifiers - # but because operator is a keyword, it might not be used - # for variable names anyways, so we can safely parse the - # operator here as identifier - if identifier == 'operator': - return self._parse_operator() - - return NameDefExpr(identifier) - - def _guess_typename(self, path): - if not path: - return [], 'int' - # for the long type, we don't want the int in there - if 'long' in path: - path = [x for x in path if x != 'int'] - # remove one long - path.remove('long') - return path, 'long' - if path[-1] in ('int', 'char'): - return path[:-1], path[-1] - return path, 'int' - - def _attach_crefptr(self, expr, is_const=False): - if is_const: - expr = ConstDefExpr(expr, prefix=True) - while 1: - self.skip_ws() - if self.skip_word('const'): - expr = ConstDefExpr(expr) - elif self.skip_string('*'): - expr = PtrDefExpr(expr) - elif self.skip_string('&'): - if self.skip_string('&'): - expr = RValRefDefExpr(expr) - else: - expr = LValRefDefExpr(expr) - else: - return expr + return ASTOperatorType(type) - def _try_parse_type_suffixes(self): - rv = [] - while self.match(_array_def_re): - rv.append(ArrayTypeSuffixDefExpr(self.last_match.group(1))) - self.skip_ws() - return rv - - def _peek_const(self, path): - try: - path.remove('const') - return True - except ValueError: - return False + def _parse_nested_name(self): + names = [] - def _parse_builtin(self, modifiers): - modifier = modifiers[-1] - path = modifiers - following = self._modifiers[modifier] + self.skip_ws() + if self.skip_string('::'): + names.append(u'') while 1: self.skip_ws() + # TODO: parse the "template" keyword if not self.match(_identifier_re): - break + self.fail("expected identifier") identifier = self.matched_text - if identifier in following: - path.append(identifier) - following = self._modifiers[modifier] - assert following + if identifier == 'operator': + op = self._parse_operator() + names.append(op) else: - self.backout() - break - - is_const = self._peek_const(path) - modifiers, typename = self._guess_typename(path) - rv = ModifierDefExpr(NameDefExpr(typename), modifiers) - return self._attach_crefptr(rv, is_const) - - def _parse_type_expr(self, in_template=False): - typename = self._parse_name_or_template_arg(in_template) - self.skip_ws() - if not self.skip_string('<'): - return typename + 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.') + names.append(ASTNestedNameElement(identifier, templateArgs)) - args = [] - while 1: self.skip_ws() - if self.skip_string('>'): + if not self.skip_string('::'): break - if args: - if not self.skip_string(','): - self.fail('"," or ">" in template expected') - self.skip_ws() - args.append(self._parse_type(True)) - return TemplateDefExpr(typename, args) + return ASTNestedName(names) - def _parse_type(self, in_template=False): + def _parse_trailing_type_spec(self): + # fundemental types self.skip_ws() - result = [] - modifiers = [] - - # if there is a leading :: or not, we don't care because we - # treat them exactly the same. Buf *if* there is one, we - # don't have to check for type modifiers - if not self.skip_string('::'): - self.skip_ws() - while self.match(_identifier_re): - modifier = self.matched_text - if modifier in self._modifiers: - following = self._modifiers[modifier] - # if the set is not none, there is a limited set - # of types that might follow. It is technically - # impossible for a template to follow, so what - # we do is go to a different function that just - # eats types - modifiers.append(modifier) - if following is not None: - return self._parse_builtin(modifiers) - self.skip_ws() - else: - self.backout() - break + for t in self._simple_fundemental_types: + if self.skip_word(t): + return ASTTrailingTypeSpecFundamental(t) + # 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'): + elements.append('unsigned') while 1: - self.skip_ws() - if (in_template and self.current_char in ',>') or \ - (result and not self.skip_string('::')) or \ - self.eof: + if self.skip_word_and_ws('short'): + elements.append('short') + elif self.skip_word_and_ws('long'): + elements.append('long') + else: break - result.append(self._parse_type_expr(in_template)) - - if not result: - self.fail('expected type') - if len(result) == 1: - rv = result[0] - else: - rv = PathDefExpr(result) - is_const = self._peek_const(modifiers) - if modifiers: - rv = ModifierDefExpr(rv, modifiers) - return self._attach_crefptr(rv, is_const) + if self.skip_word_and_ws('int'): + elements.append('int') + elif self.skip_word_and_ws('double'): + elements.append('double') + if len(elements) > 0: + return ASTTrailingTypeSpecFundamental(u' '.join(elements)) + + # decltype + self.skip_ws() + if self.skip_word_and_ws('decltype'): + self.fail('"decltype(.)" in trailing_type_spec not implemented') - def _parse_default_expr(self): + # prefixed + prefix = None self.skip_ws() - if self.match(_string_re): - return self.matched_text - paren_stack_depth = 0 - max_pos = len(self.definition) - rv_start = self.pos - while 1: - idx0 = self.definition.find('(', self.pos) - idx1 = self.definition.find(',', self.pos) - idx2 = self.definition.find(')', self.pos) - if idx0 < 0: - idx0 = max_pos - if idx1 < 0: - idx1 = max_pos - if idx2 < 0: - idx2 = max_pos - idx = min(idx0, idx1, idx2) - if idx >= max_pos: - self.fail('unexpected end in default expression') - if idx == idx0: - paren_stack_depth += 1 - elif idx == idx2: - paren_stack_depth -= 1 - if paren_stack_depth < 0: - break - elif paren_stack_depth == 0: + for k in self._prefix_keys: + if self.skip_word_and_ws(k): + prefix = k break - self.pos = idx+1 - rv = self.definition[rv_start:idx] - self.pos = idx - return rv + nestedName = self._parse_nested_name() + return ASTTrailingTypeSpecName(prefix, nestedName) - def _parse_signature(self): + def _parse_parameters_and_qualifiers(self, paramMode): self.skip_ws() if not self.skip_string('('): - self.fail('expected parentheses for function') - + if paramMode == 'function': + self.fail('Expecting "(" in parameters_and_qualifiers.') + else: + return None args = [] - while 1: - self.skip_ws() - if self.eof: - self.fail('missing closing parentheses') - if self.skip_string(')'): - break - if args: - if not self.skip_string(','): - self.fail('expected comma between arguments') + self.skip_ws() + if not self.skip_string(')'): + while 1: self.skip_ws() - - if self.skip_string('...'): - args.append(ArgumentDefExpr(None, '...', [], None)) - if self.skip_string(')'): + if self.skip_string('...'): + args.append(ASTFunctinoParameter(None, True)) + self.skip_ws() + if not self.skip_string(')'): + self.fail('Expected ")" after "..." in ' + 'parameters_and_qualifiers.') break + if paramMode == 'function': + arg = self._parse_type_with_init(named='maybe') else: - self.fail('expected closing parenthesis after ellipses') + arg = self._parse_type() + # TODO: parse default parameters + args.append(ASTFunctinoParameter(arg)) - argname = default = None - argtype = self._parse_type() - self.skip_ws() - type_suffixes = self._try_parse_type_suffixes() - if self.skip_string('='): - default = self._parse_default_expr() - elif self.current_char not in ',)': - argname = self._parse_name() self.skip_ws() - type_suffixes.extend(self._try_parse_type_suffixes()) - if self.skip_string('='): - default = self._parse_default_expr() - if argname is None: - argname = argtype - argtype = None - - args.append(ArgumentDefExpr(argtype, argname, - type_suffixes, default)) + if self.skip_string(','): + continue + elif self.skip_string(')'): + break + else: + self.fail( + 'Expecting "," or ")" in parameters_and_qualifiers, ' + 'got "%s".' % self.current_char) + + if paramMode != 'function': + return ASTParametersQualifiers( + args, None, None, None, None, None, None, None) + self.skip_ws() - attributes = dict( - signature=args, - const=self.skip_word_and_ws('const'), - volatile=self.skip_word_and_ws('volatile'), - noexcept=self.skip_word_and_ws('noexcept'), - override=self.skip_word_and_ws('override'), - pure_virtual=False, - lvalue_this=False, - rvalue_this=False, - delete=False, - default=False) + const = self.skip_word_and_ws('const') + volatile = self.skip_word_and_ws('volatile') + if not const: # the can be permuted + const = self.skip_word_and_ws('const') + refQual = None if self.skip_string('&&'): - attributes['rvalue_this'] = True - if self.skip_string('&'): - attributes['lvalue_this'] = True + refQual = '&&' + if not refQual and self.skip_string('&'): + refQual = '&' + + exceptionSpec = None + override = None + final = None + initializer = None + self.skip_ws() + if self.skip_string('noexcept'): + exceptionSpec = 'noexcept' + self.skip_ws() + if self.skip_string('('): + self.fail('Parameterised "noexcept" not implemented.') - if attributes['lvalue_this'] and attributes['rvalue_this']: - self.fail('rvalue reference for *this specifier must be one of' - '"&&" or "&"') + self.skip_ws() + override = self.skip_word_and_ws('override') + final = self.skip_word_and_ws('final') + if not override: + override = self.skip_word_and_ws( + 'override') # they can be permuted + self.skip_ws() if self.skip_string('='): self.skip_ws() - if self.skip_string('0'): - attributes['pure_virtual'] = True - return attributes - if self.skip_word('NULL') or self.skip_word('nullptr'): - attributes['pure_virtual'] = True - return attributes - if self.skip_word('delete'): - attributes['delete'] = True - return attributes - if self.skip_word('default'): - attributes['default'] = True - return attributes - - self.fail('functions must be defined with ' - 'either 0, NULL, nullptr, default or delete, other' - 'macros are not allowed') - return attributes - - def _parse_visibility_static(self): - visibility = 'public' - if self.match(_visibility_re): - visibility = self.matched_text - static = self.skip_word_and_ws('static') - return visibility, static - - def parse_type(self): - return self._parse_type() + valid = ('0', 'delete', 'default') + for w in valid: + if self.skip_word_and_ws(w): + initializer = w + break + if not initializer: + self.fail( + 'Expected "%s" in initializer-specifier.' + % u'" or "'.join(valid)) - def parse_type_object(self): - visibility, static = self._parse_visibility_static() - typename = self._parse_type() - self.skip_ws() - if not self.eof: - name = self._parse_type() - type_suffixes = self._try_parse_type_suffixes() + return ASTParametersQualifiers( + args, volatile, const, refQual, exceptionSpec, override, final, + initializer) + + def _parse_decl_specs(self, outer, typed=True): + """ + visibility storage-class-specifier function-specifier "constexpr" + "volatile" "const" trailing-type-specifier + + storage-class-specifier -> "static" (only for member_object and + function_object) + + function-specifier -> "inline" | "virtual" | "explicit" (only for + function_object) + + "constexpr" (only for member_object and function_object) + """ + visibility = None + storage = None + inline = None + virtual = None + explicit = None + constexpr = None + volatile = None + const = None + + if outer: + self.skip_ws() + if self.match(_visibility_re): + visibility = self.matched_text + + while 1: # accept any permutation of a subset of some decl-specs + self.skip_ws() + if not storage: + if outer in ('member', 'function'): + if self.skip_word('static'): + storage = 'static' + continue + if outer == 'member': + if self.skip_word('mutable'): + storage = 'mutable' + continue + if outer == 'fuction': + # TODO: maybe in more contexts, missing test cases + if self.skip_word('register'): + storage = 'register' + continue + + if outer == 'function': + # function-specifiers + if not inline: + inline = self.skip_word('inline') + if inline: + continue + if not virtual: + virtual = self.skip_word('virtual') + if virtual: + continue + if not explicit: + explicit = self.skip_word('explicit') + if explicit: + continue + + if not constexpr and outer in ('member', 'function'): + constexpr = self.skip_word("constexpr") + if constexpr: + continue + if not volatile and typed: + volatile = self.skip_word('volatile') + if volatile: + continue + if not const and typed: + const = self.skip_word('const') + if const: + continue + break + + if typed: + trailing = self._parse_trailing_type_spec() else: - name = typename - typename = None - type_suffixes = [] - return TypeObjDefExpr(name, visibility, static, typename, type_suffixes) + trailing = None + return ASTDeclSpecs( + outer, visibility, storage, inline, virtual, explicit, constexpr, + volatile, const, trailing) + + def _parse_declerator(self, named, paramMode=None, typed=True): + if paramMode: + if not paramMode 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 - def parse_member_object(self): - visibility, static = self._parse_visibility_static() - typename = self._parse_type() - name = self._parse_type() - type_suffixes = self._try_parse_type_suffixes() - self.skip_ws() - if self.skip_string('='): - value = self.read_rest().strip() + if named == 'maybe': + try: + declId = self._parse_nested_name() + except DefinitionError: + declId = None + elif named: + declId = self._parse_nested_name() else: - value = None - return MemberObjDefExpr(name, visibility, static, typename, - type_suffixes, value) + declId = None + + suffixOpts = [] + 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())) + continue + if paramMode: + paramQual = self._parse_parameters_and_qualifiers(paramMode) + if paramQual: + suffixOpts.append(paramQual) + break - def parse_function(self): - visibility, static = self._parse_visibility_static() - explicit = self.skip_word_and_ws('explicit') - constexpr = self.skip_word_and_ws('constexpr') + return ASTDeclerator(ptrOps, declId, suffixOpts) - rv = self._parse_type() + def _parse_initializer(self, outer=None): self.skip_ws() - # some things just don't have return values - if self.current_char == '(': - name = rv - rv = None + # TODO: support paren and brace initialization for memberObject + if not self.skip_string('='): + return None else: - name = self._parse_type() - return FuncDefExpr(name, visibility, static, explicit, constexpr, rv, - **self._parse_signature()) + if outer == 'member': + value = self.read_rest().strip() + return ASTInitializer(value) + elif outer == 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) + else: + self.fail( + "Internal error, initializer for outer '%s' not " + "implemented." % outer) - def parse_class(self): - visibility, static = self._parse_visibility_static() - name = self._parse_type() + def _parse_type(self, outer=None, named=False, allowParams=False): + """ + named=False|'maybe'|True: 'maybe' is e.g., for function objects which + doesn't need to name the arguments + """ + if outer: # always named + if not outer in ('type', 'member', 'function'): + raise Exception('Internal error, unknown outer "%s".' % outer) + assert not 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 + startPos = self.pos + # first try without the type + try: + declSpecs = self._parse_decl_specs(outer=outer, typed=False) + decl = self._parse_declerator(named=True, paramMode=outer, + typed=False) + self.assert_end() + except DefinitionError as exUntyped: + self.pos = startPos + try: + declSpecs = self._parse_decl_specs(outer=outer) + decl = self._parse_declerator(named=True, paramMode=outer) + except DefinitionError as exTyped: + 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)) + else: + # do it again to get the proper traceback (how do you + # relieable save a traceback when an exception is + # constructed?) + self.pos = startPos + declSpecs = self._parse_decl_specs(outer=outer) + decl = self._parse_declerator(named=True, + paramMode=outer) + else: + if outer: + named = True + allowParams = True + if allowParams: + paramMode = 'type' + else: + paramMode = None + 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): + if outer: + assert outer in ('type', 'member', 'function') + type = self._parse_type(outer=outer, named=named) + init = self._parse_initializer(outer=outer) + return ASTTypeWithInit(type, init) + + def _parse_class(self): + name = self._parse_nested_name() bases = [] + self.skip_ws() if self.skip_string(':'): - self.skip_ws() while 1: - access = 'private' + self.skip_ws() + visibility = 'private' if self.match(_visibility_re): - access = self.matched_text - base = self._parse_type() - bases.append(ClassDefExpr(base, access, False, [])) + visibility = self.matched_text + baseName = self._parse_nested_name() + bases.append(ASTBaseClass(baseName, visibility)) + self.skip_ws() if self.skip_string(','): - self.skip_ws() + continue else: break - return ClassDefExpr(name, visibility, static, bases) + return ASTClass(name, bases) - def read_rest(self): - rv = self.definition[self.pos:] - self.pos = self.end - return rv + def parse_type_object(self): + res = self._parse_type(outer='type') + res.objectType = 'type' + return res - def assert_end(self): - self.skip_ws() - if not self.eof: - self.fail('expected end of definition, got %r' % - self.definition[self.pos:]) + def parse_member_object(self): + res = self._parse_type_with_init(outer='member') + res.objectType = 'member' + return res + + def parse_function_object(self): + res = self._parse_type(outer='function') + res.objectType = 'function' + return res + + def parse_class_object(self): + res = self._parse_class() + res.objectType = 'class' + return res + + def parse_namespace_object(self): + res = self._parse_nested_name() + res.objectType = 'namespace' + return res + + def parse_xref_object(self): + res = self._parse_nested_name() + res.objectType = 'xref' + return res class CPPObject(ObjectDescription): @@ -991,214 +1646,120 @@ class CPPObject(ObjectDescription): names=('returns', 'return')), ] - def attach_name(self, node, name): - owner, name = name.split_owner() - varname = unicode(name) - if owner is not None: - owner = unicode(owner) + '::' - node += addnodes.desc_addname(owner, owner) - node += addnodes.desc_name(varname, varname) - - def attach_type_suffixes(self, node, suffixes): - for suffix in suffixes: - node += nodes.Text(unicode(suffix)) - - def attach_type(self, node, type): - # XXX: link to c? - text = unicode(type) - pnode = addnodes.pending_xref( - '', refdomain='cpp', reftype='type', - reftarget=text, modname=None, classname=None) - pnode['cpp:parent'] = self.env.temp_data.get('cpp:parent') - pnode += nodes.Text(text) - node += pnode - - def attach_modifiers(self, node, obj, visibility='public'): - if obj.visibility != visibility: - node += addnodes.desc_annotation(obj.visibility, - obj.visibility) - node += nodes.Text(' ') - if obj.static: - node += addnodes.desc_annotation('static', 'static') - node += nodes.Text(' ') - if getattr(obj, 'constexpr', False): - node += addnodes.desc_annotation('constexpr', 'constexpr') - node += nodes.Text(' ') - - def add_target_and_index(self, sigobj, sig, signode): - theid = sigobj.get_id() - name = unicode(sigobj.name) + def add_target_and_index(self, ast, sig, signode): + theid = ast.get_id() + name = text_type(ast.prefixedName) if theid not in self.state.document.ids: - signode['names'].append(theid) + # the name is not unique, the first one will win + objects = self.env.domaindata['cpp']['objects'] + if not name in objects: + signode['names'].append(name) signode['ids'].append(theid) signode['first'] = (not self.names) self.state.document.note_explicit_target(signode) - - self.env.domaindata['cpp']['objects'].setdefault(name, - (self.env.docname, self.objtype, theid)) + if not name in objects: + objects.setdefault(name, + (self.env.docname, ast.objectType, theid)) + # 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.objectType, theid)) + self.env.temp_data['cpp:lastname'] = ast.prefixedName indextext = self.get_index_text(name) - if indextext: - self.indexnode['entries'].append(('single', indextext, theid, '')) - - def before_content(self): - lastname = self.names and self.names[-1] - if lastname and not self.env.temp_data.get('cpp:parent'): - assert isinstance(lastname, NamedDefExpr) - self.env.temp_data['cpp:parent'] = lastname.name - self.parentname_set = True - else: - self.parentname_set = False - - def after_content(self): - if self.parentname_set: - self.env.temp_data['cpp:parent'] = None + 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, arg): + def describe_signature(self, signode, ast): raise NotImplementedError() def handle_signature(self, sig, signode): parser = DefinitionParser(sig) try: - rv = self.parse_definition(parser) + ast = self.parse_definition(parser) parser.assert_end() - except DefinitionError, e: - self.state_machine.reporter.warning(e.description, line=self.lineno) + except DefinitionError as e: + self.state_machine.reporter.warning(e.description, + line=self.lineno) raise ValueError - self.describe_signature(signode, rv) + self.describe_signature(signode, ast) parent = self.env.temp_data.get('cpp:parent') - if parent is not None: - rv = rv.clone() - rv.name = rv.name.prefix(parent) - return rv - - -class CPPClassObject(CPPObject): - - def get_index_text(self, name): - return _('%s (C++ class)') % name - - def parse_definition(self, parser): - return parser.parse_class() - - def describe_signature(self, signode, cls): - self.attach_modifiers(signode, cls) - signode += addnodes.desc_annotation('class ', 'class ') - self.attach_name(signode, cls.name) - if cls.bases: - signode += nodes.Text(' : ') - for base in cls.bases: - self.attach_modifiers(signode, base, 'private') - signode += nodes.emphasis(unicode(base.name), - unicode(base.name)) - signode += nodes.Text(', ') - signode.pop() # remove the trailing comma + if parent and len(parent) > 0: + ast = ast.clone() + ast.prefixedName = ast.name.prefix_nested_name(parent[-1]) + else: + ast.prefixedName = ast.name + return ast class CPPTypeObject(CPPObject): - def get_index_text(self, name): - if self.objtype == 'type': - return _('%s (C++ type)') % name - return '' + return _('%s (C++ type)') % name def parse_definition(self, parser): return parser.parse_type_object() - def describe_signature(self, signode, obj): - self.attach_modifiers(signode, obj) + def describe_signature(self, signode, ast): signode += addnodes.desc_annotation('type ', 'type ') - if obj.typename is not None: - self.attach_type(signode, obj.typename) - signode += nodes.Text(' ') - self.attach_name(signode, obj.name) - self.attach_type_suffixes(signode, obj.type_suffixes) + ast.describe_signature(signode, 'lastIsName', self.env) class CPPMemberObject(CPPObject): - def get_index_text(self, name): - if self.objtype == 'member': - return _('%s (C++ member)') % name - return '' + return _('%s (C++ member)') % name def parse_definition(self, parser): return parser.parse_member_object() - def describe_signature(self, signode, obj): - self.attach_modifiers(signode, obj) - self.attach_type(signode, obj.typename) - signode += nodes.Text(' ') - self.attach_name(signode, obj.name) - self.attach_type_suffixes(signode, obj.type_suffixes) - if obj.value is not None: - signode += nodes.Text(u' = ' + obj.value) + def describe_signature(self, signode, ast): + ast.describe_signature(signode, 'lastIsName', self.env) class CPPFunctionObject(CPPObject): + def get_index_text(self, name): + return _('%s (C++ function)') % name - def attach_function(self, node, func): - owner, name = func.name.split_owner() - if owner is not None: - owner = unicode(owner) + '::' - node += addnodes.desc_addname(owner, owner) - - # cast operator is special. in this case the return value - # is reversed. - if isinstance(name, CastOpDefExpr): - node += addnodes.desc_name('operator', 'operator') - node += nodes.Text(u' ') - self.attach_type(node, name.typename) - else: - funcname = unicode(name) - node += addnodes.desc_name(funcname, funcname) + def parse_definition(self, parser): + return parser.parse_function_object() - paramlist = addnodes.desc_parameterlist() - for arg in func.signature: - param = addnodes.desc_parameter('', '', noemph=True) - if arg.type is not None: - self.attach_type(param, arg.type) - param += nodes.Text(u' ') - param += nodes.emphasis(unicode(arg.name), unicode(arg.name)) - self.attach_type_suffixes(param, arg.type_suffixes) - if arg.default is not None: - def_ = u'=' + unicode(arg.default) - param += nodes.emphasis(def_, def_) - paramlist += param + def describe_signature(self, signode, ast): + ast.describe_signature(signode, 'lastIsName', self.env) - node += paramlist - if func.const: - node += addnodes.desc_addname(' const', ' const') - if func.noexcept: - node += addnodes.desc_addname(' noexcept', ' noexcept') - if func.pure_virtual: - node += addnodes.desc_addname(' = 0', ' = 0') +class CPPClassObject(CPPObject): def get_index_text(self, name): - return _('%s (C++ function)') % name + return _('%s (C++ class)') % name + + def before_content(self): + lastname = self.env.temp_data['cpp:lastname'] + assert lastname + if 'cpp:parent' in self.env.temp_data: + self.env.temp_data['cpp:parent'].append(lastname) + else: + self.env.temp_data['cpp:parent'] = [lastname] + + def after_content(self): + self.env.temp_data['cpp:parent'].pop() def parse_definition(self, parser): - return parser.parse_function() + return parser.parse_class_object() - def describe_signature(self, signode, func): - self.attach_modifiers(signode, func) - if func.explicit: - signode += addnodes.desc_annotation('explicit', 'explicit') - signode += nodes.Text(' ') - # return value is None for things with a reverse return value - # such as casting operator definitions or constructors - # and destructors. - if func.rv is not None: - self.attach_type(signode, func.rv) - signode += nodes.Text(u' ') - self.attach_function(signode, func) + def describe_signature(self, signode, ast): + signode += addnodes.desc_annotation('class ', 'class ') + ast.describe_signature(signode, 'lastIsName', self.env) -class CPPCurrentNamespace(Directive): +class CPPNamespaceObject(Directive): """ This directive is just to tell Sphinx that we're documenting stuff in namespace foo. @@ -1213,26 +1774,27 @@ class CPPCurrentNamespace(Directive): def run(self): env = self.state.document.settings.env if self.arguments[0].strip() in ('NULL', '0', 'nullptr'): - env.temp_data['cpp:prefix'] = None + env.temp_data['cpp:parent'] = [] else: parser = DefinitionParser(self.arguments[0]) try: - prefix = parser.parse_type() + prefix = parser.parse_namespace_object() parser.assert_end() - except DefinitionError, e: + except DefinitionError as e: self.state_machine.reporter.warning(e.description, line=self.lineno) else: - env.temp_data['cpp:prefix'] = prefix + env.temp_data['cpp:parent'] = [prefix] return [] class CPPXRefRole(XRefRole): - def process_link(self, env, refnode, has_explicit_title, title, target): - refnode['cpp:parent'] = env.temp_data.get('cpp:parent') + parent = env.temp_data.get('cpp:parent') + if parent: + refnode['cpp:parent'] = parent[:] if not has_explicit_title: - target = target.lstrip('~') # only has a meaning for the title + target = target.lstrip('~') # only has a meaning for the title # if the first character is a tilde, don't display the module/class # parts of the contents if title[:1] == '~': @@ -1248,70 +1810,69 @@ class CPPDomain(Domain): name = 'cpp' label = 'C++' object_types = { - 'class': ObjType(l_('class'), 'class'), + 'class': ObjType(l_('class'), 'class'), 'function': ObjType(l_('function'), 'func'), - 'member': ObjType(l_('member'), 'member'), - 'type': ObjType(l_('type'), 'type') + 'member': ObjType(l_('member'), 'member'), + 'type': ObjType(l_('type'), 'type') } directives = { - 'class': CPPClassObject, - 'function': CPPFunctionObject, - 'member': CPPMemberObject, - 'type': CPPTypeObject, - 'namespace': CPPCurrentNamespace + 'class': CPPClassObject, + 'function': CPPFunctionObject, + 'member': CPPMemberObject, + 'type': CPPTypeObject, + 'namespace': CPPNamespaceObject } roles = { - 'class': CPPXRefRole(), - 'func' : CPPXRefRole(fix_parens=True), + 'class': CPPXRefRole(), + 'func': CPPXRefRole(fix_parens=True), 'member': CPPXRefRole(), - 'type': CPPXRefRole() + 'type': CPPXRefRole() } initial_data = { - 'objects': {}, # fullname -> docname, objtype + 'objects': {}, # prefixedName -> (docname, objectType, id) } def clear_doc(self, docname): - for fullname, (fn, _, _) in self.data['objects'].items(): - if fn == docname: + for fullname, data in list(self.data['objects'].items()): + if data[0] == docname: del self.data['objects'][fullname] def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): - def _create_refnode(expr): - name = unicode(expr) + def _create_refnode(nameAst): + name = text_type(nameAst) if name not in self.data['objects']: - return None - obj = self.data['objects'][name] - if obj[1] not in self.objtypes_for_role(typ): - return None - return make_refnode(builder, fromdocname, obj[0], obj[2], - contnode, name) + # try dropping the last template + name = nameAst.get_name_no_last_template() + if name not in self.data['objects']: + return None + docname, objectType, id = self.data['objects'][name] + return make_refnode(builder, fromdocname, docname, id, contnode, + name) parser = DefinitionParser(target) try: - expr = parser.parse_type().get_name() + nameAst = parser.parse_xref_object().name parser.skip_ws() - if not parser.eof or expr is None: + if not parser.eof: raise DefinitionError('') except DefinitionError: env.warn_node('unparseable C++ definition: %r' % target, node) return None - parent = node.get('cpp:parent', None) - - rv = _create_refnode(expr) - if rv is not None or parent is None: - return rv - parent = parent.get_name() - - rv = _create_refnode(expr.prefix(parent)) - if rv is not None: - return rv + # try as is the name is fully qualified + refNode = _create_refnode(nameAst) + if refNode: + return refNode - parent, name = parent.split_owner() - return _create_refnode(expr.prefix(parent)) + # 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])) + else: + return None def get_objects(self): - for refname, (docname, type, theid) in self.data['objects'].iteritems(): - yield (refname, refname, type, docname, refname, 1) + for refname, (docname, type, theid) in iteritems(self.data['objects']): + yield (refname, refname, type, docname, refname, 1)
\ No newline at end of file |