Merge pull request #1710 from jakobandersen/cpp-old-style-links

C++, reintroduction of old id generation scheme. Fixes #1704.
This commit is contained in:
Georg Brandl 2015-02-06 19:57:58 +01:00
commit 097c1be57a
2 changed files with 361 additions and 64 deletions

View File

@ -7,10 +7,22 @@
:copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
"""
Important note on ids:
Multiple id generation schemes are used due to backwards compatibility.
- v1: 1.2.3 <= version < 1.3
The style used before the rewrite.
It is not the actual old code, but a replication of the behaviour.
- v2: 1.3 <= version < now
Standardised mangling scheme from
http://mentorembedded.github.io/cxx-abi/abi.html#mangling
though not completely implemented.
All versions are generated and attached to elements. The newest is used for
the index. All of the versions should work as permalinks.
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
@ -173,8 +185,86 @@ _operator_re = re.compile(r'''(?x)
| [!<>=/*%+|&^~-]=?
''')
_id_prefix = '_CPP'
_id_fundamental = {
#-------------------------------------------------------------------------------
# Id v1 constants
#-------------------------------------------------------------------------------
_id_fundamental_v1 = {
'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'
}
_id_shorthands_v1 = {
'std::string': 'ss',
'std::ostream': 'os',
'std::istream': 'is',
'std::iostream': 'ios',
'std::vector': 'v',
'std::map': 'm'
}
_id_operator_v1 = {
'new': 'new-operator',
'new[]': 'new-array-operator',
'delete': 'delete-operator',
'delete[]': 'delete-array-operator',
# the arguments will make the difference between unary and binary
# '+(unary)' : 'ps',
# '-(unary)' : 'ng',
# '&(unary)' : 'ad',
# '*(unary)' : 'de',
'~': 'inv-operator',
'+': 'add-operator',
'-': 'sub-operator',
'*': 'mul-operator',
'/': 'div-operator',
'%': 'mod-operator',
'&': 'and-operator',
'|': 'or-operator',
'^': 'xor-operator',
'=': 'assign-operator',
'+=': 'add-assign-operator',
'-=': 'sub-assign-operator',
'*=': 'mul-assign-operator',
'/=': 'div-assign-operator',
'%=': 'mod-assign-operator',
'&=': 'and-assign-operator',
'|=': 'or-assign-operator',
'^=': 'xor-assign-operator',
'<<': 'lshift-operator',
'>>': 'rshift-operator',
'<<=': 'lshift-assign-operator',
'>>=': 'rshift-assign-operator',
'==': 'eq-operator',
'!=': 'neq-operator',
'<': 'lt-operator',
'>': 'gt-operator',
'<=': 'lte-operator',
'>=': 'gte-operator',
'!': 'not-operator',
'&&': 'sand-operator',
'||': 'sor-operator',
'++': 'inc-operator',
'--': 'dec-operator',
',': 'comma-operator',
'->*': 'pointer-by-pointer-operator',
'->': 'pointer-operator',
'()': 'call-operator',
'[]': 'subscript-operator'
}
#-------------------------------------------------------------------------------
# Id v2 constants
#-------------------------------------------------------------------------------
_id_prefix_v2 = '_CPPv2'
_id_fundamental_v2 = {
# not all of these are actually parsed as fundamental types, TODO: do that
'void': 'v',
'bool': 'b',
@ -214,7 +304,7 @@ _id_fundamental = {
'decltype(auto)': 'Dc',
'std::nullptr_t': 'Dn'
}
_id_operator = {
_id_operator_v2 = {
'new': 'nw',
'new[]': 'na',
'delete': 'dl',
@ -294,8 +384,12 @@ class ASTBase(UnicodeMixin):
"""Clone a definition expression node."""
return deepcopy(self)
def get_id(self):
"""Return the id for the node."""
def get_id_v1(self):
"""Return the v1 id for the node."""
raise NotImplementedError(repr(self))
def get_id_v2(self):
"""Return the v2 id for the node."""
raise NotImplementedError(repr(self))
def get_name(self):
@ -326,11 +420,17 @@ class ASTOperatorBuildIn(ASTBase):
def __init__(self, op):
self.op = op
def get_id(self):
if self.op not in _id_operator:
def get_id_v1(self):
if self.op not in _id_operator_v1:
raise Exception('Internal error: Build-in operator "%s" can not '
'be mapped to an id.' % self.op)
return _id_operator[self.op]
return _id_operator_v1[self.op]
def get_id_v2(self):
if self.op not in _id_operator_v2:
raise Exception('Internal error: Build-in operator "%s" can not '
'be mapped to an id.' % self.op)
return _id_operator_v2[self.op]
def __unicode__(self):
if self.op in ('new', 'new[]', 'delete', 'delete[]'):
@ -357,8 +457,11 @@ 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_id_v1(self):
return u'castto-%s-operator' % self.type.get_id_v1()
def get_id_v2(self):
return u'cv' + self.type.get_id_v2()
def get_name_no_template(self):
return text_type(self)
@ -379,7 +482,10 @@ class ASTTemplateArgConstant(ASTBase):
def __unicode__(self):
return text_type(self.value)
def get_id(self):
def get_id_v1(self):
return text_type(self).replace(u' ', u'-')
def get_id_v2(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'
@ -394,17 +500,32 @@ class ASTNestedNameElement(ASTBase):
self.identifier = identifier
self.templateArgs = templateArgs
def get_id(self):
def get_id_v1(self):
res = []
if self.identifier == 'size_t':
res.append('s')
else:
res.append(self.identifier)
if self.templateArgs:
res.append(':')
res.append(u'.'.join(a.get_id_v1() for a in self.templateArgs))
res.append(':')
return u''.join(res)
def get_id_v2(self):
res = []
if self.identifier == "std":
res.append(u'St')
elif self.identifier[0] == "~":
# a destructor, just use an arbitrary version of dtors
res.append("D0")
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(a.get_id_v2())
res.append('E')
return u''.join(res)
@ -462,13 +583,24 @@ class ASTNestedName(ASTBase):
def name(self):
return self
def get_id(self):
def get_id_v1(self):
tt = text_type(self)
if tt in _id_shorthands_v1:
return _id_shorthands_v1[tt]
else:
res = []
for n in self.names:
res.append(n.get_id_v1())
return u'::'.join(res)
def get_id_v2(self, modifiers=""):
res = []
if len(self.names) > 1:
if len(self.names) > 1 or len(modifiers) > 0:
res.append('N')
res.append(modifiers)
for n in self.names:
res.append(n.get_id())
if len(self.names) > 1:
res.append(n.get_id_v2())
if len(self.names) > 1 or len(modifiers) > 0:
res.append('E')
return u''.join(res)
@ -530,13 +662,22 @@ class ASTTrailingTypeSpecFundamental(ASTBase):
def __unicode__(self):
return self.name
def get_id(self):
if self.name not in _id_fundamental:
def get_id_v1(self):
res = []
for a in self.name.split(' '):
if a in _id_fundamental_v1:
res.append(_id_fundamental_v1[a])
else:
res.append(a)
return u'-'.join(res)
def get_id_v2(self):
if self.name not in _id_fundamental_v2:
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]
return _id_fundamental_v2[self.name]
def describe_signature(self, signode, mode, env):
signode += nodes.Text(text_type(self.name))
@ -551,8 +692,11 @@ class ASTTrailingTypeSpecName(ASTBase):
def name(self):
return self.nestedName
def get_id(self):
return self.nestedName.get_id()
def get_id_v1(self):
return self.nestedName.get_id_v1()
def get_id_v2(self):
return self.nestedName.get_id_v2()
def __unicode__(self):
res = []
@ -574,11 +718,17 @@ class ASTFunctinoParameter(ASTBase):
self.arg = arg
self.ellipsis = ellipsis
def get_id(self):
def get_id_v1(self):
if self.ellipsis:
return 'z'
else:
return self.arg.get_id()
return self.arg.get_id_v1()
def get_id_v2(self):
if self.ellipsis:
return 'z'
else:
return self.arg.get_id_v2()
def __unicode__(self):
if self.ellipsis:
@ -606,7 +756,29 @@ class ASTParametersQualifiers(ASTBase):
self.final = final
self.initializer = initializer
def get_modifiers_id(self):
# Id v1 ------------------------------------------------------------------
def get_modifiers_id_v1(self):
res = []
if self.volatile:
res.append('V')
if self.const:
res.append('C')
if self.refQual == '&&':
res.append('O')
elif self.refQual == '&':
res.append('R')
return u''.join(res)
def get_param_id_v1(self):
if len(self.args) == 0:
return ''
else:
return u'__' + u'.'.join(a.get_id_v1() for a in self.args)
# Id v2 ------------------------------------------------------------------
def get_modifiers_id_v2(self):
res = []
if self.volatile:
res.append('V')
@ -618,11 +790,11 @@ class ASTParametersQualifiers(ASTBase):
res.append('R')
return u''.join(res)
def get_param_id(self):
def get_param_id_v2(self):
if len(self.args) == 0:
return 'v'
else:
return u''.join(a.get_id() for a in self.args)
return u''.join(a.get_id_v2() for a in self.args)
def __unicode__(self):
res = []
@ -698,6 +870,17 @@ class ASTDeclSpecsSimple(ASTBase):
self.volatile = volatile
self.const = const
def mergeWith(self, other):
if not other:
return self
return ASTDeclSpecsSimple(self.storage or other.storage,
self.inline or other.inline,
self.virtual or other.virtual,
self.explicit or other.explicit,
self.constexpr or other.constexpr,
self.volatile or other.volatile,
self.const or other.const)
def __unicode__(self):
res = []
if self.storage:
@ -736,32 +919,44 @@ class ASTDeclSpecsSimple(ASTBase):
if self.const:
_add(modifiers, 'const')
class ASTDeclSpecs(ASTBase):
def __init__(self, outer, visibility, leftSpecs, rightSpecs, trailing):
# leftSpecs and rightSpecs are used for output
# allSpecs are used for id generation
self.outer = outer
self.visibility = visibility
self.leftSpecs = leftSpecs
self.rightSpecs = rightSpecs
self.allSpecs = self.leftSpecs.mergeWith(self.rightSpecs)
self.trailingTypeSpec = trailing
@property
def name(self):
return self.trailingTypeSpec.name
def get_id(self):
def get_id_v1(self):
res = []
res.append(self.trailingTypeSpec.get_id_v1())
if self.allSpecs.volatile:
res.append('V')
if self.allSpecs.const:
res.append('C')
return u''.join(res)
def get_id_v2(self):
res = []
if self.leftSpecs.volatile or self.rightSpecs.volatile:
res.append('V')
if self.leftSpecs.const or self.rightSpecs.volatile:
res.append('K')
res.append(self.trailingTypeSpec.get_id())
res.append(self.trailingTypeSpec.get_id_v2())
return u''.join(res)
def _print_visibility(self):
return (self.visibility and
not (
self.outer in ('type', 'member', 'function') and
self.visibility == 'public'))
not (self.outer in ('type', 'member', 'function') and
self.visibility == 'public'))
def __unicode__(self):
res = []
@ -809,6 +1004,7 @@ class ASTDeclSpecs(ASTBase):
for m in modifiers:
signode += m
class ASTPtrOpPtr(ASTBase):
def __init__(self, volatile, const):
self.volatile = volatile
@ -822,7 +1018,15 @@ class ASTPtrOpPtr(ASTBase):
res.append('const ')
return u''.join(res)
def get_id(self):
def get_id_v1(self):
res = ['P']
if self.volatile:
res.append('V')
if self.const:
res.append('C')
return u''.join(res)
def get_id_v2(self):
res = ['P']
if self.volatile:
res.append('V')
@ -835,7 +1039,10 @@ class ASTPtrOpRef(ASTBase):
def __unicode__(self):
return '&'
def get_id(self):
def get_id_v1(self):
return 'R'
def get_id_v2(self):
return 'R'
@ -843,7 +1050,10 @@ class ASTPtrOpParamPack(ASTBase):
def __unicode__(self):
return '...'
def get_id(self):
def get_id_v1(self):
return 'Dp'
def get_id_v2(self):
return 'Dp'
@ -854,7 +1064,10 @@ class ASTArray(ASTBase):
def __unicode__(self):
return u''.join(['[', text_type(self.size), ']'])
def get_id(self):
def get_id_v1(self):
return u'A'
def get_id_v2(self):
# TODO: this should maybe be done differently
return u'A' + text_type(self.size) + u'_'
@ -873,23 +1086,47 @@ class ASTDeclerator(ASTBase):
def name(self):
return self.declId
def get_modifiers_id(self): # only the modifiers for a function, e.g.,
# Id v1 ------------------------------------------------------------------
def get_modifiers_id_v1(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()
return op.get_modifiers_id_v1()
raise Exception(
"This should only be called on a function: %s" % text_type(self))
def get_param_id(self): # only the parameters (if any)
def get_param_id_v1(self): # only the parameters (if any)
for op in self.suffixOps:
if isinstance(op, ASTParametersQualifiers):
return op.get_param_id()
return op.get_param_id_v1()
return ''
def get_ptr_suffix_id(self): # only the ptr ops and array specifiers
def get_ptr_suffix_id_v1(self): # only the ptr ops and array specifiers
return u''.join(
a.get_id()
a.get_id_v1()
for a in self.ptrOps + self.suffixOps
if not isinstance(a, ASTParametersQualifiers))
# Id v2 ------------------------------------------------------------------
def get_modifiers_id_v2(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_v2()
raise Exception(
"This should only be called on a function: %s" % text_type(self))
def get_param_id_v2(self): # only the parameters (if any)
for op in self.suffixOps:
if isinstance(op, ASTParametersQualifiers):
return op.get_param_id_v2()
return ''
def get_ptr_suffix_id_v2(self): # only the ptr ops and array specifiers
return u''.join(
a.get_id_v2()
for a in self.ptrOps + self.suffixOps
if not isinstance(a, ASTParametersQualifiers))
@ -949,23 +1186,45 @@ class ASTType(ASTBase):
name = self.declSpecs.name
return name
def get_id(self):
def get_id_v1(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())
res.append(self.name.get_id_v1())
res.append(self.decl.get_param_id_v1())
res.append(self.decl.get_modifiers_id_v1())
if (self.declSpecs.leftSpecs.constexpr
or (self.declSpecs.rightSpecs
and self.declSpecs.rightSpecs.constexpr)):
res.append('CE')
elif self.objectType == 'type': # just the name
res.append(self.prefixedName.get_id())
res.append(self.name.get_id_v1())
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())
res.append(self.declSpecs.get_id_v1())
res.append(self.decl.get_ptr_suffix_id_v1())
res.append(self.decl.get_param_id_v1())
return u''.join(res)
def get_id_v2(self):
res = []
if self.objectType: # needs the name
res.append(_id_prefix_v2)
if self.objectType == 'function': # also modifiers
modifiers = self.decl.get_modifiers_id_v2()
res.append(self.prefixedName.get_id_v2(modifiers))
res.append(self.decl.get_param_id_v2())
elif self.objectType == 'type': # just the name
res.append(self.prefixedName.get_id_v2())
else:
print(self.objectType)
assert False
else: # only type encoding
res.append(self.decl.get_ptr_suffix_id_v2())
res.append(self.declSpecs.get_id_v2())
res.append(self.decl.get_param_id_v2())
return u''.join(res)
def __unicode__(self):
@ -985,6 +1244,7 @@ class ASTType(ASTBase):
signode += nodes.Text(' ')
self.decl.describe_signature(signode, mode, env)
class ASTTypeWithInit(ASTBase):
def __init__(self, type, init):
self.objectType = None
@ -995,11 +1255,17 @@ class ASTTypeWithInit(ASTBase):
def name(self):
return self.type.name
def get_id(self):
def get_id_v1(self):
if self.objectType == 'member':
return _id_prefix + self.prefixedName.get_id()
return self.name.get_id_v1() + u'__' + self.type.get_id_v1()
else:
return self.type.get_id()
return self.type.get_id_v1()
def get_id_v2(self):
if self.objectType == 'member':
return _id_prefix_v2 + self.prefixedName.get_id_v2()
else:
return self.type.get_id_v2()
def __unicode__(self):
res = []
@ -1042,8 +1308,15 @@ class ASTClass(ASTBase):
self.name = name
self.bases = bases
def get_id(self):
return _id_prefix + self.prefixedName.get_id()
def get_id_v1(self):
return self.name.get_id_v1()
#name = _id_shortwords.get(self.name)
#if name is not None:
# return name
#return self.name.replace(u' ', u'-')
def get_id_v2(self):
return _id_prefix_v2 + self.prefixedName.get_id_v2()
def __unicode__(self):
res = []
@ -1682,14 +1955,22 @@ class CPPObject(ObjectDescription):
]
def add_target_and_index(self, ast, sig, signode):
theid = ast.get_id()
ids = [ # the newest should be first
ast.get_id_v2(),
ast.get_id_v1()
]
theid = ids[0]
name = text_type(ast.prefixedName)
if theid not in self.state.document.ids:
# the name is not unique, the first one will win
# if 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)
else:
pass
#print("[CPP] non-unique name:", name)
for id in ids:
signode['ids'].append(id)
signode['first'] = (not self.names)
self.state.document.note_explicit_target(signode)
if name not in objects:

View File

@ -15,6 +15,8 @@ from util import raises
from sphinx.domains.cpp import DefinitionParser, DefinitionError
ids = []
def parse(name, string):
parser = DefinitionParser(string)
res = getattr(parser, "parse_" + name + "_object")()
@ -37,9 +39,12 @@ def check(name, input, output=None):
print("Expected: ", output)
raise DefinitionError("")
ast.describe_signature([], 'lastIsName', None)
ast.prefixedName = ast.name # otherwise the get_id fails, it would be set
# in handle_signarue
ast.get_id()
# Artificially set the prefixedName, otherwise the get_id fails.
# It would usually have been set in handle_signarue.
ast.prefixedName = ast.name
ast.get_id_v1()
ast.get_id_v2()
ids.append(ast.get_id_v2())
#print ".. %s:: %s" % (name, input)
def test_type_definitions():
@ -78,10 +83,14 @@ def test_type_definitions():
check('member', 'module::myclass foo[n]')
check('function', 'operator bool() const')
check('function', 'A::operator bool() const')
check('function', 'A::operator bool() volatile const &')
check('function', 'A::operator bool() volatile const &&')
check('function', 'bool namespaced::theclass::method(arg1, arg2)')
x = 'std::vector<std::pair<std::string, int>> &module::test(register ' \
'foo, bar, std::string baz = "foobar, blah, bleh") const = 0'
check('function', x)
check('function', 'void f(std::pair<A, B>)')
check('function', 'explicit module::myclass::foo::foo()')
check('function', 'module::myclass::foo::~foo()')
check('function', 'int printf(const char *fmt, ...)')
@ -97,6 +106,7 @@ def test_type_definitions():
check('function', 'static constexpr int get_value()')
check('function', 'int get_value() const noexcept')
check('function', 'int get_value() const noexcept = delete')
check('function', 'int get_value() volatile const')
check('function', 'MyClass::MyClass(MyClass::MyClass&&) = default')
check('function', 'virtual MyClass::a_virtual_function() const override')
check('function', 'A B() override')
@ -140,3 +150,9 @@ def test_operators():
check('function', 'void operator bool() const', 'void operator bool() const')
for op in '*-+=/%!':
check('function', 'void operator %s ()' % op, 'void operator%s()' % op)
#def test_print():
# # used for getting all the ids out for checking
# for a in ids:
# print(a)
# raise DefinitionError("")