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""" Nodes for statements.
19
20"""
21
22from .NodeBases import StatementBase, StatementChildHavingBase
23
24
25def checkStatements(value):
26    """Check that statements list value property.
27
28    Must not be None, must not contain None, and of course only statements,
29    may be empty.
30    """
31
32    assert value is not None
33    assert None not in value
34
35    for statement in value:
36        assert (
37            statement.isStatement() or statement.isStatementsFrame()
38        ), statement.asXmlText()
39
40    return tuple(value)
41
42
43class StatementsSequence(StatementChildHavingBase):
44    kind = "STATEMENTS_SEQUENCE"
45
46    named_child = "statements"
47
48    checker = checkStatements
49
50    def __init__(self, statements, source_ref):
51        StatementChildHavingBase.__init__(
52            self, value=tuple(statements), source_ref=source_ref
53        )
54
55    def finalize(self):
56        del self.parent
57
58        for s in self.subnode_statements:
59            s.finalize()
60
61    # Overloading name based automatic check, so that derived ones know it too.
62    def isStatementsSequence(self):
63        # Virtual method, pylint: disable=no-self-use
64
65        return True
66
67    def trimStatements(self, statement):
68        assert statement.parent is self
69
70        old_statements = list(self.subnode_statements)
71        assert statement in old_statements, (statement, self)
72
73        new_statements = old_statements[: old_statements.index(statement) + 1]
74
75        self.setChild("statements", new_statements)
76
77    def removeStatement(self, statement):
78        assert statement.parent is self
79
80        statements = list(self.subnode_statements)
81        statements.remove(statement)
82        self.setChild("statements", statements)
83
84        if statements:
85            return self
86        else:
87            return None
88
89    def replaceStatement(self, statement, statements):
90        old_statements = list(self.subnode_statements)
91
92        merge_index = old_statements.index(statement)
93
94        new_statements = (
95            tuple(old_statements[:merge_index])
96            + tuple(statements)
97            + tuple(old_statements[merge_index + 1 :])
98        )
99
100        self.setChild("statements", new_statements)
101
102    def mayHaveSideEffects(self):
103        # Statement sequences have a side effect if one of the statements does.
104        for statement in self.subnode_statements:
105            if statement.mayHaveSideEffects():
106                return True
107        return False
108
109    def mayRaiseException(self, exception_type):
110        for statement in self.subnode_statements:
111            if statement.mayRaiseException(exception_type):
112                return True
113        return False
114
115    def needsFrame(self):
116        for statement in self.subnode_statements:
117            if statement.needsFrame():
118                return True
119        return False
120
121    def mayReturn(self):
122        for statement in self.subnode_statements:
123            if statement.mayReturn():
124                return True
125        return False
126
127    def mayBreak(self):
128        for statement in self.subnode_statements:
129            if statement.mayBreak():
130                return True
131        return False
132
133    def mayContinue(self):
134        for statement in self.subnode_statements:
135            if statement.mayContinue():
136                return True
137        return False
138
139    def mayRaiseExceptionOrAbort(self, exception_type):
140        return (
141            self.mayRaiseException(exception_type)
142            or self.mayReturn()
143            or self.mayBreak()
144            or self.mayContinue()
145        )
146
147    def isStatementAborting(self):
148        return self.subnode_statements[-1].isStatementAborting()
149
150    def computeStatement(self, trace_collection):
151        # Don't want to be called like this.
152        assert False, self
153
154    def computeStatementsSequence(self, trace_collection):
155        new_statements = []
156
157        statements = self.subnode_statements
158        assert statements, self
159
160        for count, statement in enumerate(statements):
161            # May be frames embedded.
162            if statement.isStatementsFrame():
163                new_statement = statement.computeStatementsSequence(trace_collection)
164            else:
165                new_statement = trace_collection.onStatement(statement=statement)
166
167            if new_statement is not None:
168                if (
169                    new_statement.isStatementsSequence()
170                    and not new_statement.isStatementsFrame()
171                ):
172                    new_statements.extend(new_statement.subnode_statements)
173                else:
174                    new_statements.append(new_statement)
175
176                if (
177                    statement is not statements[-1]
178                    and new_statement.isStatementAborting()
179                ):
180                    trace_collection.signalChange(
181                        "new_statements",
182                        statements[count + 1].getSourceReference(),
183                        "Removed dead statements.",
184                    )
185
186                    for s in statements[statements.index(statement) + 1 :]:
187                        s.finalize()
188
189                    break
190
191        if statements != new_statements:
192            if new_statements:
193                self.setChild("statements", new_statements)
194
195                return self
196            else:
197                return None
198        else:
199            return self
200
201    @staticmethod
202    def getStatementNiceName():
203        return "statements sequence"
204
205
206class StatementExpressionOnly(StatementChildHavingBase):
207    kind = "STATEMENT_EXPRESSION_ONLY"
208
209    named_child = "expression"
210
211    def __init__(self, expression, source_ref):
212        assert expression.isExpression()
213
214        StatementChildHavingBase.__init__(self, value=expression, source_ref=source_ref)
215
216    def mayHaveSideEffects(self):
217        return self.subnode_expression.mayHaveSideEffects()
218
219    def mayRaiseException(self, exception_type):
220        return self.subnode_expression.mayRaiseException(exception_type)
221
222    def computeStatement(self, trace_collection):
223        expression = trace_collection.onExpression(expression=self.subnode_expression)
224
225        if expression.mayRaiseException(BaseException):
226            trace_collection.onExceptionRaiseExit(BaseException)
227
228        result, change_tags, change_desc = expression.computeExpressionDrop(
229            statement=self, trace_collection=trace_collection
230        )
231
232        if result is not self:
233            return result, change_tags, change_desc
234
235        return self, None, None
236
237    @staticmethod
238    def getStatementNiceName():
239        return "expression only statement"
240
241    def getDetailsForDisplay(self):
242        return {"expression": self.subnode_expression.kind}
243
244
245class StatementPreserveFrameException(StatementBase):
246    kind = "STATEMENT_PRESERVE_FRAME_EXCEPTION"
247
248    __slots__ = ("preserver_id",)
249
250    def __init__(self, preserver_id, source_ref):
251        StatementBase.__init__(self, source_ref=source_ref)
252
253        self.preserver_id = preserver_id
254
255    def finalize(self):
256        del self.parent
257
258    def getDetails(self):
259        return {"preserver_id": self.preserver_id}
260
261    def getPreserverId(self):
262        return self.preserver_id
263
264    def computeStatement(self, trace_collection):
265        # For Python2 generators, it's not necessary to preserve, the frame
266        # decides it. TODO: This check makes only sense once.
267
268        if self.getParentStatementsFrame().needsExceptionFramePreservation():
269            return self, None, None
270        else:
271            return (
272                None,
273                "new_statements",
274                "Removed frame preservation for generators.",
275            )
276
277    @staticmethod
278    def mayRaiseException(exception_type):
279        return False
280
281    @staticmethod
282    def needsFrame():
283        return True
284
285
286class StatementRestoreFrameException(StatementBase):
287    kind = "STATEMENT_RESTORE_FRAME_EXCEPTION"
288
289    __slots__ = ("preserver_id",)
290
291    def __init__(self, preserver_id, source_ref):
292        StatementBase.__init__(self, source_ref=source_ref)
293
294        self.preserver_id = preserver_id
295
296    def finalize(self):
297        del self.parent
298
299    def getDetails(self):
300        return {"preserver_id": self.preserver_id}
301
302    def getPreserverId(self):
303        return self.preserver_id
304
305    def computeStatement(self, trace_collection):
306        return self, None, None
307
308    @staticmethod
309    def mayRaiseException(exception_type):
310        return False
311
312
313class StatementPublishException(StatementBase):
314    kind = "STATEMENT_PUBLISH_EXCEPTION"
315
316    def __init__(self, source_ref):
317        StatementBase.__init__(self, source_ref=source_ref)
318
319    def finalize(self):
320        del self.parent
321
322    def computeStatement(self, trace_collection):
323        # TODO: Determine the need for it.
324        return self, None, None
325
326    @staticmethod
327    def mayRaiseException(exception_type):
328        return False
329