Skip to content

AST/interpreter: stable lexical call-site ids for builtin calls#52

Open
FellowTraveler wants to merge 1 commit into
ferranbt:mainfrom
FellowTraveler:pine-call-site-ids
Open

AST/interpreter: stable lexical call-site ids for builtin calls#52
FellowTraveler wants to merge 1 commit into
ferranbt:mainfrom
FellowTraveler:pine-call-site-ids

Conversation

@FellowTraveler

@FellowTraveler FellowTraveler commented Jul 7, 2026

Copy link
Copy Markdown

Summary

Follow-up to the #49/#50/#51 series (same origin: running real Pine strategies through pinecone bar-by-bar and diffing against TradingView's strategy tester). Like those three, this PR is fully independent: it compiles and passes the full workspace suite standalone on current main (cargo test, just fmt-check, just clippy all clean), and cherry-picks conflict-free onto/under each of the other open PRs in any order.

feat(ast,interpreter): stable lexical call-site ids for builtin calls

Built-in functions that keep per-call-site state (e.g. a crossover's previous-bar operands) need a stable identity for each call site. Counting executions ("Nth call this bar = Nth call site") misidentifies sites whenever an earlier call is skipped — which happens today with calls inside untaken if/ternary branches, and will also happen under Pine's lazy and/or (#53, a separate stacked PR which does not depend on this one).

  • pine-ast: Expr::Call gains id: u32 (serde default, skipped when 0, so serialized ASTs and the testdata snapshots are unchanged). Program::new assigns ids in AST-walk (lexical) order starting at 1, covering every statement/expression position including function bodies and parameter defaults.
  • pine-interpreter: FunctionCallArgs gains call_id (0 for hand-built args), threaded from the Call node at builtin dispatch via with_call_id(). Existing constructors keep their signatures.

Hosts implementing stateful builtins (ta.crossover, ta.crossunder, etc.) can key their per-call-site state on call_id instead of execution order. We use exactly this downstream: keying ta.* state by call_id was validated against TradingView's strategy-tester trade list over a 15-year BTCUSD backtest (~1,900 trades reproduced, including exit-reason order comments).

Tests: parser test pins parent-first lexical numbering (f(g(1)) or f(2) → 1,2,3) and re-parse stability.

Built-in functions that keep per-call-site state (e.g. a crossover's
previous-bar operands) need a stable identity for each call site. With
lazy and/or and untaken if/ternary branches, a call site may not execute
on every bar, so 'Nth call this bar = Nth call site' execution-order
counting misidentifies sites whenever an earlier call is skipped.

- pine-ast: Expr::Call gains id: u32 (serde default, skipped when 0, so
  serialized ASTs and the testdata snapshots are unchanged). Program::new
  assigns ids in AST-walk (lexical) order starting at 1, covering every
  statement/expression position including function bodies and parameter
  defaults.
- pine-interpreter: FunctionCallArgs gains call_id (0 for hand-built
  args), threaded from the Call node at builtin dispatch via
  with_call_id(). Existing constructors keep their signatures.

Tests: parser test pins parent-first lexical numbering (f(g(1)) or f(2)
-> 1,2,3) and re-parse stability.

@ferranbt ferranbt left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure I understand what the issue is here? Could you write a Pinescript testcase?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants