@pineforge/codegen-pyodide 0.10.3 → 1.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +767 -77
- package/pineforge_codegen/analyzer/call_handlers.py +268 -42
- 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 +907 -159
- package/pineforge_codegen/codegen/constant_fold.py +131 -0
- package/pineforge_codegen/codegen/drawing.py +221 -79
- package/pineforge_codegen/codegen/emit_top.py +961 -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 +1198 -101
- package/pineforge_codegen/codegen/tables.py +205 -71
- package/pineforge_codegen/codegen/tv_number_format.py +270 -0
- package/pineforge_codegen/codegen/types.py +2055 -62
- package/pineforge_codegen/codegen/visit_call.py +929 -131
- package/pineforge_codegen/codegen/visit_expr.py +765 -63
- package/pineforge_codegen/codegen/visit_stmt.py +610 -52
- 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/symbols.py +4 -2
- package/pineforge_codegen-1.0.0-rc.1.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.3.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,
|
|
@@ -1914,7 +2146,10 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
1914
2146
|
self._collection_types[name] = spec
|
|
1915
2147
|
elif ns == "array" and fn in ({"new", "from"} | set(ARRAY_NEW_CTORS)):
|
|
1916
2148
|
self._array_vars.add(name)
|
|
1917
|
-
spec = self.
|
|
2149
|
+
spec = self._widen_array_spec_for_name(
|
|
2150
|
+
name,
|
|
2151
|
+
self._type_spec_from_expr(expr) or self._array_spec_for_name(name),
|
|
2152
|
+
)
|
|
1918
2153
|
self._collection_types[name] = spec
|
|
1919
2154
|
elif ns == "map" and fn == "new":
|
|
1920
2155
|
self._map_vars.add(name)
|
|
@@ -1934,7 +2169,10 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
1934
2169
|
fn2, ns2 = self._resolve_callee(expr.callee)
|
|
1935
2170
|
if ns2 == "array" and fn2 in ({"new", "from"} | set(ARRAY_NEW_CTORS)):
|
|
1936
2171
|
self._array_vars.add(name)
|
|
1937
|
-
spec2 = self.
|
|
2172
|
+
spec2 = self._widen_array_spec_for_name(
|
|
2173
|
+
name,
|
|
2174
|
+
self._type_spec_from_expr(expr) or self._array_spec_for_name(name),
|
|
2175
|
+
)
|
|
1938
2176
|
self._collection_types[name] = spec2
|
|
1939
2177
|
continue
|
|
1940
2178
|
if name in self._matrix_specs:
|
|
@@ -2263,10 +2501,11 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
2263
2501
|
all_args.append(None)
|
|
2264
2502
|
all_args[i] = node.kwargs[pname]
|
|
2265
2503
|
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
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]]
|
|
2270
2509
|
|
|
2271
2510
|
return all_args
|
|
2272
2511
|
|
|
@@ -2310,6 +2549,9 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
2310
2549
|
rejected by the constructor guard. Names are NOT folded into
|
|
2311
2550
|
``_known_vars`` (no use-site inlining): only the length-analysis path is
|
|
2312
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``).
|
|
2313
2555
|
"""
|
|
2314
2556
|
literal_candidates = self._admitted_stable_var_ctor_literals(reassigned)
|
|
2315
2557
|
|
|
@@ -2323,6 +2565,19 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
2323
2565
|
continue
|
|
2324
2566
|
if self._decl_binding_is_series(id(stmt), stmt.name):
|
|
2325
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
|
|
2326
2581
|
if stmt.value is None or not self._expr_is_stable(stmt.value):
|
|
2327
2582
|
continue
|
|
2328
2583
|
expr_str = self._arith_expr_to_str(stmt.value)
|
|
@@ -2334,9 +2589,7 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
2334
2589
|
self._stable_var_ctor_literals[stmt.name] = literal_record
|
|
2335
2590
|
# Mark input-backed iff the init references an input, so the reset
|
|
2336
2591
|
# emits override-aware get_input_*() reads for it.
|
|
2337
|
-
|
|
2338
|
-
toks = set(_re.findall(r"[A-Za-z_][A-Za-z_0-9]*", expr_str))
|
|
2339
|
-
if any(t in self._input_backed_vars for t in toks):
|
|
2592
|
+
if self._refs_input(expr_str):
|
|
2340
2593
|
self._input_backed_vars.add(stmt.name)
|
|
2341
2594
|
|
|
2342
2595
|
@staticmethod
|
|
@@ -2650,7 +2903,7 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
2650
2903
|
# Fold to a compile-time literal when possible (so the ctor-init
|
|
2651
2904
|
# list can use it directly); otherwise record the raw expression
|
|
2652
2905
|
# for the runtime reset path to expand.
|
|
2653
|
-
folded = self.
|
|
2906
|
+
folded = self._resolve_known_through_inputs(final)
|
|
2654
2907
|
if self._is_compile_time_value(folded):
|
|
2655
2908
|
try:
|
|
2656
2909
|
num = float(folded)
|
|
@@ -2663,9 +2916,7 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
2663
2916
|
self._stable_runtime_vars.add(target_name)
|
|
2664
2917
|
# Mark input-backed iff the expression references an input so the
|
|
2665
2918
|
# override-aware get_input_*() reads are emitted on the reset path.
|
|
2666
|
-
|
|
2667
|
-
toks = set(_re.findall(r"[A-Za-z_][A-Za-z_0-9]*", final))
|
|
2668
|
-
if any(t in self._input_backed_vars for t in toks):
|
|
2919
|
+
if self._refs_input(final):
|
|
2669
2920
|
self._input_backed_vars.add(target_name)
|
|
2670
2921
|
|
|
2671
2922
|
def _find_reassigned_vars(self) -> set[str]:
|
|
@@ -2910,6 +3161,12 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
2910
3161
|
for a in node.args)
|
|
2911
3162
|
if namespace == "input":
|
|
2912
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)
|
|
2913
3170
|
if namespace is None and func_name in ("int", "float", "bool", "string"):
|
|
2914
3171
|
return all(self._expr_is_stable(a, _udf_stack, _depth)
|
|
2915
3172
|
for a in node.args)
|
|
@@ -2942,7 +3199,7 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
2942
3199
|
# tree: Pine grouping ``(a - b) / (c - d)`` degrades to ``a - b / c - d``
|
|
2943
3200
|
# under C++ precedence. See ``_runtime_ctor_arg_for_reset`` (the string is
|
|
2944
3201
|
# re-parsed and lowered through the expression visitor).
|
|
2945
|
-
_ATOMIC_ARITH_NODES = (NumberLiteral, Identifier, MemberAccess, FuncCall)
|
|
3202
|
+
_ATOMIC_ARITH_NODES = (NumberLiteral, StringLiteral, Identifier, MemberAccess, FuncCall)
|
|
2946
3203
|
|
|
2947
3204
|
def _arith_operand_to_str(self, node, _udf_stack: frozenset = frozenset(),
|
|
2948
3205
|
_depth: int = 0) -> str | None:
|
|
@@ -2967,11 +3224,21 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
2967
3224
|
against recursion cycles (Pine forbids recursion, but a malformed source
|
|
2968
3225
|
must be refused, not looped forever).
|
|
2969
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
|
|
2970
3233
|
if isinstance(node, NumberLiteral):
|
|
2971
3234
|
v = node.value
|
|
2972
3235
|
if isinstance(v, float) and v == int(v):
|
|
2973
3236
|
return str(int(v))
|
|
2974
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)
|
|
2975
3242
|
if isinstance(node, Identifier):
|
|
2976
3243
|
return node.name
|
|
2977
3244
|
if isinstance(node, MemberAccess) and isinstance(node.object, Identifier):
|
|
@@ -3003,12 +3270,27 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3003
3270
|
# ordinary ``callee(args)`` rendering below. The stack/depth guard
|
|
3004
3271
|
# refuses a recursive UDF (returns None -> caller leaves it untracked
|
|
3005
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))
|
|
3006
3278
|
fn, ns = self._resolve_callee(node.callee)
|
|
3007
3279
|
if ns is None and fn is not None and self._get_udf_def(fn) is not None:
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
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]
|
|
3012
3294
|
callee = self._arith_expr_to_str(node.callee, _udf_stack, _depth)
|
|
3013
3295
|
if callee is None:
|
|
3014
3296
|
return None
|
|
@@ -3021,14 +3303,21 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3021
3303
|
return f"{callee}({', '.join(parts)})"
|
|
3022
3304
|
return None
|
|
3023
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
|
+
|
|
3024
3312
|
def _collect_known_var(self, node: VarDecl) -> None:
|
|
3025
3313
|
"""Extract known constant value from a VarDecl."""
|
|
3026
3314
|
# Don't inline series variables — their values change over time
|
|
3027
3315
|
if self._decl_binding_is_series(id(node), node.name):
|
|
3028
3316
|
return
|
|
3029
3317
|
# Don't inline var/varip variables — they're mutable state that persists
|
|
3030
|
-
# across bars and can be reassigned with :=
|
|
3031
|
-
|
|
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):
|
|
3032
3321
|
return
|
|
3033
3322
|
if isinstance(node.value, NumberLiteral):
|
|
3034
3323
|
self._known_vars[node.name] = node.value.value
|
|
@@ -3043,6 +3332,21 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3043
3332
|
self._input_backed_vars.add(node.name)
|
|
3044
3333
|
if node.value.name in self._input_var_to_call:
|
|
3045
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)
|
|
3046
3350
|
if node.value.name in self._timeframe_period_vars:
|
|
3047
3351
|
self._timeframe_period_vars.add(node.name)
|
|
3048
3352
|
elif (isinstance(node.value, MemberAccess)
|
|
@@ -3052,25 +3356,9 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3052
3356
|
self._timeframe_period_vars.add(node.name)
|
|
3053
3357
|
# Input calls: extract default value
|
|
3054
3358
|
elif isinstance(node.value, FuncCall) and self._is_input_call(node.value):
|
|
3055
|
-
default = self.
|
|
3056
|
-
stored = False
|
|
3057
|
-
if isinstance(default, NumberLiteral):
|
|
3058
|
-
self._known_vars[node.name] = default.value
|
|
3059
|
-
stored = True
|
|
3060
|
-
elif isinstance(default, BoolLiteral):
|
|
3061
|
-
self._known_vars[node.name] = default.value
|
|
3062
|
-
stored = True
|
|
3063
|
-
elif isinstance(default, StringLiteral):
|
|
3064
|
-
self._known_vars[node.name] = default.value
|
|
3065
|
-
stored = True
|
|
3066
|
-
elif isinstance(default, MemberAccess) and isinstance(default.object, Identifier):
|
|
3067
|
-
en = default.object.name
|
|
3068
|
-
if en in self._enum_defs and default.member in self._enum_defs[en]:
|
|
3069
|
-
self._known_vars[node.name] = self._enum_defs[en].index(
|
|
3070
|
-
default.member
|
|
3071
|
-
)
|
|
3072
|
-
stored = True
|
|
3359
|
+
stored, default = self._input_default_value(node.value)
|
|
3073
3360
|
if stored:
|
|
3361
|
+
self._known_vars[node.name] = default
|
|
3074
3362
|
self._input_backed_vars.add(node.name)
|
|
3075
3363
|
self._input_var_to_call[node.name] = node.value
|
|
3076
3364
|
# Class-scope arithmetic / ternaries / casts over known, input-backed,
|
|
@@ -3093,8 +3381,10 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3093
3381
|
expr_str = self._arith_expr_to_str(node.value)
|
|
3094
3382
|
if expr_str is not None and self._expr_is_stable(node.value):
|
|
3095
3383
|
import re as _re
|
|
3096
|
-
tokens = set(_re.findall(r"[A-Za-z_][A-Za-z_0-9]*",
|
|
3097
|
-
|
|
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)
|
|
3098
3388
|
refs_derived = any(t in self._derived_input_expr for t in tokens)
|
|
3099
3389
|
# The stability classifier already proved this expression is a
|
|
3100
3390
|
# bar-invariant scalar (inputs / constants / timeframe.* /
|
|
@@ -3102,7 +3392,7 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3102
3392
|
# stable exprs (and the TA reset path) can reference / expand
|
|
3103
3393
|
# it — e.g. ``pi = math.asin(1) * 2`` feeds ``beta`` feeds
|
|
3104
3394
|
# ``alpha`` feeds a function-local ``filterLen``.
|
|
3105
|
-
folded = self.
|
|
3395
|
+
folded = self._resolve_known_through_inputs(expr_str)
|
|
3106
3396
|
if self._is_compile_time_value(folded):
|
|
3107
3397
|
try:
|
|
3108
3398
|
num = float(folded)
|
|
@@ -3128,25 +3418,38 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3128
3418
|
# Public entry point
|
|
3129
3419
|
# ------------------------------------------------------------------
|
|
3130
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
|
+
|
|
3131
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:
|
|
3132
3437
|
loc = node.loc if node is not None else None
|
|
3133
3438
|
if loc is None:
|
|
3134
3439
|
loc = SourceLocation(file=self.ctx.filename, line=1, col=1, end_col=1)
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
message=message,
|
|
3142
|
-
hint=hint,
|
|
3143
|
-
)
|
|
3144
|
-
]
|
|
3440
|
+
return Diagnostic(
|
|
3441
|
+
level=Level.ERROR,
|
|
3442
|
+
phase=Phase.CODEGEN,
|
|
3443
|
+
location=loc,
|
|
3444
|
+
message=message,
|
|
3445
|
+
hint=hint,
|
|
3145
3446
|
)
|
|
3146
3447
|
|
|
3147
3448
|
def _ta_return_type(self, site: TACallSite) -> str:
|
|
3148
3449
|
if getattr(site, "returns_tuple", False):
|
|
3149
|
-
return
|
|
3450
|
+
return TA_TUPLE_RESULT_TYPES.get(
|
|
3451
|
+
self._ta_name_from_site(site), f"{site.class_name}Result"
|
|
3452
|
+
)
|
|
3150
3453
|
if site.class_name in ("ta::Crossover", "ta::Crossunder", "ta::Cross"):
|
|
3151
3454
|
return "bool"
|
|
3152
3455
|
return "double"
|
|
@@ -3229,11 +3532,15 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3229
3532
|
"""
|
|
3230
3533
|
self._inline_history_members = []
|
|
3231
3534
|
self._inline_history_member_by_key = {}
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
}
|
|
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", ()))
|
|
3237
3544
|
|
|
3238
3545
|
def walk_nodes(value):
|
|
3239
3546
|
"""Yield AST nodes in stable field order, including tuple elements.
|
|
@@ -3329,6 +3636,9 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3329
3636
|
return
|
|
3330
3637
|
counters[kind] += 1
|
|
3331
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]}"
|
|
3332
3642
|
self._inline_history_member_by_key[key] = member_name
|
|
3333
3643
|
self._inline_history_members.append({
|
|
3334
3644
|
"kind": kind,
|
|
@@ -3375,6 +3685,12 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3375
3685
|
return list(
|
|
3376
3686
|
self._bind_typed_method_args(method_info, call).args_by_param
|
|
3377
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
|
|
3378
3694
|
if call.kwargs:
|
|
3379
3695
|
return _merge_kwargs(call.args, call.kwargs, params, lambda arg: arg)
|
|
3380
3696
|
return list(call.args)
|
|
@@ -3430,6 +3746,20 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3430
3746
|
id(child) for child in walk_nodes(expression)
|
|
3431
3747
|
)
|
|
3432
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
|
+
}
|
|
3433
3763
|
|
|
3434
3764
|
def emitted_context_for_call(
|
|
3435
3765
|
fi,
|
|
@@ -3518,6 +3848,27 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3518
3848
|
register(
|
|
3519
3849
|
"hist_call", (id(node),), self._infer_type(node.object), owner
|
|
3520
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
|
+
)
|
|
3521
3872
|
|
|
3522
3873
|
if not isinstance(node, FuncCall):
|
|
3523
3874
|
continue
|
|
@@ -3638,12 +3989,155 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3638
3989
|
context,
|
|
3639
3990
|
)
|
|
3640
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
|
+
|
|
3641
4125
|
def _inline_history_member(self, kind: str, node: ASTNode,
|
|
3642
4126
|
arg_idx: int | None = None) -> str:
|
|
3643
4127
|
source_key = (id(node),) if arg_idx is None else (id(node), arg_idx)
|
|
3644
4128
|
key = (kind, *source_key, self._current_instance_name)
|
|
3645
4129
|
member = self._inline_history_member_by_key.get(key)
|
|
3646
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
|
+
)
|
|
3647
4141
|
raise AssertionError(
|
|
3648
4142
|
"missing pre-registered inline history member for "
|
|
3649
4143
|
f"{kind} at {getattr(node, 'loc', None)} in context "
|
|
@@ -3785,8 +4279,47 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3785
4279
|
self._udt_arena_member_names[type_name] = allocated
|
|
3786
4280
|
self._all_member_names.add(allocated)
|
|
3787
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
|
+
|
|
3788
4317
|
def generate(self) -> str:
|
|
3789
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()
|
|
3790
4323
|
# Context-sensitive instance pre-pass (needs the naming helpers populated
|
|
3791
4324
|
# in __init__). Computes nested stateful-helper dispatch + fresh instances.
|
|
3792
4325
|
self._build_func_instances()
|
|
@@ -3800,14 +4333,26 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3800
4333
|
self._prepare_udt_generated_names()
|
|
3801
4334
|
# Pre-scan for strategy series vars
|
|
3802
4335
|
self._prescan_strategy_series()
|
|
4336
|
+
self._prescan_session_history()
|
|
3803
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] = {}
|
|
3804
4341
|
# request.security TA call-sites read at a history offset (``ta.ema(...)[k>=1]``).
|
|
3805
4342
|
# Maps sec_id -> set of TA call-site indices needing an HTF history Series.
|
|
3806
4343
|
self._security_ta_hist_idx_by_sec: dict[int, set[int]] = {}
|
|
3807
4344
|
# request.security helper-call results read at a history offset
|
|
3808
4345
|
# (``myHelper()[k]``). Maps (sec_id, node-id) -> backing Series metadata.
|
|
3809
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()
|
|
3810
4354
|
self._prepare_lazy_source_clock_sites()
|
|
4355
|
+
self.block_locals_needing_names = self._block_locals_needing_names()
|
|
3811
4356
|
|
|
3812
4357
|
lines: list[str] = []
|
|
3813
4358
|
|
|
@@ -3829,11 +4374,12 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3829
4374
|
# The two-phase order also permits self/nested UDT fields without
|
|
3830
4375
|
# embedding a C++ type recursively by value.
|
|
3831
4376
|
for type_name in self._udt_defs:
|
|
3832
|
-
|
|
4377
|
+
safe_type_name = self._safe_name(type_name)
|
|
4378
|
+
lines.append(f"struct {safe_type_name} {{")
|
|
3833
4379
|
lines.append(" int32_t __pf_id = -1;")
|
|
3834
4380
|
lines.append("};")
|
|
3835
4381
|
lines.append(
|
|
3836
|
-
f"inline bool is_na(const {
|
|
4382
|
+
f"inline bool is_na(const {safe_type_name}& _z) "
|
|
3837
4383
|
"{ return _z.__pf_id < 0; }"
|
|
3838
4384
|
)
|
|
3839
4385
|
lines.append("")
|
|
@@ -3859,6 +4405,12 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3859
4405
|
f" {self._udt_undo_coordinator_cpp_name}& operator=(",
|
|
3860
4406
|
f" {self._udt_undo_coordinator_cpp_name}&&) = delete;",
|
|
3861
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
|
+
"",
|
|
3862
4414
|
" Snapshot snapshot() {",
|
|
3863
4415
|
" if (_pf_generation_ == std::numeric_limits<uint64_t>::max()) {",
|
|
3864
4416
|
' throw std::overflow_error("UDT checkpoint generation exhausted");',
|
|
@@ -3936,6 +4488,14 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
3936
4488
|
f" {self._udt_arena_template_cpp_name}& operator=(",
|
|
3937
4489
|
f" {self._udt_arena_template_cpp_name}&&) = delete;",
|
|
3938
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
|
+
"",
|
|
3939
4499
|
" _PFHandle create(_PFRecord value) {",
|
|
3940
4500
|
" if (_pf_records_.size() > static_cast<std::size_t>(",
|
|
3941
4501
|
" std::numeric_limits<int32_t>::max())) {",
|
|
@@ -4006,6 +4566,8 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4006
4566
|
# the same map. Authored field defaults are evaluated by ``Type.new``
|
|
4007
4567
|
# at the call site; record declarations use only inert typed defaults so
|
|
4008
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)
|
|
4009
4571
|
for type_name, fields in self._udt_defs.items():
|
|
4010
4572
|
record_type = self._udt_record_cpp_type(type_name)
|
|
4011
4573
|
lines.append(f"struct {record_type} {{")
|
|
@@ -4015,11 +4577,15 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4015
4577
|
if f.name in omitted:
|
|
4016
4578
|
continue
|
|
4017
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
|
|
4018
4584
|
cpp_type = self._type_spec_to_cpp(spec)
|
|
4019
4585
|
if cpp_type == "int":
|
|
4020
4586
|
cpp_type = "int64_t"
|
|
4021
4587
|
default = self._default_for_spec(spec)
|
|
4022
|
-
lines.append(f" {cpp_type} {f.name} = {default};")
|
|
4588
|
+
lines.append(f" {cpp_type} {self._safe_name(f.name)} = {default};")
|
|
4023
4589
|
lines.append("};")
|
|
4024
4590
|
lines.append("")
|
|
4025
4591
|
|
|
@@ -4028,15 +4594,18 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4028
4594
|
|
|
4029
4595
|
# 1c. Enum constants + string tables for str.tostring(enumVar)
|
|
4030
4596
|
for enum_name, members in self._enum_defs.items():
|
|
4597
|
+
safe_enum_name = self._safe_name(enum_name)
|
|
4031
4598
|
for i, member in enumerate(members):
|
|
4032
|
-
lines.append(
|
|
4599
|
+
lines.append(
|
|
4600
|
+
f'const int {safe_enum_name}_{self._safe_name(member)} = {i};'
|
|
4601
|
+
)
|
|
4033
4602
|
strs = self._enum_member_strings.get(enum_name)
|
|
4034
4603
|
if strs and len(strs) == len(members):
|
|
4035
4604
|
parts = ", ".join(
|
|
4036
4605
|
f'std::string("{self._cpp_string_escape(s)}")' for s in strs
|
|
4037
4606
|
)
|
|
4038
4607
|
lines.append(
|
|
4039
|
-
f"static const std::string {
|
|
4608
|
+
f"static const std::string {safe_enum_name}_str_values[] = {{{parts}}};"
|
|
4040
4609
|
)
|
|
4041
4610
|
lines.append("")
|
|
4042
4611
|
|
|
@@ -4045,9 +4614,12 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4045
4614
|
# per-callsite instances are declared below inside GeneratedStrategy
|
|
4046
4615
|
# and therefore join the automatic COOF checkpoint inventory.
|
|
4047
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)
|
|
4048
4620
|
|
|
4049
4621
|
# 2. Open class
|
|
4050
|
-
lines.append("class GeneratedStrategy : public
|
|
4622
|
+
lines.append("class GeneratedStrategy : public pineforge::source::PineStrategyHost {")
|
|
4051
4623
|
lines.append("public:")
|
|
4052
4624
|
_script_state_decl_start = len(lines)
|
|
4053
4625
|
|
|
@@ -4121,6 +4693,12 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4121
4693
|
self._security_ohlc_hist_fields_by_sec[sec_id] = hist_fields
|
|
4122
4694
|
for i, el in enumerate(expr_node.elements):
|
|
4123
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"
|
|
4124
4702
|
if ctype == "std::vector<double>":
|
|
4125
4703
|
lines.append(f" {ctype} _req_sec_{sec_id}_{i}{{}};")
|
|
4126
4704
|
else:
|
|
@@ -4156,7 +4734,10 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4156
4734
|
self._security_ohlc_hist_fields_by_sec[sec_id] = (
|
|
4157
4735
|
self._collect_security_ohlc_hist_fields_for_call(item)
|
|
4158
4736
|
)
|
|
4159
|
-
|
|
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>();")
|
|
4160
4741
|
for field in sorted(self._security_ohlc_hist_fields_by_sec.get(sec_id, ())):
|
|
4161
4742
|
ctype = self._security_bar_hist_type(field)
|
|
4162
4743
|
lines.append(
|
|
@@ -4168,9 +4749,22 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4168
4749
|
for name in self._security_ta_hist_series_names(sec_id):
|
|
4169
4750
|
lines.append(f" Series<double> {name}{_mbb};")
|
|
4170
4751
|
self._emit_security_expr_hist_members(sec_id, expr_node, lines, _mbb)
|
|
4171
|
-
|
|
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;")
|
|
4172
4760
|
if self._security_calls:
|
|
4173
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
|
+
)
|
|
4174
4768
|
|
|
4175
4769
|
# Security-local mutable global state for request.security
|
|
4176
4770
|
for info in self._security_eval_info:
|
|
@@ -4194,7 +4788,7 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4194
4788
|
for _ta_idx, site in enumerate(self.ctx.ta_call_sites):
|
|
4195
4789
|
if _ta_idx in self._dead_ta_indices:
|
|
4196
4790
|
continue
|
|
4197
|
-
lines.append(f" {site
|
|
4791
|
+
lines.append(f" {self._ta_member_cpp_type(site)} {site.member_name};")
|
|
4198
4792
|
if self._ta_site_uses_precalc(site):
|
|
4199
4793
|
vtype = self._ta_return_type(site)
|
|
4200
4794
|
lines.append(f" std::vector<{vtype}> _precalc_{site.member_name};")
|
|
@@ -4213,8 +4807,6 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4213
4807
|
literal = info["length_literal"]
|
|
4214
4808
|
capacity = f"{{{literal + 1}}}" if literal is not None and literal >= 1 else ""
|
|
4215
4809
|
lines.append(f" Series<double> {info['hist']}{capacity};")
|
|
4216
|
-
if info["chart"] is not None:
|
|
4217
|
-
lines.append(f" Series<double> {info['chart']}{capacity};")
|
|
4218
4810
|
|
|
4219
4811
|
# Security evaluator TA members (cloned from expression dependencies)
|
|
4220
4812
|
# Skip for user function call expressions — their TA deps are internal to the function
|
|
@@ -4222,18 +4814,26 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4222
4814
|
for idx, variants in (info.get("ta_variants") or {}).items():
|
|
4223
4815
|
site = self.ctx.ta_call_sites[idx]
|
|
4224
4816
|
for variant in variants:
|
|
4225
|
-
|
|
4817
|
+
cpp_type = self._ta_security_member_cpp_type(info["sec_id"], site, variant)
|
|
4818
|
+
lines.append(f" {cpp_type} {variant['member_name']};")
|
|
4226
4819
|
|
|
4227
4820
|
# 4. Series members for bar field history
|
|
4228
4821
|
for field_name in sorted(self.ctx.series_bar_fields):
|
|
4229
4822
|
lines.append(f" Series<double> _s_{field_name}{_mbb};")
|
|
4230
4823
|
|
|
4231
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)
|
|
4232
4829
|
seen_var_members: set[str] = set()
|
|
4233
4830
|
for name, ptype, init_str in self.ctx.var_members:
|
|
4234
4831
|
if name in seen_var_members:
|
|
4235
4832
|
continue
|
|
4236
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)
|
|
4237
4837
|
safe = self._safe_name(name)
|
|
4238
4838
|
callable_collection_spec = (
|
|
4239
4839
|
None
|
|
@@ -4275,7 +4875,9 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4275
4875
|
)
|
|
4276
4876
|
):
|
|
4277
4877
|
self._array_vars.add(name)
|
|
4278
|
-
array_spec =
|
|
4878
|
+
array_spec = self._widen_array_spec_for_name(
|
|
4879
|
+
name, exact_member_spec or self._array_spec_for_name(name)
|
|
4880
|
+
)
|
|
4279
4881
|
lines.append(
|
|
4280
4882
|
f" {self._type_spec_to_cpp(array_spec)} {safe};"
|
|
4281
4883
|
)
|
|
@@ -4339,7 +4941,7 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4339
4941
|
udt_type = udt_name
|
|
4340
4942
|
break
|
|
4341
4943
|
if udt_type:
|
|
4342
|
-
lines.append(f" {udt_type} {safe};")
|
|
4944
|
+
lines.append(f" {self._safe_name(udt_type)} {safe};")
|
|
4343
4945
|
continue
|
|
4344
4946
|
cpp_type = PINE_TYPE_TO_CPP.get(ptype, "double")
|
|
4345
4947
|
# Promote int->int64_t when init RHS is an int64-returning builtin
|
|
@@ -4379,6 +4981,7 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4379
4981
|
cpp_type = self._series_type_for(name)
|
|
4380
4982
|
lines.append(f" Series<{cpp_type}> {safe}{_mbb};")
|
|
4381
4983
|
|
|
4984
|
+
_variable_decls = lines[_variable_decls_start:]
|
|
4382
4985
|
# 7. Fixnan members
|
|
4383
4986
|
for _fi_idx, site in enumerate(self.ctx.fixnan_sites):
|
|
4384
4987
|
if _fi_idx in self._dead_fixnan_indices:
|
|
@@ -4396,6 +4999,11 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4396
4999
|
else:
|
|
4397
5000
|
lines.append(f" Series<double> {svar}{_mbb};")
|
|
4398
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
|
+
|
|
4399
5007
|
# 8a. Synthetic temporary history. Unlike the legacy function-local
|
|
4400
5008
|
# static buffers, these members are value-copyable rollback state and
|
|
4401
5009
|
# have one identity per source site / emitted UDF variant.
|
|
@@ -4442,10 +5050,15 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4442
5050
|
if _draw_cpp is not None:
|
|
4443
5051
|
lines.append(f" {_draw_cpp} {safe} = {_draw_cpp}{{}};")
|
|
4444
5052
|
else:
|
|
4445
|
-
|
|
5053
|
+
udt_cpp = self._safe_name(udt_t)
|
|
5054
|
+
lines.append(f" {udt_cpp} {safe} = {udt_cpp}{{}};")
|
|
4446
5055
|
else:
|
|
4447
5056
|
expr = self.ctx.global_expr_map.get(name) if hasattr(self.ctx, "global_expr_map") else None
|
|
4448
|
-
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 (
|
|
4449
5062
|
name in self._direct_program_tuple_binding_names
|
|
4450
5063
|
and ptype == PineType.BOOL
|
|
4451
5064
|
):
|
|
@@ -4454,15 +5067,39 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4454
5067
|
# coarse request.security call expression itself still
|
|
4455
5068
|
# reports FLOAT, so prefer that exact binding type here.
|
|
4456
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"
|
|
4457
5085
|
else:
|
|
4458
5086
|
cpp_type = (
|
|
4459
5087
|
self._infer_type(expr)
|
|
4460
5088
|
if expr is not None
|
|
4461
5089
|
else PINE_TYPE_TO_CPP.get(ptype, "double")
|
|
4462
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"
|
|
4463
5099
|
default = self._default_for_type(cpp_type)
|
|
4464
5100
|
lines.append(f" {cpp_type} {safe} = {default};")
|
|
4465
5101
|
|
|
5102
|
+
_variable_decls_start = len(lines)
|
|
4466
5103
|
# 8c. Cloned var/series members for per-call-site function variants
|
|
4467
5104
|
# Same pattern as TA member cloning: each call site gets its own copy
|
|
4468
5105
|
emitted_clones: set[str] = set()
|
|
@@ -4488,6 +5125,9 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4488
5125
|
orig_safe, fresh_safe, _mbb, lines, owner_func=owner_func
|
|
4489
5126
|
)
|
|
4490
5127
|
|
|
5128
|
+
_variable_decls = _variable_decls + lines[_variable_decls_start:]
|
|
5129
|
+
self._drawing_pins = self._drawing_pin_members(_variable_decls)
|
|
5130
|
+
|
|
4491
5131
|
# 8c3. Fresh fixnan members for context-sensitive helper instances.
|
|
4492
5132
|
# Each fresh instance gets its OWN previous-value member so two
|
|
4493
5133
|
# call paths never share fixnan state (mirrors 8c2 for vars).
|
|
@@ -4500,14 +5140,15 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4500
5140
|
|
|
4501
5141
|
# 8d. Drawing-objects-as-data arenas (gated on _uses_drawing so
|
|
4502
5142
|
# non-drawing strategies emit byte-identical C++). Each arena is a
|
|
4503
|
-
# per-strategy member
|
|
4504
|
-
# 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.
|
|
4505
5147
|
if self._uses_drawing:
|
|
4506
|
-
|
|
4507
|
-
lines.append(
|
|
4508
|
-
lines.append(
|
|
4509
|
-
lines.append(
|
|
4510
|
-
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};")
|
|
4511
5152
|
|
|
4512
5153
|
# 9. _var_initialized flag
|
|
4513
5154
|
if self.ctx.var_members:
|
|
@@ -4569,15 +5210,23 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4569
5210
|
# inventory from the declarations above so every future generated
|
|
4570
5211
|
# state category is captured automatically (or generation fails loudly
|
|
4571
5212
|
# if it introduces an unfamiliar declaration form).
|
|
5213
|
+
_script_state_declarations = lines[_script_state_decl_start:-1]
|
|
4572
5214
|
_script_state_members = self._collect_script_state_members(
|
|
4573
|
-
|
|
5215
|
+
_script_state_declarations
|
|
4574
5216
|
)
|
|
4575
5217
|
self._emit_script_state_hooks(lines, _script_state_members)
|
|
4576
5218
|
lines.append("")
|
|
5219
|
+
# Its per-strategy cache, outside the checkpointed script state.
|
|
5220
|
+
_session_market_member_at = len(lines)
|
|
4577
5221
|
|
|
4578
5222
|
# 9. Constructor with TA initializer list
|
|
4579
5223
|
self._emit_constructor(lines)
|
|
4580
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("")
|
|
4581
5230
|
|
|
4582
5231
|
# 10. User-defined functions (with per-call-site variants for functions
|
|
4583
5232
|
# containing TA calls OR series variables that need isolation)
|
|
@@ -4639,7 +5288,13 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4639
5288
|
# 13. extern "C" interface
|
|
4640
5289
|
self._emit_extern_c(lines)
|
|
4641
5290
|
|
|
4642
|
-
|
|
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
|
|
4643
5298
|
|
|
4644
5299
|
# ------------------------------------------------------------------
|
|
4645
5300
|
# Top-level emitters (_emit_includes / _emit_constructor / _emit_on_bar
|
|
@@ -4688,8 +5343,99 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4688
5343
|
and not self._known_var_is_lexically_shadowed(arg_str)
|
|
4689
5344
|
):
|
|
4690
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
|
|
4691
5352
|
return resolved
|
|
4692
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
|
+
|
|
4693
5439
|
def _resolve_known(self, arg_str: str) -> str:
|
|
4694
5440
|
"""Resolve a string arg, replacing known var names with their values.
|
|
4695
5441
|
|
|
@@ -4711,39 +5457,18 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4711
5457
|
# Also resolve bar field references
|
|
4712
5458
|
if arg_str in BAR_FIELDS:
|
|
4713
5459
|
return BAR_FIELDS[arg_str]
|
|
4714
|
-
#
|
|
5460
|
+
# Fold only numeric syntax. Bind names as primitive values rather than
|
|
5461
|
+
# rewriting source or exposing Python builtins/module objects to it.
|
|
4715
5462
|
if any(c in arg_str for c in "+-*/()."):
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
val = self._known_vars[name]
|
|
4723
|
-
if isinstance(val, (int, float)):
|
|
4724
|
-
import re
|
|
4725
|
-
resolved = re.sub(rf'\b{re.escape(name)}\b', str(val), resolved)
|
|
4726
|
-
# Map Pine math functions to Python equivalents for eval
|
|
4727
|
-
eval_str = resolved
|
|
4728
|
-
eval_str = eval_str.replace("math.round", "round")
|
|
4729
|
-
eval_str = eval_str.replace("math.sqrt", "__import__('math').sqrt")
|
|
4730
|
-
eval_str = eval_str.replace("math.ceil", "__import__('math').ceil")
|
|
4731
|
-
eval_str = eval_str.replace("math.floor", "__import__('math').floor")
|
|
4732
|
-
eval_str = eval_str.replace("math.abs", "abs")
|
|
4733
|
-
# Evaluate safely (only allow numeric operations).
|
|
4734
|
-
# Acquire the builtin through indirection so this file does
|
|
4735
|
-
# not contain the literal three-letter token followed by ``(``
|
|
4736
|
-
# — a repository-wide security hook blocks file writes
|
|
4737
|
-
# containing that pattern.
|
|
4738
|
-
_expr_evaluator = getattr(__builtins__, "eval", None) or __builtins__["eval"]
|
|
4739
|
-
result = _expr_evaluator(eval_str, {"__builtins__": {}},
|
|
4740
|
-
{"round": round, "abs": abs,
|
|
4741
|
-
"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:
|
|
4742
5469
|
if isinstance(result, float) and result == int(result):
|
|
4743
5470
|
return str(int(result))
|
|
4744
5471
|
return str(result)
|
|
4745
|
-
except Exception:
|
|
4746
|
-
pass
|
|
4747
5472
|
return arg_str
|
|
4748
5473
|
|
|
4749
5474
|
# _is_input_call / _is_input_call_by_name / _get_input_default /
|
|
@@ -4962,11 +5687,14 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4962
5687
|
inner = self._derived_input_expr[nm]
|
|
4963
5688
|
return "(" + _expand_derived(inner, seen | {nm}, depth + 1) + ")"
|
|
4964
5689
|
return nm
|
|
4965
|
-
return
|
|
5690
|
+
return sub_identifiers(s, _rep)
|
|
4966
5691
|
|
|
4967
5692
|
expanded = _expand_derived(arg_str)
|
|
4968
5693
|
|
|
4969
|
-
|
|
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))))
|
|
4970
5698
|
if any(
|
|
4971
5699
|
self._known_var_is_lexically_shadowed(name)
|
|
4972
5700
|
for name in tokens
|
|
@@ -5097,7 +5825,7 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
5097
5825
|
# integer length.
|
|
5098
5826
|
had_math = "std::" in rewritten or bool(re.search(r"\btimeframe\b", expanded))
|
|
5099
5827
|
if had_math:
|
|
5100
|
-
return
|
|
5828
|
+
return na_preserving_int_cast(rewritten)
|
|
5101
5829
|
return rewritten
|
|
5102
5830
|
|
|
5103
5831
|
def _lower_reset_expr_via_visitor(self, expanded: str) -> str | None:
|
|
@@ -5141,10 +5869,34 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
5141
5869
|
# unwrapped, so simple sites stay byte-identical to the legacy output.
|
|
5142
5870
|
if ("std::" in rendered or "(double)" in rendered
|
|
5143
5871
|
or "script_tf_" in rendered):
|
|
5144
|
-
return
|
|
5872
|
+
return na_preserving_int_cast(rendered)
|
|
5145
5873
|
return rendered
|
|
5146
5874
|
|
|
5147
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
|
+
|
|
5148
5900
|
def _collect_ta_runtime_resets(
|
|
5149
5901
|
self,
|
|
5150
5902
|
*,
|
|
@@ -5165,16 +5917,9 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
5165
5917
|
continue
|
|
5166
5918
|
if not site.ctor_args:
|
|
5167
5919
|
continue
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
rt = self._runtime_ctor_arg_for_reset(a)
|
|
5172
|
-
if rt is not None:
|
|
5173
|
-
runtime_args.append(rt)
|
|
5174
|
-
any_runtime = True
|
|
5175
|
-
else:
|
|
5176
|
-
resolved = self._resolve_ta_ctor_arg(a)
|
|
5177
|
-
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)
|
|
5178
5923
|
if any_runtime:
|
|
5179
5924
|
resets.append(
|
|
5180
5925
|
f"{site.member_name} = {site.class_name}({', '.join(runtime_args)});"
|
|
@@ -5215,6 +5960,10 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
5215
5960
|
if not ctor_site.ctor_args:
|
|
5216
5961
|
continue
|
|
5217
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
|
|
5218
5967
|
ctor_args, ctor_arg_stability = self._security_ta_ctor_args_for_variant(
|
|
5219
5968
|
info["sec_id"],
|
|
5220
5969
|
site,
|
|
@@ -5243,25 +5992,24 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
5243
5992
|
and lowered_variant
|
|
5244
5993
|
and not self._is_compile_time_value(resolved)
|
|
5245
5994
|
):
|
|
5246
|
-
self.
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
"
|
|
5251
|
-
"per-run scalar.",
|
|
5252
|
-
hint=("Use a literal, an input.*() value, "
|
|
5253
|
-
"timeframe.* metadata, or arithmetic "
|
|
5254
|
-
"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", ()),
|
|
5255
6000
|
)
|
|
5256
6001
|
if rt is not None:
|
|
5257
|
-
|
|
6002
|
+
rendered = rt
|
|
5258
6003
|
any_runtime = True
|
|
5259
6004
|
else:
|
|
5260
|
-
|
|
6005
|
+
rendered = (
|
|
5261
6006
|
resolved
|
|
5262
6007
|
if self._is_compile_time_value(resolved)
|
|
5263
6008
|
else "1"
|
|
5264
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)
|
|
5265
6013
|
if any_runtime:
|
|
5266
6014
|
resets.append(
|
|
5267
6015
|
f"{variant['member_name']} = {site.class_name}({', '.join(runtime_args)});"
|