@pineforge/codegen-pyodide 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/pineforge_codegen/__init__.py +6 -5
- package/pineforge_codegen/analyzer/base.py +3 -3
- package/pineforge_codegen/analyzer/types.py +7 -3
- package/pineforge_codegen/codegen/base.py +26 -16
- package/pineforge_codegen/codegen/checked_settings.py +113 -2
- package/pineforge_codegen/codegen/collection_history.py +163 -12
- package/pineforge_codegen/codegen/emit_top.py +24 -8
- package/pineforge_codegen/codegen/helpers.py +19 -1
- package/pineforge_codegen/codegen/host_members.py +1 -0
- package/pineforge_codegen/codegen/input.py +23 -8
- package/pineforge_codegen/codegen/run_stops.py +52 -0
- package/pineforge_codegen/codegen/security.py +12 -5
- package/pineforge_codegen/codegen/tables.py +132 -28
- package/pineforge_codegen/codegen/tv_number_format.py +9 -2
- package/pineforge_codegen/codegen/types.py +85 -4
- package/pineforge_codegen/codegen/visit_call.py +174 -55
- package/pineforge_codegen/codegen/visit_expr.py +32 -6
- package/pineforge_codegen/codegen/visit_stmt.py +3 -0
- package/pineforge_codegen/collection_history.py +122 -20
- package/pineforge_codegen/diagnostics_catalog.json +1 -0
- package/pineforge_codegen/external_requests.py +30 -9
- package/pineforge_codegen/matrix_overloads.py +7 -0
- package/pineforge_codegen/parser.py +7 -2
- package/pineforge_codegen/signatures.py +2 -2
- package/pineforge_codegen/support_checker.py +82 -0
- package/pineforge_codegen-1.4.0.tar.gz +0 -0
- package/release.json +2 -2
- package/tables.json +7 -3
- package/pineforge_codegen-1.3.0.tar.gz +0 -0
|
@@ -169,6 +169,7 @@ from .helpers import (
|
|
|
169
169
|
from .tables import (
|
|
170
170
|
ARRAY_DRAWING_NEW_CTORS,
|
|
171
171
|
ARRAY_METHODS,
|
|
172
|
+
ARRAY_OPTIONAL_PARAMS,
|
|
172
173
|
BAR_FIELDS,
|
|
173
174
|
BAR_SERIES_PUSH,
|
|
174
175
|
CHECKED_ARRAY_METHOD_KWARGS,
|
|
@@ -179,7 +180,10 @@ from .tables import (
|
|
|
179
180
|
MATH_FUNC_MAP,
|
|
180
181
|
MATRIX_METHODS,
|
|
181
182
|
MATRIX_METHOD_KWARGS,
|
|
183
|
+
MATRIX_METHOD_KWARG_ALIASES,
|
|
182
184
|
MATRIX_NUMERIC_ONLY,
|
|
185
|
+
MATRIX_OPTIONAL_PARAMS,
|
|
186
|
+
MATRIX_RECEIVER_KEYWORD,
|
|
183
187
|
SKIP_FUNC_NAMES,
|
|
184
188
|
SKIP_NAMESPACES,
|
|
185
189
|
SKIP_VAR_TYPES,
|
|
@@ -188,6 +192,7 @@ from .tables import (
|
|
|
188
192
|
TIME_FIELD_EXPRS,
|
|
189
193
|
_math_minmax_na_expr,
|
|
190
194
|
_math_round_digits_expr,
|
|
195
|
+
_matrix_functional_receiver,
|
|
191
196
|
_merge_kwargs,
|
|
192
197
|
)
|
|
193
198
|
|
|
@@ -690,10 +695,73 @@ class CallVisitor:
|
|
|
690
695
|
|
|
691
696
|
def _array_method_arg_nodes(self, method: str, node: FuncCall) -> list:
|
|
692
697
|
"""Merge checked-array method kwargs into Pine signature order."""
|
|
693
|
-
|
|
694
|
-
|
|
698
|
+
return self._collection_arg_nodes("array", method, node, False)
|
|
699
|
+
|
|
700
|
+
def _collection_arg_nodes(self, namespace, method, node, functional):
|
|
701
|
+
"""The arguments of a matrix or checked-array method call in Pine's
|
|
702
|
+
parameter order (the receiver first in the namespace form).
|
|
703
|
+
|
|
704
|
+
A keyword argument binds to its parameter's own slot, never shifted
|
|
705
|
+
left over a gap, and the gap before a later argument takes the
|
|
706
|
+
omitted optional parameter's default (a ``None`` default is the
|
|
707
|
+
receiver's own extent, which the method's template reads). A keyword
|
|
708
|
+
the lowering does not consume -- an unknown name, a parameter given
|
|
709
|
+
twice -- is refused, as is a gap before a required parameter. A call
|
|
710
|
+
without keywords, and an array method outside the checked-index
|
|
711
|
+
table, keep their arguments as written."""
|
|
712
|
+
if not node.kwargs:
|
|
695
713
|
return list(node.args)
|
|
696
|
-
|
|
714
|
+
if namespace == "matrix":
|
|
715
|
+
slots = list(MATRIX_METHOD_KWARGS.get(method, []))
|
|
716
|
+
optional = MATRIX_OPTIONAL_PARAMS.get(method, {})
|
|
717
|
+
aliases = MATRIX_METHOD_KWARG_ALIASES.get(method, {})
|
|
718
|
+
receiver = MATRIX_RECEIVER_KEYWORD.get(method, "id")
|
|
719
|
+
elif method in CHECKED_ARRAY_METHOD_KWARGS:
|
|
720
|
+
slots = list(CHECKED_ARRAY_METHOD_KWARGS[method])
|
|
721
|
+
optional = ARRAY_OPTIONAL_PARAMS.get(method, {})
|
|
722
|
+
aliases = {}
|
|
723
|
+
receiver = "id"
|
|
724
|
+
else:
|
|
725
|
+
return list(node.args)
|
|
726
|
+
if functional:
|
|
727
|
+
slots.insert(0, receiver)
|
|
728
|
+
arguments = list(node.args)
|
|
729
|
+
for name, value in node.kwargs.items():
|
|
730
|
+
slot = aliases.get(name, name)
|
|
731
|
+
if slot not in slots:
|
|
732
|
+
self._collection_argument_error(node, namespace, method, "unknown", name)
|
|
733
|
+
index = slots.index(slot)
|
|
734
|
+
if index < len(arguments) and arguments[index] is not None:
|
|
735
|
+
self._collection_argument_error(node, namespace, method, "twice", name)
|
|
736
|
+
arguments.extend([None] * (index + 1 - len(arguments)))
|
|
737
|
+
arguments[index] = value
|
|
738
|
+
for index, argument in enumerate(arguments):
|
|
739
|
+
if argument is not None:
|
|
740
|
+
continue
|
|
741
|
+
if slots[index] not in optional:
|
|
742
|
+
self._collection_argument_error(node, namespace, method, "missing", slots[index])
|
|
743
|
+
default = optional[slots[index]]
|
|
744
|
+
arguments[index] = None if default is None else NumberLiteral(value=default)
|
|
745
|
+
return arguments
|
|
746
|
+
|
|
747
|
+
def _collection_argument_error(self, node, namespace, method, problem, name):
|
|
748
|
+
"""Refuse a collection call's keyword binding with the diagnostic its
|
|
749
|
+
namespace already reports for a call that does not bind."""
|
|
750
|
+
if namespace == "matrix":
|
|
751
|
+
self._codegen_error(
|
|
752
|
+
node, f"matrix.{method}: wrong number of arguments",
|
|
753
|
+
hint="Check Pine v6 matrix method signature (positional vs keyword).")
|
|
754
|
+
key = ".".join((namespace, method))
|
|
755
|
+
if problem == "unknown":
|
|
756
|
+
self._codegen_error(node, f"{key}: unknown keyword argument '{name}'")
|
|
757
|
+
if problem == "twice":
|
|
758
|
+
self._codegen_error(
|
|
759
|
+
node, f"{key}: argument '{name}' passed both positionally and by keyword")
|
|
760
|
+
self._codegen_error(node, f"{key}: missing required argument '{name}'")
|
|
761
|
+
|
|
762
|
+
def _matrix_method_arg_nodes(self, method, node, functional=False):
|
|
763
|
+
arguments = self._collection_arg_nodes("matrix", method, node, functional)
|
|
764
|
+
return arguments[1:] if functional else arguments
|
|
697
765
|
|
|
698
766
|
def _matrix_bool_value_args(
|
|
699
767
|
self, method: str, args: list[str], arg_nodes: list,
|
|
@@ -740,12 +808,7 @@ class CallVisitor:
|
|
|
740
808
|
"array.copy: expected exactly one receiver 'id'",
|
|
741
809
|
hint="Use array.copy(source) or array.copy(id=source).",
|
|
742
810
|
)
|
|
743
|
-
|
|
744
|
-
if param_names is None:
|
|
745
|
-
return list(node.args)
|
|
746
|
-
return _merge_kwargs(
|
|
747
|
-
node.args, node.kwargs, ["id", *param_names], lambda arg: arg
|
|
748
|
-
)
|
|
811
|
+
return self._collection_arg_nodes("array", method, node, True)
|
|
749
812
|
|
|
750
813
|
def _map_identifier_is_visible_binding(self, node: FuncCall) -> bool:
|
|
751
814
|
"""Whether ``map`` is a lexical value at this exact source position.
|
|
@@ -1663,6 +1726,18 @@ class CallVisitor:
|
|
|
1663
1726
|
return self._matrix_history_method_expr(
|
|
1664
1727
|
callee.object, callee.member, recv_spec, node)
|
|
1665
1728
|
|
|
1729
|
+
if (isinstance(callee, MemberAccess)
|
|
1730
|
+
and isinstance(callee.object, (FuncCall, Ternary))
|
|
1731
|
+
and callee.member in MATRIX_METHODS):
|
|
1732
|
+
receiver_spec = self._type_spec_from_expr(callee.object)
|
|
1733
|
+
if receiver_spec is not None and receiver_spec.kind == "matrix":
|
|
1734
|
+
self._check_matrix_method_allowed(callee.member, receiver_spec, node)
|
|
1735
|
+
raw_args = self._matrix_method_arg_nodes(callee.member, node)
|
|
1736
|
+
arguments = self._matrix_bool_value_args(
|
|
1737
|
+
callee.member, [self._visit_expr(arg) if arg is not None else None for arg in raw_args], raw_args, receiver_spec)
|
|
1738
|
+
return self._matrix_method_expr(
|
|
1739
|
+
self._visit_expr(callee.object), callee.member, arguments, raw_args, node)
|
|
1740
|
+
|
|
1666
1741
|
# chart.point.now/new/from_index/from_time/copy — REAL data (a ChartPoint
|
|
1667
1742
|
# aggregate). Routed here BEFORE the obj.field.method receiver logic,
|
|
1668
1743
|
# which would otherwise mis-treat ``chart.point`` as a receiver object
|
|
@@ -1821,20 +1896,16 @@ class CallVisitor:
|
|
|
1821
1896
|
):
|
|
1822
1897
|
arr = self._collection_receiver_expr(oname)
|
|
1823
1898
|
self._check_matrix_method_allowed(meth_raw, recv_spec, node)
|
|
1824
|
-
|
|
1825
|
-
raw_args = (
|
|
1826
|
-
_merge_kwargs(node.args, node.kwargs, param_names, lambda a: a)
|
|
1827
|
-
if param_names and node.kwargs else list(node.args)
|
|
1828
|
-
)
|
|
1899
|
+
raw_args = self._matrix_method_arg_nodes(meth_raw, node)
|
|
1829
1900
|
margs = self._matrix_bool_value_args(
|
|
1830
1901
|
meth_raw,
|
|
1831
|
-
[self._visit_expr(a) for a in raw_args],
|
|
1902
|
+
[self._visit_expr(a) if a is not None else None for a in raw_args],
|
|
1832
1903
|
raw_args,
|
|
1833
1904
|
recv_spec,
|
|
1834
1905
|
)
|
|
1835
1906
|
fn = MATRIX_METHODS[meth_raw]
|
|
1836
1907
|
try:
|
|
1837
|
-
return
|
|
1908
|
+
return self._matrix_method_expr(arr, meth_raw, margs, raw_args, node)
|
|
1838
1909
|
except IndexError:
|
|
1839
1910
|
self._codegen_error(
|
|
1840
1911
|
node,
|
|
@@ -2054,20 +2125,16 @@ class CallVisitor:
|
|
|
2054
2125
|
):
|
|
2055
2126
|
arr = self._collection_receiver_expr(namespace)
|
|
2056
2127
|
self._check_matrix_method_allowed(func_name, namespace_spec, node)
|
|
2057
|
-
|
|
2058
|
-
raw_args = (
|
|
2059
|
-
_merge_kwargs(node.args, node.kwargs, param_names, lambda a: a)
|
|
2060
|
-
if param_names and node.kwargs else list(node.args)
|
|
2061
|
-
)
|
|
2128
|
+
raw_args = self._matrix_method_arg_nodes(func_name, node)
|
|
2062
2129
|
args = self._matrix_bool_value_args(
|
|
2063
2130
|
func_name,
|
|
2064
|
-
[self._visit_expr(a) for a in raw_args],
|
|
2131
|
+
[self._visit_expr(a) if a is not None else None for a in raw_args],
|
|
2065
2132
|
raw_args,
|
|
2066
2133
|
namespace_spec,
|
|
2067
2134
|
)
|
|
2068
2135
|
fn = MATRIX_METHODS[func_name]
|
|
2069
2136
|
try:
|
|
2070
|
-
return
|
|
2137
|
+
return self._matrix_method_expr(arr, func_name, args, raw_args, node)
|
|
2071
2138
|
except IndexError:
|
|
2072
2139
|
self._codegen_error(
|
|
2073
2140
|
node,
|
|
@@ -2108,7 +2175,22 @@ class CallVisitor:
|
|
|
2108
2175
|
init_val = self._array_init_value_expr(elem_spec, node.args[1])
|
|
2109
2176
|
else:
|
|
2110
2177
|
init_val = init_default
|
|
2111
|
-
return
|
|
2178
|
+
return (
|
|
2179
|
+
"[&](auto _pf_array_new_size, auto _pf_array_new_value){ "
|
|
2180
|
+
"using _pf_array_new_size_type=decltype(_pf_array_new_size); "
|
|
2181
|
+
"if constexpr(!std::is_same_v<_pf_array_new_size_type,bool>) { "
|
|
2182
|
+
"if(is_na(_pf_array_new_size)) "
|
|
2183
|
+
'_PF_ARRAY_STOP("size_invalid", "new", "cannot create std::vector larger than max_size()"); '
|
|
2184
|
+
"} if(!std::isfinite((double)_pf_array_new_size)||"
|
|
2185
|
+
"_pf_array_new_size<0||"
|
|
2186
|
+
"(long double)_pf_array_new_size>(long double)std::numeric_limits<size_t>::max()) "
|
|
2187
|
+
'_PF_ARRAY_STOP("size_invalid", "new", "cannot create std::vector larger than max_size()"); '
|
|
2188
|
+
f"try {{ return {cpp_type}((size_t)(_pf_array_new_size), _pf_array_new_value); "
|
|
2189
|
+
"} catch (const std::length_error& _pf_array_new_error) { "
|
|
2190
|
+
'_PF_ARRAY_STOP("size_invalid", "new", _pf_array_new_error.what()); '
|
|
2191
|
+
f"return {cpp_type}(); }} "
|
|
2192
|
+
f"}}(({size_arg}), ({init_val}))"
|
|
2193
|
+
)
|
|
2112
2194
|
return f"{cpp_type}()"
|
|
2113
2195
|
if func_name == "from":
|
|
2114
2196
|
spec = self._type_spec_from_expr(node) or TypeSpec.array(TypeSpec.primitive("float"))
|
|
@@ -2282,8 +2364,8 @@ class CallVisitor:
|
|
|
2282
2364
|
# runtime.error() and other runtime.* calls
|
|
2283
2365
|
if namespace == "runtime":
|
|
2284
2366
|
if func_name == "error":
|
|
2285
|
-
|
|
2286
|
-
msg_arg =
|
|
2367
|
+
message = node.args[0] if node.args else node.kwargs.get("message")
|
|
2368
|
+
msg_arg = self._visit_expr(message) if message is not None else '""'
|
|
2287
2369
|
return f'pine_runtime_error({msg_arg})'
|
|
2288
2370
|
return '"" /* unsupported runtime */'
|
|
2289
2371
|
|
|
@@ -2615,10 +2697,31 @@ class CallVisitor:
|
|
|
2615
2697
|
return f"na<double>() /* unsupported: ta.{func_name} */"
|
|
2616
2698
|
|
|
2617
2699
|
if namespace == "syminfo":
|
|
2618
|
-
if func_name
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2700
|
+
if func_name in {"prefix", "ticker"}:
|
|
2701
|
+
symbol = node.args[0] if node.args else node.kwargs.get("symbol")
|
|
2702
|
+
symbol_expr = self._visit_expr(symbol) if symbol is not None else "syminfo_.tickerid"
|
|
2703
|
+
if symbol_expr == "syminfo_.tickerid":
|
|
2704
|
+
return ("_pf_derive_prefix(syminfo_.tickerid)" if func_name == "prefix"
|
|
2705
|
+
else "syminfo_.ticker")
|
|
2706
|
+
if func_name == "prefix":
|
|
2707
|
+
result_expr = (
|
|
2708
|
+
"_pf_colon == std::string::npos "
|
|
2709
|
+
"|| _pf_colon + 1 == _pf_symbol.size() "
|
|
2710
|
+
"? std::string() : _pf_symbol.substr(0, _pf_colon)"
|
|
2711
|
+
)
|
|
2712
|
+
else:
|
|
2713
|
+
result_expr = (
|
|
2714
|
+
"_pf_colon == std::string::npos "
|
|
2715
|
+
"? _pf_symbol : _pf_symbol.substr(_pf_colon + 1)"
|
|
2716
|
+
)
|
|
2717
|
+
return (
|
|
2718
|
+
"([](const auto& _pf_symbol) { "
|
|
2719
|
+
"if constexpr (std::is_same_v<std::decay_t<decltype(_pf_symbol)>, std::string>) { "
|
|
2720
|
+
"const auto _pf_colon = _pf_symbol.find(':'); "
|
|
2721
|
+
f"return {result_expr}; "
|
|
2722
|
+
"} else { return std::string(); } "
|
|
2723
|
+
f"}})({symbol_expr})"
|
|
2724
|
+
)
|
|
2622
2725
|
return f"na<double>() /* unsupported: syminfo.{func_name} */"
|
|
2623
2726
|
|
|
2624
2727
|
# str.* fallback now handled by _visit_str_call above
|
|
@@ -2643,41 +2746,48 @@ class CallVisitor:
|
|
|
2643
2746
|
if len(args_e) > 2 else self._default_for_spec(elem_spec)
|
|
2644
2747
|
)
|
|
2645
2748
|
return f"PineGenericMatrix<{cpp_t}>::new_({rows}, {cols}, {init})"
|
|
2646
|
-
|
|
2749
|
+
matrix_receiver = _matrix_functional_receiver(func_name, node)
|
|
2750
|
+
if func_name in MATRIX_METHODS and matrix_receiver is None:
|
|
2751
|
+
self._codegen_error(
|
|
2752
|
+
node, f"matrix.{func_name}: wrong number of arguments",
|
|
2753
|
+
hint="Check Pine v6 matrix method signature (positional vs keyword).")
|
|
2754
|
+
if func_name in MATRIX_METHODS and matrix_receiver is not None:
|
|
2647
2755
|
from ..ast_nodes import Identifier as _Ident
|
|
2648
2756
|
if func_name in MATRIX_NUMERIC_ONLY:
|
|
2649
|
-
recv_node =
|
|
2757
|
+
recv_node = matrix_receiver
|
|
2650
2758
|
if history_annotation(recv_node) is not None:
|
|
2651
2759
|
# A matrix's history is a matrix of its type.
|
|
2652
2760
|
recv_node = recv_node.object
|
|
2653
|
-
if
|
|
2654
|
-
self.
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2761
|
+
if func_name == "sum":
|
|
2762
|
+
recv_spec = self._type_spec_from_expr(recv_node)
|
|
2763
|
+
if recv_spec is None or recv_spec.kind != "matrix":
|
|
2764
|
+
name = func_name
|
|
2765
|
+
self._codegen_error(node, f"matrix.{name}(...) is not implemented in PineForge runtime.")
|
|
2766
|
+
else:
|
|
2767
|
+
if not isinstance(recv_node, _Ident):
|
|
2768
|
+
self._codegen_error(node, f"matrix.{func_name} receiver must be a variable reference")
|
|
2769
|
+
recv_name = recv_node.name
|
|
2770
|
+
recv_spec = self._collection_spec_for_name(recv_name)
|
|
2771
|
+
if recv_spec is None or recv_spec.kind != "matrix":
|
|
2772
|
+
self._codegen_error(node, f"matrix.{func_name}: receiver '{recv_name}' is not a known matrix variable")
|
|
2659
2773
|
self._check_matrix_method_allowed(func_name, recv_spec, node)
|
|
2660
2774
|
if func_name == "sort":
|
|
2661
|
-
if isinstance(
|
|
2662
|
-
recv_name =
|
|
2775
|
+
if isinstance(matrix_receiver, _Ident):
|
|
2776
|
+
recv_name = matrix_receiver.name
|
|
2663
2777
|
recv_spec = self._collection_spec_for_name(recv_name)
|
|
2664
2778
|
if recv_spec is not None and recv_spec.kind == "matrix":
|
|
2665
2779
|
self._check_matrix_method_allowed(func_name, recv_spec, node)
|
|
2666
|
-
obj = self._visit_expr(
|
|
2667
|
-
|
|
2668
|
-
raw_rest = (
|
|
2669
|
-
_merge_kwargs(node.args[1:], node.kwargs, param_names, lambda a: a)
|
|
2670
|
-
if param_names else list(node.args[1:])
|
|
2671
|
-
)
|
|
2780
|
+
obj = self._visit_expr(matrix_receiver)
|
|
2781
|
+
raw_rest = self._matrix_method_arg_nodes(func_name, node, True)
|
|
2672
2782
|
rest = self._matrix_bool_value_args(
|
|
2673
2783
|
func_name,
|
|
2674
|
-
[self._visit_expr(a) for a in raw_rest],
|
|
2784
|
+
[self._visit_expr(a) if a is not None else None for a in raw_rest],
|
|
2675
2785
|
raw_rest,
|
|
2676
|
-
self._type_spec_from_expr(
|
|
2786
|
+
self._type_spec_from_expr(matrix_receiver),
|
|
2677
2787
|
)
|
|
2678
2788
|
fn = MATRIX_METHODS[func_name]
|
|
2679
2789
|
try:
|
|
2680
|
-
return
|
|
2790
|
+
return self._matrix_method_expr(obj, func_name, rest, raw_rest, node)
|
|
2681
2791
|
except IndexError:
|
|
2682
2792
|
self._codegen_error(
|
|
2683
2793
|
node,
|
|
@@ -3631,13 +3741,22 @@ class CallVisitor:
|
|
|
3631
3741
|
return 'std::string("")'
|
|
3632
3742
|
|
|
3633
3743
|
if func_name == "substring":
|
|
3634
|
-
if len(args)
|
|
3635
|
-
|
|
3744
|
+
if len(args) in (2, 3):
|
|
3745
|
+
end_parameter = ", auto _pf_substring_end" if len(args) == 3 else ""
|
|
3746
|
+
count = ", _pf_substring_end - _pf_substring_begin" if len(args) == 3 else ""
|
|
3747
|
+
|
|
3748
|
+
def checked_substring(bound):
|
|
3749
|
+
return (
|
|
3750
|
+
f"[&](const std::string& _pf_substring_text, auto _pf_substring_begin{end_parameter}){{ "
|
|
3751
|
+
"try { return _pf_substring_text.substr(_pf_substring_begin"
|
|
3752
|
+
f"{count}); }} catch (const std::out_of_range& _pf_substring_error) {{ "
|
|
3753
|
+
'_PF_STRING_STOP("substring_out_of_range", _pf_substring_error.what()); '
|
|
3754
|
+
"return std::string(); } "
|
|
3755
|
+
"}(" + ", ".join(f"({arg})" for arg in bound) + ")"
|
|
3756
|
+
)
|
|
3757
|
+
|
|
3636
3758
|
return evaluate_args_once(
|
|
3637
|
-
args, (
|
|
3638
|
-
"_pf_str_a")
|
|
3639
|
-
elif len(args) == 2:
|
|
3640
|
-
return f"{args[0]}.substr({args[1]})"
|
|
3759
|
+
args, range(len(args)), checked_substring, "_pf_substring_arg")
|
|
3641
3760
|
return 'std::string("")'
|
|
3642
3761
|
|
|
3643
3762
|
if func_name == "format":
|
|
@@ -87,6 +87,8 @@ classes from ``..ast_nodes``.
|
|
|
87
87
|
|
|
88
88
|
from __future__ import annotations
|
|
89
89
|
|
|
90
|
+
from .run_stops import request_stop
|
|
91
|
+
|
|
90
92
|
from ..errors import Phase
|
|
91
93
|
from ..symbols import TypeSpec
|
|
92
94
|
from ..external_requests import UNPINNED_ANNOTATION
|
|
@@ -311,12 +313,18 @@ class ExprVisitor:
|
|
|
311
313
|
while isinstance(links[-1].false_val, Ternary):
|
|
312
314
|
links.append(links[-1].false_val)
|
|
313
315
|
string_na = self._ternary_string_na(links)
|
|
316
|
+
array_history_type = self._array_history_value_expr_cpp_type(node)
|
|
314
317
|
if len(links) < self._FLAT_TERNARY_CHAIN:
|
|
315
318
|
c = self._coerce_bool_expr(
|
|
316
319
|
self._visit_expr(node.condition), node.condition
|
|
317
320
|
)
|
|
318
321
|
t = self._ternary_arm(node.true_val, string_na)
|
|
319
322
|
f = self._ternary_arm(node.false_val, string_na)
|
|
323
|
+
if array_history_type is not None:
|
|
324
|
+
t = (f"{array_history_type}{{}}" if self._is_na_expr(node.true_val)
|
|
325
|
+
else f"{array_history_type}({t})")
|
|
326
|
+
f = (f"{array_history_type}{{}}" if self._is_na_expr(node.false_val)
|
|
327
|
+
else f"{array_history_type}({f})")
|
|
320
328
|
return f"(({c}) ? ({t}) : ({f}))"
|
|
321
329
|
arms = []
|
|
322
330
|
for link in links:
|
|
@@ -324,8 +332,15 @@ class ExprVisitor:
|
|
|
324
332
|
self._visit_expr(link.condition), link.condition
|
|
325
333
|
)
|
|
326
334
|
t = self._ternary_arm(link.true_val, string_na)
|
|
335
|
+
if array_history_type is not None:
|
|
336
|
+
t = (f"{array_history_type}{{}}" if self._is_na_expr(link.true_val)
|
|
337
|
+
else f"{array_history_type}({t})")
|
|
327
338
|
arms.append(f"({c}) ? ({t}) : ")
|
|
328
|
-
|
|
339
|
+
tail = self._ternary_arm(links[-1].false_val, string_na)
|
|
340
|
+
if array_history_type is not None:
|
|
341
|
+
tail = (f"{array_history_type}{{}}" if self._is_na_expr(links[-1].false_val)
|
|
342
|
+
else f"{array_history_type}({tail})")
|
|
343
|
+
return f"({''.join(arms)}({tail}))"
|
|
329
344
|
|
|
330
345
|
def _ternary_string_na(self, links: list[Ternary]) -> bool:
|
|
331
346
|
"""A ``?:`` chain whose every value arm but a bare ``na`` is a
|
|
@@ -493,15 +508,14 @@ class ExprVisitor:
|
|
|
493
508
|
value = self._visit_expr(node)
|
|
494
509
|
finally:
|
|
495
510
|
node.annotations = notes
|
|
496
|
-
|
|
511
|
+
stop = request_stop(marker)
|
|
512
|
+
if "ref" in marker:
|
|
497
513
|
# Evaluated first: a recorded request sets its flag where it is
|
|
498
514
|
# evaluated, which may be this read.
|
|
499
|
-
stop = (f'pine_runtime_error(std::string('
|
|
500
|
-
f'"{self._cpp_string_escape(marker["message"])}"))')
|
|
501
515
|
return (f"([&]() {{ auto _pf_read = {value}; "
|
|
502
516
|
f"if ({self._request_data_missing(marker['ref'])}) {stop}; "
|
|
503
517
|
f"return _pf_read; }}())")
|
|
504
|
-
return (f'([&]() {{
|
|
518
|
+
return (f'([&]() {{ {stop}; '
|
|
505
519
|
f"return {value}; }}())")
|
|
506
520
|
|
|
507
521
|
def _identifier_reads_series(self, node: Identifier) -> bool:
|
|
@@ -1960,7 +1974,13 @@ class ExprVisitor:
|
|
|
1960
1974
|
"collection IDs, so this result can differ from TradingView "
|
|
1961
1975
|
"and a missing index is not represented faithfully.",
|
|
1962
1976
|
)
|
|
1963
|
-
|
|
1977
|
+
receiver = self._collection_receiver_expr(name)
|
|
1978
|
+
if spec.kind == "array":
|
|
1979
|
+
return self._legacy_array_history_element(
|
|
1980
|
+
receiver, spec, series_idx,
|
|
1981
|
+
mutable=getattr(self, "_udt_assignment_target", None) is node,
|
|
1982
|
+
)
|
|
1983
|
+
return f"{receiver}[{idx}]"
|
|
1964
1984
|
# Handle strategy.* history access (e.g., strategy.position_size[1])
|
|
1965
1985
|
if isinstance(node.object, MemberAccess):
|
|
1966
1986
|
if isinstance(node.object.object, Identifier):
|
|
@@ -2100,4 +2120,10 @@ class ExprVisitor:
|
|
|
2100
2120
|
self._warn_untracked_reference_history(node)
|
|
2101
2121
|
return obj
|
|
2102
2122
|
idx_int = self._history_offset_cpp(idx, node.index)
|
|
2123
|
+
object_spec = self._type_spec_from_expr(node.object)
|
|
2124
|
+
if object_spec is not None and object_spec.kind == "array":
|
|
2125
|
+
return self._legacy_array_history_element(
|
|
2126
|
+
obj, object_spec, idx_int,
|
|
2127
|
+
mutable=getattr(self, "_udt_assignment_target", None) is node,
|
|
2128
|
+
)
|
|
2103
2129
|
return f"{obj}[{idx_int}]"
|
|
@@ -1822,6 +1822,9 @@ class StmtVisitor:
|
|
|
1822
1822
|
self._current_counted_loop_vars = (
|
|
1823
1823
|
self._current_counted_loop_vars - {node.var, *(node.vars or ())})
|
|
1824
1824
|
iterable_spec = self._type_spec_from_expr(node.iterable)
|
|
1825
|
+
if (iterable_spec is not None and iterable_spec.kind == "array"
|
|
1826
|
+
and self._array_history_value_names()):
|
|
1827
|
+
iterable = f"_pf_array_id({iterable})"
|
|
1825
1828
|
elem_spec = (
|
|
1826
1829
|
iterable_spec.element
|
|
1827
1830
|
if iterable_spec is not None and iterable_spec.kind == "array"
|