@pineforge/codegen-pyodide 0.8.0 → 0.8.1
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.
- package/package.json +1 -1
- package/pineforge_codegen/analyzer/base.py +453 -15
- package/pineforge_codegen/analyzer/call_handlers.py +200 -11
- package/pineforge_codegen/analyzer/contracts.py +62 -0
- package/pineforge_codegen/analyzer/types.py +53 -0
- package/pineforge_codegen/codegen/__init__.py +4 -0
- package/pineforge_codegen/codegen/base.py +509 -44
- package/pineforge_codegen/codegen/drawing.py +560 -0
- package/pineforge_codegen/codegen/emit_top.py +286 -27
- package/pineforge_codegen/codegen/input.py +14 -0
- package/pineforge_codegen/codegen/security.py +294 -13
- package/pineforge_codegen/codegen/tables.py +59 -3
- package/pineforge_codegen/codegen/types.py +369 -19
- package/pineforge_codegen/codegen/visit_call.py +147 -22
- package/pineforge_codegen/codegen/visit_expr.py +54 -0
- package/pineforge_codegen/codegen/visit_stmt.py +161 -14
- package/pineforge_codegen/parser.py +56 -23
- package/pineforge_codegen/support_checker.py +281 -17
- package/pineforge_codegen-0.8.1.tar.gz +0 -0
- package/release.json +2 -2
- package/tables.json +2 -8
- package/pineforge_codegen-0.8.0.tar.gz +0 -0
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
"""Drawing-objects-as-data dispatch for the codegen.
|
|
2
|
+
|
|
3
|
+
Spec: ``docs/drawing-objects-as-data.md`` §4.3 (the 4-form call dispatch),
|
|
4
|
+
§L (handle lifecycle) and §U (UDT × drawing). Everything here only ever
|
|
5
|
+
runs for a strategy whose ``self._uses_drawing`` is True (the dispatch is
|
|
6
|
+
reached solely when a ``line``/``box``/``label``/``linefill``/``chart.point``
|
|
7
|
+
call or a drawing-typed receiver is present), so non-drawing strategies emit
|
|
8
|
+
byte-identical C++.
|
|
9
|
+
|
|
10
|
+
The geometry of a drawing object becomes REAL C++ state (per-type arena in
|
|
11
|
+
``pineforge/drawing.hpp``); every VISUAL kwarg/method (color / style / width /
|
|
12
|
+
bgcolor / text_* / extend-as-visual / force_overlay) is accepted-and-DROPPED.
|
|
13
|
+
|
|
14
|
+
``DrawingVisitor`` is mixed into ``CodeGen``; it owns no state of its own and
|
|
15
|
+
reads only attributes established by ``CodeGen.__init__`` (``_uses_drawing``,
|
|
16
|
+
``_udt_var_types``, ``_udt_param_udt``) and sibling mixins
|
|
17
|
+
(``_visit_expr``, ``_type_spec_from_expr``).
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from ..ast_nodes import FuncCall, Identifier, MemberAccess
|
|
23
|
+
from .tables import DRAWING_TYPE_TO_CPP, DRAWING_ARENA, ARRAY_VOID_METHODS as _ARRAY_VOID_METHODS
|
|
24
|
+
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
# Canonical Pine v6 constructor param-name lists (positional order).
|
|
27
|
+
# Only the GEOMETRY names are consumed downstream; every other (visual) name is
|
|
28
|
+
# merged-and-dropped. Two overloads each: scalar coords vs chart.point.
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
_LINE_CTOR = ["x1", "y1", "x2", "y2", "xloc", "extend", "color", "style",
|
|
31
|
+
"width", "force_overlay"]
|
|
32
|
+
_LINE_CTOR_PTS = ["first_point", "second_point", "xloc", "color", "style",
|
|
33
|
+
"width", "extend", "force_overlay"]
|
|
34
|
+
_BOX_CTOR = ["left", "top", "right", "bottom", "border_color", "border_width",
|
|
35
|
+
"border_style", "extend", "xloc", "bgcolor", "text", "text_size",
|
|
36
|
+
"text_color", "text_halign", "text_valign", "text_wrap",
|
|
37
|
+
"text_font_family", "force_overlay"]
|
|
38
|
+
_BOX_CTOR_PTS = ["top_left", "bottom_right", "border_color", "border_width",
|
|
39
|
+
"border_style", "extend", "xloc", "bgcolor", "text",
|
|
40
|
+
"text_size", "text_color", "text_halign", "text_valign",
|
|
41
|
+
"text_wrap", "text_font_family", "force_overlay"]
|
|
42
|
+
_LABEL_CTOR = ["x", "y", "text", "xloc", "yloc", "color", "style", "textcolor",
|
|
43
|
+
"size", "textalign", "tooltip", "text_font_family",
|
|
44
|
+
"force_overlay"]
|
|
45
|
+
_LABEL_CTOR_PTS = ["point", "text", "xloc", "yloc", "color", "style",
|
|
46
|
+
"textcolor", "size", "textalign", "tooltip",
|
|
47
|
+
"text_font_family", "force_overlay"]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
# Method classification per type. GEOMETRY methods emit real arena calls;
|
|
52
|
+
# *_NOOP visual setters emit pf_noop (args evaluated + discarded).
|
|
53
|
+
# ---------------------------------------------------------------------------
|
|
54
|
+
LINE_METHODS = frozenset({
|
|
55
|
+
"get_x1", "get_x2", "get_y1", "get_y2", "get_price",
|
|
56
|
+
"set_x1", "set_x2", "set_y1", "set_y2", "set_xy1", "set_xy2",
|
|
57
|
+
"set_first_point", "set_second_point", "set_xloc", "copy", "delete",
|
|
58
|
+
})
|
|
59
|
+
LINE_NOOP = frozenset({"set_color", "set_style", "set_width", "set_extend"})
|
|
60
|
+
|
|
61
|
+
BOX_METHODS = frozenset({
|
|
62
|
+
"get_left", "get_right", "get_top", "get_bottom",
|
|
63
|
+
"set_left", "set_right", "set_top", "set_bottom",
|
|
64
|
+
"set_lefttop", "set_rightbottom",
|
|
65
|
+
"set_top_left_point", "set_bottom_right_point", "set_xloc",
|
|
66
|
+
"copy", "delete",
|
|
67
|
+
})
|
|
68
|
+
BOX_NOOP = frozenset({
|
|
69
|
+
"set_border_color", "set_border_width", "set_border_style", "set_bgcolor",
|
|
70
|
+
"set_extend", "set_text", "set_text_color", "set_text_size",
|
|
71
|
+
"set_text_halign", "set_text_valign", "set_text_font_family",
|
|
72
|
+
"set_text_wrap", "set_text_formatting",
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
LABEL_METHODS = frozenset({
|
|
76
|
+
"get_x", "get_y", "get_text",
|
|
77
|
+
"set_x", "set_y", "set_xy", "set_point", "set_xloc", "set_yloc",
|
|
78
|
+
"set_text", "copy", "delete",
|
|
79
|
+
})
|
|
80
|
+
LABEL_NOOP = frozenset({
|
|
81
|
+
"set_color", "set_style", "set_textcolor", "set_size", "set_textalign",
|
|
82
|
+
"set_tooltip", "set_text_font_family", "set_text_formatting",
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
LINEFILL_METHODS = frozenset({"get_line1", "get_line2", "delete"})
|
|
86
|
+
LINEFILL_NOOP = frozenset({"set_color"})
|
|
87
|
+
|
|
88
|
+
# Scalar getter -> C++ return type. (linefill.get_line1/2 return a Line handle,
|
|
89
|
+
# typed via _type_spec_from_expr -> udt, not here.) x-coords are int64_t,
|
|
90
|
+
# y-coords/prices double, label text std::string.
|
|
91
|
+
_DRAWING_GETTER_RET = {
|
|
92
|
+
("line", "get_x1"): "int64_t", ("line", "get_x2"): "int64_t",
|
|
93
|
+
("line", "get_y1"): "double", ("line", "get_y2"): "double",
|
|
94
|
+
("line", "get_price"): "double",
|
|
95
|
+
("box", "get_left"): "int64_t", ("box", "get_right"): "int64_t",
|
|
96
|
+
("box", "get_top"): "double", ("box", "get_bottom"): "double",
|
|
97
|
+
("label", "get_x"): "int64_t", ("label", "get_y"): "double",
|
|
98
|
+
("label", "get_text"): "std::string",
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
# Per-type lookup (geometry, noop) used by the dispatcher and support_checker.
|
|
102
|
+
DRAWING_METHODS_BY_TYPE = {
|
|
103
|
+
"line": (LINE_METHODS, LINE_NOOP),
|
|
104
|
+
"box": (BOX_METHODS, BOX_NOOP),
|
|
105
|
+
"label": (LABEL_METHODS, LABEL_NOOP),
|
|
106
|
+
"linefill": (LINEFILL_METHODS, LINEFILL_NOOP),
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# All method names that the §4.3 receiver dispatch recognises. ``new`` is
|
|
110
|
+
# DELIBERATELY excluded (it is namespace-functional only and would otherwise
|
|
111
|
+
# shadow ``Type.new(...)`` UDT constructors). Gating the receiver branch on
|
|
112
|
+
# membership here enforces the L.1 precedence rule: a user method (egoigor's
|
|
113
|
+
# ``slope``) is not in this set and therefore routes to user-method dispatch.
|
|
114
|
+
ALL_DRAWING_METHODS = (
|
|
115
|
+
LINE_METHODS | LINE_NOOP | BOX_METHODS | BOX_NOOP
|
|
116
|
+
| LABEL_METHODS | LABEL_NOOP | LINEFILL_METHODS | LINEFILL_NOOP
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class DrawingVisitor:
|
|
121
|
+
"""Drawing-objects-as-data emit helpers. Mixed into ``CodeGen``."""
|
|
122
|
+
|
|
123
|
+
# ------------------------------------------------------------------
|
|
124
|
+
# Enum / extend lowering helpers
|
|
125
|
+
# ------------------------------------------------------------------
|
|
126
|
+
@staticmethod
|
|
127
|
+
def _lower_xloc(node) -> str:
|
|
128
|
+
if (isinstance(node, MemberAccess) and isinstance(node.object, Identifier)
|
|
129
|
+
and node.object.name == "xloc" and node.member == "bar_time"):
|
|
130
|
+
return "XLoc::bar_time"
|
|
131
|
+
return "XLoc::bar_index"
|
|
132
|
+
|
|
133
|
+
@staticmethod
|
|
134
|
+
def _lower_yloc(node) -> str:
|
|
135
|
+
if isinstance(node, MemberAccess) and isinstance(node.object, Identifier) and node.object.name == "yloc":
|
|
136
|
+
if node.member == "abovebar":
|
|
137
|
+
return "YLoc::abovebar"
|
|
138
|
+
if node.member == "belowbar":
|
|
139
|
+
return "YLoc::belowbar"
|
|
140
|
+
return "YLoc::price"
|
|
141
|
+
|
|
142
|
+
@staticmethod
|
|
143
|
+
def _lower_extend(node) -> tuple[str, str]:
|
|
144
|
+
"""Map ``extend.{none,left,right,both}`` -> (ext_left, ext_right)."""
|
|
145
|
+
if isinstance(node, MemberAccess) and isinstance(node.object, Identifier) and node.object.name == "extend":
|
|
146
|
+
return {
|
|
147
|
+
"both": ("true", "true"),
|
|
148
|
+
"left": ("true", "false"),
|
|
149
|
+
"right": ("false", "true"),
|
|
150
|
+
"none": ("false", "false"),
|
|
151
|
+
}.get(node.member, ("false", "false"))
|
|
152
|
+
return ("false", "false")
|
|
153
|
+
|
|
154
|
+
@staticmethod
|
|
155
|
+
def _is_chart_point_callee(callee) -> bool:
|
|
156
|
+
"""True for ``chart.point.<member>(...)`` callees."""
|
|
157
|
+
return (
|
|
158
|
+
isinstance(callee, MemberAccess)
|
|
159
|
+
and isinstance(callee.object, MemberAccess)
|
|
160
|
+
and isinstance(callee.object.object, Identifier)
|
|
161
|
+
and callee.object.object.name == "chart"
|
|
162
|
+
and callee.object.member == "point"
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# ------------------------------------------------------------------
|
|
166
|
+
# Constructor (CREATE) lowering
|
|
167
|
+
# ------------------------------------------------------------------
|
|
168
|
+
@staticmethod
|
|
169
|
+
def _merge_drawing_args(node: FuncCall, param_names: list[str]) -> dict:
|
|
170
|
+
"""Map positional args + kwargs onto ``param_names`` -> {name: node}."""
|
|
171
|
+
vals: dict = {}
|
|
172
|
+
for i, a in enumerate(node.args):
|
|
173
|
+
if i < len(param_names):
|
|
174
|
+
vals[param_names[i]] = a
|
|
175
|
+
for k, v in node.kwargs.items():
|
|
176
|
+
vals[k] = v
|
|
177
|
+
return vals
|
|
178
|
+
|
|
179
|
+
def _drawing_ctor_is_points(self, node: FuncCall) -> bool:
|
|
180
|
+
"""Pick the chart.point overload when the first geometry arg is a
|
|
181
|
+
ChartPoint (inferred TypeSpec udt('chart.point')) or a point kwarg is
|
|
182
|
+
supplied."""
|
|
183
|
+
for k in ("first_point", "top_left", "point"):
|
|
184
|
+
if k in node.kwargs:
|
|
185
|
+
return True
|
|
186
|
+
if node.args:
|
|
187
|
+
spec = self._type_spec_from_expr(node.args[0])
|
|
188
|
+
if spec is not None and spec.kind == "udt" and spec.name == "chart.point":
|
|
189
|
+
return True
|
|
190
|
+
return False
|
|
191
|
+
|
|
192
|
+
def _emit_drawing_ctor(self, dtype: str, node: FuncCall) -> str:
|
|
193
|
+
arena = DRAWING_ARENA[dtype]
|
|
194
|
+
if dtype == "linefill":
|
|
195
|
+
vals = self._merge_drawing_args(node, ["line1", "line2", "color"])
|
|
196
|
+
l1 = self._visit_expr(vals["line1"]) if vals.get("line1") is not None else "Line{}"
|
|
197
|
+
l2 = self._visit_expr(vals["line2"]) if vals.get("line2") is not None else "Line{}"
|
|
198
|
+
return f"pf_linefill_new({arena}, {l1}, {l2})"
|
|
199
|
+
|
|
200
|
+
use_pts = self._drawing_ctor_is_points(node)
|
|
201
|
+
|
|
202
|
+
if dtype == "line":
|
|
203
|
+
vals = self._merge_drawing_args(node, _LINE_CTOR_PTS if use_pts else _LINE_CTOR)
|
|
204
|
+
xloc = self._lower_xloc(vals.get("xloc"))
|
|
205
|
+
if use_pts:
|
|
206
|
+
p1 = self._visit_expr(vals.get("first_point"))
|
|
207
|
+
p2 = self._visit_expr(vals.get("second_point"))
|
|
208
|
+
return f"pf_line_new_pts({arena}, {p1}, {p2}, {xloc})"
|
|
209
|
+
el, er = self._lower_extend(vals.get("extend"))
|
|
210
|
+
x1 = self._visit_expr(vals.get("x1"))
|
|
211
|
+
y1 = self._visit_expr(vals.get("y1"))
|
|
212
|
+
x2 = self._visit_expr(vals.get("x2"))
|
|
213
|
+
y2 = self._visit_expr(vals.get("y2"))
|
|
214
|
+
return (f"pf_line_new({arena}, (int64_t)({x1}), (double)({y1}), "
|
|
215
|
+
f"(int64_t)({x2}), (double)({y2}), {xloc}, {el}, {er})")
|
|
216
|
+
|
|
217
|
+
if dtype == "box":
|
|
218
|
+
vals = self._merge_drawing_args(node, _BOX_CTOR_PTS if use_pts else _BOX_CTOR)
|
|
219
|
+
xloc = self._lower_xloc(vals.get("xloc"))
|
|
220
|
+
if use_pts:
|
|
221
|
+
tl = self._visit_expr(vals.get("top_left"))
|
|
222
|
+
br = self._visit_expr(vals.get("bottom_right"))
|
|
223
|
+
return f"pf_box_new_pts({arena}, {tl}, {br}, {xloc})"
|
|
224
|
+
left = self._visit_expr(vals.get("left"))
|
|
225
|
+
top = self._visit_expr(vals.get("top"))
|
|
226
|
+
right = self._visit_expr(vals.get("right"))
|
|
227
|
+
bottom = self._visit_expr(vals.get("bottom"))
|
|
228
|
+
return (f"pf_box_new({arena}, (int64_t)({left}), (double)({top}), "
|
|
229
|
+
f"(int64_t)({right}), (double)({bottom}), {xloc})")
|
|
230
|
+
|
|
231
|
+
if dtype == "label":
|
|
232
|
+
vals = self._merge_drawing_args(node, _LABEL_CTOR_PTS if use_pts else _LABEL_CTOR)
|
|
233
|
+
yloc = self._lower_yloc(vals.get("yloc"))
|
|
234
|
+
text = (self._visit_expr(vals["text"]) if vals.get("text") is not None
|
|
235
|
+
else 'std::string("")')
|
|
236
|
+
if use_pts:
|
|
237
|
+
pt = self._visit_expr(vals.get("point"))
|
|
238
|
+
return f"pf_label_new_pt({arena}, {pt}, {text}, {yloc})"
|
|
239
|
+
x = self._visit_expr(vals.get("x"))
|
|
240
|
+
y = self._visit_expr(vals.get("y"))
|
|
241
|
+
xloc = self._lower_xloc(vals.get("xloc"))
|
|
242
|
+
return (f"pf_label_new({arena}, (int64_t)({x}), (double)({y}), "
|
|
243
|
+
f"{text}, {xloc}, {yloc})")
|
|
244
|
+
|
|
245
|
+
return "0" # unreachable
|
|
246
|
+
|
|
247
|
+
# ------------------------------------------------------------------
|
|
248
|
+
# chart.point — inline aggregate literals (no arena)
|
|
249
|
+
# ------------------------------------------------------------------
|
|
250
|
+
def _emit_chart_point(self, func_name: str, node: FuncCall) -> str:
|
|
251
|
+
if func_name == "copy":
|
|
252
|
+
inner = self._visit_expr(node.args[0]) if node.args else "ChartPoint{}"
|
|
253
|
+
return f"ChartPoint({inner})"
|
|
254
|
+
if func_name == "now":
|
|
255
|
+
vals = self._merge_drawing_args(node, ["price"])
|
|
256
|
+
price = (self._visit_expr(vals["price"]) if vals.get("price") is not None
|
|
257
|
+
else "current_bar_.close")
|
|
258
|
+
return (f"ChartPoint{{ .index=bar_index_, "
|
|
259
|
+
f".time=(int64_t)current_bar_.timestamp, .price=({price}) }}")
|
|
260
|
+
if func_name == "from_index":
|
|
261
|
+
vals = self._merge_drawing_args(node, ["index", "price"])
|
|
262
|
+
idx = self._visit_expr(vals.get("index"))
|
|
263
|
+
price = self._visit_expr(vals.get("price"))
|
|
264
|
+
return (f"ChartPoint{{ .index=(int64_t)({idx}), .time=na<int64_t>(), "
|
|
265
|
+
f".price=({price}) }}")
|
|
266
|
+
if func_name == "from_time":
|
|
267
|
+
vals = self._merge_drawing_args(node, ["time", "price"])
|
|
268
|
+
tm = self._visit_expr(vals.get("time"))
|
|
269
|
+
price = self._visit_expr(vals.get("price"))
|
|
270
|
+
return (f"ChartPoint{{ .index=na<int64_t>(), .time=(int64_t)({tm}), "
|
|
271
|
+
f".price=({price}) }}")
|
|
272
|
+
if func_name == "new":
|
|
273
|
+
vals = self._merge_drawing_args(node, ["time", "index", "price"])
|
|
274
|
+
tm = self._visit_expr(vals.get("time"))
|
|
275
|
+
idx = self._visit_expr(vals.get("index"))
|
|
276
|
+
price = self._visit_expr(vals.get("price"))
|
|
277
|
+
return (f"ChartPoint{{ .index=(int64_t)({idx}), .time=(int64_t)({tm}), "
|
|
278
|
+
f".price=({price}) }}")
|
|
279
|
+
return "ChartPoint{}"
|
|
280
|
+
|
|
281
|
+
# ------------------------------------------------------------------
|
|
282
|
+
# Namespace-functional + method-form dispatch
|
|
283
|
+
# ------------------------------------------------------------------
|
|
284
|
+
def _emit_drawing_namespace_call(self, namespace: str, func_name: str, node: FuncCall) -> str:
|
|
285
|
+
"""``line.new(...)`` / ``line.get_y2(ln)`` / ``linefill.new(l1,l2,c)`` …"""
|
|
286
|
+
if func_name == "new":
|
|
287
|
+
return self._emit_drawing_ctor(namespace, node)
|
|
288
|
+
if not node.args:
|
|
289
|
+
return "0"
|
|
290
|
+
return self._emit_drawing_method(namespace, func_name, node.args[0], list(node.args[1:]), node)
|
|
291
|
+
|
|
292
|
+
def _emit_drawing_method(self, dtype: str, method: str, recv_node, arg_nodes: list, node: FuncCall) -> str:
|
|
293
|
+
"""Lower a drawing method onto its arena (or pf_noop for visual setters).
|
|
294
|
+
|
|
295
|
+
``recv_node`` is the handle receiver; ``arg_nodes`` the remaining args.
|
|
296
|
+
"""
|
|
297
|
+
arena = DRAWING_ARENA[dtype]
|
|
298
|
+
recv = self._visit_expr(recv_node)
|
|
299
|
+
geometry, noop = DRAWING_METHODS_BY_TYPE[dtype]
|
|
300
|
+
if method in noop:
|
|
301
|
+
extra = "".join(", " + self._visit_expr(a) for a in arg_nodes)
|
|
302
|
+
return f"pf_noop({recv}{extra})"
|
|
303
|
+
av = [self._visit_expr(a) for a in arg_nodes]
|
|
304
|
+
if dtype == "line":
|
|
305
|
+
return self._emit_line_method(method, arena, recv, av, arg_nodes)
|
|
306
|
+
if dtype == "box":
|
|
307
|
+
return self._emit_box_method(method, arena, recv, av, arg_nodes)
|
|
308
|
+
if dtype == "label":
|
|
309
|
+
return self._emit_label_method(method, arena, recv, av, arg_nodes)
|
|
310
|
+
if dtype == "linefill":
|
|
311
|
+
return self._emit_linefill_method(method, arena, recv, av)
|
|
312
|
+
return "0"
|
|
313
|
+
|
|
314
|
+
def _emit_line_method(self, m, a, r, av, raw) -> str:
|
|
315
|
+
if m == "get_x1":
|
|
316
|
+
return f"pf_line_get_x1({a}, {r})"
|
|
317
|
+
if m == "get_x2":
|
|
318
|
+
return f"pf_line_get_x2({a}, {r})"
|
|
319
|
+
if m == "get_y1":
|
|
320
|
+
return f"pf_line_get_y1({a}, {r})"
|
|
321
|
+
if m == "get_y2":
|
|
322
|
+
return f"pf_line_get_y2({a}, {r})"
|
|
323
|
+
if m == "get_price":
|
|
324
|
+
return f"pf_line_get_price({a}, {r}, (int64_t)({av[0]}))"
|
|
325
|
+
if m == "set_x1":
|
|
326
|
+
return f"pf_line_set_x1({a}, {r}, (int64_t)({av[0]}))"
|
|
327
|
+
if m == "set_x2":
|
|
328
|
+
return f"pf_line_set_x2({a}, {r}, (int64_t)({av[0]}))"
|
|
329
|
+
if m == "set_y1":
|
|
330
|
+
return f"pf_line_set_y1({a}, {r}, (double)({av[0]}))"
|
|
331
|
+
if m == "set_y2":
|
|
332
|
+
return f"pf_line_set_y2({a}, {r}, (double)({av[0]}))"
|
|
333
|
+
if m == "set_xy1":
|
|
334
|
+
return f"pf_line_set_xy1({a}, {r}, (int64_t)({av[0]}), (double)({av[1]}))"
|
|
335
|
+
if m == "set_xy2":
|
|
336
|
+
return f"pf_line_set_xy2({a}, {r}, (int64_t)({av[0]}), (double)({av[1]}))"
|
|
337
|
+
if m == "set_first_point":
|
|
338
|
+
return f"pf_line_set_first_point({a}, {r}, {av[0]})"
|
|
339
|
+
if m == "set_second_point":
|
|
340
|
+
return f"pf_line_set_second_point({a}, {r}, {av[0]})"
|
|
341
|
+
if m == "set_xloc":
|
|
342
|
+
return f"pf_line_set_xloc({a}, {r}, (int64_t)({av[0]}), (int64_t)({av[1]}), {self._lower_xloc(raw[2])})"
|
|
343
|
+
if m == "copy":
|
|
344
|
+
return f"pf_line_copy({a}, {r})"
|
|
345
|
+
if m == "delete":
|
|
346
|
+
return f"pf_line_delete({a}, {r})"
|
|
347
|
+
return "0"
|
|
348
|
+
|
|
349
|
+
def _emit_box_method(self, m, a, r, av, raw) -> str:
|
|
350
|
+
if m == "get_left":
|
|
351
|
+
return f"pf_box_get_left({a}, {r})"
|
|
352
|
+
if m == "get_right":
|
|
353
|
+
return f"pf_box_get_right({a}, {r})"
|
|
354
|
+
if m == "get_top":
|
|
355
|
+
return f"pf_box_get_top({a}, {r})"
|
|
356
|
+
if m == "get_bottom":
|
|
357
|
+
return f"pf_box_get_bottom({a}, {r})"
|
|
358
|
+
if m == "set_left":
|
|
359
|
+
return f"pf_box_set_left({a}, {r}, (int64_t)({av[0]}))"
|
|
360
|
+
if m == "set_right":
|
|
361
|
+
return f"pf_box_set_right({a}, {r}, (int64_t)({av[0]}))"
|
|
362
|
+
if m == "set_top":
|
|
363
|
+
return f"pf_box_set_top({a}, {r}, (double)({av[0]}))"
|
|
364
|
+
if m == "set_bottom":
|
|
365
|
+
return f"pf_box_set_bottom({a}, {r}, (double)({av[0]}))"
|
|
366
|
+
if m == "set_lefttop":
|
|
367
|
+
return f"pf_box_set_lefttop({a}, {r}, (int64_t)({av[0]}), (double)({av[1]}))"
|
|
368
|
+
if m == "set_rightbottom":
|
|
369
|
+
return f"pf_box_set_rightbottom({a}, {r}, (int64_t)({av[0]}), (double)({av[1]}))"
|
|
370
|
+
if m == "set_top_left_point":
|
|
371
|
+
return f"pf_box_set_top_left_point({a}, {r}, {av[0]})"
|
|
372
|
+
if m == "set_bottom_right_point":
|
|
373
|
+
return f"pf_box_set_bottom_right_point({a}, {r}, {av[0]})"
|
|
374
|
+
if m == "set_xloc":
|
|
375
|
+
return f"pf_box_set_xloc({a}, {r}, (int64_t)({av[0]}), (int64_t)({av[1]}), {self._lower_xloc(raw[2])})"
|
|
376
|
+
if m == "copy":
|
|
377
|
+
return f"pf_box_copy({a}, {r})"
|
|
378
|
+
if m == "delete":
|
|
379
|
+
return f"pf_box_delete({a}, {r})"
|
|
380
|
+
return "0"
|
|
381
|
+
|
|
382
|
+
def _emit_label_method(self, m, a, r, av, raw) -> str:
|
|
383
|
+
if m == "get_x":
|
|
384
|
+
return f"pf_label_get_x({a}, {r})"
|
|
385
|
+
if m == "get_y":
|
|
386
|
+
return f"pf_label_get_y({a}, {r})"
|
|
387
|
+
if m == "get_text":
|
|
388
|
+
return f"pf_label_get_text({a}, {r})"
|
|
389
|
+
if m == "set_x":
|
|
390
|
+
return f"pf_label_set_x({a}, {r}, (int64_t)({av[0]}))"
|
|
391
|
+
if m == "set_y":
|
|
392
|
+
return f"pf_label_set_y({a}, {r}, (double)({av[0]}))"
|
|
393
|
+
if m == "set_xy":
|
|
394
|
+
return f"pf_label_set_xy({a}, {r}, (int64_t)({av[0]}), (double)({av[1]}))"
|
|
395
|
+
if m == "set_point":
|
|
396
|
+
return f"pf_label_set_point({a}, {r}, {av[0]})"
|
|
397
|
+
if m == "set_xloc":
|
|
398
|
+
return f"pf_label_set_xloc({a}, {r}, (int64_t)({av[0]}), {self._lower_xloc(raw[1])})"
|
|
399
|
+
if m == "set_yloc":
|
|
400
|
+
return f"pf_label_set_yloc({a}, {r}, {self._lower_yloc(raw[0])})"
|
|
401
|
+
if m == "set_text":
|
|
402
|
+
return f"pf_label_set_text({a}, {r}, {av[0]})"
|
|
403
|
+
if m == "copy":
|
|
404
|
+
return f"pf_label_copy({a}, {r})"
|
|
405
|
+
if m == "delete":
|
|
406
|
+
return f"pf_label_delete({a}, {r})"
|
|
407
|
+
return "0"
|
|
408
|
+
|
|
409
|
+
def _emit_linefill_method(self, m, a, r, av) -> str:
|
|
410
|
+
if m == "get_line1":
|
|
411
|
+
return f"pf_linefill_get_line1({a}, {r})"
|
|
412
|
+
if m == "get_line2":
|
|
413
|
+
return f"pf_linefill_get_line2({a}, {r})"
|
|
414
|
+
if m == "delete":
|
|
415
|
+
return f"pf_linefill_delete({a}, {r})"
|
|
416
|
+
return "0"
|
|
417
|
+
|
|
418
|
+
def _drawing_call_return_cpp(self, node: FuncCall) -> str | None:
|
|
419
|
+
"""C++ scalar return type for a drawing GETTER call, else None.
|
|
420
|
+
|
|
421
|
+
Covers both the namespace-functional form (``label.get_text(lb)``) and
|
|
422
|
+
the method form (``lb.get_text()``). Used by ``_infer_type`` /
|
|
423
|
+
``_type_for_decl`` so the receiving local declares as the right scalar
|
|
424
|
+
(an int64_t x-coord, a double y-coord, or a std::string label text)
|
|
425
|
+
instead of the analyzer's default double.
|
|
426
|
+
"""
|
|
427
|
+
callee = node.callee
|
|
428
|
+
if not isinstance(callee, MemberAccess):
|
|
429
|
+
return None
|
|
430
|
+
method = callee.member
|
|
431
|
+
_fn, ns = self._resolve_callee(callee)
|
|
432
|
+
from .tables import DRAWING_NS
|
|
433
|
+
dtype = None
|
|
434
|
+
if ns in DRAWING_NS:
|
|
435
|
+
dtype = ns
|
|
436
|
+
else:
|
|
437
|
+
recv_spec = self._type_spec_from_expr(callee.object)
|
|
438
|
+
if (recv_spec is not None and recv_spec.kind == "udt"
|
|
439
|
+
and recv_spec.name in DRAWING_TYPE_TO_CPP):
|
|
440
|
+
dtype = recv_spec.name
|
|
441
|
+
if dtype is None:
|
|
442
|
+
return None
|
|
443
|
+
return _DRAWING_GETTER_RET.get((dtype, method))
|
|
444
|
+
|
|
445
|
+
def _drawing_call_is_void(self, node) -> bool:
|
|
446
|
+
"""True if ``node`` is a drawing call that lowers to a VOID C++
|
|
447
|
+
expression (setters, ``delete``, visual-noop) — i.e. it cannot be used
|
|
448
|
+
as a return value / RHS. Constructors (``new``), ``copy`` and getters
|
|
449
|
+
return a value. Used by UDF last-expression lowering so a function whose
|
|
450
|
+
final statement is e.g. ``label.set_text(lb, ...)`` emits it as a
|
|
451
|
+
statement instead of ``return pf_label_set_text(...);``.
|
|
452
|
+
"""
|
|
453
|
+
if not isinstance(node, FuncCall) or not isinstance(node.callee, MemberAccess):
|
|
454
|
+
return False
|
|
455
|
+
method = node.callee.member
|
|
456
|
+
_fn, ns = self._resolve_callee(node.callee)
|
|
457
|
+
from .tables import DRAWING_NS
|
|
458
|
+
dtype = None
|
|
459
|
+
if ns in DRAWING_NS:
|
|
460
|
+
dtype = ns
|
|
461
|
+
else:
|
|
462
|
+
recv_spec = self._type_spec_from_expr(node.callee.object)
|
|
463
|
+
if (recv_spec is not None and recv_spec.kind == "udt"
|
|
464
|
+
and recv_spec.name in DRAWING_TYPE_TO_CPP):
|
|
465
|
+
dtype = recv_spec.name
|
|
466
|
+
if dtype is None:
|
|
467
|
+
return False
|
|
468
|
+
if method in ("new", "copy"):
|
|
469
|
+
return False # constructors / copy return a handle
|
|
470
|
+
if (dtype, method) in _DRAWING_GETTER_RET:
|
|
471
|
+
return False # getters return a scalar
|
|
472
|
+
geometry, noop = DRAWING_METHODS_BY_TYPE.get(dtype, (frozenset(), frozenset()))
|
|
473
|
+
# any other recognised drawing method is a setter / delete / visual-noop
|
|
474
|
+
return method in geometry or method in noop
|
|
475
|
+
|
|
476
|
+
def _call_is_void(self, node) -> bool:
|
|
477
|
+
"""True if ``node`` is a call that lowers to a VOID (or non-scalar) C++
|
|
478
|
+
expression and so cannot be used as a function's return value / RHS.
|
|
479
|
+
|
|
480
|
+
Covers drawing setters/delete/visual-noop (delegated to
|
|
481
|
+
``_drawing_call_is_void``) AND the Pine array MUTATOR methods whose C++
|
|
482
|
+
lowering is void / an iterator (``array.push/insert/clear/set/fill/
|
|
483
|
+
sort/reverse/concat/unshift``) — a function ending in
|
|
484
|
+
``array.clear(x)`` returns void in Pine, so it must emit as a statement
|
|
485
|
+
with a default return, not ``return x.clear();``.
|
|
486
|
+
"""
|
|
487
|
+
if self._drawing_call_is_void(node):
|
|
488
|
+
return True
|
|
489
|
+
if not isinstance(node, FuncCall) or not isinstance(node.callee, MemberAccess):
|
|
490
|
+
return False
|
|
491
|
+
method = node.callee.member
|
|
492
|
+
_fn, ns = self._resolve_callee(node.callee)
|
|
493
|
+
if method not in _ARRAY_VOID_METHODS:
|
|
494
|
+
return False
|
|
495
|
+
# array.<method>(arr, ...) namespace form OR arr.<method>(...) method
|
|
496
|
+
# form on a std::vector receiver.
|
|
497
|
+
if ns == "array":
|
|
498
|
+
return True
|
|
499
|
+
recv_spec = self._type_spec_from_expr(node.callee.object)
|
|
500
|
+
return recv_spec is not None and recv_spec.kind == "array"
|
|
501
|
+
|
|
502
|
+
# ------------------------------------------------------------------
|
|
503
|
+
# _uses_drawing detection + arena caps
|
|
504
|
+
# ------------------------------------------------------------------
|
|
505
|
+
def _spec_mentions_drawing(self, spec) -> bool:
|
|
506
|
+
if spec is None:
|
|
507
|
+
return False
|
|
508
|
+
if spec.kind == "udt" and spec.name in DRAWING_TYPE_TO_CPP:
|
|
509
|
+
return True
|
|
510
|
+
if spec.kind == "array":
|
|
511
|
+
return self._spec_mentions_drawing(spec.element)
|
|
512
|
+
if spec.kind == "map":
|
|
513
|
+
return self._spec_mentions_drawing(spec.key) or self._spec_mentions_drawing(spec.value)
|
|
514
|
+
if spec.kind == "matrix":
|
|
515
|
+
return self._spec_mentions_drawing(spec.element)
|
|
516
|
+
return False
|
|
517
|
+
|
|
518
|
+
def _detect_drawing_usage(self) -> bool:
|
|
519
|
+
"""True iff emitted C++ needs pineforge/drawing.hpp + the arenas.
|
|
520
|
+
|
|
521
|
+
Mirrors ``_detect_matrix_usage``: any var/field/array of a drawing type
|
|
522
|
+
OR any line/box/label/linefill namespace call OR any chart.point call.
|
|
523
|
+
"""
|
|
524
|
+
for t in self._udt_var_types.values():
|
|
525
|
+
if t in DRAWING_TYPE_TO_CPP:
|
|
526
|
+
return True
|
|
527
|
+
for spec in self._collection_types.values():
|
|
528
|
+
if self._spec_mentions_drawing(spec):
|
|
529
|
+
return True
|
|
530
|
+
for fields in self._udt_field_type_specs.values():
|
|
531
|
+
for spec in fields.values():
|
|
532
|
+
if self._spec_mentions_drawing(spec):
|
|
533
|
+
return True
|
|
534
|
+
for _tname, _fields in self._udt_defs.items():
|
|
535
|
+
for f in _fields:
|
|
536
|
+
if self._spec_mentions_drawing(self._type_spec_from_hint_name(f.type_name)):
|
|
537
|
+
return True
|
|
538
|
+
from .tables import DRAWING_NS
|
|
539
|
+
for node in self._walk_ast(self.ctx.ast):
|
|
540
|
+
if isinstance(node, FuncCall):
|
|
541
|
+
_fn, ns = self._resolve_callee(node.callee)
|
|
542
|
+
if ns in DRAWING_NS:
|
|
543
|
+
return True
|
|
544
|
+
if self._is_chart_point_callee(node.callee):
|
|
545
|
+
return True
|
|
546
|
+
return False
|
|
547
|
+
|
|
548
|
+
def _compute_drawing_caps(self) -> dict:
|
|
549
|
+
"""Per-type arena capacity from the strategy() header (default 50)."""
|
|
550
|
+
from ..ast_nodes import StrategyDecl
|
|
551
|
+
caps = {"line": 50, "box": 50, "label": 50, "linefill": 50}
|
|
552
|
+
header_field = {"line": "max_lines_count", "box": "max_boxes_count",
|
|
553
|
+
"label": "max_labels_count"}
|
|
554
|
+
for node in self._walk_ast(self.ctx.ast):
|
|
555
|
+
if isinstance(node, StrategyDecl):
|
|
556
|
+
for key, field in header_field.items():
|
|
557
|
+
v = self._int_literal_value(node.kwargs.get(field))
|
|
558
|
+
if v is not None and v > 0:
|
|
559
|
+
caps[key] = v
|
|
560
|
+
return caps
|