diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index d4de0fa68..1edec5251 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -29,7 +29,7 @@ wsplit_re = re.compile(r'(\W+)') # REs for C signatures c_sig_re = re.compile( r'''^([^(]*?) # return type - ([\w:.]+) \s* # thing name (colon allowed for C++ class names) + ([\w:.]+) \s* # thing name (colon allowed for C++) (?: \((.*)\) )? # optionally arguments (\s+const)? $ # const specifier ''', re.VERBOSE) @@ -76,7 +76,7 @@ class CObject(ObjectDescription): node += tnode def handle_signature(self, sig, signode): - """Transform a C (or C++) signature into RST nodes.""" + """Transform a C signature into RST nodes.""" # first try the function pointer signature regex, it's more specific m = c_funcptr_sig_re.match(sig) if m is None: diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 9c5c185cf..e946dbe54 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -29,7 +29,7 @@ _string_re = re.compile(r"[LuU8]?('([^'\\]*(?:\\.[^'\\]*)*)'" r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S) -class DefinitionError(ValueError): +class DefinitionError(Exception): pass @@ -71,6 +71,34 @@ class _PathDefExpr(_DefExpr): 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 _TemplateDefExpr(_DefExpr): def __init__(self, typename, args): @@ -113,6 +141,22 @@ class _FunctionDefExpr(_DefExpr): 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',)), + 'long': set(('int', 'long')) + } + def __init__(self, definition): self.definition = definition.strip() self.pos = 0 @@ -121,8 +165,8 @@ class DefinitionParser(object): self._previous_state = (0, None) def fail(self, msg): - raise DefinitionError('Invalid definition: "%s", %s [error at %d]' % - (self.definition, msg, self.pos)) + 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) @@ -185,12 +229,69 @@ class DefinitionParser(object): 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 - self.skip_string('::') + # 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() @@ -203,8 +304,12 @@ class DefinitionParser(object): if not result: self.fail('expected type') if len(result) == 1: - return result[0] - return _PathDefExpr(result) + 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() @@ -282,10 +387,57 @@ class DefinitionParser(object): name = self._parse_type() return rv, _FunctionDefExpr(name, *self._parse_signature()) + 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_return_type(self, node, return_type): + # XXX: link? how could we do that + text = unicode(return_type) + u' ' + tnode = nodes.Text(text, text) + node += tnode + + def _attach_function(self, node, func): + owner, name = func.name.split_owner() + funcname = unicode(name) + if owner is not None: + owner = unicode(owner) + '::' + node += addnodes.desc_addname(owner, owner) + node += addnodes.desc_name(funcname, funcname) + else: + node += addnodes.desc_name(funcname, funcname) + + paramlist = addnodes.desc_parameterlist() + for arg in func.signature: + # XXX: is that okay? maybe more semantic here + strarg = unicode(arg) + param = addnodes.desc_parameter('', '', noemph=True) + param += nodes.emphasis(strarg, strarg) + paramlist += param + + node += paramlist + if func.const: + node += addnodes.desc_addname(' const', ' const') + if func.pure_virtual: + node += addnodes.desc_addname(' = 0', ' = 0') + + def handle_signature(self, sig, signode): + """Transform a C (or C++) signature into RST nodes.""" + parser = DefinitionParser(sig) + rv, func = parser.parse_function() + parser.assert_end() + + signode += addnodes.desc_type('', '') + self._attach_return_type(signode, rv) + self._attach_function(signode, func) + return str(func.name) + class CPPDomain(Domain): """C++ language domain."""