@pineforge/codegen-pyodide 0.10.1 → 0.10.3
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/codegen/base.py +207 -19
- package/pineforge_codegen/codegen/emit_top.py +73 -20
- package/pineforge_codegen/codegen/security.py +160 -0
- package/pineforge_codegen/codegen/ta.py +569 -287
- package/pineforge_codegen/codegen/tables.py +140 -34
- package/pineforge_codegen/codegen/types.py +6 -1
- package/pineforge_codegen/codegen/visit_call.py +144 -72
- package/pineforge_codegen/codegen/visit_expr.py +12 -2
- package/pineforge_codegen/support_checker.py +68 -4
- package/pineforge_codegen-0.10.3.tar.gz +0 -0
- package/release.json +2 -2
- package/tables.json +1 -1
- package/pineforge_codegen-0.10.1.tar.gz +0 -0
|
@@ -516,11 +516,20 @@ class SupportChecker:
|
|
|
516
516
|
# then ``request.security(haTicker, ...)``, or ``reqSym = cond ? other :
|
|
517
517
|
# syminfo.tickerid``. First binding wins (closest to declaration).
|
|
518
518
|
self._scalar_defs: dict[str, ASTNode] = {}
|
|
519
|
+
# EVERY reassignment value bound to a scalar name (``name := <expr>``
|
|
520
|
+
# and the compound forms). A declaration alone does not pin the value a
|
|
521
|
+
# ``request.security`` symbol argument actually carries: the engine is
|
|
522
|
+
# registered with no symbol at all, so an accepted-but-divergent rebind
|
|
523
|
+
# silently backtests the CHART feed. Collected in a whole-AST pre-pass
|
|
524
|
+
# (below) as well as during the visit, so a rebind that lexically
|
|
525
|
+
# FOLLOWS the request.security call cannot slip past.
|
|
526
|
+
self._scalar_rebinds: dict[str, list[ASTNode]] = {}
|
|
519
527
|
|
|
520
528
|
# -- Public API --
|
|
521
529
|
|
|
522
530
|
def check(self) -> list[Diagnostic]:
|
|
523
531
|
self._collect_user_definitions(self._ast)
|
|
532
|
+
self._collect_scalar_rebinds(self._ast)
|
|
524
533
|
for stmt in self._ast.body:
|
|
525
534
|
self._visit(stmt)
|
|
526
535
|
return self._diagnostics
|
|
@@ -533,6 +542,42 @@ class SupportChecker:
|
|
|
533
542
|
|
|
534
543
|
# -- Setup --
|
|
535
544
|
|
|
545
|
+
def _record_scalar_rebind(self, node: Assignment) -> None:
|
|
546
|
+
"""Remember ``name := <expr>`` (and the compound forms) by name.
|
|
547
|
+
|
|
548
|
+
A compound op (``+=``, ``*=``, …) records its right-hand operand, which
|
|
549
|
+
is never a current-symbol expression — so a string-built symbol is
|
|
550
|
+
rejected by the same rule that rejects a plain divergent ``:=``.
|
|
551
|
+
"""
|
|
552
|
+
target = node.target
|
|
553
|
+
if not isinstance(target, Identifier) or node.value is None:
|
|
554
|
+
return
|
|
555
|
+
recorded = self._scalar_rebinds.setdefault(target.name, [])
|
|
556
|
+
if not any(value is node.value for value in recorded):
|
|
557
|
+
recorded.append(node.value)
|
|
558
|
+
|
|
559
|
+
def _collect_scalar_rebinds(self, node: ASTNode) -> None:
|
|
560
|
+
"""Pre-pass: collect every scalar rebind anywhere in the AST.
|
|
561
|
+
|
|
562
|
+
The visit walks top-level statements in source order, so a rebind that
|
|
563
|
+
FOLLOWS a ``request.security`` call would otherwise never be seen by
|
|
564
|
+
that call's symbol check. Collecting up front is strictly fail-closed:
|
|
565
|
+
it can only add rejections, never remove one.
|
|
566
|
+
"""
|
|
567
|
+
if isinstance(node, Assignment):
|
|
568
|
+
self._record_scalar_rebind(node)
|
|
569
|
+
for value in vars(node).values():
|
|
570
|
+
if isinstance(value, ASTNode):
|
|
571
|
+
self._collect_scalar_rebinds(value)
|
|
572
|
+
elif isinstance(value, list):
|
|
573
|
+
for item in value:
|
|
574
|
+
if isinstance(item, ASTNode):
|
|
575
|
+
self._collect_scalar_rebinds(item)
|
|
576
|
+
elif isinstance(value, dict):
|
|
577
|
+
for item in value.values():
|
|
578
|
+
if isinstance(item, ASTNode):
|
|
579
|
+
self._collect_scalar_rebinds(item)
|
|
580
|
+
|
|
536
581
|
def _collect_user_definitions(self, ast: Program) -> None:
|
|
537
582
|
for stmt in ast.body:
|
|
538
583
|
if isinstance(stmt, TypeDecl):
|
|
@@ -1001,6 +1046,14 @@ class SupportChecker:
|
|
|
1001
1046
|
)
|
|
1002
1047
|
self._visit_children(node)
|
|
1003
1048
|
|
|
1049
|
+
def _visit_Assignment(self, node: Assignment) -> None:
|
|
1050
|
+
# ``_scalar_defs`` records only the DECLARATION, so a later ``:=``
|
|
1051
|
+
# rebind used to be invisible to the request.security symbol check.
|
|
1052
|
+
# (``check`` also pre-collects these; recording here keeps the visit
|
|
1053
|
+
# self-consistent for any caller that drives visitors directly.)
|
|
1054
|
+
self._record_scalar_rebind(node)
|
|
1055
|
+
self._visit_children(node)
|
|
1056
|
+
|
|
1004
1057
|
def _visit_TupleAssign(self, node: TupleAssign) -> None:
|
|
1005
1058
|
drawing_mask = self._tuple_assign_drawing_mask(node.value)
|
|
1006
1059
|
if drawing_mask:
|
|
@@ -1723,10 +1776,21 @@ class SupportChecker:
|
|
|
1723
1776
|
or self._is_current_symbol_expr(node.false_val, _seen))
|
|
1724
1777
|
# Def-use: resolve a bare identifier through its declaration value so an
|
|
1725
1778
|
# aliased symbol is accepted (``haTicker = ticker.heikinashi(...)`` then
|
|
1726
|
-
# ``request.security(haTicker, ...)``).
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1779
|
+
# ``request.security(haTicker, ...)``). Every ``:=`` rebind of the name
|
|
1780
|
+
# must resolve to the chart symbol too: the declaration alone does not
|
|
1781
|
+
# pin the value the call actually receives. Name-cycle-guarded.
|
|
1782
|
+
if isinstance(node, Identifier) and node.name not in _seen:
|
|
1783
|
+
definition = self._scalar_defs.get(node.name)
|
|
1784
|
+
rebinds = self._scalar_rebinds.get(node.name)
|
|
1785
|
+
if definition is not None or rebinds:
|
|
1786
|
+
_seen.add(node.name)
|
|
1787
|
+
if rebinds and not all(
|
|
1788
|
+
self._is_current_symbol_expr(value, _seen) for value in rebinds
|
|
1789
|
+
):
|
|
1790
|
+
return False
|
|
1791
|
+
if definition is None:
|
|
1792
|
+
return False
|
|
1793
|
+
return self._is_current_symbol_expr(definition, _seen)
|
|
1730
1794
|
return False
|
|
1731
1795
|
|
|
1732
1796
|
# -- Pine timeframe-literal validation --
|
|
Binary file
|
package/release.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"codegen": "0.10.
|
|
2
|
+
"codegen": "0.10.3",
|
|
3
3
|
"pyodide": "314.0.0",
|
|
4
4
|
"python": "3.14.0",
|
|
5
5
|
"emscripten": "emscripten_5_0_3",
|
|
6
|
-
"sha256": "
|
|
6
|
+
"sha256": "50f545e8a008c23ec01a752a5e8bd00e885dda9dcd2bb417e2af18d680c22a6e"
|
|
7
7
|
}
|
package/tables.json
CHANGED
|
Binary file
|