@pineforge/codegen-pyodide 0.9.0 → 0.10.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/package.json +1 -1
- package/pineforge_codegen/__init__.py +3 -0
- package/pineforge_codegen/analyzer/base.py +3999 -140
- package/pineforge_codegen/analyzer/call_handlers.py +662 -48
- package/pineforge_codegen/analyzer/contracts.py +104 -7
- package/pineforge_codegen/analyzer/types.py +951 -14
- package/pineforge_codegen/codegen/base.py +2359 -249
- package/pineforge_codegen/codegen/drawing.py +94 -14
- package/pineforge_codegen/codegen/emit_top.py +732 -172
- package/pineforge_codegen/codegen/security.py +1456 -150
- package/pineforge_codegen/codegen/ta.py +435 -99
- package/pineforge_codegen/codegen/tables.py +224 -77
- package/pineforge_codegen/codegen/types.py +1196 -215
- package/pineforge_codegen/codegen/visit_call.py +1168 -99
- package/pineforge_codegen/codegen/visit_expr.py +354 -33
- package/pineforge_codegen/codegen/visit_stmt.py +1144 -200
- package/pineforge_codegen/finite_ta_length.py +798 -0
- package/pineforge_codegen/method_binding.py +163 -0
- package/pineforge_codegen/parser.py +48 -9
- package/pineforge_codegen/signatures.py +4 -4
- package/pineforge_codegen/support_checker.py +194 -19
- package/pineforge_codegen/symbols.py +65 -0
- package/pineforge_codegen-0.10.1.tar.gz +0 -0
- package/release.json +2 -2
- package/tables.json +9 -11
- package/pineforge_codegen-0.9.0.tar.gz +0 -0
|
@@ -458,18 +458,154 @@ ORDER_DIRECTION_MAP = {
|
|
|
458
458
|
# Array / Map / Matrix method dispatch
|
|
459
459
|
# ---------------------------------------------------------------------------
|
|
460
460
|
|
|
461
|
+
|
|
462
|
+
def _checked_array_index_prelude(*, normalize_negative: bool = True) -> str:
|
|
463
|
+
"""C++ body shared by checked single-index operations.
|
|
464
|
+
|
|
465
|
+
Pine v6 explicitly gives end-relative negative indices to the checked
|
|
466
|
+
``get``, ``set``, and ``remove`` cluster. Other indexed functions such as
|
|
467
|
+
``percentrank`` still need the same NA/non-finite/range checks, but must
|
|
468
|
+
reject negative indices instead of normalizing them. ``insert`` remains a
|
|
469
|
+
separately bounded residual.
|
|
470
|
+
"""
|
|
471
|
+
checked_index = (
|
|
472
|
+
"__pf_raw_index<0?__pf_raw_index+__pf_array_size:__pf_raw_index"
|
|
473
|
+
if normalize_negative
|
|
474
|
+
else "__pf_raw_index"
|
|
475
|
+
)
|
|
476
|
+
return (
|
|
477
|
+
"using __pf_raw_index_type=std::decay_t<decltype(__pf_raw_index_value)>; "
|
|
478
|
+
"if constexpr(!std::is_same_v<__pf_raw_index_type,bool>) { "
|
|
479
|
+
"if(is_na(__pf_raw_index_value)) "
|
|
480
|
+
"pine_runtime_error(std::string(\"Index na is out of bounds. Array size is \")+"
|
|
481
|
+
"std::to_string((int64_t)__pf_array.size())); } "
|
|
482
|
+
"if constexpr(std::is_floating_point_v<__pf_raw_index_type>) { "
|
|
483
|
+
"if(!std::isfinite(__pf_raw_index_value)) { "
|
|
484
|
+
"std::string __pf_raw_index_text=__pf_raw_index_value>0?\"inf\":\"-inf\"; "
|
|
485
|
+
"pine_runtime_error(std::string(\"Index \")+__pf_raw_index_text+"
|
|
486
|
+
"\" is out of bounds. Array size is \"+"
|
|
487
|
+
"std::to_string((int64_t)__pf_array.size())); } "
|
|
488
|
+
"long double __pf_raw_index_wide=(long double)__pf_raw_index_value; "
|
|
489
|
+
"if(__pf_raw_index_wide<(long double)std::numeric_limits<int64_t>::min()||"
|
|
490
|
+
"__pf_raw_index_wide>(long double)std::numeric_limits<int64_t>::max()) "
|
|
491
|
+
"pine_runtime_error(std::string(\"Index \")+"
|
|
492
|
+
"std::to_string((double)__pf_raw_index_value)+"
|
|
493
|
+
"\" is out of bounds. Array size is \"+"
|
|
494
|
+
"std::to_string((int64_t)__pf_array.size())); } "
|
|
495
|
+
"int64_t __pf_raw_index=(int64_t)__pf_raw_index_value; "
|
|
496
|
+
"int64_t __pf_array_size=(int64_t)__pf_array.size(); "
|
|
497
|
+
f"int64_t __pf_array_index={checked_index}; "
|
|
498
|
+
"if(__pf_array_index<0||__pf_array_index>=__pf_array_size) "
|
|
499
|
+
"pine_runtime_error(std::string(\"Index \")+std::to_string(__pf_raw_index)+"
|
|
500
|
+
"\" is out of bounds. Array size is \"+std::to_string(__pf_array_size)); "
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
def _checked_array_get(a: str, args: list[str]) -> str:
|
|
505
|
+
check = _checked_array_index_prelude()
|
|
506
|
+
return (
|
|
507
|
+
"[&](auto&& __pf_array)->decltype(auto){ "
|
|
508
|
+
"return [&](auto&& __pf_raw_index_value)->decltype(auto){ "
|
|
509
|
+
f"{check}"
|
|
510
|
+
"if constexpr(std::is_lvalue_reference_v<decltype(__pf_array)>) "
|
|
511
|
+
"return (__pf_array[(size_t)__pf_array_index]); "
|
|
512
|
+
"else { using __pf_array_value_type=typename "
|
|
513
|
+
"std::decay_t<decltype(__pf_array)>::value_type; "
|
|
514
|
+
"return __pf_array_value_type(__pf_array[(size_t)__pf_array_index]); } "
|
|
515
|
+
f"}}(({args[0]})); }}(({a}))"
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
def _checked_array_set(a: str, args: list[str]) -> str:
|
|
520
|
+
check = _checked_array_index_prelude()
|
|
521
|
+
return (
|
|
522
|
+
"[&](auto&& __pf_array){ "
|
|
523
|
+
"return [&](auto&& __pf_raw_index_value){ "
|
|
524
|
+
"return [&](auto&& __pf_array_value){ "
|
|
525
|
+
f"{check}"
|
|
526
|
+
"__pf_array[(size_t)__pf_array_index]=__pf_array_value; "
|
|
527
|
+
f"}}(({args[1]})); }}(({args[0]})); }}(({a}))"
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def _checked_array_remove(a: str, args: list[str]) -> str:
|
|
532
|
+
check = _checked_array_index_prelude()
|
|
533
|
+
return (
|
|
534
|
+
"[&](auto&& __pf_array){ "
|
|
535
|
+
"return [&](auto&& __pf_raw_index_value){ "
|
|
536
|
+
f"{check}"
|
|
537
|
+
"using __pf_array_value_type=typename "
|
|
538
|
+
"std::decay_t<decltype(__pf_array)>::value_type; "
|
|
539
|
+
"__pf_array_value_type __pf_array_value="
|
|
540
|
+
"__pf_array[(size_t)__pf_array_index]; "
|
|
541
|
+
"__pf_array.erase(__pf_array.begin()+(size_t)__pf_array_index); "
|
|
542
|
+
"return __pf_array_value; "
|
|
543
|
+
f"}}(({args[0]})); }}(({a}))"
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
def _checked_array_end_get(a: str, method: str) -> str:
|
|
548
|
+
access = "front" if method == "first" else "back"
|
|
549
|
+
return (
|
|
550
|
+
"[&](auto&& __pf_array)->decltype(auto){ "
|
|
551
|
+
f"if(__pf_array.empty()) pine_runtime_error(\"Cannot use {method}() "
|
|
552
|
+
"if array is empty.\"); "
|
|
553
|
+
"if constexpr(std::is_lvalue_reference_v<decltype(__pf_array)>) "
|
|
554
|
+
f"return (__pf_array.{access}()); "
|
|
555
|
+
"else { using __pf_array_value_type=typename "
|
|
556
|
+
"std::decay_t<decltype(__pf_array)>::value_type; "
|
|
557
|
+
f"return __pf_array_value_type(__pf_array.{access}()); }} "
|
|
558
|
+
f"}}(({a}))"
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
def _checked_array_end_remove(a: str, method: str) -> str:
|
|
563
|
+
access = "back" if method == "pop" else "front"
|
|
564
|
+
mutation = (
|
|
565
|
+
"__pf_array.pop_back();"
|
|
566
|
+
if method == "pop"
|
|
567
|
+
else "__pf_array.erase(__pf_array.begin());"
|
|
568
|
+
)
|
|
569
|
+
return (
|
|
570
|
+
"[&](auto&& __pf_array){ "
|
|
571
|
+
f"if(__pf_array.empty()) pine_runtime_error(\"Cannot use {method}() "
|
|
572
|
+
"if array is empty.\"); "
|
|
573
|
+
"using __pf_array_value_type=typename "
|
|
574
|
+
"std::decay_t<decltype(__pf_array)>::value_type; "
|
|
575
|
+
f"__pf_array_value_type __pf_array_value=__pf_array.{access}(); "
|
|
576
|
+
f"{mutation} return __pf_array_value; }}(({a}))"
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
def _checked_array_percentrank(a: str, args: list[str]) -> str:
|
|
581
|
+
"""Preserve degenerate results, then reject invalid PercentRank indices."""
|
|
582
|
+
check = _checked_array_index_prelude(normalize_negative=False)
|
|
583
|
+
return (
|
|
584
|
+
"[&](auto&& __pf_array){ "
|
|
585
|
+
"return [&](auto&& __pf_raw_index_value){ "
|
|
586
|
+
"if(__pf_array.size()<=1) return na<double>(); "
|
|
587
|
+
f"{check}"
|
|
588
|
+
"double v=__pf_array[(size_t)__pf_array_index]; "
|
|
589
|
+
"if(std::isnan(v)) return na<double>(); "
|
|
590
|
+
"int le=0; for(auto x:__pf_array) "
|
|
591
|
+
"if(!std::isnan(x) && x<=v) le++; "
|
|
592
|
+
"return (double)(le-1)/(__pf_array.size()-1)*100.0; "
|
|
593
|
+
f"}}(({args[0]})); }}(({a}))"
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
|
|
461
597
|
# Methods called as ``array.method(arr, ...)`` or ``arr.method(...)``.
|
|
462
598
|
ARRAY_METHODS = {
|
|
463
|
-
"get":
|
|
464
|
-
"set":
|
|
599
|
+
"get": _checked_array_get,
|
|
600
|
+
"set": _checked_array_set,
|
|
465
601
|
"push": lambda a, args: f"{a}.push_back({args[0]})",
|
|
466
602
|
"unshift": lambda a, args: f"{a}.insert({a}.begin(), {args[0]})",
|
|
467
603
|
"insert": lambda a, args: f"{a}.insert({a}.begin() + (int)({args[0]}), {args[1]})",
|
|
468
|
-
"pop": lambda a, args:
|
|
469
|
-
"shift": lambda a, args:
|
|
470
|
-
"remove":
|
|
471
|
-
"first": lambda a, args:
|
|
472
|
-
"last": lambda a, args:
|
|
604
|
+
"pop": lambda a, args: _checked_array_end_remove(a, "pop"),
|
|
605
|
+
"shift": lambda a, args: _checked_array_end_remove(a, "shift"),
|
|
606
|
+
"remove": _checked_array_remove,
|
|
607
|
+
"first": lambda a, args: _checked_array_end_get(a, "first"),
|
|
608
|
+
"last": lambda a, args: _checked_array_end_get(a, "last"),
|
|
473
609
|
"size": lambda a, args: f"(double){a}.size()",
|
|
474
610
|
"clear": lambda a, args: f"{a}.clear()",
|
|
475
611
|
"fill": lambda a, args: f"std::fill({a}.begin(), {a}.end(), {args[0]})" if len(args) == 1
|
|
@@ -485,56 +621,85 @@ ARRAY_METHODS = {
|
|
|
485
621
|
"copy": lambda a, args: f"std::vector<double>({a})",
|
|
486
622
|
"slice": lambda a, args: f"std::vector<double>({a}.begin()+(int)({args[0]}),{a}.begin()+(int)({args[1]}))",
|
|
487
623
|
"concat": lambda a, args: f"{a}.insert({a}.end(),{args[0]}.begin(),{args[0]}.end())",
|
|
488
|
-
"sum": lambda a, args: f"std::accumulate({a}.begin(),{a}.end(),0.0)",
|
|
489
|
-
"avg": lambda a, args: f"({a}.empty()?
|
|
490
|
-
"min": lambda a, args: f"
|
|
491
|
-
"max": lambda a, args: f"
|
|
492
|
-
"range": lambda a, args: f"(
|
|
624
|
+
"sum": lambda a, args: f"({a}.empty()?na<double>():std::accumulate({a}.begin(),{a}.end(),0.0))",
|
|
625
|
+
"avg": lambda a, args: f"({a}.empty()?na<double>():std::accumulate({a}.begin(),{a}.end(),0.0)/{a}.size())",
|
|
626
|
+
"min": lambda a, args: f"({a}.empty()?na<double>():*std::min_element({a}.begin(),{a}.end()))",
|
|
627
|
+
"max": lambda a, args: f"({a}.empty()?na<double>():*std::max_element({a}.begin(),{a}.end()))",
|
|
628
|
+
"range": lambda a, args: f"({a}.empty()?na<double>():*std::max_element({a}.begin(),{a}.end())-*std::min_element({a}.begin(),{a}.end()))",
|
|
493
629
|
"every": lambda a, args: f"std::all_of({a}.begin(),{a}.end(),[](double v){{return v!=0.0;}})",
|
|
494
630
|
"some": lambda a, args: f"std::any_of({a}.begin(),{a}.end(),[](double v){{return v!=0.0;}})",
|
|
495
631
|
# stdev/variance honor the optional 2nd ``biased`` arg (Pine v6:
|
|
496
|
-
# biased=true → population (default), false → sample / n-1).
|
|
497
|
-
# form keeps the original population emission byte-identical.
|
|
632
|
+
# biased=true → population (default), false → sample / n-1).
|
|
498
633
|
"stdev": lambda a, args: (
|
|
499
|
-
f"[&](){{ double m=std::accumulate({a}.begin(),{a}.end(),0.0)/{a}.size(); double s=0; for(auto v:{a})s+=(v-m)*(v-m); "
|
|
634
|
+
f"[&](){{ if({a}.empty()) return na<double>(); double m=std::accumulate({a}.begin(),{a}.end(),0.0)/{a}.size(); double s=0; for(auto v:{a})s+=(v-m)*(v-m); "
|
|
500
635
|
f"double _d=({args[0]})?(double){a}.size():((double){a}.size()-1.0); "
|
|
501
636
|
f"return _d>0?std::sqrt(s/_d):na<double>(); }}()"
|
|
502
637
|
if args else
|
|
503
|
-
f"[&](){{ double m=std::accumulate({a}.begin(),{a}.end(),0.0)/{a}.size(); double s=0; for(auto v:{a})s+=(v-m)*(v-m); return std::sqrt(s/{a}.size()); }}()"
|
|
638
|
+
f"[&](){{ if({a}.empty()) return na<double>(); double m=std::accumulate({a}.begin(),{a}.end(),0.0)/{a}.size(); double s=0; for(auto v:{a})s+=(v-m)*(v-m); return std::sqrt(s/{a}.size()); }}()"
|
|
504
639
|
),
|
|
505
640
|
"variance": lambda a, args: (
|
|
506
|
-
f"[&](){{ double m=std::accumulate({a}.begin(),{a}.end(),0.0)/{a}.size(); double s=0; for(auto v:{a})s+=(v-m)*(v-m); "
|
|
641
|
+
f"[&](){{ if({a}.empty()) return na<double>(); double m=std::accumulate({a}.begin(),{a}.end(),0.0)/{a}.size(); double s=0; for(auto v:{a})s+=(v-m)*(v-m); "
|
|
507
642
|
f"double _d=({args[0]})?(double){a}.size():((double){a}.size()-1.0); "
|
|
508
643
|
f"return _d>0?s/_d:na<double>(); }}()"
|
|
509
644
|
if args else
|
|
510
|
-
f"[&](){{ double m=std::accumulate({a}.begin(),{a}.end(),0.0)/{a}.size(); double s=0; for(auto v:{a})s+=(v-m)*(v-m); return s/{a}.size(); }}()"
|
|
645
|
+
f"[&](){{ if({a}.empty()) return na<double>(); double m=std::accumulate({a}.begin(),{a}.end(),0.0)/{a}.size(); double s=0; for(auto v:{a})s+=(v-m)*(v-m); return s/{a}.size(); }}()"
|
|
511
646
|
),
|
|
512
|
-
"median": lambda a, args: f"[&](){{ auto c={a}; std::sort(c.begin(),c.end()); int n=c.size(); return n%2?c[n/2]:(c[n/2-1]+c[n/2])/2.0; }}()",
|
|
513
|
-
"mode": lambda a, args: f"[&](){{ std::unordered_map<double,int> m; for(auto v:{a})m[v]++; double best=0; int bc=0; for(auto&[v,c]:m)if(c>bc||(c==bc&&v<best)){{bc=c;best=v;}} return best; }}()",
|
|
514
|
-
"percentile_linear_interpolation": lambda a, args: f"[&](){{ auto c={a}; std::sort(c.begin(),c.end()); double k=({args[0]}/100.0)*c.size()-0.5; int i=std::max(0,(int)k); double f=k-i; if(i+1>=(int)c.size()) return c.back(); return c[i]*(1-f)+c[i+1]*f; }}()",
|
|
515
|
-
"percentile_nearest_rank": lambda a, args: f"[&](){{ auto c={a}; std::sort(c.begin(),c.end()); int r=(int)std::ceil(({args[0]}/100.0)*c.size()); return c[std::min(r-1,(int)c.size()-1)]; }}()",
|
|
516
|
-
"percentrank":
|
|
647
|
+
"median": lambda a, args: f"[&](){{ if({a}.empty()) return na<double>(); auto c={a}; std::sort(c.begin(),c.end()); int n=c.size(); return n%2?c[n/2]:(c[n/2-1]+c[n/2])/2.0; }}()",
|
|
648
|
+
"mode": lambda a, args: f"[&](){{ if({a}.empty()) return na<double>(); std::unordered_map<double,int> m; for(auto v:{a})m[v]++; double best=0; int bc=0; for(auto&[v,c]:m)if(c>bc||(c==bc&&v<best)){{bc=c;best=v;}} return best; }}()",
|
|
649
|
+
"percentile_linear_interpolation": lambda a, args: f"[&](){{ if({a}.empty()) return na<double>(); auto c={a}; std::sort(c.begin(),c.end()); double k=({args[0]}/100.0)*c.size()-0.5; int i=std::max(0,(int)k); double f=k-i; if(i+1>=(int)c.size()) return c.back(); return c[i]*(1-f)+c[i+1]*f; }}()",
|
|
650
|
+
"percentile_nearest_rank": lambda a, args: f"[&](){{ if({a}.empty()) return na<double>(); auto c={a}; std::sort(c.begin(),c.end()); int r=(int)std::ceil(({args[0]}/100.0)*c.size()); return (double)c[std::min(r-1,(int)c.size()-1)]; }}()",
|
|
651
|
+
"percentrank": _checked_array_percentrank,
|
|
517
652
|
"abs": lambda a, args: f"[&](){{ std::vector<double> r; for(auto v:{a})r.push_back(std::abs(v)); return r; }}()",
|
|
518
653
|
"join": lambda a, args: "[&](){{ std::string r; for(size_t i=0;i<{arr}.size();i++){{ if(i>0)r+={sep}; r+=std::to_string({arr}[i]); }} return r; }}()".format(arr=a, sep=args[0] if args else 'std::string(",")'),
|
|
519
654
|
"standardize": lambda a, args: f"[&](){{ double m=std::accumulate({a}.begin(),{a}.end(),0.0)/{a}.size(); double s=0; for(auto v:{a})s+=(v-m)*(v-m); s=std::sqrt(s/{a}.size()); std::vector<double> r; for(auto v:{a})r.push_back(s==0?1.0:(v-m)/s); return r; }}()",
|
|
520
|
-
"covariance": lambda a, args: f"[&](){{ auto
|
|
655
|
+
"covariance": lambda a, args: f"[&](){{ auto&&b=({args[0]}); int n=std::min({a}.size(),b.size()); if(n<=0) return na<double>(); double ma=0,mb=0; for(int i=0;i<n;i++){{ma+={a}[i];mb+=b[i];}} ma/=n;mb/=n; double c=0; for(int i=0;i<n;i++)c+=({a}[i]-ma)*(b[i]-mb); return c/n; }}()",
|
|
521
656
|
"binary_search": lambda a, args: f"[&](){{ auto it=std::lower_bound({a}.begin(),{a}.end(),{args[0]}); return (it!={a}.end()&&*it=={args[0]})?(double)(it-{a}.begin()):-1.0; }}()",
|
|
522
657
|
"binary_search_leftmost": lambda a, args: f"[&](){{ auto it=std::lower_bound({a}.begin(),{a}.end(),{args[0]}); return (it!={a}.end()&&*it=={args[0]})?(double)(it-{a}.begin()):(double)(it-{a}.begin()-1); }}()",
|
|
523
658
|
"binary_search_rightmost": lambda a, args: f"[&](){{ auto it=std::upper_bound({a}.begin(),{a}.end(),{args[0]}); return (it!={a}.begin()&&*(it-1)=={args[0]})?(double)(it-{a}.begin()-1):(double)(it-{a}.begin()); }}()",
|
|
524
659
|
"sort_indices": lambda a, args: f"[&](){{ std::vector<double> idx({a}.size()); std::iota(idx.begin(),idx.end(),0); std::sort(idx.begin(),idx.end(),[&](int i,int j){{return {a}[i]<{a}[j];}}); return idx; }}()",
|
|
525
660
|
}
|
|
526
661
|
|
|
662
|
+
# Pine parameter order for the checked-index subset. Keeping the receiver
|
|
663
|
+
# (``id``) separate lets method syntax merge ``a.get(index = i)`` while the
|
|
664
|
+
# namespace-functional path merges ``array.get(id = a, index = i)``.
|
|
665
|
+
CHECKED_ARRAY_METHOD_KWARGS: dict[str, list[str]] = {
|
|
666
|
+
"get": ["index"],
|
|
667
|
+
"set": ["index", "value"],
|
|
668
|
+
"remove": ["index"],
|
|
669
|
+
"percentrank": ["index"],
|
|
670
|
+
"first": [],
|
|
671
|
+
"last": [],
|
|
672
|
+
"pop": [],
|
|
673
|
+
"shift": [],
|
|
674
|
+
}
|
|
675
|
+
|
|
527
676
|
MAP_METHODS = {
|
|
528
|
-
"put": lambda m, args: f"
|
|
529
|
-
"get": lambda m, args: f"
|
|
530
|
-
"remove": lambda m, args: f"
|
|
531
|
-
"contains": lambda m, args: f"
|
|
532
|
-
"size": lambda m, args: f"
|
|
677
|
+
"put": lambda m, args: f"{m}.put({args[0]}, {args[1]})",
|
|
678
|
+
"get": lambda m, args: f"{m}.get({args[0]})",
|
|
679
|
+
"remove": lambda m, args: f"{m}.remove({args[0]})",
|
|
680
|
+
"contains": lambda m, args: f"{m}.contains({args[0]})",
|
|
681
|
+
"size": lambda m, args: f"{m}.size()",
|
|
533
682
|
"clear": lambda m, args: f"{m}.clear()",
|
|
534
|
-
"keys": lambda m, args: f"
|
|
535
|
-
"values": lambda m, args: f"
|
|
536
|
-
"copy": lambda m, args: f"
|
|
537
|
-
"put_all": lambda m, args: f"{m}.
|
|
683
|
+
"keys": lambda m, args: f"{m}.keys()",
|
|
684
|
+
"values": lambda m, args: f"{m}.values()",
|
|
685
|
+
"copy": lambda m, args: f"{m}.copy()",
|
|
686
|
+
"put_all": lambda m, args: f"{m}.put_all({args[0]})",
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
# Declared signature order after removing the method receiver ``id``. This is
|
|
690
|
+
# consumed by the typed-map UDF-parameter lane; established global/local and
|
|
691
|
+
# namespace map lowerings intentionally retain their existing output.
|
|
692
|
+
MAP_METHOD_KWARGS: dict[str, list[str]] = {
|
|
693
|
+
"put": ["key", "value"],
|
|
694
|
+
"get": ["key"],
|
|
695
|
+
"remove": ["key"],
|
|
696
|
+
"contains": ["key"],
|
|
697
|
+
"size": [],
|
|
698
|
+
"clear": [],
|
|
699
|
+
"keys": [],
|
|
700
|
+
"values": [],
|
|
701
|
+
"copy": [],
|
|
702
|
+
"put_all": ["id2"],
|
|
538
703
|
}
|
|
539
704
|
|
|
540
705
|
|
|
@@ -566,6 +731,20 @@ MATRIX_METHOD_KWARGS: dict[str, list[str]] = {
|
|
|
566
731
|
"add_col": ["col_index", "array_id"],
|
|
567
732
|
}
|
|
568
733
|
|
|
734
|
+
# Matrix mutators whose established C++ lowering returns ``void``. A Pine
|
|
735
|
+
# callable may use one as its terminal expression, but its generated numeric
|
|
736
|
+
# fallback wrapper must emit the mutation as a statement and then return the
|
|
737
|
+
# default value; ``return matrix.set(...)`` is invalid C++.
|
|
738
|
+
#
|
|
739
|
+
# ``remove_row`` / ``remove_col`` are intentionally absent: their lowering in
|
|
740
|
+
# ``MATRIX_METHODS`` already wraps the runtime mutation in a lambda that returns
|
|
741
|
+
# a numeric sentinel. Matrix-returning transforms belong exclusively in
|
|
742
|
+
# ``MATRIX_RETURNING_METHODS`` below.
|
|
743
|
+
MATRIX_VOID_METHODS: frozenset[str] = frozenset({
|
|
744
|
+
"set", "fill", "add_row", "add_col", "swap_rows", "swap_columns",
|
|
745
|
+
"reshape", "reverse", "sort",
|
|
746
|
+
})
|
|
747
|
+
|
|
569
748
|
# matrix.* method names whose RUNTIME return type is ``PineMatrix``. Used by
|
|
570
749
|
# the codegen aggregate-type registration to declare the LHS variable as
|
|
571
750
|
# ``PineMatrix`` instead of the analyzer's default ``double``. Without this
|
|
@@ -683,6 +862,17 @@ def _math_minmax_na_expr(func_name: str, args: list[str]) -> str:
|
|
|
683
862
|
)
|
|
684
863
|
|
|
685
864
|
|
|
865
|
+
# Pine ``math.sign`` returns a float, propagates numeric ``na``, and must not
|
|
866
|
+
# evaluate its argument more than once. Keep the callable separate from the
|
|
867
|
+
# call template because the stable-runtime TA reset fallback also substitutes
|
|
868
|
+
# bare ``math.sign`` member text with this same C++ function expression.
|
|
869
|
+
_MATH_SIGN_CPP_FN = (
|
|
870
|
+
"([](const auto _pf_sign_v) -> double { "
|
|
871
|
+
"return is_na(_pf_sign_v) ? na<double>() "
|
|
872
|
+
": (double)((_pf_sign_v > 0) - (_pf_sign_v < 0)); })"
|
|
873
|
+
)
|
|
874
|
+
|
|
875
|
+
|
|
686
876
|
MATH_FUNC_MAP = {
|
|
687
877
|
"abs": "std::abs", "max": "std::max", "min": "std::min",
|
|
688
878
|
"ceil": "std::ceil", "floor": "std::floor", "round": "std::round",
|
|
@@ -691,7 +881,7 @@ MATH_FUNC_MAP = {
|
|
|
691
881
|
"sin": "std::sin", "cos": "std::cos", "tan": "std::tan",
|
|
692
882
|
"asin": "std::asin", "acos": "std::acos", "atan": "std::atan",
|
|
693
883
|
"atan2": "std::atan2({0}, {1})",
|
|
694
|
-
"sign":
|
|
884
|
+
"sign": _MATH_SIGN_CPP_FN,
|
|
695
885
|
"avg": "(({0} + {1}) / 2.0)",
|
|
696
886
|
}
|
|
697
887
|
|
|
@@ -743,46 +933,3 @@ def _merge_kwargs(args: list, kwargs: dict, param_names: list | None, visit_expr
|
|
|
743
933
|
while merged and merged[-1] is None:
|
|
744
934
|
merged.pop()
|
|
745
935
|
return [visit_expr(a) for a in merged if a is not None]
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
def _merge_kwargs_with_defaults(
|
|
749
|
-
args: list,
|
|
750
|
-
kwargs: dict,
|
|
751
|
-
param_names: list,
|
|
752
|
-
param_defaults: list,
|
|
753
|
-
visit_expr,
|
|
754
|
-
) -> list:
|
|
755
|
-
"""Like ``_merge_kwargs`` but fills missing slots from ``param_defaults``.
|
|
756
|
-
|
|
757
|
-
``param_defaults`` must be parallel to ``param_names`` (use ``None`` for
|
|
758
|
-
parameters without a default). Used by the UDT-method call lowering so
|
|
759
|
-
callers may invoke ``cfg.threshold()``, ``cfg.threshold(atrVal)``, or
|
|
760
|
-
``cfg.threshold(mult=2.0, base=rsiVal)`` against ``method threshold(Cfg
|
|
761
|
-
self, float mult = 1.0, float base = 0.0) =>`` and have clang see the
|
|
762
|
-
full positional argument list. PineScript has no overloading, so every
|
|
763
|
-
parameter must be filled at the call site.
|
|
764
|
-
|
|
765
|
-
Probe: data/validation/udt-method-probe-04-default-param.
|
|
766
|
-
|
|
767
|
-
The result preserves the parameter order from ``param_names`` and
|
|
768
|
-
contains ``visit_expr(node)`` for each filled slot. Trailing slots that
|
|
769
|
-
have neither a caller-supplied value nor a default are dropped (matches
|
|
770
|
-
``_merge_kwargs`` behaviour for required-only signatures).
|
|
771
|
-
"""
|
|
772
|
-
n = len(param_names)
|
|
773
|
-
slots: list = [None] * n
|
|
774
|
-
for i, a in enumerate(args):
|
|
775
|
-
if i < n:
|
|
776
|
-
slots[i] = a
|
|
777
|
-
for k, v in kwargs.items():
|
|
778
|
-
if k in param_names:
|
|
779
|
-
slots[param_names.index(k)] = v
|
|
780
|
-
# Fill remaining holes from defaults (only where the caller omitted the
|
|
781
|
-
# slot AND the parameter has a declared default).
|
|
782
|
-
if param_defaults:
|
|
783
|
-
for i in range(n):
|
|
784
|
-
if slots[i] is None and i < len(param_defaults) and param_defaults[i] is not None:
|
|
785
|
-
slots[i] = param_defaults[i]
|
|
786
|
-
while slots and slots[-1] is None:
|
|
787
|
-
slots.pop()
|
|
788
|
-
return [visit_expr(a) for a in slots if a is not None]
|