From b293be1fc951605c6800838663f3b73593ab6934 Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Fri, 18 Jul 2014 18:09:25 +0200 Subject: [PATCH 01/13] C++ domain, add support for virtual functions. --- sphinx/domains/cpp.py | 19 ++++++++++--------- tests/test_cpp_domain.py | 4 ++++ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index c8505dc8f..c608ad54e 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -448,11 +448,12 @@ class MemberObjDefExpr(NamedDefExpr): class FuncDefExpr(NamedDefExpr): - def __init__(self, name, visibility, static, explicit, constexpr, rv, + def __init__(self, name, visibility, static, virtual, explicit, constexpr, rv, signature, **kwargs): NamedDefExpr.__init__(self, name, visibility, static) self.rv = rv self.signature = signature + self.virtual = virtual self.explicit = explicit self.constexpr = constexpr self.const = kwargs.get('const', False) @@ -461,7 +462,7 @@ class FuncDefExpr(NamedDefExpr): 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.pure = kwargs.get('pure', False) self.delete = kwargs.get('delete', False) self.default = kwargs.get('default', False) @@ -476,6 +477,8 @@ class FuncDefExpr(NamedDefExpr): def __unicode__(self): buf = self.get_modifiers() + if self.virtual: + buf.append(u'virtual') if self.explicit: buf.append(u'explicit') if self.constexpr: @@ -496,7 +499,7 @@ class FuncDefExpr(NamedDefExpr): buf.append(u'noexcept') if self.override: buf.append(u'override') - if self.pure_virtual: + if self.pure: buf.append(u'= 0') if self.default: buf.append(u'= default') @@ -881,11 +884,8 @@ class DefinitionParser(object): 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 + if self.skip_string('0') or self.skip_word('NULL') or self.skip_word('nullptr'): + attributes['pure'] = True return attributes if self.skip_word('delete'): attributes['delete'] = True @@ -937,6 +937,7 @@ class DefinitionParser(object): def parse_function(self): visibility, static = self._parse_visibility_static() + virtual = self.skip_word_and_ws('virtual') explicit = self.skip_word_and_ws('explicit') constexpr = self.skip_word_and_ws('constexpr') @@ -948,7 +949,7 @@ class DefinitionParser(object): rv = None else: name = self._parse_type() - return FuncDefExpr(name, visibility, static, explicit, constexpr, rv, + return FuncDefExpr(name, visibility, static, virtual, explicit, constexpr, rv, **self._parse_signature()) def parse_class(self): diff --git a/tests/test_cpp_domain.py b/tests/test_cpp_domain.py index 57dc74a5a..15bb29af6 100644 --- a/tests/test_cpp_domain.py +++ b/tests/test_cpp_domain.py @@ -129,6 +129,10 @@ def test_type_definitions(): x = 'int foo(const A&... a)' assert text_type(parse('function', x)) == x + x = 'virtual void f()' + assert text_type(parse('function', x)) == x + + def test_bases(): x = 'A' assert text_type(parse('class', x)) == x From 55eb97e9c519a655444108e15e82ca89289e1c8d Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Sat, 19 Jul 2014 18:08:46 +0200 Subject: [PATCH 02/13] C++, actually add 'virtual' to output, and fix missing rename. --- sphinx/domains/cpp.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index c608ad54e..49c843d68 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -867,7 +867,7 @@ class DefinitionParser(object): volatile=self.skip_word_and_ws('volatile'), noexcept=self.skip_word_and_ws('noexcept'), override=self.skip_word_and_ws('override'), - pure_virtual=False, + pure=False, lvalue_this=False, rvalue_this=False, delete=False, @@ -1180,7 +1180,7 @@ class CPPFunctionObject(CPPObject): node += addnodes.desc_addname(' const', ' const') if func.noexcept: node += addnodes.desc_addname(' noexcept', ' noexcept') - if func.pure_virtual: + if func.pure: node += addnodes.desc_addname(' = 0', ' = 0') def get_index_text(self, name): @@ -1194,6 +1194,9 @@ class CPPFunctionObject(CPPObject): if func.explicit: signode += addnodes.desc_annotation('explicit', 'explicit') signode += nodes.Text(' ') + if func.virtual: + signode += addnodes.desc_annotation('virtual', 'virtual') + signode += nodes.Text(' ') # return value is None for things with a reverse return value # such as casting operator definitions or constructors # and destructors. From 967ca06545b4432c6ec3e6a13aed9ea03d3874cc Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Thu, 24 Jul 2014 01:40:48 +0200 Subject: [PATCH 03/13] Revamp of C++ domain. --- sphinx/domains/c.py | 8 +- sphinx/domains/cpp.py | 2020 ++++++++++++++++-------------- sphinx/ext/napoleon/docstring.py | 14 +- tests/test_cpp_domain.py | 226 ++-- 4 files changed, 1191 insertions(+), 1077 deletions(-) diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index 1c34207f8..90351ee71 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -66,7 +66,7 @@ class CObject(ObjectDescription): 'struct', '_Bool', )) - def _parse_type(self, node, ctype): + def _parse_type_old(self, node, ctype): # add cross-ref nodes for all words for part in [_f for _f in wsplit_re.split(ctype) if _f]: tnode = nodes.Text(part, part) @@ -91,7 +91,7 @@ class CObject(ObjectDescription): rettype, name, arglist, const = m.groups() signode += addnodes.desc_type('', '') - self._parse_type(signode[-1], rettype) + self._parse_type_old(signode[-1], rettype) try: classname, funcname = name.split('::', 1) classname += '::' @@ -130,9 +130,9 @@ class CObject(ObjectDescription): ctype, argname = arg.rsplit(' ', 1) except ValueError: # no argument name given, only the type - self._parse_type(param, arg) + self._parse_type_old(param, arg) else: - self._parse_type(param, ctype) + self._parse_type_old(param, ctype) # separate by non-breaking space in the output param += nodes.emphasis(' '+argname, u'\xa0'+argname) paramlist += param diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 49c843d68..02ba872f7 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -7,6 +7,116 @@ :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. + + See http://www.nongnu.org/hcb/ + + 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 @@ -113,8 +223,7 @@ class DefinitionError(UnicodeMixin, Exception): def __unicode__(self): return self.description - -class DefExpr(UnicodeMixin): +class ASTBase(UnicodeMixin): def __eq__(self, other): if type(self) is not type(other): @@ -138,7 +247,7 @@ class DefExpr(UnicodeMixin): def get_id(self): """Return the id for the node.""" - return u'' + raise NotImplementedError(repr(self)) def get_name(self): """Return the name. @@ -146,7 +255,7 @@ class DefExpr(UnicodeMixin): Returns either `None` or a node with a name you might call :meth:`split_owner` on. """ - return None + raise NotImplementedError(repr(self)) def split_owner(self): """Nodes returned by :meth:`get_name` can split off their @@ -154,399 +263,533 @@ class DefExpr(UnicodeMixin): 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): """Prefix a name node (a node returned by :meth:`get_name`).""" - raise NotImplementedError() + raise NotImplementedError(repr(self)) def __unicode__(self): - raise NotImplementedError() + raise NotImplementedError(repr(self)) def __repr__(self): return '<%s %s>' % (self.__class__.__name__, self) +def _verify_parsing_context(context): + assert context in {'typeObject', 'functionObject', 'memberObject', 'classObject', 'namespaceObject', 'xrefObject', 'abstractDecl', 'functionObjectArgument', 'baseClass'} + +def _verify_description_mod(mode): + assert mode in {'lastIsName', 'allIsName', 'noneIsName', 'markType'} -class PrimaryDefExpr(DefExpr): +class ASTOperatorBuildIn(ASTBase): + + def __init__(self, op): + self.op = op + + def __unicode__(self): + return u''.join(['operator', self.op]) + + def describe_signature(self, signode, mode): + _verify_description_mod(mode) + identifier = text_type(self) + signode += addnodes.desc_addname(identifier, identifier) + +class ASTOperatorType(ASTBase): + + def __init__(self, type): + self.type = type + + def __unicode__(self): + return u''.join(['operator ', text_type(self.type)]) + + def describe_signature(self, signode, mode): + _verify_description_mod(mode) + identifier = text_type(self) + signode += addnodes.desc_addname(identifier, identifier) + +class ASTTemplateArgConstant(ASTBase): + + def __init__(self, value): + self.value = value + + def __unicode__(self): + return text_type(self.value) + + def describe_signature(self, signode, mode, env): + _verify_description_mod(mode) + signode += nodes.Text(text_type(self)) - def get_name(self): +class ASTNestedNameElement(ASTBase): + + def __init__(self, identifier, templateArgs): + self.identifier = identifier + self.templateArgs = templateArgs + + def __unicode__(self): + 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 describe_signature(self, signode, mode, env, prefix): + _verify_description_mod(mode) + if mode == 'markType': + identifier = text_type(self.identifier) + targetText = prefix + identifier + 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(identifier) + signode += pnode + 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, mode, env) + signode += nodes.Text('>') + else: + raise Exception('Unknown description mode: %s' % mode) + +class ASTNestedName(ASTBase): + + def __init__(self, names): + """Use an empty string as the first name if it should start with '::' """ + self.names = names + + @property + def 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]) - - -class NameDefExpr(PrimaryDefExpr): + if self.names[0] == '': + raise DefinitionError('Can not prefix nested name rooted in outer-most namespace.') + names = [prefix] + names.extend(self.names) + return ASTNestedName(names) + + def __unicode__(self): + return u'::'.join([text_type(n) for n in self.names]) + + def describe_signature(self, signode, mode, env): + _verify_description_mod(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) + signode += addnodes.desc_name(name, name) + elif mode == 'allIsName': + name = text_type(self) + signode += addnodes.desc_name(name, name) + elif mode == 'noneIsName': + name = text_type(self) + signode += nodes.Text(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 ASTTrailingTypeSpecFundemental(ASTBase): + def __init__(self, name): self.name = name - - def get_id(self): - name = _id_shortwords.get(self.name) - if name is not None: - return name - return self.name.replace(u' ', u'-') - + def __unicode__(self): - return text_type(self.name) - - -class PathDefExpr(PrimaryDefExpr): - - def __init__(self, parts): - self.path = parts - - def get_id(self): - rv = u'::'.join(x.get_id() for x in self.path) - return _id_shortwords.get(rv, rv) - - 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) - - def __unicode__(self): - return u'::'.join(map(text_type, self.path)) - - -class ArrayTypeSuffixDefExpr(UnicodeMixin): - - def __init__(self, size_hint=None): - self.size_hint = size_hint - - def get_id_suffix(self): - return 'A' - - def __unicode__(self): - return u'[%s]' % ( - self.size_hint is not None and text_type(self.size_hint) or u'', - ) - - -class TemplateDefExpr(PrimaryDefExpr): - - 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) - - def get_id(self): - return u'%s:%s:' % (self.typename.get_id(), - u'.'.join(x.get_id() for x in self.args)) - - def __unicode__(self): - return u'%s<%s>' % (self.typename, u', '.join(map(text_type, self.args))) - - -class ConstantTemplateArgExpr(PrimaryDefExpr): - - def __init__(self, arg): - self.arg = arg - - def get_id(self): - return self.arg.replace(u' ', u'-') - - def __unicode__(self): - return text_type(self.arg) - - -class WrappingDefExpr(DefExpr): - - def __init__(self, typename): - self.typename = typename - - def get_name(self): - return self.typename.get_name() - - -class ModifierDefExpr(WrappingDefExpr): - - def __init__(self, typename, modifiers): - WrappingDefExpr.__init__(self, typename) - self.modifiers = modifiers - - def get_id(self): - pieces = [_id_shortwords.get(text_type(x), text_type(x)) - for x in self.modifiers] - pieces.append(self.typename.get_id()) - return u'-'.join(pieces) - - def __unicode__(self): - return u' '.join(map(text_type, list(self.modifiers) + [self.typename])) - - -class PtrDefExpr(WrappingDefExpr): - - def get_id(self): - return self.typename.get_id() + u'P' - - def __unicode__(self): - return u'%s*' % self.typename - - -class LValRefDefExpr(WrappingDefExpr): - - def get_id(self): - return self.typename.get_id() + u'R' - - def __unicode__(self): - return u'%s&' % self.typename - - -class RValRefDefExpr(WrappingDefExpr): - - def get_id(self): - return self.typename.get_id() + u'RR' - - def __unicode__(self): - return u'%s&&' % self.typename - - -class ConstDefExpr(WrappingDefExpr): - - def __init__(self, typename, prefix=False): - WrappingDefExpr.__init__(self, typename) + return self.name + + def describe_signature(self, signode, mode, env): + signode += nodes.Text(text_type(self.name)) + +class ASTTrailingTypeSpecName(ASTBase): + + def __init__(self, prefix, nestedName): self.prefix = prefix - - def get_id(self): - return self.typename.get_id() + u'C' - + self.nestedName = nestedName + + @property + def name(self): + return self.nestedName + def __unicode__(self): - return (self.prefix and u'const %s' or u'%s const') % self.typename - - -class CastOpDefExpr(PrimaryDefExpr): - - def __init__(self, typename): - self.typename = typename - - def get_id(self): - return u'castto-%s-operator' % self.typename.get_id() + 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 ASTFunctinoParameter(ASTBase): + + def __init__(self, arg, ellipsis=False): + self.arg = arg + self.ellipsis = ellipsis + + def get_type_id(self): + if self.ellipsis: return '...' + else: return self.arg.get_type_id() + def __unicode__(self): - return u'operator %s' % self.typename - - -class ArgumentDefExpr(DefExpr): - - def __init__(self, type, name, type_suffixes, default=None, param_pack=False): - self.name = name - self.type = type - self.type_suffixes = type_suffixes - self.default = default - self.param_pack = param_pack - - def get_name(self): - return self.name.get_name() - - 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) + if self.ellipsis: return '...' + else: return text_type(self.arg) + + def describe_signature(self, signode, mode, env): + _verify_description_mod(mode) + if self.ellipsis: signode += nodes.emphasis('...') + else: self.arg.describe_signature(signode, mode, env) +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_param_id(self): + # return an id encoding whatever participates in overload resolution + args = [] + for a in self.args: + args.append(a.get_type_id()) + res = [] + res.append(u'_'.join(args)) + if self.const: res.append('C') + else: res.append('') + if self.refQual: res.append(self.refQual) + else: res.append('') + return u'__'.join(res) + '___' + def __unicode__(self): - buf = [(u'%s%s %s' % ( - self.type or u'', - '...' if self.param_pack else 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(text_type(suffix)) - return u''.join(buf) + 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_mod(mode) + paramlist = addnodes.desc_parameterlist() + for arg in self.args: + param = addnodes.desc_parameter('', '', noemph=True) + 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 NamedDefExpr(DefExpr): - - def __init__(self, name, visibility, static): - self.name = name +class ASTDeclSpecs(ASTBase): + + def __init__(self, context, visibility, storage, inline, virtual, explicit, constexpr, volatile, const, trailing): + self.context = context self.visibility = visibility - self.static = static - - 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 - - -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 - - 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) - - def __unicode__(self): - buf = self.get_modifiers() - if self.typename is None: - buf.append(text_type(self.name)) - else: - buf.extend(map(text_type, (self.typename, self.name))) - buf = [u' '.join(buf)] - for suffix in self.type_suffixes: - buf.append(text_type(suffix)) - return u''.join(buf) - - -class MemberObjDefExpr(NamedDefExpr): - - 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 - - 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) - - def __unicode__(self): - buf = self.get_modifiers() - buf.extend((text_type(self.typename), text_type(self.name))) - buf = [u' '.join(buf)] - for suffix in self.type_suffixes: - buf.append(text_type(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, virtual, explicit, constexpr, rv, - signature, **kwargs): - NamedDefExpr.__init__(self, name, visibility, static) - self.rv = rv - self.signature = signature + self.storage = storage + self.inline = inline self.virtual = virtual 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 = kwargs.get('pure', False) - self.delete = kwargs.get('delete', False) - self.default = kwargs.get('default', False) - - 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 '' - ) - + self.volatile = volatile + self.const = const + self.trailingTypeSpec = trailing + + @property + def name(self): + return self.trailingTypeSpec.name + + def get_type_id(self): + return text_type(self) + + def _print_visibility(self): + return self.visibility and not (self.context in {'typeObject', 'memberObject', 'functionObject'} and self.visibility == 'public') + def __unicode__(self): - buf = self.get_modifiers() - if self.virtual: - buf.append(u'virtual') - if self.explicit: - buf.append(u'explicit') - if self.constexpr: - buf.append(u'constexpr') - if self.rv is not None: - buf.append(text_type(self.rv)) - buf.append(u'%s(%s)' % (self.name, u', '.join( - map(text_type, 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: - buf.append(u'= 0') - if self.default: - buf.append(u'= default') - if self.delete: - buf.append(u'= delete') - return u' '.join(buf) + res = [] + if self._print_visibility(): + res.append(self.visibility) + res.append(' ') + if self.storage: + res.append(self.storage) + res.append(' ') + 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 ') + res.append(text_type(self.trailingTypeSpec)) + return u''.join(res) + + def describe_signature(self, signode, mode, env): + _verify_description_mod(mode) + def _add(signode, text): + signode += addnodes.desc_annotation(text, text) + signode += nodes.Text(' ') + if self._print_visibility(): _add(signode, self.visibility) + if self.storage: _add(signode, self.storage) + if self.inline: _add(signode, 'inline') + if self.virtual: _add(signode, 'virtual') + if self.explicit: _add(signode, 'explicit') + if self.constexpr: _add(signode, 'constexpr') + if self.volatile: _add(signode, 'volatile') + if self.const: _add(signode, 'const') + self.trailingTypeSpec.describe_signature(signode, mode, env) + +class ASTArray(ASTBase): + + def __init__(self, size): + self.size = size + + def __unicode__(self): + return u''.join(['[', text_type(self.size), ']']) + + def describe_signature(self, signode, mode, env): + _verify_description_mod(mode) + signode += nodes.Text(text_type(self)) + +class ASTDeclerator(ASTBase): + + def __init__(self, ptrOps, declId, suffixOps): + self.ptrOps = ptrOps + self.declId = declId + self.suffixOps = suffixOps + + @property + def name(self): + return self.declId + + def get_param_id(self): + for op in self.suffixOps: + if isinstance(op, ASTParametersQualifiers): + return op.get_param_id() + raise Exception("This should only be called on a function: %s" % text_type(self)) + + def get_type_id(self): + return u''.join([text_type(op) for op in (self.ptrOps + self.suffixOps)]) + + def require_start_space(self): + if '...' in self.ptrOps: return False + return not not self.declId + + def __unicode__(self): + res = [] + for op in self.ptrOps: + res.append(text_type(op)) + if op == '...' 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_mod(mode) + for op in self.ptrOps: signode += nodes.Text(text_type(op)) + 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_mod(mode) + signode += nodes.Text(text_type(self)) +class ASTType(ASTBase): + + def __init__(self, declSpecs, decl): + self.declSpecs = declSpecs + self.decl = decl + + @property + def name(self): + name = self.decl.name + if not name: name = self.declSpecs.name + return name + + def get_id(self): + res = ['___'] + res.append(text_type(self.name)) + if self.objectType == 'function': + res.append('___') + res.append(self.decl.get_param_id()) + else: + print(self.objectType) + assert False + return u''.join(res) -class ClassDefExpr(NamedDefExpr): + def get_type_id(self): + return self.declSpecs.get_type_id() + self.decl.get_type_id() + + def __unicode__(self): + res = [] + res.append(text_type(self.declSpecs)) + if self.decl.require_start_space(): + res.append(u' ') + res.append(text_type(self.decl)) + return u''.join(res) + + def describe_signature(self, signode, mode, env): + _verify_description_mod(mode) + if self.decl.name: + self.declSpecs.describe_signature(signode, 'markType', env) + else: + self.declSpecs.describe_signature(signode, mode, env) + if self.decl.require_start_space(): + signode += nodes.Text(' ') + self.decl.describe_signature(signode, mode, env) + +class ASTTypeWithInit(ASTBase): + + def __init__(self, type, init): + self.type = type + self.init = init + + @property + def name(self): + return self.type.name + + def get_id(self): + if self.objectType == 'member': + return text_type(self.name) + else: + raise NotImplementedError("Should this happen? %s, %s" % (self.objectType, text_type(self.name))) + + def get_type_id(self): + return self.type.get_type_id() + + def __unicode__(self): + res = [] + res.append(text_type(self.type)) + if self.init: res.append(text_type(self.init)) + return u''.join(res) + + def describe_signature(self, signode, mode, env): + _verify_description_mod(mode) + self.type.describe_signature(signode, mode, env) + if self.init: self.init.describe_signature(signode, mode) - def __init__(self, name, visibility, static, bases): - NamedDefExpr.__init__(self, name, visibility, static) +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_mod(mode) + if self.visibility != 'private': + signode += addnodes.desc_annotation(self.visibility, self.visibility) + signode += nodes.Text(' ') + self.name.describe_signature(signode, mode, env) + +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(text_type(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 "___" + text_type(self.prefixedName) + 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_mod(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): - - # 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')) + + # those without signedness and size modifiers + # see http://en.cppreference.com/w/cpp/language/types + _simple_fundemental_types = { + 'void', 'bool', 'char', 'wchar_t', 'char16_t', 'char32_t', 'int', 'float', 'double', 'auto' } + + _prefix_keys = {'class', 'struct', 'union', 'typename'} def __init__(self, definition): self.definition = definition.strip() @@ -556,8 +799,9 @@ 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) @@ -606,370 +850,6 @@ class DefinitionParser(object): if self.last_match is not None: return self.last_match.group() - def _parse_operator(self): - self.skip_ws() - # thank god, a regular operator definition - if self.match(_operator_re): - return NameDefExpr('operator' + - _whitespace_re.sub('', self.matched_text)) - - # new/delete operator? - for allocop in 'new', 'delete': - if not self.skip_word(allocop): - 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) - - # 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 - - 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_builtin(self, modifiers): - modifier = modifiers[-1] - path = modifiers - following = self._modifiers[modifier] - while 1: - self.skip_ws() - if not self.match(_identifier_re): - break - identifier = self.matched_text - if identifier in following: - path.append(identifier) - following = self._modifiers[modifier] - assert following - 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 - - args = [] - while 1: - self.skip_ws() - if 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) - - def _parse_type(self, in_template=False): - 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 - - while 1: - self.skip_ws() - if (in_template and self.current_char in ',>') or \ - (result and not self.skip_string('::')) or \ - self.eof: - 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) - - def _parse_default_expr(self): - 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: - break - self.pos = idx+1 - - rv = self.definition[rv_start:idx] - self.pos = idx - return rv - - def _parse_signature(self): - self.skip_ws() - if not self.skip_string('('): - self.fail('expected parentheses for function') - - 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 self.skip_string('...'): - args.append(ArgumentDefExpr(None, '...', [], None)) - if self.skip_string(')'): - break - else: - self.fail('expected closing parenthesis after ellipses') - - argname = default = None - argtype = self._parse_type() - self.skip_ws() - param_pack = self.skip_string('...') - if param_pack: - 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, param_pack)) - 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=False, - lvalue_this=False, - rvalue_this=False, - delete=False, - default=False) - - if self.skip_string('&&'): - attributes['rvalue_this'] = True - if self.skip_string('&'): - attributes['lvalue_this'] = True - - if attributes['lvalue_this'] and attributes['rvalue_this']: - self.fail('rvalue reference for *this specifier must be one of' - '"&&" or "&"') - - if self.skip_string('='): - self.skip_ws() - if self.skip_string('0') or self.skip_word('NULL') or self.skip_word('nullptr'): - attributes['pure'] = 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() - - 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() - else: - name = typename - typename = None - type_suffixes = [] - return TypeObjDefExpr(name, visibility, static, typename, type_suffixes) - - 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() - else: - value = None - return MemberObjDefExpr(name, visibility, static, typename, - type_suffixes, value) - - def parse_function(self): - visibility, static = self._parse_visibility_static() - virtual = self.skip_word_and_ws('virtual') - explicit = self.skip_word_and_ws('explicit') - constexpr = self.skip_word_and_ws('constexpr') - - rv = self._parse_type() - self.skip_ws() - # some things just don't have return values - if self.current_char == '(': - name = rv - rv = None - else: - name = self._parse_type() - return FuncDefExpr(name, visibility, static, virtual, explicit, constexpr, rv, - **self._parse_signature()) - - def parse_class(self): - visibility, static = self._parse_visibility_static() - name = self._parse_type() - bases = [] - if self.skip_string(':'): - self.skip_ws() - while 1: - access = 'private' - if self.match(_visibility_re): - access = self.matched_text - base = self._parse_type() - bases.append(ClassDefExpr(base, access, False, [])) - if self.skip_string(','): - self.skip_ws() - else: - break - return ClassDefExpr(name, visibility, static, bases) - def read_rest(self): rv = self.definition[self.pos:] self.pos = self.end @@ -978,9 +858,411 @@ class DefinitionParser(object): def assert_end(self): self.skip_ws() if not self.eof: - self.fail('expected end of definition, got %r' % + self.fail('expected end of definition, got %r' % self.definition[self.pos:]) - + + def _parse_operator(self, context): + self.skip_ws() + # adapted from the old code + # thank god, a regular operator definition + if self.match(_operator_re): + return ASTOperatorBuildIn(self.matched_text) + + # new/delete operator? + if False: + for allocop in 'new', 'delete': + if not self.skip_word(allocop): + 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) + + # oh well, looks like a cast operator definition. + # In that case, eat another type. + type = self._parse_type('abstractDecl') + return ASTOperatorType(type) + + def _parse_nested_name(self, context): + _verify_parsing_context(context) + names = [] + + 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): + self.fail("expected identifier") + identifier = self.matched_text + if identifier == 'operator': + op = self._parse_operator(context) + names.append(op) + else: + templateArgs = None + self.skip_ws() + if self.skip_string('<'): + templateArgs = [] + while 1: + pos = self.pos + try: + type = self._parse_type('abstractDecl') + 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)) + + self.skip_ws() + if not self.skip_string('::'): break + return ASTNestedName(names) + + def _parse_trailing_type_spec(self, context): + _verify_parsing_context(context) + + # fundemental types + self.skip_ws() + for t in self._simple_fundemental_types: + if self.skip_word(t): return ASTTrailingTypeSpecFundemental(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: + if self.skip_word_and_ws('short'): elements.append('short') + elif self.skip_word_and_ws('long'): elements.append('long') + else: break + if self.skip_word_and_ws('int'): elements.append('int') + elif self.skip_word_and_ws('double'): elements.append('double') + if len(elements) > 0: return ASTTrailingTypeSpecFundemental(u' '.join(elements)) + + # decltype + self.skip_ws() + if self.skip_word_and_ws('decltype'): + self.fail('"decltype(.)" in trailing_type_spec not implemented') + + # prefixed + prefix = None + self.skip_ws() + for k in self._prefix_keys: + if self.skip_word_and_ws(k): + prefix = k + break + + nestedName = self._parse_nested_name(context) + return ASTTrailingTypeSpecName(prefix, nestedName) + + def _parse_parameters_and_qualifiers(self, context): + _verify_parsing_context(context) + + self.skip_ws() + if not self.skip_string('('): + if context == 'functionObject': + self.fail('Expecting "(" in parameters_and_qualifiers.') + else: return None + args = [] + self.skip_ws() + if not self.skip_string(')'): + while 1: + self.skip_ws() + if self.skip_string('...'): + args.append(ASTFunctinoParameter(None, True)) + self.skip_ws() + if not self.skip_string(')'): + self.fail('Expected ")" after "..." in parameters_and_qualifiers.') + break + if context == 'functionObjectArgument': + arg = self._parse_type_with_init(context) + else: + arg = self._parse_type(context) + # TODO: parse default parameters + args.append(ASTFunctinoParameter(arg)) + + self.skip_ws() + if self.skip_string(','): continue + elif self.skip_string(')'): break + else: self.fail('Expecting "," or ")" in parameters_and_qualifiers, got "%s".' % self.current_char) + + self.skip_ws() + const = self.skip_word_and_ws('const') + volatile = self.skip_word_and_ws('volatile') + if not const: # the can be permuted + const = self.skip_word_and_ws('const') + + refQual = None + if self.skip_string('&&'): refQual = '&&' + if not refQual and self.skip_string('&'): refQual = '&' + + exceptionSpec = None + override = None + final = None + initializer = None + if context == 'functionObjectArgument': + self.skip_ws() + if self.skip_string('noexcept'): + exceptionSpec = 'noexcept' + self.skip_ws() + if self.skip_string('('): + self.fail('Parameterised "noexcept" not implemented.') + + self.skip_ws() + override = self.skip_word_and_ws('override') + final = self.skip_word_and_ws('final') + if not override: override = self.skip_word_and_ws('override') # they can be permuted + + self.skip_ws() + if self.skip_string('='): + self.skip_ws() + valid = {'0', 'delete', 'default'} + for w in valid: + if self.skip_word_and_ws(w): + initializer = w + break + if not initializer: + self.fail('Expected "%s" in initializer-specifier.' % u'" or "'.join(valid)) + + return ASTParametersQualifiers(args, volatile, const, refQual, exceptionSpec, override, final, initializer) + + def _parse_decl_specs(self, context): + """ + 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) + """ + _verify_parsing_context(context) + visibility = None + storage = None + inline = None + virtual = None + explicit = None + constexpr = None + volatile = None + const = None + + # visibility + if context in {'typeObject', 'memberObject', 'functionObject'}: + self.skip_ws() + visibility = 'public' + 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 context in {'memberObject', 'functionObject'}: + if self.skip_word('static'): + storage = 'static' + continue + if context == 'memberObject': + if self.skip_word('mutable'): + storage = 'mutable' + continue + if context == 'functionObject': # TODO: maybe in more contexts, missing test cases + if self.skip_word('register'): + storage = 'register' + continue + + if context == 'functionObject': + # 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 context in {'memberObject', 'functionObject'}: + constexpr = self.skip_word("constexpr") + if constexpr: continue + if not volatile: + volatile = self.skip_word('volatile') + if volatile: continue + if not const: + const = self.skip_word('const') + if const: continue + break + + trailing = self._parse_trailing_type_spec(context) + return ASTDeclSpecs(context, visibility, storage, inline, virtual, explicit, constexpr, volatile, const, trailing) + + def _parse_declerator(self, context): + _verify_parsing_context(context) + + ptrOps = [] + while 1: + self.skip_ws() + if self.skip_string('*'): + op = '*' + self.skip_ws() + if self.skip_word_and_ws('volatile'): op += 'volatile ' + if self.skip_word_and_ws('const'): op += 'const ' + ptrOps.append(op) + elif self.skip_string('&'): ptrOps.append('&') + elif self.skip_string('...'): + ptrOps.append('...') + break + else: break + + if context == 'abstractDecl': + declId = None + elif context == 'functionObjectArgument' \ + or context == 'functionObject': + # Function arguments don't need to have a name. + # Some functions (constructors/destructors) don't ahve return type, + # so the name has been parsed in the declSpecs. + # Also conversion operators (e.g., "A::operator std::string()" will have no declId at this point + pos = self.pos + try: + declId = self._parse_nested_name(context) + except DefinitionError: + self.pos = pos + declId = None + else: + declId = self._parse_nested_name(context) + + suffixOpts = [] + while 1: + self.skip_ws() + if 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 context == 'functionObject': + paramQual = self._parse_parameters_and_qualifiers('functionObjectArgument') + else: + paramQual = self._parse_parameters_and_qualifiers(context) + if paramQual: suffixOpts.append(paramQual) + break + + return ASTDeclerator(ptrOps, declId, suffixOpts) + + def _parse_initializer(self, context): + _verify_parsing_context(context) + self.skip_ws() + # TODO: support paren and brace initialization for memberObject + if not self.skip_string('='): return None + else: + if context == 'memberObject': + value = self.read_rest().strip() + return ASTInitializer(value) + elif context == 'functionObjectArgument': + 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("Initializer for context '%s' not implemented." % context) + + def _parse_type(self, context): + _verify_parsing_context(context) + declSpecs = self._parse_decl_specs(context) + decl = self._parse_declerator(context) + return ASTType(declSpecs, decl) + + def _parse_type_with_init(self, context): + _verify_parsing_context(context) + type = self._parse_type(context) + init = self._parse_initializer(context) + return ASTTypeWithInit(type, init) + + def _parse_class(self, context): + name = self._parse_nested_name(context) + bases = [] + self.skip_ws() + if self.skip_string(':'): + while 1: + self.skip_ws() + visibility = 'private' + if self.match(_visibility_re): + visibility = self.matched_text + baseName = self._parse_nested_name('baseClass') + bases.append(ASTBaseClass(baseName, visibility)) + self.skip_ws() + if self.skip_string(','): continue + else: break + return ASTClass(name, bases) + + def parse_type_object(self): + res = self._parse_type('typeObject') + res.objectType = 'type' + return res + + def parse_member_object(self): + res = self._parse_type_with_init('memberObject') + res.objectType = 'member' + return res + + def parse_function_object(self): + res = self._parse_type('functionObject') + res.objectType = 'function' + return res + + def parse_class_object(self): + res = self._parse_class('classObject') + res.objectType = 'class' + return res + + def parse_namespace_object(self): + res = self._parse_nested_name('namespaceObject') + res.objectType = 'namespace' + return res + + def parse_xref_object(self): + res = self._parse_nested_name('xrefObject') + res.objectType = 'xref' + return res class CPPObject(ObjectDescription): """Description of a C++ language object.""" @@ -996,51 +1278,19 @@ class CPPObject(ObjectDescription): names=('returns', 'return')), ] - def attach_name(self, node, name): - owner, name = name.split_owner() - varname = text_type(name) - if owner is not None: - owner = text_type(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(text_type(suffix)) - - def attach_type(self, node, type): - # XXX: link to c? - text = text_type(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 = text_type(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)) indextext = self.get_index_text(name) if indextext: @@ -1049,7 +1299,6 @@ class CPPObject(ObjectDescription): 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: @@ -1062,25 +1311,60 @@ class CPPObject(ObjectDescription): 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 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 + ast = ast.clone() + ast.prefixedName = ast.name.prefix(parent) + else: + ast.prefixedName = ast.name + return ast +class CPPTypeObject(CPPObject): + + def get_index_text(self, name): + return _('%s (C++ type)') % name + + def parse_definition(self, parser): + return parser.parse_type_object() + + def describe_signature(self, signode, ast): + signode += addnodes.desc_annotation('type ', 'type ') + ast.describe_signature(signode, 'typeObject', self.env) + +class CPPMemberObject(CPPObject): + + def get_index_text(self, name): + return _('%s (C++ member)') % name + + def parse_definition(self, parser): + return parser.parse_member_object() + + def describe_signature(self, signode, ast): + ast.describe_signature(signode, 'lastIsName', self.env) + +class CPPFunctionObject(CPPObject): + + def get_index_text(self, name): + return _('%s (C++ function)') % name + + def parse_definition(self, parser): + return parser.parse_function_object() + + def describe_signature(self, signode, ast): + ast.describe_signature(signode, 'lastIsName', self.env) class CPPClassObject(CPPObject): @@ -1088,125 +1372,13 @@ class CPPClassObject(CPPObject): return _('%s (C++ class)') % name def parse_definition(self, parser): - return parser.parse_class() + return parser.parse_class_object() - def describe_signature(self, signode, cls): - self.attach_modifiers(signode, cls) + def describe_signature(self, signode, ast): 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(text_type(base.name), - text_type(base.name)) - signode += nodes.Text(', ') - signode.pop() # remove the trailing comma - - -class CPPTypeObject(CPPObject): - - def get_index_text(self, name): - if self.objtype == 'type': - return _('%s (C++ type)') % name - return '' - - def parse_definition(self, parser): - return parser.parse_type_object() - - def describe_signature(self, signode, obj): - self.attach_modifiers(signode, obj) - 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) - - -class CPPMemberObject(CPPObject): - - def get_index_text(self, name): - if self.objtype == 'member': - return _('%s (C++ member)') % name - return '' - - 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) - - -class CPPFunctionObject(CPPObject): - - def attach_function(self, node, func): - owner, name = func.name.split_owner() - if owner is not None: - owner = text_type(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 = text_type(name) - node += addnodes.desc_name(funcname, funcname) - - 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(text_type(arg.name), text_type(arg.name)) - self.attach_type_suffixes(param, arg.type_suffixes) - if arg.default is not None: - def_ = u'=' + text_type(arg.default) - param += nodes.emphasis(def_, def_) - paramlist += param - - node += paramlist - if func.const: - node += addnodes.desc_addname(' const', ' const') - if func.noexcept: - node += addnodes.desc_addname(' noexcept', ' noexcept') - if func.pure: - node += addnodes.desc_addname(' = 0', ' = 0') - - def get_index_text(self, name): - return _('%s (C++ function)') % name - - def parse_definition(self, parser): - return parser.parse_function() - - def describe_signature(self, signode, func): - self.attach_modifiers(signode, func) - if func.explicit: - signode += addnodes.desc_annotation('explicit', 'explicit') - signode += nodes.Text(' ') - if func.virtual: - signode += addnodes.desc_annotation('virtual', 'virtual') - 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) - - -class CPPCurrentNamespace(Directive): + ast.describe_signature(signode, 'allIsName', self.env) + +class CPPNamespaceObject(Directive): """ This directive is just to tell Sphinx that we're documenting stuff in namespace foo. @@ -1221,26 +1393,24 @@ 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'] = None else: parser = DefinitionParser(self.arguments[0]) try: - prefix = parser.parse_type() + prefix = parser.parse_namespace_object() parser.assert_end() except DefinitionError as e: - self.state_machine.reporter.warning(e.description, - line=self.lineno) + 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') 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] == '~': @@ -1250,16 +1420,15 @@ class CPPXRefRole(XRefRole): title = title[dcolon + 2:] return title, target - class CPPDomain(Domain): """C++ language 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 = { @@ -1267,7 +1436,7 @@ class CPPDomain(Domain): 'function': CPPFunctionObject, 'member': CPPMemberObject, 'type': CPPTypeObject, - 'namespace': CPPCurrentNamespace + 'namespace': CPPNamespaceObject } roles = { 'class': CPPXRefRole(), @@ -1276,50 +1445,41 @@ class CPPDomain(Domain): 'type': CPPXRefRole() } initial_data = { - 'objects': {}, # fullname -> docname, objtype + 'objects': {}, # prefixedName -> (docname, objectType, id) } def clear_doc(self, docname): - for fullname, (fn, _, _) in list(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 = text_type(expr) + def _create_refnode(name): + name = text_type(name) 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) + 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: - raise DefinitionError('') + if not parser.eof: raise DefinitionError('') except DefinitionError: env.warn_node('unparseable C++ definition: %r' % target, node) return None + # try as is the name is fully qualified + refNode = _create_refnode(nameAst) + if refNode: return refNode + + # try qualifying it with the parent 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 - - parent, name = parent.split_owner() - return _create_refnode(expr.prefix(parent)) + if not parent: return None + else: return _create_refnode(nameAst.prefix(parent)) def get_objects(self): for refname, (docname, type, theid) in iteritems(self.data['objects']): - yield (refname, refname, type, docname, refname, 1) + yield (refname, refname, type, docname, refname, 1) \ No newline at end of file diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py index 19f5f395a..36e9896b4 100644 --- a/sphinx/ext/napoleon/docstring.py +++ b/sphinx/ext/napoleon/docstring.py @@ -197,12 +197,12 @@ class GoogleDocstring(UnicodeMixin): line = self._line_iter.peek() return lines - def _consume_field(self, parse_type=True, prefer_type=False): + def _consume_field(self, parse_type_old=True, prefer_type=False): line = next(self._line_iter) match = None _name, _type, _desc = line.strip(), '', '' - if parse_type: + if parse_type_old: match = _google_typed_arg_regex.match(line) if match: _name = match.group(1) @@ -222,11 +222,11 @@ class GoogleDocstring(UnicodeMixin): _desc = self.__class__(_desc, self._config).lines() return _name, _type, _desc - def _consume_fields(self, parse_type=True, prefer_type=False): + def _consume_fields(self, parse_type_old=True, prefer_type=False): self._consume_empty() fields = [] while not self._is_section_break(): - _name, _type, _desc = self._consume_field(parse_type, prefer_type) + _name, _type, _desc = self._consume_field(parse_type_old, prefer_type) if _name or _type or _desc: fields.append((_name, _type, _desc,)) return fields @@ -462,7 +462,7 @@ class GoogleDocstring(UnicodeMixin): def _parse_methods_section(self, section): lines = [] - for _name, _, _desc in self._consume_fields(parse_type=False): + for _name, _, _desc in self._consume_fields(parse_type_old=False): lines.append('.. method:: %s' % _name) if _desc: lines.extend([''] + self._indent(_desc, 3)) @@ -698,9 +698,9 @@ class NumpyDocstring(GoogleDocstring): super(NumpyDocstring, self).__init__(docstring, config, app, what, name, obj, options) - def _consume_field(self, parse_type=True, prefer_type=False): + def _consume_field(self, parse_type_old=True, prefer_type=False): line = next(self._line_iter) - if parse_type: + if parse_type_old: _name, _, _type = line.partition(':') else: _name, _type = line, '' diff --git a/tests/test_cpp_domain.py b/tests/test_cpp_domain.py index 15bb29af6..20160da9e 100644 --- a/tests/test_cpp_domain.py +++ b/tests/test_cpp_domain.py @@ -15,151 +15,105 @@ from util import raises from sphinx.domains.cpp import DefinitionParser, DefinitionError - def parse(name, string): - return getattr(DefinitionParser(string), 'parse_' + name)() + parser = DefinitionParser(string) + res = getattr(parser, "parse_" + name + "_object")() + if not parser.eof: + print("Parsing stopped at", parser.pos) + print(string) + print('-'*parser.pos + '^') + raise DefinitionError("") + return res +def check(name, input, output=None): + # first a simple check of the AST + if output == None: output = input + ast = parse(name, input) + res = text_type(ast) + if res != output: + print("Input: ", text_type(input)) + print("Result:", res) + raise DefinitionError("") + # now check describe_signature + ast.describe_signature([], 'lastIsName', None) def test_type_definitions(): - rv = parse('member_object', ' const std::string & name = 42') - assert text_type(rv) == 'const std::string& name = 42' + check("type", "public bool b", "bool b") + check("type", "bool A::b") + check("type", "bool *b") + check("type", "bool *const b") + check("type", "bool *volatile const b") + check("type", "bool *volatile const b") + check("type", "bool *volatile const *b") + check("type", "bool &b") + check("type", "bool b[]") + check("type", "std::pair coord") + check("type", "long long int foo") + check("type", 'std::vector> module::blah') + check("type", "std::function F") + check("type", "std::function F") + check("type", "std::function F") + + check('member', ' const std::string & name = 42', 'const std::string &name = 42') + check('member', ' const std::string & name', 'const std::string &name') + check('member', ' const std::string & name [ n ]', 'const std::string &name[n]') + check('member', 'const std::vector< unsigned int, long> &name', 'const std::vector &name') + check('member', 'module::myclass foo[n]') - rv = parse('member_object', ' const std::string & name leftover') - assert text_type(rv) == 'const std::string& name' - - rv = parse('member_object', ' const std::string & name [n] leftover') - assert text_type(rv) == 'const std::string& name[n]' - - rv = parse('member_object', 'const std::vector< unsigned int, long> &name') - assert text_type(rv) == 'const std::vector& name' - - x = 'std::vector>& module::test(register ' \ - 'foo, bar, std::string baz="foobar, blah, bleh") const = 0' - assert text_type(parse('function', x)) == x - - x = 'module::myclass::operator std::vector()' - assert text_type(parse('function', x)) == x - x = 'explicit module::myclass::foo::foo()' - assert text_type(parse('function', x)) == x - - x = 'int printf(const char* fmt, ...)' - assert text_type(parse('function', x)) == x - - x = 'int foo(const unsigned int j)' - assert text_type(parse('function', x)) == x - - x = 'int foo(const unsigned int const j)' - assert text_type(parse('function', x)) == x - - x = 'int foo(const int* const ptr)' - assert text_type(parse('function', x)) == x - - x = 'std::vector> module::blah' - assert text_type(parse('type_object', x)) == x - - assert text_type(parse('type_object', 'long long int foo')) == 'long long foo' - - x = 'void operator()(const boost::array& v) const' - assert text_type(parse('function', x)) == x - - x = 'void operator()(const boost::array& v) const' - assert text_type(parse('function', x)) == x - - x = 'MyClass::MyClass(MyClass::MyClass&&)' - assert text_type(parse('function', x)) == x - - x = 'constexpr int get_value()' - assert text_type(parse('function', x)) == x - - x = 'static constexpr int get_value()' - assert text_type(parse('function', x)) == x - - x = 'int get_value() const noexcept' - assert text_type(parse('function', x)) == x - - x = 'int get_value() const noexcept = delete' - assert text_type(parse('function', x)) == x - - x = 'MyClass::MyClass(MyClass::MyClass&&) = default' - assert text_type(parse('function', x)) == x - - x = 'MyClass::a_virtual_function() const override' - assert text_type(parse('function', x)) == x - - x = 'MyClass::a_member_function() volatile' - assert text_type(parse('function', x)) == x - - x = 'MyClass::a_member_function() const volatile' - assert text_type(parse('function', x)) == x - - x = 'MyClass::a_member_function() &&' - assert text_type(parse('function', x)) == x - - x = 'MyClass::a_member_function() &' - assert text_type(parse('function', x)) == x - - x = 'MyClass::a_member_function() const &' - assert text_type(parse('function', x)) == x - - x = 'int main(int argc, char* argv[][])' - assert text_type(parse('function', x)) == x - - x = 'std::vector>& module::test(register ' \ - 'foo, bar[n], std::string baz="foobar, blah, bleh") const = 0' - assert text_type(parse('function', x)) == x - - x = 'module::myclass foo[n]' - assert text_type(parse('member_object', x)) == x - - x = 'int foo(Foo f=Foo(double(), std::make_pair(int(2), double(3.4))))' - assert text_type(parse('function', x)) == x - - x = 'int foo(A a=x(a))' - assert text_type(parse('function', x)) == x - - x = 'int foo(B b=x(a)' - raises(DefinitionError, parse, 'function', x) - - x = 'int foo)C c=x(a))' - raises(DefinitionError, parse, 'function', x) - - x = 'int foo(D d=x(a' - raises(DefinitionError, parse, 'function', x) - - x = 'int foo(const A&... a)' - assert text_type(parse('function', x)) == x - - x = 'virtual void f()' - assert text_type(parse('function', x)) == x + x = 'std::vector> &module::test(register ' \ + 'foo, bar, std::string baz = "foobar, blah, bleh") const = 0' + check('function', x) + check('function', 'explicit module::myclass::foo::foo()') + check('function', 'module::myclass::foo::~foo()') + check('function', 'int printf(const char *fmt, ...)') + check('function', 'int foo(const unsigned int j)') + check('function', 'int foo(const int *const ptr)') + check('function', 'module::myclass::operator std::vector()') + check('function', 'void operator()(const boost::array &v) const') + check('function', 'void operator()(const boost::array &v) const') + check('function', 'MyClass::MyClass(MyClass::MyClass&&)') + check('function', 'constexpr int get_value()') + check('function', 'static constexpr int get_value()') + check('function', 'int get_value() const noexcept') + check('function', 'int get_value() const noexcept = delete') + check('function', 'MyClass::MyClass(MyClass::MyClass&&) = default') + check('function', 'MyClass::a_virtual_function() const override') + check('function', 'A B() override') + check('function', 'A B() final') + check('function', 'A B() final override') + check('function', 'A B() override final', 'A B() final override') + check('function', 'MyClass::a_member_function() volatile') + check('function', 'MyClass::a_member_function() volatile const') + check('function', 'MyClass::a_member_function() &&') + check('function', 'MyClass::a_member_function() &') + check('function', 'MyClass::a_member_function() const &') + check('function', 'int main(int argc, char *argv[][])') + check('function', 'MyClass &MyClass::operator++()') + check('function', 'MyClass::pointer MyClass::operator->()') + x = 'std::vector> &module::test(register ' \ + 'foo, bar[n], std::string baz = "foobar, blah, bleh") const = 0' + check('function', x) + check('function', 'int foo(Foo f = Foo(double(), std::make_pair(int(2), double(3.4))))') + check('function', 'int foo(A a = x(a))') + raises(DefinitionError, parse, 'function', 'int foo(B b=x(a)') + raises(DefinitionError, parse, 'function', 'int foo)C c=x(a))') + raises(DefinitionError, parse, 'function', 'int foo(D d=x(a') + check('function', 'int foo(const A&... a)') + check('function', 'virtual void f()') def test_bases(): - x = 'A' - assert text_type(parse('class', x)) == x - - x = 'A : B' - assert text_type(parse('class', x)) == x - - x = 'A : private B' - assert text_type(parse('class', x)) == 'A : B' - - x = 'A : public B' - assert text_type(parse('class', x)) == x - - x = 'A : B, C' - assert text_type(parse('class', x)) == x - - x = 'A : B, protected C, D' - assert text_type(parse('class', x)) == x + check('class', 'A') + check('class', 'A::B::C') + check('class', 'A : B') + check('class', 'A : private B', 'A : B') + check('class', 'A : public B') + check('class', 'A : B, C') + check('class', 'A : B, protected C, D') def test_operators(): - x = parse('function', 'void operator new [ ] ()') - assert text_type(x) == 'void operator new[]()' - - x = parse('function', 'void operator delete ()') - assert text_type(x) == 'void operator delete()' - + check('function', 'void operator new [ ] ()', 'void operator new[]()') + check('function', 'void operator delete ()', 'void operator delete()') for op in '*-+=/%!': - x = parse('function', 'void operator %s ()' % op) - assert text_type(x) == 'void operator%s()' % op + check('function', 'void operator %s ()' % op, 'void operator%s()' % op) From caad958de49df5f8ab6adb629e69ee93dc45efc8 Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Thu, 24 Jul 2014 11:09:28 +0200 Subject: [PATCH 04/13] C++, fix namespacing of elements and xrefs. --- sphinx/domains/cpp.py | 73 +++++++++++++++++++--------------------- tests/test_cpp_domain.py | 1 + 2 files changed, 36 insertions(+), 38 deletions(-) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 02ba872f7..4ff2a0ab7 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -257,15 +257,7 @@ class ASTBase(UnicodeMixin): """ raise NotImplementedError(repr(self)) - 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. - """ - 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(repr(self)) @@ -276,10 +268,12 @@ class ASTBase(UnicodeMixin): return '<%s %s>' % (self.__class__.__name__, self) def _verify_parsing_context(context): - assert context in {'typeObject', 'functionObject', 'memberObject', 'classObject', 'namespaceObject', 'xrefObject', 'abstractDecl', 'functionObjectArgument', 'baseClass'} + if not context in {'typeObject', 'functionObject', 'memberObject', 'classObject', 'namespaceObject', 'xrefObject', 'abstractDecl', 'functionObjectArgument', 'baseClass'}: + raise Exception("Parsing context '%s' is invalid." % context) def _verify_description_mod(mode): - assert mode in {'lastIsName', 'allIsName', 'noneIsName', 'markType'} + if not mode in {'lastIsName', 'allIsName', 'noneIsName', 'markType'}: + raise Exception("Description mode '%s' is invalid." % mode) class ASTOperatorBuildIn(ASTBase): @@ -371,10 +365,11 @@ class ASTNestedName(ASTBase): def name(self): return self - def prefix(self, prefix): + def prefix_nested_name(self, prefix): if self.names[0] == '': - raise DefinitionError('Can not prefix nested name rooted in outer-most namespace.') - names = [prefix] + 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) @@ -669,10 +664,12 @@ class ASTType(ASTBase): def get_id(self): res = ['___'] - res.append(text_type(self.name)) + res.append(text_type(self.prefixedName)) if self.objectType == 'function': res.append('___') res.append(self.decl.get_param_id()) + elif self.objectType == 'type': + pass else: print(self.objectType) assert False @@ -711,7 +708,7 @@ class ASTTypeWithInit(ASTBase): def get_id(self): if self.objectType == 'member': - return text_type(self.name) + return "___" + text_type(self.prefixedName) else: raise NotImplementedError("Should this happen? %s, %s" % (self.objectType, text_type(self.name))) @@ -1133,12 +1130,12 @@ class DefinitionParser(object): if context == 'abstractDecl': declId = None - elif context == 'functionObjectArgument' \ - or context == 'functionObject': + elif context in {'functionObjectArgument', 'functionObject', 'typeObject'}: # Function arguments don't need to have a name. # Some functions (constructors/destructors) don't ahve return type, # so the name has been parsed in the declSpecs. # Also conversion operators (e.g., "A::operator std::string()" will have no declId at this point + # For types we want to allow the plain declaration that there is a type, i.e., "MyContainer::const_iterator" pos = self.pos try: declId = self._parse_nested_name(context) @@ -1291,23 +1288,12 @@ class CPPObject(ObjectDescription): self.state.document.note_explicit_target(signode) if not name in objects: objects.setdefault(name, (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'): - 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 - def parse_definition(self, parser): raise NotImplementedError() @@ -1325,9 +1311,9 @@ class CPPObject(ObjectDescription): self.describe_signature(signode, ast) parent = self.env.temp_data.get('cpp:parent') - if parent is not None: + if parent and len(parent) > 0: ast = ast.clone() - ast.prefixedName = ast.name.prefix(parent) + ast.prefixedName = ast.name.prefix_nested_name(parent[-1]) else: ast.prefixedName = ast.name return ast @@ -1342,7 +1328,7 @@ class CPPTypeObject(CPPObject): def describe_signature(self, signode, ast): signode += addnodes.desc_annotation('type ', 'type ') - ast.describe_signature(signode, 'typeObject', self.env) + ast.describe_signature(signode, 'lastIsName', self.env) class CPPMemberObject(CPPObject): @@ -1371,6 +1357,14 @@ class CPPClassObject(CPPObject): def get_index_text(self, name): return _('%s (C++ class)') % name + def before_content(self): + lastname = self.env.temp_data['cpp:lastname'] + assert lastname + self.env.temp_data['cpp:parent'].append(lastname) + + def after_content(self): + self.env.temp_data['cpp:parent'].pop() + def parse_definition(self, parser): return parser.parse_class_object() @@ -1393,7 +1387,7 @@ class CPPNamespaceObject(Directive): def run(self): env = self.state.document.settings.env if self.arguments[0].strip() in ('NULL', '0', 'nullptr'): - env.temp_data['cpp:parent'] = None + env.temp_data['cpp:parent'] = [] else: parser = DefinitionParser(self.arguments[0]) try: @@ -1402,13 +1396,15 @@ class CPPNamespaceObject(Directive): except DefinitionError as e: self.state_machine.reporter.warning(e.description,line=self.lineno) else: - env.temp_data['cpp:parent'] = 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 # if the first character is a tilde, don't display the module/class @@ -1477,8 +1473,9 @@ class CPPDomain(Domain): # try qualifying it with the parent parent = node.get('cpp:parent', None) - if not parent: return None - else: return _create_refnode(nameAst.prefix(parent)) + 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 iteritems(self.data['objects']): diff --git a/tests/test_cpp_domain.py b/tests/test_cpp_domain.py index 20160da9e..b3df490f9 100644 --- a/tests/test_cpp_domain.py +++ b/tests/test_cpp_domain.py @@ -53,6 +53,7 @@ def test_type_definitions(): check("type", "std::function F") check("type", "std::function F") check("type", "std::function F") + check("type", "MyContainer::const_iterator") check('member', ' const std::string & name = 42', 'const std::string &name = 42') check('member', ' const std::string & name', 'const std::string &name') From 6cde3d596a7ec421dd169c25b2f23f7d97ed870b Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Thu, 24 Jul 2014 16:30:50 +0200 Subject: [PATCH 05/13] C++, improve xrefs with templates. --- sphinx/domains/cpp.py | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 4ff2a0ab7..dced374e6 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -283,6 +283,9 @@ class ASTOperatorBuildIn(ASTBase): def __unicode__(self): return u''.join(['operator', self.op]) + def get_name_no_template(self): + return text_type(self) + def describe_signature(self, signode, mode): _verify_description_mod(mode) identifier = text_type(self) @@ -332,17 +335,19 @@ class ASTNestedNameElement(ASTBase): 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_mod(mode) if mode == 'markType': - identifier = text_type(self.identifier) - targetText = prefix + identifier + 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(identifier) + pnode += nodes.Text(text_type(self.identifier)) signode += pnode if self.templateArgs: signode += nodes.Text('<') @@ -365,6 +370,12 @@ class ASTNestedName(ASTBase): def name(self): return self + 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 @@ -1288,6 +1299,11 @@ class CPPObject(ObjectDescription): self.state.document.note_explicit_target(signode) 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) @@ -1451,10 +1467,13 @@ class CPPDomain(Domain): def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): - def _create_refnode(name): - name = text_type(name) + def _create_refnode(nameAst): + name = text_type(nameAst) if name not in self.data['objects']: - return None + # 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) From eb2a0a090e0f247f722f5fbeb773b0abc93775eb Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Sat, 26 Jul 2014 14:14:49 +0200 Subject: [PATCH 06/13] C++, fix class name rendering to 'lastIsName', though not the template part. Put function argument names in emphasis. --- sphinx/domains/cpp.py | 55 ++++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index dced374e6..0786c2a88 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -272,7 +272,7 @@ def _verify_parsing_context(context): raise Exception("Parsing context '%s' is invalid." % context) def _verify_description_mod(mode): - if not mode in {'lastIsName', 'allIsName', 'noneIsName', 'markType'}: + if not mode in {'lastIsName', 'noneIsName', 'markType', 'param'}: raise Exception("Description mode '%s' is invalid." % mode) class ASTOperatorBuildIn(ASTBase): @@ -286,10 +286,13 @@ class ASTOperatorBuildIn(ASTBase): def get_name_no_template(self): return text_type(self) - def describe_signature(self, signode, mode): + def describe_signature(self, signode, mode, env, prefix): _verify_description_mod(mode) identifier = text_type(self) - signode += addnodes.desc_addname(identifier, identifier) + if mode == 'lastIsName': + signode += addnodes.desc_name(identifier, identifier) + else: + signode += addnodes.desc_addname(identifier, identifier) class ASTOperatorType(ASTBase): @@ -299,10 +302,13 @@ class ASTOperatorType(ASTBase): def __unicode__(self): return u''.join(['operator ', text_type(self.type)]) - def describe_signature(self, signode, mode): + def describe_signature(self, signode, mode, env, prefix): _verify_description_mod(mode) identifier = text_type(self) - signode += addnodes.desc_addname(identifier, identifier) + if mode == 'lastIsName': + signode += addnodes.desc_name(identifier, identifier) + else: + signode += addnodes.desc_addname(identifier, identifier) class ASTTemplateArgConstant(ASTBase): @@ -349,16 +355,19 @@ class ASTNestedNameElement(ASTBase): pnode['cpp:parent'] = env.temp_data.get('cpp:parent') pnode += nodes.Text(text_type(self.identifier)) signode += pnode - 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, mode, env) - signode += nodes.Text('>') + 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): @@ -394,13 +403,13 @@ class ASTNestedName(ASTBase): if len(self.names) > 1: addname += u'::' name = text_type(self.names[-1]) signode += addnodes.desc_addname(addname, addname) - signode += addnodes.desc_name(name, name) - elif mode == 'allIsName': - name = text_type(self) - signode += addnodes.desc_name(name, name) + 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 @@ -468,7 +477,7 @@ class ASTFunctinoParameter(ASTBase): def describe_signature(self, signode, mode, env): _verify_description_mod(mode) - if self.ellipsis: signode += nodes.emphasis('...') + if self.ellipsis: signode += nodes.Text('...') else: self.arg.describe_signature(signode, mode, env) class ASTParametersQualifiers(ASTBase): @@ -525,7 +534,9 @@ class ASTParametersQualifiers(ASTBase): paramlist = addnodes.desc_parameterlist() for arg in self.args: param = addnodes.desc_parameter('', '', noemph=True) - arg.describe_signature(param, 'markType', env) + 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): @@ -645,7 +656,9 @@ class ASTDeclerator(ASTBase): def describe_signature(self, signode, mode, env): _verify_description_mod(mode) - for op in self.ptrOps: signode += nodes.Text(text_type(op)) + for op in self.ptrOps: + signode += nodes.Text(text_type(op)) + if op == '...' 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) @@ -1386,7 +1399,7 @@ class CPPClassObject(CPPObject): def describe_signature(self, signode, ast): signode += addnodes.desc_annotation('class ', 'class ') - ast.describe_signature(signode, 'allIsName', self.env) + ast.describe_signature(signode, 'lastIsName', self.env) class CPPNamespaceObject(Directive): """ From 23df17b4040850054efda9a3c7be58e31c489001 Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Sun, 27 Jul 2014 02:57:41 +0200 Subject: [PATCH 07/13] C++, change parsing context to explicit arguments, fixes some casting operators. --- sphinx/domains/cpp.py | 341 +++++++++++++++++++++------------------ tests/test_cpp_domain.py | 7 +- 2 files changed, 187 insertions(+), 161 deletions(-) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 0786c2a88..9a30a58fd 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -120,6 +120,7 @@ """ import re +import traceback from copy import deepcopy from six import iteritems, text_type @@ -266,12 +267,8 @@ class ASTBase(UnicodeMixin): def __repr__(self): return '<%s %s>' % (self.__class__.__name__, self) - -def _verify_parsing_context(context): - if not context in {'typeObject', 'functionObject', 'memberObject', 'classObject', 'namespaceObject', 'xrefObject', 'abstractDecl', 'functionObjectArgument', 'baseClass'}: - raise Exception("Parsing context '%s' is invalid." % context) -def _verify_description_mod(mode): +def _verify_description_mode(mode): if not mode in {'lastIsName', 'noneIsName', 'markType', 'param'}: raise Exception("Description mode '%s' is invalid." % mode) @@ -287,7 +284,7 @@ class ASTOperatorBuildIn(ASTBase): return text_type(self) def describe_signature(self, signode, mode, env, prefix): - _verify_description_mod(mode) + _verify_description_mode(mode) identifier = text_type(self) if mode == 'lastIsName': signode += addnodes.desc_name(identifier, identifier) @@ -302,8 +299,11 @@ class ASTOperatorType(ASTBase): 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): - _verify_description_mod(mode) + _verify_description_mode(mode) identifier = text_type(self) if mode == 'lastIsName': signode += addnodes.desc_name(identifier, identifier) @@ -319,7 +319,7 @@ class ASTTemplateArgConstant(ASTBase): return text_type(self.value) def describe_signature(self, signode, mode, env): - _verify_description_mod(mode) + _verify_description_mode(mode) signode += nodes.Text(text_type(self)) class ASTNestedNameElement(ASTBase): @@ -345,7 +345,7 @@ class ASTNestedNameElement(ASTBase): return text_type(self.identifier) def describe_signature(self, signode, mode, env, prefix): - _verify_description_mod(mode) + _verify_description_mode(mode) if mode == 'markType': targetText = prefix + text_type(self) pnode = addnodes.pending_xref( @@ -397,7 +397,7 @@ class ASTNestedName(ASTBase): return u'::'.join([text_type(n) for n in self.names]) def describe_signature(self, signode, mode, env): - _verify_description_mod(mode) + _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'::' @@ -476,7 +476,7 @@ class ASTFunctinoParameter(ASTBase): else: return text_type(self.arg) def describe_signature(self, signode, mode, env): - _verify_description_mod(mode) + _verify_description_mode(mode) if self.ellipsis: signode += nodes.Text('...') else: self.arg.describe_signature(signode, mode, env) @@ -530,7 +530,7 @@ class ASTParametersQualifiers(ASTBase): return u''.join(res) def describe_signature(self, signode, mode, env): - _verify_description_mod(mode) + _verify_description_mode(mode) paramlist = addnodes.desc_parameterlist() for arg in self.args: param = addnodes.desc_parameter('', '', noemph=True) @@ -554,8 +554,8 @@ class ASTParametersQualifiers(ASTBase): class ASTDeclSpecs(ASTBase): - def __init__(self, context, visibility, storage, inline, virtual, explicit, constexpr, volatile, const, trailing): - self.context = context + 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 @@ -574,39 +574,41 @@ class ASTDeclSpecs(ASTBase): return text_type(self) def _print_visibility(self): - return self.visibility and not (self.context in {'typeObject', 'memberObject', 'functionObject'} and self.visibility == 'public') + 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) - res.append(' ') - if self.storage: - res.append(self.storage) - res.append(' ') - 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 ') - res.append(text_type(self.trailingTypeSpec)) - return u''.join(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_mod(mode) - def _add(signode, text): - signode += addnodes.desc_annotation(text, text) - signode += nodes.Text(' ') - if self._print_visibility(): _add(signode, self.visibility) - if self.storage: _add(signode, self.storage) - if self.inline: _add(signode, 'inline') - if self.virtual: _add(signode, 'virtual') - if self.explicit: _add(signode, 'explicit') - if self.constexpr: _add(signode, 'constexpr') - if self.volatile: _add(signode, 'volatile') - if self.const: _add(signode, 'const') - self.trailingTypeSpec.describe_signature(signode, mode, env) + _verify_description_mode(mode) + modifiers = [] + 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) class ASTArray(ASTBase): @@ -617,7 +619,7 @@ class ASTArray(ASTBase): return u''.join(['[', text_type(self.size), ']']) def describe_signature(self, signode, mode, env): - _verify_description_mod(mode) + _verify_description_mode(mode) signode += nodes.Text(text_type(self)) class ASTDeclerator(ASTBase): @@ -642,7 +644,7 @@ class ASTDeclerator(ASTBase): def require_start_space(self): if '...' in self.ptrOps: return False - return not not self.declId + else: return self.declId != None def __unicode__(self): res = [] @@ -655,7 +657,7 @@ class ASTDeclerator(ASTBase): return u''.join(res) def describe_signature(self, signode, mode, env): - _verify_description_mod(mode) + _verify_description_mode(mode) for op in self.ptrOps: signode += nodes.Text(text_type(op)) if op == '...' and self.declId: signode += nodes.Text(' ') @@ -671,7 +673,7 @@ class ASTInitializer(ASTBase): return u''.join([' = ', text_type(self.value)]) def describe_signature(self, signode, mode): - _verify_description_mod(mode) + _verify_description_mode(mode) signode += nodes.Text(text_type(self)) class ASTType(ASTBase): @@ -704,19 +706,17 @@ class ASTType(ASTBase): def __unicode__(self): res = [] - res.append(text_type(self.declSpecs)) - if self.decl.require_start_space(): + 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_mod(mode) - if self.decl.name: - self.declSpecs.describe_signature(signode, 'markType', env) - else: - self.declSpecs.describe_signature(signode, mode, env) - if self.decl.require_start_space(): + _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) @@ -746,7 +746,7 @@ class ASTTypeWithInit(ASTBase): return u''.join(res) def describe_signature(self, signode, mode, env): - _verify_description_mod(mode) + _verify_description_mode(mode) self.type.describe_signature(signode, mode, env) if self.init: self.init.describe_signature(signode, mode) @@ -765,7 +765,7 @@ class ASTBaseClass(ASTBase): return u''.join(res) def describe_signature(self, signode, mode, env): - _verify_description_mod(mode) + _verify_description_mode(mode) if self.visibility != 'private': signode += addnodes.desc_annotation(self.visibility, self.visibility) signode += nodes.Text(' ') @@ -793,7 +793,7 @@ class ASTClass(ASTBase): return u''.join(res) def describe_signature(self, signode, mode, env): - _verify_description_mod(mode) + _verify_description_mode(mode) self.name.describe_signature(signode, mode, env) if len(self.bases) > 0: signode += nodes.Text(' : ') @@ -882,7 +882,7 @@ class DefinitionParser(object): self.fail('expected end of definition, got %r' % self.definition[self.pos:]) - def _parse_operator(self, context): + def _parse_operator(self): self.skip_ws() # adapted from the old code # thank god, a regular operator definition @@ -890,25 +890,23 @@ class DefinitionParser(object): return ASTOperatorBuildIn(self.matched_text) # new/delete operator? - if False: - for allocop in 'new', 'delete': - if not self.skip_word(allocop): - continue + for op in 'new', 'delete': + if not self.skip_word(op): + continue + self.skip_ws() + if self.skip_string('['): 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) + if not self.skip_string(']'): + 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('abstractDecl') + type = self._parse_type() return ASTOperatorType(type) - def _parse_nested_name(self, context): - _verify_parsing_context(context) + def _parse_nested_name(self): names = [] self.skip_ws() @@ -920,7 +918,7 @@ class DefinitionParser(object): self.fail("expected identifier") identifier = self.matched_text if identifier == 'operator': - op = self._parse_operator(context) + op = self._parse_operator() names.append(op) else: templateArgs = None @@ -930,7 +928,7 @@ class DefinitionParser(object): while 1: pos = self.pos try: - type = self._parse_type('abstractDecl') + type = self._parse_type(allowParams=True) templateArgs.append(type) except DefinitionError: self.pos = pos @@ -960,9 +958,7 @@ class DefinitionParser(object): if not self.skip_string('::'): break return ASTNestedName(names) - def _parse_trailing_type_spec(self, context): - _verify_parsing_context(context) - + def _parse_trailing_type_spec(self): # fundemental types self.skip_ws() for t in self._simple_fundemental_types: @@ -994,15 +990,13 @@ class DefinitionParser(object): prefix = k break - nestedName = self._parse_nested_name(context) + nestedName = self._parse_nested_name() return ASTTrailingTypeSpecName(prefix, nestedName) - def _parse_parameters_and_qualifiers(self, context): - _verify_parsing_context(context) - + def _parse_parameters_and_qualifiers(self, paramMode): self.skip_ws() if not self.skip_string('('): - if context == 'functionObject': + if paramMode == 'function': self.fail('Expecting "(" in parameters_and_qualifiers.') else: return None args = [] @@ -1016,10 +1010,10 @@ class DefinitionParser(object): if not self.skip_string(')'): self.fail('Expected ")" after "..." in parameters_and_qualifiers.') break - if context == 'functionObjectArgument': - arg = self._parse_type_with_init(context) + if paramMode == 'function': + arg = self._parse_type_with_init(named='maybe') else: - arg = self._parse_type(context) + arg = self._parse_type() # TODO: parse default parameters args.append(ASTFunctinoParameter(arg)) @@ -1028,6 +1022,9 @@ class DefinitionParser(object): 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() const = self.skip_word_and_ws('const') volatile = self.skip_word_and_ws('volatile') @@ -1042,40 +1039,38 @@ class DefinitionParser(object): override = None final = None initializer = None - if context == 'functionObjectArgument': + self.skip_ws() + if self.skip_string('noexcept'): + exceptionSpec = 'noexcept' self.skip_ws() - if self.skip_string('noexcept'): - exceptionSpec = 'noexcept' - self.skip_ws() - if self.skip_string('('): - self.fail('Parameterised "noexcept" not implemented.') + if self.skip_string('('): + self.fail('Parameterised "noexcept" not implemented.') - self.skip_ws() - override = self.skip_word_and_ws('override') - final = self.skip_word_and_ws('final') - if not override: override = self.skip_word_and_ws('override') # they can be permuted + self.skip_ws() + 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('='): - self.skip_ws() - valid = {'0', 'delete', 'default'} - for w in valid: - if self.skip_word_and_ws(w): - initializer = w - break - if not initializer: - self.fail('Expected "%s" in initializer-specifier.' % u'" or "'.join(valid)) + valid = {'0', 'delete', 'default'} + for w in valid: + if self.skip_word_and_ws(w): + initializer = w + break + if not initializer: + self.fail('Expected "%s" in initializer-specifier.' % u'" or "'.join(valid)) return ASTParametersQualifiers(args, volatile, const, refQual, exceptionSpec, override, final, initializer) - def _parse_decl_specs(self, context): + 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) """ - _verify_parsing_context(context) visibility = None storage = None inline = None @@ -1085,30 +1080,28 @@ class DefinitionParser(object): volatile = None const = None - # visibility - if context in {'typeObject', 'memberObject', 'functionObject'}: + if outer: self.skip_ws() - visibility = 'public' 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 context in {'memberObject', 'functionObject'}: + if outer in {'member', 'function'}: if self.skip_word('static'): storage = 'static' continue - if context == 'memberObject': + if outer == 'member': if self.skip_word('mutable'): storage = 'mutable' continue - if context == 'functionObject': # TODO: maybe in more contexts, missing test cases + if outer == 'fuction': # TODO: maybe in more contexts, missing test cases if self.skip_word('register'): storage = 'register' continue - if context == 'functionObject': + if outer == 'function': # function-specifiers if not inline: inline = self.skip_word('inline') @@ -1120,25 +1113,28 @@ class DefinitionParser(object): explicit = self.skip_word('explicit') if explicit: continue - if not constexpr and context in {'memberObject', 'functionObject'}: + if not constexpr and outer in {'member', 'function'}: constexpr = self.skip_word("constexpr") if constexpr: continue - if not volatile: + if not volatile and typed: volatile = self.skip_word('volatile') if volatile: continue - if not const: + if not const and typed: const = self.skip_word('const') if const: continue break - trailing = self._parse_trailing_type_spec(context) - return ASTDeclSpecs(context, visibility, storage, inline, virtual, explicit, constexpr, volatile, const, trailing) + if typed: trailing = self._parse_trailing_type_spec() + else: trailing = None + return ASTDeclSpecs(outer, visibility, storage, inline, virtual, explicit, constexpr, volatile, const, trailing) - def _parse_declerator(self, context): - _verify_parsing_context(context) - + 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('*'): op = '*' @@ -1152,27 +1148,20 @@ class DefinitionParser(object): break else: break - if context == 'abstractDecl': - declId = None - elif context in {'functionObjectArgument', 'functionObject', 'typeObject'}: - # Function arguments don't need to have a name. - # Some functions (constructors/destructors) don't ahve return type, - # so the name has been parsed in the declSpecs. - # Also conversion operators (e.g., "A::operator std::string()" will have no declId at this point - # For types we want to allow the plain declaration that there is a type, i.e., "MyContainer::const_iterator" - pos = self.pos + if named == 'maybe': try: - declId = self._parse_nested_name(context) + declId = self._parse_nested_name() except DefinitionError: - self.pos = pos declId = None + elif named: + declId = self._parse_nested_name() else: - declId = self._parse_nested_name(context) + declId = None suffixOpts = [] while 1: self.skip_ws() - if self.skip_string('['): + if typed and self.skip_string('['): startPos = self.pos - 1 openCount = 1 while not self.eof: @@ -1187,25 +1176,22 @@ class DefinitionParser(object): self.pos += 1 suffixOpts.append(ASTArray(self.definition[startPos+1:self.pos-1].strip())) continue - if context == 'functionObject': - paramQual = self._parse_parameters_and_qualifiers('functionObjectArgument') - else: - paramQual = self._parse_parameters_and_qualifiers(context) - if paramQual: suffixOpts.append(paramQual) + if paramMode: + paramQual = self._parse_parameters_and_qualifiers(paramMode) + if paramQual: suffixOpts.append(paramQual) break return ASTDeclerator(ptrOps, declId, suffixOpts) - def _parse_initializer(self, context): - _verify_parsing_context(context) + def _parse_initializer(self, outer=None): self.skip_ws() # TODO: support paren and brace initialization for memberObject if not self.skip_string('='): return None else: - if context == 'memberObject': + if outer == 'member': value = self.read_rest().strip() return ASTInitializer(value) - elif context == 'functionObjectArgument': + elif outer == None: # function parameter symbols = [] startPos = self.pos self.skip_ws() @@ -1224,22 +1210,57 @@ class DefinitionParser(object): value = self.definition[startPos:self.pos].strip() return ASTInitializer(value) else: - self.fail("Initializer for context '%s' not implemented." % context) + self.fail("Internal error, initializer for outer '%s' not implemented." % outer) - def _parse_type(self, context): - _verify_parsing_context(context) - declSpecs = self._parse_decl_specs(context) - decl = self._parse_declerator(context) + 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, context): - _verify_parsing_context(context) - type = self._parse_type(context) - init = self._parse_initializer(context) + 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, context): - name = self._parse_nested_name(context) + def _parse_class(self): + name = self._parse_nested_name() bases = [] self.skip_ws() if self.skip_string(':'): @@ -1248,7 +1269,7 @@ class DefinitionParser(object): visibility = 'private' if self.match(_visibility_re): visibility = self.matched_text - baseName = self._parse_nested_name('baseClass') + baseName = self._parse_nested_name() bases.append(ASTBaseClass(baseName, visibility)) self.skip_ws() if self.skip_string(','): continue @@ -1256,32 +1277,32 @@ class DefinitionParser(object): return ASTClass(name, bases) def parse_type_object(self): - res = self._parse_type('typeObject') + res = self._parse_type(outer='type') res.objectType = 'type' return res def parse_member_object(self): - res = self._parse_type_with_init('memberObject') + res = self._parse_type_with_init(outer='member') res.objectType = 'member' return res def parse_function_object(self): - res = self._parse_type('functionObject') + res = self._parse_type(outer='function') res.objectType = 'function' return res def parse_class_object(self): - res = self._parse_class('classObject') + res = self._parse_class() res.objectType = 'class' return res def parse_namespace_object(self): - res = self._parse_nested_name('namespaceObject') + res = self._parse_nested_name() res.objectType = 'namespace' return res def parse_xref_object(self): - res = self._parse_nested_name('xrefObject') + res = self._parse_nested_name() res.objectType = 'xref' return res diff --git a/tests/test_cpp_domain.py b/tests/test_cpp_domain.py index b3df490f9..8750ddbed 100644 --- a/tests/test_cpp_domain.py +++ b/tests/test_cpp_domain.py @@ -36,6 +36,7 @@ def check(name, input, output=None): raise DefinitionError("") # now check describe_signature ast.describe_signature([], 'lastIsName', None) + #print ".. %s:: %s" % (name, input) def test_type_definitions(): check("type", "public bool b", "bool b") @@ -54,6 +55,7 @@ def test_type_definitions(): check("type", "std::function F") check("type", "std::function F") check("type", "MyContainer::const_iterator") + check("type", "public MyContainer::const_iterator", "MyContainer::const_iterator") check('member', ' const std::string & name = 42', 'const std::string &name = 42') check('member', ' const std::string & name', 'const std::string &name') @@ -61,6 +63,8 @@ def test_type_definitions(): check('member', 'const std::vector< unsigned int, long> &name', 'const std::vector &name') check('member', 'module::myclass foo[n]') + check('function', 'operator bool() const') + check('function', 'bool namespaced::theclass::method(arg1, arg2)') x = 'std::vector> &module::test(register ' \ 'foo, bar, std::string baz = "foobar, blah, bleh") const = 0' check('function', x) @@ -78,7 +82,7 @@ def test_type_definitions(): check('function', 'int get_value() const noexcept') check('function', 'int get_value() const noexcept = delete') check('function', 'MyClass::MyClass(MyClass::MyClass&&) = default') - check('function', 'MyClass::a_virtual_function() const override') + check('function', 'virtual MyClass::a_virtual_function() const override') check('function', 'A B() override') check('function', 'A B() final') check('function', 'A B() final override') @@ -116,5 +120,6 @@ def test_bases(): def test_operators(): check('function', 'void operator new [ ] ()', 'void operator new[]()') check('function', 'void operator delete ()', 'void operator delete()') + check('function', 'void operator bool() const', 'void operator bool() const') for op in '*-+=/%!': check('function', 'void operator %s ()' % op, 'void operator%s()' % op) From c19a15ea0d87e8851597cd14813c19ed8312da83 Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Sun, 27 Jul 2014 11:42:01 +0200 Subject: [PATCH 08/13] Doc, update C++ domain. --- doc/domains.rst | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/doc/domains.rst b/doc/domains.rst index 155bd235f..9b8b38612 100644 --- a/doc/domains.rst +++ b/doc/domains.rst @@ -536,7 +536,7 @@ The following directives are available: .. cpp:function:: bool namespaced::theclass::method(arg1, arg2) - Describes a method without types. + Describes a method with unnamed parameters. .. cpp:function:: const T &array::operator[]() const @@ -554,8 +554,14 @@ The following directives are available: .. cpp:member:: std::string theclass::name[N][M] + .. cpp::type:: std::vector MyList + + A typedef-like declaration of a type. + .. cpp:type:: theclass::const_iterator + Declaration of a type alias with unspecified type. + Will be rendered like this: .. cpp:function:: bool namespaced::theclass::method(int arg1, std::string arg2) @@ -564,7 +570,7 @@ The following directives are available: .. cpp:function:: bool namespaced::theclass::method(arg1, arg2) - Describes a method without types. + Describes a method with unnamed parameters. .. cpp:function:: const T &array::operator[]() const @@ -582,12 +588,19 @@ The following directives are available: .. cpp:member:: std::string theclass::name[N][M] + .. cpp::type:: std::vector MyList + + A typedef-like declaration of a type. + .. cpp:type:: theclass::const_iterator + Declaration of a type alias with unspecified type. + .. rst:directive:: .. cpp:namespace:: namespace - Select the current C++ namespace for the following objects. - + Select the current namespace for the following objects. Note that the namespace + does not need to correspond to C++ namespaces, but can end in names of classes. + (e.g., ``Namespace1::Namespace2::SomeClass::AnInnerClass``) .. _cpp-roles: From 14e6affa7d243948592062750c025523bc8302a1 Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Wed, 6 Aug 2014 17:41:51 +0200 Subject: [PATCH 09/13] C++, update id generation to use name mangling --- sphinx/domains/cpp.py | 341 ++++++++++++++++++++++++++------------- tests/test_cpp_domain.py | 10 +- 2 files changed, 231 insertions(+), 120 deletions(-) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 9a30a58fd..446a77584 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -8,7 +8,8 @@ :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. - See http://www.nongnu.org/hcb/ + 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 @@ -153,68 +154,80 @@ _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', + 'wchar_t' : 'w', + 'bool' : 'b', + 'char' : 'c', + 'signed char' : 'a', + 'unsigned char' : 'h', + 'short' : 's', + 'unsigned short' : 't', + 'int' : 'i', + 'unsigned int' : 'j', + 'long' : 'l', + 'unsigned long' : 'm', + 'long long' : 'x', + 'unsigned long long' : 'y', + 'float' : 'f', + 'double' : 'd', + 'long double' : 'e', + 'char32_t' : 'Di', + 'char16_t' : 'Ds', + '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(UnicodeMixin, Exception): @@ -277,8 +290,15 @@ class ASTOperatorBuildIn(ASTBase): def __init__(self, op): self.op = op + def get_id(self): + 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 u''.join(['operator', self.op]) + if self.op in {'new', 'new[]', 'delete', 'delete[]'}: + return u'operator ' + self.op + else: return u'operator' + self.op def get_name_no_template(self): return text_type(self) @@ -299,6 +319,9 @@ class ASTOperatorType(ASTBase): def __unicode__(self): return u''.join(['operator ', text_type(self.type)]) + def get_id(self): + return u'cv' + self.type.get_id() + def get_name_no_template(self): return text_type(self) @@ -318,6 +341,10 @@ class ASTTemplateArgConstant(ASTBase): def __unicode__(self): 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' + def describe_signature(self, signode, mode, env): _verify_description_mode(mode) signode += nodes.Text(text_type(self)) @@ -328,6 +355,20 @@ class ASTNestedNameElement(ASTBase): self.identifier = identifier self.templateArgs = templateArgs + def get_id(self): + 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): res = [] res.append(self.identifier) @@ -379,6 +420,16 @@ class ASTNestedName(ASTBase): def name(self): return self + def get_id(self): + 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 += '::' @@ -426,7 +477,7 @@ class ASTNestedName(ASTBase): else: raise Exception('Unknown description mode: %s' % mode) -class ASTTrailingTypeSpecFundemental(ASTBase): +class ASTTrailingTypeSpecFundamental(ASTBase): def __init__(self, name): self.name = name @@ -434,6 +485,11 @@ class ASTTrailingTypeSpecFundemental(ASTBase): def __unicode__(self): return self.name + 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 describe_signature(self, signode, mode, env): signode += nodes.Text(text_type(self.name)) @@ -446,6 +502,9 @@ class ASTTrailingTypeSpecName(ASTBase): @property def name(self): return self.nestedName + + def get_id(self): + return self.nestedName.get_id() def __unicode__(self): res = [] @@ -467,9 +526,9 @@ class ASTFunctinoParameter(ASTBase): self.arg = arg self.ellipsis = ellipsis - def get_type_id(self): - if self.ellipsis: return '...' - else: return self.arg.get_type_id() + def get_id(self): + if self.ellipsis: return 'z' + else: return self.arg.get_id() def __unicode__(self): if self.ellipsis: return '...' @@ -492,18 +551,17 @@ class ASTParametersQualifiers(ASTBase): self.final = final self.initializer = initializer - def get_param_id(self): - # return an id encoding whatever participates in overload resolution - args = [] - for a in self.args: - args.append(a.get_type_id()) + def get_modifiers_id(self): res = [] - res.append(u'_'.join(args)) - if self.const: res.append('C') - else: res.append('') - if self.refQual: res.append(self.refQual) - else: res.append('') - return u'__'.join(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): res = [] @@ -570,8 +628,12 @@ class ASTDeclSpecs(ASTBase): def name(self): return self.trailingTypeSpec.name - def get_type_id(self): - return text_type(self) + def get_id(self): + 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') @@ -610,6 +672,40 @@ class ASTDeclSpecs(ASTBase): signode += nodes.Text(' ') self.trailingTypeSpec.describe_signature(signode, mode, env) +class ASTPtrOpPtr(ASTBase): + + def __init__(self, volatile, const): + self.volatile = volatile + self.const = const + + def __unicode__(self): + 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 ASTPtrOpRef(ASTBase): + + def __unicode__(self): + return '&' + + def get_id(self): + return 'R' + +class ASTPtrOpParamPack(ASTBase): + + def __unicode__(self): + return '...' + + def get_id(self): + return 'Dp' + class ASTArray(ASTBase): def __init__(self, size): @@ -618,6 +714,10 @@ class ASTArray(ASTBase): def __unicode__(self): return u''.join(['[', text_type(self.size), ']']) + def get_id(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)) @@ -633,24 +733,30 @@ class ASTDeclerator(ASTBase): def name(self): return self.declId - def get_param_id(self): + 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() - raise Exception("This should only be called on a function: %s" % text_type(self)) + return '' - def get_type_id(self): - return u''.join([text_type(op) for op in (self.ptrOps + self.suffixOps)]) + 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 '...' in self.ptrOps: return False + 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 op == '...' and self.declId: res.append(' ') + 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)) @@ -660,7 +766,7 @@ class ASTDeclerator(ASTBase): _verify_description_mode(mode) for op in self.ptrOps: signode += nodes.Text(text_type(op)) - if op == '...' and self.declId: signode += nodes.Text(' ') + 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) @@ -681,6 +787,7 @@ class ASTType(ASTBase): def __init__(self, declSpecs, decl): self.declSpecs = declSpecs self.decl = decl + self.objectType = None @property def name(self): @@ -689,20 +796,23 @@ class ASTType(ASTBase): return name def get_id(self): - res = ['___'] - res.append(text_type(self.prefixedName)) - if self.objectType == 'function': - res.append('___') + 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()) - elif self.objectType == 'type': - pass - else: - print(self.objectType) - assert False return u''.join(res) - - def get_type_id(self): - return self.declSpecs.get_type_id() + self.decl.get_type_id() def __unicode__(self): res = [] @@ -723,6 +833,7 @@ class ASTType(ASTBase): class ASTTypeWithInit(ASTBase): def __init__(self, type, init): + self.objectType = None self.type = type self.init = init @@ -732,12 +843,9 @@ class ASTTypeWithInit(ASTBase): def get_id(self): if self.objectType == 'member': - return "___" + text_type(self.prefixedName) + return _id_prefix + self.prefixedName.get_id() else: - raise NotImplementedError("Should this happen? %s, %s" % (self.objectType, text_type(self.name))) - - def get_type_id(self): - return self.type.get_type_id() + return self.type.get_id() def __unicode__(self): res = [] @@ -778,7 +886,7 @@ class ASTClass(ASTBase): self.bases = bases def get_id(self): - return "___" + text_type(self.prefixedName) + return _id_prefix + self.prefixedName.get_id() def __unicode__(self): res = [] @@ -899,7 +1007,7 @@ class DefinitionParser(object): if not self.skip_string(']'): self.fail('Expected "]" after "operator ' + op + '["') op += '[]' - return ASTOperatorBuildIn(' ' + op) + return ASTOperatorBuildIn(op) # oh well, looks like a cast operator definition. # In that case, eat another type. @@ -962,7 +1070,7 @@ class DefinitionParser(object): # fundemental types self.skip_ws() for t in self._simple_fundemental_types: - if self.skip_word(t): return ASTTrailingTypeSpecFundemental(t) + if self.skip_word(t): return ASTTrailingTypeSpecFundamental(t) # TODO: this could/should be more strict elements = [] @@ -975,7 +1083,7 @@ class DefinitionParser(object): else: break 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 ASTTrailingTypeSpecFundemental(u' '.join(elements)) + if len(elements) > 0: return ASTTrailingTypeSpecFundamental(u' '.join(elements)) # decltype self.skip_ws() @@ -1137,14 +1245,13 @@ class DefinitionParser(object): if not typed: break self.skip_ws() if self.skip_string('*'): - op = '*' self.skip_ws() - if self.skip_word_and_ws('volatile'): op += 'volatile ' - if self.skip_word_and_ws('const'): op += 'const ' - ptrOps.append(op) - elif self.skip_string('&'): ptrOps.append('&') + 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('...') + ptrOps.append(ASTPtrOpParamPack()) break else: break @@ -1341,8 +1448,10 @@ class CPPObject(ObjectDescription): self.env.temp_data['cpp:lastname'] = ast.prefixedName indextext = self.get_index_text(name) - if indextext: - self.indexnode['entries'].append(('single', indextext, theid, '')) + 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() diff --git a/tests/test_cpp_domain.py b/tests/test_cpp_domain.py index 8750ddbed..7be8fe3f4 100644 --- a/tests/test_cpp_domain.py +++ b/tests/test_cpp_domain.py @@ -31,11 +31,13 @@ def check(name, input, output=None): ast = parse(name, input) res = text_type(ast) if res != output: - print("Input: ", text_type(input)) - print("Result:", res) + print "Input: ", text_type(input) + print "Result: ", res + print "Expected: ", output raise DefinitionError("") - # now check describe_signature ast.describe_signature([], 'lastIsName', None) + ast.prefixedName = ast.name # otherwise the get_id fails, it would be set in handle_signarue + ast.get_id() #print ".. %s:: %s" % (name, input) def test_type_definitions(): @@ -92,7 +94,7 @@ def test_type_definitions(): check('function', 'MyClass::a_member_function() &&') check('function', 'MyClass::a_member_function() &') check('function', 'MyClass::a_member_function() const &') - check('function', 'int main(int argc, char *argv[][])') + check('function', 'int main(int argc, char *argv[])') check('function', 'MyClass &MyClass::operator++()') check('function', 'MyClass::pointer MyClass::operator->()') From 9230dd6a892e8d42013292c215d699a0dfde0171 Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Wed, 6 Aug 2014 18:54:00 +0200 Subject: [PATCH 10/13] Doc, update C++ domain. --- doc/domains.rst | 78 +++++++++++++++++++++++-------------------------- 1 file changed, 36 insertions(+), 42 deletions(-) diff --git a/doc/domains.rst b/doc/domains.rst index 9b8b38612..4bfc91ec2 100644 --- a/doc/domains.rst +++ b/doc/domains.rst @@ -520,21 +520,24 @@ The C++ Domain The C++ domain (name **cpp**) supports documenting C++ projects. -The following directives are available: +The following directives are available. All declarations can start with a visibility statement +(``public``, ``private`` or ``protected``). -.. rst:directive:: .. cpp:class:: signatures - .. cpp:function:: signatures - .. cpp:member:: signatures - .. cpp:type:: signatures +.. rst:directive:: .. cpp:class:: class speicifer - Describe a C++ object. Full signature specification is supported -- give the - signature as you would in the declaration. Here some examples:: + Describe a class/struct, possibly with specification of inheritance, e.g.,:: + + .. cpp:class:: SomeName::SomeClass : public MyBase, MyOtherBase + +.. rst:directive:: .. cpp:function:: (member-)function prototype + + Describe a function or member function, e.g.,:: .. cpp:function:: bool namespaced::theclass::method(int arg1, std::string arg2) Describes a method with parameters and types. - .. cpp:function:: bool namespaced::theclass::method(arg1, arg2) + .. cpp:function:: bool namespaced::theclass::method(T1, T2) Describes a method with unnamed parameters. @@ -550,45 +553,24 @@ The following directives are available: Describe a constexpr function here. - .. cpp:member:: std::string theclass::name + .. cpp:function:: MyClass::MyClass(const MyClass&) = default - .. cpp:member:: std::string theclass::name[N][M] + Describe a copy constructor with default implementation. - .. cpp::type:: std::vector MyList +.. rst:directive:: .. cpp:member:: variable or member declaration - A typedef-like declaration of a type. - - .. cpp:type:: theclass::const_iterator - - Declaration of a type alias with unspecified type. - - Will be rendered like this: - - .. cpp:function:: bool namespaced::theclass::method(int arg1, std::string arg2) - - Describes a method with parameters and types. - - .. cpp:function:: bool namespaced::theclass::method(arg1, arg2) - - Describes a method with unnamed parameters. - - .. cpp:function:: const T &array::operator[]() const - - Describes the constant indexing operator of a templated array. - - .. cpp:function:: operator bool() const - - Describe a casting operator here. - - .. cpp:function:: constexpr void foo(std::string &bar[2]) noexcept - - Describe a constexpr function here. + Describe a varible or member variable, e.g.,:: .. cpp:member:: std::string theclass::name .. cpp:member:: std::string theclass::name[N][M] - .. cpp::type:: std::vector MyList +.. rst:directive:: .. cpp:type:: typedef-like declaration + .. cpp:type:: name + + Describe a type as in a typedef declaration, or the name of a type with unspecified type, e.g.,:: + + .. cpp:type:: std::vector MyList A typedef-like declaration of a type. @@ -599,8 +581,14 @@ The following directives are available: .. rst:directive:: .. cpp:namespace:: namespace Select the current namespace for the following objects. Note that the namespace - does not need to correspond to C++ namespaces, but can end in names of classes. - (e.g., ``Namespace1::Namespace2::SomeClass::AnInnerClass``) + does not need to correspond to C++ namespaces, but can end in names of classes, e.g.,:: + + .. cpp:namespace:: Namespace1::Namespace2::SomeClass::AnInnerClass + + All following objects will be defined as if their name were declared with the namespace + prepended. The following cross-references will be search for by both their specified name + and with the namespace prepended. + .. _cpp-roles: @@ -611,7 +599,7 @@ These roles link to the given object types: cpp:member cpp:type - Reference a C++ object. You can give the full signature (and need to, for + Reference a C++ object. You can give the full specification (and need to, for overloaded functions.) .. note:: @@ -632,6 +620,12 @@ These roles link to the given object types: specific overload. Currently Sphinx will link to the first overloaded version of the method / function. +.. admonition:: Note on Template Delcarations + + The C++ domain currently does not support template classes/functions/aliases/variables + (e.g., ``template MyClass``), only template instantiations + (e.g., ``MyClass``). + The Standard Domain ------------------- From 0de1022a7890f8826a04eb4a21055277ed30a88c Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Thu, 7 Aug 2014 13:33:52 +0200 Subject: [PATCH 11/13] Ooops, revert stray function renames. --- sphinx/domains/c.py | 8 ++++---- sphinx/ext/napoleon/docstring.py | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index 90351ee71..1c34207f8 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -66,7 +66,7 @@ class CObject(ObjectDescription): 'struct', '_Bool', )) - def _parse_type_old(self, node, ctype): + def _parse_type(self, node, ctype): # add cross-ref nodes for all words for part in [_f for _f in wsplit_re.split(ctype) if _f]: tnode = nodes.Text(part, part) @@ -91,7 +91,7 @@ class CObject(ObjectDescription): rettype, name, arglist, const = m.groups() signode += addnodes.desc_type('', '') - self._parse_type_old(signode[-1], rettype) + self._parse_type(signode[-1], rettype) try: classname, funcname = name.split('::', 1) classname += '::' @@ -130,9 +130,9 @@ class CObject(ObjectDescription): ctype, argname = arg.rsplit(' ', 1) except ValueError: # no argument name given, only the type - self._parse_type_old(param, arg) + self._parse_type(param, arg) else: - self._parse_type_old(param, ctype) + self._parse_type(param, ctype) # separate by non-breaking space in the output param += nodes.emphasis(' '+argname, u'\xa0'+argname) paramlist += param diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py index 36e9896b4..19f5f395a 100644 --- a/sphinx/ext/napoleon/docstring.py +++ b/sphinx/ext/napoleon/docstring.py @@ -197,12 +197,12 @@ class GoogleDocstring(UnicodeMixin): line = self._line_iter.peek() return lines - def _consume_field(self, parse_type_old=True, prefer_type=False): + def _consume_field(self, parse_type=True, prefer_type=False): line = next(self._line_iter) match = None _name, _type, _desc = line.strip(), '', '' - if parse_type_old: + if parse_type: match = _google_typed_arg_regex.match(line) if match: _name = match.group(1) @@ -222,11 +222,11 @@ class GoogleDocstring(UnicodeMixin): _desc = self.__class__(_desc, self._config).lines() return _name, _type, _desc - def _consume_fields(self, parse_type_old=True, prefer_type=False): + def _consume_fields(self, parse_type=True, prefer_type=False): self._consume_empty() fields = [] while not self._is_section_break(): - _name, _type, _desc = self._consume_field(parse_type_old, prefer_type) + _name, _type, _desc = self._consume_field(parse_type, prefer_type) if _name or _type or _desc: fields.append((_name, _type, _desc,)) return fields @@ -462,7 +462,7 @@ class GoogleDocstring(UnicodeMixin): def _parse_methods_section(self, section): lines = [] - for _name, _, _desc in self._consume_fields(parse_type_old=False): + for _name, _, _desc in self._consume_fields(parse_type=False): lines.append('.. method:: %s' % _name) if _desc: lines.extend([''] + self._indent(_desc, 3)) @@ -698,9 +698,9 @@ class NumpyDocstring(GoogleDocstring): super(NumpyDocstring, self).__init__(docstring, config, app, what, name, obj, options) - def _consume_field(self, parse_type_old=True, prefer_type=False): + def _consume_field(self, parse_type=True, prefer_type=False): line = next(self._line_iter) - if parse_type_old: + if parse_type: _name, _, _type = line.partition(':') else: _name, _type = line, '' From e65287ec9c10ffb706017dc671db20a4bfdaede6 Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Sun, 10 Aug 2014 11:14:47 +0200 Subject: [PATCH 12/13] C++, fix cpp:parent bug and add all aliases of fundamental integer types. --- sphinx/domains/cpp.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 446a77584..199c8ea4b 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -157,24 +157,24 @@ _operator_re = re.compile(r'''(?x) _id_prefix = '_CPP' _id_fundamental = { # not all of these are actually parsed as fundamental types, TODO: do that 'void' : 'v', - 'wchar_t' : 'w', 'bool' : 'b', 'char' : 'c', 'signed char' : 'a', 'unsigned char' : 'h', - 'short' : 's', - 'unsigned short' : 't', - 'int' : 'i', - 'unsigned int' : 'j', - 'long' : 'l', - 'unsigned long' : 'm', - 'long long' : 'x', - 'unsigned long long' : 'y', + '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', - 'char32_t' : 'Di', - 'char16_t' : 'Ds', 'auto' : 'Da', 'decltype(auto)' : 'Dc', 'std::nullptr_t' : 'Dn' @@ -1519,7 +1519,10 @@ class CPPClassObject(CPPObject): def before_content(self): lastname = self.env.temp_data['cpp:lastname'] assert lastname - self.env.temp_data['cpp:parent'].append(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() From e43fcfa1f48cd2adf988356c21db15f61850fdaf Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Sun, 10 Aug 2014 11:59:22 +0200 Subject: [PATCH 13/13] C++, test now also work with Python 3. --- tests/test_cpp_domain.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_cpp_domain.py b/tests/test_cpp_domain.py index 7be8fe3f4..38d167545 100644 --- a/tests/test_cpp_domain.py +++ b/tests/test_cpp_domain.py @@ -31,9 +31,9 @@ def check(name, input, output=None): ast = parse(name, input) res = text_type(ast) if res != output: - print "Input: ", text_type(input) - print "Result: ", res - print "Expected: ", output + print("Input: ", text_type(input)) + print("Result: ", res) + print("Expected: ", output) raise DefinitionError("") ast.describe_signature([], 'lastIsName', None) ast.prefixedName = ast.name # otherwise the get_id fails, it would be set in handle_signarue