diff options
author | Jakob Lykke Andersen <jakob@caput.dk> | 2014-08-06 17:41:51 +0200 |
---|---|---|
committer | Jakob Lykke Andersen <jakob@caput.dk> | 2014-08-06 17:41:51 +0200 |
commit | f7458cd87327d00756a4e9c29f7b0f6bc34ebcb1 (patch) | |
tree | eab76a9ddd9e39fbf749319560e827d638640252 | |
parent | c1eb39a7b2898ff84a4d511b55984d55badccd6a (diff) | |
download | sphinx-f7458cd87327d00756a4e9c29f7b0f6bc34ebcb1.tar.gz |
C++, update id generation to use name mangling
-rw-r--r-- | sphinx/domains/cpp.py | 341 | ||||
-rw-r--r-- | tests/test_cpp_domain.py | 10 |
2 files changed, 231 insertions, 120 deletions
diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 9a30a58f..446a7758 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -8,7 +8,8 @@ :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. - See http://www.nongnu.org/hcb/ + 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 @@ -153,68 +154,80 @@ _operator_re = re.compile(r'''(?x) | [!<>=/*%+|&^~-]=? ''') -_id_shortwords = { - 'char': 'c', - 'signed char': 'c', - 'unsigned char': 'C', - 'int': 'i', - 'signed int': 'i', - 'unsigned int': 'U', - 'long': 'l', - 'signed long': 'l', - 'unsigned long': 'L', - 'bool': 'b', - 'size_t': 's', - 'std::string': 'ss', - 'std::ostream': 'os', - 'std::istream': 'is', - 'std::iostream': 'ios', - 'std::vector': 'v', - 'std::map': 'm', - 'operator[]': 'subscript-operator', - 'operator()': 'call-operator', - 'operator!': 'not-operator', - 'operator<': 'lt-operator', - 'operator<=': 'lte-operator', - 'operator>': 'gt-operator', - 'operator>=': 'gte-operator', - 'operator=': 'assign-operator', - 'operator/': 'div-operator', - 'operator*': 'mul-operator', - 'operator%': 'mod-operator', - 'operator+': 'add-operator', - 'operator-': 'sub-operator', - 'operator|': 'or-operator', - 'operator&': 'and-operator', - 'operator^': 'xor-operator', - 'operator&&': 'sand-operator', - 'operator||': 'sor-operator', - 'operator==': 'eq-operator', - 'operator!=': 'neq-operator', - 'operator<<': 'lshift-operator', - 'operator>>': 'rshift-operator', - 'operator-=': 'sub-assign-operator', - 'operator+=': 'add-assign-operator', - 'operator*-': 'mul-assign-operator', - 'operator/=': 'div-assign-operator', - 'operator%=': 'mod-assign-operator', - 'operator&=': 'and-assign-operator', - 'operator|=': 'or-assign-operator', - 'operator<<=': 'lshift-assign-operator', - 'operator>>=': 'rshift-assign-operator', - 'operator^=': 'xor-assign-operator', - 'operator,': 'comma-operator', - 'operator->': 'pointer-operator', - 'operator->*': 'pointer-by-pointer-operator', - 'operator~': 'inv-operator', - 'operator++': 'inc-operator', - 'operator--': 'dec-operator', - 'operator new': 'new-operator', - 'operator new[]': 'new-array-operator', - 'operator delete': 'delete-operator', - 'operator delete[]': 'delete-array-operator' +_id_prefix = '_CPP' +_id_fundamental = { # not all of these are actually parsed as fundamental types, TODO: do that + 'void' : 'v', + 'wchar_t' : 'w', + 'bool' : 'b', + 'char' : 'c', + 'signed char' : 'a', + 'unsigned char' : 'h', + 'short' : 's', + 'unsigned short' : 't', + 'int' : 'i', + 'unsigned int' : 'j', + 'long' : 'l', + 'unsigned long' : 'm', + 'long long' : 'x', + 'unsigned long long' : 'y', + 'float' : 'f', + 'double' : 'd', + 'long double' : 'e', + 'char32_t' : 'Di', + 'char16_t' : 'Ds', + '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): @@ -277,8 +290,15 @@ class ASTOperatorBuildIn(ASTBase): def __init__(self, op): self.op = op + def get_id(self): + if not self.op 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): - return u''.join(['operator', self.op]) + 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) @@ -299,6 +319,9 @@ class ASTOperatorType(ASTBase): 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) @@ -318,6 +341,10 @@ class ASTTemplateArgConstant(ASTBase): 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)) @@ -328,6 +355,20 @@ class ASTNestedNameElement(ASTBase): 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) @@ -379,6 +420,16 @@ class ASTNestedName(ASTBase): 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 += '::' @@ -426,7 +477,7 @@ class ASTNestedName(ASTBase): else: raise Exception('Unknown description mode: %s' % mode) -class ASTTrailingTypeSpecFundemental(ASTBase): +class ASTTrailingTypeSpecFundamental(ASTBase): def __init__(self, name): self.name = name @@ -434,6 +485,11 @@ class ASTTrailingTypeSpecFundemental(ASTBase): def __unicode__(self): return self.name + def get_id(self): + if not self.name 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)) @@ -446,6 +502,9 @@ class ASTTrailingTypeSpecName(ASTBase): @property def name(self): return self.nestedName + + def get_id(self): + return self.nestedName.get_id() def __unicode__(self): res = [] @@ -467,9 +526,9 @@ class ASTFunctinoParameter(ASTBase): self.arg = arg self.ellipsis = ellipsis - def get_type_id(self): - if self.ellipsis: return '...' - else: return self.arg.get_type_id() + def get_id(self): + if self.ellipsis: return 'z' + else: return self.arg.get_id() def __unicode__(self): if self.ellipsis: return '...' @@ -492,18 +551,17 @@ class ASTParametersQualifiers(ASTBase): self.final = final self.initializer = initializer - def get_param_id(self): - # return an id encoding whatever participates in overload resolution - args = [] - for a in self.args: - args.append(a.get_type_id()) + def get_modifiers_id(self): res = [] - res.append(u'_'.join(args)) - if self.const: res.append('C') - else: res.append('') - if self.refQual: res.append(self.refQual) - else: res.append('') - return u'__'.join(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 = [] @@ -570,8 +628,12 @@ class ASTDeclSpecs(ASTBase): def name(self): return self.trailingTypeSpec.name - def get_type_id(self): - return text_type(self) + 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') @@ -610,6 +672,40 @@ class ASTDeclSpecs(ASTBase): 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): @@ -618,6 +714,10 @@ class ASTArray(ASTBase): 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)) @@ -633,24 +733,30 @@ class ASTDeclerator(ASTBase): def name(self): return self.declId - def get_param_id(self): + 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_param_id() + return op.get_modifiers_id() raise Exception("This should only be called on a function: %s" % text_type(self)) - def get_type_id(self): - return u''.join([text_type(op) for op in (self.ptrOps + self.suffixOps)]) + 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 '...' in self.ptrOps: return False + if len(self.ptrOps) > 0 and isinstance(self.ptrOps[-1], ASTPtrOpParamPack): return False else: return self.declId != None def __unicode__(self): res = [] for op in self.ptrOps: res.append(text_type(op)) - if op == '...' and self.declId: res.append(' ') + 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)) @@ -660,7 +766,7 @@ class ASTDeclerator(ASTBase): _verify_description_mode(mode) for op in self.ptrOps: signode += nodes.Text(text_type(op)) - if op == '...' and self.declId: signode += nodes.Text(' ') + 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) @@ -681,6 +787,7 @@ class ASTType(ASTBase): def __init__(self, declSpecs, decl): self.declSpecs = declSpecs self.decl = decl + self.objectType = None @property def name(self): @@ -689,20 +796,23 @@ class ASTType(ASTBase): return name def get_id(self): - res = ['___'] - res.append(text_type(self.prefixedName)) - if self.objectType == 'function': - res.append('___') + 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()) - elif self.objectType == 'type': - pass - else: - print(self.objectType) - assert False return u''.join(res) - - def get_type_id(self): - return self.declSpecs.get_type_id() + self.decl.get_type_id() def __unicode__(self): res = [] @@ -723,6 +833,7 @@ class ASTType(ASTBase): class ASTTypeWithInit(ASTBase): def __init__(self, type, init): + self.objectType = None self.type = type self.init = init @@ -732,12 +843,9 @@ class ASTTypeWithInit(ASTBase): def get_id(self): if self.objectType == 'member': - return "___" + text_type(self.prefixedName) + return _id_prefix + self.prefixedName.get_id() else: - raise NotImplementedError("Should this happen? %s, %s" % (self.objectType, text_type(self.name))) - - def get_type_id(self): - return self.type.get_type_id() + return self.type.get_id() def __unicode__(self): res = [] @@ -778,7 +886,7 @@ class ASTClass(ASTBase): self.bases = bases def get_id(self): - return "___" + text_type(self.prefixedName) + return _id_prefix + self.prefixedName.get_id() def __unicode__(self): res = [] @@ -899,7 +1007,7 @@ class DefinitionParser(object): if not self.skip_string(']'): self.fail('Expected "]" after "operator ' + op + '["') op += '[]' - return ASTOperatorBuildIn(' ' + op) + return ASTOperatorBuildIn(op) # oh well, looks like a cast operator definition. # In that case, eat another type. @@ -962,7 +1070,7 @@ class DefinitionParser(object): # fundemental types self.skip_ws() for t in self._simple_fundemental_types: - if self.skip_word(t): return ASTTrailingTypeSpecFundemental(t) + if self.skip_word(t): return ASTTrailingTypeSpecFundamental(t) # TODO: this could/should be more strict elements = [] @@ -975,7 +1083,7 @@ class DefinitionParser(object): 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 ASTTrailingTypeSpecFundemental(u' '.join(elements)) + if len(elements) > 0: return ASTTrailingTypeSpecFundamental(u' '.join(elements)) # decltype self.skip_ws() @@ -1137,14 +1245,13 @@ class DefinitionParser(object): if not typed: break self.skip_ws() if self.skip_string('*'): - op = '*' self.skip_ws() - if self.skip_word_and_ws('volatile'): op += 'volatile ' - if self.skip_word_and_ws('const'): op += 'const ' - ptrOps.append(op) - elif self.skip_string('&'): ptrOps.append('&') + 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('...') + ptrOps.append(ASTPtrOpParamPack()) break else: break @@ -1341,8 +1448,10 @@ class CPPObject(ObjectDescription): self.env.temp_data['cpp:lastname'] = ast.prefixedName indextext = self.get_index_text(name) - if indextext: - self.indexnode['entries'].append(('single', indextext, theid, '')) + 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() diff --git a/tests/test_cpp_domain.py b/tests/test_cpp_domain.py index 8750ddbe..7be8fe3f 100644 --- a/tests/test_cpp_domain.py +++ b/tests/test_cpp_domain.py @@ -31,11 +31,13 @@ def check(name, input, output=None): ast = parse(name, input) res = text_type(ast) if res != output: - print("Input: ", text_type(input)) - print("Result:", res) + print "Input: ", text_type(input) + print "Result: ", res + print "Expected: ", output raise DefinitionError("") - # now check describe_signature ast.describe_signature([], 'lastIsName', None) + ast.prefixedName = ast.name # otherwise the get_id fails, it would be set in handle_signarue + ast.get_id() #print ".. %s:: %s" % (name, input) def test_type_definitions(): @@ -92,7 +94,7 @@ def test_type_definitions(): check('function', 'MyClass::a_member_function() &&') check('function', 'MyClass::a_member_function() &') check('function', 'MyClass::a_member_function() const &') - check('function', 'int main(int argc, char *argv[][])') + check('function', 'int main(int argc, char *argv[])') check('function', 'MyClass &MyClass::operator++()') check('function', 'MyClass::pointer MyClass::operator->()') |