pine2pyne 0.2.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pine2pyne/__init__.py +84 -0
- pine2pyne/__main__.py +12 -0
- pine2pyne/ast_nodes.py +405 -0
- pine2pyne/ast_walk.py +93 -0
- pine2pyne/block_value_lowering.py +694 -0
- pine2pyne/bool_builtins.py +42 -0
- pine2pyne/bool_value_typing.py +580 -0
- pine2pyne/cli.py +247 -0
- pine2pyne/codegen.py +886 -0
- pine2pyne/compat/__init__.py +19 -0
- pine2pyne/compat/__main__.py +6 -0
- pine2pyne/compat/cli.py +114 -0
- pine2pyne/compat/comparison.py +105 -0
- pine2pyne/compat/corpus.py +73 -0
- pine2pyne/compat/report.py +95 -0
- pine2pyne/compat/runner.py +63 -0
- pine2pyne/condition_typing.py +260 -0
- pine2pyne/conditional_history_calls.py +96 -0
- pine2pyne/core/__init__.py +26 -0
- pine2pyne/core/contracts.py +125 -0
- pine2pyne/di.py +173 -0
- pine2pyne/emit/__init__.py +5 -0
- pine2pyne/emit/legacy.py +14 -0
- pine2pyne/emit/pynecomp/__init__.py +20 -0
- pine2pyne/emit/pynecomp/banner.py +30 -0
- pine2pyne/emit/pynecomp/blank_lines.py +69 -0
- pine2pyne/emit/pynecomp/emitter.py +61 -0
- pine2pyne/emit/pynecomp/imports.py +65 -0
- pine2pyne/emit/pynecomp/passes/__init__.py +84 -0
- pine2pyne/emit/pynecomp/passes/annotations.py +422 -0
- pine2pyne/emit/pynecomp/passes/base.py +69 -0
- pine2pyne/emit/pynecomp/passes/builtins.py +169 -0
- pine2pyne/emit/pynecomp/passes/hoisting.py +205 -0
- pine2pyne/emit/pynecomp/passes/imports.py +112 -0
- pine2pyne/emit/pynecomp/passes/legacy_colors.py +101 -0
- pine2pyne/emit/pynecomp/passes/library.py +205 -0
- pine2pyne/emit/pynecomp/passes/rewrites.py +215 -0
- pine2pyne/emit/pynecomp/printer.py +288 -0
- pine2pyne/emit/pynecomp/unparser.py +186 -0
- pine2pyne/emitted_names.py +152 -0
- pine2pyne/errors.py +69 -0
- pine2pyne/for_loop_end_bound.py +172 -0
- pine2pyne/grouped_history_lowering.py +310 -0
- pine2pyne/import_resolver.py +469 -0
- pine2pyne/input_hoisting.py +195 -0
- pine2pyne/legacy_function_names.py +74 -0
- pine2pyne/legacy_sessions.py +68 -0
- pine2pyne/lexer.py +630 -0
- pine2pyne/library_collision.py +434 -0
- pine2pyne/namespace_members.py +247 -0
- pine2pyne/node_ids.py +235 -0
- pine2pyne/once_lowering.py +75 -0
- pine2pyne/overload_resolution.py +475 -0
- pine2pyne/parse_repair.py +398 -0
- pine2pyne/parser.py +1873 -0
- pine2pyne/pine_builtins.py +394 -0
- pine2pyne/pine_semantics.py +604 -0
- pine2pyne/pipeline/__init__.py +18 -0
- pine2pyne/pipeline/service.py +70 -0
- pine2pyne/pipeline/stages.py +73 -0
- pine2pyne/scope_renamer.py +965 -0
- pine2pyne/strategy_when_lowering.py +101 -0
- pine2pyne/strict_boolean.py +101 -0
- pine2pyne/symbol_table.py +166 -0
- pine2pyne/tokens.py +143 -0
- pine2pyne/transformer.py +2510 -0
- pine2pyne/type_inference.py +422 -0
- pine2pyne/udt_field_defaults.py +149 -0
- pine2pyne/value_blocks.py +357 -0
- pine2pyne-0.2.4.dist-info/METADATA +99 -0
- pine2pyne-0.2.4.dist-info/RECORD +75 -0
- pine2pyne-0.2.4.dist-info/WHEEL +5 -0
- pine2pyne-0.2.4.dist-info/entry_points.txt +3 -0
- pine2pyne-0.2.4.dist-info/licenses/LICENSE +201 -0
- pine2pyne-0.2.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Grouped-history lowering: a history index on an expression that is not a name.
|
|
3
|
+
|
|
4
|
+
Pine's `(a + b)[1]` is the grouped value one bar back, `a[1] + b[1]`, and
|
|
5
|
+
`f()[1]` is the value the call returned one bar back. Python has no series of
|
|
6
|
+
an expression: the emitter bound `[1]` to the last operand (card #32), and a
|
|
7
|
+
call's value is a plain number. This pre-pass names the object of every such
|
|
8
|
+
history index as a Pine local, in the idiom a Pine author writes by hand:
|
|
9
|
+
|
|
10
|
+
y = (close + open)[1] -> grouped_history1 = close + open
|
|
11
|
+
y = grouped_history1[1]
|
|
12
|
+
|
|
13
|
+
The local sits immediately before the statement that uses it, in the same
|
|
14
|
+
statement list, so every later stage (symbol table, type inference, Series
|
|
15
|
+
annotation, function-instance series) treats it as an ordinary user local.
|
|
16
|
+
A group in a ternary arm or an and/or operand is therefore evaluated on every
|
|
17
|
+
bar its statement runs; a group in an if/switch-expression branch stays in
|
|
18
|
+
that branch (the branch becomes a block, which block-value lowering handles);
|
|
19
|
+
a group in an else-if condition, or in a later switch case, runs in the else
|
|
20
|
+
block, where Pine puts it. A top-level `var` whose initialiser holds a group
|
|
21
|
+
moves into the body, after its local. A group whose position has no statement
|
|
22
|
+
to precede it (a while condition, the header of a loop used as a value) is
|
|
23
|
+
refused with its line, never emitted wrong.
|
|
24
|
+
|
|
25
|
+
The history of a field of a user-defined type, `t.x[1]`, is such a site too (card #85):
|
|
26
|
+
Pine v5 reads the field's own per-bar series, as `value = t.x; value[1]` does, and
|
|
27
|
+
Python's `t.x` is a plain number. Pine v6 refuses the form (pine_semantics.py, which
|
|
28
|
+
runs before this pass and asks the same rule about the head's binding in scope), so only
|
|
29
|
+
a v5 script gets here. A dotted name is a field read when its head is a binding that owns
|
|
30
|
+
the member (`library_collision.FieldReadRule`); a namespace member (`ta.tr[1]`,
|
|
31
|
+
`session.ismarket[1]` beside an input named `session`) is not.
|
|
32
|
+
|
|
33
|
+
This pass runs after the scope renamer, which has resolved every `N.m` against the binding
|
|
34
|
+
in scope: a binding named like a namespace that owns the member was given a fresh name, and
|
|
35
|
+
a namespace read under a binding that keeps its name (an input, an export) reads an alias.
|
|
36
|
+
So the binding a head names here is the one the script binds under that emitted name, and
|
|
37
|
+
its type is the one its declarations give it (`library_collision.binding_types`, card #88).
|
|
38
|
+
One approximation is left: an `export` function's parameter keeps a namespace's name in its
|
|
39
|
+
own body only, so a namespace read with history elsewhere, or a second such parameter of
|
|
40
|
+
another type, is judged by the script-wide type of that name.
|
|
41
|
+
"""
|
|
42
|
+
import dataclasses
|
|
43
|
+
from typing import List, Optional
|
|
44
|
+
|
|
45
|
+
from .ast_nodes import (
|
|
46
|
+
Assignment, BinaryOp, Expression, ExpressionStatement, ForInLoop, ForLoop,
|
|
47
|
+
FuncDecl, FunctionCall, Identifier, IfExpression, IfStatement, IndexAccess,
|
|
48
|
+
Literal, MethodCall, Reassignment, Script, SwitchExpression, SwitchStatement,
|
|
49
|
+
TernaryOp, UnaryOp, VarDecl, VaripDecl, WhileLoop,
|
|
50
|
+
)
|
|
51
|
+
from .ast_walk import child_field_names, contains, map_children
|
|
52
|
+
from .errors import UnsupportedFeatureError
|
|
53
|
+
from .library_collision import FieldReadRule, HeadBinding, binding_types
|
|
54
|
+
from .scope_renamer import collect_names, fresh_name
|
|
55
|
+
|
|
56
|
+
# The objects of a history index that are named as a local: every expression
|
|
57
|
+
# whose value Python cannot index, i.e. all but a plain name (a field read is
|
|
58
|
+
# decided by `FieldReadRule`)
|
|
59
|
+
HISTORY_SOURCES = (BinaryOp, UnaryOp, TernaryOp, IndexAccess, FunctionCall, MethodCall)
|
|
60
|
+
LOCAL_STEM = 'grouped_history'
|
|
61
|
+
SUBJECT_STEM = 'switch_subject'
|
|
62
|
+
ASSIGNING_STATEMENTS = (Assignment, Reassignment, VarDecl, VaripDecl)
|
|
63
|
+
LOOP_HEADER_FIELDS = ('from_val', 'to_val', 'step', 'iterable')
|
|
64
|
+
LOOPS = (WhileLoop, ForLoop, ForInLoop)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def lower_grouped_history(script: Script) -> Script:
|
|
68
|
+
"""Return a copy of `script` with the object of every history index that
|
|
69
|
+
is not a name bound to a local. A script without one is returned unchanged."""
|
|
70
|
+
field_read_rule = _FieldReads(script)
|
|
71
|
+
if not has_history_site(script, field_read_rule):
|
|
72
|
+
return script
|
|
73
|
+
return _GroupedHistoryLowering(collect_names(script), field_read_rule).script(script)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class _FieldReads:
|
|
77
|
+
"""`FieldReadRule` over the renamed script: a head names the binding of its emitted name."""
|
|
78
|
+
|
|
79
|
+
def __init__(self, script: Script):
|
|
80
|
+
self.rule = FieldReadRule(script)
|
|
81
|
+
self.types = binding_types(script)
|
|
82
|
+
|
|
83
|
+
def binding(self, head: str) -> Optional[HeadBinding]:
|
|
84
|
+
return HeadBinding(self.types[head]) if head in self.types else None
|
|
85
|
+
|
|
86
|
+
def is_field_read(self, value) -> bool:
|
|
87
|
+
return self.rule.is_field_read(value, self.binding)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def is_history_site(node, field_read_rule: Optional[_FieldReads] = None) -> bool:
|
|
91
|
+
"""A history index whose object is an expression or a field read, not a name."""
|
|
92
|
+
return isinstance(node, IndexAccess) and (
|
|
93
|
+
isinstance(node.object, HISTORY_SOURCES)
|
|
94
|
+
or (field_read_rule is not None and field_read_rule.is_field_read(node.object)))
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def has_history_site(tree, field_read_rule: Optional[_FieldReads] = None) -> bool:
|
|
98
|
+
return contains(tree, lambda node: is_history_site(node, field_read_rule))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _switch_case_has_site(switch, field_read_rule: _FieldReads) -> bool:
|
|
102
|
+
return any(has_history_site(case, field_read_rule) for case, _ in switch.cases)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class _GroupedHistoryLowering:
|
|
106
|
+
def __init__(self, used_names, field_read_rule: _FieldReads):
|
|
107
|
+
self.used_names = used_names
|
|
108
|
+
self.field_read_rule = field_read_rule
|
|
109
|
+
self.line = 0 # nearest enclosing source line, for refusals and new nodes
|
|
110
|
+
|
|
111
|
+
def refuse(self, position: str):
|
|
112
|
+
raise UnsupportedFeatureError(
|
|
113
|
+
f'a history index on an expression in {position} (assign the expression to a variable first)',
|
|
114
|
+
self.line)
|
|
115
|
+
|
|
116
|
+
# ------------------------------------------------------------ expressions
|
|
117
|
+
|
|
118
|
+
def expression(self, value, hoisted: List):
|
|
119
|
+
"""`value` with the object of each history site named as a local; the
|
|
120
|
+
locals' assignments are appended to `hoisted`, innermost first."""
|
|
121
|
+
if isinstance(value, (IfExpression, SwitchExpression)):
|
|
122
|
+
return self.value_branches(value, hoisted)
|
|
123
|
+
if isinstance(value, (list, tuple, dict)):
|
|
124
|
+
return map_children(value, lambda item: self.expression(item, hoisted))
|
|
125
|
+
if isinstance(value, LOOPS):
|
|
126
|
+
return self.loop_value(value)
|
|
127
|
+
if not isinstance(value, Expression):
|
|
128
|
+
return value
|
|
129
|
+
children = {name: self.expression(getattr(value, name), hoisted) for name in child_field_names(type(value))}
|
|
130
|
+
rebuilt = dataclasses.replace(value, **children)
|
|
131
|
+
return self.named_source(rebuilt, hoisted) if is_history_site(rebuilt, self.field_read_rule) else rebuilt
|
|
132
|
+
|
|
133
|
+
def named_source(self, site: IndexAccess, hoisted: List) -> IndexAccess:
|
|
134
|
+
"""The site reads a new local that holds its object."""
|
|
135
|
+
name = fresh_name(LOCAL_STEM, self.used_names)
|
|
136
|
+
hoisted.append(Assignment(target=name, value=site.object, line=self.line))
|
|
137
|
+
return dataclasses.replace(site, object=Identifier(name=name, line=self.line))
|
|
138
|
+
|
|
139
|
+
def value_branches(self, value, hoisted: List):
|
|
140
|
+
"""An if/switch expression: the condition and subject run with the
|
|
141
|
+
statement; each branch keeps its own locals (a Pine local block)."""
|
|
142
|
+
if isinstance(value, SwitchExpression) and _switch_case_has_site(value, self.field_read_rule):
|
|
143
|
+
# A later case runs only when the earlier ones fail: an else-if chain
|
|
144
|
+
value = self.switch_as_if_expression(value, hoisted)
|
|
145
|
+
if isinstance(value, IfExpression):
|
|
146
|
+
return dataclasses.replace(value, condition=self.expression(value.condition, hoisted),
|
|
147
|
+
true_expr=self.branch(value.true_expr),
|
|
148
|
+
false_expr=self.branch(value.false_expr))
|
|
149
|
+
return dataclasses.replace(value, expr=self.expression(value.expr, hoisted),
|
|
150
|
+
cases=[(case, self.branch(body)) for case, body in value.cases],
|
|
151
|
+
default=self.branch(value.default))
|
|
152
|
+
|
|
153
|
+
def branch(self, branch):
|
|
154
|
+
"""A branch of an if/switch expression: a block keeps its statements'
|
|
155
|
+
locals; a single expression with a site becomes a block ending in it."""
|
|
156
|
+
if branch is None or isinstance(branch, list):
|
|
157
|
+
return self.block(branch)
|
|
158
|
+
hoisted: List = []
|
|
159
|
+
value = self.expression(branch, hoisted)
|
|
160
|
+
return hoisted + [ExpressionStatement(expr=value, line=self.line)] if hoisted else value
|
|
161
|
+
|
|
162
|
+
# ------------------------------------------------------------ switch cases
|
|
163
|
+
|
|
164
|
+
def switch_as_if_expression(self, switch: SwitchExpression, hoisted: List) -> IfExpression:
|
|
165
|
+
"""`switch` as nested if expressions over its case conditions."""
|
|
166
|
+
case_condition = self.case_condition_builder(switch.expr, hoisted)
|
|
167
|
+
chain = switch.default
|
|
168
|
+
for case, body in reversed(switch.cases):
|
|
169
|
+
chain = IfExpression(condition=case_condition(case), true_expr=body, false_expr=chain, line=self.line)
|
|
170
|
+
return chain
|
|
171
|
+
|
|
172
|
+
def switch_as_if_statement(self, switch: SwitchStatement, hoisted: List) -> IfStatement:
|
|
173
|
+
"""`switch` as an if / else-if chain over its case conditions."""
|
|
174
|
+
case_condition = self.case_condition_builder(switch.expr, hoisted)
|
|
175
|
+
(first_case, first_body), *other_cases = switch.cases
|
|
176
|
+
return IfStatement(condition=case_condition(first_case), body=first_body,
|
|
177
|
+
elseifs=[(case_condition(case), body) for case, body in other_cases],
|
|
178
|
+
else_body=switch.default, line=self.line)
|
|
179
|
+
|
|
180
|
+
def case_condition_builder(self, subject, hoisted: List):
|
|
181
|
+
"""The condition of a case: the case itself without a subject, else
|
|
182
|
+
`subject == case`. A subject that is not a plain name or a literal is
|
|
183
|
+
evaluated once, into a local, as Pine evaluates it once."""
|
|
184
|
+
if subject is None:
|
|
185
|
+
return lambda case: case
|
|
186
|
+
subject = self.expression(subject, hoisted)
|
|
187
|
+
if not isinstance(subject, Literal) and not (isinstance(subject, Identifier) and '.' not in subject.name):
|
|
188
|
+
name = fresh_name(SUBJECT_STEM, self.used_names)
|
|
189
|
+
hoisted.append(Assignment(target=name, value=subject, line=self.line))
|
|
190
|
+
subject = Identifier(name=name, line=self.line)
|
|
191
|
+
return lambda case: BinaryOp(left=dataclasses.replace(subject), op='==', right=case, line=self.line)
|
|
192
|
+
|
|
193
|
+
# ------------------------------------------------------------ statements
|
|
194
|
+
|
|
195
|
+
def block(self, body):
|
|
196
|
+
if body is None:
|
|
197
|
+
return None
|
|
198
|
+
lowered = []
|
|
199
|
+
for statement in body:
|
|
200
|
+
lowered.extend(self.statement(statement))
|
|
201
|
+
return lowered
|
|
202
|
+
|
|
203
|
+
def statement(self, node) -> list:
|
|
204
|
+
"""The statement lowered, preceded by the locals its sites read."""
|
|
205
|
+
enclosing_line, self.line = self.line, node.line or self.line
|
|
206
|
+
try:
|
|
207
|
+
hoisted: List = []
|
|
208
|
+
lowered = self.lowered_statement(node, hoisted)
|
|
209
|
+
return hoisted + [lowered]
|
|
210
|
+
finally:
|
|
211
|
+
self.line = enclosing_line
|
|
212
|
+
|
|
213
|
+
def lowered_statement(self, node, hoisted: List):
|
|
214
|
+
if isinstance(node, IfStatement):
|
|
215
|
+
return self.if_statement(node, hoisted)
|
|
216
|
+
if isinstance(node, SwitchStatement):
|
|
217
|
+
return self.switch_statement(node, hoisted)
|
|
218
|
+
if isinstance(node, LOOPS):
|
|
219
|
+
return self.loop(node, hoisted)
|
|
220
|
+
if isinstance(node, FuncDecl):
|
|
221
|
+
return self.function(node)
|
|
222
|
+
if isinstance(node, ASSIGNING_STATEMENTS):
|
|
223
|
+
return dataclasses.replace(node, value=self.expression(node.value, hoisted))
|
|
224
|
+
if isinstance(node, ExpressionStatement):
|
|
225
|
+
return dataclasses.replace(node, expr=self.expression(node.expr, hoisted))
|
|
226
|
+
if isinstance(node, Expression):
|
|
227
|
+
# A block branch may end in a bare expression
|
|
228
|
+
return self.expression(node, hoisted)
|
|
229
|
+
return node
|
|
230
|
+
|
|
231
|
+
def loop(self, node, hoisted: List):
|
|
232
|
+
"""A for header runs once, before the loop; a while condition runs per
|
|
233
|
+
iteration, where a statement cannot precede it, and so does the end
|
|
234
|
+
bound of a v6 for loop that re-reads it (card #82)."""
|
|
235
|
+
if isinstance(node, WhileLoop):
|
|
236
|
+
if has_history_site(node.condition, self.field_read_rule):
|
|
237
|
+
self.refuse('a while condition')
|
|
238
|
+
return dataclasses.replace(node, body=self.block(node.body))
|
|
239
|
+
if getattr(node, 'counter_name', '') and has_history_site(node.to_val, self.field_read_rule):
|
|
240
|
+
self.refuse('the end bound of a for loop whose body changes it')
|
|
241
|
+
header = {name: self.expression(getattr(node, name), hoisted)
|
|
242
|
+
for name in LOOP_HEADER_FIELDS if hasattr(node, name)}
|
|
243
|
+
return dataclasses.replace(node, body=self.block(node.body), **header)
|
|
244
|
+
|
|
245
|
+
def loop_value(self, loop):
|
|
246
|
+
"""A loop used as a value (a bound value, an operand, a while too since
|
|
247
|
+
card #50): its header has no statement of its own to precede."""
|
|
248
|
+
if has_history_site([getattr(loop, name, None) for name in LOOP_HEADER_FIELDS + ('condition',)],
|
|
249
|
+
self.field_read_rule):
|
|
250
|
+
self.refuse('the header of a loop used as a value')
|
|
251
|
+
return dataclasses.replace(loop, body=self.block(loop.body))
|
|
252
|
+
|
|
253
|
+
def if_statement(self, node: IfStatement, hoisted: List) -> IfStatement:
|
|
254
|
+
"""An else-if is an if inside the else block: a group in its condition
|
|
255
|
+
is named there, so it runs only on bars that reach that condition."""
|
|
256
|
+
condition = self.expression(node.condition, hoisted)
|
|
257
|
+
body = self.block(node.body)
|
|
258
|
+
for index, (elseif_condition, elseif_body) in enumerate(node.elseifs):
|
|
259
|
+
if has_history_site(elseif_condition, self.field_read_rule):
|
|
260
|
+
rest = IfStatement(condition=elseif_condition, body=elseif_body, elseifs=node.elseifs[index + 1:],
|
|
261
|
+
else_body=node.else_body, line=node.line)
|
|
262
|
+
return dataclasses.replace(
|
|
263
|
+
node, condition=condition, body=body,
|
|
264
|
+
elseifs=[(earlier_condition, self.block(earlier_body))
|
|
265
|
+
for earlier_condition, earlier_body in node.elseifs[:index]],
|
|
266
|
+
else_body=self.statement(rest))
|
|
267
|
+
return dataclasses.replace(node, condition=condition, body=body,
|
|
268
|
+
elseifs=[(elseif_condition, self.block(elseif_body))
|
|
269
|
+
for elseif_condition, elseif_body in node.elseifs],
|
|
270
|
+
else_body=self.block(node.else_body))
|
|
271
|
+
|
|
272
|
+
def switch_statement(self, node: SwitchStatement, hoisted: List):
|
|
273
|
+
"""A site in a case: the switch becomes the if / else-if chain Pine runs."""
|
|
274
|
+
if _switch_case_has_site(node, self.field_read_rule):
|
|
275
|
+
return self.if_statement(self.switch_as_if_statement(node, hoisted), hoisted)
|
|
276
|
+
return dataclasses.replace(node, expr=self.expression(node.expr, hoisted),
|
|
277
|
+
cases=[(case, self.block(body)) for case, body in node.cases],
|
|
278
|
+
default=self.block(node.default))
|
|
279
|
+
|
|
280
|
+
def function(self, node: FuncDecl) -> FuncDecl:
|
|
281
|
+
if isinstance(node.body, list):
|
|
282
|
+
return dataclasses.replace(node, body=self.block(node.body))
|
|
283
|
+
return dataclasses.replace(node, body=self.branch(node.body))
|
|
284
|
+
|
|
285
|
+
# ------------------------------------------------------------ script
|
|
286
|
+
|
|
287
|
+
def script(self, script: Script) -> Script:
|
|
288
|
+
declarations: List = []
|
|
289
|
+
moved_to_body: List = []
|
|
290
|
+
for declaration in script.declarations:
|
|
291
|
+
if isinstance(declaration, FuncDecl):
|
|
292
|
+
declarations.extend(self.statement(declaration))
|
|
293
|
+
elif isinstance(declaration, (VarDecl, VaripDecl)) and has_history_site(declaration.value, self.field_read_rule):
|
|
294
|
+
# The local must precede the declaration, so both run as body statements
|
|
295
|
+
moved_to_body.extend(self.statement(declaration))
|
|
296
|
+
else:
|
|
297
|
+
declarations.append(declaration)
|
|
298
|
+
body = _merged_by_line(self.block(script.body), moved_to_body)
|
|
299
|
+
return dataclasses.replace(script, declarations=declarations, body=body)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _merged_by_line(body: list, moved: list) -> list:
|
|
303
|
+
"""`moved` placed among `body` by source line: each goes before the first
|
|
304
|
+
body statement on a later line. The order within each list is kept."""
|
|
305
|
+
merged = list(body)
|
|
306
|
+
for statement in moved:
|
|
307
|
+
position = next((index for index, item in enumerate(merged) if (item.line or 0) > statement.line),
|
|
308
|
+
len(merged))
|
|
309
|
+
merged = merged[:position] + [statement] + merged[position:]
|
|
310
|
+
return merged
|