pineforge-codegen 0.6.5__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.
- pineforge_codegen/__init__.py +53 -0
- pineforge_codegen/analyzer/__init__.py +60 -0
- pineforge_codegen/analyzer/base.py +1563 -0
- pineforge_codegen/analyzer/call_handlers.py +895 -0
- pineforge_codegen/analyzer/contracts.py +163 -0
- pineforge_codegen/analyzer/diagnostics.py +118 -0
- pineforge_codegen/analyzer/tables.py +204 -0
- pineforge_codegen/analyzer/types.py +250 -0
- pineforge_codegen/ast_nodes.py +293 -0
- pineforge_codegen/codegen/__init__.py +78 -0
- pineforge_codegen/codegen/base.py +1381 -0
- pineforge_codegen/codegen/emit_top.py +875 -0
- pineforge_codegen/codegen/helpers.py +163 -0
- pineforge_codegen/codegen/helpers_syminfo.py +134 -0
- pineforge_codegen/codegen/input.py +189 -0
- pineforge_codegen/codegen/security.py +1564 -0
- pineforge_codegen/codegen/ta.py +298 -0
- pineforge_codegen/codegen/tables.py +613 -0
- pineforge_codegen/codegen/types.py +573 -0
- pineforge_codegen/codegen/visit_call.py +1305 -0
- pineforge_codegen/codegen/visit_expr.py +701 -0
- pineforge_codegen/codegen/visit_stmt.py +729 -0
- pineforge_codegen/errors.py +98 -0
- pineforge_codegen/lexer.py +531 -0
- pineforge_codegen/parser.py +1198 -0
- pineforge_codegen/pragmas.py +117 -0
- pineforge_codegen/signatures.py +808 -0
- pineforge_codegen/support_checker.py +1111 -0
- pineforge_codegen/symbols.py +118 -0
- pineforge_codegen/tokens.py +406 -0
- pineforge_codegen/tv_input_choices.py +86 -0
- pineforge_codegen-0.6.5.dist-info/METADATA +462 -0
- pineforge_codegen-0.6.5.dist-info/RECORD +35 -0
- pineforge_codegen-0.6.5.dist-info/WHEEL +4 -0
- pineforge_codegen-0.6.5.dist-info/licenses/LICENSE +197 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"""Pine type-hint and expression -> TypeSpec / PineType inference for the analyzer.
|
|
2
|
+
|
|
3
|
+
The historic ``analyzer.py`` had a small cluster of helpers that
|
|
4
|
+
answered one question: "given this Pine type hint string or AST
|
|
5
|
+
expression, what TypeSpec / PineType should we record on the
|
|
6
|
+
symbol?" This mixin collects them in one place. ``Analyzer`` mixes
|
|
7
|
+
``TypeHelper`` in alongside the future visitor mixins.
|
|
8
|
+
|
|
9
|
+
Mixin contract -- host class must provide the following attributes:
|
|
10
|
+
|
|
11
|
+
- ``self._symbols`` (``SymbolTable``): symbol-table source for
|
|
12
|
+
``Identifier`` lookups in ``_type_spec_from_expr``.
|
|
13
|
+
- ``self._udt_fields`` (``dict[str, dict[str, PineType]]``): UDT
|
|
14
|
+
type name -> field schema. Read by ``_type_spec_from_hint`` (to
|
|
15
|
+
fall back to ``TypeSpec.udt(name)``) and by
|
|
16
|
+
``_type_spec_from_expr`` (to recognise ``TypeName.new(...)``).
|
|
17
|
+
- ``self._udt_field_type_specs``
|
|
18
|
+
(``dict[str, dict[str, TypeSpec]]``): structured UDT field
|
|
19
|
+
metadata, used to resolve ``a.field`` member access.
|
|
20
|
+
- ``self._enum_defs`` (``dict[str, list[str]]``): enum name ->
|
|
21
|
+
member list, used by ``_extract_literal_value`` when constant-
|
|
22
|
+
folding ``EnumName.MEMBER`` to its ordinal.
|
|
23
|
+
|
|
24
|
+
And the following sibling methods (expected to come from
|
|
25
|
+
``Analyzer.base`` in the current step; future steps may move them
|
|
26
|
+
into their own mixins):
|
|
27
|
+
|
|
28
|
+
- ``self._visit`` -- visitor entry; used by ``_type_spec_from_expr``
|
|
29
|
+
to type-check ``array.from(...)``'s first arg.
|
|
30
|
+
|
|
31
|
+
Consumed by visitor methods to convert Pine type hints / expression
|
|
32
|
+
nodes into ``TypeSpec`` and ``PineType`` values, and by
|
|
33
|
+
``_handle_*_call`` paths in the upcoming ``CallHandlers`` mixin.
|
|
34
|
+
The mixin avoids importing from ``base.py`` to stay free of import
|
|
35
|
+
cycles.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
from __future__ import annotations
|
|
39
|
+
|
|
40
|
+
from typing import Any
|
|
41
|
+
|
|
42
|
+
from ..ast_nodes import (
|
|
43
|
+
ASTNode, BoolLiteral, FuncCall, Identifier, MemberAccess,
|
|
44
|
+
NaLiteral, NumberLiteral, StringLiteral, UnaryOp,
|
|
45
|
+
)
|
|
46
|
+
from ..symbols import PineType, TypeSpec
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class TypeHelper:
|
|
50
|
+
"""Pine type-hint / expression inference.
|
|
51
|
+
|
|
52
|
+
Mixed into ``Analyzer``; not meant to be instantiated standalone.
|
|
53
|
+
Methods that need shared state (``self._symbols``,
|
|
54
|
+
``self._udt_fields``, ``self._udt_field_type_specs``,
|
|
55
|
+
``self._enum_defs``) document the contract in the module
|
|
56
|
+
docstring above.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def _split_top_level_type_args(self, text: str) -> list[str]:
|
|
60
|
+
args: list[str] = []
|
|
61
|
+
cur: list[str] = []
|
|
62
|
+
depth = 0
|
|
63
|
+
for ch in text:
|
|
64
|
+
if ch == "<":
|
|
65
|
+
depth += 1
|
|
66
|
+
cur.append(ch)
|
|
67
|
+
elif ch == ">":
|
|
68
|
+
depth -= 1
|
|
69
|
+
cur.append(ch)
|
|
70
|
+
elif ch == "," and depth == 0:
|
|
71
|
+
args.append("".join(cur).strip())
|
|
72
|
+
cur = []
|
|
73
|
+
else:
|
|
74
|
+
cur.append(ch)
|
|
75
|
+
tail = "".join(cur).strip()
|
|
76
|
+
if tail:
|
|
77
|
+
args.append(tail)
|
|
78
|
+
return args
|
|
79
|
+
|
|
80
|
+
def _type_spec_from_hint(self, hint: str | None) -> TypeSpec | None:
|
|
81
|
+
if not hint:
|
|
82
|
+
return None
|
|
83
|
+
hint = hint.strip().replace(" ", "")
|
|
84
|
+
primitive = {"int", "float", "bool", "string", "color"}
|
|
85
|
+
if hint in primitive:
|
|
86
|
+
return TypeSpec.primitive(hint)
|
|
87
|
+
if hint.startswith("array<") and hint.endswith(">"):
|
|
88
|
+
inner = hint[len("array<"):-1]
|
|
89
|
+
elem = self._type_spec_from_hint(inner) or TypeSpec.udt(inner)
|
|
90
|
+
return TypeSpec.array(elem)
|
|
91
|
+
if hint.startswith("matrix<") and hint.endswith(">"):
|
|
92
|
+
inner = hint[len("matrix<"):-1]
|
|
93
|
+
return TypeSpec.matrix(self._type_spec_from_hint(inner) or TypeSpec.udt(inner))
|
|
94
|
+
if hint.startswith("map<") and hint.endswith(">"):
|
|
95
|
+
inner = hint[len("map<"):-1]
|
|
96
|
+
parts = self._split_top_level_type_args(inner)
|
|
97
|
+
if len(parts) == 2:
|
|
98
|
+
key = self._type_spec_from_hint(parts[0]) or TypeSpec.udt(parts[0])
|
|
99
|
+
val = self._type_spec_from_hint(parts[1]) or TypeSpec.udt(parts[1])
|
|
100
|
+
return TypeSpec.map(key, val)
|
|
101
|
+
if hint in self._udt_fields:
|
|
102
|
+
return TypeSpec.udt(hint)
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
def _template_args_from_call(self, node: FuncCall) -> list[str]:
|
|
106
|
+
callee = node.callee
|
|
107
|
+
ann = getattr(callee, "annotations", None) or {}
|
|
108
|
+
raw = ann.get("template_args") or []
|
|
109
|
+
return [str(x).replace(" ", "") for x in raw]
|
|
110
|
+
|
|
111
|
+
def _type_spec_from_expr(self, value: ASTNode | None) -> TypeSpec | None:
|
|
112
|
+
if value is None:
|
|
113
|
+
return None
|
|
114
|
+
if isinstance(value, FuncCall):
|
|
115
|
+
cal = value.callee
|
|
116
|
+
func = cal.member if isinstance(cal, MemberAccess) else None
|
|
117
|
+
ns = cal.object.name if isinstance(cal, MemberAccess) and isinstance(cal.object, Identifier) else None
|
|
118
|
+
targs = self._template_args_from_call(value)
|
|
119
|
+
if ns == "array" and func in ("new", "new_float", "new_int", "new_bool", "new_string", "from"):
|
|
120
|
+
if func == "new_float":
|
|
121
|
+
return TypeSpec.array(TypeSpec.primitive("float"))
|
|
122
|
+
if func == "new_int":
|
|
123
|
+
return TypeSpec.array(TypeSpec.primitive("int"))
|
|
124
|
+
if func == "new_bool":
|
|
125
|
+
return TypeSpec.array(TypeSpec.primitive("bool"))
|
|
126
|
+
if func == "new_string":
|
|
127
|
+
return TypeSpec.array(TypeSpec.primitive("string"))
|
|
128
|
+
if targs:
|
|
129
|
+
elem = self._type_spec_from_hint(targs[0]) or TypeSpec.udt(targs[0])
|
|
130
|
+
return TypeSpec.array(elem)
|
|
131
|
+
if func == "from" and value.args:
|
|
132
|
+
first = self._visit(value.args[0])
|
|
133
|
+
return TypeSpec.array(self._pine_type_to_spec(first))
|
|
134
|
+
return TypeSpec.array(TypeSpec.primitive("float"))
|
|
135
|
+
if ns == "matrix" and func == "new":
|
|
136
|
+
if targs:
|
|
137
|
+
elem = self._type_spec_from_hint(targs[0]) or TypeSpec.udt(targs[0])
|
|
138
|
+
else:
|
|
139
|
+
elem = TypeSpec.primitive("float")
|
|
140
|
+
return TypeSpec.matrix(elem)
|
|
141
|
+
if ns == "map" and func == "new":
|
|
142
|
+
key = self._type_spec_from_hint(targs[0]) if len(targs) > 0 else TypeSpec.primitive("string")
|
|
143
|
+
val = self._type_spec_from_hint(targs[1]) if len(targs) > 1 else TypeSpec.primitive("float")
|
|
144
|
+
return TypeSpec.map(key or TypeSpec.primitive("string"), val or TypeSpec.primitive("float"))
|
|
145
|
+
if ns == "str" and func == "split":
|
|
146
|
+
return TypeSpec.array(TypeSpec.primitive("string"))
|
|
147
|
+
if ns == "ta" and func == "pivot_point_levels":
|
|
148
|
+
return TypeSpec.array(TypeSpec.primitive("float"))
|
|
149
|
+
if ns == "request" and func == "security_lower_tf":
|
|
150
|
+
# ``request.security_lower_tf`` returns array<T> where T
|
|
151
|
+
# is the Pine type of the expression argument. We use the
|
|
152
|
+
# PineType cached on the FuncCall by the analyzer's
|
|
153
|
+
# ``_handle_request_security_lower_tf`` to avoid a second
|
|
154
|
+
# ``_visit`` (which would double-allocate TA call sites
|
|
155
|
+
# for TA-bearing expressions).
|
|
156
|
+
anns = getattr(value, "annotations", None) or {}
|
|
157
|
+
inner = anns.get("lower_tf_element_pine_type")
|
|
158
|
+
if inner is None:
|
|
159
|
+
return TypeSpec.array(TypeSpec.primitive("float"))
|
|
160
|
+
return TypeSpec.array(self._pine_type_to_spec(inner))
|
|
161
|
+
if ns in self._udt_fields and func == "new":
|
|
162
|
+
return TypeSpec.udt(ns)
|
|
163
|
+
if isinstance(cal, MemberAccess):
|
|
164
|
+
recv_spec = self._type_spec_from_expr(cal.object)
|
|
165
|
+
if recv_spec is not None and recv_spec.kind == "array":
|
|
166
|
+
if func in ("get", "first", "last", "pop", "shift", "remove"):
|
|
167
|
+
return recv_spec.element
|
|
168
|
+
if func in ("copy", "slice"):
|
|
169
|
+
return recv_spec
|
|
170
|
+
if recv_spec is not None and recv_spec.kind == "map":
|
|
171
|
+
if func in ("get", "remove"):
|
|
172
|
+
return recv_spec.value
|
|
173
|
+
if func == "keys":
|
|
174
|
+
return TypeSpec.array(recv_spec.key or TypeSpec.primitive("string"))
|
|
175
|
+
if func == "values":
|
|
176
|
+
return TypeSpec.array(recv_spec.value or TypeSpec.primitive("float"))
|
|
177
|
+
if recv_spec is not None and recv_spec.kind == "matrix":
|
|
178
|
+
if func in ("copy", "submatrix", "transpose", "concat"):
|
|
179
|
+
return recv_spec
|
|
180
|
+
if func in ("row", "col"):
|
|
181
|
+
return TypeSpec.array(recv_spec.element)
|
|
182
|
+
if func == "get":
|
|
183
|
+
return recv_spec.element
|
|
184
|
+
if func == "eigenvalues":
|
|
185
|
+
return TypeSpec.array(TypeSpec.primitive("float"))
|
|
186
|
+
if isinstance(value, Identifier):
|
|
187
|
+
sym = self._symbols.resolve(value.name)
|
|
188
|
+
if sym is not None and sym.type_spec is not None:
|
|
189
|
+
return sym.type_spec
|
|
190
|
+
if isinstance(value, MemberAccess):
|
|
191
|
+
owner = self._type_spec_from_expr(value.object)
|
|
192
|
+
if owner is not None and owner.kind == "udt" and owner.name:
|
|
193
|
+
return (self._udt_field_type_specs.get(owner.name) or {}).get(value.member)
|
|
194
|
+
return None
|
|
195
|
+
|
|
196
|
+
@staticmethod
|
|
197
|
+
def _pine_type_to_spec(pine_type: PineType) -> TypeSpec:
|
|
198
|
+
mapping = {
|
|
199
|
+
PineType.INT: "int",
|
|
200
|
+
PineType.FLOAT: "float",
|
|
201
|
+
PineType.BOOL: "bool",
|
|
202
|
+
PineType.STRING: "string",
|
|
203
|
+
PineType.COLOR: "color",
|
|
204
|
+
}
|
|
205
|
+
return TypeSpec.primitive(mapping.get(pine_type, "float"))
|
|
206
|
+
|
|
207
|
+
def _type_hint_to_pine(self, hint: str) -> PineType:
|
|
208
|
+
"""Convert a type hint string to PineType."""
|
|
209
|
+
if "<" in hint:
|
|
210
|
+
return PineType.UNKNOWN
|
|
211
|
+
mapping = {
|
|
212
|
+
"int": PineType.INT,
|
|
213
|
+
"float": PineType.FLOAT,
|
|
214
|
+
"bool": PineType.BOOL,
|
|
215
|
+
"string": PineType.STRING,
|
|
216
|
+
"color": PineType.COLOR,
|
|
217
|
+
}
|
|
218
|
+
return mapping.get(hint, PineType.UNKNOWN)
|
|
219
|
+
|
|
220
|
+
def _extract_literal_value(self, node: ASTNode) -> Any:
|
|
221
|
+
"""Extract a Python literal value from an AST node."""
|
|
222
|
+
if isinstance(node, NumberLiteral):
|
|
223
|
+
return node.value
|
|
224
|
+
if isinstance(node, StringLiteral):
|
|
225
|
+
return node.value
|
|
226
|
+
if isinstance(node, BoolLiteral):
|
|
227
|
+
return node.value
|
|
228
|
+
if isinstance(node, NaLiteral):
|
|
229
|
+
return None
|
|
230
|
+
if isinstance(node, Identifier):
|
|
231
|
+
return node.name
|
|
232
|
+
if isinstance(node, MemberAccess) and isinstance(node.object, Identifier):
|
|
233
|
+
ename = node.object.name
|
|
234
|
+
if ename in self._enum_defs:
|
|
235
|
+
members = self._enum_defs[ename]
|
|
236
|
+
if node.member in members:
|
|
237
|
+
return members.index(node.member)
|
|
238
|
+
return None
|
|
239
|
+
if isinstance(node, MemberAccess):
|
|
240
|
+
# strategy.fixed, strategy.percent_of_equity, strategy.cash,
|
|
241
|
+
# strategy.commission.percent, currency.USD, etc.
|
|
242
|
+
obj = self._extract_literal_value(node.object)
|
|
243
|
+
if obj is not None:
|
|
244
|
+
return f"{obj}.{node.member}"
|
|
245
|
+
return node.member
|
|
246
|
+
if isinstance(node, UnaryOp) and node.op == "-":
|
|
247
|
+
val = self._extract_literal_value(node.operand)
|
|
248
|
+
if isinstance(val, (int, float)):
|
|
249
|
+
return -val
|
|
250
|
+
return None
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"""AST node definitions for PineScript v6."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from pineforge_codegen.errors import SourceLocation
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# ---------------------------------------------------------------------------
|
|
10
|
+
# Base class
|
|
11
|
+
# ---------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class ASTNode:
|
|
15
|
+
"""Base class for all AST nodes.
|
|
16
|
+
|
|
17
|
+
*loc* carries the source span that produced this node; it is always set by
|
|
18
|
+
the new parser but defaults to None so that old code (Tasks 4-6 are still
|
|
19
|
+
pending) does not immediately break.
|
|
20
|
+
|
|
21
|
+
*annotations* is an open-ended dict used by later compiler phases (type
|
|
22
|
+
inference, optimiser, etc.) to attach arbitrary metadata without touching
|
|
23
|
+
the node definition.
|
|
24
|
+
"""
|
|
25
|
+
loc: SourceLocation | None = field(default=None, compare=False)
|
|
26
|
+
annotations: dict | None = field(default=None, compare=False)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
# Top-level / structural nodes
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class Program(ASTNode):
|
|
35
|
+
body: list = field(default_factory=list)
|
|
36
|
+
version: int | None = None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class StrategyDecl(ASTNode):
|
|
41
|
+
args: list = field(default_factory=list)
|
|
42
|
+
kwargs: dict = field(default_factory=dict)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class ImportStmt(ASTNode):
|
|
47
|
+
"""import <path> — parsed for error reporting."""
|
|
48
|
+
path: str = ""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# ---------------------------------------------------------------------------
|
|
52
|
+
# Declaration / assignment nodes
|
|
53
|
+
# ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class VarDecl(ASTNode):
|
|
57
|
+
"""Variable declaration: [var|varip] [type] name = value"""
|
|
58
|
+
name: str = ""
|
|
59
|
+
value: Any = None
|
|
60
|
+
is_var: bool = False
|
|
61
|
+
is_varip: bool = False
|
|
62
|
+
type_hint: str | None = None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class Assignment(ASTNode):
|
|
67
|
+
"""Reassignment / compound assignment: target <op> value.
|
|
68
|
+
|
|
69
|
+
*op* is one of: ``:=`` ``+=`` ``-=`` ``*=`` ``/=`` ``%=``
|
|
70
|
+
*target* is an expression (Identifier, Subscript, MemberAccess, …).
|
|
71
|
+
"""
|
|
72
|
+
target: Any = None
|
|
73
|
+
op: str = ":="
|
|
74
|
+
value: Any = None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@dataclass
|
|
78
|
+
class TupleAssign(ASTNode):
|
|
79
|
+
"""[a, b, c] = expr"""
|
|
80
|
+
names: list[str] = field(default_factory=list)
|
|
81
|
+
value: Any = None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# ---------------------------------------------------------------------------
|
|
85
|
+
# Control-flow nodes
|
|
86
|
+
# ---------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
@dataclass
|
|
89
|
+
class IfStmt(ASTNode):
|
|
90
|
+
condition: Any = None
|
|
91
|
+
body: list = field(default_factory=list)
|
|
92
|
+
else_body: list = field(default_factory=list)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dataclass
|
|
96
|
+
class ForStmt(ASTNode):
|
|
97
|
+
"""for var = start to end [by step]"""
|
|
98
|
+
var: str = ""
|
|
99
|
+
start: Any = None
|
|
100
|
+
end: Any = None
|
|
101
|
+
step: Any | None = None
|
|
102
|
+
body: list = field(default_factory=list)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@dataclass
|
|
106
|
+
class ForInStmt(ASTNode):
|
|
107
|
+
"""for x in iterable / for [a, b] in iterable"""
|
|
108
|
+
var: str | None = None # single variable name, or None if destructured
|
|
109
|
+
vars: list[str] | None = None # destructured variable names [a, b]
|
|
110
|
+
iterable: ASTNode | None = None
|
|
111
|
+
body: list = field(default_factory=list)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@dataclass
|
|
115
|
+
class WhileStmt(ASTNode):
|
|
116
|
+
condition: Any = None
|
|
117
|
+
body: list = field(default_factory=list)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@dataclass
|
|
121
|
+
class SwitchStmt(ASTNode):
|
|
122
|
+
expr: Any | None = None
|
|
123
|
+
cases: list = field(default_factory=list) # list of (expr|None, body_stmts)
|
|
124
|
+
default_body: list = field(default_factory=list)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@dataclass
|
|
128
|
+
class BreakStmt(ASTNode):
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@dataclass
|
|
133
|
+
class ContinueStmt(ASTNode):
|
|
134
|
+
pass
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# ---------------------------------------------------------------------------
|
|
138
|
+
# Function definition
|
|
139
|
+
# ---------------------------------------------------------------------------
|
|
140
|
+
|
|
141
|
+
@dataclass
|
|
142
|
+
class FuncDef(ASTNode):
|
|
143
|
+
name: str = ""
|
|
144
|
+
params: list[str] = field(default_factory=list)
|
|
145
|
+
body: list = field(default_factory=list)
|
|
146
|
+
is_single_expr: bool = False
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# ---------------------------------------------------------------------------
|
|
150
|
+
# Expression wrapper (statement context)
|
|
151
|
+
# ---------------------------------------------------------------------------
|
|
152
|
+
|
|
153
|
+
@dataclass
|
|
154
|
+
class ExprStmt(ASTNode):
|
|
155
|
+
expr: Any = None
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# ---------------------------------------------------------------------------
|
|
159
|
+
# Expression nodes
|
|
160
|
+
# ---------------------------------------------------------------------------
|
|
161
|
+
|
|
162
|
+
@dataclass
|
|
163
|
+
class BinOp(ASTNode):
|
|
164
|
+
"""Binary operation. Field order: left, op, right."""
|
|
165
|
+
left: Any = None
|
|
166
|
+
op: str = ""
|
|
167
|
+
right: Any = None
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@dataclass
|
|
171
|
+
class UnaryOp(ASTNode):
|
|
172
|
+
op: str = ""
|
|
173
|
+
operand: Any = None
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@dataclass
|
|
177
|
+
class Ternary(ASTNode):
|
|
178
|
+
condition: Any = None
|
|
179
|
+
true_val: Any = None
|
|
180
|
+
false_val: Any = None
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@dataclass
|
|
184
|
+
class FuncCall(ASTNode):
|
|
185
|
+
"""Function / method call.
|
|
186
|
+
|
|
187
|
+
*callee* is an expression node — typically an Identifier (``foo``) or a
|
|
188
|
+
MemberAccess (``strategy.entry``). The old ``name`` + ``namespace`` split
|
|
189
|
+
is replaced by a single expression so that arbitrary call targets are
|
|
190
|
+
representable.
|
|
191
|
+
"""
|
|
192
|
+
callee: Any = None
|
|
193
|
+
args: list = field(default_factory=list)
|
|
194
|
+
kwargs: dict = field(default_factory=dict)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@dataclass
|
|
198
|
+
class Subscript(ASTNode):
|
|
199
|
+
"""History referencing / index access: expr[offset]"""
|
|
200
|
+
object: Any = None
|
|
201
|
+
index: Any = None
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@dataclass
|
|
205
|
+
class Identifier(ASTNode):
|
|
206
|
+
name: str = ""
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@dataclass
|
|
210
|
+
class MemberAccess(ASTNode):
|
|
211
|
+
object: Any = None
|
|
212
|
+
member: str = ""
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@dataclass
|
|
216
|
+
class TypeAnnotation(ASTNode):
|
|
217
|
+
"""Type name used as an expression node (e.g. in cast syntax)."""
|
|
218
|
+
type_name: str = ""
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
# ---------------------------------------------------------------------------
|
|
222
|
+
# Literal nodes
|
|
223
|
+
# ---------------------------------------------------------------------------
|
|
224
|
+
|
|
225
|
+
@dataclass
|
|
226
|
+
class NumberLiteral(ASTNode):
|
|
227
|
+
value: int | float = 0
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
@dataclass
|
|
231
|
+
class StringLiteral(ASTNode):
|
|
232
|
+
value: str = ""
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
@dataclass
|
|
236
|
+
class BoolLiteral(ASTNode):
|
|
237
|
+
value: bool = False
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@dataclass
|
|
241
|
+
class NaLiteral(ASTNode):
|
|
242
|
+
pass
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@dataclass
|
|
246
|
+
class ColorLiteral(ASTNode):
|
|
247
|
+
"""Hex colour literal: ``#rrggbb`` or ``#rrggbbaa``."""
|
|
248
|
+
value: str = ""
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@dataclass
|
|
252
|
+
class TupleLiteral(ASTNode):
|
|
253
|
+
"""[a, b, c] tuple literal used in function returns."""
|
|
254
|
+
elements: list = field(default_factory=list)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
# ---------------------------------------------------------------------------
|
|
258
|
+
# User-Defined Type (UDT) nodes
|
|
259
|
+
# ---------------------------------------------------------------------------
|
|
260
|
+
|
|
261
|
+
@dataclass
|
|
262
|
+
class TypeField:
|
|
263
|
+
"""One field in a type declaration."""
|
|
264
|
+
type_name: str = ""
|
|
265
|
+
name: str = ""
|
|
266
|
+
default: ASTNode | None = None
|
|
267
|
+
|
|
268
|
+
@dataclass
|
|
269
|
+
class TypeDecl(ASTNode):
|
|
270
|
+
"""type MyType\n float field = default"""
|
|
271
|
+
name: str = ""
|
|
272
|
+
fields: list = field(default_factory=list) # list of TypeField
|
|
273
|
+
|
|
274
|
+
@dataclass
|
|
275
|
+
class EnumDecl(ASTNode):
|
|
276
|
+
"""User-defined enum (derived type). Members are ordered; optional RHS per field.
|
|
277
|
+
|
|
278
|
+
TradingView allows `member = <expr>` so field “payload” (titles, IANA strings, …)
|
|
279
|
+
is separate from the member’s ordinal — the language type is still the enum, not
|
|
280
|
+
the RHS type.
|
|
281
|
+
"""
|
|
282
|
+
name: str = ""
|
|
283
|
+
members: list = field(default_factory=list) # list of str (declaration order)
|
|
284
|
+
member_values: dict[str, ASTNode] = field(default_factory=dict)
|
|
285
|
+
|
|
286
|
+
@dataclass
|
|
287
|
+
class MethodDef(ASTNode):
|
|
288
|
+
"""method myMethod(self, param) => body"""
|
|
289
|
+
name: str = ""
|
|
290
|
+
type_name: str = "" # the type this method belongs to
|
|
291
|
+
params: list = field(default_factory=list) # list of str (first is self)
|
|
292
|
+
body: list = field(default_factory=list)
|
|
293
|
+
is_single_expr: bool = False
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Codegen package facade.
|
|
2
|
+
|
|
3
|
+
Historically the codegen lived in a single ``codegen.py`` module that hit
|
|
4
|
+
~5,700 lines and ~115 methods on a single ``CodeGen`` class. We are
|
|
5
|
+
incrementally splitting it into focused submodules without changing the
|
|
6
|
+
public API:
|
|
7
|
+
|
|
8
|
+
from pineforge_codegen.codegen import CodeGen # primary
|
|
9
|
+
from pineforge_codegen.codegen import BAR_FIELDS, ... # constants
|
|
10
|
+
|
|
11
|
+
Re-exports preserved for ``support_checker.py`` and any external consumers
|
|
12
|
+
that imported module-level rule tables. Add new helper modules under
|
|
13
|
+
``compiler/transpiler/codegen/`` and re-export their public surface here.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from .base import CodeGen
|
|
17
|
+
from .helpers import CPP_RESERVED
|
|
18
|
+
from .tables import (
|
|
19
|
+
# Bar-data tables (consumed by helpers + support_checker).
|
|
20
|
+
BAR_FIELDS,
|
|
21
|
+
BAR_BUILTINS,
|
|
22
|
+
BAR_SERIES_PUSH,
|
|
23
|
+
SECURITY_OHLC_BAR_FIELDS,
|
|
24
|
+
# Runtime function-name constants emitted as C++ string literals.
|
|
25
|
+
RUNTIME_REGISTER_SECURITY_EVAL_FN,
|
|
26
|
+
RUNTIME_REGISTER_SECURITY_LOWER_TF_EVAL_FN,
|
|
27
|
+
# TA dispatch / arg tables.
|
|
28
|
+
TA_RETURNS_BOOL,
|
|
29
|
+
TA_IMPLICIT_COMPUTE,
|
|
30
|
+
TA_COMPUTE_ARGS,
|
|
31
|
+
TA_IMPLICIT_COMPUTE_FULL,
|
|
32
|
+
TA_IMPLICIT_APPEND,
|
|
33
|
+
TA_TUPLE_FIELDS,
|
|
34
|
+
# Type and reserved-name tables.
|
|
35
|
+
PINE_TYPE_TO_CPP,
|
|
36
|
+
# Skip / passthrough catalogs (used by support_checker).
|
|
37
|
+
SKIP_FUNC_NAMES,
|
|
38
|
+
SKIP_NAMESPACES,
|
|
39
|
+
SKIP_VAR_TYPES,
|
|
40
|
+
# Built-in dispatch tables (consumed by visitors + support_checker).
|
|
41
|
+
SYMINFO_MEMBER_MAP,
|
|
42
|
+
COLOR_CONST_MAP,
|
|
43
|
+
ARRAY_METHODS,
|
|
44
|
+
MAP_METHODS,
|
|
45
|
+
MATRIX_METHODS,
|
|
46
|
+
MATRIX_METHOD_KWARGS,
|
|
47
|
+
MATRIX_RETURNING_METHODS,
|
|
48
|
+
MATH_FUNC_MAP,
|
|
49
|
+
STR_FUNC_MAP,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
__all__ = [
|
|
53
|
+
"CodeGen",
|
|
54
|
+
"BAR_FIELDS",
|
|
55
|
+
"BAR_BUILTINS",
|
|
56
|
+
"BAR_SERIES_PUSH",
|
|
57
|
+
"SECURITY_OHLC_BAR_FIELDS",
|
|
58
|
+
"TA_RETURNS_BOOL",
|
|
59
|
+
"TA_IMPLICIT_COMPUTE",
|
|
60
|
+
"TA_COMPUTE_ARGS",
|
|
61
|
+
"TA_IMPLICIT_COMPUTE_FULL",
|
|
62
|
+
"TA_IMPLICIT_APPEND",
|
|
63
|
+
"TA_TUPLE_FIELDS",
|
|
64
|
+
"PINE_TYPE_TO_CPP",
|
|
65
|
+
"CPP_RESERVED",
|
|
66
|
+
"SKIP_FUNC_NAMES",
|
|
67
|
+
"SKIP_NAMESPACES",
|
|
68
|
+
"SKIP_VAR_TYPES",
|
|
69
|
+
"SYMINFO_MEMBER_MAP",
|
|
70
|
+
"COLOR_CONST_MAP",
|
|
71
|
+
"ARRAY_METHODS",
|
|
72
|
+
"MAP_METHODS",
|
|
73
|
+
"MATRIX_METHODS",
|
|
74
|
+
"MATRIX_METHOD_KWARGS",
|
|
75
|
+
"MATRIX_RETURNING_METHODS",
|
|
76
|
+
"MATH_FUNC_MAP",
|
|
77
|
+
"STR_FUNC_MAP",
|
|
78
|
+
]
|