@pineforge/codegen-pyodide 0.10.0 → 0.10.2
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 +2280 -228
- package/pineforge_codegen/codegen/drawing.py +94 -14
- package/pineforge_codegen/codegen/emit_top.py +747 -177
- package/pineforge_codegen/codegen/security.py +1546 -132
- package/pineforge_codegen/codegen/ta.py +435 -99
- package/pineforge_codegen/codegen/tables.py +328 -80
- package/pineforge_codegen/codegen/types.py +1160 -227
- package/pineforge_codegen/codegen/visit_call.py +1191 -104
- package/pineforge_codegen/codegen/visit_expr.py +283 -48
- package/pineforge_codegen/codegen/visit_stmt.py +1137 -202
- 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 +262 -23
- package/pineforge_codegen/symbols.py +65 -0
- package/pineforge_codegen-0.10.2.tar.gz +0 -0
- package/release.json +2 -2
- package/tables.json +9 -11
- package/pineforge_codegen-0.10.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
|
|
@@ -624,6 +949,21 @@ class TopLevelEmitter:
|
|
|
624
949
|
f"{info['member_name']}.clear();"
|
|
625
950
|
)
|
|
626
951
|
|
|
952
|
+
# A history-reading plain-UDF parameter advances on the chart clock,
|
|
953
|
+
# even when lazy control flow skips its written call on this bar. Seed
|
|
954
|
+
# the new slot with the prior current value (``na`` before first reach);
|
|
955
|
+
# an executed call later in the bar updates this same slot with its
|
|
956
|
+
# scalar actual. Typed-method ``series_arg`` bridges intentionally keep
|
|
957
|
+
# their existing execution-clock behavior.
|
|
958
|
+
for info in self._inline_history_members:
|
|
959
|
+
if info["kind"] != "udf_series_arg":
|
|
960
|
+
continue
|
|
961
|
+
member = info["member_name"]
|
|
962
|
+
lines.append(
|
|
963
|
+
f" if (history_advances_new_bar()) "
|
|
964
|
+
f"{member}.push({member}.current());"
|
|
965
|
+
)
|
|
966
|
+
|
|
627
967
|
# a. Push bar field series (with bar magnifier support)
|
|
628
968
|
for field_name in sorted(self.ctx.series_bar_fields):
|
|
629
969
|
push_expr = BAR_SERIES_PUSH.get(field_name, f"current_bar_.{field_name}")
|
|
@@ -659,11 +999,36 @@ class TopLevelEmitter:
|
|
|
659
999
|
if self.ctx.var_members:
|
|
660
1000
|
lines.append(" if (!_var_initialized) {")
|
|
661
1001
|
for name, ptype, init_expr in self.ctx.var_members:
|
|
662
|
-
#
|
|
663
|
-
#
|
|
1002
|
+
# Callable-scoped ``var`` members initialize at their exact
|
|
1003
|
+
# declaration statements, not in the global on_bar preamble.
|
|
664
1004
|
if name in getattr(self, "_func_local_var_names", ()):
|
|
665
1005
|
continue
|
|
666
1006
|
safe = self._safe_name(name)
|
|
1007
|
+
runtime_info = self._runtime_scalar_var_init_by_member.get(name)
|
|
1008
|
+
if (runtime_info is not None
|
|
1009
|
+
and runtime_info.get("drawing_cpp") is not None):
|
|
1010
|
+
if runtime_info.get("is_series"):
|
|
1011
|
+
self._emit_history_series_write(
|
|
1012
|
+
lines,
|
|
1013
|
+
" ",
|
|
1014
|
+
safe,
|
|
1015
|
+
f"{runtime_info['drawing_cpp']}{{}}",
|
|
1016
|
+
)
|
|
1017
|
+
continue
|
|
1018
|
+
# Declaration-site initialization owns nullable collection
|
|
1019
|
+
# members carrying runtime metadata. A persistent map/matrix
|
|
1020
|
+
# if/switch initializer must not also enter the legacy
|
|
1021
|
+
# first-bar aggregate preamble (which cannot lower a block
|
|
1022
|
+
# expression and formerly emitted ``/* unknown */`` for maps).
|
|
1023
|
+
# Keep primitive and ternary paths on their established order.
|
|
1024
|
+
runtime_spec = (
|
|
1025
|
+
runtime_info.get("type_spec")
|
|
1026
|
+
if runtime_info is not None
|
|
1027
|
+
else None
|
|
1028
|
+
)
|
|
1029
|
+
if (runtime_spec is not None
|
|
1030
|
+
and runtime_spec.kind in {"map", "matrix"}):
|
|
1031
|
+
continue
|
|
667
1032
|
if name in self._array_vars:
|
|
668
1033
|
for stmt in self.ctx.ast.body:
|
|
669
1034
|
if isinstance(stmt, VarDecl) and stmt.name == name:
|
|
@@ -672,39 +1037,55 @@ class TopLevelEmitter:
|
|
|
672
1037
|
break
|
|
673
1038
|
continue
|
|
674
1039
|
if name in self._matrix_specs:
|
|
675
|
-
# Matrix vars
|
|
1040
|
+
# Matrix vars are nullable ID handles. Initialize from the
|
|
1041
|
+
# authored RHS in declaration order, not only from a
|
|
1042
|
+
# matrix.new(...) call: ``var matrix<int> alias = source``
|
|
1043
|
+
# must copy source's ID once, while a later local rebind
|
|
1044
|
+
# remains independent.
|
|
676
1045
|
for stmt in self.ctx.ast.body:
|
|
677
|
-
if isinstance(stmt, VarDecl) and stmt.name == name
|
|
678
|
-
cpp_val = self.
|
|
1046
|
+
if isinstance(stmt, VarDecl) and stmt.name == name:
|
|
1047
|
+
cpp_val = self._visit_rhs_value(
|
|
1048
|
+
stmt.value,
|
|
1049
|
+
name,
|
|
1050
|
+
target_cpp_type=self._type_spec_to_cpp(
|
|
1051
|
+
self._matrix_specs[name]
|
|
1052
|
+
),
|
|
1053
|
+
)
|
|
679
1054
|
lines.append(f" {safe} = {cpp_val};")
|
|
680
1055
|
break
|
|
681
1056
|
continue
|
|
682
1057
|
if name in self._map_vars:
|
|
683
1058
|
for stmt in self.ctx.ast.body:
|
|
684
1059
|
if isinstance(stmt, VarDecl) and stmt.name == name:
|
|
685
|
-
cpp_val = self.
|
|
1060
|
+
cpp_val = self._visit_rhs_value(
|
|
1061
|
+
stmt.value,
|
|
1062
|
+
name,
|
|
1063
|
+
target_cpp_type=self._type_spec_to_cpp(
|
|
1064
|
+
self._map_spec_for_name(name)
|
|
1065
|
+
),
|
|
1066
|
+
)
|
|
686
1067
|
lines.append(f" {safe} = {cpp_val};")
|
|
687
1068
|
break
|
|
688
1069
|
continue
|
|
689
|
-
|
|
690
|
-
init_s = str(init_expr)
|
|
691
|
-
is_udt_init = False
|
|
692
|
-
for udt_name in self._udt_defs:
|
|
693
|
-
if init_s.startswith(f"{udt_name}.new"):
|
|
694
|
-
# Find the actual AST node to generate the init expression
|
|
695
|
-
for stmt in self.ctx.ast.body:
|
|
696
|
-
if isinstance(stmt, VarDecl) and stmt.name == name and isinstance(stmt.value, FuncCall):
|
|
697
|
-
cpp_val = self._visit_expr(stmt.value)
|
|
698
|
-
lines.append(f" {safe} = {cpp_val};")
|
|
699
|
-
is_udt_init = True
|
|
700
|
-
break
|
|
701
|
-
if not is_udt_init:
|
|
702
|
-
lines.append(f" {safe} = {udt_name}{{}};")
|
|
703
|
-
is_udt_init = True
|
|
704
|
-
break
|
|
705
|
-
if is_udt_init:
|
|
1070
|
+
if name in self._runtime_scalar_var_init_members:
|
|
706
1071
|
continue
|
|
707
|
-
|
|
1072
|
+
# Persistent authored UDT variables initialize from their full
|
|
1073
|
+
# source RHS, not only from ``Type.new``. In particular,
|
|
1074
|
+
# ``var Item alias = original`` must copy the numeric handle
|
|
1075
|
+
# once and therefore alias the same arena record.
|
|
1076
|
+
udt_name = self._member_udt_type(name)
|
|
1077
|
+
if udt_name in self._udt_defs:
|
|
1078
|
+
for stmt in self.ctx.ast.body:
|
|
1079
|
+
if isinstance(stmt, VarDecl) and stmt.name == name:
|
|
1080
|
+
cpp_val = self._visit_rhs_value(
|
|
1081
|
+
stmt.value,
|
|
1082
|
+
name,
|
|
1083
|
+
target_cpp_type=udt_name,
|
|
1084
|
+
)
|
|
1085
|
+
lines.append(f" {safe} = {cpp_val};")
|
|
1086
|
+
break
|
|
1087
|
+
continue
|
|
1088
|
+
if self._binding_is_series(name, safe):
|
|
708
1089
|
cpp_val = self._resolve_known(init_expr)
|
|
709
1090
|
cpp_val = self._typed_na_init(cpp_val, name, ptype)
|
|
710
1091
|
lines.append(f" {safe}.push({cpp_val});")
|
|
@@ -722,9 +1103,26 @@ class TopLevelEmitter:
|
|
|
722
1103
|
lines.append(" } else {")
|
|
723
1104
|
for name, _, _ in self.ctx.var_members:
|
|
724
1105
|
safe = self._safe_name(name)
|
|
1106
|
+
runtime_info = self._runtime_scalar_var_init_by_member.get(name)
|
|
1107
|
+
if (runtime_info is not None
|
|
1108
|
+
and runtime_info.get("drawing_cpp") is not None):
|
|
1109
|
+
if runtime_info.get("is_series"):
|
|
1110
|
+
lines.append(f" if ({runtime_info['flag']}) {{")
|
|
1111
|
+
self._emit_history_series_write(
|
|
1112
|
+
lines, " ", safe, f"{safe}[0]"
|
|
1113
|
+
)
|
|
1114
|
+
lines.append(" } else {")
|
|
1115
|
+
self._emit_history_series_write(
|
|
1116
|
+
lines,
|
|
1117
|
+
" ",
|
|
1118
|
+
safe,
|
|
1119
|
+
f"{runtime_info['drawing_cpp']}{{}}",
|
|
1120
|
+
)
|
|
1121
|
+
lines.append(" }")
|
|
1122
|
+
continue
|
|
725
1123
|
if name in self._array_vars:
|
|
726
1124
|
continue
|
|
727
|
-
if name
|
|
1125
|
+
if self._binding_is_series(name, safe):
|
|
728
1126
|
self._emit_history_series_write(
|
|
729
1127
|
lines, " ", safe, f"{safe}[0]"
|
|
730
1128
|
)
|
|
@@ -740,6 +1138,20 @@ class TopLevelEmitter:
|
|
|
740
1138
|
self._emit_history_series_write(
|
|
741
1139
|
lines, " ", cloned, f"{cloned}[0]"
|
|
742
1140
|
)
|
|
1141
|
+
# Context-sensitive nested helper instances own fresh
|
|
1142
|
+
# Series members outside the flat cs remap table. Advance
|
|
1143
|
+
# each exact fresh member once per bar as well; otherwise a
|
|
1144
|
+
# valid ``fresh[1]`` history read never moves past slot 0.
|
|
1145
|
+
for _owner, orig_safe, fresh_safe in self._fresh_var_members:
|
|
1146
|
+
if orig_safe != safe or fresh_safe in carry_emitted:
|
|
1147
|
+
continue
|
|
1148
|
+
carry_emitted.add(fresh_safe)
|
|
1149
|
+
self._emit_history_series_write(
|
|
1150
|
+
lines,
|
|
1151
|
+
" ",
|
|
1152
|
+
fresh_safe,
|
|
1153
|
+
f"{fresh_safe}[0]",
|
|
1154
|
+
)
|
|
743
1155
|
lines.append(" }")
|
|
744
1156
|
|
|
745
1157
|
# c. Push non-var series (they start fresh each bar with a push)
|
|
@@ -927,10 +1339,18 @@ class TopLevelEmitter:
|
|
|
927
1339
|
lines.append("")
|
|
928
1340
|
|
|
929
1341
|
def _emit_udt_method_cpp_name(self, fi: FuncInfo) -> str:
|
|
930
|
-
"""Stable C++ identifier for a
|
|
931
|
-
|
|
1342
|
+
"""Stable C++ identifier for a typed instance method."""
|
|
1343
|
+
receiver_spec = (
|
|
1344
|
+
fi.param_type_specs[0]
|
|
1345
|
+
if getattr(fi, "param_type_specs", None)
|
|
1346
|
+
else None
|
|
1347
|
+
)
|
|
1348
|
+
receiver = method_receiver_cpp_token(
|
|
1349
|
+
receiver_spec,
|
|
1350
|
+
fi.udt_type_name,
|
|
1351
|
+
)
|
|
932
1352
|
base = fi.node.name if fi.node else ""
|
|
933
|
-
return self._func_safe_name(f"_udt_{
|
|
1353
|
+
return self._func_safe_name(f"_udt_{receiver}_{base}")
|
|
934
1354
|
|
|
935
1355
|
def _emit_func_def(self, fi: FuncInfo, lines: list[str], call_site_idx: int | None = None,
|
|
936
1356
|
instance: dict | None = None) -> None:
|
|
@@ -948,51 +1368,191 @@ class TopLevelEmitter:
|
|
|
948
1368
|
if node is None:
|
|
949
1369
|
return
|
|
950
1370
|
|
|
951
|
-
|
|
1371
|
+
# Collection registries historically used raw variable names for the
|
|
1372
|
+
# whole translation unit. Emit each callable against copy-on-write
|
|
1373
|
+
# registries overlaid with its analyzer-captured lexical TypeSpecs, then
|
|
1374
|
+
# restore the top-level state. This keeps legacy readers working while
|
|
1375
|
+
# preventing local declarations/method dispatch from contaminating a
|
|
1376
|
+
# sibling UDF, UDT method, clone, or on_bar.
|
|
1377
|
+
prev_func_collection_specs = self._current_func_collection_specs
|
|
1378
|
+
prev_func_collection_shadows = self._current_func_collection_shadows
|
|
1379
|
+
prev_collection_types = self._collection_types
|
|
1380
|
+
prev_array_vars = self._array_vars
|
|
1381
|
+
prev_map_vars = self._map_vars
|
|
1382
|
+
prev_matrix_specs = self._matrix_specs
|
|
1383
|
+
# Start from top-level state only. Callable locals become visible at
|
|
1384
|
+
# their declaration statement (after its RHS), not at function entry:
|
|
1385
|
+
# preloading the analyzer's final inventory would make a later local
|
|
1386
|
+
# shadow an earlier read of a same-named global collection.
|
|
1387
|
+
self._current_func_collection_specs = {}
|
|
1388
|
+
self._current_func_collection_shadows = set()
|
|
1389
|
+
self._collection_types = dict(prev_collection_types)
|
|
1390
|
+
self._array_vars = set(prev_array_vars)
|
|
1391
|
+
self._map_vars = set(prev_map_vars)
|
|
1392
|
+
self._matrix_specs = dict(prev_matrix_specs)
|
|
1393
|
+
|
|
1394
|
+
is_method = (
|
|
1395
|
+
bool(getattr(fi, "is_udt_method", False))
|
|
1396
|
+
and fi.udt_type_name
|
|
1397
|
+
)
|
|
952
1398
|
|
|
953
1399
|
# Determine param types and set context for type inference inside body
|
|
954
1400
|
param_strs = []
|
|
955
1401
|
self._current_func_param_types = {}
|
|
956
1402
|
self._current_func_param_specs = {}
|
|
1403
|
+
param_hints = (getattr(node, "annotations", None) or {}).get(
|
|
1404
|
+
"param_type_hints", []
|
|
1405
|
+
)
|
|
1406
|
+
self._current_func_declared_param_names = {
|
|
1407
|
+
alias
|
|
1408
|
+
for i, param in enumerate(node.params)
|
|
1409
|
+
if i < len(param_hints) and param_hints[i]
|
|
1410
|
+
for alias in (param, self._safe_name(param))
|
|
1411
|
+
}
|
|
957
1412
|
self._current_func_series_params = set()
|
|
1413
|
+
self._current_func_series_param_types = {}
|
|
958
1414
|
self._udt_param_udt = {}
|
|
959
1415
|
func_sv = self.ctx.func_series_vars.get(fi.name, set())
|
|
1416
|
+
declared_param_specs = list(
|
|
1417
|
+
getattr(self.ctx, "func_declared_param_type_specs", {}).get(
|
|
1418
|
+
fi.name, ()
|
|
1419
|
+
)
|
|
1420
|
+
)
|
|
1421
|
+
variant_param_types = (
|
|
1422
|
+
getattr(self.ctx, "func_callsite_param_types", {}).get(
|
|
1423
|
+
(fi.name, call_site_idx), ()
|
|
1424
|
+
)
|
|
1425
|
+
if call_site_idx is not None
|
|
1426
|
+
else ()
|
|
1427
|
+
)
|
|
960
1428
|
for i, p in enumerate(node.params):
|
|
961
1429
|
spec = None
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1430
|
+
receiver_spec = None
|
|
1431
|
+
if is_method and i == 0:
|
|
1432
|
+
recv_spec = (
|
|
1433
|
+
fi.param_type_specs[i]
|
|
1434
|
+
if i < len(fi.param_type_specs)
|
|
1435
|
+
else None
|
|
1436
|
+
)
|
|
1437
|
+
receiver_spec = recv_spec
|
|
1438
|
+
|
|
1439
|
+
if (
|
|
1440
|
+
receiver_spec is not None
|
|
1441
|
+
and receiver_spec.kind == "primitive"
|
|
1442
|
+
and p in func_sv
|
|
1443
|
+
):
|
|
1444
|
+
# A primitive receiver used with history is a Series boundary,
|
|
1445
|
+
# just like an ordinary history-bearing UDF parameter.
|
|
1446
|
+
elem_cpp_t = self._series_param_element_cpp_type(
|
|
1447
|
+
fi, i, call_site_idx
|
|
1448
|
+
)
|
|
1449
|
+
cpp_t = f"const Series<{elem_cpp_t}>&"
|
|
1450
|
+
spec = receiver_spec
|
|
1451
|
+
self._current_func_series_params.add(p)
|
|
1452
|
+
self._current_func_series_param_types[p] = elem_cpp_t
|
|
1453
|
+
self._current_func_series_param_types[
|
|
1454
|
+
self._safe_name(p)
|
|
1455
|
+
] = elem_cpp_t
|
|
1456
|
+
elif is_method and i == 0 and fi.udt_type_name:
|
|
1457
|
+
# Receiver pass modes follow Pine's value/ID families. A user
|
|
1458
|
+
# UDT is itself a numeric object-ID handle, so pass it by value:
|
|
1459
|
+
# field mutation dereferences the same arena record while
|
|
1460
|
+
# ``self := other`` remains a local handle rebind.
|
|
1461
|
+
recv_spec = receiver_spec
|
|
1462
|
+
if recv_spec is None:
|
|
1463
|
+
recv_spec = self._type_spec_from_hint_name(
|
|
1464
|
+
fi.udt_type_name
|
|
1465
|
+
)
|
|
1466
|
+
spec = recv_spec
|
|
1467
|
+
if recv_spec is not None:
|
|
1468
|
+
cpp_t = self._type_spec_to_cpp(recv_spec)
|
|
1469
|
+
if (
|
|
1470
|
+
recv_spec.kind in {"array", "matrix"}
|
|
1471
|
+
or (
|
|
1472
|
+
recv_spec.kind == "udt"
|
|
1473
|
+
and recv_spec.name not in self._udt_defs
|
|
1474
|
+
)
|
|
1475
|
+
):
|
|
1476
|
+
cpp_t = f"{cpp_t}&"
|
|
1477
|
+
if recv_spec.kind == "udt" and recv_spec.name:
|
|
1478
|
+
safe_p = self._safe_name(p)
|
|
1479
|
+
self._udt_param_udt[safe_p] = recv_spec.name
|
|
1480
|
+
self._udt_param_udt[p] = recv_spec.name
|
|
1481
|
+
else:
|
|
1482
|
+
# Compatibility for synthetic/legacy method records that
|
|
1483
|
+
# carry only the old receiver-name field.
|
|
1484
|
+
recv_cpp = DRAWING_TYPE_TO_CPP.get(
|
|
1485
|
+
fi.udt_type_name, fi.udt_type_name
|
|
1486
|
+
)
|
|
1487
|
+
cpp_t = (
|
|
1488
|
+
recv_cpp
|
|
1489
|
+
if fi.udt_type_name in self._udt_defs
|
|
1490
|
+
else f"{recv_cpp}&"
|
|
1491
|
+
)
|
|
1492
|
+
safe_p = self._safe_name(p)
|
|
1493
|
+
self._udt_param_udt[safe_p] = fi.udt_type_name
|
|
1494
|
+
self._udt_param_udt[p] = fi.udt_type_name
|
|
972
1495
|
elif fi.name == "isInSession" and i < 2:
|
|
973
1496
|
cpp_t = "std::string"
|
|
974
1497
|
elif p in func_sv:
|
|
975
|
-
# This param uses history access (e.g. src[1]) —
|
|
976
|
-
|
|
1498
|
+
# This param uses history access (e.g. src[1]) — preserve its
|
|
1499
|
+
# Pine scalar family instead of hard-coding Series<double>.
|
|
1500
|
+
elem_cpp_t = self._series_param_element_cpp_type(
|
|
1501
|
+
fi, i, call_site_idx
|
|
1502
|
+
)
|
|
1503
|
+
cpp_t = f"const Series<{elem_cpp_t}>&"
|
|
977
1504
|
self._current_func_series_params.add(p)
|
|
1505
|
+
self._current_func_series_param_types[p] = elem_cpp_t
|
|
1506
|
+
self._current_func_series_param_types[
|
|
1507
|
+
self._safe_name(p)
|
|
1508
|
+
] = elem_cpp_t
|
|
1509
|
+
if (
|
|
1510
|
+
i < len(getattr(fi, "param_type_specs", []))
|
|
1511
|
+
and fi.param_type_specs[i] is not None
|
|
1512
|
+
):
|
|
1513
|
+
spec = fi.param_type_specs[i]
|
|
1514
|
+
elif (
|
|
1515
|
+
i >= len(declared_param_specs)
|
|
1516
|
+
or declared_param_specs[i] is None
|
|
1517
|
+
) and i < len(variant_param_types) and variant_param_types[i] in {
|
|
1518
|
+
PineType.INT,
|
|
1519
|
+
PineType.FLOAT,
|
|
1520
|
+
PineType.BOOL,
|
|
1521
|
+
PineType.STRING,
|
|
1522
|
+
PineType.COLOR,
|
|
1523
|
+
}:
|
|
1524
|
+
# Non-history untyped parameters are polymorphic too. A pure
|
|
1525
|
+
# wrapper around a stateful helper is cloned per written call,
|
|
1526
|
+
# so its scalar boundary must use that clone's actual family
|
|
1527
|
+
# instead of the first call's shared FuncInfo inference.
|
|
1528
|
+
variant_pt = variant_param_types[i]
|
|
1529
|
+
cpp_t = {
|
|
1530
|
+
PineType.INT: "int64_t",
|
|
1531
|
+
PineType.FLOAT: "double",
|
|
1532
|
+
PineType.BOOL: "bool",
|
|
1533
|
+
PineType.STRING: "std::string",
|
|
1534
|
+
PineType.COLOR: "int",
|
|
1535
|
+
}[variant_pt]
|
|
978
1536
|
elif i < len(getattr(fi, "param_type_specs", [])) and fi.param_type_specs[i] is not None:
|
|
979
1537
|
# Precise per-param TypeSpec (declared hint or call-site inference):
|
|
980
|
-
# ``pivot hi`` -> ``pivot
|
|
981
|
-
#
|
|
982
|
-
#
|
|
983
|
-
#
|
|
1538
|
+
# ``pivot hi`` -> ``pivot`` numeric handle, ``line ln`` keeps its
|
|
1539
|
+
# established drawing-handle route, and an untyped ``s`` used as
|
|
1540
|
+
# a string -> ``std::string``. User UDT field mutation propagates
|
|
1541
|
+
# through the arena even though the handle parameter is by value.
|
|
984
1542
|
spec = fi.param_type_specs[i]
|
|
985
1543
|
cpp_t = self._type_spec_to_cpp(spec)
|
|
986
1544
|
if spec.kind == "udt":
|
|
987
1545
|
self._udt_param_udt[p] = spec.name
|
|
988
1546
|
self._udt_param_udt[self._safe_name(p)] = spec.name
|
|
989
|
-
|
|
1547
|
+
if spec.name not in self._udt_defs:
|
|
1548
|
+
cpp_t = f"{cpp_t}&"
|
|
990
1549
|
elif spec.kind in ("array", "map"):
|
|
991
1550
|
elem = spec.element if spec.kind == "array" else spec.value
|
|
992
1551
|
if elem is not None and elem.kind == "udt":
|
|
993
1552
|
self._udt_param_udt[p] = elem.name
|
|
994
1553
|
self._udt_param_udt[self._safe_name(p)] = elem.name
|
|
995
|
-
|
|
1554
|
+
if spec.kind == "array":
|
|
1555
|
+
cpp_t = f"{cpp_t}&"
|
|
996
1556
|
elif i < len(fi.param_types):
|
|
997
1557
|
pt = fi.param_types[i]
|
|
998
1558
|
cpp_t = PINE_TYPE_TO_CPP.get(pt, "double")
|
|
@@ -1017,16 +1577,44 @@ class TopLevelEmitter:
|
|
|
1017
1577
|
# A function returning a drawing handle must emit the C++ handle
|
|
1018
1578
|
# struct (Line/Box/Label/Linefill), not the unknown lowercase name.
|
|
1019
1579
|
ret_type = DRAWING_TYPE_TO_CPP.get(fi.udt_return_type, fi.udt_return_type)
|
|
1580
|
+
elif self._func_int_return_uses_wide_history(
|
|
1581
|
+
fi, call_site_idx=call_site_idx
|
|
1582
|
+
):
|
|
1583
|
+
# The history parameter is represented as int64_t so timestamp
|
|
1584
|
+
# values and their na sentinel cannot narrow at the return edge.
|
|
1585
|
+
ret_type = "int64_t"
|
|
1586
|
+
elif (
|
|
1587
|
+
call_site_idx is not None
|
|
1588
|
+
and self._callsite_callable_return_pine_type(
|
|
1589
|
+
fi, call_site_idx
|
|
1590
|
+
) != PineType.UNKNOWN
|
|
1591
|
+
):
|
|
1592
|
+
ret_type = PINE_TYPE_TO_CPP.get(
|
|
1593
|
+
self._callsite_callable_return_pine_type(
|
|
1594
|
+
fi, call_site_idx
|
|
1595
|
+
),
|
|
1596
|
+
"double",
|
|
1597
|
+
)
|
|
1020
1598
|
elif getattr(fi, "return_type_spec", None) is not None:
|
|
1021
|
-
#
|
|
1022
|
-
#
|
|
1599
|
+
# Exact return TypeSpec (collections plus narrowly cached primitive
|
|
1600
|
+
# terminal reads) takes precedence over the coarse PineType slot.
|
|
1023
1601
|
ret_type = self._type_spec_to_cpp(fi.return_type_spec)
|
|
1024
1602
|
else:
|
|
1025
1603
|
ret_type = PINE_TYPE_TO_CPP.get(fi.return_type, "double")
|
|
1604
|
+
rhs_return_cpp_type = (
|
|
1605
|
+
ret_type
|
|
1606
|
+
if (self._is_nullable_collection_cpp_type(ret_type)
|
|
1607
|
+
or ret_type in DRAWING_TYPE_TO_CPP.values()
|
|
1608
|
+
or ret_type in self._udt_defs)
|
|
1609
|
+
else None
|
|
1610
|
+
)
|
|
1026
1611
|
|
|
1027
1612
|
# For per-call-site variants, suffix the function name and activate TA + var remapping
|
|
1028
|
-
func_name =
|
|
1029
|
-
|
|
1613
|
+
func_name = (
|
|
1614
|
+
self._emit_udt_method_cpp_name(fi)
|
|
1615
|
+
if is_method
|
|
1616
|
+
else self._func_safe_name(fi.name)
|
|
1617
|
+
)
|
|
1030
1618
|
if instance is not None:
|
|
1031
1619
|
# Fresh context-sensitive instance: name + composed remaps come from
|
|
1032
1620
|
# the instance record. No textual cs index (dispatch is via the
|
|
@@ -1035,10 +1623,8 @@ class TopLevelEmitter:
|
|
|
1035
1623
|
self._active_ta_remap = instance["ta_remap"]
|
|
1036
1624
|
self._active_var_remap = instance["var_remap"]
|
|
1037
1625
|
self._active_fixnan_remap = instance.get("fixnan_remap", {})
|
|
1038
|
-
self._in_ta_func_variant = True
|
|
1039
1626
|
self._active_call_site_idx = None
|
|
1040
1627
|
self._current_instance_name = instance["name"]
|
|
1041
|
-
var_init_flag = f"_fvinit_{instance['name']}"
|
|
1042
1628
|
elif call_site_idx is not None:
|
|
1043
1629
|
func_name = f"{func_name}_cs{call_site_idx}"
|
|
1044
1630
|
remap = self._func_cs_ta_remap.get((fi.name, call_site_idx), {})
|
|
@@ -1046,7 +1632,6 @@ class TopLevelEmitter:
|
|
|
1046
1632
|
var_remap = self._func_cs_var_remap.get((fi.name, call_site_idx), {})
|
|
1047
1633
|
self._active_var_remap = var_remap
|
|
1048
1634
|
self._active_fixnan_remap = self._func_cs_fixnan_remap.get((fi.name, call_site_idx), {})
|
|
1049
|
-
self._in_ta_func_variant = True
|
|
1050
1635
|
self._active_call_site_idx = call_site_idx
|
|
1051
1636
|
# Use the actual emitted name. Plain UDFs are unchanged; UDT
|
|
1052
1637
|
# methods carry their `_udt_Type_method` prefix. This identity is
|
|
@@ -1057,26 +1642,62 @@ class TopLevelEmitter:
|
|
|
1057
1642
|
self._active_ta_remap = {}
|
|
1058
1643
|
self._active_var_remap = {}
|
|
1059
1644
|
self._active_fixnan_remap = {}
|
|
1060
|
-
self._in_ta_func_variant = False
|
|
1061
1645
|
self._active_call_site_idx = None
|
|
1062
1646
|
self._current_instance_name = None
|
|
1063
1647
|
|
|
1064
1648
|
prev_func_locals = self._current_func_locals
|
|
1065
1649
|
prev_func_local_types = self._current_func_local_types
|
|
1650
|
+
prev_lexical_drawing_types = self._lexical_drawing_types
|
|
1651
|
+
prev_lexical_udt_types = self._lexical_udt_types
|
|
1652
|
+
prev_lexical_series_bindings = self._lexical_series_bindings
|
|
1653
|
+
prev_known_var_tombstones = self._lexical_known_var_tombstones
|
|
1066
1654
|
prev_func_body = getattr(self, "_current_func_body", None)
|
|
1067
1655
|
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
1656
|
self._current_func_body = node.body
|
|
1072
1657
|
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
1658
|
self._current_func_locals = {n for n, _, _ in self.ctx.func_var_members.get(fi.name, [])}
|
|
1079
1659
|
self._current_func_local_types = {}
|
|
1660
|
+
self._lexical_drawing_types = {
|
|
1661
|
+
param: DRAWING_TYPE_TO_CPP[drawing_name]
|
|
1662
|
+
for param in node.params
|
|
1663
|
+
for drawing_name in (
|
|
1664
|
+
(
|
|
1665
|
+
self._current_func_param_specs[param].name
|
|
1666
|
+
if (param in self._current_func_param_specs
|
|
1667
|
+
and self._current_func_param_specs[param].kind == "udt")
|
|
1668
|
+
else self._udt_param_udt.get(param)
|
|
1669
|
+
),
|
|
1670
|
+
)
|
|
1671
|
+
if (drawing_name in DRAWING_TYPE_TO_CPP
|
|
1672
|
+
and self._current_func_param_types.get(
|
|
1673
|
+
param, ""
|
|
1674
|
+
).removesuffix("&").removesuffix("*")
|
|
1675
|
+
== DRAWING_TYPE_TO_CPP[drawing_name])
|
|
1676
|
+
}
|
|
1677
|
+
self._lexical_udt_types = {
|
|
1678
|
+
param: (
|
|
1679
|
+
spec.name
|
|
1680
|
+
if spec is not None
|
|
1681
|
+
and spec.kind == "udt"
|
|
1682
|
+
and spec.name in self._udt_defs
|
|
1683
|
+
else (
|
|
1684
|
+
self._udt_param_udt.get(param)
|
|
1685
|
+
if (self._udt_param_udt.get(param) in self._udt_defs
|
|
1686
|
+
and self._current_func_param_types.get(
|
|
1687
|
+
param, ""
|
|
1688
|
+
).removesuffix("&").removesuffix("*")
|
|
1689
|
+
== self._udt_param_udt.get(param))
|
|
1690
|
+
else None
|
|
1691
|
+
)
|
|
1692
|
+
)
|
|
1693
|
+
for param in node.params
|
|
1694
|
+
for spec in (self._current_func_param_specs.get(param),)
|
|
1695
|
+
}
|
|
1696
|
+
self._lexical_series_bindings = {
|
|
1697
|
+
param: param in self._current_func_series_params
|
|
1698
|
+
for param in node.params
|
|
1699
|
+
}
|
|
1700
|
+
self._lexical_known_var_tombstones = set(node.params)
|
|
1080
1701
|
# Plain (non-persistent) scalar locals are emitted inline and live in
|
|
1081
1702
|
# no other set; collect them so the unknown-identifier guard in
|
|
1082
1703
|
# _visit_ident does not mistake them for undeclared symbols.
|
|
@@ -1084,12 +1705,6 @@ class TopLevelEmitter:
|
|
|
1084
1705
|
|
|
1085
1706
|
lines.append(f" {ret_type} {func_name}({', '.join(param_strs)}) {{")
|
|
1086
1707
|
|
|
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
1708
|
emitted_return = False
|
|
1094
1709
|
if node.is_single_expr and node.body:
|
|
1095
1710
|
expr = node.body[0].expr if isinstance(node.body[0], ExprStmt) else None
|
|
@@ -1098,7 +1713,10 @@ class TopLevelEmitter:
|
|
|
1098
1713
|
# through to the default return.
|
|
1099
1714
|
self._visit_stmt(node.body[0], lines, indent=2)
|
|
1100
1715
|
elif expr:
|
|
1101
|
-
lines.append(
|
|
1716
|
+
lines.append(
|
|
1717
|
+
" return "
|
|
1718
|
+
f"{self._visit_rhs_value(expr, target_cpp_type=rhs_return_cpp_type)};"
|
|
1719
|
+
)
|
|
1102
1720
|
emitted_return = True
|
|
1103
1721
|
else:
|
|
1104
1722
|
for i, s in enumerate(node.body):
|
|
@@ -1112,7 +1730,10 @@ class TopLevelEmitter:
|
|
|
1112
1730
|
if self._call_is_void(s.expr) or self._is_skip_expr(s.expr):
|
|
1113
1731
|
self._visit_stmt(s, lines, indent=2)
|
|
1114
1732
|
else:
|
|
1115
|
-
lines.append(
|
|
1733
|
+
lines.append(
|
|
1734
|
+
" return "
|
|
1735
|
+
f"{self._visit_rhs_value(s.expr, target_cpp_type=rhs_return_cpp_type)};"
|
|
1736
|
+
)
|
|
1116
1737
|
emitted_return = True
|
|
1117
1738
|
elif i == len(node.body) - 1 and isinstance(s, (SwitchStmt, IfStmt)):
|
|
1118
1739
|
# Switch/if as last statement = return expression in PineScript
|
|
@@ -1126,7 +1747,13 @@ class TopLevelEmitter:
|
|
|
1126
1747
|
else:
|
|
1127
1748
|
default_ret = self._default_for_type(ret_type)
|
|
1128
1749
|
lines.append(f" {ret_type} _func_ret = {default_ret};")
|
|
1129
|
-
self._visit_if_switch_expr(
|
|
1750
|
+
self._visit_if_switch_expr(
|
|
1751
|
+
s,
|
|
1752
|
+
"_func_ret",
|
|
1753
|
+
lines,
|
|
1754
|
+
indent=2,
|
|
1755
|
+
target_cpp_type=rhs_return_cpp_type,
|
|
1756
|
+
)
|
|
1130
1757
|
lines.append(f" return _func_ret;")
|
|
1131
1758
|
emitted_return = True
|
|
1132
1759
|
else:
|
|
@@ -1148,87 +1775,30 @@ class TopLevelEmitter:
|
|
|
1148
1775
|
lines.append(" }")
|
|
1149
1776
|
self._current_func_param_types = {}
|
|
1150
1777
|
self._current_func_param_specs = {}
|
|
1778
|
+
self._current_func_declared_param_names = set()
|
|
1151
1779
|
self._current_func_series_params = set()
|
|
1780
|
+
self._current_func_series_param_types = {}
|
|
1152
1781
|
self._udt_param_udt = {}
|
|
1153
1782
|
self._current_func_locals = prev_func_locals
|
|
1154
1783
|
self._current_func_local_types = prev_func_local_types
|
|
1784
|
+
self._lexical_drawing_types = prev_lexical_drawing_types
|
|
1785
|
+
self._lexical_udt_types = prev_lexical_udt_types
|
|
1786
|
+
self._lexical_series_bindings = prev_lexical_series_bindings
|
|
1787
|
+
self._lexical_known_var_tombstones = prev_known_var_tombstones
|
|
1155
1788
|
self._current_func_body = prev_func_body
|
|
1156
1789
|
self._active_func_name = prev_func_name
|
|
1157
|
-
self.
|
|
1790
|
+
self._current_func_collection_specs = prev_func_collection_specs
|
|
1791
|
+
self._current_func_collection_shadows = prev_func_collection_shadows
|
|
1792
|
+
self._collection_types = prev_collection_types
|
|
1793
|
+
self._array_vars = prev_array_vars
|
|
1794
|
+
self._map_vars = prev_map_vars
|
|
1795
|
+
self._matrix_specs = prev_matrix_specs
|
|
1158
1796
|
self._active_ta_remap = {}
|
|
1159
1797
|
self._active_var_remap = {}
|
|
1160
1798
|
self._active_fixnan_remap = {}
|
|
1161
|
-
self._in_ta_func_variant = False
|
|
1162
1799
|
self._active_call_site_idx = None
|
|
1163
1800
|
self._current_instance_name = None
|
|
1164
1801
|
|
|
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
|
-
# A bare-``na`` initializer for an int/int64_t/bool ``var`` member
|
|
1220
|
-
# must be typed (``na<int>()``), mirroring the class-scope ctor
|
|
1221
|
-
# init (``_typed_na_init`` at _emit_constructor); a raw ``na<double>()``
|
|
1222
|
-
# NaN stored into an int member is UB and defeats is_na<T>().
|
|
1223
|
-
init_cpp = self._typed_na_init(init_cpp, name, ptype)
|
|
1224
|
-
init_lines.append(f" {target} = {init_cpp};")
|
|
1225
|
-
if not init_lines:
|
|
1226
|
-
return
|
|
1227
|
-
lines.append(f" if (!{flag}) {{")
|
|
1228
|
-
lines.extend(init_lines)
|
|
1229
|
-
lines.append(f" {flag} = true;")
|
|
1230
|
-
lines.append(" }")
|
|
1231
|
-
|
|
1232
1802
|
def _emit_precalculate_and_run(self, lines: list[str]) -> None:
|
|
1233
1803
|
has_static_ta = any(
|
|
1234
1804
|
self._ta_site_uses_precalc(site)
|
|
@@ -1246,7 +1816,7 @@ class TopLevelEmitter:
|
|
|
1246
1816
|
continue
|
|
1247
1817
|
if not (isinstance(stmt.value, FuncCall) and self._is_source_input(stmt.value)):
|
|
1248
1818
|
continue
|
|
1249
|
-
if stmt.name
|
|
1819
|
+
if self._decl_binding_is_series(id(stmt), stmt.name):
|
|
1250
1820
|
replayed_source_series.append(self._safe_name(stmt.name))
|
|
1251
1821
|
replayed_source_series = sorted(set(replayed_source_series))
|
|
1252
1822
|
|
|
@@ -1268,7 +1838,7 @@ class TopLevelEmitter:
|
|
|
1268
1838
|
if _ti in self._dead_ta_indices:
|
|
1269
1839
|
continue
|
|
1270
1840
|
if self._ta_site_uses_precalc(site):
|
|
1271
|
-
resolved = [self.
|
|
1841
|
+
resolved = [self._resolve_ta_ctor_arg(a) for a in site.ctor_args]
|
|
1272
1842
|
safe_resolved = []
|
|
1273
1843
|
for r in resolved:
|
|
1274
1844
|
safe_resolved.append(r if self._is_compile_time_value(r) else "1")
|
|
@@ -1354,7 +1924,7 @@ class TopLevelEmitter:
|
|
|
1354
1924
|
# the normal per-bar path's ``{safe}.push({cpp_val})`` (see
|
|
1355
1925
|
# ``_visit_var_decl``'s ``node.name in self.ctx.series_vars``
|
|
1356
1926
|
# branch) — a plain ``=`` there is a compile error.
|
|
1357
|
-
if stmt.name
|
|
1927
|
+
if self._decl_binding_is_series(id(stmt), stmt.name):
|
|
1358
1928
|
lines.append(f' {safe}.push({cpp_val});')
|
|
1359
1929
|
else:
|
|
1360
1930
|
lines.append(f' {safe} = {cpp_val};')
|
|
@@ -1380,7 +1950,7 @@ class TopLevelEmitter:
|
|
|
1380
1950
|
if _ti in self._dead_ta_indices:
|
|
1381
1951
|
continue
|
|
1382
1952
|
if self._ta_site_uses_precalc(site):
|
|
1383
|
-
resolved = [self.
|
|
1953
|
+
resolved = [self._resolve_ta_ctor_arg(a) for a in site.ctor_args]
|
|
1384
1954
|
safe_resolved = []
|
|
1385
1955
|
for r in resolved:
|
|
1386
1956
|
safe_resolved.append(r if self._is_compile_time_value(r) else "1")
|