Skip to main content

pine_interpreter/
lib.rs

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