Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
406 changes: 351 additions & 55 deletions pineforge_codegen/analyzer/base.py

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions pineforge_codegen/analyzer/call_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1185,6 +1185,29 @@ def _handle_user_func_call(self, func_name: str, node: FuncCall) -> PineType:
caller_name = sym.scope[5:]
self._func_series_vars.setdefault(caller_name, set()).add(arg.name)
else:
# Keep the exact declaration/member registries
# in lockstep with the legacy raw-name series
# promotion. Codegen uses those identities to
# distinguish the real global binding from a
# same-named lexical scalar tombstone.
exact_member = getattr(
sym, "_pf_var_member_name", None
)
if exact_member is not None:
self._series_var_members.add(exact_member)
decl_node_id = getattr(
sym, "_pf_decl_node_id", None
)
if decl_node_id is not None:
self._series_decl_nodes.add(decl_node_id)
binding_name = getattr(
sym,
"_pf_decl_binding_name",
arg.name,
)
self._series_decl_bindings.add(
(decl_node_id, binding_name)
)
self._series_vars.add(arg.name)

# Per-call-site cloning: TA, series/var, and fixnan state all advance
Expand Down
18 changes: 18 additions & 0 deletions pineforge_codegen/analyzer/contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,18 @@ class AnalyzerContext:
symbols: Any # SymbolTable
ta_call_sites: list = field(default_factory=list)
series_vars: set = field(default_factory=set)
# Exact emitted member identities for persistent ``var``/``varip``
# bindings that are history-referenced. Unlike ``series_vars`` this set
# preserves sibling-block renames such as ``x__blk1``.
series_var_members: set = field(default_factory=set)
# Exact VarDecl node ids whose binding is history-referenced. This lets
# codegen distinguish a scalar local shadow from a same-named global
# Series without relying on the raw-name union above.
series_decl_nodes: set = field(default_factory=set)
# Exact declaration binding keys ``(node_id, raw_name)``. TupleAssign has
# one AST node for several independently scoped names, so node identity
# alone cannot say which destructured element owns history storage.
series_decl_bindings: set = field(default_factory=set)
series_bar_fields: set = field(default_factory=set)
var_members: list = field(default_factory=list) # [(name, PineType, init_expr_str)]
func_infos: list = field(default_factory=list)
Expand All @@ -202,6 +214,12 @@ class AnalyzerContext:
# id(VarDecl) -> (node, emitted member name, PineType, rendered initializer,
# is function/method scoped)
var_member_metadata_by_node: dict = field(default_factory=dict)
# id(VarDecl) -> exact structured declaration type. Name-keyed type
# registries cannot distinguish sibling/callable persistent bindings.
var_member_type_specs_by_node: dict = field(default_factory=dict)
# id(VarDecl) -> owning callable key (``FuncInfo.name``), or None for
# top-level/on-bar persistent bindings.
var_member_owners_by_node: dict = field(default_factory=dict)
# Per-call-site TA member cloning for user functions:
func_ta_ranges: dict = field(default_factory=dict) # func_name -> (start_idx, end_idx)
func_call_cs_map: dict = field(default_factory=dict) # call_node_id -> (func_name, call_site_index)
Expand Down
59 changes: 58 additions & 1 deletion pineforge_codegen/analyzer/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@

from ..ast_nodes import (
ASTNode, BinOp, BoolLiteral, ExprStmt, FuncCall, Identifier, IfStmt,
MemberAccess, NaLiteral, NumberLiteral, StringLiteral, Ternary,
MemberAccess, NaLiteral, NumberLiteral, StringLiteral, Subscript, Ternary,
TupleLiteral, UnaryOp,
)
from ..symbols import PineType, TypeSpec
Expand Down Expand Up @@ -200,6 +200,34 @@ def _type_spec_from_expr(self, value: ASTNode | None) -> TypeSpec | None:
and false_spec.kind == "map"
and isinstance(value.true_val, NaLiteral)):
return false_spec
# Drawing handles are nullable reference-like values in Pine. A
# bare ``na`` arm therefore acquires the other arm's exact handle
# type, just like the established PineMap path above. Keep this
# intentionally narrower than arbitrary UDTs/collections: their
# target-typed selection semantics are not established here.
if (true_spec is not None
and true_spec.kind == "udt"
and true_spec.name in _DRAWING_TYPE_NAMES
and isinstance(value.false_val, NaLiteral)):
return true_spec
if (false_spec is not None
and false_spec.kind == "udt"
and false_spec.name in _DRAWING_TYPE_NAMES
and isinstance(value.true_val, NaLiteral)):
return false_spec
return None
if isinstance(value, Subscript):
# Pine's history operator preserves the value type: a
# ``Series<line>`` read such as ``h[1]`` is a scalar ``line``
# handle, not the legacy numeric fallback. Keep this refinement
# drawing-only; collection subscripts have separate array/map
# semantics and primitive history inference already flows through
# PineType in ``_visit_Subscript``.
receiver_spec = self._type_spec_from_expr(value.object)
if (receiver_spec is not None
and receiver_spec.kind == "udt"
and receiver_spec.name in _DRAWING_TYPE_NAMES):
return receiver_spec
return None
if isinstance(value, IfStmt):
def terminal_expr(body):
Expand All @@ -214,6 +242,16 @@ def terminal_expr(body):
return None
true_spec = self._type_spec_from_expr(true_node)
false_spec = self._type_spec_from_expr(false_node)
true_is_na = (
isinstance(true_node, NaLiteral)
or (isinstance(true_node, Identifier)
and true_node.name == "na")
)
false_is_na = (
isinstance(false_node, NaLiteral)
or (isinstance(false_node, Identifier)
and false_node.name == "na")
)
if (true_spec is not None
and true_spec.kind == "map"
and true_spec == false_spec):
Expand All @@ -226,6 +264,21 @@ def terminal_expr(body):
and false_spec.kind == "map"
and isinstance(true_node, NaLiteral)):
return false_spec
if (true_spec is not None
and true_spec.kind == "udt"
and true_spec.name in _DRAWING_TYPE_NAMES
and true_spec == false_spec):
return true_spec
if (true_spec is not None
and true_spec.kind == "udt"
and true_spec.name in _DRAWING_TYPE_NAMES
and false_is_na):
return true_spec
if (false_spec is not None
and false_spec.kind == "udt"
and false_spec.name in _DRAWING_TYPE_NAMES
and true_is_na):
return false_spec
return None
if isinstance(value, FuncCall):
cal = value.callee
Expand Down Expand Up @@ -390,6 +443,10 @@ def terminal_expr(body):
cal.name if isinstance(cal, Identifier) else None)
if fname and fname in getattr(self, "_func_return_type_specs", {}):
return self._func_return_type_specs[fname]
if fname and fname in getattr(self, "_func_udt_return_types", {}):
udt_return = self._func_udt_return_types[fname]
if udt_return in _DRAWING_TYPE_NAMES:
return TypeSpec.udt(udt_return)
if isinstance(value, MemberAccess):
owner = self._type_spec_from_expr(value.object)
if owner is not None and owner.kind == "udt" and owner.name:
Expand Down
Loading
Loading