C++, increase support for attributes.

User-defined attributes and simple C++11 style attributes.
Closes sphinx-doc/sphinx#2682.
This commit is contained in:
Jakob Lykke Andersen 2016-08-06 00:08:02 +02:00
parent 86f3e5aabb
commit c85e60a5ac
5 changed files with 148 additions and 5 deletions

View File

@ -87,6 +87,9 @@ Features added
* Python domain signature parser now uses the xref mixin for 'exceptions',
allowing exception classes to be autolinked.
* #2513: Add `latex_engine` to switch the LaTeX engine by conf.py
* #2682: C++, basic support for attributes (C++11 style and GNU style).
The new configuration variables 'cpp_id_attributes' and 'cpp_paren_attributes'
can be used to introduce custom attributes.
Bugs fixed
----------

View File

@ -1999,3 +1999,26 @@ Options for the XML builder
constructs ``*``, ``?``, ``[...]`` and ``[!...]`` with the feature that
these all don't match slashes. A double star ``**`` can be used to match
any sequence of characters *including* slashes.
.. _cpp-config:
Options for the C++ domain
--------------------------
.. confval:: cpp_id_attributes
A list of strings that the parser additionally should accept as attributes.
This can for example be used when attributes have been ``#define`` d for portability.
.. versionadded:: 1.5
.. confval:: cpp_paren_attributes
A list of strings that the parser additionally should accept as attributes with one argument.
That is, if ``my_align_as`` is in the list, then ``my_align_as(X)`` is parsed as an attribute
for all strings ``X`` that have balanced brances (``()``, ``[]``, and ``{}``).
This can for example be used when attributes have been ``#define`` d for portability.
.. versionadded:: 1.5

View File

@ -900,6 +900,11 @@ References to partial specialisations must always include the template parameter
Currently the lookup only succeed if the template parameter identifiers are equal strings.
Configuration Variables
~~~~~~~~~~~~~~~~~~~~~~~
See :ref:`cpp-config`.
The Standard Domain
-------------------

View File

@ -540,6 +540,18 @@ def _verify_description_mode(mode):
raise Exception("Description mode '%s' is invalid." % mode)
class ASTCPPAttribute(ASTBase):
def __init__(self, arg):
self.arg = arg
def __unicode__(self):
return "[[" + self.arg + "]]"
def describe_signature(self, signode):
txt = text_type(self)
signode.append(nodes.Text(txt, txt))
class ASTGnuAttribute(ASTBase):
def __init__(self, name, args):
self.name = name
@ -574,6 +586,34 @@ class ASTGnuAttributeList(ASTBase):
signode.append(nodes.Text(txt, txt))
class ASTIdAttribute(ASTBase):
"""For simple attributes defined by the user."""
def __init__(self, id):
self.id = id
def __unicode__(self):
return self.id
def describe_signature(self, signode):
signode.append(nodes.Text(self.id, self.id))
class ASTParenAttribute(ASTBase):
"""For paren attributes defined by the user."""
def __init__(self, id, arg):
self.id = id
self.arg = arg
def __unicode__(self):
return self.id + '(' + self.arg + ')'
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
@ -2750,7 +2790,7 @@ class DefinitionParser(object):
_prefix_keys = ('class', 'struct', 'enum', 'union', 'typename')
def __init__(self, definition, warnEnv):
def __init__(self, definition, warnEnv, config):
self.definition = definition.strip()
self.pos = 0
self.end = len(self.definition)
@ -2758,6 +2798,7 @@ class DefinitionParser(object):
self._previous_state = (0, None)
self.warnEnv = warnEnv
self.config = config
def _make_multi_error(self, errors, header):
if len(errors) == 1:
@ -2858,10 +2899,41 @@ class DefinitionParser(object):
if not self.eof:
self.fail('Expected end of definition.')
def _parse_balanced_token_seq(self, end):
# TODO: add handling of string literals and similar
brackets = {'(': ')', '[': ']', '{': '}'}
startPos = self.pos
symbols = []
while not self.eof:
if len(symbols) == 0 and self.current_char in end:
break
if self.current_char in brackets.keys():
symbols.append(brackets[self.current_char])
elif len(symbols) > 0 and self.current_char == symbols[-1]:
symbols.pop()
elif self.current_char in ")]}":
self.fail("Unexpected '%s' in balanced-token-seq." % self.current_char)
self.pos += 1
if self.eof:
self.fail("Could not find end of balanced-token-seq starting at %d."
% startPos)
return self.definition[startPos:self.pos]
def _parse_attribute(self):
self.skip_ws()
# try C++11 style
# TODO: implement
startPos = self.pos
if self.skip_string_and_ws('['):
if not self.skip_string('['):
self.pos = startPos
else:
# TODO: actually implement the correct grammar
arg = self._parse_balanced_token_seq(end=[']'])
if not self.skip_string_and_ws(']'):
self.fail("Expected ']' in end of attribute.")
if not self.skip_string_and_ws(']'):
self.fail("Expected ']' in end of attribute after [[...]")
return ASTCPPAttribute(arg)
# try GNU style
if self.skip_word_and_ws('__attribute__'):
@ -2888,6 +2960,22 @@ class DefinitionParser(object):
self.fail("Expected ')' after '__attribute__((...)'")
return ASTGnuAttributeList(attrs)
# try the simple id attributes defined by the user
for id in self.config.cpp_id_attributes:
if self.skip_word_and_ws(id):
return ASTIdAttribute(id)
# try the paren attributes defined by the user
for id in self.config.cpp_paren_attributes:
if not self.skip_string_and_ws(id):
continue
if not self.skip_string('('):
self.fail("Expected '(' after user-defined paren-attribute.")
arg = self._parse_balanced_token_seq(end=[')'])
if not self.skip_string(')'):
self.fail("Expected ')' to end user-defined paren-attribute.")
return ASTParenAttribute(id, arg)
return None
def _parse_expression(self, end):
@ -3867,7 +3955,7 @@ class CPPObject(ObjectDescription):
self.env.ref_context['cpp:parent_symbol'] = root
parentSymbol = self.env.ref_context['cpp:parent_symbol']
parser = DefinitionParser(sig, self)
parser = DefinitionParser(sig, self, self.env.config)
try:
ast = self.parse_definition(parser)
parser.assert_end()
@ -4178,7 +4266,7 @@ class CPPDomain(Domain):
if emitWarnings:
env.warn_node(msg, node)
warner = Warner()
parser = DefinitionParser(target, warner)
parser = DefinitionParser(target, warner, env.config)
try:
ast = parser.parse_xref_object()
parser.skip_ws()
@ -4276,3 +4364,5 @@ class CPPDomain(Domain):
def setup(app):
app.add_domain(CPPDomain)
app.add_config_value("cpp_id_attributes", [], 'env')
app.add_config_value("cpp_paren_attributes", [], 'env')

View File

@ -24,7 +24,10 @@ ids = []
def parse(name, string):
parser = DefinitionParser(string, None)
class Config(object):
cpp_id_attributes = ["id_attr"]
cpp_paren_attributes = ["paren_attr"]
parser = DefinitionParser(string, None, Config())
ast = parser.parse_declaration(name)
if not parser.eof:
print("Parsing stopped at", parser.pos)
@ -403,10 +406,29 @@ def test_templates():
def test_attributes():
# style: C++
check('member', '[[]] int f', 'f__i', '1f')
check('member', '[ [ ] ] int f', 'f__i', '1f',
# this will fail when the proper grammar is implemented
output='[[ ]] int f')
check('member', '[[a]] int f', 'f__i', '1f')
# 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')
# style: user-defined id
check('member', 'id_attr int f', 'f__i', '1f')
# style: user-defined paren
check('member', 'paren_attr() int f', 'f__i', '1f')
check('member', 'paren_attr(a) int f', 'f__i', '1f')
check('member', 'paren_attr("") int f', 'f__i', '1f')
check('member', 'paren_attr(()[{}][]{}) int f', 'f__i', '1f')
raises(DefinitionError, parse, 'member', 'paren_attr(() int f')
raises(DefinitionError, parse, 'member', 'paren_attr([) int f')
raises(DefinitionError, parse, 'member', 'paren_attr({) int f')
raises(DefinitionError, parse, 'member', 'paren_attr([)]) int f')
raises(DefinitionError, parse, 'member', 'paren_attr((])) int f')
raises(DefinitionError, parse, 'member', 'paren_attr({]}) int f')
# position: decl specs
check('function', 'static inline __attribute__(()) void f()',