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
pine2pyne/__init__.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pine2pyne: Pine Script v6 to PyneCore Python transpiler.
|
|
3
|
+
|
|
4
|
+
A tool to convert TradingView Pine Script indicators and strategies
|
|
5
|
+
to PyneCore Python code for local backtesting and analysis.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = '0.2.4'
|
|
9
|
+
__author__ = 'Pine2Pyne Transpiler'
|
|
10
|
+
|
|
11
|
+
from .lexer import Lexer, tokenize
|
|
12
|
+
from .parser import Parser, parse
|
|
13
|
+
from .parse_repair import parse_source
|
|
14
|
+
from .legacy_function_names import qualify_legacy_function_names
|
|
15
|
+
from .pine_semantics import enforce as enforce_pine_semantics
|
|
16
|
+
from .transformer import Transformer, PyneTransformedScript
|
|
17
|
+
from .codegen import CodeGenerator, generate_code
|
|
18
|
+
from .errors import (
|
|
19
|
+
Pine2PyneError,
|
|
20
|
+
LexerError,
|
|
21
|
+
ParserError,
|
|
22
|
+
TransformerError,
|
|
23
|
+
CodeGenError,
|
|
24
|
+
SemanticError,
|
|
25
|
+
SemanticWarning,
|
|
26
|
+
UnsupportedFeatureError,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
from .core.contracts import OutputProfile, Transpiler
|
|
31
|
+
from .pipeline.stages import CommentOnlySourceFilter
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _is_comment_only(source: str) -> bool:
|
|
35
|
+
"""Check if source contains only comments and whitespace."""
|
|
36
|
+
return CommentOnlySourceFilter().is_blank(source)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def transpile(source: str, profile: 'OutputProfile | str | None' = None) -> str:
|
|
40
|
+
"""
|
|
41
|
+
Transpile Pine Script source code to PyneCore Python.
|
|
42
|
+
|
|
43
|
+
The pipeline (parse -> normalize -> semantic guard -> transform -> emit) is
|
|
44
|
+
assembled by the dishka container in :mod:`pine2pyne.di`; ``profile`` picks
|
|
45
|
+
the output layout:
|
|
46
|
+
|
|
47
|
+
* ``'pynecomp'`` (default) - byte-compatible with the closed PyneComp v6.0.79 compiler;
|
|
48
|
+
* ``'legacy'`` - the historical pine2pyne layout, kept as a reference.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
source: Pine Script v6 source code
|
|
52
|
+
profile: output profile (an :class:`OutputProfile` or its name)
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
PyneCore-compatible Python source code (empty string if source is comment-only)
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
Pine2PyneError: If any stage of transpilation fails
|
|
59
|
+
"""
|
|
60
|
+
from .di import get_transpiler
|
|
61
|
+
return get_transpiler(profile).transpile(source)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
__all__ = [
|
|
65
|
+
'transpile',
|
|
66
|
+
'OutputProfile',
|
|
67
|
+
'Transpiler',
|
|
68
|
+
'Lexer',
|
|
69
|
+
'Parser',
|
|
70
|
+
'Transformer',
|
|
71
|
+
'CodeGenerator',
|
|
72
|
+
'tokenize',
|
|
73
|
+
'parse',
|
|
74
|
+
'generate_code',
|
|
75
|
+
'Pine2PyneError',
|
|
76
|
+
'LexerError',
|
|
77
|
+
'ParserError',
|
|
78
|
+
'TransformerError',
|
|
79
|
+
'CodeGenError',
|
|
80
|
+
'SemanticError',
|
|
81
|
+
'SemanticWarning',
|
|
82
|
+
'UnsupportedFeatureError',
|
|
83
|
+
'PyneTransformedScript',
|
|
84
|
+
]
|
pine2pyne/__main__.py
ADDED
pine2pyne/ast_nodes.py
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AST (Abstract Syntax Tree) node definitions for Pine Script v6.
|
|
3
|
+
|
|
4
|
+
All node types used by the parser to represent Pine Script syntax.
|
|
5
|
+
"""
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import Any, List, Optional, Union
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Base class for all AST nodes
|
|
11
|
+
@dataclass
|
|
12
|
+
class ASTNode:
|
|
13
|
+
"""Base class for all AST nodes."""
|
|
14
|
+
line: int = 0
|
|
15
|
+
column: int = 0
|
|
16
|
+
# the Pine line a statement built by a lowering is laid out at, where `line` stays unset
|
|
17
|
+
source_line: int = field(default=0, compare=False, repr=False)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# ============================================================================
|
|
21
|
+
# Top-level nodes
|
|
22
|
+
# ============================================================================
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class Script(ASTNode):
|
|
26
|
+
"""Root node representing the entire Pine Script."""
|
|
27
|
+
version: Optional['VersionAnnotation'] = None
|
|
28
|
+
script_decl: Optional[Union['IndicatorDecl', 'StrategyDecl', 'LibraryDecl']] = None
|
|
29
|
+
imports: List['ImportDecl'] = field(default_factory=list)
|
|
30
|
+
declarations: List[Union['VarDecl', 'VaripDecl', 'TypeDecl', 'EnumDecl', 'FuncDecl', 'InputDecl']] = field(default_factory=list)
|
|
31
|
+
body: List['Statement'] = field(default_factory=list)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class VersionAnnotation(ASTNode):
|
|
36
|
+
"""//@version=6 annotation."""
|
|
37
|
+
version: str = "" # "6" or "5"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class IndicatorDecl(ASTNode):
|
|
42
|
+
"""indicator("Title", ...) declaration."""
|
|
43
|
+
title: Optional[str] = "" # None: the title is an expression, kept in kwargs['title']
|
|
44
|
+
kwargs: dict = field(default_factory=dict)
|
|
45
|
+
# How the source wrote the call: how many arguments were positional, and the
|
|
46
|
+
# title's quotes. Codegen ignores it; a layout printer may reproduce it
|
|
47
|
+
positional_count: int = 0
|
|
48
|
+
title_double_quoted: bool = False
|
|
49
|
+
keyword_order: tuple = () # the keyword arguments' names as the source orders them
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class StrategyDecl(ASTNode):
|
|
54
|
+
"""strategy("Title", ...) declaration."""
|
|
55
|
+
title: Optional[str] = "" # None: the title is an expression, kept in kwargs['title']
|
|
56
|
+
kwargs: dict = field(default_factory=dict)
|
|
57
|
+
# How the source wrote the call: how many arguments were positional, and the
|
|
58
|
+
# title's quotes. Codegen ignores it; a layout printer may reproduce it
|
|
59
|
+
positional_count: int = 0
|
|
60
|
+
title_double_quoted: bool = False
|
|
61
|
+
keyword_order: tuple = () # the keyword arguments' names as the source orders them
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass
|
|
65
|
+
class LibraryDecl(ASTNode):
|
|
66
|
+
"""library("Title", ...) declaration."""
|
|
67
|
+
title: Optional[str] = "" # None: the title is an expression, kept in kwargs['title']
|
|
68
|
+
kwargs: dict = field(default_factory=dict)
|
|
69
|
+
# How the source wrote the call: how many arguments were positional, and the
|
|
70
|
+
# title's quotes. Codegen ignores it; a layout printer may reproduce it
|
|
71
|
+
positional_count: int = 0
|
|
72
|
+
title_double_quoted: bool = False
|
|
73
|
+
keyword_order: tuple = () # the keyword arguments' names as the source orders them
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass
|
|
77
|
+
class ImportDecl(ASTNode):
|
|
78
|
+
"""import user/library/version as alias"""
|
|
79
|
+
user: str = ""
|
|
80
|
+
library: str = ""
|
|
81
|
+
version: Optional[str] = None
|
|
82
|
+
alias: Optional[str] = None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# ============================================================================
|
|
86
|
+
# Declarations
|
|
87
|
+
# ============================================================================
|
|
88
|
+
|
|
89
|
+
@dataclass
|
|
90
|
+
class VarDecl(ASTNode):
|
|
91
|
+
"""var [type] name = value"""
|
|
92
|
+
name: str = ""
|
|
93
|
+
value: 'Expression' = None
|
|
94
|
+
type_hint: Optional[str] = None
|
|
95
|
+
is_var: bool = True # True for 'var', False for 'varip'
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@dataclass
|
|
99
|
+
class VaripDecl(ASTNode):
|
|
100
|
+
"""varip [type] name = value"""
|
|
101
|
+
name: str = ""
|
|
102
|
+
value: 'Expression' = None
|
|
103
|
+
type_hint: Optional[str] = None
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclass
|
|
107
|
+
class TypeDecl(ASTNode):
|
|
108
|
+
"""type MyType ... (User-defined type)"""
|
|
109
|
+
name: str = ""
|
|
110
|
+
fields: List[tuple] = field(default_factory=list) # [(field_name, type_hint, default_value), ...] or [(field_name, type_hint), ...]
|
|
111
|
+
field_lines: List[int] = field(default_factory=list) # each field's Pine line
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@dataclass
|
|
115
|
+
class EnumDecl(ASTNode):
|
|
116
|
+
"""enum MyEnum ... (Enum definition)"""
|
|
117
|
+
name: str = ""
|
|
118
|
+
members: List = field(default_factory=list) # List[str] or List[tuple[str, Expression]]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@dataclass
|
|
122
|
+
class FuncDecl(ASTNode):
|
|
123
|
+
"""Function declaration: name(params) => body"""
|
|
124
|
+
name: str = ""
|
|
125
|
+
params: List['Parameter'] = field(default_factory=list)
|
|
126
|
+
body: Union['Expression', List['Statement']] = None
|
|
127
|
+
return_type: Optional[str] = None
|
|
128
|
+
is_method: bool = False
|
|
129
|
+
is_export: bool = False
|
|
130
|
+
is_overload: bool = False # One of several same-name declarations (card #39)
|
|
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 (card #56)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@dataclass
|
|
136
|
+
class Parameter(ASTNode):
|
|
137
|
+
"""Function parameter: [type] name [= default]"""
|
|
138
|
+
name: str = ""
|
|
139
|
+
type_hint: Optional[str] = None
|
|
140
|
+
default: Optional['Expression'] = None
|
|
141
|
+
is_series: bool = False # declared `series T`: PyneComp annotates it Series[T]
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@dataclass
|
|
145
|
+
class InputDecl(ASTNode):
|
|
146
|
+
"""Input declaration: name = input.*(...)"""
|
|
147
|
+
name: str = ""
|
|
148
|
+
func: str = "" # e.g., "input.int"
|
|
149
|
+
args: List['Expression'] = field(default_factory=list)
|
|
150
|
+
kwargs: dict = field(default_factory=dict)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# ============================================================================
|
|
154
|
+
# Expressions
|
|
155
|
+
# ============================================================================
|
|
156
|
+
|
|
157
|
+
@dataclass
|
|
158
|
+
class Expression(ASTNode):
|
|
159
|
+
"""Base class for all expressions."""
|
|
160
|
+
pass
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@dataclass
|
|
164
|
+
class BinaryOp(Expression):
|
|
165
|
+
"""Binary operation: left op right"""
|
|
166
|
+
left: Expression = None
|
|
167
|
+
op: str = ""
|
|
168
|
+
right: Expression = None
|
|
169
|
+
compound: bool = False # written `x op= y`: printed that way, `x := x op y` is not
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@dataclass
|
|
173
|
+
class UnaryOp(Expression):
|
|
174
|
+
"""Unary operation: op operand"""
|
|
175
|
+
op: str = ""
|
|
176
|
+
operand: Expression = None
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@dataclass
|
|
180
|
+
class TernaryOp(Expression):
|
|
181
|
+
"""Ternary operation: condition ? true_expr : false_expr"""
|
|
182
|
+
condition: Expression = None
|
|
183
|
+
true_expr: Expression = None
|
|
184
|
+
false_expr: Expression = None
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@dataclass
|
|
188
|
+
class FunctionCall(Expression):
|
|
189
|
+
"""Function call: func(args, kwargs)"""
|
|
190
|
+
func: Union[str, 'MemberAccess'] = "" # Can be "ta.sma" or MemberAccess node
|
|
191
|
+
args: List[Expression] = field(default_factory=list)
|
|
192
|
+
kwargs: dict = field(default_factory=dict)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@dataclass
|
|
196
|
+
class MethodCall(Expression):
|
|
197
|
+
"""Method call: object.method(args)"""
|
|
198
|
+
object: Expression = None
|
|
199
|
+
method: str = ""
|
|
200
|
+
args: List[Expression] = field(default_factory=list)
|
|
201
|
+
kwargs: dict = field(default_factory=dict)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@dataclass
|
|
205
|
+
class MemberAccess(Expression):
|
|
206
|
+
"""Member access: object.member"""
|
|
207
|
+
object: Union[str, Expression] = "" # Can be identifier string or expression
|
|
208
|
+
member: str = ""
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@dataclass
|
|
212
|
+
class IndexAccess(Expression):
|
|
213
|
+
"""Index access: object[index]"""
|
|
214
|
+
object: Expression = None
|
|
215
|
+
index: Expression = None
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@dataclass
|
|
219
|
+
class ArrayLiteral(Expression):
|
|
220
|
+
"""Array literal: [elem1, elem2, ...]"""
|
|
221
|
+
elements: List[Expression] = field(default_factory=list)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
@dataclass
|
|
225
|
+
class TupleDestructure(Expression):
|
|
226
|
+
"""Tuple destructuring: [a, b, c] = expr"""
|
|
227
|
+
names: List[str] = field(default_factory=list)
|
|
228
|
+
value: Expression = None
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@dataclass
|
|
232
|
+
class Identifier(Expression):
|
|
233
|
+
"""Identifier (variable name)"""
|
|
234
|
+
name: str = ""
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
@dataclass
|
|
238
|
+
class NamedExpression(Expression):
|
|
239
|
+
"""Transformer output only: binds `name` to `value` and is that value
|
|
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 (card #43)."""
|
|
242
|
+
name: str = ""
|
|
243
|
+
value: Expression = None
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@dataclass
|
|
247
|
+
class Literal(Expression):
|
|
248
|
+
"""Literal value (int, float, string, bool, color)"""
|
|
249
|
+
value: Any = None
|
|
250
|
+
literal_type: str = "" # 'int', 'float', 'string', 'bool', 'color'
|
|
251
|
+
is_double_quoted: bool = False # Track if original source used double quotes
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
@dataclass
|
|
255
|
+
class NaLiteral(Expression):
|
|
256
|
+
"""The 'na' literal value"""
|
|
257
|
+
pass
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
@dataclass
|
|
261
|
+
class IfExpression(Expression):
|
|
262
|
+
"""If expression (returns a value): if cond\n val1\nelse\n val2"""
|
|
263
|
+
condition: Expression = None
|
|
264
|
+
true_expr: Union[Expression, List['Statement']] = None
|
|
265
|
+
false_expr: Optional[Union[Expression, List['Statement']]] = None
|
|
266
|
+
value_is_bool: bool = False # Pine v6: its value is of type bool (card #56)
|
|
267
|
+
chained: bool = False # written `else if` after the if before it (not an `if` inside an `else` block)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
@dataclass
|
|
271
|
+
class SwitchExpression(Expression):
|
|
272
|
+
"""Switch expression (returns a value)"""
|
|
273
|
+
expr: Optional[Expression] = None
|
|
274
|
+
cases: List[tuple[Expression, Union[Expression, List['Statement']]]] = field(default_factory=list)
|
|
275
|
+
default: Optional[Union[Expression, List['Statement']]] = None
|
|
276
|
+
value_is_bool: bool = False # Pine v6: its value is of type bool (card #56)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
# ============================================================================
|
|
280
|
+
# Statements
|
|
281
|
+
# ============================================================================
|
|
282
|
+
|
|
283
|
+
@dataclass
|
|
284
|
+
class Statement(ASTNode):
|
|
285
|
+
"""Base class for all statements."""
|
|
286
|
+
pass
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
@dataclass
|
|
290
|
+
class Assignment(Statement):
|
|
291
|
+
"""Assignment statement: target = value"""
|
|
292
|
+
target: Union[str, 'TupleDestructure'] = ""
|
|
293
|
+
value: Expression = None
|
|
294
|
+
type_hint: Optional[str] = None
|
|
295
|
+
is_const: bool = False # declared `const T name = ...`: PyneComp moves a top-level one to the module
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
@dataclass
|
|
299
|
+
class Reassignment(Statement):
|
|
300
|
+
"""Reassignment statement: target := value"""
|
|
301
|
+
target: str = ""
|
|
302
|
+
value: Expression = None
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
@dataclass
|
|
306
|
+
class IfStatement(Statement):
|
|
307
|
+
"""If statement: if cond\n body\nelse if ...\nelse ..."""
|
|
308
|
+
condition: Expression = None
|
|
309
|
+
body: List[Statement] = field(default_factory=list)
|
|
310
|
+
elseifs: List[tuple[Expression, List[Statement]]] = field(default_factory=list)
|
|
311
|
+
else_body: Optional[List[Statement]] = None
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
@dataclass
|
|
315
|
+
class ForLoop(Statement):
|
|
316
|
+
"""For loop: for var = from to to_val [by step]"""
|
|
317
|
+
var: str = ""
|
|
318
|
+
from_val: Expression = None
|
|
319
|
+
to_val: Expression = None
|
|
320
|
+
step: Optional[Expression] = None
|
|
321
|
+
body: List[Statement] = field(default_factory=list)
|
|
322
|
+
value_is_bool: bool = False # Pine v6: its value is of type bool (card #56)
|
|
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` (card #82)
|
|
325
|
+
counter_name: str = ""
|
|
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` (card #94)
|
|
328
|
+
# the value of an assignment (`x = for ...`): PyneComp writes no default step for it, even in Pine v4/v5
|
|
329
|
+
is_assigned_value: bool = False
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
@dataclass
|
|
333
|
+
class ForInLoop(Statement):
|
|
334
|
+
"""For-in loop: for [vars] in iterable"""
|
|
335
|
+
vars: List[str] = field(default_factory=list)
|
|
336
|
+
iterable: Expression = None
|
|
337
|
+
body: List[Statement] = field(default_factory=list)
|
|
338
|
+
value_is_bool: bool = False # Pine v6: its value is of type bool (card #56)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
@dataclass
|
|
342
|
+
class WhileLoop(Statement):
|
|
343
|
+
"""While loop: while condition\n body"""
|
|
344
|
+
condition: Expression = None
|
|
345
|
+
body: List[Statement] = field(default_factory=list)
|
|
346
|
+
value_is_bool: bool = False # Pine v6: its value is of type bool (card #56)
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
@dataclass
|
|
350
|
+
class SwitchStatement(Statement):
|
|
351
|
+
"""Switch statement (imperative form)"""
|
|
352
|
+
expr: Optional[Expression] = None
|
|
353
|
+
cases: List[tuple[Expression, List[Statement]]] = field(default_factory=list)
|
|
354
|
+
default: Optional[List[Statement]] = None
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
@dataclass
|
|
358
|
+
class OnceStatement(Statement):
|
|
359
|
+
"""Pine v6 once structure: once [cond]\n body. The parser lowers it to
|
|
360
|
+
a `var` flag and an `if` (once_lowering.py, card #59); no later stage sees it."""
|
|
361
|
+
condition: Optional[Expression] = None
|
|
362
|
+
body: List[Statement] = field(default_factory=list)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
@dataclass
|
|
366
|
+
class BreakStatement(Statement):
|
|
367
|
+
"""Break statement"""
|
|
368
|
+
pass
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
@dataclass
|
|
372
|
+
class ContinueStatement(Statement):
|
|
373
|
+
"""Continue statement"""
|
|
374
|
+
pass
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
@dataclass
|
|
378
|
+
class ExpressionStatement(Statement):
|
|
379
|
+
"""Expression as statement (e.g., function call on its own line)"""
|
|
380
|
+
expr: Expression = None
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
@dataclass
|
|
384
|
+
class ReturnStatement(Statement):
|
|
385
|
+
"""Explicit return statement (for codegen)"""
|
|
386
|
+
expr: Optional[Expression] = None
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
@dataclass
|
|
390
|
+
class RawCode(Statement):
|
|
391
|
+
"""Raw Python code to be emitted as-is by codegen."""
|
|
392
|
+
code: str = ""
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
# ============================================================================
|
|
396
|
+
# Type annotations
|
|
397
|
+
# ============================================================================
|
|
398
|
+
|
|
399
|
+
# Forward references for type checking
|
|
400
|
+
Statement = Union[
|
|
401
|
+
Assignment, Reassignment, IfStatement, ForLoop, ForInLoop,
|
|
402
|
+
WhileLoop, SwitchStatement, BreakStatement, ContinueStatement,
|
|
403
|
+
ExpressionStatement, ReturnStatement, VarDecl, VaripDecl,
|
|
404
|
+
FuncDecl, InputDecl
|
|
405
|
+
]
|
pine2pyne/ast_walk.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AST walk: the child traversal shared by the Pine-AST pre-passes and scans.
|
|
3
|
+
|
|
4
|
+
A Pine AST node is a dataclass; its children sit in its fields, directly or
|
|
5
|
+
inside lists, tuples (the arms of an else-if, the cases of a switch) and dicts
|
|
6
|
+
(the keyword arguments of a call). A walk that forgets one of these containers
|
|
7
|
+
silently misses what is inside it: a history index in a keyword argument or in
|
|
8
|
+
an else-if arm was not seen (cards #32, #44). Every generic walk goes through
|
|
9
|
+
`walk` (to find nodes) or `map_children` (to rebuild them), so the containers
|
|
10
|
+
are decided in one place.
|
|
11
|
+
|
|
12
|
+
Every transpile walks every file, so `walk` is iterative (a deep expression
|
|
13
|
+
does not use up the recursion limit) and caches the child fields per node type.
|
|
14
|
+
"""
|
|
15
|
+
import dataclasses
|
|
16
|
+
from typing import Callable, Dict, Iterator, Tuple, Union
|
|
17
|
+
|
|
18
|
+
# The source position is not a child
|
|
19
|
+
POSITION_FIELDS = frozenset({'line', 'column'})
|
|
20
|
+
# Field values that are never a node nor hold one
|
|
21
|
+
LEAF_TYPES = frozenset({str, int, float, bool, type(None)})
|
|
22
|
+
CONTAINER_TYPES = frozenset({list, tuple})
|
|
23
|
+
|
|
24
|
+
# node type -> its child field names, last first; False for a type that is not a node
|
|
25
|
+
_CHILD_FIELDS_LAST_FIRST: Dict[type, Union[Tuple[str, ...], bool]] = {}
|
|
26
|
+
# node type -> its child field names in declaration order
|
|
27
|
+
_CHILD_FIELDS_IN_ORDER: Dict[type, Tuple[str, ...]] = {}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def child_field_names(node_type: type) -> Tuple[str, ...]:
|
|
31
|
+
"""The fields of a node type that can hold children, in declaration order."""
|
|
32
|
+
fields = _CHILD_FIELDS_IN_ORDER.get(node_type)
|
|
33
|
+
if fields is None:
|
|
34
|
+
fields = _CHILD_FIELDS_IN_ORDER[node_type] = tuple(reversed(_child_fields_last_first(node_type) or ()))
|
|
35
|
+
return fields
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _child_fields_last_first(value_type: type):
|
|
39
|
+
fields = _CHILD_FIELDS_LAST_FIRST.get(value_type)
|
|
40
|
+
if fields is None:
|
|
41
|
+
is_node_type = hasattr(value_type, '__dataclass_fields__')
|
|
42
|
+
fields = tuple(reversed([field.name for field in dataclasses.fields(value_type)
|
|
43
|
+
if field.name not in POSITION_FIELDS])) if is_node_type else False
|
|
44
|
+
_CHILD_FIELDS_LAST_FIRST[value_type] = fields
|
|
45
|
+
return fields
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def is_node(value) -> bool:
|
|
49
|
+
"""True for an AST node instance (a dataclass instance, not a class)."""
|
|
50
|
+
return _child_fields_last_first(type(value)) is not False and not isinstance(value, type)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def walk(tree) -> Iterator:
|
|
54
|
+
"""Every node in `tree` (a node, or lists, tuples and dicts of them),
|
|
55
|
+
each parent before its children, in source order."""
|
|
56
|
+
pending = [tree]
|
|
57
|
+
while pending:
|
|
58
|
+
value = pending.pop()
|
|
59
|
+
value_type = type(value)
|
|
60
|
+
if value_type in CONTAINER_TYPES:
|
|
61
|
+
pending.extend(item for item in reversed(value) if type(item) not in LEAF_TYPES)
|
|
62
|
+
elif value_type is dict:
|
|
63
|
+
pending.extend(item for item in reversed(list(value.values())) if type(item) not in LEAF_TYPES)
|
|
64
|
+
elif value_type not in LEAF_TYPES:
|
|
65
|
+
fields = _child_fields_last_first(value_type)
|
|
66
|
+
if fields is False:
|
|
67
|
+
continue
|
|
68
|
+
yield value
|
|
69
|
+
for name in fields:
|
|
70
|
+
child = getattr(value, name)
|
|
71
|
+
if type(child) not in LEAF_TYPES:
|
|
72
|
+
pending.append(child)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def contains(tree, predicate: Callable) -> bool:
|
|
76
|
+
"""True when a node in `tree` satisfies `predicate`."""
|
|
77
|
+
for node in walk(tree):
|
|
78
|
+
if predicate(node):
|
|
79
|
+
return True
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def map_children(value, rebuild: Callable):
|
|
84
|
+
"""`value` with `rebuild` applied to each node it holds directly: a list,
|
|
85
|
+
tuple or dict is copied around the rebuilt items, anything else that is
|
|
86
|
+
not a node (a name, a number) is kept."""
|
|
87
|
+
if isinstance(value, list):
|
|
88
|
+
return [map_children(item, rebuild) for item in value]
|
|
89
|
+
if isinstance(value, tuple):
|
|
90
|
+
return tuple(map_children(item, rebuild) for item in value)
|
|
91
|
+
if isinstance(value, dict):
|
|
92
|
+
return {key: map_children(item, rebuild) for key, item in value.items()}
|
|
93
|
+
return rebuild(value) if is_node(value) else value
|