Skip to main content

pine_interpreter/
lib.rs

1mod num;
2mod signature;
3
4pub use num::Num;
5pub use signature::{BuiltinSignature, Param, ParamType};
6
7use pine_core::{Color, DefaultPineOutput, PineOutput, MAX_LOOKBACK};
8
9use pine_ast::{Argument, BinOp, Expr, Literal, MethodParam, Program, Stmt, TypeField, UnOp};
10use std::cell::RefCell;
11use std::collections::HashMap;
12use std::rc::Rc;
13use thiserror::Error;
14
15pub use pine_core::LibraryLoader;
16
17/// Record `value` as what `name` held on a completed bar, so `name[n]` can reach
18/// it. Entries beyond [`MAX_LOOKBACK`] are dropped.
19///
20/// Takes the history map rather than `&mut self` so callers can hold a borrow of
21/// another interpreter field while recording.
22fn push_history<O: PineOutput>(
23    history: &mut HashMap<String, Vec<Value<O>>>,
24    name: &str,
25    value: Value<O>,
26) {
27    let entries = history.entry(name.to_string()).or_default();
28    entries.push(value);
29    if entries.len() > MAX_LOOKBACK {
30        entries.drain(..entries.len() - MAX_LOOKBACK);
31    }
32}
33
34/// Apply a numeric binary operator under Pine's int/float rule (see [`Num`]).
35/// An na operand, a non-numeric operand, or a `None` from `op` (a zero divisor)
36/// all yield `na`.
37fn numeric_op<O: PineOutput>(
38    left: &Value<O>,
39    right: &Value<O>,
40    op: impl Fn(Num, Num) -> Option<Num>,
41) -> Result<Value<O>, RuntimeError> {
42    // Reject a genuinely non-numeric operand (a string), while letting na through.
43    left.to_number()?;
44    right.to_number()?;
45
46    match (left.as_num(), right.as_num()) {
47        (Some(a), Some(b)) => Ok(op(a, b).map_or(Value::Na, Value::from)),
48        _ => Ok(Value::Na),
49    }
50}
51
52#[derive(Error, Debug)]
53pub enum RuntimeError {
54    #[error("Variable '{0}' not found")]
55    UndefinedVariable(String),
56
57    #[error("Type error: {0}")]
58    TypeError(String),
59
60    #[error("Division by zero")]
61    DivisionByZero,
62
63    #[error("Index out of bounds: {0}")]
64    IndexOutOfBounds(usize),
65
66    #[error("Cannot iterate: from={0}, to={1}")]
67    InvalidForLoop(f64, f64),
68
69    #[error("Break statement outside of loop")]
70    BreakOutsideLoop,
71
72    #[error("Continue statement outside of loop")]
73    ContinueOutsideLoop,
74
75    #[error("Library error: {0}")]
76    LibraryError(String),
77
78    #[error("Cannot reassign const variable '{0}'")]
79    ConstReassignment(String),
80
81    #[error("{0}")]
82    UserError(String),
83}
84
85/// Control flow signals for loops
86#[derive(Debug, Clone, PartialEq)]
87enum LoopControl {
88    None,
89    Break,
90    Continue,
91}
92
93/// Variable storage with const qualifier tracking
94#[derive(Clone)]
95struct Variable<O: PineOutput = DefaultPineOutput> {
96    value: Value<O>,
97    is_const: bool,
98    /// true when declared with `var`/`varip` — this variable survives function call boundaries
99    is_var_persistent: bool,
100}
101
102/// Represents a time series with an identifier and current value
103#[derive(Clone, Debug)]
104pub struct Series<O: PineOutput = DefaultPineOutput> {
105    pub id: String,
106    pub current: Box<Value<O>>,
107    pub history: Option<Rc<RefCell<Vec<Value<O>>>>>,
108}
109
110/// The lazy scalar an object carries, so a single name can be *both* a namespace
111/// and a value: `dayofweek.monday` reads a member while bare `dayofweek` invokes
112/// this to get the current day. Computed from live interpreter state on each use
113/// (never called with `()`), so there is no per-bar value to keep refreshed.
114pub type ObjectValueFn<O> = Rc<dyn Fn(&mut Interpreter<O>) -> Result<Value<O>, RuntimeError>>;
115
116pub type PerBarAdvance<O> = Rc<dyn Fn(&mut Interpreter<O>)>;
117
118/// The insertion-ordered key/value pairs backing a [`Value::Map`].
119pub type MapEntries<O> = Rc<RefCell<Vec<(Value<O>, Value<O>)>>>;
120
121/// Value types in the interpreter
122#[derive(Clone)]
123pub enum Value<O: PineOutput> {
124    Int(i64),
125    Number(f64),
126    String(String),
127    Bool(bool),
128    Na,                                // PineScript's N/A value
129    Array(Rc<RefCell<Vec<Value<O>>>>), // Mutable shared array reference
130    Series(Series<O>),                 // Time series - ID and current value only
131    Object {
132        type_name: String, // The type name of this object (e.g., "InfoLabel")
133        fields: Rc<RefCell<HashMap<String, Value<O>>>>, // Dictionary/Object with string keys
134        call: Option<Builtin<O>>,
135        value: Option<ObjectValueFn<O>>,
136    },
137    Function {
138        params: Vec<pine_ast::FunctionParam>,
139        body: Vec<Stmt>,
140    },
141    BuiltinFunction(Builtin<O>), // Builtin callable plus the arguments it accepts
142    /// An unevaluated expression, passed to a builtin that captured it (a lazy
143    /// parameter) to run in another context — e.g. `request.security`.
144    Expr(Rc<Expr>),
145    Type {
146        name: String,
147        fields: Vec<TypeField>,
148    }, // User-defined type
149    Enum {
150        enum_name: String,  // The enum type name (e.g., "Signal")
151        field_name: String, // The specific field/member name (e.g., "buy")
152        title: String,      // The title of this enum member
153    }, // Enum member value
154    Color(Color), // Color value
155    Matrix {
156        element_type: String, // Type of elements: "int", "float", "string", "bool"
157        data: Rc<RefCell<Vec<Vec<Value<O>>>>>, // 2D matrix - mutable shared reference to rows of columns
158    },
159    Map {
160        key_type: String,
161        value_type: String,
162        data: MapEntries<O>,
163    },
164}
165
166impl<O: PineOutput> From<Num> for Value<O> {
167    /// A [`Num`] carries its own type, so it lands on the matching variant.
168    fn from(n: Num) -> Self {
169        match n {
170            Num::Int(n) => Value::Int(n),
171            Num::Float(n) => Value::Number(n),
172        }
173    }
174}
175
176impl<O: PineOutput> Value<O> {
177    pub fn new_color(r: u8, g: u8, b: u8, t: u8) -> Value<O> {
178        Value::Color(Color::new(r, g, b, t))
179    }
180}
181
182// Manual Debug impl since function pointers don't implement Debug
183impl<O: PineOutput> std::fmt::Debug for Value<O> {
184    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185        match self {
186            Value::Int(n) => write!(f, "Int({:?})", n),
187            Value::Number(n) => write!(f, "Number({:?})", n),
188            Value::String(s) => write!(f, "String({:?})", s),
189            Value::Bool(b) => write!(f, "Bool({:?})", b),
190            Value::Na => write!(f, "Na"),
191            Value::Array(a) => write!(f, "Array({:?})", a),
192            Value::Series(s) => write!(f, "Series({:?})", s),
193            Value::Object {
194                type_name, fields, ..
195            } => write!(f, "Object({}:{:?})", type_name, fields),
196            Value::Function { params, .. } => write!(f, "Function({} params)", params.len()),
197            Value::BuiltinFunction(_) => write!(f, "BuiltinFunction"),
198            Value::Expr(_) => write!(f, "Expr"),
199            Value::Type { name, .. } => write!(f, "Type({})", name),
200            Value::Enum {
201                enum_name,
202                field_name,
203                ..
204            } => write!(f, "Enum({}::{})", enum_name, field_name),
205            Value::Color(color) => write!(
206                f,
207                "Color(rgba({}, {}, {}, {}))",
208                color.r, color.g, color.b, color.t
209            ),
210            Value::Matrix { element_type, data } => {
211                write!(f, "Matrix<{}>({:?})", element_type, data)
212            }
213            Value::Map {
214                key_type,
215                value_type,
216                data,
217            } => {
218                write!(f, "Map<{}, {}>({:?})", key_type, value_type, data)
219            }
220        }
221    }
222}
223
224impl<O: PineOutput> PartialEq for Value<O> {
225    fn eq(&self, other: &Self) -> bool {
226        match (self, other) {
227            (Value::Int(a), Value::Int(b)) => a == b,
228            // int and float compare by value, so `1 == 1.0`.
229            (Value::Int(a), Value::Number(b)) | (Value::Number(b), Value::Int(a)) => {
230                (*a as f64 - b).abs() < f64::EPSILON
231            }
232            (Value::Number(a), Value::Number(b)) => (a - b).abs() < f64::EPSILON,
233            (Value::String(a), Value::String(b)) => a == b,
234            (Value::Bool(a), Value::Bool(b)) => a == b,
235            (Value::Na, Value::Na) => true,
236            // Arrays compare by reference (Rc pointer equality)
237            (Value::Array(a), Value::Array(b)) => Rc::ptr_eq(a, b),
238            // Series compare by ID and current value
239            (Value::Series(a), Value::Series(b)) => a.id == b.id && *a.current == *b.current,
240            (Value::Object { fields: a, .. }, Value::Object { fields: b, .. }) => Rc::ptr_eq(a, b),
241            // Functions never equal (can't compare closures or function pointers)
242            (Value::Function { .. }, Value::Function { .. }) => false,
243            (Value::BuiltinFunction(_), Value::BuiltinFunction(_)) => false,
244            // Types compare by name
245            (Value::Type { name: a, .. }, Value::Type { name: b, .. }) => a == b,
246            // Enums compare by enum name and field name (ensuring type safety)
247            (
248                Value::Enum {
249                    enum_name: a_enum,
250                    field_name: a_field,
251                    ..
252                },
253                Value::Enum {
254                    enum_name: b_enum,
255                    field_name: b_field,
256                    ..
257                },
258            ) => a_enum == b_enum && a_field == b_field,
259            // Colors compare by all components
260            (Value::Color(c1), Value::Color(c2)) => c1 == c2,
261            // Matrices compare by reference (Rc pointer equality)
262            (Value::Matrix { data: a, .. }, Value::Matrix { data: b, .. }) => Rc::ptr_eq(a, b),
263            // Maps compare by reference (Rc pointer equality)
264            (Value::Map { data: a, .. }, Value::Map { data: b, .. }) => Rc::ptr_eq(a, b),
265            _ => false,
266        }
267    }
268}
269
270/// Evaluated function argument
271#[derive(Debug, Clone)]
272pub enum EvaluatedArg<O: PineOutput = DefaultPineOutput> {
273    Positional(Value<O>),
274    Named { name: String, value: Value<O> },
275}
276
277/// Container for function call arguments including type parameters
278#[derive(Debug, Clone)]
279pub struct FunctionCallArgs<O: PineOutput = DefaultPineOutput> {
280    pub type_args: Vec<String>,
281    pub args: Vec<EvaluatedArg<O>>,
282    pub call_id: u32,
283}
284
285impl<O: PineOutput> FunctionCallArgs<O> {
286    pub fn new(type_args: Vec<String>, args: Vec<EvaluatedArg<O>>) -> Self {
287        Self {
288            type_args,
289            args,
290            call_id: 0,
291        }
292    }
293
294    pub fn without_types(args: Vec<EvaluatedArg<O>>) -> Self {
295        Self {
296            type_args: vec![],
297            args,
298            call_id: 0,
299        }
300    }
301
302    pub fn with_call_id(mut self, call_id: u32) -> Self {
303        self.call_id = call_id;
304        self
305    }
306}
307
308/// Type signature for builtin functions (can be function pointers or closures)
309pub type BuiltinFn<O> =
310    Rc<dyn Fn(&mut Interpreter<O>, FunctionCallArgs<O>) -> Result<Value<O>, RuntimeError>>;
311
312/// A callable builtin together with the arguments it accepts, so semantic
313/// analysis can reject a bad call without running it. Both come from the same
314/// `#[derive(BuiltinFunction)]`, so they cannot drift apart.
315#[derive(Clone)]
316pub struct Builtin<O: PineOutput> {
317    pub call: BuiltinFn<O>,
318    // Shared for the life of the process: signatures are invariant, so they are
319    // built once (per builtin) rather than reallocated on every compile/call.
320    pub signature: &'static BuiltinSignature,
321}
322
323impl<O: PineOutput> Builtin<O> {
324    /// A builtin whose arguments are not described, so nothing is checked.
325    /// For the few callables written by hand rather than derived.
326    pub fn untyped(call: BuiltinFn<O>) -> Self {
327        Self {
328            call,
329            signature: BuiltinSignature::empty(),
330        }
331    }
332}
333
334impl<O: PineOutput> Value<O> {
335    /// Extract as f64. Na → NaN (propagates via IEEE 754). Type mismatch → Err.
336    /// For external callers (builtins, adapters). Internal operator code uses `to_number`.
337    pub fn as_number(&self) -> Result<f64, RuntimeError> {
338        self.to_number().map(|opt| opt.unwrap_or(f64::NAN))
339    }
340
341    /// Extract as bool. Na → false (Pine v6: booleans are never na). Type mismatch → Err.
342    /// For external callers. Internal conditional code uses `truthy_for_condition`.
343    pub fn as_bool(&self) -> Result<bool, RuntimeError> {
344        Ok(self.to_bool()?.unwrap_or(false))
345    }
346
347    /// This value as a [`Num`], preserving whether it is an int or a float, so
348    /// callers can apply Pine's overload rule. `None` for `na` and for anything
349    /// non-numeric.
350    pub fn as_num(&self) -> Option<Num> {
351        match self {
352            Value::Int(n) => Some(Num::Int(*n)),
353            Value::Number(n) => Some(Num::Float(*n)),
354            Value::Bool(b) => Some(Num::Int(if *b { 1 } else { 0 })),
355            Value::Series(series) => series.current.as_num(),
356            _ => None,
357        }
358    }
359
360    /// The integer this value carries, if it is int-typed.
361    fn as_int(&self) -> Option<i64> {
362        match self.as_num() {
363            Some(Num::Int(n)) => Some(n),
364            _ => None,
365        }
366    }
367
368    /// Returns Ok(None) when self is Na so callers can propagate na correctly.
369    /// Returns Err for genuine type mismatches (e.g. passing a string to arithmetic).
370    pub fn to_number(&self) -> Result<Option<f64>, RuntimeError> {
371        match self {
372            Value::Int(n) => Ok(Some(*n as f64)),
373            Value::Number(n) => Ok(Some(*n)),
374            Value::Bool(b) => Ok(Some(if *b { 1.0 } else { 0.0 })),
375            Value::Series(series) => series.current.to_number(),
376            Value::Na => Ok(None),
377            _ => Err(RuntimeError::TypeError(format!(
378                "Expected number, got {:?}",
379                self
380            ))),
381        }
382    }
383
384    /// Returns Ok(None) when self is Na. Returns Err for type mismatches.
385    pub fn to_bool(&self) -> Result<Option<bool>, RuntimeError> {
386        match self {
387            Value::Bool(b) => Ok(Some(*b)),
388            Value::Int(n) => Ok(Some(*n != 0)),
389            // NaN must not become true: `n != 0.0` is true for NaN in IEEE 754.
390            Value::Number(n) => Ok(Some(*n != 0.0 && !n.is_nan())),
391            Value::Na => Ok(None),
392            _ => Err(RuntimeError::TypeError(format!(
393                "Expected bool, got {:?}",
394                self
395            ))),
396        }
397    }
398
399    /// For conditional boundaries only (if, while, ternary).
400    /// Pine v6: na in a condition takes the false/else branch.
401    pub fn truthy_for_condition(&self) -> Result<bool, RuntimeError> {
402        Ok(self.to_bool()?.unwrap_or(false))
403    }
404
405    pub fn as_string(&self) -> Result<String, RuntimeError> {
406        match self {
407            Value::String(s) => Ok(s.clone()),
408            Value::Int(n) => Ok(n.to_string()),
409            Value::Number(n) => Ok(n.to_string()),
410            Value::Bool(b) => Ok(b.to_string()),
411            Value::Na => Ok("na".to_string()),
412            _ => Err(RuntimeError::TypeError(format!(
413                "Cannot convert {:?} to string",
414                self
415            ))),
416        }
417    }
418
419    pub fn as_array(&self) -> Result<&Rc<RefCell<Vec<Value<O>>>>, RuntimeError> {
420        match self {
421            Value::Array(arr) => Ok(arr),
422            _ => Err(RuntimeError::TypeError(format!(
423                "Expected array, got {:?}",
424                self
425            ))),
426        }
427    }
428
429    pub fn as_color(&self) -> Result<Color, RuntimeError> {
430        match self {
431            Value::Color(color) => Ok(color.clone()),
432            _ => Err(RuntimeError::TypeError(format!(
433                "Expected color, got {:?}",
434                self
435            ))),
436        }
437    }
438}
439
440/// Method definition stored in the interpreter
441#[derive(Clone)]
442struct MethodDef {
443    type_name: String, // The type this method belongs to (from first param's type annotation)
444    params: Vec<pine_ast::MethodParam>,
445    body: Vec<Stmt>,
446}
447
448/// One history-carrying subscript site — `expr[n]` where `expr` is not a plain
449/// variable (a call, arithmetic, …). Mirrors `user_series_history`, keyed by the
450/// `Expr::Index` node's stable id, so `(expr)[n]` matches `v = expr; v[n]`.
451struct SeriesSite<O: PineOutput> {
452    /// Past bars, oldest first; the last entry is the previous bar.
453    history: Vec<Value<O>>,
454    /// This bar's value, once the site has been evaluated on it.
455    current: Option<Value<O>>,
456    /// `bar_seq` when `current` was recorded, so history rolls once per bar.
457    bar: u64,
458}
459
460impl<O: PineOutput> SeriesSite<O> {
461    fn new() -> Self {
462        Self {
463            history: Vec::new(),
464            current: None,
465            bar: 0,
466        }
467    }
468}
469
470/// The interpreter executes a program with a given bar
471pub struct Interpreter<O: PineOutput> {
472    /// Local variables in the current scope
473    variables: HashMap<String, Variable<O>>,
474    /// User-defined types, kept separate from `variables` so a UDT and a
475    /// function/variable may share a name (Pine's type and value namespaces are
476    /// distinct). `Type.new` / `Type.copy` resolve here.
477    user_types: HashMap<String, Value<O>>,
478    /// Method registry (method_name -> Vec<MethodDef>) - can have multiple methods with same name for different types
479    methods: HashMap<String, Vec<MethodDef>>,
480    /// Library loader for importing external libraries
481    pub library_loader: Option<Box<dyn LibraryLoader>>,
482    /// Exported items from this module (for library mode)
483    exports: HashMap<String, Value<O>>,
484    /// Output storage for plots, labels, logs, etc.
485    pub output: O,
486    /// Per-variable history for user-computed series (`var` declarations).
487    /// history[len-1] = previous bar, history[len-2] = two bars ago, etc.
488    /// Populated on each `Stmt::Assignment`; supports Pine's `name[n]` lookback.
489    pub user_series_history: HashMap<String, Vec<Value<O>>>,
490    /// History for subscripted non-variable series expressions (`ta.sma(..)[1]`,
491    /// `(high+low)[1]`), keyed by the `Expr::Index` node's id — the same
492    /// site-keyed pattern as `function_local_state`.
493    expr_history: HashMap<u32, SeriesSite<O>>,
494    /// Persistent local state for user-defined functions, keyed by the call
495    /// site's stable lexical id (`Expr::Call::id`). Keying by call site — not
496    /// by function name — means two calls to the same function keep independent
497    /// state, mirroring TradingView (e.g. `o[1]` inside a function returns the
498    /// previous bar's value of that call site's local `o`). A `call_id` of 0
499    /// (a call with no stable identity) is not persisted.
500    function_local_state: HashMap<u32, HashMap<String, Variable<O>>>,
501    /// `var`/`varip` declarations whose initializer already ran, keyed by
502    /// (call-site id, name). Pine `var` initializes only the FIRST time
503    /// execution reaches the declaration (once ever, not per bar/iteration).
504    /// The call-site id (0 at top level) scopes it per call site, so the same
505    /// function-local `var` at two call sites initializes independently.
506    /// Tracked separately from `variables` so a `var` declaration can shadow a
507    /// pre-existing host-injected variable (e.g. `var close = 10`).
508    ///
509    /// The value is the bar it initialized on, so a reassignment can tell that
510    /// there is no previous bar to read back yet.
511    var_decls_initialized: HashMap<(u32, String), u64>,
512    /// Lexical id of the call site currently executing (0 at top level). Scopes
513    /// `var` init-once tracking to the active call site.
514    current_call_id: u32,
515    /// Counts bars executed. Stateful builtins compare against it to advance
516    /// their state at most once per bar, however often their call site runs.
517    bar_seq: u64,
518    /// The simulated broker a `strategy` script trades against. `None` for an
519    /// `indicator`. The `strategy.*` order builtins reach it through `ctx`.
520    pub broker: Option<Box<dyn pine_broker::Broker>>,
521    /// Builds [`broker`](Self::broker) the first bar a `strategy` runs.
522    pub broker_factory: Option<Box<dyn pine_broker::BrokerFactory>>,
523    /// The feed `request.security` draws other symbols/timeframes from.
524    pub request_provider: Option<Rc<dyn pine_core::DataProvider>>,
525    pub chart_period: Option<i64>,
526    /// The current bar's opening time (UNIX ms), the raw datum every date name
527    /// (`time`, `year`, …) derives its bare value from. Set by the host each bar.
528    pub current_time: Option<i64>,
529    pub per_bar_advances: Vec<PerBarAdvance<O>>,
530    /// Host-supplied `input.*` overrides, keyed by the input's title.
531    pub inputs: HashMap<String, pine_core::InputValue>,
532}
533
534/// Names a statement block ASSIGNS (declares or writes) directly — i.e. the true
535/// locals of a function body. Reads (e.g. `open`) are ignored, and nested function
536/// declarations are a separate scope so their bodies are not descended into. Used to
537/// decide which variables a call site's persistent state should carry across bars.
538fn collect_assigned_names(body: &[Stmt], out: &mut std::collections::HashSet<String>) {
539    for s in body {
540        match s {
541            Stmt::VarDecl { name, .. } => {
542                out.insert(name.clone());
543            }
544            Stmt::Assignment {
545                target: Expr::Variable { name: n, .. },
546                ..
547            } => {
548                out.insert(n.clone());
549            }
550            Stmt::TupleAssignment { names, .. } => {
551                for n in names {
552                    out.insert(n.clone());
553                }
554            }
555            Stmt::If {
556                then_branch,
557                else_if_branches,
558                else_branch,
559                ..
560            } => {
561                collect_assigned_names(then_branch, out);
562                for (_, b) in else_if_branches {
563                    collect_assigned_names(b, out);
564                }
565                if let Some(b) = else_branch {
566                    collect_assigned_names(b, out);
567                }
568            }
569            Stmt::For { var_name, body, .. } => {
570                out.insert(var_name.clone());
571                collect_assigned_names(body, out);
572            }
573            Stmt::While { body, .. } | Stmt::ForIn { body, .. } => {
574                collect_assigned_names(body, out)
575            }
576            _ => {}
577        }
578    }
579}
580
581/// The builtin namespace whose functions back a value's method syntax, e.g.
582/// `arr.push(v)` dispatches to `array.push(arr, v)`.
583fn builtin_namespace<O: PineOutput>(value: &Value<O>) -> Option<&'static str> {
584    match value {
585        Value::Array(_) => Some("array"),
586        Value::Matrix { .. } => Some("matrix"),
587        Value::Map { .. } => Some("map"),
588        _ => None,
589    }
590}
591
592/// Pine `na` is float NaN. NaN can also reach `==`/`!=` wrapped as a
593/// `Value::Number(NaN)` (ta.* functions return `Number(NaN)` for all-NaN
594/// windows) rather than `Value::Na` — both forms make the comparison yield na.
595fn is_na_operand<O: PineOutput>(v: &Value<O>) -> bool {
596    matches!(v, Value::Na) || matches!(v, Value::Number(n) if n.is_nan())
597}
598
599impl<O: PineOutput> Interpreter<O> {
600    pub fn new() -> Self {
601        Self {
602            variables: HashMap::new(),
603            user_types: HashMap::new(),
604            methods: HashMap::new(),
605            library_loader: None,
606            exports: HashMap::new(),
607            output: O::default(),
608            user_series_history: HashMap::new(),
609            expr_history: HashMap::new(),
610            function_local_state: HashMap::new(),
611            var_decls_initialized: HashMap::new(),
612            current_call_id: 0,
613            bar_seq: 0,
614            broker: None,
615            broker_factory: Some(Box::new(pine_broker::DefaultBrokerFactory)),
616            request_provider: None,
617            chart_period: None,
618            current_time: None,
619            per_bar_advances: Vec::new(),
620            inputs: HashMap::new(),
621        }
622    }
623
624    /// A host-supplied `input.*` override for `title`, if any. An empty title
625    /// (an untitled input) is never overridable.
626    pub fn input(&self, title: &str) -> Option<&pine_core::InputValue> {
627        if title.is_empty() {
628            None
629        } else {
630            self.inputs.get(title)
631        }
632    }
633
634    /// How many bars have been executed. A stateful builtin advances its state
635    /// when this differs from the value it last saw.
636    pub fn bar_seq(&self) -> u64 {
637        self.bar_seq
638    }
639
640    /// Set the library loader
641    pub fn set_library_loader(&mut self, library_loader: Box<dyn LibraryLoader>) {
642        self.library_loader = Some(library_loader);
643    }
644
645    /// A copy of every defined variable's value, so a secondary interpreter
646    /// (`request.security`) can start from the same namespaces and builtins
647    /// without re-registering them.
648    pub fn snapshot(&self) -> HashMap<String, Value<O>> {
649        self.variables
650            .iter()
651            .map(|(name, var)| (name.clone(), var.value.clone()))
652            .collect()
653    }
654
655    /// Get the exported items from this interpreter (for library mode)
656    pub fn exports(&self) -> &HashMap<String, Value<O>> {
657        &self.exports
658    }
659
660    /// Execute a program with a single bar
661    pub fn execute(&mut self, program: &Program) -> Result<O, RuntimeError> {
662        // Clear output from previous iteration
663        self.output.clear();
664        // A new bar: stateful builtins may advance their state again.
665        self.bar_seq += 1;
666
667        for advance in self.per_bar_advances.clone() {
668            advance(self);
669        }
670
671        for stmt in &program.statements {
672            self.execute_stmt(stmt)?;
673        }
674
675        // Return a clone of the output
676        Ok(self.output.clone())
677    }
678
679    /// Get a variable value
680    pub fn get_variable(&self, name: &str) -> Option<&Value<O>> {
681        self.variables.get(name).map(|var| &var.value)
682    }
683
684    /// Whether `name` is a declared user-defined type.
685    pub fn is_user_type(&self, name: &str) -> bool {
686        self.user_types.contains_key(name)
687    }
688
689    /// The `member` field of a builtin namespace object (e.g. `array`'s `push`).
690    fn namespace_member(&self, namespace: &str, member: &str) -> Option<Value<O>> {
691        match self.variables.get(namespace).map(|var| &var.value) {
692            Some(Value::Object { fields, .. }) => fields.borrow().get(member).cloned(),
693            _ => None,
694        }
695    }
696
697    /// Set a variable value (useful for loading objects and test setup)
698    pub fn set_variable(&mut self, name: &str, value: Value<O>) {
699        self.variables.insert(
700            name.to_string(),
701            Variable {
702                value,
703                is_const: false,
704                is_var_persistent: false,
705            },
706        );
707    }
708
709    /// Set a built-in series to its value for a new bar, keeping the outgoing
710    /// value reachable as `name[1]`.
711    ///
712    /// This is how the OHLCV series and their derivations get the same lookback
713    /// as any user variable: history accumulates as bars execute, so `close[1]`
714    /// is na until a second bar has run.
715    pub fn advance_series(&mut self, name: &str, value: Value<O>) {
716        if let Some(existing) = self.variables.get(name) {
717            // Record the number the series held, not the series wrapper, so a
718            // `name[1]` lookback reads as a plain value.
719            let previous = match &existing.value {
720                Value::Series(series) => (*series.current).clone(),
721                other => other.clone(),
722            };
723            push_history(&mut self.user_series_history, name, previous);
724        }
725        self.set_variable(name, value);
726    }
727
728    /// Set a field on a namespace object (e.g. `strategy.position_size`),
729    /// leaving the object's other fields untouched. A no-op if `object` is not
730    /// a registered namespace object.
731    pub fn set_object_field(&mut self, object: &str, field: &str, value: Value<O>) {
732        if let Some(Variable {
733            value: Value::Object { fields, .. },
734            ..
735        }) = self.variables.get(object)
736        {
737            fields.borrow_mut().insert(field.to_string(), value);
738        }
739    }
740
741    /// Set a const variable (cannot be reassigned)
742    pub fn set_const_variable(&mut self, name: &str, value: Value<O>) {
743        self.variables.insert(
744            name.to_string(),
745            Variable {
746                value,
747                is_const: true,
748                is_var_persistent: false,
749            },
750        );
751    }
752
753    /// Register every entry as a const variable — used to load the builtin
754    /// namespaces and any host-supplied globals before a run.
755    pub fn set_const_variables(&mut self, variables: HashMap<String, Value<O>>) {
756        for (name, value) in variables {
757            self.set_const_variable(&name, value);
758        }
759    }
760
761    /// Helper to evaluate arguments and validate positional-before-named rule
762    /// Evaluate a call's arguments. A parameter marked lazy in `signature`
763    /// receives its argument unevaluated, as a captured [`Value::Expr`].
764    fn evaluate_arguments(
765        &mut self,
766        args: &[Argument],
767        signature: Option<&BuiltinSignature>,
768    ) -> Result<Vec<EvaluatedArg<O>>, RuntimeError> {
769        let mut evaluated_args = Vec::new();
770        let mut seen_named = false;
771        let mut positional_index = 0;
772
773        for arg in args {
774            match arg {
775                Argument::Positional(expr) => {
776                    if seen_named {
777                        return Err(RuntimeError::TypeError(
778                            "Positional arguments cannot follow named arguments".to_string(),
779                        ));
780                    }
781                    let lazy = signature.is_some_and(|s| s.positional_is_lazy(positional_index));
782                    let value = self.eval_or_capture(expr, lazy)?;
783                    evaluated_args.push(EvaluatedArg::Positional(value));
784                    positional_index += 1;
785                }
786                Argument::Named { name, value: expr } => {
787                    seen_named = true;
788                    let lazy = signature.is_some_and(|s| s.named_is_lazy(name));
789                    let value = self.eval_or_capture(expr, lazy)?;
790                    evaluated_args.push(EvaluatedArg::Named {
791                        name: name.clone(),
792                        value,
793                    });
794                }
795            }
796        }
797
798        Ok(evaluated_args)
799    }
800
801    /// Evaluate `expr`, or capture it unevaluated as a [`Value::Expr`] when the
802    /// parameter it binds to is lazy.
803    fn eval_or_capture(&mut self, expr: &Expr, lazy: bool) -> Result<Value<O>, RuntimeError> {
804        if lazy {
805            Ok(Value::Expr(Rc::new(expr.clone())))
806        } else {
807            self.eval_expr(expr)
808        }
809    }
810
811    fn execute_stmt(&mut self, stmt: &Stmt) -> Result<Option<Value<O>>, RuntimeError> {
812        match stmt {
813            Stmt::VarDecl {
814                name,
815                type_qualifier,
816                type_annotation: _,
817                initializer,
818                // varip's intrabar-update behavior is not yet implemented; it
819                // persists across bars exactly like var (is_persistent()).
820                var_kind,
821                ..
822            } => {
823                let is_var_persistent = var_kind.is_persistent();
824                // Pine `var`/`varip` semantics: the initializer runs only the
825                // FIRST time execution reaches this declaration (once ever).
826                // Scoped by the current call site (0 at top level) so the same
827                // function-local `var` at two call sites initializes
828                // independently. Tracked separately from `variables` so a `var`
829                // declaration can shadow a pre-existing host-injected builtin.
830                if is_var_persistent {
831                    let init_key = (self.current_call_id, name.clone());
832                    if self.var_decls_initialized.contains_key(&init_key) {
833                        return Ok(None);
834                    }
835                    self.var_decls_initialized.insert(init_key, self.bar_seq);
836                }
837                // Non-`var` declarations (e.g. `ha_bull_4h = expr`) re-execute on every bar.
838                // Push the previous value to history so `name[1]` lookbacks work, exactly as
839                // the Assignment handler does for `:=` reassignments.
840                if !is_var_persistent {
841                    if let Some(existing) = self.variables.get(name) {
842                        push_history(&mut self.user_series_history, name, existing.value.clone());
843                    }
844                }
845                let value = if let Some(init_expr) = initializer {
846                    self.eval_expr(init_expr)?
847                } else {
848                    Value::Na
849                };
850                let is_const = matches!(type_qualifier, Some(pine_ast::TypeQualifier::Const));
851                self.variables.insert(
852                    name.clone(),
853                    Variable {
854                        value,
855                        is_const,
856                        is_var_persistent,
857                    },
858                );
859                Ok(None)
860            }
861
862            Stmt::Assignment { target, value } => {
863                // Pine `var`-persistent variables: push their current (previous-bar) value to
864                // history BEFORE evaluating the RHS so that [1] lookback in the expression
865                // sees the correct previous-bar value.  Non-var variables push after eval
866                // (their old value was already pushed by VarDecl, or there is no VarDecl).
867                //
868                // Nothing is pushed on the bar the `var` initialized: there is no
869                // previous bar yet, and inventing one would make `acc[1]` read the
870                // initializer instead of na.
871                if let Expr::Variable { name, .. } = target {
872                    if let Some(var) = self.variables.get(name) {
873                        let born_this_bar = self
874                            .var_decls_initialized
875                            .get(&(self.current_call_id, name.clone()))
876                            == Some(&self.bar_seq);
877                        if var.is_var_persistent && !born_this_bar {
878                            push_history(&mut self.user_series_history, name, var.value.clone());
879                        }
880                    }
881                }
882
883                let val = self.eval_expr(value)?;
884                match target {
885                    Expr::Variable { name, .. } => {
886                        // Preserve the existing variable's flags (const, persistent).
887                        let (is_const, is_var_persistent) =
888                            if let Some(var) = self.variables.get(name) {
889                                if var.is_const {
890                                    return Err(RuntimeError::ConstReassignment(name.clone()));
891                                }
892                                if !var.is_var_persistent {
893                                    // Non-var: push current value to history after eval (Pine [n] lookback).
894                                    push_history(
895                                        &mut self.user_series_history,
896                                        name,
897                                        var.value.clone(),
898                                    );
899                                }
900                                // var-persistent: already pushed before eval above.
901                                (false, var.is_var_persistent)
902                            } else {
903                                (false, false)
904                            };
905
906                        self.variables.insert(
907                            name.clone(),
908                            Variable {
909                                value: val,
910                                is_const,
911                                is_var_persistent,
912                            },
913                        );
914                        Ok(None)
915                    }
916                    Expr::MemberAccess { object, member, .. } => {
917                        // Check if we're trying to modify a member of a const variable
918                        if let Expr::Variable { name: var_name, .. } = object.as_ref() {
919                            if let Some(var) = self.variables.get(var_name) {
920                                if var.is_const {
921                                    return Err(RuntimeError::ConstReassignment(format!(
922                                        "{}.{}",
923                                        var_name, member
924                                    )));
925                                }
926                            }
927                        }
928
929                        // Get the object
930                        let obj_value = self.eval_expr(object)?;
931
932                        if let Value::Object { fields, .. } = obj_value {
933                            let mut obj = fields.borrow_mut();
934                            obj.insert(member.clone(), val);
935                            Ok(None)
936                        } else {
937                            Err(RuntimeError::TypeError(
938                                "Cannot assign to member of non-object value".to_string(),
939                            ))
940                        }
941                    }
942                    _ => Err(RuntimeError::TypeError(
943                        "Invalid assignment target".to_string(),
944                    )),
945                }
946            }
947
948            Stmt::TupleAssignment { names, value, .. } => {
949                let val = self.eval_expr(value)?;
950                if let Value::Array(arr_ref) = val {
951                    let arr = arr_ref.borrow();
952                    for (i, name) in names.iter().enumerate() {
953                        // Push current value to history before overwriting (supports [n] lookback).
954                        if let Some(var) = self.variables.get(name) {
955                            push_history(&mut self.user_series_history, name, var.value.clone());
956                        }
957                        let element_val = arr.get(i).cloned().unwrap_or(Value::Na);
958                        self.variables.insert(
959                            name.clone(),
960                            Variable {
961                                value: element_val,
962                                is_const: false,
963                                is_var_persistent: false,
964                            },
965                        );
966                    }
967                    Ok(None)
968                } else {
969                    Err(RuntimeError::TypeError(
970                        "Expected array for tuple destructuring".to_string(),
971                    ))
972                }
973            }
974
975            Stmt::Expression(expr) => {
976                self.eval_expr(expr)?;
977                Ok(None)
978            }
979
980            Stmt::If {
981                condition,
982                then_branch,
983                else_if_branches,
984                else_branch,
985            } => {
986                let cond_value = self.eval_expr(condition)?;
987                if cond_value.truthy_for_condition()? {
988                    for stmt in then_branch {
989                        self.execute_stmt(stmt)?;
990                    }
991                } else {
992                    // Try each else if branch in order
993                    let mut executed = false;
994                    for (else_if_cond, else_if_body) in else_if_branches {
995                        let else_if_value = self.eval_expr(else_if_cond)?;
996                        if else_if_value.truthy_for_condition()? {
997                            for stmt in else_if_body {
998                                self.execute_stmt(stmt)?;
999                            }
1000                            executed = true;
1001                            break;
1002                        }
1003                    }
1004
1005                    // If no else if matched, try else branch
1006                    if !executed {
1007                        if let Some(else_stmts) = else_branch {
1008                            for stmt in else_stmts {
1009                                self.execute_stmt(stmt)?;
1010                            }
1011                        }
1012                    }
1013                }
1014                Ok(None)
1015            }
1016
1017            Stmt::For {
1018                var_name,
1019                from,
1020                to,
1021                step,
1022                body,
1023                ..
1024            } => {
1025                let from_val = self.eval_expr(from)?.as_number()?;
1026                let to_val = self.eval_expr(to)?.as_number()?;
1027
1028                // `by <step>` is a positive magnitude (default 1); the direction
1029                // comes from `from` vs `to`, so `for i = 3 to 0` counts down.
1030                let step_val = match step {
1031                    Some(expr) => self.eval_expr(expr)?.as_number()?.abs(),
1032                    None => 1.0,
1033                };
1034                if step_val == 0.0 {
1035                    return Err(RuntimeError::InvalidForLoop(from_val, to_val));
1036                }
1037                let down = from_val > to_val;
1038
1039                let mut i = from_val as i64;
1040                let end = to_val as i64;
1041                let step = step_val as i64;
1042
1043                while if down { i >= end } else { i <= end } {
1044                    self.variables.insert(
1045                        var_name.clone(),
1046                        Variable {
1047                            value: Value::Int(i),
1048                            is_const: false,
1049                            is_var_persistent: false,
1050                        },
1051                    );
1052
1053                    let control = self.execute_loop_body(body)?;
1054                    if control == LoopControl::Break {
1055                        break;
1056                    }
1057
1058                    if down {
1059                        i -= step;
1060                    } else {
1061                        i += step;
1062                    }
1063                }
1064
1065                Ok(None)
1066            }
1067
1068            Stmt::ForIn {
1069                index_var,
1070                item_var,
1071                collection,
1072                body,
1073                ..
1074            } => {
1075                let collection_value = self.eval_expr(collection)?;
1076                let arr = collection_value.as_array()?;
1077                let arr_borrowed = arr.borrow();
1078
1079                for (index, item) in arr_borrowed.iter().enumerate() {
1080                    // Set index variable if tuple form
1081                    if let Some(idx_var) = index_var {
1082                        self.variables.insert(
1083                            idx_var.clone(),
1084                            Variable {
1085                                value: Value::Int(index as i64),
1086                                is_const: false,
1087                                is_var_persistent: false,
1088                            },
1089                        );
1090                    }
1091
1092                    // Set item variable
1093                    self.variables.insert(
1094                        item_var.clone(),
1095                        Variable {
1096                            value: item.clone(),
1097                            is_const: false,
1098                            is_var_persistent: false,
1099                        },
1100                    );
1101
1102                    let control = self.execute_loop_body(body)?;
1103                    if control == LoopControl::Break {
1104                        break;
1105                    }
1106                }
1107
1108                Ok(None)
1109            }
1110
1111            Stmt::While { condition, body } => {
1112                loop {
1113                    let cond_value = self.eval_expr(condition)?;
1114                    if !cond_value.truthy_for_condition()? {
1115                        break;
1116                    }
1117
1118                    let control = self.execute_loop_body(body)?;
1119                    if control == LoopControl::Break {
1120                        break;
1121                    }
1122                }
1123                Ok(None)
1124            }
1125
1126            Stmt::Break { .. } => Err(RuntimeError::BreakOutsideLoop),
1127            Stmt::Continue { .. } => Err(RuntimeError::ContinueOutsideLoop),
1128
1129            Stmt::TypeDecl {
1130                name,
1131                fields,
1132                export,
1133                ..
1134            } => {
1135                // Create a Type value and store it as a variable
1136                let type_value = Value::Type {
1137                    name: name.clone(),
1138                    fields: fields.clone(),
1139                };
1140                self.user_types.insert(name.clone(), type_value.clone());
1141                self.variables.insert(
1142                    name.clone(),
1143                    Variable {
1144                        value: type_value.clone(),
1145                        is_const: false,
1146                        is_var_persistent: false,
1147                    },
1148                );
1149
1150                // If exported, also store in exports
1151                if *export {
1152                    self.exports.insert(name.clone(), type_value);
1153                }
1154                Ok(None)
1155            }
1156
1157            Stmt::EnumDecl {
1158                name,
1159                fields,
1160                export,
1161                ..
1162            } => {
1163                // Create an Object that contains all enum members as fields
1164                let mut enum_fields = HashMap::new();
1165
1166                for field in fields {
1167                    let title = field.title.clone().unwrap_or_else(|| field.name.clone());
1168                    let enum_value = Value::Enum {
1169                        enum_name: name.clone(),
1170                        field_name: field.name.clone(),
1171                        title,
1172                    };
1173                    enum_fields.insert(field.name.clone(), enum_value);
1174                }
1175
1176                let enum_object = Value::Object {
1177                    type_name: name.clone(),
1178                    fields: Rc::new(RefCell::new(enum_fields)),
1179                    call: None,
1180                    value: None,
1181                };
1182                self.variables.insert(
1183                    name.clone(),
1184                    Variable {
1185                        value: enum_object.clone(),
1186                        is_const: false,
1187                        is_var_persistent: false,
1188                    },
1189                );
1190
1191                // If exported, also store in exports
1192                if *export {
1193                    self.exports.insert(name.clone(), enum_object);
1194                }
1195                Ok(None)
1196            }
1197
1198            Stmt::Export { item } => {
1199                // Mark the item for export
1200                match item {
1201                    pine_ast::ExportItem::Type(type_name) => {
1202                        // Export the type - it should already be in variables
1203                        if let Some(var) = self.variables.get(type_name) {
1204                            self.exports.insert(type_name.clone(), var.value.clone());
1205                        }
1206                    }
1207                    pine_ast::ExportItem::Function(func_name) => {
1208                        // Export the function - it should already be in variables
1209                        if let Some(var) = self.variables.get(func_name) {
1210                            self.exports.insert(func_name.clone(), var.value.clone());
1211                        }
1212                    }
1213                }
1214                Ok(None)
1215            }
1216
1217            Stmt::Import { path, alias, .. } => {
1218                let source = match &self.library_loader {
1219                    Some(loader) => loader.load_library(path),
1220                    None => {
1221                        return Err(RuntimeError::LibraryError(
1222                            "Cannot import library: no library loader configured".to_string(),
1223                        ))
1224                    }
1225                }
1226                .map_err(|e| {
1227                    RuntimeError::LibraryError(format!("Failed to load library '{}': {}", path, e))
1228                })?;
1229
1230                let library_program = pine_parser::Parser::parse_source(&source).map_err(|e| {
1231                    RuntimeError::LibraryError(format!("Failed to parse library '{}': {}", path, e))
1232                })?;
1233
1234                // Seed the library with the same built-in namespaces/globals
1235                // (e.g. `library`, `math`) so its declaration and body resolve.
1236                let mut library_interp = Interpreter::new();
1237                for (name, value) in self.snapshot() {
1238                    library_interp.set_variable(&name, value);
1239                }
1240                library_interp.execute(&library_program)?;
1241                let library_exports = library_interp.exports();
1242
1243                for (method_name, method_defs) in &library_interp.methods {
1244                    for method_def in method_defs {
1245                        self.methods
1246                            .entry(method_name.clone())
1247                            .or_default()
1248                            .push(method_def.clone());
1249                    }
1250                }
1251
1252                let namespace: Value<O> = Value::Object {
1253                    type_name: alias.clone(),
1254                    fields: Rc::new(RefCell::new(library_exports.clone())),
1255                    call: None,
1256                    value: None,
1257                };
1258                self.variables.insert(
1259                    alias.clone(),
1260                    Variable {
1261                        value: namespace,
1262                        is_const: false,
1263                        is_var_persistent: false,
1264                    },
1265                );
1266                Ok(None)
1267            }
1268
1269            Stmt::MethodDecl {
1270                name,
1271                params,
1272                body,
1273                export,
1274                ..
1275            } => {
1276                // Extract the type name from the first parameter's type annotation
1277                let type_name = if let Some(first_param) = params.first() {
1278                    first_param.type_annotation.clone().ok_or_else(|| {
1279                        RuntimeError::TypeError(
1280                            "Method's first parameter must have a type annotation".to_string(),
1281                        )
1282                    })?
1283                } else {
1284                    return Err(RuntimeError::TypeError(
1285                        "Method must have at least one parameter (this)".to_string(),
1286                    ));
1287                };
1288
1289                // Store the method definition
1290                let method_def = MethodDef {
1291                    type_name,
1292                    params: params.clone(),
1293                    body: body.clone(),
1294                };
1295
1296                self.methods
1297                    .entry(name.clone())
1298                    .or_default()
1299                    .push(method_def);
1300
1301                // If exported, store the method in exports
1302                // Methods are exported as part of their type, so we may need to handle this differently
1303                // For now, just mark it as exported (this might need more work)
1304                if *export {
1305                    // TODO: Handle method exports properly
1306                }
1307
1308                Ok(None)
1309            }
1310
1311            Stmt::FunctionDecl {
1312                name,
1313                params,
1314                body,
1315                export,
1316                ..
1317            } => {
1318                // Create a function value
1319                let func_value = Value::Function {
1320                    params: params.clone(),
1321                    body: body.clone(),
1322                };
1323                self.variables.insert(
1324                    name.clone(),
1325                    Variable {
1326                        value: func_value.clone(),
1327                        is_const: false,
1328                        is_var_persistent: false,
1329                    },
1330                );
1331
1332                // If exported, also store in exports
1333                if *export {
1334                    self.exports.insert(name.clone(), func_value);
1335                }
1336
1337                Ok(None)
1338            }
1339        }
1340    }
1341
1342    /// Execute loop body, handling break/continue
1343    fn execute_loop_body(&mut self, body: &[Stmt]) -> Result<LoopControl, RuntimeError> {
1344        for stmt in body {
1345            match stmt {
1346                Stmt::Break { .. } => return Ok(LoopControl::Break),
1347                Stmt::Continue { .. } => return Ok(LoopControl::Continue),
1348                Stmt::If {
1349                    condition,
1350                    then_branch,
1351                    else_if_branches,
1352                    else_branch,
1353                } => {
1354                    let cond_value = self.eval_expr(condition)?;
1355                    let branch = if cond_value.truthy_for_condition()? {
1356                        then_branch
1357                    } else {
1358                        // Try each else if branch
1359                        let mut matched_branch = None;
1360                        for (else_if_cond, else_if_body) in else_if_branches {
1361                            let else_if_value = self.eval_expr(else_if_cond)?;
1362                            if else_if_value.truthy_for_condition()? {
1363                                matched_branch = Some(else_if_body);
1364                                break;
1365                            }
1366                        }
1367
1368                        if let Some(branch) = matched_branch {
1369                            branch
1370                        } else if let Some(else_stmts) = else_branch {
1371                            else_stmts
1372                        } else {
1373                            continue;
1374                        }
1375                    };
1376
1377                    let control = self.execute_loop_body(branch)?;
1378                    if control != LoopControl::None {
1379                        return Ok(control);
1380                    }
1381                }
1382                Stmt::For { .. } | Stmt::ForIn { .. } | Stmt::While { .. } => {
1383                    // Nested loops handle their own break/continue
1384                    self.execute_stmt(stmt)?;
1385                }
1386                _ => {
1387                    self.execute_stmt(stmt)?;
1388                }
1389            }
1390        }
1391        Ok(LoopControl::None)
1392    }
1393
1394    /// Bare use unwraps a value-carrying object to its value; `.member` and
1395    /// `(...)` positions use [`eval_expr_raw`] to keep the object.
1396    fn eval_expr(&mut self, expr: &Expr) -> Result<Value<O>, RuntimeError> {
1397        let value = self.eval_expr_raw(expr)?;
1398        if let Value::Object {
1399            value: Some(compute),
1400            ..
1401        } = &value
1402        {
1403            let compute = compute.clone();
1404            return compute(self);
1405        }
1406        Ok(value)
1407    }
1408
1409    fn eval_expr_raw(&mut self, expr: &Expr) -> Result<Value<O>, RuntimeError> {
1410        match expr {
1411            Expr::Literal(lit) => Ok(self.eval_literal(lit)),
1412
1413            Expr::Variable { name, .. } => self
1414                .variables
1415                .get(name)
1416                .map(|var| var.value.clone())
1417                .ok_or_else(|| RuntimeError::UndefinedVariable(name.clone())),
1418
1419            Expr::Binary {
1420                left, op, right, ..
1421            } => {
1422                let left_val = self.eval_expr(left)?;
1423                // Pine `and`/`or` are lazy: when the left operand alone decides
1424                // the result (false-and / true-or), the right operand is NOT
1425                // evaluated — side effects inside it (e.g. stateful ta.* calls)
1426                // must not run. The three-valued na results are unchanged:
1427                // false absorbs na in `and`, true absorbs na in `or`, and an
1428                // na left operand still requires the right operand's value.
1429                if matches!(op, BinOp::And | BinOp::Or) {
1430                    match (op, left_val.to_bool()?) {
1431                        (BinOp::And, Some(false)) => return Ok(Value::Bool(false)),
1432                        (BinOp::Or, Some(true)) => return Ok(Value::Bool(true)),
1433                        _ => {}
1434                    }
1435                }
1436                let right_val = self.eval_expr(right)?;
1437                self.eval_binary_op(&left_val, op, &right_val)
1438            }
1439
1440            Expr::Unary { op, expr } => {
1441                let val = self.eval_expr(expr)?;
1442                self.eval_unary_op(op, &val)
1443            }
1444
1445            Expr::Ternary {
1446                condition,
1447                then_expr,
1448                else_expr,
1449            } => {
1450                let cond_val = self.eval_expr(condition)?;
1451                if cond_val.truthy_for_condition()? {
1452                    self.eval_expr(then_expr)
1453                } else {
1454                    self.eval_expr(else_expr)
1455                }
1456            }
1457
1458            Expr::IfExpr {
1459                condition,
1460                then_expr,
1461                else_if_branches,
1462                else_expr,
1463            } => {
1464                let cond_val = self.eval_expr(condition)?;
1465                if cond_val.truthy_for_condition()? {
1466                    self.eval_expr(then_expr)
1467                } else {
1468                    // Try each else if branch
1469                    for (else_if_cond, else_if_expr) in else_if_branches {
1470                        let else_if_val = self.eval_expr(else_if_cond)?;
1471                        if else_if_val.truthy_for_condition()? {
1472                            return self.eval_expr(else_if_expr);
1473                        }
1474                    }
1475                    // No else if matched, evaluate else branch or return na
1476                    if let Some(expr) = else_expr {
1477                        self.eval_expr(expr)
1478                    } else {
1479                        Ok(Value::Na)
1480                    }
1481                }
1482            }
1483
1484            Expr::Array(elements) => {
1485                let values: Result<Vec<_>, _> =
1486                    elements.iter().map(|e| self.eval_expr(e)).collect();
1487                Ok(Value::Array(Rc::new(RefCell::new(values?))))
1488            }
1489
1490            Expr::Index { expr, index, id } => {
1491                let index_val = self.eval_expr(index)?.as_number()? as usize;
1492
1493                // A named variable with tracked history looks up
1494                // user_series_history: history[len-1] = previous bar. A tracked
1495                // variable with insufficient depth yields na (warm-up). Variables
1496                // WITHOUT tracked history (e.g. builtin Series like `close` fed by
1497                // the host) fall through to the shared path below.
1498                if index_val > 0 {
1499                    if let Expr::Variable { name: var_name, .. } = expr.as_ref() {
1500                        if let Some(h) = self.user_series_history.get(var_name) {
1501                            return Ok(if h.len() >= index_val {
1502                                h[h.len() - index_val].clone()
1503                            } else {
1504                                Value::Na
1505                            });
1506                        }
1507                        // A plain non-series value with no history (a user var
1508                        // assigned only this bar) indexes as na, not an error.
1509                        if let Some(var) = self.variables.get(var_name) {
1510                            if !matches!(var.value, Value::Series(_) | Value::Array(_)) {
1511                                return Ok(Value::Na);
1512                            }
1513                        }
1514                    }
1515                }
1516
1517                let val = self.eval_expr(expr)?;
1518
1519                // A builtin-owned series (e.g. `ta.obv`) carries its own lookback,
1520                // advanced every bar, so `[n]` is robust regardless of where it is
1521                // read.
1522                if let Value::Series(series) = &val {
1523                    if let Some(history) = &series.history {
1524                        if index_val == 0 {
1525                            return Ok((*series.current).clone());
1526                        }
1527                        let h = history.borrow();
1528                        return Ok(if h.len() >= index_val {
1529                            h[h.len() - index_val].clone()
1530                        } else {
1531                            Value::Na
1532                        });
1533                    }
1534                }
1535
1536                // Array element access — decided by the value, not the id.
1537                if let Value::Array(arr_ref) = &val {
1538                    let arr = arr_ref.borrow();
1539                    return arr
1540                        .get(index_val)
1541                        .cloned()
1542                        .ok_or(RuntimeError::IndexOutOfBounds(index_val));
1543                }
1544
1545                // Series subscript on a non-variable expression (a call,
1546                // arithmetic, a fell-through Series). A per-site history buffer
1547                // keyed by the node's id makes `(expr)[n]` match `v = expr; v[n]`,
1548                // and a warm-up `na` yields `na` instead of erroring.
1549                let current = match val {
1550                    Value::Series(series) => (*series.current).clone(),
1551                    other => other,
1552                };
1553                if index_val == 0 {
1554                    return Ok(current);
1555                }
1556                let seq = self.bar_seq;
1557                let site = self.expr_history.entry(*id).or_insert_with(SeriesSite::new);
1558                if site.bar != seq {
1559                    // A new bar: last bar's value rolls into history. Bounded to
1560                    // MAX_LOOKBACK, exactly like user_series_history, so memory
1561                    // stays flat over a long run.
1562                    if let Some(previous) = site.current.take() {
1563                        site.history.push(previous);
1564                        if site.history.len() > MAX_LOOKBACK {
1565                            let drop = site.history.len() - MAX_LOOKBACK;
1566                            site.history.drain(..drop);
1567                        }
1568                    }
1569                    site.bar = seq;
1570                }
1571                site.current = Some(current);
1572                Ok(if site.history.len() >= index_val {
1573                    site.history[site.history.len() - index_val].clone()
1574                } else {
1575                    Value::Na
1576                })
1577            }
1578
1579            Expr::Switch { value, cases } => {
1580                let switch_val = self.eval_expr(value)?;
1581
1582                for (pattern, result) in cases {
1583                    // Check if pattern matches
1584                    let pattern_val = self.eval_expr(pattern)?;
1585
1586                    // Special case: default pattern (true literal)
1587                    if pattern_val == Value::Bool(true)
1588                        && matches!(pattern, Expr::Literal(Literal::Bool(true)))
1589                    {
1590                        return self.eval_expr(result);
1591                    }
1592
1593                    // Check equality
1594                    if self.values_equal(&switch_val, &pattern_val)? {
1595                        return self.eval_expr(result);
1596                    }
1597                }
1598
1599                // No match found
1600                Ok(Value::Na)
1601            }
1602
1603            Expr::Call {
1604                callee,
1605                type_args,
1606                args,
1607                id,
1608                ..
1609            } => {
1610                // Check if this is a method call (object.method())
1611                if let Expr::MemberAccess { object, member, .. } = callee.as_ref() {
1612                    // Try to find a method with this name
1613                    if let Some(method_defs) = self.methods.get(member).cloned() {
1614                        // Evaluate the object (this will be the first parameter)
1615                        let obj_value = self.eval_expr_raw(object)?;
1616
1617                        // Find the method that matches the object's type
1618                        let obj_type = self.get_object_type_name(&obj_value)?;
1619
1620                        if let Some(method_def) =
1621                            method_defs.iter().find(|m| m.type_name == obj_type)
1622                        {
1623                            // Evaluate the other arguments
1624                            let mut evaluated_args: Vec<EvaluatedArg<O>> =
1625                                vec![EvaluatedArg::Positional(obj_value)];
1626                            evaluated_args.extend(self.evaluate_arguments(args, None)?);
1627
1628                            // Call the method (treating it like a function),
1629                            // threading the call site id so method-local state
1630                            // persists per call site.
1631                            return self.call_method(
1632                                &method_def.params,
1633                                &method_def.body,
1634                                evaluated_args,
1635                                *id,
1636                            );
1637                        }
1638                    }
1639                }
1640
1641                // Builtin method syntax: a collection receiver `x.m(args)` is
1642                // sugar for `namespace.m(x, args)` — the same builtins in
1643                // function form, with the receiver passed first. (Skip `Call`
1644                // receivers so a side-effecting `f().m()` is not evaluated twice.)
1645                if let Expr::MemberAccess { object, member, .. } = callee.as_ref() {
1646                    if !matches!(object.as_ref(), Expr::Call { .. }) {
1647                        let receiver = self.eval_expr_raw(object)?;
1648                        if let Some(namespace) = builtin_namespace(&receiver) {
1649                            if let Some(Value::BuiltinFunction(builtin_fn)) =
1650                                self.namespace_member(namespace, member)
1651                            {
1652                                let mut evaluated_args = vec![EvaluatedArg::Positional(receiver)];
1653                                evaluated_args.extend(self.evaluate_arguments(args, None)?);
1654                                let call_args =
1655                                    FunctionCallArgs::new(type_args.clone(), evaluated_args)
1656                                        .with_call_id(*id);
1657                                return (builtin_fn.call)(self, call_args);
1658                            }
1659                        }
1660                    }
1661                }
1662
1663                // Not a method call, proceed with regular function call.
1664                // Resolve the callee first so a builtin's lazy parameters can
1665                // capture their arguments unevaluated.
1666                let callee_value = self.eval_expr_raw(callee)?;
1667                let signature = match &callee_value {
1668                    Value::BuiltinFunction(builtin) => Some(builtin.signature),
1669                    _ => None,
1670                };
1671                let evaluated_args = self.evaluate_arguments(args, signature)?;
1672
1673                // Call the function based on its type
1674                match callee_value {
1675                    Value::Function { params, body } => {
1676                        // Thread the call site's lexical id so function-local
1677                        // state persists per call site, not per function name.
1678                        self.call_user_function(&params, &body, args, evaluated_args, *id)
1679                    }
1680                    Value::BuiltinFunction(builtin_fn) => {
1681                        // Pass type_args from the parsed call expression, and the
1682                        // call node's lexical id for per-call-site builtin state.
1683                        let call_args = FunctionCallArgs::new(type_args.clone(), evaluated_args)
1684                            .with_call_id(*id);
1685                        (builtin_fn.call)(self, call_args)
1686                    }
1687                    // A callable namespace object, like `input(...)` alongside
1688                    // `input.int(...)`. Objects without a `call` are not callable.
1689                    Value::Object {
1690                        call: Some(builtin),
1691                        ..
1692                    } => {
1693                        let call_args = FunctionCallArgs::new(type_args.clone(), evaluated_args)
1694                            .with_call_id(*id);
1695                        (builtin.call)(self, call_args)
1696                    }
1697                    // Pine's `na` is a keyword that doubles as a function: na(x) → is x na?
1698                    Value::Na => {
1699                        let is_na = matches!(
1700                            evaluated_args.first(),
1701                            Some(EvaluatedArg::Positional(Value::Na)) | None
1702                        );
1703                        Ok(Value::Bool(is_na))
1704                    }
1705                    _ => Err(RuntimeError::TypeError(
1706                        "Attempted to call a non-function value".to_string(),
1707                    )),
1708                }
1709            }
1710
1711            Expr::MemberAccess { object, member, .. } => {
1712                // `Type.new` / `Type.copy` resolve via the type namespace, so a
1713                // type may share its name with a shadowing function/variable.
1714                let obj_value = match object.as_ref() {
1715                    Expr::Variable { name, .. }
1716                        if (member == "new" || member == "copy")
1717                            && self.user_types.contains_key(name) =>
1718                    {
1719                        self.user_types[name].clone()
1720                    }
1721                    _ => self.eval_expr_raw(object)?,
1722                };
1723                match obj_value {
1724                    Value::Object { fields, .. } => {
1725                        let obj = fields.borrow();
1726                        obj.get(member).cloned().ok_or_else(|| {
1727                            RuntimeError::TypeError(format!("Object has no member '{}'", member))
1728                        })
1729                    }
1730                    Value::Type { name, fields } => {
1731                        // Types have 'new' and 'copy' methods
1732                        if member == "new" {
1733                            // Return a constructor function
1734                            Ok(Value::BuiltinFunction(Builtin::untyped(
1735                                Self::create_constructor(name, fields),
1736                            )))
1737                        } else if member == "copy" {
1738                            // Return a copy function
1739                            Ok(Value::BuiltinFunction(Builtin::untyped(
1740                                Self::create_copy_function(),
1741                            )))
1742                        } else {
1743                            Err(RuntimeError::TypeError(format!(
1744                                "Type '{}' has no member '{}' (only 'new' and 'copy' are supported)",
1745                                name, member
1746                            )))
1747                        }
1748                    }
1749                    _ => Err(RuntimeError::TypeError(format!(
1750                        "Cannot access member '{}' on non-object value",
1751                        member
1752                    ))),
1753                }
1754            }
1755
1756            Expr::Function { params, body } => {
1757                // params is already Vec<FunctionParam> from the AST
1758                Ok(Value::Function {
1759                    params: params.clone(),
1760                    body: body.clone(),
1761                })
1762            }
1763        }
1764    }
1765
1766    fn eval_literal(&self, lit: &Literal) -> Value<O> {
1767        match lit {
1768            Literal::Int(n) => Value::Int(*n),
1769            Literal::Number(n) => Value::Number(*n),
1770            Literal::String(s) => Value::String(s.clone()),
1771            Literal::Bool(b) => Value::Bool(*b),
1772            Literal::Na => Value::Na,
1773            Literal::HexColor(hex) => Value::String(hex.clone()),
1774        }
1775    }
1776
1777    fn eval_binary_op(
1778        &self,
1779        left: &Value<O>,
1780        op: &BinOp,
1781        right: &Value<O>,
1782    ) -> Result<Value<O>, RuntimeError> {
1783        match op {
1784            BinOp::Add => {
1785                // String concatenation or numeric addition
1786                if matches!(left, Value::String(_)) || matches!(right, Value::String(_)) {
1787                    Ok(Value::String(format!(
1788                        "{}{}",
1789                        left.as_string()?,
1790                        right.as_string()?
1791                    )))
1792                } else {
1793                    numeric_op(left, right, |a, b| Some(a + b))
1794                }
1795            }
1796
1797            BinOp::Sub => numeric_op(left, right, |a, b| Some(a - b)),
1798
1799            BinOp::Mul => numeric_op(left, right, |a, b| Some(a * b)),
1800
1801            // Pine semantics: a zero divisor yields `na`, not an error. Two ints
1802            // divide as ints (`15 / 2 == 7`); any float operand divides as float.
1803            BinOp::Div => numeric_op(left, right, Num::checked_div),
1804
1805            BinOp::Mod => numeric_op(left, right, Num::checked_rem),
1806
1807            // Pine semantics: a comparison with an `na` operand yields `na`
1808            // (including `na == na` — testing for na requires the na() function).
1809            // `is_na_operand` also treats a `Value::Number(NaN)` as na (a
1810            // computed NaN such as math.sqrt(-1.0), or a ta.* window that
1811            // returns `Number(NaN)` rather than `Value::Na`). Eq/NotEq must not
1812            // leak a structural bool through values_equal, otherwise e.g.
1813            // `dayofweek != dayofweek[1]` evaluates true on the first bar.
1814            BinOp::Eq => {
1815                if is_na_operand(left) || is_na_operand(right) {
1816                    return Ok(Value::Na);
1817                }
1818                Ok(Value::Bool(self.values_equal(left, right)?))
1819            }
1820
1821            BinOp::NotEq => {
1822                if is_na_operand(left) || is_na_operand(right) {
1823                    return Ok(Value::Na);
1824                }
1825                Ok(Value::Bool(!self.values_equal(left, right)?))
1826            }
1827
1828            // Relational arms need the same guard: to_number() maps `Value::Na`
1829            // to None (caught by the match) but passes `Number(NaN)` through as
1830            // Some(NaN), where a raw float comparison yields false — a
1831            // structural bool that e.g. `not` then flips to true, instead of
1832            // the `na` TradingView produces.
1833            BinOp::Less => {
1834                if is_na_operand(left) || is_na_operand(right) {
1835                    return Ok(Value::Na);
1836                }
1837                match (left.to_number()?, right.to_number()?) {
1838                    (Some(l), Some(r)) => Ok(Value::Bool(l < r)),
1839                    _ => Ok(Value::Na),
1840                }
1841            }
1842
1843            BinOp::Greater => {
1844                if is_na_operand(left) || is_na_operand(right) {
1845                    return Ok(Value::Na);
1846                }
1847                match (left.to_number()?, right.to_number()?) {
1848                    (Some(l), Some(r)) => Ok(Value::Bool(l > r)),
1849                    _ => Ok(Value::Na),
1850                }
1851            }
1852
1853            BinOp::LessEq => {
1854                if is_na_operand(left) || is_na_operand(right) {
1855                    return Ok(Value::Na);
1856                }
1857                match (left.to_number()?, right.to_number()?) {
1858                    (Some(l), Some(r)) => Ok(Value::Bool(l <= r)),
1859                    _ => Ok(Value::Na),
1860                }
1861            }
1862
1863            BinOp::GreaterEq => {
1864                if is_na_operand(left) || is_na_operand(right) {
1865                    return Ok(Value::Na);
1866                }
1867                match (left.to_number()?, right.to_number()?) {
1868                    (Some(l), Some(r)) => Ok(Value::Bool(l >= r)),
1869                    _ => Ok(Value::Na),
1870                }
1871            }
1872
1873            // Three-valued logic: false absorbs na; true and na → na.
1874            BinOp::And => match (left.to_bool()?, right.to_bool()?) {
1875                (Some(false), _) | (_, Some(false)) => Ok(Value::Bool(false)),
1876                (Some(true), Some(true)) => Ok(Value::Bool(true)),
1877                _ => Ok(Value::Na),
1878            },
1879
1880            // Three-valued logic: true absorbs na; false or na → na.
1881            BinOp::Or => match (left.to_bool()?, right.to_bool()?) {
1882                (Some(true), _) | (_, Some(true)) => Ok(Value::Bool(true)),
1883                (Some(false), Some(false)) => Ok(Value::Bool(false)),
1884                _ => Ok(Value::Na),
1885            },
1886        }
1887    }
1888
1889    fn eval_unary_op(&self, op: &UnOp, val: &Value<O>) -> Result<Value<O>, RuntimeError> {
1890        match op {
1891            // Negating an int stays an int.
1892            UnOp::Neg => match val.as_int() {
1893                Some(n) => Ok(Value::Int(-n)),
1894                None => match val.to_number()? {
1895                    Some(n) => Ok(Value::Number(-n)),
1896                    None => Ok(Value::Na),
1897                },
1898            },
1899            UnOp::Not => match val.to_bool()? {
1900                Some(b) => Ok(Value::Bool(!b)),
1901                None => Ok(Value::Na),
1902            },
1903        }
1904    }
1905
1906    fn values_equal(&self, left: &Value<O>, right: &Value<O>) -> Result<bool, RuntimeError> {
1907        match (left, right) {
1908            (Value::Int(l), Value::Int(r)) => Ok(l == r),
1909            // int and float compare by value, so `1 == 1.0`.
1910            (Value::Int(l), Value::Number(r)) | (Value::Number(r), Value::Int(l)) => {
1911                Ok((*l as f64 - r).abs() < f64::EPSILON)
1912            }
1913            (Value::Number(l), Value::Number(r)) => Ok((l - r).abs() < f64::EPSILON),
1914            (Value::String(l), Value::String(r)) => Ok(l == r),
1915            (Value::Bool(l), Value::Bool(r)) => Ok(l == r),
1916            (Value::Na, Value::Na) => Ok(true),
1917            (
1918                Value::Enum {
1919                    enum_name: a_enum,
1920                    field_name: a_field,
1921                    ..
1922                },
1923                Value::Enum {
1924                    enum_name: b_enum,
1925                    field_name: b_field,
1926                    ..
1927                },
1928            ) => Ok(a_enum == b_enum && a_field == b_field),
1929            _ => Ok(false),
1930        }
1931    }
1932
1933    /// Check if an expression evaluates to a const value
1934    fn is_const_expr(&self, expr: &Expr) -> bool {
1935        match expr {
1936            // Literals are always const
1937            Expr::Literal(_) => true,
1938            // Variable is const if it's stored as const
1939            Expr::Variable { name, .. } => self
1940                .variables
1941                .get(name)
1942                .map(|var| var.is_const)
1943                .unwrap_or(false),
1944            // Member access is const if the base object is const
1945            Expr::MemberAccess { object, .. } => self.is_const_expr(object),
1946            // All other expressions are not const
1947            _ => false,
1948        }
1949    }
1950
1951    fn call_user_function(
1952        &mut self,
1953        params: &[pine_ast::FunctionParam],
1954        body: &[Stmt],
1955        arg_exprs: &[Argument],
1956        args: Vec<EvaluatedArg<O>>,
1957        call_id: u32,
1958    ) -> Result<Value<O>, RuntimeError> {
1959        // Extract positional arguments (user functions don't support named args yet)
1960        let mut positional_values = Vec::new();
1961        let mut positional_exprs = Vec::new();
1962
1963        for (i, arg) in args.iter().enumerate() {
1964            match arg {
1965                EvaluatedArg::Positional(value) => {
1966                    positional_values.push(value.clone());
1967                    if let Some(Argument::Positional(expr)) = arg_exprs.get(i) {
1968                        positional_exprs.push(expr);
1969                    }
1970                }
1971                EvaluatedArg::Named { .. } => {
1972                    return Err(RuntimeError::TypeError(
1973                        "User-defined functions do not support named arguments yet".to_string(),
1974                    ))
1975                }
1976            }
1977        }
1978
1979        // Check argument count
1980        if positional_values.len() != params.len() {
1981            return Err(RuntimeError::TypeError(format!(
1982                "Expected {} arguments, got {}",
1983                params.len(),
1984                positional_values.len()
1985            )));
1986        }
1987
1988        // Validate const parameters receive const arguments
1989        for (i, param) in params.iter().enumerate() {
1990            if matches!(param.type_qualifier, Some(pine_ast::TypeQualifier::Const)) {
1991                if let Some(arg_expr) = positional_exprs.get(i) {
1992                    if !self.is_const_expr(arg_expr) {
1993                        return Err(RuntimeError::TypeError(format!(
1994                            "Parameter '{}' requires a const argument, but received a non-const value",
1995                            param.name
1996                        )));
1997                    }
1998                }
1999            }
2000        }
2001
2002        // Bind parameters to arguments with the appropriate const flag, then run
2003        // the body as a stateful call site.
2004        let param_bindings: Vec<(String, Variable<O>)> = params
2005            .iter()
2006            .zip(positional_values)
2007            .map(|(param, value)| {
2008                let is_const = matches!(param.type_qualifier, Some(pine_ast::TypeQualifier::Const));
2009                (
2010                    param.name.clone(),
2011                    Variable {
2012                        value,
2013                        is_const,
2014                        is_var_persistent: false,
2015                    },
2016                )
2017            })
2018            .collect();
2019
2020        self.run_call_site_body(call_id, param_bindings, body)
2021    }
2022
2023    /// Run a user function or method body as a stateful call site: restore this
2024    /// call site's persisted locals, bind the given parameters, execute the body
2025    /// under the call site's id (scoping `var` init-once), then persist the call
2026    /// site's locals and restore the outer scope. Keying state by call site —
2027    /// not by callable name — keeps two call sites of the same function/method
2028    /// independent, matching TradingView. A `call_id` of 0 (no stable identity)
2029    /// is not persisted.
2030    fn run_call_site_body(
2031        &mut self,
2032        call_id: u32,
2033        param_bindings: Vec<(String, Variable<O>)>,
2034        body: &[Stmt],
2035    ) -> Result<Value<O>, RuntimeError> {
2036        let param_names: std::collections::HashSet<String> =
2037            param_bindings.iter().map(|(n, _)| n.clone()).collect();
2038
2039        // Save the outer scope.
2040        let saved_vars = self.variables.clone();
2041
2042        // Restore this call site's locals (all locals persist across calls, not
2043        // just `var`s, so series indexing like `o[1]` works inside the body).
2044        // Parameters are excluded — they are freshly bound below.
2045        if call_id != 0 {
2046            if let Some(local_state) = self.function_local_state.get(&call_id) {
2047                for (var_name, var) in local_state {
2048                    if !param_names.contains(var_name) {
2049                        self.variables.insert(var_name.clone(), var.clone());
2050                    }
2051                }
2052            }
2053        }
2054
2055        // Bind parameters (freshly each call).
2056        for (name, var) in param_bindings {
2057            self.variables.insert(name, var);
2058        }
2059
2060        // Execute the body under this call site's id, so `var` init-once tracking
2061        // is scoped to the call site. Restored afterwards to support
2062        // nested/recursive calls. (Like the scope restore below, an error just
2063        // propagates and aborts the script.)
2064        let prev_call_id = self.current_call_id;
2065        self.current_call_id = call_id;
2066        let mut result: Value<O> = Value::Na;
2067        for stmt in body {
2068            if let Some(return_value) = self.execute_stmt(stmt)? {
2069                result = return_value;
2070            } else if let Stmt::Expression(expr) = stmt {
2071                // Last expression is the return value
2072                result = self.eval_expr(expr)?;
2073            }
2074        }
2075        self.current_call_id = prev_call_id;
2076
2077        // Restore the outer scope. This call site's locals live only in
2078        // function_local_state (keyed by call_id) and are NOT leaked into the
2079        // outer/global scope, so two call sites keep independent state.
2080        let call_vars = std::mem::replace(&mut self.variables, saved_vars);
2081        if call_id != 0 {
2082            // Persist only the names the body actually ASSIGNS — its true locals
2083            // (both `var` and plain, so their series history advances). The scope
2084            // also holds read-only builtins/globals inherited from the outer scope
2085            // (`open`/`high`/`low`/`close`/…); saving one of those would restore it
2086            // stale on the next call, freezing any indicator that reads it inside
2087            // the function (e.g. a recursive Heikin-Ashi open).
2088            let mut assigned: std::collections::HashSet<String> = std::collections::HashSet::new();
2089            collect_assigned_names(body, &mut assigned);
2090            let local_state: HashMap<String, Variable<O>> = call_vars
2091                .into_iter()
2092                .filter(|(k, _)| !param_names.contains(k) && assigned.contains(k))
2093                .collect();
2094            self.function_local_state.insert(call_id, local_state);
2095        }
2096
2097        Ok(result)
2098    }
2099
2100    /// Get the type name for an object value
2101    fn get_object_type_name(&self, value: &Value<O>) -> Result<String, RuntimeError> {
2102        match value {
2103            Value::Object { type_name, .. } => Ok(type_name.clone()),
2104            _ => Err(RuntimeError::TypeError(
2105                "Cannot determine type of non-object value".to_string(),
2106            )),
2107        }
2108    }
2109
2110    /// Call a method (similar to call_user_function but handles MethodParam with defaults)
2111    fn call_method(
2112        &mut self,
2113        params: &[MethodParam],
2114        body: &[Stmt],
2115        args: Vec<EvaluatedArg<O>>,
2116        call_id: u32,
2117    ) -> Result<Value<O>, RuntimeError> {
2118        // Resolve parameter bindings (positional, named, and defaults), then run
2119        // the body as a stateful call site. Defaults are evaluated in the caller
2120        // scope, before entering the method's scope.
2121        let mut positional_idx = 0;
2122        let mut param_bindings: Vec<(String, Variable<O>)> = Vec::with_capacity(params.len());
2123
2124        for param in params {
2125            let param_value = if positional_idx < args.len() {
2126                match &args[positional_idx] {
2127                    EvaluatedArg::Positional(value) => {
2128                        positional_idx += 1;
2129                        value.clone()
2130                    }
2131                    EvaluatedArg::Named { name, value } => {
2132                        if name == &param.name {
2133                            positional_idx += 1;
2134                            value.clone()
2135                        } else if let Some(default_expr) = &param.default_value {
2136                            self.eval_expr(default_expr)?
2137                        } else {
2138                            Value::Na
2139                        }
2140                    }
2141                }
2142            } else if let Some(default_expr) = &param.default_value {
2143                self.eval_expr(default_expr)?
2144            } else {
2145                Value::Na
2146            };
2147
2148            param_bindings.push((
2149                param.name.clone(),
2150                Variable {
2151                    value: param_value,
2152                    is_const: false,
2153                    is_var_persistent: false,
2154                },
2155            ));
2156        }
2157
2158        self.run_call_site_body(call_id, param_bindings, body)
2159    }
2160
2161    /// Create a constructor function for a user-defined type
2162    fn create_constructor(type_name: String, fields: Vec<TypeField>) -> BuiltinFn<O> {
2163        Rc::new(
2164            move |interp: &mut Interpreter<O>, call_args: FunctionCallArgs<O>| {
2165                let mut instance_fields = HashMap::new();
2166
2167                // Match arguments to fields
2168                let mut positional_idx = 0;
2169
2170                for arg in &call_args.args {
2171                    match arg {
2172                        EvaluatedArg::Positional(value) => {
2173                            // Assign to field by position
2174                            if positional_idx < fields.len() {
2175                                let field = &fields[positional_idx];
2176                                instance_fields.insert(field.name.clone(), value.clone());
2177                                positional_idx += 1;
2178                            } else {
2179                                return Err(RuntimeError::TypeError(format!(
2180                                    "Too many arguments for type '{}' (expected {} fields)",
2181                                    type_name,
2182                                    fields.len()
2183                                )));
2184                            }
2185                        }
2186                        EvaluatedArg::Named { name, value } => {
2187                            // Find field by name
2188                            if let Some(field) = fields.iter().find(|f| f.name == *name) {
2189                                instance_fields.insert(field.name.clone(), value.clone());
2190                            } else {
2191                                return Err(RuntimeError::TypeError(format!(
2192                                    "Type '{}' has no field '{}'",
2193                                    type_name, name
2194                                )));
2195                            }
2196                        }
2197                    }
2198                }
2199
2200                // Fill in defaults for missing fields
2201                for field in &fields {
2202                    if !instance_fields.contains_key(&field.name) {
2203                        if let Some(default_expr) = &field.default_value {
2204                            let default_val = interp.eval_expr(default_expr)?;
2205                            instance_fields.insert(field.name.clone(), default_val);
2206                        } else {
2207                            // Field has no default and wasn't provided
2208                            instance_fields.insert(field.name.clone(), Value::Na);
2209                        }
2210                    }
2211                }
2212
2213                Ok(Value::Object {
2214                    type_name: type_name.clone(),
2215                    fields: Rc::new(RefCell::new(instance_fields)),
2216                    call: None,
2217                    value: None,
2218                })
2219            },
2220        )
2221    }
2222
2223    /// Creates a copy function for types that takes an object and returns a shallow copy
2224    fn create_copy_function() -> BuiltinFn<O> {
2225        Rc::new(
2226            |_interp: &mut Interpreter<O>, call_args: FunctionCallArgs<O>| {
2227                // Expect exactly one positional argument (the object to copy)
2228                if call_args.args.len() != 1 {
2229                    return Err(RuntimeError::TypeError(
2230                        "copy() expects exactly one argument".to_string(),
2231                    ));
2232                }
2233
2234                match &call_args.args[0] {
2235                    EvaluatedArg::Positional(value) => {
2236                        if let Value::Object {
2237                            type_name,
2238                            fields,
2239                            call,
2240                            value: value_fn,
2241                        } = value
2242                        {
2243                            // Create a shallow copy of the object's fields
2244                            let obj = fields.borrow();
2245                            let copied_fields = obj.clone();
2246                            Ok(Value::Object {
2247                                type_name: type_name.clone(),
2248                                fields: Rc::new(RefCell::new(copied_fields)),
2249                                call: call.clone(),
2250                                value: value_fn.clone(),
2251                            })
2252                        } else {
2253                            Err(RuntimeError::TypeError(
2254                                "copy() expects an object argument".to_string(),
2255                            ))
2256                        }
2257                    }
2258                    EvaluatedArg::Named { .. } => Err(RuntimeError::TypeError(
2259                        "copy() does not accept named arguments".to_string(),
2260                    )),
2261                }
2262            },
2263        )
2264    }
2265}
2266
2267impl<O: PineOutput> Default for Interpreter<O> {
2268    fn default() -> Self {
2269        Self::new()
2270    }
2271}