@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.
@@ -166,6 +166,21 @@ _BUILTIN_NAMESPACE_NAMES: frozenset[str] = frozenset({
166
166
  })
167
167
 
168
168
 
169
+ # KI-71/KI-73: Pine relational comparisons (``==`` ``!=`` ``<`` ``>``
170
+ # ``<=`` ``>=``) with an ``na`` operand evaluate *falsy*. Pine also treats
171
+ # finite float operands at most 1e-10 apart as equal, independent of their
172
+ # magnitude. Naive C++ relationals honour neither rule completely, so numeric
173
+ # comparisons route through the shared lowering below.
174
+ _RELATIONAL_OPS: frozenset[str] = frozenset({"==", "!=", "<", ">", "<=", ">="})
175
+
176
+ # C++ scalar types carrying a detectable ``na`` sentinel via ``is_na``:
177
+ # ``double`` -> NaN (IEEE), ``int``/``int64_t`` -> ``numeric_limits<T>::min()``.
178
+ # Only these route through the na-aware relational lowering — ``is_na`` has no
179
+ # overload for ``bool``/``std::string``/vector/UDT-value operands, and Pine's
180
+ # na-bool is engine-indistinguishable from ``false`` (na<bool>() == false).
181
+ _NA_SCALAR_CPP: frozenset[str] = frozenset({"int", "int64_t", "double"})
182
+
183
+
169
184
  class ExprVisitor:
170
185
  """Expression-level visitor methods shared across the codegen.
171
186
 
@@ -207,7 +222,21 @@ class ExprVisitor:
207
222
  if isinstance(node, Subscript):
208
223
  return self._visit_subscript(node)
209
224
  if isinstance(node, TupleLiteral):
210
- elems = ", ".join(self._visit_expr(e) for e in node.elements)
225
+ elems = []
226
+ for element in node.elements:
227
+ element_spec = self._type_spec_from_expr(element)
228
+ element_target = None
229
+ if (element_spec is not None
230
+ and element_spec.kind == "udt"
231
+ and element_spec.name in DRAWING_TYPE_TO_CPP):
232
+ element_target = self._type_spec_to_cpp(element_spec)
233
+ elems.append(
234
+ self._visit_rhs_value(
235
+ element,
236
+ target_cpp_type=element_target,
237
+ )
238
+ )
239
+ elems = ", ".join(elems)
211
240
  return f"std::make_tuple({elems})"
212
241
  return "/* unknown */"
213
242
 
@@ -219,34 +248,132 @@ class ExprVisitor:
219
248
  return (isinstance(node, NaLiteral)
220
249
  or (isinstance(node, Identifier) and node.name == "na"))
221
250
 
222
- def _drawing_na_default(self, target_name: str | None) -> str | None:
223
- """If ``target_name`` is a drawing-handle variable (line/box/label/
224
- linefill/chart.point), return its na default literal (e.g. ``Box{}`` —
225
- a default-constructed handle whose id == -1 == na); otherwise None."""
251
+ def _drawing_target_cpp_type(
252
+ self,
253
+ target_name: str | None,
254
+ target_cpp_type: str | None,
255
+ ) -> str | None:
256
+ """Resolve a drawing target without leaking a shadowed global type.
257
+
258
+ An explicit contextual type always wins. For reassignments, where the
259
+ caller may not have one, consult function-local and parameter types
260
+ before the analyzer's flat global drawing registry. A same-named
261
+ scalar local/parameter must therefore block the global fallback.
262
+ """
263
+ if target_cpp_type is not None:
264
+ return (
265
+ target_cpp_type
266
+ if target_cpp_type in DRAWING_TYPE_TO_CPP.values()
267
+ else None
268
+ )
269
+ if not target_name:
270
+ return None
271
+ if target_name in self._lexical_drawing_types:
272
+ return self._lexical_drawing_types[target_name]
273
+ param_spec = getattr(self, "_current_func_param_specs", {}).get(
274
+ target_name
275
+ )
276
+ if (param_spec is not None
277
+ and param_spec.kind == "udt"
278
+ and param_spec.name in DRAWING_TYPE_TO_CPP):
279
+ return DRAWING_TYPE_TO_CPP[param_spec.name]
280
+ for scoped_types in (
281
+ getattr(self, "_current_func_local_types", {}),
282
+ getattr(self, "_current_func_param_types", {}),
283
+ ):
284
+ if target_name in scoped_types:
285
+ scoped_type = scoped_types[target_name].removesuffix("&")
286
+ return (
287
+ scoped_type
288
+ if scoped_type in DRAWING_TYPE_TO_CPP.values()
289
+ else None
290
+ )
291
+ # Only declarations already emitted into the current lexical path may
292
+ # shadow a global. Scanning the whole future function body makes a
293
+ # later local declaration pre-shadow earlier statements. Exact
294
+ # top-level types are captured separately because the analyzer's flat
295
+ # raw-name registry can be overwritten by a same-named local.
296
+ return self._global_drawing_cpp_types.get(target_name)
297
+
298
+ def _udt_target_cpp_type(
299
+ self,
300
+ *,
301
+ target_name: str | None = None,
302
+ target_node=None,
303
+ type_hint: str | None = None,
304
+ ) -> str | None:
305
+ """Return the exact authored UDT type for a contextual RHS target.
306
+
307
+ Pine's bare ``na`` is target typed. Arbitrary UDTs represent their na
308
+ ID with a default-constructed ``T{}``, just as generated drawing
309
+ handles do, but the drawing-only registry cannot safely answer for
310
+ same-spelled ordinary UDT declarations in sibling blocks/functions.
311
+ Prefer explicit/node context, then the source-ordered lexical map, and
312
+ consult legacy raw-name metadata only when no lexical binding exists.
313
+ """
314
+ spec = self._type_spec_from_hint_name(type_hint) if type_hint else None
315
+ if spec is None and target_node is not None:
316
+ spec = self._type_spec_from_expr(target_node)
317
+ if spec is not None and spec.kind == "udt" and spec.name in self._udt_defs:
318
+ return spec.name
226
319
  if not target_name:
227
320
  return None
228
- udt = self._udt_var_types.get(target_name)
229
- if udt in DRAWING_TYPE_TO_CPP:
230
- return f"{DRAWING_TYPE_TO_CPP[udt]}{{}}"
231
- return None
321
+ udt_name = self._identifier_udt_type(target_name)
322
+ return udt_name if udt_name in self._udt_defs else None
232
323
 
233
324
  def _visit_rhs_value(self, value_node, target_name: str | None = None,
234
325
  target_cpp_type: str | None = None) -> str:
235
326
  """Visit an assignment / declaration RHS.
236
327
 
237
328
  A bare ``na`` lowers to a type-appropriate initializer for the target
238
- instead of ``na<double>()``: drawing handles brace-init to their na
239
- handle (``Box{}`` / ``Line{}`` / …); ``std::string``/``int``/``int64_t``/
240
- ``bool`` use ``na<T>()``. Without this, ``Box b = na;`` and
241
- ``string s = na;`` would both emit ``na<double>()`` and fail to compile
242
- (no viable ``operator=`` / conversion). Every other RHS lowers unchanged.
329
+ instead of ``na<double>()``: drawing and authored UDT handles
330
+ brace-init to their na object (``Box{}`` / ``State{}``), while
331
+ ``std::string``/``int``/``int64_t``/``bool`` use ``na<T>()``. Without
332
+ this, ``State s = na;`` and ``string x = na;`` would both emit
333
+ ``na<double>()`` and fail to compile (no viable assignment/conversion).
334
+ Every other RHS lowers unchanged.
243
335
  """
244
- if target_name and self._is_na_expr(value_node):
245
- draw_default = self._drawing_na_default(target_name)
246
- if draw_default is not None:
247
- return draw_default
336
+ drawing_target = self._drawing_target_cpp_type(
337
+ target_name,
338
+ target_cpp_type,
339
+ )
340
+ if self._is_na_expr(value_node):
341
+ if drawing_target is not None:
342
+ return f"{drawing_target}{{}}"
343
+ if target_cpp_type in self._udt_defs:
344
+ return f"{target_cpp_type}{{}}"
345
+ if self._is_nullable_collection_cpp_type(target_cpp_type):
346
+ # Maps and matrices use default construction for a typed
347
+ # ``na`` ID. Their ``*.new`` factories create a valid ID,
348
+ # including a valid empty collection.
349
+ return f"{target_cpp_type}{{}}"
248
350
  if target_cpp_type in ("std::string", "int", "int64_t", "bool"):
249
351
  return f"na<{target_cpp_type}>()"
352
+ if (isinstance(value_node, Ternary)
353
+ and (self._is_nullable_collection_cpp_type(target_cpp_type)
354
+ or drawing_target is not None
355
+ or target_cpp_type in self._udt_defs)):
356
+ # C++ cannot deduce a common type for ``na<double>()`` and a
357
+ # collection/drawing handle. Pine's ternary is target typed, so
358
+ # propagate the exact declared/reassignment target into both arms.
359
+ # Arrays and scalar ternaries retain the established generic path;
360
+ # target typing is safe only for collections with nullable runtime
361
+ # representations.
362
+ branch_target = drawing_target or target_cpp_type
363
+ condition = self._visit_expr(value_node.condition)
364
+ true_value = self._visit_rhs_value(
365
+ value_node.true_val,
366
+ target_name,
367
+ target_cpp_type=branch_target,
368
+ )
369
+ false_value = self._visit_rhs_value(
370
+ value_node.false_val,
371
+ target_name,
372
+ target_cpp_type=branch_target,
373
+ )
374
+ return (
375
+ f"(({condition}) ? ({true_value}) : ({false_value}))"
376
+ )
250
377
  return self._visit_expr(value_node)
251
378
 
252
379
  def _visit_ident(self, node: Identifier) -> str:
@@ -254,6 +381,9 @@ class ExprVisitor:
254
381
  # Bare 'na' identifier → na<double>()
255
382
  if name == "na":
256
383
  return "na<double>()"
384
+ pending_outer = getattr(self, "_pending_decl_outer_alias", {})
385
+ if name in pending_outer:
386
+ return pending_outer[name]
257
387
  # Function parameters that are series — read current value
258
388
  if name in self._current_func_series_params:
259
389
  return f"{self._safe_name(name)}[0]"
@@ -267,7 +397,9 @@ class ExprVisitor:
267
397
  # Inline known constants (literals). Input-backed names are NOT inlined
268
398
  # because strategy_set_input() can override them at runtime; emit the
269
399
  # variable name and let get_input_*() produce the current value.
270
- if name in self._known_vars and name not in self._input_backed_vars:
400
+ if (name in self._known_vars
401
+ and name not in self._input_backed_vars
402
+ and not self._known_var_is_lexically_shadowed(name)):
271
403
  val = self._known_vars[name]
272
404
  if isinstance(val, bool):
273
405
  return "true" if val else "false"
@@ -302,7 +434,7 @@ class ExprVisitor:
302
434
  # Apply per-call-site var remap (for function-local vars)
303
435
  if self._active_var_remap and safe in self._active_var_remap:
304
436
  safe = self._active_var_remap[safe]
305
- if name in self.ctx.series_vars:
437
+ if self._binding_is_series(name, safe):
306
438
  return f"{safe}[0]"
307
439
  # Safety net: by here the name resolved to none of the builtins,
308
440
  # constants, parameters, or declared variables handled above, so
@@ -363,6 +495,16 @@ class ExprVisitor:
363
495
  return True
364
496
  return False
365
497
 
498
+ def _visit_mutable_expr(self, node: ASTNode) -> str:
499
+ """Lower an assignment target with UDT journal capture enabled."""
500
+
501
+ previous = getattr(self, "_udt_mutable_expr_depth", 0)
502
+ self._udt_mutable_expr_depth = previous + 1
503
+ try:
504
+ return self._visit_expr(node)
505
+ finally:
506
+ self._udt_mutable_expr_depth = previous
507
+
366
508
  def _visit_member_access(self, node: MemberAccess) -> str:
367
509
  # UDT field whose type was a drawing primitive (label/line/box/
368
510
  # linefill/polyline/table/chart.point) — the field is dropped from
@@ -371,6 +513,34 @@ class ExprVisitor:
371
513
  # See: pineforge-codegen issue #10.
372
514
  if self._is_omitted_udt_field(node):
373
515
  return "/* drawing field omitted */ 0"
516
+
517
+ # User-defined objects are numeric handles into a per-type arena. The
518
+ # arena lookup returns a real record lvalue, so this one lowering serves
519
+ # reads, assignment targets, nested field chains and method receivers.
520
+ owner_spec = self._type_spec_from_expr(node.object)
521
+ if (
522
+ owner_spec is not None
523
+ and owner_spec.kind == "udt"
524
+ and owner_spec.name in self._udt_defs
525
+ ):
526
+ owner = self._visit_expr(node.object)
527
+ arena = self._udt_arena_member_name(owner_spec.name)
528
+ field_spec = (
529
+ self._udt_field_type_specs.get(owner_spec.name, {}).get(
530
+ node.member
531
+ )
532
+ )
533
+ mutable_collection = (
534
+ field_spec is not None
535
+ and field_spec.kind in {"array", "map", "matrix"}
536
+ )
537
+ access = (
538
+ "get"
539
+ if getattr(self, "_udt_mutable_expr_depth", 0)
540
+ or mutable_collection
541
+ else "read"
542
+ )
543
+ return f"{arena}.{access}({owner}).{node.member}"
374
544
  if isinstance(node.object, Identifier):
375
545
  ns = node.object.name
376
546
  if ns == "strategy":
@@ -750,16 +920,118 @@ class ExprVisitor:
750
920
  safe = self._safe_name(name)
751
921
  if self._active_var_remap and safe in self._active_var_remap:
752
922
  safe = self._active_var_remap[safe]
753
- # Pointer-aliased UDT local (BUG C, rebinding case): field access
754
- # goes through ``->`` since the local holds ``UDT*``.
755
- if name in self._udt_ptr_alias_locals:
756
- return f"{safe}->{node.member}"
757
923
  return f"{safe}.{node.member}"
758
924
  if name not in self.ctx.series_vars:
759
925
  # Unknown identifier — likely an enum value
760
926
  return f'std::string("{node.member}")'
761
927
  return f"{obj}.{node.member}"
762
928
 
929
+ def _operand_na_kind(self, node, cpp_type: str) -> str | None:
930
+ """Classify a relational operand by the ``na`` sentinel it can carry.
931
+
932
+ Returns:
933
+
934
+ * ``"int"`` — an ``int``/``int64_t`` expression that can hold the
935
+ ``INT_MIN`` sentinel. Because that sentinel is a *finite* very-negative
936
+ integer (not NaN), naive C++ diverges from Pine's falsy-on-na rule for
937
+ EVERY relational — ordered (``<`` ``>`` ``<=`` ``>=``) and equality
938
+ (``==`` ``!=``) alike.
939
+ * ``"float"`` — a ``double`` expression that can hold NaN. IEEE already
940
+ yields ``false`` for ``==`` ``<`` ``>`` ``<=`` ``>=`` against NaN
941
+ (matching Pine's falsy), so the ONLY diverging float cell is ``!=``
942
+ (IEEE ``NaN != x`` is true; Pine is falsy).
943
+ * ``None`` — provably not na (numeric/bool literal, inlined
944
+ compile-time constant) or a non-scalar type with no ``is_na`` overload;
945
+ the naive emission is already correct.
946
+ """
947
+ if cpp_type not in _NA_SCALAR_CPP:
948
+ return None
949
+ # Literals are never na.
950
+ if isinstance(node, (NumberLiteral, BoolLiteral)):
951
+ return None
952
+ # A bare ``na`` lowers to ``na<double>()`` — a real NaN, i.e. it IS na.
953
+ if self._is_na_expr(node):
954
+ return "float"
955
+ # Inlined compile-time constants (non-input known vars) never hold na.
956
+ if (isinstance(node, Identifier)
957
+ and node.name in self._known_vars
958
+ and node.name not in self._input_backed_vars
959
+ and not self._known_var_is_lexically_shadowed(node.name)):
960
+ return None
961
+ return "int" if cpp_type in ("int", "int64_t") else "float"
962
+
963
+ def _emit_na_relational(self, op: str, left: str, right: str) -> str:
964
+ """Emit an na-aware relational: ``false`` when either operand is ``na``.
965
+
966
+ Mirrors the ``nz()`` lambda idiom (``visit_call``): each operand is
967
+ hoisted to a temporary so a stateful operand expression is evaluated
968
+ exactly once (no double-step), then compared only when neither side is
969
+ na. ``is_na`` resolves via the emitted ``using namespace pineforge;``
970
+ (``double`` -> ``isnan``; integral -> ``== numeric_limits<T>::min()``).
971
+ """
972
+ return (f"([&]{{ auto _pna_l = ({left}); auto _pna_r = ({right}); "
973
+ f"return !is_na(_pna_l) && !is_na(_pna_r) && "
974
+ f"(_pna_l {op} _pna_r); }}())")
975
+
976
+ def _emit_float_relational(self, op: str, left: str, right: str) -> str:
977
+ """Emit Pine's na-aware float comparator with its fixed equality band.
978
+
979
+ Pine evaluates each operand once, rejects ``na``, and converts an int
980
+ to float when paired with a float. TradingView oracle probes pin a
981
+ magnitude-independent comparison band: finite operands with
982
+ ``abs(left - right) <= 1e-10`` compare equal. Every ordered operator is
983
+ derived from that same predicate, so strict ``<``/``>`` are suppressed
984
+ inside the band while ``<=``/``>=`` accept it.
985
+
986
+ Exact equality is checked first so equal infinities remain equal;
987
+ opposite infinities and finite/infinite pairs remain normally ordered.
988
+ The subtraction can overflow only to infinity, which is safely outside
989
+ the equality band. The preceding ``is_na`` guard keeps every NaN
990
+ comparison falsy, including ``!=``.
991
+ """
992
+ compare = {
993
+ "==": "_pfc_eq",
994
+ "!=": "!_pfc_eq",
995
+ "<": "(_pfc_l < _pfc_r) && !_pfc_eq",
996
+ ">": "(_pfc_l > _pfc_r) && !_pfc_eq",
997
+ "<=": "(_pfc_l < _pfc_r) || _pfc_eq",
998
+ ">=": "(_pfc_l > _pfc_r) || _pfc_eq",
999
+ }[op]
1000
+ return (
1001
+ f"([&]{{ auto _pna_l = ({left}); auto _pna_r = ({right}); "
1002
+ "double _pfc_l = static_cast<double>(_pna_l); "
1003
+ "double _pfc_r = static_cast<double>(_pna_r); "
1004
+ "bool _pfc_eq = (_pfc_l == _pfc_r) || "
1005
+ "(std::isfinite(_pfc_l) && std::isfinite(_pfc_r) && "
1006
+ "std::fabs(_pfc_l - _pfc_r) <= 1e-10); "
1007
+ f"return !is_na(_pna_l) && !is_na(_pna_r) && "
1008
+ f"({compare}); }}())"
1009
+ )
1010
+
1011
+ def _lower_relational(self, op: str, left_node, right_node,
1012
+ left_cpp: str, right_cpp: str) -> str:
1013
+ """Lower a Pine relational with Pine's na and float-precision rules.
1014
+
1015
+ Shared by ``_visit_binop`` and the ``request.security`` expression
1016
+ builder so EVERY relational emission site honours Pine's rules. Any
1017
+ numeric comparison involving an inferred float gets the fixed-band
1018
+ wrapper for all six operators. Pure integer comparisons get the smaller
1019
+ KI-71 wrapper only when an operand can carry the INT_MIN ``na`` sentinel.
1020
+ Non-relational operators and nonnumeric operands fall through unchanged.
1021
+ """
1022
+ if op in _RELATIONAL_OPS:
1023
+ lt = self._infer_type(left_node)
1024
+ rt = self._infer_type(right_node)
1025
+ if lt in _NA_SCALAR_CPP and rt in _NA_SCALAR_CPP:
1026
+ if "double" in (lt, rt):
1027
+ return self._emit_float_relational(op, left_cpp, right_cpp)
1028
+ lk = self._operand_na_kind(left_node, lt)
1029
+ rk = self._operand_na_kind(right_node, rt)
1030
+ int_na = "int" in (lk, rk)
1031
+ if int_na:
1032
+ return self._emit_na_relational(op, left_cpp, right_cpp)
1033
+ return f"({left_cpp} {op} {right_cpp})"
1034
+
763
1035
  def _visit_binop(self, node: BinOp) -> str:
764
1036
  left = self._visit_expr(node.left)
765
1037
  right = self._visit_expr(node.right)
@@ -786,7 +1058,7 @@ class ExprVisitor:
786
1058
  # Ref: https://www.tradingview.com/pine-script-docs/concepts/operators/
787
1059
  if node.op == "/":
788
1060
  return f"((double)({left}) / (double)({right}))"
789
- return f"({left} {op} {right})"
1061
+ return self._lower_relational(op, node.left, node.right, left, right)
790
1062
 
791
1063
  def _visit_unaryop(self, node: UnaryOp) -> str:
792
1064
  operand = self._visit_expr(node.operand)
@@ -807,16 +1079,17 @@ class ExprVisitor:
807
1079
  if name in BAR_FIELDS or name in BAR_SERIES_PUSH:
808
1080
  # Index matches Pine: [0] current bar, [k] k bars ago (runtime Series deque).
809
1081
  return f"_s_{name}[{idx}]"
810
- if name in self.ctx.series_vars:
811
- safe = self._safe_name(name)
812
- # Apply per-call-site var remap
813
- if self._active_var_remap and safe in self._active_var_remap:
814
- safe = self._active_var_remap[safe]
1082
+ safe = self._safe_name(name)
1083
+ # Apply per-call-site / exact block-member remap before deciding
1084
+ # whether the current lexical binding is a Series.
1085
+ if self._active_var_remap and safe in self._active_var_remap:
1086
+ safe = self._active_var_remap[safe]
1087
+ if self._binding_is_series(name, safe):
815
1088
  # Same Pine [k] semantics as Series in runtime/series.hpp
816
1089
  return f"{safe}[{idx}]"
817
- spec = self._collection_types.get(name)
1090
+ spec = self._collection_spec_for_name(name)
818
1091
  if spec is not None and spec.kind in ("array", "map"):
819
- return f"{self._safe_name(name)}[{idx}]"
1092
+ return f"{self._collection_receiver_expr(name)}[{idx}]"
820
1093
  # Handle strategy.* history access (e.g., strategy.position_size[1])
821
1094
  if isinstance(node.object, MemberAccess):
822
1095
  if isinstance(node.object.object, Identifier):
@@ -843,9 +1116,57 @@ class ExprVisitor:
843
1116
  if isinstance(node.object, FuncCall):
844
1117
  inner = self._visit_expr(node.object)
845
1118
  cpp_t = self._infer_type(node.object)
846
- if cpp_t not in ("double", "int", "bool"):
1119
+ if cpp_t not in ("double", "int", "int64_t", "bool"):
847
1120
  cpp_t = "double"
848
1121
  member = self._inline_history_member("hist_call", node)
1122
+ ta_site = self._get_ta_site(node.object)
1123
+ ta_name = (
1124
+ self._ta_name_from_site(ta_site)
1125
+ if ta_site is not None
1126
+ else ""
1127
+ )
1128
+ if (
1129
+ ta_site is not None
1130
+ and ta_name in {"highest", "lowest"}
1131
+ and self._ta_site_uses_precalc(ta_site)
1132
+ ):
1133
+ # Static chart Highest/Lowest sites already advance in the
1134
+ # full-bar precalculation pass, including when the source call
1135
+ # sits behind a Pine-v6 lazy boolean edge. Their direct history
1136
+ # operator must use that same chart-bar clock. Advancing only
1137
+ # ``_hist_call`` when the lazy RHS is reached changes ``[1]``
1138
+ # into "previous evaluation" and can permanently miss a first
1139
+ # qualifying signal (Filter V2's source-bound v6 oracle).
1140
+ #
1141
+ # Keep evaluation lazy: this branch only reads the immutable
1142
+ # precalculated result when the expression is reached. Dynamic
1143
+ # runs, UDF sites, and non-precalculated calls retain the
1144
+ # established call-local history fallback below.
1145
+ ta_mem = self._ta_member_name(ta_site)
1146
+ precalc = f"_precalc_{ta_mem}"
1147
+ return (
1148
+ f"([&]() -> {cpp_t} {{ "
1149
+ f"if (_use_precalc) {{ "
1150
+ f"auto _pf_hist_offset_raw = ({idx}); "
1151
+ f"if (is_na(_pf_hist_offset_raw)) return na<{cpp_t}>(); "
1152
+ f"long double _pf_hist_offset_numeric = "
1153
+ f"static_cast<long double>(_pf_hist_offset_raw); "
1154
+ f"if (bar_index_ < 0 || _pf_hist_offset_numeric < 0.0L || "
1155
+ f"_pf_hist_offset_numeric > "
1156
+ f"static_cast<long double>(bar_index_)) "
1157
+ f"return na<{cpp_t}>(); "
1158
+ f"std::size_t _pf_hist_offset = "
1159
+ f"static_cast<std::size_t>(_pf_hist_offset_numeric); "
1160
+ f"std::size_t _pf_hist_bar = "
1161
+ f"static_cast<std::size_t>(bar_index_) - _pf_hist_offset; "
1162
+ f"if (_pf_hist_bar >= {precalc}.size()) "
1163
+ f"return na<{cpp_t}>(); "
1164
+ f"return {precalc}[(std::size_t)_pf_hist_bar]; }} "
1165
+ f"{cpp_t} _hv = ({inner}); "
1166
+ f"if (history_advances_new_bar()) {member}.push(_hv); "
1167
+ f"else {member}.update(_hv); "
1168
+ f"return {member}[(int)({idx})]; }}())"
1169
+ )
849
1170
  return (
850
1171
  f"([&]() -> {cpp_t} {{ "
851
1172
  f"{cpp_t} _hv = ({inner}); "