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
+3 -1
View File
@@ -187,7 +187,7 @@ Bugs fixed
Thanks to Takeshi Komiya. Thanks to Takeshi Komiya.
* PR#265: Fix could not capture caption of graphviz node by xref. Thanks to * PR#265: Fix could not capture caption of graphviz node by xref. Thanks to
Takeshi Komiya. 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). * Hyperlinks to all found nested names and template arguments (#1103).
* Support for function types everywhere, e.g., in * 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 * Index names may be different now. Elements are indexed by their fully
qualified name. It should be rather easy to change this behaviour and qualified name. It should be rather easy to change this behaviour and
potentially index by namespaces/classes as well. 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 * PR#258, #939: Add dedent option for :rst:dir:`code-block` and
:rst:dir:`literal-include`. Thanks to Zafar Siddiqui. :rst:dir:`literal-include`. Thanks to Zafar Siddiqui.
+31 -2
View File
@@ -520,8 +520,8 @@ The C++ Domain
The C++ domain (name **cpp**) supports documenting C++ projects. The C++ domain (name **cpp**) supports documenting C++ projects.
The following directives are available. All declarations can start with a visibility statement The following directives are available. All declarations except for enumerators
(``public``, ``private`` or ``protected``). can start with a visibility statement (``public``, ``private`` or ``protected``).
.. rst:directive:: .. cpp:class:: class speicifer .. 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. 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 .. rst:directive:: .. cpp:namespace:: namespace
Select the current namespace for the following objects. Note that the namespace Select the current namespace for the following objects. Note that the namespace
+207 -18
View File
@@ -108,7 +108,15 @@
memberFunctionInit -> "=" "0" memberFunctionInit -> "=" "0"
# (note: only "0" is allowed as the value, according to the standard, # (note: only "0" is allowed as the value, according to the standard,
# right?) # 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 We additionally add the possibility for specifying the visibility as the
first thing. first thing.
@@ -127,7 +135,6 @@
grammar, typedef-like: no initilizer grammar, typedef-like: no initilizer
decl-specifier-seq declerator decl-specifier-seq declerator
member_object: member_object:
goal: as a type_object which must have a declerator, and optionally goal: as a type_object which must have a declerator, and optionally
with a initializer with a initializer
@@ -138,6 +145,26 @@
goal: a function declaration, TODO: what about templates? for now: skip goal: a function declaration, TODO: what about templates? for now: skip
grammar: no initializer grammar: no initializer
decl-specifier-seq declerator 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 import re
@@ -1003,12 +1030,13 @@ class ASTBaseClass(ASTBase):
signode += addnodes.desc_annotation( signode += addnodes.desc_annotation(
self.visibility, self.visibility) self.visibility, self.visibility)
signode += nodes.Text(' ') signode += nodes.Text(' ')
self.name.describe_signature(signode, mode, env) self.name.describe_signature(signode, mode, env)
class ASTClass(ASTBase): class ASTClass(ASTBase):
def __init__(self, name, bases): def __init__(self, name, visibility, bases):
self.name = name self.name = name
self.visibility = visibility
self.bases = bases self.bases = bases
def get_id(self): def get_id(self):
@@ -1016,6 +1044,9 @@ class ASTClass(ASTBase):
def __unicode__(self): def __unicode__(self):
res = [] res = []
if self.visibility != 'public':
res.append(self.visibility)
res.append(' ')
res.append(text_type(self.name)) res.append(text_type(self.name))
if len(self.bases) > 0: if len(self.bases) > 0:
res.append(' : ') res.append(' : ')
@@ -1029,6 +1060,10 @@ class ASTClass(ASTBase):
def describe_signature(self, signode, mode, env): def describe_signature(self, signode, mode, env):
_verify_description_mode(mode) _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) self.name.describe_signature(signode, mode, env)
if len(self.bases) > 0: if len(self.bases) > 0:
signode += nodes.Text(' : ') signode += nodes.Text(' : ')
@@ -1037,6 +1072,63 @@ class ASTClass(ASTBase):
signode += nodes.Text(', ') signode += nodes.Text(', ')
signode.pop() 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): class DefinitionParser(object):
# those without signedness and size modifiers # those without signedness and size modifiers
@@ -1583,6 +1675,9 @@ class DefinitionParser(object):
return ASTTypeWithInit(type, init) return ASTTypeWithInit(type, init)
def _parse_class(self): def _parse_class(self):
classVisibility = 'public'
if self.match(_visibility_re):
classVisibility = self.matched_text
name = self._parse_nested_name() name = self._parse_nested_name()
bases = [] bases = []
self.skip_ws() self.skip_ws()
@@ -1599,8 +1694,35 @@ class DefinitionParser(object):
continue continue
else: else:
break 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): def parse_type_object(self):
res = self._parse_type(outer='type') res = self._parse_type(outer='type')
res.objectType = 'type' res.objectType = 'type'
@@ -1620,6 +1742,16 @@ class DefinitionParser(object):
res = self._parse_class() res = self._parse_class()
res.objectType = 'class' res.objectType = 'class'
return res 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): def parse_namespace_object(self):
res = self._parse_nested_name() res = self._parse_nested_name()
@@ -1658,14 +1790,27 @@ class CPPObject(ObjectDescription):
signode['first'] = (not self.names) signode['first'] = (not self.names)
self.state.document.note_explicit_target(signode) self.state.document.note_explicit_target(signode)
if not name in objects: if not name in objects:
objects.setdefault(name, objects.setdefault(name, (self.env.docname, ast))
(self.env.docname, ast.objectType, theid)) 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 # add the uninstantiated template if it doesn't exist
uninstantiated = ast.prefixedName.get_name_no_last_template() uninstantiated = ast.prefixedName.get_name_no_last_template()
if uninstantiated != name and uninstantiated not in objects: if uninstantiated != name and uninstantiated not in objects:
signode['names'].append(uninstantiated) signode['names'].append(uninstantiated)
objects.setdefault(uninstantiated, ( objects.setdefault(uninstantiated, (self.env.docname, ast))
self.env.docname, ast.objectType, theid))
self.env.temp_data['cpp:lastname'] = ast.prefixedName self.env.temp_data['cpp:lastname'] = ast.prefixedName
indextext = self.get_index_text(name) indextext = self.get_index_text(name)
@@ -1757,6 +1902,45 @@ class CPPClassObject(CPPObject):
def describe_signature(self, signode, ast): def describe_signature(self, signode, ast):
signode += addnodes.desc_annotation('class ', 'class ') signode += addnodes.desc_annotation('class ', 'class ')
ast.describe_signature(signode, 'lastIsName', self.env) 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): class CPPNamespaceObject(Directive):
@@ -1804,7 +1988,6 @@ class CPPXRefRole(XRefRole):
title = title[dcolon + 2:] title = title[dcolon + 2:]
return title, target return title, target
class CPPDomain(Domain): class CPPDomain(Domain):
"""C++ language domain.""" """C++ language domain."""
name = 'cpp' name = 'cpp'
@@ -1813,7 +1996,9 @@ class CPPDomain(Domain):
'class': ObjType(l_('class'), 'class'), 'class': ObjType(l_('class'), 'class'),
'function': ObjType(l_('function'), 'func'), 'function': ObjType(l_('function'), 'func'),
'member': ObjType(l_('member'), 'member'), '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 = { directives = {
@@ -1821,16 +2006,20 @@ class CPPDomain(Domain):
'function': CPPFunctionObject, 'function': CPPFunctionObject,
'member': CPPMemberObject, 'member': CPPMemberObject,
'type': CPPTypeObject, 'type': CPPTypeObject,
'enum': CPPEnumObject,
'enumerator': CPPEnumeratorObject,
'namespace': CPPNamespaceObject 'namespace': CPPNamespaceObject
} }
roles = { roles = {
'class': CPPXRefRole(), 'class': CPPXRefRole(),
'func': CPPXRefRole(fix_parens=True), 'func': CPPXRefRole(fix_parens=True),
'member': CPPXRefRole(), 'member': CPPXRefRole(),
'type': CPPXRefRole() 'type': CPPXRefRole(),
'enum': CPPXRefRole(),
'enumerator': CPPXRefRole()
} }
initial_data = { initial_data = {
'objects': {}, # prefixedName -> (docname, objectType, id) 'objects': {}, # prefixedName -> (docname, ast)
} }
def clear_doc(self, docname): def clear_doc(self, docname):
@@ -1847,8 +2036,8 @@ class CPPDomain(Domain):
name = nameAst.get_name_no_last_template() name = nameAst.get_name_no_last_template()
if name not in self.data['objects']: if name not in self.data['objects']:
return None return None
docname, objectType, id = self.data['objects'][name] docname, ast = self.data['objects'][name]
return make_refnode(builder, fromdocname, docname, id, contnode, return make_refnode(builder, fromdocname, docname, ast.get_id(), contnode,
name) name)
parser = DefinitionParser(target) parser = DefinitionParser(target)
@@ -1874,5 +2063,5 @@ class CPPDomain(Domain):
return None return None
def get_objects(self): def get_objects(self):
for refname, (docname, type, theid) in iteritems(self.data['objects']): for refname, (docname, ast) in iteritems(self.data['objects']):
yield (refname, refname, type, docname, refname, 1) yield (refname, refname, ast.objectType, docname, refname, 1)
+15
View File
@@ -108,6 +108,21 @@ def test_type_definitions():
raises(DefinitionError, parse, 'function', 'int foo(D d=x(a') raises(DefinitionError, parse, 'function', 'int foo(D d=x(a')
check('function', 'int foo(const A&... a)') check('function', 'int foo(const A&... a)')
check('function', 'virtual void f()') 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(): def test_bases():
check('class', 'A') check('class', 'A')