@pineforge/codegen-pyodide 0.10.4 → 1.0.0
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/README.md +16 -16
- package/glue.py +24 -16
- package/package.json +1 -1
- package/pineforge_codegen/__init__.py +125 -34
- package/pineforge_codegen/analyzer/__init__.py +2 -0
- package/pineforge_codegen/analyzer/base.py +754 -76
- package/pineforge_codegen/analyzer/call_handlers.py +260 -40
- package/pineforge_codegen/analyzer/contracts.py +37 -0
- package/pineforge_codegen/analyzer/diagnostics.py +30 -4
- package/pineforge_codegen/analyzer/tables.py +49 -8
- package/pineforge_codegen/analyzer/types.py +33 -1
- package/pineforge_codegen/ast_nodes.py +32 -1
- package/pineforge_codegen/block_locals.py +185 -0
- package/pineforge_codegen/builtin_keywords.py +42 -0
- package/pineforge_codegen/codegen/base.py +896 -156
- package/pineforge_codegen/codegen/constant_fold.py +131 -0
- package/pineforge_codegen/codegen/drawing.py +221 -79
- package/pineforge_codegen/codegen/emit_top.py +946 -213
- package/pineforge_codegen/codegen/helpers.py +435 -14
- package/pineforge_codegen/codegen/host_members.py +162 -0
- package/pineforge_codegen/codegen/input.py +252 -85
- package/pineforge_codegen/codegen/security.py +4372 -377
- package/pineforge_codegen/codegen/session_market.py +71 -0
- package/pineforge_codegen/codegen/ta.py +1188 -100
- package/pineforge_codegen/codegen/tables.py +193 -71
- package/pineforge_codegen/codegen/tv_number_format.py +270 -0
- package/pineforge_codegen/codegen/types.py +1882 -78
- package/pineforge_codegen/codegen/visit_call.py +920 -131
- package/pineforge_codegen/codegen/visit_expr.py +738 -57
- package/pineforge_codegen/codegen/visit_stmt.py +595 -49
- package/pineforge_codegen/external_requests.py +877 -0
- package/pineforge_codegen/lexer.py +104 -22
- package/pineforge_codegen/library_inline.py +1304 -0
- package/pineforge_codegen/library_modules.py +126 -0
- package/pineforge_codegen/library_v5.py +683 -0
- package/pineforge_codegen/limits.py +138 -0
- package/pineforge_codegen/method_binding.py +33 -0
- package/pineforge_codegen/parser.py +384 -68
- package/pineforge_codegen/pine_libraries.py +266 -0
- package/pineforge_codegen/pine_spelling.py +216 -0
- package/pineforge_codegen/pragmas.py +64 -10
- package/pineforge_codegen/security_contexts.py +1585 -0
- package/pineforge_codegen/session_reads.py +84 -0
- package/pineforge_codegen/signatures.py +48 -23
- package/pineforge_codegen/support_checker.py +1106 -85
- package/pineforge_codegen-1.0.0.tar.gz +0 -0
- package/release.json +2 -2
- package/tables.json +23 -21
- package/transpile.worker.mjs +24 -16
- package/pineforge_codegen-0.10.4.tar.gz +0 -0
|
@@ -25,10 +25,18 @@ from ..analyzer import (
|
|
|
25
25
|
TA_MULTI_CTOR,
|
|
26
26
|
TA_NO_CTOR,
|
|
27
27
|
TA_PERIOD_ARG,
|
|
28
|
+
TA_LENGTH_ONLY_DEFAULT_SOURCE,
|
|
28
29
|
)
|
|
29
30
|
from ..symbols import PineType, TypeSpec, method_receiver_type_name
|
|
30
31
|
from .. import signatures as sigs
|
|
31
32
|
from ..errors import CompileError, Diagnostic, Level, Phase, SourceLocation
|
|
33
|
+
from ..limits import TimeBudget, iter_ast_nodes
|
|
34
|
+
from ..session_reads import emitted_session_reads
|
|
35
|
+
from ..block_locals import block_declarations, decl_key
|
|
36
|
+
from ..pine_spelling import (
|
|
37
|
+
blank_string_literals, input_call_spans, pine_string_literal,
|
|
38
|
+
spell_input_call, sub_identifiers,
|
|
39
|
+
)
|
|
32
40
|
|
|
33
41
|
|
|
34
42
|
@dataclass(frozen=True)
|
|
@@ -79,6 +87,41 @@ from .tables import (
|
|
|
79
87
|
_merge_kwargs,
|
|
80
88
|
)
|
|
81
89
|
|
|
90
|
+
# A UDT field of array type. Pine arrays are references: ``T.new(a)`` holds
|
|
91
|
+
# the array ``a`` itself, not a copy. The field aliases a script ``var``
|
|
92
|
+
# array its record is built from (its storage lives as long as the strategy
|
|
93
|
+
# and keeps its identity across bars), and owns, by move or copy, any other.
|
|
94
|
+
# Copying the field shares what it holds, as Pine's shallow ``copy()`` does.
|
|
95
|
+
UDT_ARRAY_FIELD_CPP = r"""
|
|
96
|
+
template <typename T>
|
|
97
|
+
class _PFArrayField {
|
|
98
|
+
public:
|
|
99
|
+
_PFArrayField()
|
|
100
|
+
: owned_(std::make_shared<std::vector<T>>()), data_(owned_.get()) {}
|
|
101
|
+
_PFArrayField(const std::vector<T>& value)
|
|
102
|
+
: owned_(std::make_shared<std::vector<T>>(value)), data_(owned_.get()) {}
|
|
103
|
+
_PFArrayField(std::vector<T>&& value)
|
|
104
|
+
: owned_(std::make_shared<std::vector<T>>(std::move(value))),
|
|
105
|
+
data_(owned_.get()) {}
|
|
106
|
+
static _PFArrayField alias(std::vector<T>& value) {
|
|
107
|
+
return _PFArrayField(&value);
|
|
108
|
+
}
|
|
109
|
+
std::vector<T>& operator*() const { return *data_; }
|
|
110
|
+
const std::shared_ptr<std::vector<T>>& owned() const { return owned_; }
|
|
111
|
+
std::vector<T>* data() const { return data_; }
|
|
112
|
+
void rebind(std::shared_ptr<std::vector<T>> owned, std::vector<T>* data) {
|
|
113
|
+
owned_ = std::move(owned);
|
|
114
|
+
data_ = data;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private:
|
|
118
|
+
explicit _PFArrayField(std::vector<T>* data) : data_(data) {}
|
|
119
|
+
|
|
120
|
+
std::shared_ptr<std::vector<T>> owned_;
|
|
121
|
+
std::vector<T>* data_ = nullptr;
|
|
122
|
+
};
|
|
123
|
+
"""
|
|
124
|
+
|
|
82
125
|
TA_TUPLE_RESULT_TYPES = {
|
|
83
126
|
"macd": "ta::MACDResult",
|
|
84
127
|
"supertrend": "ta::SupertrendResult",
|
|
@@ -86,6 +129,7 @@ TA_TUPLE_RESULT_TYPES = {
|
|
|
86
129
|
"bb": "ta::BBResult",
|
|
87
130
|
"kc": "ta::KCResult",
|
|
88
131
|
"vwap_bands": "ta::VWAPBandsResult",
|
|
132
|
+
"vwap_anchored_bands": "ta::VWAPBandsResult",
|
|
89
133
|
}
|
|
90
134
|
|
|
91
135
|
# (TA_IMPLICIT_COMPUTE / TA_COMPUTE_ARGS now imported from .tables above.)
|
|
@@ -101,7 +145,12 @@ TA_TUPLE_RESULT_TYPES = {
|
|
|
101
145
|
|
|
102
146
|
# CPP_RESERVED + the NamingHelper mixin are pulled in from helpers.py so the
|
|
103
147
|
# small naming/walk utilities can be shared with future visitor mixins.
|
|
104
|
-
from .helpers import
|
|
148
|
+
from .helpers import (
|
|
149
|
+
CPP_RESERVED, INLINE_HISTORY_KINDS, SESSION_FLAG_MEMBERS, NamingHelper,
|
|
150
|
+
cpp_code_only, na_preserving_int_cast, pine_truth_cast,
|
|
151
|
+
)
|
|
152
|
+
from .constant_fold import fold_numeric_expression
|
|
153
|
+
from .session_market import SESSION_MARKET_CPP, SESSION_MARKET_MEMBER
|
|
105
154
|
|
|
106
155
|
# TypeInferer mixin owns the ~15 type-spec / C++-type inference helpers
|
|
107
156
|
# previously scattered across this module; see ``codegen/types.py``.
|
|
@@ -109,8 +158,7 @@ from .types import TypeInferer
|
|
|
109
158
|
|
|
110
159
|
# TaSiteHelper owns site lookup, .compute() arg construction, and the TA
|
|
111
160
|
# call-site machinery. The runtime-reset chain (_resolve_known and friends)
|
|
112
|
-
# stays on CodeGen for now because it
|
|
113
|
-
# expression evaluator.
|
|
161
|
+
# stays on CodeGen for now because it shares the constructor constant folder.
|
|
114
162
|
from .ta import TaSiteHelper
|
|
115
163
|
|
|
116
164
|
# InputHelper owns Pine input.* analysis (defaults, titles, getter dispatch,
|
|
@@ -179,12 +227,21 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
179
227
|
_prescan_strategy_series), and the runtime-reset chain
|
|
180
228
|
(_resolve_known / _is_skip_expr / _runtime_ctor_arg_for_reset /
|
|
181
229
|
_collect_ta_runtime_resets / _emit_ta_runtime_reset) — kept here
|
|
182
|
-
because the chain
|
|
183
|
-
evaluator.
|
|
230
|
+
because the chain shares the constructor constant folder.
|
|
184
231
|
"""
|
|
185
232
|
|
|
186
|
-
|
|
233
|
+
# True while emitting the body of a function or method inlined from a
|
|
234
|
+
# v5 library (``library_v5``): and/or evaluate both operands, a for
|
|
235
|
+
# loop's end is fixed before its first iteration, a negative array
|
|
236
|
+
# index stops the run, and an observer of v5's na bool is refused.
|
|
237
|
+
_pine_v5_body = False
|
|
238
|
+
|
|
239
|
+
def __init__(self, ctx: AnalyzerContext,
|
|
240
|
+
budget: TimeBudget | None = None) -> None:
|
|
187
241
|
self.ctx = ctx
|
|
242
|
+
self._budget = budget
|
|
243
|
+
self._budget_visit_count = 0
|
|
244
|
+
self._initialise_safe_names(ctx.ast)
|
|
188
245
|
# Lexical Pine names remain in ``ctx.func_var_members``. This overlay
|
|
189
246
|
# carries exact class-member identities only for collision-qualified
|
|
190
247
|
# ordinary FuncDefs (identity mappings for every other ordinary UDF).
|
|
@@ -310,11 +367,57 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
310
367
|
# Set of var/series member names that belong to user functions (need cloning)
|
|
311
368
|
self._func_var_members_set: set[str] = set()
|
|
312
369
|
self._precalc_loop_active: bool = False
|
|
370
|
+
# Nonzero while the expression visitor lowers a request.security
|
|
371
|
+
# payload (``_build_security_expr``): the session helper below reads
|
|
372
|
+
# the chart's timeframe, which does not describe the security bar.
|
|
373
|
+
self._security_payload_depth: int = 0
|
|
374
|
+
# The request.security builder's context while its expression visitor
|
|
375
|
+
# fallback lowers a payload node: a user call, TA site, helper-bound
|
|
376
|
+
# name or requested-bar history the visitor reaches under that node is
|
|
377
|
+
# handed back to the builder (``_security_fallback_delegate``).
|
|
378
|
+
self._security_fallback_frame: dict | None = None
|
|
379
|
+
# Whether the evaluator being emitted lowers its payload's user calls
|
|
380
|
+
# under builtin calls, and its typed methods, on the requested bar;
|
|
381
|
+
# whether it did, and the first thing it left on the chart's terms
|
|
382
|
+
# (``_emit_security_evaluator_requested``).
|
|
383
|
+
self._security_requested_calls: bool = True
|
|
384
|
+
self._security_requested_used: bool = False
|
|
385
|
+
self._security_chart_read: tuple | None = None
|
|
386
|
+
# Evaluators that keep every earlier build's lowering, decided while
|
|
387
|
+
# a method's TA constructor arguments were lowered: sec_id -> reason.
|
|
388
|
+
self._security_chart_evaluators: dict[int, tuple] = {}
|
|
389
|
+
# request.security helper series of string values (``var string``, a
|
|
390
|
+
# string local read with history) live in their own map; the member
|
|
391
|
+
# is declared when a payload's helper holds one.
|
|
392
|
+
self._security_string_series_declared: bool = False
|
|
393
|
+
self._security_string_series: set[str] = set()
|
|
394
|
+
# Each request.security evaluator local's C++ type (a string local
|
|
395
|
+
# takes ``na<std::string>()``; the builder's ``/`` reads it).
|
|
396
|
+
self._security_local_cpp_types: dict[str, str] = {}
|
|
397
|
+
# C++ the security builder hands a synthetic name: a helper-local
|
|
398
|
+
# history index lowered in its helper's scope.
|
|
399
|
+
self._security_raw_cpp: dict[str, str] = {}
|
|
400
|
+
# Set while a request.security TA history index is lowered: an input
|
|
401
|
+
# it reads is its override-aware getter, since the evaluator can run
|
|
402
|
+
# before on_bar initializes the input members.
|
|
403
|
+
self._security_index_inputs: bool = False
|
|
404
|
+
# A source input's selected series read at a history offset in a
|
|
405
|
+
# payload (``_security_bar_history_field``): the payload-method
|
|
406
|
+
# pre-pass below reads it before generate() starts the run's own.
|
|
407
|
+
self._security_source_hist_fields: dict[tuple[str, str], tuple] = {}
|
|
408
|
+
# Set when a chart expression calls ``_pf_session_market_``; its type
|
|
409
|
+
# and member are emitted once the whole TU is lowered.
|
|
410
|
+
self._uses_session_market: bool = False
|
|
411
|
+
# session.* reads inside a request.security payload already warned.
|
|
412
|
+
self._warned_security_session_sites: set[int] = set()
|
|
313
413
|
# Top-level lazy-edge TA sites hoisted to every-bar evaluation for the
|
|
314
414
|
# statement currently being lowered: FuncCall id -> local name, and
|
|
315
415
|
# Subscript id -> ``_hist_call_*`` member (see ``ta.py``).
|
|
316
416
|
self._hoisted_ta_values: dict[int, str] = {}
|
|
317
417
|
self._hoisted_hist_reads: dict[int, str] = {}
|
|
418
|
+
# ``*`` nodes a ``%`` or ``/`` reads: an int product among them is
|
|
419
|
+
# computed in 64 bits (visit_expr._lower_binop).
|
|
420
|
+
self._wide_int_products: set[int] = set()
|
|
318
421
|
# Names of ``var`` members that live in a callable scope (not global).
|
|
319
422
|
# Their exact declaration statements own initialization; they must not
|
|
320
423
|
# be initialized by the constructor or the global on_bar preamble.
|
|
@@ -712,6 +815,29 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
712
815
|
self._fixnan_site_map[id(_fsite.node)] = _fsite
|
|
713
816
|
# Track strategy series vars (e.g., strategy.closedtrades[1])
|
|
714
817
|
self._strategy_series_vars: set[str] = set()
|
|
818
|
+
# session.* flags read at an offset at the top level (their per-bar
|
|
819
|
+
# Series): _prescan_session_history.
|
|
820
|
+
self._session_history_flags: set[str] = set()
|
|
821
|
+
self._session_call_flags: dict[str, set[str]] = {}
|
|
822
|
+
self._session_call_owner: dict[int, str] = {}
|
|
823
|
+
self._session_history_member_names: dict[str, str] = {}
|
|
824
|
+
# The names a rendered read may not take (the script's own first: a
|
|
825
|
+
# request.security timeframe renders here, before the prescans), and
|
|
826
|
+
# each read refused where it renders: its stand-in name, then the
|
|
827
|
+
# read (kept alive, so its id stays its own), where to report it, why,
|
|
828
|
+
# and the uncloned function it asks to clone, if any; raised or asked
|
|
829
|
+
# only if the C++ emits the stand-in (_settle_session_reads).
|
|
830
|
+
self._session_names_used: set[str] = set(self._safe_name_occupied)
|
|
831
|
+
self._refused_session_read_names: dict[tuple[int, str], str] = {}
|
|
832
|
+
self._refused_session_reads: dict[
|
|
833
|
+
str, tuple[ASTNode, ASTNode, str, str | None]] = {}
|
|
834
|
+
# The uncloned functions whose reads the emitted C++ holds: another
|
|
835
|
+
# analysis clones them (pineforge_codegen._generate).
|
|
836
|
+
self.session_functions_needing_clones: frozenset[str] = frozenset()
|
|
837
|
+
# The top-level block declarations whose type the member of their name
|
|
838
|
+
# cannot hold (``block_locals``): transpile() names each apart and
|
|
839
|
+
# runs again.
|
|
840
|
+
self.block_locals_needing_names: frozenset = frozenset()
|
|
715
841
|
# Track global-scope non-var declarations (emitted as class members)
|
|
716
842
|
self._global_member_vars: set[str] = set()
|
|
717
843
|
for name, _ in ctx.global_var_decls:
|
|
@@ -816,6 +942,8 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
816
942
|
# for-in loop iterator names (must resolve member access, not enum fallback)
|
|
817
943
|
self._current_loop_vars: set[str] = set()
|
|
818
944
|
self._current_loop_var_specs: dict[str, "TypeSpec"] = {}
|
|
945
|
+
# The counted loops' binders among them (``for (int i = ...)``).
|
|
946
|
+
self._current_counted_loop_vars: set[str] = set()
|
|
819
947
|
# Track array variables for codegen
|
|
820
948
|
self._array_vars: set[str] = set()
|
|
821
949
|
# Track map variables for codegen
|
|
@@ -852,9 +980,20 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
852
980
|
_h = _hints[_i] if _i < len(_hints) else None
|
|
853
981
|
if _h and str(_h).replace(" ", "") in _SKIP_DECL_TYPES:
|
|
854
982
|
self._visual_drop_vars.add(_p)
|
|
983
|
+
# Build set of all member names (series vars, var members) for collision
|
|
984
|
+
# detection. It precedes the security metadata below: a timeframe
|
|
985
|
+
# computed by a user function (``tf = tfFromLabel(choice)``) renders
|
|
986
|
+
# that call through ``_func_safe_name`` at registration time.
|
|
987
|
+
self._all_member_names: set[str] = set()
|
|
988
|
+
for name in ctx.series_vars:
|
|
989
|
+
self._all_member_names.add(self._safe_name(name))
|
|
990
|
+
for name, _, _ in ctx.var_members:
|
|
991
|
+
self._all_member_names.add(self._safe_name(name))
|
|
855
992
|
# Collect request.security metadata per call
|
|
856
993
|
self._security_eval_info: list[dict] = []
|
|
857
994
|
self._security_ta_variant_names: dict[tuple[int, int, tuple], str] = {}
|
|
995
|
+
# Reassigned globals a timeframe reads (``_security_tf_replay_prologue``).
|
|
996
|
+
self._security_tf_mutable_reads: set[str] = set()
|
|
858
997
|
for item in self._security_calls:
|
|
859
998
|
sec_id = item["sec_id"]
|
|
860
999
|
tf_node = item["tf_node"]
|
|
@@ -865,8 +1004,15 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
865
1004
|
# Resolve the timeframe: a literal/const/global gives a static tf;
|
|
866
1005
|
# a function-parameter tf is resolved from the call sites (the
|
|
867
1006
|
# evaluator is a class method, so the param is not in scope there).
|
|
868
|
-
|
|
869
|
-
|
|
1007
|
+
# A lower-timeframe request has no chart-timeframe fallback.
|
|
1008
|
+
self._security_tf_lower = bool(item.get("is_lower_tf_array"))
|
|
1009
|
+
if item.get("dead"):
|
|
1010
|
+
# A helper no top-level statement reaches: its evaluator is
|
|
1011
|
+
# never read, registered on the chart timeframe.
|
|
1012
|
+
tf_str, tf_expr = None, "input_tf_"
|
|
1013
|
+
else:
|
|
1014
|
+
tf_str, tf_expr = self._resolve_security_tf(
|
|
1015
|
+
tf_node, item.get("containing_func", ""))
|
|
870
1016
|
|
|
871
1017
|
is_lookahead_on = False
|
|
872
1018
|
if lookahead_node is not None:
|
|
@@ -879,7 +1025,7 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
879
1025
|
is_gaps_on = True
|
|
880
1026
|
|
|
881
1027
|
expr_node = item["expr_node"]
|
|
882
|
-
inline_helper_ta_indices: set[
|
|
1028
|
+
inline_helper_ta_indices: set[tuple] = set()
|
|
883
1029
|
ta_binding_stacks = self._collect_security_ta_binding_stacks(
|
|
884
1030
|
expr_node,
|
|
885
1031
|
inline_ta_indices=inline_helper_ta_indices,
|
|
@@ -889,7 +1035,10 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
889
1035
|
for idx in sorted(ta_indices):
|
|
890
1036
|
site = self.ctx.ta_call_sites[idx]
|
|
891
1037
|
binding_map = ta_binding_stacks.get(idx) or {(): ()}
|
|
892
|
-
signatures = sorted(
|
|
1038
|
+
signatures = sorted(
|
|
1039
|
+
binding_map.keys(),
|
|
1040
|
+
key=lambda sig: self._security_variant_order_key(sig, binding_map[sig]),
|
|
1041
|
+
)
|
|
893
1042
|
use_base_name = len(signatures) == 1
|
|
894
1043
|
variants: list[dict] = []
|
|
895
1044
|
for variant_idx, signature in enumerate(signatures):
|
|
@@ -926,17 +1075,19 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
926
1075
|
"ta_indices": sorted(ta_indices),
|
|
927
1076
|
"ta_binding_stacks": ta_binding_stacks,
|
|
928
1077
|
"ta_variants": ta_variants,
|
|
929
|
-
|
|
1078
|
+
# (index, signature) of each TA variant computed inline, and
|
|
1079
|
+
# (index, _SECURITY_THROUGH_GLOBAL) of a global's site a
|
|
1080
|
+
# multi-statement helper reads, which the prologue computes.
|
|
1081
|
+
"inline_helper_ta_indices": sorted(inline_helper_ta_indices, key=repr),
|
|
930
1082
|
"depends_on_mutable_globals": item.get("depends_on_mutable_globals", False),
|
|
931
1083
|
"mutable_globals": list(item.get("mutable_globals", [])),
|
|
932
1084
|
"is_lower_tf_array": bool(item.get("is_lower_tf_array", False)),
|
|
1085
|
+
# Another symbol's feed (``_emit_foreign_security_registration``);
|
|
1086
|
+
# a helper nothing reaches keeps the chart registration.
|
|
1087
|
+
"foreign": bool(item.get("foreign")) and not item.get("dead"),
|
|
1088
|
+
"symbol_node": item.get("symbol_node"),
|
|
1089
|
+
"ignore_invalid_node": item.get("ignore_invalid_node"),
|
|
933
1090
|
})
|
|
934
|
-
# Build set of all member names (series vars, var members) for collision detection
|
|
935
|
-
self._all_member_names: set[str] = set()
|
|
936
|
-
for name in ctx.series_vars:
|
|
937
|
-
self._all_member_names.add(self._safe_name(name))
|
|
938
|
-
for name, _, _ in ctx.var_members:
|
|
939
|
-
self._all_member_names.add(self._safe_name(name))
|
|
940
1091
|
self._register_global_aggregate_member_types()
|
|
941
1092
|
self._uses_map = self._detect_map_usage()
|
|
942
1093
|
self._uses_matrix = self._detect_matrix_usage()
|
|
@@ -998,10 +1149,36 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
998
1149
|
return True
|
|
999
1150
|
return False
|
|
1000
1151
|
|
|
1152
|
+
def _series_var_init_keeps_preamble(self, init_str: str, init_ast,
|
|
1153
|
+
top_level: bool = True) -> bool:
|
|
1154
|
+
"""Whether a history-read ``var``'s first value can be pushed in the
|
|
1155
|
+
first-bar preamble, ahead of the script body: a top-level declaration
|
|
1156
|
+
of a constant, a string literal, ``na`` or a bar field (the bar is in
|
|
1157
|
+
place by then). Any other initializer reads what the body computes
|
|
1158
|
+
before the declaration on the first bar -- a global (``b = close *
|
|
1159
|
+
2``), a UDT field (``direction.neutral``), a call, an input's
|
|
1160
|
+
override -- which the preamble read before it was assigned, or
|
|
1161
|
+
spelled as raw Pine that did not compile
|
|
1162
|
+
(``signal.push(direction.neutral)``); and a declaration in a block
|
|
1163
|
+
initializes on the first bar that reaches it, which the preamble's
|
|
1164
|
+
bar 0 is not."""
|
|
1165
|
+
if not top_level:
|
|
1166
|
+
return False
|
|
1167
|
+
if init_ast is None:
|
|
1168
|
+
return True
|
|
1169
|
+
if self._scalar_var_init_depends_on_runtime_input(init_ast):
|
|
1170
|
+
return False
|
|
1171
|
+
if isinstance(init_ast, StringLiteral):
|
|
1172
|
+
return True
|
|
1173
|
+
if isinstance(init_ast, Identifier) and init_ast.name in BAR_FIELDS:
|
|
1174
|
+
return True
|
|
1175
|
+
return self._is_compile_time_value(self._resolve_known(init_str))
|
|
1176
|
+
|
|
1001
1177
|
def _is_runtime_scalar_var_initializer(
|
|
1002
1178
|
self, name: str, ptype, init_str: str, init_ast,
|
|
1003
1179
|
drawing_cpp: str | None = None,
|
|
1004
|
-
is_series: bool = False
|
|
1180
|
+
is_series: bool = False,
|
|
1181
|
+
top_level: bool = True) -> bool:
|
|
1005
1182
|
"""Return True for a persistent primitive that must init in execution.
|
|
1006
1183
|
|
|
1007
1184
|
Global Series and aggregate state keep their specialized preamble
|
|
@@ -1025,7 +1202,7 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
1025
1202
|
is_series
|
|
1026
1203
|
or not self._is_na_expr(init_ast)
|
|
1027
1204
|
)
|
|
1028
|
-
if is_series and
|
|
1205
|
+
if is_series and self._series_var_init_keeps_preamble(init_str, init_ast, top_level):
|
|
1029
1206
|
return False
|
|
1030
1207
|
udt_type = self._member_udt_type(name)
|
|
1031
1208
|
if udt_type in self._udt_defs:
|
|
@@ -1041,6 +1218,10 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
1041
1218
|
return (
|
|
1042
1219
|
not self._is_compile_time_value(ctor_val)
|
|
1043
1220
|
or self._scalar_var_init_depends_on_runtime_input(init_ast)
|
|
1221
|
+
# A history-read var in a block (its member may be renamed,
|
|
1222
|
+
# ``k__blk1``): its history is na until the first bar that
|
|
1223
|
+
# reaches it (``_series_var_init_keeps_preamble``).
|
|
1224
|
+
or (is_series and not top_level)
|
|
1044
1225
|
)
|
|
1045
1226
|
|
|
1046
1227
|
def _prepare_runtime_scalar_var_initializers(self) -> None:
|
|
@@ -1166,6 +1347,9 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
1166
1347
|
top_level_node_ids = {id(stmt) for stmt in self.ctx.ast.body}
|
|
1167
1348
|
for node_id, meta in metadata_by_node.items():
|
|
1168
1349
|
stmt, member_name, ptype, init_str, is_callable_scoped = meta
|
|
1350
|
+
if self._budget is not None:
|
|
1351
|
+
# Each declaration rescans every declaration.
|
|
1352
|
+
self._budget.check(stmt.loc, Phase.CODEGEN)
|
|
1169
1353
|
if not isinstance(stmt, VarDecl) or not (stmt.is_var or stmt.is_varip):
|
|
1170
1354
|
continue
|
|
1171
1355
|
stmt_spec = type_specs_by_node.get(node_id)
|
|
@@ -1223,7 +1407,7 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
1223
1407
|
nullable_collection_selection_decl_site
|
|
1224
1408
|
or self._is_runtime_scalar_var_initializer(
|
|
1225
1409
|
member_name, ptype, init_str, stmt.value, drawing_cpp,
|
|
1226
|
-
is_series
|
|
1410
|
+
is_series, node_id in top_level_node_ids
|
|
1227
1411
|
)
|
|
1228
1412
|
)
|
|
1229
1413
|
)
|
|
@@ -1280,8 +1464,29 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
1280
1464
|
# var-member metadata. Record their exact source declaration type so
|
|
1281
1465
|
# functions can resolve globals without consulting the analyzer's
|
|
1282
1466
|
# legacy raw-name UDT registry (which later locals may overwrite).
|
|
1283
|
-
|
|
1284
|
-
|
|
1467
|
+
# A declaration in a top-level block (``if c`` / ``for``) is hoisted
|
|
1468
|
+
# to the same class-member storage, so it is recorded too: its
|
|
1469
|
+
# ``cond ? box.new(...) : na`` needs the handle type for the ``na``.
|
|
1470
|
+
def top_level_scope_decls(stmts):
|
|
1471
|
+
for stmt in stmts:
|
|
1472
|
+
if isinstance(stmt, VarDecl):
|
|
1473
|
+
yield stmt
|
|
1474
|
+
elif isinstance(stmt, IfStmt):
|
|
1475
|
+
yield from top_level_scope_decls(stmt.body)
|
|
1476
|
+
yield from top_level_scope_decls(stmt.else_body)
|
|
1477
|
+
elif isinstance(stmt, (ForStmt, ForInStmt, WhileStmt)):
|
|
1478
|
+
yield from top_level_scope_decls(stmt.body)
|
|
1479
|
+
elif isinstance(stmt, SwitchStmt):
|
|
1480
|
+
for _case, body in stmt.cases:
|
|
1481
|
+
yield from top_level_scope_decls(body)
|
|
1482
|
+
yield from top_level_scope_decls(stmt.default_body)
|
|
1483
|
+
|
|
1484
|
+
top_level_ids = {id(stmt) for stmt in self.ctx.ast.body}
|
|
1485
|
+
for stmt in top_level_scope_decls(self.ctx.ast.body):
|
|
1486
|
+
if stmt.is_var or stmt.is_varip:
|
|
1487
|
+
continue
|
|
1488
|
+
if (id(stmt) not in top_level_ids
|
|
1489
|
+
and stmt.name in self._global_drawing_cpp_types):
|
|
1285
1490
|
continue
|
|
1286
1491
|
spec = (
|
|
1287
1492
|
self._type_spec_from_hint_name(stmt.type_hint)
|
|
@@ -1362,10 +1567,12 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
1362
1567
|
return
|
|
1363
1568
|
|
|
1364
1569
|
func_bodies: dict[str, list] = {}
|
|
1570
|
+
func_params: dict[str, set[str]] = {}
|
|
1365
1571
|
for fi in ctx.func_infos:
|
|
1366
1572
|
node = getattr(fi, "node", None)
|
|
1367
1573
|
if node is not None and getattr(node, "body", None):
|
|
1368
1574
|
func_bodies.setdefault(fi.name, node.body)
|
|
1575
|
+
func_params.setdefault(fi.name, set(getattr(node, "params", ()) or ()))
|
|
1369
1576
|
|
|
1370
1577
|
# Pine forbids recursive callable execution. Most scalar-only cycles
|
|
1371
1578
|
# never enter this state-instance pass (and some legacy dead-branch
|
|
@@ -1426,10 +1633,30 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
1426
1633
|
return list(self._func_cs_ta_remap.get((fname, 0), {}).keys())
|
|
1427
1634
|
|
|
1428
1635
|
def var_originals(fname: str) -> list[str]:
|
|
1429
|
-
|
|
1636
|
+
names = [
|
|
1430
1637
|
self._safe_name(self._func_var_storage_name(fname, n))
|
|
1431
1638
|
for n, _, _ in ctx.func_var_members.get(fname, [])
|
|
1432
1639
|
]
|
|
1640
|
+
# A history-read local (``float f = 0.0`` then ``f := ... f[1]``)
|
|
1641
|
+
# is persistent Series state too. Without it a fresh instance
|
|
1642
|
+
# kept writing the original member, so every call path it served
|
|
1643
|
+
# shared one series (cs-lev's second f_pole path fed its nine
|
|
1644
|
+
# true-range filters into the price filter's ``_f``).
|
|
1645
|
+
# Same storage rule as the natural csN remap of the callee's own
|
|
1646
|
+
# series vars (``orig_names`` above).
|
|
1647
|
+
params = func_params.get(fname, set())
|
|
1648
|
+
for sv in sorted(ctx.func_series_vars.get(fname, ())):
|
|
1649
|
+
if sv in params:
|
|
1650
|
+
continue # a history-read parameter is the caller's series
|
|
1651
|
+
exact = self._func_var_storage_name(fname, sv)
|
|
1652
|
+
storage = self._safe_name(
|
|
1653
|
+
exact
|
|
1654
|
+
if self._safe_name(exact) in self._series_var_member_names
|
|
1655
|
+
else sv
|
|
1656
|
+
)
|
|
1657
|
+
if storage not in names:
|
|
1658
|
+
names.append(storage)
|
|
1659
|
+
return names
|
|
1433
1660
|
|
|
1434
1661
|
def fixnan_originals(fname: str) -> list[str]:
|
|
1435
1662
|
return list(self._func_cs_fixnan_remap.get((fname, 0), {}).keys())
|
|
@@ -1581,6 +1808,11 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
1581
1808
|
"name": inst_name,
|
|
1582
1809
|
"fresh": True,
|
|
1583
1810
|
"call_site_idx": None,
|
|
1811
|
+
# The written call this instance runs, for its
|
|
1812
|
+
# parameter and return types: an untyped parameter
|
|
1813
|
+
# takes the argument that call passes, as the
|
|
1814
|
+
# call's own cs{j} clone does.
|
|
1815
|
+
"type_call_site_idx": j,
|
|
1584
1816
|
"ta_remap": composed_ta,
|
|
1585
1817
|
"var_remap": fvar_remap,
|
|
1586
1818
|
"fixnan_remap": ffixnan_remap,
|
|
@@ -2269,10 +2501,11 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
2269
2501
|
all_args.append(None)
|
|
2270
2502
|
all_args[i] = node.kwargs[pname]
|
|
2271
2503
|
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2504
|
+
# The one-arg length forms read high / low (as the analyzer's merge).
|
|
2505
|
+
default_source = TA_LENGTH_ONLY_DEFAULT_SOURCE.get(func_name)
|
|
2506
|
+
if default_source is not None and (
|
|
2507
|
+
len(all_args) == 1 or (len(all_args) == 2 and all_args[0] is None)):
|
|
2508
|
+
all_args = [Identifier(name=default_source), all_args[-1]]
|
|
2276
2509
|
|
|
2277
2510
|
return all_args
|
|
2278
2511
|
|
|
@@ -2316,6 +2549,9 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
2316
2549
|
rejected by the constructor guard. Names are NOT folded into
|
|
2317
2550
|
``_known_vars`` (no use-site inlining): only the length-analysis path is
|
|
2318
2551
|
affected, and the ``var`` member still emits and initializes normally.
|
|
2552
|
+
|
|
2553
|
+
The exception is ``var v = input.*()`` itself, which is registered like
|
|
2554
|
+
the plain binding ``v = input.*()`` (``_collect_known_var``).
|
|
2319
2555
|
"""
|
|
2320
2556
|
literal_candidates = self._admitted_stable_var_ctor_literals(reassigned)
|
|
2321
2557
|
|
|
@@ -2329,6 +2565,19 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
2329
2565
|
continue
|
|
2330
2566
|
if self._decl_binding_is_series(id(stmt), stmt.name):
|
|
2331
2567
|
continue
|
|
2568
|
+
# A never-reassigned ``var v = input.*()`` holds the input's value
|
|
2569
|
+
# on every bar, as ``v = input.*()`` does, so it is registered the
|
|
2570
|
+
# same way: an input-backed name read through its input call. The
|
|
2571
|
+
# TA runtime reset then keys the input by
|
|
2572
|
+
# ``_get_input_title(call, var_name=v)``, the key the member reads
|
|
2573
|
+
# it by. Recorded below as a derived expression, the reset re-read
|
|
2574
|
+
# the call's own spelling instead: an untitled input under "" and
|
|
2575
|
+
# without its keyword arguments, so an override never resized the
|
|
2576
|
+
# indicator.
|
|
2577
|
+
if self._is_var_input_binding(stmt):
|
|
2578
|
+
self._collect_known_var(stmt)
|
|
2579
|
+
if stmt.name in self._input_var_to_call:
|
|
2580
|
+
continue
|
|
2332
2581
|
if stmt.value is None or not self._expr_is_stable(stmt.value):
|
|
2333
2582
|
continue
|
|
2334
2583
|
expr_str = self._arith_expr_to_str(stmt.value)
|
|
@@ -2340,9 +2589,7 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
2340
2589
|
self._stable_var_ctor_literals[stmt.name] = literal_record
|
|
2341
2590
|
# Mark input-backed iff the init references an input, so the reset
|
|
2342
2591
|
# emits override-aware get_input_*() reads for it.
|
|
2343
|
-
|
|
2344
|
-
toks = set(_re.findall(r"[A-Za-z_][A-Za-z_0-9]*", expr_str))
|
|
2345
|
-
if any(t in self._input_backed_vars for t in toks):
|
|
2592
|
+
if self._refs_input(expr_str):
|
|
2346
2593
|
self._input_backed_vars.add(stmt.name)
|
|
2347
2594
|
|
|
2348
2595
|
@staticmethod
|
|
@@ -2656,7 +2903,7 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
2656
2903
|
# Fold to a compile-time literal when possible (so the ctor-init
|
|
2657
2904
|
# list can use it directly); otherwise record the raw expression
|
|
2658
2905
|
# for the runtime reset path to expand.
|
|
2659
|
-
folded = self.
|
|
2906
|
+
folded = self._resolve_known_through_inputs(final)
|
|
2660
2907
|
if self._is_compile_time_value(folded):
|
|
2661
2908
|
try:
|
|
2662
2909
|
num = float(folded)
|
|
@@ -2669,9 +2916,7 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
2669
2916
|
self._stable_runtime_vars.add(target_name)
|
|
2670
2917
|
# Mark input-backed iff the expression references an input so the
|
|
2671
2918
|
# override-aware get_input_*() reads are emitted on the reset path.
|
|
2672
|
-
|
|
2673
|
-
toks = set(_re.findall(r"[A-Za-z_][A-Za-z_0-9]*", final))
|
|
2674
|
-
if any(t in self._input_backed_vars for t in toks):
|
|
2919
|
+
if self._refs_input(final):
|
|
2675
2920
|
self._input_backed_vars.add(target_name)
|
|
2676
2921
|
|
|
2677
2922
|
def _find_reassigned_vars(self) -> set[str]:
|
|
@@ -2916,6 +3161,12 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
2916
3161
|
for a in node.args)
|
|
2917
3162
|
if namespace == "input":
|
|
2918
3163
|
return True
|
|
3164
|
+
# The generic ``input(...)`` is the leaf an inline TA length
|
|
3165
|
+
# already admits (``_is_stable_inline_input``: not the source
|
|
3166
|
+
# overload ``input(close)``, a constant defval); it used to fall to
|
|
3167
|
+
# the user-function branch below and read as a series.
|
|
3168
|
+
if namespace is None and func_name == "input":
|
|
3169
|
+
return self._is_stable_inline_input(node)
|
|
2919
3170
|
if namespace is None and func_name in ("int", "float", "bool", "string"):
|
|
2920
3171
|
return all(self._expr_is_stable(a, _udf_stack, _depth)
|
|
2921
3172
|
for a in node.args)
|
|
@@ -2948,7 +3199,7 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
2948
3199
|
# tree: Pine grouping ``(a - b) / (c - d)`` degrades to ``a - b / c - d``
|
|
2949
3200
|
# under C++ precedence. See ``_runtime_ctor_arg_for_reset`` (the string is
|
|
2950
3201
|
# re-parsed and lowered through the expression visitor).
|
|
2951
|
-
_ATOMIC_ARITH_NODES = (NumberLiteral, Identifier, MemberAccess, FuncCall)
|
|
3202
|
+
_ATOMIC_ARITH_NODES = (NumberLiteral, StringLiteral, Identifier, MemberAccess, FuncCall)
|
|
2952
3203
|
|
|
2953
3204
|
def _arith_operand_to_str(self, node, _udf_stack: frozenset = frozenset(),
|
|
2954
3205
|
_depth: int = 0) -> str | None:
|
|
@@ -2973,11 +3224,21 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
2973
3224
|
against recursion cycles (Pine forbids recursion, but a malformed source
|
|
2974
3225
|
must be refused, not looped forever).
|
|
2975
3226
|
"""
|
|
3227
|
+
if getattr(self, "_arith_udf_memo", None) is None:
|
|
3228
|
+
self._arith_udf_memo = {}
|
|
3229
|
+
try:
|
|
3230
|
+
return self._arith_expr_to_str(node, _udf_stack, _depth)
|
|
3231
|
+
finally:
|
|
3232
|
+
self._arith_udf_memo = None
|
|
2976
3233
|
if isinstance(node, NumberLiteral):
|
|
2977
3234
|
v = node.value
|
|
2978
3235
|
if isinstance(v, float) and v == int(v):
|
|
2979
3236
|
return str(int(v))
|
|
2980
3237
|
return str(v)
|
|
3238
|
+
if isinstance(node, StringLiteral):
|
|
3239
|
+
# ``len = mode == "Fast" ? 8 : 21``: a length chosen by comparing
|
|
3240
|
+
# an input.string with its options.
|
|
3241
|
+
return pine_string_literal(node.value)
|
|
2981
3242
|
if isinstance(node, Identifier):
|
|
2982
3243
|
return node.name
|
|
2983
3244
|
if isinstance(node, MemberAccess) and isinstance(node.object, Identifier):
|
|
@@ -3009,12 +3270,27 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3009
3270
|
# ordinary ``callee(args)`` rendering below. The stack/depth guard
|
|
3010
3271
|
# refuses a recursive UDF (returns None -> caller leaves it untracked
|
|
3011
3272
|
# -> the ctor guard rejects it loudly) instead of recursing forever.
|
|
3273
|
+
# An inline input call is spelled in full (title, keyword args):
|
|
3274
|
+
# the reset re-parses it and keys the override by that title. An
|
|
3275
|
+
# untitled call a declaration names carries that name as title=.
|
|
3276
|
+
if self._is_stable_inline_input(node):
|
|
3277
|
+
return spell_input_call(node, title=self._input_spelling_title(node))
|
|
3012
3278
|
fn, ns = self._resolve_callee(node.callee)
|
|
3013
3279
|
if ns is None and fn is not None and self._get_udf_def(fn) is not None:
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3280
|
+
# A call of the same function on the same argument nodes (a
|
|
3281
|
+
# caller's inlined body calling it twice, a diamond of such
|
|
3282
|
+
# helpers) is spelled once; the entry keeps the arguments
|
|
3283
|
+
# alive, so their ids stay theirs until the outermost call.
|
|
3284
|
+
key = (fn, _depth, _udf_stack, tuple(id(a) for a in node.args), bool(node.kwargs))
|
|
3285
|
+
memo = self._arith_udf_memo
|
|
3286
|
+
if key not in memo:
|
|
3287
|
+
inlined = self._inline_single_expr_udf(node, _udf_stack, _depth)
|
|
3288
|
+
memo[key] = (
|
|
3289
|
+
None if inlined is None
|
|
3290
|
+
else self._arith_expr_to_str(inlined, _udf_stack | {fn}, _depth + 1),
|
|
3291
|
+
node.args,
|
|
3292
|
+
)
|
|
3293
|
+
return memo[key][0]
|
|
3018
3294
|
callee = self._arith_expr_to_str(node.callee, _udf_stack, _depth)
|
|
3019
3295
|
if callee is None:
|
|
3020
3296
|
return None
|
|
@@ -3027,14 +3303,21 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3027
3303
|
return f"{callee}({', '.join(parts)})"
|
|
3028
3304
|
return None
|
|
3029
3305
|
|
|
3306
|
+
def _is_var_input_binding(self, node: VarDecl) -> bool:
|
|
3307
|
+
"""``var v = input.*()``: a ``var`` bound straight to an input call."""
|
|
3308
|
+
return (node.is_var and not node.is_varip
|
|
3309
|
+
and isinstance(node.value, FuncCall)
|
|
3310
|
+
and self._is_input_call(node.value))
|
|
3311
|
+
|
|
3030
3312
|
def _collect_known_var(self, node: VarDecl) -> None:
|
|
3031
3313
|
"""Extract known constant value from a VarDecl."""
|
|
3032
3314
|
# Don't inline series variables — their values change over time
|
|
3033
3315
|
if self._decl_binding_is_series(id(node), node.name):
|
|
3034
3316
|
return
|
|
3035
3317
|
# Don't inline var/varip variables — they're mutable state that persists
|
|
3036
|
-
# across bars and can be reassigned with :=
|
|
3037
|
-
|
|
3318
|
+
# across bars and can be reassigned with := (callers pass never-
|
|
3319
|
+
# reassigned names only, so ``var v = input.*()`` is the plain binding).
|
|
3320
|
+
if (node.is_var or node.is_varip) and not self._is_var_input_binding(node):
|
|
3038
3321
|
return
|
|
3039
3322
|
if isinstance(node.value, NumberLiteral):
|
|
3040
3323
|
self._known_vars[node.name] = node.value.value
|
|
@@ -3049,6 +3332,21 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3049
3332
|
self._input_backed_vars.add(node.name)
|
|
3050
3333
|
if node.value.name in self._input_var_to_call:
|
|
3051
3334
|
self._input_var_to_call[node.name] = self._input_var_to_call[node.value.name]
|
|
3335
|
+
# A plain alias of a derived stable scalar (``emaLen = calcEmaLen``
|
|
3336
|
+
# over ``calcEmaLen = swingLen * emaRatio``) holds that scalar on
|
|
3337
|
+
# every bar. Recorded as a derived expression of the name it
|
|
3338
|
+
# copies, the TA runtime reset re-expands it to the inputs'
|
|
3339
|
+
# getter reads; unrecorded, the reset spelled the alias's own
|
|
3340
|
+
# member, which the body has not assigned yet on the first bar
|
|
3341
|
+
# (0: ``ta.ema(close, emaLen)`` ran as an EMA of length 0).
|
|
3342
|
+
src = node.value.name
|
|
3343
|
+
if (src in self._derived_input_expr
|
|
3344
|
+
and src not in self._input_var_to_call
|
|
3345
|
+
and self._expr_is_stable(node.value)):
|
|
3346
|
+
self._derived_input_expr[node.name] = src
|
|
3347
|
+
self._stable_runtime_vars.add(node.name)
|
|
3348
|
+
if src in self._input_backed_vars:
|
|
3349
|
+
self._input_backed_vars.add(node.name)
|
|
3052
3350
|
if node.value.name in self._timeframe_period_vars:
|
|
3053
3351
|
self._timeframe_period_vars.add(node.name)
|
|
3054
3352
|
elif (isinstance(node.value, MemberAccess)
|
|
@@ -3058,25 +3356,9 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3058
3356
|
self._timeframe_period_vars.add(node.name)
|
|
3059
3357
|
# Input calls: extract default value
|
|
3060
3358
|
elif isinstance(node.value, FuncCall) and self._is_input_call(node.value):
|
|
3061
|
-
default = self.
|
|
3062
|
-
stored = False
|
|
3063
|
-
if isinstance(default, NumberLiteral):
|
|
3064
|
-
self._known_vars[node.name] = default.value
|
|
3065
|
-
stored = True
|
|
3066
|
-
elif isinstance(default, BoolLiteral):
|
|
3067
|
-
self._known_vars[node.name] = default.value
|
|
3068
|
-
stored = True
|
|
3069
|
-
elif isinstance(default, StringLiteral):
|
|
3070
|
-
self._known_vars[node.name] = default.value
|
|
3071
|
-
stored = True
|
|
3072
|
-
elif isinstance(default, MemberAccess) and isinstance(default.object, Identifier):
|
|
3073
|
-
en = default.object.name
|
|
3074
|
-
if en in self._enum_defs and default.member in self._enum_defs[en]:
|
|
3075
|
-
self._known_vars[node.name] = self._enum_defs[en].index(
|
|
3076
|
-
default.member
|
|
3077
|
-
)
|
|
3078
|
-
stored = True
|
|
3359
|
+
stored, default = self._input_default_value(node.value)
|
|
3079
3360
|
if stored:
|
|
3361
|
+
self._known_vars[node.name] = default
|
|
3080
3362
|
self._input_backed_vars.add(node.name)
|
|
3081
3363
|
self._input_var_to_call[node.name] = node.value
|
|
3082
3364
|
# Class-scope arithmetic / ternaries / casts over known, input-backed,
|
|
@@ -3099,8 +3381,10 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3099
3381
|
expr_str = self._arith_expr_to_str(node.value)
|
|
3100
3382
|
if expr_str is not None and self._expr_is_stable(node.value):
|
|
3101
3383
|
import re as _re
|
|
3102
|
-
tokens = set(_re.findall(r"[A-Za-z_][A-Za-z_0-9]*",
|
|
3103
|
-
|
|
3384
|
+
tokens = set(_re.findall(r"[A-Za-z_][A-Za-z_0-9]*",
|
|
3385
|
+
blank_string_literals(
|
|
3386
|
+
self._inline_inputs_masked(expr_str))))
|
|
3387
|
+
refs_input = self._refs_input(expr_str)
|
|
3104
3388
|
refs_derived = any(t in self._derived_input_expr for t in tokens)
|
|
3105
3389
|
# The stability classifier already proved this expression is a
|
|
3106
3390
|
# bar-invariant scalar (inputs / constants / timeframe.* /
|
|
@@ -3108,7 +3392,7 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3108
3392
|
# stable exprs (and the TA reset path) can reference / expand
|
|
3109
3393
|
# it — e.g. ``pi = math.asin(1) * 2`` feeds ``beta`` feeds
|
|
3110
3394
|
# ``alpha`` feeds a function-local ``filterLen``.
|
|
3111
|
-
folded = self.
|
|
3395
|
+
folded = self._resolve_known_through_inputs(expr_str)
|
|
3112
3396
|
if self._is_compile_time_value(folded):
|
|
3113
3397
|
try:
|
|
3114
3398
|
num = float(folded)
|
|
@@ -3134,25 +3418,38 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3134
3418
|
# Public entry point
|
|
3135
3419
|
# ------------------------------------------------------------------
|
|
3136
3420
|
|
|
3421
|
+
def _codegen_warning(self, node: ASTNode | None, message: str,
|
|
3422
|
+
hint: str | None = None) -> None:
|
|
3423
|
+
"""Record a warning on ``ctx.diagnostics``, the list ``transpile_full``
|
|
3424
|
+
returns beside the C++ (after the support checker's)."""
|
|
3425
|
+
loc = node.loc if node is not None else None
|
|
3426
|
+
if loc is None:
|
|
3427
|
+
loc = SourceLocation(file=self.ctx.filename, line=1, col=1, end_col=1)
|
|
3428
|
+
self.ctx.diagnostics.append(Diagnostic(
|
|
3429
|
+
level=Level.WARNING, phase=Phase.CODEGEN, location=loc,
|
|
3430
|
+
message=message, hint=hint))
|
|
3431
|
+
|
|
3137
3432
|
def _codegen_error(self, node: ASTNode | None, message: str, hint: str | None = None) -> None:
|
|
3433
|
+
raise CompileError([self._codegen_error_diagnostic(node, message, hint)])
|
|
3434
|
+
|
|
3435
|
+
def _codegen_error_diagnostic(self, node: ASTNode | None, message: str,
|
|
3436
|
+
hint: str | None = None) -> Diagnostic:
|
|
3138
3437
|
loc = node.loc if node is not None else None
|
|
3139
3438
|
if loc is None:
|
|
3140
3439
|
loc = SourceLocation(file=self.ctx.filename, line=1, col=1, end_col=1)
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
message=message,
|
|
3148
|
-
hint=hint,
|
|
3149
|
-
)
|
|
3150
|
-
]
|
|
3440
|
+
return Diagnostic(
|
|
3441
|
+
level=Level.ERROR,
|
|
3442
|
+
phase=Phase.CODEGEN,
|
|
3443
|
+
location=loc,
|
|
3444
|
+
message=message,
|
|
3445
|
+
hint=hint,
|
|
3151
3446
|
)
|
|
3152
3447
|
|
|
3153
3448
|
def _ta_return_type(self, site: TACallSite) -> str:
|
|
3154
3449
|
if getattr(site, "returns_tuple", False):
|
|
3155
|
-
return
|
|
3450
|
+
return TA_TUPLE_RESULT_TYPES.get(
|
|
3451
|
+
self._ta_name_from_site(site), f"{site.class_name}Result"
|
|
3452
|
+
)
|
|
3156
3453
|
if site.class_name in ("ta::Crossover", "ta::Crossunder", "ta::Cross"):
|
|
3157
3454
|
return "bool"
|
|
3158
3455
|
return "double"
|
|
@@ -3235,11 +3532,15 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3235
3532
|
"""
|
|
3236
3533
|
self._inline_history_members = []
|
|
3237
3534
|
self._inline_history_member_by_key = {}
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
}
|
|
3535
|
+
# session.* flags a function reads at an offset (owner -> flags) and
|
|
3536
|
+
# the owner of each such read: one Series per flag and emitted call
|
|
3537
|
+
# site, pushed at the function's entry (emit_top.py).
|
|
3538
|
+
self._session_call_flags: dict[str, set[str]] = {}
|
|
3539
|
+
self._session_call_owner: dict[int, str] = {}
|
|
3540
|
+
counters = {kind: 0 for kind in INLINE_HISTORY_KINDS}
|
|
3541
|
+
# A per-call session Series is numbered past a script name spelled
|
|
3542
|
+
# like one; the older kinds keep their numbering.
|
|
3543
|
+
authored_names = set(getattr(self, "_safe_name_occupied", ()))
|
|
3243
3544
|
|
|
3244
3545
|
def walk_nodes(value):
|
|
3245
3546
|
"""Yield AST nodes in stable field order, including tuple elements.
|
|
@@ -3335,6 +3636,9 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3335
3636
|
return
|
|
3336
3637
|
counters[kind] += 1
|
|
3337
3638
|
member_name = f"_{kind}_{counters[kind]}"
|
|
3639
|
+
while kind == "session_call" and member_name in authored_names:
|
|
3640
|
+
counters[kind] += 1
|
|
3641
|
+
member_name = f"_{kind}_{counters[kind]}"
|
|
3338
3642
|
self._inline_history_member_by_key[key] = member_name
|
|
3339
3643
|
self._inline_history_members.append({
|
|
3340
3644
|
"kind": kind,
|
|
@@ -3381,6 +3685,12 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3381
3685
|
return list(
|
|
3382
3686
|
self._bind_typed_method_args(method_info, call).args_by_param
|
|
3383
3687
|
)
|
|
3688
|
+
if isinstance(call.callee, Identifier):
|
|
3689
|
+
defaulted = self._user_call_args_with_defaults(
|
|
3690
|
+
call.callee.name, call
|
|
3691
|
+
)
|
|
3692
|
+
if defaulted is not None:
|
|
3693
|
+
return defaulted
|
|
3384
3694
|
if call.kwargs:
|
|
3385
3695
|
return _merge_kwargs(call.args, call.kwargs, params, lambda arg: arg)
|
|
3386
3696
|
return list(call.args)
|
|
@@ -3436,6 +3746,20 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3436
3746
|
id(child) for child in walk_nodes(expression)
|
|
3437
3747
|
)
|
|
3438
3748
|
self._requested_context_inline_node_ids = requested_node_ids
|
|
3749
|
+
# The session.* reads a function body emits, outside its own
|
|
3750
|
+
# request.security expressions (session_reads.py); a callable that
|
|
3751
|
+
# cannot keep its call sites apart, or that this analysis did not
|
|
3752
|
+
# clone, gets no per-call Series (a read it emits is refused, or asks
|
|
3753
|
+
# for the clones).
|
|
3754
|
+
no_call_history = (
|
|
3755
|
+
set(getattr(self.ctx, "session_history_unsafe", None) or {})
|
|
3756
|
+
| set(getattr(self.ctx, "session_uncloned", None) or ())
|
|
3757
|
+
)
|
|
3758
|
+
emitted_function_reads = {
|
|
3759
|
+
id(read)
|
|
3760
|
+
for fi in self.ctx.func_infos if fi.node is not None
|
|
3761
|
+
for read in emitted_session_reads(fi.node, requested_node_ids)
|
|
3762
|
+
}
|
|
3439
3763
|
|
|
3440
3764
|
def emitted_context_for_call(
|
|
3441
3765
|
fi,
|
|
@@ -3524,6 +3848,27 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3524
3848
|
register(
|
|
3525
3849
|
"hist_call", (id(node),), self._infer_type(node.object), owner
|
|
3526
3850
|
)
|
|
3851
|
+
elif (isinstance(node, Subscript)
|
|
3852
|
+
and self._is_session_flag(node.object)):
|
|
3853
|
+
# In a function body the flag's history is the calls' of its
|
|
3854
|
+
# call site: one Series per flag and emitted variant (the
|
|
3855
|
+
# analyzer clones the function per call site), pushed once
|
|
3856
|
+
# per call at the function's entry. A top-level read uses the
|
|
3857
|
+
# flag's per-bar Series (_prescan_session_history), and one
|
|
3858
|
+
# written in a request.security expression the requested
|
|
3859
|
+
# clock's (security.py).
|
|
3860
|
+
if owner is not None and id(node) not in requested_node_ids:
|
|
3861
|
+
self._session_call_owner[id(node)] = owner
|
|
3862
|
+
if (id(node) in emitted_function_reads
|
|
3863
|
+
and owner not in no_call_history):
|
|
3864
|
+
flag = node.object.member
|
|
3865
|
+
self._session_call_flags.setdefault(owner, set()).add(flag)
|
|
3866
|
+
register("session_call", (owner, flag), "bool", owner)
|
|
3867
|
+
elif (isinstance(node, Subscript)
|
|
3868
|
+
and self._is_compound_history_object(node.object)):
|
|
3869
|
+
register(
|
|
3870
|
+
"hist_call", (id(node),), self._infer_type(node.object), owner
|
|
3871
|
+
)
|
|
3527
3872
|
|
|
3528
3873
|
if not isinstance(node, FuncCall):
|
|
3529
3874
|
continue
|
|
@@ -3644,12 +3989,155 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3644
3989
|
context,
|
|
3645
3990
|
)
|
|
3646
3991
|
|
|
3992
|
+
# A plain UDF reading a script variable (or bar_index) through history
|
|
3993
|
+
# reads its call site's history of it, on the same chart clock as a
|
|
3994
|
+
# history parameter: every chart-executed body owns one buffer per
|
|
3995
|
+
# variable, updated when the body is entered. A body that is emitted
|
|
3996
|
+
# once (never called) or only for a requested context keeps the
|
|
3997
|
+
# chart's history.
|
|
3998
|
+
global_reads = getattr(self.ctx, "func_global_history_reads", {}) or {}
|
|
3999
|
+
for fi in self.ctx.func_infos:
|
|
4000
|
+
names = global_reads.get(fi.name)
|
|
4001
|
+
if not names or fi.node is None:
|
|
4002
|
+
continue
|
|
4003
|
+
for context in self._inline_history_contexts_for_owner(fi.name):
|
|
4004
|
+
if (context is None or (fi.name, context)
|
|
4005
|
+
in self._requested_context_only_inline_contexts):
|
|
4006
|
+
continue
|
|
4007
|
+
for name in names:
|
|
4008
|
+
register_one(
|
|
4009
|
+
"fn_global_hist",
|
|
4010
|
+
(id(fi.node), name),
|
|
4011
|
+
self._series_type_for(name),
|
|
4012
|
+
context,
|
|
4013
|
+
)
|
|
4014
|
+
|
|
4015
|
+
def _function_global_history_member(self, name: str) -> str | None:
|
|
4016
|
+
"""The emitted body's buffer of script variable ``name``, or None."""
|
|
4017
|
+
fi = self._func_info_map.get(getattr(self, "_active_func_name", None))
|
|
4018
|
+
if fi is None or fi.node is None or self._security_payload_depth:
|
|
4019
|
+
return None
|
|
4020
|
+
return self._inline_history_member_by_key.get(
|
|
4021
|
+
("fn_global_hist", id(fi.node), name, self._current_instance_name)
|
|
4022
|
+
)
|
|
4023
|
+
|
|
4024
|
+
def _is_compound_history_object(self, node) -> bool:
|
|
4025
|
+
"""Whether ``node[k]`` is history on an operator expression or a
|
|
4026
|
+
``session.*`` flag.
|
|
4027
|
+
|
|
4028
|
+
``(a > b)[1]``, ``(x - y)[2]`` and ``(c ? p : q)[1]`` read the
|
|
4029
|
+
expression's value k bars ago; its C++ scalar cannot be indexed, so
|
|
4030
|
+
the subscript owns a synthetic ``_hist_call_*`` Series exactly like an
|
|
4031
|
+
inline call result. Numeric and bool expressions only: other families
|
|
4032
|
+
keep their established lowering. A ``session.*`` flag's C++ value
|
|
4033
|
+
cannot be indexed either: a request.security payload keeps its history
|
|
4034
|
+
on the requested clock like an operator expression's (security.py); a
|
|
4035
|
+
function body reads its call site's per-call Series
|
|
4036
|
+
(``_prepare_inline_history_members``) and a top-level read the flag's
|
|
4037
|
+
per-bar Series (``_prescan_session_history``).
|
|
4038
|
+
"""
|
|
4039
|
+
if self._is_session_flag(node):
|
|
4040
|
+
return True
|
|
4041
|
+
return (isinstance(node, (BinOp, UnaryOp, Ternary))
|
|
4042
|
+
and self._infer_type(node) in ("double", "int", "int64_t", "bool"))
|
|
4043
|
+
|
|
4044
|
+
@staticmethod
|
|
4045
|
+
def _is_session_flag(node) -> bool:
|
|
4046
|
+
"""Whether ``node`` is a ``session.*`` boolean (``session.ismarket``)."""
|
|
4047
|
+
return (isinstance(node, MemberAccess)
|
|
4048
|
+
and isinstance(node.object, Identifier)
|
|
4049
|
+
and node.object.name == "session"
|
|
4050
|
+
and node.member in SESSION_FLAG_MEMBERS)
|
|
4051
|
+
|
|
4052
|
+
def _session_history_member(self, flag: str) -> str:
|
|
4053
|
+
"""The per-bar Series of a ``session.*`` flag read at the top level:
|
|
4054
|
+
``_pf_session_hist_<flag>``, suffixed past a script name spelled so."""
|
|
4055
|
+
return self._session_history_member_names[flag]
|
|
4056
|
+
|
|
4057
|
+
def _settle_session_reads(self, cpp: str) -> None:
|
|
4058
|
+
"""Refuse the ``session.<flag>[k]`` reads the C++ emits with no
|
|
4059
|
+
history, or, for a function this analysis did not clone
|
|
4060
|
+
(``session_uncloned``), ask for its clones
|
|
4061
|
+
(``session_functions_needing_clones``).
|
|
4062
|
+
|
|
4063
|
+
Both follow the emitted code, not the rendered reads: a call can render
|
|
4064
|
+
an argument and leave it out of the C++ (``color.from_gradient``'s
|
|
4065
|
+
arguments, a drawing's xloc), and a read there refuses nothing and
|
|
4066
|
+
needs no clone. Such a read renders as a stand-in name
|
|
4067
|
+
(``_refused_session_read``), which only that read can spell in code.
|
|
4068
|
+
"""
|
|
4069
|
+
if not self._refused_session_reads:
|
|
4070
|
+
return
|
|
4071
|
+
pattern = re.compile(r"(?<![\w.])(" + "|".join(map(
|
|
4072
|
+
re.escape, sorted(self._refused_session_reads, key=len, reverse=True))) + r")\b")
|
|
4073
|
+
emitted = set(pattern.findall(cpp_code_only(cpp)))
|
|
4074
|
+
# One error per read, the refusal rendered first (a read can be
|
|
4075
|
+
# rendered on the chart and in a request.security evaluator).
|
|
4076
|
+
refused: dict[tuple[int, int], Diagnostic] = {}
|
|
4077
|
+
needing: set[str] = set()
|
|
4078
|
+
for name, (_read, where, message, uncloned) in self._refused_session_reads.items():
|
|
4079
|
+
if name not in emitted:
|
|
4080
|
+
continue
|
|
4081
|
+
if uncloned is not None:
|
|
4082
|
+
needing.add(uncloned)
|
|
4083
|
+
continue
|
|
4084
|
+
diagnostic = self._codegen_error_diagnostic(where, message)
|
|
4085
|
+
refused.setdefault(
|
|
4086
|
+
(diagnostic.location.line, diagnostic.location.col), diagnostic)
|
|
4087
|
+
if refused:
|
|
4088
|
+
raise CompileError([refused[key] for key in sorted(refused)])
|
|
4089
|
+
self.session_functions_needing_clones = frozenset(needing)
|
|
4090
|
+
|
|
4091
|
+
def _prescan_session_history(self) -> None:
|
|
4092
|
+
"""The ``session.*`` flags the script reads at an offset at its top
|
|
4093
|
+
level, each of which gets one Series pushed on every chart bar.
|
|
4094
|
+
|
|
4095
|
+
TradingView reads a flag's history by bars at the top level of the
|
|
4096
|
+
script, in a block and on a lazy operand as well, and by calls inside a
|
|
4097
|
+
function (``tests/test_e2e_session_history.py``): function bodies
|
|
4098
|
+
(``_prepare_inline_history_members``) and ``request.security`` payloads
|
|
4099
|
+
(the requested clock, security.py) keep their own history instead. A
|
|
4100
|
+
``// @pf-trace`` expression is read at the top level.
|
|
4101
|
+
"""
|
|
4102
|
+
roots = [stmt for stmt in self.ctx.ast.body
|
|
4103
|
+
if not isinstance(stmt, (FuncDef, MethodDef))]
|
|
4104
|
+
roots += [pragma.expr_node for pragma in (self.ctx.pf_trace_pragmas or [])
|
|
4105
|
+
if getattr(pragma, "expr_node", None) is not None]
|
|
4106
|
+
payloads: set[int] = set()
|
|
4107
|
+
for root in roots:
|
|
4108
|
+
for node, _depth in iter_ast_nodes(root):
|
|
4109
|
+
if not isinstance(node, FuncCall):
|
|
4110
|
+
continue
|
|
4111
|
+
name, namespace = self._resolve_callee(node.callee)
|
|
4112
|
+
if namespace == "request" and name in ("security", "security_lower_tf"):
|
|
4113
|
+
payload = node.args[2] if len(node.args) > 2 else node.kwargs.get("expression")
|
|
4114
|
+
if payload is not None:
|
|
4115
|
+
payloads.add(id(payload))
|
|
4116
|
+
flags = {read.object.member for root in roots
|
|
4117
|
+
for read in emitted_session_reads(root, payloads)}
|
|
4118
|
+
used = self._session_names_used
|
|
4119
|
+
self._session_history_member_names = {
|
|
4120
|
+
flag: self._allocate_generated_cpp_name(f"_pf_session_hist_{flag}", used)
|
|
4121
|
+
for flag in sorted(flags)
|
|
4122
|
+
}
|
|
4123
|
+
self._session_history_flags = flags
|
|
4124
|
+
|
|
3647
4125
|
def _inline_history_member(self, kind: str, node: ASTNode,
|
|
3648
4126
|
arg_idx: int | None = None) -> str:
|
|
3649
4127
|
source_key = (id(node),) if arg_idx is None else (id(node), arg_idx)
|
|
3650
4128
|
key = (kind, *source_key, self._current_instance_name)
|
|
3651
4129
|
member = self._inline_history_member_by_key.get(key)
|
|
3652
4130
|
if member is None:
|
|
4131
|
+
if getattr(self, "_security_fallback_frame", None) is not None:
|
|
4132
|
+
# A builtin call in a request.security payload reads history
|
|
4133
|
+
# the evaluator keeps no series for (a helper call's inside a
|
|
4134
|
+
# helper body the payload inlines twice): refused as the bare
|
|
4135
|
+
# read is, where this used to crash the transpiler.
|
|
4136
|
+
self._codegen_error(
|
|
4137
|
+
node,
|
|
4138
|
+
"request.security helper call history is only supported in "
|
|
4139
|
+
"the payload itself",
|
|
4140
|
+
)
|
|
3653
4141
|
raise AssertionError(
|
|
3654
4142
|
"missing pre-registered inline history member for "
|
|
3655
4143
|
f"{kind} at {getattr(node, 'loc', None)} in context "
|
|
@@ -3791,8 +4279,47 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3791
4279
|
self._udt_arena_member_names[type_name] = allocated
|
|
3792
4280
|
self._all_member_names.add(allocated)
|
|
3793
4281
|
|
|
4282
|
+
# C++ types one member holds for every declaration of a name.
|
|
4283
|
+
_SHARED_MEMBER_TYPES = frozenset({"int", "int64_t", "double", "bool"})
|
|
4284
|
+
|
|
4285
|
+
def _block_locals_needing_names(self) -> frozenset:
|
|
4286
|
+
"""The declarations in the script's top-level blocks whose type the
|
|
4287
|
+
member of their name cannot hold (``block_locals``). The member has
|
|
4288
|
+
the type of the name's direct top-level declaration, else of its
|
|
4289
|
+
first declaration; a string, a collection or an object beside another
|
|
4290
|
+
type did not compile. Numbers and bools share one member, as before."""
|
|
4291
|
+
decls = [(decl, nested) for decl, nested in block_declarations(self.ctx.ast.body)
|
|
4292
|
+
if not decl.is_var and not decl.is_varip and decl.value is not None]
|
|
4293
|
+
member: dict[str, str | None] = {}
|
|
4294
|
+
for decl, nested in decls:
|
|
4295
|
+
if not nested:
|
|
4296
|
+
member.setdefault(decl.name, self._declared_cpp_type(decl))
|
|
4297
|
+
needing: set = set()
|
|
4298
|
+
for decl, nested in decls:
|
|
4299
|
+
cpp = self._declared_cpp_type(decl)
|
|
4300
|
+
first = member.setdefault(decl.name, cpp)
|
|
4301
|
+
if (nested and first is not None and cpp is not None and first != cpp
|
|
4302
|
+
and not {first, cpp} <= self._SHARED_MEMBER_TYPES
|
|
4303
|
+
and decl_key(decl) is not None):
|
|
4304
|
+
needing.add(decl_key(decl))
|
|
4305
|
+
return frozenset(needing)
|
|
4306
|
+
|
|
4307
|
+
def _declared_cpp_type(self, decl: VarDecl) -> str | None:
|
|
4308
|
+
"""The C++ type of a declaration's value (its hint's, when typed)."""
|
|
4309
|
+
if decl.type_hint:
|
|
4310
|
+
spec = self._type_spec_from_hint_name(decl.type_hint)
|
|
4311
|
+
return self._type_spec_to_cpp(spec) if spec is not None else None
|
|
4312
|
+
try:
|
|
4313
|
+
return self._infer_type(decl.value)
|
|
4314
|
+
except Exception:
|
|
4315
|
+
return None
|
|
4316
|
+
|
|
3794
4317
|
def generate(self) -> str:
|
|
3795
4318
|
"""Generate C++ source from the AnalyzerContext."""
|
|
4319
|
+
# Every input is keyed by its title: refuse a non-constant one first,
|
|
4320
|
+
# then flag inputs one override key would reach together.
|
|
4321
|
+
self._check_input_titles()
|
|
4322
|
+
self._check_input_keys()
|
|
3796
4323
|
# Context-sensitive instance pre-pass (needs the naming helpers populated
|
|
3797
4324
|
# in __init__). Computes nested stateful-helper dispatch + fresh instances.
|
|
3798
4325
|
self._build_func_instances()
|
|
@@ -3806,14 +4333,26 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3806
4333
|
self._prepare_udt_generated_names()
|
|
3807
4334
|
# Pre-scan for strategy series vars
|
|
3808
4335
|
self._prescan_strategy_series()
|
|
4336
|
+
self._prescan_session_history()
|
|
3809
4337
|
self._security_ohlc_hist_fields_by_sec: dict[int, set[str]] = {}
|
|
4338
|
+
# A source input's selected series read at a history offset in a
|
|
4339
|
+
# payload: (key, default) -> (the input call, its history field).
|
|
4340
|
+
self._security_source_hist_fields: dict[tuple[str, str], tuple] = {}
|
|
3810
4341
|
# request.security TA call-sites read at a history offset (``ta.ema(...)[k>=1]``).
|
|
3811
4342
|
# Maps sec_id -> set of TA call-site indices needing an HTF history Series.
|
|
3812
4343
|
self._security_ta_hist_idx_by_sec: dict[int, set[int]] = {}
|
|
3813
4344
|
# request.security helper-call results read at a history offset
|
|
3814
4345
|
# (``myHelper()[k]``). Maps (sec_id, node-id) -> backing Series metadata.
|
|
3815
4346
|
self._security_expr_hist_by_node: dict[tuple[int, int], dict] = {}
|
|
4347
|
+
# request.security id -> the globals its payload reads with history
|
|
4348
|
+
# (``_security_global_history_value``).
|
|
4349
|
+
self._security_global_hist_names: dict[int, set[str]] = {}
|
|
4350
|
+
# ids of the global history reads kept in a double (``_security_emits_double``).
|
|
4351
|
+
self._security_double_hist_nodes: set[int] = set()
|
|
4352
|
+
# request.security ids whose payload reads the requested bar_index.
|
|
4353
|
+
self._security_bar_index_secs: set[int] = set()
|
|
3816
4354
|
self._prepare_lazy_source_clock_sites()
|
|
4355
|
+
self.block_locals_needing_names = self._block_locals_needing_names()
|
|
3817
4356
|
|
|
3818
4357
|
lines: list[str] = []
|
|
3819
4358
|
|
|
@@ -3835,11 +4374,12 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3835
4374
|
# The two-phase order also permits self/nested UDT fields without
|
|
3836
4375
|
# embedding a C++ type recursively by value.
|
|
3837
4376
|
for type_name in self._udt_defs:
|
|
3838
|
-
|
|
4377
|
+
safe_type_name = self._safe_name(type_name)
|
|
4378
|
+
lines.append(f"struct {safe_type_name} {{")
|
|
3839
4379
|
lines.append(" int32_t __pf_id = -1;")
|
|
3840
4380
|
lines.append("};")
|
|
3841
4381
|
lines.append(
|
|
3842
|
-
f"inline bool is_na(const {
|
|
4382
|
+
f"inline bool is_na(const {safe_type_name}& _z) "
|
|
3843
4383
|
"{ return _z.__pf_id < 0; }"
|
|
3844
4384
|
)
|
|
3845
4385
|
lines.append("")
|
|
@@ -3865,6 +4405,12 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3865
4405
|
f" {self._udt_undo_coordinator_cpp_name}& operator=(",
|
|
3866
4406
|
f" {self._udt_undo_coordinator_cpp_name}&&) = delete;",
|
|
3867
4407
|
"",
|
|
4408
|
+
" void reset_for_run() {",
|
|
4409
|
+
" _pf_undo_.clear();",
|
|
4410
|
+
" _pf_active_ = false;",
|
|
4411
|
+
" // Keep generation monotonic: an old token cannot become valid again.",
|
|
4412
|
+
" }",
|
|
4413
|
+
"",
|
|
3868
4414
|
" Snapshot snapshot() {",
|
|
3869
4415
|
" if (_pf_generation_ == std::numeric_limits<uint64_t>::max()) {",
|
|
3870
4416
|
' throw std::overflow_error("UDT checkpoint generation exhausted");',
|
|
@@ -3942,6 +4488,14 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3942
4488
|
f" {self._udt_arena_template_cpp_name}& operator=(",
|
|
3943
4489
|
f" {self._udt_arena_template_cpp_name}&&) = delete;",
|
|
3944
4490
|
"",
|
|
4491
|
+
" void reset_for_run() {",
|
|
4492
|
+
" _pf_records_.clear();",
|
|
4493
|
+
" _pf_checkpoint_size_ = 0;",
|
|
4494
|
+
" _pf_checkpoint_generation_ = 0;",
|
|
4495
|
+
" _pf_checkpoint_active_ = false;",
|
|
4496
|
+
" // The arena remains attached to its original coordinator.",
|
|
4497
|
+
" }",
|
|
4498
|
+
"",
|
|
3945
4499
|
" _PFHandle create(_PFRecord value) {",
|
|
3946
4500
|
" if (_pf_records_.size() > static_cast<std::size_t>(",
|
|
3947
4501
|
" std::numeric_limits<int32_t>::max())) {",
|
|
@@ -4012,6 +4566,8 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4012
4566
|
# the same map. Authored field defaults are evaluated by ``Type.new``
|
|
4013
4567
|
# at the call site; record declarations use only inert typed defaults so
|
|
4014
4568
|
# bar/TA expressions never leak into namespace-scope C++ initializers.
|
|
4569
|
+
if self._udt_has_array_fields():
|
|
4570
|
+
lines.append(UDT_ARRAY_FIELD_CPP)
|
|
4015
4571
|
for type_name, fields in self._udt_defs.items():
|
|
4016
4572
|
record_type = self._udt_record_cpp_type(type_name)
|
|
4017
4573
|
lines.append(f"struct {record_type} {{")
|
|
@@ -4021,11 +4577,15 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4021
4577
|
if f.name in omitted:
|
|
4022
4578
|
continue
|
|
4023
4579
|
spec = field_specs.get(f.name) or self._type_spec_from_hint_name(f.type_name)
|
|
4580
|
+
array_field = self._udt_array_field_cpp(spec, type_name)
|
|
4581
|
+
if array_field is not None:
|
|
4582
|
+
lines.append(f" {array_field} {self._safe_name(f.name)} = {array_field}();")
|
|
4583
|
+
continue
|
|
4024
4584
|
cpp_type = self._type_spec_to_cpp(spec)
|
|
4025
4585
|
if cpp_type == "int":
|
|
4026
4586
|
cpp_type = "int64_t"
|
|
4027
4587
|
default = self._default_for_spec(spec)
|
|
4028
|
-
lines.append(f" {cpp_type} {f.name} = {default};")
|
|
4588
|
+
lines.append(f" {cpp_type} {self._safe_name(f.name)} = {default};")
|
|
4029
4589
|
lines.append("};")
|
|
4030
4590
|
lines.append("")
|
|
4031
4591
|
|
|
@@ -4034,15 +4594,18 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4034
4594
|
|
|
4035
4595
|
# 1c. Enum constants + string tables for str.tostring(enumVar)
|
|
4036
4596
|
for enum_name, members in self._enum_defs.items():
|
|
4597
|
+
safe_enum_name = self._safe_name(enum_name)
|
|
4037
4598
|
for i, member in enumerate(members):
|
|
4038
|
-
lines.append(
|
|
4599
|
+
lines.append(
|
|
4600
|
+
f'const int {safe_enum_name}_{self._safe_name(member)} = {i};'
|
|
4601
|
+
)
|
|
4039
4602
|
strs = self._enum_member_strings.get(enum_name)
|
|
4040
4603
|
if strs and len(strs) == len(members):
|
|
4041
4604
|
parts = ", ".join(
|
|
4042
4605
|
f'std::string("{self._cpp_string_escape(s)}")' for s in strs
|
|
4043
4606
|
)
|
|
4044
4607
|
lines.append(
|
|
4045
|
-
f"static const std::string {
|
|
4608
|
+
f"static const std::string {safe_enum_name}_str_values[] = {{{parts}}};"
|
|
4046
4609
|
)
|
|
4047
4610
|
lines.append("")
|
|
4048
4611
|
|
|
@@ -4051,9 +4614,12 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4051
4614
|
# per-callsite instances are declared below inside GeneratedStrategy
|
|
4052
4615
|
# and therefore join the automatic COOF checkpoint inventory.
|
|
4053
4616
|
self._emit_lazy_source_clock_helper(lines)
|
|
4617
|
+
# The chart-bar session type, inserted here once the class is lowered
|
|
4618
|
+
# and known to call it (codegen/session_market.py).
|
|
4619
|
+
_session_market_at = len(lines)
|
|
4054
4620
|
|
|
4055
4621
|
# 2. Open class
|
|
4056
|
-
lines.append("class GeneratedStrategy : public
|
|
4622
|
+
lines.append("class GeneratedStrategy : public pineforge::source::PineStrategyHost {")
|
|
4057
4623
|
lines.append("public:")
|
|
4058
4624
|
_script_state_decl_start = len(lines)
|
|
4059
4625
|
|
|
@@ -4127,6 +4693,12 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4127
4693
|
self._security_ohlc_hist_fields_by_sec[sec_id] = hist_fields
|
|
4128
4694
|
for i, el in enumerate(expr_node.elements):
|
|
4129
4695
|
ctype = self._infer_cpp_type_for_security_elem(el)
|
|
4696
|
+
if ctype == "int" and item.get("foreign"):
|
|
4697
|
+
# Another symbol's int element (its time, time_close,
|
|
4698
|
+
# bar_index) is held as a double: a millisecond
|
|
4699
|
+
# stamp overflows an int, and it reads na until the
|
|
4700
|
+
# symbol's first bar.
|
|
4701
|
+
ctype = "double"
|
|
4130
4702
|
if ctype == "std::vector<double>":
|
|
4131
4703
|
lines.append(f" {ctype} _req_sec_{sec_id}_{i}{{}};")
|
|
4132
4704
|
else:
|
|
@@ -4162,7 +4734,10 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4162
4734
|
self._security_ohlc_hist_fields_by_sec[sec_id] = (
|
|
4163
4735
|
self._collect_security_ohlc_hist_fields_for_call(item)
|
|
4164
4736
|
)
|
|
4165
|
-
|
|
4737
|
+
if item.get("string_result"):
|
|
4738
|
+
lines.append(f" std::string _req_sec_{sec_id} = na<std::string>();")
|
|
4739
|
+
else:
|
|
4740
|
+
lines.append(f" double _req_sec_{sec_id} = na<double>();")
|
|
4166
4741
|
for field in sorted(self._security_ohlc_hist_fields_by_sec.get(sec_id, ())):
|
|
4167
4742
|
ctype = self._security_bar_hist_type(field)
|
|
4168
4743
|
lines.append(
|
|
@@ -4174,9 +4749,22 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4174
4749
|
for name in self._security_ta_hist_series_names(sec_id):
|
|
4175
4750
|
lines.append(f" Series<double> {name}{_mbb};")
|
|
4176
4751
|
self._emit_security_expr_hist_members(sec_id, expr_node, lines, _mbb)
|
|
4177
|
-
|
|
4752
|
+
if item.get("foreign"):
|
|
4753
|
+
# configure_security_evaluators() clears it when the site is
|
|
4754
|
+
# registered: its reads stop the run while it is set.
|
|
4755
|
+
lines.append(f" bool _pf_sec_missing_{sec_id} = true;")
|
|
4756
|
+
|
|
4757
|
+
# A recorded request's missing-data flag, set where it is evaluated.
|
|
4758
|
+
for n in range(len(self._recorded_sites())):
|
|
4759
|
+
lines.append(f" bool _pf_rec_missing_{n} = true;")
|
|
4178
4760
|
if self._security_calls:
|
|
4179
4761
|
lines.append(' std::unordered_map<std::string, Series<double>> _security_helper_series_;')
|
|
4762
|
+
self._security_string_series_declared = self._security_needs_string_series()
|
|
4763
|
+
if self._security_string_series_declared:
|
|
4764
|
+
lines.append(
|
|
4765
|
+
' std::unordered_map<std::string, Series<std::string>> '
|
|
4766
|
+
'_security_helper_series_str_;'
|
|
4767
|
+
)
|
|
4180
4768
|
|
|
4181
4769
|
# Security-local mutable global state for request.security
|
|
4182
4770
|
for info in self._security_eval_info:
|
|
@@ -4200,7 +4788,7 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4200
4788
|
for _ta_idx, site in enumerate(self.ctx.ta_call_sites):
|
|
4201
4789
|
if _ta_idx in self._dead_ta_indices:
|
|
4202
4790
|
continue
|
|
4203
|
-
lines.append(f" {site
|
|
4791
|
+
lines.append(f" {self._ta_member_cpp_type(site)} {site.member_name};")
|
|
4204
4792
|
if self._ta_site_uses_precalc(site):
|
|
4205
4793
|
vtype = self._ta_return_type(site)
|
|
4206
4794
|
lines.append(f" std::vector<{vtype}> _precalc_{site.member_name};")
|
|
@@ -4219,8 +4807,6 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4219
4807
|
literal = info["length_literal"]
|
|
4220
4808
|
capacity = f"{{{literal + 1}}}" if literal is not None and literal >= 1 else ""
|
|
4221
4809
|
lines.append(f" Series<double> {info['hist']}{capacity};")
|
|
4222
|
-
if info["chart"] is not None:
|
|
4223
|
-
lines.append(f" Series<double> {info['chart']}{capacity};")
|
|
4224
4810
|
|
|
4225
4811
|
# Security evaluator TA members (cloned from expression dependencies)
|
|
4226
4812
|
# Skip for user function call expressions — their TA deps are internal to the function
|
|
@@ -4228,18 +4814,26 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4228
4814
|
for idx, variants in (info.get("ta_variants") or {}).items():
|
|
4229
4815
|
site = self.ctx.ta_call_sites[idx]
|
|
4230
4816
|
for variant in variants:
|
|
4231
|
-
|
|
4817
|
+
cpp_type = self._ta_security_member_cpp_type(info["sec_id"], site, variant)
|
|
4818
|
+
lines.append(f" {cpp_type} {variant['member_name']};")
|
|
4232
4819
|
|
|
4233
4820
|
# 4. Series members for bar field history
|
|
4234
4821
|
for field_name in sorted(self.ctx.series_bar_fields):
|
|
4235
4822
|
lines.append(f" Series<double> _s_{field_name}{_mbb};")
|
|
4236
4823
|
|
|
4237
4824
|
# 5. var/varip members (deduplicate by name)
|
|
4825
|
+
# Sections 5-6 and 8c-8c2 declare the script's var/varip and
|
|
4826
|
+
# history-read variables, whose drawing handles keep a drawing from
|
|
4827
|
+
# the collection; 8b's plain non-var globals do not.
|
|
4828
|
+
_variable_decls_start = len(lines)
|
|
4238
4829
|
seen_var_members: set[str] = set()
|
|
4239
4830
|
for name, ptype, init_str in self.ctx.var_members:
|
|
4240
4831
|
if name in seen_var_members:
|
|
4241
4832
|
continue
|
|
4242
4833
|
seen_var_members.add(name)
|
|
4834
|
+
if self._budget is not None:
|
|
4835
|
+
# _callable_var_udt_spec scans every declaration.
|
|
4836
|
+
self._budget.check(phase=Phase.CODEGEN)
|
|
4243
4837
|
safe = self._safe_name(name)
|
|
4244
4838
|
callable_collection_spec = (
|
|
4245
4839
|
None
|
|
@@ -4347,7 +4941,7 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4347
4941
|
udt_type = udt_name
|
|
4348
4942
|
break
|
|
4349
4943
|
if udt_type:
|
|
4350
|
-
lines.append(f" {udt_type} {safe};")
|
|
4944
|
+
lines.append(f" {self._safe_name(udt_type)} {safe};")
|
|
4351
4945
|
continue
|
|
4352
4946
|
cpp_type = PINE_TYPE_TO_CPP.get(ptype, "double")
|
|
4353
4947
|
# Promote int->int64_t when init RHS is an int64-returning builtin
|
|
@@ -4387,6 +4981,7 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4387
4981
|
cpp_type = self._series_type_for(name)
|
|
4388
4982
|
lines.append(f" Series<{cpp_type}> {safe}{_mbb};")
|
|
4389
4983
|
|
|
4984
|
+
_variable_decls = lines[_variable_decls_start:]
|
|
4390
4985
|
# 7. Fixnan members
|
|
4391
4986
|
for _fi_idx, site in enumerate(self.ctx.fixnan_sites):
|
|
4392
4987
|
if _fi_idx in self._dead_fixnan_indices:
|
|
@@ -4404,6 +4999,11 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4404
4999
|
else:
|
|
4405
5000
|
lines.append(f" Series<double> {svar}{_mbb};")
|
|
4406
5001
|
|
|
5002
|
+
# 8. session.* flags read at an offset at the top level: one Series per
|
|
5003
|
+
# flag, pushed on every chart bar (emit_top.py).
|
|
5004
|
+
for flag in sorted(self._session_history_flags):
|
|
5005
|
+
lines.append(f" Series<bool> {self._session_history_member(flag)}{_mbb};")
|
|
5006
|
+
|
|
4407
5007
|
# 8a. Synthetic temporary history. Unlike the legacy function-local
|
|
4408
5008
|
# static buffers, these members are value-copyable rollback state and
|
|
4409
5009
|
# have one identity per source site / emitted UDF variant.
|
|
@@ -4450,10 +5050,15 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4450
5050
|
if _draw_cpp is not None:
|
|
4451
5051
|
lines.append(f" {_draw_cpp} {safe} = {_draw_cpp}{{}};")
|
|
4452
5052
|
else:
|
|
4453
|
-
|
|
5053
|
+
udt_cpp = self._safe_name(udt_t)
|
|
5054
|
+
lines.append(f" {udt_cpp} {safe} = {udt_cpp}{{}};")
|
|
4454
5055
|
else:
|
|
4455
5056
|
expr = self.ctx.global_expr_map.get(name) if hasattr(self.ctx, "global_expr_map") else None
|
|
4456
|
-
if (
|
|
5057
|
+
if self._global_color_hint(name):
|
|
5058
|
+
cpp_type = "int64_t"
|
|
5059
|
+
elif self._global_bool_hint(name):
|
|
5060
|
+
cpp_type = "bool"
|
|
5061
|
+
elif (
|
|
4457
5062
|
name in self._direct_program_tuple_binding_names
|
|
4458
5063
|
and ptype == PineType.BOOL
|
|
4459
5064
|
):
|
|
@@ -4462,15 +5067,39 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4462
5067
|
# coarse request.security call expression itself still
|
|
4463
5068
|
# reports FLOAT, so prefer that exact binding type here.
|
|
4464
5069
|
cpp_type = "bool"
|
|
5070
|
+
elif (
|
|
5071
|
+
ptype == PineType.STRING
|
|
5072
|
+
and name in self._security_tuple_binding_names()
|
|
5073
|
+
):
|
|
5074
|
+
# A string element of a request.security helper tuple.
|
|
5075
|
+
cpp_type = "std::string"
|
|
5076
|
+
elif (
|
|
5077
|
+
ptype == PineType.STRING
|
|
5078
|
+
and isinstance(expr, (IfStmt, SwitchStmt))
|
|
5079
|
+
):
|
|
5080
|
+
# A string element of an if/switch selection tuple
|
|
5081
|
+
# (``[a, b] = if c ... f() else [string(na), string(na)]``):
|
|
5082
|
+
# the whole selection infers as double, which a
|
|
5083
|
+
# std::string element could not be assigned to.
|
|
5084
|
+
cpp_type = "std::string"
|
|
4465
5085
|
else:
|
|
4466
5086
|
cpp_type = (
|
|
4467
5087
|
self._infer_type(expr)
|
|
4468
5088
|
if expr is not None
|
|
4469
5089
|
else PINE_TYPE_TO_CPP.get(ptype, "double")
|
|
4470
5090
|
)
|
|
5091
|
+
# The var-member rule: a plain global an epoch reaches
|
|
5092
|
+
# (``_wide_int_provenance``) is stored in 64 bits too.
|
|
5093
|
+
if cpp_type == "int" and self._is_int64_builtin_init(name):
|
|
5094
|
+
cpp_type = "int64_t"
|
|
5095
|
+
# An int that can hold TradingView's infinity
|
|
5096
|
+
# (``_nonfinite_int_names``) keeps its double.
|
|
5097
|
+
elif cpp_type == "int" and name in self._nonfinite_int_names():
|
|
5098
|
+
cpp_type = "double"
|
|
4471
5099
|
default = self._default_for_type(cpp_type)
|
|
4472
5100
|
lines.append(f" {cpp_type} {safe} = {default};")
|
|
4473
5101
|
|
|
5102
|
+
_variable_decls_start = len(lines)
|
|
4474
5103
|
# 8c. Cloned var/series members for per-call-site function variants
|
|
4475
5104
|
# Same pattern as TA member cloning: each call site gets its own copy
|
|
4476
5105
|
emitted_clones: set[str] = set()
|
|
@@ -4496,6 +5125,9 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4496
5125
|
orig_safe, fresh_safe, _mbb, lines, owner_func=owner_func
|
|
4497
5126
|
)
|
|
4498
5127
|
|
|
5128
|
+
_variable_decls = _variable_decls + lines[_variable_decls_start:]
|
|
5129
|
+
self._drawing_pins = self._drawing_pin_members(_variable_decls)
|
|
5130
|
+
|
|
4499
5131
|
# 8c3. Fresh fixnan members for context-sensitive helper instances.
|
|
4500
5132
|
# Each fresh instance gets its OWN previous-value member so two
|
|
4501
5133
|
# call paths never share fixnan state (mirrors 8c2 for vars).
|
|
@@ -4508,14 +5140,15 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4508
5140
|
|
|
4509
5141
|
# 8d. Drawing-objects-as-data arenas (gated on _uses_drawing so
|
|
4510
5142
|
# non-drawing strategies emit byte-identical C++). Each arena is a
|
|
4511
|
-
# per-strategy member
|
|
4512
|
-
# the strategy() header
|
|
5143
|
+
# per-strategy member, reset by prepare_script_run. The arenas never
|
|
5144
|
+
# evict: the generated collectors apply the strategy() header's
|
|
5145
|
+
# max_*_count the way TradingView does (default 50; see
|
|
5146
|
+
# DRAWING_LIFETIME_CPP), and linefills are never collected.
|
|
4513
5147
|
if self._uses_drawing:
|
|
4514
|
-
|
|
4515
|
-
lines.append(
|
|
4516
|
-
lines.append(
|
|
4517
|
-
lines.append(
|
|
4518
|
-
lines.append(f" DrawingArena<LinefillRec> _pf_linefills_{{{caps.get('linefill', 50)}}};")
|
|
5148
|
+
lines.append(" DrawingArena<LineRec> _pf_lines_{_PF_DRAWING_UNBOUNDED};")
|
|
5149
|
+
lines.append(" DrawingArena<BoxRec> _pf_boxes_{_PF_DRAWING_UNBOUNDED};")
|
|
5150
|
+
lines.append(" DrawingArena<LabelRec> _pf_labels_{_PF_DRAWING_UNBOUNDED};")
|
|
5151
|
+
lines.append(" DrawingArena<LinefillRec> _pf_linefills_{_PF_DRAWING_UNBOUNDED};")
|
|
4519
5152
|
|
|
4520
5153
|
# 9. _var_initialized flag
|
|
4521
5154
|
if self.ctx.var_members:
|
|
@@ -4577,15 +5210,23 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4577
5210
|
# inventory from the declarations above so every future generated
|
|
4578
5211
|
# state category is captured automatically (or generation fails loudly
|
|
4579
5212
|
# if it introduces an unfamiliar declaration form).
|
|
5213
|
+
_script_state_declarations = lines[_script_state_decl_start:-1]
|
|
4580
5214
|
_script_state_members = self._collect_script_state_members(
|
|
4581
|
-
|
|
5215
|
+
_script_state_declarations
|
|
4582
5216
|
)
|
|
4583
5217
|
self._emit_script_state_hooks(lines, _script_state_members)
|
|
4584
5218
|
lines.append("")
|
|
5219
|
+
# Its per-strategy cache, outside the checkpointed script state.
|
|
5220
|
+
_session_market_member_at = len(lines)
|
|
4585
5221
|
|
|
4586
5222
|
# 9. Constructor with TA initializer list
|
|
4587
5223
|
self._emit_constructor(lines)
|
|
4588
5224
|
lines.append("")
|
|
5225
|
+
self._emit_script_run_prepare(lines, _script_state_declarations)
|
|
5226
|
+
lines.append("")
|
|
5227
|
+
if self._uses_drawing:
|
|
5228
|
+
self._emit_drawing_collectors(lines)
|
|
5229
|
+
lines.append("")
|
|
4589
5230
|
|
|
4590
5231
|
# 10. User-defined functions (with per-call-site variants for functions
|
|
4591
5232
|
# containing TA calls OR series variables that need isolation)
|
|
@@ -4647,7 +5288,13 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4647
5288
|
# 13. extern "C" interface
|
|
4648
5289
|
self._emit_extern_c(lines)
|
|
4649
5290
|
|
|
4650
|
-
|
|
5291
|
+
if self._uses_session_market:
|
|
5292
|
+
lines.insert(_session_market_member_at, SESSION_MARKET_MEMBER)
|
|
5293
|
+
lines.insert(_session_market_at, SESSION_MARKET_CPP)
|
|
5294
|
+
|
|
5295
|
+
cpp = "\n".join(lines)
|
|
5296
|
+
self._settle_session_reads(cpp)
|
|
5297
|
+
return cpp
|
|
4651
5298
|
|
|
4652
5299
|
# ------------------------------------------------------------------
|
|
4653
5300
|
# Top-level emitters (_emit_includes / _emit_constructor / _emit_on_bar
|
|
@@ -4696,8 +5343,99 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4696
5343
|
and not self._known_var_is_lexically_shadowed(arg_str)
|
|
4697
5344
|
):
|
|
4698
5345
|
return str(self._stable_var_ctor_literals[arg_str].value)
|
|
5346
|
+
# An inline input's placeholder is its defval, as a bound
|
|
5347
|
+
# ``v = input.int(9, ...)`` contributes through ``_known_vars``; the
|
|
5348
|
+
# runtime reset still sizes the indicator from the live input.
|
|
5349
|
+
folded = self._fold_inline_input_defaults(arg_str)
|
|
5350
|
+
if folded is not None and self._is_compile_time_value(folded):
|
|
5351
|
+
return folded
|
|
4699
5352
|
return resolved
|
|
4700
5353
|
|
|
5354
|
+
@staticmethod
|
|
5355
|
+
def _ta_ctor_arg_is_bool(site: TACallSite, position: int) -> bool:
|
|
5356
|
+
"""Whether a TA shim constructor slot is a Pine simple bool."""
|
|
5357
|
+
return (
|
|
5358
|
+
(site.class_name == "_PFALMA" and position == 3)
|
|
5359
|
+
or (site.class_name in ("_PFKC", "_PFKCW") and position == 2)
|
|
5360
|
+
or (site.class_name == "ta::TR" and position == 0)
|
|
5361
|
+
or (site.class_name in ("ta::StdDev", "ta::Variance") and position == 1)
|
|
5362
|
+
)
|
|
5363
|
+
|
|
5364
|
+
@staticmethod
|
|
5365
|
+
def _ta_ctor_bool_cpp(value: str) -> str:
|
|
5366
|
+
"""Pine truthiness for a runtime TA constructor flag."""
|
|
5367
|
+
if value.strip() in {"true", "false"}:
|
|
5368
|
+
return value
|
|
5369
|
+
return pine_truth_cast(value)
|
|
5370
|
+
|
|
5371
|
+
# An inline ``input.*()`` call inside a ctor-arg / derived-length spelling
|
|
5372
|
+
# is one leaf of the expression. It qualifies exactly when its bound
|
|
5373
|
+
# spelling ``v = <call>`` would make ``v`` input-backed
|
|
5374
|
+
# (``_collect_known_var``): not a source input, and a constant defval.
|
|
5375
|
+
|
|
5376
|
+
def _is_stable_inline_input(self, node) -> bool:
|
|
5377
|
+
return (isinstance(node, FuncCall)
|
|
5378
|
+
and self._is_input_call(node)
|
|
5379
|
+
and not self._is_source_input(node)
|
|
5380
|
+
and self._input_default_value(node)[0])
|
|
5381
|
+
|
|
5382
|
+
def _inline_input_calls(self, expr: str) -> list[tuple[int, int, FuncCall]] | None:
|
|
5383
|
+
"""Each inline input call in ``expr`` as ``(start, end, call)``; None
|
|
5384
|
+
when one is not a stable inline input or does not re-parse."""
|
|
5385
|
+
from ..lexer import Lexer
|
|
5386
|
+
from ..parser import Parser
|
|
5387
|
+
calls = []
|
|
5388
|
+
for start, end in input_call_spans(expr):
|
|
5389
|
+
text = expr[start:end]
|
|
5390
|
+
try:
|
|
5391
|
+
node = Parser(Lexer(text).tokenize(), source=text)._parse_expression()
|
|
5392
|
+
except Exception:
|
|
5393
|
+
return None
|
|
5394
|
+
if not self._is_stable_inline_input(node):
|
|
5395
|
+
return None
|
|
5396
|
+
calls.append((start, end, node))
|
|
5397
|
+
return calls
|
|
5398
|
+
|
|
5399
|
+
def _inline_inputs_masked(self, expr: str) -> str:
|
|
5400
|
+
"""``expr`` with each inline input call reduced to the bare word
|
|
5401
|
+
``input``, so identifier scans never read its title string or keyword
|
|
5402
|
+
names. Unchanged when one of the calls is not a stable inline input."""
|
|
5403
|
+
for start, end, _call in reversed(self._inline_input_calls(expr) or ()):
|
|
5404
|
+
expr = expr[:start] + "input" + expr[end:]
|
|
5405
|
+
return expr
|
|
5406
|
+
|
|
5407
|
+
def _refs_input(self, expr: str) -> bool:
|
|
5408
|
+
"""Whether a stable-expression spelling reads an input: an inline
|
|
5409
|
+
input call or an input-backed name."""
|
|
5410
|
+
masked = self._inline_inputs_masked(expr)
|
|
5411
|
+
if masked != expr:
|
|
5412
|
+
return True
|
|
5413
|
+
return any(t in self._input_backed_vars
|
|
5414
|
+
for t in re.findall(r"[A-Za-z_][A-Za-z_0-9]*",
|
|
5415
|
+
blank_string_literals(expr)))
|
|
5416
|
+
|
|
5417
|
+
def _fold_inline_input_defaults(self, expr: str) -> str | None:
|
|
5418
|
+
"""``expr`` const-folded with each inline input call read as its
|
|
5419
|
+
defval; None when it has none or one is not a stable inline input."""
|
|
5420
|
+
calls = self._inline_input_calls(expr)
|
|
5421
|
+
if not calls:
|
|
5422
|
+
return None
|
|
5423
|
+
for start, end, call in reversed(calls):
|
|
5424
|
+
value = self._input_default_value(call)[1]
|
|
5425
|
+
if isinstance(value, bool):
|
|
5426
|
+
literal = "true" if value else "false"
|
|
5427
|
+
elif isinstance(value, str):
|
|
5428
|
+
literal = pine_string_literal(value)
|
|
5429
|
+
else:
|
|
5430
|
+
literal = str(value)
|
|
5431
|
+
expr = expr[:start] + literal + expr[end:]
|
|
5432
|
+
return self._resolve_known(expr)
|
|
5433
|
+
|
|
5434
|
+
def _resolve_known_through_inputs(self, expr: str) -> str:
|
|
5435
|
+
"""``_resolve_known``, reading each inline input call as its defval."""
|
|
5436
|
+
folded = self._fold_inline_input_defaults(expr)
|
|
5437
|
+
return folded if folded is not None else self._resolve_known(expr)
|
|
5438
|
+
|
|
4701
5439
|
def _resolve_known(self, arg_str: str) -> str:
|
|
4702
5440
|
"""Resolve a string arg, replacing known var names with their values.
|
|
4703
5441
|
|
|
@@ -4719,39 +5457,18 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4719
5457
|
# Also resolve bar field references
|
|
4720
5458
|
if arg_str in BAR_FIELDS:
|
|
4721
5459
|
return BAR_FIELDS[arg_str]
|
|
4722
|
-
#
|
|
5460
|
+
# Fold only numeric syntax. Bind names as primitive values rather than
|
|
5461
|
+
# rewriting source or exposing Python builtins/module objects to it.
|
|
4723
5462
|
if any(c in arg_str for c in "+-*/()."):
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
val = self._known_vars[name]
|
|
4731
|
-
if isinstance(val, (int, float)):
|
|
4732
|
-
import re
|
|
4733
|
-
resolved = re.sub(rf'\b{re.escape(name)}\b', str(val), resolved)
|
|
4734
|
-
# Map Pine math functions to Python equivalents for eval
|
|
4735
|
-
eval_str = resolved
|
|
4736
|
-
eval_str = eval_str.replace("math.round", "round")
|
|
4737
|
-
eval_str = eval_str.replace("math.sqrt", "__import__('math').sqrt")
|
|
4738
|
-
eval_str = eval_str.replace("math.ceil", "__import__('math').ceil")
|
|
4739
|
-
eval_str = eval_str.replace("math.floor", "__import__('math').floor")
|
|
4740
|
-
eval_str = eval_str.replace("math.abs", "abs")
|
|
4741
|
-
# Evaluate safely (only allow numeric operations).
|
|
4742
|
-
# Acquire the builtin through indirection so this file does
|
|
4743
|
-
# not contain the literal three-letter token followed by ``(``
|
|
4744
|
-
# — a repository-wide security hook blocks file writes
|
|
4745
|
-
# containing that pattern.
|
|
4746
|
-
_expr_evaluator = getattr(__builtins__, "eval", None) or __builtins__["eval"]
|
|
4747
|
-
result = _expr_evaluator(eval_str, {"__builtins__": {}},
|
|
4748
|
-
{"round": round, "abs": abs,
|
|
4749
|
-
"math": __import__("math")})
|
|
5463
|
+
values = {
|
|
5464
|
+
name: value for name, value in self._known_vars.items()
|
|
5465
|
+
if not self._known_var_is_lexically_shadowed(name)
|
|
5466
|
+
}
|
|
5467
|
+
result = fold_numeric_expression(arg_str, values)
|
|
5468
|
+
if result is not None:
|
|
4750
5469
|
if isinstance(result, float) and result == int(result):
|
|
4751
5470
|
return str(int(result))
|
|
4752
5471
|
return str(result)
|
|
4753
|
-
except Exception:
|
|
4754
|
-
pass
|
|
4755
5472
|
return arg_str
|
|
4756
5473
|
|
|
4757
5474
|
# _is_input_call / _is_input_call_by_name / _get_input_default /
|
|
@@ -4970,11 +5687,14 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4970
5687
|
inner = self._derived_input_expr[nm]
|
|
4971
5688
|
return "(" + _expand_derived(inner, seen | {nm}, depth + 1) + ")"
|
|
4972
5689
|
return nm
|
|
4973
|
-
return
|
|
5690
|
+
return sub_identifiers(s, _rep)
|
|
4974
5691
|
|
|
4975
5692
|
expanded = _expand_derived(arg_str)
|
|
4976
5693
|
|
|
4977
|
-
|
|
5694
|
+
# An inline input call is one leaf of the expression; its title string
|
|
5695
|
+
# and keyword names are not identifiers the gate below should judge.
|
|
5696
|
+
tokens = set(ident_re.findall(
|
|
5697
|
+
blank_string_literals(self._inline_inputs_masked(expanded))))
|
|
4978
5698
|
if any(
|
|
4979
5699
|
self._known_var_is_lexically_shadowed(name)
|
|
4980
5700
|
for name in tokens
|
|
@@ -5105,7 +5825,7 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
5105
5825
|
# integer length.
|
|
5106
5826
|
had_math = "std::" in rewritten or bool(re.search(r"\btimeframe\b", expanded))
|
|
5107
5827
|
if had_math:
|
|
5108
|
-
return
|
|
5828
|
+
return na_preserving_int_cast(rewritten)
|
|
5109
5829
|
return rewritten
|
|
5110
5830
|
|
|
5111
5831
|
def _lower_reset_expr_via_visitor(self, expanded: str) -> str | None:
|
|
@@ -5149,10 +5869,34 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
5149
5869
|
# unwrapped, so simple sites stay byte-identical to the legacy output.
|
|
5150
5870
|
if ("std::" in rendered or "(double)" in rendered
|
|
5151
5871
|
or "script_tf_" in rendered):
|
|
5152
|
-
return
|
|
5872
|
+
return na_preserving_int_cast(rendered)
|
|
5153
5873
|
return rendered
|
|
5154
5874
|
|
|
5155
5875
|
|
|
5876
|
+
def _ta_run_ctor_args(self, site: TACallSite) -> tuple[list[str], bool]:
|
|
5877
|
+
"""The constructor arguments a chart TA site runs with, and whether any
|
|
5878
|
+
is a runtime expression: an input- or timeframe-backed argument as its
|
|
5879
|
+
override-aware runtime read, a compile-time one as its value, any other
|
|
5880
|
+
as the placeholder ``1`` (the constructor guard refuses those first).
|
|
5881
|
+
The ``_ta_initialized_`` reset and ``precalculate()`` both build the
|
|
5882
|
+
site from these, so a precalculated series is sized like the live
|
|
5883
|
+
indicator; built from the compile-time values instead, it kept the
|
|
5884
|
+
input's default (or ``1``) under every override."""
|
|
5885
|
+
args: list[str] = []
|
|
5886
|
+
any_runtime = False
|
|
5887
|
+
for arg_pos, a in enumerate(site.ctor_args):
|
|
5888
|
+
rt = self._runtime_ctor_arg_for_reset(a)
|
|
5889
|
+
if rt is not None:
|
|
5890
|
+
rendered = rt
|
|
5891
|
+
any_runtime = True
|
|
5892
|
+
else:
|
|
5893
|
+
resolved = self._resolve_ta_ctor_arg(a)
|
|
5894
|
+
rendered = resolved if self._is_compile_time_value(resolved) else "1"
|
|
5895
|
+
if self._ta_ctor_arg_is_bool(site, arg_pos):
|
|
5896
|
+
rendered = self._ta_ctor_bool_cpp(rendered)
|
|
5897
|
+
args.append(rendered)
|
|
5898
|
+
return args, any_runtime
|
|
5899
|
+
|
|
5156
5900
|
def _collect_ta_runtime_resets(
|
|
5157
5901
|
self,
|
|
5158
5902
|
*,
|
|
@@ -5173,16 +5917,9 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
5173
5917
|
continue
|
|
5174
5918
|
if not site.ctor_args:
|
|
5175
5919
|
continue
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
rt = self._runtime_ctor_arg_for_reset(a)
|
|
5180
|
-
if rt is not None:
|
|
5181
|
-
runtime_args.append(rt)
|
|
5182
|
-
any_runtime = True
|
|
5183
|
-
else:
|
|
5184
|
-
resolved = self._resolve_ta_ctor_arg(a)
|
|
5185
|
-
runtime_args.append(resolved if self._is_compile_time_value(resolved) else "1")
|
|
5920
|
+
if self._ta_dynamic_plan(site) is not None:
|
|
5921
|
+
continue
|
|
5922
|
+
runtime_args, any_runtime = self._ta_run_ctor_args(site)
|
|
5186
5923
|
if any_runtime:
|
|
5187
5924
|
resets.append(
|
|
5188
5925
|
f"{site.member_name} = {site.class_name}({', '.join(runtime_args)});"
|
|
@@ -5223,6 +5960,10 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
5223
5960
|
if not ctor_site.ctor_args:
|
|
5224
5961
|
continue
|
|
5225
5962
|
for variant in variants:
|
|
5963
|
+
if self._ta_security_plan(
|
|
5964
|
+
info["sec_id"], site, variant.get("binding_stack", ())
|
|
5965
|
+
) is not None:
|
|
5966
|
+
continue
|
|
5226
5967
|
ctor_args, ctor_arg_stability = self._security_ta_ctor_args_for_variant(
|
|
5227
5968
|
info["sec_id"],
|
|
5228
5969
|
site,
|
|
@@ -5251,25 +5992,24 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
5251
5992
|
and lowered_variant
|
|
5252
5993
|
and not self._is_compile_time_value(resolved)
|
|
5253
5994
|
):
|
|
5254
|
-
self.
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
"
|
|
5259
|
-
"per-run scalar.",
|
|
5260
|
-
hint=("Use a literal, an input.*() value, "
|
|
5261
|
-
"timeframe.* metadata, or arithmetic "
|
|
5262
|
-
"over those for TA lengths."),
|
|
5995
|
+
arg_nodes = self._security_ta_ctor_arg_nodes(site)
|
|
5996
|
+
self._refuse_security_ta_ctor_arg(
|
|
5997
|
+
site, arg_pos, a,
|
|
5998
|
+
arg_nodes[arg_pos] if arg_pos < len(arg_nodes) else None,
|
|
5999
|
+
variant.get("binding_stack", ()),
|
|
5263
6000
|
)
|
|
5264
6001
|
if rt is not None:
|
|
5265
|
-
|
|
6002
|
+
rendered = rt
|
|
5266
6003
|
any_runtime = True
|
|
5267
6004
|
else:
|
|
5268
|
-
|
|
6005
|
+
rendered = (
|
|
5269
6006
|
resolved
|
|
5270
6007
|
if self._is_compile_time_value(resolved)
|
|
5271
6008
|
else "1"
|
|
5272
6009
|
)
|
|
6010
|
+
if self._ta_ctor_arg_is_bool(site, arg_pos):
|
|
6011
|
+
rendered = self._ta_ctor_bool_cpp(rendered)
|
|
6012
|
+
runtime_args.append(rendered)
|
|
5273
6013
|
if any_runtime:
|
|
5274
6014
|
resets.append(
|
|
5275
6015
|
f"{variant['member_name']} = {site.class_name}({', '.join(runtime_args)});"
|