@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
|
@@ -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,13 +279,26 @@ 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],
|
|
223
288
|
}
|
|
224
289
|
|
|
290
|
+
# issue #178: ta.atr / ta.tr read their true range against the previous
|
|
291
|
+
# CHART bar's close (close[1]) even when the call site executes sparsely —
|
|
292
|
+
# the RMA advances on the executions only (TradingView pin 2026-09-06,
|
|
293
|
+
# lab tv i178-sparse-atr-sense, BINANCE:BTCUSDT 60: 398/398 executions equal
|
|
294
|
+
# the chart-close model, 0/398 the previous-execution model). The chart
|
|
295
|
+
# context therefore passes ``BacktestEngine::prev_chart_close()`` as the
|
|
296
|
+
# 4th compute() argument (the engine's always-on tracker); the per-object
|
|
297
|
+
# prev_close (3-arg form) remains for request.security contexts, whose
|
|
298
|
+
# ``bar.`` rewrite has no chart-close tracker.
|
|
299
|
+
TA_CHART_PREV_CLOSE = {"atr", "tr"}
|
|
300
|
+
TA_CHART_PREV_CLOSE_ARG = "prev_chart_close()"
|
|
301
|
+
|
|
225
302
|
# TA functions whose ``.compute()`` always receives bar OHLC implicitly.
|
|
226
303
|
TA_IMPLICIT_COMPUTE_FULL = {
|
|
227
304
|
"atr": "current_bar_.high, current_bar_.low, current_bar_.close",
|
|
@@ -270,6 +347,7 @@ TA_TUPLE_FIELDS = {
|
|
|
270
347
|
"dmi": ["diplus", "diminus", "adx"],
|
|
271
348
|
# ta.vwap 3-arg bands form → VWAPBandsResult {vwap, upper, lower}
|
|
272
349
|
"vwap_bands": ["vwap", "upper", "lower"],
|
|
350
|
+
"vwap_anchored_bands": ["vwap", "upper", "lower"],
|
|
273
351
|
}
|
|
274
352
|
|
|
275
353
|
|
|
@@ -294,7 +372,7 @@ PINE_TYPE_TO_CPP = {
|
|
|
294
372
|
PineType.INT: "int", PineType.FLOAT: "double", PineType.BOOL: "bool",
|
|
295
373
|
PineType.STRING: "std::string", PineType.NA: "double",
|
|
296
374
|
PineType.UNKNOWN: "double", PineType.VOID: "double",
|
|
297
|
-
PineType.COLOR: "
|
|
375
|
+
PineType.COLOR: "int64_t",
|
|
298
376
|
# Drawing-objects-as-data: the value-view handle structs (see
|
|
299
377
|
# drawing.hpp). Explicit-hint decls (``var line x``), UDT-method drawing
|
|
300
378
|
# params, and ``_type_for_decl`` resolve through here.
|
|
@@ -367,7 +445,13 @@ SYMINFO_MEMBER_MAP = {
|
|
|
367
445
|
"currency": "syminfo_.currency",
|
|
368
446
|
"basecurrency": "syminfo_.basecurrency",
|
|
369
447
|
"type": "syminfo_.type",
|
|
370
|
-
"
|
|
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)'),
|
|
371
455
|
"session": "syminfo_.session",
|
|
372
456
|
"volumetype": "syminfo_.volumetype",
|
|
373
457
|
"description": "syminfo_.description",
|
|
@@ -376,8 +460,11 @@ SYMINFO_MEMBER_MAP = {
|
|
|
376
460
|
"root": 'na<std::string>()',
|
|
377
461
|
"pricescale": 'na<double>()',
|
|
378
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")',
|
|
379
467
|
# --- External-data fields: na-accept so scripts compile ---
|
|
380
|
-
"mincontract": 'na<double>()',
|
|
381
468
|
"current_contract": 'na<std::string>()',
|
|
382
469
|
"expiration_date": 'na<int64_t>()',
|
|
383
470
|
"isin": 'na<std::string>()',
|
|
@@ -527,15 +614,14 @@ def _checked_array_index_prelude(
|
|
|
527
614
|
)
|
|
528
615
|
|
|
529
616
|
|
|
530
|
-
def _checked_array_range_prelude() -> str:
|
|
617
|
+
def _checked_array_range_prelude(*, reject_inverted: bool = True) -> str:
|
|
531
618
|
"""Validate the half-open ``[index_from, index_to)`` range of fill/slice.
|
|
532
619
|
|
|
533
620
|
Both endpoints are checked with ``allow_size`` (``index_to`` is exclusive,
|
|
534
621
|
so ``size`` is a legal endpoint) and without negative normalization
|
|
535
622
|
(neither function appears in the Pine v6 reference's negative-indexing
|
|
536
|
-
set).
|
|
537
|
-
|
|
538
|
-
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.
|
|
539
625
|
"""
|
|
540
626
|
return (
|
|
541
627
|
_checked_array_index_prelude(
|
|
@@ -544,22 +630,22 @@ def _checked_array_range_prelude() -> str:
|
|
|
544
630
|
+ _checked_array_index_prelude(
|
|
545
631
|
normalize_negative=False, allow_size=True, name="index_to"
|
|
546
632
|
)
|
|
547
|
-
+
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
"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
|
+
)
|
|
553
638
|
)
|
|
554
639
|
|
|
555
640
|
|
|
556
|
-
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:
|
|
557
642
|
"""``array.insert(id, index, value)`` — index is an INSERTION point.
|
|
558
643
|
|
|
559
644
|
``index == size`` appends, so the bound is ``index <= size``; a negative
|
|
560
645
|
index is end-relative, exactly as for ``get``/``set``/``remove``.
|
|
561
646
|
"""
|
|
562
|
-
check = _checked_array_index_prelude(allow_size=True
|
|
647
|
+
check = _checked_array_index_prelude(allow_size=True,
|
|
648
|
+
normalize_negative=normalize_negative)
|
|
563
649
|
return (
|
|
564
650
|
"[&](auto&& __pf_array){ "
|
|
565
651
|
"return [&](auto&& __pf_raw_index_value){ "
|
|
@@ -573,13 +659,14 @@ def _checked_array_insert(a: str, args: list[str]) -> str:
|
|
|
573
659
|
|
|
574
660
|
def _checked_array_fill_range(a: str, args: list[str]) -> str:
|
|
575
661
|
"""``array.fill(id, value, index_from, index_to)`` — bounded range fill."""
|
|
576
|
-
check = _checked_array_range_prelude()
|
|
662
|
+
check = _checked_array_range_prelude(reject_inverted=False)
|
|
577
663
|
return (
|
|
578
664
|
"[&](auto&& __pf_array){ "
|
|
579
665
|
"return [&](auto&& __pf_array_value){ "
|
|
580
666
|
"return [&](auto&& __pf_raw_index_from_value){ "
|
|
581
667
|
"return [&](auto&& __pf_raw_index_to_value){ "
|
|
582
668
|
f"{check}"
|
|
669
|
+
"if(__pf_array_index_from<__pf_array_index_to) "
|
|
583
670
|
"std::fill(__pf_array.begin()+(size_t)__pf_array_index_from, "
|
|
584
671
|
"__pf_array.begin()+(size_t)__pf_array_index_to, __pf_array_value); "
|
|
585
672
|
f"}}(({args[2]})); }}(({args[1]})); }}(({args[0]})); }}(({a}))"
|
|
@@ -604,8 +691,8 @@ def checked_array_slice(a: str, args: list[str], *, result_type: str) -> str:
|
|
|
604
691
|
)
|
|
605
692
|
|
|
606
693
|
|
|
607
|
-
def _checked_array_get(a: str, args: list[str]) -> str:
|
|
608
|
-
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)
|
|
609
696
|
return (
|
|
610
697
|
"[&](auto&& __pf_array)->decltype(auto){ "
|
|
611
698
|
"return [&](auto&& __pf_raw_index_value)->decltype(auto){ "
|
|
@@ -619,8 +706,8 @@ def _checked_array_get(a: str, args: list[str]) -> str:
|
|
|
619
706
|
)
|
|
620
707
|
|
|
621
708
|
|
|
622
|
-
def _checked_array_set(a: str, args: list[str]) -> str:
|
|
623
|
-
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)
|
|
624
711
|
return (
|
|
625
712
|
"[&](auto&& __pf_array){ "
|
|
626
713
|
"return [&](auto&& __pf_raw_index_value){ "
|
|
@@ -631,8 +718,8 @@ def _checked_array_set(a: str, args: list[str]) -> str:
|
|
|
631
718
|
)
|
|
632
719
|
|
|
633
720
|
|
|
634
|
-
def _checked_array_remove(a: str, args: list[str]) -> str:
|
|
635
|
-
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)
|
|
636
723
|
return (
|
|
637
724
|
"[&](auto&& __pf_array){ "
|
|
638
725
|
"return [&](auto&& __pf_raw_index_value){ "
|
|
@@ -697,6 +784,17 @@ def _checked_array_percentrank(a: str, args: list[str]) -> str:
|
|
|
697
784
|
)
|
|
698
785
|
|
|
699
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
|
+
|
|
700
798
|
# Methods called as ``array.method(arr, ...)`` or ``arr.method(...)``.
|
|
701
799
|
ARRAY_METHODS = {
|
|
702
800
|
"get": _checked_array_get,
|
|
@@ -714,8 +812,8 @@ ARRAY_METHODS = {
|
|
|
714
812
|
"fill": lambda a, args: f"std::fill({a}.begin(), {a}.end(), {args[0]})" if len(args) == 1
|
|
715
813
|
else _checked_array_fill_range(a, args),
|
|
716
814
|
"includes": lambda a, args: f"(std::find({a}.begin(), {a}.end(), {args[0]}) != {a}.end())",
|
|
717
|
-
"indexof": lambda a, args: f"[&](){{ auto
|
|
718
|
-
"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; }}()",
|
|
719
817
|
"sort": lambda a, args: (
|
|
720
818
|
f"[&](){{ if (({args[0]}) == \"descending\") std::sort({a}.begin(), {a}.end(), std::greater<>()); else std::sort({a}.begin(), {a}.end()); }}()"
|
|
721
819
|
if args else f"std::sort({a}.begin(), {a}.end())"
|
|
@@ -729,37 +827,50 @@ ARRAY_METHODS = {
|
|
|
729
827
|
"min": lambda a, args: f"({a}.empty()?na<double>():*std::min_element({a}.begin(),{a}.end()))",
|
|
730
828
|
"max": lambda a, args: f"({a}.empty()?na<double>():*std::max_element({a}.begin(),{a}.end()))",
|
|
731
829
|
"range": lambda a, args: f"({a}.empty()?na<double>():*std::max_element({a}.begin(),{a}.end())-*std::min_element({a}.begin(),{a}.end()))",
|
|
732
|
-
"every": lambda a, args: f"std::all_of({a}.begin(),{a}.end(),[](
|
|
733
|
-
"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; }})",
|
|
734
832
|
# stdev/variance honor the optional 2nd ``biased`` arg (Pine v6:
|
|
735
833
|
# biased=true → population (default), false → sample / n-1).
|
|
736
834
|
"stdev": lambda a, args: (
|
|
737
|
-
f"[&](){{ if({a}.empty()) return na<double>(); double
|
|
738
|
-
f"double
|
|
739
|
-
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>(); }}()"
|
|
740
838
|
if args else
|
|
741
|
-
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()); }}()"
|
|
742
840
|
),
|
|
743
841
|
"variance": lambda a, args: (
|
|
744
|
-
f"[&](){{ if({a}.empty()) return na<double>(); double
|
|
745
|
-
f"double
|
|
746
|
-
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>(); }}()"
|
|
747
845
|
if args else
|
|
748
|
-
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(); }}()"
|
|
749
847
|
),
|
|
750
|
-
"median": lambda a, args: f"[&](){{ if({a}.empty()) return na<double>(); auto
|
|
751
|
-
"mode": lambda a, args: f"[&](){{ if({a}.empty()) return na<double>(); std::unordered_map<double,int>
|
|
752
|
-
"percentile_linear_interpolation": lambda a, args: f"[&](){{ if({a}.empty()) return na<double>(); auto
|
|
753
|
-
"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]; }}()",
|
|
754
852
|
"percentrank": _checked_array_percentrank,
|
|
755
|
-
"abs": lambda a, args: f"[&](){{ std::vector<double>
|
|
756
|
-
"join": lambda a, args: "[&](){{ std::string
|
|
757
|
-
"standardize": lambda a, args: f"[&](){{ double
|
|
758
|
-
"covariance": lambda a, args: f"[&](){{ auto&&
|
|
759
|
-
"binary_search": lambda a, args: f"[&](){{ auto
|
|
760
|
-
"binary_search_leftmost": lambda a, args: f"[&](){{ auto
|
|
761
|
-
"binary_search_rightmost": lambda a, args: f"[&](){{ auto
|
|
762
|
-
"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,),
|
|
763
874
|
}
|
|
764
875
|
|
|
765
876
|
# Pine parameter order for the checked-index subset. Keeping the receiver
|
|
@@ -818,7 +929,7 @@ def _matrix_add_row(m: str, args: list) -> str:
|
|
|
818
929
|
if len(args) == 1:
|
|
819
930
|
return f"{m}.add_row((int)({m}.rows()), {args[0]})"
|
|
820
931
|
if len(args) == 2:
|
|
821
|
-
return f"{m}.add_row((
|
|
932
|
+
return f"{m}.add_row({_matrix_int_arg(args[0])}, {args[1]})"
|
|
822
933
|
raise IndexError("matrix.add_row")
|
|
823
934
|
|
|
824
935
|
|
|
@@ -827,7 +938,7 @@ def _matrix_add_col(m: str, args: list) -> str:
|
|
|
827
938
|
if len(args) == 1:
|
|
828
939
|
return f"{m}.add_col((int)({m}.columns()), {args[0]})"
|
|
829
940
|
if len(args) == 2:
|
|
830
|
-
return f"{m}.add_col((
|
|
941
|
+
return f"{m}.add_col({_matrix_int_arg(args[0])}, {args[1]})"
|
|
831
942
|
raise IndexError("matrix.add_col")
|
|
832
943
|
|
|
833
944
|
|
|
@@ -885,28 +996,28 @@ MATRIX_NUMERIC_ONLY: frozenset[str] = frozenset({
|
|
|
885
996
|
MATRIX_SORT_ALLOWED_GENERIC_ELEMS: frozenset[str] = frozenset({"int", "bool", "string"})
|
|
886
997
|
|
|
887
998
|
MATRIX_METHODS = {
|
|
888
|
-
"get": lambda m, args: f"{m}.get((
|
|
889
|
-
"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]})",
|
|
890
1001
|
"fill": lambda m, args: f"{m}.fill({args[0]})",
|
|
891
|
-
"row": lambda m, args: f"{m}.row((
|
|
892
|
-
"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])})",
|
|
893
1004
|
"rows": lambda m, args: f"(int){m}.rows()",
|
|
894
1005
|
"columns": lambda m, args: f"(int){m}.columns()",
|
|
895
1006
|
"add_row": _matrix_add_row,
|
|
896
1007
|
"add_col": _matrix_add_col,
|
|
897
1008
|
# ``remove_row`` is void in C++; Pine may assign the result, so we wrap
|
|
898
1009
|
# it in a lambda that returns a sentinel double after the side effect.
|
|
899
|
-
"remove_row": lambda m, args: f"[&](){{ {m}.remove_row((
|
|
900
|
-
"remove_col":lambda m, args: f"[&](){{ {m}.remove_col((
|
|
901
|
-
"swap_rows": lambda m, args: f"{m}.swap_rows((
|
|
902
|
-
"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])})",
|
|
903
1014
|
"copy": lambda m, args: f"{m}.copy()",
|
|
904
|
-
"submatrix": lambda m, args: f"{m}.submatrix((
|
|
905
|
-
"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])})",
|
|
906
1017
|
"reverse": lambda m, args: f"{m}.reverse()",
|
|
907
1018
|
"transpose": lambda m, args: f"{m}.transpose()",
|
|
908
|
-
"sort": lambda m, args: f"{m}.sort((
|
|
909
|
-
"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)",
|
|
910
1021
|
"avg": lambda m, args: f"{m}.avg()",
|
|
911
1022
|
"min": lambda m, args: f"{m}.min()",
|
|
912
1023
|
"max": lambda m, args: f"{m}.max()",
|
|
@@ -914,7 +1025,7 @@ MATRIX_METHODS = {
|
|
|
914
1025
|
"sum": lambda m, args: f"{m}.sum()",
|
|
915
1026
|
"diff": lambda m, args: f"{m}.diff({args[0]})",
|
|
916
1027
|
"mult": lambda m, args: f"{m}.mult({args[0]})",
|
|
917
|
-
"pow": lambda m, args: f"{m}.pow((
|
|
1028
|
+
"pow": lambda m, args: f"{m}.pow({_matrix_int_arg(args[0])})",
|
|
918
1029
|
"det": lambda m, args: f"{m}.det()",
|
|
919
1030
|
"inv": lambda m, args: f"{m}.inv()",
|
|
920
1031
|
"pinv": lambda m, args: f"{m}.pinv()",
|
|
@@ -968,6 +1079,17 @@ def _math_minmax_na_expr(func_name: str, args: list[str]) -> str:
|
|
|
968
1079
|
)
|
|
969
1080
|
|
|
970
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
|
+
|
|
971
1093
|
# Pine ``math.sign`` returns a float, propagates numeric ``na``, and must not
|
|
972
1094
|
# evaluate its argument more than once. Keep the callable separate from the
|
|
973
1095
|
# call template because the stable-runtime TA reset fallback also substitutes
|
|
@@ -1003,18 +1125,30 @@ STR_FUNC_MAP = {
|
|
|
1003
1125
|
"contains": lambda args: f"({args[0]}.find({args[1]}) != std::string::npos)",
|
|
1004
1126
|
"startswith": lambda args: f"({args[0]}.substr(0, {args[1]}.length()) == {args[1]})",
|
|
1005
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)",
|
|
1006
|
-
"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; }}()",
|
|
1007
1129
|
"substring": None, # handled separately (2 vs 3 args)
|
|
1008
|
-
"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; }}()",
|
|
1009
1131
|
"replace": None, # handled separately (3 vs 4 args)
|
|
1010
|
-
"lower": lambda args: f"[&](){{ std::string
|
|
1011
|
-
"upper": lambda args: f"[&](){{ std::string
|
|
1012
|
-
"trim": lambda args: f'[&](){{ std::string
|
|
1013
|
-
"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
|
+
),
|
|
1014
1140
|
"match": lambda args: f'pine_str_match({args[0]}, {args[1]})',
|
|
1015
1141
|
"split": lambda args: f'pine_str_split({args[0]}, {args[1]})',
|
|
1016
1142
|
"format": None, # handled separately
|
|
1017
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
|
+
}
|
|
1018
1152
|
|
|
1019
1153
|
|
|
1020
1154
|
# ---------------------------------------------------------------------------
|