@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
|
@@ -565,6 +565,7 @@ NAME_ECHO_STRING_MEMBERS = {
|
|
|
565
565
|
|
|
566
566
|
def _checked_array_index_prelude(
|
|
567
567
|
*,
|
|
568
|
+
method: str,
|
|
568
569
|
normalize_negative: bool = True,
|
|
569
570
|
allow_size: bool = False,
|
|
570
571
|
name: str = "index",
|
|
@@ -602,18 +603,18 @@ def _checked_array_index_prelude(
|
|
|
602
603
|
f"using {raw_type}=std::decay_t<decltype({raw_value})>; "
|
|
603
604
|
f"if constexpr(!std::is_same_v<{raw_type},bool>) {{ "
|
|
604
605
|
f"if(is_na({raw_value})) "
|
|
605
|
-
"
|
|
606
|
+
f'_PF_ARRAY_STOP("index_out_of_bounds", "{method}", std::string("Index na is out of bounds. Array size is ")+ '
|
|
606
607
|
"std::to_string((int64_t)__pf_array.size())); } "
|
|
607
608
|
f"if constexpr(std::is_floating_point_v<{raw_type}>) {{ "
|
|
608
609
|
f"if(!std::isfinite({raw_value})) {{ "
|
|
609
610
|
f"std::string {raw_text}={raw_value}>0?\"inf\":\"-inf\"; "
|
|
610
|
-
f"
|
|
611
|
+
f'_PF_ARRAY_STOP("index_out_of_bounds", "{method}", std::string("Index ")+{raw_text}+'
|
|
611
612
|
"\" is out of bounds. Array size is \"+"
|
|
612
613
|
"std::to_string((int64_t)__pf_array.size())); } "
|
|
613
614
|
f"long double {raw_wide}=(long double){raw_value}; "
|
|
614
615
|
f"if({raw_wide}<(long double)std::numeric_limits<int64_t>::min()||"
|
|
615
616
|
f"{raw_wide}>(long double)std::numeric_limits<int64_t>::max()) "
|
|
616
|
-
"
|
|
617
|
+
f'_PF_ARRAY_STOP("index_out_of_bounds", "{method}", std::string("Index ")+ '
|
|
617
618
|
f"std::to_string((double){raw_value})+"
|
|
618
619
|
"\" is out of bounds. Array size is \"+"
|
|
619
620
|
"std::to_string((int64_t)__pf_array.size())); } "
|
|
@@ -621,12 +622,12 @@ def _checked_array_index_prelude(
|
|
|
621
622
|
f"int64_t {size}=(int64_t)__pf_array.size(); "
|
|
622
623
|
f"int64_t {checked}={checked_index}; "
|
|
623
624
|
f"if({checked}<0||{checked}{upper}{size}) "
|
|
624
|
-
f"
|
|
625
|
+
f'_PF_ARRAY_STOP("index_out_of_bounds", "{method}", std::string("Index ")+std::to_string({raw})+'
|
|
625
626
|
f"\" is out of bounds. Array size is \"+std::to_string({size})); "
|
|
626
627
|
)
|
|
627
628
|
|
|
628
629
|
|
|
629
|
-
def _checked_array_range_prelude(*, reject_inverted: bool = True) -> str:
|
|
630
|
+
def _checked_array_range_prelude(*, method: str, reject_inverted: bool = True) -> str:
|
|
630
631
|
"""Validate the half-open ``[index_from, index_to)`` range of fill/slice.
|
|
631
632
|
|
|
632
633
|
Both endpoints are checked with ``allow_size`` (``index_to`` is exclusive,
|
|
@@ -637,14 +638,14 @@ def _checked_array_range_prelude(*, reject_inverted: bool = True) -> str:
|
|
|
637
638
|
"""
|
|
638
639
|
return (
|
|
639
640
|
_checked_array_index_prelude(
|
|
640
|
-
normalize_negative=False, allow_size=True, name="index_from"
|
|
641
|
+
method=method, normalize_negative=False, allow_size=True, name="index_from"
|
|
641
642
|
)
|
|
642
643
|
+ _checked_array_index_prelude(
|
|
643
|
-
normalize_negative=False, allow_size=True, name="index_to"
|
|
644
|
+
method=method, normalize_negative=False, allow_size=True, name="index_to"
|
|
644
645
|
)
|
|
645
646
|
+ (
|
|
646
647
|
"if(__pf_array_index_from>__pf_array_index_to) "
|
|
647
|
-
|
|
648
|
+
'_PF_ARRAY_STOP("slice_range_inverted", "slice", "Index \'from\' should be less than index \'to\'."); '
|
|
648
649
|
if reject_inverted else ""
|
|
649
650
|
)
|
|
650
651
|
)
|
|
@@ -656,7 +657,7 @@ def _checked_array_insert(a: str, args: list[str], normalize_negative: bool = Tr
|
|
|
656
657
|
``index == size`` appends, so the bound is ``index <= size``; a negative
|
|
657
658
|
index is end-relative, exactly as for ``get``/``set``/``remove``.
|
|
658
659
|
"""
|
|
659
|
-
check = _checked_array_index_prelude(allow_size=True,
|
|
660
|
+
check = _checked_array_index_prelude(method="insert", allow_size=True,
|
|
660
661
|
normalize_negative=normalize_negative)
|
|
661
662
|
return (
|
|
662
663
|
"[&](auto&& __pf_array){ "
|
|
@@ -671,7 +672,7 @@ def _checked_array_insert(a: str, args: list[str], normalize_negative: bool = Tr
|
|
|
671
672
|
|
|
672
673
|
def _checked_array_fill_range(a: str, args: list[str]) -> str:
|
|
673
674
|
"""``array.fill(id, value, index_from, index_to)`` — bounded range fill."""
|
|
674
|
-
check = _checked_array_range_prelude(reject_inverted=False)
|
|
675
|
+
check = _checked_array_range_prelude(method="fill", reject_inverted=False)
|
|
675
676
|
return (
|
|
676
677
|
"[&](auto&& __pf_array){ "
|
|
677
678
|
"return [&](auto&& __pf_array_value){ "
|
|
@@ -691,7 +692,7 @@ def checked_array_slice(a: str, args: list[str], *, result_type: str) -> str:
|
|
|
691
692
|
``result_type`` stays caller-supplied so the typed method lane keeps
|
|
692
693
|
emitting the receiver's own element type; only the bounds checks are new.
|
|
693
694
|
"""
|
|
694
|
-
check = _checked_array_range_prelude()
|
|
695
|
+
check = _checked_array_range_prelude(method="slice")
|
|
695
696
|
return (
|
|
696
697
|
"[&](auto&& __pf_array){ "
|
|
697
698
|
"return [&](auto&& __pf_raw_index_from_value){ "
|
|
@@ -704,7 +705,7 @@ def checked_array_slice(a: str, args: list[str], *, result_type: str) -> str:
|
|
|
704
705
|
|
|
705
706
|
|
|
706
707
|
def _checked_array_get(a: str, args: list[str], normalize_negative: bool = True) -> str:
|
|
707
|
-
check = _checked_array_index_prelude(normalize_negative=normalize_negative)
|
|
708
|
+
check = _checked_array_index_prelude(method="get", normalize_negative=normalize_negative)
|
|
708
709
|
return (
|
|
709
710
|
"[&](auto&& __pf_array)->decltype(auto){ "
|
|
710
711
|
"return [&](auto&& __pf_raw_index_value)->decltype(auto){ "
|
|
@@ -719,7 +720,7 @@ def _checked_array_get(a: str, args: list[str], normalize_negative: bool = True)
|
|
|
719
720
|
|
|
720
721
|
|
|
721
722
|
def _checked_array_set(a: str, args: list[str], normalize_negative: bool = True) -> str:
|
|
722
|
-
check = _checked_array_index_prelude(normalize_negative=normalize_negative)
|
|
723
|
+
check = _checked_array_index_prelude(method="set", normalize_negative=normalize_negative)
|
|
723
724
|
return (
|
|
724
725
|
"[&](auto&& __pf_array){ "
|
|
725
726
|
"return [&](auto&& __pf_raw_index_value){ "
|
|
@@ -731,7 +732,7 @@ def _checked_array_set(a: str, args: list[str], normalize_negative: bool = True)
|
|
|
731
732
|
|
|
732
733
|
|
|
733
734
|
def _checked_array_remove(a: str, args: list[str], normalize_negative: bool = True) -> str:
|
|
734
|
-
check = _checked_array_index_prelude(normalize_negative=normalize_negative)
|
|
735
|
+
check = _checked_array_index_prelude(method="remove", normalize_negative=normalize_negative)
|
|
735
736
|
return (
|
|
736
737
|
"[&](auto&& __pf_array){ "
|
|
737
738
|
"return [&](auto&& __pf_raw_index_value){ "
|
|
@@ -750,7 +751,7 @@ def _checked_array_end_get(a: str, method: str) -> str:
|
|
|
750
751
|
access = "front" if method == "first" else "back"
|
|
751
752
|
return (
|
|
752
753
|
"[&](auto&& __pf_array)->decltype(auto){ "
|
|
753
|
-
f
|
|
754
|
+
f'if(__pf_array.empty()) _PF_ARRAY_STOP("empty_array_access", "{method}", "Cannot use {method}() '
|
|
754
755
|
"if array is empty.\"); "
|
|
755
756
|
"if constexpr(std::is_lvalue_reference_v<decltype(__pf_array)>) "
|
|
756
757
|
f"return (__pf_array.{access}()); "
|
|
@@ -770,7 +771,7 @@ def _checked_array_end_remove(a: str, method: str) -> str:
|
|
|
770
771
|
)
|
|
771
772
|
return (
|
|
772
773
|
"[&](auto&& __pf_array){ "
|
|
773
|
-
f
|
|
774
|
+
f'if(__pf_array.empty()) _PF_ARRAY_STOP("empty_array_access", "{method}", "Cannot use {method}() '
|
|
774
775
|
"if array is empty.\"); "
|
|
775
776
|
"using __pf_array_value_type=typename "
|
|
776
777
|
"std::decay_t<decltype(__pf_array)>::value_type; "
|
|
@@ -781,7 +782,7 @@ def _checked_array_end_remove(a: str, method: str) -> str:
|
|
|
781
782
|
|
|
782
783
|
def _checked_array_percentrank(a: str, args: list[str]) -> str:
|
|
783
784
|
"""Preserve degenerate results, then reject invalid PercentRank indices."""
|
|
784
|
-
check = _checked_array_index_prelude(normalize_negative=False)
|
|
785
|
+
check = _checked_array_index_prelude(method="percentrank", normalize_negative=False)
|
|
785
786
|
return (
|
|
786
787
|
"[&](auto&& __pf_array){ "
|
|
787
788
|
"return [&](auto&& __pf_raw_index_value){ "
|
|
@@ -822,7 +823,7 @@ ARRAY_METHODS = {
|
|
|
822
823
|
"size": lambda a, args: f"(double){a}.size()",
|
|
823
824
|
"clear": lambda a, args: f"{a}.clear()",
|
|
824
825
|
"fill": lambda a, args: f"std::fill({a}.begin(), {a}.end(), {args[0]})" if len(args) == 1
|
|
825
|
-
else _checked_array_fill_range(a, args),
|
|
826
|
+
else _checked_array_fill_range(a, args + [f"(int){a}.size()"] if len(args) == 2 else args),
|
|
826
827
|
"includes": lambda a, args: f"(std::find({a}.begin(), {a}.end(), {args[0]}) != {a}.end())",
|
|
827
828
|
"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; }}()",
|
|
828
829
|
"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; }}()",
|
|
@@ -900,6 +901,9 @@ CHECKED_ARRAY_METHOD_KWARGS: dict[str, list[str]] = {
|
|
|
900
901
|
"last": [],
|
|
901
902
|
"pop": [],
|
|
902
903
|
"shift": [],
|
|
904
|
+
"sort": ["order"],
|
|
905
|
+
"indexof": ["value"],
|
|
906
|
+
"unshift": ["value"],
|
|
903
907
|
}
|
|
904
908
|
|
|
905
909
|
MAP_METHODS = {
|
|
@@ -954,10 +958,109 @@ def _matrix_add_col(m: str, args: list) -> str:
|
|
|
954
958
|
raise IndexError("matrix.add_col")
|
|
955
959
|
|
|
956
960
|
|
|
957
|
-
|
|
961
|
+
def _matrix_sum(receiver: str, arguments: list[str]) -> str:
|
|
962
|
+
"""Use the runtime's exposed Eigen arithmetic for matrix/scalar sums."""
|
|
963
|
+
if not arguments:
|
|
964
|
+
return f"{receiver}.sum()"
|
|
965
|
+
if len(arguments) != 1:
|
|
966
|
+
raise IndexError("matrix.sum")
|
|
967
|
+
return (
|
|
968
|
+
"([](const auto& _pf_sum_left, const auto& _pf_sum_right) { "
|
|
969
|
+
"auto _pf_sum_result = _pf_sum_left.copy(); "
|
|
970
|
+
"if constexpr (std::is_arithmetic_v<std::decay_t<decltype(_pf_sum_right)>>) { "
|
|
971
|
+
"_pf_sum_result.data().array() += _pf_sum_right; "
|
|
972
|
+
"} else { "
|
|
973
|
+
"if (_pf_sum_left.rows() != _pf_sum_right.rows() || "
|
|
974
|
+
"_pf_sum_left.columns() != _pf_sum_right.columns()) "
|
|
975
|
+
'throw std::runtime_error("Cannot sum matrices with different dimensions."); '
|
|
976
|
+
"_pf_sum_result.data() += _pf_sum_right.data(); "
|
|
977
|
+
"} return _pf_sum_result; "
|
|
978
|
+
f"}}({receiver}, {arguments[0]}))"
|
|
979
|
+
)
|
|
980
|
+
|
|
981
|
+
|
|
982
|
+
def _matrix_submatrix(receiver: str, arguments: list[str]) -> str:
|
|
983
|
+
if len(arguments) > 4:
|
|
984
|
+
raise IndexError("matrix.submatrix")
|
|
985
|
+
if len(arguments) == 4 and all(argument is not None for argument in arguments):
|
|
986
|
+
return f"{receiver}.submatrix({', '.join(_matrix_int_arg(argument) for argument in arguments)})"
|
|
987
|
+
defaults = ["0", "_pf_matrix_target.rows()", "0", "_pf_matrix_target.columns()"]
|
|
988
|
+
arguments = arguments + [None] * (4 - len(arguments))
|
|
989
|
+
bounds = [argument if argument is not None else defaults[index]
|
|
990
|
+
for index, argument in enumerate(arguments)]
|
|
991
|
+
return ("[&](auto&& _pf_matrix_target) { return _pf_matrix_target.submatrix("
|
|
992
|
+
f"{', '.join(_matrix_int_arg(argument) for argument in bounds)}); }}({receiver})")
|
|
993
|
+
|
|
994
|
+
|
|
995
|
+
# Parameter order of the matrix methods (Pine v6), receiver excluded: the
|
|
996
|
+
# slots ``CallVisitor._collection_arg_nodes`` binds keyword arguments to. A
|
|
997
|
+
# method absent here takes only its receiver. ``fill`` names its range
|
|
998
|
+
# parameters so that a keyword one binds as a positional one does: the
|
|
999
|
+
# lowering reads only ``value`` (the engine fills the whole matrix), a
|
|
1000
|
+
# pre-existing approximation of both spellings that the call does not refuse.
|
|
958
1001
|
MATRIX_METHOD_KWARGS: dict[str, list[str]] = {
|
|
959
|
-
"add_row": ["
|
|
960
|
-
"add_col": ["
|
|
1002
|
+
"add_row": ["row", "array_id"],
|
|
1003
|
+
"add_col": ["column", "array_id"],
|
|
1004
|
+
"sort": ["column", "order"],
|
|
1005
|
+
"submatrix": ["from_row", "to_row", "from_column", "to_column"],
|
|
1006
|
+
"sum": ["id2"],
|
|
1007
|
+
"diff": ["id2"],
|
|
1008
|
+
"mult": ["id2"],
|
|
1009
|
+
"get": ["row", "column"],
|
|
1010
|
+
"set": ["row", "column", "value"],
|
|
1011
|
+
"fill": ["value", "from_row", "to_row", "from_column", "to_column"],
|
|
1012
|
+
"row": ["row"],
|
|
1013
|
+
"col": ["column"],
|
|
1014
|
+
"remove_row": ["row"],
|
|
1015
|
+
"remove_col": ["column"],
|
|
1016
|
+
"swap_rows": ["row1", "row2"],
|
|
1017
|
+
"swap_columns": ["column1", "column2"],
|
|
1018
|
+
"reshape": ["rows", "columns"],
|
|
1019
|
+
"concat": ["id2", "vertical"],
|
|
1020
|
+
"pow": ["power"],
|
|
1021
|
+
"kron": ["id2"],
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
# Keyword spellings an earlier build bound that Pine's reference does not
|
|
1025
|
+
# name; they keep binding to the same slot.
|
|
1026
|
+
MATRIX_METHOD_KWARG_ALIASES: dict[str, dict[str, str]] = {
|
|
1027
|
+
"add_row": {"row_index": "row"},
|
|
1028
|
+
"add_col": {"col_index": "column"},
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
# The receiver's own keyword in the namespace form (``matrix.diff(id1=m, ...)``).
|
|
1032
|
+
MATRIX_RECEIVER_KEYWORD: dict[str, str] = {
|
|
1033
|
+
"sum": "id1", "diff": "id1", "mult": "id1", "concat": "id1", "kron": "id1",
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
|
|
1037
|
+
def _matrix_functional_receiver(method: str, node):
|
|
1038
|
+
"""The receiver node of ``matrix.<method>(...)``: its first positional
|
|
1039
|
+
argument, else its keyword."""
|
|
1040
|
+
return node.args[0] if node.args else node.kwargs.get(
|
|
1041
|
+
MATRIX_RECEIVER_KEYWORD.get(method, "id"))
|
|
1042
|
+
|
|
1043
|
+
|
|
1044
|
+
# The optional parameters of the collection methods whose keywords bind by
|
|
1045
|
+
# position, and what a gap left before one stands for when only a later
|
|
1046
|
+
# keyword is written (``m.sort(order = order.descending)`` leaves ``column``
|
|
1047
|
+
# open): a number is Pine's default, ``None`` an omitted argument the method's
|
|
1048
|
+
# template defaults from the receiver's own extent (``_matrix_submatrix``'s
|
|
1049
|
+
# ``to_row`` / ``to_column``, the defaulted ``add_row`` / ``add_col`` of
|
|
1050
|
+
# ``TypeInferer._matrix_method_expr``). A parameter that is not listed is
|
|
1051
|
+
# required: a gap before it is refused.
|
|
1052
|
+
MATRIX_OPTIONAL_PARAMS: dict[str, dict[str, int | None]] = {
|
|
1053
|
+
"sort": {"column": 0, "order": None},
|
|
1054
|
+
"submatrix": {"from_row": 0, "to_row": None, "from_column": 0, "to_column": None},
|
|
1055
|
+
"add_row": {"row": None, "array_id": None},
|
|
1056
|
+
"add_col": {"column": None, "array_id": None},
|
|
1057
|
+
"fill": {"from_row": None, "to_row": None, "from_column": None, "to_column": None},
|
|
1058
|
+
"sum": {"id2": None},
|
|
1059
|
+
"concat": {"vertical": None},
|
|
1060
|
+
}
|
|
1061
|
+
ARRAY_OPTIONAL_PARAMS: dict[str, dict[str, int | None]] = {
|
|
1062
|
+
"fill": {"index_from": 0, "index_to": None},
|
|
1063
|
+
"sort": {"order": None},
|
|
961
1064
|
}
|
|
962
1065
|
|
|
963
1066
|
# Matrix mutators whose established C++ lowering returns ``void``. A Pine
|
|
@@ -1024,17 +1127,17 @@ MATRIX_METHODS = {
|
|
|
1024
1127
|
"swap_rows": lambda m, args: f"{m}.swap_rows({_matrix_int_arg(args[0])}, {_matrix_int_arg(args[1])})",
|
|
1025
1128
|
"swap_columns": lambda m, args: f"{m}.swap_columns({_matrix_int_arg(args[0])}, {_matrix_int_arg(args[1])})",
|
|
1026
1129
|
"copy": lambda m, args: f"{m}.copy()",
|
|
1027
|
-
"submatrix":
|
|
1130
|
+
"submatrix": _matrix_submatrix,
|
|
1028
1131
|
"reshape": lambda m, args: f"{m}.reshape({_matrix_int_arg(args[0])}, {_matrix_int_arg(args[1])})",
|
|
1029
1132
|
"reverse": lambda m, args: f"{m}.reverse()",
|
|
1030
1133
|
"transpose": lambda m, args: f"{m}.transpose()",
|
|
1031
|
-
"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])})",
|
|
1134
|
+
"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])})" if args else f"{m}.sort(0)",
|
|
1032
1135
|
"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)",
|
|
1033
1136
|
"avg": lambda m, args: f"{m}.avg()",
|
|
1034
1137
|
"min": lambda m, args: f"{m}.min()",
|
|
1035
1138
|
"max": lambda m, args: f"{m}.max()",
|
|
1036
1139
|
"mode": lambda m, args: f"{m}.mode()",
|
|
1037
|
-
"sum":
|
|
1140
|
+
"sum": _matrix_sum,
|
|
1038
1141
|
"diff": lambda m, args: f"{m}.diff({args[0]})",
|
|
1039
1142
|
"mult": lambda m, args: f"{m}.mult({args[0]})",
|
|
1040
1143
|
"pow": lambda m, args: f"{m}.pow({_matrix_int_arg(args[0])})",
|
|
@@ -1128,10 +1231,11 @@ MATH_FUNC_MAP = {
|
|
|
1128
1231
|
STR_FUNC_MAP = {
|
|
1129
1232
|
"tostring": None, # handled separately (already works)
|
|
1130
1233
|
"tonumber": lambda args: (
|
|
1131
|
-
f"[&](){{ "
|
|
1132
|
-
f"try {{ return std::stod(
|
|
1133
|
-
f"catch (
|
|
1134
|
-
f"}}
|
|
1234
|
+
f"[&](std::string _pf_number_text){{ "
|
|
1235
|
+
f"try {{ return std::stod(_pf_number_text); }} "
|
|
1236
|
+
f"catch (const std::invalid_argument&) {{ return na<double>(); }} "
|
|
1237
|
+
f"catch (const std::out_of_range&) {{ return na<double>(); }} "
|
|
1238
|
+
f"}}(({args[0]}))"
|
|
1135
1239
|
),
|
|
1136
1240
|
"length": lambda args: f"(int){args[0]}.length()",
|
|
1137
1241
|
"contains": lambda args: f"({args[0]}.find({args[1]}) != std::string::npos)",
|
|
@@ -60,7 +60,7 @@ static std::string _pf_tv_decimal(double value, int min_fraction,
|
|
|
60
60
|
char buffer[128];
|
|
61
61
|
const auto converted = std::to_chars(buffer, buffer + sizeof buffer, value);
|
|
62
62
|
if (converted.ec != std::errc{})
|
|
63
|
-
|
|
63
|
+
_PF_ENGINE_INVARIANT("shortest-decimal conversion failed", std::runtime_error);
|
|
64
64
|
std::string spelling(buffer, converted.ptr);
|
|
65
65
|
const bool negative = !spelling.empty() && spelling[0] == '-';
|
|
66
66
|
if (negative) spelling.erase(0, 1);
|
|
@@ -240,7 +240,14 @@ static std::string pine_str_format_tv(
|
|
|
240
240
|
i = end + 1;
|
|
241
241
|
continue;
|
|
242
242
|
}
|
|
243
|
-
|
|
243
|
+
size_t index = 0;
|
|
244
|
+
for (char _pf_digit : index_text) {
|
|
245
|
+
const size_t _pf_value = static_cast<size_t>(_pf_digit - '0');
|
|
246
|
+
if (index > (std::numeric_limits<size_t>::max() - _pf_value) / 10) {
|
|
247
|
+
_PF_STRING_STOP("format_index_overflow", "stoul");
|
|
248
|
+
}
|
|
249
|
+
index = index * 10 + _pf_value;
|
|
250
|
+
}
|
|
244
251
|
if (index >= args.size()) {
|
|
245
252
|
result += format_string.substr(i, end - i + 1);
|
|
246
253
|
i = end + 1;
|
|
@@ -46,6 +46,7 @@ from ..collection_history import history_annotation
|
|
|
46
46
|
from ..errors import Phase
|
|
47
47
|
from ..external_requests import UNPINNED_ANNOTATION
|
|
48
48
|
from ..limits import iter_ast_nodes
|
|
49
|
+
from ..matrix_overloads import matrix_sum_has_rhs
|
|
49
50
|
from ..symbols import PineType, TypeSpec, method_receiver_type_name
|
|
50
51
|
from .helpers import (
|
|
51
52
|
NA_PRESERVING_INT_TYPES,
|
|
@@ -59,6 +60,7 @@ from .tables import (
|
|
|
59
60
|
ARRAY_ARGS_READ_REPEATEDLY,
|
|
60
61
|
ARRAY_DRAWING_NEW_CTORS,
|
|
61
62
|
ARRAY_METHODS,
|
|
63
|
+
MATRIX_METHODS,
|
|
62
64
|
V5_ARRAY_INDEX_METHODS,
|
|
63
65
|
BAR_BUILTINS,
|
|
64
66
|
BAR_FIELDS,
|
|
@@ -68,6 +70,7 @@ from .tables import (
|
|
|
68
70
|
MATRIX_RETURNING_METHODS,
|
|
69
71
|
PINE_TYPE_TO_CPP,
|
|
70
72
|
TA_RETURNS_BOOL,
|
|
73
|
+
_matrix_functional_receiver,
|
|
71
74
|
checked_array_slice,
|
|
72
75
|
)
|
|
73
76
|
|
|
@@ -1776,8 +1779,10 @@ class TypeInferer:
|
|
|
1776
1779
|
if func_name in ("copy", "slice"):
|
|
1777
1780
|
return arg_spec
|
|
1778
1781
|
return arg_spec.element
|
|
1779
|
-
if namespace == "matrix" and func_name in MATRIX_RETURNING_METHODS
|
|
1780
|
-
|
|
1782
|
+
if namespace == "matrix" and (func_name in MATRIX_RETURNING_METHODS or (
|
|
1783
|
+
func_name == "sum" and matrix_sum_has_rhs(node, namespace=True))):
|
|
1784
|
+
receiver_node = (node.args[0] if node.args else
|
|
1785
|
+
node.kwargs.get("id1", node.kwargs.get("id")))
|
|
1781
1786
|
receiver_spec = self._type_spec_from_expr(receiver_node)
|
|
1782
1787
|
if receiver_spec is not None and receiver_spec.kind == "matrix":
|
|
1783
1788
|
return receiver_spec
|
|
@@ -1869,7 +1874,8 @@ class TypeInferer:
|
|
|
1869
1874
|
method = (member_name
|
|
1870
1875
|
if history_annotation(node.callee.object) is not None
|
|
1871
1876
|
else func_name)
|
|
1872
|
-
if method in MATRIX_RETURNING_METHODS
|
|
1877
|
+
if method in MATRIX_RETURNING_METHODS or (
|
|
1878
|
+
method == "sum" and matrix_sum_has_rhs(node)):
|
|
1873
1879
|
return recv_spec
|
|
1874
1880
|
if method in ("row", "col"):
|
|
1875
1881
|
return TypeSpec.array(recv_spec.element)
|
|
@@ -1961,6 +1967,70 @@ class TypeInferer:
|
|
|
1961
1967
|
# Method lowering for collection types (used by visit_call paths)
|
|
1962
1968
|
# ------------------------------------------------------------------
|
|
1963
1969
|
|
|
1970
|
+
def _matrix_method_expr(self, receiver, method, arguments, argument_nodes, node):
|
|
1971
|
+
functional = (isinstance(node.callee, MemberAccess)
|
|
1972
|
+
and isinstance(node.callee.object, Identifier)
|
|
1973
|
+
and node.callee.object.name == "matrix")
|
|
1974
|
+
receiver_node = (_matrix_functional_receiver(method, node)
|
|
1975
|
+
if functional else node.callee.object)
|
|
1976
|
+
receiver = self._checked_matrix_history_receiver(receiver, receiver_node)
|
|
1977
|
+
if method in ("add_row", "add_col") and len(arguments) == 2 and arguments[0] is None:
|
|
1978
|
+
# Only ``array_id`` is written (``m.add_row(array_id = a)``): the
|
|
1979
|
+
# call is the array-only form, which appends it.
|
|
1980
|
+
arguments = [arguments[1]]
|
|
1981
|
+
argument_nodes = [argument_nodes[1]]
|
|
1982
|
+
if method in ("add_row", "add_col") and len(arguments) < 2:
|
|
1983
|
+
argument_spec = (self._type_spec_from_expr(argument_nodes[0])
|
|
1984
|
+
if argument_nodes else None)
|
|
1985
|
+
# An index-only call: the argument is a number whose spec the
|
|
1986
|
+
# analysis knows, or one it cannot type that reads as a number
|
|
1987
|
+
# (a call, an input, ``bar_index % 2``): the generated lambda then
|
|
1988
|
+
# tells an index from an array by the argument's C++ type.
|
|
1989
|
+
typed_index = not arguments or (
|
|
1990
|
+
argument_spec is not None and argument_spec.kind == "primitive")
|
|
1991
|
+
untyped_number = (argument_spec is None and bool(arguments)
|
|
1992
|
+
and self._infer_type(argument_nodes[0]) in ("double", "int", "int64_t"))
|
|
1993
|
+
receiver_spec = self._type_spec_from_expr(receiver_node)
|
|
1994
|
+
if ((typed_index or untyped_number) and receiver_spec is not None
|
|
1995
|
+
and receiver_spec.element is not None):
|
|
1996
|
+
element_spec = receiver_spec.element
|
|
1997
|
+
element_cpp = self._type_spec_to_cpp(element_spec)
|
|
1998
|
+
count = "columns" if method == "add_row" else "rows"
|
|
1999
|
+
append = "rows" if method == "add_row" else "columns"
|
|
2000
|
+
values = (f"std::vector<{element_cpp}>((size_t)_pf_matrix_target.{count}(), "
|
|
2001
|
+
f"{self._array_init_value_expr(element_spec, NaLiteral())})")
|
|
2002
|
+
if untyped_number:
|
|
2003
|
+
inserted = MATRIX_METHODS[method]("_pf_matrix_target", ["_pf_matrix_arg", values])
|
|
2004
|
+
appended = MATRIX_METHODS[method]("_pf_matrix_target", ["_pf_matrix_arg"])
|
|
2005
|
+
return (
|
|
2006
|
+
"[&](auto&& _pf_matrix_target, auto&& _pf_matrix_arg) { "
|
|
2007
|
+
"if constexpr (std::is_arithmetic_v<std::decay_t<decltype(_pf_matrix_arg)>>) { "
|
|
2008
|
+
f"{inserted}; }} else {{ {appended}; }} }}({receiver}, {arguments[0]})")
|
|
2009
|
+
index = arguments[0] if arguments else f"_pf_matrix_target.{append}()"
|
|
2010
|
+
mutation = MATRIX_METHODS[method]("_pf_matrix_target", [index, values])
|
|
2011
|
+
return f"[&](auto&& _pf_matrix_target) {{ {mutation}; }}({receiver})"
|
|
2012
|
+
if method in ("sum", "diff", "mult") and argument_nodes:
|
|
2013
|
+
other_spec = self._type_spec_from_expr(argument_nodes[0])
|
|
2014
|
+
if (method == "mult" and other_spec is not None
|
|
2015
|
+
and other_spec.kind == "array"):
|
|
2016
|
+
name = method
|
|
2017
|
+
self._codegen_error(
|
|
2018
|
+
node, f"matrix.{name}(...) is not implemented in PineForge runtime.")
|
|
2019
|
+
numeric_scalar = ((other_spec is not None and other_spec.kind == "primitive"
|
|
2020
|
+
and other_spec.name in ("int", "float"))
|
|
2021
|
+
or (other_spec is None and self._infer_type(argument_nodes[0])
|
|
2022
|
+
in ("double", "int", "int64_t")))
|
|
2023
|
+
if method in ("diff", "mult") and len(arguments) == 1 and numeric_scalar:
|
|
2024
|
+
operation = "-=" if method == "diff" else "*="
|
|
2025
|
+
return (
|
|
2026
|
+
"([](const auto& _pf_matrix_left, const auto _pf_matrix_right) { "
|
|
2027
|
+
"auto _pf_matrix_result = _pf_matrix_left.copy(); "
|
|
2028
|
+
f"_pf_matrix_result.data().array() {operation} _pf_matrix_right; "
|
|
2029
|
+
"return _pf_matrix_result; "
|
|
2030
|
+
f"}}({receiver}, {arguments[0]}))"
|
|
2031
|
+
)
|
|
2032
|
+
return MATRIX_METHODS[method](receiver, arguments)
|
|
2033
|
+
|
|
1964
2034
|
def _array_receiver_once_expr(
|
|
1965
2035
|
self, array_expr: str, args: list[str], lower_receiver,
|
|
1966
2036
|
) -> str:
|
|
@@ -2004,6 +2074,10 @@ class TypeInferer:
|
|
|
2004
2074
|
spec: TypeSpec | None = None, node: ASTNode | None = None,
|
|
2005
2075
|
) -> str:
|
|
2006
2076
|
"""Lower ``arr.method(...)`` to its C++ form, validating numeric requirements."""
|
|
2077
|
+
if self._array_history_value_names():
|
|
2078
|
+
array_expr = f"_pf_array_id({array_expr})"
|
|
2079
|
+
if method in ("covariance", "concat") and args:
|
|
2080
|
+
args = [f"_pf_array_id({args[0]})", *args[1:]]
|
|
2007
2081
|
spec = spec or TypeSpec.array(TypeSpec.primitive("float"))
|
|
2008
2082
|
arr_cpp_type = self._type_spec_to_cpp(spec)
|
|
2009
2083
|
elem_cpp = self._type_spec_to_cpp(spec.element) if spec.element is not None else "double"
|
|
@@ -2103,7 +2177,10 @@ class TypeInferer:
|
|
|
2103
2177
|
)
|
|
2104
2178
|
return lowered
|
|
2105
2179
|
|
|
2106
|
-
|
|
2180
|
+
try:
|
|
2181
|
+
return self._array_receiver_once_expr(array_expr, args, lower_receiver)
|
|
2182
|
+
except IndexError:
|
|
2183
|
+
self._codegen_error(node, f"array.{method}: wrong number of arguments")
|
|
2107
2184
|
|
|
2108
2185
|
def _map_method_expr(
|
|
2109
2186
|
self, map_expr: str, method: str, args: list[str], spec: TypeSpec | None = None,
|
|
@@ -2150,6 +2227,10 @@ class TypeInferer:
|
|
|
2150
2227
|
|
|
2151
2228
|
def _type_for_decl(self, node: VarDecl) -> str:
|
|
2152
2229
|
"""Determine the C++ type for a ``VarDecl``: explicit hint, then symbol, then RHS inference."""
|
|
2230
|
+
history_value_cpp = (self._array_history_value_cpp_type(node.name)
|
|
2231
|
+
if not getattr(self, "_active_func_name", None) else None)
|
|
2232
|
+
if history_value_cpp is not None:
|
|
2233
|
+
return history_value_cpp
|
|
2153
2234
|
def promote_wide_int(cpp_type: str) -> str:
|
|
2154
2235
|
if cpp_type != "int":
|
|
2155
2236
|
return cpp_type
|