mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
monolithic class into multiple separate classes and refactored a bit. Still does not handle overloads.
637 lines
19 KiB
Python
637 lines
19 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
sphinx.domains.cpp
|
|
~~~~~~~~~~~~~~~~~~
|
|
|
|
The C++ language domain.
|
|
|
|
:copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
|
|
:license: BSD, see LICENSE for details.
|
|
"""
|
|
|
|
import re
|
|
import string
|
|
|
|
from docutils import nodes
|
|
|
|
from sphinx import addnodes
|
|
from sphinx.roles import XRefRole
|
|
from sphinx.locale import l_, _
|
|
from sphinx.domains import Domain, ObjType
|
|
from sphinx.directives import ObjectDescription
|
|
from sphinx.util.nodes import make_refnode
|
|
from sphinx.util.docfields import Field, TypedField
|
|
|
|
|
|
_identifier_re = re.compile(r'(~?[a-zA-Z_][a-zA-Z0-9_]*)')
|
|
_whitespace_re = re.compile(r'\s+(?u)')
|
|
_string_re = re.compile(r"[LuU8]?('([^'\\]*(?:\\.[^'\\]*)*)'"
|
|
r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S)
|
|
_operator_re = re.compile(r'''(?x)
|
|
\[\s*\]
|
|
| \(\s*\)
|
|
| [!<>=/*%+-|&^]=?
|
|
| <<=? | >>=? | ~ | ^ | & | && | \| | \|\|
|
|
''')
|
|
|
|
|
|
class DefinitionError(Exception):
|
|
pass
|
|
|
|
|
|
class DefExpr(object):
|
|
|
|
def __unicode__(self):
|
|
raise NotImplementedError()
|
|
|
|
def split_owner(self):
|
|
return None, self
|
|
|
|
def __str__(self):
|
|
return unicode(self).encode('utf-8')
|
|
|
|
def __repr__(self):
|
|
return '<defexpr %s>' % self
|
|
|
|
|
|
class NameDefExpr(DefExpr):
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
def __unicode__(self):
|
|
return unicode(self.name)
|
|
|
|
|
|
class PathDefExpr(DefExpr):
|
|
|
|
def __init__(self, parts):
|
|
self.path = parts
|
|
|
|
def split_owner(self):
|
|
if len(self.path) > 1:
|
|
return PathDefExpr(self.path[:-1]), self.path[-1]
|
|
return DefExpr.split_owner(self)
|
|
|
|
def __unicode__(self):
|
|
return u'::'.join(map(unicode, self.path))
|
|
|
|
|
|
class ModifierDefExpr(DefExpr):
|
|
|
|
def __init__(self, modifiers, typename):
|
|
self.modifiers = modifiers
|
|
self.typename = typename
|
|
|
|
def __unicode__(self):
|
|
return u' '.join(map(unicode, list(self.modifiers) + [self.typename]))
|
|
|
|
|
|
class PtrDefExpr(DefExpr):
|
|
|
|
def __init__(self, typename):
|
|
self.typename = typename
|
|
|
|
def __unicode__(self):
|
|
return u'%s*' % self.typename
|
|
|
|
|
|
class RefDefExpr(DefExpr):
|
|
|
|
def __init__(self, typename):
|
|
self.typename = typename
|
|
|
|
def __unicode__(self):
|
|
return u'%s&' % self.typename
|
|
|
|
|
|
class CastOpDefExpr(DefExpr):
|
|
|
|
def __init__(self, typename):
|
|
self.typename = typename
|
|
|
|
def __unicode__(self):
|
|
return u'operator %s' % self.typename
|
|
|
|
|
|
class TemplateDefExpr(DefExpr):
|
|
|
|
def __init__(self, typename, args):
|
|
self.typename = typename
|
|
self.args = args
|
|
|
|
def __unicode__(self):
|
|
return u'%s<%s>' % (self.typename, u', '.join(map(unicode, self.args)))
|
|
|
|
|
|
class ArgumentDefExpr(DefExpr):
|
|
|
|
def __init__(self, type, name, default=None):
|
|
self.type = type
|
|
self.name = name
|
|
self.default = default
|
|
|
|
def __unicode__(self):
|
|
return (self.type is not None and u'%s %s' % (self.type, self.name)
|
|
or unicode(self.name)) + (self.default is not None and
|
|
u'=%s' % self.default or u'')
|
|
|
|
|
|
class FunctionDefExpr(DefExpr):
|
|
|
|
def __init__(self, name, signature, const, pure_virtual):
|
|
self.name = name
|
|
self.signature = signature
|
|
self.const = const
|
|
self.pure_virtual = pure_virtual
|
|
|
|
def __unicode__(self):
|
|
return u'%s(%s)%s%s' % (
|
|
self.name,
|
|
u', '.join(map(unicode, self.signature)),
|
|
self.const and u' const' or u'',
|
|
self.pure_virtual and ' = 0' or ''
|
|
)
|
|
|
|
|
|
class DefinitionParser(object):
|
|
|
|
# mapping of valid type modifiers. if the set is None it means
|
|
# the modifier can prefix all types, otherwise only the types
|
|
# (actually more keywords) in the set. Also check
|
|
# _guess_typename when changing this
|
|
_modifiers = {
|
|
'volatile': None,
|
|
'register': None,
|
|
'const': None,
|
|
'mutable': None,
|
|
'typename': None,
|
|
'unsigned': set(('char', 'int', 'long')),
|
|
'signed': set(('char', 'int', 'long')),
|
|
'short': set(('int', 'short')),
|
|
'long': set(('int', 'long'))
|
|
}
|
|
|
|
def __init__(self, definition):
|
|
self.definition = definition.strip()
|
|
self.pos = 0
|
|
self.end = len(self.definition)
|
|
self.last_match = None
|
|
self._previous_state = (0, None)
|
|
|
|
def fail(self, msg):
|
|
raise DefinitionError('Invalid definition: %s [error at %d]\n %s' %
|
|
(msg, self.pos, self.definition))
|
|
|
|
def match(self, regex):
|
|
match = regex.match(self.definition, self.pos)
|
|
if match is not None:
|
|
self._previous_state = (self.pos, self.last_match)
|
|
self.pos = match.end()
|
|
self.last_match = match
|
|
return True
|
|
return False
|
|
|
|
def backout(self):
|
|
self.pos, self.last_match = self._previous_state
|
|
|
|
def skip_string(self, string):
|
|
strlen = len(string)
|
|
if self.definition[self.pos:self.pos + strlen] == string:
|
|
self.pos += strlen
|
|
return True
|
|
return False
|
|
|
|
def skip_ws(self):
|
|
return self.match(_whitespace_re) is not None
|
|
|
|
@property
|
|
def eof(self):
|
|
return self.pos >= self.end
|
|
|
|
@property
|
|
def current_char(self):
|
|
try:
|
|
return self.definition[self.pos]
|
|
except IndexError:
|
|
return 'EOF'
|
|
|
|
@property
|
|
def matched_text(self):
|
|
if self.last_match is not None:
|
|
return self.last_match.group()
|
|
|
|
def _parse_operator(self):
|
|
# thank god, a regular operator definition
|
|
if self.match(_operator_re):
|
|
return NameDefExpr('operator' +
|
|
_whitespace_re.sub('', self.matched_text))
|
|
|
|
# oh well, looks like a cast operator definition.
|
|
# In that case, eat another type.
|
|
type = self._parse_type()
|
|
return CastOpDefExpr(type)
|
|
|
|
def _parse_name(self):
|
|
if not self.match(_identifier_re):
|
|
self.fail('expected name')
|
|
identifier = self.matched_text
|
|
|
|
# strictly speaking, operators are not regular identifiers
|
|
# but because operator is a keyword, it might not be used
|
|
# for variable names anyways, so we can safely parse the
|
|
# operator here as identifier
|
|
if identifier == 'operator':
|
|
return self._parse_operator()
|
|
|
|
return NameDefExpr(identifier)
|
|
|
|
def _parse_type_expr(self):
|
|
typename = self._parse_name()
|
|
self.skip_ws()
|
|
if not self.skip_string('<'):
|
|
return typename
|
|
|
|
args = []
|
|
while 1:
|
|
self.skip_ws()
|
|
if self.skip_string('>'):
|
|
break
|
|
if args:
|
|
if not self.skip_string(','):
|
|
self.fail('"," or ">" in template expected')
|
|
self.skip_ws()
|
|
args.append(self._parse_type(True))
|
|
return TemplateDefExpr(typename, args)
|
|
|
|
def _guess_typename(self, path):
|
|
if not path:
|
|
return [], 'int'
|
|
# for the long type, we don't want the int in there
|
|
if 'long' in path:
|
|
path = [x for x in path if x != 'int']
|
|
# remove one long
|
|
path.remove('long')
|
|
return path, 'long'
|
|
if path[-1] in ('int', 'char'):
|
|
return path[:-1], path[-1]
|
|
return path, 'int'
|
|
|
|
def _attach_refptr(self, expr):
|
|
self.skip_ws()
|
|
if self.skip_string('*'):
|
|
return PtrDefExpr(expr)
|
|
elif self.skip_string('&'):
|
|
return RefDefExpr(expr)
|
|
return expr
|
|
|
|
def _parse_builtin(self, modifier):
|
|
path = [modifier]
|
|
following = self._modifiers[modifier]
|
|
while 1:
|
|
self.skip_ws()
|
|
if not self.match(_identifier_re):
|
|
break
|
|
identifier = self.matched_text
|
|
if identifier in following:
|
|
path.append(identifier)
|
|
following = self._modifiers[modifier]
|
|
assert following
|
|
else:
|
|
self.backout()
|
|
break
|
|
modifiers, typename = self._guess_typename(path)
|
|
rv = ModifierDefExpr(modifiers, _NameDefExpr(typename))
|
|
return self._attach_refptr(rv)
|
|
|
|
def _parse_type(self, in_template=False):
|
|
result = []
|
|
modifiers = []
|
|
|
|
# if there is a leading :: or not, we don't care because we
|
|
# treat them exactly the same. Buf *if* there is one, we
|
|
# don't have to check for type modifiers
|
|
if not self.skip_string('::'):
|
|
self.skip_ws()
|
|
if self.match(_identifier_re):
|
|
modifier = self.matched_text
|
|
if modifier in self._modifiers:
|
|
following = self._modifiers[modifier]
|
|
# if the set is not none, there is a limited set
|
|
# of types that might follow. It is technically
|
|
# impossible for a template to follow, so what
|
|
# we do is go to a different function that just
|
|
# eats types
|
|
if following is not None:
|
|
return self._parse_builtin(modifier)
|
|
modifiers.append(modifier)
|
|
else:
|
|
self.backout()
|
|
|
|
while 1:
|
|
self.skip_ws()
|
|
if (in_template and self.current_char in ',>') or \
|
|
(result and not self.skip_string('::')) or \
|
|
self.eof:
|
|
break
|
|
result.append(self._parse_type_expr())
|
|
|
|
if not result:
|
|
self.fail('expected type')
|
|
if len(result) == 1:
|
|
rv = result[0]
|
|
else:
|
|
rv = PathDefExpr(result)
|
|
if modifiers:
|
|
rv = ModifierDefExpr(modifiers, rv)
|
|
return self._attach_refptr(rv)
|
|
|
|
def _parse_default_expr(self):
|
|
self.skip_ws()
|
|
if self.match(_string_re):
|
|
return self.matched_text
|
|
idx1 = self.definition.find(',', self.pos)
|
|
idx2 = self.definition.find(')', self.pos)
|
|
if idx1 < 0:
|
|
idx = idx2
|
|
elif idx2 < 0:
|
|
idx = idx1
|
|
else:
|
|
idx = min(idx1, idx2)
|
|
if idx < 0:
|
|
self.fail('unexpected end in default expression')
|
|
rv = self.definition[self.pos:idx]
|
|
self.pos = idx
|
|
return rv
|
|
|
|
def _parse_signature(self):
|
|
self.skip_ws()
|
|
if not self.skip_string('('):
|
|
self.fail('expected parentheses for function')
|
|
|
|
args = []
|
|
while 1:
|
|
self.skip_ws()
|
|
if self.eof:
|
|
self.fail('missing closing parentheses')
|
|
if self.skip_string(')'):
|
|
break
|
|
if args:
|
|
if not self.skip_string(','):
|
|
self.fail('expected comma between arguments')
|
|
self.skip_ws()
|
|
|
|
argname = self._parse_type()
|
|
argtype = default = None
|
|
self.skip_ws()
|
|
if self.skip_string('='):
|
|
self.pos += 1
|
|
default = self._parse_default_expr()
|
|
elif self.current_char not in ',)':
|
|
argtype = argname
|
|
argname = self._parse_name()
|
|
self.skip_ws()
|
|
if self.skip_string('='):
|
|
default = self._parse_default_expr()
|
|
|
|
args.append(ArgumentDefExpr(argtype, argname, default))
|
|
self.skip_ws()
|
|
const = self.skip_string('const')
|
|
if const:
|
|
self.skip_ws()
|
|
if self.skip_string('='):
|
|
self.skip_ws()
|
|
if not (self.skip_string('0') or \
|
|
self.skip_string('NULL') or \
|
|
self.skip_string('nullptr')):
|
|
self.fail('pure virtual functions must be defined with '
|
|
'either 0, NULL or nullptr, other macros are '
|
|
'not allowed')
|
|
pure_virtual = True
|
|
else:
|
|
pure_virtual = False
|
|
return args, const, pure_virtual
|
|
|
|
def parse_variable(self):
|
|
type = self._parse_type()
|
|
name = self._parse_type()
|
|
return type, name
|
|
|
|
def parse_function(self):
|
|
rv = self._parse_type()
|
|
if isinstance(rv, CastOpDefExpr):
|
|
name = rv
|
|
rv = None
|
|
else:
|
|
name = self._parse_type()
|
|
return rv, FunctionDefExpr(name, *self._parse_signature())
|
|
|
|
def parse_typename(self):
|
|
return self._parse_type()
|
|
|
|
def assert_end(self):
|
|
self.skip_ws()
|
|
if not self.eof:
|
|
self.fail('expected end of definition, got %r' %
|
|
self.definition[self.pos:])
|
|
|
|
|
|
class CPPObject(ObjectDescription):
|
|
"""Description of a C++ language object."""
|
|
|
|
def attach_type(self, node, type):
|
|
# XXX: link? how could we do that
|
|
if type is not None:
|
|
text = unicode(type)
|
|
pnode = addnodes.pending_xref(
|
|
'', refdomain='cpp', reftype='type',
|
|
reftarget=text, modname=None, classname=None)
|
|
pnode += nodes.Text(text)
|
|
node += pnode
|
|
|
|
def add_target_and_index(self, name, sig, signode):
|
|
if name not in self.state.document.ids:
|
|
# XXX: how to handle method overloading?
|
|
signode['names'].append(name)
|
|
signode['ids'].append(name)
|
|
signode['first'] = (not self.names)
|
|
self.state.document.note_explicit_target(signode)
|
|
self.env.domaindata['cpp']['objects'][name] = \
|
|
(self.env.docname, self.objtype)
|
|
|
|
indextext = self.get_index_text(name)
|
|
if indextext:
|
|
self.indexnode['entries'].append(('single', indextext, name, name))
|
|
|
|
def before_content(self):
|
|
lastname = self.names and self.names[-1]
|
|
if lastname and not self.env.temp_data.get('cpp:parent'):
|
|
self.env.temp_data['cpp:parent'] = lastname
|
|
self.parentname_set = True
|
|
else:
|
|
self.parentname_set = False
|
|
|
|
def after_content(self):
|
|
if self.parentname_set:
|
|
self.env.temp_data['cpp:parent'] = None
|
|
|
|
def parse_definition(self, parser):
|
|
raise NotImplementedError()
|
|
|
|
def describe_signature(self, signode, arg):
|
|
raise NotImplementedError()
|
|
|
|
def handle_signature(self, sig, signode):
|
|
parser = DefinitionParser(sig)
|
|
rv = self.parse_definition(parser)
|
|
parser.assert_end()
|
|
name = self.describe_signature(signode, rv)
|
|
|
|
parentname = self.env.temp_data.get('cpp:parent')
|
|
if parentname:
|
|
return u'%s::%s' % (parentname, name)
|
|
return unicode(name)
|
|
|
|
|
|
class CPPClassObject(CPPObject):
|
|
|
|
def get_index_text(self, name):
|
|
return _('%s (C++ class)') % name
|
|
|
|
def parse_definition(self, parser):
|
|
return parser.parse_typename()
|
|
|
|
def describe_signature(self, signode, typename):
|
|
signode += addnodes.desc_type('', '')
|
|
self.attach_type(signode, typename)
|
|
return typename
|
|
|
|
|
|
class CPPTypedObject(CPPObject):
|
|
|
|
def _attach_var(self, node, var):
|
|
owner, name = var.name.split_owner()
|
|
varname = unicode(name)
|
|
if owner is not None:
|
|
owner = unicode(owner) + '::'
|
|
node += addnodes.desc_addname(owner, owner)
|
|
node += addnodes.desc_name(varname, varname)
|
|
|
|
def get_index_text(self, name):
|
|
if self.objtype == 'member':
|
|
return _('%s (C++ member)') % name
|
|
elif self.objtype == 'type':
|
|
return _('%s (C++ type)') % name
|
|
elif self.objtype == 'var':
|
|
return _('%s (C++ variable)') % name
|
|
return ''
|
|
|
|
def parse_definition(self, parser):
|
|
return parser.parse_variable()
|
|
|
|
def describe_signature(self, signode, (rv, var)):
|
|
signode += addnodes.desc_type('', '')
|
|
self.attach_type(signode, rv)
|
|
signode += nodes.Text(' ')
|
|
self._attach_var(signode, var)
|
|
return var.name
|
|
|
|
|
|
class CPPFunctionObject(CPPTypedObject):
|
|
|
|
def _attach_function(self, node, func):
|
|
owner, name = func.name.split_owner()
|
|
if owner is not None:
|
|
owner = unicode(owner) + '::'
|
|
node += addnodes.desc_addname(owner, owner)
|
|
|
|
if isinstance(name, CastOpDefExpr):
|
|
node += addnodes.desc_name('operator', 'operator')
|
|
node += nodes.Text(u' ')
|
|
self.attach_type(node, name.typename)
|
|
else:
|
|
funcname = unicode(name)
|
|
node += addnodes.desc_name(funcname, funcname)
|
|
|
|
paramlist = addnodes.desc_parameterlist()
|
|
for arg in func.signature:
|
|
param = addnodes.desc_parameter('', '', noemph=True)
|
|
if arg.type is not None:
|
|
self.attach_type(param, arg.type)
|
|
param += nodes.Text(u' ')
|
|
param += nodes.emphasis(unicode(arg.name), unicode(arg.name))
|
|
if arg.default is not None:
|
|
def_ = u'=' + unicode(arg.default)
|
|
param += nodes.emphasis(def_, def_)
|
|
paramlist += param
|
|
|
|
node += paramlist
|
|
if func.const:
|
|
node += addnodes.desc_addname(' const', ' const')
|
|
if func.pure_virtual:
|
|
node += addnodes.desc_addname(' = 0', ' = 0')
|
|
|
|
def get_index_text(self, name):
|
|
return _('%s (C++ function)') % name
|
|
|
|
def parse_definition(self, parser):
|
|
return parser.parse_function()
|
|
|
|
def describe_signature(self, signode, (rv, func)):
|
|
signode += addnodes.desc_type('', '')
|
|
self.attach_type(signode, rv)
|
|
signode += nodes.Text(u' ')
|
|
self._attach_function(signode, func)
|
|
return func.name
|
|
|
|
|
|
class CPPDomain(Domain):
|
|
"""C++ language domain."""
|
|
name = 'cpp'
|
|
label = 'C++'
|
|
object_types = {
|
|
'class': ObjType(l_('C++ class'), 'class'),
|
|
'function': ObjType(l_('C++ function'), 'func'),
|
|
'member': ObjType(l_('C++ member'), 'member'),
|
|
'type': ObjType(l_('C++ type'), 'type'),
|
|
'var': ObjType(l_('C++ variable'), 'data'),
|
|
}
|
|
|
|
directives = {
|
|
'class': CPPClassObject,
|
|
'function': CPPFunctionObject,
|
|
'member': CPPTypedObject,
|
|
'type': CPPTypedObject,
|
|
'var': CPPTypedObject,
|
|
}
|
|
roles = {
|
|
'class': XRefRole(),
|
|
'func' : XRefRole(fix_parens=True),
|
|
'member': XRefRole(),
|
|
'macro': XRefRole(),
|
|
'data': XRefRole(),
|
|
'type': XRefRole(),
|
|
}
|
|
initial_data = {
|
|
'objects': {}, # fullname -> docname, objtype
|
|
}
|
|
|
|
def clear_doc(self, docname):
|
|
for fullname, (fn, _) in self.data['objects'].items():
|
|
if fn == docname:
|
|
del self.data['objects'][fullname]
|
|
|
|
def resolve_xref(self, env, fromdocname, builder,
|
|
typ, target, node, contnode):
|
|
# strip pointer asterisk
|
|
target = target.rstrip(' *')
|
|
if target not in self.data['objects']:
|
|
return None
|
|
obj = self.data['objects'][target]
|
|
return make_refnode(builder, fromdocname, obj[0], target,
|
|
contnode, target)
|
|
|
|
def get_objects(self):
|
|
for refname, (docname, type) in self.data['objects'].iteritems():
|
|
yield (refname, type, docname, refname, 1)
|