mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
C++, increase support for attributes.
User-defined attributes and simple C++11 style attributes. Closes sphinx-doc/sphinx#2682.
This commit is contained in:
parent
86f3e5aabb
commit
c85e60a5ac
3
CHANGES
3
CHANGES
@ -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
|
||||
----------
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
-------------------
|
||||
|
@ -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')
|
||||
|
@ -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()',
|
||||
|
Loading…
Reference in New Issue
Block a user