From f64ec1349a3a4a7644f02264cd10a3c5ad32b468 Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Sun, 26 Jun 2016 10:00:54 +0200 Subject: [PATCH] C++, initial support for attributes. Only simple GNU style supported, and only in the decl-specifier-list. See sphinx-doc/sphinx#2682. --- sphinx/domains/cpp.py | 95 ++++++++++++++++++++++++++++++++++++++-- tests/test_domain_cpp.py | 13 ++++++ 2 files changed, 104 insertions(+), 4 deletions(-) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index cf935bf22b..c7d9d49382 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -540,6 +540,40 @@ def _verify_description_mode(mode): raise Exception("Description mode '%s' is invalid." % mode) +class ASTGnuAttribute(ASTBase): + def __init__(self, name, args): + self.name = name + self.args = args + + def __unicode__(self): + res = [self.name] + if self.args: + res.append('(') + res.append(text_type(self.args)) + res.append(')') + return ''.join(res) + + +class ASTGnuAttributeList(ASTBase): + def __init__(self, attrs): + self.attrs = attrs + + def __unicode__(self): + res = ['__attribute__(('] + first = True + for attr in self.attrs: + if not first: + res.append(', ') + first = False + res.append(text_type(attr)) + res.append('))') + return ''.join(res) + + def describe_signature(self, signode): + txt = text_type(self) + signode.append(nodes.Text(txt, txt)) + + class ASTIdentifier(ASTBase): def __init__(self, identifier): assert identifier is not None @@ -1256,7 +1290,7 @@ class ASTParametersQualifiers(ASTBase): class ASTDeclSpecsSimple(ASTBase): def __init__(self, storage, threadLocal, inline, virtual, explicit, - constexpr, volatile, const, friend): + constexpr, volatile, const, friend, attrs): self.storage = storage self.threadLocal = threadLocal self.inline = inline @@ -1266,6 +1300,7 @@ class ASTDeclSpecsSimple(ASTBase): self.volatile = volatile self.const = const self.friend = friend + self.attrs = attrs def mergeWith(self, other): if not other: @@ -1278,10 +1313,12 @@ class ASTDeclSpecsSimple(ASTBase): self.constexpr or other.constexpr, self.volatile or other.volatile, self.const or other.const, - self.friend or other.friend) + self.friend or other.friend, + self.attrs + other.attrs) def __unicode__(self): res = [] + res.extend(text_type(attr) for attr in self.attrs) if self.storage: res.append(self.storage) if self.threadLocal: @@ -1307,6 +1344,10 @@ class ASTDeclSpecsSimple(ASTBase): if len(modifiers) > 0: modifiers.append(nodes.Text(' ')) modifiers.append(addnodes.desc_annotation(text, text)) + for attr in self.attrs: + if len(modifiers) > 0: + modifiers.append(nodes.Text(' ')) + modifiers.append(attr.describe_signature(modifiers)) if self.storage: _add(modifiers, self.storage) if self.threadLocal: @@ -2785,6 +2826,12 @@ class DefinitionParser(object): return True return False + def skip_string_and_ws(self, string): + if self.skip_string(string): + self.skip_ws() + return True + return False + @property def eof(self): return self.pos >= self.end @@ -2811,6 +2858,38 @@ class DefinitionParser(object): if not self.eof: self.fail('Expected end of definition.') + def _parse_attribute(self): + self.skip_ws() + # try C++11 style + # TODO: implement + + # try GNU style + if self.skip_word_and_ws('__attribute__'): + if not self.skip_string_and_ws('('): + self.fail("Expected '(' after '__attribute__'.") + if not self.skip_string_and_ws('('): + self.fail("Expected '(' after '__attribute__('.") + attrs = [] + while 1: + if self.match(_identifier_re): + name = self.matched_text + self.skip_ws() + if self.skip_string_and_ws('('): + self.fail('Parameterized GNU style attribute not yet supported.') + attrs.append(ASTGnuAttribute(name, None)) + # TODO: parse arguments for the attribute + if self.skip_string_and_ws(','): + continue + elif self.skip_string_and_ws(')'): + break + else: + self.fail("Expected identifier, ')', or ',' in __attribute__.") + if not self.skip_string_and_ws(')'): + self.fail("Expected ')' after '__attribute__((...)'") + return ASTGnuAttributeList(attrs) + + return None + def _parse_expression(self, end): # Stupidly "parse" an expression. # 'end' should be a list of characters which ends the expression. @@ -3092,6 +3171,7 @@ class DefinitionParser(object): volatile = None const = None friend = None + attrs = [] while 1: # accept any permutation of a subset of some decl-specs self.skip_ws() if not storage: @@ -3145,9 +3225,14 @@ class DefinitionParser(object): const = self.skip_word('const') if const: continue + attr = self._parse_attribute() + if attr: + attrs.append(attr) + continue break return ASTDeclSpecsSimple(storage, threadLocal, inline, virtual, - explicit, constexpr, volatile, const, friend) + explicit, constexpr, volatile, const, + friend, attrs) def _parse_decl_specs(self, outer, typed=True): if outer: @@ -4144,7 +4229,9 @@ class CPPDomain(Domain): print("Type is %s" % typ) assert False if not checkType(): - warner.warn("cpp:%s targets a %s." % (typ, s.declaration.objectType)) + warner.warn("cpp:%s targets a %s (%s)." + % (typ, s.declaration.objectType, + s.get_full_nested_name())) declaration = s.declaration fullNestedName = s.get_full_nested_name() diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index 778ac1c55c..5d24de0582 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -402,6 +402,19 @@ def test_templates(): "RK18c_string_view_baseIK4Char6TraitsE") +def test_attributes(): + # style: GNU + check('member', '__attribute__(()) int f', 'f__i', '1f') + check('member', '__attribute__((a)) int f', 'f__i', '1f') + check('member', '__attribute__((a, b)) int f', 'f__i', '1f') + + # position: decl specs + check('function', 'static inline __attribute__(()) void f()', + 'f', '1fv', + output='__attribute__(()) static inline void f()') + + + # def test_print(): # # used for getting all the ids out for checking # for a in ids: