C++, add support for concept declarations.

Concept placeholders are automatically linked.
Thanks to mickk-on-cpp.
This commit is contained in:
Jakob Lykke Andersen 2016-05-28 15:31:03 +09:00
parent 1825559f74
commit 74191207db
4 changed files with 162 additions and 9 deletions

View File

@ -17,6 +17,7 @@ Features added
* #2463, #2516: Add keywords of "meta" directive to search index
* ``:maxdepth:`` option of toctree affects ``secnumdepth`` (ref: #2547)
* #2575: Now ``sphinx.ext.graphviz`` allows ``:align:`` option
* C++, added concept directive. Thanks to mickk-on-cpp.
Bugs fixed
----------

View File

@ -685,6 +685,61 @@ a visibility statement (``public``, ``private`` or ``protected``).
.. cpp::enumerator:: MyEnum::myOtherEnumerator = 42
.. rst:directive:: .. cpp:concept:: template-parameter-list name
.. cpp:concept:: template-parameter-list name()
.. warning:: The support for concepts is experimental. It is based on the
Concepts Technical Specification, and the features may change as the TS evolves.
Describe a variable concept or a function concept. Both must have exactly 1
template parameter list. The name may be a nested name. Examples::
.. cpp:concept:: template<typename It> std::Iterator
Proxy to an element of a notional sequence that can be compared,
indirected, or incremented.
.. cpp:concept:: template<typename Cont> std::Container()
Holder of elements, to which it can provide access via
:cpp:concept:`Iterator` s.
They will render as follows:
.. cpp:concept:: template<typename It> std::Iterator
Proxy to an element of a notional sequence that can be compared,
indirected, or incremented.
.. cpp:concept:: template<typename Cont> std::Container()
Holder of elements, to which it can provide access via
:cpp:concept:`Iterator` s.
Constrained Templates
~~~~~~~~~~~~~~~~~~~~~
.. warning:: The support for constrained templates is experimental. It is based on the
Concepts Technical Specification, and the features may change as the TS evolves.
.. note:: Sphinx does not currently support ``requires`` clauses.
Placeholders
............
Declarations may use the name of a concept to introduce constrained template
parameters, or the keyword ``auto`` to introduce unconstrained template parameters::
.. cpp:function:: void f(auto &&arg)
A function template with a single unconstrained template parameter.
.. cpp:function:: void f(std::Iterator it)
A function template with a single template parameter, constrained by the
Iterator concept.
Namespacing
~~~~~~~~~~~~~~~~~
@ -790,6 +845,7 @@ These roles link to the given declaration types:
cpp:member
cpp:var
cpp:type
cpp:concept
cpp:enum
cpp:enumerator
@ -888,7 +944,6 @@ References to partial specialisations must always include the template parameter
Currently the lookup only succeed if the template parameter identifiers are equal strings.
The Standard Domain
-------------------

View File

@ -54,7 +54,8 @@ from sphinx.util.docfields import Field, GroupedField
----------------------------------------------------------------------------
See http://www.nongnu.org/hcb/ for the grammar,
or https://github.com/cplusplus/draft/blob/master/source/grammar.tex
and https://github.com/cplusplus/draft/blob/master/source/grammar.tex,
and https://github.com/cplusplus/concepts-ts
for the newest grammar.
common grammar things:
@ -201,6 +202,17 @@ from sphinx.util.docfields import Field, GroupedField
We additionally add the possibility for specifying the visibility as the
first thing.
concept_object:
goal:
just a declaration of the name (for now)
either a variable concept or function concept
grammar: only a single template parameter list, and the nested name
may not have any template argument lists
"template" "<" template-parameter-list ">"
nested-name-specifier "()"[opt]
type_object:
goal:
either a single type (e.g., "MyClass:Something_T" or a typedef-like
@ -2025,6 +2037,34 @@ class ASTTypeUsing(ASTBase):
self.type.describe_signature(signode, 'markType', env, symbol=symbol)
class ASTConcept(ASTBase):
def __init__(self, nestedName, isFunction):
self.nestedName = nestedName
self.isFunction = isFunction # otherwise it's a variable concept
@property
def name(self):
return self.nestedName
def get_id_v1(self, objectType=None, symbol=None):
raise NoOldIdError()
def get_id_v2(self, objectType, symbol):
return symbol.get_full_nested_name().get_id_v2()
def __unicode__(self):
res = text_type(self.nestedName)
if self.isFunction:
res += "()"
return res
def describe_signature(self, signode, mode, env, symbol):
signode += nodes.Text(text_type("bool "))
self.nestedName.describe_signature(signode, mode, env, symbol)
if self.isFunction:
signode += nodes.Text("()")
class ASTBaseClass(ASTBase):
def __init__(self, name, visibility, virtual, pack):
self.name = name
@ -2233,6 +2273,8 @@ class ASTDeclaration(ASTBase):
prefix = self.declaration.get_type_declaration_prefix()
prefix += ' '
mainDeclNode += addnodes.desc_annotation(prefix, prefix)
elif self.objectType == 'concept':
mainDeclNode += addnodes.desc_annotation('concept ', 'concept ')
elif self.objectType == 'member':
pass
elif self.objectType == 'function':
@ -3427,6 +3469,18 @@ class DefinitionParser(object):
type = self._parse_type(False, None)
return ASTTypeUsing(name, type)
def _parse_concept(self):
nestedName = self._parse_nested_name()
isFunction = False
self.skip_ws()
if self.skip_string('('):
isFunction = True
self.skip_ws()
if not self.skip_string(')'):
self.fail("Expected ')' in function concept declaration.")
return ASTConcept(nestedName, isFunction)
def _parse_class(self):
name = self._parse_nested_name()
self.skip_ws()
@ -3545,15 +3599,19 @@ class DefinitionParser(object):
prevErrors.append((e, ""))
raise self._make_multi_error(prevErrors, header)
def _parse_template_declaration_prefix(self):
def _parse_template_declaration_prefix(self, objectType):
templates = []
while 1:
self.skip_ws()
if not self.skip_word("template"):
break
if objectType == 'concept' and len(templates) > 0:
self.fail("More than 1 template parameter list for concept.")
params = self._parse_template_parameter_list()
templates.append(params)
if len(templates) == 0:
if objectType == 'concept':
self.fail('Missing template parameter list for concept.')
return None
else:
return ASTTemplateDeclarationPrefix(templates)
@ -3591,7 +3649,7 @@ class DefinitionParser(object):
return templatePrefix
def parse_declaration(self, objectType):
if objectType not in ('type', 'member',
if objectType not in ('type', 'concept', 'member',
'function', 'class', 'enum', 'enumerator'):
raise Exception('Internal error, unknown objectType "%s".' % objectType)
visibility = None
@ -3602,8 +3660,8 @@ class DefinitionParser(object):
if self.match(_visibility_re):
visibility = self.matched_text
if objectType in ('type', 'member', 'function', 'class'):
templatePrefix = self._parse_template_declaration_prefix()
if objectType in ('type', 'concept', 'member', 'function', 'class'):
templatePrefix = self._parse_template_declaration_prefix(objectType)
if objectType == 'type':
prevErrors = []
@ -3623,6 +3681,8 @@ class DefinitionParser(object):
prevErrors.append((e, "If type alias or template alias"))
header = "Error in type declaration."
raise self._make_multi_error(prevErrors, header)
elif objectType == 'concept':
declaration = self._parse_concept()
elif objectType == 'member':
declaration = self._parse_type_with_init(named=True, outer='member')
elif objectType == 'function':
@ -3642,7 +3702,7 @@ class DefinitionParser(object):
templatePrefix, declaration)
def parse_namespace_object(self):
templatePrefix = self._parse_template_declaration_prefix()
templatePrefix = self._parse_template_declaration_prefix(objectType="namespace")
name = self._parse_nested_name()
templatePrefix = self._check_template_consistency(name, templatePrefix,
fullSpecShorthand=False)
@ -3651,7 +3711,7 @@ class DefinitionParser(object):
return res
def parse_xref_object(self):
templatePrefix = self._parse_template_declaration_prefix()
templatePrefix = self._parse_template_declaration_prefix(objectType="xref")
name = self._parse_nested_name()
templatePrefix = self._check_template_consistency(name, templatePrefix,
fullSpecShorthand=True)
@ -3812,6 +3872,26 @@ class CPPTypeObject(CPPObject):
ast.describe_signature(signode, 'lastIsName', self.env)
class CPPConceptObject(CPPObject):
def get_index_text(self, name):
return _('%s (C++ concept)') % name
def before_content(self):
lastSymbol = self.env.ref_context['cpp:last_symbol']
assert lastSymbol
self.oldParentSymbol = self.env.ref_context['cpp:parent_symbol']
self.env.ref_context['cpp:parent_symbol'] = lastSymbol
def after_content(self):
self.env.ref_context['cpp:parent_symbol'] = self.oldParentSymbol
def parse_definition(self, parser):
return parser.parse_declaration("concept")
def describe_signature(self, signode, ast):
ast.describe_signature(signode, 'lastIsName', self.env)
class CPPMemberObject(CPPObject):
def get_index_text(self, name):
return _('%s (C++ member)') % name
@ -4026,6 +4106,7 @@ class CPPDomain(Domain):
'function': ObjType(l_('function'), 'func'),
'member': ObjType(l_('member'), 'member'),
'type': ObjType(l_('type'), 'type'),
'concept': ObjType(l_('concept'), 'concept'),
'enum': ObjType(l_('enum'), 'enum'),
'enumerator': ObjType(l_('enumerator'), 'enumerator')
}
@ -4036,6 +4117,7 @@ class CPPDomain(Domain):
'member': CPPMemberObject,
'var': CPPMemberObject,
'type': CPPTypeObject,
'concept': CPPConceptObject,
'enum': CPPEnumObject,
'enum-struct': CPPEnumObject,
'enum-class': CPPEnumObject,
@ -4051,6 +4133,7 @@ class CPPDomain(Domain):
'member': CPPXRefRole(),
'var': CPPXRefRole(),
'type': CPPXRefRole(),
'concept': CPPXRefRole(),
'enum': CPPXRefRole(),
'enumerator': CPPXRefRole()
}

View File

@ -50,7 +50,7 @@ def check(name, input, idv1output=None, idv2output=None, output=None):
print("Expected: ", output)
raise DefinitionError("")
rootSymbol = Symbol(None, None, None, None, None, None)
symbol = rootSymbol.add_declaration(ast, docname="Test")
symbol = rootSymbol.add_declaration(ast, docname="TestDoc")
parentNode = addnodes.desc()
signode = addnodes.desc_signature(input, '')
parentNode += signode
@ -133,6 +133,20 @@ def test_type_definitions():
check('type', 'A = B', None, '1A')
def test_concept_definitions():
check('concept', 'template<typename Param> A::B::Concept',
None, 'I0EN1A1B7ConceptE')
check('concept', 'template<typename A, typename B, typename ...C> Foo',
None, 'I00DpE3Foo')
check('concept', 'template<typename Param> A::B::Concept()',
None, 'I0EN1A1B7ConceptE')
check('concept', 'template<typename A, typename B, typename ...C> Foo()',
None, 'I00DpE3Foo')
raises(DefinitionError, parse, 'concept', 'Foo')
raises(DefinitionError, parse, 'concept',
'template<typename T> template<typename U> Foo')
def test_member_definitions():
check('member', ' const std::string & name = 42',
"name__ssCR", "4name", output='const std::string &name = 42')