pine2pyne 0.2.5__tar.gz → 0.2.6__tar.gz
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-0.2.5/pine2pyne.egg-info → pine2pyne-0.2.6}/PKG-INFO +3 -3
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/__init__.py +3 -3
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/ast_nodes.py +12 -12
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/ast_walk.py +1 -1
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/block_value_lowering.py +17 -17
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/bool_builtins.py +1 -1
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/bool_value_typing.py +6 -6
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/cli.py +5 -5
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/codegen.py +17 -17
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/condition_typing.py +8 -8
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/conditional_history_calls.py +3 -3
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/emit/pynecomp/blank_lines.py +2 -2
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/emit/pynecomp/imports.py +1 -1
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/emit/pynecomp/passes/annotations.py +4 -4
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/emit/pynecomp/passes/imports.py +1 -1
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/emit/pynecomp/passes/library.py +3 -3
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/emit/pynecomp/passes/rewrites.py +4 -4
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/emit/pynecomp/printer.py +1 -1
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/emit/pynecomp/unparser.py +3 -4
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/emitted_names.py +1 -2
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/errors.py +4 -4
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/for_loop_end_bound.py +3 -3
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/grouped_history_lowering.py +6 -6
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/import_resolver.py +17 -17
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/legacy_function_names.py +2 -2
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/legacy_sessions.py +1 -1
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/lexer.py +4 -4
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/library_collision.py +9 -9
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/namespace_members.py +1 -1
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/node_ids.py +1 -1
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/once_lowering.py +1 -2
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/overload_resolution.py +5 -5
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/parse_repair.py +2 -2
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/parser.py +34 -34
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/pine_builtins.py +13 -13
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/pine_semantics.py +9 -9
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/pipeline/stages.py +4 -4
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/scope_renamer.py +30 -31
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/strategy_when_lowering.py +2 -2
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/strict_boolean.py +1 -1
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/tokens.py +2 -2
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/transformer.py +86 -87
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/type_inference.py +3 -3
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/udt_field_defaults.py +7 -8
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/value_blocks.py +5 -5
- {pine2pyne-0.2.5 → pine2pyne-0.2.6/pine2pyne.egg-info}/PKG-INFO +3 -3
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pyproject.toml +11 -12
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/LICENSE +0 -0
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/MANIFEST.in +0 -0
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/README.md +0 -0
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/__main__.py +0 -0
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/core/__init__.py +0 -0
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/core/contracts.py +0 -0
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/di.py +0 -0
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/emit/__init__.py +0 -0
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/emit/legacy.py +0 -0
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/emit/pynecomp/__init__.py +0 -0
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/emit/pynecomp/banner.py +0 -0
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/emit/pynecomp/emitter.py +0 -0
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/emit/pynecomp/passes/__init__.py +0 -0
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/emit/pynecomp/passes/base.py +0 -0
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/emit/pynecomp/passes/builtins.py +0 -0
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/emit/pynecomp/passes/hoisting.py +0 -0
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/emit/pynecomp/passes/legacy_colors.py +0 -0
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/input_hoisting.py +0 -0
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/pipeline/__init__.py +0 -0
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/pipeline/service.py +0 -0
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne/symbol_table.py +0 -0
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne.egg-info/SOURCES.txt +0 -0
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne.egg-info/dependency_links.txt +0 -0
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne.egg-info/entry_points.txt +0 -0
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne.egg-info/requires.txt +0 -0
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/pine2pyne.egg-info/top_level.txt +0 -0
- {pine2pyne-0.2.5 → pine2pyne-0.2.6}/setup.cfg +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pine2pyne
|
|
3
|
-
Version: 0.2.
|
|
4
|
-
Summary: Pine Script v6 to PyneCore Python transpiler
|
|
5
|
-
Author:
|
|
3
|
+
Version: 0.2.6
|
|
4
|
+
Summary: Pine Script v4, v5 and v6 to PyneCore Python transpiler
|
|
5
|
+
Author: tripolskypetr
|
|
6
6
|
License-Expression: Apache-2.0
|
|
7
7
|
Project-URL: Homepage, https://github.com/pine2pyne/pine2pyne
|
|
8
8
|
Project-URL: Repository, https://github.com/pine2pyne/pine2pyne
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"""
|
|
2
|
-
pine2pyne: Pine Script v6 to PyneCore Python transpiler.
|
|
2
|
+
pine2pyne: Pine Script v4, v5 and v6 to PyneCore Python transpiler.
|
|
3
3
|
|
|
4
4
|
A tool to convert TradingView Pine Script indicators and strategies
|
|
5
5
|
to PyneCore Python code for local backtesting and analysis.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
__version__ = '0.2.
|
|
8
|
+
__version__ = '0.2.6'
|
|
9
9
|
__author__ = 'Pine2Pyne Transpiler'
|
|
10
10
|
|
|
11
11
|
from .lexer import Lexer, tokenize
|
|
@@ -48,7 +48,7 @@ def transpile(source: str, profile: 'OutputProfile | str | None' = None) -> str:
|
|
|
48
48
|
* ``'legacy'`` - the historical pine2pyne layout, kept as a reference.
|
|
49
49
|
|
|
50
50
|
Args:
|
|
51
|
-
source: Pine Script v6 source code
|
|
51
|
+
source: Pine Script v4, v5 or v6 source code
|
|
52
52
|
profile: output profile (an :class:`OutputProfile` or its name)
|
|
53
53
|
|
|
54
54
|
Returns:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
AST (Abstract Syntax Tree) node definitions for Pine Script v6.
|
|
2
|
+
AST (Abstract Syntax Tree) node definitions for Pine Script v4, v5 and v6.
|
|
3
3
|
|
|
4
4
|
All node types used by the parser to represent Pine Script syntax.
|
|
5
5
|
"""
|
|
@@ -127,9 +127,9 @@ class FuncDecl(ASTNode):
|
|
|
127
127
|
return_type: Optional[str] = None
|
|
128
128
|
is_method: bool = False
|
|
129
129
|
is_export: bool = False
|
|
130
|
-
is_overload: bool = False # One of several same-name declarations
|
|
130
|
+
is_overload: bool = False # One of several same-name declarations
|
|
131
131
|
indexed_params: set = field(default_factory=set) # Params that use [n] history access
|
|
132
|
-
value_is_bool: bool = False # Pine v6: its value is of type bool
|
|
132
|
+
value_is_bool: bool = False # Pine v6: its value is of type bool
|
|
133
133
|
|
|
134
134
|
|
|
135
135
|
@dataclass
|
|
@@ -238,7 +238,7 @@ class Identifier(Expression):
|
|
|
238
238
|
class NamedExpression(Expression):
|
|
239
239
|
"""Transformer output only: binds `name` to `value` and is that value
|
|
240
240
|
(Python `(name := value)`). A switch subject is bound this way in the
|
|
241
|
-
first case test, so it is evaluated once, where Pine evaluates it
|
|
241
|
+
first case test, so it is evaluated once, where Pine evaluates it."""
|
|
242
242
|
name: str = ""
|
|
243
243
|
value: Expression = None
|
|
244
244
|
|
|
@@ -263,7 +263,7 @@ class IfExpression(Expression):
|
|
|
263
263
|
condition: Expression = None
|
|
264
264
|
true_expr: Union[Expression, List['Statement']] = None
|
|
265
265
|
false_expr: Optional[Union[Expression, List['Statement']]] = None
|
|
266
|
-
value_is_bool: bool = False # Pine v6: its value is of type bool
|
|
266
|
+
value_is_bool: bool = False # Pine v6: its value is of type bool
|
|
267
267
|
chained: bool = False # written `else if` after the if before it (not an `if` inside an `else` block)
|
|
268
268
|
|
|
269
269
|
|
|
@@ -273,7 +273,7 @@ class SwitchExpression(Expression):
|
|
|
273
273
|
expr: Optional[Expression] = None
|
|
274
274
|
cases: List[tuple[Expression, Union[Expression, List['Statement']]]] = field(default_factory=list)
|
|
275
275
|
default: Optional[Union[Expression, List['Statement']]] = None
|
|
276
|
-
value_is_bool: bool = False # Pine v6: its value is of type bool
|
|
276
|
+
value_is_bool: bool = False # Pine v6: its value is of type bool
|
|
277
277
|
|
|
278
278
|
|
|
279
279
|
# ============================================================================
|
|
@@ -319,12 +319,12 @@ class ForLoop(Statement):
|
|
|
319
319
|
to_val: Expression = None
|
|
320
320
|
step: Optional[Expression] = None
|
|
321
321
|
body: List[Statement] = field(default_factory=list)
|
|
322
|
-
value_is_bool: bool = False # Pine v6: its value is of type bool
|
|
322
|
+
value_is_bool: bool = False # Pine v6: its value is of type bool
|
|
323
323
|
# Pine v6, an end bound that can change between passes: the name of the runtime
|
|
324
|
-
# counter (`pine_loop`) that re-reads it; empty for a fixed `pine_range`
|
|
324
|
+
# counter (`pine_loop`) that re-reads it; empty for a fixed `pine_range`
|
|
325
325
|
counter_name: str = ""
|
|
326
326
|
counter_helper: str = "" # the name `pine_loop` is imported under (an alias when the script uses the name)
|
|
327
|
-
range_helper: str = "" # the name `pine_range` is imported under for a fixed loop; empty is `pine_range`
|
|
327
|
+
range_helper: str = "" # the name `pine_range` is imported under for a fixed loop; empty is `pine_range`
|
|
328
328
|
# the value of an assignment (`x = for ...`): PyneComp writes no default step for it, even in Pine v4/v5
|
|
329
329
|
is_assigned_value: bool = False
|
|
330
330
|
|
|
@@ -335,7 +335,7 @@ class ForInLoop(Statement):
|
|
|
335
335
|
vars: List[str] = field(default_factory=list)
|
|
336
336
|
iterable: Expression = None
|
|
337
337
|
body: List[Statement] = field(default_factory=list)
|
|
338
|
-
value_is_bool: bool = False # Pine v6: its value is of type bool
|
|
338
|
+
value_is_bool: bool = False # Pine v6: its value is of type bool
|
|
339
339
|
|
|
340
340
|
|
|
341
341
|
@dataclass
|
|
@@ -343,7 +343,7 @@ class WhileLoop(Statement):
|
|
|
343
343
|
"""While loop: while condition\n body"""
|
|
344
344
|
condition: Expression = None
|
|
345
345
|
body: List[Statement] = field(default_factory=list)
|
|
346
|
-
value_is_bool: bool = False # Pine v6: its value is of type bool
|
|
346
|
+
value_is_bool: bool = False # Pine v6: its value is of type bool
|
|
347
347
|
|
|
348
348
|
|
|
349
349
|
@dataclass
|
|
@@ -357,7 +357,7 @@ class SwitchStatement(Statement):
|
|
|
357
357
|
@dataclass
|
|
358
358
|
class OnceStatement(Statement):
|
|
359
359
|
"""Pine v6 once structure: once [cond]\n body. The parser lowers it to
|
|
360
|
-
a `var` flag and an `if` (once_lowering.py
|
|
360
|
+
a `var` flag and an `if` (once_lowering.py); no later stage sees it."""
|
|
361
361
|
condition: Optional[Expression] = None
|
|
362
362
|
body: List[Statement] = field(default_factory=list)
|
|
363
363
|
|
|
@@ -5,7 +5,7 @@ A Pine AST node is a dataclass; its children sit in its fields, directly or
|
|
|
5
5
|
inside lists, tuples (the arms of an else-if, the cases of a switch) and dicts
|
|
6
6
|
(the keyword arguments of a call). A walk that forgets one of these containers
|
|
7
7
|
silently misses what is inside it: a history index in a keyword argument or in
|
|
8
|
-
an else-if arm was not seen
|
|
8
|
+
an else-if arm was not seen. Every generic walk goes through
|
|
9
9
|
`walk` (to find nodes) or `map_children` (to rebuild them), so the containers
|
|
10
10
|
are decided in one place.
|
|
11
11
|
|
|
@@ -11,7 +11,7 @@ they run when the branch is taken, and its last expression is the value:
|
|
|
11
11
|
else
|
|
12
12
|
0.0
|
|
13
13
|
|
|
14
|
-
A Python conditional expression cannot hold statements (
|
|
14
|
+
A Python conditional expression cannot hold statements (the branch
|
|
15
15
|
was emitted as `None`). This pre-pass rewrites every such value into the idiom
|
|
16
16
|
Pine authors write by hand, which the transformer already emits correctly:
|
|
17
17
|
|
|
@@ -25,7 +25,7 @@ Pine authors write by hand, which the transformer already emits correctly:
|
|
|
25
25
|
A block-valued switch becomes an if / else-if chain over a temporary that holds
|
|
26
26
|
the subject, so the subject is evaluated once. `var`/`varip` targets run the
|
|
27
27
|
lowered statements once, behind a flag. A branch that is a single binding
|
|
28
|
-
(`k := close`) is a block too: its binding runs, and its value is k
|
|
28
|
+
(`k := close`) is a block too: its binding runs, and its value is k.
|
|
29
29
|
|
|
30
30
|
A loop used as a value (`s = for …`, `s = while …`, a branch or a function tail
|
|
31
31
|
that ends in a loop) yields what its body's tail gave on the last iteration
|
|
@@ -40,7 +40,7 @@ written to a fresh local, so the body still reads the target's old value:
|
|
|
40
40
|
|
|
41
41
|
An arm that does not run (an if without else) yields false for a bool value
|
|
42
42
|
and na otherwise; a tuple-returning one yields a tuple of na as wide as its
|
|
43
|
-
tuples
|
|
43
|
+
tuples. `s op= <block>` reads s, then runs the block into a
|
|
44
44
|
fresh local (premise P1: Pine's operand order is UNVERIFIED on TradingView).
|
|
45
45
|
`[a, b] = <if/switch/loop>` fills a fresh local that starts as a tuple of na,
|
|
46
46
|
destructured once. A block value anywhere else (an operand of an operator, a
|
|
@@ -80,7 +80,7 @@ Write = Callable[[Expression], object] # the statement that takes a value
|
|
|
80
80
|
def lower_block_values(script: Script) -> Script:
|
|
81
81
|
"""Return a copy of `script` with every block value lowered to statements.
|
|
82
82
|
A script without one is returned unchanged. An untaken bool arm is false
|
|
83
|
-
only where `stamp_bool_values` stamped its value first
|
|
83
|
+
only where `stamp_bool_values` stamped its value first."""
|
|
84
84
|
if not _needs_lowering_somewhere(script):
|
|
85
85
|
return script
|
|
86
86
|
return _BlockValueLowering(collect_names(script), _UserFunctions.of(script),
|
|
@@ -107,7 +107,7 @@ def _needs_lowering_somewhere(script: Script) -> bool:
|
|
|
107
107
|
accepts a function in a block. Other such functions are left to the
|
|
108
108
|
transformer, so they do not cost this pass a copy of the script. This runs
|
|
109
109
|
on every node of every script, hence the dispatch on the node's type
|
|
110
|
-
(
|
|
110
|
+
(transpile-time gate)."""
|
|
111
111
|
untaken_tails = []
|
|
112
112
|
for node in walk(script):
|
|
113
113
|
if type(node) in CHILDLESS_NODE_TYPES:
|
|
@@ -130,7 +130,7 @@ def _needs_lowering_somewhere(script: Script) -> bool:
|
|
|
130
130
|
def _has_loop_operand(node) -> bool:
|
|
131
131
|
"""True when a loop is a direct child of `node`: a bound value, an operand.
|
|
132
132
|
Runs on every expression of every script, so it reads the fields once and
|
|
133
|
-
builds no lists (
|
|
133
|
+
builds no lists (transpile-time gate)."""
|
|
134
134
|
for name in child_field_names(type(node)):
|
|
135
135
|
child = getattr(node, name)
|
|
136
136
|
if isinstance(child, LOOPS) or (type(child) is list and any(isinstance(item, LOOPS) for item in child)):
|
|
@@ -154,7 +154,7 @@ def has_block_branch(expression) -> bool:
|
|
|
154
154
|
tail, is a block: several statements, a sole binding, or a tail statement
|
|
155
155
|
with no value. A default-only switch with a subject counts too: its
|
|
156
156
|
subject runs as a statement before the default, so its side effect is
|
|
157
|
-
kept
|
|
157
|
+
kept."""
|
|
158
158
|
if not isinstance(expression, VALUE_EXPRESSIONS):
|
|
159
159
|
return False
|
|
160
160
|
if isinstance(expression, SwitchExpression) and not expression.cases and expression.expr is not None:
|
|
@@ -164,7 +164,7 @@ def has_block_branch(expression) -> bool:
|
|
|
164
164
|
return True # an inline switch case `0 => for …` keeps its loop bare
|
|
165
165
|
if isinstance(branch, list) and branch and (len(branch) > 1
|
|
166
166
|
or isinstance(branch[-1], VALUELESS_TAILS + BINDINGS)):
|
|
167
|
-
# a sole binding `k := close` is a block: the binding must run
|
|
167
|
+
# a sole binding `k := close` is a block: the binding must run
|
|
168
168
|
return True
|
|
169
169
|
tail = _tail_expression(branch)
|
|
170
170
|
if has_block_branch(tail):
|
|
@@ -262,8 +262,8 @@ def _is_bool_value(value) -> bool:
|
|
|
262
262
|
def yields_bool(value) -> bool:
|
|
263
263
|
"""True when an arm of `value` yields a bool by its syntax. Only the slot of
|
|
264
264
|
a loop that runs no iteration reads it: Pine's guide gives false to an
|
|
265
|
-
untaken if/switch arm, which the bool typing decides
|
|
266
|
-
nothing of a loop, so that slot stays as
|
|
265
|
+
untaken if/switch arm, which the bool typing decides, and says
|
|
266
|
+
nothing of a loop, so that slot stays as it is (UNVERIFIED)."""
|
|
267
267
|
return any(_is_bool_value(tail) for tail in _value_tails(value))
|
|
268
268
|
|
|
269
269
|
|
|
@@ -280,8 +280,8 @@ THREE_STATE_BOOL_VERSIONS = ('4', '5')
|
|
|
280
280
|
|
|
281
281
|
def keeps_three_state_bool(script: Script) -> bool:
|
|
282
282
|
"""True for a Pine v4 or v5 script: its bool may be na (`bool b = na`, a bool history before
|
|
283
|
-
warm-up), so its decorator sets the runtime's `na_bool` switch (
|
|
284
|
-
TradingView 2026-10-06
|
|
283
|
+
warm-up), so its decorator sets the runtime's `na_bool` switch (MEASURED on
|
|
284
|
+
TradingView 2026-10-06). A script with no version annotation is v6, as in `bool_is_never_na`."""
|
|
285
285
|
return bool(script.version) and script.version.version in THREE_STATE_BOOL_VERSIONS
|
|
286
286
|
|
|
287
287
|
|
|
@@ -391,7 +391,7 @@ class _BlockValueLowering:
|
|
|
391
391
|
|
|
392
392
|
def untaken_of(self, value, value_is_bool: bool) -> _Untaken:
|
|
393
393
|
"""`value_is_bool` is the stamp of the if/switch/loop, or of the
|
|
394
|
-
function, whose value `value` is (bool_value_typing
|
|
394
|
+
function, whose value `value` is (bool_value_typing)."""
|
|
395
395
|
return _Untaken(tuple_width=_tuple_width(value, self.functions), is_bool=value_is_bool,
|
|
396
396
|
never_run_is_bool=self.bool_is_never_na and yields_bool(value))
|
|
397
397
|
|
|
@@ -575,7 +575,7 @@ class _BlockValueLowering:
|
|
|
575
575
|
def compound_statement(self, node: Reassignment) -> list:
|
|
576
576
|
"""`s op= <block>`: s is read first, then the block runs into a fresh
|
|
577
577
|
local, then `s := <s as read> op <block value>`. Reading s first is
|
|
578
|
-
premise P1, UNVERIFIED on TradingView
|
|
578
|
+
premise P1, UNVERIFIED on TradingView."""
|
|
579
579
|
operation = node.value
|
|
580
580
|
left_operand = fresh_name('left_operand', self.used_names)
|
|
581
581
|
block_value = fresh_name('block_value', self.used_names)
|
|
@@ -590,7 +590,7 @@ class _BlockValueLowering:
|
|
|
590
590
|
def tuple_statement(self, node: Assignment) -> list:
|
|
591
591
|
"""`[a, b] = <if/switch/loop>`: the value goes to a fresh local that
|
|
592
592
|
starts as a tuple of na as wide as the target, as does an arm that
|
|
593
|
-
does not run; the target destructures it once
|
|
593
|
+
does not run; the target destructures it once."""
|
|
594
594
|
names = list(node.target.names)
|
|
595
595
|
untaken = _Untaken(tuple_width=len(names))
|
|
596
596
|
slot = fresh_name('tuple_value', self.used_names)
|
|
@@ -630,7 +630,7 @@ class _BlockValueLowering:
|
|
|
630
630
|
line=self.line)])
|
|
631
631
|
if node.body and self.tail_is_lowered(node):
|
|
632
632
|
# every arm of the tail ends in the expression it yields, which the
|
|
633
|
-
# transformer's tail walk returns
|
|
633
|
+
# transformer's tail walk returns
|
|
634
634
|
*leading, tail = node.body
|
|
635
635
|
lowered_leading = self.statements(leading)
|
|
636
636
|
enclosing_line, self.line = self.line, tail.line or self.line
|
|
@@ -645,7 +645,7 @@ class _BlockValueLowering:
|
|
|
645
645
|
def tail_is_lowered(self, node: FuncDecl) -> bool:
|
|
646
646
|
"""A tail the transformer cannot return by itself: one that reaches a
|
|
647
647
|
loop, or a tuple-returning if/switch with an arm that may not run,
|
|
648
|
-
which yields a tuple of na
|
|
648
|
+
which yields a tuple of na. A function the script
|
|
649
649
|
destructures, whose tuple width is unknown, is refused."""
|
|
650
650
|
if _tail_reaches_loop(node.body):
|
|
651
651
|
return True
|
|
@@ -4,7 +4,7 @@ each builtin series, as the runtime annotates them.
|
|
|
4
4
|
|
|
5
5
|
GENERATED by tools/collect_bool_builtins.py from opencode-pyneruntime 6.9.4; do not
|
|
6
6
|
edit by hand. In Pine v6 an untaken if/switch whose value is of type bool is
|
|
7
|
-
false, not na; bool_value_typing reads these to type a builtin
|
|
7
|
+
false, not na; bool_value_typing reads these to type a builtin.
|
|
8
8
|
"""
|
|
9
9
|
from typing import Dict, FrozenSet
|
|
10
10
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Pine v6 bool typing of the values an untaken arm can default
|
|
2
|
+
Pine v6 bool typing of the values an untaken arm can default.
|
|
3
3
|
|
|
4
4
|
In Pine v6 an if/switch whose value is of TYPE bool gives false for an arm
|
|
5
5
|
that does not run, not na (migration guide to v6). The type comes from the
|
|
@@ -67,12 +67,12 @@ def reads_na_of_bool(script: Script) -> bool:
|
|
|
67
67
|
"""Whether a named bool value is read where it may be na: with history (`b[1]`) or
|
|
68
68
|
through `na(b)`. Pine v5 marks only such a script `na_bool=True`; the history of an
|
|
69
69
|
expression (`(a > b)[1]`) or of an untyped parameter does not count. Measured with
|
|
70
|
-
|
|
70
|
+
scripts compiled by PyneComp v6.0.80; 82 of the 327 v5 scripts of the reference."""
|
|
71
71
|
typing = _BoolTyping(script)
|
|
72
72
|
for name, scope in typing.na_calls:
|
|
73
73
|
binding = scope.lookup(name)
|
|
74
74
|
if binding is not None and _is_bool_collection(binding.declared_type):
|
|
75
|
-
return True # `na(a)` of an `array<bool>`, not its history: any Pine version
|
|
75
|
+
return True # `na(a)` of an `array<bool>`, not its history: any Pine version
|
|
76
76
|
if _reads_na_of_a_bool_collection_argument(script):
|
|
77
77
|
return True
|
|
78
78
|
if _declared_version(script) != 5:
|
|
@@ -98,7 +98,7 @@ def _is_bool_collection(declared_type) -> bool:
|
|
|
98
98
|
|
|
99
99
|
def _reads_na_of_a_bool_collection_argument(script: Script) -> bool:
|
|
100
100
|
"""`is_empty(arr) => na(arr) or ...` called with an `array<bool>`: the untyped parameter is a bool
|
|
101
|
-
collection where it is called with one (
|
|
101
|
+
collection where it is called with one (PyneComp v6.0.80)."""
|
|
102
102
|
from .ast_walk import walk
|
|
103
103
|
from .ast_nodes import MethodCall, Parameter
|
|
104
104
|
nodes = list(walk(script))
|
|
@@ -133,7 +133,7 @@ def _declared_version(script: Script) -> int:
|
|
|
133
133
|
|
|
134
134
|
def has_untaken_bool_switch(script: Script) -> bool:
|
|
135
135
|
"""Whether a `switch` without a default arm gives a bool: an untaken switch is na, so a Pine v5
|
|
136
|
-
script that has one is `na_bool=True` (
|
|
136
|
+
script that has one is `na_bool=True` (PyneComp v6.0.80). A switch used as
|
|
137
137
|
a value or as a statement whose arms end with a bool (`x := true`) counts; an `if` does not."""
|
|
138
138
|
switches = [node for node in walk_nodes(script)
|
|
139
139
|
if isinstance(node, (SwitchStatement, SwitchExpression)) and node.default is None and node.cases]
|
|
@@ -254,7 +254,7 @@ class _BoolTyping:
|
|
|
254
254
|
# ------------------------------------------------------------ the walk
|
|
255
255
|
def _walk(self, script: Script) -> None:
|
|
256
256
|
"""Visit every node once, giving each the scope it is in. Every file is
|
|
257
|
-
walked, so the loop makes no call per plain node
|
|
257
|
+
walked, so the loop makes no call per plain node."""
|
|
258
258
|
pending = [(script, BlockScope(), None)]
|
|
259
259
|
fields_by_type: Dict[type, tuple] = {}
|
|
260
260
|
while pending:
|
|
@@ -15,7 +15,7 @@ def parse_args(args: Optional[List[str]] = None) -> argparse.Namespace:
|
|
|
15
15
|
"""Parse command-line arguments."""
|
|
16
16
|
parser = argparse.ArgumentParser(
|
|
17
17
|
prog='pine2pyne',
|
|
18
|
-
description='Transpile Pine Script v6 to PyneCore Python',
|
|
18
|
+
description='Transpile Pine Script v4, v5 and v6 to PyneCore Python',
|
|
19
19
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
20
20
|
epilog='''
|
|
21
21
|
Examples:
|
|
@@ -76,7 +76,7 @@ Examples:
|
|
|
76
76
|
|
|
77
77
|
def reporting_semantic_warnings(input_path: Path, action: Callable[[], object]):
|
|
78
78
|
"""Run `action`, printing each SemanticWarning it raises as
|
|
79
|
-
`⚠ <file>:<line>:<column>: <message> [<code>]
|
|
79
|
+
`⚠ <file>:<line>:<column>: <message> [<code>]`. The warning
|
|
80
80
|
filters stay the user's: a fresh catch_warnings resets Python's
|
|
81
81
|
once-per-location record, so every file reports its own warnings, and
|
|
82
82
|
`-W ignore::pine2pyne.errors.SemanticWarning` silences them. A filter that
|
|
@@ -112,8 +112,8 @@ def transpile_file(input_path: Path, output_path: Optional[Path], validate_only:
|
|
|
112
112
|
source = input_path.read_text(encoding='utf-8')
|
|
113
113
|
|
|
114
114
|
if validate_only:
|
|
115
|
-
# Only lex and parse to validate syntax, with transpile()'s repairs
|
|
116
|
-
# and refuse what transpile() refuses
|
|
115
|
+
# Only lex and parse to validate syntax, with transpile()'s repairs,
|
|
116
|
+
# and refuse what transpile() refuses
|
|
117
117
|
from .di import get_transpiler
|
|
118
118
|
|
|
119
119
|
if verbose:
|
|
@@ -161,7 +161,7 @@ def transpile_file(input_path: Path, output_path: Optional[Path], validate_only:
|
|
|
161
161
|
|
|
162
162
|
except Pine2PyneError as e:
|
|
163
163
|
print(f'✗ {input_path}: {e}', file=sys.stderr)
|
|
164
|
-
# A repaired source that still fails: the first error above, then where it stopped
|
|
164
|
+
# A repaired source that still fails: the first error above, then where it stopped
|
|
165
165
|
cause = e.__cause__
|
|
166
166
|
while isinstance(cause, Pine2PyneError):
|
|
167
167
|
print(f' after repairing it as valid Pine, the source stops at: {cause}', file=sys.stderr)
|
|
@@ -37,11 +37,11 @@ class CodeGenerator:
|
|
|
37
37
|
self.output: List[str] = []
|
|
38
38
|
# The Pine line of the top-level statement being emitted. The parser
|
|
39
39
|
# stamps only top-level statements, so a refusal of a nested node
|
|
40
|
-
# reports this line
|
|
40
|
+
# reports this line
|
|
41
41
|
self.top_level_line = 0
|
|
42
|
-
# Pine v5 and earlier evaluate both operands of and/or
|
|
42
|
+
# Pine v5 and earlier evaluate both operands of and/or
|
|
43
43
|
self.strict_boolean_operators = False
|
|
44
|
-
# Pine v6 gives a UDT bool field without a default false; before v6 it is na
|
|
44
|
+
# Pine v6 gives a UDT bool field without a default false; before v6 it is na
|
|
45
45
|
self.bool_is_never_na = True
|
|
46
46
|
self.user_function_names: frozenset = frozenset()
|
|
47
47
|
# Output line index -> the Pine line its statement came from. A layout
|
|
@@ -119,7 +119,7 @@ class CodeGenerator:
|
|
|
119
119
|
if script.imports or uses_any:
|
|
120
120
|
self.emit('')
|
|
121
121
|
|
|
122
|
-
# 3-5. Module prelude in three phases
|
|
122
|
+
# 3-5. Module prelude in three phases: enum classes, then the module
|
|
123
123
|
# constants (they may read an enum member), then the UDT classes (a field default may
|
|
124
124
|
# read an enum member, and cannot read a constant)
|
|
125
125
|
# Enum declarations
|
|
@@ -145,7 +145,7 @@ class CodeGenerator:
|
|
|
145
145
|
self.generate_type_declaration(type_decl)
|
|
146
146
|
self.emit('')
|
|
147
147
|
|
|
148
|
-
# A library's export surface: one Exported proxy per exported name, published in __all__
|
|
148
|
+
# A library's export surface: one Exported proxy per exported name, published in __all__.
|
|
149
149
|
# pynecore's `@export` resolves the proxy by name in this module's globals
|
|
150
150
|
exported_names = list(dict.fromkeys(
|
|
151
151
|
statement.name for statement in script.main_body if isinstance(statement, FuncDecl) and statement.is_export))
|
|
@@ -237,7 +237,7 @@ class CodeGenerator:
|
|
|
237
237
|
prev_source_line = source_line
|
|
238
238
|
|
|
239
239
|
# A script with no executable statement (a type-only or comment-only
|
|
240
|
-
# Pine script) still needs a statement under `def main():`
|
|
240
|
+
# Pine script) still needs a statement under `def main():`
|
|
241
241
|
if len(self.output) == main_body_start:
|
|
242
242
|
self.emit('pass')
|
|
243
243
|
|
|
@@ -271,7 +271,7 @@ class CodeGenerator:
|
|
|
271
271
|
def generate_decorator(self, decl: Union[IndicatorDecl, StrategyDecl, LibraryDecl]) -> str:
|
|
272
272
|
"""Generate @script.indicator, @script.strategy or @script.library decorator."""
|
|
273
273
|
name = {IndicatorDecl: 'indicator', LibraryDecl: 'library'}.get(type(decl), 'strategy')
|
|
274
|
-
# A None title is an expression, emitted as the 'title' keyword
|
|
274
|
+
# A None title is an expression, emitted as the 'title' keyword
|
|
275
275
|
arguments = [] if decl.title is None else [f"'{self._escape_string(decl.title, chr(39))}'"]
|
|
276
276
|
arguments += [f'{key}={self.generate_decorator_value(value)}' for key, value in decl.kwargs.items()]
|
|
277
277
|
return f"@script.{name}({', '.join(arguments)})"
|
|
@@ -333,7 +333,7 @@ class CodeGenerator:
|
|
|
333
333
|
self.indent_level += 1
|
|
334
334
|
|
|
335
335
|
# Generate fields: a series built-in default is read per instance, a
|
|
336
|
-
# field without a default is na (
|
|
336
|
+
# field without a default is na (udt_field_defaults)
|
|
337
337
|
lines = list(getattr(type_decl, 'field_lines', ()) or ())
|
|
338
338
|
for index, (field_name, field_type, default_value) in enumerate(type_decl.fields):
|
|
339
339
|
if getattr(self, '_keeps_field_gaps', False) and 0 < index < len(lines) and lines[index] - lines[index - 1] > 1:
|
|
@@ -388,10 +388,10 @@ class CodeGenerator:
|
|
|
388
388
|
# Emit @method decorator if this is a method
|
|
389
389
|
if hasattr(func, 'is_method') and func.is_method:
|
|
390
390
|
self.emit('@method')
|
|
391
|
-
# A library's exported function
|
|
391
|
+
# A library's exported function; pynecore's @export goes above @overload
|
|
392
392
|
if func.is_export:
|
|
393
393
|
self.emit('@export')
|
|
394
|
-
# Same-name declarations share one pynecore @overload dispatcher
|
|
394
|
+
# Same-name declarations share one pynecore @overload dispatcher
|
|
395
395
|
if func.is_overload:
|
|
396
396
|
self.emit('@overload')
|
|
397
397
|
|
|
@@ -480,7 +480,7 @@ class CodeGenerator:
|
|
|
480
480
|
self.emit(line)
|
|
481
481
|
else:
|
|
482
482
|
# Refuse rather than drop the statement: a comment in its place
|
|
483
|
-
# changes what the program computes, or leaves a block empty
|
|
483
|
+
# changes what the program computes, or leaves a block empty
|
|
484
484
|
raise CodeGenError(f'Unsupported statement type: {type(stmt).__name__}',
|
|
485
485
|
line=self.refusal_line(stmt))
|
|
486
486
|
|
|
@@ -559,7 +559,7 @@ class CodeGenerator:
|
|
|
559
559
|
|
|
560
560
|
def generate_for_loop(self, stmt: ForLoop) -> None:
|
|
561
561
|
"""Generate for loop with pine_range(), or with pine_loop() when the end
|
|
562
|
-
bound is re-read on every pass (Pine v6
|
|
562
|
+
bound is re-read on every pass (Pine v6)."""
|
|
563
563
|
# Convert Pine 'for i = from to to_val [by step]' to Python pine_range()
|
|
564
564
|
from_val = self.generate_expression(stmt.from_val)
|
|
565
565
|
to_val = self.generate_expression(stmt.to_val)
|
|
@@ -596,7 +596,7 @@ class CodeGenerator:
|
|
|
596
596
|
iterable = self.generate_expression(stmt.iterable)
|
|
597
597
|
|
|
598
598
|
# `for [k, v] in x` reaches here with its pairs already chosen by the
|
|
599
|
-
# transformer: enumerate(), a map's keys, or a run-time choice
|
|
599
|
+
# transformer: enumerate(), a map's keys, or a run-time choice
|
|
600
600
|
self.emit(f'for {vars_str} in {iterable}:')
|
|
601
601
|
|
|
602
602
|
self.indent_level += 1
|
|
@@ -652,7 +652,7 @@ class CodeGenerator:
|
|
|
652
652
|
elif isinstance(expr, NaLiteral):
|
|
653
653
|
return 'na'
|
|
654
654
|
else:
|
|
655
|
-
# A comment in expression position is not Python; refuse
|
|
655
|
+
# A comment in expression position is not Python; refuse
|
|
656
656
|
raise CodeGenError(f'Unsupported expression type: {type(expr).__name__}',
|
|
657
657
|
line=self.refusal_line(expr))
|
|
658
658
|
|
|
@@ -694,7 +694,7 @@ class CodeGenerator:
|
|
|
694
694
|
return f'{left} {expr.op} {right}'
|
|
695
695
|
|
|
696
696
|
def _generate_strict_boolean(self, operator: str, operands: List[Expression]) -> str:
|
|
697
|
-
"""`all((a, b))` / `any((a, b))`: the tuple evaluates every operand, in order
|
|
697
|
+
"""`all((a, b))` / `any((a, b))`: the tuple evaluates every operand, in order."""
|
|
698
698
|
function = 'all' if operator == 'and' else 'any'
|
|
699
699
|
return f'{function}(({", ".join(self.generate_expression(operand) for operand in operands)}))'
|
|
700
700
|
|
|
@@ -705,7 +705,7 @@ class CodeGenerator:
|
|
|
705
705
|
The parser keeps no parenthesis node, so grouping exists only in the
|
|
706
706
|
tree shape and every operator-precedence parenthesis between binary
|
|
707
707
|
and unary operators is re-derived here (index and member access on a
|
|
708
|
-
grouped object are not
|
|
708
|
+
grouped object are not).
|
|
709
709
|
"""
|
|
710
710
|
if isinstance(operand, UnaryOp):
|
|
711
711
|
# Pine `not` binds tightest; Python `not` binds looser than any
|
|
@@ -793,7 +793,7 @@ class CodeGenerator:
|
|
|
793
793
|
|
|
794
794
|
def generate_method_call(self, call: MethodCall) -> str:
|
|
795
795
|
"""Generate method call. The transformer routes every builtin method to its
|
|
796
|
-
namespace
|
|
796
|
+
namespace, so what reaches here is a Python method call."""
|
|
797
797
|
from .ast_nodes import BinaryOp, UnaryOp, TernaryOp, IfExpression
|
|
798
798
|
|
|
799
799
|
obj = self.generate_expression(call.object)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""Which values PyneComp reads as numbers where Pine wants a condition.
|
|
2
2
|
|
|
3
3
|
A number in a condition is cast: ``if x`` is ``if cast_bool(x):``. PyneComp
|
|
4
|
-
decides it from what it knows of the value's type;
|
|
5
|
-
PyneComp v6.0.80
|
|
4
|
+
decides it from what it knows of the value's type; scripts compiled by
|
|
5
|
+
PyneComp v6.0.80 and its reference output show what that is.
|
|
6
6
|
|
|
7
7
|
A *name* (or its history, or a literal) is typed from its declaration, at any
|
|
8
8
|
distance: a number literal, a built-in price or time series (``close``,
|
|
@@ -51,7 +51,7 @@ COMPARISONS = frozenset({'==', '!=', '<', '>', '<=', '>=', 'and', 'or'})
|
|
|
51
51
|
_DEPTH = 32
|
|
52
52
|
#: a name declared without a type PyneComp reads: a parameter or a tuple element
|
|
53
53
|
_UNTYPED = object()
|
|
54
|
-
#: a parameter declared without a type: beside it, an `and`/`or` operand is not cast
|
|
54
|
+
#: a parameter declared without a type: beside it, an `and`/`or` operand is not cast
|
|
55
55
|
_UNTYPED_PARAMETER = object()
|
|
56
56
|
|
|
57
57
|
|
|
@@ -81,7 +81,7 @@ class ConditionTyping:
|
|
|
81
81
|
functions = [node for node in walk(script) if isinstance(node, FuncDecl)]
|
|
82
82
|
inside = set()
|
|
83
83
|
for function in functions:
|
|
84
|
-
# a typed parameter is its type (`b = vx * 2` with `float vx` is a number
|
|
84
|
+
# a typed parameter is its type (`b = vx * 2` with `float vx` is a number)
|
|
85
85
|
values: Dict[str, object] = {parameter.name: ((parameter.type_hint, NaLiteral())
|
|
86
86
|
if parameter.type_hint in ('int', 'float', 'bool')
|
|
87
87
|
else _UNTYPED_PARAMETER if not parameter.type_hint
|
|
@@ -103,7 +103,7 @@ class ConditionTyping:
|
|
|
103
103
|
if isinstance(node, Assignment) and isinstance(node.target, str):
|
|
104
104
|
values.setdefault(node.target, (node.type_hint, node.value))
|
|
105
105
|
elif isinstance(node, Assignment) and hasattr(node.target, 'names'):
|
|
106
|
-
for name in node.target.names: # `[a, b] = f()`: untyped
|
|
106
|
+
for name in node.target.names: # `[a, b] = f()`: untyped
|
|
107
107
|
values.setdefault(name, _UNTYPED)
|
|
108
108
|
elif isinstance(node, (VarDecl, VaripDecl)):
|
|
109
109
|
values.setdefault(node.name, (node.type_hint, node.value))
|
|
@@ -139,7 +139,7 @@ class ConditionTyping:
|
|
|
139
139
|
return False
|
|
140
140
|
|
|
141
141
|
def has_a_type(self, expression) -> bool:
|
|
142
|
-
"""False for a parameter, beside which PyneComp casts no `and`/`or` operand
|
|
142
|
+
"""False for a parameter, beside which PyneComp casts no `and`/`or` operand."""
|
|
143
143
|
return not (isinstance(expression, Identifier) and expression.name in self._parameters)
|
|
144
144
|
|
|
145
145
|
def is_bool(self, expression) -> bool:
|
|
@@ -241,14 +241,14 @@ class ConditionTyping:
|
|
|
241
241
|
return None # PyneComp does not type a user function's result in an if/while header
|
|
242
242
|
if self._in_function:
|
|
243
243
|
# inside a function PyneComp reads no function's result type: a call is a number when
|
|
244
|
-
# an argument is arithmetic (`f(x, -1.0)`, `f(x, 0 - 1)`), else untyped
|
|
244
|
+
# an argument is arithmetic (`f(x, -1.0)`, `f(x, 0 - 1)`), else untyped
|
|
245
245
|
arguments = list(call.args) + list(call.kwargs.values())
|
|
246
246
|
return 'number' if any(_is_arithmetic(argument) for argument in arguments) else None
|
|
247
247
|
declaration = self._functions[function]
|
|
248
248
|
body = declaration.body
|
|
249
249
|
tail = body[-1] if isinstance(body, list) and body else body
|
|
250
250
|
tail = getattr(tail, 'expr', tail)
|
|
251
|
-
# the tail reads the callee's names: its typed parameters are their type (
|
|
251
|
+
# the tail reads the callee's names: its typed parameters are their type (`v * 2`)
|
|
252
252
|
outer = self._values
|
|
253
253
|
self._values = {**self._global_values, **self._function_values.get(_scope_key(declaration), {}),
|
|
254
254
|
**{parameter.name: (parameter.type_hint, NaLiteral()) for parameter in declaration.params
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
"""A `ta.*` call that runs on only some bars
|
|
1
|
+
"""A `ta.*` call that runs on only some bars.
|
|
2
2
|
|
|
3
3
|
Pine keeps a call site's series history by running the call on every bar. A `ta.*` call
|
|
4
4
|
reached only when a condition holds keeps a history that skips the other bars, so its value
|
|
5
|
-
differs from the same call made on every bar (
|
|
5
|
+
differs from the same call made on every bar (measured: the transpiled output
|
|
6
6
|
matches Pine's call-site semantics in every construct, and a hoisted call gives another value).
|
|
7
7
|
The Pine User Manual (Execution model) says to run such functions in the global scope for
|
|
8
8
|
consistent results. The transpiler never hoists: it keeps the call where the script has it and
|
|
@@ -14,7 +14,7 @@ A call is conditional when it sits in:
|
|
|
14
14
|
- a branch of `?:`;
|
|
15
15
|
- a `switch` arm or its default, or a case after the first (the first case is always read);
|
|
16
16
|
- the right operand of `and` / `or` in Pine v6, which evaluates them lazily. Pine v5 and earlier
|
|
17
|
-
evaluate both operands
|
|
17
|
+
evaluate both operands, so the right operand there is not conditional.
|
|
18
18
|
|
|
19
19
|
A function body starts unconditional again: whether the function is called conditionally is not
|
|
20
20
|
decided here. A loop body is not conditional. `ta.vwap` and the other argument-free `ta.`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Where PyneComp leaves blank lines inside a function body.
|
|
2
2
|
|
|
3
|
-
Measured on
|
|
3
|
+
Measured on PyneComp's output: for a Pine v4/v5 script every blank line in
|
|
4
4
|
``main`` follows a nested ``def``/``class`` (619 of 622 modules); a Pine v6
|
|
5
5
|
script keeps the blank lines of its source (291 of 293 modules have some).
|
|
6
6
|
|
|
@@ -9,7 +9,7 @@ a comment. The statements PyneComp moves out of ``main`` to the module (the
|
|
|
9
9
|
constants the inputs read) keep their marks: between two statements of
|
|
10
10
|
``main``, ``n`` marks (the moved statements' and the second statement's own)
|
|
11
11
|
print ``(2n + 1) // 3`` blank lines. Inputs, which become ``main``'s
|
|
12
|
-
parameters, leave no mark. Measured with
|
|
12
|
+
parameters, leave no mark. Measured with scripts compiled by PyneComp
|
|
13
13
|
v6.0.80 (``n`` = 1..6) and checked on the 71 such gaps of the reference.
|
|
14
14
|
"""
|
|
15
15
|
from __future__ import annotations
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""The import block, laid out the way PyneComp lays it out.
|
|
2
2
|
|
|
3
|
-
Measured on
|
|
3
|
+
Measured on 1005 modules compiled by PyneComp:
|
|
4
4
|
|
|
5
5
|
* ``from`` imports come first, one per module, modules in alphabetical order;
|
|
6
6
|
plain ``import`` statements (library imports) follow, sorted;
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* A series or persistent annotation keeps its element type (``Series[float]``)
|
|
10
10
|
only then; otherwise it is bare (``Series``).
|
|
11
11
|
|
|
12
|
-
Measured on
|
|
12
|
+
Measured on PyneComp's output: 2626 ``str``, 1254 ``int``, 1048 ``float``, 1146
|
|
13
13
|
``bool``, 941 ``Color`` declarations; ``Series`` 2108 bare against 859 typed.
|
|
14
14
|
"""
|
|
15
15
|
from __future__ import annotations
|
|
@@ -62,7 +62,7 @@ def inferable_type(value: ast.expr | None, classes: frozenset = frozenset(),
|
|
|
62
62
|
methods: frozenset = frozenset()) -> str | None:
|
|
63
63
|
"""The type PyneComp reads off a value without inference, or None. ``classes``:
|
|
64
64
|
the module's UDT classes, whose constructor call names its type; ``methods``: the user
|
|
65
|
-
methods, whose call on a constructor (`T.new().m()`) PyneComp types as `T
|
|
65
|
+
methods, whose call on a constructor (`T.new().m()`) PyneComp types as `T`."""
|
|
66
66
|
found = literal_type(value)
|
|
67
67
|
if found:
|
|
68
68
|
return found
|
|
@@ -129,7 +129,7 @@ class DeclaredTypes:
|
|
|
129
129
|
|
|
130
130
|
@staticmethod
|
|
131
131
|
def _generic_constructor_type(value) -> str | None:
|
|
132
|
-
"""`map.new<string, bool>()` names its type, `map<string, bool
|
|
132
|
+
"""`map.new<string, bool>()` names its type, `map<string, bool>`; `T.copy(x)` names `T`
|
|
133
133
|
(Trendoscope/Zigzag `lPivot: Pivot = udt_copy(...)`)."""
|
|
134
134
|
from ....ast_nodes import FunctionCall
|
|
135
135
|
if isinstance(value, FunctionCall) and isinstance(value.func, str) and value.func.startswith('map.new<'):
|
|
@@ -232,7 +232,7 @@ class DeclarationAnnotationPass:
|
|
|
232
232
|
self._classes = frozenset(node.name for node in tree.body if isinstance(node, ast.ClassDef))
|
|
233
233
|
self._methods = frozenset(node.name for node in ast.walk(tree) if isinstance(node, ast.FunctionDef)
|
|
234
234
|
and any(isinstance(d, ast.Name) and d.id == 'method' for d in node.decorator_list))
|
|
235
|
-
# each UDT's fields and their annotations: a field read is typed by its field
|
|
235
|
+
# each UDT's fields and their annotations: a field read is typed by its field
|
|
236
236
|
self._fields = {node.name: {field.target.id: _unquoted(field.annotation) for field in node.body
|
|
237
237
|
if isinstance(field, ast.AnnAssign) and isinstance(field.target, ast.Name)}
|
|
238
238
|
for node in tree.body if isinstance(node, ast.ClassDef)}
|