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,357 @@
|
|
|
1
|
+
"""Value blocks lowered the way PyneComp lowers them.
|
|
2
|
+
|
|
3
|
+
A Pine ``if`` or ``switch`` used as a value (assigned, or ending a function body)
|
|
4
|
+
becomes statements around one temporary, numbered in source order across the
|
|
5
|
+
module (``__block_result__``, ``__block_result_1__``, ...)::
|
|
6
|
+
|
|
7
|
+
ma = switch mode __block_result__ = na
|
|
8
|
+
"A" => ta.sma(close, 5) __switch__ = mode
|
|
9
|
+
"B" => ta.ema(close, 5) if __switch__ == 'A':
|
|
10
|
+
__block_result__ = ta.sma(close, 5)
|
|
11
|
+
elif __switch__ == 'B':
|
|
12
|
+
__block_result__ = ta.ema(close, 5)
|
|
13
|
+
ma = __block_result__
|
|
14
|
+
|
|
15
|
+
A switch with a subject reads it once, into ``__switch__``; one nested ``n`` blocks
|
|
16
|
+
deep in its function reads it into ``__switch_<n>__``. A switch statement does too. A branch whose value is itself an ``if`` or a
|
|
17
|
+
``switch`` gets the next number and hands its value to the outer temporary. A
|
|
18
|
+
function whose body ends with an ``if`` or a ``switch`` returns the temporary.
|
|
19
|
+
Measured on ``assets/pyne``: 302 modules hold block results, 151 a ``__switch__``.
|
|
20
|
+
"""
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import dataclasses
|
|
24
|
+
from typing import List, Optional
|
|
25
|
+
|
|
26
|
+
from .ast_nodes import (
|
|
27
|
+
ArrayLiteral, Assignment, BinaryOp, ExpressionStatement, ForInLoop, ForLoop, FuncDecl, FunctionCall, Identifier,
|
|
28
|
+
IfExpression, IfStatement, Literal, NaLiteral, RawCode, Reassignment, Script, SwitchExpression, SwitchStatement,
|
|
29
|
+
TupleDestructure, VarDecl, VaripDecl, WhileLoop,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
_VALUE_NODES = (IfExpression, SwitchExpression)
|
|
33
|
+
_LOOPS = (ForLoop, ForInLoop, WhileLoop)
|
|
34
|
+
#: what may give a value: an if/switch (statement or expression) or a loop (its last pass's last statement)
|
|
35
|
+
_VALUE_GIVERS = (IfStatement, SwitchStatement, IfExpression, SwitchExpression) + _LOOPS
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _laid_out(statement, origin):
|
|
39
|
+
"""`statement` without a Pine line of its own printed as at the first line found in `origin`: the
|
|
40
|
+
blank lines around it follow that line (`source_line`, read by the code generator only)."""
|
|
41
|
+
if not statement.line:
|
|
42
|
+
from .ast_walk import walk
|
|
43
|
+
statement.source_line = next((found.line for found in walk(origin) if getattr(found, 'line', 0)), 0)
|
|
44
|
+
return statement
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _is_bare_value(statement) -> bool:
|
|
48
|
+
"""A statement that only names a value (`x`, `na`, `false`): it does nothing in a block."""
|
|
49
|
+
if not isinstance(statement, ExpressionStatement):
|
|
50
|
+
return False
|
|
51
|
+
expr = statement.expr
|
|
52
|
+
if isinstance(expr, FunctionCall) and expr.func in _CASTS and len(expr.args) == 1 and not expr.kwargs:
|
|
53
|
+
expr = expr.args[0] # `int(na)` too (PyneComp)
|
|
54
|
+
return isinstance(expr, (Identifier, NaLiteral, Literal))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
_CASTS = frozenset({'int', 'float', 'bool', 'string', 'color'})
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _without_bare_values(statements: list) -> list:
|
|
61
|
+
"""A loop's or a branch's statements without bare values; `pass` when nothing is left."""
|
|
62
|
+
kept = [statement for statement in statements if not _is_bare_value(statement)]
|
|
63
|
+
if kept or not statements:
|
|
64
|
+
return kept
|
|
65
|
+
return [RawCode(code='pass', line=getattr(statements[0], 'line', 0))]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _discarding_last_declaration(statements):
|
|
69
|
+
"""A statement block (an ``if``, ``switch`` or loop not used as a value) whose last statement
|
|
70
|
+
declares a variable: PyneComp keeps only the declared value, an expression statement, as a
|
|
71
|
+
block's last statement is its (here unused) value. ``float x = na`` keeps ``na(float)``."""
|
|
72
|
+
if not statements:
|
|
73
|
+
return statements
|
|
74
|
+
last = statements[-1]
|
|
75
|
+
if isinstance(last, (VarDecl, VaripDecl)) or (isinstance(last, Assignment)
|
|
76
|
+
and isinstance(last.target, (str, TupleDestructure))):
|
|
77
|
+
value = last.value
|
|
78
|
+
if isinstance(value, _VALUE_NODES):
|
|
79
|
+
return statements
|
|
80
|
+
if isinstance(value, NaLiteral) and getattr(last, 'type_hint', None):
|
|
81
|
+
value = FunctionCall(func=NaLiteral(), args=[Identifier(name=last.type_hint)],
|
|
82
|
+
line=last.line, column=last.column)
|
|
83
|
+
return statements[:-1] + [ExpressionStatement(expr=value, line=last.line, column=last.column)]
|
|
84
|
+
return statements
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _statement_block(statements):
|
|
88
|
+
return _discarding_last_declaration(_without_bare_values(statements))
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
#: user function name -> how many values its result has (0: not a tuple); set per script
|
|
92
|
+
_FUNCTION_WIDTHS: dict = {}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _tuple_width(node) -> int:
|
|
96
|
+
"""How many values an if/switch value yields: a tuple branch `[a, b]` gives 2, as does a call
|
|
97
|
+
of a user function whose value is a 2-tuple; else 0."""
|
|
98
|
+
if isinstance(node, ArrayLiteral):
|
|
99
|
+
return len(node.elements)
|
|
100
|
+
if isinstance(node, FunctionCall) and isinstance(node.func, str):
|
|
101
|
+
return _FUNCTION_WIDTHS.get(node.func, 0)
|
|
102
|
+
if isinstance(node, list):
|
|
103
|
+
return _tuple_width(node[-1]) if node else 0
|
|
104
|
+
if isinstance(node, ExpressionStatement):
|
|
105
|
+
return _tuple_width(node.expr)
|
|
106
|
+
if isinstance(node, IfExpression):
|
|
107
|
+
return max(_tuple_width(node.true_expr), _tuple_width(node.false_expr) if node.false_expr is not None else 0)
|
|
108
|
+
if isinstance(node, IfStatement):
|
|
109
|
+
branches = [node.body] + [body for _, body in node.elseifs] + ([node.else_body] if node.else_body else [])
|
|
110
|
+
return max(_tuple_width(branch) for branch in branches)
|
|
111
|
+
if isinstance(node, (SwitchExpression, SwitchStatement)):
|
|
112
|
+
branches = [body for _, body in node.cases] + ([node.default] if node.default is not None else [])
|
|
113
|
+
return max((_tuple_width(branch) for branch in branches), default=0)
|
|
114
|
+
if isinstance(node, _LOOPS):
|
|
115
|
+
return _tuple_width(node.body)
|
|
116
|
+
return 0
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _function_widths(script: Script) -> dict:
|
|
120
|
+
"""The tuple width of every user function's value, followed through calls of other functions."""
|
|
121
|
+
functions = [node for node in list(script.declarations) + list(script.body) if isinstance(node, FuncDecl)]
|
|
122
|
+
_FUNCTION_WIDTHS.clear()
|
|
123
|
+
for _ in range(len(functions) + 1):
|
|
124
|
+
changed = False
|
|
125
|
+
for function in functions:
|
|
126
|
+
width = _tuple_width(function.body if isinstance(function.body, list) else [function.body])
|
|
127
|
+
if width and _FUNCTION_WIDTHS.get(function.name) != width:
|
|
128
|
+
_FUNCTION_WIDTHS[function.name] = width
|
|
129
|
+
changed = True
|
|
130
|
+
if not changed:
|
|
131
|
+
break
|
|
132
|
+
return dict(_FUNCTION_WIDTHS)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class _Names:
|
|
136
|
+
"""``__<stem>__``, then ``__<stem>_1__``, ``__<stem>_2__``, ... per stem."""
|
|
137
|
+
|
|
138
|
+
def __init__(self) -> None:
|
|
139
|
+
self._counts: dict = {}
|
|
140
|
+
|
|
141
|
+
def next(self, stem: str) -> str:
|
|
142
|
+
count = self._counts.get(stem, 0)
|
|
143
|
+
self._counts[stem] = count + 1
|
|
144
|
+
return f'__{stem}__' if count == 0 else f'__{stem}_{count}__'
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class _Lowering:
|
|
148
|
+
def __init__(self, version: int = 6) -> None:
|
|
149
|
+
self.version = version
|
|
150
|
+
self.names = _Names()
|
|
151
|
+
# how deep the statement being lowered is nested in blocks of its function (main() or a user
|
|
152
|
+
# function): PyneComp names a switch subject by it, `__switch__` at 0, `__switch_<depth>__` below
|
|
153
|
+
self.depth = 0
|
|
154
|
+
|
|
155
|
+
def switch_subject(self) -> str:
|
|
156
|
+
return '__switch__' if self.depth == 0 else f'__switch_{self.depth}__'
|
|
157
|
+
|
|
158
|
+
# ------------------------------------------------------------ statements
|
|
159
|
+
|
|
160
|
+
def nested(self, statements, lower):
|
|
161
|
+
"""`lower(statements)` one block deeper."""
|
|
162
|
+
self.depth += 1
|
|
163
|
+
try:
|
|
164
|
+
return lower(statements)
|
|
165
|
+
finally:
|
|
166
|
+
self.depth -= 1
|
|
167
|
+
|
|
168
|
+
def block(self, statements) -> list:
|
|
169
|
+
if statements is None:
|
|
170
|
+
return None
|
|
171
|
+
lowered: List = []
|
|
172
|
+
for statement in statements:
|
|
173
|
+
lowered.extend(self.statement(statement))
|
|
174
|
+
return lowered
|
|
175
|
+
|
|
176
|
+
def statement(self, node) -> list:
|
|
177
|
+
replace = dataclasses.replace
|
|
178
|
+
if isinstance(node, (Assignment, Reassignment, VarDecl, VaripDecl)) \
|
|
179
|
+
and isinstance(node.value, _VALUE_NODES + _LOOPS):
|
|
180
|
+
target = getattr(node, 'target', None)
|
|
181
|
+
width = len(target.names) if isinstance(target, TupleDestructure) else _tuple_width(node.value)
|
|
182
|
+
result = self.names.next('block_result')
|
|
183
|
+
value = node.value
|
|
184
|
+
if isinstance(value, ForLoop):
|
|
185
|
+
value = dataclasses.replace(value, is_assigned_value=True)
|
|
186
|
+
statements = [self._initial(result, width, node.line)] + self.value(value, result)
|
|
187
|
+
statements.append(replace(node, value=Identifier(name=result)))
|
|
188
|
+
return statements
|
|
189
|
+
if isinstance(node, SwitchStatement):
|
|
190
|
+
return self.switch(node, None)
|
|
191
|
+
if isinstance(node, IfStatement):
|
|
192
|
+
return [replace(node, body=_statement_block(self.nested(node.body, self.block)),
|
|
193
|
+
elseifs=[(condition, _statement_block(self.nested(body, self.block)))
|
|
194
|
+
for condition, body in node.elseifs],
|
|
195
|
+
else_body=_statement_block(self.nested(node.else_body, self.block))
|
|
196
|
+
if node.else_body is not None else None)]
|
|
197
|
+
if isinstance(node, (ForLoop, ForInLoop, WhileLoop)):
|
|
198
|
+
return [replace(node, body=_statement_block(self.nested(node.body, self.block)))]
|
|
199
|
+
if isinstance(node, FuncDecl):
|
|
200
|
+
return [self.function(node)]
|
|
201
|
+
return [node]
|
|
202
|
+
|
|
203
|
+
def function(self, node: FuncDecl) -> FuncDecl:
|
|
204
|
+
saved, self.depth = self.depth, 0 # a function's body is the top of its own blocks
|
|
205
|
+
try:
|
|
206
|
+
lowered = self._function(node)
|
|
207
|
+
finally:
|
|
208
|
+
self.depth = saved
|
|
209
|
+
original = node.body[-1] if isinstance(node.body, list) and node.body else node.body
|
|
210
|
+
returns_if_value = isinstance(getattr(original, 'value', None), IfExpression) and isinstance(
|
|
211
|
+
original, (Assignment, VarDecl, VaripDecl))
|
|
212
|
+
if (self.version >= 6 or returns_if_value) and isinstance(lowered.body, list) and lowered.body:
|
|
213
|
+
# Pine v6: a function ending with a declaration returns the declared value itself
|
|
214
|
+
# (probe tmp/probes/tail6.pine); v5 keeps the declaration and returns its name, but for an
|
|
215
|
+
# if value, whose block result it returns (tail5b.pine: not for a switch value)
|
|
216
|
+
last = lowered.body[-1]
|
|
217
|
+
declares = isinstance(last, (VarDecl, VaripDecl)) or (
|
|
218
|
+
isinstance(last, Assignment) and isinstance(last.target, (str, TupleDestructure)))
|
|
219
|
+
if declares and last.value is not None and not isinstance(last.value, _VALUE_NODES + _LOOPS):
|
|
220
|
+
tail = ExpressionStatement(expr=last.value, line=last.line, column=last.column)
|
|
221
|
+
lowered = dataclasses.replace(lowered, body=lowered.body[:-1] + [tail])
|
|
222
|
+
return lowered
|
|
223
|
+
|
|
224
|
+
def _function(self, node: FuncDecl) -> FuncDecl:
|
|
225
|
+
body = node.body
|
|
226
|
+
if isinstance(body, _VALUE_NODES):
|
|
227
|
+
body = [ExpressionStatement(expr=body, line=node.line)]
|
|
228
|
+
elif isinstance(body, (Assignment, Reassignment, VarDecl, VaripDecl)):
|
|
229
|
+
body = [body] # a one-line body that declares: `f(x) => float r = x + 1`
|
|
230
|
+
node = dataclasses.replace(node, body=body)
|
|
231
|
+
if not isinstance(body, list) or not body:
|
|
232
|
+
return node
|
|
233
|
+
last = body[-1]
|
|
234
|
+
if isinstance(last, ExpressionStatement) and isinstance(last.expr, _VALUE_NODES):
|
|
235
|
+
last = last.expr
|
|
236
|
+
if not isinstance(last, _VALUE_GIVERS):
|
|
237
|
+
return dataclasses.replace(node, body=self.block(body))
|
|
238
|
+
lowered = self.block(body[:-1])
|
|
239
|
+
result = self.names.next('block_result')
|
|
240
|
+
lowered.append(self._initial(result, _tuple_width(last), getattr(body[-1], 'line', 0)))
|
|
241
|
+
lowered.extend(self.value(last, result))
|
|
242
|
+
lowered.append(ExpressionStatement(expr=Identifier(name=result))) # the function's value
|
|
243
|
+
return dataclasses.replace(node, body=lowered)
|
|
244
|
+
|
|
245
|
+
# ------------------------------------------------------------ values
|
|
246
|
+
|
|
247
|
+
@staticmethod
|
|
248
|
+
def _initial(result: str, width: int, line: int) -> Assignment:
|
|
249
|
+
value = ArrayLiteral(elements=[NaLiteral() for _ in range(width)]) if width else NaLiteral()
|
|
250
|
+
return Assignment(target=result, value=value, line=line)
|
|
251
|
+
|
|
252
|
+
def value(self, node, result: str) -> list:
|
|
253
|
+
"""Statements that leave the value of an if/switch (expression or statement) in `result`."""
|
|
254
|
+
if isinstance(node, (SwitchExpression, SwitchStatement)):
|
|
255
|
+
return self.switch(node, result)
|
|
256
|
+
if isinstance(node, _LOOPS):
|
|
257
|
+
# the loop's value is its last pass's: each pass assigns its last statement's value
|
|
258
|
+
return [dataclasses.replace(node, body=self.nested(node.body, lambda body: self.branch(body, result)))]
|
|
259
|
+
def arm(body):
|
|
260
|
+
return self.nested(body, lambda statements: self.branch(statements, result))
|
|
261
|
+
if isinstance(node, IfExpression):
|
|
262
|
+
# an `else if` chain is one if/elif with one block result (probe nb3_05); its blocks are numbered
|
|
263
|
+
# in source order, the first arm's first (TFlab/AlertSenderLibrary)
|
|
264
|
+
first = arm(node.true_expr)
|
|
265
|
+
elseifs, rest = [], node.false_expr
|
|
266
|
+
while isinstance(rest, IfExpression) and rest.chained: # `else if`; an `if` inside an `else` block stays
|
|
267
|
+
chained = rest
|
|
268
|
+
elseifs.append((chained.condition, arm(chained.true_expr)))
|
|
269
|
+
rest = chained.false_expr
|
|
270
|
+
return [_laid_out(IfStatement(condition=node.condition, body=first, elseifs=elseifs,
|
|
271
|
+
else_body=arm(rest) if rest is not None else None,
|
|
272
|
+
line=node.line), node)]
|
|
273
|
+
return [dataclasses.replace(
|
|
274
|
+
node, body=arm(node.body),
|
|
275
|
+
elseifs=[(condition, arm(body)) for condition, body in node.elseifs],
|
|
276
|
+
else_body=arm(node.else_body) if node.else_body is not None else None)]
|
|
277
|
+
|
|
278
|
+
def switch(self, node, result: Optional[str]) -> list:
|
|
279
|
+
lowered: List = []
|
|
280
|
+
conditions = [case for case, _ in node.cases]
|
|
281
|
+
if node.expr is not None:
|
|
282
|
+
subject = self.switch_subject()
|
|
283
|
+
lowered.append(_laid_out(Assignment(target=subject, value=node.expr, line=node.line), node))
|
|
284
|
+
conditions = [BinaryOp(left=Identifier(name=subject), op='==', right=case) for case in conditions]
|
|
285
|
+
bodies = [body for _, body in node.cases]
|
|
286
|
+
if result is None:
|
|
287
|
+
arms = [_discarding_last_declaration(self.nested(body, self.block)) for body in bodies]
|
|
288
|
+
default = _discarding_last_declaration(self.nested(node.default, self.block))
|
|
289
|
+
else:
|
|
290
|
+
arms = [self.nested(body, lambda statements: self.branch(statements, result)) for body in bodies]
|
|
291
|
+
default = (self.nested(node.default, lambda statements: self.branch(statements, result))
|
|
292
|
+
if node.default is not None else None)
|
|
293
|
+
if not arms:
|
|
294
|
+
return lowered + (default or [])
|
|
295
|
+
lowered.append(_laid_out(IfStatement(condition=conditions[0], body=arms[0],
|
|
296
|
+
elseifs=list(zip(conditions[1:], arms[1:])), else_body=default,
|
|
297
|
+
line=node.line), node))
|
|
298
|
+
return lowered
|
|
299
|
+
|
|
300
|
+
def branch(self, body, result: str) -> list:
|
|
301
|
+
"""A branch's statements, its last one's value assigned to `result`."""
|
|
302
|
+
if not isinstance(body, list):
|
|
303
|
+
body = [ExpressionStatement(expr=body)]
|
|
304
|
+
if not body:
|
|
305
|
+
return body
|
|
306
|
+
lowered = self.block(body[:-1])
|
|
307
|
+
last = body[-1]
|
|
308
|
+
nested = last.expr if isinstance(last, ExpressionStatement) and isinstance(last.expr, _VALUE_NODES) else last
|
|
309
|
+
if isinstance(nested, _VALUE_GIVERS):
|
|
310
|
+
inner = self.names.next('block_result')
|
|
311
|
+
lowered.append(self._initial(inner, _tuple_width(nested), getattr(last, 'line', 0)))
|
|
312
|
+
lowered.extend(self.value(nested, inner))
|
|
313
|
+
lowered.append(Reassignment(target=result, value=Identifier(name=inner)))
|
|
314
|
+
elif isinstance(last, ExpressionStatement):
|
|
315
|
+
lowered.append(Reassignment(target=result, value=last.expr, line=last.line))
|
|
316
|
+
else:
|
|
317
|
+
lowered.extend(self.statement(last))
|
|
318
|
+
target = getattr(last, 'target', None)
|
|
319
|
+
if isinstance(target, str):
|
|
320
|
+
lowered.append(Reassignment(target=result, value=Identifier(name=target)))
|
|
321
|
+
return lowered
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def lower_value_blocks(script: Script) -> Script:
|
|
325
|
+
"""`script` with every if/switch value lowered to statements, PyneComp's way."""
|
|
326
|
+
version_text = getattr(script.version, 'version', '') if script.version else ''
|
|
327
|
+
lowering = _Lowering(int(version_text) if str(version_text).isdigit() else 6)
|
|
328
|
+
_function_widths(script)
|
|
329
|
+
items = sorted([(item.line, 0, index, item) for index, item in enumerate(script.declarations)]
|
|
330
|
+
+ [(item.line, 1, index, item) for index, item in enumerate(script.body)],
|
|
331
|
+
key=lambda entry: (entry[0], entry[1], entry[2]))
|
|
332
|
+
declarations = list(script.declarations)
|
|
333
|
+
lowered_vars: list = []
|
|
334
|
+
body_parts = {index: [item] for index, item in enumerate(script.body)}
|
|
335
|
+
for _, kind, index, item in items:
|
|
336
|
+
if kind == 0:
|
|
337
|
+
if isinstance(item, FuncDecl):
|
|
338
|
+
declarations[index] = lowering.function(item)
|
|
339
|
+
elif isinstance(item, (VarDecl, VaripDecl)) and isinstance(item.value, _VALUE_NODES + _LOOPS):
|
|
340
|
+
# the lowered statements run in main(): they move to the body, at the var's line
|
|
341
|
+
parts = lowering.statement(item)
|
|
342
|
+
for part in parts:
|
|
343
|
+
if not getattr(part, 'line', 0):
|
|
344
|
+
part.line = item.line
|
|
345
|
+
declarations[index] = None
|
|
346
|
+
lowered_vars.append((item.line, parts))
|
|
347
|
+
else:
|
|
348
|
+
parts = lowering.statement(item)
|
|
349
|
+
for part in parts: # the transformer orders the top level by Pine line
|
|
350
|
+
if not getattr(part, 'line', 0):
|
|
351
|
+
part.line = item.line
|
|
352
|
+
body_parts[index] = parts
|
|
353
|
+
body = [statement for index in range(len(script.body)) for statement in body_parts[index]]
|
|
354
|
+
for _, parts in lowered_vars:
|
|
355
|
+
body.extend(parts) # the transformer orders the top level by line
|
|
356
|
+
declarations = [item for item in declarations if item is not None]
|
|
357
|
+
return dataclasses.replace(script, declarations=declarations, body=body)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pine2pyne
|
|
3
|
+
Version: 0.2.4
|
|
4
|
+
Summary: Pine Script v6 to PyneCore Python transpiler
|
|
5
|
+
Author: rubycell
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/pine2pyne/pine2pyne
|
|
8
|
+
Project-URL: Repository, https://github.com/pine2pyne/pine2pyne
|
|
9
|
+
Keywords: pinescript,tradingview,transpiler,pynecore,backtesting
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
14
|
+
Requires-Python: >=3.11
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: dishka>=1.4
|
|
18
|
+
Provides-Extra: runtime
|
|
19
|
+
Requires-Dist: opencode-pyneruntime[cli]>=6.9.1; extra == "runtime"
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
<p align="center">
|
|
23
|
+
<img src="https://raw.githubusercontent.com/pine2pyne/.github/main/images/logo.png" height="115px" alt="pine2pyne" />
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
<p align="center">
|
|
27
|
+
<strong>TradingView Pine Script, transpiled to Python on your own machine</strong><br>
|
|
28
|
+
Indicators, strategies and libraries in Pine Script v4, v5 and v6, compiled to PyneCore.<br>
|
|
29
|
+
No API key, no quota, no network.
|
|
30
|
+
</p>
|
|
31
|
+
|
|
32
|
+
> pine2pyne converts TradingView Pine Script indicators, strategies and libraries into Python scripts
|
|
33
|
+
for [PyneCore](https://pynecore.org), the open-source runtime that runs Pine-style code bar by bar.
|
|
34
|
+
A converted script can backtest offline on any candles you have, without a TradingView account or
|
|
35
|
+
chart.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
Requires Python 3.11+.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install pine2pyne
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
This installs the `pine2pyne` command. `python -m pine2pyne` does the same.
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pine2pyne strategy.pine -o strategy.py
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
pine2pyne [-h] [-o OUTPUT] [--validate] [-v] [--version] input [input ...]
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
| Option | Meaning |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `input` | One or more `.pine` files. A quoted pattern such as `"src/*.pine"` is expanded by pine2pyne itself |
|
|
60
|
+
| `-o`, `--output` | Output file, or a directory for several inputs. Without it, the code goes to stdout |
|
|
61
|
+
| `--validate` | Check the script, write nothing |
|
|
62
|
+
| `-v`, `--verbose` | Print each step |
|
|
63
|
+
| `--version` | Print the version and exit |
|
|
64
|
+
|
|
65
|
+
## Examples
|
|
66
|
+
|
|
67
|
+
| Command | Result |
|
|
68
|
+
|---|---|
|
|
69
|
+
| `pine2pyne a.pine` | The code on stdout, so `pine2pyne a.pine > a.py` gives a clean file |
|
|
70
|
+
| `pine2pyne a.pine -o out/a.py` | Written to `out/a.py`. Missing directories are created |
|
|
71
|
+
| `pine2pyne a.pine b.pine -o out/` | `out/a.py` and `out/b.py` |
|
|
72
|
+
| `pine2pyne "src/*.pine" -o out/` | One `<name>.py` in `out/` for every match of the pattern |
|
|
73
|
+
| `pine2pyne a.pine --validate` | `✓ a.pine - Valid syntax`, or the error |
|
|
74
|
+
|
|
75
|
+
With several inputs, `-o` is a directory. A failed file does not stop the batch: every input is
|
|
76
|
+
processed, then a summary is printed:
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
Summary: 41 succeeded, 2 failed
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Errors
|
|
83
|
+
|
|
84
|
+
Errors and warnings go to stderr with the Pine line and column:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
✗ bad.pine: Error at 4:6: Pine refuses: cannot assign with ':=' to a history reference (x[n]) [history-target]
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
A warning (`⚠`) does not stop the file from being written.
|
|
91
|
+
|
|
92
|
+
| Exit code | When |
|
|
93
|
+
|---|---|
|
|
94
|
+
| `0` | Every input was converted or validated |
|
|
95
|
+
| `1` | At least one input failed, or no input file was found |
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
Apache-2.0, see [LICENSE](https://github.com/pine2pyne/pine2pyne/blob/main/LICENSE).
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
pine2pyne/__init__.py,sha256=SzwH5GzUz3MGhzC6YY-aHrCjT47kKEuI_CqFTdahZCI,2352
|
|
2
|
+
pine2pyne/__main__.py,sha256=w6UY1yZijkjI0YRgq03guOunEYTp9S4DjwsAips2GOk,228
|
|
3
|
+
pine2pyne/ast_nodes.py,sha256=4jFE1sXocScgshlBtAmOYMU_Et25JmLhA13Vr7QG9M8,13066
|
|
4
|
+
pine2pyne/ast_walk.py,sha256=Qt0HUyF7nR3G9kErTB22TuarUJ0g4xQV8QBQ3YeuuIg,4037
|
|
5
|
+
pine2pyne/block_value_lowering.py,sha256=gAtc0tX0tBfpqrMwIRhbXOUP5q8F60WHR-5Xjj0SjUE,35086
|
|
6
|
+
pine2pyne/bool_builtins.py,sha256=VSJqSKn06bPNBHm00LDcurzr24ff7lyGr3SrFbGR0XA,1921
|
|
7
|
+
pine2pyne/bool_value_typing.py,sha256=oR4bVYRQD-zJVWnxRSVStnR8ZeRpVqpJKwWCp71uS9c,29274
|
|
8
|
+
pine2pyne/cli.py,sha256=OBEbgDEEUh-kR8J5bUVIQA1XAo_KDYDUgLSzLctrfFY,8519
|
|
9
|
+
pine2pyne/codegen.py,sha256=3uAbxOkres6A_GRh0X7OgenQ3eFBBipqQCwBrxCp4Ns,39412
|
|
10
|
+
pine2pyne/condition_typing.py,sha256=absbpo0fm-ZOzehZzJGvCFPMaY0T15HdMSBgI9ZPLdw,14469
|
|
11
|
+
pine2pyne/conditional_history_calls.py,sha256=T2BIN0MpPxLc17nj1VIynHsM2i4l_jd9dTol4Nb0V-E,4556
|
|
12
|
+
pine2pyne/di.py,sha256=0xikVhk_1HplqMBCZkEr0eDwG76d__wXSwZZC5yw4Rg,5347
|
|
13
|
+
pine2pyne/emitted_names.py,sha256=vB5-d-2ChM7Bvp_VdrSseEX3ElGEH4Hz2krWlPbxreI,7168
|
|
14
|
+
pine2pyne/errors.py,sha256=6T2OoC0u2inr1XWMIeC3glI6aplL8dcgET7maA4jDjY,2622
|
|
15
|
+
pine2pyne/for_loop_end_bound.py,sha256=n5RO0yzkGJyckDbbMQoxs9T99QE4UUHjM--lEY4U10o,9813
|
|
16
|
+
pine2pyne/grouped_history_lowering.py,sha256=hxoQcJy0HyvzA7jXQLkcYeJUJ0onDJgKs3Jm5auf5FM,16963
|
|
17
|
+
pine2pyne/import_resolver.py,sha256=2x32cs22REpPc912u8r4mZDNtRPftnZ1Vzq844leU5s,23400
|
|
18
|
+
pine2pyne/input_hoisting.py,sha256=YXCpV_4r7Cf3CJMP66Yh3LbSaXL4SRddyAObkQcA4CM,10020
|
|
19
|
+
pine2pyne/legacy_function_names.py,sha256=4vnHEJsmYbO7kMh2aXgh4OcogsAdOa_Q6Rruxnxh-4s,3772
|
|
20
|
+
pine2pyne/legacy_sessions.py,sha256=AOu_y1iyzpZcfjkJo8Orny68acVcZDBVLovwdExzMGU,3286
|
|
21
|
+
pine2pyne/lexer.py,sha256=LwgFH26MXksgb5FK5RVXwVK6iyAeNLnGqFAQuaNJNiw,26210
|
|
22
|
+
pine2pyne/library_collision.py,sha256=lQO68h0XOuQ3y3UAGjJCAqQgCQgvJvC_BePueHlDQA8,22407
|
|
23
|
+
pine2pyne/namespace_members.py,sha256=CO-oAesSy6LWthD2qItUrL3cTm8ZZ2Yulg3q_mD69BA,12859
|
|
24
|
+
pine2pyne/node_ids.py,sha256=hu2VVtbRKPSaS1K2Nm5qeeG_0reGY5E_lSon9HbHX_k,10500
|
|
25
|
+
pine2pyne/once_lowering.py,sha256=frr0g9nX612MKsPiLyeFEDvyoRcQDQTsBiUZ-qMUiq8,3481
|
|
26
|
+
pine2pyne/overload_resolution.py,sha256=zWA2jbJ-RgnSUuNYoS0GmYwqygW4avPwYa4yfDDYevE,24635
|
|
27
|
+
pine2pyne/parse_repair.py,sha256=Uec959EDCvnqeHdVD1ZpSizH150rUAk55BnYF9EbNBM,18882
|
|
28
|
+
pine2pyne/parser.py,sha256=Tq8BYIaFEewXHRp08jU0ZO9qFDcBsp-ssoynOCHhqcM,81678
|
|
29
|
+
pine2pyne/pine_builtins.py,sha256=dI7plYXbyho1Wsz7lWBjjYlQhdtwXCE5QTkG90kpHsI,17703
|
|
30
|
+
pine2pyne/pine_semantics.py,sha256=e0CSoUCFp6BYzb2XNIi5_cQVgHfm--lMTKGvaTuraJw,34765
|
|
31
|
+
pine2pyne/scope_renamer.py,sha256=GN9myfB5tBPKflPoPsCw6JqH7toqhj804AoBAO4hzIg,57178
|
|
32
|
+
pine2pyne/strategy_when_lowering.py,sha256=bN5rAEKGy6bkp3Rm093GnAfpwtswExYsBbBGOdMeMbo,5334
|
|
33
|
+
pine2pyne/strict_boolean.py,sha256=7Z-q2tdVk0rFgu4SecX9GVaWgBof_IFtBKjw1tBolB0,5471
|
|
34
|
+
pine2pyne/symbol_table.py,sha256=SO40do-faSu-n29-5GOrkKAqEK6Ef9BGtw4sC0w9Jbo,6146
|
|
35
|
+
pine2pyne/tokens.py,sha256=diG6z5_9DsDCdSN-sapdDAbdIwvklQPuC-05tkhQzDk,3792
|
|
36
|
+
pine2pyne/transformer.py,sha256=Qpzeahx8wYntviybWPlE1Fq0CaV1uRrE7_tadRivFyo,138650
|
|
37
|
+
pine2pyne/type_inference.py,sha256=8_ElqzbCefS_lvLBzgue7_gMU3jQBWGkT6Tz3K5LB34,20317
|
|
38
|
+
pine2pyne/udt_field_defaults.py,sha256=OV2lnodQo7m_5dh7UFp2E7OcT8DQuMCe4xcTQGmReng,8231
|
|
39
|
+
pine2pyne/value_blocks.py,sha256=6uR5Jy6329S1j8NpvGqTM6ntUq6BCsamURomiMA3PSQ,18528
|
|
40
|
+
pine2pyne/compat/__init__.py,sha256=NPkw4Hs_p7-oXO4utBMpLzRtCr3VTNzsiSo8jgzITwM,536
|
|
41
|
+
pine2pyne/compat/__main__.py,sha256=5zfJK8Dgm9BJ69PSo2ZGGVk-DPdq3Ihw9qpncugs6Hg,83
|
|
42
|
+
pine2pyne/compat/cli.py,sha256=jEnPPqk_PzQ5QP1XxBkigy00T9qCRUENRVnGyHhrA7Q,4625
|
|
43
|
+
pine2pyne/compat/comparison.py,sha256=00zi4ycrCmubVu4_Y9kZG6ah69e_V0b6XnIh22w90mY,4097
|
|
44
|
+
pine2pyne/compat/corpus.py,sha256=E_FMkYo5hmRJDw-vmD0AGa565V1AxPNLhQR16C5YnmU,2363
|
|
45
|
+
pine2pyne/compat/report.py,sha256=W1JHMJycVkCI_6lsoTaoOXJ9XjTv_biKPQZ-pMbw3ls,4446
|
|
46
|
+
pine2pyne/compat/runner.py,sha256=Glq1rt_ypxdnt3s95d-juVyMMZXsDDkO7_DGPfhmsBA,2416
|
|
47
|
+
pine2pyne/core/__init__.py,sha256=3QUg8SctseD0BtJRp472aUOBULjBnY5fZLTkBL1cKRQ,527
|
|
48
|
+
pine2pyne/core/contracts.py,sha256=_UD56mxszLlotsddvVihkr8KoIMidjagMbziOljCh9s,3974
|
|
49
|
+
pine2pyne/emit/__init__.py,sha256=y7RXTwj9ynChimkymw4vq9hx6EMFqjm4ovKKvmSt1HQ,257
|
|
50
|
+
pine2pyne/emit/legacy.py,sha256=JSYMd40prbGZZ-TUoR2QsywRcjmMF2WUWBvuxNCGXrE,454
|
|
51
|
+
pine2pyne/emit/pynecomp/__init__.py,sha256=Wiu9RniZKeeLbAKle-4-eU52iiYw9mwyKtuquf0UbCU,711
|
|
52
|
+
pine2pyne/emit/pynecomp/banner.py,sha256=9JjQ7uKgfR9fXC-lqHqJQ2PgTi44Q8n8_wD4qM2avk8,902
|
|
53
|
+
pine2pyne/emit/pynecomp/blank_lines.py,sha256=lArRBwTxhgHixAQ_bTtW-_QX_J4lc4sgCIzf6fDM-RQ,3297
|
|
54
|
+
pine2pyne/emit/pynecomp/emitter.py,sha256=7uohVRtp11w0igx60Usq_IrN3pHD6T6knvPOZUZNS3Q,2657
|
|
55
|
+
pine2pyne/emit/pynecomp/imports.py,sha256=M2SCp4VinQKW-AVEaVS7e0_3nzWQGGwfqfw6h8-XF6A,2858
|
|
56
|
+
pine2pyne/emit/pynecomp/printer.py,sha256=Ff-8RP9_RlT6xwFjgzJy09tj8uKpUrue_KM2ln0_fa8,14661
|
|
57
|
+
pine2pyne/emit/pynecomp/unparser.py,sha256=I7A8f4PR6fAa6g4rz1l7imvwPfevV-biz6wtwoW0l_k,9256
|
|
58
|
+
pine2pyne/emit/pynecomp/passes/__init__.py,sha256=y5UO5oSL3jSXkg3wvpH71hK_DD6bCQee72iIVIyS57U,2378
|
|
59
|
+
pine2pyne/emit/pynecomp/passes/annotations.py,sha256=QnHM8LK2ZANqXpiU1-ZtbglaSDJ6A1M_q4gk9j0rayk,23309
|
|
60
|
+
pine2pyne/emit/pynecomp/passes/base.py,sha256=WtfImL0bxhDAqt4JtnbofWGpvGTnbRUKTN56lxb6ANA,2501
|
|
61
|
+
pine2pyne/emit/pynecomp/passes/builtins.py,sha256=vrHnWW8Dq0qwl_hrYGbX81OIyYLn_B92MaXMxdg3CQo,7891
|
|
62
|
+
pine2pyne/emit/pynecomp/passes/hoisting.py,sha256=glfVQT3SLqJOaFy8cIJFSS3eFEV62TIX2Ax-kMZHats,9827
|
|
63
|
+
pine2pyne/emit/pynecomp/passes/imports.py,sha256=Wm-NGUh6ALJiSufHeVZfhO5Z4oqZGfJParl48pETzxE,6079
|
|
64
|
+
pine2pyne/emit/pynecomp/passes/legacy_colors.py,sha256=cOIeIhPQHAFBUycL11UaoZUzDp-mLxfw65cD2pY4gKU,5240
|
|
65
|
+
pine2pyne/emit/pynecomp/passes/library.py,sha256=K41iVWyV9Thelwo9KSECl765qo70NpJm9FMXk2QO4QA,11405
|
|
66
|
+
pine2pyne/emit/pynecomp/passes/rewrites.py,sha256=tDWAVHCSh1cKoaUMiR5HAQjHqJK3ItrM65QZNTqHCw0,10186
|
|
67
|
+
pine2pyne/pipeline/__init__.py,sha256=FB-erFlezSyRIsFphjHZJklZ6CDAcEN9GU7v_dBKguw,462
|
|
68
|
+
pine2pyne/pipeline/service.py,sha256=6iJjpSDgY1GIPJMmP33oDw9UGwr4wCXzeGKzMw1VTq4,2216
|
|
69
|
+
pine2pyne/pipeline/stages.py,sha256=woUABTKnqX57i9Om_o4E2_-UHEpFec8E-uVyTXsT06E,2491
|
|
70
|
+
pine2pyne-0.2.4.dist-info/licenses/LICENSE,sha256=l6ZEJ0BCsllGRX5Pg-ygs6mQD03fLXJggIyKnSMwC8E,11342
|
|
71
|
+
pine2pyne-0.2.4.dist-info/METADATA,sha256=RrQS_llKCO62x5EiHZHXfXfoRC9_prUnJutVjrAYryw,3319
|
|
72
|
+
pine2pyne-0.2.4.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
73
|
+
pine2pyne-0.2.4.dist-info/entry_points.txt,sha256=RfEHcGj3OVRyq7G-igSZAhR7ff7QZFBzUVWtPIo4Yjw,94
|
|
74
|
+
pine2pyne-0.2.4.dist-info/top_level.txt,sha256=ge_qvuxsfM_9zOv_hIwyLCMe1S8RAr5nUR6n3aBO6J0,10
|
|
75
|
+
pine2pyne-0.2.4.dist-info/RECORD,,
|