From 46affe279cb670c7b80e71c6906f742fef5cda02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nenad=20=C4=8Caklovi=C4=87?= Date: Tue, 29 Mar 2022 12:30:06 +0200 Subject: [PATCH 1/2] Support for conditonal expression (if-expression) --- ASTNode.h | 31 ++++++++++++++++++++++- ASTree.cpp | 73 ++++++++++++++++++++++++++++++++++++++++++++--------- FastStack.h | 5 ++++ 3 files changed, 96 insertions(+), 13 deletions(-) diff --git a/ASTNode.h b/ASTNode.h index 26df413..feef017 100644 --- a/ASTNode.h +++ b/ASTNode.h @@ -16,7 +16,7 @@ public: NODE_CONVERT, NODE_KEYWORD, NODE_RAISE, NODE_EXEC, NODE_BLOCK, NODE_COMPREHENSION, NODE_LOADBUILDCLASS, NODE_AWAITABLE, NODE_FORMATTEDVALUE, NODE_JOINEDSTR, NODE_CONST_MAP, - NODE_ANNOTATED_VAR, NODE_CHAINSTORE, + NODE_ANNOTATED_VAR, NODE_CHAINSTORE, NODE_TERNARY, // Empty node types NODE_LOCALS, @@ -699,4 +699,33 @@ private: PycRef m_type; }; +class ASTTernary : public ASTNode +{ +public: + ASTTernary(PycRef if_block, PycRef if_expr, PycRef else_expr) + : ASTNode(NODE_TERNARY), + m_if_block(std::move(if_block)), + m_if_expr(std::move(if_expr)), + m_else_expr(std::move(else_expr)) + { + } + const PycRef& if_block() const noexcept + { + return m_if_block; + } + const PycRef& if_expr() const noexcept + { + return m_if_expr; + } + const PycRef& else_expr() const noexcept + { + return m_else_expr; + } + +private: + PycRef m_if_block; // contains "condition" and "negative" + PycRef m_if_expr; + PycRef m_else_expr; +}; + #endif diff --git a/ASTree.cpp b/ASTree.cpp index 247d22a..4e597a1 100644 --- a/ASTree.cpp +++ b/ASTree.cpp @@ -29,6 +29,43 @@ static bool printDocstringAndGlobals = false; /* Use this to keep track of whether we need to print a class or module docstring */ static bool printClassDocstring = true; +// shortcut for all top/pop calls +PycRef StackPopTop(FastStack& stack) +{ + const auto node{ stack.top() }; + stack.pop(); + return node; +} + +/* compiler generates very, VERY similar byte code for if/else statement block and if-expression + * statement + * if a: b = 1 + * else: b = 2 + * expression: + * b = 1 if a else 2 + * (see for instance https://stackoverflow.com/a/52202007) + * here, try to guess if just finished else statement is part of if-expression (ternary operator) + * if it is, remove statements from the block and put a ternary node on top of stack + */ +void CheckIfExpr(FastStack& stack, PycRef curblock) +{ + if (stack.empty()) + return; + if (curblock->nodes().size() < 2) + return; + auto rit{ curblock->nodes().crbegin() }; + ++rit; // the last is "else" block, the one before should be "if" (could be "for", ...) + if ((*rit)->type() != ASTNode::NODE_BLOCK || + (*rit).cast()->blktype() != ASTBlock::BLK_IF) + return; + auto else_expr{ StackPopTop(stack) }; + curblock->removeLast(); + auto if_block{ curblock->nodes().back() }; + auto if_expr{ StackPopTop(stack) }; + curblock->removeLast(); + stack.push(new ASTTernary(std::move(if_block), std::move(if_expr), std::move(else_expr))); +} + PycRef BuildFromCode(PycRef code, PycModule* mod) { PycBuffer source(code->code()->value(), code->code()->length()); @@ -109,6 +146,8 @@ PycRef BuildFromCode(PycRef code, PycModule* mod) curblock->append(prev.cast()); prev = curblock; + + CheckIfExpr(stack, curblock); } } @@ -1363,19 +1402,9 @@ PycRef BuildFromCode(PycRef code, PycModule* mod) break; } - if ((curblock->blktype() == ASTBlock::BLK_WHILE - && !curblock->inited()) - || (curblock->blktype() == ASTBlock::BLK_IF - && curblock->size() == 0)) { - PycRef fakeint = new PycInt(1); - PycRef truthy = new ASTObject(fakeint); - - stack.push(truthy); - break; - } - if (!stack_hist.empty()) { - stack = stack_hist.top(); + if (stack.empty()) // if it's part of if-expression, TOS at the moment is the result of "if" part + stack = stack_hist.top(); stack_hist.pop(); } @@ -3256,6 +3285,26 @@ void print_src(PycRef node, PycModule* mod) print_src(type, mod); } break; + case ASTNode::NODE_TERNARY: + { + /* parenthesis might not be needed, + * but when if-expr is part of numerical expression, ternary has the LOWEST precedence + * print(a + b if False else c) + * output is c, not a+c (a+b is calculated first) + */ + PycRef ternary = node.cast(); + fputs("( ", pyc_output); + print_src(ternary->if_expr(), mod); + const auto if_block = ternary->if_block().require_cast(); + fputs(" if ", pyc_output); + if (if_block->negative()) + fputs("not ", pyc_output); + print_src(if_block->cond(), mod); + fputs(" else ", pyc_output); + print_src(ternary->else_expr(), mod); + fputs(" )", pyc_output); + } + break; default: fprintf(pyc_output, "", node->type()); fprintf(stderr, "Unsupported Node type: %d\n", node->type()); diff --git a/FastStack.h b/FastStack.h index dd39464..45f8ed5 100644 --- a/FastStack.h +++ b/FastStack.h @@ -40,6 +40,11 @@ public: return nullptr; } + bool empty() const + { + return m_ptr == -1; + } + private: std::vector> m_stack; int m_ptr; From 38a1ee59c3ae039bdc1504f9b7b0b29e793a38b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nenad=20=C4=8Caklovi=C4=87?= Date: Sat, 2 Apr 2022 13:57:10 +0200 Subject: [PATCH 2/2] Support for conditonal expression (if-expression) - review findings --- ASTree.cpp | 15 ++++--- .../compiled/conditional_expressions.3.9.pyc | Bin 0 -> 549 bytes tests/input/conditional_expressions.py | 42 ++++++++++++++++++ tests/tokenized/conditional_expressions.txt | 29 ++++++++++++ 4 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 tests/compiled/conditional_expressions.3.9.pyc create mode 100644 tests/input/conditional_expressions.py create mode 100644 tests/tokenized/conditional_expressions.txt diff --git a/ASTree.cpp b/ASTree.cpp index 4e597a1..9f928fc 100644 --- a/ASTree.cpp +++ b/ASTree.cpp @@ -30,7 +30,7 @@ static bool printDocstringAndGlobals = false; static bool printClassDocstring = true; // shortcut for all top/pop calls -PycRef StackPopTop(FastStack& stack) +static PycRef StackPopTop(FastStack& stack) { const auto node{ stack.top() }; stack.pop(); @@ -47,7 +47,7 @@ PycRef StackPopTop(FastStack& stack) * here, try to guess if just finished else statement is part of if-expression (ternary operator) * if it is, remove statements from the block and put a ternary node on top of stack */ -void CheckIfExpr(FastStack& stack, PycRef curblock) +static void CheckIfExpr(FastStack& stack, PycRef curblock) { if (stack.empty()) return; @@ -3287,13 +3287,16 @@ void print_src(PycRef node, PycModule* mod) break; case ASTNode::NODE_TERNARY: { - /* parenthesis might not be needed, - * but when if-expr is part of numerical expression, ternary has the LOWEST precedence + /* parenthesis might be needed + * + * when if-expr is part of numerical expression, ternary has the LOWEST precedence * print(a + b if False else c) * output is c, not a+c (a+b is calculated first) + * + * but, let's not add parenthesis - to keep the source as close to original as possible in most cases */ PycRef ternary = node.cast(); - fputs("( ", pyc_output); + //fputs("(", pyc_output); print_src(ternary->if_expr(), mod); const auto if_block = ternary->if_block().require_cast(); fputs(" if ", pyc_output); @@ -3302,7 +3305,7 @@ void print_src(PycRef node, PycModule* mod) print_src(if_block->cond(), mod); fputs(" else ", pyc_output); print_src(ternary->else_expr(), mod); - fputs(" )", pyc_output); + //fputs(")", pyc_output); } break; default: diff --git a/tests/compiled/conditional_expressions.3.9.pyc b/tests/compiled/conditional_expressions.3.9.pyc new file mode 100644 index 0000000000000000000000000000000000000000..07e36c0d0f35a6343b3cd014918dccf01d7af809 GIT binary patch literal 549 zcmZ{hu};G<5QgvUw1hx~kRpbPA!`{rAV!23nOIm*78J0Bq6Ry1(h52u^#ynY#00O> zN65;Qr4PVBIHwd6Rkh@^@BYuH-*(jNc|has_~Q1J0esq|Ij9EbG_xZF5DrExMW8aJ0SRUeNUaNt>;)-W3dXN_sCS?emp1A#~(Ks*r0TN^Xna zRaH&*mAsGgcp`&HiNF%my;Ns$UD777G;!7CfEO4QT_dw`k{6xS#47&)WB;xWU;R6+ z1fA0=N^az1N@PY!HcX!i_w+%FaU##GrEEySK_6Ydiih?7H*$yVqQ|VdtOWQ2(<6CC literal 0 HcmV?d00001 diff --git a/tests/input/conditional_expressions.py b/tests/input/conditional_expressions.py new file mode 100644 index 0000000..eea2cd0 --- /dev/null +++ b/tests/input/conditional_expressions.py @@ -0,0 +1,42 @@ +a = 1 +result = 'even' if a % 2 == 0 else 'odd' +print(result) +# odd + +a = 2 +result = 'even' if a % 2 == 0 else 'odd' +print(result) +# even + +a = 1 +result = a * 2 if a % 2 == 0 else a * 3 +print(result) +# 3 + +a = 2 +result = a * 2 if a % 2 == 0 else a * 3 +print(result) +# 4 + +a = 1 +print('even') if a % 2 == 0 else print('odd') +# odd + +a = 1 + +if a % 2 == 0: + print('even') +else: + print('odd') +# odd + +a = -2 +result = 'negative and even' if a < 0 and a % 2 == 0 else 'positive or odd' +print(result) +# negative and even + +a = -1 +result = 'negative and even' if a < 0 and a % 2 == 0 else 'positive or odd' +print(result) +# positive or odd + diff --git a/tests/tokenized/conditional_expressions.txt b/tests/tokenized/conditional_expressions.txt new file mode 100644 index 0000000..c9d6449 --- /dev/null +++ b/tests/tokenized/conditional_expressions.txt @@ -0,0 +1,29 @@ +a = 1 +result = 'even' if a % 2 == 0 else 'odd' +print ( result ) +a = 2 +result = 'even' if a % 2 == 0 else 'odd' +print ( result ) +a = 1 +result = a * 2 if a % 2 == 0 else a * 3 +print ( result ) +a = 2 +result = a * 2 if a % 2 == 0 else a * 3 +print ( result ) +a = 1 +print ( 'even' ) if a % 2 == 0 else print ( 'odd' ) +a = 1 +if a % 2 == 0 : + +print ( 'even' ) + +else : + +print ( 'odd' ) + +a = - 2 +result = 'negative and even' if a < 0 and a % 2 == 0 else 'positive or odd' +print ( result ) +a = - 1 +result = 'negative and even' if a < 0 and a % 2 == 0 else 'positive or odd' +print ( result )