@pineforge/codegen-pyodide 0.10.4 → 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 +754 -76
- package/pineforge_codegen/analyzer/call_handlers.py +260 -40
- package/pineforge_codegen/analyzer/contracts.py +37 -0
- package/pineforge_codegen/analyzer/diagnostics.py +30 -4
- package/pineforge_codegen/analyzer/tables.py +49 -8
- package/pineforge_codegen/analyzer/types.py +33 -1
- package/pineforge_codegen/ast_nodes.py +32 -1
- package/pineforge_codegen/block_locals.py +185 -0
- package/pineforge_codegen/builtin_keywords.py +42 -0
- package/pineforge_codegen/codegen/base.py +896 -156
- package/pineforge_codegen/codegen/constant_fold.py +131 -0
- package/pineforge_codegen/codegen/drawing.py +221 -79
- package/pineforge_codegen/codegen/emit_top.py +946 -213
- package/pineforge_codegen/codegen/helpers.py +435 -14
- package/pineforge_codegen/codegen/host_members.py +162 -0
- package/pineforge_codegen/codegen/input.py +252 -85
- package/pineforge_codegen/codegen/security.py +4372 -377
- package/pineforge_codegen/codegen/session_market.py +71 -0
- package/pineforge_codegen/codegen/ta.py +1188 -100
- package/pineforge_codegen/codegen/tables.py +193 -71
- package/pineforge_codegen/codegen/tv_number_format.py +270 -0
- package/pineforge_codegen/codegen/types.py +1882 -78
- package/pineforge_codegen/codegen/visit_call.py +920 -131
- package/pineforge_codegen/codegen/visit_expr.py +738 -57
- package/pineforge_codegen/codegen/visit_stmt.py +595 -49
- package/pineforge_codegen/external_requests.py +877 -0
- package/pineforge_codegen/lexer.py +104 -22
- package/pineforge_codegen/library_inline.py +1304 -0
- package/pineforge_codegen/library_modules.py +126 -0
- package/pineforge_codegen/library_v5.py +683 -0
- package/pineforge_codegen/limits.py +138 -0
- package/pineforge_codegen/method_binding.py +33 -0
- package/pineforge_codegen/parser.py +384 -68
- package/pineforge_codegen/pine_libraries.py +266 -0
- package/pineforge_codegen/pine_spelling.py +216 -0
- package/pineforge_codegen/pragmas.py +64 -10
- package/pineforge_codegen/security_contexts.py +1585 -0
- package/pineforge_codegen/session_reads.py +84 -0
- package/pineforge_codegen/signatures.py +48 -23
- package/pineforge_codegen/support_checker.py +1106 -85
- package/pineforge_codegen-1.0.0-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.4.tar.gz +0 -0
|
@@ -16,7 +16,33 @@ consumers should never reach for them directly.
|
|
|
16
16
|
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
19
|
+
import re
|
|
20
|
+
|
|
19
21
|
from ..symbols import PineType
|
|
22
|
+
from .helpers import (
|
|
23
|
+
evaluate_args_once, na_preserving_int_cast, pine_index_int_cast,
|
|
24
|
+
pine_truth_cast,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
_PROVEN_INT_ARG = re.compile(
|
|
29
|
+
r"^\s*(?:(?:[-+]?\d+)|get_input_int\(.*\)|pine_(?:bar_index|year|month|dayofmonth|dayofweek|hour|minute|second|weekofyear)\(.*\))\s*$"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _matrix_int_arg(value: str) -> str:
|
|
34
|
+
"""Keep old casts for typed integer matrix indices; guard other values."""
|
|
35
|
+
if _PROVEN_INT_ARG.fullmatch(value):
|
|
36
|
+
return f"(int)({value})"
|
|
37
|
+
return pine_index_int_cast(value)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _pine_bool_arg(value: str) -> str:
|
|
41
|
+
"""Keep literal bool spellings; guard numeric/dynamic bool arguments."""
|
|
42
|
+
stripped = value.strip()
|
|
43
|
+
if stripped in {"true", "false"}:
|
|
44
|
+
return value
|
|
45
|
+
return pine_truth_cast(value)
|
|
20
46
|
|
|
21
47
|
|
|
22
48
|
# ---------------------------------------------------------------------------
|
|
@@ -75,6 +101,15 @@ def tz_time_field_lambda(field_expr: str, ts_arg: str, tz_arg: str) -> str:
|
|
|
75
101
|
)
|
|
76
102
|
|
|
77
103
|
|
|
104
|
+
# ``alert.freq_*``: TradingView's ``const string`` values. A TradingView tape
|
|
105
|
+
# spells them as order comments: "all", "once_per_bar", "once_per_bar_close".
|
|
106
|
+
ALERT_FREQ_VALUES = {
|
|
107
|
+
"freq_all": "all",
|
|
108
|
+
"freq_once_per_bar": "once_per_bar",
|
|
109
|
+
"freq_once_per_bar_close": "once_per_bar_close",
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
78
113
|
BAR_BUILTINS = {
|
|
79
114
|
"bar_index": "pine_bar_index()",
|
|
80
115
|
"time": "current_bar_.timestamp",
|
|
@@ -113,6 +148,7 @@ BAR_SERIES_PUSH = {
|
|
|
113
148
|
"hl2": "((current_bar_.high + current_bar_.low) / 2.0)",
|
|
114
149
|
"hlc3": "((current_bar_.high + current_bar_.low + current_bar_.close) / 3.0)",
|
|
115
150
|
"ohlc4": "((current_bar_.open + current_bar_.high + current_bar_.low + current_bar_.close) / 4.0)",
|
|
151
|
+
"hlcc4": "((current_bar_.high + current_bar_.low + current_bar_.close + current_bar_.close) / 4.0)",
|
|
116
152
|
}
|
|
117
153
|
|
|
118
154
|
# Bar identifiers that refer to the *security* (HTF) bar inside
|
|
@@ -124,6 +160,12 @@ SECURITY_BAR_FIELD_EXPRS = {
|
|
|
124
160
|
"close": "bar.close",
|
|
125
161
|
"volume": "bar.volume",
|
|
126
162
|
"time": "bar.timestamp",
|
|
163
|
+
# The derived series, as the engine computes its source series of a bar;
|
|
164
|
+
# their history is the requested bar's, like ``close[1]``'s.
|
|
165
|
+
"hl2": "((bar.high + bar.low) / 2.0)",
|
|
166
|
+
"hlc3": "((bar.high + bar.low + bar.close) / 3.0)",
|
|
167
|
+
"ohlc4": "((bar.open + bar.high + bar.low + bar.close) / 4.0)",
|
|
168
|
+
"hlcc4": "((bar.high + bar.low + bar.close + bar.close) / 4.0)",
|
|
127
169
|
}
|
|
128
170
|
SECURITY_BAR_FIELD_TYPES = {
|
|
129
171
|
"open": "double",
|
|
@@ -132,8 +174,12 @@ SECURITY_BAR_FIELD_TYPES = {
|
|
|
132
174
|
"close": "double",
|
|
133
175
|
"volume": "double",
|
|
134
176
|
"time": "int64_t",
|
|
177
|
+
"time_close": "int64_t",
|
|
135
178
|
}
|
|
136
|
-
|
|
179
|
+
# ``time_close`` is the requested bar's close time, which depends on the
|
|
180
|
+
# requested timeframe: the evaluator renders it (security.py
|
|
181
|
+
# ``_security_bar_field_expr``) instead of a fixed ``bar.*`` read.
|
|
182
|
+
SECURITY_BAR_FIELDS = frozenset(SECURITY_BAR_FIELD_EXPRS) | {"time_close"}
|
|
137
183
|
|
|
138
184
|
# Backwards-compatible name for consumers that only need the OHLCV subset.
|
|
139
185
|
SECURITY_OHLC_BAR_FIELDS = frozenset({"open", "high", "low", "close", "volume"})
|
|
@@ -153,6 +199,24 @@ RUNTIME_REGISTER_SECURITY_EVAL_FN = "register_security_eval"
|
|
|
153
199
|
# higher-or-equal TF requests with a precise diagnostic.
|
|
154
200
|
RUNTIME_REGISTER_SECURITY_LOWER_TF_EVAL_FN = "register_security_lower_tf_eval"
|
|
155
201
|
|
|
202
|
+
# TradingView's Pine v6 ``strategy()`` defaults for the three parameters whose
|
|
203
|
+
# default it changed on 2026-09-24: an omitted ``initial_capital`` is 100000,
|
|
204
|
+
# an omitted ``default_qty_type`` is ``strategy.percent_of_equity``, and an
|
|
205
|
+
# omitted ``default_qty_value`` is 100 whatever the quantity type (100
|
|
206
|
+
# contracts under ``strategy.fixed``, 100 of the account currency under
|
|
207
|
+
# ``strategy.cash``). Pine v5 kept 1000000 / ``strategy.fixed`` / 1, which are
|
|
208
|
+
# still the source host's own ``PineStrategyConfig`` defaults, so the
|
|
209
|
+
# constructor declares these three whenever a script omits them (the analyzer
|
|
210
|
+
# admits Pine v6 only). Every other ``strategy()`` parameter the lane probed --
|
|
211
|
+
# currency, commission, slippage, margin, pyramiding, process_orders_on_close,
|
|
212
|
+
# close_entries_rule, use_bar_magnifier -- kept its default, which stays the
|
|
213
|
+
# host's. Pinned by TradingView tapes (``tests/fixtures/strategy_defaults``).
|
|
214
|
+
PINE_V6_STRATEGY_DEFAULTS = {
|
|
215
|
+
"initial_capital": 100000.0,
|
|
216
|
+
"default_qty_type": "strategy.percent_of_equity",
|
|
217
|
+
"default_qty_value": 100.0,
|
|
218
|
+
}
|
|
219
|
+
|
|
156
220
|
|
|
157
221
|
# ---------------------------------------------------------------------------
|
|
158
222
|
# TA dispatch tables
|
|
@@ -173,7 +237,7 @@ TA_COMPUTE_ARGS = {
|
|
|
173
237
|
"stoch": [0, 1, 2],
|
|
174
238
|
"highest": [0], "lowest": [0],
|
|
175
239
|
"crossover": [0, 1], "crossunder": [0, 1], "cross": [0, 1],
|
|
176
|
-
"change": [0],
|
|
240
|
+
"change": [0, 1], # source + length (the ctor only bounds the history)
|
|
177
241
|
"supertrend": [], # supertrend gets bar data implicitly
|
|
178
242
|
"dmi": [], # dmi gets bar data implicitly
|
|
179
243
|
"bb": [0],
|
|
@@ -215,8 +279,9 @@ TA_COMPUTE_ARGS = {
|
|
|
215
279
|
"wvad": [],
|
|
216
280
|
"iii": [],
|
|
217
281
|
"vwap": [0], # source explicit, volume appended
|
|
218
|
-
# 3-arg bands form:
|
|
219
|
-
#
|
|
282
|
+
# 3-arg bands form without an explicit anchor: source (arg 0) goes to
|
|
283
|
+
# compute(); stdev_mult (arg 2) goes to the ctor. Explicit anchors are
|
|
284
|
+
# remapped to the anchored shim by the analyzer.
|
|
220
285
|
"vwap_bands": [0],
|
|
221
286
|
"mode": [0], "range": [0], "dev": [0],
|
|
222
287
|
"max": [0], "min": [0], "rci": [0],
|
|
@@ -282,6 +347,7 @@ TA_TUPLE_FIELDS = {
|
|
|
282
347
|
"dmi": ["diplus", "diminus", "adx"],
|
|
283
348
|
# ta.vwap 3-arg bands form → VWAPBandsResult {vwap, upper, lower}
|
|
284
349
|
"vwap_bands": ["vwap", "upper", "lower"],
|
|
350
|
+
"vwap_anchored_bands": ["vwap", "upper", "lower"],
|
|
285
351
|
}
|
|
286
352
|
|
|
287
353
|
|
|
@@ -306,7 +372,7 @@ PINE_TYPE_TO_CPP = {
|
|
|
306
372
|
PineType.INT: "int", PineType.FLOAT: "double", PineType.BOOL: "bool",
|
|
307
373
|
PineType.STRING: "std::string", PineType.NA: "double",
|
|
308
374
|
PineType.UNKNOWN: "double", PineType.VOID: "double",
|
|
309
|
-
PineType.COLOR: "
|
|
375
|
+
PineType.COLOR: "int64_t",
|
|
310
376
|
# Drawing-objects-as-data: the value-view handle structs (see
|
|
311
377
|
# drawing.hpp). Explicit-hint decls (``var line x``), UDT-method drawing
|
|
312
378
|
# params, and ``_type_for_decl`` resolve through here.
|
|
@@ -379,7 +445,13 @@ SYMINFO_MEMBER_MAP = {
|
|
|
379
445
|
"currency": "syminfo_.currency",
|
|
380
446
|
"basecurrency": "syminfo_.basecurrency",
|
|
381
447
|
"type": "syminfo_.type",
|
|
382
|
-
"
|
|
448
|
+
# TradingView spells a UTC exchange zone "Etc/UTC" (lab tv tapes
|
|
449
|
+
# te_syminfo_timezone on BINANCE:ETHUSDT.P 15 and BINANCE:BTCUSDT 1D:
|
|
450
|
+
# syminfo.timezone == "UTC" is false there); the engine names that zone
|
|
451
|
+
# "UTC", its SymInfo default. The Pine read spells it TradingView's way;
|
|
452
|
+
# the engine calls taking the symbol's zone keep reading syminfo_.timezone.
|
|
453
|
+
"timezone": ('(syminfo_.timezone.empty() || syminfo_.timezone == "UTC"'
|
|
454
|
+
' ? std::string("Etc/UTC") : syminfo_.timezone)'),
|
|
383
455
|
"session": "syminfo_.session",
|
|
384
456
|
"volumetype": "syminfo_.volumetype",
|
|
385
457
|
"description": "syminfo_.description",
|
|
@@ -388,8 +460,11 @@ SYMINFO_MEMBER_MAP = {
|
|
|
388
460
|
"root": 'na<std::string>()',
|
|
389
461
|
"pricescale": 'na<double>()',
|
|
390
462
|
"minmove": 'na<double>()',
|
|
463
|
+
# --- Run facts: the value the run declares through the syminfo
|
|
464
|
+
# metadata channel (runtime_overrides.syminfo_metadata, a lane fact),
|
|
465
|
+
# na when it declares none. ---
|
|
466
|
+
"mincontract": 'get_syminfo_metadata("mincontract")',
|
|
391
467
|
# --- External-data fields: na-accept so scripts compile ---
|
|
392
|
-
"mincontract": 'na<double>()',
|
|
393
468
|
"current_contract": 'na<std::string>()',
|
|
394
469
|
"expiration_date": 'na<int64_t>()',
|
|
395
470
|
"isin": 'na<std::string>()',
|
|
@@ -539,15 +614,14 @@ def _checked_array_index_prelude(
|
|
|
539
614
|
)
|
|
540
615
|
|
|
541
616
|
|
|
542
|
-
def _checked_array_range_prelude() -> str:
|
|
617
|
+
def _checked_array_range_prelude(*, reject_inverted: bool = True) -> str:
|
|
543
618
|
"""Validate the half-open ``[index_from, index_to)`` range of fill/slice.
|
|
544
619
|
|
|
545
620
|
Both endpoints are checked with ``allow_size`` (``index_to`` is exclusive,
|
|
546
621
|
so ``size`` is a legal endpoint) and without negative normalization
|
|
547
622
|
(neither function appears in the Pine v6 reference's negative-indexing
|
|
548
|
-
set).
|
|
549
|
-
|
|
550
|
-
replaces, and no evidence pins TradingView's behaviour there.
|
|
623
|
+
set). TradingView treats an inverted ``fill`` range as empty, but raises
|
|
624
|
+
for an inverted ``slice`` range. The caller selects that distinction.
|
|
551
625
|
"""
|
|
552
626
|
return (
|
|
553
627
|
_checked_array_index_prelude(
|
|
@@ -556,22 +630,22 @@ def _checked_array_range_prelude() -> str:
|
|
|
556
630
|
+ _checked_array_index_prelude(
|
|
557
631
|
normalize_negative=False, allow_size=True, name="index_to"
|
|
558
632
|
)
|
|
559
|
-
+
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
"std::to_string(__pf_array_size_index_from)); "
|
|
633
|
+
+ (
|
|
634
|
+
"if(__pf_array_index_from>__pf_array_index_to) "
|
|
635
|
+
"pine_runtime_error(\"Index 'from' should be less than index 'to'.\"); "
|
|
636
|
+
if reject_inverted else ""
|
|
637
|
+
)
|
|
565
638
|
)
|
|
566
639
|
|
|
567
640
|
|
|
568
|
-
def _checked_array_insert(a: str, args: list[str]) -> str:
|
|
641
|
+
def _checked_array_insert(a: str, args: list[str], normalize_negative: bool = True) -> str:
|
|
569
642
|
"""``array.insert(id, index, value)`` — index is an INSERTION point.
|
|
570
643
|
|
|
571
644
|
``index == size`` appends, so the bound is ``index <= size``; a negative
|
|
572
645
|
index is end-relative, exactly as for ``get``/``set``/``remove``.
|
|
573
646
|
"""
|
|
574
|
-
check = _checked_array_index_prelude(allow_size=True
|
|
647
|
+
check = _checked_array_index_prelude(allow_size=True,
|
|
648
|
+
normalize_negative=normalize_negative)
|
|
575
649
|
return (
|
|
576
650
|
"[&](auto&& __pf_array){ "
|
|
577
651
|
"return [&](auto&& __pf_raw_index_value){ "
|
|
@@ -585,13 +659,14 @@ def _checked_array_insert(a: str, args: list[str]) -> str:
|
|
|
585
659
|
|
|
586
660
|
def _checked_array_fill_range(a: str, args: list[str]) -> str:
|
|
587
661
|
"""``array.fill(id, value, index_from, index_to)`` — bounded range fill."""
|
|
588
|
-
check = _checked_array_range_prelude()
|
|
662
|
+
check = _checked_array_range_prelude(reject_inverted=False)
|
|
589
663
|
return (
|
|
590
664
|
"[&](auto&& __pf_array){ "
|
|
591
665
|
"return [&](auto&& __pf_array_value){ "
|
|
592
666
|
"return [&](auto&& __pf_raw_index_from_value){ "
|
|
593
667
|
"return [&](auto&& __pf_raw_index_to_value){ "
|
|
594
668
|
f"{check}"
|
|
669
|
+
"if(__pf_array_index_from<__pf_array_index_to) "
|
|
595
670
|
"std::fill(__pf_array.begin()+(size_t)__pf_array_index_from, "
|
|
596
671
|
"__pf_array.begin()+(size_t)__pf_array_index_to, __pf_array_value); "
|
|
597
672
|
f"}}(({args[2]})); }}(({args[1]})); }}(({args[0]})); }}(({a}))"
|
|
@@ -616,8 +691,8 @@ def checked_array_slice(a: str, args: list[str], *, result_type: str) -> str:
|
|
|
616
691
|
)
|
|
617
692
|
|
|
618
693
|
|
|
619
|
-
def _checked_array_get(a: str, args: list[str]) -> str:
|
|
620
|
-
check = _checked_array_index_prelude()
|
|
694
|
+
def _checked_array_get(a: str, args: list[str], normalize_negative: bool = True) -> str:
|
|
695
|
+
check = _checked_array_index_prelude(normalize_negative=normalize_negative)
|
|
621
696
|
return (
|
|
622
697
|
"[&](auto&& __pf_array)->decltype(auto){ "
|
|
623
698
|
"return [&](auto&& __pf_raw_index_value)->decltype(auto){ "
|
|
@@ -631,8 +706,8 @@ def _checked_array_get(a: str, args: list[str]) -> str:
|
|
|
631
706
|
)
|
|
632
707
|
|
|
633
708
|
|
|
634
|
-
def _checked_array_set(a: str, args: list[str]) -> str:
|
|
635
|
-
check = _checked_array_index_prelude()
|
|
709
|
+
def _checked_array_set(a: str, args: list[str], normalize_negative: bool = True) -> str:
|
|
710
|
+
check = _checked_array_index_prelude(normalize_negative=normalize_negative)
|
|
636
711
|
return (
|
|
637
712
|
"[&](auto&& __pf_array){ "
|
|
638
713
|
"return [&](auto&& __pf_raw_index_value){ "
|
|
@@ -643,8 +718,8 @@ def _checked_array_set(a: str, args: list[str]) -> str:
|
|
|
643
718
|
)
|
|
644
719
|
|
|
645
720
|
|
|
646
|
-
def _checked_array_remove(a: str, args: list[str]) -> str:
|
|
647
|
-
check = _checked_array_index_prelude()
|
|
721
|
+
def _checked_array_remove(a: str, args: list[str], normalize_negative: bool = True) -> str:
|
|
722
|
+
check = _checked_array_index_prelude(normalize_negative=normalize_negative)
|
|
648
723
|
return (
|
|
649
724
|
"[&](auto&& __pf_array){ "
|
|
650
725
|
"return [&](auto&& __pf_raw_index_value){ "
|
|
@@ -709,6 +784,17 @@ def _checked_array_percentrank(a: str, args: list[str]) -> str:
|
|
|
709
784
|
)
|
|
710
785
|
|
|
711
786
|
|
|
787
|
+
# Pine v5 raises a runtime error on a negative index to these (TradingView:
|
|
788
|
+
# "Index -1 is out of bounds"); v6 counts from the array's end. The v5 forms
|
|
789
|
+
# lower a v5 library's body (``library_v5``).
|
|
790
|
+
V5_ARRAY_INDEX_METHODS = {
|
|
791
|
+
"get": lambda a, args: _checked_array_get(a, args, normalize_negative=False),
|
|
792
|
+
"set": lambda a, args: _checked_array_set(a, args, normalize_negative=False),
|
|
793
|
+
"insert": lambda a, args: _checked_array_insert(a, args, normalize_negative=False),
|
|
794
|
+
"remove": lambda a, args: _checked_array_remove(a, args, normalize_negative=False),
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
|
|
712
798
|
# Methods called as ``array.method(arr, ...)`` or ``arr.method(...)``.
|
|
713
799
|
ARRAY_METHODS = {
|
|
714
800
|
"get": _checked_array_get,
|
|
@@ -726,8 +812,8 @@ ARRAY_METHODS = {
|
|
|
726
812
|
"fill": lambda a, args: f"std::fill({a}.begin(), {a}.end(), {args[0]})" if len(args) == 1
|
|
727
813
|
else _checked_array_fill_range(a, args),
|
|
728
814
|
"includes": lambda a, args: f"(std::find({a}.begin(), {a}.end(), {args[0]}) != {a}.end())",
|
|
729
|
-
"indexof": lambda a, args: f"[&](){{ auto
|
|
730
|
-
"lastindexof": lambda a, args: f"[&](){{ for(int
|
|
815
|
+
"indexof": lambda a, args: f"[&](){{ auto __pf_it=std::find({a}.begin(),{a}.end(),{args[0]}); return __pf_it!={a}.end()?(double)(__pf_it-{a}.begin()):-1.0; }}()",
|
|
816
|
+
"lastindexof": lambda a, args: f"[&](){{ for(int __pf_i=(int){a}.size()-1;__pf_i>=0;__pf_i--)if({a}[__pf_i]=={args[0]})return(double)__pf_i; return -1.0; }}()",
|
|
731
817
|
"sort": lambda a, args: (
|
|
732
818
|
f"[&](){{ if (({args[0]}) == \"descending\") std::sort({a}.begin(), {a}.end(), std::greater<>()); else std::sort({a}.begin(), {a}.end()); }}()"
|
|
733
819
|
if args else f"std::sort({a}.begin(), {a}.end())"
|
|
@@ -741,37 +827,50 @@ ARRAY_METHODS = {
|
|
|
741
827
|
"min": lambda a, args: f"({a}.empty()?na<double>():*std::min_element({a}.begin(),{a}.end()))",
|
|
742
828
|
"max": lambda a, args: f"({a}.empty()?na<double>():*std::max_element({a}.begin(),{a}.end()))",
|
|
743
829
|
"range": lambda a, args: f"({a}.empty()?na<double>():*std::max_element({a}.begin(),{a}.end())-*std::min_element({a}.begin(),{a}.end()))",
|
|
744
|
-
"every": lambda a, args: f"std::all_of({a}.begin(),{a}.end(),[](
|
|
745
|
-
"some": lambda a, args: f"std::any_of({a}.begin(),{a}.end(),[](
|
|
830
|
+
"every": lambda a, args: f"std::all_of({a}.begin(),{a}.end(),[](auto _pf_raw){{ using _pf_val_t=typename std::decay_t<decltype({a})>::value_type; _pf_val_t v=_pf_raw; if constexpr(std::is_same_v<_pf_val_t,bool>) return v; else return !is_na(v) && v!=0; }})",
|
|
831
|
+
"some": lambda a, args: f"std::any_of({a}.begin(),{a}.end(),[](auto _pf_raw){{ using _pf_val_t=typename std::decay_t<decltype({a})>::value_type; _pf_val_t v=_pf_raw; if constexpr(std::is_same_v<_pf_val_t,bool>) return v; else return !is_na(v) && v!=0; }})",
|
|
746
832
|
# stdev/variance honor the optional 2nd ``biased`` arg (Pine v6:
|
|
747
833
|
# biased=true → population (default), false → sample / n-1).
|
|
748
834
|
"stdev": lambda a, args: (
|
|
749
|
-
f"[&](){{ if({a}.empty()) return na<double>(); double
|
|
750
|
-
f"double
|
|
751
|
-
f"return
|
|
835
|
+
f"[&](){{ if({a}.empty()) return na<double>(); double __pf_m=std::accumulate({a}.begin(),{a}.end(),0.0)/{a}.size(); double __pf_s=0; for(auto v:{a})__pf_s+=(v-__pf_m)*(v-__pf_m); "
|
|
836
|
+
f"double __pf_d=({_pine_bool_arg(args[0])})?(double){a}.size():((double){a}.size()-1.0); "
|
|
837
|
+
f"return __pf_d>0?std::sqrt(__pf_s/__pf_d):na<double>(); }}()"
|
|
752
838
|
if args else
|
|
753
|
-
f"[&](){{ if({a}.empty()) return na<double>(); double
|
|
839
|
+
f"[&](){{ if({a}.empty()) return na<double>(); double __pf_m=std::accumulate({a}.begin(),{a}.end(),0.0)/{a}.size(); double __pf_s=0; for(auto v:{a})__pf_s+=(v-__pf_m)*(v-__pf_m); return std::sqrt(__pf_s/{a}.size()); }}()"
|
|
754
840
|
),
|
|
755
841
|
"variance": lambda a, args: (
|
|
756
|
-
f"[&](){{ if({a}.empty()) return na<double>(); double
|
|
757
|
-
f"double
|
|
758
|
-
f"return
|
|
842
|
+
f"[&](){{ if({a}.empty()) return na<double>(); double __pf_m=std::accumulate({a}.begin(),{a}.end(),0.0)/{a}.size(); double __pf_s=0; for(auto v:{a})__pf_s+=(v-__pf_m)*(v-__pf_m); "
|
|
843
|
+
f"double __pf_d=({_pine_bool_arg(args[0])})?(double){a}.size():((double){a}.size()-1.0); "
|
|
844
|
+
f"return __pf_d>0?__pf_s/__pf_d:na<double>(); }}()"
|
|
759
845
|
if args else
|
|
760
|
-
f"[&](){{ if({a}.empty()) return na<double>(); double
|
|
846
|
+
f"[&](){{ if({a}.empty()) return na<double>(); double __pf_m=std::accumulate({a}.begin(),{a}.end(),0.0)/{a}.size(); double __pf_s=0; for(auto v:{a})__pf_s+=(v-__pf_m)*(v-__pf_m); return __pf_s/{a}.size(); }}()"
|
|
761
847
|
),
|
|
762
|
-
"median": lambda a, args: f"[&](){{ if({a}.empty()) return na<double>(); auto
|
|
763
|
-
"mode": lambda a, args: f"[&](){{ if({a}.empty()) return na<double>(); std::unordered_map<double,int>
|
|
764
|
-
"percentile_linear_interpolation": lambda a, args: f"[&](){{ if({a}.empty()) return na<double>(); auto
|
|
765
|
-
"percentile_nearest_rank": lambda a, args: f"[&](){{ if({a}.empty()) return na<double>(); auto
|
|
848
|
+
"median": lambda a, args: f"[&](){{ if({a}.empty()) return na<double>(); auto __pf_c={a}; std::sort(__pf_c.begin(),__pf_c.end()); int __pf_n=__pf_c.size(); return __pf_n%2?__pf_c[__pf_n/2]:(__pf_c[__pf_n/2-1]+__pf_c[__pf_n/2])/2.0; }}()",
|
|
849
|
+
"mode": lambda a, args: f"[&](){{ if({a}.empty()) return na<double>(); std::unordered_map<double,int> __pf_m; for(auto v:{a})__pf_m[v]++; double __pf_best=0; int __pf_bc=0; for(auto&[v,c]:__pf_m)if(c>__pf_bc||(c==__pf_bc&&v<__pf_best)){{__pf_bc=c;__pf_best=v;}} return __pf_best; }}()",
|
|
850
|
+
"percentile_linear_interpolation": lambda a, args: f"[&](){{ if({a}.empty()) return na<double>(); auto __pf_c={a}; std::sort(__pf_c.begin(),__pf_c.end()); double __pf_k=({args[0]}/100.0)*__pf_c.size()-0.5; if(!std::isfinite(__pf_k)) return na<double>(); if(__pf_k>=__pf_c.size()-1.0) return (double)__pf_c.back(); int __pf_i=__pf_k<=0.0?0:(int)__pf_k; double __pf_f=__pf_k-__pf_i; return __pf_c[__pf_i]*(1-__pf_f)+__pf_c[__pf_i+1]*__pf_f; }}()",
|
|
851
|
+
"percentile_nearest_rank": lambda a, args: f"[&](){{ if({a}.empty()) return na<double>(); auto __pf_c={a}; std::sort(__pf_c.begin(),__pf_c.end()); double __pf_p=({args[0]}); if(is_na(__pf_p)) return (double)__pf_c.front(); double __pf_rank=std::ceil((__pf_p/100.0)*__pf_c.size()); if(!std::isfinite(__pf_rank)) return na<double>(); int __pf_r=(int)std::clamp(__pf_rank,1.0,(double)__pf_c.size()); return (double)__pf_c[__pf_r-1]; }}()",
|
|
766
852
|
"percentrank": _checked_array_percentrank,
|
|
767
|
-
"abs": lambda a, args: f"[&](){{ std::vector<double>
|
|
768
|
-
"join": lambda a, args: "[&](){{ std::string
|
|
769
|
-
"standardize": lambda a, args: f"[&](){{ double
|
|
770
|
-
"covariance": lambda a, args: f"[&](){{ auto&&
|
|
771
|
-
"binary_search": lambda a, args: f"[&](){{ auto
|
|
772
|
-
"binary_search_leftmost": lambda a, args: f"[&](){{ auto
|
|
773
|
-
"binary_search_rightmost": lambda a, args: f"[&](){{ auto
|
|
774
|
-
"sort_indices": lambda a, args: f"[&](){{ std::vector<double>
|
|
853
|
+
"abs": lambda a, args: f"[&](){{ std::vector<double> __pf_r; for(auto v:{a})__pf_r.push_back(std::abs(v)); return __pf_r; }}()",
|
|
854
|
+
"join": lambda a, args: "[&](){{ std::string __pf_r; for(size_t __pf_i=0;__pf_i<{arr}.size();__pf_i++){{ if(__pf_i>0)__pf_r+={sep}; __pf_r+=std::to_string({arr}[__pf_i]); }} return __pf_r; }}()".format(arr=a, sep=args[0] if args else 'std::string(",")'),
|
|
855
|
+
"standardize": lambda a, args: f"[&](){{ double __pf_m=std::accumulate({a}.begin(),{a}.end(),0.0)/{a}.size(); double __pf_s=0; for(auto v:{a})__pf_s+=(v-__pf_m)*(v-__pf_m); __pf_s=std::sqrt(__pf_s/{a}.size()); std::vector<double> __pf_r; for(auto v:{a})__pf_r.push_back(__pf_s==0?1.0:(v-__pf_m)/__pf_s); return __pf_r; }}()",
|
|
856
|
+
"covariance": lambda a, args: f"[&](){{ auto&&__pf_b=({args[0]}); int __pf_n=std::min({a}.size(),__pf_b.size()); if(__pf_n<=0) return na<double>(); double __pf_ma=0,__pf_mb=0; for(int __pf_i=0;__pf_i<__pf_n;__pf_i++){{__pf_ma+={a}[__pf_i];__pf_mb+=__pf_b[__pf_i];}} __pf_ma/=__pf_n;__pf_mb/=__pf_n; double __pf_c=0; for(int __pf_i=0;__pf_i<__pf_n;__pf_i++)__pf_c+=({a}[__pf_i]-__pf_ma)*(__pf_b[__pf_i]-__pf_mb); return __pf_c/__pf_n; }}()",
|
|
857
|
+
"binary_search": lambda a, args: f"[&](){{ auto __pf_it=std::lower_bound({a}.begin(),{a}.end(),{args[0]}); return (__pf_it!={a}.end()&&*__pf_it=={args[0]})?(double)(__pf_it-{a}.begin()):-1.0; }}()",
|
|
858
|
+
"binary_search_leftmost": lambda a, args: f"[&](){{ auto __pf_it=std::lower_bound({a}.begin(),{a}.end(),{args[0]}); return (__pf_it!={a}.end()&&*__pf_it=={args[0]})?(double)(__pf_it-{a}.begin()):(double)(__pf_it-{a}.begin()-1); }}()",
|
|
859
|
+
"binary_search_rightmost": lambda a, args: f"[&](){{ auto __pf_it=std::upper_bound({a}.begin(),{a}.end(),{args[0]}); return (__pf_it!={a}.begin()&&*(__pf_it-1)=={args[0]})?(double)(__pf_it-{a}.begin()-1):(double)(__pf_it-{a}.begin()); }}()",
|
|
860
|
+
"sort_indices": lambda a, args: f"[&](){{ std::vector<double> __pf_idx({a}.size()); std::iota(__pf_idx.begin(),__pf_idx.end(),0); std::sort(__pf_idx.begin(),__pf_idx.end(),[&](int __pf_i,int __pf_j){{return {a}[__pf_i]<{a}[__pf_j];}}); return __pf_idx; }}()",
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
# The ARRAY_METHODS arguments (receiver excluded) a template reads more than
|
|
864
|
+
# once, or once per loop iteration: evaluate_args_once binds each that is not
|
|
865
|
+
# a plain read. The typed string ``join`` lowering reads its separator the
|
|
866
|
+
# same way.
|
|
867
|
+
ARRAY_ARGS_READ_REPEATEDLY = {
|
|
868
|
+
"lastindexof": (0,),
|
|
869
|
+
"binary_search": (0,),
|
|
870
|
+
"binary_search_leftmost": (0,),
|
|
871
|
+
"binary_search_rightmost": (0,),
|
|
872
|
+
"join": (0,),
|
|
873
|
+
"concat": (0,),
|
|
775
874
|
}
|
|
776
875
|
|
|
777
876
|
# Pine parameter order for the checked-index subset. Keeping the receiver
|
|
@@ -830,7 +929,7 @@ def _matrix_add_row(m: str, args: list) -> str:
|
|
|
830
929
|
if len(args) == 1:
|
|
831
930
|
return f"{m}.add_row((int)({m}.rows()), {args[0]})"
|
|
832
931
|
if len(args) == 2:
|
|
833
|
-
return f"{m}.add_row((
|
|
932
|
+
return f"{m}.add_row({_matrix_int_arg(args[0])}, {args[1]})"
|
|
834
933
|
raise IndexError("matrix.add_row")
|
|
835
934
|
|
|
836
935
|
|
|
@@ -839,7 +938,7 @@ def _matrix_add_col(m: str, args: list) -> str:
|
|
|
839
938
|
if len(args) == 1:
|
|
840
939
|
return f"{m}.add_col((int)({m}.columns()), {args[0]})"
|
|
841
940
|
if len(args) == 2:
|
|
842
|
-
return f"{m}.add_col((
|
|
941
|
+
return f"{m}.add_col({_matrix_int_arg(args[0])}, {args[1]})"
|
|
843
942
|
raise IndexError("matrix.add_col")
|
|
844
943
|
|
|
845
944
|
|
|
@@ -897,28 +996,28 @@ MATRIX_NUMERIC_ONLY: frozenset[str] = frozenset({
|
|
|
897
996
|
MATRIX_SORT_ALLOWED_GENERIC_ELEMS: frozenset[str] = frozenset({"int", "bool", "string"})
|
|
898
997
|
|
|
899
998
|
MATRIX_METHODS = {
|
|
900
|
-
"get": lambda m, args: f"{m}.get((
|
|
901
|
-
"set": lambda m, args: f"{m}.set((
|
|
999
|
+
"get": lambda m, args: f"{m}.get({_matrix_int_arg(args[0])}, {_matrix_int_arg(args[1])})",
|
|
1000
|
+
"set": lambda m, args: f"{m}.set({_matrix_int_arg(args[0])}, {_matrix_int_arg(args[1])}, {args[2]})",
|
|
902
1001
|
"fill": lambda m, args: f"{m}.fill({args[0]})",
|
|
903
|
-
"row": lambda m, args: f"{m}.row((
|
|
904
|
-
"col": lambda m, args: f"{m}.col((
|
|
1002
|
+
"row": lambda m, args: f"{m}.row({_matrix_int_arg(args[0])})",
|
|
1003
|
+
"col": lambda m, args: f"{m}.col({_matrix_int_arg(args[0])})",
|
|
905
1004
|
"rows": lambda m, args: f"(int){m}.rows()",
|
|
906
1005
|
"columns": lambda m, args: f"(int){m}.columns()",
|
|
907
1006
|
"add_row": _matrix_add_row,
|
|
908
1007
|
"add_col": _matrix_add_col,
|
|
909
1008
|
# ``remove_row`` is void in C++; Pine may assign the result, so we wrap
|
|
910
1009
|
# it in a lambda that returns a sentinel double after the side effect.
|
|
911
|
-
"remove_row": lambda m, args: f"[&](){{ {m}.remove_row((
|
|
912
|
-
"remove_col":lambda m, args: f"[&](){{ {m}.remove_col((
|
|
913
|
-
"swap_rows": lambda m, args: f"{m}.swap_rows((
|
|
914
|
-
"swap_columns": lambda m, args: f"{m}.swap_columns((
|
|
1010
|
+
"remove_row": lambda m, args: f"[&](){{ {m}.remove_row({_matrix_int_arg(args[0])}); return 0.0; }}()",
|
|
1011
|
+
"remove_col":lambda m, args: f"[&](){{ {m}.remove_col({_matrix_int_arg(args[0])}); return 0.0; }}()",
|
|
1012
|
+
"swap_rows": lambda m, args: f"{m}.swap_rows({_matrix_int_arg(args[0])}, {_matrix_int_arg(args[1])})",
|
|
1013
|
+
"swap_columns": lambda m, args: f"{m}.swap_columns({_matrix_int_arg(args[0])}, {_matrix_int_arg(args[1])})",
|
|
915
1014
|
"copy": lambda m, args: f"{m}.copy()",
|
|
916
|
-
"submatrix": lambda m, args: f"{m}.submatrix((
|
|
917
|
-
"reshape": lambda m, args: f"{m}.reshape((
|
|
1015
|
+
"submatrix": lambda m, args: f"{m}.submatrix({_matrix_int_arg(args[0])}, {_matrix_int_arg(args[1])}, {_matrix_int_arg(args[2])}, {_matrix_int_arg(args[3])})",
|
|
1016
|
+
"reshape": lambda m, args: f"{m}.reshape({_matrix_int_arg(args[0])}, {_matrix_int_arg(args[1])})",
|
|
918
1017
|
"reverse": lambda m, args: f"{m}.reverse()",
|
|
919
1018
|
"transpose": lambda m, args: f"{m}.transpose()",
|
|
920
|
-
"sort": lambda m, args: f"{m}.sort((
|
|
921
|
-
"concat": lambda m, args: f"{m}.concat({args[0]}, (
|
|
1019
|
+
"sort": lambda m, args: f"{m}.sort({_matrix_int_arg(args[0])}, {args[1]} != \"descending\")" if len(args)>1 else f"{m}.sort({_matrix_int_arg(args[0])})",
|
|
1020
|
+
"concat": lambda m, args: f"{m}.concat({args[0]}, {pine_truth_cast(args[1])})" if len(args)>1 else f"{m}.concat({args[0]}, true)",
|
|
922
1021
|
"avg": lambda m, args: f"{m}.avg()",
|
|
923
1022
|
"min": lambda m, args: f"{m}.min()",
|
|
924
1023
|
"max": lambda m, args: f"{m}.max()",
|
|
@@ -926,7 +1025,7 @@ MATRIX_METHODS = {
|
|
|
926
1025
|
"sum": lambda m, args: f"{m}.sum()",
|
|
927
1026
|
"diff": lambda m, args: f"{m}.diff({args[0]})",
|
|
928
1027
|
"mult": lambda m, args: f"{m}.mult({args[0]})",
|
|
929
|
-
"pow": lambda m, args: f"{m}.pow((
|
|
1028
|
+
"pow": lambda m, args: f"{m}.pow({_matrix_int_arg(args[0])})",
|
|
930
1029
|
"det": lambda m, args: f"{m}.det()",
|
|
931
1030
|
"inv": lambda m, args: f"{m}.inv()",
|
|
932
1031
|
"pinv": lambda m, args: f"{m}.pinv()",
|
|
@@ -980,6 +1079,17 @@ def _math_minmax_na_expr(func_name: str, args: list[str]) -> str:
|
|
|
980
1079
|
)
|
|
981
1080
|
|
|
982
1081
|
|
|
1082
|
+
def _math_round_digits_expr(args: list[str]) -> str:
|
|
1083
|
+
"""``math.round(number, precision)``: the template reads the precision
|
|
1084
|
+
twice, so one that is more than a plain read is evaluated once."""
|
|
1085
|
+
return evaluate_args_once(
|
|
1086
|
+
args, (1,),
|
|
1087
|
+
lambda a: (f"(std::round({a[0]} * std::pow(10.0, {a[1]})) / "
|
|
1088
|
+
f"std::pow(10.0, {a[1]}))"),
|
|
1089
|
+
"_pf_round_a",
|
|
1090
|
+
)
|
|
1091
|
+
|
|
1092
|
+
|
|
983
1093
|
# Pine ``math.sign`` returns a float, propagates numeric ``na``, and must not
|
|
984
1094
|
# evaluate its argument more than once. Keep the callable separate from the
|
|
985
1095
|
# call template because the stable-runtime TA reset fallback also substitutes
|
|
@@ -1015,18 +1125,30 @@ STR_FUNC_MAP = {
|
|
|
1015
1125
|
"contains": lambda args: f"({args[0]}.find({args[1]}) != std::string::npos)",
|
|
1016
1126
|
"startswith": lambda args: f"({args[0]}.substr(0, {args[1]}.length()) == {args[1]})",
|
|
1017
1127
|
"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)",
|
|
1018
|
-
"pos": lambda args: f"[&](){{ auto
|
|
1128
|
+
"pos": lambda args: f"[&](){{ auto __pf_p={args[0]}.find({args[1]}); return __pf_p!=std::string::npos?(int)__pf_p:-1; }}()",
|
|
1019
1129
|
"substring": None, # handled separately (2 vs 3 args)
|
|
1020
|
-
"replace_all": lambda args: f"[&](){{ std::string
|
|
1130
|
+
"replace_all": lambda args: f"[&](){{ std::string __pf_s={args[0]}; size_t __pf_p=0; while((__pf_p=__pf_s.find({args[1]},__pf_p))!=std::string::npos){{ __pf_s.replace(__pf_p,{args[1]}.length(),{args[2]}); __pf_p+={args[2]}.length(); }} return __pf_s; }}()",
|
|
1021
1131
|
"replace": None, # handled separately (3 vs 4 args)
|
|
1022
|
-
"lower": lambda args: f"[&](){{ std::string
|
|
1023
|
-
"upper": lambda args: f"[&](){{ std::string
|
|
1024
|
-
"trim": lambda args: f'[&](){{ std::string
|
|
1025
|
-
"repeat": lambda args:
|
|
1132
|
+
"lower": lambda args: f"[&](){{ std::string __pf_s={args[0]}; std::transform(__pf_s.begin(),__pf_s.end(),__pf_s.begin(),::tolower); return __pf_s; }}()",
|
|
1133
|
+
"upper": lambda args: f"[&](){{ std::string __pf_s={args[0]}; std::transform(__pf_s.begin(),__pf_s.end(),__pf_s.begin(),::toupper); return __pf_s; }}()",
|
|
1134
|
+
"trim": lambda args: f'[&](){{ std::string __pf_s={args[0]}; __pf_s.erase(0,__pf_s.find_first_not_of(" \\t\\n\\r")); __pf_s.erase(__pf_s.find_last_not_of(" \\t\\n\\r")+1); return __pf_s; }}()',
|
|
1135
|
+
"repeat": lambda args: (
|
|
1136
|
+
f"[&](){{ std::string __pf_r; for(int __pf_i=0;__pf_i<{na_preserving_int_cast(args[1])};__pf_i++) __pf_r+={args[0]}; return __pf_r; }}()"
|
|
1137
|
+
if len(args) < 3 else
|
|
1138
|
+
f"[&](){{ std::string __pf_r; for(int __pf_i=0;__pf_i<{na_preserving_int_cast(args[1])};__pf_i++){{ if(__pf_i>0) __pf_r+={args[2]}; __pf_r+={args[0]}; }} return __pf_r; }}()"
|
|
1139
|
+
),
|
|
1026
1140
|
"match": lambda args: f'pine_str_match({args[0]}, {args[1]})',
|
|
1027
1141
|
"split": lambda args: f'pine_str_split({args[0]}, {args[1]})',
|
|
1028
1142
|
"format": None, # handled separately
|
|
1029
1143
|
}
|
|
1144
|
+
# The STR_FUNC_MAP arguments a template reads more than once, or once per
|
|
1145
|
+
# loop iteration: evaluate_args_once binds each that is not a plain read.
|
|
1146
|
+
STR_ARGS_READ_REPEATEDLY = {
|
|
1147
|
+
"startswith": (1,),
|
|
1148
|
+
"endswith": (0, 1),
|
|
1149
|
+
"replace_all": (1, 2),
|
|
1150
|
+
"repeat": (0, 1, 2),
|
|
1151
|
+
}
|
|
1030
1152
|
|
|
1031
1153
|
|
|
1032
1154
|
# ---------------------------------------------------------------------------
|