@pineforge/codegen-pyodide 0.9.0 → 0.10.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/__init__.py +3 -0
- package/pineforge_codegen/analyzer/base.py +3999 -140
- package/pineforge_codegen/analyzer/call_handlers.py +662 -48
- package/pineforge_codegen/analyzer/contracts.py +104 -7
- package/pineforge_codegen/analyzer/types.py +951 -14
- package/pineforge_codegen/codegen/base.py +2359 -249
- package/pineforge_codegen/codegen/drawing.py +94 -14
- package/pineforge_codegen/codegen/emit_top.py +732 -172
- package/pineforge_codegen/codegen/security.py +1456 -150
- package/pineforge_codegen/codegen/ta.py +435 -99
- package/pineforge_codegen/codegen/tables.py +224 -77
- package/pineforge_codegen/codegen/types.py +1196 -215
- package/pineforge_codegen/codegen/visit_call.py +1168 -99
- package/pineforge_codegen/codegen/visit_expr.py +354 -33
- package/pineforge_codegen/codegen/visit_stmt.py +1144 -200
- package/pineforge_codegen/finite_ta_length.py +798 -0
- package/pineforge_codegen/method_binding.py +163 -0
- package/pineforge_codegen/parser.py +48 -9
- package/pineforge_codegen/signatures.py +4 -4
- package/pineforge_codegen/support_checker.py +194 -19
- package/pineforge_codegen/symbols.py +65 -0
- package/pineforge_codegen-0.10.1.tar.gz +0 -0
- package/release.json +2 -2
- package/tables.json +9 -11
- package/pineforge_codegen-0.9.0.tar.gz +0 -0
|
@@ -37,8 +37,6 @@ Mixin contract — host class must provide the following attributes
|
|
|
37
37
|
``_visit_var_decl`` populates it from inferred specs.
|
|
38
38
|
- ``self._active_var_remap`` (``dict[str, str]``): per-call-site
|
|
39
39
|
rename map for cloned function-local var/series names.
|
|
40
|
-
- ``self._in_ta_func_variant`` (``bool``): set during per-call-site
|
|
41
|
-
function emission; gates the TA-hoist branch in ``_visit_if``.
|
|
42
40
|
- ``self._current_loop_vars`` (``set[str]``): for-in iterator names;
|
|
43
41
|
saved/restored around ``_visit_for_in`` bodies so member-access
|
|
44
42
|
resolution can distinguish iterators from enum constants.
|
|
@@ -58,7 +56,7 @@ Sibling-mixin methods consumed via ``self``:
|
|
|
58
56
|
``_map_spec_for_name``.
|
|
59
57
|
- ``TaSiteHelper`` (``codegen/ta.py``): ``_get_ta_site``,
|
|
60
58
|
``_ta_member_name``, ``_ta_compute_args_for_site``,
|
|
61
|
-
``_ta_name_from_site
|
|
59
|
+
``_ta_name_from_site``.
|
|
62
60
|
- ``InputHelper`` (``codegen/input.py``): ``_is_input_call``,
|
|
63
61
|
``_get_input_default``, ``_get_input_title``,
|
|
64
62
|
``_input_type_to_getter``,
|
|
@@ -93,21 +91,24 @@ from ..ast_nodes import (
|
|
|
93
91
|
MethodDef,
|
|
94
92
|
StrategyDecl,
|
|
95
93
|
SwitchStmt,
|
|
94
|
+
Ternary,
|
|
96
95
|
TupleAssign,
|
|
96
|
+
TupleLiteral,
|
|
97
97
|
TypeDecl,
|
|
98
98
|
VarDecl,
|
|
99
99
|
WhileStmt,
|
|
100
100
|
)
|
|
101
|
-
from ..symbols import TypeSpec
|
|
101
|
+
from ..symbols import PineType, TypeSpec, method_receiver_type_name
|
|
102
102
|
from .tables import (
|
|
103
103
|
ARRAY_NEW_CTORS,
|
|
104
|
+
DRAWING_TYPE_TO_CPP,
|
|
104
105
|
TA_RETURNS_BOOL,
|
|
105
106
|
TA_TUPLE_FIELDS,
|
|
106
107
|
MATRIX_RETURNING_METHODS,
|
|
107
108
|
)
|
|
108
109
|
|
|
109
|
-
# Sentinel for "no block-scoped
|
|
110
|
-
#
|
|
110
|
+
# Sentinel for "no block-scoped overlay was activated" so an empty saved map
|
|
111
|
+
# is still distinguishable from the no-op case.
|
|
111
112
|
_NO_BLOCK_REMAP = object()
|
|
112
113
|
|
|
113
114
|
|
|
@@ -137,11 +138,191 @@ class StmtVisitor:
|
|
|
137
138
|
if isinstance(node, MethodDef):
|
|
138
139
|
return # handled as class method via FuncInfo
|
|
139
140
|
if isinstance(node, VarDecl):
|
|
140
|
-
|
|
141
|
+
is_callable_binding = (
|
|
142
|
+
getattr(self, "_active_func_name", None) is not None
|
|
143
|
+
and id(node) in self._callable_collection_bindings
|
|
144
|
+
)
|
|
145
|
+
activation_spec = (
|
|
146
|
+
self._callable_collection_bindings.get(id(node))
|
|
147
|
+
if is_callable_binding
|
|
148
|
+
else None
|
|
149
|
+
)
|
|
150
|
+
if is_callable_binding and activation_spec is None:
|
|
151
|
+
inferred_spec = self._type_spec_from_expr(node.value)
|
|
152
|
+
if (inferred_spec is not None
|
|
153
|
+
and inferred_spec.kind in {"array", "map", "matrix"}):
|
|
154
|
+
activation_spec = inferred_spec
|
|
155
|
+
previous_pending_alias = self._pending_decl_outer_alias
|
|
156
|
+
if (is_callable_binding
|
|
157
|
+
and not node.is_var
|
|
158
|
+
and not node.is_varip
|
|
159
|
+
and self._collection_spec_for_name(node.name) is not None
|
|
160
|
+
and any(
|
|
161
|
+
isinstance(part, Identifier) and part.name == node.name
|
|
162
|
+
for part in self._walk_ast(node.value)
|
|
163
|
+
)):
|
|
164
|
+
safe_name = self._safe_name(node.name)
|
|
165
|
+
if (node.name in self._global_collection_types
|
|
166
|
+
and node.name not in self._current_func_param_specs
|
|
167
|
+
and node.name not in self._current_func_collection_specs
|
|
168
|
+
and node.name not in self._current_loop_var_specs):
|
|
169
|
+
outer_expr = f"this->{safe_name}"
|
|
170
|
+
else:
|
|
171
|
+
outer_expr = self._active_var_remap.get(safe_name, safe_name)
|
|
172
|
+
occupied_alias_names = (
|
|
173
|
+
set(self._all_bound_names)
|
|
174
|
+
| set(self._collection_shadow_tmp_names)
|
|
175
|
+
| set(self._current_func_param_types)
|
|
176
|
+
| {
|
|
177
|
+
self._safe_name(param)
|
|
178
|
+
for param in self._current_func_param_types
|
|
179
|
+
}
|
|
180
|
+
)
|
|
181
|
+
while True:
|
|
182
|
+
alias = (
|
|
183
|
+
f"_pf_outer_{safe_name}_"
|
|
184
|
+
f"{self._collection_shadow_tmp_counter}"
|
|
185
|
+
)
|
|
186
|
+
self._collection_shadow_tmp_counter += 1
|
|
187
|
+
if alias not in occupied_alias_names:
|
|
188
|
+
break
|
|
189
|
+
self._collection_shadow_tmp_names.add(alias)
|
|
190
|
+
lines.append(f"{pad}auto& {alias} = {outer_expr};")
|
|
191
|
+
self._pending_decl_outer_alias = {
|
|
192
|
+
**previous_pending_alias,
|
|
193
|
+
node.name: alias,
|
|
194
|
+
}
|
|
195
|
+
try:
|
|
196
|
+
self._visit_var_decl(node, lines, pad)
|
|
197
|
+
finally:
|
|
198
|
+
self._pending_decl_outer_alias = previous_pending_alias
|
|
199
|
+
if is_callable_binding:
|
|
200
|
+
self._activate_callable_collection_binding(
|
|
201
|
+
node.name,
|
|
202
|
+
activation_spec,
|
|
203
|
+
)
|
|
204
|
+
metadata = getattr(
|
|
205
|
+
self.ctx, "var_member_metadata_by_node", {}
|
|
206
|
+
).get(id(node))
|
|
207
|
+
if metadata is not None:
|
|
208
|
+
member_name = metadata[1]
|
|
209
|
+
if member_name != node.name:
|
|
210
|
+
# The declaration RHS was emitted against the inherited
|
|
211
|
+
# binding. Only now activate the collision-safe member for
|
|
212
|
+
# later statements in this exact lexical block.
|
|
213
|
+
exact_storage = self._safe_name(member_name)
|
|
214
|
+
# A callable variant may already map the exact member to a
|
|
215
|
+
# per-call-site clone (``x__blk1`` -> ``x__blk1_cs1``).
|
|
216
|
+
# Preserve that outer clone mapping when exposing the raw
|
|
217
|
+
# lexical spelling after the declaration.
|
|
218
|
+
exact_storage = self._active_var_remap.get(
|
|
219
|
+
exact_storage, exact_storage
|
|
220
|
+
)
|
|
221
|
+
self._active_var_remap = dict(self._active_var_remap)
|
|
222
|
+
self._active_var_remap[self._safe_name(node.name)] = (
|
|
223
|
+
exact_storage
|
|
224
|
+
)
|
|
225
|
+
drawing_info = self._drawing_var_decl_info_by_node.get(id(node))
|
|
226
|
+
if (drawing_info is not None
|
|
227
|
+
and drawing_info.get("is_callable_scoped")):
|
|
228
|
+
raw_safe = self._safe_name(node.name)
|
|
229
|
+
storage = self._active_var_remap.get(
|
|
230
|
+
raw_safe, self._safe_name(member_name)
|
|
231
|
+
)
|
|
232
|
+
if storage.startswith("this->"):
|
|
233
|
+
storage = storage[len("this->"):]
|
|
234
|
+
self._active_var_remap = dict(self._active_var_remap)
|
|
235
|
+
self._active_var_remap[raw_safe] = f"this->{storage}"
|
|
236
|
+
self._lexical_series_bindings[node.name] = (
|
|
237
|
+
self._safe_name(member_name)
|
|
238
|
+
in self._series_var_member_names
|
|
239
|
+
)
|
|
240
|
+
else:
|
|
241
|
+
self._lexical_series_bindings[node.name] = (
|
|
242
|
+
self._decl_binding_is_series(id(node), node.name)
|
|
243
|
+
)
|
|
244
|
+
if id(node) not in self._direct_program_var_decl_nodes:
|
|
245
|
+
self._lexical_known_var_tombstones.add(node.name)
|
|
246
|
+
decl_spec = getattr(
|
|
247
|
+
self.ctx, "var_member_type_specs_by_node", {}
|
|
248
|
+
).get(id(node))
|
|
249
|
+
if decl_spec is None:
|
|
250
|
+
decl_spec = (
|
|
251
|
+
self._type_spec_from_hint_name(node.type_hint)
|
|
252
|
+
if node.type_hint
|
|
253
|
+
else self._type_spec_from_expr(node.value)
|
|
254
|
+
)
|
|
255
|
+
self._lexical_drawing_types[node.name] = (
|
|
256
|
+
DRAWING_TYPE_TO_CPP.get(decl_spec.name)
|
|
257
|
+
if decl_spec is not None and decl_spec.kind == "udt"
|
|
258
|
+
else None
|
|
259
|
+
)
|
|
260
|
+
self._lexical_udt_types[node.name] = (
|
|
261
|
+
decl_spec.name
|
|
262
|
+
if (decl_spec is not None
|
|
263
|
+
and decl_spec.kind == "udt"
|
|
264
|
+
and decl_spec.name in self._udt_defs)
|
|
265
|
+
else None
|
|
266
|
+
)
|
|
267
|
+
if (
|
|
268
|
+
node.name == "map"
|
|
269
|
+
and getattr(self, "_block_map_visibility_depth", 0) > 0
|
|
270
|
+
):
|
|
271
|
+
# The declaration RHS was emitted against the outer lexical
|
|
272
|
+
# state. Only subsequent statements in this exact block see
|
|
273
|
+
# the new ``map`` value; block pop restores sibling/outside
|
|
274
|
+
# visibility.
|
|
275
|
+
self._block_map_binding_visible = True
|
|
141
276
|
elif isinstance(node, Assignment):
|
|
142
277
|
self._visit_assignment(node, lines, pad)
|
|
143
278
|
elif isinstance(node, TupleAssign):
|
|
144
279
|
self._visit_tuple_assign(node, lines, pad)
|
|
280
|
+
tuple_cpp_types = self._tuple_binding_cpp_types(node)
|
|
281
|
+
if getattr(self, "_active_func_name", None) is not None:
|
|
282
|
+
for name in node.names:
|
|
283
|
+
if name and name != "_":
|
|
284
|
+
self._activate_callable_collection_binding(name, None)
|
|
285
|
+
scalar_names = [
|
|
286
|
+
name
|
|
287
|
+
for name in node.names
|
|
288
|
+
if name
|
|
289
|
+
and name != "_"
|
|
290
|
+
and not self._decl_binding_is_series(id(node), name)
|
|
291
|
+
]
|
|
292
|
+
if any(
|
|
293
|
+
self._safe_name(name) in self._active_var_remap
|
|
294
|
+
for name in scalar_names
|
|
295
|
+
):
|
|
296
|
+
# Tuple declarations are lexical bindings. A scalar element
|
|
297
|
+
# must shadow any same-spelled Series storage inherited from a
|
|
298
|
+
# broad callable call-site remap; otherwise a later read can
|
|
299
|
+
# silently resolve to an unrelated ``x_csN`` member even
|
|
300
|
+
# though the structured binding declared a local ``x``.
|
|
301
|
+
self._active_var_remap = dict(self._active_var_remap)
|
|
302
|
+
for name in scalar_names:
|
|
303
|
+
self._active_var_remap.pop(self._safe_name(name), None)
|
|
304
|
+
for index, name in enumerate(node.names):
|
|
305
|
+
if name and name != "_":
|
|
306
|
+
# A supported tuple destructure creates fresh lexical
|
|
307
|
+
# bindings. Tuple elements are primitive on the current
|
|
308
|
+
# supported surface, so install an explicit tombstone: an
|
|
309
|
+
# unrelated same-named global/callable UDT must not target-
|
|
310
|
+
# type a later ``name := na`` as ``State{}``.
|
|
311
|
+
self._lexical_udt_types[name] = None
|
|
312
|
+
if (getattr(self, "_active_func_name", None) is not None
|
|
313
|
+
and not self._decl_binding_is_series(
|
|
314
|
+
id(node), name
|
|
315
|
+
)):
|
|
316
|
+
self._current_func_local_types[name] = (
|
|
317
|
+
tuple_cpp_types[index]
|
|
318
|
+
if index < len(tuple_cpp_types)
|
|
319
|
+
else "double"
|
|
320
|
+
)
|
|
321
|
+
self._lexical_series_bindings[name] = (
|
|
322
|
+
self._decl_binding_is_series(id(node), name)
|
|
323
|
+
)
|
|
324
|
+
if id(node) not in self._direct_program_tuple_decl_nodes:
|
|
325
|
+
self._lexical_known_var_tombstones.add(name)
|
|
145
326
|
elif isinstance(node, IfStmt):
|
|
146
327
|
self._visit_if(node, lines, indent)
|
|
147
328
|
elif isinstance(node, ForStmt):
|
|
@@ -234,7 +415,8 @@ class StmtVisitor:
|
|
|
234
415
|
# m.concat(other, ...) — receiver is callee.object
|
|
235
416
|
if isinstance(callee.object, Identifier):
|
|
236
417
|
recv = callee.object.name
|
|
237
|
-
|
|
418
|
+
recv_spec = self._collection_spec_for_name(recv)
|
|
419
|
+
if recv_spec is not None and recv_spec.kind == "matrix":
|
|
238
420
|
return recv
|
|
239
421
|
# matrix.concat(m, other, ...) — receiver is first arg
|
|
240
422
|
if (isinstance(callee.object, Identifier)
|
|
@@ -242,21 +424,155 @@ class StmtVisitor:
|
|
|
242
424
|
and expr.args
|
|
243
425
|
and isinstance(expr.args[0], Identifier)):
|
|
244
426
|
recv = expr.args[0].name
|
|
245
|
-
|
|
427
|
+
recv_spec = self._collection_spec_for_name(recv)
|
|
428
|
+
if recv_spec is not None and recv_spec.kind == "matrix":
|
|
246
429
|
return recv
|
|
247
430
|
return None
|
|
248
431
|
|
|
432
|
+
def _nullable_collection_target_cpp_type(
|
|
433
|
+
self,
|
|
434
|
+
*,
|
|
435
|
+
name: str | None = None,
|
|
436
|
+
target_node=None,
|
|
437
|
+
type_hint: str | None = None,
|
|
438
|
+
) -> str | None:
|
|
439
|
+
"""Return the exact nullable collection type for an RHS target.
|
|
440
|
+
|
|
441
|
+
Declarations need the explicit hint before their lexical collection
|
|
442
|
+
binding is activated; reassignments can use the active name registry,
|
|
443
|
+
while UDT fields are resolved from the target expression itself.
|
|
444
|
+
Arrays are deliberately excluded until their runtime representation
|
|
445
|
+
can distinguish ``na`` from a valid empty ID.
|
|
446
|
+
"""
|
|
447
|
+
spec = self._type_spec_from_hint_name(type_hint) if type_hint else None
|
|
448
|
+
if spec is None and target_node is not None:
|
|
449
|
+
spec = self._type_spec_from_expr(target_node)
|
|
450
|
+
if spec is None and name is not None:
|
|
451
|
+
spec = self._collection_spec_for_name(name)
|
|
452
|
+
if spec is None or spec.kind not in {"map", "matrix"}:
|
|
453
|
+
return None
|
|
454
|
+
return self._type_spec_to_cpp(spec)
|
|
455
|
+
|
|
249
456
|
def _visit_var_decl(self, node: VarDecl, lines: list[str], pad: str) -> None:
|
|
250
|
-
|
|
457
|
+
member_meta = getattr(
|
|
458
|
+
self.ctx, "var_member_metadata_by_node", {}
|
|
459
|
+
).get(id(node))
|
|
460
|
+
if member_meta is not None:
|
|
461
|
+
declaration_is_series = (
|
|
462
|
+
self._safe_name(member_meta[1])
|
|
463
|
+
in self._series_var_member_names
|
|
464
|
+
)
|
|
465
|
+
else:
|
|
466
|
+
declaration_is_series = (
|
|
467
|
+
self._decl_binding_is_series(id(node), node.name)
|
|
468
|
+
)
|
|
469
|
+
# Primitive runtime var/varip initializers execute at the declaration
|
|
470
|
+
# site under a dedicated once flag. This preserves source order (a
|
|
471
|
+
# preceding input/plain/TA declaration is already available) and Pine's
|
|
472
|
+
# first-entry semantics for declarations nested in conditional blocks.
|
|
473
|
+
# Series, aggregates, constructor constants, and function-local vars
|
|
474
|
+
# retain their specialized initialization paths.
|
|
251
475
|
if node.is_var or node.is_varip:
|
|
476
|
+
info = self._runtime_scalar_var_init_by_node.get(id(node))
|
|
477
|
+
if info is not None:
|
|
478
|
+
member_name = info["member_name"]
|
|
479
|
+
target = self._safe_name(member_name)
|
|
480
|
+
if self._active_var_remap and target in self._active_var_remap:
|
|
481
|
+
target = self._active_var_remap[target]
|
|
482
|
+
flag = self._runtime_var_init_flags.get(
|
|
483
|
+
(info["node_id"], target),
|
|
484
|
+
info["flag"],
|
|
485
|
+
)
|
|
486
|
+
target_expr = (
|
|
487
|
+
f"this->{target}"
|
|
488
|
+
if info.get("is_callable_scoped")
|
|
489
|
+
else target
|
|
490
|
+
)
|
|
491
|
+
flag_expr = (
|
|
492
|
+
f"this->{flag}"
|
|
493
|
+
if info.get("is_callable_scoped")
|
|
494
|
+
else flag
|
|
495
|
+
)
|
|
496
|
+
previous_input_name = self._current_input_var_name
|
|
497
|
+
self._current_input_var_name = node.name
|
|
498
|
+
try:
|
|
499
|
+
type_spec = info.get("type_spec")
|
|
500
|
+
target_cpp_type = info.get("drawing_cpp")
|
|
501
|
+
if (
|
|
502
|
+
target_cpp_type is None
|
|
503
|
+
and type_spec is not None
|
|
504
|
+
and type_spec.kind in {"map", "matrix"}
|
|
505
|
+
):
|
|
506
|
+
target_cpp_type = self._type_spec_to_cpp(type_spec)
|
|
507
|
+
if (
|
|
508
|
+
target_cpp_type is not None
|
|
509
|
+
and type_spec is not None
|
|
510
|
+
and type_spec.kind in {"map", "matrix"}
|
|
511
|
+
and isinstance(node.value, (IfStmt, SwitchStmt))
|
|
512
|
+
):
|
|
513
|
+
lines.append(f"{pad}if (!{flag_expr}) {{")
|
|
514
|
+
self._visit_if_switch_expr(
|
|
515
|
+
node.value,
|
|
516
|
+
target_expr,
|
|
517
|
+
lines,
|
|
518
|
+
len(pad) // 4 + 1,
|
|
519
|
+
target_cpp_type=target_cpp_type,
|
|
520
|
+
)
|
|
521
|
+
# The flag belongs after the complete selection. If it
|
|
522
|
+
# were emitted in an individual branch, unmatched paths
|
|
523
|
+
# or later arms could retry a Pine ``var`` initializer,
|
|
524
|
+
# violating first-reach / one-time semantics.
|
|
525
|
+
lines.append(f"{pad} {flag_expr} = true;")
|
|
526
|
+
lines.append(f"{pad}}}")
|
|
527
|
+
return
|
|
528
|
+
if target_cpp_type is not None:
|
|
529
|
+
init_cpp = self._visit_rhs_value(
|
|
530
|
+
node.value,
|
|
531
|
+
member_name,
|
|
532
|
+
target_cpp_type=target_cpp_type,
|
|
533
|
+
)
|
|
534
|
+
else:
|
|
535
|
+
init_cpp = self._visit_expr(node.value)
|
|
536
|
+
finally:
|
|
537
|
+
self._current_input_var_name = previous_input_name
|
|
538
|
+
if info.get("drawing_cpp") is None:
|
|
539
|
+
init_cpp = self._typed_na_init(
|
|
540
|
+
init_cpp, member_name, info["ptype"]
|
|
541
|
+
)
|
|
542
|
+
lines.append(f"{pad}if (!{flag_expr}) {{")
|
|
543
|
+
# A history-referenced persistent primitive is a Series<T> too,
|
|
544
|
+
# not just a drawing handle. The per-bar carry has already
|
|
545
|
+
# advanced it before this declaration-site one-shot runs, so
|
|
546
|
+
# replace the current slot rather than assigning a scalar to
|
|
547
|
+
# the Series object (or pushing a duplicate bar).
|
|
548
|
+
if info.get("is_series"):
|
|
549
|
+
lines.append(f"{pad} {target_expr}.update({init_cpp});")
|
|
550
|
+
else:
|
|
551
|
+
lines.append(f"{pad} {target_expr} = {init_cpp};")
|
|
552
|
+
lines.append(f"{pad} {flag_expr} = true;")
|
|
553
|
+
lines.append(f"{pad}}}")
|
|
252
554
|
return
|
|
253
555
|
|
|
254
556
|
safe = self._safe_name(node.name)
|
|
255
557
|
# Apply per-call-site var remap (for function-local vars)
|
|
256
558
|
if self._active_var_remap and safe in self._active_var_remap:
|
|
257
559
|
safe = self._active_var_remap[safe]
|
|
258
|
-
#
|
|
259
|
-
|
|
560
|
+
# Only a declaration in the script body can bind a hoisted global
|
|
561
|
+
# member. A callable-local declaration may legally shadow that global
|
|
562
|
+
# with the same raw name; treating it as the member emits an assignment
|
|
563
|
+
# into unrelated storage (and can assign a scalar into Series<T> when
|
|
564
|
+
# the global was promoted for an indirect history call).
|
|
565
|
+
is_global_member = (
|
|
566
|
+
getattr(self, "_active_func_name", None) is None
|
|
567
|
+
and node.name in self._global_member_vars
|
|
568
|
+
and (
|
|
569
|
+
id(node) in self._ordinary_global_var_decl_nodes
|
|
570
|
+
or (
|
|
571
|
+
node.name not in self._direct_program_binding_names
|
|
572
|
+
and node.name not in self._callable_state_raw_names
|
|
573
|
+
)
|
|
574
|
+
)
|
|
575
|
+
)
|
|
260
576
|
|
|
261
577
|
def remember_local_type(cpp_type: str | None) -> None:
|
|
262
578
|
if cpp_type and not is_global_member:
|
|
@@ -287,7 +603,7 @@ class StmtVisitor:
|
|
|
287
603
|
self._enforce_enum_declared_before_input_enum(node.value)
|
|
288
604
|
title = self._get_input_title(node.value, var_name=node.name)
|
|
289
605
|
cpp_val = self._render_input_value(node.value, func_name_i, namespace_i, title)
|
|
290
|
-
if
|
|
606
|
+
if declaration_is_series:
|
|
291
607
|
self._emit_history_series_write(lines, pad, safe, cpp_val)
|
|
292
608
|
elif is_global_member:
|
|
293
609
|
lines.append(f"{pad}{safe} = {cpp_val};")
|
|
@@ -301,10 +617,16 @@ class StmtVisitor:
|
|
|
301
617
|
if isinstance(node.value, FuncCall):
|
|
302
618
|
func_name, namespace = self._resolve_callee(node.value.callee)
|
|
303
619
|
if namespace == "array" and func_name in ARRAY_NEW_CTORS | {"new", "from", "copy", "slice"}:
|
|
620
|
+
captured = self._callable_collection_bindings.get(id(node))
|
|
621
|
+
spec = (
|
|
622
|
+
captured
|
|
623
|
+
if captured is not None and captured.kind == "array"
|
|
624
|
+
else self._type_spec_from_expr(node.value)
|
|
625
|
+
or self._array_spec_for_name(node.name)
|
|
626
|
+
)
|
|
627
|
+
init = self._visit_expr(node.value)
|
|
304
628
|
self._array_vars.add(node.name)
|
|
305
|
-
spec = self._type_spec_from_expr(node.value) or self._array_spec_for_name(node.name)
|
|
306
629
|
self._collection_types.setdefault(node.name, spec)
|
|
307
|
-
init = self._visit_expr(node.value)
|
|
308
630
|
cpp_type = self._type_spec_to_cpp(spec)
|
|
309
631
|
if is_global_member:
|
|
310
632
|
lines.append(f"{pad}{safe} = {init};")
|
|
@@ -318,9 +640,13 @@ class StmtVisitor:
|
|
|
318
640
|
if namespace == "matrix" and func_name == "new":
|
|
319
641
|
targs = self._template_args_from_call(node.value) if hasattr(node.value, "annotations") else []
|
|
320
642
|
elem_spec = self._type_spec_from_hint_name(targs[0]) if targs else TypeSpec.primitive("float")
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
643
|
+
captured = self._callable_collection_bindings.get(id(node))
|
|
644
|
+
spec = (
|
|
645
|
+
captured
|
|
646
|
+
if captured is not None and captured.kind == "matrix"
|
|
647
|
+
else TypeSpec.matrix(elem_spec)
|
|
648
|
+
)
|
|
649
|
+
elem_spec = spec.element or elem_spec
|
|
324
650
|
cpp_type = self._type_spec_to_cpp(spec)
|
|
325
651
|
if len(node.value.args) >= 2:
|
|
326
652
|
r = self._visit_expr(node.value.args[0])
|
|
@@ -329,6 +655,8 @@ class StmtVisitor:
|
|
|
329
655
|
init = f"{cpp_type}::new_({r}, {c}, {v})"
|
|
330
656
|
else:
|
|
331
657
|
init = f"{cpp_type}::new_(0, 0, {self._default_for_spec(elem_spec)})"
|
|
658
|
+
self._matrix_specs[node.name] = spec
|
|
659
|
+
self._collection_types[node.name] = spec
|
|
332
660
|
if is_global_member:
|
|
333
661
|
lines.append(f"{pad}{safe} = {init};")
|
|
334
662
|
else:
|
|
@@ -341,12 +669,20 @@ class StmtVisitor:
|
|
|
341
669
|
# rejects ``double = PineMatrix``. The RHS expression itself is
|
|
342
670
|
# already lowered to the right ``m.inv()`` form by visit_call.
|
|
343
671
|
if namespace == "matrix" and func_name in MATRIX_RETURNING_METHODS:
|
|
344
|
-
|
|
345
|
-
|
|
672
|
+
recv_name = self._extract_receiver_name(node.value)
|
|
673
|
+
recv_spec = (
|
|
674
|
+
self._collection_spec_for_name(recv_name)
|
|
675
|
+
if recv_name is not None
|
|
676
|
+
else None
|
|
677
|
+
)
|
|
678
|
+
if recv_spec is None or recv_spec.kind != "matrix":
|
|
346
679
|
recv_spec = TypeSpec.matrix(TypeSpec.primitive("float"))
|
|
680
|
+
captured = self._callable_collection_bindings.get(id(node))
|
|
681
|
+
if captured is not None and captured.kind == "matrix":
|
|
682
|
+
recv_spec = captured
|
|
683
|
+
init = self._visit_expr(node.value)
|
|
347
684
|
self._matrix_specs[node.name] = recv_spec
|
|
348
685
|
self._collection_types[node.name] = recv_spec
|
|
349
|
-
init = self._visit_expr(node.value)
|
|
350
686
|
cpp_type = self._type_spec_to_cpp(recv_spec)
|
|
351
687
|
if is_global_member:
|
|
352
688
|
lines.append(f"{pad}{safe} = {init};")
|
|
@@ -354,14 +690,21 @@ class StmtVisitor:
|
|
|
354
690
|
lines.append(f"{pad}{cpp_type} {safe} = {init};")
|
|
355
691
|
return
|
|
356
692
|
if namespace == "map" and func_name == "new":
|
|
693
|
+
captured = self._callable_collection_bindings.get(id(node))
|
|
694
|
+
spec = (
|
|
695
|
+
captured
|
|
696
|
+
if captured is not None and captured.kind == "map"
|
|
697
|
+
else self._type_spec_from_expr(node.value)
|
|
698
|
+
or self._map_spec_for_name(node.name)
|
|
699
|
+
)
|
|
357
700
|
self._map_vars.add(node.name)
|
|
358
|
-
spec = self._type_spec_from_expr(node.value) or self._map_spec_for_name(node.name)
|
|
359
701
|
self._collection_types.setdefault(node.name, spec)
|
|
360
702
|
cpp_type = self._type_spec_to_cpp(spec)
|
|
703
|
+
init = f"{cpp_type}::new_()"
|
|
361
704
|
if is_global_member:
|
|
362
|
-
lines.append(f"{pad}{safe} = {
|
|
705
|
+
lines.append(f"{pad}{safe} = {init};")
|
|
363
706
|
else:
|
|
364
|
-
lines.append(f"{pad}{cpp_type} {safe};")
|
|
707
|
+
lines.append(f"{pad}{cpp_type} {safe} = {init};")
|
|
365
708
|
return
|
|
366
709
|
|
|
367
710
|
# Visual/drawing function assignments (line.new, label.new, box.new,
|
|
@@ -389,7 +732,7 @@ class StmtVisitor:
|
|
|
389
732
|
f"(history_advances_new_bar() ? {ta_name}.compute({compute_args}) "
|
|
390
733
|
f": {ta_name}.recompute({compute_args}))"
|
|
391
734
|
)
|
|
392
|
-
if
|
|
735
|
+
if declaration_is_series:
|
|
393
736
|
self._emit_history_series_write(lines, pad, safe, ta_expr)
|
|
394
737
|
elif is_global_member:
|
|
395
738
|
lines.append(f"{pad}{safe} = {ta_expr};")
|
|
@@ -398,41 +741,58 @@ class StmtVisitor:
|
|
|
398
741
|
return
|
|
399
742
|
|
|
400
743
|
# Non-var series variable — push instead of declare
|
|
401
|
-
if
|
|
402
|
-
|
|
744
|
+
if declaration_is_series:
|
|
745
|
+
target_cpp_type = self._type_for_decl(node)
|
|
746
|
+
cpp_val = self._visit_rhs_value(
|
|
747
|
+
node.value,
|
|
748
|
+
node.name,
|
|
749
|
+
target_cpp_type=(
|
|
750
|
+
target_cpp_type
|
|
751
|
+
if target_cpp_type in DRAWING_TYPE_TO_CPP.values()
|
|
752
|
+
else None
|
|
753
|
+
),
|
|
754
|
+
)
|
|
403
755
|
self._emit_history_series_write(lines, pad, safe, cpp_val)
|
|
404
756
|
return
|
|
405
757
|
|
|
406
758
|
# If/switch expression: x = if cond ... else ...
|
|
407
759
|
if isinstance(node.value, (IfStmt, SwitchStmt)):
|
|
760
|
+
cpp_type = self._type_for_decl(node) if not is_global_member else None
|
|
761
|
+
selection_cpp_type = (
|
|
762
|
+
cpp_type
|
|
763
|
+
if (cpp_type is not None
|
|
764
|
+
and (self._is_nullable_collection_cpp_type(cpp_type)
|
|
765
|
+
or cpp_type in DRAWING_TYPE_TO_CPP.values()
|
|
766
|
+
or cpp_type in self._udt_defs))
|
|
767
|
+
else self._nullable_collection_target_cpp_type(
|
|
768
|
+
name=node.name,
|
|
769
|
+
type_hint=node.type_hint,
|
|
770
|
+
)
|
|
771
|
+
)
|
|
772
|
+
if selection_cpp_type is None:
|
|
773
|
+
selection_cpp_type = self._udt_target_cpp_type(
|
|
774
|
+
target_name=node.name,
|
|
775
|
+
type_hint=node.type_hint,
|
|
776
|
+
)
|
|
777
|
+
if selection_cpp_type is None:
|
|
778
|
+
selection_cpp_type = self._drawing_target_cpp_type(
|
|
779
|
+
node.name,
|
|
780
|
+
cpp_type,
|
|
781
|
+
)
|
|
408
782
|
if not is_global_member:
|
|
409
|
-
cpp_type = self._type_for_decl(node)
|
|
410
783
|
default = self._default_for_type(cpp_type)
|
|
411
784
|
lines.append(f"{pad}{cpp_type} {safe} = {default};")
|
|
785
|
+
remember_local_type(cpp_type)
|
|
412
786
|
indent = len(pad) // 4
|
|
413
|
-
self._visit_if_switch_expr(
|
|
787
|
+
self._visit_if_switch_expr(
|
|
788
|
+
node.value,
|
|
789
|
+
safe,
|
|
790
|
+
lines,
|
|
791
|
+
indent,
|
|
792
|
+
target_cpp_type=selection_cpp_type,
|
|
793
|
+
)
|
|
414
794
|
return
|
|
415
795
|
|
|
416
|
-
# UDT lvalue alias (BUG C): a local initialised from a user-defined-UDT
|
|
417
|
-
# var/global lvalue (or a ternary/switch of such lvalues) and then
|
|
418
|
-
# mutated through must ALIAS the global, not value-copy — Pine UDTs are
|
|
419
|
-
# reference types. Emit a C++ reference (non-rebinding) or pointer
|
|
420
|
-
# (rebinding) alias instead of the default copy.
|
|
421
|
-
if not is_global_member:
|
|
422
|
-
alias = self._udt_local_alias_kind(node)
|
|
423
|
-
if alias is not None:
|
|
424
|
-
kind, udt_t = alias
|
|
425
|
-
if kind == "ref":
|
|
426
|
-
cpp_val = self._visit_rhs_value(node.value, node.name, target_cpp_type=udt_t)
|
|
427
|
-
lines.append(f"{pad}{udt_t}& {safe} = {cpp_val};")
|
|
428
|
-
return
|
|
429
|
-
# Pointer alias: take address of each selected lvalue; subsequent
|
|
430
|
-
# field access lowers to ``->`` and rebinds to ``&(other)``.
|
|
431
|
-
self._udt_ptr_alias_locals.add(node.name)
|
|
432
|
-
cpp_val = self._addr_of_udt_selection(node.value, node.name)
|
|
433
|
-
lines.append(f"{pad}{udt_t}* {safe} = {cpp_val};")
|
|
434
|
-
return
|
|
435
|
-
|
|
436
796
|
# Collection lvalue alias (BUG 2): a local bound to an existing array /
|
|
437
797
|
# map / matrix lvalue (or a ternary/switch selecting same-typed ones)
|
|
438
798
|
# and later MUTATED through must ALIAS the member, not value-copy — Pine
|
|
@@ -444,6 +804,8 @@ class StmtVisitor:
|
|
|
444
804
|
if not is_global_member:
|
|
445
805
|
coll_spec = self._collection_lvalue_selection_spec(node.value)
|
|
446
806
|
if coll_spec is not None and self._collection_local_must_alias(node):
|
|
807
|
+
cpp_type = self._type_spec_to_cpp(coll_spec)
|
|
808
|
+
cpp_val = self._visit_rhs_value(node.value, node.name, target_cpp_type=cpp_type)
|
|
447
809
|
# Register the local's collection kind so subsequent
|
|
448
810
|
# ``.size()/.get()/.unshift()`` dispatch resolves correctly.
|
|
449
811
|
self._collection_types[node.name] = coll_spec
|
|
@@ -453,30 +815,30 @@ class StmtVisitor:
|
|
|
453
815
|
self._map_vars.add(node.name)
|
|
454
816
|
elif coll_spec.kind == "matrix":
|
|
455
817
|
self._matrix_specs[node.name] = coll_spec
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
818
|
+
# PineMap is itself a shared-ID handle. Copying it by value is
|
|
819
|
+
# the correct alias operation and, unlike a C++ reference,
|
|
820
|
+
# keeps a later local rebind local. Arrays/matrices still need
|
|
821
|
+
# their established reference alias route.
|
|
822
|
+
ref = "" if coll_spec.kind == "map" else "&"
|
|
823
|
+
lines.append(f"{pad}{cpp_type}{ref} {safe} = {cpp_val};")
|
|
459
824
|
return
|
|
460
825
|
|
|
461
|
-
# Global-scope UDT array-element alias (BUG: Pine array elements of a
|
|
462
|
-
# user-defined type are references). A global loop-local bound from
|
|
463
|
-
# ``arr.get(i)`` and later field-mutated must ALIAS the element so the
|
|
464
|
-
# mutation writes back into the array. Emit a fresh per-iteration
|
|
465
|
-
# ``UDT& z = arr[i];`` reference (any hoisted class member was
|
|
466
|
-
# suppressed) — identical to the non-hoisted function-local alias path.
|
|
467
|
-
# Gated by _register_udt_array_get_ref_locals (which only records global-
|
|
468
|
-
# scope names); the ``_current_func_body is None`` guard confines this to
|
|
469
|
-
# global scope, so a function-local sharing the name keeps its own path.
|
|
470
|
-
if (node.name in self._udt_array_get_ref_locals
|
|
471
|
-
and getattr(self, "_current_func_body", None) is None):
|
|
472
|
-
udt_t = self._udt_var_types.get(node.name)
|
|
473
|
-
cpp_val = self._visit_rhs_value(node.value, node.name, target_cpp_type=udt_t)
|
|
474
|
-
lines.append(f"{pad}{udt_t}& {safe} = {cpp_val};")
|
|
475
|
-
return
|
|
476
|
-
|
|
477
826
|
# General declaration
|
|
478
827
|
cpp_type = self._type_for_decl(node) if not is_global_member else None
|
|
479
|
-
|
|
828
|
+
target_cpp_type = cpp_type or self._nullable_collection_target_cpp_type(
|
|
829
|
+
name=node.name,
|
|
830
|
+
type_hint=node.type_hint,
|
|
831
|
+
)
|
|
832
|
+
if target_cpp_type is None:
|
|
833
|
+
target_cpp_type = self._udt_target_cpp_type(
|
|
834
|
+
target_name=node.name,
|
|
835
|
+
type_hint=node.type_hint,
|
|
836
|
+
)
|
|
837
|
+
cpp_val = self._visit_rhs_value(
|
|
838
|
+
node.value,
|
|
839
|
+
node.name,
|
|
840
|
+
target_cpp_type=target_cpp_type,
|
|
841
|
+
)
|
|
480
842
|
if is_global_member:
|
|
481
843
|
lines.append(f"{pad}{safe} = {cpp_val};")
|
|
482
844
|
else:
|
|
@@ -502,20 +864,124 @@ class StmtVisitor:
|
|
|
502
864
|
return f"std::fmod((double)({target_read}), (double)({val_cpp}))"
|
|
503
865
|
return None
|
|
504
866
|
|
|
867
|
+
def _matrix_rhs_specs(self, value: ASTNode | None) -> list[TypeSpec]:
|
|
868
|
+
"""Return every known concrete matrix type in an RHS selection.
|
|
869
|
+
|
|
870
|
+
``_type_spec_from_expr`` intentionally returns ``None`` for a
|
|
871
|
+
selection whose concrete arms disagree. Reassignment validation must
|
|
872
|
+
still inspect those arms individually: otherwise
|
|
873
|
+
``matrix<int> := cond ? matrix<int> : matrix<float>`` evades the type
|
|
874
|
+
check precisely because the complete selection has no unified type.
|
|
875
|
+
Explicit and implicit ``na`` arms contribute no concrete type.
|
|
876
|
+
"""
|
|
877
|
+
if self._selection_node_is_na(value):
|
|
878
|
+
return []
|
|
879
|
+
if isinstance(value, Ternary):
|
|
880
|
+
return (
|
|
881
|
+
self._matrix_rhs_specs(value.true_val)
|
|
882
|
+
+ self._matrix_rhs_specs(value.false_val)
|
|
883
|
+
)
|
|
884
|
+
if isinstance(value, IfStmt):
|
|
885
|
+
return (
|
|
886
|
+
self._matrix_rhs_specs(
|
|
887
|
+
self._selection_terminal_expr(value.body)
|
|
888
|
+
)
|
|
889
|
+
+ self._matrix_rhs_specs(
|
|
890
|
+
self._selection_terminal_expr(value.else_body)
|
|
891
|
+
)
|
|
892
|
+
)
|
|
893
|
+
if isinstance(value, SwitchStmt):
|
|
894
|
+
specs: list[TypeSpec] = []
|
|
895
|
+
for _case_expr, case_body in value.cases:
|
|
896
|
+
specs.extend(
|
|
897
|
+
self._matrix_rhs_specs(
|
|
898
|
+
self._selection_terminal_expr(case_body)
|
|
899
|
+
)
|
|
900
|
+
)
|
|
901
|
+
specs.extend(
|
|
902
|
+
self._matrix_rhs_specs(
|
|
903
|
+
self._selection_terminal_expr(value.default_body)
|
|
904
|
+
)
|
|
905
|
+
)
|
|
906
|
+
return specs
|
|
907
|
+
spec = self._type_spec_from_expr(value)
|
|
908
|
+
return [spec] if spec is not None and spec.kind == "matrix" else []
|
|
909
|
+
|
|
910
|
+
def _validate_matrix_reassignment(
|
|
911
|
+
self,
|
|
912
|
+
node: Assignment,
|
|
913
|
+
target_name: str | None,
|
|
914
|
+
) -> None:
|
|
915
|
+
"""Reject any known matrix RHS whose element type changes the LHS.
|
|
916
|
+
|
|
917
|
+
This check belongs to the lexical target rather than any particular
|
|
918
|
+
storage representation. A matrix may be a persistent ``var``, a plain
|
|
919
|
+
global member, a callable local, or a UDT field, but Pine forbids
|
|
920
|
+
changing its declared element type in every case.
|
|
921
|
+
"""
|
|
922
|
+
if node.op != ":=":
|
|
923
|
+
return
|
|
924
|
+
target_spec = self._type_spec_from_expr(node.target)
|
|
925
|
+
if target_spec is None and target_name is not None:
|
|
926
|
+
target_spec = self._collection_spec_for_name(target_name)
|
|
927
|
+
if target_spec is None or target_spec.kind != "matrix":
|
|
928
|
+
return
|
|
929
|
+
target_label = target_name
|
|
930
|
+
if target_label is None and isinstance(node.target, MemberAccess):
|
|
931
|
+
target_label = node.target.member
|
|
932
|
+
target_label = target_label or "<expression>"
|
|
933
|
+
for rhs_spec in self._matrix_rhs_specs(node.value):
|
|
934
|
+
if rhs_spec.element == target_spec.element:
|
|
935
|
+
continue
|
|
936
|
+
self._codegen_error(
|
|
937
|
+
node,
|
|
938
|
+
f"matrix '{target_label}' element type mismatch on reassignment: "
|
|
939
|
+
f"expected {self._type_spec_to_cpp(target_spec)}, "
|
|
940
|
+
f"got {self._type_spec_to_cpp(rhs_spec)}",
|
|
941
|
+
)
|
|
942
|
+
|
|
505
943
|
def _visit_assignment(self, node: Assignment, lines: list[str], pad: str) -> None:
|
|
506
944
|
if isinstance(node.value, FuncCall) and self._is_skip_expr(node.value):
|
|
507
945
|
return
|
|
508
946
|
|
|
947
|
+
# Validate against the active lexical target before any syntax-specific
|
|
948
|
+
# early return. In particular, if/switch expressions and UDT-field
|
|
949
|
+
# targets otherwise bypass the ordinary identifier assignment path.
|
|
950
|
+
target_name = self._get_target_name(node.target)
|
|
951
|
+
self._validate_matrix_reassignment(node, target_name)
|
|
952
|
+
|
|
509
953
|
# If/switch expression in assignment: x := if cond ...
|
|
510
954
|
if isinstance(node.value, (IfStmt, SwitchStmt)):
|
|
511
|
-
|
|
512
|
-
|
|
955
|
+
safe = (
|
|
956
|
+
self._safe_name(target_name)
|
|
957
|
+
if target_name
|
|
958
|
+
else self._visit_mutable_expr(node.target)
|
|
959
|
+
)
|
|
960
|
+
selection_cpp_type = self._nullable_collection_target_cpp_type(
|
|
961
|
+
name=target_name,
|
|
962
|
+
target_node=node.target if target_name is None else None,
|
|
963
|
+
)
|
|
964
|
+
if selection_cpp_type is None:
|
|
965
|
+
selection_cpp_type = self._udt_target_cpp_type(
|
|
966
|
+
target_name=target_name,
|
|
967
|
+
target_node=node.target if target_name is None else None,
|
|
968
|
+
)
|
|
969
|
+
if selection_cpp_type is None:
|
|
970
|
+
selection_cpp_type = self._drawing_target_cpp_type(
|
|
971
|
+
target_name,
|
|
972
|
+
None,
|
|
973
|
+
)
|
|
513
974
|
indent = len(pad) // 4
|
|
514
|
-
self._visit_if_switch_expr(
|
|
975
|
+
self._visit_if_switch_expr(
|
|
976
|
+
node.value,
|
|
977
|
+
safe,
|
|
978
|
+
lines,
|
|
979
|
+
indent,
|
|
980
|
+
target_cpp_type=selection_cpp_type,
|
|
981
|
+
)
|
|
515
982
|
return
|
|
516
983
|
|
|
517
984
|
# Get target name
|
|
518
|
-
target_name = self._get_target_name(node.target)
|
|
519
985
|
if target_name is None:
|
|
520
986
|
# Assignment to a UDT field that was dropped from the emitted
|
|
521
987
|
# struct because it had a drawing-only type (label/line/box/
|
|
@@ -533,8 +999,17 @@ class StmtVisitor:
|
|
|
533
999
|
)
|
|
534
1000
|
return
|
|
535
1001
|
# General expression target (e.g., member access)
|
|
536
|
-
target_cpp = self.
|
|
537
|
-
|
|
1002
|
+
target_cpp = self._visit_mutable_expr(node.target)
|
|
1003
|
+
target_cpp_type = self._nullable_collection_target_cpp_type(
|
|
1004
|
+
target_node=node.target,
|
|
1005
|
+
)
|
|
1006
|
+
if target_cpp_type is None:
|
|
1007
|
+
target_cpp_type = self._udt_target_cpp_type(
|
|
1008
|
+
target_node=node.target,
|
|
1009
|
+
)
|
|
1010
|
+
val_cpp = self._visit_rhs_value(
|
|
1011
|
+
node.value, target_cpp_type=target_cpp_type
|
|
1012
|
+
)
|
|
538
1013
|
if node.op == ":=":
|
|
539
1014
|
lines.append(f"{pad}{target_cpp} = {val_cpp};")
|
|
540
1015
|
else:
|
|
@@ -550,14 +1025,15 @@ class StmtVisitor:
|
|
|
550
1025
|
if self._active_var_remap and safe in self._active_var_remap:
|
|
551
1026
|
safe = self._active_var_remap[safe]
|
|
552
1027
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
1028
|
+
if self._binding_is_series(target_name, safe):
|
|
1029
|
+
val_cpp = self._visit_rhs_value(
|
|
1030
|
+
node.value,
|
|
1031
|
+
target_name,
|
|
1032
|
+
target_cpp_type=self._drawing_target_cpp_type(
|
|
1033
|
+
target_name,
|
|
1034
|
+
None,
|
|
1035
|
+
),
|
|
1036
|
+
)
|
|
561
1037
|
if node.op == ":=":
|
|
562
1038
|
lines.append(f"{pad}{safe}.update({val_cpp});")
|
|
563
1039
|
else:
|
|
@@ -570,26 +1046,17 @@ class StmtVisitor:
|
|
|
570
1046
|
op_char = node.op[0] # e.g., "+" from "+="
|
|
571
1047
|
lines.append(f"{pad}{safe}.update({safe}[0] {op_char} {val_cpp});")
|
|
572
1048
|
elif target_name in self._var_names:
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
lhs_spec = self._matrix_specs[target_name]
|
|
585
|
-
if rhs_spec.element != lhs_spec.element:
|
|
586
|
-
self._codegen_error(
|
|
587
|
-
node,
|
|
588
|
-
f"matrix '{target_name}' element type mismatch on reassignment: "
|
|
589
|
-
f"expected {self._type_spec_to_cpp(lhs_spec)}, "
|
|
590
|
-
f"got {self._type_spec_to_cpp(rhs_spec)}",
|
|
591
|
-
)
|
|
592
|
-
val_cpp = self._visit_rhs_value(node.value, target_name)
|
|
1049
|
+
# A bare-``na`` reassignment must adopt the target's declared scalar
|
|
1050
|
+
# type (``x := na`` -> ``na<int>()`` not ``na<double>()``); otherwise
|
|
1051
|
+
# a double NaN is stored into an int/int64_t/bool member (UB, defeats
|
|
1052
|
+
# is_na<T>()). Only computed for bare na — every other RHS is
|
|
1053
|
+
# unaffected.
|
|
1054
|
+
tct = self._nullable_collection_target_cpp_type(name=target_name)
|
|
1055
|
+
if tct is None:
|
|
1056
|
+
tct = self._udt_target_cpp_type(target_name=target_name)
|
|
1057
|
+
if tct is None and self._is_na_expr(node.value):
|
|
1058
|
+
tct = self._na_reassign_cpp_type(target_name)
|
|
1059
|
+
val_cpp = self._visit_rhs_value(node.value, target_name, target_cpp_type=tct)
|
|
593
1060
|
if node.op == ":=":
|
|
594
1061
|
lines.append(f"{pad}{safe} = {val_cpp};")
|
|
595
1062
|
else:
|
|
@@ -599,7 +1066,12 @@ class StmtVisitor:
|
|
|
599
1066
|
else:
|
|
600
1067
|
lines.append(f"{pad}{safe} {node.op} {val_cpp};")
|
|
601
1068
|
else:
|
|
602
|
-
|
|
1069
|
+
tct = self._nullable_collection_target_cpp_type(name=target_name)
|
|
1070
|
+
if tct is None:
|
|
1071
|
+
tct = self._udt_target_cpp_type(target_name=target_name)
|
|
1072
|
+
if tct is None and self._is_na_expr(node.value):
|
|
1073
|
+
tct = self._na_reassign_cpp_type(target_name)
|
|
1074
|
+
val_cpp = self._visit_rhs_value(node.value, target_name, target_cpp_type=tct)
|
|
603
1075
|
if node.op == ":=":
|
|
604
1076
|
lines.append(f"{pad}{safe} = {val_cpp};")
|
|
605
1077
|
else:
|
|
@@ -610,6 +1082,143 @@ class StmtVisitor:
|
|
|
610
1082
|
lines.append(f"{pad}{safe} {node.op} {val_cpp};")
|
|
611
1083
|
|
|
612
1084
|
def _visit_tuple_assign(self, node: TupleAssign, lines: list[str], pad: str) -> None:
|
|
1085
|
+
is_top_level = any(id(node) == id(stmt) for stmt in self.ctx.ast.body)
|
|
1086
|
+
global_targets = (
|
|
1087
|
+
set(getattr(self.ctx, "ordinary_global_binding_names", set()))
|
|
1088
|
+
& self._qualified_func_var_raw_names
|
|
1089
|
+
if is_top_level
|
|
1090
|
+
else set()
|
|
1091
|
+
)
|
|
1092
|
+
security_result_fields: list[str] | None = None
|
|
1093
|
+
|
|
1094
|
+
# Exact supported request.security tuples are authoritative Program
|
|
1095
|
+
# globals: a later UDF reads their class-member storage, so a local
|
|
1096
|
+
# structured binding would shadow the members with fresh values while
|
|
1097
|
+
# leaving the UDF-visible state stale. Keep this narrow to security
|
|
1098
|
+
# tuples whose analyzer metadata proves a supported helper family, an
|
|
1099
|
+
# exact direct literal, or a known TA tuple result. Ordinary
|
|
1100
|
+
# non-security heterogeneous tuples retain their established local
|
|
1101
|
+
# destructuring behavior.
|
|
1102
|
+
if is_top_level and isinstance(node.value, FuncCall):
|
|
1103
|
+
func_name, namespace = self._resolve_callee(node.value.callee)
|
|
1104
|
+
if namespace == "request" and func_name == "security":
|
|
1105
|
+
param_names = [
|
|
1106
|
+
"symbol", "timeframe", "expression", "gaps",
|
|
1107
|
+
"lookahead", "ignore_invalid_symbol", "currency",
|
|
1108
|
+
]
|
|
1109
|
+
all_args = list(node.value.args)
|
|
1110
|
+
for idx, param_name in enumerate(param_names):
|
|
1111
|
+
if param_name in node.value.kwargs:
|
|
1112
|
+
while len(all_args) <= idx:
|
|
1113
|
+
all_args.append(None)
|
|
1114
|
+
all_args[idx] = node.value.kwargs[param_name]
|
|
1115
|
+
expr_node = all_args[2] if len(all_args) > 2 else None
|
|
1116
|
+
info = next(
|
|
1117
|
+
(
|
|
1118
|
+
item
|
|
1119
|
+
for item in self._security_calls
|
|
1120
|
+
if not item.get("is_lower_tf_array")
|
|
1121
|
+
and item.get("expr_node") is expr_node
|
|
1122
|
+
),
|
|
1123
|
+
None,
|
|
1124
|
+
)
|
|
1125
|
+
if info is not None:
|
|
1126
|
+
tuple_size = info.get("tuple_size", 0)
|
|
1127
|
+
tuple_types = tuple(
|
|
1128
|
+
info.get("tuple_element_types", ()) or ()
|
|
1129
|
+
)
|
|
1130
|
+
numeric_tuple = (
|
|
1131
|
+
tuple_size >= 2
|
|
1132
|
+
and len(tuple_types) == tuple_size
|
|
1133
|
+
and all(
|
|
1134
|
+
item in (PineType.INT, PineType.FLOAT)
|
|
1135
|
+
for item in tuple_types
|
|
1136
|
+
)
|
|
1137
|
+
)
|
|
1138
|
+
bool_tuple = (
|
|
1139
|
+
tuple_size >= 2
|
|
1140
|
+
and len(tuple_types) == tuple_size
|
|
1141
|
+
and all(item == PineType.BOOL for item in tuple_types)
|
|
1142
|
+
)
|
|
1143
|
+
exact_direct_tuple = (
|
|
1144
|
+
isinstance(expr_node, TupleLiteral)
|
|
1145
|
+
and tuple_size >= 2
|
|
1146
|
+
and len(tuple_types) == tuple_size
|
|
1147
|
+
and all(
|
|
1148
|
+
item in (
|
|
1149
|
+
PineType.INT,
|
|
1150
|
+
PineType.FLOAT,
|
|
1151
|
+
PineType.BOOL,
|
|
1152
|
+
)
|
|
1153
|
+
for item in tuple_types
|
|
1154
|
+
)
|
|
1155
|
+
)
|
|
1156
|
+
ta_site = self._get_ta_site(expr_node)
|
|
1157
|
+
known_ta_tuple = (
|
|
1158
|
+
ta_site is not None
|
|
1159
|
+
and self._ta_name_from_site(ta_site) in TA_TUPLE_FIELDS
|
|
1160
|
+
)
|
|
1161
|
+
if known_ta_tuple:
|
|
1162
|
+
security_result_fields = TA_TUPLE_FIELDS[
|
|
1163
|
+
self._ta_name_from_site(ta_site)
|
|
1164
|
+
]
|
|
1165
|
+
if (
|
|
1166
|
+
numeric_tuple
|
|
1167
|
+
or bool_tuple
|
|
1168
|
+
or exact_direct_tuple
|
|
1169
|
+
or known_ta_tuple
|
|
1170
|
+
):
|
|
1171
|
+
global_targets.update(
|
|
1172
|
+
name for name in node.names if name != "_"
|
|
1173
|
+
)
|
|
1174
|
+
|
|
1175
|
+
def emit_call_tuple(call_expr: str) -> None:
|
|
1176
|
+
"""Destructure once, routing exact history elements to Series.
|
|
1177
|
+
|
|
1178
|
+
A structured binding always creates scalar C++ locals, which would
|
|
1179
|
+
shadow the class Series storage required by a later ``x[n]`` read.
|
|
1180
|
+
It also cannot assign an existing top-level class member. Materialize
|
|
1181
|
+
the tuple once whenever either case applies; ordinary lexical scalar
|
|
1182
|
+
elements remain locals, Series elements advance their remapped
|
|
1183
|
+
members, and top-level scalars assign their class storage.
|
|
1184
|
+
"""
|
|
1185
|
+
series_names = {
|
|
1186
|
+
name
|
|
1187
|
+
for name in node.names
|
|
1188
|
+
if name != "_"
|
|
1189
|
+
and self._decl_binding_is_series(id(node), name)
|
|
1190
|
+
}
|
|
1191
|
+
if not series_names and not global_targets.intersection(node.names):
|
|
1192
|
+
binding_names = ", ".join(node.names)
|
|
1193
|
+
lines.append(f"{pad}auto [{binding_names}] = {call_expr};")
|
|
1194
|
+
return
|
|
1195
|
+
|
|
1196
|
+
temp = f"_tuple_result_{self._tuple_assign_counter}"
|
|
1197
|
+
self._tuple_assign_counter += 1
|
|
1198
|
+
lines.append(f"{pad}auto {temp} = {call_expr};")
|
|
1199
|
+
for idx, name in enumerate(node.names):
|
|
1200
|
+
if name == "_":
|
|
1201
|
+
continue
|
|
1202
|
+
value = (
|
|
1203
|
+
f"{temp}.{security_result_fields[idx]}"
|
|
1204
|
+
if (
|
|
1205
|
+
security_result_fields is not None
|
|
1206
|
+
and idx < len(security_result_fields)
|
|
1207
|
+
)
|
|
1208
|
+
else f"std::get<{idx}>({temp})"
|
|
1209
|
+
)
|
|
1210
|
+
safe = self._safe_name(name)
|
|
1211
|
+
if name in series_names:
|
|
1212
|
+
if safe in self._active_var_remap:
|
|
1213
|
+
safe = self._active_var_remap[safe]
|
|
1214
|
+
self._emit_history_series_write(
|
|
1215
|
+
lines, pad, safe, value
|
|
1216
|
+
)
|
|
1217
|
+
elif name in global_targets:
|
|
1218
|
+
lines.append(f"{pad}{safe} = {value};")
|
|
1219
|
+
else:
|
|
1220
|
+
lines.append(f"{pad}auto {safe} = {value};")
|
|
1221
|
+
|
|
613
1222
|
site = self._get_ta_site(node.value)
|
|
614
1223
|
if site is not None:
|
|
615
1224
|
compute_args = self._ta_compute_args_for_site(site)
|
|
@@ -637,11 +1246,17 @@ class StmtVisitor:
|
|
|
637
1246
|
# fresh ``double`` local would shadow the member and make
|
|
638
1247
|
# ``dir[n]`` a scalar subscript (clang error). Non-series
|
|
639
1248
|
# destructured names keep the plain scalar declaration.
|
|
640
|
-
if
|
|
1249
|
+
if self._decl_binding_is_series(id(node), name):
|
|
641
1250
|
safe = self._safe_name(name)
|
|
1251
|
+
if safe in self._active_var_remap:
|
|
1252
|
+
safe = self._active_var_remap[safe]
|
|
642
1253
|
self._emit_history_series_write(
|
|
643
1254
|
lines, pad, safe, field_expr
|
|
644
1255
|
)
|
|
1256
|
+
elif name in global_targets:
|
|
1257
|
+
lines.append(
|
|
1258
|
+
f"{pad}{self._safe_name(name)} = {field_expr};"
|
|
1259
|
+
)
|
|
645
1260
|
else:
|
|
646
1261
|
lines.append(f"{pad}double {name} = {field_expr};")
|
|
647
1262
|
return
|
|
@@ -650,14 +1265,12 @@ class StmtVisitor:
|
|
|
650
1265
|
if isinstance(node.value, FuncCall):
|
|
651
1266
|
func_name, namespace = self._resolve_callee(node.value.callee)
|
|
652
1267
|
if namespace == "request" and func_name == "security":
|
|
653
|
-
binding_names = ", ".join(n for n in node.names if n != "_")
|
|
654
1268
|
call_expr = self._visit_func_call(node.value)
|
|
655
|
-
|
|
1269
|
+
emit_call_tuple(call_expr)
|
|
656
1270
|
return
|
|
657
1271
|
if func_name and namespace is None and func_name in self._func_names:
|
|
658
|
-
binding_names = ", ".join(node.names)
|
|
659
1272
|
call_expr = self._visit_func_call(node.value)
|
|
660
|
-
|
|
1273
|
+
emit_call_tuple(call_expr)
|
|
661
1274
|
return
|
|
662
1275
|
|
|
663
1276
|
# UDT instance method returning a tuple: ``[a, b, c] = receiver.method(...)``.
|
|
@@ -673,64 +1286,188 @@ class StmtVisitor:
|
|
|
673
1286
|
callee = node.value.callee
|
|
674
1287
|
if isinstance(callee, MemberAccess):
|
|
675
1288
|
recv_spec = self._type_spec_from_expr(callee.object)
|
|
676
|
-
|
|
677
|
-
|
|
1289
|
+
receiver_name = method_receiver_type_name(recv_spec)
|
|
1290
|
+
if receiver_name is not None:
|
|
1291
|
+
method_key = f"{receiver_name}.{callee.member}"
|
|
678
1292
|
fi_u = self._func_info_map.get(method_key)
|
|
679
1293
|
if (fi_u is not None
|
|
680
1294
|
and getattr(fi_u, "is_udt_method", False)
|
|
681
1295
|
and getattr(fi_u, "returns_tuple", False)):
|
|
682
|
-
binding_names = ", ".join(node.names)
|
|
683
1296
|
call_expr = self._visit_func_call(node.value)
|
|
684
|
-
|
|
1297
|
+
emit_call_tuple(call_expr)
|
|
685
1298
|
return
|
|
686
1299
|
|
|
687
1300
|
lines.append(f"{pad}/* unsupported tuple assignment */")
|
|
688
1301
|
|
|
689
|
-
def
|
|
690
|
-
"""
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
1302
|
+
def _tuple_binding_cpp_types(self, node: TupleAssign) -> list[str]:
|
|
1303
|
+
"""Exact supported tuple element types for later lexical operations."""
|
|
1304
|
+
count = len(node.names)
|
|
1305
|
+
if not isinstance(node.value, FuncCall):
|
|
1306
|
+
return ["double"] * count
|
|
1307
|
+
func_name, namespace = self._resolve_callee(node.value.callee)
|
|
1308
|
+
fi = None
|
|
1309
|
+
if namespace is None:
|
|
1310
|
+
fi = self._func_info_map.get(func_name)
|
|
1311
|
+
elif isinstance(node.value.callee, MemberAccess):
|
|
1312
|
+
recv_spec = self._type_spec_from_expr(node.value.callee.object)
|
|
1313
|
+
receiver_name = method_receiver_type_name(recv_spec)
|
|
1314
|
+
if receiver_name is not None:
|
|
1315
|
+
fi = self._func_info_map.get(
|
|
1316
|
+
f"{receiver_name}.{node.value.callee.member}"
|
|
1317
|
+
)
|
|
1318
|
+
if (fi is not None
|
|
1319
|
+
and fi.node is not None
|
|
1320
|
+
and getattr(fi, "returns_tuple", False)):
|
|
1321
|
+
return self._infer_tuple_types(fi.node, count)
|
|
1322
|
+
return ["double"] * count
|
|
1323
|
+
|
|
1324
|
+
def _push_block_var_remap(self, owner):
|
|
1325
|
+
"""Activate exact lexical metadata for one branch/loop body.
|
|
1326
|
+
|
|
1327
|
+
Persistent-var renames use copy-on-write and activate only after their
|
|
1328
|
+
declarations. Collection registries likewise use copy-on-write and
|
|
1329
|
+
declarations activate their bindings in
|
|
1330
|
+
source order, and block exit restores the outer state. Thus a sibling
|
|
1331
|
+
block can reuse the same raw name without either pre-shadowing earlier
|
|
1332
|
+
statements or controlling dispatch in the other branch.
|
|
1333
|
+
"""
|
|
1334
|
+
previous_map_visible = getattr(
|
|
1335
|
+
self, "_block_map_binding_visible", False
|
|
1336
|
+
)
|
|
1337
|
+
previous_map_depth = getattr(self, "_block_map_visibility_depth", 0)
|
|
1338
|
+
self._block_map_visibility_depth = previous_map_depth + 1
|
|
1339
|
+
saved_drawing_types = self._lexical_drawing_types
|
|
1340
|
+
self._lexical_drawing_types = dict(saved_drawing_types)
|
|
1341
|
+
saved_udt_types = self._lexical_udt_types
|
|
1342
|
+
self._lexical_udt_types = dict(saved_udt_types)
|
|
1343
|
+
saved_series_bindings = self._lexical_series_bindings
|
|
1344
|
+
self._lexical_series_bindings = dict(saved_series_bindings)
|
|
1345
|
+
saved_known_tombstones = self._lexical_known_var_tombstones
|
|
1346
|
+
self._lexical_known_var_tombstones = set(
|
|
1347
|
+
saved_known_tombstones
|
|
1348
|
+
)
|
|
700
1349
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
1350
|
+
renames = self._block_var_renames.get(id(owner))
|
|
1351
|
+
collection_specs = self._block_collection_types.get(id(owner))
|
|
1352
|
+
if not renames and collection_specs is None:
|
|
1353
|
+
return (
|
|
1354
|
+
_NO_BLOCK_REMAP,
|
|
1355
|
+
None,
|
|
1356
|
+
previous_map_visible,
|
|
1357
|
+
previous_map_depth,
|
|
1358
|
+
saved_drawing_types,
|
|
1359
|
+
saved_udt_types,
|
|
1360
|
+
saved_series_bindings,
|
|
1361
|
+
saved_known_tombstones,
|
|
1362
|
+
)
|
|
1363
|
+
saved_remap = self._active_var_remap
|
|
1364
|
+
if renames:
|
|
1365
|
+
# Do not pre-shadow an outer binding at block entry. _visit_stmt
|
|
1366
|
+
# installs each exact rename immediately after that VarDecl's RHS.
|
|
1367
|
+
self._active_var_remap = dict(saved_remap)
|
|
1368
|
+
|
|
1369
|
+
saved_collections = None
|
|
1370
|
+
if collection_specs is not None:
|
|
1371
|
+
saved_collections = (
|
|
1372
|
+
self._current_func_collection_specs,
|
|
1373
|
+
self._current_func_collection_shadows,
|
|
1374
|
+
self._collection_types,
|
|
1375
|
+
self._array_vars,
|
|
1376
|
+
self._map_vars,
|
|
1377
|
+
self._matrix_specs,
|
|
1378
|
+
)
|
|
1379
|
+
self._current_func_collection_specs = dict(
|
|
1380
|
+
self._current_func_collection_specs
|
|
1381
|
+
)
|
|
1382
|
+
self._current_func_collection_shadows = set(
|
|
1383
|
+
self._current_func_collection_shadows
|
|
1384
|
+
)
|
|
1385
|
+
self._collection_types = dict(self._collection_types)
|
|
1386
|
+
self._array_vars = set(self._array_vars)
|
|
1387
|
+
self._map_vars = set(self._map_vars)
|
|
1388
|
+
self._matrix_specs = dict(self._matrix_specs)
|
|
1389
|
+
return (
|
|
1390
|
+
saved_remap,
|
|
1391
|
+
saved_collections,
|
|
1392
|
+
previous_map_visible,
|
|
1393
|
+
previous_map_depth,
|
|
1394
|
+
saved_drawing_types,
|
|
1395
|
+
saved_udt_types,
|
|
1396
|
+
saved_series_bindings,
|
|
1397
|
+
saved_known_tombstones,
|
|
1398
|
+
)
|
|
704
1399
|
|
|
705
|
-
def
|
|
706
|
-
|
|
1400
|
+
def _pop_block_var_remap(self, saved) -> None:
|
|
1401
|
+
(
|
|
1402
|
+
saved_remap,
|
|
1403
|
+
saved_collections,
|
|
1404
|
+
previous_map_visible,
|
|
1405
|
+
previous_map_depth,
|
|
1406
|
+
saved_drawing_types,
|
|
1407
|
+
saved_udt_types,
|
|
1408
|
+
saved_series_bindings,
|
|
1409
|
+
saved_known_tombstones,
|
|
1410
|
+
) = saved
|
|
707
1411
|
try:
|
|
708
|
-
|
|
1412
|
+
if saved_remap is not _NO_BLOCK_REMAP:
|
|
1413
|
+
self._active_var_remap = saved_remap
|
|
1414
|
+
if saved_collections is not None:
|
|
1415
|
+
(
|
|
1416
|
+
self._current_func_collection_specs,
|
|
1417
|
+
self._current_func_collection_shadows,
|
|
1418
|
+
self._collection_types,
|
|
1419
|
+
self._array_vars,
|
|
1420
|
+
self._map_vars,
|
|
1421
|
+
self._matrix_specs,
|
|
1422
|
+
) = saved_collections
|
|
709
1423
|
finally:
|
|
710
|
-
self.
|
|
1424
|
+
self._lexical_drawing_types = saved_drawing_types
|
|
1425
|
+
self._lexical_udt_types = saved_udt_types
|
|
1426
|
+
self._lexical_series_bindings = saved_series_bindings
|
|
1427
|
+
self._lexical_known_var_tombstones = saved_known_tombstones
|
|
1428
|
+
self._block_map_binding_visible = previous_map_visible
|
|
1429
|
+
self._block_map_visibility_depth = previous_map_depth
|
|
1430
|
+
|
|
1431
|
+
def _visit_block_statements(self, body: list, lines: list[str],
|
|
1432
|
+
indent: int) -> None:
|
|
1433
|
+
"""Emit one lexical branch/loop body with its exact var remap."""
|
|
1434
|
+
saved = self._push_block_var_remap(body)
|
|
1435
|
+
try:
|
|
1436
|
+
for stmt in body:
|
|
1437
|
+
self._visit_stmt(stmt, lines, indent)
|
|
1438
|
+
finally:
|
|
1439
|
+
self._pop_block_var_remap(saved)
|
|
1440
|
+
|
|
1441
|
+
def _emit_block_with_assign(
|
|
1442
|
+
self,
|
|
1443
|
+
body: list,
|
|
1444
|
+
target: str,
|
|
1445
|
+
lines: list[str],
|
|
1446
|
+
indent: int,
|
|
1447
|
+
target_cpp_type: str | None = None,
|
|
1448
|
+
) -> None:
|
|
1449
|
+
"""Expression-body counterpart of :meth:`_visit_block_statements`."""
|
|
1450
|
+
saved = self._push_block_var_remap(body)
|
|
1451
|
+
try:
|
|
1452
|
+
self._emit_body_with_assign(
|
|
1453
|
+
body,
|
|
1454
|
+
target,
|
|
1455
|
+
lines,
|
|
1456
|
+
indent,
|
|
1457
|
+
target_cpp_type=target_cpp_type,
|
|
1458
|
+
)
|
|
1459
|
+
finally:
|
|
1460
|
+
self._pop_block_var_remap(saved)
|
|
1461
|
+
|
|
1462
|
+
def _visit_if(self, node: IfStmt, lines: list[str], indent: int) -> None:
|
|
1463
|
+
self._visit_if_body(node, lines, indent)
|
|
711
1464
|
|
|
712
1465
|
def _visit_if_body(self, node: IfStmt, lines: list[str], indent: int) -> None:
|
|
713
1466
|
pad = " " * indent
|
|
714
1467
|
|
|
715
|
-
# TA hoisting: inside per-call-site function variants, execute ALL
|
|
716
|
-
# statements unconditionally (PineScript execution model) but wrap
|
|
717
|
-
# the result assignment in the condition.
|
|
718
|
-
if self._in_ta_func_variant and self._if_body_has_ta(node.body):
|
|
719
|
-
cond = self._visit_expr(node.condition)
|
|
720
|
-
self._hoist_if_body(node.body, cond, lines, pad, indent)
|
|
721
|
-
# Handle else_body similarly
|
|
722
|
-
if node.else_body:
|
|
723
|
-
if len(node.else_body) == 1 and isinstance(node.else_body[0], IfStmt):
|
|
724
|
-
self._visit_if(node.else_body[0], lines, indent)
|
|
725
|
-
else:
|
|
726
|
-
neg_cond = f"!({cond})"
|
|
727
|
-
self._hoist_if_body(node.else_body, neg_cond, lines, pad, indent)
|
|
728
|
-
return
|
|
729
|
-
|
|
730
1468
|
cond = self._visit_expr(node.condition)
|
|
731
1469
|
lines.append(f"{pad}if ({cond}) {{")
|
|
732
|
-
|
|
733
|
-
self._visit_stmt(s, lines, indent + 1)
|
|
1470
|
+
self._visit_block_statements(node.body, lines, indent + 1)
|
|
734
1471
|
lines.append(f"{pad}}}")
|
|
735
1472
|
if node.else_body:
|
|
736
1473
|
if len(node.else_body) == 1 and isinstance(node.else_body[0], IfStmt):
|
|
@@ -738,8 +1475,9 @@ class StmtVisitor:
|
|
|
738
1475
|
self._visit_if(node.else_body[0], lines, indent)
|
|
739
1476
|
else:
|
|
740
1477
|
lines[-1] = f"{pad}}} else {{"
|
|
741
|
-
|
|
742
|
-
|
|
1478
|
+
self._visit_block_statements(
|
|
1479
|
+
node.else_body, lines, indent + 1
|
|
1480
|
+
)
|
|
743
1481
|
lines.append(f"{pad}}}")
|
|
744
1482
|
|
|
745
1483
|
def _visit_for(self, node: ForStmt, lines: list[str], indent: int) -> None:
|
|
@@ -760,8 +1498,24 @@ class StmtVisitor:
|
|
|
760
1498
|
e_var = f"_for_end_{fid}"
|
|
761
1499
|
step_var = f"_for_step_{fid}"
|
|
762
1500
|
down_var = f"_for_down_{fid}"
|
|
1501
|
+
end_mentions_binder = bool(
|
|
1502
|
+
node.var
|
|
1503
|
+
and any(
|
|
1504
|
+
isinstance(part, Identifier) and part.name == node.var
|
|
1505
|
+
for part in self._walk_ast(node.end)
|
|
1506
|
+
)
|
|
1507
|
+
)
|
|
1508
|
+
end_eval = f"_for_end_eval_{fid}" if end_mentions_binder else None
|
|
1509
|
+
if end_eval is not None:
|
|
1510
|
+
# The ``to`` expression is authored outside the loop-binder scope,
|
|
1511
|
+
# but its refresh executes inside the generated C++ ``for`` where
|
|
1512
|
+
# the binder would shadow a same-named outer member/parameter. A
|
|
1513
|
+
# pre-binder lambda preserves the authored lexical binding while
|
|
1514
|
+
# still reevaluating the expression after every iteration.
|
|
1515
|
+
lines.append(f"{pad}auto {end_eval} = [&]() {{ return ({end}); }};")
|
|
763
1516
|
lines.append(f"{pad}int {s_var} = ({start});")
|
|
764
|
-
|
|
1517
|
+
end_expr = f"{end_eval}()" if end_eval is not None else f"({end})"
|
|
1518
|
+
lines.append(f"{pad}int {e_var} = {end_expr};")
|
|
765
1519
|
lines.append(f"{pad}int {step_var} = ({step});")
|
|
766
1520
|
lines.append(f"{pad}if ({step_var} < 0) {step_var} = -{step_var};")
|
|
767
1521
|
lines.append(f"{pad}if ({step_var} == 0) {step_var} = 1;")
|
|
@@ -769,47 +1523,35 @@ class StmtVisitor:
|
|
|
769
1523
|
lines.append(
|
|
770
1524
|
f"{pad}for (int {var} = {s_var}; "
|
|
771
1525
|
f"({down_var} ? ({var} >= {e_var}) : ({var} <= {e_var})); "
|
|
772
|
-
f"{var} += ({down_var} ? -{step_var} : {step_var}),
|
|
1526
|
+
f"{var} += ({down_var} ? -{step_var} : {step_var}), "
|
|
1527
|
+
f"{e_var} = {end_expr}) {{"
|
|
773
1528
|
)
|
|
774
1529
|
# Register the loop counter so reads of it inside the body resolve (the
|
|
775
1530
|
# unknown-identifier guard in _visit_ident would otherwise flag it).
|
|
776
1531
|
saved_loop = self._current_loop_vars
|
|
1532
|
+
saved_loop_specs = self._current_loop_var_specs
|
|
777
1533
|
self._current_loop_vars = set(self._current_loop_vars)
|
|
1534
|
+
self._current_loop_var_specs = dict(self._current_loop_var_specs)
|
|
778
1535
|
if node.var:
|
|
779
1536
|
self._current_loop_vars.add(node.var)
|
|
1537
|
+
self._current_loop_var_specs[node.var] = TypeSpec.primitive("int")
|
|
780
1538
|
_blk_saved = self._push_block_var_remap(node)
|
|
1539
|
+
if node.var:
|
|
1540
|
+
# The loop counter is a fresh primitive lexical binding. Keep it
|
|
1541
|
+
# from inheriting a same-spelled outer/raw UDT or drawing type.
|
|
1542
|
+
self._lexical_drawing_types[node.var] = None
|
|
1543
|
+
self._lexical_udt_types[node.var] = None
|
|
1544
|
+
self._lexical_series_bindings[node.var] = False
|
|
1545
|
+
self._lexical_known_var_tombstones.add(node.var)
|
|
781
1546
|
try:
|
|
782
1547
|
for s in node.body:
|
|
783
1548
|
self._visit_stmt(s, lines, indent + 1)
|
|
784
1549
|
finally:
|
|
785
1550
|
self._pop_block_var_remap(_blk_saved)
|
|
786
1551
|
self._current_loop_vars = saved_loop
|
|
1552
|
+
self._current_loop_var_specs = saved_loop_specs
|
|
787
1553
|
lines.append(f"{pad}}}")
|
|
788
1554
|
|
|
789
|
-
def _loop_elem_is_writeback_udt(self, iterable) -> bool:
|
|
790
|
-
"""Whether a ``for x in coll`` loop variable must bind by reference.
|
|
791
|
-
|
|
792
|
-
In Pine a ``for x in arr`` loop variable over an array of *user-defined
|
|
793
|
-
objects* is a reference to the element — field writes (``x.f := v``)
|
|
794
|
-
mutate the array in place — whereas over a primitive array it is a
|
|
795
|
-
copy. So emit C++ ``auto&`` only for arrays whose element is a
|
|
796
|
-
user-defined UDT struct. Primitive elements keep ``auto`` (Pine copy
|
|
797
|
-
semantics: writing the loop var must NOT write back). Drawing handles
|
|
798
|
-
(line/box/label/linefill/...) also keep ``auto``: their element type
|
|
799
|
-
name is a builtin, not in ``_udt_defs``, and a handle copy already
|
|
800
|
-
mutates the shared engine object. (Reassigning the loop var itself —
|
|
801
|
-
``x := ...`` — is not modelled by either form, but Pine forbids it for
|
|
802
|
-
objects in practice and it does not occur in the corpus.)
|
|
803
|
-
"""
|
|
804
|
-
spec = self._type_spec_from_expr(iterable)
|
|
805
|
-
return (
|
|
806
|
-
spec is not None
|
|
807
|
-
and spec.kind == "array"
|
|
808
|
-
and spec.element is not None
|
|
809
|
-
and spec.element.kind == "udt"
|
|
810
|
-
and spec.element.name in self._udt_defs
|
|
811
|
-
)
|
|
812
|
-
|
|
813
1555
|
def _visit_for_in(self, node, lines: list[str], indent: int) -> None:
|
|
814
1556
|
pad = " " * indent
|
|
815
1557
|
iterable = self._visit_expr(node.iterable)
|
|
@@ -828,17 +1570,123 @@ class StmtVisitor:
|
|
|
828
1570
|
if elem_spec is not None:
|
|
829
1571
|
self._current_loop_var_specs[node.var] = elem_spec
|
|
830
1572
|
if node.vars:
|
|
831
|
-
|
|
1573
|
+
tuple_specs: list[TypeSpec | None] = []
|
|
1574
|
+
if iterable_spec is not None and iterable_spec.kind == "map":
|
|
1575
|
+
tuple_specs = [iterable_spec.key, iterable_spec.value]
|
|
1576
|
+
for idx, v in enumerate(node.vars):
|
|
832
1577
|
if v != "_":
|
|
833
1578
|
self._current_loop_vars.add(v)
|
|
1579
|
+
if (idx < len(tuple_specs)
|
|
1580
|
+
and tuple_specs[idx] is not None):
|
|
1581
|
+
self._current_loop_var_specs[v] = tuple_specs[idx]
|
|
1582
|
+
map_pair_loop = (
|
|
1583
|
+
iterable_spec is not None
|
|
1584
|
+
and iterable_spec.kind == "map"
|
|
1585
|
+
and node.vars is not None
|
|
1586
|
+
and len(node.vars) == 2
|
|
1587
|
+
)
|
|
834
1588
|
if node.var:
|
|
835
1589
|
v_cpp = self._safe_name(node.var)
|
|
836
|
-
|
|
837
|
-
|
|
1590
|
+
# User UDT elements are numeric object-ID handles. Copying the
|
|
1591
|
+
# loop binding preserves Pine semantics: field writes still reach
|
|
1592
|
+
# the shared arena record, while rebinding the loop variable does
|
|
1593
|
+
# not overwrite the array slot. Primitive elements use the same
|
|
1594
|
+
# value-binding rule.
|
|
1595
|
+
lines.append(f"{pad}for (auto {v_cpp} : {iterable}) {{")
|
|
1596
|
+
elif map_pair_loop:
|
|
1597
|
+
# Pine map iteration exposes insertion-ordered ``[key, value]``
|
|
1598
|
+
# pairs. PineMap intentionally keeps its storage private, so take
|
|
1599
|
+
# one handle copy (which aliases the same map ID), iterate keys(),
|
|
1600
|
+
# and obtain each value through the public API. Binding the
|
|
1601
|
+
# iterable once also preserves single-evaluation semantics for an
|
|
1602
|
+
# arbitrary map-producing expression.
|
|
1603
|
+
key_name, value_name = node.vars
|
|
1604
|
+
authored_names = (
|
|
1605
|
+
set(self._all_bound_names)
|
|
1606
|
+
| set(self._func_names)
|
|
1607
|
+
| set(self._udt_defs)
|
|
1608
|
+
)
|
|
1609
|
+
occupied_names = authored_names | {
|
|
1610
|
+
self._safe_name(name) for name in authored_names
|
|
1611
|
+
} | {
|
|
1612
|
+
self._func_safe_name(name) for name in self._func_names
|
|
1613
|
+
}
|
|
1614
|
+
# Parameters are not statement bindings, so they are absent from
|
|
1615
|
+
# ``_all_bound_names``. Include both their authored and C++-safe
|
|
1616
|
+
# spellings before minting loop temporaries; otherwise a legal UDF
|
|
1617
|
+
# or method parameter named ``__pf_map_iter_0`` (or, for ``_`` key
|
|
1618
|
+
# loops, ``__pf_map_key_0``) is redeclared and then shadowed by the
|
|
1619
|
+
# generated loop machinery.
|
|
1620
|
+
active_params = set(self._current_func_param_types)
|
|
1621
|
+
occupied_names.update(active_params)
|
|
1622
|
+
occupied_names.update(
|
|
1623
|
+
self._safe_name(name) for name in active_params
|
|
1624
|
+
)
|
|
1625
|
+
while True:
|
|
1626
|
+
fid = self._for_counter
|
|
1627
|
+
self._for_counter += 1
|
|
1628
|
+
map_token = f"__pf_map_iter_{fid}"
|
|
1629
|
+
internal_key = f"__pf_map_key_{fid}"
|
|
1630
|
+
generated_names = {map_token}
|
|
1631
|
+
if key_name == "_":
|
|
1632
|
+
generated_names.add(internal_key)
|
|
1633
|
+
if not (generated_names & occupied_names):
|
|
1634
|
+
break
|
|
1635
|
+
key_cpp = (
|
|
1636
|
+
self._safe_name(key_name)
|
|
1637
|
+
if key_name != "_"
|
|
1638
|
+
else internal_key
|
|
1639
|
+
)
|
|
1640
|
+
lines.append(f"{pad}auto {map_token} = {iterable};")
|
|
1641
|
+
lines.append(f"{pad}for (auto {key_cpp} : {map_token}.keys()) {{")
|
|
1642
|
+
if value_name != "_":
|
|
1643
|
+
value_cpp = self._safe_name(value_name)
|
|
1644
|
+
lines.append(
|
|
1645
|
+
f"{pad} auto {value_cpp} = {map_token}.get({key_cpp});"
|
|
1646
|
+
)
|
|
838
1647
|
elif node.vars:
|
|
839
1648
|
bindings = ", ".join(node.vars)
|
|
840
1649
|
lines.append(f"{pad}for (auto [{bindings}] : {iterable}) {{")
|
|
841
1650
|
_blk_saved = self._push_block_var_remap(node)
|
|
1651
|
+
loop_binding_names = (
|
|
1652
|
+
[node.var] if node.var else list(node.vars or [])
|
|
1653
|
+
)
|
|
1654
|
+
loop_binding_specs = (
|
|
1655
|
+
[elem_spec]
|
|
1656
|
+
if node.var
|
|
1657
|
+
else [
|
|
1658
|
+
tuple_specs[index] if index < len(tuple_specs) else None
|
|
1659
|
+
for index in range(len(loop_binding_names))
|
|
1660
|
+
]
|
|
1661
|
+
)
|
|
1662
|
+
for index, name in enumerate(loop_binding_names):
|
|
1663
|
+
if name and name != "_":
|
|
1664
|
+
spec = (
|
|
1665
|
+
loop_binding_specs[index]
|
|
1666
|
+
if index < len(loop_binding_specs)
|
|
1667
|
+
else None
|
|
1668
|
+
)
|
|
1669
|
+
drawing_name = (
|
|
1670
|
+
spec.name
|
|
1671
|
+
if (spec is not None
|
|
1672
|
+
and spec.kind == "udt"
|
|
1673
|
+
and spec.name in DRAWING_TYPE_TO_CPP)
|
|
1674
|
+
else None
|
|
1675
|
+
)
|
|
1676
|
+
self._lexical_drawing_types[name] = (
|
|
1677
|
+
DRAWING_TYPE_TO_CPP[drawing_name]
|
|
1678
|
+
if drawing_name is not None
|
|
1679
|
+
else None
|
|
1680
|
+
)
|
|
1681
|
+
self._lexical_udt_types[name] = (
|
|
1682
|
+
spec.name
|
|
1683
|
+
if (spec is not None
|
|
1684
|
+
and spec.kind == "udt"
|
|
1685
|
+
and spec.name in self._udt_defs)
|
|
1686
|
+
else None
|
|
1687
|
+
)
|
|
1688
|
+
self._lexical_series_bindings[name] = False
|
|
1689
|
+
self._lexical_known_var_tombstones.add(name)
|
|
842
1690
|
try:
|
|
843
1691
|
for s in node.body:
|
|
844
1692
|
self._visit_stmt(s, lines, indent + 1)
|
|
@@ -870,22 +1718,21 @@ class StmtVisitor:
|
|
|
870
1718
|
prefix = "if" if i == 0 else "else if"
|
|
871
1719
|
case_val = self._visit_expr(case_expr)
|
|
872
1720
|
lines.append(f"{pad}{prefix} ({expr_var} == {case_val}) {{")
|
|
873
|
-
|
|
874
|
-
self._visit_stmt(s, lines, indent + 1)
|
|
1721
|
+
self._visit_block_statements(case_body, lines, indent + 1)
|
|
875
1722
|
lines.append(f"{pad}}}")
|
|
876
1723
|
else:
|
|
877
1724
|
for i, (case_expr, case_body) in enumerate(node.cases):
|
|
878
1725
|
prefix = "if" if i == 0 else "else if"
|
|
879
1726
|
cond = self._visit_expr(case_expr)
|
|
880
1727
|
lines.append(f"{pad}{prefix} ({cond}) {{")
|
|
881
|
-
|
|
882
|
-
self._visit_stmt(s, lines, indent + 1)
|
|
1728
|
+
self._visit_block_statements(case_body, lines, indent + 1)
|
|
883
1729
|
lines.append(f"{pad}}}")
|
|
884
1730
|
|
|
885
1731
|
if node.default_body:
|
|
886
1732
|
lines.append(f"{pad}else {{")
|
|
887
|
-
|
|
888
|
-
|
|
1733
|
+
self._visit_block_statements(
|
|
1734
|
+
node.default_body, lines, indent + 1
|
|
1735
|
+
)
|
|
889
1736
|
lines.append(f"{pad}}}")
|
|
890
1737
|
|
|
891
1738
|
# ------------------------------------------------------------------
|
|
@@ -894,8 +1741,14 @@ class StmtVisitor:
|
|
|
894
1741
|
|
|
895
1742
|
# _default_for_type lives on TypeInferer — see codegen/types.py.
|
|
896
1743
|
|
|
897
|
-
def _emit_body_with_assign(
|
|
898
|
-
|
|
1744
|
+
def _emit_body_with_assign(
|
|
1745
|
+
self,
|
|
1746
|
+
body: list,
|
|
1747
|
+
target: str,
|
|
1748
|
+
lines: list[str],
|
|
1749
|
+
indent: int,
|
|
1750
|
+
target_cpp_type: str | None = None,
|
|
1751
|
+
) -> None:
|
|
899
1752
|
"""Emit a body block where the last expression becomes an assignment."""
|
|
900
1753
|
if not body:
|
|
901
1754
|
return
|
|
@@ -912,36 +1765,93 @@ class StmtVisitor:
|
|
|
912
1765
|
if self._call_is_void(stmt.expr):
|
|
913
1766
|
self._visit_stmt(stmt, lines, indent)
|
|
914
1767
|
return
|
|
915
|
-
cpp = self.
|
|
1768
|
+
cpp = self._visit_rhs_value(
|
|
1769
|
+
stmt.expr,
|
|
1770
|
+
target_cpp_type=target_cpp_type,
|
|
1771
|
+
)
|
|
916
1772
|
pad = " " * indent
|
|
917
1773
|
lines.append(f"{pad}{target} = {cpp};")
|
|
918
1774
|
elif isinstance(stmt, IfStmt):
|
|
919
1775
|
# Nested if expression
|
|
920
|
-
self._visit_if_switch_expr(
|
|
1776
|
+
self._visit_if_switch_expr(
|
|
1777
|
+
stmt,
|
|
1778
|
+
target,
|
|
1779
|
+
lines,
|
|
1780
|
+
indent,
|
|
1781
|
+
target_cpp_type=target_cpp_type,
|
|
1782
|
+
)
|
|
921
1783
|
elif isinstance(stmt, SwitchStmt):
|
|
922
|
-
self._visit_if_switch_expr(
|
|
1784
|
+
self._visit_if_switch_expr(
|
|
1785
|
+
stmt,
|
|
1786
|
+
target,
|
|
1787
|
+
lines,
|
|
1788
|
+
indent,
|
|
1789
|
+
target_cpp_type=target_cpp_type,
|
|
1790
|
+
)
|
|
923
1791
|
else:
|
|
924
1792
|
self._visit_stmt(stmt, lines, indent)
|
|
925
1793
|
else:
|
|
926
1794
|
self._visit_stmt(stmt, lines, indent)
|
|
927
1795
|
|
|
928
|
-
def _visit_if_switch_expr(
|
|
929
|
-
|
|
1796
|
+
def _visit_if_switch_expr(
|
|
1797
|
+
self,
|
|
1798
|
+
node,
|
|
1799
|
+
target: str,
|
|
1800
|
+
lines: list[str],
|
|
1801
|
+
indent: int,
|
|
1802
|
+
target_cpp_type: str | None = None,
|
|
1803
|
+
) -> None:
|
|
930
1804
|
"""Emit an if/switch used as an expression, assigning to target."""
|
|
931
1805
|
pad = " " * indent
|
|
1806
|
+
nullable_collection_target = self._is_nullable_collection_cpp_type(
|
|
1807
|
+
target_cpp_type
|
|
1808
|
+
)
|
|
1809
|
+
|
|
1810
|
+
def emit_implicit_na_fallback() -> None:
|
|
1811
|
+
"""Reset a nullable ID when an expression has no matching arm."""
|
|
1812
|
+
lines.append(f"{pad}else {{")
|
|
1813
|
+
lines.append(
|
|
1814
|
+
f"{pad} {target} = {target_cpp_type}{{}};"
|
|
1815
|
+
)
|
|
1816
|
+
lines.append(f"{pad}}}")
|
|
1817
|
+
|
|
932
1818
|
if isinstance(node, IfStmt):
|
|
933
1819
|
cond = self._visit_expr(node.condition)
|
|
934
1820
|
lines.append(f"{pad}if ({cond}) {{")
|
|
935
|
-
self.
|
|
1821
|
+
self._emit_block_with_assign(
|
|
1822
|
+
node.body,
|
|
1823
|
+
target,
|
|
1824
|
+
lines,
|
|
1825
|
+
indent + 1,
|
|
1826
|
+
target_cpp_type=target_cpp_type,
|
|
1827
|
+
)
|
|
936
1828
|
lines.append(f"{pad}}}")
|
|
937
1829
|
if node.else_body:
|
|
938
1830
|
if len(node.else_body) == 1 and isinstance(node.else_body[0], IfStmt):
|
|
939
1831
|
lines[-1] = f"{pad}}} else"
|
|
940
|
-
self._visit_if_switch_expr(
|
|
1832
|
+
self._visit_if_switch_expr(
|
|
1833
|
+
node.else_body[0],
|
|
1834
|
+
target,
|
|
1835
|
+
lines,
|
|
1836
|
+
indent,
|
|
1837
|
+
target_cpp_type=target_cpp_type,
|
|
1838
|
+
)
|
|
941
1839
|
else:
|
|
942
1840
|
lines[-1] = f"{pad}}} else {{"
|
|
943
|
-
self.
|
|
1841
|
+
self._emit_block_with_assign(
|
|
1842
|
+
node.else_body,
|
|
1843
|
+
target,
|
|
1844
|
+
lines,
|
|
1845
|
+
indent + 1,
|
|
1846
|
+
target_cpp_type=target_cpp_type,
|
|
1847
|
+
)
|
|
944
1848
|
lines.append(f"{pad}}}")
|
|
1849
|
+
elif nullable_collection_target:
|
|
1850
|
+
# A Pine if-expression without an else evaluates to ``na`` on
|
|
1851
|
+
# the unmatched path. This assignment is essential for
|
|
1852
|
+
# non-var globals and reassignments: retaining the prior bar's
|
|
1853
|
+
# map/matrix ID would turn the expression into implicit state.
|
|
1854
|
+
emit_implicit_na_fallback()
|
|
945
1855
|
elif isinstance(node, SwitchStmt):
|
|
946
1856
|
if node.expr:
|
|
947
1857
|
expr_var = f"__switch_val_{self._switch_counter}"
|
|
@@ -951,16 +1861,50 @@ class StmtVisitor:
|
|
|
951
1861
|
prefix = "if" if i == 0 else "else if"
|
|
952
1862
|
case_val = self._visit_expr(case_expr)
|
|
953
1863
|
lines.append(f"{pad}{prefix} ({expr_var} == {case_val}) {{")
|
|
954
|
-
self.
|
|
1864
|
+
self._emit_block_with_assign(
|
|
1865
|
+
case_body,
|
|
1866
|
+
target,
|
|
1867
|
+
lines,
|
|
1868
|
+
indent + 1,
|
|
1869
|
+
target_cpp_type=target_cpp_type,
|
|
1870
|
+
)
|
|
955
1871
|
lines.append(f"{pad}}}")
|
|
956
1872
|
else:
|
|
957
1873
|
for i, (case_expr, case_body) in enumerate(node.cases):
|
|
958
1874
|
prefix = "if" if i == 0 else "else if"
|
|
959
1875
|
cond = self._visit_expr(case_expr)
|
|
960
1876
|
lines.append(f"{pad}{prefix} ({cond}) {{")
|
|
961
|
-
self.
|
|
1877
|
+
self._emit_block_with_assign(
|
|
1878
|
+
case_body,
|
|
1879
|
+
target,
|
|
1880
|
+
lines,
|
|
1881
|
+
indent + 1,
|
|
1882
|
+
target_cpp_type=target_cpp_type,
|
|
1883
|
+
)
|
|
962
1884
|
lines.append(f"{pad}}}")
|
|
963
1885
|
if node.default_body:
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1886
|
+
if node.cases:
|
|
1887
|
+
lines.append(f"{pad}else {{")
|
|
1888
|
+
self._emit_block_with_assign(
|
|
1889
|
+
node.default_body,
|
|
1890
|
+
target,
|
|
1891
|
+
lines,
|
|
1892
|
+
indent + 1,
|
|
1893
|
+
target_cpp_type=target_cpp_type,
|
|
1894
|
+
)
|
|
1895
|
+
lines.append(f"{pad}}}")
|
|
1896
|
+
else:
|
|
1897
|
+
self._emit_block_with_assign(
|
|
1898
|
+
node.default_body,
|
|
1899
|
+
target,
|
|
1900
|
+
lines,
|
|
1901
|
+
indent,
|
|
1902
|
+
target_cpp_type=target_cpp_type,
|
|
1903
|
+
)
|
|
1904
|
+
elif nullable_collection_target:
|
|
1905
|
+
if node.cases:
|
|
1906
|
+
emit_implicit_na_fallback()
|
|
1907
|
+
else:
|
|
1908
|
+
lines.append(
|
|
1909
|
+
f"{pad}{target} = {target_cpp_type}{{}};"
|
|
1910
|
+
)
|