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.
Files changed (75) hide show
  1. pine2pyne/__init__.py +84 -0
  2. pine2pyne/__main__.py +12 -0
  3. pine2pyne/ast_nodes.py +405 -0
  4. pine2pyne/ast_walk.py +93 -0
  5. pine2pyne/block_value_lowering.py +694 -0
  6. pine2pyne/bool_builtins.py +42 -0
  7. pine2pyne/bool_value_typing.py +580 -0
  8. pine2pyne/cli.py +247 -0
  9. pine2pyne/codegen.py +886 -0
  10. pine2pyne/compat/__init__.py +19 -0
  11. pine2pyne/compat/__main__.py +6 -0
  12. pine2pyne/compat/cli.py +114 -0
  13. pine2pyne/compat/comparison.py +105 -0
  14. pine2pyne/compat/corpus.py +73 -0
  15. pine2pyne/compat/report.py +95 -0
  16. pine2pyne/compat/runner.py +63 -0
  17. pine2pyne/condition_typing.py +260 -0
  18. pine2pyne/conditional_history_calls.py +96 -0
  19. pine2pyne/core/__init__.py +26 -0
  20. pine2pyne/core/contracts.py +125 -0
  21. pine2pyne/di.py +173 -0
  22. pine2pyne/emit/__init__.py +5 -0
  23. pine2pyne/emit/legacy.py +14 -0
  24. pine2pyne/emit/pynecomp/__init__.py +20 -0
  25. pine2pyne/emit/pynecomp/banner.py +30 -0
  26. pine2pyne/emit/pynecomp/blank_lines.py +69 -0
  27. pine2pyne/emit/pynecomp/emitter.py +61 -0
  28. pine2pyne/emit/pynecomp/imports.py +65 -0
  29. pine2pyne/emit/pynecomp/passes/__init__.py +84 -0
  30. pine2pyne/emit/pynecomp/passes/annotations.py +422 -0
  31. pine2pyne/emit/pynecomp/passes/base.py +69 -0
  32. pine2pyne/emit/pynecomp/passes/builtins.py +169 -0
  33. pine2pyne/emit/pynecomp/passes/hoisting.py +205 -0
  34. pine2pyne/emit/pynecomp/passes/imports.py +112 -0
  35. pine2pyne/emit/pynecomp/passes/legacy_colors.py +101 -0
  36. pine2pyne/emit/pynecomp/passes/library.py +205 -0
  37. pine2pyne/emit/pynecomp/passes/rewrites.py +215 -0
  38. pine2pyne/emit/pynecomp/printer.py +288 -0
  39. pine2pyne/emit/pynecomp/unparser.py +186 -0
  40. pine2pyne/emitted_names.py +152 -0
  41. pine2pyne/errors.py +69 -0
  42. pine2pyne/for_loop_end_bound.py +172 -0
  43. pine2pyne/grouped_history_lowering.py +310 -0
  44. pine2pyne/import_resolver.py +469 -0
  45. pine2pyne/input_hoisting.py +195 -0
  46. pine2pyne/legacy_function_names.py +74 -0
  47. pine2pyne/legacy_sessions.py +68 -0
  48. pine2pyne/lexer.py +630 -0
  49. pine2pyne/library_collision.py +434 -0
  50. pine2pyne/namespace_members.py +247 -0
  51. pine2pyne/node_ids.py +235 -0
  52. pine2pyne/once_lowering.py +75 -0
  53. pine2pyne/overload_resolution.py +475 -0
  54. pine2pyne/parse_repair.py +398 -0
  55. pine2pyne/parser.py +1873 -0
  56. pine2pyne/pine_builtins.py +394 -0
  57. pine2pyne/pine_semantics.py +604 -0
  58. pine2pyne/pipeline/__init__.py +18 -0
  59. pine2pyne/pipeline/service.py +70 -0
  60. pine2pyne/pipeline/stages.py +73 -0
  61. pine2pyne/scope_renamer.py +965 -0
  62. pine2pyne/strategy_when_lowering.py +101 -0
  63. pine2pyne/strict_boolean.py +101 -0
  64. pine2pyne/symbol_table.py +166 -0
  65. pine2pyne/tokens.py +143 -0
  66. pine2pyne/transformer.py +2510 -0
  67. pine2pyne/type_inference.py +422 -0
  68. pine2pyne/udt_field_defaults.py +149 -0
  69. pine2pyne/value_blocks.py +357 -0
  70. pine2pyne-0.2.4.dist-info/METADATA +99 -0
  71. pine2pyne-0.2.4.dist-info/RECORD +75 -0
  72. pine2pyne-0.2.4.dist-info/WHEEL +5 -0
  73. pine2pyne-0.2.4.dist-info/entry_points.txt +3 -0
  74. pine2pyne-0.2.4.dist-info/licenses/LICENSE +201 -0
  75. 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