C++, initial enum support (see #772)

This commit is contained in:
Jakob Lykke Andersen 2015-01-03 16:59:08 +01:00
parent 90c7adba6d
commit c010b47589
4 changed files with 256 additions and 21 deletions

View File

@ -187,7 +187,7 @@ Bugs fixed
Thanks to Takeshi Komiya.
* PR#265: Fix could not capture caption of graphviz node by xref. Thanks to
Takeshi Komiya.
* PR#263, #1013, #1103: Rewrite of C++ domain. Thanks to Jakob Lykke Andersen.
* PR#263, #1013, #1103, #772: Rewrite of C++ domain. Thanks to Jakob Lykke Andersen.
* Hyperlinks to all found nested names and template arguments (#1103).
* Support for function types everywhere, e.g., in
@ -203,6 +203,8 @@ Bugs fixed
* Index names may be different now. Elements are indexed by their fully
qualified name. It should be rather easy to change this behaviour and
potentially index by namespaces/classes as well.
* Support for scoped and unscoped enums. Enumerators in unscoped enums
are injected into the parent scope in addition to the enum scope.
* PR#258, #939: Add dedent option for :rst:dir:`code-block` and
:rst:dir:`literal-include`. Thanks to Zafar Siddiqui.

View File

@ -520,8 +520,8 @@ The C++ Domain
The C++ domain (name **cpp**) supports documenting C++ projects.
The following directives are available. All declarations can start with a visibility statement
(``public``, ``private`` or ``protected``).
The following directives are available. All declarations except for enumerators
can start with a visibility statement (``public``, ``private`` or ``protected``).
.. rst:directive:: .. cpp:class:: class speicifer
@ -578,6 +578,35 @@ The following directives are available. All declarations can start with a visibi
Declaration of a type alias with unspecified type.
.. rst:directive:: .. cpp:enum:: enum declaration
Describe a (scoped) enum, possibly with the underlying type specified. Note that for scoped
enums the ``struct``/``class`` keyword must come before the optional visibility specifier.
Any enumerators declared inside an unscoped enum will be declared both in the enum scope
and in the parent scope.
Examples:
.. cpp:enum:: MyEnum
An unscoped enum.
.. cpp:enum:: MySpecificEnum : long
An unscoped enum with specified underlying type.
.. cpp:enum:: class MyScopedEnum
A scoped enum.
.. cpp:enum:: struct protected MyScopedVisibilityEnum : std::underlying_type<MySpecificEnum>::type
A scoped enum with non-default visibility, and with a specified underlying type.
.. rst:directive:: .. cpp:enumerator:: name
.. cpp:enumerator:: name = constant
Describe an enumerator, optionally with its value defined.
.. rst:directive:: .. cpp:namespace:: namespace
Select the current namespace for the following objects. Note that the namespace

View File

@ -108,7 +108,15 @@
memberFunctionInit -> "=" "0"
# (note: only "0" is allowed as the value, according to the standard,
# right?)
enum-head ->
enum-key attribute-specifier-seq[opt] nested-name-specifier[opt] identifier enum-base[opt]
enum-key -> "enum" | "enum struct" | "enum class"
enum-base ->
":" type
enumerator-definition ->
identifier
| identifier "=" constant-expression
We additionally add the possibility for specifying the visibility as the
first thing.
@ -127,7 +135,6 @@
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
@ -138,6 +145,26 @@
goal: a function declaration, TODO: what about templates? for now: skip
grammar: no initializer
decl-specifier-seq declerator
class_object:
goal: a class declaration, but with specification of a base class, TODO: what about templates? for now: skip
grammar:
nested-name
| nested-name ":" 'comma-separated list of nested-name optionally with visibility'
enum_object:
goal: an unscoped enum or a scoped enum, optionally with the underlying type specified
grammar:
("class" | "struct")[opt] visibility[opt] nested-name (":" type)[opt]
enumerator_object:
goal: an element in a scoped or unscoped enum. The name should be injected according to the scopedness.
grammar:
nested-name ("=" constant-expression)
namespace_object:
goal: a directive to put all following declarations in a specific scope
grammar:
nested-name
"""
import re
@ -1003,12 +1030,13 @@ class ASTBaseClass(ASTBase):
signode += addnodes.desc_annotation(
self.visibility, self.visibility)
signode += nodes.Text(' ')
self.name.describe_signature(signode, mode, env)
self.name.describe_signature(signode, mode, env)
class ASTClass(ASTBase):
def __init__(self, name, bases):
def __init__(self, name, visibility, bases):
self.name = name
self.visibility = visibility
self.bases = bases
def get_id(self):
@ -1016,6 +1044,9 @@ class ASTClass(ASTBase):
def __unicode__(self):
res = []
if self.visibility != 'public':
res.append(self.visibility)
res.append(' ')
res.append(text_type(self.name))
if len(self.bases) > 0:
res.append(' : ')
@ -1029,6 +1060,10 @@ class ASTClass(ASTBase):
def describe_signature(self, signode, mode, env):
_verify_description_mode(mode)
if self.visibility != 'public':
signode += addnodes.desc_annotation(
self.visibility, self.visibility)
signode += nodes.Text(' ')
self.name.describe_signature(signode, mode, env)
if len(self.bases) > 0:
signode += nodes.Text(' : ')
@ -1037,6 +1072,63 @@ class ASTClass(ASTBase):
signode += nodes.Text(', ')
signode.pop()
class ASTEnum(ASTBase):
def __init__(self, name, visibility, scoped, underlyingType):
self.name = name
self.visibility = visibility
self.scoped = scoped
self.underlyingType = underlyingType
def get_id(self):
return _id_prefix + self.prefixedName.get_id()
def __unicode__(self):
res = []
if self.scoped:
res.append(self.scoped)
res.append(' ')
if self.visibility != 'public':
res.append(self.visibility)
res.append(' ')
res.append(text_type(self.name))
if self.underlyingType:
res.append(' : ')
res.append(text_type(self.underlyingType))
return u''.join(res)
def describe_signature(self, signode, mode, env):
_verify_description_mode(mode)
# self.scoped has been done by the CPPEnumObject
if self.visibility != 'public':
signode += addnodes.desc_annotation(
self.visibility, self.visibility)
signode += nodes.Text(' ')
self.name.describe_signature(signode, mode, env)
if self.underlyingType:
signode += nodes.Text(' : ')
self.underlyingType.describe_signature(signode, 'noneIsName', env)
class ASTEnumerator(ASTBase):
def __init__(self, name, init):
self.name = name
self.init = init
def get_id(self):
return _id_prefix + self.prefixedName.get_id()
def __unicode__(self):
res = []
res.append(text_type(self.name))
if self.init:
res.append(text_type(self.init))
return u''.join(res)
def describe_signature(self, signode, mode, env):
_verify_description_mode(mode)
self.name.describe_signature(signode, mode, env)
if self.init:
self.init.describe_signature(signode, 'noneIsName')
class DefinitionParser(object):
# those without signedness and size modifiers
@ -1583,6 +1675,9 @@ class DefinitionParser(object):
return ASTTypeWithInit(type, init)
def _parse_class(self):
classVisibility = 'public'
if self.match(_visibility_re):
classVisibility = self.matched_text
name = self._parse_nested_name()
bases = []
self.skip_ws()
@ -1599,8 +1694,35 @@ class DefinitionParser(object):
continue
else:
break
return ASTClass(name, bases)
return ASTClass(name, classVisibility, bases)
def _parse_enum(self):
scoped = None
self.skip_ws()
if self.skip_word_and_ws('class'):
scoped = 'class'
elif self.skip_word_and_ws('struct'):
scoped = 'struct'
visibility = 'public'
if self.match(_visibility_re):
visibility = self.matched_text
self.skip_ws()
name = self._parse_nested_name()
self.skip_ws()
underlyingType = None
if self.skip_string(':'):
underlyingType = self._parse_type()
return ASTEnum(name, visibility, scoped, underlyingType)
def _parse_enumerator(self):
name = self._parse_nested_name()
self.skip_ws()
init = None
if self.skip_string('='):
self.skip_ws()
init = ASTInitializer(self.read_rest())
return ASTEnumerator(name, init)
def parse_type_object(self):
res = self._parse_type(outer='type')
res.objectType = 'type'
@ -1620,6 +1742,16 @@ class DefinitionParser(object):
res = self._parse_class()
res.objectType = 'class'
return res
def parse_enum_object(self):
res = self._parse_enum()
res.objectType = 'enum'
return res
def parse_enumerator_object(self):
res = self._parse_enumerator()
res.objectType = 'enumerator'
return res
def parse_namespace_object(self):
res = self._parse_nested_name()
@ -1658,14 +1790,27 @@ class CPPObject(ObjectDescription):
signode['first'] = (not self.names)
self.state.document.note_explicit_target(signode)
if not name in objects:
objects.setdefault(name,
(self.env.docname, ast.objectType, theid))
objects.setdefault(name, (self.env.docname, ast))
if ast.objectType == 'enumerator':
# find the parent, if it exists && is an enum && it's unscoped, then add the name to the parent scope
assert len(ast.prefixedName.names) > 0
parentPrefixedAstName = ASTNestedName(ast.prefixedName.names[:-1])
parentPrefixedName = text_type(parentPrefixedAstName)
if parentPrefixedName in objects:
docname, parentAst = objects[parentPrefixedName]
if parentAst.objectType == 'enum' and not parentAst.scoped:
enumeratorName = ASTNestedName([ast.prefixedName.names[-1]])
assert len(parentAst.prefixedName.names) > 0
enumScope = ASTNestedName(parentAst.prefixedName.names[:-1])
unscopedName = enumeratorName.prefix_nested_name(enumScope)
txtUnscopedName = text_type(unscopedName)
if not txtUnscopedName in objects:
objects.setdefault(txtUnscopedName, (self.env.docname, ast))
# 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))
objects.setdefault(uninstantiated, (self.env.docname, ast))
self.env.temp_data['cpp:lastname'] = ast.prefixedName
indextext = self.get_index_text(name)
@ -1757,6 +1902,45 @@ class CPPClassObject(CPPObject):
def describe_signature(self, signode, ast):
signode += addnodes.desc_annotation('class ', 'class ')
ast.describe_signature(signode, 'lastIsName', self.env)
class CPPEnumObject(CPPObject):
def get_index_text(self, name):
return _('%s (C++ enum)') % name
def before_content(self):
lastname = self.env.temp_data['cpp:lastname']
assert 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()
def parse_definition(self, parser):
return parser.parse_enum_object()
def describe_signature(self, signode, ast):
prefix = 'enum '
if ast.scoped:
prefix += ast.scoped
prefix += ' '
signode += addnodes.desc_annotation(prefix, prefix)
ast.describe_signature(signode, 'lastIsName', self.env)
class CPPEnumeratorObject(CPPObject):
def get_index_text(self, name):
return _('%s (C++ enumerator)') % name
def parse_definition(self, parser):
return parser.parse_enumerator_object()
def describe_signature(self, signode, ast):
signode += addnodes.desc_annotation('enumerator ', 'enumerator ')
ast.describe_signature(signode, 'lastIsName', self.env)
class CPPNamespaceObject(Directive):
@ -1804,7 +1988,6 @@ class CPPXRefRole(XRefRole):
title = title[dcolon + 2:]
return title, target
class CPPDomain(Domain):
"""C++ language domain."""
name = 'cpp'
@ -1813,7 +1996,9 @@ class CPPDomain(Domain):
'class': ObjType(l_('class'), 'class'),
'function': ObjType(l_('function'), 'func'),
'member': ObjType(l_('member'), 'member'),
'type': ObjType(l_('type'), 'type')
'type': ObjType(l_('type'), 'type'),
'enum': ObjType(l_('enum'), 'enum'),
'enumerator': ObjType(l_('enumerator'), 'enumerator')
}
directives = {
@ -1821,16 +2006,20 @@ class CPPDomain(Domain):
'function': CPPFunctionObject,
'member': CPPMemberObject,
'type': CPPTypeObject,
'enum': CPPEnumObject,
'enumerator': CPPEnumeratorObject,
'namespace': CPPNamespaceObject
}
roles = {
'class': CPPXRefRole(),
'func': CPPXRefRole(fix_parens=True),
'member': CPPXRefRole(),
'type': CPPXRefRole()
'type': CPPXRefRole(),
'enum': CPPXRefRole(),
'enumerator': CPPXRefRole()
}
initial_data = {
'objects': {}, # prefixedName -> (docname, objectType, id)
'objects': {}, # prefixedName -> (docname, ast)
}
def clear_doc(self, docname):
@ -1847,8 +2036,8 @@ class CPPDomain(Domain):
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,
docname, ast = self.data['objects'][name]
return make_refnode(builder, fromdocname, docname, ast.get_id(), contnode,
name)
parser = DefinitionParser(target)
@ -1874,5 +2063,5 @@ class CPPDomain(Domain):
return None
def get_objects(self):
for refname, (docname, type, theid) in iteritems(self.data['objects']):
yield (refname, refname, type, docname, refname, 1)
for refname, (docname, ast) in iteritems(self.data['objects']):
yield (refname, refname, ast.objectType, docname, refname, 1)

View File

@ -108,6 +108,21 @@ def test_type_definitions():
raises(DefinitionError, parse, 'function', 'int foo(D d=x(a')
check('function', 'int foo(const A&... a)')
check('function', 'virtual void f()')
check('class', 'public A', 'A')
check('class', 'private A')
check('enum', 'A')
check('enum', 'A : std::underlying_type<B>::type')
check('enum', 'struct A')
check('enum', 'struct A : unsigned int')
check('enum', 'class A')
check('enum', 'class A : unsigned int')
check('enum', 'class public A', 'class A')
check('enum', 'class private A')
check('enumerator', 'A')
check('enumerator', 'A = std::numeric_limits<unsigned long>::max()')
def test_bases():
check('class', 'A')