Skip to content

Commit

Permalink
Merge pull request #10302 from jakobandersen/cpp_cond_expr
Browse files Browse the repository at this point in the history
C++, conditional expression (update of #10270)
  • Loading branch information
jakobandersen committed Apr 17, 2022
2 parents 128f0cc + 5e435fa commit d951e55
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 61 deletions.
1 change: 1 addition & 0 deletions CHANGES
Expand Up @@ -67,6 +67,7 @@ Features added
* #10028: Removed internal usages of JavaScript frameworks (jQuery and
underscore.js) and modernised ``doctools.js`` and ``searchtools.js`` to
EMCAScript 2018.
* #10302: C++, add support for conditional expressions (``?:``).

Bugs fixed
----------
Expand Down
166 changes: 106 additions & 60 deletions sphinx/domains/cpp.py
Expand Up @@ -529,7 +529,8 @@
'->': '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
'?': 'qu',
}
_id_operator_unary_v2 = {
'++': 'pp_',
Expand Down Expand Up @@ -1518,6 +1519,44 @@ def describe_signature(self, signode: TextElement, mode: str,
self.exprs[i].describe_signature(signode, mode, env, symbol)


class ASTConditionalExpr(ASTExpression):
def __init__(self, ifExpr: ASTExpression, thenExpr: ASTExpression,
elseExpr: ASTExpression):
self.ifExpr = ifExpr
self.thenExpr = thenExpr
self.elseExpr = elseExpr

def _stringify(self, transform: StringifyTransform) -> str:
res = []
res.append(transform(self.ifExpr))
res.append(' ? ')
res.append(transform(self.thenExpr))
res.append(' : ')
res.append(transform(self.elseExpr))
return ''.join(res)

def get_id(self, version: int) -> str:
assert version >= 2
res = []
res.append(_id_operator_v2['?'])
res.append(self.ifExpr.get_id(version))
res.append(self.thenExpr.get_id(version))
res.append(self.elseExpr.get_id(version))
return ''.join(res)

def describe_signature(self, signode: TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
self.ifExpr.describe_signature(signode, mode, env, symbol)
signode += addnodes.desc_sig_space()
signode += addnodes.desc_sig_operator('?', '?')
signode += addnodes.desc_sig_space()
self.thenExpr.describe_signature(signode, mode, env, symbol)
signode += addnodes.desc_sig_space()
signode += addnodes.desc_sig_operator(':', ':')
signode += addnodes.desc_sig_space()
self.elseExpr.describe_signature(signode, mode, env, symbol)


class ASTBracedInitList(ASTBase):
def __init__(self, exprs: List[Union[ASTExpression, "ASTBracedInitList"]],
trailingComma: bool) -> None:
Expand Down Expand Up @@ -1550,42 +1589,39 @@ def describe_signature(self, signode: TextElement, mode: str,


class ASTAssignmentExpr(ASTExpression):
def __init__(self, exprs: List[Union[ASTExpression, ASTBracedInitList]], ops: List[str]):
assert len(exprs) > 0
assert len(exprs) == len(ops) + 1
self.exprs = exprs
self.ops = ops
def __init__(self, leftExpr: ASTExpression, op: str,
rightExpr: Union[ASTExpression, ASTBracedInitList]):
self.leftExpr = leftExpr
self.op = op
self.rightExpr = rightExpr

def _stringify(self, transform: StringifyTransform) -> str:
res = []
res.append(transform(self.exprs[0]))
for i in range(1, len(self.exprs)):
res.append(' ')
res.append(self.ops[i - 1])
res.append(' ')
res.append(transform(self.exprs[i]))
res.append(transform(self.leftExpr))
res.append(' ')
res.append(self.op)
res.append(' ')
res.append(transform(self.rightExpr))
return ''.join(res)

def get_id(self, version: int) -> str:
# we end up generating the ID from left to right, instead of right to left
res = []
for i in range(len(self.ops)):
res.append(_id_operator_v2[self.ops[i]])
res.append(self.exprs[i].get_id(version))
res.append(self.exprs[-1].get_id(version))
res.append(_id_operator_v2[self.op])
res.append(self.leftExpr.get_id(version))
res.append(self.rightExpr.get_id(version))
return ''.join(res)

def describe_signature(self, signode: TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
self.exprs[0].describe_signature(signode, mode, env, symbol)
for i in range(1, len(self.exprs)):
signode += addnodes.desc_sig_space()
op = self.ops[i - 1]
if ord(op[0]) >= ord('a') and ord(op[0]) <= ord('z'):
signode += addnodes.desc_sig_keyword(op, op)
else:
signode += addnodes.desc_sig_operator(op, op)
signode += addnodes.desc_sig_space()
self.exprs[i].describe_signature(signode, mode, env, symbol)
self.leftExpr.describe_signature(signode, mode, env, symbol)
signode += addnodes.desc_sig_space()
if ord(self.op[0]) >= ord('a') and ord(self.op[0]) <= ord('z'):
signode += addnodes.desc_sig_keyword(self.op, self.op)
else:
signode += addnodes.desc_sig_operator(self.op, self.op)
signode += addnodes.desc_sig_space()
self.rightExpr.describe_signature(signode, mode, env, symbol)


class ASTCommaExpr(ASTExpression):
Expand Down Expand Up @@ -5613,50 +5649,60 @@ def parser(inTemplate: bool) -> ASTExpression:
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]:
# Consumes the orExprHead on success.

# -> "?" expression ":" assignment-expression
return None
self.skip_ws()
if not self.skip_string("?"):
return None
thenExpr = self._parse_expression()
self.skip_ws()
if not self.skip_string(":"):
self.fail('Expected ":" after then-expression in conditional expression.')
elseExpr = self._parse_assignment_expression(inTemplate)
return ASTConditionalExpr(orExprHead, thenExpr, elseExpr)

def _parse_assignment_expression(self, inTemplate: bool) -> ASTExpression:
# -> conditional-expression
# | logical-or-expression assignment-operator initializer-clause
# | throw-expression
# TODO: parse throw-expression: "throw" assignment-expression [opt]
# if not a throw expression, then:
# -> conditional-expression ->
# | yield-expression -> "co_yield" assignment-expression
# | "co_yield" braced-init-list
# | throw-expression -> "throw" assignment-expression[opt]
# TODO: yield-expression
# TODO: throw-expression

# Now we have (after expanding conditional-expression:
# logical-or-expression
# | logical-or-expression "?" expression ":" assignment-expression
# | logical-or-expression assignment-operator initializer-clause
exprs: List[Union[ASTExpression, ASTBracedInitList]] = []
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()
for op in _expression_assignment_ops:
if op[0] in 'anox':
if not self.skip_word(op):
continue
else:
if not self.skip_string(op):
continue
expr = self._parse_initializer_clause()
exprs.append(expr)
ops.append(op)
oneMore = True
if not oneMore:
break
if len(ops) == 0:
return orExpr
else:
return ASTAssignmentExpr(exprs, ops)
leftExpr = self._parse_logical_or_expression(inTemplate=inTemplate)
# the ternary operator
condExpr = self._parse_conditional_expression_tail(leftExpr, inTemplate)
if condExpr is not None:
return condExpr
# and actual assignment
for op in _expression_assignment_ops:
if op[0] in 'anox':
if not self.skip_word(op):
continue
else:
if not self.skip_string(op):
continue
rightExpr = self._parse_initializer_clause()
return ASTAssignmentExpr(leftExpr, op, rightExpr)
# just a logical-or-expression
return leftExpr

def _parse_constant_expression(self, inTemplate: bool) -> ASTExpression:
# -> conditional-expression
# -> conditional-expression ->
# logical-or-expression
# | logical-or-expression "?" expression ":" assignment-expression
orExpr = self._parse_logical_or_expression(inTemplate=inTemplate)
# TODO: use _parse_conditional_expression_tail
condExpr = self._parse_conditional_expression_tail(orExpr, inTemplate)
if condExpr is not None:
return condExpr
return orExpr

def _parse_expression(self) -> ASTExpression:
Expand Down Expand Up @@ -8025,7 +8071,7 @@ def initStuff(app):

return {
'version': 'builtin',
'env_version': 5,
'env_version': 6,
'parallel_read_safe': True,
'parallel_write_safe': True,
}
5 changes: 4 additions & 1 deletion tests/test_domain_cpp.py
Expand Up @@ -326,7 +326,7 @@ class Config:
exprCheck('5 .* 42', 'dsL5EL42E')
exprCheck('5 ->* 42', 'pmL5EL42E')
# conditional
# TODO
exprCheck('5 ? 7 : 3', 'quL5EL7EL3E')
# assignment
exprCheck('a = 5', 'aS1aL5E')
exprCheck('a *= 5', 'mL1aL5E')
Expand All @@ -343,6 +343,9 @@ class Config:
exprCheck('a |= 5', 'oR1aL5E')
exprCheck('a or_eq 5', 'oR1aL5E')
exprCheck('a = {{1, 2, 3}}', 'aS1ailL1EL2EL3EE')
# complex assignment and conditional
exprCheck('5 = 6 = 7', 'aSL5EaSL6EL7E')
exprCheck('5 = 6 ? 7 = 8 : 3', 'aSL5EquL6EaSL7EL8EL3E')
# comma operator
exprCheck('a, 5', 'cm1aL5E')

Expand Down

0 comments on commit d951e55

Please sign in to comment.