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