diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 64985a37f..9248a35e0 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -529,7 +529,8 @@ _id_operator_v2 = { '->': 'pt', '()': 'cl', '[]': 'ix', - '.*': 'ds' # this one is not overloadable, but we need it for expressions + '.*': 'ds', # this one is not overloadable, but we need it for expressions + '?': 'cn', } _id_operator_unary_v2 = { '++': 'pp_', @@ -1518,6 +1519,44 @@ class ASTBinOpExpr(ASTExpression): self.exprs[i].describe_signature(signode, mode, env, symbol) +class ASTConditionalExpr(ASTExpression): + def __init__(self, if_expr: ASTExpression, then_expr: ASTExpression, + else_expr: ASTExpression): + self.if_expr = if_expr + self.then_expr = then_expr + self.else_expr = else_expr + + def _stringify(self, transform: StringifyTransform) -> str: + res = [] + res.append(transform(self.if_expr)) + res.append(' ? ') + res.append(transform(self.then_expr)) + res.append(' : ') + res.append(transform(self.else_expr)) + return ''.join(res) + + def get_id(self, version: int) -> str: + assert version >= 2 + res = [] + res.append(_id_operator_v2['?']) + res.append(self.if_expr.get_id(version)) + res.append(self.then_expr.get_id(version)) + res.append(self.else_expr.get_id(version)) + return ''.join(res) + + def describe_signature(self, signode: TextElement, mode: str, + env: "BuildEnvironment", symbol: "Symbol") -> None: + self.if_expr.describe_signature(signode, mode, env, symbol) + signode += addnodes.desc_sig_space() + signode += addnodes.desc_sig_operator('?', '?') + signode += addnodes.desc_sig_space() + self.then_expr.describe_signature(signode, mode, env, symbol) + signode += addnodes.desc_sig_space() + signode += addnodes.desc_sig_operator(':', ':') + signode += addnodes.desc_sig_space() + self.else_expr.describe_signature(signode, mode, env, symbol) + + class ASTBracedInitList(ASTBase): def __init__(self, exprs: List[Union[ASTExpression, "ASTBracedInitList"]], trailingComma: bool) -> None: @@ -5613,9 +5652,17 @@ class DefinitionParser(BaseParser): return ASTBinOpExpr(exprs, ops) return _parse_bin_op_expr(self, 0, inTemplate=inTemplate) - def _parse_conditional_expression_tail(self, orExprHead: Any) -> None: + def _parse_conditional_expression_tail(self, orExprHead: ASTExpression, + inTemplate: bool) -> Optional[ASTConditionalExpr]: # -> "?" expression ":" assignment-expression - return None + if not self.skip_string("?"): + return None + then_expr = self._parse_expression() + self.skip_ws() + if not self.skip_string(":"): + self.fail('Expected ":" after "?"') + else_expr = self._parse_assignment_expression(inTemplate) + return ASTConditionalExpr(orExprHead, then_expr, else_expr) def _parse_assignment_expression(self, inTemplate: bool) -> ASTExpression: # -> conditional-expression @@ -5631,10 +5678,15 @@ class DefinitionParser(BaseParser): ops = [] orExpr = self._parse_logical_or_expression(inTemplate=inTemplate) exprs.append(orExpr) - # TODO: handle ternary with _parse_conditional_expression_tail while True: oneMore = False self.skip_ws() + prev_expr = exprs[-1] + if isinstance(prev_expr, ASTExpression): + cond_expr = self._parse_conditional_expression_tail(prev_expr, inTemplate) + if cond_expr is not None: + exprs[-1] = cond_expr + continue for op in _expression_assignment_ops: if op[0] in 'anox': if not self.skip_word(op): @@ -5649,14 +5701,17 @@ class DefinitionParser(BaseParser): if not oneMore: break if len(ops) == 0: - return orExpr + return cast(ASTExpression, exprs[-1]) else: return ASTAssignmentExpr(exprs, ops) def _parse_constant_expression(self, inTemplate: bool) -> ASTExpression: # -> conditional-expression orExpr = self._parse_logical_or_expression(inTemplate=inTemplate) - # TODO: use _parse_conditional_expression_tail + self.skip_ws() + cond_expr = self._parse_conditional_expression_tail(orExpr, inTemplate) + if cond_expr is not None: + return cond_expr return orExpr def _parse_expression(self) -> ASTExpression: diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index 72ffc474d..230c97ccd 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -326,7 +326,8 @@ def test_domain_cpp_ast_expressions(): exprCheck('5 .* 42', 'dsL5EL42E') exprCheck('5 ->* 42', 'pmL5EL42E') # conditional - # TODO + exprCheck('5 ? 7 : 3', 'cnL5EL7EL3E') + exprCheck('5 = 6 ? 7 = 8 : 3', 'aSL5EcnL6EaSL7EL8EL3E') # assignment exprCheck('a = 5', 'aS1aL5E') exprCheck('a *= 5', 'mL1aL5E')