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,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,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ pine2pyne = pine2pyne.cli:main
3
+ pine2pyne-compat = pine2pyne.compat.cli:main