# -*- coding: utf-8 -*- """ sphinx.domains.cpp ~~~~~~~~~~~~~~~~~~ The C++ language domain. :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. See http://www.nongnu.org/hcb/ for the grammar. See http://mentorembedded.github.io/cxx-abi/abi.html#mangling for the inspiration for the id generation. common grammar things: simple-declaration -> attribute-specifier-seq[opt] decl-specifier-seq[opt] init-declarator-list[opt] ; # Drop the semi-colon. For now: drop the attributes (TODO). # Use at most 1 init-declerator. -> decl-specifier-seq init-declerator -> decl-specifier-seq declerator initializer decl-specifier -> storage-class-specifier -> "static" (only for member_object and function_object) | type-specifier -> trailing-type-specifier | function-specifier -> "inline" | "virtual" | "explicit" (only for function_object) | "friend" (only for function_object) | "constexpr" (only for member_object and function_object) trailing-type-specifier -> simple-type-specifier | elaborated-type-specifier | typename-specifier | cv-qualifier -> "const" | "volatile" stricter grammar for decl-specifier-seq (with everything, each object uses a subset): visibility storage-class-specifier function-specifier "friend" "constexpr" "volatile" "const" trailing-type-specifier # where trailing-type-specifier can no be cv-qualifier # Inside e.g., template paramters a strict subset is used # (see type-specifier-seq) trailing-type-specifier -> simple-type-specifier -> ::[opt] nested-name-specifier[opt] type-name | ::[opt] nested-name-specifier "template" simple-template-id | "char" | "bool" | ect. | decltype-specifier | elaborated-type-specifier -> class-key attribute-specifier-seq[opt] ::[opt] nested-name-specifier[opt] identifier | class-key ::[opt] nested-name-specifier[opt] template[opt] simple-template-id | "enum" ::[opt] nested-name-specifier[opt] identifier | typename-specifier -> "typename" ::[opt] nested-name-specifier identifier | "typename" ::[opt] nested-name-specifier template[opt] simple-template-id class-key -> "class" | "struct" | "union" type-name ->* identifier | simple-template-id # ignoring attributes and decltype, and then some left-factoring trailing-type-specifier -> rest-of-trailing ("class" | "struct" | "union" | "typename") rest-of-trailing build-in -> "char" | "bool" | ect. decltype-specifier rest-of-trailing -> (with some simplification) "::"[opt] list-of-elements-separated-by-:: element -> "template"[opt] identifier ("<" template-argument-list ">")[opt] template-argument-list -> template-argument "..."[opt] | template-argument-list "," template-argument "..."[opt] template-argument -> constant-expression | type-specifier-seq abstract-declerator | id-expression declerator -> ptr-declerator | noptr-declarator parameters-and-qualifiers trailing-return-type (TODO: for now we don't support it) ptr-declerator -> noptr-declerator | ptr-operator ptr-declarator noptr-declerator -> declarator-id attribute-specifier-seq[opt] -> "..."[opt] id-expression | rest-of-trailing | noptr-declerator parameters-and-qualifiers | noptr-declarator "[" constant-expression[opt] "]" attribute-specifier-seq[opt] | "(" ptr-declarator ")" # TODO: not implemented yet # function_object must use a parameters-and-qualifiers, the others may # use it (e.g., function poitners) parameters-and-qualifiers -> "(" parameter-clause ")" attribute-specifier-seq[opt] cv-qualifier-seq[opt] ref-qualifier[opt] exception-specification[opt] ref-qualifier -> "&" | "&&" exception-specification -> "noexcept" ("(" constant-expression ")")[opt] "throw" ("(" type-id-list ")")[opt] # TODO: we don't implement attributes # member functions can have initializers, but we fold them into here memberFunctionInit -> "=" "0" # (note: only "0" is allowed as the value, according to the standard, # right?) We additionally add the possibility for specifying the visibility as the first thing. type_object: goal: either a single type (e.g., "MyClass:Something_T" or a typedef-like thing (e.g. "Something Something_T" or "int I_arr[]" grammar, single type: based on a type in a function parameter, but without a name: parameter-declaration -> attribute-specifier-seq[opt] decl-specifier-seq abstract-declarator[opt] # Drop the attributes -> decl-specifier-seq abstract-declarator[opt] 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 grammar: decl-specifier-seq declerator initializer function_object: goal: a function declaration, TODO: what about templates? for now: skip grammar: no initializer decl-specifier-seq declerator """ import re from copy import deepcopy from six import iteritems, text_type 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.compat import Directive from sphinx.util.pycompat import UnicodeMixin from sphinx.util.docfields import Field, GroupedField _identifier_re = re.compile(r'(~?\b[a-zA-Z_][a-zA-Z0-9_]*)\b') _whitespace_re = re.compile(r'\s+(?u)') _string_re = re.compile(r"[LuU8]?('([^'\\]*(?:\\.[^'\\]*)*)'" r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S) _visibility_re = re.compile(r'\b(public|private|protected)\b') _array_def_re = re.compile(r'\[\s*([^\]]+?)?\s*\]') _template_arg_re = re.compile(r'(%s)|([^,>]+)' % _string_re.pattern, re.S) _operator_re = re.compile(r'''(?x) \[\s*\] | \(\s*\) | \+\+ | -- | ->\*? | \, | (<<|>>)=? | && | \|\| | [!<>=/*%+|&^~-]=? ''') _id_prefix = '_CPP' _id_fundamental = { # not all of these are actually parsed as fundamental types, TODO: do that 'void': 'v', 'bool': 'b', 'char': 'c', 'signed char': 'a', 'unsigned char': 'h', 'wchar_t': 'w', 'char32_t': 'Di', 'char16_t': 'Ds', 'short': 's', 'short int': 's', 'signed short': 's', 'signed short int': 's', 'unsigned short': 't', 'unsigned short int': 't', 'int': 'i', 'signed': 'i', 'signed int': 'i', 'unsigned': 'j', 'unsigned int': 'j', 'long': 'l', 'long int': 'l', 'signed long': 'l', 'signed long int': 'l', 'unsigned long': 'm', 'unsigned long int': 'm', 'long long': 'x', 'long long int': 'x', 'signed long long': 'x', 'signed long long int': 'x', 'unsigned long long': 'y', 'unsigned long long int': 'y', 'float': 'f', 'double': 'd', 'long double': 'e', 'auto': 'Da', 'decltype(auto)': 'Dc', 'std::nullptr_t': 'Dn' } _id_operator = { 'new': 'nw', 'new[]': 'na', 'delete': 'dl', 'delete[]': 'da', # the arguments will make the difference between unary and binary # '+(unary)' : 'ps', # '-(unary)' : 'ng', # '&(unary)' : 'ad', # '*(unary)' : 'de', '~': 'co', '+': 'pl', '-': 'mi', '*': 'ml', '/': 'dv', '%': 'rm', '&': 'an', '|': 'or', '^': 'eo', '=': 'aS', '+=': 'pL', '-=': 'mI', '*=': 'mL', '/=': 'dV', '%=': 'rM', '&=': 'aN', '|=': 'oR', '^=': 'eO', '<<': 'ls', '>>': 'rs', '<<=': 'lS', '>>=': 'rS', '==': 'eq', '!=': 'ne', '<': 'lt', '>': 'gt', '<=': 'le', '>=': 'ge', '!': 'nt', '&&': 'aa', '||': 'oo', '++': 'pp', '--': 'mm', ',': 'cm', '->*': 'pm', '->': 'pt', '()': 'cl', '[]': 'ix' } class DefinitionError(UnicodeMixin, Exception): def __init__(self, description): self.description = description def __unicode__(self): return self.description class ASTBase(UnicodeMixin): def __eq__(self, other): if type(self) is not type(other): return False try: for key, value in iteritems(self.__dict__): if value != getattr(other, key): return False except AttributeError: return False return True def __ne__(self, other): return not self.__eq__(other) __hash__ = None def clone(self): """Clone a definition expression node.""" return deepcopy(self) def get_id(self): """Return the id for the node.""" raise NotImplementedError(repr(self)) def get_name(self): """Return the name. Returns either `None` or a node with a name you might call :meth:`split_owner` on. """ raise NotImplementedError(repr(self)) def prefix_nested_name(self, prefix): """Prefix a name node (a node returned by :meth:`get_name`).""" raise NotImplementedError(repr(self)) def __unicode__(self): raise NotImplementedError(repr(self)) def __repr__(self): return '<%s %s>' % (self.__class__.__name__, self) def _verify_description_mode(mode): if mode not in ('lastIsName', 'noneIsName', 'markType', 'param'): raise Exception("Description mode '%s' is invalid." % mode) class ASTOperatorBuildIn(ASTBase): def __init__(self, op): self.op = op def get_id(self): if self.op not in _id_operator: raise Exception('Internal error: Build-in operator "%s" can not ' 'be mapped to an id.' % self.op) return _id_operator[self.op] def __unicode__(self): if self.op in ('new', 'new[]', 'delete', 'delete[]'): return u'operator ' + self.op else: return u'operator' + self.op def get_name_no_template(self): return text_type(self) def describe_signature(self, signode, mode, env, prefix): _verify_description_mode(mode) identifier = text_type(self) if mode == 'lastIsName': signode += addnodes.desc_name(identifier, identifier) else: signode += addnodes.desc_addname(identifier, identifier) class ASTOperatorType(ASTBase): def __init__(self, type): self.type = type def __unicode__(self): return u''.join(['operator ', text_type(self.type)]) def get_id(self): return u'cv' + self.type.get_id() def get_name_no_template(self): return text_type(self) def describe_signature(self, signode, mode, env, prefix): _verify_description_mode(mode) identifier = text_type(self) if mode == 'lastIsName': signode += addnodes.desc_name(identifier, identifier) else: signode += addnodes.desc_addname(identifier, identifier) class ASTTemplateArgConstant(ASTBase): def __init__(self, value): self.value = value def __unicode__(self): return text_type(self.value) def get_id(self): # TODO: doing this properly needs parsing of expressions, let's just # juse it verbatim for now return u'X' + text_type(self) + u'E' def describe_signature(self, signode, mode, env): _verify_description_mode(mode) signode += nodes.Text(text_type(self)) class ASTNestedNameElement(ASTBase): def __init__(self, identifier, templateArgs): self.identifier = identifier self.templateArgs = templateArgs def get_id(self): res = [] if self.identifier == "std": res.append(u'St') else: res.append(text_type(len(self.identifier))) res.append(self.identifier) if self.templateArgs: res.append('I') for a in self.templateArgs: res.append(a.get_id()) res.append('E') return u''.join(res) def __unicode__(self): res = [] res.append(self.identifier) if self.templateArgs: res.append('<') first = True for a in self.templateArgs: if not first: res.append(', ') first = False res.append(text_type(a)) res.append('>') return u''.join(res) def get_name_no_template(self): return text_type(self.identifier) def describe_signature(self, signode, mode, env, prefix): _verify_description_mode(mode) if mode == 'markType': targetText = prefix + text_type(self) pnode = addnodes.pending_xref( '', refdomain='cpp', reftype='type', reftarget=targetText, modname=None, classname=None) if env: # during testing we don't have an env, do we? pnode['cpp:parent'] = env.ref_context.get('cpp:parent') pnode += nodes.Text(text_type(self.identifier)) signode += pnode elif mode == 'lastIsName': name = text_type(self.identifier) signode += addnodes.desc_name(name, name) else: raise Exception('Unknown description mode: %s' % mode) if self.templateArgs: signode += nodes.Text('<') first = True for a in self.templateArgs: if not first: signode += nodes.Text(', ') first = False a.describe_signature(signode, 'markType', env) signode += nodes.Text('>') class ASTNestedName(ASTBase): def __init__(self, names): """Use an empty string as the first name if it should start with '::' """ self.names = names @property def name(self): return self def get_id(self): res = [] if len(self.names) > 1: res.append('N') for n in self.names: res.append(n.get_id()) if len(self.names) > 1: res.append('E') return u''.join(res) def get_name_no_last_template(self): res = u'::'.join([text_type(n) for n in self.names[:-1]]) if len(self.names) > 1: res += '::' res += self.names[-1].get_name_no_template() return res def prefix_nested_name(self, prefix): if self.names[0] == '': return self # it's defined at global namespace, don't tuch it assert isinstance(prefix, ASTNestedName) names = prefix.names[:] names.extend(self.names) return ASTNestedName(names) def __unicode__(self): return u'::'.join([text_type(n) for n in self.names]) def describe_signature(self, signode, mode, env): _verify_description_mode(mode) if mode == 'lastIsName': addname = u'::'.join([text_type(n) for n in self.names[:-1]]) if len(self.names) > 1: addname += u'::' name = text_type(self.names[-1]) signode += addnodes.desc_addname(addname, addname) self.names[-1].describe_signature(signode, mode, env, '') elif mode == 'noneIsName': name = text_type(self) signode += nodes.Text(name) elif mode == 'param': name = text_type(self) signode += nodes.emphasis(name, name) elif mode == 'markType': # each element should be a pending xref targeting the complete # prefix. however, only the identifier part should be a link, such # that template args can be a link as well. prefix = '' first = True for name in self.names: if not first: signode += nodes.Text('::') prefix += '::' first = False if name != '': name.describe_signature(signode, mode, env, prefix) prefix += text_type(name) else: raise Exception('Unknown description mode: %s' % mode) class ASTTrailingTypeSpecFundamental(ASTBase): def __init__(self, name): self.name = name def __unicode__(self): return self.name def get_id(self): if self.name not in _id_fundamental: raise Exception( 'Semi-internal error: Fundamental type "%s" can not be mapped ' 'to an id. Is it a true fundamental type? If not so, the ' 'parser should have rejected it.' % self.name) return _id_fundamental[self.name] def describe_signature(self, signode, mode, env): signode += nodes.Text(text_type(self.name)) class ASTTrailingTypeSpecName(ASTBase): def __init__(self, prefix, nestedName): self.prefix = prefix self.nestedName = nestedName @property def name(self): return self.nestedName def get_id(self): return self.nestedName.get_id() def __unicode__(self): res = [] if self.prefix: res.append(self.prefix) res.append(' ') res.append(text_type(self.nestedName)) return u''.join(res) def describe_signature(self, signode, mode, env): if self.prefix: signode += addnodes.desc_annotation(self.prefix, self.prefix) signode += nodes.Text(' ') self.nestedName.describe_signature(signode, mode, env) class ASTFunctinoParameter(ASTBase): def __init__(self, arg, ellipsis=False): self.arg = arg self.ellipsis = ellipsis def get_id(self): if self.ellipsis: return 'z' else: return self.arg.get_id() def __unicode__(self): if self.ellipsis: return '...' else: return text_type(self.arg) def describe_signature(self, signode, mode, env): _verify_description_mode(mode) if self.ellipsis: signode += nodes.Text('...') else: self.arg.describe_signature(signode, mode, env) class ASTParametersQualifiers(ASTBase): def __init__(self, args, volatile, const, refQual, exceptionSpec, override, final, initializer): self.args = args self.volatile = volatile self.const = const self.refQual = refQual self.exceptionSpec = exceptionSpec self.override = override self.final = final self.initializer = initializer def get_modifiers_id(self): res = [] if self.volatile: res.append('V') if self.const: res.append('K') if self.refQual == '&&': res.append('O') elif self.refQual == '&': res.append('R') return u''.join(res) def get_param_id(self): if len(self.args) == 0: return 'v' else: return u''.join(a.get_id() for a in self.args) def __unicode__(self): res = [] res.append('(') first = True for a in self.args: if not first: res.append(', ') first = False res.append(text_type(a)) res.append(')') if self.volatile: res.append(' volatile') if self.const: res.append(' const') if self.refQual: res.append(' ') res.append(self.refQual) if self.exceptionSpec: res.append(' ') res.append(text_type(self.exceptionSpec)) if self.final: res.append(' final') if self.override: res.append(' override') if self.initializer: res.append(' = ') res.append(self.initializer) return u''.join(res) def describe_signature(self, signode, mode, env): _verify_description_mode(mode) paramlist = addnodes.desc_parameterlist() for arg in self.args: param = addnodes.desc_parameter('', '', noemph=True) if mode == 'lastIsName': # i.e., outer-function params arg.describe_signature(param, 'param', env) else: arg.describe_signature(param, 'markType', env) paramlist += param signode += paramlist def _add_anno(signode, text): signode += nodes.Text(' ') signode += addnodes.desc_annotation(text, text) def _add_text(signode, text): signode += nodes.Text(' ' + text) if self.volatile: _add_anno(signode, 'volatile') if self.const: _add_anno(signode, 'const') if self.refQual: _add_text(signode, self.refQual) if self.exceptionSpec: _add_anno(signode, text_type(self.exceptionSpec)) if self.final: _add_anno(signode, 'final') if self.override: _add_anno(signode, 'override') if self.initializer: _add_text(signode, '= ' + text_type(self.initializer)) class ASTDeclSpecs(ASTBase): def __init__(self, outer, visibility, storage, inline, virtual, explicit, constexpr, volatile, const, trailing): self.outer = outer self.visibility = visibility self.storage = storage self.inline = inline self.virtual = virtual self.explicit = explicit self.constexpr = constexpr self.volatile = volatile self.const = const self.trailingTypeSpec = trailing @property def name(self): return self.trailingTypeSpec.name def get_id(self): res = [] if self.volatile: res.append('V') if self.const: res.append('K') res.append(self.trailingTypeSpec.get_id()) return u''.join(res) def _print_visibility(self): return (self.visibility and not ( self.outer in ('type', 'member', 'function') and self.visibility == 'public')) def __unicode__(self): res = [] if self._print_visibility(): res.append(self.visibility) if self.storage: res.append(self.storage) if self.inline: res.append('inline') if self.virtual: res.append('virtual') if self.explicit: res.append('explicit') if self.constexpr: res.append('constexpr') if self.volatile: res.append('volatile') if self.const: res.append('const') if self.trailingTypeSpec: res.append(text_type(self.trailingTypeSpec)) return u' '.join(res) def describe_signature(self, signode, mode, env): _verify_description_mode(mode) modifiers = [] def _add(modifiers, text): if len(modifiers) > 0: modifiers.append(nodes.Text(' ')) modifiers.append(addnodes.desc_annotation(text, text)) if self._print_visibility(): _add(modifiers, self.visibility) if self.storage: _add(modifiers, self.storage) if self.inline: _add(modifiers, 'inline') if self.virtual: _add(modifiers, 'virtual') if self.explicit: _add(modifiers, 'explicit') if self.constexpr: _add(modifiers, 'constexpr') if self.volatile: _add(modifiers, 'volatile') if self.const: _add(modifiers, 'const') for m in modifiers: signode += m if self.trailingTypeSpec: if len(modifiers) > 0: signode += nodes.Text(' ') self.trailingTypeSpec.describe_signature(signode, mode, env) class ASTPtrOpPtr(ASTBase): def __init__(self, volatile, const): self.volatile = volatile self.const = const def __unicode__(self): res = ['*'] if self.volatile: res.append('volatile ') if self.const: res.append('const ') return u''.join(res) def get_id(self): res = ['P'] if self.volatile: res.append('V') if self.const: res.append('C') return u''.join(res) class ASTPtrOpRef(ASTBase): def __unicode__(self): return '&' def get_id(self): return 'R' class ASTPtrOpParamPack(ASTBase): def __unicode__(self): return '...' def get_id(self): return 'Dp' class ASTArray(ASTBase): def __init__(self, size): self.size = size def __unicode__(self): return u''.join(['[', text_type(self.size), ']']) def get_id(self): # TODO: this should maybe be done differently return u'A' + text_type(self.size) + u'_' def describe_signature(self, signode, mode, env): _verify_description_mode(mode) signode += nodes.Text(text_type(self)) class ASTDeclerator(ASTBase): def __init__(self, ptrOps, declId, suffixOps): self.ptrOps = ptrOps self.declId = declId self.suffixOps = suffixOps @property def name(self): return self.declId def get_modifiers_id(self): # only the modifiers for a function, e.g., # cv-qualifiers for op in self.suffixOps: if isinstance(op, ASTParametersQualifiers): return op.get_modifiers_id() raise Exception( "This should only be called on a function: %s" % text_type(self)) def get_param_id(self): # only the parameters (if any) for op in self.suffixOps: if isinstance(op, ASTParametersQualifiers): return op.get_param_id() return '' def get_ptr_suffix_id(self): # only the ptr ops and array specifiers return u''.join( a.get_id() for a in self.ptrOps + self.suffixOps if not isinstance(a, ASTParametersQualifiers)) def require_start_space(self): if (len(self.ptrOps) > 0 and isinstance(self.ptrOps[-1], ASTPtrOpParamPack)): return False else: return self.declId is not None def __unicode__(self): res = [] for op in self.ptrOps: res.append(text_type(op)) if isinstance(op, ASTPtrOpParamPack) and self.declId: res.append(' ') if self.declId: res.append(text_type(self.declId)) for op in self.suffixOps: res.append(text_type(op)) return u''.join(res) def describe_signature(self, signode, mode, env): _verify_description_mode(mode) for op in self.ptrOps: signode += nodes.Text(text_type(op)) if isinstance(op, ASTPtrOpParamPack) and self.declId: signode += nodes.Text(' ') if self.declId: self.declId.describe_signature(signode, mode, env) for op in self.suffixOps: op.describe_signature(signode, mode, env) class ASTInitializer(ASTBase): def __init__(self, value): self.value = value def __unicode__(self): return u''.join([' = ', text_type(self.value)]) def describe_signature(self, signode, mode): _verify_description_mode(mode) signode += nodes.Text(text_type(self)) class ASTType(ASTBase): def __init__(self, declSpecs, decl): self.declSpecs = declSpecs self.decl = decl self.objectType = None @property def name(self): name = self.decl.name if not name: name = self.declSpecs.name return name def get_id(self): res = [] if self.objectType: # needs the name res.append(_id_prefix) if self.objectType == 'function': # also modifiers res.append(self.decl.get_modifiers_id()) res.append(self.prefixedName.get_id()) res.append(self.decl.get_param_id()) elif self.objectType == 'type': # just the name res.append(self.prefixedName.get_id()) else: print(self.objectType) assert False else: # only type encoding res.append(self.decl.get_ptr_suffix_id()) res.append(self.declSpecs.get_id()) res.append(self.decl.get_param_id()) return u''.join(res) def __unicode__(self): res = [] declSpecs = text_type(self.declSpecs) res.append(declSpecs) if self.decl.require_start_space() and len(declSpecs) > 0: res.append(u' ') res.append(text_type(self.decl)) return u''.join(res) def describe_signature(self, signode, mode, env): _verify_description_mode(mode) self.declSpecs.describe_signature(signode, 'markType', env) if (self.decl.require_start_space() and len(text_type(self.declSpecs)) > 0): signode += nodes.Text(' ') self.decl.describe_signature(signode, mode, env) class ASTTypeWithInit(ASTBase): def __init__(self, type, init): self.objectType = None self.type = type self.init = init @property def name(self): return self.type.name def get_id(self): if self.objectType == 'member': return _id_prefix + self.prefixedName.get_id() else: return self.type.get_id() def __unicode__(self): res = [] res.append(text_type(self.type)) 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.type.describe_signature(signode, mode, env) if self.init: self.init.describe_signature(signode, mode) class ASTBaseClass(ASTBase): def __init__(self, name, visibility): self.name = name self.visibility = visibility def __unicode__(self): res = [] if self.visibility != 'private': res.append(self.visibility) res.append(' ') res.append(text_type(self.name)) return u''.join(res) def describe_signature(self, signode, mode, env): _verify_description_mode(mode) if self.visibility != 'private': signode += addnodes.desc_annotation( self.visibility, self.visibility) signode += nodes.Text(' ') self.name.describe_signature(signode, mode, env) class ASTClass(ASTBase): def __init__(self, name, bases): self.name = name self.bases = bases def get_id(self): return _id_prefix + self.prefixedName.get_id() def __unicode__(self): res = [] res.append(text_type(self.name)) if len(self.bases) > 0: res.append(' : ') first = True for b in self.bases: if not first: res.append(', ') first = False res.append(text_type(b)) return u''.join(res) def describe_signature(self, signode, mode, env): _verify_description_mode(mode) self.name.describe_signature(signode, mode, env) if len(self.bases) > 0: signode += nodes.Text(' : ') for b in self.bases: b.describe_signature(signode, mode, env) signode += nodes.Text(', ') signode.pop() class DefinitionParser(object): # those without signedness and size modifiers # see http://en.cppreference.com/w/cpp/language/types _simple_fundemental_types = ( 'void', 'bool', 'char', 'wchar_t', 'char16_t', 'char32_t', 'int', 'float', 'double', 'auto' ) _prefix_keys = ('class', 'struct', 'union', 'typename') 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): indicator = '-' * self.pos + '^' raise DefinitionError( 'Invalid definition: %s [error at %d]\n %s\n %s' % (msg, self.pos, self.definition, indicator)) 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_word(self, word): return self.match(re.compile(r'\b%s\b' % re.escape(word))) def skip_ws(self): return self.match(_whitespace_re) def skip_word_and_ws(self, word): if self.skip_word(word): self.skip_ws() return True return False @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 read_rest(self): rv = self.definition[self.pos:] self.pos = self.end return rv def assert_end(self): self.skip_ws() if not self.eof: self.fail('expected end of definition, got %r' % self.definition[self.pos:]) def _parse_operator(self): self.skip_ws() # adapted from the old code # thank god, a regular operator definition if self.match(_operator_re): return ASTOperatorBuildIn(self.matched_text) # new/delete operator? for op in 'new', 'delete': if not self.skip_word(op): continue self.skip_ws() if self.skip_string('['): self.skip_ws() if not self.skip_string(']'): self.fail('Expected "]" after "operator ' + op + '["') op += '[]' return ASTOperatorBuildIn(op) # oh well, looks like a cast operator definition. # In that case, eat another type. type = self._parse_type() return ASTOperatorType(type) def _parse_nested_name(self): names = [] self.skip_ws() if self.skip_string('::'): names.append(u'') while 1: self.skip_ws() # TODO: parse the "template" keyword if not self.match(_identifier_re): self.fail("expected identifier") identifier = self.matched_text if identifier == 'operator': op = self._parse_operator() names.append(op) else: templateArgs = None self.skip_ws() if self.skip_string('<'): templateArgs = [] while 1: pos = self.pos try: type = self._parse_type(allowParams=True) templateArgs.append(type) except DefinitionError: self.pos = pos symbols = [] startPos = self.pos self.skip_ws() if self.match(_string_re): value = self.matched_text else: while not self.eof: if (len(symbols) == 0 and self.current_char in ( ',', '>')): break # TODO: actually implement nice handling # of quotes, braces, brackets, parens, and # whatever self.pos += 1 if self.eof: self.pos = startPos self.fail( 'Could not find end of constant ' 'template argument.') value = self.definition[startPos:self.pos].strip() templateArgs.append(ASTTemplateArgConstant(value)) self.skip_ws() if self.skip_string('>'): break elif self.skip_string(','): continue else: self.fail('Expected ">" or "," in template ' 'argument list.') names.append(ASTNestedNameElement(identifier, templateArgs)) self.skip_ws() if not self.skip_string('::'): break return ASTNestedName(names) def _parse_trailing_type_spec(self): # fundemental types self.skip_ws() for t in self._simple_fundemental_types: if self.skip_word(t): return ASTTrailingTypeSpecFundamental(t) # TODO: this could/should be more strict elements = [] self.skip_ws() if self.skip_word_and_ws('signed'): elements.append('signed') elif self.skip_word_and_ws('unsigned'): elements.append('unsigned') while 1: if self.skip_word_and_ws('short'): elements.append('short') elif self.skip_word_and_ws('long'): elements.append('long') else: break if self.skip_word_and_ws('int'): elements.append('int') elif self.skip_word_and_ws('double'): elements.append('double') if len(elements) > 0: return ASTTrailingTypeSpecFundamental(u' '.join(elements)) # decltype self.skip_ws() if self.skip_word_and_ws('decltype'): self.fail('"decltype(.)" in trailing_type_spec not implemented') # prefixed prefix = None self.skip_ws() for k in self._prefix_keys: if self.skip_word_and_ws(k): prefix = k break nestedName = self._parse_nested_name() return ASTTrailingTypeSpecName(prefix, nestedName) def _parse_parameters_and_qualifiers(self, paramMode): self.skip_ws() if not self.skip_string('('): if paramMode == 'function': self.fail('Expecting "(" in parameters_and_qualifiers.') else: return None args = [] self.skip_ws() if not self.skip_string(')'): while 1: self.skip_ws() if self.skip_string('...'): args.append(ASTFunctinoParameter(None, True)) self.skip_ws() if not self.skip_string(')'): self.fail('Expected ")" after "..." in ' 'parameters_and_qualifiers.') break if paramMode == 'function': arg = self._parse_type_with_init(named='maybe') else: arg = self._parse_type() # TODO: parse default parameters args.append(ASTFunctinoParameter(arg)) self.skip_ws() if self.skip_string(','): continue elif self.skip_string(')'): break else: self.fail( 'Expecting "," or ")" in parameters_and_qualifiers, ' 'got "%s".' % self.current_char) if paramMode != 'function': return ASTParametersQualifiers( args, None, None, None, None, None, None, None) self.skip_ws() const = self.skip_word_and_ws('const') volatile = self.skip_word_and_ws('volatile') if not const: # the can be permuted const = self.skip_word_and_ws('const') refQual = None if self.skip_string('&&'): refQual = '&&' if not refQual and self.skip_string('&'): refQual = '&' exceptionSpec = None override = None final = None initializer = None self.skip_ws() if self.skip_string('noexcept'): exceptionSpec = 'noexcept' self.skip_ws() if self.skip_string('('): self.fail('Parameterised "noexcept" not implemented.') self.skip_ws() override = self.skip_word_and_ws('override') final = self.skip_word_and_ws('final') if not override: override = self.skip_word_and_ws( 'override') # they can be permuted self.skip_ws() if self.skip_string('='): self.skip_ws() valid = ('0', 'delete', 'default') for w in valid: if self.skip_word_and_ws(w): initializer = w break if not initializer: self.fail( 'Expected "%s" in initializer-specifier.' % u'" or "'.join(valid)) return ASTParametersQualifiers( args, volatile, const, refQual, exceptionSpec, override, final, initializer) def _parse_decl_specs(self, outer, typed=True): """ visibility storage-class-specifier function-specifier "constexpr" "volatile" "const" trailing-type-specifier storage-class-specifier -> "static" (only for member_object and function_object) function-specifier -> "inline" | "virtual" | "explicit" (only for function_object) "constexpr" (only for member_object and function_object) """ visibility = None storage = None inline = None virtual = None explicit = None constexpr = None volatile = None const = None if outer: self.skip_ws() if self.match(_visibility_re): visibility = self.matched_text while 1: # accept any permutation of a subset of some decl-specs self.skip_ws() if not storage: if outer in ('member', 'function'): if self.skip_word('static'): storage = 'static' continue if outer == 'member': if self.skip_word('mutable'): storage = 'mutable' continue if outer == 'fuction': # TODO: maybe in more contexts, missing test cases if self.skip_word('register'): storage = 'register' continue if outer == 'function': # function-specifiers if not inline: inline = self.skip_word('inline') if inline: continue if not virtual: virtual = self.skip_word('virtual') if virtual: continue if not explicit: explicit = self.skip_word('explicit') if explicit: continue if not constexpr and outer in ('member', 'function'): constexpr = self.skip_word("constexpr") if constexpr: continue if not volatile and typed: volatile = self.skip_word('volatile') if volatile: continue if not const and typed: const = self.skip_word('const') if const: continue break if typed: trailing = self._parse_trailing_type_spec() else: trailing = None return ASTDeclSpecs( outer, visibility, storage, inline, virtual, explicit, constexpr, volatile, const, trailing) def _parse_declerator(self, named, paramMode=None, typed=True): if paramMode: if paramMode not in ('type', 'function'): raise Exception( "Internal error, unknown paramMode '%s'." % paramMode) ptrOps = [] while 1: if not typed: break self.skip_ws() if self.skip_string('*'): self.skip_ws() volatile = self.skip_word_and_ws('volatile') const = self.skip_word_and_ws('const') ptrOps.append(ASTPtrOpPtr(volatile=volatile, const=const)) elif self.skip_string('&'): ptrOps.append(ASTPtrOpRef()) elif self.skip_string('...'): ptrOps.append(ASTPtrOpParamPack()) break else: break if named == 'maybe': try: declId = self._parse_nested_name() except DefinitionError: declId = None elif named: declId = self._parse_nested_name() else: declId = None suffixOpts = [] while 1: self.skip_ws() if typed and self.skip_string('['): startPos = self.pos - 1 openCount = 1 while not self.eof: c = self.current_char if c == '[': openCount += 1 elif c == ']': openCount -= 1 if openCount == 0: break self.pos += 1 if self.eof: self.pos = startPos self.fail( "Could not find closing square bracket for array.") self.pos += 1 suffixOpts.append(ASTArray( self.definition[startPos + 1:self.pos - 1].strip())) continue if paramMode: paramQual = self._parse_parameters_and_qualifiers(paramMode) if paramQual: suffixOpts.append(paramQual) break return ASTDeclerator(ptrOps, declId, suffixOpts) def _parse_initializer(self, outer=None): self.skip_ws() # TODO: support paren and brace initialization for memberObject if not self.skip_string('='): return None else: if outer == 'member': value = self.read_rest().strip() return ASTInitializer(value) elif outer is None: # function parameter symbols = [] startPos = self.pos self.skip_ws() if self.match(_string_re): value = self.matched_text return ASTInitializer(value) while not self.eof: if len(symbols) == 0 and self.current_char in (',', ')'): break elif len(symbols) > 0 and self.current_char == symbols[-1]: symbols.pop() elif self.current_char == '(': symbols.append(')') # TODO: actually implement nice handling of quotes, braces, # brackets, parens, and whatever self.pos += 1 if self.eof: self.pos = startPos self.fail( 'Could not find end of default value for function ' 'parameter.') value = self.definition[startPos:self.pos].strip() return ASTInitializer(value) else: self.fail( "Internal error, initializer for outer '%s' not " "implemented." % outer) def _parse_type(self, outer=None, named=False, allowParams=False): """ named=False|'maybe'|True: 'maybe' is e.g., for function objects which doesn't need to name the arguments """ if outer: # always named if outer not in ('type', 'member', 'function'): raise Exception('Internal error, unknown outer "%s".' % outer) assert not named if outer in ('type', 'function'): # We allow type objects to just be a name. # Some functions don't have normal return types: constructors, # destrutors, cast operators startPos = self.pos # first try without the type try: declSpecs = self._parse_decl_specs(outer=outer, typed=False) decl = self._parse_declerator(named=True, paramMode=outer, typed=False) self.assert_end() except DefinitionError as exUntyped: self.pos = startPos try: declSpecs = self._parse_decl_specs(outer=outer) decl = self._parse_declerator(named=True, paramMode=outer) except DefinitionError as exTyped: if outer == 'type': raise DefinitionError( 'Type must be either just a name or a ' 'typedef-like declaration.\nJust a name error: ' '%s\nTypedef-like expression error: %s' % (exUntyped.description, exTyped.description)) else: # do it again to get the proper traceback (how do you # relieable save a traceback when an exception is # constructed?) self.pos = startPos declSpecs = self._parse_decl_specs(outer=outer) decl = self._parse_declerator(named=True, paramMode=outer) else: if outer: named = True allowParams = True if allowParams: paramMode = 'type' else: paramMode = None declSpecs = self._parse_decl_specs(outer=outer) decl = self._parse_declerator(named=named, paramMode=paramMode) return ASTType(declSpecs, decl) def _parse_type_with_init(self, outer=None, named=False): if outer: assert outer in ('type', 'member', 'function') type = self._parse_type(outer=outer, named=named) init = self._parse_initializer(outer=outer) return ASTTypeWithInit(type, init) def _parse_class(self): name = self._parse_nested_name() bases = [] self.skip_ws() if self.skip_string(':'): while 1: self.skip_ws() visibility = 'private' if self.match(_visibility_re): visibility = self.matched_text baseName = self._parse_nested_name() bases.append(ASTBaseClass(baseName, visibility)) self.skip_ws() if self.skip_string(','): continue else: break return ASTClass(name, bases) def parse_type_object(self): res = self._parse_type(outer='type') res.objectType = 'type' return res def parse_member_object(self): res = self._parse_type_with_init(outer='member') res.objectType = 'member' return res def parse_function_object(self): res = self._parse_type(outer='function') res.objectType = 'function' return res def parse_class_object(self): res = self._parse_class() res.objectType = 'class' return res def parse_namespace_object(self): res = self._parse_nested_name() res.objectType = 'namespace' return res def parse_xref_object(self): res = self._parse_nested_name() res.objectType = 'xref' return res class CPPObject(ObjectDescription): """Description of a C++ language object.""" doc_field_types = [ GroupedField('parameter', label=l_('Parameters'), names=('param', 'parameter', 'arg', 'argument'), can_collapse=True), GroupedField('exceptions', label=l_('Throws'), rolename='cpp:class', names=('throws', 'throw', 'exception'), can_collapse=True), Field('returnvalue', label=l_('Returns'), has_arg=False, names=('returns', 'return')), ] def add_target_and_index(self, ast, sig, signode): theid = ast.get_id() name = text_type(ast.prefixedName) if theid not in self.state.document.ids: # the name is not unique, the first one will win objects = self.env.domaindata['cpp']['objects'] if name not in objects: signode['names'].append(name) signode['ids'].append(theid) signode['first'] = (not self.names) self.state.document.note_explicit_target(signode) if name not in objects: objects.setdefault(name, (self.env.docname, ast.objectType, theid)) # 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)) self.env.ref_context['cpp:lastname'] = ast.prefixedName indextext = self.get_index_text(name) if not re.compile(r'^[a-zA-Z0-9_]*$').match(theid): self.state_machine.reporter.warning( 'Index id generation for C++ object "%s" failed, please ' 'report as bug (id=%s).' % (text_type(ast), theid), line=self.lineno) self.indexnode['entries'].append(('single', indextext, theid, '')) def parse_definition(self, parser): raise NotImplementedError() def describe_signature(self, signode, ast): raise NotImplementedError() def handle_signature(self, sig, signode): parser = DefinitionParser(sig) try: ast = self.parse_definition(parser) parser.assert_end() except DefinitionError as e: self.state_machine.reporter.warning(e.description, line=self.lineno) raise ValueError self.describe_signature(signode, ast) parent = self.env.ref_context.get('cpp:parent') if parent and len(parent) > 0: ast = ast.clone() ast.prefixedName = ast.name.prefix_nested_name(parent[-1]) else: ast.prefixedName = ast.name return ast class CPPTypeObject(CPPObject): def get_index_text(self, name): return _('%s (C++ type)') % name def parse_definition(self, parser): return parser.parse_type_object() def describe_signature(self, signode, ast): signode += addnodes.desc_annotation('type ', 'type ') ast.describe_signature(signode, 'lastIsName', self.env) class CPPMemberObject(CPPObject): def get_index_text(self, name): return _('%s (C++ member)') % name def parse_definition(self, parser): return parser.parse_member_object() def describe_signature(self, signode, ast): ast.describe_signature(signode, 'lastIsName', self.env) class CPPFunctionObject(CPPObject): def get_index_text(self, name): return _('%s (C++ function)') % name def parse_definition(self, parser): return parser.parse_function_object() def describe_signature(self, signode, ast): ast.describe_signature(signode, 'lastIsName', self.env) class CPPClassObject(CPPObject): def get_index_text(self, name): return _('%s (C++ class)') % name def before_content(self): lastname = self.env.ref_context['cpp:lastname'] assert lastname if 'cpp:parent' in self.env.ref_context: self.env.ref_context['cpp:parent'].append(lastname) else: self.env.ref_context['cpp:parent'] = [lastname] def after_content(self): self.env.ref_context['cpp:parent'].pop() def parse_definition(self, parser): return parser.parse_class_object() def describe_signature(self, signode, ast): signode += addnodes.desc_annotation('class ', 'class ') ast.describe_signature(signode, 'lastIsName', self.env) class CPPNamespaceObject(Directive): """ This directive is just to tell Sphinx that we're documenting stuff in namespace foo. """ has_content = False required_arguments = 1 optional_arguments = 0 final_argument_whitespace = True option_spec = {} def run(self): env = self.state.document.settings.env if self.arguments[0].strip() in ('NULL', '0', 'nullptr'): env.ref_context['cpp:parent'] = [] else: parser = DefinitionParser(self.arguments[0]) try: prefix = parser.parse_namespace_object() parser.assert_end() except DefinitionError as e: self.state_machine.reporter.warning(e.description, line=self.lineno) else: env.ref_context['cpp:parent'] = [prefix] return [] class CPPXRefRole(XRefRole): def process_link(self, env, refnode, has_explicit_title, title, target): parent = env.ref_context.get('cpp:parent') if parent: refnode['cpp:parent'] = parent[:] if not has_explicit_title: target = target.lstrip('~') # only has a meaning for the title # if the first character is a tilde, don't display the module/class # parts of the contents if title[:1] == '~': title = title[1:] dcolon = title.rfind('::') if dcolon != -1: title = title[dcolon + 2:] return title, target class CPPDomain(Domain): """C++ language domain.""" name = 'cpp' label = 'C++' object_types = { 'class': ObjType(l_('class'), 'class'), 'function': ObjType(l_('function'), 'func'), 'member': ObjType(l_('member'), 'member'), 'type': ObjType(l_('type'), 'type') } directives = { 'class': CPPClassObject, 'function': CPPFunctionObject, 'member': CPPMemberObject, 'type': CPPTypeObject, 'namespace': CPPNamespaceObject } roles = { 'class': CPPXRefRole(), 'func': CPPXRefRole(fix_parens=True), 'member': CPPXRefRole(), 'type': CPPXRefRole() } initial_data = { 'objects': {}, # prefixedName -> (docname, objectType, id) } def clear_doc(self, docname): for fullname, data in list(self.data['objects'].items()): if data[0] == docname: del self.data['objects'][fullname] def merge_domaindata(self, docnames, otherdata): # XXX check duplicates for fullname, data in otherdata['objects'].items(): if data[0] in docnames: self.data['objects'][fullname] = data def _resolve_xref_inner(self, env, fromdocname, builder, target, node, contnode, warn=True): def _create_refnode(nameAst): name = text_type(nameAst) if name not in self.data['objects']: # try dropping the last template name = nameAst.get_name_no_last_template() if name not in self.data['objects']: return None, None docname, objectType, id = self.data['objects'][name] return make_refnode(builder, fromdocname, docname, id, contnode, name), objectType parser = DefinitionParser(target) try: nameAst = parser.parse_xref_object().name parser.skip_ws() if not parser.eof: raise DefinitionError('') except DefinitionError: if warn: env.warn_node('unparseable C++ definition: %r' % target, node) return None, None # try as is the name is fully qualified res = _create_refnode(nameAst) if res[0]: return res # try qualifying it with the parent parent = node.get('cpp:parent', None) if parent and len(parent) > 0: return _create_refnode(nameAst.prefix_nested_name(parent[-1])) else: return None, None def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): return self._resolve_xref_inner(env, fromdocname, builder, target, node, contnode)[0] def resolve_any_xref(self, env, fromdocname, builder, target, node, contnode): node, objtype = self._resolve_xref_inner(env, fromdocname, builder, target, node, contnode, warn=False) if node: return [('cpp:' + self.role_for_objtype(objtype), node)] return [] def get_objects(self): for refname, (docname, type, theid) in iteritems(self.data['objects']): yield (refname, refname, type, docname, refname, 1)