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    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    /// The feed `request.security` draws other symbols/timeframes from, and the
464    /// chart's bar spacing in ms (for `request.security_lower_tf`). `None` when
465    /// no host provider is set. The `request.*` builtins reach these through
466    /// `ctx`, the same way `strategy.*` reaches the broker.
467    pub request_provider: Option<Rc<dyn pine_core::DataProvider>>,
468    pub chart_period: Option<i64>,
469}
470
471/// Names a statement block ASSIGNS (declares or writes) directly — i.e. the true
472/// locals of a function body. Reads (e.g. `open`) are ignored, and nested function
473/// declarations are a separate scope so their bodies are not descended into. Used to
474/// decide which variables a call site's persistent state should carry across bars.
475fn collect_assigned_names(body: &[Stmt], out: &mut std::collections::HashSet<String>) {
476    for s in body {
477        match s {
478            Stmt::VarDecl { name, .. } => {
479                out.insert(name.clone());
480            }
481            Stmt::Assignment {
482                target: Expr::Variable { name: n, .. },
483                ..
484            } => {
485                out.insert(n.clone());
486            }
487            Stmt::TupleAssignment { names, .. } => {
488                for n in names {
489                    out.insert(n.clone());
490                }
491            }
492            Stmt::If {
493                then_branch,
494                else_if_branches,
495                else_branch,
496                ..
497            } => {
498                collect_assigned_names(then_branch, out);
499                for (_, b) in else_if_branches {
500                    collect_assigned_names(b, out);
501                }
502                if let Some(b) = else_branch {
503                    collect_assigned_names(b, out);
504                }
505            }
506            Stmt::For { var_name, body, .. } => {
507                out.insert(var_name.clone());
508                collect_assigned_names(body, out);
509            }
510            Stmt::While { body, .. } | Stmt::ForIn { body, .. } => {
511                collect_assigned_names(body, out)
512            }
513            _ => {}
514        }
515    }
516}
517
518/// The builtin namespace whose functions back a value's method syntax, e.g.
519/// `arr.push(v)` dispatches to `array.push(arr, v)`.
520fn builtin_namespace<O: PineOutput>(value: &Value<O>) -> Option<&'static str> {
521    match value {
522        Value::Array(_) => Some("array"),
523        Value::Matrix { .. } => Some("matrix"),
524        _ => None,
525    }
526}
527
528/// Pine `na` is float NaN. NaN can also reach `==`/`!=` wrapped as a
529/// `Value::Number(NaN)` (ta.* functions return `Number(NaN)` for all-NaN
530/// windows) rather than `Value::Na` — both forms make the comparison yield na.
531fn is_na_operand<O: PineOutput>(v: &Value<O>) -> bool {
532    matches!(v, Value::Na) || matches!(v, Value::Number(n) if n.is_nan())
533}
534
535impl<O: PineOutput> Interpreter<O> {
536    pub fn new() -> Self {
537        Self {
538            variables: HashMap::new(),
539            user_types: HashMap::new(),
540            methods: HashMap::new(),
541            library_loader: None,
542            exports: HashMap::new(),
543            output: O::default(),
544            user_series_history: HashMap::new(),
545            function_local_state: HashMap::new(),
546            var_decls_initialized: HashMap::new(),
547            current_call_id: 0,
548            bar_seq: 0,
549            broker: None,
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    /// Helper to evaluate arguments and validate positional-before-named rule
671    /// Evaluate a call's arguments. A parameter marked lazy in `signature`
672    /// receives its argument unevaluated, as a captured [`Value::Expr`].
673    fn evaluate_arguments(
674        &mut self,
675        args: &[Argument],
676        signature: Option<&BuiltinSignature>,
677    ) -> Result<Vec<EvaluatedArg<O>>, RuntimeError> {
678        let mut evaluated_args = Vec::new();
679        let mut seen_named = false;
680        let mut positional_index = 0;
681
682        for arg in args {
683            match arg {
684                Argument::Positional(expr) => {
685                    if seen_named {
686                        return Err(RuntimeError::TypeError(
687                            "Positional arguments cannot follow named arguments".to_string(),
688                        ));
689                    }
690                    let lazy = signature.is_some_and(|s| s.positional_is_lazy(positional_index));
691                    let value = self.eval_or_capture(expr, lazy)?;
692                    evaluated_args.push(EvaluatedArg::Positional(value));
693                    positional_index += 1;
694                }
695                Argument::Named { name, value: expr } => {
696                    seen_named = true;
697                    let lazy = signature.is_some_and(|s| s.named_is_lazy(name));
698                    let value = self.eval_or_capture(expr, lazy)?;
699                    evaluated_args.push(EvaluatedArg::Named {
700                        name: name.clone(),
701                        value,
702                    });
703                }
704            }
705        }
706
707        Ok(evaluated_args)
708    }
709
710    /// Evaluate `expr`, or capture it unevaluated as a [`Value::Expr`] when the
711    /// parameter it binds to is lazy.
712    fn eval_or_capture(&mut self, expr: &Expr, lazy: bool) -> Result<Value<O>, RuntimeError> {
713        if lazy {
714            Ok(Value::Expr(Rc::new(expr.clone())))
715        } else {
716            self.eval_expr(expr)
717        }
718    }
719
720    fn execute_stmt(&mut self, stmt: &Stmt) -> Result<Option<Value<O>>, RuntimeError> {
721        match stmt {
722            Stmt::VarDecl {
723                name,
724                type_qualifier,
725                type_annotation: _,
726                initializer,
727                // varip's intrabar-update behavior is not yet implemented; it
728                // persists across bars exactly like var (is_persistent()).
729                var_kind,
730                ..
731            } => {
732                let is_var_persistent = var_kind.is_persistent();
733                // Pine `var`/`varip` semantics: the initializer runs only the
734                // FIRST time execution reaches this declaration (once ever).
735                // Scoped by the current call site (0 at top level) so the same
736                // function-local `var` at two call sites initializes
737                // independently. Tracked separately from `variables` so a `var`
738                // declaration can shadow a pre-existing host-injected builtin.
739                if is_var_persistent {
740                    let init_key = (self.current_call_id, name.clone());
741                    if self.var_decls_initialized.contains_key(&init_key) {
742                        return Ok(None);
743                    }
744                    self.var_decls_initialized.insert(init_key, self.bar_seq);
745                }
746                // Non-`var` declarations (e.g. `ha_bull_4h = expr`) re-execute on every bar.
747                // Push the previous value to history so `name[1]` lookbacks work, exactly as
748                // the Assignment handler does for `:=` reassignments.
749                if !is_var_persistent {
750                    if let Some(existing) = self.variables.get(name) {
751                        push_history(&mut self.user_series_history, name, existing.value.clone());
752                    }
753                }
754                let value = if let Some(init_expr) = initializer {
755                    self.eval_expr(init_expr)?
756                } else {
757                    Value::Na
758                };
759                let is_const = matches!(type_qualifier, Some(pine_ast::TypeQualifier::Const));
760                self.variables.insert(
761                    name.clone(),
762                    Variable {
763                        value,
764                        is_const,
765                        is_var_persistent,
766                    },
767                );
768                Ok(None)
769            }
770
771            Stmt::Assignment { target, value } => {
772                // Pine `var`-persistent variables: push their current (previous-bar) value to
773                // history BEFORE evaluating the RHS so that [1] lookback in the expression
774                // sees the correct previous-bar value.  Non-var variables push after eval
775                // (their old value was already pushed by VarDecl, or there is no VarDecl).
776                //
777                // Nothing is pushed on the bar the `var` initialized: there is no
778                // previous bar yet, and inventing one would make `acc[1]` read the
779                // initializer instead of na.
780                if let Expr::Variable { name, .. } = target {
781                    if let Some(var) = self.variables.get(name) {
782                        let born_this_bar = self
783                            .var_decls_initialized
784                            .get(&(self.current_call_id, name.clone()))
785                            == Some(&self.bar_seq);
786                        if var.is_var_persistent && !born_this_bar {
787                            push_history(&mut self.user_series_history, name, var.value.clone());
788                        }
789                    }
790                }
791
792                let val = self.eval_expr(value)?;
793                match target {
794                    Expr::Variable { name, .. } => {
795                        // Preserve the existing variable's flags (const, persistent).
796                        let (is_const, is_var_persistent) =
797                            if let Some(var) = self.variables.get(name) {
798                                if var.is_const {
799                                    return Err(RuntimeError::ConstReassignment(name.clone()));
800                                }
801                                if !var.is_var_persistent {
802                                    // Non-var: push current value to history after eval (Pine [n] lookback).
803                                    push_history(
804                                        &mut self.user_series_history,
805                                        name,
806                                        var.value.clone(),
807                                    );
808                                }
809                                // var-persistent: already pushed before eval above.
810                                (false, var.is_var_persistent)
811                            } else {
812                                (false, false)
813                            };
814
815                        self.variables.insert(
816                            name.clone(),
817                            Variable {
818                                value: val,
819                                is_const,
820                                is_var_persistent,
821                            },
822                        );
823                        Ok(None)
824                    }
825                    Expr::MemberAccess { object, member, .. } => {
826                        // Check if we're trying to modify a member of a const variable
827                        if let Expr::Variable { name: var_name, .. } = object.as_ref() {
828                            if let Some(var) = self.variables.get(var_name) {
829                                if var.is_const {
830                                    return Err(RuntimeError::ConstReassignment(format!(
831                                        "{}.{}",
832                                        var_name, member
833                                    )));
834                                }
835                            }
836                        }
837
838                        // Get the object
839                        let obj_value = self.eval_expr(object)?;
840
841                        if let Value::Object { fields, .. } = obj_value {
842                            let mut obj = fields.borrow_mut();
843                            obj.insert(member.clone(), val);
844                            Ok(None)
845                        } else {
846                            Err(RuntimeError::TypeError(
847                                "Cannot assign to member of non-object value".to_string(),
848                            ))
849                        }
850                    }
851                    _ => Err(RuntimeError::TypeError(
852                        "Invalid assignment target".to_string(),
853                    )),
854                }
855            }
856
857            Stmt::TupleAssignment { names, value, .. } => {
858                let val = self.eval_expr(value)?;
859                if let Value::Array(arr_ref) = val {
860                    let arr = arr_ref.borrow();
861                    for (i, name) in names.iter().enumerate() {
862                        // Push current value to history before overwriting (supports [n] lookback).
863                        if let Some(var) = self.variables.get(name) {
864                            push_history(&mut self.user_series_history, name, var.value.clone());
865                        }
866                        let element_val = arr.get(i).cloned().unwrap_or(Value::Na);
867                        self.variables.insert(
868                            name.clone(),
869                            Variable {
870                                value: element_val,
871                                is_const: false,
872                                is_var_persistent: false,
873                            },
874                        );
875                    }
876                    Ok(None)
877                } else {
878                    Err(RuntimeError::TypeError(
879                        "Expected array for tuple destructuring".to_string(),
880                    ))
881                }
882            }
883
884            Stmt::Expression(expr) => {
885                self.eval_expr(expr)?;
886                Ok(None)
887            }
888
889            Stmt::If {
890                condition,
891                then_branch,
892                else_if_branches,
893                else_branch,
894            } => {
895                let cond_value = self.eval_expr(condition)?;
896                if cond_value.truthy_for_condition()? {
897                    for stmt in then_branch {
898                        self.execute_stmt(stmt)?;
899                    }
900                } else {
901                    // Try each else if branch in order
902                    let mut executed = false;
903                    for (else_if_cond, else_if_body) in else_if_branches {
904                        let else_if_value = self.eval_expr(else_if_cond)?;
905                        if else_if_value.truthy_for_condition()? {
906                            for stmt in else_if_body {
907                                self.execute_stmt(stmt)?;
908                            }
909                            executed = true;
910                            break;
911                        }
912                    }
913
914                    // If no else if matched, try else branch
915                    if !executed {
916                        if let Some(else_stmts) = else_branch {
917                            for stmt in else_stmts {
918                                self.execute_stmt(stmt)?;
919                            }
920                        }
921                    }
922                }
923                Ok(None)
924            }
925
926            Stmt::For {
927                var_name,
928                from,
929                to,
930                body,
931                ..
932            } => {
933                let from_val = self.eval_expr(from)?.as_number()?;
934                let to_val = self.eval_expr(to)?.as_number()?;
935
936                if from_val > to_val {
937                    return Err(RuntimeError::InvalidForLoop(from_val, to_val));
938                }
939
940                let mut i = from_val as i64;
941                let end = to_val as i64;
942
943                while i <= end {
944                    self.variables.insert(
945                        var_name.clone(),
946                        Variable {
947                            value: Value::Int(i),
948                            is_const: false,
949                            is_var_persistent: false,
950                        },
951                    );
952
953                    let control = self.execute_loop_body(body)?;
954                    if control == LoopControl::Break {
955                        break;
956                    }
957
958                    i += 1;
959                }
960
961                Ok(None)
962            }
963
964            Stmt::ForIn {
965                index_var,
966                item_var,
967                collection,
968                body,
969                ..
970            } => {
971                let collection_value = self.eval_expr(collection)?;
972                let arr = collection_value.as_array()?;
973                let arr_borrowed = arr.borrow();
974
975                for (index, item) in arr_borrowed.iter().enumerate() {
976                    // Set index variable if tuple form
977                    if let Some(idx_var) = index_var {
978                        self.variables.insert(
979                            idx_var.clone(),
980                            Variable {
981                                value: Value::Int(index as i64),
982                                is_const: false,
983                                is_var_persistent: false,
984                            },
985                        );
986                    }
987
988                    // Set item variable
989                    self.variables.insert(
990                        item_var.clone(),
991                        Variable {
992                            value: item.clone(),
993                            is_const: false,
994                            is_var_persistent: false,
995                        },
996                    );
997
998                    let control = self.execute_loop_body(body)?;
999                    if control == LoopControl::Break {
1000                        break;
1001                    }
1002                }
1003
1004                Ok(None)
1005            }
1006
1007            Stmt::While { condition, body } => {
1008                loop {
1009                    let cond_value = self.eval_expr(condition)?;
1010                    if !cond_value.truthy_for_condition()? {
1011                        break;
1012                    }
1013
1014                    let control = self.execute_loop_body(body)?;
1015                    if control == LoopControl::Break {
1016                        break;
1017                    }
1018                }
1019                Ok(None)
1020            }
1021
1022            Stmt::Break => Err(RuntimeError::BreakOutsideLoop),
1023            Stmt::Continue => Err(RuntimeError::ContinueOutsideLoop),
1024
1025            Stmt::TypeDecl {
1026                name,
1027                fields,
1028                export,
1029                ..
1030            } => {
1031                // Create a Type value and store it as a variable
1032                let type_value = Value::Type {
1033                    name: name.clone(),
1034                    fields: fields.clone(),
1035                };
1036                self.user_types.insert(name.clone(), type_value.clone());
1037                self.variables.insert(
1038                    name.clone(),
1039                    Variable {
1040                        value: type_value.clone(),
1041                        is_const: false,
1042                        is_var_persistent: false,
1043                    },
1044                );
1045
1046                // If exported, also store in exports
1047                if *export {
1048                    self.exports.insert(name.clone(), type_value);
1049                }
1050                Ok(None)
1051            }
1052
1053            Stmt::EnumDecl {
1054                name,
1055                fields,
1056                export,
1057                ..
1058            } => {
1059                // Create an Object that contains all enum members as fields
1060                let mut enum_fields = HashMap::new();
1061
1062                for field in fields {
1063                    let title = field.title.clone().unwrap_or_else(|| field.name.clone());
1064                    let enum_value = Value::Enum {
1065                        enum_name: name.clone(),
1066                        field_name: field.name.clone(),
1067                        title,
1068                    };
1069                    enum_fields.insert(field.name.clone(), enum_value);
1070                }
1071
1072                let enum_object = Value::Object {
1073                    type_name: name.clone(),
1074                    fields: Rc::new(RefCell::new(enum_fields)),
1075                    call: None,
1076                };
1077                self.variables.insert(
1078                    name.clone(),
1079                    Variable {
1080                        value: enum_object.clone(),
1081                        is_const: false,
1082                        is_var_persistent: false,
1083                    },
1084                );
1085
1086                // If exported, also store in exports
1087                if *export {
1088                    self.exports.insert(name.clone(), enum_object);
1089                }
1090                Ok(None)
1091            }
1092
1093            Stmt::Export { item } => {
1094                // Mark the item for export
1095                match item {
1096                    pine_ast::ExportItem::Type(type_name) => {
1097                        // Export the type - it should already be in variables
1098                        if let Some(var) = self.variables.get(type_name) {
1099                            self.exports.insert(type_name.clone(), var.value.clone());
1100                        }
1101                    }
1102                    pine_ast::ExportItem::Function(func_name) => {
1103                        // Export the function - it should already be in variables
1104                        if let Some(var) = self.variables.get(func_name) {
1105                            self.exports.insert(func_name.clone(), var.value.clone());
1106                        }
1107                    }
1108                }
1109                Ok(None)
1110            }
1111
1112            Stmt::Import { path, alias, .. } => {
1113                let source = match &self.library_loader {
1114                    Some(loader) => loader.load_library(path),
1115                    None => {
1116                        return Err(RuntimeError::LibraryError(
1117                            "Cannot import library: no library loader configured".to_string(),
1118                        ))
1119                    }
1120                }
1121                .map_err(|e| {
1122                    RuntimeError::LibraryError(format!("Failed to load library '{}': {}", path, e))
1123                })?;
1124
1125                let library_program = pine_parser::Parser::parse_source(&source).map_err(|e| {
1126                    RuntimeError::LibraryError(format!("Failed to parse library '{}': {}", path, e))
1127                })?;
1128
1129                let mut library_interp = Interpreter::new();
1130                library_interp.execute(&library_program)?;
1131                let library_exports = library_interp.exports();
1132
1133                for (method_name, method_defs) in &library_interp.methods {
1134                    for method_def in method_defs {
1135                        self.methods
1136                            .entry(method_name.clone())
1137                            .or_default()
1138                            .push(method_def.clone());
1139                    }
1140                }
1141
1142                let namespace: Value<O> = Value::Object {
1143                    type_name: alias.clone(),
1144                    fields: Rc::new(RefCell::new(library_exports.clone())),
1145                    call: None,
1146                };
1147                self.variables.insert(
1148                    alias.clone(),
1149                    Variable {
1150                        value: namespace,
1151                        is_const: false,
1152                        is_var_persistent: false,
1153                    },
1154                );
1155                Ok(None)
1156            }
1157
1158            Stmt::MethodDecl {
1159                name,
1160                params,
1161                body,
1162                export,
1163                ..
1164            } => {
1165                // Extract the type name from the first parameter's type annotation
1166                let type_name = if let Some(first_param) = params.first() {
1167                    first_param.type_annotation.clone().ok_or_else(|| {
1168                        RuntimeError::TypeError(
1169                            "Method's first parameter must have a type annotation".to_string(),
1170                        )
1171                    })?
1172                } else {
1173                    return Err(RuntimeError::TypeError(
1174                        "Method must have at least one parameter (this)".to_string(),
1175                    ));
1176                };
1177
1178                // Store the method definition
1179                let method_def = MethodDef {
1180                    type_name,
1181                    params: params.clone(),
1182                    body: body.clone(),
1183                };
1184
1185                self.methods
1186                    .entry(name.clone())
1187                    .or_default()
1188                    .push(method_def);
1189
1190                // If exported, store the method in exports
1191                // Methods are exported as part of their type, so we may need to handle this differently
1192                // For now, just mark it as exported (this might need more work)
1193                if *export {
1194                    // TODO: Handle method exports properly
1195                }
1196
1197                Ok(None)
1198            }
1199
1200            Stmt::FunctionDecl {
1201                name,
1202                params,
1203                body,
1204                export,
1205                ..
1206            } => {
1207                // Create a function value
1208                let func_value = Value::Function {
1209                    params: params.clone(),
1210                    body: body.clone(),
1211                };
1212                self.variables.insert(
1213                    name.clone(),
1214                    Variable {
1215                        value: func_value.clone(),
1216                        is_const: false,
1217                        is_var_persistent: false,
1218                    },
1219                );
1220
1221                // If exported, also store in exports
1222                if *export {
1223                    self.exports.insert(name.clone(), func_value);
1224                }
1225
1226                Ok(None)
1227            }
1228        }
1229    }
1230
1231    /// Execute loop body, handling break/continue
1232    fn execute_loop_body(&mut self, body: &[Stmt]) -> Result<LoopControl, RuntimeError> {
1233        for stmt in body {
1234            match stmt {
1235                Stmt::Break => return Ok(LoopControl::Break),
1236                Stmt::Continue => return Ok(LoopControl::Continue),
1237                Stmt::If {
1238                    condition,
1239                    then_branch,
1240                    else_if_branches,
1241                    else_branch,
1242                } => {
1243                    let cond_value = self.eval_expr(condition)?;
1244                    let branch = if cond_value.truthy_for_condition()? {
1245                        then_branch
1246                    } else {
1247                        // Try each else if branch
1248                        let mut matched_branch = None;
1249                        for (else_if_cond, else_if_body) in else_if_branches {
1250                            let else_if_value = self.eval_expr(else_if_cond)?;
1251                            if else_if_value.truthy_for_condition()? {
1252                                matched_branch = Some(else_if_body);
1253                                break;
1254                            }
1255                        }
1256
1257                        if let Some(branch) = matched_branch {
1258                            branch
1259                        } else if let Some(else_stmts) = else_branch {
1260                            else_stmts
1261                        } else {
1262                            continue;
1263                        }
1264                    };
1265
1266                    let control = self.execute_loop_body(branch)?;
1267                    if control != LoopControl::None {
1268                        return Ok(control);
1269                    }
1270                }
1271                Stmt::For { .. } | Stmt::ForIn { .. } | Stmt::While { .. } => {
1272                    // Nested loops handle their own break/continue
1273                    self.execute_stmt(stmt)?;
1274                }
1275                _ => {
1276                    self.execute_stmt(stmt)?;
1277                }
1278            }
1279        }
1280        Ok(LoopControl::None)
1281    }
1282
1283    fn eval_expr(&mut self, expr: &Expr) -> Result<Value<O>, RuntimeError> {
1284        match expr {
1285            Expr::Literal(lit) => Ok(self.eval_literal(lit)),
1286
1287            Expr::Variable { name, .. } => self
1288                .variables
1289                .get(name)
1290                .map(|var| var.value.clone())
1291                .ok_or_else(|| RuntimeError::UndefinedVariable(name.clone())),
1292
1293            Expr::Binary {
1294                left, op, right, ..
1295            } => {
1296                let left_val = self.eval_expr(left)?;
1297                // Pine `and`/`or` are lazy: when the left operand alone decides
1298                // the result (false-and / true-or), the right operand is NOT
1299                // evaluated — side effects inside it (e.g. stateful ta.* calls)
1300                // must not run. The three-valued na results are unchanged:
1301                // false absorbs na in `and`, true absorbs na in `or`, and an
1302                // na left operand still requires the right operand's value.
1303                if matches!(op, BinOp::And | BinOp::Or) {
1304                    match (op, left_val.to_bool()?) {
1305                        (BinOp::And, Some(false)) => return Ok(Value::Bool(false)),
1306                        (BinOp::Or, Some(true)) => return Ok(Value::Bool(true)),
1307                        _ => {}
1308                    }
1309                }
1310                let right_val = self.eval_expr(right)?;
1311                self.eval_binary_op(&left_val, op, &right_val)
1312            }
1313
1314            Expr::Unary { op, expr } => {
1315                let val = self.eval_expr(expr)?;
1316                self.eval_unary_op(op, &val)
1317            }
1318
1319            Expr::Ternary {
1320                condition,
1321                then_expr,
1322                else_expr,
1323            } => {
1324                let cond_val = self.eval_expr(condition)?;
1325                if cond_val.truthy_for_condition()? {
1326                    self.eval_expr(then_expr)
1327                } else {
1328                    self.eval_expr(else_expr)
1329                }
1330            }
1331
1332            Expr::IfExpr {
1333                condition,
1334                then_expr,
1335                else_if_branches,
1336                else_expr,
1337            } => {
1338                let cond_val = self.eval_expr(condition)?;
1339                if cond_val.truthy_for_condition()? {
1340                    self.eval_expr(then_expr)
1341                } else {
1342                    // Try each else if branch
1343                    for (else_if_cond, else_if_expr) in else_if_branches {
1344                        let else_if_val = self.eval_expr(else_if_cond)?;
1345                        if else_if_val.truthy_for_condition()? {
1346                            return self.eval_expr(else_if_expr);
1347                        }
1348                    }
1349                    // No else if matched, evaluate else branch or return na
1350                    if let Some(expr) = else_expr {
1351                        self.eval_expr(expr)
1352                    } else {
1353                        Ok(Value::Na)
1354                    }
1355                }
1356            }
1357
1358            Expr::Array(elements) => {
1359                let values: Result<Vec<_>, _> =
1360                    elements.iter().map(|e| self.eval_expr(e)).collect();
1361                Ok(Value::Array(Rc::new(RefCell::new(values?))))
1362            }
1363
1364            Expr::Index { expr, index } => {
1365                let index_val = self.eval_expr(index)?.as_number()? as usize;
1366
1367                // For user-computed variables with tracked history, look up
1368                // user_series_history: history[len-1] = previous bar. A tracked
1369                // variable with insufficient depth yields na (warm-up), never a
1370                // fall-through to Number indexing. Variables WITHOUT tracked
1371                // history (e.g. builtin Series like `close` fed by the host)
1372                // fall through to the Series/Array indexing below.
1373                if index_val > 0 {
1374                    if let Expr::Variable { name: var_name, .. } = expr.as_ref() {
1375                        if let Some(h) = self.user_series_history.get(var_name) {
1376                            return Ok(if h.len() >= index_val {
1377                                h[h.len() - index_val].clone()
1378                            } else {
1379                                Value::Na
1380                            });
1381                        }
1382                        // Non-Series plain values with no history (a user var
1383                        // assigned only this bar) still index as na rather
1384                        // than erroring below.
1385                        if let Some(var) = self.variables.get(var_name) {
1386                            if !matches!(var.value, Value::Series(_) | Value::Array(_)) {
1387                                return Ok(Value::Na);
1388                            }
1389                        }
1390                    }
1391                }
1392
1393                let val = self.eval_expr(expr)?;
1394
1395                match val {
1396                    Value::Array(arr_ref) => {
1397                        let arr = arr_ref.borrow();
1398                        arr.get(index_val)
1399                            .cloned()
1400                            .ok_or(RuntimeError::IndexOutOfBounds(index_val))
1401                    }
1402                    Value::Series(series) => {
1403                        // Index 0 is this bar. Anything further back lives in
1404                        // `user_series_history`, which the branch above already
1405                        // consulted for a named variable — reaching here means
1406                        // the series has no recorded history, which is na.
1407                        if index_val == 0 {
1408                            Ok((*series.current).clone())
1409                        } else {
1410                            Ok(Value::Na)
1411                        }
1412                    }
1413                    ref v => Err(RuntimeError::TypeError(format!(
1414                        "Cannot index non-array/non-series value: {:?}",
1415                        v
1416                    ))),
1417                }
1418            }
1419
1420            Expr::Switch { value, cases } => {
1421                let switch_val = self.eval_expr(value)?;
1422
1423                for (pattern, result) in cases {
1424                    // Check if pattern matches
1425                    let pattern_val = self.eval_expr(pattern)?;
1426
1427                    // Special case: default pattern (true literal)
1428                    if pattern_val == Value::Bool(true)
1429                        && matches!(pattern, Expr::Literal(Literal::Bool(true)))
1430                    {
1431                        return self.eval_expr(result);
1432                    }
1433
1434                    // Check equality
1435                    if self.values_equal(&switch_val, &pattern_val)? {
1436                        return self.eval_expr(result);
1437                    }
1438                }
1439
1440                // No match found
1441                Ok(Value::Na)
1442            }
1443
1444            Expr::Call {
1445                callee,
1446                type_args,
1447                args,
1448                id,
1449                ..
1450            } => {
1451                // Check if this is a method call (object.method())
1452                if let Expr::MemberAccess { object, member, .. } = callee.as_ref() {
1453                    // Try to find a method with this name
1454                    if let Some(method_defs) = self.methods.get(member).cloned() {
1455                        // Evaluate the object (this will be the first parameter)
1456                        let obj_value = self.eval_expr(object)?;
1457
1458                        // Find the method that matches the object's type
1459                        let obj_type = self.get_object_type_name(&obj_value)?;
1460
1461                        if let Some(method_def) =
1462                            method_defs.iter().find(|m| m.type_name == obj_type)
1463                        {
1464                            // Evaluate the other arguments
1465                            let mut evaluated_args: Vec<EvaluatedArg<O>> =
1466                                vec![EvaluatedArg::Positional(obj_value)];
1467                            evaluated_args.extend(self.evaluate_arguments(args, None)?);
1468
1469                            // Call the method (treating it like a function),
1470                            // threading the call site id so method-local state
1471                            // persists per call site.
1472                            return self.call_method(
1473                                &method_def.params,
1474                                &method_def.body,
1475                                evaluated_args,
1476                                *id,
1477                            );
1478                        }
1479                    }
1480                }
1481
1482                // Builtin method syntax: a collection receiver `x.m(args)` is
1483                // sugar for `namespace.m(x, args)` — the same builtins in
1484                // function form, with the receiver passed first. (Skip `Call`
1485                // receivers so a side-effecting `f().m()` is not evaluated twice.)
1486                if let Expr::MemberAccess { object, member, .. } = callee.as_ref() {
1487                    if !matches!(object.as_ref(), Expr::Call { .. }) {
1488                        let receiver = self.eval_expr(object)?;
1489                        if let Some(namespace) = builtin_namespace(&receiver) {
1490                            if let Some(Value::BuiltinFunction(builtin_fn)) =
1491                                self.namespace_member(namespace, member)
1492                            {
1493                                let mut evaluated_args = vec![EvaluatedArg::Positional(receiver)];
1494                                evaluated_args.extend(self.evaluate_arguments(args, None)?);
1495                                let call_args =
1496                                    FunctionCallArgs::new(type_args.clone(), evaluated_args)
1497                                        .with_call_id(*id);
1498                                return (builtin_fn.call)(self, call_args);
1499                            }
1500                        }
1501                    }
1502                }
1503
1504                // Not a method call, proceed with regular function call.
1505                // Resolve the callee first so a builtin's lazy parameters can
1506                // capture their arguments unevaluated.
1507                let callee_value = self.eval_expr(callee)?;
1508                let signature = match &callee_value {
1509                    Value::BuiltinFunction(builtin) => Some(builtin.signature.clone()),
1510                    _ => None,
1511                };
1512                let evaluated_args = self.evaluate_arguments(args, signature.as_ref())?;
1513
1514                // Call the function based on its type
1515                match callee_value {
1516                    Value::Function { params, body } => {
1517                        // Thread the call site's lexical id so function-local
1518                        // state persists per call site, not per function name.
1519                        self.call_user_function(&params, &body, args, evaluated_args, *id)
1520                    }
1521                    Value::BuiltinFunction(builtin_fn) => {
1522                        // Pass type_args from the parsed call expression, and the
1523                        // call node's lexical id for per-call-site builtin state.
1524                        let call_args = FunctionCallArgs::new(type_args.clone(), evaluated_args)
1525                            .with_call_id(*id);
1526                        (builtin_fn.call)(self, call_args)
1527                    }
1528                    // A callable namespace object, like `input(...)` alongside
1529                    // `input.int(...)`. Objects without a `call` are not callable.
1530                    Value::Object {
1531                        call: Some(builtin_fn),
1532                        ..
1533                    } => {
1534                        let call_args = FunctionCallArgs::new(type_args.clone(), evaluated_args)
1535                            .with_call_id(*id);
1536                        (builtin_fn)(self, call_args)
1537                    }
1538                    // Pine's `na` is a keyword that doubles as a function: na(x) → is x na?
1539                    Value::Na => {
1540                        let is_na = matches!(
1541                            evaluated_args.first(),
1542                            Some(EvaluatedArg::Positional(Value::Na)) | None
1543                        );
1544                        Ok(Value::Bool(is_na))
1545                    }
1546                    _ => Err(RuntimeError::TypeError(
1547                        "Attempted to call a non-function value".to_string(),
1548                    )),
1549                }
1550            }
1551
1552            Expr::MemberAccess { object, member, .. } => {
1553                // `Type.new` / `Type.copy` resolve via the type namespace, so a
1554                // type may share its name with a shadowing function/variable.
1555                let obj_value = match object.as_ref() {
1556                    Expr::Variable { name, .. }
1557                        if (member == "new" || member == "copy")
1558                            && self.user_types.contains_key(name) =>
1559                    {
1560                        self.user_types[name].clone()
1561                    }
1562                    _ => self.eval_expr(object)?,
1563                };
1564                match obj_value {
1565                    Value::Object { fields, .. } => {
1566                        let obj = fields.borrow();
1567                        obj.get(member).cloned().ok_or_else(|| {
1568                            RuntimeError::TypeError(format!("Object has no member '{}'", member))
1569                        })
1570                    }
1571                    Value::Type { name, fields } => {
1572                        // Types have 'new' and 'copy' methods
1573                        if member == "new" {
1574                            // Return a constructor function
1575                            Ok(Value::BuiltinFunction(Builtin::untyped(
1576                                Self::create_constructor(name, fields),
1577                            )))
1578                        } else if member == "copy" {
1579                            // Return a copy function
1580                            Ok(Value::BuiltinFunction(Builtin::untyped(
1581                                Self::create_copy_function(),
1582                            )))
1583                        } else {
1584                            Err(RuntimeError::TypeError(format!(
1585                                "Type '{}' has no member '{}' (only 'new' and 'copy' are supported)",
1586                                name, member
1587                            )))
1588                        }
1589                    }
1590                    _ => Err(RuntimeError::TypeError(format!(
1591                        "Cannot access member '{}' on non-object value",
1592                        member
1593                    ))),
1594                }
1595            }
1596
1597            Expr::Function { params, body } => {
1598                // params is already Vec<FunctionParam> from the AST
1599                Ok(Value::Function {
1600                    params: params.clone(),
1601                    body: body.clone(),
1602                })
1603            }
1604        }
1605    }
1606
1607    fn eval_literal(&self, lit: &Literal) -> Value<O> {
1608        match lit {
1609            Literal::Int(n) => Value::Int(*n),
1610            Literal::Number(n) => Value::Number(*n),
1611            Literal::String(s) => Value::String(s.clone()),
1612            Literal::Bool(b) => Value::Bool(*b),
1613            Literal::Na => Value::Na,
1614            Literal::HexColor(hex) => Value::String(hex.clone()),
1615        }
1616    }
1617
1618    fn eval_binary_op(
1619        &self,
1620        left: &Value<O>,
1621        op: &BinOp,
1622        right: &Value<O>,
1623    ) -> Result<Value<O>, RuntimeError> {
1624        match op {
1625            BinOp::Add => {
1626                // String concatenation or numeric addition
1627                if matches!(left, Value::String(_)) || matches!(right, Value::String(_)) {
1628                    Ok(Value::String(format!(
1629                        "{}{}",
1630                        left.as_string()?,
1631                        right.as_string()?
1632                    )))
1633                } else {
1634                    numeric_op(left, right, |a, b| Some(a + b))
1635                }
1636            }
1637
1638            BinOp::Sub => numeric_op(left, right, |a, b| Some(a - b)),
1639
1640            BinOp::Mul => numeric_op(left, right, |a, b| Some(a * b)),
1641
1642            // Pine semantics: a zero divisor yields `na`, not an error. Two ints
1643            // divide as ints (`15 / 2 == 7`); any float operand divides as float.
1644            BinOp::Div => numeric_op(left, right, Num::checked_div),
1645
1646            BinOp::Mod => numeric_op(left, right, Num::checked_rem),
1647
1648            // Pine semantics: a comparison with an `na` operand yields `na`
1649            // (including `na == na` — testing for na requires the na() function).
1650            // `is_na_operand` also treats a `Value::Number(NaN)` as na (a
1651            // computed NaN such as math.sqrt(-1.0), or a ta.* window that
1652            // returns `Number(NaN)` rather than `Value::Na`). Eq/NotEq must not
1653            // leak a structural bool through values_equal, otherwise e.g.
1654            // `dayofweek != dayofweek[1]` evaluates true on the first bar.
1655            BinOp::Eq => {
1656                if is_na_operand(left) || is_na_operand(right) {
1657                    return Ok(Value::Na);
1658                }
1659                Ok(Value::Bool(self.values_equal(left, right)?))
1660            }
1661
1662            BinOp::NotEq => {
1663                if is_na_operand(left) || is_na_operand(right) {
1664                    return Ok(Value::Na);
1665                }
1666                Ok(Value::Bool(!self.values_equal(left, right)?))
1667            }
1668
1669            // Relational arms need the same guard: to_number() maps `Value::Na`
1670            // to None (caught by the match) but passes `Number(NaN)` through as
1671            // Some(NaN), where a raw float comparison yields false — a
1672            // structural bool that e.g. `not` then flips to true, instead of
1673            // the `na` TradingView produces.
1674            BinOp::Less => {
1675                if is_na_operand(left) || is_na_operand(right) {
1676                    return Ok(Value::Na);
1677                }
1678                match (left.to_number()?, right.to_number()?) {
1679                    (Some(l), Some(r)) => Ok(Value::Bool(l < r)),
1680                    _ => Ok(Value::Na),
1681                }
1682            }
1683
1684            BinOp::Greater => {
1685                if is_na_operand(left) || is_na_operand(right) {
1686                    return Ok(Value::Na);
1687                }
1688                match (left.to_number()?, right.to_number()?) {
1689                    (Some(l), Some(r)) => Ok(Value::Bool(l > r)),
1690                    _ => Ok(Value::Na),
1691                }
1692            }
1693
1694            BinOp::LessEq => {
1695                if is_na_operand(left) || is_na_operand(right) {
1696                    return Ok(Value::Na);
1697                }
1698                match (left.to_number()?, right.to_number()?) {
1699                    (Some(l), Some(r)) => Ok(Value::Bool(l <= r)),
1700                    _ => Ok(Value::Na),
1701                }
1702            }
1703
1704            BinOp::GreaterEq => {
1705                if is_na_operand(left) || is_na_operand(right) {
1706                    return Ok(Value::Na);
1707                }
1708                match (left.to_number()?, right.to_number()?) {
1709                    (Some(l), Some(r)) => Ok(Value::Bool(l >= r)),
1710                    _ => Ok(Value::Na),
1711                }
1712            }
1713
1714            // Three-valued logic: false absorbs na; true and na → na.
1715            BinOp::And => match (left.to_bool()?, right.to_bool()?) {
1716                (Some(false), _) | (_, Some(false)) => Ok(Value::Bool(false)),
1717                (Some(true), Some(true)) => Ok(Value::Bool(true)),
1718                _ => Ok(Value::Na),
1719            },
1720
1721            // Three-valued logic: true absorbs na; false or na → na.
1722            BinOp::Or => match (left.to_bool()?, right.to_bool()?) {
1723                (Some(true), _) | (_, Some(true)) => Ok(Value::Bool(true)),
1724                (Some(false), Some(false)) => Ok(Value::Bool(false)),
1725                _ => Ok(Value::Na),
1726            },
1727        }
1728    }
1729
1730    fn eval_unary_op(&self, op: &UnOp, val: &Value<O>) -> Result<Value<O>, RuntimeError> {
1731        match op {
1732            // Negating an int stays an int.
1733            UnOp::Neg => match val.as_int() {
1734                Some(n) => Ok(Value::Int(-n)),
1735                None => match val.to_number()? {
1736                    Some(n) => Ok(Value::Number(-n)),
1737                    None => Ok(Value::Na),
1738                },
1739            },
1740            UnOp::Not => match val.to_bool()? {
1741                Some(b) => Ok(Value::Bool(!b)),
1742                None => Ok(Value::Na),
1743            },
1744        }
1745    }
1746
1747    fn values_equal(&self, left: &Value<O>, right: &Value<O>) -> Result<bool, RuntimeError> {
1748        match (left, right) {
1749            (Value::Int(l), Value::Int(r)) => Ok(l == r),
1750            // int and float compare by value, so `1 == 1.0`.
1751            (Value::Int(l), Value::Number(r)) | (Value::Number(r), Value::Int(l)) => {
1752                Ok((*l as f64 - r).abs() < f64::EPSILON)
1753            }
1754            (Value::Number(l), Value::Number(r)) => Ok((l - r).abs() < f64::EPSILON),
1755            (Value::String(l), Value::String(r)) => Ok(l == r),
1756            (Value::Bool(l), Value::Bool(r)) => Ok(l == r),
1757            (Value::Na, Value::Na) => Ok(true),
1758            (
1759                Value::Enum {
1760                    enum_name: a_enum,
1761                    field_name: a_field,
1762                    ..
1763                },
1764                Value::Enum {
1765                    enum_name: b_enum,
1766                    field_name: b_field,
1767                    ..
1768                },
1769            ) => Ok(a_enum == b_enum && a_field == b_field),
1770            _ => Ok(false),
1771        }
1772    }
1773
1774    /// Check if an expression evaluates to a const value
1775    fn is_const_expr(&self, expr: &Expr) -> bool {
1776        match expr {
1777            // Literals are always const
1778            Expr::Literal(_) => true,
1779            // Variable is const if it's stored as const
1780            Expr::Variable { name, .. } => self
1781                .variables
1782                .get(name)
1783                .map(|var| var.is_const)
1784                .unwrap_or(false),
1785            // Member access is const if the base object is const
1786            Expr::MemberAccess { object, .. } => self.is_const_expr(object),
1787            // All other expressions are not const
1788            _ => false,
1789        }
1790    }
1791
1792    fn call_user_function(
1793        &mut self,
1794        params: &[pine_ast::FunctionParam],
1795        body: &[Stmt],
1796        arg_exprs: &[Argument],
1797        args: Vec<EvaluatedArg<O>>,
1798        call_id: u32,
1799    ) -> Result<Value<O>, RuntimeError> {
1800        // Extract positional arguments (user functions don't support named args yet)
1801        let mut positional_values = Vec::new();
1802        let mut positional_exprs = Vec::new();
1803
1804        for (i, arg) in args.iter().enumerate() {
1805            match arg {
1806                EvaluatedArg::Positional(value) => {
1807                    positional_values.push(value.clone());
1808                    if let Some(Argument::Positional(expr)) = arg_exprs.get(i) {
1809                        positional_exprs.push(expr);
1810                    }
1811                }
1812                EvaluatedArg::Named { .. } => {
1813                    return Err(RuntimeError::TypeError(
1814                        "User-defined functions do not support named arguments yet".to_string(),
1815                    ))
1816                }
1817            }
1818        }
1819
1820        // Check argument count
1821        if positional_values.len() != params.len() {
1822            return Err(RuntimeError::TypeError(format!(
1823                "Expected {} arguments, got {}",
1824                params.len(),
1825                positional_values.len()
1826            )));
1827        }
1828
1829        // Validate const parameters receive const arguments
1830        for (i, param) in params.iter().enumerate() {
1831            if matches!(param.type_qualifier, Some(pine_ast::TypeQualifier::Const)) {
1832                if let Some(arg_expr) = positional_exprs.get(i) {
1833                    if !self.is_const_expr(arg_expr) {
1834                        return Err(RuntimeError::TypeError(format!(
1835                            "Parameter '{}' requires a const argument, but received a non-const value",
1836                            param.name
1837                        )));
1838                    }
1839                }
1840            }
1841        }
1842
1843        // Bind parameters to arguments with the appropriate const flag, then run
1844        // the body as a stateful call site.
1845        let param_bindings: Vec<(String, Variable<O>)> = params
1846            .iter()
1847            .zip(positional_values)
1848            .map(|(param, value)| {
1849                let is_const = matches!(param.type_qualifier, Some(pine_ast::TypeQualifier::Const));
1850                (
1851                    param.name.clone(),
1852                    Variable {
1853                        value,
1854                        is_const,
1855                        is_var_persistent: false,
1856                    },
1857                )
1858            })
1859            .collect();
1860
1861        self.run_call_site_body(call_id, param_bindings, body)
1862    }
1863
1864    /// Run a user function or method body as a stateful call site: restore this
1865    /// call site's persisted locals, bind the given parameters, execute the body
1866    /// under the call site's id (scoping `var` init-once), then persist the call
1867    /// site's locals and restore the outer scope. Keying state by call site —
1868    /// not by callable name — keeps two call sites of the same function/method
1869    /// independent, matching TradingView. A `call_id` of 0 (no stable identity)
1870    /// is not persisted.
1871    fn run_call_site_body(
1872        &mut self,
1873        call_id: u32,
1874        param_bindings: Vec<(String, Variable<O>)>,
1875        body: &[Stmt],
1876    ) -> Result<Value<O>, RuntimeError> {
1877        let param_names: std::collections::HashSet<String> =
1878            param_bindings.iter().map(|(n, _)| n.clone()).collect();
1879
1880        // Save the outer scope.
1881        let saved_vars = self.variables.clone();
1882
1883        // Restore this call site's locals (all locals persist across calls, not
1884        // just `var`s, so series indexing like `o[1]` works inside the body).
1885        // Parameters are excluded — they are freshly bound below.
1886        if call_id != 0 {
1887            if let Some(local_state) = self.function_local_state.get(&call_id) {
1888                for (var_name, var) in local_state {
1889                    if !param_names.contains(var_name) {
1890                        self.variables.insert(var_name.clone(), var.clone());
1891                    }
1892                }
1893            }
1894        }
1895
1896        // Bind parameters (freshly each call).
1897        for (name, var) in param_bindings {
1898            self.variables.insert(name, var);
1899        }
1900
1901        // Execute the body under this call site's id, so `var` init-once tracking
1902        // is scoped to the call site. Restored afterwards to support
1903        // nested/recursive calls. (Like the scope restore below, an error just
1904        // propagates and aborts the script.)
1905        let prev_call_id = self.current_call_id;
1906        self.current_call_id = call_id;
1907        let mut result: Value<O> = Value::Na;
1908        for stmt in body {
1909            if let Some(return_value) = self.execute_stmt(stmt)? {
1910                result = return_value;
1911            } else if let Stmt::Expression(expr) = stmt {
1912                // Last expression is the return value
1913                result = self.eval_expr(expr)?;
1914            }
1915        }
1916        self.current_call_id = prev_call_id;
1917
1918        // Restore the outer scope. This call site's locals live only in
1919        // function_local_state (keyed by call_id) and are NOT leaked into the
1920        // outer/global scope, so two call sites keep independent state.
1921        let call_vars = std::mem::replace(&mut self.variables, saved_vars);
1922        if call_id != 0 {
1923            // Persist only the names the body actually ASSIGNS — its true locals
1924            // (both `var` and plain, so their series history advances). The scope
1925            // also holds read-only builtins/globals inherited from the outer scope
1926            // (`open`/`high`/`low`/`close`/…); saving one of those would restore it
1927            // stale on the next call, freezing any indicator that reads it inside
1928            // the function (e.g. a recursive Heikin-Ashi open).
1929            let mut assigned: std::collections::HashSet<String> = std::collections::HashSet::new();
1930            collect_assigned_names(body, &mut assigned);
1931            let local_state: HashMap<String, Variable<O>> = call_vars
1932                .into_iter()
1933                .filter(|(k, _)| !param_names.contains(k) && assigned.contains(k))
1934                .collect();
1935            self.function_local_state.insert(call_id, local_state);
1936        }
1937
1938        Ok(result)
1939    }
1940
1941    /// Get the type name for an object value
1942    fn get_object_type_name(&self, value: &Value<O>) -> Result<String, RuntimeError> {
1943        match value {
1944            Value::Object { type_name, .. } => Ok(type_name.clone()),
1945            _ => Err(RuntimeError::TypeError(
1946                "Cannot determine type of non-object value".to_string(),
1947            )),
1948        }
1949    }
1950
1951    /// Call a method (similar to call_user_function but handles MethodParam with defaults)
1952    fn call_method(
1953        &mut self,
1954        params: &[MethodParam],
1955        body: &[Stmt],
1956        args: Vec<EvaluatedArg<O>>,
1957        call_id: u32,
1958    ) -> Result<Value<O>, RuntimeError> {
1959        // Resolve parameter bindings (positional, named, and defaults), then run
1960        // the body as a stateful call site. Defaults are evaluated in the caller
1961        // scope, before entering the method's scope.
1962        let mut positional_idx = 0;
1963        let mut param_bindings: Vec<(String, Variable<O>)> = Vec::with_capacity(params.len());
1964
1965        for param in params {
1966            let param_value = if positional_idx < args.len() {
1967                match &args[positional_idx] {
1968                    EvaluatedArg::Positional(value) => {
1969                        positional_idx += 1;
1970                        value.clone()
1971                    }
1972                    EvaluatedArg::Named { name, value } => {
1973                        if name == &param.name {
1974                            positional_idx += 1;
1975                            value.clone()
1976                        } else if let Some(default_expr) = &param.default_value {
1977                            self.eval_expr(default_expr)?
1978                        } else {
1979                            Value::Na
1980                        }
1981                    }
1982                }
1983            } else if let Some(default_expr) = &param.default_value {
1984                self.eval_expr(default_expr)?
1985            } else {
1986                Value::Na
1987            };
1988
1989            param_bindings.push((
1990                param.name.clone(),
1991                Variable {
1992                    value: param_value,
1993                    is_const: false,
1994                    is_var_persistent: false,
1995                },
1996            ));
1997        }
1998
1999        self.run_call_site_body(call_id, param_bindings, body)
2000    }
2001
2002    /// Create a constructor function for a user-defined type
2003    fn create_constructor(type_name: String, fields: Vec<TypeField>) -> BuiltinFn<O> {
2004        Rc::new(
2005            move |interp: &mut Interpreter<O>, call_args: FunctionCallArgs<O>| {
2006                let mut instance_fields = HashMap::new();
2007
2008                // Match arguments to fields
2009                let mut positional_idx = 0;
2010
2011                for arg in &call_args.args {
2012                    match arg {
2013                        EvaluatedArg::Positional(value) => {
2014                            // Assign to field by position
2015                            if positional_idx < fields.len() {
2016                                let field = &fields[positional_idx];
2017                                instance_fields.insert(field.name.clone(), value.clone());
2018                                positional_idx += 1;
2019                            } else {
2020                                return Err(RuntimeError::TypeError(format!(
2021                                    "Too many arguments for type '{}' (expected {} fields)",
2022                                    type_name,
2023                                    fields.len()
2024                                )));
2025                            }
2026                        }
2027                        EvaluatedArg::Named { name, value } => {
2028                            // Find field by name
2029                            if let Some(field) = fields.iter().find(|f| f.name == *name) {
2030                                instance_fields.insert(field.name.clone(), value.clone());
2031                            } else {
2032                                return Err(RuntimeError::TypeError(format!(
2033                                    "Type '{}' has no field '{}'",
2034                                    type_name, name
2035                                )));
2036                            }
2037                        }
2038                    }
2039                }
2040
2041                // Fill in defaults for missing fields
2042                for field in &fields {
2043                    if !instance_fields.contains_key(&field.name) {
2044                        if let Some(default_expr) = &field.default_value {
2045                            let default_val = interp.eval_expr(default_expr)?;
2046                            instance_fields.insert(field.name.clone(), default_val);
2047                        } else {
2048                            // Field has no default and wasn't provided
2049                            instance_fields.insert(field.name.clone(), Value::Na);
2050                        }
2051                    }
2052                }
2053
2054                Ok(Value::Object {
2055                    type_name: type_name.clone(),
2056                    fields: Rc::new(RefCell::new(instance_fields)),
2057                    call: None,
2058                })
2059            },
2060        )
2061    }
2062
2063    /// Creates a copy function for types that takes an object and returns a shallow copy
2064    fn create_copy_function() -> BuiltinFn<O> {
2065        Rc::new(
2066            |_interp: &mut Interpreter<O>, call_args: FunctionCallArgs<O>| {
2067                // Expect exactly one positional argument (the object to copy)
2068                if call_args.args.len() != 1 {
2069                    return Err(RuntimeError::TypeError(
2070                        "copy() expects exactly one argument".to_string(),
2071                    ));
2072                }
2073
2074                match &call_args.args[0] {
2075                    EvaluatedArg::Positional(value) => {
2076                        if let Value::Object {
2077                            type_name,
2078                            fields,
2079                            call,
2080                        } = value
2081                        {
2082                            // Create a shallow copy of the object's fields
2083                            let obj = fields.borrow();
2084                            let copied_fields = obj.clone();
2085                            Ok(Value::Object {
2086                                type_name: type_name.clone(),
2087                                fields: Rc::new(RefCell::new(copied_fields)),
2088                                call: call.clone(),
2089                            })
2090                        } else {
2091                            Err(RuntimeError::TypeError(
2092                                "copy() expects an object argument".to_string(),
2093                            ))
2094                        }
2095                    }
2096                    EvaluatedArg::Named { .. } => Err(RuntimeError::TypeError(
2097                        "copy() does not accept named arguments".to_string(),
2098                    )),
2099                }
2100            },
2101        )
2102    }
2103}
2104
2105impl<O: PineOutput> Default for Interpreter<O> {
2106    fn default() -> Self {
2107        Self::new()
2108    }
2109}