C++, fix parsing of initializers

Specifically, add parsing of braced-init-list.

See also michaeljones/breathe#425
This commit is contained in:
Jakob Lykke Andersen 2019-03-16 17:29:53 +01:00
parent 9b6edde726
commit ceb72a78b8
3 changed files with 230 additions and 119 deletions

View File

@ -31,6 +31,7 @@ Bugs fixed
* AssertionError is raised when custom ``citation_reference`` node having
classes attribute refers missing citation (refs: #6147)
* #2155: Support ``code`` directive
* C++, fix parsing of braced initializers.
Testing
--------

View File

@ -1159,15 +1159,12 @@ class ASTNoexceptExpr(ASTBase):
class ASTNewExpr(ASTBase):
def __init__(self, rooted, isNewTypeId, typ, initList, initType):
# type: (bool, bool, ASTType, List[Any], str) -> None
def __init__(self, rooted, isNewTypeId, typ, initList):
# type: (bool, bool, ASTType, Any) -> None
self.rooted = rooted
self.isNewTypeId = isNewTypeId
self.typ = typ
self.initList = initList
self.initType = initType
if self.initList is not None:
assert self.initType in ')}'
def _stringify(self, transform):
# type: (Callable[[Any], str]) -> str
@ -1181,15 +1178,7 @@ class ASTNewExpr(ASTBase):
else:
assert False
if self.initList is not None:
if self.initType == ')':
res.append('(')
first = True
for e in self.initList:
if not first:
res.append(', ')
first = False
res.append(transform(e))
res.append(self.initType)
res.append(transform(self.initList))
return ''.join(res)
def get_id(self, version):
@ -1200,13 +1189,7 @@ class ASTNewExpr(ASTBase):
res.append('_')
res.append(self.typ.get_id(version))
if self.initList is not None:
if self.initType == ')':
res.append('pi')
for e in self.initList:
res.append(e.get_id(version))
res.append('E')
else:
assert False
res.append(self.initList.get_id(version))
else:
res.append('E')
return ''.join(res)
@ -1221,17 +1204,7 @@ class ASTNewExpr(ASTBase):
else:
assert False
if self.initList is not None:
if self.initType == ')':
signode.append(nodes.Text('('))
first = True
for e in self.initList:
if not first:
signode.append(nodes.Text(', '))
first = False
e.describe_signature(signode, mode, env, symbol)
signode.append(nodes.Text(')'))
else:
assert False
self.initList.describe_signature(signode, mode, env, symbol)
class ASTDeleteExpr(ASTBase):
@ -1323,38 +1296,24 @@ class ASTTypeId(ASTBase):
class ASTPostfixCallExpr(ASTBase):
def __init__(self, exprs):
self.exprs = exprs
def __init__(self, lst):
# type: (Union[ASTParenExprList, ASTBracedInitList]) -> None
self.lst = lst
def _stringify(self, transform):
# type: (Callable[[Any], str]) -> str
res = ['(']
first = True
for e in self.exprs:
if not first:
res.append(', ')
first = False
res.append(transform(e))
res.append(')')
return ''.join(res)
return transform(self.lst)
def get_id(self, idPrefix, version):
# type: (str, int) -> str
res = ['cl', idPrefix]
for e in self.exprs:
for e in self.lst.exprs:
res.append(e.get_id(version))
res.append('E')
return ''.join(res)
def describe_signature(self, signode, mode, env, symbol):
signode.append(nodes.Text('('))
first = True
for e in self.exprs:
if not first:
signode.append(nodes.Text(', '))
first = False
e.describe_signature(signode, mode, env, symbol)
signode.append(nodes.Text(')'))
self.lst.describe_signature(signode, mode, env, symbol)
class ASTPostfixArray(ASTBase):
@ -3232,17 +3191,84 @@ class ASTDeclaratorNameParamQual(ASTBase):
self.paramQual.describe_signature(signode, mode, env, symbol)
class ASTInitializer(ASTBase):
def __init__(self, value):
self.value = value
class ASTParenExprList(ASTBase):
def __init__(self, exprs):
# type: (List[Any]) -> None
self.exprs = exprs
def get_id(self, version):
# type: (int) -> str
return "pi%sE" % ''.join(e.get_id(version) for e in self.exprs)
def _stringify(self, transform):
# type: (Callable[[Any], str]) -> str
return ' = ' + transform(self.value)
exprs = [transform(e) for e in self.exprs]
return '(%s)' % ', '.join(exprs)
def describe_signature(self, signode, mode, env, symbol):
# type: (addnodes.desc_signature, str, BuildEnvironment, Symbol) -> None
_verify_description_mode(mode)
signode.append(nodes.Text('('))
first = True
for e in self.exprs:
if not first:
signode.append(nodes.Text(', '))
else:
first = False
e.describe_signature(signode, mode, env, symbol)
signode.append(nodes.Text(')'))
class ASTBracedInitList(ASTBase):
def __init__(self, exprs, trailingComma):
# type: (List[Any], bool) -> None
self.exprs = exprs
self.trailingComma = trailingComma
def get_id(self, version):
# type: (int) -> str
return "il%sE" % ''.join(e.get_id(version) for e in self.exprs)
def _stringify(self, transform):
# type: (Callable[[Any], str]) -> str
exprs = [transform(e) for e in self.exprs]
trailingComma = ',' if self.trailingComma else ''
return '{%s%s}' % (', '.join(exprs), trailingComma)
def describe_signature(self, signode, mode, env, symbol):
# type: (addnodes.desc_signature, str, BuildEnvironment, Symbol) -> None
_verify_description_mode(mode)
signode.append(nodes.Text('{'))
first = True
for e in self.exprs:
if not first:
signode.append(nodes.Text(', '))
else:
first = False
e.describe_signature(signode, mode, env, symbol)
if self.trailingComma:
signode.append(nodes.Text(','))
signode.append(nodes.Text('}'))
class ASTInitializer(ASTBase):
def __init__(self, value, hasAssign=True):
# type: (Any, bool) -> None
self.value = value
self.hasAssign = hasAssign
def _stringify(self, transform):
# type: (Callable[[Any], str]) -> str
val = transform(self.value)
if self.hasAssign:
return ' = ' + val
else:
return val
def describe_signature(self, signode, mode, env, symbol):
# type: (addnodes.desc_signature, str, BuildEnvironment, Symbol) -> None
_verify_description_mode(mode)
if self.hasAssign:
signode.append(nodes.Text(' = '))
self.value.describe_signature(signode, 'markType', env, symbol)
@ -4844,21 +4870,19 @@ class DefinitionParser:
return res
return self._parse_nested_name()
def _parse_expression_list_or_braced_init_list(self):
# type: () -> Tuple[List[Any], str]
def _parse_initializer_list(self, name, open, close):
# type: (str, str, str) -> Tuple[List[Any], bool]
# Parse open and close with the actual initializer-list inbetween
# -> initializer-clause '...'[opt]
# | initializer-list ',' initializer-clause '...'[opt]
self.skip_ws()
if self.skip_string_and_ws('('):
close = ')'
name = 'parenthesized expression-list'
elif self.skip_string_and_ws('{'):
close = '}'
name = 'braced-init-list'
self.fail('Sorry, braced-init-list not yet supported.')
else:
if not self.skip_string_and_ws(open):
return None, None
if self.skip_string(close):
return [], False
exprs = []
self.skip_ws()
if not self.skip_string(close):
trailingComma = False
while True:
self.skip_ws()
expr = self._parse_expression(inTemplate=False)
@ -4870,9 +4894,43 @@ class DefinitionParser:
self.skip_ws()
if self.skip_string(close):
break
if not self.skip_string(','):
if not self.skip_string_and_ws(','):
self.fail("Error in %s, expected ',' or '%s'." % (name, close))
return exprs, close
if self.current_char == close and close == '}':
self.pos += 1
trailingComma = True
break
return exprs, trailingComma
def _parse_paren_expression_list(self):
# type: () -> ASTParenExprList
# -> '(' expression-list ')'
# though, we relax it to also allow empty parens
# as it's needed in some cases
#
# expression-list
# -> initializer-list
exprs, trailingComma = self._parse_initializer_list("parenthesized expression-list",
'(', ')')
if exprs is None:
return None
return ASTParenExprList(exprs)
def _parse_braced_init_list(self):
# type: () -> ASTBracedInitList
# -> '{' initializer-list ','[opt] '}'
# | '{' '}'
exprs, trailingComma = self._parse_initializer_list("braced-init-list", '{', '}')
if exprs is None:
return None
return ASTBracedInitList(exprs, trailingComma)
def _parse_expression_list_or_braced_init_list(self):
# type: () -> Union[ASTParenExprList, ASTBracedInitList]
paren = self._parse_paren_expression_list()
if paren is not None:
return paren
return self._parse_braced_init_list()
def _parse_postfix_expression(self):
# -> primary
@ -4980,7 +5038,7 @@ class DefinitionParser:
raise self._make_multi_error(errors, header)
# and now parse postfixes
postFixes = []
postFixes = [] # type: List[Any]
while True:
self.skip_ws()
if prefixType in ['expr', 'cast', 'typeid']:
@ -5000,7 +5058,7 @@ class DefinitionParser:
self.pos -= 3
else:
name = self._parse_nested_name()
postFixes.append(ASTPostfixMember(name)) # type: ignore
postFixes.append(ASTPostfixMember(name))
continue
if self.skip_string('->'):
if self.skip_string('*'):
@ -5008,21 +5066,17 @@ class DefinitionParser:
self.pos -= 3
else:
name = self._parse_nested_name()
postFixes.append(ASTPostfixMemberOfPointer(name)) # type: ignore
postFixes.append(ASTPostfixMemberOfPointer(name))
continue
if self.skip_string('++'):
postFixes.append(ASTPostfixInc()) # type: ignore
postFixes.append(ASTPostfixInc())
continue
if self.skip_string('--'):
postFixes.append(ASTPostfixDec()) # type: ignore
postFixes.append(ASTPostfixDec())
continue
lst, typ = self._parse_expression_list_or_braced_init_list()
lst = self._parse_expression_list_or_braced_init_list()
if lst is not None:
if typ == ')':
postFixes.append(ASTPostfixCallExpr(lst)) # type: ignore
else:
assert typ == '}'
assert False
postFixes.append(ASTPostfixCallExpr(lst))
continue
break
if len(postFixes) == 0:
@ -5105,10 +5159,8 @@ class DefinitionParser:
decl = self._parse_declarator(named=False, paramMode="new")
else:
self.fail("Sorry, parenthesised type-id in new expression not yet supported.")
lst, typ = self._parse_expression_list_or_braced_init_list()
if lst:
assert typ in ")}"
return ASTNewExpr(rooted, isNewTypeId, ASTType(declSpecs, decl), lst, typ)
lst = self._parse_expression_list_or_braced_init_list()
return ASTNewExpr(rooted, isNewTypeId, ASTType(declSpecs, decl), lst)
# delete-expression
pos = self.pos
rooted = self.skip_string('::')
@ -5267,7 +5319,7 @@ class DefinitionParser:
value = self.matched_text
else:
# TODO: add handling of more bracket-like things, and quote handling
brackets = {'(': ')', '[': ']', '<': '>'}
brackets = {'(': ')', '{': '}', '[': ']', '<': '>'}
symbols = [] # type: List[str]
while not self.eof:
if (len(symbols) == 0 and self.current_char in end):
@ -5825,29 +5877,51 @@ class DefinitionParser:
def _parse_initializer(self, outer=None, allowFallback=True):
# type: (str, bool) -> ASTInitializer
# initializer # global vars
# -> brace-or-equal-initializer
# | '(' expression-list ')'
#
# brace-or-equal-initializer # member vars
# -> '=' initializer-clause
# | braced-init-list
#
# initializer-clause # function params, non-type template params (with '=' in front)
# -> assignment-expression
# | braced-init-list
#
# we don't distinguish between global and member vars, so disallow paren:
#
# -> braced-init-list # var only
# | '=' assignment-expression
# | '=' braced-init-list
self.skip_ws()
# TODO: support paren and brace initialization for memberObject
if outer == 'member':
bracedInit = self._parse_braced_init_list()
if bracedInit is not None:
return ASTInitializer(bracedInit, hasAssign=False)
if not self.skip_string('='):
return None
else:
bracedInit = self._parse_braced_init_list()
if bracedInit is not None:
return ASTInitializer(bracedInit)
if outer == 'member':
def parser():
return self._parse_assignment_expression(inTemplate=False)
value = self._parse_expression_fallback([], parser,
allow=allowFallback)
fallbackEnd = [] # type: List[str]
elif outer == 'templateParam':
def parser():
return self._parse_assignment_expression(inTemplate=True)
value = self._parse_expression_fallback([',', '>'], parser,
allow=allowFallback)
fallbackEnd = [',', '>']
elif outer is None: # function parameter
def parser():
return self._parse_assignment_expression(inTemplate=False)
value = self._parse_expression_fallback([',', ')'], parser,
allow=allowFallback)
fallbackEnd = [',', ')']
else:
self.fail("Internal error, initializer for outer '%s' not "
"implemented." % outer)
inTemplate = outer == 'templateParam'
def parser():
return self._parse_assignment_expression(inTemplate=inTemplate)
value = self._parse_expression_fallback(fallbackEnd, parser, allow=allowFallback)
return ASTInitializer(value)
def _parse_type(self, named, outer=None):

View File

@ -194,6 +194,8 @@ def test_expressions():
exprCheck('new int()', 'nw_ipiE')
exprCheck('new int(5, 42)', 'nw_ipiL5EL42EE')
exprCheck('::new int', 'nw_iE')
exprCheck('new int{}', 'nw_iilE')
exprCheck('new int{5, 42}', 'nw_iilL5EL42EE')
# delete-expression
exprCheck('delete p', 'dl1p')
exprCheck('delete [] p', 'da1p')
@ -673,6 +675,40 @@ def test_template_args():
{2: "I0E21enable_if_not_array_t"})
def test_initializers():
idsMember = {1: 'v__T', 2:'1v'}
idsFunction = {1: 'f__T', 2: '1f1T'}
idsTemplate = {2: 'I_1TE1fv', 4: 'I_1TE1fvv'}
# no init
check('member', 'T v', idsMember)
check('function', 'void f(T v)', idsFunction)
check('function', 'template<T v> void f()', idsTemplate)
# with '=', assignment-expression
check('member', 'T v = 42', idsMember)
check('function', 'void f(T v = 42)', idsFunction)
check('function', 'template<T v = 42> void f()', idsTemplate)
# with '=', braced-init
check('member', 'T v = {}', idsMember)
check('function', 'void f(T v = {})', idsFunction)
check('function', 'template<T v = {}> void f()', idsTemplate)
check('member', 'T v = {42, 42, 42}', idsMember)
check('function', 'void f(T v = {42, 42, 42})', idsFunction)
check('function', 'template<T v = {42, 42, 42}> void f()', idsTemplate)
check('member', 'T v = {42, 42, 42,}', idsMember)
check('function', 'void f(T v = {42, 42, 42,})', idsFunction)
check('function', 'template<T v = {42, 42, 42,}> void f()', idsTemplate)
check('member', 'T v = {42, 42, args...}', idsMember)
check('function', 'void f(T v = {42, 42, args...})', idsFunction)
check('function', 'template<T v = {42, 42, args...}> void f()', idsTemplate)
# without '=', braced-init
check('member', 'T v{}', idsMember)
check('member', 'T v{42, 42, 42}', idsMember)
check('member', 'T v{42, 42, 42,}', idsMember)
check('member', 'T v{42, 42, args...}', idsMember)
# other
check('member', 'T v = T{}', idsMember)
def test_attributes():
# style: C++
check('member', '[[]] int f', {1: 'f__i', 2: '1f'})