@pineforge/codegen-pyodide 0.10.0 → 0.10.2
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 +2280 -228
- package/pineforge_codegen/codegen/drawing.py +94 -14
- package/pineforge_codegen/codegen/emit_top.py +747 -177
- package/pineforge_codegen/codegen/security.py +1546 -132
- package/pineforge_codegen/codegen/ta.py +435 -99
- package/pineforge_codegen/codegen/tables.py +328 -80
- package/pineforge_codegen/codegen/types.py +1160 -227
- package/pineforge_codegen/codegen/visit_call.py +1191 -104
- package/pineforge_codegen/codegen/visit_expr.py +283 -48
- package/pineforge_codegen/codegen/visit_stmt.py +1137 -202
- 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 +262 -23
- package/pineforge_codegen/symbols.py +65 -0
- package/pineforge_codegen-0.10.2.tar.gz +0 -0
- package/release.json +2 -2
- package/tables.json +9 -11
- package/pineforge_codegen-0.10.0.tar.gz +0 -0
|
@@ -166,10 +166,11 @@ _BUILTIN_NAMESPACE_NAMES: frozenset[str] = frozenset({
|
|
|
166
166
|
})
|
|
167
167
|
|
|
168
168
|
|
|
169
|
-
# KI-71: Pine relational comparisons (``==`` ``!=`` ``<`` ``>``
|
|
170
|
-
# with an ``na`` operand evaluate *falsy*.
|
|
171
|
-
#
|
|
172
|
-
#
|
|
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.
|
|
173
174
|
_RELATIONAL_OPS: frozenset[str] = frozenset({"==", "!=", "<", ">", "<=", ">="})
|
|
174
175
|
|
|
175
176
|
# C++ scalar types carrying a detectable ``na`` sentinel via ``is_na``:
|
|
@@ -221,7 +222,21 @@ class ExprVisitor:
|
|
|
221
222
|
if isinstance(node, Subscript):
|
|
222
223
|
return self._visit_subscript(node)
|
|
223
224
|
if isinstance(node, TupleLiteral):
|
|
224
|
-
elems =
|
|
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)
|
|
225
240
|
return f"std::make_tuple({elems})"
|
|
226
241
|
return "/* unknown */"
|
|
227
242
|
|
|
@@ -233,34 +248,132 @@ class ExprVisitor:
|
|
|
233
248
|
return (isinstance(node, NaLiteral)
|
|
234
249
|
or (isinstance(node, Identifier) and node.name == "na"))
|
|
235
250
|
|
|
236
|
-
def
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
|
240
319
|
if not target_name:
|
|
241
320
|
return None
|
|
242
|
-
|
|
243
|
-
if
|
|
244
|
-
return f"{DRAWING_TYPE_TO_CPP[udt]}{{}}"
|
|
245
|
-
return None
|
|
321
|
+
udt_name = self._identifier_udt_type(target_name)
|
|
322
|
+
return udt_name if udt_name in self._udt_defs else None
|
|
246
323
|
|
|
247
324
|
def _visit_rhs_value(self, value_node, target_name: str | None = None,
|
|
248
325
|
target_cpp_type: str | None = None) -> str:
|
|
249
326
|
"""Visit an assignment / declaration RHS.
|
|
250
327
|
|
|
251
328
|
A bare ``na`` lowers to a type-appropriate initializer for the target
|
|
252
|
-
instead of ``na<double>()``: drawing
|
|
253
|
-
|
|
254
|
-
``bool`` use ``na<T>()``. Without
|
|
255
|
-
``
|
|
256
|
-
(
|
|
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.
|
|
257
335
|
"""
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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}{{}}"
|
|
262
350
|
if target_cpp_type in ("std::string", "int", "int64_t", "bool"):
|
|
263
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
|
+
)
|
|
264
377
|
return self._visit_expr(value_node)
|
|
265
378
|
|
|
266
379
|
def _visit_ident(self, node: Identifier) -> str:
|
|
@@ -268,6 +381,9 @@ class ExprVisitor:
|
|
|
268
381
|
# Bare 'na' identifier → na<double>()
|
|
269
382
|
if name == "na":
|
|
270
383
|
return "na<double>()"
|
|
384
|
+
pending_outer = getattr(self, "_pending_decl_outer_alias", {})
|
|
385
|
+
if name in pending_outer:
|
|
386
|
+
return pending_outer[name]
|
|
271
387
|
# Function parameters that are series — read current value
|
|
272
388
|
if name in self._current_func_series_params:
|
|
273
389
|
return f"{self._safe_name(name)}[0]"
|
|
@@ -281,7 +397,9 @@ class ExprVisitor:
|
|
|
281
397
|
# Inline known constants (literals). Input-backed names are NOT inlined
|
|
282
398
|
# because strategy_set_input() can override them at runtime; emit the
|
|
283
399
|
# variable name and let get_input_*() produce the current value.
|
|
284
|
-
if name in self._known_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)):
|
|
285
403
|
val = self._known_vars[name]
|
|
286
404
|
if isinstance(val, bool):
|
|
287
405
|
return "true" if val else "false"
|
|
@@ -316,7 +434,7 @@ class ExprVisitor:
|
|
|
316
434
|
# Apply per-call-site var remap (for function-local vars)
|
|
317
435
|
if self._active_var_remap and safe in self._active_var_remap:
|
|
318
436
|
safe = self._active_var_remap[safe]
|
|
319
|
-
if name
|
|
437
|
+
if self._binding_is_series(name, safe):
|
|
320
438
|
return f"{safe}[0]"
|
|
321
439
|
# Safety net: by here the name resolved to none of the builtins,
|
|
322
440
|
# constants, parameters, or declared variables handled above, so
|
|
@@ -377,6 +495,16 @@ class ExprVisitor:
|
|
|
377
495
|
return True
|
|
378
496
|
return False
|
|
379
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
|
+
|
|
380
508
|
def _visit_member_access(self, node: MemberAccess) -> str:
|
|
381
509
|
# UDT field whose type was a drawing primitive (label/line/box/
|
|
382
510
|
# linefill/polyline/table/chart.point) — the field is dropped from
|
|
@@ -385,6 +513,34 @@ class ExprVisitor:
|
|
|
385
513
|
# See: pineforge-codegen issue #10.
|
|
386
514
|
if self._is_omitted_udt_field(node):
|
|
387
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}"
|
|
388
544
|
if isinstance(node.object, Identifier):
|
|
389
545
|
ns = node.object.name
|
|
390
546
|
if ns == "strategy":
|
|
@@ -764,10 +920,6 @@ class ExprVisitor:
|
|
|
764
920
|
safe = self._safe_name(name)
|
|
765
921
|
if self._active_var_remap and safe in self._active_var_remap:
|
|
766
922
|
safe = self._active_var_remap[safe]
|
|
767
|
-
# Pointer-aliased UDT local (BUG C, rebinding case): field access
|
|
768
|
-
# goes through ``->`` since the local holds ``UDT*``.
|
|
769
|
-
if name in self._udt_ptr_alias_locals:
|
|
770
|
-
return f"{safe}->{node.member}"
|
|
771
923
|
return f"{safe}.{node.member}"
|
|
772
924
|
if name not in self.ctx.series_vars:
|
|
773
925
|
# Unknown identifier — likely an enum value
|
|
@@ -803,7 +955,8 @@ class ExprVisitor:
|
|
|
803
955
|
# Inlined compile-time constants (non-input known vars) never hold na.
|
|
804
956
|
if (isinstance(node, Identifier)
|
|
805
957
|
and node.name in self._known_vars
|
|
806
|
-
and node.name not in self._input_backed_vars
|
|
958
|
+
and node.name not in self._input_backed_vars
|
|
959
|
+
and not self._known_var_is_lexically_shadowed(node.name)):
|
|
807
960
|
return None
|
|
808
961
|
return "int" if cpp_type in ("int", "int64_t") else "float"
|
|
809
962
|
|
|
@@ -820,29 +973,62 @@ class ExprVisitor:
|
|
|
820
973
|
f"return !is_na(_pna_l) && !is_na(_pna_r) && "
|
|
821
974
|
f"(_pna_l {op} _pna_r); }}())")
|
|
822
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
|
+
|
|
823
1011
|
def _lower_relational(self, op: str, left_node, right_node,
|
|
824
1012
|
left_cpp: str, right_cpp: str) -> str:
|
|
825
|
-
"""Lower a Pine relational
|
|
1013
|
+
"""Lower a Pine relational with Pine's na and float-precision rules.
|
|
826
1014
|
|
|
827
1015
|
Shared by ``_visit_binop`` and the ``request.security`` expression
|
|
828
|
-
builder so EVERY relational emission site honours Pine's
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
IEEE is already falsy-on-NaN there, so wrapping would be a pure no-op.
|
|
834
|
-
Non-relational ``op`` (or non-scalar operands with no ``is_na``) fall
|
|
835
|
-
through to the naive ``(left op right)`` form unchanged.
|
|
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.
|
|
836
1021
|
"""
|
|
837
1022
|
if op in _RELATIONAL_OPS:
|
|
838
1023
|
lt = self._infer_type(left_node)
|
|
839
1024
|
rt = self._infer_type(right_node)
|
|
840
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)
|
|
841
1028
|
lk = self._operand_na_kind(left_node, lt)
|
|
842
1029
|
rk = self._operand_na_kind(right_node, rt)
|
|
843
1030
|
int_na = "int" in (lk, rk)
|
|
844
|
-
|
|
845
|
-
if int_na or (op == "!=" and float_na):
|
|
1031
|
+
if int_na:
|
|
846
1032
|
return self._emit_na_relational(op, left_cpp, right_cpp)
|
|
847
1033
|
return f"({left_cpp} {op} {right_cpp})"
|
|
848
1034
|
|
|
@@ -893,16 +1079,17 @@ class ExprVisitor:
|
|
|
893
1079
|
if name in BAR_FIELDS or name in BAR_SERIES_PUSH:
|
|
894
1080
|
# Index matches Pine: [0] current bar, [k] k bars ago (runtime Series deque).
|
|
895
1081
|
return f"_s_{name}[{idx}]"
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
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):
|
|
901
1088
|
# Same Pine [k] semantics as Series in runtime/series.hpp
|
|
902
1089
|
return f"{safe}[{idx}]"
|
|
903
|
-
spec = self.
|
|
1090
|
+
spec = self._collection_spec_for_name(name)
|
|
904
1091
|
if spec is not None and spec.kind in ("array", "map"):
|
|
905
|
-
return f"{self.
|
|
1092
|
+
return f"{self._collection_receiver_expr(name)}[{idx}]"
|
|
906
1093
|
# Handle strategy.* history access (e.g., strategy.position_size[1])
|
|
907
1094
|
if isinstance(node.object, MemberAccess):
|
|
908
1095
|
if isinstance(node.object.object, Identifier):
|
|
@@ -929,9 +1116,57 @@ class ExprVisitor:
|
|
|
929
1116
|
if isinstance(node.object, FuncCall):
|
|
930
1117
|
inner = self._visit_expr(node.object)
|
|
931
1118
|
cpp_t = self._infer_type(node.object)
|
|
932
|
-
if cpp_t not in ("double", "int", "bool"):
|
|
1119
|
+
if cpp_t not in ("double", "int", "int64_t", "bool"):
|
|
933
1120
|
cpp_t = "double"
|
|
934
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
|
+
)
|
|
935
1170
|
return (
|
|
936
1171
|
f"([&]() -> {cpp_t} {{ "
|
|
937
1172
|
f"{cpp_t} _hv = ({inner}); "
|