Skip to content

Commit 46affe2

Browse files
author
Nenad Čaklović
committed
Support for conditonal expression (if-expression)
1 parent f19b38d commit 46affe2

File tree

3 files changed

+96
-13
lines changed

3 files changed

+96
-13
lines changed

ASTNode.h

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class ASTNode {
1616
NODE_CONVERT, NODE_KEYWORD, NODE_RAISE, NODE_EXEC, NODE_BLOCK,
1717
NODE_COMPREHENSION, NODE_LOADBUILDCLASS, NODE_AWAITABLE,
1818
NODE_FORMATTEDVALUE, NODE_JOINEDSTR, NODE_CONST_MAP,
19-
NODE_ANNOTATED_VAR, NODE_CHAINSTORE,
19+
NODE_ANNOTATED_VAR, NODE_CHAINSTORE, NODE_TERNARY,
2020

2121
// Empty node types
2222
NODE_LOCALS,
@@ -699,4 +699,33 @@ class ASTAnnotatedVar : public ASTNode {
699699
PycRef<ASTNode> m_type;
700700
};
701701

702+
class ASTTernary : public ASTNode
703+
{
704+
public:
705+
ASTTernary(PycRef<ASTNode> if_block, PycRef<ASTNode> if_expr, PycRef<ASTNode> else_expr)
706+
: ASTNode(NODE_TERNARY),
707+
m_if_block(std::move(if_block)),
708+
m_if_expr(std::move(if_expr)),
709+
m_else_expr(std::move(else_expr))
710+
{
711+
}
712+
const PycRef<ASTNode>& if_block() const noexcept
713+
{
714+
return m_if_block;
715+
}
716+
const PycRef<ASTNode>& if_expr() const noexcept
717+
{
718+
return m_if_expr;
719+
}
720+
const PycRef<ASTNode>& else_expr() const noexcept
721+
{
722+
return m_else_expr;
723+
}
724+
725+
private:
726+
PycRef<ASTNode> m_if_block; // contains "condition" and "negative"
727+
PycRef<ASTNode> m_if_expr;
728+
PycRef<ASTNode> m_else_expr;
729+
};
730+
702731
#endif

ASTree.cpp

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,43 @@ static bool printDocstringAndGlobals = false;
2929
/* Use this to keep track of whether we need to print a class or module docstring */
3030
static bool printClassDocstring = true;
3131

32+
// shortcut for all top/pop calls
33+
PycRef<ASTNode> StackPopTop(FastStack& stack)
34+
{
35+
const auto node{ stack.top() };
36+
stack.pop();
37+
return node;
38+
}
39+
40+
/* compiler generates very, VERY similar byte code for if/else statement block and if-expression
41+
* statement
42+
* if a: b = 1
43+
* else: b = 2
44+
* expression:
45+
* b = 1 if a else 2
46+
* (see for instance https://stackoverflow.com/a/52202007)
47+
* here, try to guess if just finished else statement is part of if-expression (ternary operator)
48+
* if it is, remove statements from the block and put a ternary node on top of stack
49+
*/
50+
void CheckIfExpr(FastStack& stack, PycRef<ASTBlock> curblock)
51+
{
52+
if (stack.empty())
53+
return;
54+
if (curblock->nodes().size() < 2)
55+
return;
56+
auto rit{ curblock->nodes().crbegin() };
57+
++rit; // the last is "else" block, the one before should be "if" (could be "for", ...)
58+
if ((*rit)->type() != ASTNode::NODE_BLOCK ||
59+
(*rit).cast<ASTBlock>()->blktype() != ASTBlock::BLK_IF)
60+
return;
61+
auto else_expr{ StackPopTop(stack) };
62+
curblock->removeLast();
63+
auto if_block{ curblock->nodes().back() };
64+
auto if_expr{ StackPopTop(stack) };
65+
curblock->removeLast();
66+
stack.push(new ASTTernary(std::move(if_block), std::move(if_expr), std::move(else_expr)));
67+
}
68+
3269
PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
3370
{
3471
PycBuffer source(code->code()->value(), code->code()->length());
@@ -109,6 +146,8 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
109146
curblock->append(prev.cast<ASTNode>());
110147

111148
prev = curblock;
149+
150+
CheckIfExpr(stack, curblock);
112151
}
113152
}
114153

@@ -1363,19 +1402,9 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
13631402
break;
13641403
}
13651404

1366-
if ((curblock->blktype() == ASTBlock::BLK_WHILE
1367-
&& !curblock->inited())
1368-
|| (curblock->blktype() == ASTBlock::BLK_IF
1369-
&& curblock->size() == 0)) {
1370-
PycRef<PycObject> fakeint = new PycInt(1);
1371-
PycRef<ASTNode> truthy = new ASTObject(fakeint);
1372-
1373-
stack.push(truthy);
1374-
break;
1375-
}
1376-
13771405
if (!stack_hist.empty()) {
1378-
stack = stack_hist.top();
1406+
if (stack.empty()) // if it's part of if-expression, TOS at the moment is the result of "if" part
1407+
stack = stack_hist.top();
13791408
stack_hist.pop();
13801409
}
13811410

@@ -3256,6 +3285,26 @@ void print_src(PycRef<ASTNode> node, PycModule* mod)
32563285
print_src(type, mod);
32573286
}
32583287
break;
3288+
case ASTNode::NODE_TERNARY:
3289+
{
3290+
/* parenthesis might not be needed,
3291+
* but when if-expr is part of numerical expression, ternary has the LOWEST precedence
3292+
* print(a + b if False else c)
3293+
* output is c, not a+c (a+b is calculated first)
3294+
*/
3295+
PycRef<ASTTernary> ternary = node.cast<ASTTernary>();
3296+
fputs("( ", pyc_output);
3297+
print_src(ternary->if_expr(), mod);
3298+
const auto if_block = ternary->if_block().require_cast<ASTCondBlock>();
3299+
fputs(" if ", pyc_output);
3300+
if (if_block->negative())
3301+
fputs("not ", pyc_output);
3302+
print_src(if_block->cond(), mod);
3303+
fputs(" else ", pyc_output);
3304+
print_src(ternary->else_expr(), mod);
3305+
fputs(" )", pyc_output);
3306+
}
3307+
break;
32593308
default:
32603309
fprintf(pyc_output, "<NODE:%d>", node->type());
32613310
fprintf(stderr, "Unsupported Node type: %d\n", node->type());

FastStack.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ class FastStack {
4040
return nullptr;
4141
}
4242

43+
bool empty() const
44+
{
45+
return m_ptr == -1;
46+
}
47+
4348
private:
4449
std::vector<PycRef<ASTNode>> m_stack;
4550
int m_ptr;

0 commit comments

Comments
 (0)