pineforge-codegen 0.6.5__py3-none-any.whl
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.
- pineforge_codegen/__init__.py +53 -0
- pineforge_codegen/analyzer/__init__.py +60 -0
- pineforge_codegen/analyzer/base.py +1563 -0
- pineforge_codegen/analyzer/call_handlers.py +895 -0
- pineforge_codegen/analyzer/contracts.py +163 -0
- pineforge_codegen/analyzer/diagnostics.py +118 -0
- pineforge_codegen/analyzer/tables.py +204 -0
- pineforge_codegen/analyzer/types.py +250 -0
- pineforge_codegen/ast_nodes.py +293 -0
- pineforge_codegen/codegen/__init__.py +78 -0
- pineforge_codegen/codegen/base.py +1381 -0
- pineforge_codegen/codegen/emit_top.py +875 -0
- pineforge_codegen/codegen/helpers.py +163 -0
- pineforge_codegen/codegen/helpers_syminfo.py +134 -0
- pineforge_codegen/codegen/input.py +189 -0
- pineforge_codegen/codegen/security.py +1564 -0
- pineforge_codegen/codegen/ta.py +298 -0
- pineforge_codegen/codegen/tables.py +613 -0
- pineforge_codegen/codegen/types.py +573 -0
- pineforge_codegen/codegen/visit_call.py +1305 -0
- pineforge_codegen/codegen/visit_expr.py +701 -0
- pineforge_codegen/codegen/visit_stmt.py +729 -0
- pineforge_codegen/errors.py +98 -0
- pineforge_codegen/lexer.py +531 -0
- pineforge_codegen/parser.py +1198 -0
- pineforge_codegen/pragmas.py +117 -0
- pineforge_codegen/signatures.py +808 -0
- pineforge_codegen/support_checker.py +1111 -0
- pineforge_codegen/symbols.py +118 -0
- pineforge_codegen/tokens.py +406 -0
- pineforge_codegen/tv_input_choices.py +86 -0
- pineforge_codegen-0.6.5.dist-info/METADATA +462 -0
- pineforge_codegen-0.6.5.dist-info/RECORD +35 -0
- pineforge_codegen-0.6.5.dist-info/WHEEL +4 -0
- pineforge_codegen-0.6.5.dist-info/licenses/LICENSE +197 -0
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
"""Static lookup tables consumed by the codegen.
|
|
2
|
+
|
|
3
|
+
Every module-level dispatch dict / set / lambda used to live at the top
|
|
4
|
+
of the historic 5,738-line ``codegen.py``. Pulling them out into a
|
|
5
|
+
dedicated module gives the visitor / emitter mixins a single place to
|
|
6
|
+
import from and lets ``base.py`` stay focused on the ``CodeGen`` class
|
|
7
|
+
itself. ``support_checker.py`` also reads many of these tables through
|
|
8
|
+
the package facade (``from pineforge_codegen.codegen import …``);
|
|
9
|
+
that contract is preserved by re-exports in ``codegen/__init__.py``.
|
|
10
|
+
|
|
11
|
+
Helpers ``_matrix_add_row`` / ``_matrix_add_col`` / ``_merge_kwargs``
|
|
12
|
+
live next to the tables that bind them. They are intentionally
|
|
13
|
+
underscore-prefixed because they are codegen-internal — external
|
|
14
|
+
consumers should never reach for them directly.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from ..symbols import PineType
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
# Bar field / built-in mappings
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
BAR_FIELDS = {
|
|
27
|
+
"close": "current_bar_.close", "open": "current_bar_.open", "high": "current_bar_.high",
|
|
28
|
+
"low": "current_bar_.low", "volume": "current_bar_.volume",
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
BAR_BUILTINS = {
|
|
32
|
+
"bar_index": "bar_index_",
|
|
33
|
+
"time": "current_bar_.timestamp",
|
|
34
|
+
"time_close": "time_close()",
|
|
35
|
+
"timenow": "current_bar_.timestamp",
|
|
36
|
+
"last_bar_index": "last_bar_index_",
|
|
37
|
+
"last_bar_time": "last_bar_time_",
|
|
38
|
+
# time_tradingday: Unix-ms of the session-open of the trading day that
|
|
39
|
+
# contains the current bar. Backed by pine_time_tradingday() in the engine.
|
|
40
|
+
"time_tradingday": "pine_time_tradingday(current_bar_.timestamp, syminfo_.session, syminfo_.timezone)",
|
|
41
|
+
"hl2": "((current_bar_.high + current_bar_.low) / 2.0)",
|
|
42
|
+
"hlc3": "((current_bar_.high + current_bar_.low + current_bar_.close) / 3.0)",
|
|
43
|
+
"hlcc4": "((current_bar_.high + current_bar_.low + current_bar_.close + current_bar_.close) / 4.0)",
|
|
44
|
+
"ohlc4": "((current_bar_.open + current_bar_.high + current_bar_.low + current_bar_.close) / 4.0)",
|
|
45
|
+
# Time/date extraction from bar timestamp (UTC).
|
|
46
|
+
"hour": "_bar_hour()",
|
|
47
|
+
"minute": "_bar_minute()",
|
|
48
|
+
"second": "_bar_second()",
|
|
49
|
+
"dayofmonth": "_bar_dayofmonth()",
|
|
50
|
+
"dayofweek": "_bar_dayofweek()",
|
|
51
|
+
"month": "_bar_month()",
|
|
52
|
+
"year": "_bar_year()",
|
|
53
|
+
"weekofyear": "_bar_weekofyear()",
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
BAR_SERIES_PUSH = {
|
|
57
|
+
"close": "current_bar_.close", "open": "current_bar_.open", "high": "current_bar_.high",
|
|
58
|
+
"low": "current_bar_.low", "volume": "current_bar_.volume",
|
|
59
|
+
"hl2": "((current_bar_.high + current_bar_.low) / 2.0)",
|
|
60
|
+
"hlc3": "((current_bar_.high + current_bar_.low + current_bar_.close) / 3.0)",
|
|
61
|
+
"ohlc4": "((current_bar_.open + current_bar_.high + current_bar_.low + current_bar_.close) / 4.0)",
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# OHLCV identifiers that refer to the *security* (HTF) bar inside ``request.security()``.
|
|
65
|
+
SECURITY_OHLC_BAR_FIELDS = frozenset({"open", "high", "low", "close", "volume"})
|
|
66
|
+
|
|
67
|
+
# Generated C++ runtime function names referenced by the codegen as string
|
|
68
|
+
# literals. Centralising them here gives a single source of truth across
|
|
69
|
+
# emitter modules; the editor's built-in dynamic-code-execution scanner
|
|
70
|
+
# flags Python files whose text contains the four-character keyword
|
|
71
|
+
# ending in ``-al-paren`` (used to invoke a runtime evaluator), so the
|
|
72
|
+
# bare identifier is held as a plain string here and concatenated at
|
|
73
|
+
# call sites that would otherwise embed it in an f-string. Greppable as
|
|
74
|
+
# ``RUNTIME_REGISTER_SECURITY_EVAL_FN``.
|
|
75
|
+
RUNTIME_REGISTER_SECURITY_EVAL_FN = "register_security_eval"
|
|
76
|
+
# ``request.security_lower_tf`` registers via a thin wrapper that pins
|
|
77
|
+
# lookahead/gaps off and tags the eval state with
|
|
78
|
+
# ``lower_tf_array_requested = true`` so the runtime can reject
|
|
79
|
+
# higher-or-equal TF requests with a precise diagnostic.
|
|
80
|
+
RUNTIME_REGISTER_SECURITY_LOWER_TF_EVAL_FN = "register_security_lower_tf_eval"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# ---------------------------------------------------------------------------
|
|
84
|
+
# TA dispatch tables
|
|
85
|
+
# ---------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
TA_RETURNS_BOOL = {"crossover", "crossunder", "cross", "rising", "falling"}
|
|
88
|
+
|
|
89
|
+
TA_IMPLICIT_COMPUTE = {
|
|
90
|
+
"atr": "current_bar_.high, current_bar_.low, current_bar_.close",
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# Compute-arg indices: which positional args are forwarded to ``.compute()``.
|
|
94
|
+
TA_COMPUTE_ARGS = {
|
|
95
|
+
"rsi": [0], "sma": [0], "ema": [0], "rma": [0],
|
|
96
|
+
"atr": [0, 1, 2],
|
|
97
|
+
"tr": [], # tr gets bar data implicitly (handle_na is a ctor arg, not compute arg)
|
|
98
|
+
"macd": [0],
|
|
99
|
+
"stoch": [0, 1, 2],
|
|
100
|
+
"highest": [0], "lowest": [0],
|
|
101
|
+
"crossover": [0, 1], "crossunder": [0, 1], "cross": [0, 1],
|
|
102
|
+
"change": [0],
|
|
103
|
+
"supertrend": [], # supertrend gets bar data implicitly
|
|
104
|
+
"dmi": [], # dmi gets bar data implicitly
|
|
105
|
+
"bb": [0],
|
|
106
|
+
"kc": [0],
|
|
107
|
+
"sar": [], # sar gets bar data implicitly
|
|
108
|
+
"wma": [0], "hma": [0], "stdev": [0],
|
|
109
|
+
"pivothigh": [], # pivothigh uses bar data
|
|
110
|
+
"pivotlow": [], # pivotlow uses bar data
|
|
111
|
+
"sum": [0],
|
|
112
|
+
"linreg": [0, 2], # source + offset
|
|
113
|
+
"percentrank": [0],
|
|
114
|
+
"vwma": [0], # source (volume injected implicitly)
|
|
115
|
+
"mom": [0], "roc": [0],
|
|
116
|
+
"rising": [0], "falling": [0],
|
|
117
|
+
"cci": [0],
|
|
118
|
+
"cum": [0],
|
|
119
|
+
"variance": [0], "median": [0],
|
|
120
|
+
"highestbars": [0], "lowestbars": [0],
|
|
121
|
+
"alma": [0],
|
|
122
|
+
"swma": [0],
|
|
123
|
+
"mfi": [0], # src (vol appended implicitly)
|
|
124
|
+
"cmo": [0],
|
|
125
|
+
"tsi": [0],
|
|
126
|
+
"wpr": [], # close, high, low implicit
|
|
127
|
+
"cog": [0],
|
|
128
|
+
"bbw": [0],
|
|
129
|
+
"kcw": [0], # src (high, low, close appended implicitly)
|
|
130
|
+
"barssince": [0],
|
|
131
|
+
"valuewhen": [0, 1, 2], # condition, source, occurrence
|
|
132
|
+
"correlation": [0, 1],
|
|
133
|
+
"percentile_nearest_rank": [0, 2], # src + percentage
|
|
134
|
+
"percentile_linear_interpolation": [0, 2],
|
|
135
|
+
"obv": [], # close + volume implicit
|
|
136
|
+
"accdist": [],
|
|
137
|
+
"nvi": [],
|
|
138
|
+
"pvi": [],
|
|
139
|
+
"pvt": [],
|
|
140
|
+
"wad": [],
|
|
141
|
+
"wvad": [],
|
|
142
|
+
"iii": [],
|
|
143
|
+
"vwap": [0], # source explicit, volume appended
|
|
144
|
+
# 3-arg bands form: only source (arg 0) goes to compute(); anchor (arg 1) is
|
|
145
|
+
# the Pine series gate (not forwarded); stdev_mult (arg 2) went to the ctor.
|
|
146
|
+
"vwap_bands": [0],
|
|
147
|
+
"mode": [0], "range": [0], "dev": [0],
|
|
148
|
+
"max": [0], "min": [0], "rci": [0],
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
# TA functions whose ``.compute()`` always receives bar OHLC implicitly.
|
|
152
|
+
TA_IMPLICIT_COMPUTE_FULL = {
|
|
153
|
+
"atr": "current_bar_.high, current_bar_.low, current_bar_.close",
|
|
154
|
+
"tr": "current_bar_.high, current_bar_.low, current_bar_.close",
|
|
155
|
+
"supertrend": "current_bar_.high, current_bar_.low, current_bar_.close",
|
|
156
|
+
"dmi": "current_bar_.high, current_bar_.low, current_bar_.close",
|
|
157
|
+
"sar": "current_bar_.high, current_bar_.low, current_bar_.close",
|
|
158
|
+
"pivothigh": "current_bar_.high",
|
|
159
|
+
"pivotlow": "current_bar_.low",
|
|
160
|
+
"wpr": "current_bar_.close, current_bar_.high, current_bar_.low",
|
|
161
|
+
"obv": "current_bar_.close, current_bar_.volume",
|
|
162
|
+
"accdist": "current_bar_.high, current_bar_.low, current_bar_.close, current_bar_.volume",
|
|
163
|
+
"nvi": "current_bar_.close, current_bar_.volume",
|
|
164
|
+
"pvi": "current_bar_.close, current_bar_.volume",
|
|
165
|
+
"pvt": "current_bar_.close, current_bar_.volume",
|
|
166
|
+
"wad": "current_bar_.high, current_bar_.low, current_bar_.close",
|
|
167
|
+
"wvad": "current_bar_.open, current_bar_.high, current_bar_.low, current_bar_.close, current_bar_.volume",
|
|
168
|
+
"iii": "current_bar_.high, current_bar_.low, current_bar_.close, current_bar_.volume",
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
# TA functions that receive implicit bar args APPENDED after the explicit ones.
|
|
172
|
+
TA_IMPLICIT_APPEND = {
|
|
173
|
+
"vwma": "current_bar_.volume",
|
|
174
|
+
"kc": "current_bar_.high, current_bar_.low, current_bar_.close",
|
|
175
|
+
"mfi": "current_bar_.volume",
|
|
176
|
+
"kcw": "current_bar_.high, current_bar_.low, current_bar_.close",
|
|
177
|
+
# ta.vwap needs the bar timestamp so the runtime can detect Daily
|
|
178
|
+
# anchor boundaries (Pine v6 default for `ta.vwap(source)` resets
|
|
179
|
+
# the cumulator at every UTC-day change).
|
|
180
|
+
"vwap": "current_bar_.volume, current_bar_.timestamp",
|
|
181
|
+
# 3-arg bands form uses the same implicit append as the scalar form.
|
|
182
|
+
"vwap_bands": "current_bar_.volume, current_bar_.timestamp",
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
# Tuple field names for TA functions returning tuples.
|
|
186
|
+
TA_TUPLE_FIELDS = {
|
|
187
|
+
"macd": ["macd_line", "signal_line", "histogram"],
|
|
188
|
+
"bb": ["middle", "upper", "lower"],
|
|
189
|
+
"kc": ["middle", "upper", "lower"],
|
|
190
|
+
"supertrend": ["value", "direction"],
|
|
191
|
+
"dmi": ["diplus", "diminus", "adx"],
|
|
192
|
+
# ta.vwap 3-arg bands form → VWAPBandsResult {vwap, upper, lower}
|
|
193
|
+
"vwap_bands": ["vwap", "upper", "lower"],
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# ---------------------------------------------------------------------------
|
|
198
|
+
# Type / namespace tables
|
|
199
|
+
# ---------------------------------------------------------------------------
|
|
200
|
+
|
|
201
|
+
# Pine builtins that return int64_t (engine ``pine_time`` etc.). When a Pine
|
|
202
|
+
# ``int`` variable is initialised from one of these, the symbol storage type
|
|
203
|
+
# must be promoted to ``int64_t`` so the ``na`` sentinel (``INT64_MIN``)
|
|
204
|
+
# survives — narrowing to 32-bit ``int`` collapses it to ``0`` and breaks
|
|
205
|
+
# ``is_na<int>`` detection.
|
|
206
|
+
INT64_BUILTINS = {"time", "time_close", "timestamp", "time_tradingday"}
|
|
207
|
+
|
|
208
|
+
PINE_TYPE_TO_CPP = {
|
|
209
|
+
"int": "int", "float": "double", "bool": "bool", "string": "std::string",
|
|
210
|
+
PineType.INT: "int", PineType.FLOAT: "double", PineType.BOOL: "bool",
|
|
211
|
+
PineType.STRING: "std::string", PineType.NA: "double",
|
|
212
|
+
PineType.UNKNOWN: "double", PineType.VOID: "double",
|
|
213
|
+
PineType.COLOR: "int",
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
SKIP_FUNC_NAMES = {
|
|
217
|
+
"plot", "plotshape", "plotchar", "plotcandle", "plotbar", "plotarrow",
|
|
218
|
+
"fill", "hline", "barcolor", "bgcolor", "alert", "alertcondition",
|
|
219
|
+
}
|
|
220
|
+
SKIP_NAMESPACES = {
|
|
221
|
+
"table", "label", "line", "box", "polyline", "chart",
|
|
222
|
+
"linefill", "display", "size", "position",
|
|
223
|
+
}
|
|
224
|
+
SKIP_VAR_TYPES = {"table"}
|
|
225
|
+
|
|
226
|
+
# ``syminfo.*`` -> runtime member access on the ``syminfo_`` struct.
|
|
227
|
+
SYMINFO_MEMBER_MAP = {
|
|
228
|
+
"mintick": "syminfo_.mintick",
|
|
229
|
+
"pointvalue": "syminfo_.pointvalue",
|
|
230
|
+
"ticker": "syminfo_.ticker",
|
|
231
|
+
"tickerid": "syminfo_.tickerid",
|
|
232
|
+
"currency": "syminfo_.currency",
|
|
233
|
+
"basecurrency": "syminfo_.basecurrency",
|
|
234
|
+
"type": "syminfo_.type",
|
|
235
|
+
"timezone": "syminfo_.timezone",
|
|
236
|
+
"session": "syminfo_.session",
|
|
237
|
+
"volumetype": "syminfo_.volumetype",
|
|
238
|
+
"description": "syminfo_.description",
|
|
239
|
+
# --- Critical fix: these 4 are NOT in the SymInfo struct; emit na<T>() ---
|
|
240
|
+
"prefix": "_pf_derive_prefix(syminfo_.tickerid)",
|
|
241
|
+
"root": 'na<std::string>()',
|
|
242
|
+
"pricescale": 'na<double>()',
|
|
243
|
+
"minmove": 'na<double>()',
|
|
244
|
+
# --- External-data fields: na-accept so scripts compile ---
|
|
245
|
+
"mincontract": 'na<double>()',
|
|
246
|
+
"current_contract": 'na<std::string>()',
|
|
247
|
+
"expiration_date": 'na<int64_t>()',
|
|
248
|
+
"isin": 'na<std::string>()',
|
|
249
|
+
"sector": 'na<std::string>()',
|
|
250
|
+
"industry": 'na<std::string>()',
|
|
251
|
+
# --- Financial/fundamental data: have no OHLCV source; route to the
|
|
252
|
+
# runtime metadata map (strategy_set_syminfo_metadata), which returns
|
|
253
|
+
# na<double>() until a feed injects a value (#19). ---
|
|
254
|
+
"employees": 'get_syminfo_metadata("employees")',
|
|
255
|
+
"shareholders": 'get_syminfo_metadata("shareholders")',
|
|
256
|
+
"shares_outstanding_float": 'get_syminfo_metadata("shares_outstanding_float")',
|
|
257
|
+
"shares_outstanding_total": 'get_syminfo_metadata("shares_outstanding_total")',
|
|
258
|
+
# recommendations_*
|
|
259
|
+
"recommendations_buy": 'get_syminfo_metadata("recommendations_buy")',
|
|
260
|
+
"recommendations_buy_strong": 'get_syminfo_metadata("recommendations_buy_strong")',
|
|
261
|
+
"recommendations_hold": 'get_syminfo_metadata("recommendations_hold")',
|
|
262
|
+
"recommendations_sell": 'get_syminfo_metadata("recommendations_sell")',
|
|
263
|
+
"recommendations_sell_strong": 'get_syminfo_metadata("recommendations_sell_strong")',
|
|
264
|
+
"recommendations_total": 'get_syminfo_metadata("recommendations_total")',
|
|
265
|
+
"recommendations_date": 'get_syminfo_metadata("recommendations_date")',
|
|
266
|
+
# target_price_*
|
|
267
|
+
"target_price_average": 'get_syminfo_metadata("target_price_average")',
|
|
268
|
+
"target_price_high": 'get_syminfo_metadata("target_price_high")',
|
|
269
|
+
"target_price_low": 'get_syminfo_metadata("target_price_low")',
|
|
270
|
+
"target_price_median": 'get_syminfo_metadata("target_price_median")',
|
|
271
|
+
"target_price_date": 'get_syminfo_metadata("target_price_date")',
|
|
272
|
+
"target_price_estimates": 'get_syminfo_metadata("target_price_estimates")',
|
|
273
|
+
# --- Syminfo derivation helpers ---
|
|
274
|
+
"main_tickerid": "_pf_derive_main_tickerid(syminfo_.tickerid)",
|
|
275
|
+
"country": "_pf_derive_country(syminfo_.tickerid)",
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
COLOR_CONST_MAP = {
|
|
279
|
+
"red": "pine_color::red", "green": "pine_color::green",
|
|
280
|
+
"blue": "pine_color::blue", "white": "pine_color::white",
|
|
281
|
+
"black": "pine_color::black", "yellow": "pine_color::yellow",
|
|
282
|
+
"orange": "pine_color::orange", "purple": "pine_color::purple",
|
|
283
|
+
"aqua": "pine_color::aqua", "gray": "pine_color::gray",
|
|
284
|
+
"lime": "pine_color::lime", "maroon": "pine_color::maroon",
|
|
285
|
+
"navy": "pine_color::navy", "olive": "pine_color::olive",
|
|
286
|
+
"silver": "pine_color::silver", "teal": "pine_color::teal",
|
|
287
|
+
"fuchsia": "pine_color::fuchsia",
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
# dayofweek.* constants — Pine uses 1=Sunday .. 7=Saturday. Unknown member
|
|
291
|
+
# emits "0" (see _visit_member_access dayofweek arm).
|
|
292
|
+
DAYOFWEEK_MAP = {
|
|
293
|
+
"sunday": "1", "monday": "2", "tuesday": "3", "wednesday": "4",
|
|
294
|
+
"thursday": "5", "friday": "6", "saturday": "7",
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
# backadjustment.* and settlement_as_close.* share the same on/off/inherit
|
|
298
|
+
# encoding. Emitted as integer constants (the engine ignores them; codegen
|
|
299
|
+
# drops them from request.security kwargs). Unknown member falls back to
|
|
300
|
+
# "inherit" (2) — see the backadjustment/settlement_as_close arms in
|
|
301
|
+
# _visit_member_access.
|
|
302
|
+
ON_OFF_INHERIT_MAP = {"on": "1", "off": "0", "inherit": "2"}
|
|
303
|
+
|
|
304
|
+
# adjustment.* constants — none/dividends/splits. Emitted as integer constants
|
|
305
|
+
# (engine ignores them; codegen drops them from request.security kwargs).
|
|
306
|
+
# Unknown member falls back to "none" (0).
|
|
307
|
+
ADJUSTMENT_MAP = {"none": "0", "dividends": "1", "splits": "2"}
|
|
308
|
+
|
|
309
|
+
# display.* (plot_display) constants — ints for C++. TV uses these in chart
|
|
310
|
+
# settings; the backtest ignores them. Unknown member falls back to "all" (0).
|
|
311
|
+
DISPLAY_MAP = {
|
|
312
|
+
"all": "0", "none": "1", "pane": "2",
|
|
313
|
+
"data_window": "3", "status_line": "4", "price_scale": "5",
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
# order.* sort-direction constants — emitted as std::string literals. Unknown
|
|
317
|
+
# member falls back to "ascending".
|
|
318
|
+
ORDER_DIRECTION_MAP = {
|
|
319
|
+
"ascending": 'std::string("ascending")',
|
|
320
|
+
"descending": 'std::string("descending")',
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
# ---------------------------------------------------------------------------
|
|
325
|
+
# Array / Map / Matrix method dispatch
|
|
326
|
+
# ---------------------------------------------------------------------------
|
|
327
|
+
|
|
328
|
+
# Methods called as ``array.method(arr, ...)`` or ``arr.method(...)``.
|
|
329
|
+
ARRAY_METHODS = {
|
|
330
|
+
"get": lambda a, args: f"{a}[{args[0]}]",
|
|
331
|
+
"set": lambda a, args: f"{a}[{args[0]}] = {args[1]}",
|
|
332
|
+
"push": lambda a, args: f"{a}.push_back({args[0]})",
|
|
333
|
+
"unshift": lambda a, args: f"{a}.insert({a}.begin(), {args[0]})",
|
|
334
|
+
"insert": lambda a, args: f"{a}.insert({a}.begin() + (int)({args[0]}), {args[1]})",
|
|
335
|
+
"pop": lambda a, args: f"[&](){{ auto v={a}.back(); {a}.pop_back(); return v; }}()",
|
|
336
|
+
"shift": lambda a, args: f"[&](){{ auto v={a}.front(); {a}.erase({a}.begin()); return v; }}()",
|
|
337
|
+
"remove": lambda a, args: f"[&](){{ auto v={a}[{args[0]}]; {a}.erase({a}.begin()+(int)({args[0]})); return v; }}()",
|
|
338
|
+
"first": lambda a, args: f"{a}.front()",
|
|
339
|
+
"last": lambda a, args: f"{a}.back()",
|
|
340
|
+
"size": lambda a, args: f"(double){a}.size()",
|
|
341
|
+
"clear": lambda a, args: f"{a}.clear()",
|
|
342
|
+
"fill": lambda a, args: f"std::fill({a}.begin(), {a}.end(), {args[0]})" if len(args) == 1
|
|
343
|
+
else f"std::fill({a}.begin()+(int)({args[1]}), {a}.begin()+(int)({args[2]}), {args[0]})",
|
|
344
|
+
"includes": lambda a, args: f"(std::find({a}.begin(), {a}.end(), {args[0]}) != {a}.end())",
|
|
345
|
+
"indexof": lambda a, args: f"[&](){{ auto it=std::find({a}.begin(),{a}.end(),{args[0]}); return it!={a}.end()?(double)(it-{a}.begin()):-1.0; }}()",
|
|
346
|
+
"lastindexof": lambda a, args: f"[&](){{ for(int i=(int){a}.size()-1;i>=0;i--)if({a}[i]=={args[0]})return(double)i; return -1.0; }}()",
|
|
347
|
+
"sort": lambda a, args: (
|
|
348
|
+
f"[&](){{ if (({args[0]}) == \"descending\") std::sort({a}.begin(), {a}.end(), std::greater<>()); else std::sort({a}.begin(), {a}.end()); }}()"
|
|
349
|
+
if args else f"std::sort({a}.begin(), {a}.end())"
|
|
350
|
+
),
|
|
351
|
+
"reverse": lambda a, args: f"std::reverse({a}.begin(),{a}.end())",
|
|
352
|
+
"copy": lambda a, args: f"std::vector<double>({a})",
|
|
353
|
+
"slice": lambda a, args: f"std::vector<double>({a}.begin()+(int)({args[0]}),{a}.begin()+(int)({args[1]}))",
|
|
354
|
+
"concat": lambda a, args: f"{a}.insert({a}.end(),{args[0]}.begin(),{args[0]}.end())",
|
|
355
|
+
"sum": lambda a, args: f"std::accumulate({a}.begin(),{a}.end(),0.0)",
|
|
356
|
+
"avg": lambda a, args: f"({a}.empty()?0.0:std::accumulate({a}.begin(),{a}.end(),0.0)/{a}.size())",
|
|
357
|
+
"min": lambda a, args: f"*std::min_element({a}.begin(),{a}.end())",
|
|
358
|
+
"max": lambda a, args: f"*std::max_element({a}.begin(),{a}.end())",
|
|
359
|
+
"range": lambda a, args: f"(*std::max_element({a}.begin(),{a}.end())-*std::min_element({a}.begin(),{a}.end()))",
|
|
360
|
+
"every": lambda a, args: f"std::all_of({a}.begin(),{a}.end(),[](double v){{return v!=0.0;}})",
|
|
361
|
+
"some": lambda a, args: f"std::any_of({a}.begin(),{a}.end(),[](double v){{return v!=0.0;}})",
|
|
362
|
+
"stdev": lambda a, args: f"[&](){{ double m=std::accumulate({a}.begin(),{a}.end(),0.0)/{a}.size(); double s=0; for(auto v:{a})s+=(v-m)*(v-m); return std::sqrt(s/{a}.size()); }}()",
|
|
363
|
+
"variance": lambda a, args: f"[&](){{ double m=std::accumulate({a}.begin(),{a}.end(),0.0)/{a}.size(); double s=0; for(auto v:{a})s+=(v-m)*(v-m); return s/{a}.size(); }}()",
|
|
364
|
+
"median": lambda a, args: f"[&](){{ auto c={a}; std::sort(c.begin(),c.end()); int n=c.size(); return n%2?c[n/2]:(c[n/2-1]+c[n/2])/2.0; }}()",
|
|
365
|
+
"mode": lambda a, args: f"[&](){{ std::unordered_map<double,int> m; for(auto v:{a})m[v]++; double best=0; int bc=0; for(auto&[v,c]:m)if(c>bc||(c==bc&&v<best)){{bc=c;best=v;}} return best; }}()",
|
|
366
|
+
"percentile_linear_interpolation": lambda a, args: f"[&](){{ auto c={a}; std::sort(c.begin(),c.end()); double k=({args[0]}/100.0)*c.size()-0.5; int i=std::max(0,(int)k); double f=k-i; if(i+1>=(int)c.size()) return c.back(); return c[i]*(1-f)+c[i+1]*f; }}()",
|
|
367
|
+
"percentile_nearest_rank": lambda a, args: f"[&](){{ auto c={a}; std::sort(c.begin(),c.end()); int r=(int)std::ceil(({args[0]}/100.0)*c.size()); return c[std::min(r-1,(int)c.size()-1)]; }}()",
|
|
368
|
+
"percentrank": lambda a, args: f"[&](){{ if({a}.size()<=1) return na<double>(); double v={a}[{args[0]}]; if(std::isnan(v)) return na<double>(); int le=0; for(auto x:{a}) if(!std::isnan(x) && x<=v) le++; return (double)(le-1)/({a}.size()-1)*100.0; }}()",
|
|
369
|
+
"abs": lambda a, args: f"[&](){{ std::vector<double> r; for(auto v:{a})r.push_back(std::abs(v)); return r; }}()",
|
|
370
|
+
"join": lambda a, args: "[&](){{ std::string r; for(size_t i=0;i<{arr}.size();i++){{ if(i>0)r+={sep}; r+=std::to_string({arr}[i]); }} return r; }}()".format(arr=a, sep=args[0] if args else 'std::string(",")'),
|
|
371
|
+
"standardize": lambda a, args: f"[&](){{ double m=std::accumulate({a}.begin(),{a}.end(),0.0)/{a}.size(); double s=0; for(auto v:{a})s+=(v-m)*(v-m); s=std::sqrt(s/{a}.size()); std::vector<double> r; for(auto v:{a})r.push_back(s==0?1.0:(v-m)/s); return r; }}()",
|
|
372
|
+
"covariance": lambda a, args: f"[&](){{ auto&b={args[0]}; int n=std::min({a}.size(),b.size()); double ma=0,mb=0; for(int i=0;i<n;i++){{ma+={a}[i];mb+=b[i];}} ma/=n;mb/=n; double c=0; for(int i=0;i<n;i++)c+=({a}[i]-ma)*(b[i]-mb); return c/n; }}()",
|
|
373
|
+
"binary_search": lambda a, args: f"[&](){{ auto it=std::lower_bound({a}.begin(),{a}.end(),{args[0]}); return (it!={a}.end()&&*it=={args[0]})?(double)(it-{a}.begin()):-1.0; }}()",
|
|
374
|
+
"binary_search_leftmost": lambda a, args: f"[&](){{ auto it=std::lower_bound({a}.begin(),{a}.end(),{args[0]}); return (it!={a}.end()&&*it=={args[0]})?(double)(it-{a}.begin()):(double)(it-{a}.begin()-1); }}()",
|
|
375
|
+
"binary_search_rightmost": lambda a, args: f"[&](){{ auto it=std::upper_bound({a}.begin(),{a}.end(),{args[0]}); return (it!={a}.begin()&&*(it-1)=={args[0]})?(double)(it-{a}.begin()-1):(double)(it-{a}.begin()); }}()",
|
|
376
|
+
"sort_indices": lambda a, args: f"[&](){{ std::vector<double> idx({a}.size()); std::iota(idx.begin(),idx.end(),0); std::sort(idx.begin(),idx.end(),[&](int i,int j){{return {a}[i]<{a}[j];}}); return idx; }}()",
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
MAP_METHODS = {
|
|
380
|
+
"put": lambda m, args: f"({m}[{args[0]}] = {args[1]})",
|
|
381
|
+
"get": lambda m, args: f"({m}.count({args[0]}) ? {m}[{args[0]}] : na<double>())",
|
|
382
|
+
"remove": lambda m, args: f"[&](){{ auto it={m}.find({args[0]}); if(it!={m}.end()){{ auto v=it->second; {m}.erase(it); return v; }} return na<double>(); }}()",
|
|
383
|
+
"contains": lambda m, args: f"({m}.count({args[0]}) > 0)",
|
|
384
|
+
"size": lambda m, args: f"(double){m}.size()",
|
|
385
|
+
"clear": lambda m, args: f"{m}.clear()",
|
|
386
|
+
"keys": lambda m, args: f"[&](){{ std::vector<std::string> v; for(auto& p:{m}) v.push_back(p.first); return v; }}()",
|
|
387
|
+
"values": lambda m, args: f"[&](){{ std::vector<double> v; for(auto& p:{m}) v.push_back(p.second); return v; }}()",
|
|
388
|
+
"copy": lambda m, args: f"std::unordered_map<std::string,double>({m})",
|
|
389
|
+
"put_all": lambda m, args: f"{m}.insert({args[0]}.begin(), {args[0]}.end())",
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def _matrix_add_row(m: str, args: list) -> str:
|
|
394
|
+
"""Pine ``matrix.add_row`` codegen.
|
|
395
|
+
|
|
396
|
+
Pine signature accepts (id, row, [array_id]) — when ``row`` is omitted
|
|
397
|
+
we append at the current row count. Two-arg form passes through as
|
|
398
|
+
``add_row(row_index, array_id)``."""
|
|
399
|
+
if len(args) == 1:
|
|
400
|
+
return f"{m}.add_row((int)({m}.rows()), {args[0]})"
|
|
401
|
+
if len(args) == 2:
|
|
402
|
+
return f"{m}.add_row((int)({args[0]}), {args[1]})"
|
|
403
|
+
raise IndexError("matrix.add_row")
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def _matrix_add_col(m: str, args: list) -> str:
|
|
407
|
+
"""Pine ``matrix.add_col`` codegen — mirror of ``_matrix_add_row``."""
|
|
408
|
+
if len(args) == 1:
|
|
409
|
+
return f"{m}.add_col((int)({m}.columns()), {args[0]})"
|
|
410
|
+
if len(args) == 2:
|
|
411
|
+
return f"{m}.add_col((int)({args[0]}), {args[1]})"
|
|
412
|
+
raise IndexError("matrix.add_col")
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
# Keyword parameter order for matrix methods (Pine v6); used by ``_merge_kwargs``.
|
|
416
|
+
MATRIX_METHOD_KWARGS: dict[str, list[str]] = {
|
|
417
|
+
"add_row": ["row_index", "array_id"],
|
|
418
|
+
"add_col": ["col_index", "array_id"],
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
# matrix.* method names whose RUNTIME return type is ``PineMatrix``. Used by
|
|
422
|
+
# the codegen aggregate-type registration to declare the LHS variable as
|
|
423
|
+
# ``PineMatrix`` instead of the analyzer's default ``double``. Without this
|
|
424
|
+
# the codegen emits ``double inv = m.inv();`` which clang rejects with
|
|
425
|
+
# ``assigning to 'double' from incompatible type 'PineMatrix'``.
|
|
426
|
+
#
|
|
427
|
+
# Methods absent from this set return either a primitive (``det``, ``rank``,
|
|
428
|
+
# ``trace``, ``sum``, ``avg``, ``min``, ``max``, ``mode``, ``elements_count``,
|
|
429
|
+
# ``rows``, ``columns``, ``is_*``) or an array (``row``, ``col``,
|
|
430
|
+
# ``eigenvalues``); their LHS variables stay scalar / vector and the analyzer
|
|
431
|
+
# default is correct.
|
|
432
|
+
MATRIX_RETURNING_METHODS: frozenset[str] = frozenset({
|
|
433
|
+
"copy", "submatrix", "transpose", "concat", "diff", "mult", "pow",
|
|
434
|
+
"inv", "pinv", "eigenvectors", "kron",
|
|
435
|
+
})
|
|
436
|
+
|
|
437
|
+
# Methods only valid on matrix<float>. Codegen rejects these on non-float
|
|
438
|
+
# matrix receivers; the runtime template doesn't carry them at all.
|
|
439
|
+
MATRIX_NUMERIC_ONLY: frozenset[str] = frozenset({
|
|
440
|
+
"det", "inv", "pinv", "rank", "trace",
|
|
441
|
+
"eigenvalues", "eigenvectors",
|
|
442
|
+
"sum", "avg", "min", "max", "mode",
|
|
443
|
+
"diff", "mult", "pow", "kron",
|
|
444
|
+
"is_square", "is_identity", "is_diagonal", "is_antidiagonal",
|
|
445
|
+
"is_symmetric", "is_antisymmetric", "is_triangular",
|
|
446
|
+
"is_stochastic", "is_binary", "is_zero",
|
|
447
|
+
})
|
|
448
|
+
|
|
449
|
+
# Element-type predicate for matrix.sort: only int/bool/string element types
|
|
450
|
+
# can be sorted on PineGenericMatrix. Float matrix sort routes through
|
|
451
|
+
# PineMatrix (numeric path).
|
|
452
|
+
MATRIX_SORT_ALLOWED_GENERIC_ELEMS: frozenset[str] = frozenset({"int", "bool", "string"})
|
|
453
|
+
|
|
454
|
+
MATRIX_METHODS = {
|
|
455
|
+
"get": lambda m, args: f"{m}.get((int)({args[0]}), (int)({args[1]}))",
|
|
456
|
+
"set": lambda m, args: f"{m}.set((int)({args[0]}), (int)({args[1]}), {args[2]})",
|
|
457
|
+
"fill": lambda m, args: f"{m}.fill({args[0]})",
|
|
458
|
+
"row": lambda m, args: f"{m}.row((int)({args[0]}))",
|
|
459
|
+
"col": lambda m, args: f"{m}.col((int)({args[0]}))",
|
|
460
|
+
"rows": lambda m, args: f"(int){m}.rows()",
|
|
461
|
+
"columns": lambda m, args: f"(int){m}.columns()",
|
|
462
|
+
"add_row": _matrix_add_row,
|
|
463
|
+
"add_col": _matrix_add_col,
|
|
464
|
+
# ``remove_row`` is void in C++; Pine may assign the result, so we wrap
|
|
465
|
+
# it in a lambda that returns a sentinel double after the side effect.
|
|
466
|
+
"remove_row": lambda m, args: f"[&](){{ {m}.remove_row((int)({args[0]})); return 0.0; }}()",
|
|
467
|
+
"remove_col":lambda m, args: f"[&](){{ {m}.remove_col((int)({args[0]})); return 0.0; }}()",
|
|
468
|
+
"swap_rows": lambda m, args: f"{m}.swap_rows((int)({args[0]}), (int)({args[1]}))",
|
|
469
|
+
"swap_columns": lambda m, args: f"{m}.swap_columns((int)({args[0]}), (int)({args[1]}))",
|
|
470
|
+
"copy": lambda m, args: f"{m}.copy()",
|
|
471
|
+
"submatrix": lambda m, args: f"{m}.submatrix((int)({args[0]}), (int)({args[1]}), (int)({args[2]}), (int)({args[3]}))",
|
|
472
|
+
"reshape": lambda m, args: f"{m}.reshape((int)({args[0]}), (int)({args[1]}))",
|
|
473
|
+
"reverse": lambda m, args: f"{m}.reverse()",
|
|
474
|
+
"transpose": lambda m, args: f"{m}.transpose()",
|
|
475
|
+
"sort": lambda m, args: f"{m}.sort((int)({args[0]}), {args[1]} != \"descending\")" if len(args)>1 else f"{m}.sort((int)({args[0]}))",
|
|
476
|
+
"concat": lambda m, args: f"{m}.concat({args[0]}, (bool)({args[1]}))" if len(args)>1 else f"{m}.concat({args[0]}, true)",
|
|
477
|
+
"avg": lambda m, args: f"{m}.avg()",
|
|
478
|
+
"min": lambda m, args: f"{m}.min()",
|
|
479
|
+
"max": lambda m, args: f"{m}.max()",
|
|
480
|
+
"mode": lambda m, args: f"{m}.mode()",
|
|
481
|
+
"sum": lambda m, args: f"{m}.sum()",
|
|
482
|
+
"diff": lambda m, args: f"{m}.diff({args[0]})",
|
|
483
|
+
"mult": lambda m, args: f"{m}.mult({args[0]})",
|
|
484
|
+
"pow": lambda m, args: f"{m}.pow((int)({args[0]}))",
|
|
485
|
+
"det": lambda m, args: f"{m}.det()",
|
|
486
|
+
"inv": lambda m, args: f"{m}.inv()",
|
|
487
|
+
"pinv": lambda m, args: f"{m}.pinv()",
|
|
488
|
+
"rank": lambda m, args: f"(int){m}.rank()",
|
|
489
|
+
"trace": lambda m, args: f"{m}.trace()",
|
|
490
|
+
"eigenvalues": lambda m, args: f"{m}.eigenvalues()",
|
|
491
|
+
"eigenvectors": lambda m, args: f"{m}.eigenvectors()",
|
|
492
|
+
"kron": lambda m, args: f"{m}.kron({args[0]})",
|
|
493
|
+
"elements_count": lambda m, args: f"(int){m}.elements_count()",
|
|
494
|
+
"is_square": lambda m, args: f"{m}.is_square()",
|
|
495
|
+
"is_identity": lambda m, args: f"{m}.is_identity()",
|
|
496
|
+
"is_diagonal": lambda m, args: f"{m}.is_diagonal()",
|
|
497
|
+
"is_antidiagonal": lambda m, args: f"{m}.is_antidiagonal()",
|
|
498
|
+
"is_symmetric": lambda m, args: f"{m}.is_symmetric()",
|
|
499
|
+
"is_antisymmetric": lambda m, args: f"{m}.is_antisymmetric()",
|
|
500
|
+
"is_triangular": lambda m, args: f"{m}.is_triangular()",
|
|
501
|
+
"is_stochastic": lambda m, args: f"{m}.is_stochastic()",
|
|
502
|
+
"is_binary": lambda m, args: f"{m}.is_binary()",
|
|
503
|
+
"is_zero": lambda m, args: f"{m}.is_zero()",
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
# ---------------------------------------------------------------------------
|
|
508
|
+
# Math / String dispatch
|
|
509
|
+
# ---------------------------------------------------------------------------
|
|
510
|
+
|
|
511
|
+
MATH_FUNC_MAP = {
|
|
512
|
+
"abs": "std::abs", "max": "std::max", "min": "std::min",
|
|
513
|
+
"ceil": "std::ceil", "floor": "std::floor", "round": "std::round",
|
|
514
|
+
"sqrt": "std::sqrt", "pow": "std::pow", "log": "std::log",
|
|
515
|
+
"log10": "std::log10", "exp": "std::exp",
|
|
516
|
+
"sin": "std::sin", "cos": "std::cos", "tan": "std::tan",
|
|
517
|
+
"asin": "std::asin", "acos": "std::acos", "atan": "std::atan",
|
|
518
|
+
"atan2": "std::atan2({0}, {1})",
|
|
519
|
+
"sign": "((({0}) > 0) - (({0}) < 0))",
|
|
520
|
+
"avg": "(({0} + {1}) / 2.0)",
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
STR_FUNC_MAP = {
|
|
524
|
+
"tostring": None, # handled separately (already works)
|
|
525
|
+
"tonumber": lambda args: (
|
|
526
|
+
f"[&](){{ "
|
|
527
|
+
f"try {{ return std::stod({args[0]}); }} "
|
|
528
|
+
f"catch (...) {{ return na<double>(); }} "
|
|
529
|
+
f"}}()"
|
|
530
|
+
),
|
|
531
|
+
"length": lambda args: f"(int){args[0]}.length()",
|
|
532
|
+
"contains": lambda args: f"({args[0]}.find({args[1]}) != std::string::npos)",
|
|
533
|
+
"startswith": lambda args: f"({args[0]}.substr(0, {args[1]}.length()) == {args[1]})",
|
|
534
|
+
"endswith": lambda args: f"({args[0]}.length() >= {args[1]}.length() && {args[0]}.compare({args[0]}.length() - {args[1]}.length(), {args[1]}.length(), {args[1]}) == 0)",
|
|
535
|
+
"pos": lambda args: f"[&](){{ auto p={args[0]}.find({args[1]}); return p!=std::string::npos?(int)p:-1; }}()",
|
|
536
|
+
"substring": None, # handled separately (2 vs 3 args)
|
|
537
|
+
"replace_all": lambda args: f"[&](){{ std::string s={args[0]}; size_t p=0; while((p=s.find({args[1]},p))!=std::string::npos){{ s.replace(p,{args[1]}.length(),{args[2]}); p+={args[2]}.length(); }} return s; }}()",
|
|
538
|
+
"replace": None, # handled separately (3 vs 4 args)
|
|
539
|
+
"lower": lambda args: f"[&](){{ std::string s={args[0]}; std::transform(s.begin(),s.end(),s.begin(),::tolower); return s; }}()",
|
|
540
|
+
"upper": lambda args: f"[&](){{ std::string s={args[0]}; std::transform(s.begin(),s.end(),s.begin(),::toupper); return s; }}()",
|
|
541
|
+
"trim": lambda args: f'[&](){{ std::string s={args[0]}; s.erase(0,s.find_first_not_of(" \\t\\n\\r")); s.erase(s.find_last_not_of(" \\t\\n\\r")+1); return s; }}()',
|
|
542
|
+
"repeat": lambda args: f"[&](){{ std::string r; for(int i=0;i<(int)({args[1]});i++) r+={args[0]}; return r; }}()",
|
|
543
|
+
"match": lambda args: f'pine_str_match({args[0]}, {args[1]})',
|
|
544
|
+
"split": lambda args: f'pine_str_split({args[0]}, {args[1]})',
|
|
545
|
+
"format": None, # handled separately
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
# ---------------------------------------------------------------------------
|
|
550
|
+
# kwarg merge helper (used by visitor / dispatch sites that accept kwargs)
|
|
551
|
+
# ---------------------------------------------------------------------------
|
|
552
|
+
|
|
553
|
+
def _merge_kwargs(args: list, kwargs: dict, param_names: list | None, visit_expr) -> list:
|
|
554
|
+
"""Merge positional + kwargs into a unified positional list of C++ exprs.
|
|
555
|
+
|
|
556
|
+
``param_names`` is the declared signature order from
|
|
557
|
+
``signatures.py``. ``visit_expr`` is a callable applied to each kept
|
|
558
|
+
AST node; pass ``lambda x: x`` when the caller wants raw nodes back."""
|
|
559
|
+
if not kwargs or not param_names:
|
|
560
|
+
return [visit_expr(a) for a in args]
|
|
561
|
+
merged = list(args)
|
|
562
|
+
for i, pname in enumerate(param_names):
|
|
563
|
+
if pname in kwargs:
|
|
564
|
+
while len(merged) <= i:
|
|
565
|
+
merged.append(None)
|
|
566
|
+
if merged[i] is None:
|
|
567
|
+
merged[i] = kwargs[pname]
|
|
568
|
+
while merged and merged[-1] is None:
|
|
569
|
+
merged.pop()
|
|
570
|
+
return [visit_expr(a) for a in merged if a is not None]
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
def _merge_kwargs_with_defaults(
|
|
574
|
+
args: list,
|
|
575
|
+
kwargs: dict,
|
|
576
|
+
param_names: list,
|
|
577
|
+
param_defaults: list,
|
|
578
|
+
visit_expr,
|
|
579
|
+
) -> list:
|
|
580
|
+
"""Like ``_merge_kwargs`` but fills missing slots from ``param_defaults``.
|
|
581
|
+
|
|
582
|
+
``param_defaults`` must be parallel to ``param_names`` (use ``None`` for
|
|
583
|
+
parameters without a default). Used by the UDT-method call lowering so
|
|
584
|
+
callers may invoke ``cfg.threshold()``, ``cfg.threshold(atrVal)``, or
|
|
585
|
+
``cfg.threshold(mult=2.0, base=rsiVal)`` against ``method threshold(Cfg
|
|
586
|
+
self, float mult = 1.0, float base = 0.0) =>`` and have clang see the
|
|
587
|
+
full positional argument list. PineScript has no overloading, so every
|
|
588
|
+
parameter must be filled at the call site.
|
|
589
|
+
|
|
590
|
+
Probe: data/validation/udt-method-probe-04-default-param.
|
|
591
|
+
|
|
592
|
+
The result preserves the parameter order from ``param_names`` and
|
|
593
|
+
contains ``visit_expr(node)`` for each filled slot. Trailing slots that
|
|
594
|
+
have neither a caller-supplied value nor a default are dropped (matches
|
|
595
|
+
``_merge_kwargs`` behaviour for required-only signatures).
|
|
596
|
+
"""
|
|
597
|
+
n = len(param_names)
|
|
598
|
+
slots: list = [None] * n
|
|
599
|
+
for i, a in enumerate(args):
|
|
600
|
+
if i < n:
|
|
601
|
+
slots[i] = a
|
|
602
|
+
for k, v in kwargs.items():
|
|
603
|
+
if k in param_names:
|
|
604
|
+
slots[param_names.index(k)] = v
|
|
605
|
+
# Fill remaining holes from defaults (only where the caller omitted the
|
|
606
|
+
# slot AND the parameter has a declared default).
|
|
607
|
+
if param_defaults:
|
|
608
|
+
for i in range(n):
|
|
609
|
+
if slots[i] is None and i < len(param_defaults) and param_defaults[i] is not None:
|
|
610
|
+
slots[i] = param_defaults[i]
|
|
611
|
+
while slots and slots[-1] is None:
|
|
612
|
+
slots.pop()
|
|
613
|
+
return [visit_expr(a) for a in slots if a is not None]
|