@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
|
@@ -59,7 +59,6 @@ State written by ``_emit_func_def`` (the per-function emit context):
|
|
|
59
59
|
- ``self._current_func_locals`` (``set[str]``).
|
|
60
60
|
- ``self._active_ta_remap`` (``dict[str, str]``).
|
|
61
61
|
- ``self._active_var_remap`` (``dict[str, str]``).
|
|
62
|
-
- ``self._in_ta_func_variant`` (``bool``).
|
|
63
62
|
- ``self._active_call_site_idx`` (``int | None``).
|
|
64
63
|
|
|
65
64
|
Sibling-mixin methods consumed via ``self``:
|
|
@@ -87,7 +86,7 @@ from ..ast_nodes import (
|
|
|
87
86
|
ExprStmt, FuncCall, Identifier, IfStmt, SwitchStmt, VarDecl,
|
|
88
87
|
)
|
|
89
88
|
from ..analyzer import FuncInfo
|
|
90
|
-
from ..symbols import
|
|
89
|
+
from ..symbols import PineType, method_receiver_cpp_token
|
|
91
90
|
from .tables import (
|
|
92
91
|
BAR_SERIES_PUSH,
|
|
93
92
|
DRAWING_TYPE_TO_CPP,
|
|
@@ -110,6 +109,8 @@ class TopLevelEmitter:
|
|
|
110
109
|
lines.append('#include <pineforge/math.hpp>')
|
|
111
110
|
lines.append('#include <pineforge/series.hpp>')
|
|
112
111
|
lines.append('#include <pineforge/na.hpp>')
|
|
112
|
+
if getattr(self, "_uses_map", False):
|
|
113
|
+
lines.append('#include <pineforge/map.hpp>')
|
|
113
114
|
lines.append("#include <cstdint>")
|
|
114
115
|
lines.append("#include <cmath>")
|
|
115
116
|
lines.append("#include <algorithm>")
|
|
@@ -117,9 +118,16 @@ class TopLevelEmitter:
|
|
|
117
118
|
lines.append("#include <numeric>")
|
|
118
119
|
lines.append("#include <string>")
|
|
119
120
|
lines.append("#include <vector>")
|
|
121
|
+
if getattr(self, "_udt_defs", {}):
|
|
122
|
+
lines.append("#include <deque>")
|
|
123
|
+
lines.append("#include <functional>")
|
|
124
|
+
lines.append("#include <limits>")
|
|
120
125
|
lines.append("#include <tuple>")
|
|
121
126
|
lines.append("#include <optional>")
|
|
122
127
|
lines.append("#include <type_traits>")
|
|
128
|
+
if getattr(self, "_udt_defs", {}):
|
|
129
|
+
lines.append("#include <stdexcept>")
|
|
130
|
+
lines.append("#include <utility>")
|
|
123
131
|
lines.append("#include <memory>")
|
|
124
132
|
lines.append("#include <mutex>")
|
|
125
133
|
lines.append("#include <unordered_map>")
|
|
@@ -136,8 +144,7 @@ class TopLevelEmitter:
|
|
|
136
144
|
# is present in the TU. Float matrices route through PineMatrix in
|
|
137
145
|
# matrix.hpp; pulling in the generic header otherwise is a wasted
|
|
138
146
|
# include.
|
|
139
|
-
|
|
140
|
-
if any(spec.element != float_spec for spec in self._matrix_specs.values()):
|
|
147
|
+
if self._detect_generic_matrix_usage():
|
|
141
148
|
lines.append('#include <pineforge/generic_matrix.hpp>')
|
|
142
149
|
# Drawing-objects-as-data runtime (line/box/label/linefill arenas +
|
|
143
150
|
# ChartPoint). Gated on _uses_drawing so non-drawing strategies stay
|
|
@@ -277,26 +284,272 @@ class TopLevelEmitter:
|
|
|
277
284
|
members.append(name)
|
|
278
285
|
return members
|
|
279
286
|
|
|
287
|
+
def _emit_handle_checkpoint_traits(self, lines: list[str]) -> None:
|
|
288
|
+
"""Emit recursive rollback adapters for shared-ID collection state.
|
|
289
|
+
|
|
290
|
+
This support block is emitted for scripts using a shared-ID map/matrix
|
|
291
|
+
or an authored UDT arena. The primary trait retains the historical
|
|
292
|
+
value-copy checkpoint for ordinary runtime state; specialized map,
|
|
293
|
+
matrix, vector, UDT-record, and arena traits snapshot their owned state
|
|
294
|
+
without recursively following nested UDT handle graphs.
|
|
295
|
+
"""
|
|
296
|
+
checkpoint_traits = self._checkpoint_traits_cpp_type()
|
|
297
|
+
lines.extend([
|
|
298
|
+
"template <typename _PFValue>",
|
|
299
|
+
f"struct {checkpoint_traits} {{",
|
|
300
|
+
" using snapshot_type = _PFValue;",
|
|
301
|
+
" static snapshot_type take(const _PFValue& value) { return value; }",
|
|
302
|
+
" static void restore(_PFValue& value, const snapshot_type& snapshot) {",
|
|
303
|
+
" value = snapshot;",
|
|
304
|
+
" }",
|
|
305
|
+
"};",
|
|
306
|
+
"",
|
|
307
|
+
])
|
|
308
|
+
|
|
309
|
+
if getattr(self, "_udt_defs", {}):
|
|
310
|
+
coordinator_type = self._udt_undo_coordinator_cpp_name
|
|
311
|
+
lines.extend([
|
|
312
|
+
"template <>",
|
|
313
|
+
f"struct {checkpoint_traits}<{coordinator_type}> {{",
|
|
314
|
+
f" using coordinator_type = {coordinator_type};",
|
|
315
|
+
" using snapshot_type = typename coordinator_type::Snapshot;",
|
|
316
|
+
" static snapshot_type take(coordinator_type& value) {",
|
|
317
|
+
" return value.snapshot();",
|
|
318
|
+
" }",
|
|
319
|
+
" static void restore(coordinator_type& value,",
|
|
320
|
+
" const snapshot_type& snapshot) {",
|
|
321
|
+
" value.restore(snapshot);",
|
|
322
|
+
" }",
|
|
323
|
+
"};",
|
|
324
|
+
"",
|
|
325
|
+
])
|
|
326
|
+
|
|
327
|
+
if getattr(self, "_uses_map", False):
|
|
328
|
+
lines.extend([
|
|
329
|
+
"template <typename _PFKey, typename _PFValue>",
|
|
330
|
+
f"struct {checkpoint_traits}<PineMap<_PFKey, _PFValue>> {{",
|
|
331
|
+
" using map_type = PineMap<_PFKey, _PFValue>;",
|
|
332
|
+
" static_assert(map_type::snapshot_supported,",
|
|
333
|
+
' "generated map checkpoints require primitive map values");',
|
|
334
|
+
" using snapshot_type = std::optional<typename map_type::Snapshot>;",
|
|
335
|
+
" static snapshot_type take(const map_type& value) {",
|
|
336
|
+
" if (value.is_na()) return std::nullopt;",
|
|
337
|
+
" return value.snapshot();",
|
|
338
|
+
" }",
|
|
339
|
+
" static void restore(map_type& value, const snapshot_type& snapshot) {",
|
|
340
|
+
" if (!snapshot) {",
|
|
341
|
+
" value = map_type{};",
|
|
342
|
+
" return;",
|
|
343
|
+
" }",
|
|
344
|
+
" value.restore(*snapshot);",
|
|
345
|
+
" }",
|
|
346
|
+
"};",
|
|
347
|
+
"",
|
|
348
|
+
])
|
|
349
|
+
|
|
350
|
+
if getattr(self, "_uses_matrix", False):
|
|
351
|
+
lines.extend([
|
|
352
|
+
"template <>",
|
|
353
|
+
f"struct {checkpoint_traits}<PineMatrix> {{",
|
|
354
|
+
" using matrix_type = PineMatrix;",
|
|
355
|
+
" using snapshot_type = std::optional<typename matrix_type::Snapshot>;",
|
|
356
|
+
" static snapshot_type take(const matrix_type& value) {",
|
|
357
|
+
" if (value.is_na()) return std::nullopt;",
|
|
358
|
+
" return value.snapshot();",
|
|
359
|
+
" }",
|
|
360
|
+
" static void restore(matrix_type& value, const snapshot_type& snapshot) {",
|
|
361
|
+
" if (!snapshot) {",
|
|
362
|
+
" value = matrix_type{};",
|
|
363
|
+
" return;",
|
|
364
|
+
" }",
|
|
365
|
+
" value.restore(*snapshot);",
|
|
366
|
+
" }",
|
|
367
|
+
"};",
|
|
368
|
+
"",
|
|
369
|
+
])
|
|
370
|
+
|
|
371
|
+
if self._detect_generic_matrix_usage():
|
|
372
|
+
lines.extend([
|
|
373
|
+
"template <typename _PFElement>",
|
|
374
|
+
f"struct {checkpoint_traits}<PineGenericMatrix<_PFElement>> {{",
|
|
375
|
+
" using matrix_type = PineGenericMatrix<_PFElement>;",
|
|
376
|
+
f" using element_traits = {checkpoint_traits}<_PFElement>;",
|
|
377
|
+
" using element_snapshot = typename element_traits::snapshot_type;",
|
|
378
|
+
" struct active_snapshot {",
|
|
379
|
+
" typename matrix_type::Snapshot outer;",
|
|
380
|
+
" int rows;",
|
|
381
|
+
" int columns;",
|
|
382
|
+
" std::vector<element_snapshot> elements;",
|
|
383
|
+
" };",
|
|
384
|
+
" using snapshot_type = std::optional<active_snapshot>;",
|
|
385
|
+
" static snapshot_type take(const matrix_type& value) {",
|
|
386
|
+
" if (value.is_na()) return std::nullopt;",
|
|
387
|
+
" active_snapshot snapshot{value.snapshot(), value.rows(),",
|
|
388
|
+
" value.columns(), {}};",
|
|
389
|
+
" snapshot.elements.reserve(",
|
|
390
|
+
" static_cast<std::size_t>(snapshot.rows) *",
|
|
391
|
+
" static_cast<std::size_t>(snapshot.columns));",
|
|
392
|
+
" for (int row = 0; row < snapshot.rows; ++row) {",
|
|
393
|
+
" for (int column = 0; column < snapshot.columns; ++column) {",
|
|
394
|
+
" snapshot.elements.push_back(",
|
|
395
|
+
" element_traits::take(value.get(row, column)));",
|
|
396
|
+
" }",
|
|
397
|
+
" }",
|
|
398
|
+
" return snapshot;",
|
|
399
|
+
" }",
|
|
400
|
+
" static void restore(matrix_type& value, const snapshot_type& snapshot) {",
|
|
401
|
+
" if (!snapshot) {",
|
|
402
|
+
" value = matrix_type{};",
|
|
403
|
+
" return;",
|
|
404
|
+
" }",
|
|
405
|
+
" value.restore(snapshot->outer);",
|
|
406
|
+
" std::size_t index = 0;",
|
|
407
|
+
" for (int row = 0; row < snapshot->rows; ++row) {",
|
|
408
|
+
" for (int column = 0; column < snapshot->columns; ++column) {",
|
|
409
|
+
" _PFElement element = value.get(row, column);",
|
|
410
|
+
" element_traits::restore(element, snapshot->elements[index++]);",
|
|
411
|
+
" value.set(row, column, element);",
|
|
412
|
+
" }",
|
|
413
|
+
" }",
|
|
414
|
+
" }",
|
|
415
|
+
"};",
|
|
416
|
+
"",
|
|
417
|
+
])
|
|
418
|
+
|
|
419
|
+
lines.extend([
|
|
420
|
+
"template <typename _PFElement, typename _PFAllocator>",
|
|
421
|
+
f"struct {checkpoint_traits}<std::vector<_PFElement, _PFAllocator>> {{",
|
|
422
|
+
f" using element_traits = {checkpoint_traits}<_PFElement>;",
|
|
423
|
+
" using element_snapshot = typename element_traits::snapshot_type;",
|
|
424
|
+
" using snapshot_type = std::vector<element_snapshot>;",
|
|
425
|
+
" static snapshot_type take(",
|
|
426
|
+
" const std::vector<_PFElement, _PFAllocator>& value) {",
|
|
427
|
+
" snapshot_type snapshot;",
|
|
428
|
+
" snapshot.reserve(value.size());",
|
|
429
|
+
" for (std::size_t index = 0; index < value.size(); ++index) {",
|
|
430
|
+
" const _PFElement element = value[index];",
|
|
431
|
+
" snapshot.push_back(element_traits::take(element));",
|
|
432
|
+
" }",
|
|
433
|
+
" return snapshot;",
|
|
434
|
+
" }",
|
|
435
|
+
" static void restore(",
|
|
436
|
+
" std::vector<_PFElement, _PFAllocator>& value,",
|
|
437
|
+
" const snapshot_type& snapshot) {",
|
|
438
|
+
" value.clear();",
|
|
439
|
+
" value.reserve(snapshot.size());",
|
|
440
|
+
" for (const auto& element_snapshot_value : snapshot) {",
|
|
441
|
+
" _PFElement element{};",
|
|
442
|
+
" element_traits::restore(element, element_snapshot_value);",
|
|
443
|
+
" value.push_back(element);",
|
|
444
|
+
" }",
|
|
445
|
+
" }",
|
|
446
|
+
"};",
|
|
447
|
+
"",
|
|
448
|
+
])
|
|
449
|
+
|
|
450
|
+
# Snapshot backing records field-by-field. A nested UDT field is only
|
|
451
|
+
# a numeric handle, so the primary trait copies its ID without recursing
|
|
452
|
+
# into another record. This makes self/cyclic object graphs finite;
|
|
453
|
+
# each arena snapshots every owned record exactly once. Shared-ID map
|
|
454
|
+
# and matrix fields still enter their specialized recursive traits.
|
|
455
|
+
for type_name, fields in self._udt_defs.items():
|
|
456
|
+
record_type = self._udt_record_cpp_type(type_name)
|
|
457
|
+
emitted_fields = [
|
|
458
|
+
field
|
|
459
|
+
for field in fields
|
|
460
|
+
if field.name not in self._udt_omitted_fields.get(type_name, set())
|
|
461
|
+
]
|
|
462
|
+
checkpoint_fields = [field.name for field in emitted_fields]
|
|
463
|
+
lines.append("template <>")
|
|
464
|
+
lines.append(f"struct {checkpoint_traits}<{record_type}> {{")
|
|
465
|
+
lines.append(" struct snapshot_type {")
|
|
466
|
+
for index, field_name in enumerate(checkpoint_fields):
|
|
467
|
+
lines.append(
|
|
468
|
+
f" {checkpoint_traits}<"
|
|
469
|
+
f"decltype({record_type}::{field_name})>::snapshot_type "
|
|
470
|
+
f"_pf_field_{index};"
|
|
471
|
+
)
|
|
472
|
+
lines.append(" };")
|
|
473
|
+
lines.append(
|
|
474
|
+
f" static snapshot_type take(const {record_type}& value) {{"
|
|
475
|
+
)
|
|
476
|
+
lines.append(" return snapshot_type{")
|
|
477
|
+
for field_name in checkpoint_fields:
|
|
478
|
+
lines.append(
|
|
479
|
+
f" {checkpoint_traits}<"
|
|
480
|
+
f"decltype({record_type}::{field_name})>::take(value.{field_name}),"
|
|
481
|
+
)
|
|
482
|
+
lines.append(" };")
|
|
483
|
+
lines.append(" }")
|
|
484
|
+
lines.append(
|
|
485
|
+
f" static void restore({record_type}& value, "
|
|
486
|
+
"const snapshot_type& snapshot) {"
|
|
487
|
+
)
|
|
488
|
+
for index, field_name in enumerate(checkpoint_fields):
|
|
489
|
+
lines.append(
|
|
490
|
+
f" {checkpoint_traits}<"
|
|
491
|
+
f"decltype({record_type}::{field_name})>::restore("
|
|
492
|
+
f"value.{field_name}, snapshot._pf_field_{index});"
|
|
493
|
+
)
|
|
494
|
+
lines.append(" }")
|
|
495
|
+
lines.append("};")
|
|
496
|
+
lines.append("")
|
|
497
|
+
|
|
498
|
+
arena_type = self._udt_arena_cpp_type(type_name)
|
|
499
|
+
lines.append("template <>")
|
|
500
|
+
lines.append(f"struct {checkpoint_traits}<{arena_type}> {{")
|
|
501
|
+
lines.append(
|
|
502
|
+
f" using arena_type = {arena_type};"
|
|
503
|
+
)
|
|
504
|
+
lines.append(
|
|
505
|
+
" using snapshot_type = typename arena_type::Snapshot;"
|
|
506
|
+
)
|
|
507
|
+
lines.append(" static snapshot_type take(arena_type& value) {")
|
|
508
|
+
lines.append(" return value.snapshot();")
|
|
509
|
+
lines.append(" }")
|
|
510
|
+
lines.append(
|
|
511
|
+
" static void restore(arena_type& value, const snapshot_type& snapshot) {"
|
|
512
|
+
)
|
|
513
|
+
lines.append(" value.restore(snapshot);")
|
|
514
|
+
lines.append(" }")
|
|
515
|
+
lines.append("};")
|
|
516
|
+
lines.append("")
|
|
517
|
+
|
|
280
518
|
def _emit_script_state_hooks(self, lines: list[str], members: list[str]) -> None:
|
|
281
519
|
"""Emit the engine's Pine rollback checkpoint hook implementation.
|
|
282
520
|
|
|
283
|
-
The checkpoint owns
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
521
|
+
The checkpoint owns detached snapshots of all generated mutable state.
|
|
522
|
+
Shared-ID maps and matrices retain their original backing identity so
|
|
523
|
+
rollback updates every alias. Generated UDT arenas snapshot each owned
|
|
524
|
+
record once; nested UDT/map/matrix fields therefore restore through
|
|
525
|
+
their numeric/shared IDs without recursively walking object graphs.
|
|
526
|
+
Vectors snapshot their elements, including UDT handle values. Drawing
|
|
527
|
+
handles themselves are stable ids and their arenas are captured in the
|
|
528
|
+
same checkpoint.
|
|
289
529
|
|
|
290
530
|
The static assertions deliberately turn any future non-copyable member
|
|
291
531
|
into a compile failure instead of a nominal, shallow rollback. Engine
|
|
292
532
|
broker/order state lives in the base class and is intentionally absent:
|
|
293
533
|
fills must survive while Pine script variables roll back.
|
|
294
534
|
"""
|
|
535
|
+
checkpoint_traits = self._checkpoint_traits_cpp_type()
|
|
536
|
+
handle_aware = bool(
|
|
537
|
+
getattr(self, "_uses_map", False)
|
|
538
|
+
or getattr(self, "_uses_matrix", False)
|
|
539
|
+
or getattr(self, "_udt_defs", {})
|
|
540
|
+
)
|
|
295
541
|
lines.append(" struct _PFScriptState {")
|
|
296
542
|
for idx, name in enumerate(members):
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
543
|
+
if handle_aware:
|
|
544
|
+
lines.append(
|
|
545
|
+
f" {checkpoint_traits}<"
|
|
546
|
+
f"decltype(GeneratedStrategy::{name})>::snapshot_type "
|
|
547
|
+
f"_pf_value_{idx};"
|
|
548
|
+
)
|
|
549
|
+
else:
|
|
550
|
+
lines.append(
|
|
551
|
+
f" decltype(GeneratedStrategy::{name}) _pf_value_{idx};"
|
|
552
|
+
)
|
|
300
553
|
lines.append(" };")
|
|
301
554
|
lines.append(
|
|
302
555
|
" static_assert(std::is_copy_constructible_v<_PFScriptState>, "
|
|
@@ -311,16 +564,29 @@ class TopLevelEmitter:
|
|
|
311
564
|
lines.append(" void snapshot_script_state() override {")
|
|
312
565
|
lines.append(" _pf_script_state_checkpoint_.emplace(_PFScriptState{")
|
|
313
566
|
for name in members:
|
|
314
|
-
|
|
567
|
+
if handle_aware:
|
|
568
|
+
lines.append(
|
|
569
|
+
f" {checkpoint_traits}<"
|
|
570
|
+
f"decltype(GeneratedStrategy::{name})>::take({name}),"
|
|
571
|
+
)
|
|
572
|
+
else:
|
|
573
|
+
lines.append(f" {name},")
|
|
315
574
|
lines.append(" });")
|
|
316
575
|
lines.append(" }")
|
|
317
576
|
lines.append("")
|
|
318
577
|
lines.append(" void restore_script_state() override {")
|
|
319
578
|
lines.append(" if (!_pf_script_state_checkpoint_) return;")
|
|
320
579
|
for idx, name in enumerate(members):
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
580
|
+
if handle_aware:
|
|
581
|
+
lines.append(
|
|
582
|
+
f" {checkpoint_traits}<"
|
|
583
|
+
f"decltype(GeneratedStrategy::{name})>::restore("
|
|
584
|
+
f"this->{name}, _pf_script_state_checkpoint_->_pf_value_{idx});"
|
|
585
|
+
)
|
|
586
|
+
else:
|
|
587
|
+
lines.append(
|
|
588
|
+
f" this->{name} = _pf_script_state_checkpoint_->_pf_value_{idx};"
|
|
589
|
+
)
|
|
324
590
|
lines.append(" }")
|
|
325
591
|
lines.append("")
|
|
326
592
|
lines.append(" void commit_script_state() override {")
|
|
@@ -361,9 +627,20 @@ class TopLevelEmitter:
|
|
|
361
627
|
# incl. function-derived lengths) are safe: the `!_ta_initialized_`
|
|
362
628
|
# reset overwrites the placeholder before the first compute.
|
|
363
629
|
for a in site.ctor_args:
|
|
364
|
-
r = self.
|
|
630
|
+
r = self._resolve_ta_ctor_arg(a)
|
|
365
631
|
if (not self._is_compile_time_value(r)
|
|
366
632
|
and self._runtime_ctor_arg_for_reset(a) is None):
|
|
633
|
+
# A TA source reached through request.security can have
|
|
634
|
+
# several helper-bound constructor variants. Validate
|
|
635
|
+
# only variants of this exact source node before the
|
|
636
|
+
# ordinary guard fires, so an unstable requested-context
|
|
637
|
+
# expression keeps its provenance-specific diagnostic
|
|
638
|
+
# without allowing a later, unrelated security error to
|
|
639
|
+
# reorder an earlier ordinary error.
|
|
640
|
+
if self._security_eval_info and site.node is not None:
|
|
641
|
+
self._collect_ta_runtime_resets(
|
|
642
|
+
security_source_node=site.node
|
|
643
|
+
)
|
|
367
644
|
self._codegen_error(
|
|
368
645
|
getattr(site, "node", None),
|
|
369
646
|
f"Unsupported TA constructor length '{a}' for "
|
|
@@ -373,7 +650,7 @@ class TopLevelEmitter:
|
|
|
373
650
|
hint=("Use a literal, an input.*() value, or "
|
|
374
651
|
"arithmetic over those for TA lengths."),
|
|
375
652
|
)
|
|
376
|
-
resolved = [self.
|
|
653
|
+
resolved = [self._resolve_ta_ctor_arg(a) for a in site.ctor_args]
|
|
377
654
|
# Compile-time placeholder for the init list; the runtime reset
|
|
378
655
|
# (when the arg is input-derived) overwrites it on the first bar.
|
|
379
656
|
safe_resolved = []
|
|
@@ -389,11 +666,18 @@ class TopLevelEmitter:
|
|
|
389
666
|
site = self.ctx.ta_call_sites[idx]
|
|
390
667
|
if not site.ctor_args:
|
|
391
668
|
continue
|
|
392
|
-
resolved = [self._resolve_known(a) for a in site.ctor_args]
|
|
393
|
-
safe_resolved = []
|
|
394
|
-
for r in resolved:
|
|
395
|
-
safe_resolved.append(r if self._is_compile_time_value(r) else "1")
|
|
396
669
|
for variant in variants:
|
|
670
|
+
ctor_args, _ctor_arg_stability = self._security_ta_ctor_args_for_variant(
|
|
671
|
+
info["sec_id"],
|
|
672
|
+
site,
|
|
673
|
+
variant.get("binding_stack", ()),
|
|
674
|
+
)
|
|
675
|
+
resolved = [self._resolve_ta_ctor_arg(a) for a in ctor_args]
|
|
676
|
+
safe_resolved = []
|
|
677
|
+
for r in resolved:
|
|
678
|
+
safe_resolved.append(
|
|
679
|
+
r if self._is_compile_time_value(r) else "1"
|
|
680
|
+
)
|
|
397
681
|
init_parts.append(f"{variant['member_name']}({', '.join(safe_resolved)})")
|
|
398
682
|
|
|
399
683
|
# Non-series var members with compile-time init (deduplicate by name)
|
|
@@ -403,27 +687,39 @@ class TopLevelEmitter:
|
|
|
403
687
|
continue
|
|
404
688
|
seen_ctor_vars.add(name)
|
|
405
689
|
safe = self._safe_name(name)
|
|
406
|
-
if name in self._array_vars
|
|
690
|
+
if (name in self._array_vars
|
|
691
|
+
or name in self._map_vars
|
|
692
|
+
or name in self._matrix_specs):
|
|
693
|
+
# Collection members are initialized in the first-bar block.
|
|
694
|
+
# A matrix declared as ``var matrix<T> x = na`` has no
|
|
695
|
+
# constructor call to replay there and its default constructor
|
|
696
|
+
# is already the correctly typed null ID. Letting the generic
|
|
697
|
+
# scalar path add ``x(na<double>())`` is ill-typed.
|
|
407
698
|
continue
|
|
408
|
-
#
|
|
409
|
-
#
|
|
410
|
-
#
|
|
411
|
-
# (possibly bar-dependent) initializer is lowered in the function's
|
|
412
|
-
# own scope (with its active var remap for clones).
|
|
699
|
+
# Callable-scoped ``var`` members initialize at their exact source
|
|
700
|
+
# declarations; exclude them from the constructor so source order,
|
|
701
|
+
# first reach, and the active clone remap remain authoritative.
|
|
413
702
|
if name in getattr(self, "_func_local_var_names", ()):
|
|
414
703
|
continue
|
|
704
|
+
# Runtime primitive initializers execute at their Pine declaration
|
|
705
|
+
# site under a per-member once flag. This preserves source-order
|
|
706
|
+
# dependencies and first-entry semantics for conditional blocks.
|
|
707
|
+
if name in self._runtime_scalar_var_init_members:
|
|
708
|
+
continue
|
|
415
709
|
# UDT-typed var members (``var SDZone z = na``) default-construct to
|
|
416
710
|
# na via the struct's in-class ``__pf_na = true``; a ctor init like
|
|
417
711
|
# ``z(na<double>())`` would not type-match the struct member.
|
|
418
|
-
|
|
712
|
+
member_udt_type = self._member_udt_type(name)
|
|
713
|
+
if member_udt_type in self._udt_defs:
|
|
419
714
|
continue
|
|
420
715
|
# Drawing handle var member (L-N3): ``var line x`` / ``var box b``
|
|
421
716
|
# default-construct to {-1} (na). A ``b(na<double>())`` ctor init
|
|
422
717
|
# would not type-match the handle struct — skip it (the in-class
|
|
423
718
|
# member default is the once-only persistent na init).
|
|
424
|
-
if name in self.
|
|
719
|
+
if (name in self._drawing_var_member_cpp_types
|
|
720
|
+
or member_udt_type in DRAWING_TYPE_TO_CPP):
|
|
425
721
|
continue
|
|
426
|
-
if
|
|
722
|
+
if safe not in self._series_var_member_names:
|
|
427
723
|
cpp_val = self._resolve_known(init_expr)
|
|
428
724
|
cpp_val = self._typed_na_init(cpp_val, name, ptype)
|
|
429
725
|
if self._is_compile_time_value(cpp_val):
|
|
@@ -608,8 +904,37 @@ class TopLevelEmitter:
|
|
|
608
904
|
lines.append(f"{pad}else {member}.update({value});")
|
|
609
905
|
|
|
610
906
|
def _emit_on_bar(self, lines: list[str]) -> None:
|
|
907
|
+
self._lexical_drawing_types = {}
|
|
908
|
+
self._lexical_udt_types = {}
|
|
909
|
+
self._lexical_series_bindings = {}
|
|
910
|
+
self._lexical_known_var_tombstones = set()
|
|
611
911
|
lines.append(" void on_bar(const Bar& bar) override {")
|
|
612
912
|
|
|
913
|
+
# A GeneratedStrategy handle may execute multiple batch runs or
|
|
914
|
+
# streaming lifecycles. BacktestEngine resets broker/base state, but
|
|
915
|
+
# generated members survive. Reset source-shaped lazy ROC clocks and
|
|
916
|
+
# their forced eager-fallback close history at the first genuine slot
|
|
917
|
+
# of each lifecycle. This lives in on_bar rather than a generated run
|
|
918
|
+
# wrapper because stream_begin() enters the base run path directly.
|
|
919
|
+
# COOF post-close recalculations have history_advances_new_bar()==false
|
|
920
|
+
# and therefore preserve the current committed clock/base.
|
|
921
|
+
if self._lazy_saturated_roc3_clock_by_node:
|
|
922
|
+
lines.append(
|
|
923
|
+
" if (history_advances_new_bar() && bar_index_ == 0) {"
|
|
924
|
+
)
|
|
925
|
+
for clock_name in self._lazy_saturated_roc3_clock_by_node.values():
|
|
926
|
+
lines.append(f" {clock_name}.reset();")
|
|
927
|
+
lines.append(
|
|
928
|
+
f" {self._lazy_saturated_roc3_history_name}.clear();"
|
|
929
|
+
)
|
|
930
|
+
lines.append(" }")
|
|
931
|
+
self._emit_history_series_write(
|
|
932
|
+
lines,
|
|
933
|
+
" ",
|
|
934
|
+
self._lazy_saturated_roc3_history_name,
|
|
935
|
+
"current_bar_.close",
|
|
936
|
+
)
|
|
937
|
+
|
|
613
938
|
# reset_run_state() owns engine/broker state, while these generated
|
|
614
939
|
# Series members belong to the strategy object. Clear all of them on
|
|
615
940
|
# the first genuine history slot of bar zero, unconditionally: a site
|
|
@@ -659,11 +984,36 @@ class TopLevelEmitter:
|
|
|
659
984
|
if self.ctx.var_members:
|
|
660
985
|
lines.append(" if (!_var_initialized) {")
|
|
661
986
|
for name, ptype, init_expr in self.ctx.var_members:
|
|
662
|
-
#
|
|
663
|
-
#
|
|
987
|
+
# Callable-scoped ``var`` members initialize at their exact
|
|
988
|
+
# declaration statements, not in the global on_bar preamble.
|
|
664
989
|
if name in getattr(self, "_func_local_var_names", ()):
|
|
665
990
|
continue
|
|
666
991
|
safe = self._safe_name(name)
|
|
992
|
+
runtime_info = self._runtime_scalar_var_init_by_member.get(name)
|
|
993
|
+
if (runtime_info is not None
|
|
994
|
+
and runtime_info.get("drawing_cpp") is not None):
|
|
995
|
+
if runtime_info.get("is_series"):
|
|
996
|
+
self._emit_history_series_write(
|
|
997
|
+
lines,
|
|
998
|
+
" ",
|
|
999
|
+
safe,
|
|
1000
|
+
f"{runtime_info['drawing_cpp']}{{}}",
|
|
1001
|
+
)
|
|
1002
|
+
continue
|
|
1003
|
+
# Declaration-site initialization owns nullable collection
|
|
1004
|
+
# members carrying runtime metadata. A persistent map/matrix
|
|
1005
|
+
# if/switch initializer must not also enter the legacy
|
|
1006
|
+
# first-bar aggregate preamble (which cannot lower a block
|
|
1007
|
+
# expression and formerly emitted ``/* unknown */`` for maps).
|
|
1008
|
+
# Keep primitive and ternary paths on their established order.
|
|
1009
|
+
runtime_spec = (
|
|
1010
|
+
runtime_info.get("type_spec")
|
|
1011
|
+
if runtime_info is not None
|
|
1012
|
+
else None
|
|
1013
|
+
)
|
|
1014
|
+
if (runtime_spec is not None
|
|
1015
|
+
and runtime_spec.kind in {"map", "matrix"}):
|
|
1016
|
+
continue
|
|
667
1017
|
if name in self._array_vars:
|
|
668
1018
|
for stmt in self.ctx.ast.body:
|
|
669
1019
|
if isinstance(stmt, VarDecl) and stmt.name == name:
|
|
@@ -672,39 +1022,55 @@ class TopLevelEmitter:
|
|
|
672
1022
|
break
|
|
673
1023
|
continue
|
|
674
1024
|
if name in self._matrix_specs:
|
|
675
|
-
# Matrix vars
|
|
1025
|
+
# Matrix vars are nullable ID handles. Initialize from the
|
|
1026
|
+
# authored RHS in declaration order, not only from a
|
|
1027
|
+
# matrix.new(...) call: ``var matrix<int> alias = source``
|
|
1028
|
+
# must copy source's ID once, while a later local rebind
|
|
1029
|
+
# remains independent.
|
|
676
1030
|
for stmt in self.ctx.ast.body:
|
|
677
|
-
if isinstance(stmt, VarDecl) and stmt.name == name
|
|
678
|
-
cpp_val = self.
|
|
1031
|
+
if isinstance(stmt, VarDecl) and stmt.name == name:
|
|
1032
|
+
cpp_val = self._visit_rhs_value(
|
|
1033
|
+
stmt.value,
|
|
1034
|
+
name,
|
|
1035
|
+
target_cpp_type=self._type_spec_to_cpp(
|
|
1036
|
+
self._matrix_specs[name]
|
|
1037
|
+
),
|
|
1038
|
+
)
|
|
679
1039
|
lines.append(f" {safe} = {cpp_val};")
|
|
680
1040
|
break
|
|
681
1041
|
continue
|
|
682
1042
|
if name in self._map_vars:
|
|
683
1043
|
for stmt in self.ctx.ast.body:
|
|
684
1044
|
if isinstance(stmt, VarDecl) and stmt.name == name:
|
|
685
|
-
cpp_val = self.
|
|
1045
|
+
cpp_val = self._visit_rhs_value(
|
|
1046
|
+
stmt.value,
|
|
1047
|
+
name,
|
|
1048
|
+
target_cpp_type=self._type_spec_to_cpp(
|
|
1049
|
+
self._map_spec_for_name(name)
|
|
1050
|
+
),
|
|
1051
|
+
)
|
|
686
1052
|
lines.append(f" {safe} = {cpp_val};")
|
|
687
1053
|
break
|
|
688
1054
|
continue
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
1055
|
+
if name in self._runtime_scalar_var_init_members:
|
|
1056
|
+
continue
|
|
1057
|
+
# Persistent authored UDT variables initialize from their full
|
|
1058
|
+
# source RHS, not only from ``Type.new``. In particular,
|
|
1059
|
+
# ``var Item alias = original`` must copy the numeric handle
|
|
1060
|
+
# once and therefore alias the same arena record.
|
|
1061
|
+
udt_name = self._member_udt_type(name)
|
|
1062
|
+
if udt_name in self._udt_defs:
|
|
1063
|
+
for stmt in self.ctx.ast.body:
|
|
1064
|
+
if isinstance(stmt, VarDecl) and stmt.name == name:
|
|
1065
|
+
cpp_val = self._visit_rhs_value(
|
|
1066
|
+
stmt.value,
|
|
1067
|
+
name,
|
|
1068
|
+
target_cpp_type=udt_name,
|
|
1069
|
+
)
|
|
1070
|
+
lines.append(f" {safe} = {cpp_val};")
|
|
1071
|
+
break
|
|
706
1072
|
continue
|
|
707
|
-
if name
|
|
1073
|
+
if self._binding_is_series(name, safe):
|
|
708
1074
|
cpp_val = self._resolve_known(init_expr)
|
|
709
1075
|
cpp_val = self._typed_na_init(cpp_val, name, ptype)
|
|
710
1076
|
lines.append(f" {safe}.push({cpp_val});")
|
|
@@ -722,9 +1088,26 @@ class TopLevelEmitter:
|
|
|
722
1088
|
lines.append(" } else {")
|
|
723
1089
|
for name, _, _ in self.ctx.var_members:
|
|
724
1090
|
safe = self._safe_name(name)
|
|
1091
|
+
runtime_info = self._runtime_scalar_var_init_by_member.get(name)
|
|
1092
|
+
if (runtime_info is not None
|
|
1093
|
+
and runtime_info.get("drawing_cpp") is not None):
|
|
1094
|
+
if runtime_info.get("is_series"):
|
|
1095
|
+
lines.append(f" if ({runtime_info['flag']}) {{")
|
|
1096
|
+
self._emit_history_series_write(
|
|
1097
|
+
lines, " ", safe, f"{safe}[0]"
|
|
1098
|
+
)
|
|
1099
|
+
lines.append(" } else {")
|
|
1100
|
+
self._emit_history_series_write(
|
|
1101
|
+
lines,
|
|
1102
|
+
" ",
|
|
1103
|
+
safe,
|
|
1104
|
+
f"{runtime_info['drawing_cpp']}{{}}",
|
|
1105
|
+
)
|
|
1106
|
+
lines.append(" }")
|
|
1107
|
+
continue
|
|
725
1108
|
if name in self._array_vars:
|
|
726
1109
|
continue
|
|
727
|
-
if name
|
|
1110
|
+
if self._binding_is_series(name, safe):
|
|
728
1111
|
self._emit_history_series_write(
|
|
729
1112
|
lines, " ", safe, f"{safe}[0]"
|
|
730
1113
|
)
|
|
@@ -740,6 +1123,20 @@ class TopLevelEmitter:
|
|
|
740
1123
|
self._emit_history_series_write(
|
|
741
1124
|
lines, " ", cloned, f"{cloned}[0]"
|
|
742
1125
|
)
|
|
1126
|
+
# Context-sensitive nested helper instances own fresh
|
|
1127
|
+
# Series members outside the flat cs remap table. Advance
|
|
1128
|
+
# each exact fresh member once per bar as well; otherwise a
|
|
1129
|
+
# valid ``fresh[1]`` history read never moves past slot 0.
|
|
1130
|
+
for _owner, orig_safe, fresh_safe in self._fresh_var_members:
|
|
1131
|
+
if orig_safe != safe or fresh_safe in carry_emitted:
|
|
1132
|
+
continue
|
|
1133
|
+
carry_emitted.add(fresh_safe)
|
|
1134
|
+
self._emit_history_series_write(
|
|
1135
|
+
lines,
|
|
1136
|
+
" ",
|
|
1137
|
+
fresh_safe,
|
|
1138
|
+
f"{fresh_safe}[0]",
|
|
1139
|
+
)
|
|
743
1140
|
lines.append(" }")
|
|
744
1141
|
|
|
745
1142
|
# c. Push non-var series (they start fresh each bar with a push)
|
|
@@ -927,10 +1324,18 @@ class TopLevelEmitter:
|
|
|
927
1324
|
lines.append("")
|
|
928
1325
|
|
|
929
1326
|
def _emit_udt_method_cpp_name(self, fi: FuncInfo) -> str:
|
|
930
|
-
"""Stable C++ identifier for a
|
|
931
|
-
|
|
1327
|
+
"""Stable C++ identifier for a typed instance method."""
|
|
1328
|
+
receiver_spec = (
|
|
1329
|
+
fi.param_type_specs[0]
|
|
1330
|
+
if getattr(fi, "param_type_specs", None)
|
|
1331
|
+
else None
|
|
1332
|
+
)
|
|
1333
|
+
receiver = method_receiver_cpp_token(
|
|
1334
|
+
receiver_spec,
|
|
1335
|
+
fi.udt_type_name,
|
|
1336
|
+
)
|
|
932
1337
|
base = fi.node.name if fi.node else ""
|
|
933
|
-
return self._func_safe_name(f"_udt_{
|
|
1338
|
+
return self._func_safe_name(f"_udt_{receiver}_{base}")
|
|
934
1339
|
|
|
935
1340
|
def _emit_func_def(self, fi: FuncInfo, lines: list[str], call_site_idx: int | None = None,
|
|
936
1341
|
instance: dict | None = None) -> None:
|
|
@@ -948,51 +1353,191 @@ class TopLevelEmitter:
|
|
|
948
1353
|
if node is None:
|
|
949
1354
|
return
|
|
950
1355
|
|
|
951
|
-
|
|
1356
|
+
# Collection registries historically used raw variable names for the
|
|
1357
|
+
# whole translation unit. Emit each callable against copy-on-write
|
|
1358
|
+
# registries overlaid with its analyzer-captured lexical TypeSpecs, then
|
|
1359
|
+
# restore the top-level state. This keeps legacy readers working while
|
|
1360
|
+
# preventing local declarations/method dispatch from contaminating a
|
|
1361
|
+
# sibling UDF, UDT method, clone, or on_bar.
|
|
1362
|
+
prev_func_collection_specs = self._current_func_collection_specs
|
|
1363
|
+
prev_func_collection_shadows = self._current_func_collection_shadows
|
|
1364
|
+
prev_collection_types = self._collection_types
|
|
1365
|
+
prev_array_vars = self._array_vars
|
|
1366
|
+
prev_map_vars = self._map_vars
|
|
1367
|
+
prev_matrix_specs = self._matrix_specs
|
|
1368
|
+
# Start from top-level state only. Callable locals become visible at
|
|
1369
|
+
# their declaration statement (after its RHS), not at function entry:
|
|
1370
|
+
# preloading the analyzer's final inventory would make a later local
|
|
1371
|
+
# shadow an earlier read of a same-named global collection.
|
|
1372
|
+
self._current_func_collection_specs = {}
|
|
1373
|
+
self._current_func_collection_shadows = set()
|
|
1374
|
+
self._collection_types = dict(prev_collection_types)
|
|
1375
|
+
self._array_vars = set(prev_array_vars)
|
|
1376
|
+
self._map_vars = set(prev_map_vars)
|
|
1377
|
+
self._matrix_specs = dict(prev_matrix_specs)
|
|
1378
|
+
|
|
1379
|
+
is_method = (
|
|
1380
|
+
bool(getattr(fi, "is_udt_method", False))
|
|
1381
|
+
and fi.udt_type_name
|
|
1382
|
+
)
|
|
952
1383
|
|
|
953
1384
|
# Determine param types and set context for type inference inside body
|
|
954
1385
|
param_strs = []
|
|
955
1386
|
self._current_func_param_types = {}
|
|
956
1387
|
self._current_func_param_specs = {}
|
|
1388
|
+
param_hints = (getattr(node, "annotations", None) or {}).get(
|
|
1389
|
+
"param_type_hints", []
|
|
1390
|
+
)
|
|
1391
|
+
self._current_func_declared_param_names = {
|
|
1392
|
+
alias
|
|
1393
|
+
for i, param in enumerate(node.params)
|
|
1394
|
+
if i < len(param_hints) and param_hints[i]
|
|
1395
|
+
for alias in (param, self._safe_name(param))
|
|
1396
|
+
}
|
|
957
1397
|
self._current_func_series_params = set()
|
|
1398
|
+
self._current_func_series_param_types = {}
|
|
958
1399
|
self._udt_param_udt = {}
|
|
959
1400
|
func_sv = self.ctx.func_series_vars.get(fi.name, set())
|
|
1401
|
+
declared_param_specs = list(
|
|
1402
|
+
getattr(self.ctx, "func_declared_param_type_specs", {}).get(
|
|
1403
|
+
fi.name, ()
|
|
1404
|
+
)
|
|
1405
|
+
)
|
|
1406
|
+
variant_param_types = (
|
|
1407
|
+
getattr(self.ctx, "func_callsite_param_types", {}).get(
|
|
1408
|
+
(fi.name, call_site_idx), ()
|
|
1409
|
+
)
|
|
1410
|
+
if call_site_idx is not None
|
|
1411
|
+
else ()
|
|
1412
|
+
)
|
|
960
1413
|
for i, p in enumerate(node.params):
|
|
961
1414
|
spec = None
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1415
|
+
receiver_spec = None
|
|
1416
|
+
if is_method and i == 0:
|
|
1417
|
+
recv_spec = (
|
|
1418
|
+
fi.param_type_specs[i]
|
|
1419
|
+
if i < len(fi.param_type_specs)
|
|
1420
|
+
else None
|
|
1421
|
+
)
|
|
1422
|
+
receiver_spec = recv_spec
|
|
1423
|
+
|
|
1424
|
+
if (
|
|
1425
|
+
receiver_spec is not None
|
|
1426
|
+
and receiver_spec.kind == "primitive"
|
|
1427
|
+
and p in func_sv
|
|
1428
|
+
):
|
|
1429
|
+
# A primitive receiver used with history is a Series boundary,
|
|
1430
|
+
# just like an ordinary history-bearing UDF parameter.
|
|
1431
|
+
elem_cpp_t = self._series_param_element_cpp_type(
|
|
1432
|
+
fi, i, call_site_idx
|
|
1433
|
+
)
|
|
1434
|
+
cpp_t = f"const Series<{elem_cpp_t}>&"
|
|
1435
|
+
spec = receiver_spec
|
|
1436
|
+
self._current_func_series_params.add(p)
|
|
1437
|
+
self._current_func_series_param_types[p] = elem_cpp_t
|
|
1438
|
+
self._current_func_series_param_types[
|
|
1439
|
+
self._safe_name(p)
|
|
1440
|
+
] = elem_cpp_t
|
|
1441
|
+
elif is_method and i == 0 and fi.udt_type_name:
|
|
1442
|
+
# Receiver pass modes follow Pine's value/ID families. A user
|
|
1443
|
+
# UDT is itself a numeric object-ID handle, so pass it by value:
|
|
1444
|
+
# field mutation dereferences the same arena record while
|
|
1445
|
+
# ``self := other`` remains a local handle rebind.
|
|
1446
|
+
recv_spec = receiver_spec
|
|
1447
|
+
if recv_spec is None:
|
|
1448
|
+
recv_spec = self._type_spec_from_hint_name(
|
|
1449
|
+
fi.udt_type_name
|
|
1450
|
+
)
|
|
1451
|
+
spec = recv_spec
|
|
1452
|
+
if recv_spec is not None:
|
|
1453
|
+
cpp_t = self._type_spec_to_cpp(recv_spec)
|
|
1454
|
+
if (
|
|
1455
|
+
recv_spec.kind in {"array", "matrix"}
|
|
1456
|
+
or (
|
|
1457
|
+
recv_spec.kind == "udt"
|
|
1458
|
+
and recv_spec.name not in self._udt_defs
|
|
1459
|
+
)
|
|
1460
|
+
):
|
|
1461
|
+
cpp_t = f"{cpp_t}&"
|
|
1462
|
+
if recv_spec.kind == "udt" and recv_spec.name:
|
|
1463
|
+
safe_p = self._safe_name(p)
|
|
1464
|
+
self._udt_param_udt[safe_p] = recv_spec.name
|
|
1465
|
+
self._udt_param_udt[p] = recv_spec.name
|
|
1466
|
+
else:
|
|
1467
|
+
# Compatibility for synthetic/legacy method records that
|
|
1468
|
+
# carry only the old receiver-name field.
|
|
1469
|
+
recv_cpp = DRAWING_TYPE_TO_CPP.get(
|
|
1470
|
+
fi.udt_type_name, fi.udt_type_name
|
|
1471
|
+
)
|
|
1472
|
+
cpp_t = (
|
|
1473
|
+
recv_cpp
|
|
1474
|
+
if fi.udt_type_name in self._udt_defs
|
|
1475
|
+
else f"{recv_cpp}&"
|
|
1476
|
+
)
|
|
1477
|
+
safe_p = self._safe_name(p)
|
|
1478
|
+
self._udt_param_udt[safe_p] = fi.udt_type_name
|
|
1479
|
+
self._udt_param_udt[p] = fi.udt_type_name
|
|
972
1480
|
elif fi.name == "isInSession" and i < 2:
|
|
973
1481
|
cpp_t = "std::string"
|
|
974
1482
|
elif p in func_sv:
|
|
975
|
-
# This param uses history access (e.g. src[1]) —
|
|
976
|
-
|
|
1483
|
+
# This param uses history access (e.g. src[1]) — preserve its
|
|
1484
|
+
# Pine scalar family instead of hard-coding Series<double>.
|
|
1485
|
+
elem_cpp_t = self._series_param_element_cpp_type(
|
|
1486
|
+
fi, i, call_site_idx
|
|
1487
|
+
)
|
|
1488
|
+
cpp_t = f"const Series<{elem_cpp_t}>&"
|
|
977
1489
|
self._current_func_series_params.add(p)
|
|
1490
|
+
self._current_func_series_param_types[p] = elem_cpp_t
|
|
1491
|
+
self._current_func_series_param_types[
|
|
1492
|
+
self._safe_name(p)
|
|
1493
|
+
] = elem_cpp_t
|
|
1494
|
+
if (
|
|
1495
|
+
i < len(getattr(fi, "param_type_specs", []))
|
|
1496
|
+
and fi.param_type_specs[i] is not None
|
|
1497
|
+
):
|
|
1498
|
+
spec = fi.param_type_specs[i]
|
|
1499
|
+
elif (
|
|
1500
|
+
i >= len(declared_param_specs)
|
|
1501
|
+
or declared_param_specs[i] is None
|
|
1502
|
+
) and i < len(variant_param_types) and variant_param_types[i] in {
|
|
1503
|
+
PineType.INT,
|
|
1504
|
+
PineType.FLOAT,
|
|
1505
|
+
PineType.BOOL,
|
|
1506
|
+
PineType.STRING,
|
|
1507
|
+
PineType.COLOR,
|
|
1508
|
+
}:
|
|
1509
|
+
# Non-history untyped parameters are polymorphic too. A pure
|
|
1510
|
+
# wrapper around a stateful helper is cloned per written call,
|
|
1511
|
+
# so its scalar boundary must use that clone's actual family
|
|
1512
|
+
# instead of the first call's shared FuncInfo inference.
|
|
1513
|
+
variant_pt = variant_param_types[i]
|
|
1514
|
+
cpp_t = {
|
|
1515
|
+
PineType.INT: "int64_t",
|
|
1516
|
+
PineType.FLOAT: "double",
|
|
1517
|
+
PineType.BOOL: "bool",
|
|
1518
|
+
PineType.STRING: "std::string",
|
|
1519
|
+
PineType.COLOR: "int",
|
|
1520
|
+
}[variant_pt]
|
|
978
1521
|
elif i < len(getattr(fi, "param_type_specs", [])) and fi.param_type_specs[i] is not None:
|
|
979
1522
|
# Precise per-param TypeSpec (declared hint or call-site inference):
|
|
980
|
-
# ``pivot hi`` -> ``pivot
|
|
981
|
-
#
|
|
982
|
-
#
|
|
983
|
-
#
|
|
1523
|
+
# ``pivot hi`` -> ``pivot`` numeric handle, ``line ln`` keeps its
|
|
1524
|
+
# established drawing-handle route, and an untyped ``s`` used as
|
|
1525
|
+
# a string -> ``std::string``. User UDT field mutation propagates
|
|
1526
|
+
# through the arena even though the handle parameter is by value.
|
|
984
1527
|
spec = fi.param_type_specs[i]
|
|
985
1528
|
cpp_t = self._type_spec_to_cpp(spec)
|
|
986
1529
|
if spec.kind == "udt":
|
|
987
1530
|
self._udt_param_udt[p] = spec.name
|
|
988
1531
|
self._udt_param_udt[self._safe_name(p)] = spec.name
|
|
989
|
-
|
|
1532
|
+
if spec.name not in self._udt_defs:
|
|
1533
|
+
cpp_t = f"{cpp_t}&"
|
|
990
1534
|
elif spec.kind in ("array", "map"):
|
|
991
1535
|
elem = spec.element if spec.kind == "array" else spec.value
|
|
992
1536
|
if elem is not None and elem.kind == "udt":
|
|
993
1537
|
self._udt_param_udt[p] = elem.name
|
|
994
1538
|
self._udt_param_udt[self._safe_name(p)] = elem.name
|
|
995
|
-
|
|
1539
|
+
if spec.kind == "array":
|
|
1540
|
+
cpp_t = f"{cpp_t}&"
|
|
996
1541
|
elif i < len(fi.param_types):
|
|
997
1542
|
pt = fi.param_types[i]
|
|
998
1543
|
cpp_t = PINE_TYPE_TO_CPP.get(pt, "double")
|
|
@@ -1017,16 +1562,44 @@ class TopLevelEmitter:
|
|
|
1017
1562
|
# A function returning a drawing handle must emit the C++ handle
|
|
1018
1563
|
# struct (Line/Box/Label/Linefill), not the unknown lowercase name.
|
|
1019
1564
|
ret_type = DRAWING_TYPE_TO_CPP.get(fi.udt_return_type, fi.udt_return_type)
|
|
1565
|
+
elif self._func_int_return_uses_wide_history(
|
|
1566
|
+
fi, call_site_idx=call_site_idx
|
|
1567
|
+
):
|
|
1568
|
+
# The history parameter is represented as int64_t so timestamp
|
|
1569
|
+
# values and their na sentinel cannot narrow at the return edge.
|
|
1570
|
+
ret_type = "int64_t"
|
|
1571
|
+
elif (
|
|
1572
|
+
call_site_idx is not None
|
|
1573
|
+
and self._callsite_callable_return_pine_type(
|
|
1574
|
+
fi, call_site_idx
|
|
1575
|
+
) != PineType.UNKNOWN
|
|
1576
|
+
):
|
|
1577
|
+
ret_type = PINE_TYPE_TO_CPP.get(
|
|
1578
|
+
self._callsite_callable_return_pine_type(
|
|
1579
|
+
fi, call_site_idx
|
|
1580
|
+
),
|
|
1581
|
+
"double",
|
|
1582
|
+
)
|
|
1020
1583
|
elif getattr(fi, "return_type_spec", None) is not None:
|
|
1021
|
-
#
|
|
1022
|
-
#
|
|
1584
|
+
# Exact return TypeSpec (collections plus narrowly cached primitive
|
|
1585
|
+
# terminal reads) takes precedence over the coarse PineType slot.
|
|
1023
1586
|
ret_type = self._type_spec_to_cpp(fi.return_type_spec)
|
|
1024
1587
|
else:
|
|
1025
1588
|
ret_type = PINE_TYPE_TO_CPP.get(fi.return_type, "double")
|
|
1589
|
+
rhs_return_cpp_type = (
|
|
1590
|
+
ret_type
|
|
1591
|
+
if (self._is_nullable_collection_cpp_type(ret_type)
|
|
1592
|
+
or ret_type in DRAWING_TYPE_TO_CPP.values()
|
|
1593
|
+
or ret_type in self._udt_defs)
|
|
1594
|
+
else None
|
|
1595
|
+
)
|
|
1026
1596
|
|
|
1027
1597
|
# For per-call-site variants, suffix the function name and activate TA + var remapping
|
|
1028
|
-
func_name =
|
|
1029
|
-
|
|
1598
|
+
func_name = (
|
|
1599
|
+
self._emit_udt_method_cpp_name(fi)
|
|
1600
|
+
if is_method
|
|
1601
|
+
else self._func_safe_name(fi.name)
|
|
1602
|
+
)
|
|
1030
1603
|
if instance is not None:
|
|
1031
1604
|
# Fresh context-sensitive instance: name + composed remaps come from
|
|
1032
1605
|
# the instance record. No textual cs index (dispatch is via the
|
|
@@ -1035,10 +1608,8 @@ class TopLevelEmitter:
|
|
|
1035
1608
|
self._active_ta_remap = instance["ta_remap"]
|
|
1036
1609
|
self._active_var_remap = instance["var_remap"]
|
|
1037
1610
|
self._active_fixnan_remap = instance.get("fixnan_remap", {})
|
|
1038
|
-
self._in_ta_func_variant = True
|
|
1039
1611
|
self._active_call_site_idx = None
|
|
1040
1612
|
self._current_instance_name = instance["name"]
|
|
1041
|
-
var_init_flag = f"_fvinit_{instance['name']}"
|
|
1042
1613
|
elif call_site_idx is not None:
|
|
1043
1614
|
func_name = f"{func_name}_cs{call_site_idx}"
|
|
1044
1615
|
remap = self._func_cs_ta_remap.get((fi.name, call_site_idx), {})
|
|
@@ -1046,7 +1617,6 @@ class TopLevelEmitter:
|
|
|
1046
1617
|
var_remap = self._func_cs_var_remap.get((fi.name, call_site_idx), {})
|
|
1047
1618
|
self._active_var_remap = var_remap
|
|
1048
1619
|
self._active_fixnan_remap = self._func_cs_fixnan_remap.get((fi.name, call_site_idx), {})
|
|
1049
|
-
self._in_ta_func_variant = True
|
|
1050
1620
|
self._active_call_site_idx = call_site_idx
|
|
1051
1621
|
# Use the actual emitted name. Plain UDFs are unchanged; UDT
|
|
1052
1622
|
# methods carry their `_udt_Type_method` prefix. This identity is
|
|
@@ -1057,26 +1627,62 @@ class TopLevelEmitter:
|
|
|
1057
1627
|
self._active_ta_remap = {}
|
|
1058
1628
|
self._active_var_remap = {}
|
|
1059
1629
|
self._active_fixnan_remap = {}
|
|
1060
|
-
self._in_ta_func_variant = False
|
|
1061
1630
|
self._active_call_site_idx = None
|
|
1062
1631
|
self._current_instance_name = None
|
|
1063
1632
|
|
|
1064
1633
|
prev_func_locals = self._current_func_locals
|
|
1065
1634
|
prev_func_local_types = self._current_func_local_types
|
|
1635
|
+
prev_lexical_drawing_types = self._lexical_drawing_types
|
|
1636
|
+
prev_lexical_udt_types = self._lexical_udt_types
|
|
1637
|
+
prev_lexical_series_bindings = self._lexical_series_bindings
|
|
1638
|
+
prev_known_var_tombstones = self._lexical_known_var_tombstones
|
|
1066
1639
|
prev_func_body = getattr(self, "_current_func_body", None)
|
|
1067
1640
|
prev_func_name = getattr(self, "_active_func_name", None)
|
|
1068
|
-
# The function body is the lexical scope used by the UDT-alias analysis
|
|
1069
|
-
# (BUG C): a local initialised from a var/global UDT lvalue and later
|
|
1070
|
-
# mutated through must alias, not value-copy.
|
|
1071
1641
|
self._current_func_body = node.body
|
|
1072
1642
|
self._active_func_name = fi.name
|
|
1073
|
-
# Pointer-aliased UDT locals are function-scoped: a name like ``p_ivot``
|
|
1074
|
-
# may be a rebinding pointer alias in one function and a ``pivot&``
|
|
1075
|
-
# parameter in another, so reset per function to avoid cross-contamination.
|
|
1076
|
-
prev_ptr_alias = self._udt_ptr_alias_locals
|
|
1077
|
-
self._udt_ptr_alias_locals = set()
|
|
1078
1643
|
self._current_func_locals = {n for n, _, _ in self.ctx.func_var_members.get(fi.name, [])}
|
|
1079
1644
|
self._current_func_local_types = {}
|
|
1645
|
+
self._lexical_drawing_types = {
|
|
1646
|
+
param: DRAWING_TYPE_TO_CPP[drawing_name]
|
|
1647
|
+
for param in node.params
|
|
1648
|
+
for drawing_name in (
|
|
1649
|
+
(
|
|
1650
|
+
self._current_func_param_specs[param].name
|
|
1651
|
+
if (param in self._current_func_param_specs
|
|
1652
|
+
and self._current_func_param_specs[param].kind == "udt")
|
|
1653
|
+
else self._udt_param_udt.get(param)
|
|
1654
|
+
),
|
|
1655
|
+
)
|
|
1656
|
+
if (drawing_name in DRAWING_TYPE_TO_CPP
|
|
1657
|
+
and self._current_func_param_types.get(
|
|
1658
|
+
param, ""
|
|
1659
|
+
).removesuffix("&").removesuffix("*")
|
|
1660
|
+
== DRAWING_TYPE_TO_CPP[drawing_name])
|
|
1661
|
+
}
|
|
1662
|
+
self._lexical_udt_types = {
|
|
1663
|
+
param: (
|
|
1664
|
+
spec.name
|
|
1665
|
+
if spec is not None
|
|
1666
|
+
and spec.kind == "udt"
|
|
1667
|
+
and spec.name in self._udt_defs
|
|
1668
|
+
else (
|
|
1669
|
+
self._udt_param_udt.get(param)
|
|
1670
|
+
if (self._udt_param_udt.get(param) in self._udt_defs
|
|
1671
|
+
and self._current_func_param_types.get(
|
|
1672
|
+
param, ""
|
|
1673
|
+
).removesuffix("&").removesuffix("*")
|
|
1674
|
+
== self._udt_param_udt.get(param))
|
|
1675
|
+
else None
|
|
1676
|
+
)
|
|
1677
|
+
)
|
|
1678
|
+
for param in node.params
|
|
1679
|
+
for spec in (self._current_func_param_specs.get(param),)
|
|
1680
|
+
}
|
|
1681
|
+
self._lexical_series_bindings = {
|
|
1682
|
+
param: param in self._current_func_series_params
|
|
1683
|
+
for param in node.params
|
|
1684
|
+
}
|
|
1685
|
+
self._lexical_known_var_tombstones = set(node.params)
|
|
1080
1686
|
# Plain (non-persistent) scalar locals are emitted inline and live in
|
|
1081
1687
|
# no other set; collect them so the unknown-identifier guard in
|
|
1082
1688
|
# _visit_ident does not mistake them for undeclared symbols.
|
|
@@ -1084,12 +1690,6 @@ class TopLevelEmitter:
|
|
|
1084
1690
|
|
|
1085
1691
|
lines.append(f" {ret_type} {func_name}({', '.join(param_strs)}) {{")
|
|
1086
1692
|
|
|
1087
|
-
# Function-scoped ``var`` one-shot initializer: Pine ``var`` inside a
|
|
1088
|
-
# function is a function-local static — its initializer runs exactly
|
|
1089
|
-
# once, on the first call to THIS variant, with the first bar's values
|
|
1090
|
-
# the function actually sees. Each clone (cs0/cs1/…) is independent.
|
|
1091
|
-
self._emit_func_var_init_block(fi, call_site_idx, lines, flag_override=var_init_flag)
|
|
1092
|
-
|
|
1093
1693
|
emitted_return = False
|
|
1094
1694
|
if node.is_single_expr and node.body:
|
|
1095
1695
|
expr = node.body[0].expr if isinstance(node.body[0], ExprStmt) else None
|
|
@@ -1098,7 +1698,10 @@ class TopLevelEmitter:
|
|
|
1098
1698
|
# through to the default return.
|
|
1099
1699
|
self._visit_stmt(node.body[0], lines, indent=2)
|
|
1100
1700
|
elif expr:
|
|
1101
|
-
lines.append(
|
|
1701
|
+
lines.append(
|
|
1702
|
+
" return "
|
|
1703
|
+
f"{self._visit_rhs_value(expr, target_cpp_type=rhs_return_cpp_type)};"
|
|
1704
|
+
)
|
|
1102
1705
|
emitted_return = True
|
|
1103
1706
|
else:
|
|
1104
1707
|
for i, s in enumerate(node.body):
|
|
@@ -1112,7 +1715,10 @@ class TopLevelEmitter:
|
|
|
1112
1715
|
if self._call_is_void(s.expr) or self._is_skip_expr(s.expr):
|
|
1113
1716
|
self._visit_stmt(s, lines, indent=2)
|
|
1114
1717
|
else:
|
|
1115
|
-
lines.append(
|
|
1718
|
+
lines.append(
|
|
1719
|
+
" return "
|
|
1720
|
+
f"{self._visit_rhs_value(s.expr, target_cpp_type=rhs_return_cpp_type)};"
|
|
1721
|
+
)
|
|
1116
1722
|
emitted_return = True
|
|
1117
1723
|
elif i == len(node.body) - 1 and isinstance(s, (SwitchStmt, IfStmt)):
|
|
1118
1724
|
# Switch/if as last statement = return expression in PineScript
|
|
@@ -1126,7 +1732,13 @@ class TopLevelEmitter:
|
|
|
1126
1732
|
else:
|
|
1127
1733
|
default_ret = self._default_for_type(ret_type)
|
|
1128
1734
|
lines.append(f" {ret_type} _func_ret = {default_ret};")
|
|
1129
|
-
self._visit_if_switch_expr(
|
|
1735
|
+
self._visit_if_switch_expr(
|
|
1736
|
+
s,
|
|
1737
|
+
"_func_ret",
|
|
1738
|
+
lines,
|
|
1739
|
+
indent=2,
|
|
1740
|
+
target_cpp_type=rhs_return_cpp_type,
|
|
1741
|
+
)
|
|
1130
1742
|
lines.append(f" return _func_ret;")
|
|
1131
1743
|
emitted_return = True
|
|
1132
1744
|
else:
|
|
@@ -1148,82 +1760,30 @@ class TopLevelEmitter:
|
|
|
1148
1760
|
lines.append(" }")
|
|
1149
1761
|
self._current_func_param_types = {}
|
|
1150
1762
|
self._current_func_param_specs = {}
|
|
1763
|
+
self._current_func_declared_param_names = set()
|
|
1151
1764
|
self._current_func_series_params = set()
|
|
1765
|
+
self._current_func_series_param_types = {}
|
|
1152
1766
|
self._udt_param_udt = {}
|
|
1153
1767
|
self._current_func_locals = prev_func_locals
|
|
1154
1768
|
self._current_func_local_types = prev_func_local_types
|
|
1769
|
+
self._lexical_drawing_types = prev_lexical_drawing_types
|
|
1770
|
+
self._lexical_udt_types = prev_lexical_udt_types
|
|
1771
|
+
self._lexical_series_bindings = prev_lexical_series_bindings
|
|
1772
|
+
self._lexical_known_var_tombstones = prev_known_var_tombstones
|
|
1155
1773
|
self._current_func_body = prev_func_body
|
|
1156
1774
|
self._active_func_name = prev_func_name
|
|
1157
|
-
self.
|
|
1775
|
+
self._current_func_collection_specs = prev_func_collection_specs
|
|
1776
|
+
self._current_func_collection_shadows = prev_func_collection_shadows
|
|
1777
|
+
self._collection_types = prev_collection_types
|
|
1778
|
+
self._array_vars = prev_array_vars
|
|
1779
|
+
self._map_vars = prev_map_vars
|
|
1780
|
+
self._matrix_specs = prev_matrix_specs
|
|
1158
1781
|
self._active_ta_remap = {}
|
|
1159
1782
|
self._active_var_remap = {}
|
|
1160
1783
|
self._active_fixnan_remap = {}
|
|
1161
|
-
self._in_ta_func_variant = False
|
|
1162
1784
|
self._active_call_site_idx = None
|
|
1163
1785
|
self._current_instance_name = None
|
|
1164
1786
|
|
|
1165
|
-
def _func_var_init_flag_name(self, fname: str, call_site_idx: int | None) -> str:
|
|
1166
|
-
suffix = f"_cs{call_site_idx}" if call_site_idx is not None else ""
|
|
1167
|
-
return f"_fvinit_{self._func_safe_name(fname)}{suffix}"
|
|
1168
|
-
|
|
1169
|
-
def _emit_func_var_init_block(self, fi: FuncInfo, call_site_idx: int | None,
|
|
1170
|
-
lines: list[str], flag_override: str | None = None) -> None:
|
|
1171
|
-
"""Emit the one-shot initializer block for a function's ``var`` members.
|
|
1172
|
-
|
|
1173
|
-
Pine ``var`` declared inside a function is a function-local static:
|
|
1174
|
-
the initializer runs exactly once on the FIRST call to this variant
|
|
1175
|
-
(using the first bar's values the function actually sees) and the
|
|
1176
|
-
result persists for the strategy's lifetime. Each per-call-site clone
|
|
1177
|
-
is an independent instance with its own flag and its own set of
|
|
1178
|
-
(remapped) members.
|
|
1179
|
-
|
|
1180
|
-
This closes the gap where a function-scoped ``var line x = line.new(...)``
|
|
1181
|
-
(or any non-compile-time initializer — drawing handles, UDT ctors,
|
|
1182
|
-
arrays, runtime expressions) was declared as a default-constructed
|
|
1183
|
-
class member but its initializer was dropped, leaving the member ``na``
|
|
1184
|
-
/ uninitialised and causing "drawing access on na handle" at runtime.
|
|
1185
|
-
"""
|
|
1186
|
-
members = self.ctx.func_var_members.get(fi.name)
|
|
1187
|
-
if not members:
|
|
1188
|
-
return
|
|
1189
|
-
flag = flag_override or self._func_var_init_flag_name(fi.name, call_site_idx)
|
|
1190
|
-
# ``_active_var_remap`` is already set for this variant by the caller,
|
|
1191
|
-
# so lowering each init expression here correctly resolves references
|
|
1192
|
-
# to sibling var members (which are themselves remapped for clones).
|
|
1193
|
-
init_lines: list[str] = []
|
|
1194
|
-
for name, ptype, _init_str in members:
|
|
1195
|
-
init_ast = self.ctx.var_member_init_exprs.get(name)
|
|
1196
|
-
safe = self._safe_name(name)
|
|
1197
|
-
target = self._active_var_remap.get(safe, safe)
|
|
1198
|
-
if name in self.ctx.series_vars:
|
|
1199
|
-
if init_ast is None:
|
|
1200
|
-
continue
|
|
1201
|
-
init_cpp = self._visit_expr(init_ast)
|
|
1202
|
-
init_cpp = self._typed_na_init(init_cpp, name, ptype)
|
|
1203
|
-
init_lines.append(f" {target}.push({init_cpp});")
|
|
1204
|
-
continue
|
|
1205
|
-
if init_ast is None:
|
|
1206
|
-
# No initializer to lower (e.g. bare ``var box b``); leave the
|
|
1207
|
-
# member at its default-constructed value.
|
|
1208
|
-
continue
|
|
1209
|
-
# Skip a plain ``na`` initializer for drawing handles / UDTs whose
|
|
1210
|
-
# default-constructed member is already the na sentinel; assigning
|
|
1211
|
-
# ``na<double>()`` would not type-match the handle / struct.
|
|
1212
|
-
udt_t = self._udt_var_types.get(name)
|
|
1213
|
-
is_drawing = udt_t in DRAWING_TYPE_TO_CPP if udt_t else False
|
|
1214
|
-
is_udt = udt_t in self._udt_defs if udt_t else False
|
|
1215
|
-
from ..ast_nodes import NaLiteral
|
|
1216
|
-
if (is_drawing or is_udt) and isinstance(init_ast, NaLiteral):
|
|
1217
|
-
continue
|
|
1218
|
-
init_cpp = self._visit_expr(init_ast)
|
|
1219
|
-
init_lines.append(f" {target} = {init_cpp};")
|
|
1220
|
-
if not init_lines:
|
|
1221
|
-
return
|
|
1222
|
-
lines.append(f" if (!{flag}) {{")
|
|
1223
|
-
lines.extend(init_lines)
|
|
1224
|
-
lines.append(f" {flag} = true;")
|
|
1225
|
-
lines.append(" }")
|
|
1226
|
-
|
|
1227
1787
|
def _emit_precalculate_and_run(self, lines: list[str]) -> None:
|
|
1228
1788
|
has_static_ta = any(
|
|
1229
1789
|
self._ta_site_uses_precalc(site)
|
|
@@ -1241,7 +1801,7 @@ class TopLevelEmitter:
|
|
|
1241
1801
|
continue
|
|
1242
1802
|
if not (isinstance(stmt.value, FuncCall) and self._is_source_input(stmt.value)):
|
|
1243
1803
|
continue
|
|
1244
|
-
if stmt.name
|
|
1804
|
+
if self._decl_binding_is_series(id(stmt), stmt.name):
|
|
1245
1805
|
replayed_source_series.append(self._safe_name(stmt.name))
|
|
1246
1806
|
replayed_source_series = sorted(set(replayed_source_series))
|
|
1247
1807
|
|
|
@@ -1263,7 +1823,7 @@ class TopLevelEmitter:
|
|
|
1263
1823
|
if _ti in self._dead_ta_indices:
|
|
1264
1824
|
continue
|
|
1265
1825
|
if self._ta_site_uses_precalc(site):
|
|
1266
|
-
resolved = [self.
|
|
1826
|
+
resolved = [self._resolve_ta_ctor_arg(a) for a in site.ctor_args]
|
|
1267
1827
|
safe_resolved = []
|
|
1268
1828
|
for r in resolved:
|
|
1269
1829
|
safe_resolved.append(r if self._is_compile_time_value(r) else "1")
|
|
@@ -1349,7 +1909,7 @@ class TopLevelEmitter:
|
|
|
1349
1909
|
# the normal per-bar path's ``{safe}.push({cpp_val})`` (see
|
|
1350
1910
|
# ``_visit_var_decl``'s ``node.name in self.ctx.series_vars``
|
|
1351
1911
|
# branch) — a plain ``=`` there is a compile error.
|
|
1352
|
-
if stmt.name
|
|
1912
|
+
if self._decl_binding_is_series(id(stmt), stmt.name):
|
|
1353
1913
|
lines.append(f' {safe}.push({cpp_val});')
|
|
1354
1914
|
else:
|
|
1355
1915
|
lines.append(f' {safe} = {cpp_val};')
|
|
@@ -1375,7 +1935,7 @@ class TopLevelEmitter:
|
|
|
1375
1935
|
if _ti in self._dead_ta_indices:
|
|
1376
1936
|
continue
|
|
1377
1937
|
if self._ta_site_uses_precalc(site):
|
|
1378
|
-
resolved = [self.
|
|
1938
|
+
resolved = [self._resolve_ta_ctor_arg(a) for a in site.ctor_args]
|
|
1379
1939
|
safe_resolved = []
|
|
1380
1940
|
for r in resolved:
|
|
1381
1941
|
safe_resolved.append(r if self._is_compile_time_value(r) else "1")
|