1#     Copyright 2021, Kay Hayen, mailto:kay.hayen@gmail.com
2#
3#     Part of "Nuitka", an optimizing Python compiler that is compatible and
4#     integrates with CPython, but also works on its own.
5#
6#     Licensed under the Apache License, Version 2.0 (the "License");
7#     you may not use this file except in compliance with the License.
8#     You may obtain a copy of the License at
9#
10#        http://www.apache.org/licenses/LICENSE-2.0
11#
12#     Unless required by applicable law or agreed to in writing, software
13#     distributed under the License is distributed on an "AS IS" BASIS,
14#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15#     See the License for the specific language governing permissions and
16#     limitations under the License.
17#
18""" Reformulation of while loop statements.
19
20Loops in Nuitka have no condition attached anymore, so while loops are
21re-formulated like this:
22
23.. code-block:: python
24
25    while condition:
26        something()
27
28.. code-block:: python
29
30    while 1:
31        if not condition:
32            break
33
34        something()
35
36This is to totally remove the specialization of loops, with the condition moved
37to the loop body in an initial conditional statement, which contains a ``break``
38statement.
39
40That achieves, that only ``break`` statements exit the loop, and allow for
41optimization to remove always true loop conditions, without concerning code
42generation about it, and to detect such a situation, consider e.g. endless
43loops.
44
45.. note::
46
47   Loop analysis (not yet done) can then work on a reduced problem (which
48   ``break`` statements are executed under what conditions) and is then
49   automatically very general.
50
51   The fact that the loop body may not be entered at all, is still optimized,
52   but also in the general sense. Explicit breaks at the loop start and loop
53   conditions are the same.
54
55"""
56
57from nuitka.nodes.AssignNodes import StatementAssignmentVariable
58from nuitka.nodes.ComparisonNodes import ExpressionComparisonIs
59from nuitka.nodes.ConditionalNodes import makeStatementConditional
60from nuitka.nodes.ConstantRefNodes import makeConstantRefNode
61from nuitka.nodes.LoopNodes import StatementLoop, StatementLoopBreak
62from nuitka.nodes.OperatorNodesUnary import ExpressionOperationNot
63from nuitka.nodes.StatementNodes import StatementsSequence
64from nuitka.nodes.VariableRefNodes import ExpressionTempVariableRef
65
66from .TreeHelpers import (
67    buildNode,
68    buildStatementsNode,
69    makeStatementsSequence,
70    makeStatementsSequenceFromStatements,
71    popBuildContext,
72    pushBuildContext,
73)
74
75
76def buildWhileLoopNode(provider, node, source_ref):
77    # The while loop is re-formulated according to developer manual. The
78    # condition becomes an early condition to break the loop. The else block is
79    # taken if a while loop exits normally, i.e. because of condition not being
80    # true. We do this by introducing an indicator variable.
81
82    else_block = buildStatementsNode(
83        provider=provider,
84        nodes=node.orelse if node.orelse else None,
85        source_ref=source_ref,
86    )
87
88    if else_block is not None:
89        temp_scope = provider.allocateTempScope("while_loop")
90
91        # Indicator variable, will end up with C bool type, and need not be released.
92        tmp_break_indicator = provider.allocateTempVariable(
93            temp_scope=temp_scope, name="break_indicator", temp_type="bool"
94        )
95
96        statements = (
97            StatementAssignmentVariable(
98                variable=tmp_break_indicator,
99                source=makeConstantRefNode(constant=True, source_ref=source_ref),
100                source_ref=source_ref,
101            ),
102            StatementLoopBreak(source_ref=source_ref),
103        )
104    else:
105        statements = (StatementLoopBreak(source_ref=source_ref),)
106
107    pushBuildContext("loop_body")
108    loop_statements = buildStatementsNode(
109        provider=provider, nodes=node.body, source_ref=source_ref
110    )
111    popBuildContext()
112
113    # The loop body contains a conditional statement at the start that breaks
114    # the loop if it fails.
115    loop_body = makeStatementsSequence(
116        statements=(
117            makeStatementConditional(
118                condition=ExpressionOperationNot(
119                    operand=buildNode(provider, node.test, source_ref),
120                    source_ref=source_ref,
121                ),
122                yes_branch=StatementsSequence(
123                    statements=statements, source_ref=source_ref
124                ),
125                no_branch=None,
126                source_ref=source_ref,
127            ),
128            loop_statements,
129        ),
130        allow_none=True,
131        source_ref=source_ref,
132    )
133
134    loop_statement = StatementLoop(loop_body=loop_body, source_ref=source_ref)
135
136    if else_block is None:
137        return loop_statement
138    else:
139        return makeStatementsSequenceFromStatements(
140            StatementAssignmentVariable(
141                variable=tmp_break_indicator,
142                source=makeConstantRefNode(constant=False, source_ref=source_ref),
143                source_ref=source_ref,
144            ),
145            loop_statement,
146            makeStatementConditional(
147                condition=ExpressionComparisonIs(
148                    left=ExpressionTempVariableRef(
149                        variable=tmp_break_indicator, source_ref=source_ref
150                    ),
151                    right=makeConstantRefNode(constant=True, source_ref=source_ref),
152                    source_ref=source_ref,
153                ),
154                yes_branch=else_block,
155                no_branch=None,
156                source_ref=source_ref,
157            ),
158        )
159